Skip to content

Commit

Permalink
Add helper functions for generating security policy and setup CRI tes…
Browse files Browse the repository at this point in the history
…ts (microsoft#1309)

Split dev tool logic to create security policy into several helper
functions, which can be reused in other places, e.g., integration tests.
Create a small helpers package under internal/tools/securitypolicy,
which hosts the above functions. Another option would be to put these
functions into securitypolicy package, however the dev-tool does
network requests, which didn't look like a good dependency to add for
the securitypolicy package itself, since creating a policy by itself
doesn't require any network access, given that caller knows all the
necessary information, mainly root hashes.

Add simple integration tests for running a pod with container and
security policy passed via annotations.

Signed-off-by: Maksim An <maksiman@microsoft.com>
  • Loading branch information
anmaxvl authored Mar 10, 2022
1 parent d605133 commit d5841d3
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 91 deletions.
147 changes: 147 additions & 0 deletions tools/securitypolicy/helpers/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package helpers

import (
"fmt"

"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/pkg/securitypolicy"
)

// RemoteImageFromImageName parses a given imageName reference and creates a v1.Image with
// provided remote.Option opts.
func RemoteImageFromImageName(imageName string, opts ...remote.Option) (v1.Image, error) {
ref, err := name.ParseReference(imageName)
if err != nil {
return nil, err
}

return remote.Image(ref, opts...)
}

// ComputeLayerHashes computes cryptographic digests of image layers and returns
// them as slice of string hashes.
func ComputeLayerHashes(img v1.Image) ([]string, error) {
imgLayers, err := img.Layers()
if err != nil {
return nil, err
}

var layerHashes []string

for _, layer := range imgLayers {
r, err := layer.Uncompressed()
if err != nil {
return nil, err
}

hashString, err := tar2ext4.ConvertAndComputeRootDigest(r)
if err != nil {
return nil, err
}
layerHashes = append(layerHashes, hashString)
}
return layerHashes, nil
}

// ParseEnvFromImage inspects the image spec and adds security policy rules for
// environment variables from the spec. Additionally, includes "TERM=xterm"
// rule, which is added for linux containers by CRI.
func ParseEnvFromImage(img v1.Image) ([]string, error) {
imgConfig, err := img.ConfigFile()
if err != nil {
return nil, err
}

// cri adds TERM=xterm for all workload containers. we add to all containers
// to prevent any possible error
envVars := append(imgConfig.Config.Env, "TERM=xterm")

return envVars, nil
}

// 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(
"k8s.gcr.io/pause:3.1",
[]string{"/pause"},
[]securitypolicy.EnvRuleConfig{},
securitypolicy.AuthConfig{},
"",
)
return []securitypolicy.ContainerConfig{pause}
}

// ParseWorkingDirFromImage inspects the image spec and returns working directory if
// one was set via CWD Docker directive, otherwise returns "/".
func ParseWorkingDirFromImage(img v1.Image) (string, error) {
imgConfig, err := img.ConfigFile()
if err != nil {
return "", err
}

if imgConfig.Config.WorkingDir != "" {
return imgConfig.Config.WorkingDir, nil
}
return "/", nil
}

// 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
for _, containerConfig := range containerConfigs {
var imageOptions []remote.Option

if containerConfig.Auth.Username != "" && containerConfig.Auth.Password != "" {
auth := authn.Basic{
Username: containerConfig.Auth.Username,
Password: containerConfig.Auth.Password}
c, _ := auth.Authorization()
authOption := remote.WithAuth(authn.FromConfig(*c))
imageOptions = append(imageOptions, authOption)
}

img, err := RemoteImageFromImageName(containerConfig.ImageName, imageOptions...)
if err != nil {
return nil, fmt.Errorf("unable to fetch image: %w", err)
}

layerHashes, err := ComputeLayerHashes(img)
if err != nil {
return nil, err
}

// add rules for all known environment variables from the configuration
// these are in addition to "other rules" from the policy definition file
envVars, err := ParseEnvFromImage(img)
if err != nil {
return nil, err
}
envRules := securitypolicy.NewEnvVarRules(envVars)
envRules = append(envRules, containerConfig.EnvRules...)

workingDir, err := ParseWorkingDirFromImage(img)
if err != nil {
return nil, err
}

if containerConfig.WorkingDir != "" {
workingDir = containerConfig.WorkingDir
}

container, err := securitypolicy.NewContainer(containerConfig.Command, layerHashes, envRules, workingDir)
if err != nil {
return nil, err
}
policyContainers = append(policyContainers, container)
}

return policyContainers, nil
}
100 changes: 9 additions & 91 deletions tools/securitypolicy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import (
"os"

"github.com/BurntSushi/toml"
"github.com/Microsoft/hcsshim/ext4/tar2ext4"

"github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers"
"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"
)

var (
Expand Down Expand Up @@ -73,94 +71,14 @@ func main() {
}

func createPolicyFromConfig(config *securitypolicy.PolicyConfig) (*securitypolicy.SecurityPolicy, error) {
// Hardcode the pause container version and command. We still pull it
// to get the root hash and any environment variable rules we might need.
pause := securitypolicy.NewContainerConfig(
"k8s.gcr.io/pause:3.1",
[]string{"/pause"},
[]securitypolicy.EnvRule{},
securitypolicy.AuthConfig{},
"",
)
config.Containers = append(config.Containers, pause)

var policyContainers []*securitypolicy.Container
for _, containerConfig := range config.Containers {
var imageOptions []remote.Option

if containerConfig.Auth.Username != "" && containerConfig.Auth.Password != "" {
auth := authn.Basic{
Username: containerConfig.Auth.Username,
Password: containerConfig.Auth.Password}
c, _ := auth.Authorization()
authOption := remote.WithAuth(authn.FromConfig(*c))
imageOptions = append(imageOptions, authOption)
}

ref, err := name.ParseReference(containerConfig.ImageName)
if err != nil {
return nil, fmt.Errorf("'%s' isn't a valid image name", containerConfig.ImageName)
}
img, err := remote.Image(ref, imageOptions...)
if err != nil {
return nil, fmt.Errorf("unable to fetch image '%s': %s", containerConfig.ImageName, err.Error())
}

layers, err := img.Layers()
if err != nil {
return nil, err
}

var layerHashes []string
for _, layer := range layers {
r, err := layer.Uncompressed()
if err != nil {
return nil, err
}

hashString, err := tar2ext4.ConvertAndComputeRootDigest(r)
if err != nil {
return nil, err
}
layerHashes = append(layerHashes, hashString)
}

// add rules for all known environment variables from the configuration
// these are in addition to "other rules" from the policy definition file
imgConfig, err := img.ConfigFile()
if err != nil {
return nil, err
}
// 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...)

envRules := containerConfig.EnvRules
for _, env := range imgConfig.Config.Env {
rule := securitypolicy.EnvRule{
Strategy: securitypolicy.EnvVarRuleString,
Rule: env,
}
envRules = append(envRules, rule)
}
// cri adds TERM=xterm for all workload containers. we add to all containers
// to prevent any possible error
rule := securitypolicy.EnvRule{
Strategy: securitypolicy.EnvVarRuleString,
Rule: "TERM=xterm",
}
envRules = append(envRules, rule)

workingDir := "/"
if imgConfig.Config.WorkingDir != "" {
workingDir = imgConfig.Config.WorkingDir
}
if containerConfig.WorkingDir != "" {
workingDir = containerConfig.WorkingDir
}
container, err := securitypolicy.NewContainer(containerConfig.Command, layerHashes, envRules, workingDir)
if err != nil {
return nil, err
}
policyContainers = append(policyContainers, container)
policyContainers, err := helpers.PolicyContainersFromConfigs(config.Containers)
if err != nil {
return nil, err
}

return securitypolicy.NewSecurityPolicy(false, policyContainers), nil
}

0 comments on commit d5841d3

Please sign in to comment.