diff --git a/internal/tools/securitypolicy/README.md b/internal/tools/securitypolicy/README.md index fede4c931c..52ecf95c54 100644 --- a/internal/tools/securitypolicy/README.md +++ b/internal/tools/securitypolicy/README.md @@ -34,70 +34,84 @@ represented in JSON. ```json { "allow_all": false, - "num_containers": 2, "containers": { - "0": { - "num_commands": 2, - "command": { - "0": "rustc", - "1": "--help" - }, - "num_env_rules": 6, - "env_rules": { - "0": { - "strategy": "string", - "rule": "PATH=/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - }, - "1": { - "strategy": "string", - "rule": "RUSTUP_HOME=/usr/local/rustup" - }, - "2": { - "strategy": "string", - "rule": "CARGO_HOME=/usr/local/cargo" - }, - "3": { - "strategy": "string", - "rule": "RUST_VERSION=1.52.1" + "length": 2, + "elements": { + "0": { + "command": { + "length": 2, + "elements": { + "0": "rustc", + "1": "--help" + } }, - "4": { - "strategy": "string", - "rule": "TERM=xterm" + "env_rules": { + "length": 6, + "elements": { + "0": { + "strategy": "re2", + "rule": "PREFIX_.+=.+" + }, + "1": { + "strategy": "string", + "rule": "PATH=/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + }, + "2": { + "strategy": "string", + "rule": "RUSTUP_HOME=/usr/local/rustup" + }, + "3": { + "strategy": "string", + "rule": "CARGO_HOME=/usr/local/cargo" + }, + "4": { + "strategy": "string", + "rule": "RUST_VERSION=1.52.1" + }, + "5": { + "strategy": "string", + "rule": "TERM=xterm" + } + } }, - "5": { - "strategy": "re2", - "rule": "PREFIX_.+=.+" + "layers": { + "length": 6, + "elements": { + "0": "fe84c9d5bfddd07a2624d00333cf13c1a9c941f3a261f13ead44fc6a93bc0e7a", + "1": "4dedae42847c704da891a28c25d32201a1ae440bce2aecccfa8e6f03b97a6a6c", + "2": "41d64cdeb347bf236b4c13b7403b633ff11f1cf94dbc7cf881a44d6da88c5156", + "3": "eb36921e1f82af46dfe248ef8f1b3afb6a5230a64181d960d10237a08cd73c79", + "4": "e769d7487cc314d3ee748a4440805317c19262c7acd2fdbdb0d47d2e4613a15c", + "5": "1b80f120dbd88e4355d6241b519c3e25290215c469516b49dece9cf07175a766" + } } }, - "num_layers": 6, - "layers": { - "0": "fe84c9d5bfddd07a2624d00333cf13c1a9c941f3a261f13ead44fc6a93bc0e7a", - "1": "4dedae42847c704da891a28c25d32201a1ae440bce2aecccfa8e6f03b97a6a6c", - "2": "41d64cdeb347bf236b4c13b7403b633ff11f1cf94dbc7cf881a44d6da88c5156", - "3": "eb36921e1f82af46dfe248ef8f1b3afb6a5230a64181d960d10237a08cd73c79", - "4": "e769d7487cc314d3ee748a4440805317c19262c7acd2fdbdb0d47d2e4613a15c", - "5": "1b80f120dbd88e4355d6241b519c3e25290215c469516b49dece9cf07175a766" - } - }, - "1": { - "num_commands": 1, - "command": { - "0": "/pause" - }, - "num_env_rules": 2, - "env_rules": { - "0": { - "strategy": "string", - "rule": "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + "1": { + "command": { + "length": 1, + "elements": { + "0": "/pause" + } }, - "1": { - "strategy": "string", - "rule": "TERM=xterm" + "env_rules": { + "length": 2, + "elements": { + "0": { + "strategy": "string", + "rule": "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + }, + "1": { + "strategy": "string", + "rule": "TERM=xterm" + } + } + }, + "layers": { + "length": 1, + "elements": { + "0": "16b514057a06ad665f92c02863aca074fd5976c755d26bff16365299169e8415" + } } - }, - "num_layers": 1, - "layers": { - "0": "16b514057a06ad665f92c02863aca074fd5976c755d26bff16365299169e8415" } } } diff --git a/internal/tools/securitypolicy/main.go b/internal/tools/securitypolicy/main.go index d68936689b..58100455c9 100644 --- a/internal/tools/securitypolicy/main.go +++ b/internal/tools/securitypolicy/main.go @@ -13,7 +13,7 @@ import ( "github.com/BurntSushi/toml" "github.com/Microsoft/hcsshim/ext4/dmverity" "github.com/Microsoft/hcsshim/ext4/tar2ext4" - sp "github.com/Microsoft/hcsshim/pkg/securitypolicy" + "github.com/Microsoft/hcsshim/pkg/securitypolicy" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" @@ -47,7 +47,7 @@ func main() { return err } - policy, err := func() (sp.SecurityPolicy, error) { + policy, err := func() (securitypolicy.SecurityPolicy, error) { if config.AllowAll { return createOpenDoorPolicy(), nil } else { @@ -79,8 +79,8 @@ func main() { } type EnvironmentVariableRule struct { - Strategy sp.EnvVarRule `toml:"strategy"` - Rule string `toml:"rule"` + Strategy securitypolicy.EnvVarRule `toml:"strategy"` + Rule string `toml:"rule"` } type Container struct { @@ -100,15 +100,17 @@ type Config struct { Containers []Container `toml:"container"` } -func createOpenDoorPolicy() sp.SecurityPolicy { - return sp.SecurityPolicy{ +func createOpenDoorPolicy() securitypolicy.SecurityPolicy { + return securitypolicy.SecurityPolicy{ AllowAll: true, } } -func createPolicyFromConfig(config Config) (sp.SecurityPolicy, error) { - p := sp.SecurityPolicy{ - Containers: map[string]sp.SecurityPolicyContainer{}, +func createPolicyFromConfig(config Config) (securitypolicy.SecurityPolicy, error) { + p := securitypolicy.SecurityPolicy{ + Containers: securitypolicy.Containers{ + Elements: map[string]securitypolicy.Container{}, + }, } // Hardcode the pause container version and command. We still pull it @@ -139,11 +141,12 @@ func createPolicyFromConfig(config Config) (sp.SecurityPolicy, error) { command := convertCommand(configContainer.Command) envRules := convertEnvironmentVariableRules(configContainer.EnvRules) - container := sp.SecurityPolicyContainer{ - NumCommands: len(command), - Command: command, - EnvRules: envRules, - Layers: map[string]string{}, + container := securitypolicy.Container{ + Command: command, + EnvRules: envRules, + Layers: securitypolicy.Layers{ + Elements: map[string]string{}, + }, } ref, err := name.ParseReference(configContainer.Name) if err != nil { @@ -192,11 +195,9 @@ func createPolicyFromConfig(config Config) (sp.SecurityPolicy, error) { } hash := dmverity.RootHash(tree) hashString := fmt.Sprintf("%x", hash) - container.Layers = addLayer(container.Layers, hashString) + addLayer(&container.Layers, hashString) } - container.NumLayers = len(layers) - // add rules for all known environment variables from the configuration // these are in addition to "other rules" from the policy definition file config, err := img.ConfigFile() @@ -204,36 +205,33 @@ func createPolicyFromConfig(config Config) (sp.SecurityPolicy, error) { return p, err } for _, env := range config.Config.Env { - rule := sp.SecurityPolicyEnvironmentVariableRule{ - Strategy: sp.EnvVarRuleString, + rule := securitypolicy.EnvRule{ + Strategy: securitypolicy.EnvVarRuleString, Rule: env, } - container.EnvRules = addEnvRule(container.EnvRules, rule) + addEnvRule(&container.EnvRules, rule) } // cri adds TERM=xterm for all workload containers. we add to all containers // to prevent any possble erroring - rule := sp.SecurityPolicyEnvironmentVariableRule{ - Strategy: sp.EnvVarRuleString, + rule := securitypolicy.EnvRule{ + Strategy: securitypolicy.EnvVarRuleString, Rule: "TERM=xterm", } - container.EnvRules = addEnvRule(container.EnvRules, rule) - container.NumEnvRules = len(container.EnvRules) + addEnvRule(&container.EnvRules, rule) - p.Containers = addContainer(p.Containers, container) + addContainer(&p.Containers, container) } - p.NumContainers = len(p.Containers) - return p, nil } func validateEnvRules(rules []EnvironmentVariableRule) error { for _, rule := range rules { switch rule.Strategy { - case sp.EnvVarRuleRegex: + case securitypolicy.EnvVarRuleRegex: _, err := regexp.Compile(rule.Rule) if err != nil { return err @@ -244,21 +242,23 @@ func validateEnvRules(rules []EnvironmentVariableRule) error { return nil } -func convertCommand(toml []string) map[string]string { +func convertCommand(toml []string) securitypolicy.CommandArgs { json := map[string]string{} for i, arg := range toml { json[strconv.Itoa(i)] = arg } - return json + return securitypolicy.CommandArgs{ + Elements: json, + } } -func convertEnvironmentVariableRules(toml []EnvironmentVariableRule) map[string]sp.SecurityPolicyEnvironmentVariableRule { - json := map[string]sp.SecurityPolicyEnvironmentVariableRule{} +func convertEnvironmentVariableRules(toml []EnvironmentVariableRule) securitypolicy.EnvRules { + json := map[string]securitypolicy.EnvRule{} for i, rule := range toml { - jsonRule := sp.SecurityPolicyEnvironmentVariableRule{ + jsonRule := securitypolicy.EnvRule{ Strategy: rule.Strategy, Rule: rule.Rule, } @@ -266,29 +266,25 @@ func convertEnvironmentVariableRules(toml []EnvironmentVariableRule) map[string] json[strconv.Itoa(i)] = jsonRule } - return json + return securitypolicy.EnvRules{ + Elements: json, + } } -func addContainer(containers map[string]sp.SecurityPolicyContainer, container sp.SecurityPolicyContainer) map[string]sp.SecurityPolicyContainer { - index := strconv.Itoa(len(containers)) +func addContainer(containers *securitypolicy.Containers, container securitypolicy.Container) { + index := strconv.Itoa(len(containers.Elements)) - containers[index] = container - - return containers + containers.Elements[index] = container } -func addLayer(layers map[string]string, layer string) map[string]string { - index := strconv.Itoa(len(layers)) - - layers[index] = layer +func addLayer(layers *securitypolicy.Layers, layer string) { + index := strconv.Itoa(len(layers.Elements)) - return layers + layers.Elements[index] = layer } -func addEnvRule(rules map[string]sp.SecurityPolicyEnvironmentVariableRule, rule sp.SecurityPolicyEnvironmentVariableRule) map[string]sp.SecurityPolicyEnvironmentVariableRule { - index := strconv.Itoa(len(rules)) - - rules[index] = rule +func addEnvRule(rules *securitypolicy.EnvRules, rule securitypolicy.EnvRule) { + index := strconv.Itoa(len(rules.Elements)) - return rules + rules.Elements[index] = rule } diff --git a/pkg/securitypolicy/securitypolicy.go b/pkg/securitypolicy/securitypolicy.go index 5079e890ec..3b381ccf2a 100644 --- a/pkg/securitypolicy/securitypolicy.go +++ b/pkg/securitypolicy/securitypolicy.go @@ -48,52 +48,6 @@ type EncodedSecurityPolicy struct { SecurityPolicy string `json:"SecurityPolicy,omitempty"` } -// JSON transport version -type SecurityPolicy struct { - // Flag that when set to true allows for all checks to pass. Currently used - // to run with security policy enforcement "running dark"; checks can be in - // place but the default policy that is created on startup has AllowAll set - // to true, thus making policy enforcement effectively "off" from a logical - // standpoint. Policy enforcement isn't actually off as the policy is "allow - // everything:. - AllowAll bool `json:"allow_all"` - // Total number of containers in our map - NumContainers int `json:"num_containers"` - // One or more containers that are allowed to run - Containers map[string]SecurityPolicyContainer `json:"containers"` -} - -// SecurityPolicyContainer contains information about a container that should be -// allowed to run. "Allowed to run" is a bit of misnomer. For example, we -// enforce that when an overlay file system is constructed that it must be a -// an ordering of layers (as seen through dm-verity root hashes of devices) -// that match a listing from Layers in one of any valid SecurityPolicyContainer -// entries. Once that overlay creation is allowed, the command could not match -// policy and running the command would be rejected. -type SecurityPolicyContainer struct { - // Number of entries that should be in the "Command" map - NumCommands int `json:"num_commands"` - // The command that we will allow the container to execute - Command map[string]string `json:"command"` - // Number of entries that should be in the "EnvRules" map - NumEnvRules int `json:"num_env_rules"` - // The rules for determining if a given environment variable is allowed - EnvRules map[string]SecurityPolicyEnvironmentVariableRule `json:"env_rules"` - // Number of entries that should in the "Layers" map - NumLayers int `json:"num_layers"` - // 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. The map is interpreted as an ordered list by arranging - // the keys of the map as indexes like 0,1,2,3 to establish the order. - Layers map[string]string `json:"layers"` -} - -type SecurityPolicyEnvironmentVariableRule struct { - Strategy EnvVarRule `json:"strategy"` - Rule string `json:"rule"` -} - // Constructs SecurityPolicyState from base64Policy string. It first decodes // base64 policy and returns the structs security policy struct and encoded // security policy for given policy. The security policy is transmitted as json @@ -127,3 +81,94 @@ func NewSecurityPolicyState(base64Policy string) (*SecurityPolicyState, error) { EncodedSecurityPolicy: encodedSecurityPolicy, }, nil } + +type SecurityPolicy struct { + // Flag that when set to true allows for all checks to pass. Currently used + // to run with security policy enforcement "running dark"; checks can be in + // place but the default policy that is created on startup has AllowAll set + // to true, thus making policy enforcement effectively "off" from a logical + // standpoint. Policy enforcement isn't actually off as the policy is "allow + // everything:. + AllowAll bool `json:"allow_all"` + // One or more containers that are allowed to run + Containers Containers `json:"containers"` +} + +type Containers struct { + Length int `json:"length"` + Elements map[string]Container `json:"elements"` +} + +type Container struct { + Command CommandArgs `json:"command"` + EnvRules EnvRules `json:"env_rules"` + Layers Layers `json:"layers"` +} + +type Layers struct { + Length int `json:"length"` + // an ordered list of args where the key is in the index for ordering + Elements map[string]string `json:"elements"` +} + +type CommandArgs struct { + Length int `json:"length"` + // an ordered list of args where the key is in the index for ordering + Elements map[string]string `json:"elements"` +} + +type EnvRules struct { + Length int `json:"length"` + Elements map[string]EnvRule `json:"elements"` +} + +type EnvRule struct { + Strategy EnvVarRule `json:"strategy"` + Rule string `json:"rule"` +} + +// Custom JSON marshalling to add `lenth` field that matches the number of +// elements present in the `elements` field. +func (c Containers) MarshalJSON() ([]byte, error) { + type Alias Containers + return json.Marshal(&struct { + Length int `json:"length"` + *Alias + }{ + Length: len(c.Elements), + Alias: (*Alias)(&c), + }) +} + +func (l Layers) MarshalJSON() ([]byte, error) { + type Alias Layers + return json.Marshal(&struct { + Length int `json:"length"` + *Alias + }{ + Length: len(l.Elements), + Alias: (*Alias)(&l), + }) +} + +func (c CommandArgs) MarshalJSON() ([]byte, error) { + type Alias CommandArgs + return json.Marshal(&struct { + Length int `json:"length"` + *Alias + }{ + Length: len(c.Elements), + Alias: (*Alias)(&c), + }) +} + +func (e EnvRules) MarshalJSON() ([]byte, error) { + type Alias EnvRules + return json.Marshal(&struct { + Length int `json:"length"` + *Alias + }{ + Length: len(e.Elements), + Alias: (*Alias)(&e), + }) +} diff --git a/pkg/securitypolicy/securitypolicy_test.go b/pkg/securitypolicy/securitypolicy_test.go index 604b0b99fc..a60a749b10 100644 --- a/pkg/securitypolicy/securitypolicy_test.go +++ b/pkg/securitypolicy/securitypolicy_test.go @@ -34,46 +34,45 @@ const ( // to our internal format is done correctly. func Test_StandardSecurityPolicyEnforcer_From_Security_Policy_Conversion(t *testing.T) { f := func(p *SecurityPolicy) bool { - - containers, err := toInternal(p) + containers, err := p.Containers.toInternal() if err != nil { t.Logf("unexpected setup error. this might mean test fixture setup has a bug: %v", err) return false } - if len(containers) != p.NumContainers { - t.Errorf("numContainers don't match. internal: %d, external: %d", len(containers), p.NumContainers) + if len(containers) != p.Containers.Length { + t.Errorf("number of containers doesn't match. internal: %d, external: %d", len(containers), p.Containers.Length) return false } // do by index comparison of containers for i := 0; i < len(containers); i++ { internal := containers[i] - external := p.Containers[strconv.Itoa(i)] + external := p.Containers.Elements[strconv.Itoa(i)] // verify sanity with size - if len(internal.Command) != external.NumCommands { - t.Errorf("numCommands don't match for container %d. internal: %d, external: %d", i, len(internal.Command), external.NumCommands) + if len(internal.Command) != external.Command.Length { + t.Errorf("number of command args doesn't match for container %d. internal: %d, external: %d", i, len(internal.Command), external.Command.Length) } - if len(internal.EnvRules) != external.NumEnvRules { - t.Errorf("numEnvRules don't match for container %d. internal: %d, external: %d", i, len(internal.EnvRules), external.NumEnvRules) + if len(internal.EnvRules) != external.EnvRules.Length { + t.Errorf("number of env rules doesn't match for container %d. internal: %d, external: %d", i, len(internal.EnvRules), external.EnvRules.Length) } - if len(internal.Layers) != external.NumLayers { - t.Errorf("numLayers don't match for container %d. internal: %d, external: %d", i, len(internal.Layers), external.NumLayers) + if len(internal.Layers) != external.Layers.Length { + t.Errorf("number of layers doesn't match for container %d. internal: %d, external: %d", i, len(internal.Layers), external.Layers.Length) } // do by index comparison of sub-items for j := 0; j < len(internal.Command); j++ { - if internal.Command[j] != external.Command[strconv.Itoa(j)] { - t.Errorf("command entries at index %d for for container %d don't match. internal: %s, external: %s", j, i, internal.Command[j], external.Command[strconv.Itoa(j)]) + if internal.Command[j] != external.Command.Elements[strconv.Itoa(j)] { + t.Errorf("command entries at index %d for for container %d don't match. internal: %s, external: %s", j, i, internal.Command[j], external.Command.Elements[strconv.Itoa(j)]) } } for j := 0; j < len(internal.EnvRules); j++ { irule := internal.EnvRules[j] - erule := external.EnvRules[strconv.Itoa(j)] + erule := external.EnvRules.Elements[strconv.Itoa(j)] if (irule.Strategy != erule.Strategy) || (irule.Rule != erule.Rule) { t.Errorf("env rule entries at index %d for for container %d don't match. internal: %v, external: %v", j, i, irule, erule) @@ -81,8 +80,8 @@ func Test_StandardSecurityPolicyEnforcer_From_Security_Policy_Conversion(t *test } for j := 0; j < len(internal.Layers); j++ { - if internal.Layers[j] != external.Layers[strconv.Itoa(j)] { - t.Errorf("layer entries at index %d for for container %d don't match. internal: %s, external: %s", j, i, internal.Layers[j], external.Layers[strconv.Itoa(j)]) + if internal.Layers[j] != external.Layers.Elements[strconv.Itoa(j)] { + t.Errorf("layer entries at index %d for for container %d don't match. internal: %s, external: %s", j, i, internal.Layers[j], external.Layers.Elements[strconv.Itoa(j)]) } } } @@ -742,46 +741,54 @@ func (*SecurityPolicy) Generate(r *rand.Rand, size int) reflect.Value { // confusing fixture name functions where we have generate* for both internal // and external versions p := &SecurityPolicy{ - Containers: map[string]SecurityPolicyContainer{}, + Containers: Containers{ + Elements: map[string]Container{}, + }, } p.AllowAll = false numContainers := int(atLeastOneAtMost(r, maxContainersInGeneratedPolicy)) for i := 0; i < numContainers; i++ { - c := SecurityPolicyContainer{ - Command: map[string]string{}, - EnvRules: map[string]SecurityPolicyEnvironmentVariableRule{}, - Layers: map[string]string{}, + c := Container{ + Command: CommandArgs{ + Elements: map[string]string{}, + }, + EnvRules: EnvRules{ + Elements: map[string]EnvRule{}, + }, + Layers: Layers{ + Elements: map[string]string{}, + }, } // command numArgs := int(atLeastOneAtMost(r, maxGeneratedCommandArgs)) for i := 0; i < numArgs; i++ { - c.Command[strconv.Itoa(i)] = randVariableString(r, maxGeneratedCommandLength) + c.Command.Elements[strconv.Itoa(i)] = randVariableString(r, maxGeneratedCommandLength) } - c.NumCommands = numArgs + c.Command.Length = numArgs // layers numLayers := int(atLeastOneAtMost(r, maxLayersInGeneratedContainer)) for i := 0; i < numLayers; i++ { - c.Layers[strconv.Itoa(i)] = generateRootHash(r) + c.Layers.Elements[strconv.Itoa(i)] = generateRootHash(r) } - c.NumLayers = numLayers + c.Layers.Length = numLayers // env variable rules numEnvRules := int(atMost(r, maxGeneratedEnvironmentVariableRules)) for i := 0; i < numEnvRules; i++ { - rule := SecurityPolicyEnvironmentVariableRule{ + rule := EnvRule{ Strategy: "string", Rule: randVariableString(r, maxGeneratedEnvironmentVariableRuleLength), } - c.EnvRules[strconv.Itoa(i)] = rule + c.EnvRules.Elements[strconv.Itoa(i)] = rule } - c.NumEnvRules = numEnvRules + c.EnvRules.Length = numEnvRules - p.Containers[strconv.Itoa(i)] = c + p.Containers.Elements[strconv.Itoa(i)] = c } - p.NumContainers = numContainers + p.Containers.Length = numContainers return reflect.ValueOf(p) } diff --git a/pkg/securitypolicy/securitypolicyenforcer.go b/pkg/securitypolicy/securitypolicyenforcer.go index 3a72e6f5bd..f17526aedf 100644 --- a/pkg/securitypolicy/securitypolicyenforcer.go +++ b/pkg/securitypolicy/securitypolicyenforcer.go @@ -21,7 +21,7 @@ func NewSecurityPolicyEnforcer(state SecurityPolicyState) (SecurityPolicyEnforce if state.SecurityPolicy.AllowAll { return &OpenDoorSecurityPolicyEnforcer{}, nil } else { - containers, err := toInternal(&state.SecurityPolicy) + containers, err := state.SecurityPolicy.Containers.toInternal() if err != nil { return nil, err } @@ -121,57 +121,83 @@ func NewStandardSecurityPolicyEnforcer(containers []securityPolicyContainer, enc } } -func toInternal(external *SecurityPolicy) ([]securityPolicyContainer, error) { - containerMapLength := len(external.Containers) - if external.NumContainers != containerMapLength { - errmsg := fmt.Sprintf("container numbers don't match in policy. expected: %d, actual: %d", external.NumContainers, containerMapLength) - return nil, errors.New(errmsg) +func (c Containers) toInternal() ([]securityPolicyContainer, error) { + containerMapLength := len(c.Elements) + if c.Length != containerMapLength { + return nil, fmt.Errorf("container numbers don't match in policy. expected: %d, actual: %d", c.Length, containerMapLength) } internal := make([]securityPolicyContainer, containerMapLength) for i := 0; i < containerMapLength; i++ { - iContainer := securityPolicyContainer{} + iContainer, err := c.Elements[strconv.Itoa(i)].toInternal() + if err != nil { + return nil, err + } - eContainer := external.Containers[strconv.Itoa(i)] + // save off new container + internal[i] = iContainer + } - // Command conversion - if eContainer.NumCommands != len(eContainer.Command) { - errmsg := fmt.Sprintf("command argument numbers don't match in policy. expected: %d, actual: %d", eContainer.NumCommands, len(eContainer.Command)) - return nil, errors.New(errmsg) - } - iContainer.Command = stringMapToStringArray(eContainer.Command) + return internal, nil +} - // Layers conversion - if eContainer.NumLayers != len(eContainer.Layers) { - errmsg := fmt.Sprintf("layer numbers don't match in policy. expected: %d, actual: %d", eContainer.NumLayers, len(eContainer.Layers)) - return nil, errors.New(errmsg) - } - iContainer.Layers = stringMapToStringArray(eContainer.Layers) +func (c Container) toInternal() (securityPolicyContainer, error) { + command, err := c.Command.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } - // EnvRules conversion - envRulesMapLength := len(eContainer.EnvRules) - if eContainer.NumEnvRules != envRulesMapLength { - errmsg := fmt.Sprintf("env rule numbers don't match in policy. expected: %d, actual: %d", eContainer.NumEnvRules, envRulesMapLength) - return nil, errors.New(errmsg) - } + envRules, err := c.EnvRules.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } - envRules := make([]securityPolicyEnvironmentVariableRule, envRulesMapLength) - for i := 0; i < envRulesMapLength; i++ { - eIndex := strconv.Itoa(i) - rule := securityPolicyEnvironmentVariableRule{ - Strategy: eContainer.EnvRules[eIndex].Strategy, - Rule: eContainer.EnvRules[eIndex].Rule, - } - envRules[i] = rule + layers, err := c.Layers.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + return securityPolicyContainer{ + Command: command, + EnvRules: envRules, + Layers: layers, + }, 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), nil +} + +func (e EnvRules) toInternal() ([]securityPolicyEnvironmentVariableRule, 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([]securityPolicyEnvironmentVariableRule, envRulesMapLength) + for i := 0; i < envRulesMapLength; i++ { + eIndex := strconv.Itoa(i) + rule := securityPolicyEnvironmentVariableRule{ + Strategy: e.Elements[eIndex].Strategy, + Rule: e.Elements[eIndex].Rule, } - iContainer.EnvRules = envRules + envRules[i] = rule + } - // save off new container - internal[i] = iContainer + 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 internal, nil + return stringMapToStringArray(l.Elements), nil } func stringMapToStringArray(in map[string]string) []string { 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 5079e890ec..3b381ccf2a 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go @@ -48,52 +48,6 @@ type EncodedSecurityPolicy struct { SecurityPolicy string `json:"SecurityPolicy,omitempty"` } -// JSON transport version -type SecurityPolicy struct { - // Flag that when set to true allows for all checks to pass. Currently used - // to run with security policy enforcement "running dark"; checks can be in - // place but the default policy that is created on startup has AllowAll set - // to true, thus making policy enforcement effectively "off" from a logical - // standpoint. Policy enforcement isn't actually off as the policy is "allow - // everything:. - AllowAll bool `json:"allow_all"` - // Total number of containers in our map - NumContainers int `json:"num_containers"` - // One or more containers that are allowed to run - Containers map[string]SecurityPolicyContainer `json:"containers"` -} - -// SecurityPolicyContainer contains information about a container that should be -// allowed to run. "Allowed to run" is a bit of misnomer. For example, we -// enforce that when an overlay file system is constructed that it must be a -// an ordering of layers (as seen through dm-verity root hashes of devices) -// that match a listing from Layers in one of any valid SecurityPolicyContainer -// entries. Once that overlay creation is allowed, the command could not match -// policy and running the command would be rejected. -type SecurityPolicyContainer struct { - // Number of entries that should be in the "Command" map - NumCommands int `json:"num_commands"` - // The command that we will allow the container to execute - Command map[string]string `json:"command"` - // Number of entries that should be in the "EnvRules" map - NumEnvRules int `json:"num_env_rules"` - // The rules for determining if a given environment variable is allowed - EnvRules map[string]SecurityPolicyEnvironmentVariableRule `json:"env_rules"` - // Number of entries that should in the "Layers" map - NumLayers int `json:"num_layers"` - // 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. The map is interpreted as an ordered list by arranging - // the keys of the map as indexes like 0,1,2,3 to establish the order. - Layers map[string]string `json:"layers"` -} - -type SecurityPolicyEnvironmentVariableRule struct { - Strategy EnvVarRule `json:"strategy"` - Rule string `json:"rule"` -} - // Constructs SecurityPolicyState from base64Policy string. It first decodes // base64 policy and returns the structs security policy struct and encoded // security policy for given policy. The security policy is transmitted as json @@ -127,3 +81,94 @@ func NewSecurityPolicyState(base64Policy string) (*SecurityPolicyState, error) { EncodedSecurityPolicy: encodedSecurityPolicy, }, nil } + +type SecurityPolicy struct { + // Flag that when set to true allows for all checks to pass. Currently used + // to run with security policy enforcement "running dark"; checks can be in + // place but the default policy that is created on startup has AllowAll set + // to true, thus making policy enforcement effectively "off" from a logical + // standpoint. Policy enforcement isn't actually off as the policy is "allow + // everything:. + AllowAll bool `json:"allow_all"` + // One or more containers that are allowed to run + Containers Containers `json:"containers"` +} + +type Containers struct { + Length int `json:"length"` + Elements map[string]Container `json:"elements"` +} + +type Container struct { + Command CommandArgs `json:"command"` + EnvRules EnvRules `json:"env_rules"` + Layers Layers `json:"layers"` +} + +type Layers struct { + Length int `json:"length"` + // an ordered list of args where the key is in the index for ordering + Elements map[string]string `json:"elements"` +} + +type CommandArgs struct { + Length int `json:"length"` + // an ordered list of args where the key is in the index for ordering + Elements map[string]string `json:"elements"` +} + +type EnvRules struct { + Length int `json:"length"` + Elements map[string]EnvRule `json:"elements"` +} + +type EnvRule struct { + Strategy EnvVarRule `json:"strategy"` + Rule string `json:"rule"` +} + +// Custom JSON marshalling to add `lenth` field that matches the number of +// elements present in the `elements` field. +func (c Containers) MarshalJSON() ([]byte, error) { + type Alias Containers + return json.Marshal(&struct { + Length int `json:"length"` + *Alias + }{ + Length: len(c.Elements), + Alias: (*Alias)(&c), + }) +} + +func (l Layers) MarshalJSON() ([]byte, error) { + type Alias Layers + return json.Marshal(&struct { + Length int `json:"length"` + *Alias + }{ + Length: len(l.Elements), + Alias: (*Alias)(&l), + }) +} + +func (c CommandArgs) MarshalJSON() ([]byte, error) { + type Alias CommandArgs + return json.Marshal(&struct { + Length int `json:"length"` + *Alias + }{ + Length: len(c.Elements), + Alias: (*Alias)(&c), + }) +} + +func (e EnvRules) MarshalJSON() ([]byte, error) { + type Alias EnvRules + return json.Marshal(&struct { + Length int `json:"length"` + *Alias + }{ + Length: len(e.Elements), + Alias: (*Alias)(&e), + }) +} 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 3a72e6f5bd..f17526aedf 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go @@ -21,7 +21,7 @@ func NewSecurityPolicyEnforcer(state SecurityPolicyState) (SecurityPolicyEnforce if state.SecurityPolicy.AllowAll { return &OpenDoorSecurityPolicyEnforcer{}, nil } else { - containers, err := toInternal(&state.SecurityPolicy) + containers, err := state.SecurityPolicy.Containers.toInternal() if err != nil { return nil, err } @@ -121,57 +121,83 @@ func NewStandardSecurityPolicyEnforcer(containers []securityPolicyContainer, enc } } -func toInternal(external *SecurityPolicy) ([]securityPolicyContainer, error) { - containerMapLength := len(external.Containers) - if external.NumContainers != containerMapLength { - errmsg := fmt.Sprintf("container numbers don't match in policy. expected: %d, actual: %d", external.NumContainers, containerMapLength) - return nil, errors.New(errmsg) +func (c Containers) toInternal() ([]securityPolicyContainer, error) { + containerMapLength := len(c.Elements) + if c.Length != containerMapLength { + return nil, fmt.Errorf("container numbers don't match in policy. expected: %d, actual: %d", c.Length, containerMapLength) } internal := make([]securityPolicyContainer, containerMapLength) for i := 0; i < containerMapLength; i++ { - iContainer := securityPolicyContainer{} + iContainer, err := c.Elements[strconv.Itoa(i)].toInternal() + if err != nil { + return nil, err + } - eContainer := external.Containers[strconv.Itoa(i)] + // save off new container + internal[i] = iContainer + } - // Command conversion - if eContainer.NumCommands != len(eContainer.Command) { - errmsg := fmt.Sprintf("command argument numbers don't match in policy. expected: %d, actual: %d", eContainer.NumCommands, len(eContainer.Command)) - return nil, errors.New(errmsg) - } - iContainer.Command = stringMapToStringArray(eContainer.Command) + return internal, nil +} - // Layers conversion - if eContainer.NumLayers != len(eContainer.Layers) { - errmsg := fmt.Sprintf("layer numbers don't match in policy. expected: %d, actual: %d", eContainer.NumLayers, len(eContainer.Layers)) - return nil, errors.New(errmsg) - } - iContainer.Layers = stringMapToStringArray(eContainer.Layers) +func (c Container) toInternal() (securityPolicyContainer, error) { + command, err := c.Command.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } - // EnvRules conversion - envRulesMapLength := len(eContainer.EnvRules) - if eContainer.NumEnvRules != envRulesMapLength { - errmsg := fmt.Sprintf("env rule numbers don't match in policy. expected: %d, actual: %d", eContainer.NumEnvRules, envRulesMapLength) - return nil, errors.New(errmsg) - } + envRules, err := c.EnvRules.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } - envRules := make([]securityPolicyEnvironmentVariableRule, envRulesMapLength) - for i := 0; i < envRulesMapLength; i++ { - eIndex := strconv.Itoa(i) - rule := securityPolicyEnvironmentVariableRule{ - Strategy: eContainer.EnvRules[eIndex].Strategy, - Rule: eContainer.EnvRules[eIndex].Rule, - } - envRules[i] = rule + layers, err := c.Layers.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } + + return securityPolicyContainer{ + Command: command, + EnvRules: envRules, + Layers: layers, + }, 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), nil +} + +func (e EnvRules) toInternal() ([]securityPolicyEnvironmentVariableRule, 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([]securityPolicyEnvironmentVariableRule, envRulesMapLength) + for i := 0; i < envRulesMapLength; i++ { + eIndex := strconv.Itoa(i) + rule := securityPolicyEnvironmentVariableRule{ + Strategy: e.Elements[eIndex].Strategy, + Rule: e.Elements[eIndex].Rule, } - iContainer.EnvRules = envRules + envRules[i] = rule + } - // save off new container - internal[i] = iContainer + 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 internal, nil + return stringMapToStringArray(l.Elements), nil } func stringMapToStringArray(in map[string]string) []string {