diff --git a/internal/spec/spec.go b/internal/spec/spec.go index cd2730843b..03b510f7e6 100644 --- a/internal/spec/spec.go +++ b/internal/spec/spec.go @@ -20,12 +20,12 @@ func DevShmMountWithSize(sizeString string) (*specs.Mount, error) { return nil, errors.New("only linux runtimes are supported") } - size, err := strconv.ParseInt(sizeString, 10, 64) + size, err := strconv.ParseUint(sizeString, 10, 64) if err != nil { return nil, fmt.Errorf("/dev/shm size must be a valid integer: %w", err) } - if size <= 0 { - return nil, fmt.Errorf("/dev/shm size must be a positive integer, got: %d", size) + if size == 0 { + return nil, errors.New("/dev/shm size must be non-zero") } // Use the same options as in upstream https://github.com/containerd/containerd/blob/0def98e462706286e6eaeff4a90be22fda75e761/oci/mounts.go#L49 @@ -39,7 +39,7 @@ func DevShmMountWithSize(sizeString string) (*specs.Mount, error) { } // GenerateWorkloadContainerNetworkMounts generates an array of specs.Mount -// required for container networking. Original spec is left untouched and +// required for container networking. Original spec is left untouched, so // it's the responsibility of a caller to update it. func GenerateWorkloadContainerNetworkMounts(sandboxID string, spec *specs.Spec) []specs.Mount { var nMounts []specs.Mount diff --git a/pkg/securitypolicy/config.go b/internal/tools/securitypolicy/config/config.go similarity index 84% rename from pkg/securitypolicy/config.go rename to internal/tools/securitypolicy/config/config.go index 7f0a1a352b..38c4ee219e 100644 --- a/pkg/securitypolicy/config.go +++ b/internal/tools/securitypolicy/config/config.go @@ -1,10 +1,7 @@ -package securitypolicy +package config -type EnvVarRule string - -const ( - EnvVarRuleString EnvVarRule = "string" - EnvVarRuleRegex EnvVarRule = "re2" +import ( + "github.com/Microsoft/hcsshim/pkg/securitypolicy" ) // PolicyConfig contains toml or JSON config for security policy. @@ -22,8 +19,8 @@ type AuthConfig struct { // EnvRuleConfig contains toml or JSON config for environment variable // security policy enforcement. type EnvRuleConfig struct { - Strategy EnvVarRule `json:"strategy" toml:"strategy"` - Rule string `json:"rule" toml:"rule"` + Strategy securitypolicy.EnvRuleStrategy `json:"strategy" toml:"strategy"` + Rule string `json:"rule" toml:"rule"` } // ContainerConfig contains toml or JSON config for container described @@ -46,13 +43,13 @@ type MountConfig struct { Readonly bool `json:"readonly" toml:"readonly"` } -// NewEnvVarRules creates slice of EnvRuleConfig's from environment variables +// NewEnvVarRuleConfigs creates slice of EnvRuleConfig's from environment variables // strings slice. -func NewEnvVarRules(envVars []string) []EnvRuleConfig { +func NewEnvVarRuleConfigs(envVars []string) []EnvRuleConfig { var rules []EnvRuleConfig for _, env := range envVars { r := EnvRuleConfig{ - Strategy: EnvVarRuleString, + Strategy: securitypolicy.EnvRuleString, Rule: env, } rules = append(rules, r) diff --git a/internal/tools/securitypolicy/helpers/helpers.go b/internal/tools/securitypolicy/helpers/helpers.go index 9170bec6ae..0788434058 100644 --- a/internal/tools/securitypolicy/helpers/helpers.go +++ b/internal/tools/securitypolicy/helpers/helpers.go @@ -2,13 +2,17 @@ package helpers import ( "fmt" + "regexp" + "strings" + "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/Microsoft/hcsshim/ext4/tar2ext4" + "github.com/Microsoft/hcsshim/internal/tools/securitypolicy/config" "github.com/Microsoft/hcsshim/pkg/securitypolicy" ) @@ -67,17 +71,17 @@ func ParseEnvFromImage(img v1.Image) ([]string, error) { // DefaultContainerConfigs returns a hardcoded slice of container configs, which should // be included by default in the security policy. // The slice includes only a sandbox pause container. -func DefaultContainerConfigs() []securitypolicy.ContainerConfig { - pause := securitypolicy.NewContainerConfig( +func DefaultContainerConfigs() []config.ContainerConfig { + pause := config.NewContainerConfig( "k8s.gcr.io/pause:3.1", []string{"/pause"}, - []securitypolicy.EnvRuleConfig{}, - securitypolicy.AuthConfig{}, + []config.EnvRuleConfig{}, + config.AuthConfig{}, "", []string{}, - []securitypolicy.MountConfig{}, + []config.MountConfig{}, ) - return []securitypolicy.ContainerConfig{pause} + return []config.ContainerConfig{pause} } // ParseWorkingDirFromImage inspects the image spec and returns working directory if @@ -95,9 +99,9 @@ func ParseWorkingDirFromImage(img v1.Image) (string, error) { } // PolicyContainersFromConfigs returns a slice of securitypolicy.Container generated -// from a slice of securitypolicy.ContainerConfig's -func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConfig) ([]*securitypolicy.Container, error) { - var policyContainers []*securitypolicy.Container +// from a slice of config.ContainerConfig's +func PolicyContainersFromConfigs(containerConfigs []config.ContainerConfig) ([]securitypolicy.Container, error) { + var policyContainers []securitypolicy.Container for _, containerConfig := range containerConfigs { var imageOptions []remote.Option @@ -126,7 +130,7 @@ func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConf if err != nil { return nil, err } - envRules := securitypolicy.NewEnvVarRules(envVars) + envRules := config.NewEnvVarRuleConfigs(envVars) envRules = append(envRules, containerConfig.EnvRules...) workingDir, err := ParseWorkingDirFromImage(img) @@ -138,7 +142,7 @@ func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConf workingDir = containerConfig.WorkingDir } - container, err := securitypolicy.CreateContainerPolicy( + container, err := CreateContainerPolicy( containerConfig.Command, layerHashes, envRules, @@ -154,3 +158,107 @@ func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConf return policyContainers, nil } + +// CreateContainerPolicy creates a new Container policy instance from the +// provided constraints or an error if parameter validation fails. +func CreateContainerPolicy( + command, layers []string, + envRuleConfigs []config.EnvRuleConfig, + workingDir string, + eMounts []string, + mntConfigs []config.MountConfig, +) (securitypolicy.Container, error) { + envRules, err := createEnvVarRules(envRuleConfigs) + if err != nil { + return securitypolicy.Container{}, err + } + mntConstraints, err := createMountConstraints(mntConfigs) + if err != nil { + return securitypolicy.Container{}, err + } + return securitypolicy.NewContainer( + command, + layers, + envRules, + workingDir, + eMounts, + mntConstraints, + ), nil +} + +func createEnvVarRules(rules []config.EnvRuleConfig) ([]securitypolicy.EnvVarRule, error) { + var envRules []securitypolicy.EnvVarRule + for _, rule := range rules { + switch rule.Strategy { + case securitypolicy.EnvRuleRegex: + if _, err := regexp.Compile(rule.Rule); err != nil { + return nil, err + } + case securitypolicy.EnvRuleString: + default: + return nil, fmt.Errorf("invalid env_var strategy: %s", rule.Strategy) + } + envRules = append(envRules, securitypolicy.EnvVarRule{ + Strategy: rule.Strategy, + Rule: rule.Rule, + }) + } + return envRules, nil +} + +func createMountConstraints(mntConfigs []config.MountConfig) ([]securitypolicy.Mount, error) { + var mntConstraints []securitypolicy.Mount + for _, m := range mntConfigs { + if _, err := regexp.Compile(m.HostPath); err != nil { + return nil, err + } + mntConstraints = append(mntConstraints, newMountFromConfig(&m)) + } + return mntConstraints, nil +} + +// newOptionsFromConfig applies the same logic as CRI plugin to generate +// mount options given readonly and propagation config. +// TODO: (anmaxvl) update when support for other mount types is added, +// e.g., vhd:// or evd:// +// TODO: (anmaxvl) Do we need to set/validate Linux rootfs propagation? +// In case we do, update securityPolicyContainer and Container structs +// as well as mount enforcement logic. +func newOptionsFromConfig(mCfg *config.MountConfig) []string { + mountOpts := []string{"rbind"} + + if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || + strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { + mountOpts = append(mountOpts, "rshared") + } else { + mountOpts = append(mountOpts, "rprivate") + } + + if mCfg.Readonly { + mountOpts = append(mountOpts, "ro") + } else { + mountOpts = append(mountOpts, "rw") + } + return mountOpts +} + +// newMountTypeFromConfig mimics the behavior in CRI when figuring out OCI +// mount type. +func newMountTypeFromConfig(mCfg *config.MountConfig) string { + if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || + strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { + return "bind" + } + return "none" +} + +// newMountFromConfig converts user provided MountConfig into internal representation +// of mount constraint. +func newMountFromConfig(mntConfig *config.MountConfig) securitypolicy.Mount { + return securitypolicy.NewMountConstraint( + mntConfig.HostPath, + mntConfig.ContainerPath, + newMountTypeFromConfig(mntConfig), + newOptionsFromConfig(mntConfig), + ) +} diff --git a/internal/tools/securitypolicy/main.go b/internal/tools/securitypolicy/main.go index 4073a5d847..a8b85387e8 100644 --- a/internal/tools/securitypolicy/main.go +++ b/internal/tools/securitypolicy/main.go @@ -10,6 +10,7 @@ import ( "github.com/BurntSushi/toml" + "github.com/Microsoft/hcsshim/internal/tools/securitypolicy/config" "github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers" "github.com/Microsoft/hcsshim/pkg/securitypolicy" ) @@ -32,18 +33,18 @@ func main() { return err } - config := &securitypolicy.PolicyConfig{} + cfg := &config.PolicyConfig{} - err = toml.Unmarshal(configData, config) + err = toml.Unmarshal(configData, cfg) if err != nil { return err } policy, err := func() (*securitypolicy.SecurityPolicy, error) { - if config.AllowAll { + if cfg.AllowAll { return securitypolicy.NewOpenDoorPolicy(), nil } else { - return createPolicyFromConfig(config) + return createPolicyFromConfig(cfg) } }() @@ -70,12 +71,12 @@ func main() { } } -func createPolicyFromConfig(config *securitypolicy.PolicyConfig) (*securitypolicy.SecurityPolicy, error) { +func createPolicyFromConfig(cfg *config.PolicyConfig) (*securitypolicy.SecurityPolicy, error) { // Add default containers to the policy config to get the root hash // and any environment variable rules we might need defaultContainers := helpers.DefaultContainerConfigs() - config.Containers = append(config.Containers, defaultContainers...) - policyContainers, err := helpers.PolicyContainersFromConfigs(config.Containers) + cfg.Containers = append(cfg.Containers, defaultContainers...) + policyContainers, err := helpers.PolicyContainersFromConfigs(cfg.Containers) if err != nil { return nil, err } diff --git a/pkg/securitypolicy/internal.go b/pkg/securitypolicy/internal.go new file mode 100644 index 0000000000..5cd5f83564 --- /dev/null +++ b/pkg/securitypolicy/internal.go @@ -0,0 +1,251 @@ +package securitypolicy + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/google/go-cmp/cmp" + oci "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +type mountInternal struct { + Source string + Destination string + Type string + Options []string +} + +// newMountInternal creates an internal mount constraint object from given +// source, destination, type and options +func newMountInternal(src, dst string, mType string, mOpts []string) mountInternal { + return mountInternal{ + Source: src, + Destination: dst, + Type: mType, + Options: mOpts, + } +} + +// securityPolicyContainer is an internal version of Container +type securityPolicyContainer struct { + // The command that we will allow the container to execute + Command []string + // The rules for determining if a given environment variable is allowed + EnvRules []EnvVarRule + // An ordered list of dm-verity root hashes for each layer that makes up + // "a container". Containers are constructed as an overlay file system. The + // order that the layers are overlayed is important and needs to be enforced + // as part of policy. + Layers []string + // WorkingDir is a path to container's working directory, which all the processes + // will default to. + WorkingDir string + // Unordered list of mounts which are expected to be present when the container + // starts + ExpectedMounts []string `json:"expected_mounts"` + // A list of constraints for determining if a given mount is allowed. + Mounts []mountInternal +} + +// validate checks given OCI mount against mount policy. Source and destination +// are checked by direct string comparisons. Different matching strategies can +// be added by introducing a separate path matching config, which isn't needed +// at the moment. +func (m *mountInternal) validate(mSpec oci.Mount) error { + if m.Type != mSpec.Type { + return fmt.Errorf("mount type not allowed by policy: expected=%q, actual=%q", m.Type, mSpec.Type) + } + if ok, _ := regexp.MatchString(m.Source, mSpec.Source); !ok { + return fmt.Errorf("mount source not allowed by policy: expected=%q, actual=%q", m.Source, mSpec.Source) + } + if m.Destination != mSpec.Destination && m.Destination != "" { + return fmt.Errorf("mount destination not allowed by policy: expected=%q, actual=%q", m.Destination, mSpec.Destination) + } + if !cmp.Equal(m.Options, mSpec.Options) { + return fmt.Errorf("mount options not allowed by policy: expected=%q, actual=%q", m.Options, mSpec.Options) + } + return nil +} + +// matchMount matches given OCI mount against mount constraints. If no match +// found, the mount is not allowed. +func (c *securityPolicyContainer) matchMount(sandboxID string, m oci.Mount) (err error) { + mountOk := false + for _, constraint := range c.Mounts { + // now that we know the sandboxID we can get the actual path for + // various destination path types by adding a UVM mount prefix + logrus.Debugf("validating constraint: %+v", constraint) + constraint = substituteUVMPath(sandboxID, constraint) + if err = constraint.validate(m); err == nil { + mountOk = true + break + } + } + if !mountOk { + return fmt.Errorf("mount is not allowed by policy: %+v", m) + } + return nil +} + +func (c Containers) toInternal() ([]securityPolicyContainer, error) { + containerMapLength := len(c.Elements) + if c.Length != containerMapLength { + err := fmt.Errorf( + "container numbers don't match in policy. expected: %d, actual: %d", + c.Length, + containerMapLength, + ) + return nil, err + } + + internal := make([]securityPolicyContainer, containerMapLength) + + for i := 0; i < containerMapLength; i++ { + index := strconv.Itoa(i) + cConf, ok := c.Elements[index] + if !ok { + return nil, fmt.Errorf("container constraint with index %q not found", index) + } + cInternal, err := cConf.toInternal() + if err != nil { + return nil, err + } + // save off new container + internal[i] = cInternal + } + + return internal, nil +} + +func (c Container) toInternal() (securityPolicyContainer, error) { + command, err := c.Command.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + envRules, err := c.EnvRules.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + layers, err := c.Layers.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + expectedMounts, err := c.ExpectedMounts.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + mounts, err := c.Mounts.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + return securityPolicyContainer{ + Command: command, + EnvRules: envRules, + Layers: layers, + // No need to have toInternal(), because WorkingDir is a string both + // internally and in the policy. + WorkingDir: c.WorkingDir, + ExpectedMounts: expectedMounts, + Mounts: mounts, + }, nil +} + +func (c CommandArgs) toInternal() ([]string, error) { + if c.Length != len(c.Elements) { + return nil, fmt.Errorf("command argument numbers don't match in policy. expected: %d, actual: %d", c.Length, len(c.Elements)) + } + + return stringMapToStringArray(c.Elements) +} + +func (e EnvRules) toInternal() ([]EnvVarRule, error) { + envRulesMapLength := len(e.Elements) + if e.Length != envRulesMapLength { + return nil, fmt.Errorf("env rule numbers don't match in policy. expected: %d, actual: %d", e.Length, envRulesMapLength) + } + + envRules := make([]EnvVarRule, envRulesMapLength) + for i := 0; i < envRulesMapLength; i++ { + eIndex := strconv.Itoa(i) + rule := EnvVarRule{ + Strategy: e.Elements[eIndex].Strategy, + Rule: e.Elements[eIndex].Rule, + } + envRules[i] = rule + } + + return envRules, nil +} + +func (l Layers) toInternal() ([]string, error) { + if l.Length != len(l.Elements) { + return nil, fmt.Errorf("layer numbers don't match in policy. expected: %d, actual: %d", l.Length, len(l.Elements)) + } + + return stringMapToStringArray(l.Elements) +} + +func (em ExpectedMounts) toInternal() ([]string, error) { + if em.Length != len(em.Elements) { + return nil, fmt.Errorf("expectedMounts numbers don't match in policy. expected: %d, actual: %d", em.Length, len(em.Elements)) + } + + return stringMapToStringArray(em.Elements) +} + +func (o Options) toInternal() ([]string, error) { + optLength := len(o.Elements) + if o.Length != optLength { + return nil, fmt.Errorf("mount option numbers don't match in policy. expected: %d, actual: %d", o.Length, optLength) + } + return stringMapToStringArray(o.Elements) +} + +func (m Mounts) toInternal() ([]mountInternal, error) { + mountLength := len(m.Elements) + if m.Length != mountLength { + return nil, fmt.Errorf("mount constraint numbers don't match in policy. expected: %d, actual: %d", m.Length, mountLength) + } + + mountConstraints := make([]mountInternal, mountLength) + for i := 0; i < mountLength; i++ { + mIndex := strconv.Itoa(i) + mount, ok := m.Elements[mIndex] + if !ok { + return nil, fmt.Errorf("mount constraint with index %q not found", mIndex) + } + opts, err := mount.Options.toInternal() + if err != nil { + return nil, err + } + mountConstraints[i] = mountInternal{ + Source: mount.Source, + Destination: mount.Destination, + Type: mount.Type, + Options: opts, + } + } + return mountConstraints, nil +} + +func stringMapToStringArray(m map[string]string) ([]string, error) { + mapSize := len(m) + out := make([]string, mapSize) + + for i := 0; i < mapSize; i++ { + index := strconv.Itoa(i) + value, ok := m[index] + if !ok { + return nil, fmt.Errorf("element with index %q not found", index) + } + out[i] = value + } + + return out, nil +} diff --git a/pkg/securitypolicy/mounts.go b/pkg/securitypolicy/mounts.go index 6faf7ce2c9..3921a3983a 100644 --- a/pkg/securitypolicy/mounts.go +++ b/pkg/securitypolicy/mounts.go @@ -23,7 +23,7 @@ func getDefaultMountConstraints(sandboxID string, spec *specs.Spec) ([]mountInte var defaultMountConstraints []mountInternal for _, m := range defaultOCIMounts { - mc := newMountConstraint(m.Source, m.Destination, m.Type, m.Options) + mc := newMountInternal(m.Source, m.Destination, m.Type, m.Options) defaultMountConstraints = append(defaultMountConstraints, mc) } diff --git a/pkg/securitypolicy/securitypolicy.go b/pkg/securitypolicy/securitypolicy.go index 9ac382ea7a..2c23c091d6 100644 --- a/pkg/securitypolicy/securitypolicy.go +++ b/pkg/securitypolicy/securitypolicy.go @@ -3,14 +3,18 @@ package securitypolicy import ( "encoding/base64" "encoding/json" - "regexp" "strconv" - "strings" - "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/pkg/errors" ) +type EnvRuleStrategy string + +const ( + EnvRuleString EnvRuleStrategy = "string" + EnvRuleRegex EnvRuleStrategy = "re2" +) + // NewOpenDoorPolicy creates a new SecurityPolicy with AllowAll set to `true` func NewOpenDoorPolicy() *SecurityPolicy { return &SecurityPolicy{ @@ -18,34 +22,6 @@ func NewOpenDoorPolicy() *SecurityPolicy { } } -type mountInternal struct { - Source string - Destination string - Type string - Options []string -} - -// Internal version of SecurityPolicyContainer -type securityPolicyContainer struct { - // The command that we will allow the container to execute - Command []string - // The rules for determining if a given environment variable is allowed - EnvRules []EnvRuleConfig - // An ordered list of dm-verity root hashes for each layer that makes up - // "a container". Containers are constructed as an overlay file system. The - // order that the layers are overlayed is important and needs to be enforced - // as part of policy. - Layers []string - // WorkingDir is a path to container's working directory, which all the processes - // will default to. - WorkingDir string - // Unordered list of mounts which are expected to be present when the container - // starts - ExpectedMounts []string `json:"expected_mounts"` - // A list of constraints for determining if a given mount is allowed. - Mounts []mountInternal -} - // SecurityPolicyState is a structure that holds user supplied policy to enforce // we keep both the encoded representation and the unmarshalled representation // because different components need to have access to either of these @@ -145,8 +121,8 @@ type ExpectedMounts StringArrayMap type Options StringArrayMap type EnvRules struct { - Length int `json:"length"` - Elements map[string]EnvRuleConfig `json:"elements"` + Length int `json:"length"` + Elements map[string]EnvVarRule `json:"elements"` } type Mount struct { @@ -161,29 +137,37 @@ type Mounts struct { Elements map[string]Mount `json:"elements"` } -// CreateContainerPolicy creates a new Container policy instance from the -// provided constraints or an error if parameter validation fails. -func CreateContainerPolicy( +type EnvVarRule struct { + Strategy EnvRuleStrategy `json:"strategy"` + Rule string `json:"rule"` +} + +// NewMountConstraint creates new Mount policy. +func NewMountConstraint(source, destination, mntType string, opts []string) Mount { + return Mount{ + Source: source, + Destination: destination, + Type: mntType, + Options: newMountOptions(opts), + } +} + +// NewContainer creates new Container policy. +func NewContainer( command, layers []string, - envRules []EnvRuleConfig, + envRules []EnvVarRule, workingDir string, - eMounts []string, - mounts []MountConfig, -) (*Container, error) { - if err := validateEnvRules(envRules); err != nil { - return nil, err - } - if err := validateMountConstraint(mounts); err != nil { - return nil, err - } - return &Container{ + expMounts []string, + mntConstraints []Mount, +) Container { + return Container{ Command: newCommandArgs(command), Layers: newLayers(layers), EnvRules: newEnvRules(envRules), WorkingDir: workingDir, - ExpectedMounts: newExpectedMounts(eMounts), - Mounts: newMountConstraints(mounts), - }, nil + ExpectedMounts: newExpectedMounts(expMounts), + Mounts: newMountConstraints(mntConstraints), + } } // NewSecurityPolicy creates a new SecurityPolicy from the provided values. @@ -200,27 +184,6 @@ func NewSecurityPolicy(allowAll bool, containers []*Container) *SecurityPolicy { } } -func validateEnvRules(rules []EnvRuleConfig) error { - for _, rule := range rules { - switch rule.Strategy { - case EnvVarRuleRegex: - if _, err := regexp.Compile(rule.Rule); err != nil { - return err - } - } - } - return nil -} - -func validateMountConstraint(mounts []MountConfig) error { - for _, m := range mounts { - if _, err := regexp.Compile(m.HostPath); err != nil { - return err - } - } - return nil -} - func newCommandArgs(args []string) CommandArgs { command := map[string]string{} for i, arg := range args { @@ -231,8 +194,8 @@ func newCommandArgs(args []string) CommandArgs { } } -func newEnvRules(rs []EnvRuleConfig) EnvRules { - envRules := map[string]EnvRuleConfig{} +func newEnvRules(rs []EnvVarRule) EnvRules { + envRules := map[string]EnvVarRule{} for i, r := range rs { envRules[strconv.Itoa(i)] = r } @@ -271,69 +234,11 @@ func newMountOptions(opts []string) Options { } } -// newOptionsFromConfig applies the same logic as CRI plugin to generate -// mount options given readonly and propagation config. -// TODO: (anmaxvl) update when support for other mount types is added, -// e.g., vhd:// or evd:// -// TODO: (anmaxvl) Do we need to set/validate Linux rootfs propagation? -// In case we do, update securityPolicyContainer and Container structs -// as well as mount enforcement logic. -func newOptionsFromConfig(mCfg *MountConfig) []string { - mountOpts := []string{"rbind"} - - if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || - strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { - mountOpts = append(mountOpts, "rshared") - } else { - mountOpts = append(mountOpts, "rprivate") - } - - if mCfg.Readonly { - mountOpts = append(mountOpts, "ro") - } else { - mountOpts = append(mountOpts, "rw") - } - return mountOpts -} - -// newMountTypeFromConfig mimics the behavior in CRI when figuring out OCI -// mount type. -func newMountTypeFromConfig(mCfg *MountConfig) string { - if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || - strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { - return "bind" - } - return "none" -} - -// newMountFromConfig converts user provided MountConfig into internal representation -// of mount constraint. -func newMountFromConfig(mCfg *MountConfig) Mount { - opts := newOptionsFromConfig(mCfg) - return Mount{ - Source: mCfg.HostPath, - Destination: mCfg.ContainerPath, - Type: newMountTypeFromConfig(mCfg), - Options: newMountOptions(opts), - } -} - -// newMountConstraint creates an internal mount constraint object from given -// source, destination, type and options -func newMountConstraint(src, dst string, mType string, mOpts []string) mountInternal { - return mountInternal{ - Source: src, - Destination: dst, - Type: mType, - Options: mOpts, - } -} - // newMountConstraints creates Mounts from a given array of MountConfig's. -func newMountConstraints(mountConfigs []MountConfig) Mounts { +func newMountConstraints(mountConfigs []Mount) Mounts { mounts := map[string]Mount{} for i, mc := range mountConfigs { - mounts[strconv.Itoa(i)] = newMountFromConfig(&mc) + mounts[strconv.Itoa(i)] = mc } return Mounts{ Elements: mounts, diff --git a/pkg/securitypolicy/securitypolicy_test.go b/pkg/securitypolicy/securitypolicy_test.go index 775c2e3744..a571a0eddd 100644 --- a/pkg/securitypolicy/securitypolicy_test.go +++ b/pkg/securitypolicy/securitypolicy_test.go @@ -535,8 +535,8 @@ func Test_EnforceEnvironmentVariablePolicy_Re2Match(t *testing.T) { container := generateContainersContainer(testRand, 1) // add a rule to re2 match - re2MatchRule := EnvRuleConfig{ - Strategy: EnvVarRuleRegex, + re2MatchRule := EnvVarRule{ + Strategy: EnvRuleRegex, Rule: "PREFIX_.+=.+", } container.EnvRules = append(container.EnvRules, re2MatchRule) @@ -758,7 +758,7 @@ func (*SecurityPolicy) Generate(r *rand.Rand, _ int) reflect.Value { Elements: map[string]string{}, }, EnvRules: EnvRules{ - Elements: map[string]EnvRuleConfig{}, + Elements: map[string]EnvVarRule{}, }, Layers: Layers{ Elements: map[string]string{}, @@ -782,7 +782,7 @@ func (*SecurityPolicy) Generate(r *rand.Rand, _ int) reflect.Value { // env variable rules numEnvRules := int(atMost(r, maxGeneratedEnvironmentVariableRules)) for i := 0; i < numEnvRules; i++ { - rule := EnvRuleConfig{ + rule := EnvVarRule{ Strategy: "string", Rule: randVariableString(r, maxGeneratedEnvironmentVariableRuleLength), } @@ -878,12 +878,12 @@ func generateCommand(r *rand.Rand) []string { return args } -func generateEnvironmentVariableRules(r *rand.Rand) []EnvRuleConfig { - var rules []EnvRuleConfig +func generateEnvironmentVariableRules(r *rand.Rand) []EnvVarRule { + var rules []EnvVarRule numArgs := atLeastOneAtMost(r, maxGeneratedEnvironmentVariableRules) for i := 0; i < int(numArgs); i++ { - rule := EnvRuleConfig{ + rule := EnvVarRule{ Strategy: "string", Rule: randVariableString(r, maxGeneratedEnvironmentVariableRuleLength), } diff --git a/pkg/securitypolicy/securitypolicyenforcer.go b/pkg/securitypolicy/securitypolicyenforcer.go index e284781ffb..e146dbbc8b 100644 --- a/pkg/securitypolicy/securitypolicyenforcer.go +++ b/pkg/securitypolicy/securitypolicyenforcer.go @@ -6,18 +6,15 @@ import ( "os" "path/filepath" "regexp" - "strconv" "strings" "sync" - "github.com/google/go-cmp/cmp" - oci "github.com/opencontainers/runtime-spec/specs-go" - "github.com/sirupsen/logrus" - "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/Microsoft/hcsshim/internal/hooks" specInternal "github.com/Microsoft/hcsshim/internal/spec" "github.com/Microsoft/hcsshim/pkg/annotations" + "github.com/google/go-cmp/cmp" + oci "github.com/opencontainers/runtime-spec/specs-go" ) type SecurityPolicyEnforcer interface { @@ -135,167 +132,6 @@ func NewStandardSecurityPolicyEnforcer(containers []securityPolicyContainer, enc } } -func (c Containers) toInternal() ([]securityPolicyContainer, error) { - containerMapLength := len(c.Elements) - if c.Length != containerMapLength { - err := fmt.Errorf( - "container numbers don't match in policy. expected: %d, actual: %d", - c.Length, - containerMapLength, - ) - return nil, err - } - - internal := make([]securityPolicyContainer, containerMapLength) - - for i := 0; i < containerMapLength; i++ { - index := strconv.Itoa(i) - cConf, ok := c.Elements[index] - if !ok { - return nil, fmt.Errorf("container constraint with index %q not found", index) - } - cInternal, err := cConf.toInternal() - if err != nil { - return nil, err - } - // save off new container - internal[i] = cInternal - } - - return internal, nil -} - -func (c Container) toInternal() (securityPolicyContainer, error) { - command, err := c.Command.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - - envRules, err := c.EnvRules.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - - layers, err := c.Layers.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - - expectedMounts, err := c.ExpectedMounts.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - - mounts, err := c.Mounts.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - return securityPolicyContainer{ - Command: command, - EnvRules: envRules, - Layers: layers, - // No need to have toInternal(), because WorkingDir is a string both - // internally and in the policy. - WorkingDir: c.WorkingDir, - ExpectedMounts: expectedMounts, - Mounts: mounts, - }, nil -} - -func (c CommandArgs) toInternal() ([]string, error) { - if c.Length != len(c.Elements) { - return nil, fmt.Errorf("command argument numbers don't match in policy. expected: %d, actual: %d", c.Length, len(c.Elements)) - } - - return stringMapToStringArray(c.Elements) -} - -func (e EnvRules) toInternal() ([]EnvRuleConfig, error) { - envRulesMapLength := len(e.Elements) - if e.Length != envRulesMapLength { - return nil, fmt.Errorf("env rule numbers don't match in policy. expected: %d, actual: %d", e.Length, envRulesMapLength) - } - - envRules := make([]EnvRuleConfig, envRulesMapLength) - for i := 0; i < envRulesMapLength; i++ { - eIndex := strconv.Itoa(i) - rule := EnvRuleConfig{ - Strategy: e.Elements[eIndex].Strategy, - Rule: e.Elements[eIndex].Rule, - } - envRules[i] = rule - } - - return envRules, nil -} - -func (l Layers) toInternal() ([]string, error) { - if l.Length != len(l.Elements) { - return nil, fmt.Errorf("layer numbers don't match in policy. expected: %d, actual: %d", l.Length, len(l.Elements)) - } - - return stringMapToStringArray(l.Elements) -} - -func (em ExpectedMounts) toInternal() ([]string, error) { - if em.Length != len(em.Elements) { - return nil, fmt.Errorf("expectedMounts numbers don't match in policy. expected: %d, actual: %d", em.Length, len(em.Elements)) - } - - return stringMapToStringArray(em.Elements) -} - -func (o Options) toInternal() ([]string, error) { - optLength := len(o.Elements) - if o.Length != optLength { - return nil, fmt.Errorf("mount option numbers don't match in policy. expected: %d, actual: %d", o.Length, optLength) - } - return stringMapToStringArray(o.Elements) -} - -func (m Mounts) toInternal() ([]mountInternal, error) { - mountLength := len(m.Elements) - if m.Length != mountLength { - return nil, fmt.Errorf("mount constraint numbers don't match in policy. expected: %d, actual: %d", m.Length, mountLength) - } - - mountConstraints := make([]mountInternal, mountLength) - for i := 0; i < mountLength; i++ { - mIndex := strconv.Itoa(i) - mount, ok := m.Elements[mIndex] - if !ok { - return nil, fmt.Errorf("mount constraint with index %q not found", mIndex) - } - opts, err := mount.Options.toInternal() - if err != nil { - return nil, err - } - mountConstraints[i] = mountInternal{ - Source: mount.Source, - Destination: mount.Destination, - Type: mount.Type, - Options: opts, - } - } - return mountConstraints, nil -} - -func stringMapToStringArray(m map[string]string) ([]string, error) { - mapSize := len(m) - out := make([]string, mapSize) - - for i := 0; i < mapSize; i++ { - index := strconv.Itoa(i) - value, ok := m[index] - if !ok { - return nil, fmt.Errorf("element with index %q not found", index) - } - out[i] = value - } - - return out, nil -} - func (se *StandardSecurityPolicyEnforcer) EnforceDeviceMountPolicy(target string, deviceHash string) (err error) { se.mutex.Lock() defer se.mutex.Unlock() @@ -493,7 +329,7 @@ func (se *StandardSecurityPolicyEnforcer) enforceWorkingDirPolicy(containerID st return nil } -func envIsMatchedByRule(envVariable string, rules []EnvRuleConfig) bool { +func envIsMatchedByRule(envVariable string, rules []EnvVarRule) bool { for _, rule := range rules { switch rule.Strategy { case "string": @@ -567,13 +403,8 @@ func (se *StandardSecurityPolicyEnforcer) EnforceMountPolicy(sandboxID, containe // first check against default mounts for _, mountConstraint := range defaultAllowedMounts { if err = mountConstraint.validate(specMnt); err == nil { - // TODO: Remove before checking in - logrus.Debugf("mount: %+v matched constraint: %+v", specMnt, mountConstraint) mountOk = true break - } else { - // TODO: Remove before checking in - logrus.Debugf("mount: %+v didn't match mount constraint: %+v", specMnt, mountConstraint) } } @@ -601,46 +432,6 @@ func (se *StandardSecurityPolicyEnforcer) EnforceMountPolicy(sandboxID, containe return nil } -// validate checks given OCI mount against mount policy. Source and destination -// are checked by direct string comparisons. Different matching strategies can -// be added by introducing a separate path matching config, which isn't needed -// at the moment. -func (m *mountInternal) validate(mSpec oci.Mount) error { - if m.Type != mSpec.Type { - return fmt.Errorf("mount type not allowed by policy: expected=%q, actual=%q", m.Type, mSpec.Type) - } - if ok, _ := regexp.MatchString(m.Source, mSpec.Source); !ok { - return fmt.Errorf("mount source not allowed by policy: expected=%q, actual=%q", m.Source, mSpec.Source) - } - if m.Destination != mSpec.Destination && m.Destination != "" { - return fmt.Errorf("mount destination not allowed by policy: expected=%q, actual=%q", m.Destination, mSpec.Destination) - } - if !cmp.Equal(m.Options, mSpec.Options) { - return fmt.Errorf("mount options not allowed by policy: expected=%q, actual=%q", m.Options, mSpec.Options) - } - return nil -} - -// matchMount matches given OCI mount against mount constraints. If no match -// found, the mount is not allowed. -func (c *securityPolicyContainer) matchMount(sandboxID string, m oci.Mount) (err error) { - mountOk := false - for _, constraint := range c.Mounts { - // now that we know the sandboxID we can get the actual path for - // various destination path types by adding a UVM mount prefix - logrus.Debugf("validating constraint: %+v", constraint) - constraint = substituteUVMPath(sandboxID, constraint) - if err = constraint.validate(m); err == nil { - mountOk = true - break - } - } - if !mountOk { - return fmt.Errorf("mount is not allowed by policy: %+v", m) - } - return nil -} - // substituteUVMPath substitutes mount prefix to an appropriate path inside // UVM. At policy generation time, it's impossible to tell what the sandboxID // will be, so the prefix substitution needs to happen during runtime. diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/spec/spec.go b/test/vendor/github.com/Microsoft/hcsshim/internal/spec/spec.go index cd2730843b..03b510f7e6 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/spec/spec.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/spec/spec.go @@ -20,12 +20,12 @@ func DevShmMountWithSize(sizeString string) (*specs.Mount, error) { return nil, errors.New("only linux runtimes are supported") } - size, err := strconv.ParseInt(sizeString, 10, 64) + size, err := strconv.ParseUint(sizeString, 10, 64) if err != nil { return nil, fmt.Errorf("/dev/shm size must be a valid integer: %w", err) } - if size <= 0 { - return nil, fmt.Errorf("/dev/shm size must be a positive integer, got: %d", size) + if size == 0 { + return nil, errors.New("/dev/shm size must be non-zero") } // Use the same options as in upstream https://github.com/containerd/containerd/blob/0def98e462706286e6eaeff4a90be22fda75e761/oci/mounts.go#L49 @@ -39,7 +39,7 @@ func DevShmMountWithSize(sizeString string) (*specs.Mount, error) { } // GenerateWorkloadContainerNetworkMounts generates an array of specs.Mount -// required for container networking. Original spec is left untouched and +// required for container networking. Original spec is left untouched, so // it's the responsibility of a caller to update it. func GenerateWorkloadContainerNetworkMounts(sandboxID string, spec *specs.Spec) []specs.Mount { var nMounts []specs.Mount diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/config.go b/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/config/config.go similarity index 84% rename from test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/config.go rename to test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/config/config.go index 7f0a1a352b..38c4ee219e 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/config.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/config/config.go @@ -1,10 +1,7 @@ -package securitypolicy +package config -type EnvVarRule string - -const ( - EnvVarRuleString EnvVarRule = "string" - EnvVarRuleRegex EnvVarRule = "re2" +import ( + "github.com/Microsoft/hcsshim/pkg/securitypolicy" ) // PolicyConfig contains toml or JSON config for security policy. @@ -22,8 +19,8 @@ type AuthConfig struct { // EnvRuleConfig contains toml or JSON config for environment variable // security policy enforcement. type EnvRuleConfig struct { - Strategy EnvVarRule `json:"strategy" toml:"strategy"` - Rule string `json:"rule" toml:"rule"` + Strategy securitypolicy.EnvRuleStrategy `json:"strategy" toml:"strategy"` + Rule string `json:"rule" toml:"rule"` } // ContainerConfig contains toml or JSON config for container described @@ -46,13 +43,13 @@ type MountConfig struct { Readonly bool `json:"readonly" toml:"readonly"` } -// NewEnvVarRules creates slice of EnvRuleConfig's from environment variables +// NewEnvVarRuleConfigs creates slice of EnvRuleConfig's from environment variables // strings slice. -func NewEnvVarRules(envVars []string) []EnvRuleConfig { +func NewEnvVarRuleConfigs(envVars []string) []EnvRuleConfig { var rules []EnvRuleConfig for _, env := range envVars { r := EnvRuleConfig{ - Strategy: EnvVarRuleString, + Strategy: securitypolicy.EnvRuleString, Rule: env, } rules = append(rules, r) diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers/helpers.go b/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers/helpers.go index 9170bec6ae..0788434058 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers/helpers.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers/helpers.go @@ -2,13 +2,17 @@ package helpers import ( "fmt" + "regexp" + "strings" + "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/Microsoft/hcsshim/ext4/tar2ext4" + "github.com/Microsoft/hcsshim/internal/tools/securitypolicy/config" "github.com/Microsoft/hcsshim/pkg/securitypolicy" ) @@ -67,17 +71,17 @@ func ParseEnvFromImage(img v1.Image) ([]string, error) { // DefaultContainerConfigs returns a hardcoded slice of container configs, which should // be included by default in the security policy. // The slice includes only a sandbox pause container. -func DefaultContainerConfigs() []securitypolicy.ContainerConfig { - pause := securitypolicy.NewContainerConfig( +func DefaultContainerConfigs() []config.ContainerConfig { + pause := config.NewContainerConfig( "k8s.gcr.io/pause:3.1", []string{"/pause"}, - []securitypolicy.EnvRuleConfig{}, - securitypolicy.AuthConfig{}, + []config.EnvRuleConfig{}, + config.AuthConfig{}, "", []string{}, - []securitypolicy.MountConfig{}, + []config.MountConfig{}, ) - return []securitypolicy.ContainerConfig{pause} + return []config.ContainerConfig{pause} } // ParseWorkingDirFromImage inspects the image spec and returns working directory if @@ -95,9 +99,9 @@ func ParseWorkingDirFromImage(img v1.Image) (string, error) { } // PolicyContainersFromConfigs returns a slice of securitypolicy.Container generated -// from a slice of securitypolicy.ContainerConfig's -func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConfig) ([]*securitypolicy.Container, error) { - var policyContainers []*securitypolicy.Container +// from a slice of config.ContainerConfig's +func PolicyContainersFromConfigs(containerConfigs []config.ContainerConfig) ([]securitypolicy.Container, error) { + var policyContainers []securitypolicy.Container for _, containerConfig := range containerConfigs { var imageOptions []remote.Option @@ -126,7 +130,7 @@ func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConf if err != nil { return nil, err } - envRules := securitypolicy.NewEnvVarRules(envVars) + envRules := config.NewEnvVarRuleConfigs(envVars) envRules = append(envRules, containerConfig.EnvRules...) workingDir, err := ParseWorkingDirFromImage(img) @@ -138,7 +142,7 @@ func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConf workingDir = containerConfig.WorkingDir } - container, err := securitypolicy.CreateContainerPolicy( + container, err := CreateContainerPolicy( containerConfig.Command, layerHashes, envRules, @@ -154,3 +158,107 @@ func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConf return policyContainers, nil } + +// CreateContainerPolicy creates a new Container policy instance from the +// provided constraints or an error if parameter validation fails. +func CreateContainerPolicy( + command, layers []string, + envRuleConfigs []config.EnvRuleConfig, + workingDir string, + eMounts []string, + mntConfigs []config.MountConfig, +) (securitypolicy.Container, error) { + envRules, err := createEnvVarRules(envRuleConfigs) + if err != nil { + return securitypolicy.Container{}, err + } + mntConstraints, err := createMountConstraints(mntConfigs) + if err != nil { + return securitypolicy.Container{}, err + } + return securitypolicy.NewContainer( + command, + layers, + envRules, + workingDir, + eMounts, + mntConstraints, + ), nil +} + +func createEnvVarRules(rules []config.EnvRuleConfig) ([]securitypolicy.EnvVarRule, error) { + var envRules []securitypolicy.EnvVarRule + for _, rule := range rules { + switch rule.Strategy { + case securitypolicy.EnvRuleRegex: + if _, err := regexp.Compile(rule.Rule); err != nil { + return nil, err + } + case securitypolicy.EnvRuleString: + default: + return nil, fmt.Errorf("invalid env_var strategy: %s", rule.Strategy) + } + envRules = append(envRules, securitypolicy.EnvVarRule{ + Strategy: rule.Strategy, + Rule: rule.Rule, + }) + } + return envRules, nil +} + +func createMountConstraints(mntConfigs []config.MountConfig) ([]securitypolicy.Mount, error) { + var mntConstraints []securitypolicy.Mount + for _, m := range mntConfigs { + if _, err := regexp.Compile(m.HostPath); err != nil { + return nil, err + } + mntConstraints = append(mntConstraints, newMountFromConfig(&m)) + } + return mntConstraints, nil +} + +// newOptionsFromConfig applies the same logic as CRI plugin to generate +// mount options given readonly and propagation config. +// TODO: (anmaxvl) update when support for other mount types is added, +// e.g., vhd:// or evd:// +// TODO: (anmaxvl) Do we need to set/validate Linux rootfs propagation? +// In case we do, update securityPolicyContainer and Container structs +// as well as mount enforcement logic. +func newOptionsFromConfig(mCfg *config.MountConfig) []string { + mountOpts := []string{"rbind"} + + if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || + strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { + mountOpts = append(mountOpts, "rshared") + } else { + mountOpts = append(mountOpts, "rprivate") + } + + if mCfg.Readonly { + mountOpts = append(mountOpts, "ro") + } else { + mountOpts = append(mountOpts, "rw") + } + return mountOpts +} + +// newMountTypeFromConfig mimics the behavior in CRI when figuring out OCI +// mount type. +func newMountTypeFromConfig(mCfg *config.MountConfig) string { + if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || + strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { + return "bind" + } + return "none" +} + +// newMountFromConfig converts user provided MountConfig into internal representation +// of mount constraint. +func newMountFromConfig(mntConfig *config.MountConfig) securitypolicy.Mount { + return securitypolicy.NewMountConstraint( + mntConfig.HostPath, + mntConfig.ContainerPath, + newMountTypeFromConfig(mntConfig), + newOptionsFromConfig(mntConfig), + ) +} diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/internal.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/internal.go new file mode 100644 index 0000000000..5cd5f83564 --- /dev/null +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/internal.go @@ -0,0 +1,251 @@ +package securitypolicy + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/google/go-cmp/cmp" + oci "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +type mountInternal struct { + Source string + Destination string + Type string + Options []string +} + +// newMountInternal creates an internal mount constraint object from given +// source, destination, type and options +func newMountInternal(src, dst string, mType string, mOpts []string) mountInternal { + return mountInternal{ + Source: src, + Destination: dst, + Type: mType, + Options: mOpts, + } +} + +// securityPolicyContainer is an internal version of Container +type securityPolicyContainer struct { + // The command that we will allow the container to execute + Command []string + // The rules for determining if a given environment variable is allowed + EnvRules []EnvVarRule + // An ordered list of dm-verity root hashes for each layer that makes up + // "a container". Containers are constructed as an overlay file system. The + // order that the layers are overlayed is important and needs to be enforced + // as part of policy. + Layers []string + // WorkingDir is a path to container's working directory, which all the processes + // will default to. + WorkingDir string + // Unordered list of mounts which are expected to be present when the container + // starts + ExpectedMounts []string `json:"expected_mounts"` + // A list of constraints for determining if a given mount is allowed. + Mounts []mountInternal +} + +// validate checks given OCI mount against mount policy. Source and destination +// are checked by direct string comparisons. Different matching strategies can +// be added by introducing a separate path matching config, which isn't needed +// at the moment. +func (m *mountInternal) validate(mSpec oci.Mount) error { + if m.Type != mSpec.Type { + return fmt.Errorf("mount type not allowed by policy: expected=%q, actual=%q", m.Type, mSpec.Type) + } + if ok, _ := regexp.MatchString(m.Source, mSpec.Source); !ok { + return fmt.Errorf("mount source not allowed by policy: expected=%q, actual=%q", m.Source, mSpec.Source) + } + if m.Destination != mSpec.Destination && m.Destination != "" { + return fmt.Errorf("mount destination not allowed by policy: expected=%q, actual=%q", m.Destination, mSpec.Destination) + } + if !cmp.Equal(m.Options, mSpec.Options) { + return fmt.Errorf("mount options not allowed by policy: expected=%q, actual=%q", m.Options, mSpec.Options) + } + return nil +} + +// matchMount matches given OCI mount against mount constraints. If no match +// found, the mount is not allowed. +func (c *securityPolicyContainer) matchMount(sandboxID string, m oci.Mount) (err error) { + mountOk := false + for _, constraint := range c.Mounts { + // now that we know the sandboxID we can get the actual path for + // various destination path types by adding a UVM mount prefix + logrus.Debugf("validating constraint: %+v", constraint) + constraint = substituteUVMPath(sandboxID, constraint) + if err = constraint.validate(m); err == nil { + mountOk = true + break + } + } + if !mountOk { + return fmt.Errorf("mount is not allowed by policy: %+v", m) + } + return nil +} + +func (c Containers) toInternal() ([]securityPolicyContainer, error) { + containerMapLength := len(c.Elements) + if c.Length != containerMapLength { + err := fmt.Errorf( + "container numbers don't match in policy. expected: %d, actual: %d", + c.Length, + containerMapLength, + ) + return nil, err + } + + internal := make([]securityPolicyContainer, containerMapLength) + + for i := 0; i < containerMapLength; i++ { + index := strconv.Itoa(i) + cConf, ok := c.Elements[index] + if !ok { + return nil, fmt.Errorf("container constraint with index %q not found", index) + } + cInternal, err := cConf.toInternal() + if err != nil { + return nil, err + } + // save off new container + internal[i] = cInternal + } + + return internal, nil +} + +func (c Container) toInternal() (securityPolicyContainer, error) { + command, err := c.Command.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + envRules, err := c.EnvRules.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + layers, err := c.Layers.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + expectedMounts, err := c.ExpectedMounts.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + mounts, err := c.Mounts.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + return securityPolicyContainer{ + Command: command, + EnvRules: envRules, + Layers: layers, + // No need to have toInternal(), because WorkingDir is a string both + // internally and in the policy. + WorkingDir: c.WorkingDir, + ExpectedMounts: expectedMounts, + Mounts: mounts, + }, nil +} + +func (c CommandArgs) toInternal() ([]string, error) { + if c.Length != len(c.Elements) { + return nil, fmt.Errorf("command argument numbers don't match in policy. expected: %d, actual: %d", c.Length, len(c.Elements)) + } + + return stringMapToStringArray(c.Elements) +} + +func (e EnvRules) toInternal() ([]EnvVarRule, error) { + envRulesMapLength := len(e.Elements) + if e.Length != envRulesMapLength { + return nil, fmt.Errorf("env rule numbers don't match in policy. expected: %d, actual: %d", e.Length, envRulesMapLength) + } + + envRules := make([]EnvVarRule, envRulesMapLength) + for i := 0; i < envRulesMapLength; i++ { + eIndex := strconv.Itoa(i) + rule := EnvVarRule{ + Strategy: e.Elements[eIndex].Strategy, + Rule: e.Elements[eIndex].Rule, + } + envRules[i] = rule + } + + return envRules, nil +} + +func (l Layers) toInternal() ([]string, error) { + if l.Length != len(l.Elements) { + return nil, fmt.Errorf("layer numbers don't match in policy. expected: %d, actual: %d", l.Length, len(l.Elements)) + } + + return stringMapToStringArray(l.Elements) +} + +func (em ExpectedMounts) toInternal() ([]string, error) { + if em.Length != len(em.Elements) { + return nil, fmt.Errorf("expectedMounts numbers don't match in policy. expected: %d, actual: %d", em.Length, len(em.Elements)) + } + + return stringMapToStringArray(em.Elements) +} + +func (o Options) toInternal() ([]string, error) { + optLength := len(o.Elements) + if o.Length != optLength { + return nil, fmt.Errorf("mount option numbers don't match in policy. expected: %d, actual: %d", o.Length, optLength) + } + return stringMapToStringArray(o.Elements) +} + +func (m Mounts) toInternal() ([]mountInternal, error) { + mountLength := len(m.Elements) + if m.Length != mountLength { + return nil, fmt.Errorf("mount constraint numbers don't match in policy. expected: %d, actual: %d", m.Length, mountLength) + } + + mountConstraints := make([]mountInternal, mountLength) + for i := 0; i < mountLength; i++ { + mIndex := strconv.Itoa(i) + mount, ok := m.Elements[mIndex] + if !ok { + return nil, fmt.Errorf("mount constraint with index %q not found", mIndex) + } + opts, err := mount.Options.toInternal() + if err != nil { + return nil, err + } + mountConstraints[i] = mountInternal{ + Source: mount.Source, + Destination: mount.Destination, + Type: mount.Type, + Options: opts, + } + } + return mountConstraints, nil +} + +func stringMapToStringArray(m map[string]string) ([]string, error) { + mapSize := len(m) + out := make([]string, mapSize) + + for i := 0; i < mapSize; i++ { + index := strconv.Itoa(i) + value, ok := m[index] + if !ok { + return nil, fmt.Errorf("element with index %q not found", index) + } + out[i] = value + } + + return out, nil +} diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/mounts.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/mounts.go index 6faf7ce2c9..3921a3983a 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/mounts.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/mounts.go @@ -23,7 +23,7 @@ func getDefaultMountConstraints(sandboxID string, spec *specs.Spec) ([]mountInte var defaultMountConstraints []mountInternal for _, m := range defaultOCIMounts { - mc := newMountConstraint(m.Source, m.Destination, m.Type, m.Options) + mc := newMountInternal(m.Source, m.Destination, m.Type, m.Options) defaultMountConstraints = append(defaultMountConstraints, mc) } diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go index 9ac382ea7a..2c23c091d6 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go @@ -3,14 +3,18 @@ package securitypolicy import ( "encoding/base64" "encoding/json" - "regexp" "strconv" - "strings" - "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/pkg/errors" ) +type EnvRuleStrategy string + +const ( + EnvRuleString EnvRuleStrategy = "string" + EnvRuleRegex EnvRuleStrategy = "re2" +) + // NewOpenDoorPolicy creates a new SecurityPolicy with AllowAll set to `true` func NewOpenDoorPolicy() *SecurityPolicy { return &SecurityPolicy{ @@ -18,34 +22,6 @@ func NewOpenDoorPolicy() *SecurityPolicy { } } -type mountInternal struct { - Source string - Destination string - Type string - Options []string -} - -// Internal version of SecurityPolicyContainer -type securityPolicyContainer struct { - // The command that we will allow the container to execute - Command []string - // The rules for determining if a given environment variable is allowed - EnvRules []EnvRuleConfig - // An ordered list of dm-verity root hashes for each layer that makes up - // "a container". Containers are constructed as an overlay file system. The - // order that the layers are overlayed is important and needs to be enforced - // as part of policy. - Layers []string - // WorkingDir is a path to container's working directory, which all the processes - // will default to. - WorkingDir string - // Unordered list of mounts which are expected to be present when the container - // starts - ExpectedMounts []string `json:"expected_mounts"` - // A list of constraints for determining if a given mount is allowed. - Mounts []mountInternal -} - // SecurityPolicyState is a structure that holds user supplied policy to enforce // we keep both the encoded representation and the unmarshalled representation // because different components need to have access to either of these @@ -145,8 +121,8 @@ type ExpectedMounts StringArrayMap type Options StringArrayMap type EnvRules struct { - Length int `json:"length"` - Elements map[string]EnvRuleConfig `json:"elements"` + Length int `json:"length"` + Elements map[string]EnvVarRule `json:"elements"` } type Mount struct { @@ -161,29 +137,37 @@ type Mounts struct { Elements map[string]Mount `json:"elements"` } -// CreateContainerPolicy creates a new Container policy instance from the -// provided constraints or an error if parameter validation fails. -func CreateContainerPolicy( +type EnvVarRule struct { + Strategy EnvRuleStrategy `json:"strategy"` + Rule string `json:"rule"` +} + +// NewMountConstraint creates new Mount policy. +func NewMountConstraint(source, destination, mntType string, opts []string) Mount { + return Mount{ + Source: source, + Destination: destination, + Type: mntType, + Options: newMountOptions(opts), + } +} + +// NewContainer creates new Container policy. +func NewContainer( command, layers []string, - envRules []EnvRuleConfig, + envRules []EnvVarRule, workingDir string, - eMounts []string, - mounts []MountConfig, -) (*Container, error) { - if err := validateEnvRules(envRules); err != nil { - return nil, err - } - if err := validateMountConstraint(mounts); err != nil { - return nil, err - } - return &Container{ + expMounts []string, + mntConstraints []Mount, +) Container { + return Container{ Command: newCommandArgs(command), Layers: newLayers(layers), EnvRules: newEnvRules(envRules), WorkingDir: workingDir, - ExpectedMounts: newExpectedMounts(eMounts), - Mounts: newMountConstraints(mounts), - }, nil + ExpectedMounts: newExpectedMounts(expMounts), + Mounts: newMountConstraints(mntConstraints), + } } // NewSecurityPolicy creates a new SecurityPolicy from the provided values. @@ -200,27 +184,6 @@ func NewSecurityPolicy(allowAll bool, containers []*Container) *SecurityPolicy { } } -func validateEnvRules(rules []EnvRuleConfig) error { - for _, rule := range rules { - switch rule.Strategy { - case EnvVarRuleRegex: - if _, err := regexp.Compile(rule.Rule); err != nil { - return err - } - } - } - return nil -} - -func validateMountConstraint(mounts []MountConfig) error { - for _, m := range mounts { - if _, err := regexp.Compile(m.HostPath); err != nil { - return err - } - } - return nil -} - func newCommandArgs(args []string) CommandArgs { command := map[string]string{} for i, arg := range args { @@ -231,8 +194,8 @@ func newCommandArgs(args []string) CommandArgs { } } -func newEnvRules(rs []EnvRuleConfig) EnvRules { - envRules := map[string]EnvRuleConfig{} +func newEnvRules(rs []EnvVarRule) EnvRules { + envRules := map[string]EnvVarRule{} for i, r := range rs { envRules[strconv.Itoa(i)] = r } @@ -271,69 +234,11 @@ func newMountOptions(opts []string) Options { } } -// newOptionsFromConfig applies the same logic as CRI plugin to generate -// mount options given readonly and propagation config. -// TODO: (anmaxvl) update when support for other mount types is added, -// e.g., vhd:// or evd:// -// TODO: (anmaxvl) Do we need to set/validate Linux rootfs propagation? -// In case we do, update securityPolicyContainer and Container structs -// as well as mount enforcement logic. -func newOptionsFromConfig(mCfg *MountConfig) []string { - mountOpts := []string{"rbind"} - - if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || - strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { - mountOpts = append(mountOpts, "rshared") - } else { - mountOpts = append(mountOpts, "rprivate") - } - - if mCfg.Readonly { - mountOpts = append(mountOpts, "ro") - } else { - mountOpts = append(mountOpts, "rw") - } - return mountOpts -} - -// newMountTypeFromConfig mimics the behavior in CRI when figuring out OCI -// mount type. -func newMountTypeFromConfig(mCfg *MountConfig) string { - if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || - strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { - return "bind" - } - return "none" -} - -// newMountFromConfig converts user provided MountConfig into internal representation -// of mount constraint. -func newMountFromConfig(mCfg *MountConfig) Mount { - opts := newOptionsFromConfig(mCfg) - return Mount{ - Source: mCfg.HostPath, - Destination: mCfg.ContainerPath, - Type: newMountTypeFromConfig(mCfg), - Options: newMountOptions(opts), - } -} - -// newMountConstraint creates an internal mount constraint object from given -// source, destination, type and options -func newMountConstraint(src, dst string, mType string, mOpts []string) mountInternal { - return mountInternal{ - Source: src, - Destination: dst, - Type: mType, - Options: mOpts, - } -} - // newMountConstraints creates Mounts from a given array of MountConfig's. -func newMountConstraints(mountConfigs []MountConfig) Mounts { +func newMountConstraints(mountConfigs []Mount) Mounts { mounts := map[string]Mount{} for i, mc := range mountConfigs { - mounts[strconv.Itoa(i)] = newMountFromConfig(&mc) + mounts[strconv.Itoa(i)] = mc } return Mounts{ Elements: mounts, diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go index e284781ffb..e146dbbc8b 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go @@ -6,18 +6,15 @@ import ( "os" "path/filepath" "regexp" - "strconv" "strings" "sync" - "github.com/google/go-cmp/cmp" - oci "github.com/opencontainers/runtime-spec/specs-go" - "github.com/sirupsen/logrus" - "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/Microsoft/hcsshim/internal/hooks" specInternal "github.com/Microsoft/hcsshim/internal/spec" "github.com/Microsoft/hcsshim/pkg/annotations" + "github.com/google/go-cmp/cmp" + oci "github.com/opencontainers/runtime-spec/specs-go" ) type SecurityPolicyEnforcer interface { @@ -135,167 +132,6 @@ func NewStandardSecurityPolicyEnforcer(containers []securityPolicyContainer, enc } } -func (c Containers) toInternal() ([]securityPolicyContainer, error) { - containerMapLength := len(c.Elements) - if c.Length != containerMapLength { - err := fmt.Errorf( - "container numbers don't match in policy. expected: %d, actual: %d", - c.Length, - containerMapLength, - ) - return nil, err - } - - internal := make([]securityPolicyContainer, containerMapLength) - - for i := 0; i < containerMapLength; i++ { - index := strconv.Itoa(i) - cConf, ok := c.Elements[index] - if !ok { - return nil, fmt.Errorf("container constraint with index %q not found", index) - } - cInternal, err := cConf.toInternal() - if err != nil { - return nil, err - } - // save off new container - internal[i] = cInternal - } - - return internal, nil -} - -func (c Container) toInternal() (securityPolicyContainer, error) { - command, err := c.Command.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - - envRules, err := c.EnvRules.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - - layers, err := c.Layers.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - - expectedMounts, err := c.ExpectedMounts.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - - mounts, err := c.Mounts.toInternal() - if err != nil { - return securityPolicyContainer{}, err - } - return securityPolicyContainer{ - Command: command, - EnvRules: envRules, - Layers: layers, - // No need to have toInternal(), because WorkingDir is a string both - // internally and in the policy. - WorkingDir: c.WorkingDir, - ExpectedMounts: expectedMounts, - Mounts: mounts, - }, nil -} - -func (c CommandArgs) toInternal() ([]string, error) { - if c.Length != len(c.Elements) { - return nil, fmt.Errorf("command argument numbers don't match in policy. expected: %d, actual: %d", c.Length, len(c.Elements)) - } - - return stringMapToStringArray(c.Elements) -} - -func (e EnvRules) toInternal() ([]EnvRuleConfig, error) { - envRulesMapLength := len(e.Elements) - if e.Length != envRulesMapLength { - return nil, fmt.Errorf("env rule numbers don't match in policy. expected: %d, actual: %d", e.Length, envRulesMapLength) - } - - envRules := make([]EnvRuleConfig, envRulesMapLength) - for i := 0; i < envRulesMapLength; i++ { - eIndex := strconv.Itoa(i) - rule := EnvRuleConfig{ - Strategy: e.Elements[eIndex].Strategy, - Rule: e.Elements[eIndex].Rule, - } - envRules[i] = rule - } - - return envRules, nil -} - -func (l Layers) toInternal() ([]string, error) { - if l.Length != len(l.Elements) { - return nil, fmt.Errorf("layer numbers don't match in policy. expected: %d, actual: %d", l.Length, len(l.Elements)) - } - - return stringMapToStringArray(l.Elements) -} - -func (em ExpectedMounts) toInternal() ([]string, error) { - if em.Length != len(em.Elements) { - return nil, fmt.Errorf("expectedMounts numbers don't match in policy. expected: %d, actual: %d", em.Length, len(em.Elements)) - } - - return stringMapToStringArray(em.Elements) -} - -func (o Options) toInternal() ([]string, error) { - optLength := len(o.Elements) - if o.Length != optLength { - return nil, fmt.Errorf("mount option numbers don't match in policy. expected: %d, actual: %d", o.Length, optLength) - } - return stringMapToStringArray(o.Elements) -} - -func (m Mounts) toInternal() ([]mountInternal, error) { - mountLength := len(m.Elements) - if m.Length != mountLength { - return nil, fmt.Errorf("mount constraint numbers don't match in policy. expected: %d, actual: %d", m.Length, mountLength) - } - - mountConstraints := make([]mountInternal, mountLength) - for i := 0; i < mountLength; i++ { - mIndex := strconv.Itoa(i) - mount, ok := m.Elements[mIndex] - if !ok { - return nil, fmt.Errorf("mount constraint with index %q not found", mIndex) - } - opts, err := mount.Options.toInternal() - if err != nil { - return nil, err - } - mountConstraints[i] = mountInternal{ - Source: mount.Source, - Destination: mount.Destination, - Type: mount.Type, - Options: opts, - } - } - return mountConstraints, nil -} - -func stringMapToStringArray(m map[string]string) ([]string, error) { - mapSize := len(m) - out := make([]string, mapSize) - - for i := 0; i < mapSize; i++ { - index := strconv.Itoa(i) - value, ok := m[index] - if !ok { - return nil, fmt.Errorf("element with index %q not found", index) - } - out[i] = value - } - - return out, nil -} - func (se *StandardSecurityPolicyEnforcer) EnforceDeviceMountPolicy(target string, deviceHash string) (err error) { se.mutex.Lock() defer se.mutex.Unlock() @@ -493,7 +329,7 @@ func (se *StandardSecurityPolicyEnforcer) enforceWorkingDirPolicy(containerID st return nil } -func envIsMatchedByRule(envVariable string, rules []EnvRuleConfig) bool { +func envIsMatchedByRule(envVariable string, rules []EnvVarRule) bool { for _, rule := range rules { switch rule.Strategy { case "string": @@ -567,13 +403,8 @@ func (se *StandardSecurityPolicyEnforcer) EnforceMountPolicy(sandboxID, containe // first check against default mounts for _, mountConstraint := range defaultAllowedMounts { if err = mountConstraint.validate(specMnt); err == nil { - // TODO: Remove before checking in - logrus.Debugf("mount: %+v matched constraint: %+v", specMnt, mountConstraint) mountOk = true break - } else { - // TODO: Remove before checking in - logrus.Debugf("mount: %+v didn't match mount constraint: %+v", specMnt, mountConstraint) } } @@ -601,46 +432,6 @@ func (se *StandardSecurityPolicyEnforcer) EnforceMountPolicy(sandboxID, containe return nil } -// validate checks given OCI mount against mount policy. Source and destination -// are checked by direct string comparisons. Different matching strategies can -// be added by introducing a separate path matching config, which isn't needed -// at the moment. -func (m *mountInternal) validate(mSpec oci.Mount) error { - if m.Type != mSpec.Type { - return fmt.Errorf("mount type not allowed by policy: expected=%q, actual=%q", m.Type, mSpec.Type) - } - if ok, _ := regexp.MatchString(m.Source, mSpec.Source); !ok { - return fmt.Errorf("mount source not allowed by policy: expected=%q, actual=%q", m.Source, mSpec.Source) - } - if m.Destination != mSpec.Destination && m.Destination != "" { - return fmt.Errorf("mount destination not allowed by policy: expected=%q, actual=%q", m.Destination, mSpec.Destination) - } - if !cmp.Equal(m.Options, mSpec.Options) { - return fmt.Errorf("mount options not allowed by policy: expected=%q, actual=%q", m.Options, mSpec.Options) - } - return nil -} - -// matchMount matches given OCI mount against mount constraints. If no match -// found, the mount is not allowed. -func (c *securityPolicyContainer) matchMount(sandboxID string, m oci.Mount) (err error) { - mountOk := false - for _, constraint := range c.Mounts { - // now that we know the sandboxID we can get the actual path for - // various destination path types by adding a UVM mount prefix - logrus.Debugf("validating constraint: %+v", constraint) - constraint = substituteUVMPath(sandboxID, constraint) - if err = constraint.validate(m); err == nil { - mountOk = true - break - } - } - if !mountOk { - return fmt.Errorf("mount is not allowed by policy: %+v", m) - } - return nil -} - // substituteUVMPath substitutes mount prefix to an appropriate path inside // UVM. At policy generation time, it's impossible to tell what the sandboxID // will be, so the prefix substitution needs to happen during runtime. diff --git a/test/vendor/modules.txt b/test/vendor/modules.txt index 417c5af20a..7c87f5d1ce 100644 --- a/test/vendor/modules.txt +++ b/test/vendor/modules.txt @@ -61,6 +61,7 @@ github.com/Microsoft/hcsshim/internal/schemaversion github.com/Microsoft/hcsshim/internal/shimdiag github.com/Microsoft/hcsshim/internal/spec github.com/Microsoft/hcsshim/internal/timeout +github.com/Microsoft/hcsshim/internal/tools/securitypolicy/config github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers github.com/Microsoft/hcsshim/internal/uvm github.com/Microsoft/hcsshim/internal/uvmfolder