...

Source file src/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go

Documentation: github.com/Microsoft/hcsshim/pkg/securitypolicy

     1  package securitypolicy
     2  
     3  import (
     4  	"crypto/sha256"
     5  	_ "embed"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"fmt"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"syscall"
    13  
    14  	"github.com/Microsoft/hcsshim/internal/guestpath"
    15  	"github.com/opencontainers/runtime-spec/specs-go"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  //go:embed framework.rego
    20  var frameworkCodeTemplate string
    21  
    22  //go:embed api.rego
    23  var apiCodeTemplate string
    24  
    25  var APICode = strings.Replace(apiCodeTemplate, "@@API_VERSION@@", apiVersion, 1)
    26  var FrameworkCode = strings.Replace(frameworkCodeTemplate, "@@FRAMEWORK_VERSION@@", frameworkVersion, 1)
    27  
    28  var ErrInvalidOpenDoorPolicy = errors.New("allow_all cannot be set to 'true' when Containers are non-empty")
    29  
    30  type EnvVarRule string
    31  
    32  const (
    33  	EnvVarRuleString EnvVarRule = "string"
    34  	EnvVarRuleRegex  EnvVarRule = "re2"
    35  )
    36  
    37  type IDNameStrategy string
    38  
    39  const (
    40  	IDNameStrategyName  IDNameStrategy = "name"
    41  	IDNameStrategyID    IDNameStrategy = "id"
    42  	IDNameStrategyRegex IDNameStrategy = "re2"
    43  	IDNameStrategyAny   IDNameStrategy = "any"
    44  )
    45  
    46  const plan9Prefix = "plan9://"
    47  
    48  const (
    49  	SecurityContextDirTemplate = "security-context-*"
    50  	PolicyFilename             = "security-policy-base64"
    51  	HostAMDCertFilename        = "host-amd-cert-base64"
    52  	ReferenceInfoFilename      = "reference-info-base64"
    53  )
    54  
    55  // PolicyConfig contains toml or JSON config for security policy.
    56  type PolicyConfig struct {
    57  	AllowAll                         bool                    `json:"allow_all" toml:"allow_all"`
    58  	Containers                       []ContainerConfig       `json:"containers" toml:"container"`
    59  	ExternalProcesses                []ExternalProcessConfig `json:"external_processes" toml:"external_process"`
    60  	Fragments                        []FragmentConfig        `json:"fragments" toml:"fragment"`
    61  	AllowPropertiesAccess            bool                    `json:"allow_properties_access" toml:"allow_properties_access"`
    62  	AllowDumpStacks                  bool                    `json:"allow_dump_stacks" toml:"allow_dump_stacks"`
    63  	AllowRuntimeLogging              bool                    `json:"allow_runtime_logging" toml:"allow_runtime_logging"`
    64  	AllowEnvironmentVariableDropping bool                    `json:"allow_environment_variable_dropping" toml:"allow_environment_variable_dropping"`
    65  	// AllowUnencryptedScratch is a global policy configuration that allows
    66  	// all containers within a pod to be run without scratch encryption.
    67  	AllowUnencryptedScratch bool `json:"allow_unencrypted_scratch" toml:"allow_unencrypted_scratch"`
    68  	AllowCapabilityDropping bool `json:"allow_capability_dropping" toml:"allow_capability_dropping"`
    69  }
    70  
    71  func NewPolicyConfig(opts ...PolicyConfigOpt) (*PolicyConfig, error) {
    72  	p := &PolicyConfig{}
    73  	for _, o := range opts {
    74  		if err := o(p); err != nil {
    75  			return nil, err
    76  		}
    77  	}
    78  	return p, nil
    79  }
    80  
    81  // ExternalProcessConfig contains toml or JSON config for running external processes in the UVM.
    82  type ExternalProcessConfig struct {
    83  	Command          []string `json:"command" toml:"command"`
    84  	WorkingDir       string   `json:"working_dir" toml:"working_dir"`
    85  	AllowStdioAccess bool     `json:"allow_stdio_access" toml:"allow_stdio_access"`
    86  }
    87  
    88  // FragmentConfig contains toml or JSON config for including elements from fragments.
    89  type FragmentConfig struct {
    90  	Issuer     string   `json:"issuer" toml:"issuer"`
    91  	Feed       string   `json:"feed" toml:"feed"`
    92  	MinimumSVN string   `json:"minimum_svn" toml:"minimum_svn"`
    93  	Includes   []string `json:"includes" toml:"include"`
    94  }
    95  
    96  // AuthConfig contains toml or JSON config for registry authentication.
    97  type AuthConfig struct {
    98  	Username string `json:"username" toml:"username"`
    99  	Password string `json:"password" toml:"password"`
   100  }
   101  
   102  // EnvRuleConfig contains toml or JSON config for environment variable
   103  // security policy enforcement.
   104  type EnvRuleConfig struct {
   105  	Strategy EnvVarRule `json:"strategy" toml:"strategy"`
   106  	Rule     string     `json:"rule" toml:"rule"`
   107  	Required bool       `json:"required" toml:"required"`
   108  }
   109  
   110  type IDNameConfig struct {
   111  	Strategy IDNameStrategy `json:"strategy" toml:"strategy"`
   112  	Rule     string         `json:"rule" toml:"rule"`
   113  }
   114  
   115  type UserConfig struct {
   116  	UserIDName   IDNameConfig   `json:"user_idname" toml:"user_idname"`
   117  	GroupIDNames []IDNameConfig `json:"group_idnames" toml:"group_idname"`
   118  	Umask        string         `json:"umask" toml:"umask"`
   119  }
   120  
   121  type IDName struct {
   122  	ID   string
   123  	Name string
   124  }
   125  
   126  func MeasureSeccompProfile(seccomp *specs.LinuxSeccomp) (string, error) {
   127  	if seccomp == nil {
   128  		return "", nil
   129  	}
   130  
   131  	buf, err := json.Marshal(seccomp)
   132  	if err != nil {
   133  		return "", err
   134  	}
   135  
   136  	profileSHA256 := sha256.Sum256(buf)
   137  	return fmt.Sprintf("%x", profileSHA256), nil
   138  }
   139  
   140  const policyDecisionPattern = `policyDecision< %s >policyDecision`
   141  
   142  func ExtractPolicyDecision(errorMessage string) (string, error) {
   143  	re := regexp.MustCompile(fmt.Sprintf(policyDecisionPattern, `(.*)`))
   144  	matches := re.FindStringSubmatch(errorMessage)
   145  	if len(matches) != 2 {
   146  		return "", errors.Errorf("unable to extract policy decision from error message: %s", errorMessage)
   147  	}
   148  
   149  	errorBytes, err := base64.StdEncoding.DecodeString(matches[1])
   150  	if err != nil {
   151  		return "", err
   152  	}
   153  
   154  	return string(errorBytes), nil
   155  }
   156  
   157  // ContainerConfig contains toml or JSON config for container described
   158  // in security policy.
   159  type ContainerConfig struct {
   160  	ImageName                string              `json:"image_name" toml:"image_name"`
   161  	Command                  []string            `json:"command" toml:"command"`
   162  	Auth                     AuthConfig          `json:"auth" toml:"auth"`
   163  	EnvRules                 []EnvRuleConfig     `json:"env_rules" toml:"env_rule"`
   164  	WorkingDir               string              `json:"working_dir" toml:"working_dir"`
   165  	Mounts                   []MountConfig       `json:"mounts" toml:"mount"`
   166  	AllowElevated            bool                `json:"allow_elevated" toml:"allow_elevated"`
   167  	ExecProcesses            []ExecProcessConfig `json:"exec_processes" toml:"exec_process"`
   168  	Signals                  []syscall.Signal    `json:"signals" toml:"signals"`
   169  	AllowStdioAccess         bool                `json:"allow_stdio_access" toml:"allow_stdio_access"`
   170  	AllowPrivilegeEscalation bool                `json:"allow_privilege_escalation" toml:"allow_privilege_escalation"`
   171  	User                     *UserConfig         `json:"user" toml:"user"`
   172  	Capabilities             *CapabilitiesConfig `json:"capabilities" toml:"capabilities"`
   173  	SeccompProfilePath       string              `json:"seccomp_profile_path" toml:"seccomp_profile_path"`
   174  }
   175  
   176  // MountConfig contains toml or JSON config for mount security policy
   177  // constraint description.
   178  type MountConfig struct {
   179  	HostPath      string `json:"host_path" toml:"host_path"`
   180  	ContainerPath string `json:"container_path" toml:"container_path"`
   181  	Readonly      bool   `json:"readonly" toml:"readonly"`
   182  }
   183  
   184  // ExecProcessConfig contains toml or JSON config for exec process security
   185  // policy constraint description
   186  type ExecProcessConfig struct {
   187  	Command []string         `json:"command" toml:"command"`
   188  	Signals []syscall.Signal `json:"signals" toml:"signals"`
   189  }
   190  
   191  // CapabilitiesConfig contains the toml or JSON config for capabilies security
   192  // polict constraint description
   193  type CapabilitiesConfig struct {
   194  	Bounding    []string `json:"bounding" toml:"bounding"`
   195  	Effective   []string `json:"effective" toml:"effective"`
   196  	Inheritable []string `json:"inheritable" toml:"inheritable"`
   197  	Permitted   []string `json:"permitted" toml:"permitted"`
   198  	Ambient     []string `json:"ambient" toml:"ambient"`
   199  }
   200  
   201  //go:embed version_api
   202  var apiVersion string
   203  
   204  //go:embed version_framework
   205  var frameworkVersion string
   206  
   207  // NewEnvVarRules creates slice of EnvRuleConfig's from environment variables
   208  // strings slice.
   209  func NewEnvVarRules(envVars []string, required bool) []EnvRuleConfig {
   210  	var rules []EnvRuleConfig
   211  	for _, env := range envVars {
   212  		r := EnvRuleConfig{
   213  			Strategy: EnvVarRuleString,
   214  			Rule:     env,
   215  			Required: required,
   216  		}
   217  		rules = append(rules, r)
   218  	}
   219  	return rules
   220  }
   221  
   222  // NewOpenDoorPolicy creates a new SecurityPolicy with AllowAll set to `true`
   223  func NewOpenDoorPolicy() *SecurityPolicy {
   224  	return &SecurityPolicy{
   225  		AllowAll: true,
   226  	}
   227  }
   228  
   229  // NewSecurityPolicyDigest decodes base64 encoded policy string, computes
   230  // and returns sha256 digest
   231  func NewSecurityPolicyDigest(base64policy string) ([]byte, error) {
   232  	jsonPolicy, err := base64.StdEncoding.DecodeString(base64policy)
   233  	if err != nil {
   234  		return nil, fmt.Errorf("failed to decode base64 security policy: %w", err)
   235  	}
   236  	digest := sha256.New()
   237  	digest.Write(jsonPolicy)
   238  	digestBytes := digest.Sum(nil)
   239  	return digestBytes, nil
   240  }
   241  
   242  // EncodedSecurityPolicy is a JSON representation of SecurityPolicy that has
   243  // been base64 encoded for storage in an annotation embedded within another
   244  // JSON configuration
   245  type EncodedSecurityPolicy struct {
   246  	SecurityPolicy string `json:"SecurityPolicy,omitempty"`
   247  }
   248  
   249  type SecurityPolicy struct {
   250  	// Flag that when set to true allows for all checks to pass. Currently, used
   251  	// to run with security policy enforcement "running dark"; checks can be in
   252  	// place but the default policy that is created on startup has AllowAll set
   253  	// to true, thus making policy enforcement effectively "off" from a logical
   254  	// standpoint. Policy enforcement isn't actually off as the policy is "allow
   255  	// everything".
   256  	AllowAll bool `json:"allow_all"`
   257  	// One or more containers that are allowed to run
   258  	Containers Containers `json:"containers"`
   259  }
   260  
   261  // EncodeToString returns base64 encoded string representation of SecurityPolicy.
   262  func (sp *SecurityPolicy) EncodeToString() (string, error) {
   263  	jsn, err := json.Marshal(sp)
   264  	if err != nil {
   265  		return "", err
   266  	}
   267  	return base64.StdEncoding.EncodeToString(jsn), nil
   268  }
   269  
   270  type Containers struct {
   271  	Length   int                  `json:"length"`
   272  	Elements map[string]Container `json:"elements"`
   273  }
   274  
   275  type Container struct {
   276  	Command              CommandArgs         `json:"command"`
   277  	EnvRules             EnvRules            `json:"env_rules"`
   278  	Layers               Layers              `json:"layers"`
   279  	WorkingDir           string              `json:"working_dir"`
   280  	Mounts               Mounts              `json:"mounts"`
   281  	AllowElevated        bool                `json:"allow_elevated"`
   282  	ExecProcesses        []ExecProcessConfig `json:"-"`
   283  	Signals              []syscall.Signal    `json:"-"`
   284  	AllowStdioAccess     bool                `json:"-"`
   285  	NoNewPrivileges      bool                `json:"-"`
   286  	User                 UserConfig          `json:"-"`
   287  	Capabilities         *CapabilitiesConfig `json:"-"`
   288  	SeccompProfileSHA256 string              `json:"-"`
   289  }
   290  
   291  // StringArrayMap wraps an array of strings as a string map.
   292  type StringArrayMap struct {
   293  	Length   int               `json:"length"`
   294  	Elements map[string]string `json:"elements"`
   295  }
   296  
   297  type Layers StringArrayMap
   298  
   299  type CommandArgs StringArrayMap
   300  
   301  type Options StringArrayMap
   302  
   303  type EnvRules struct {
   304  	Length   int                      `json:"length"`
   305  	Elements map[string]EnvRuleConfig `json:"elements"`
   306  }
   307  
   308  type Mount struct {
   309  	Source      string  `json:"source"`
   310  	Destination string  `json:"destination"`
   311  	Type        string  `json:"type"`
   312  	Options     Options `json:"options"`
   313  }
   314  
   315  type Mounts struct {
   316  	Length   int              `json:"length"`
   317  	Elements map[string]Mount `json:"elements"`
   318  }
   319  
   320  // CreateContainerPolicy creates a new Container policy instance from the
   321  // provided constraints or an error if parameter validation fails.
   322  func CreateContainerPolicy(
   323  	command, layers []string,
   324  	envRules []EnvRuleConfig,
   325  	workingDir string,
   326  	mounts []MountConfig,
   327  	allowElevated bool,
   328  	execProcesses []ExecProcessConfig,
   329  	signals []syscall.Signal,
   330  	allowStdioAccess bool,
   331  	noNewPrivileges bool,
   332  	user UserConfig,
   333  	capabilities *CapabilitiesConfig,
   334  	seccompProfileSHA256 string,
   335  ) (*Container, error) {
   336  	if err := validateEnvRules(envRules); err != nil {
   337  		return nil, err
   338  	}
   339  	if err := validateMountConstraint(mounts); err != nil {
   340  		return nil, err
   341  	}
   342  	return &Container{
   343  		Command:              newCommandArgs(command),
   344  		Layers:               newLayers(layers),
   345  		EnvRules:             newEnvRules(envRules),
   346  		WorkingDir:           workingDir,
   347  		Mounts:               newMountConstraints(mounts),
   348  		AllowElevated:        allowElevated,
   349  		ExecProcesses:        execProcesses,
   350  		Signals:              signals,
   351  		AllowStdioAccess:     allowStdioAccess,
   352  		NoNewPrivileges:      noNewPrivileges,
   353  		User:                 user,
   354  		Capabilities:         capabilities,
   355  		SeccompProfileSHA256: seccompProfileSHA256,
   356  	}, nil
   357  }
   358  
   359  // NewSecurityPolicy creates a new SecurityPolicy from the provided values.
   360  func NewSecurityPolicy(allowAll bool, containers []*Container) *SecurityPolicy {
   361  	containersMap := map[string]Container{}
   362  	for i, c := range containers {
   363  		containersMap[strconv.Itoa(i)] = *c
   364  	}
   365  	return &SecurityPolicy{
   366  		AllowAll: allowAll,
   367  		Containers: Containers{
   368  			Elements: containersMap,
   369  		},
   370  	}
   371  }
   372  
   373  func validateEnvRules(rules []EnvRuleConfig) error {
   374  	for _, rule := range rules {
   375  		switch rule.Strategy {
   376  		case EnvVarRuleRegex:
   377  			if _, err := regexp.Compile(rule.Rule); err != nil {
   378  				return err
   379  			}
   380  		}
   381  	}
   382  	return nil
   383  }
   384  
   385  func validateMountConstraint(mounts []MountConfig) error {
   386  	for _, m := range mounts {
   387  		if _, err := regexp.Compile(m.HostPath); err != nil {
   388  			return err
   389  		}
   390  	}
   391  	return nil
   392  }
   393  
   394  func newCommandArgs(args []string) CommandArgs {
   395  	command := map[string]string{}
   396  	for i, arg := range args {
   397  		command[strconv.Itoa(i)] = arg
   398  	}
   399  	return CommandArgs{
   400  		Elements: command,
   401  	}
   402  }
   403  
   404  func newEnvRules(rs []EnvRuleConfig) EnvRules {
   405  	envRules := map[string]EnvRuleConfig{}
   406  	for i, r := range rs {
   407  		envRules[strconv.Itoa(i)] = r
   408  	}
   409  	return EnvRules{
   410  		Elements: envRules,
   411  	}
   412  }
   413  
   414  func newLayers(ls []string) Layers {
   415  	layers := map[string]string{}
   416  	for i, l := range ls {
   417  		layers[strconv.Itoa(i)] = l
   418  	}
   419  	return Layers{
   420  		Elements: layers,
   421  	}
   422  }
   423  
   424  func newMountOptions(opts []string) Options {
   425  	mountOpts := map[string]string{}
   426  	for i, o := range opts {
   427  		mountOpts[strconv.Itoa(i)] = o
   428  	}
   429  	return Options{
   430  		Elements: mountOpts,
   431  	}
   432  }
   433  
   434  // newOptionsFromConfig applies the same logic as CRI plugin to generate
   435  // mount options given readonly and propagation config.
   436  // TODO: (anmaxvl) update when support for other mount types is added,
   437  // e.g., vhd:// or evd://
   438  // TODO: (anmaxvl) Do we need to set/validate Linux rootfs propagation?
   439  // In case we do, update securityPolicyContainer and Container structs
   440  // as well as mount enforcement logic.
   441  func newOptionsFromConfig(mCfg *MountConfig) []string {
   442  	mountOpts := []string{"rbind"}
   443  
   444  	if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) ||
   445  		strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) {
   446  		mountOpts = append(mountOpts, "rshared")
   447  	} else {
   448  		mountOpts = append(mountOpts, "rprivate")
   449  	}
   450  
   451  	if mCfg.Readonly {
   452  		mountOpts = append(mountOpts, "ro")
   453  	} else {
   454  		mountOpts = append(mountOpts, "rw")
   455  	}
   456  	return mountOpts
   457  }
   458  
   459  // newMountTypeFromConfig mimics the behavior in CRI when figuring out OCI
   460  // mount type.
   461  func newMountTypeFromConfig(mCfg *MountConfig) string {
   462  	if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) ||
   463  		strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) ||
   464  		strings.HasPrefix(mCfg.HostPath, plan9Prefix) {
   465  		return "bind"
   466  	}
   467  	return "none"
   468  }
   469  
   470  // newMountFromConfig converts user provided MountConfig into internal representation
   471  // of mount constraint.
   472  func newMountFromConfig(mCfg *MountConfig) Mount {
   473  	opts := newOptionsFromConfig(mCfg)
   474  	return Mount{
   475  		Source:      mCfg.HostPath,
   476  		Destination: mCfg.ContainerPath,
   477  		Type:        newMountTypeFromConfig(mCfg),
   478  		Options:     newMountOptions(opts),
   479  	}
   480  }
   481  
   482  // newMountConstraints creates Mounts from a given array of MountConfig's.
   483  func newMountConstraints(mountConfigs []MountConfig) Mounts {
   484  	mounts := map[string]Mount{}
   485  	for i, mc := range mountConfigs {
   486  		mounts[strconv.Itoa(i)] = newMountFromConfig(&mc)
   487  	}
   488  	return Mounts{
   489  		Elements: mounts,
   490  	}
   491  }
   492  
   493  func EmptyCapabiltiesSet() []string {
   494  	return make([]string, 0)
   495  }
   496  
   497  func DefaultUnprivilegedCapabilities() []string {
   498  	return []string{"CAP_CHOWN",
   499  		"CAP_DAC_OVERRIDE",
   500  		"CAP_FSETID",
   501  		"CAP_FOWNER",
   502  		"CAP_MKNOD",
   503  		"CAP_NET_RAW",
   504  		"CAP_SETGID",
   505  		"CAP_SETUID",
   506  		"CAP_SETFCAP",
   507  		"CAP_SETPCAP",
   508  		"CAP_NET_BIND_SERVICE",
   509  		"CAP_SYS_CHROOT",
   510  		"CAP_KILL",
   511  		"CAP_AUDIT_WRITE",
   512  	}
   513  }
   514  
   515  func DefaultPrivilegedCapabilities() []string {
   516  	return []string{"CAP_CHOWN",
   517  		"CAP_DAC_OVERRIDE",
   518  		"CAP_DAC_READ_SEARCH",
   519  		"CAP_FOWNER",
   520  		"CAP_FSETID",
   521  		"CAP_KILL",
   522  		"CAP_SETGID",
   523  		"CAP_SETUID",
   524  		"CAP_SETPCAP",
   525  		"CAP_LINUX_IMMUTABLE",
   526  		"CAP_NET_BIND_SERVICE",
   527  		"CAP_NET_BROADCAST",
   528  		"CAP_NET_ADMIN",
   529  		"CAP_NET_RAW",
   530  		"CAP_IPC_LOCK",
   531  		"CAP_IPC_OWNER",
   532  		"CAP_SYS_MODULE",
   533  		"CAP_SYS_RAWIO",
   534  		"CAP_SYS_CHROOT",
   535  		"CAP_SYS_PTRACE",
   536  		"CAP_SYS_PACCT",
   537  		"CAP_SYS_ADMIN",
   538  		"CAP_SYS_BOOT",
   539  		"CAP_SYS_NICE",
   540  		"CAP_SYS_RESOURCE",
   541  		"CAP_SYS_TIME",
   542  		"CAP_SYS_TTY_CONFIG",
   543  		"CAP_MKNOD",
   544  		"CAP_LEASE",
   545  		"CAP_AUDIT_WRITE",
   546  		"CAP_AUDIT_CONTROL",
   547  		"CAP_SETFCAP",
   548  		"CAP_MAC_OVERRIDE",
   549  		"CAP_MAC_ADMIN",
   550  		"CAP_SYSLOG",
   551  		"CAP_WAKE_ALARM",
   552  		"CAP_BLOCK_SUSPEND",
   553  		"CAP_AUDIT_READ",
   554  		"CAP_PERFMON",
   555  		"CAP_BPF",
   556  		"CAP_CHECKPOINT_RESTORE",
   557  	}
   558  }
   559  

View as plain text