...

Source file src/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers/helpers.go

Documentation: github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers

     1  package helpers
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/google/go-containerregistry/pkg/authn"
    12  	"github.com/google/go-containerregistry/pkg/name"
    13  	v1 "github.com/google/go-containerregistry/pkg/v1"
    14  	"github.com/google/go-containerregistry/pkg/v1/remote"
    15  	"github.com/opencontainers/runtime-spec/specs-go"
    16  
    17  	"github.com/Microsoft/hcsshim/ext4/tar2ext4"
    18  	sp "github.com/Microsoft/hcsshim/pkg/securitypolicy"
    19  )
    20  
    21  // RemoteImageFromImageName parses a given imageName reference and creates a v1.Image with
    22  // provided remote.Option opts.
    23  func RemoteImageFromImageName(imageName string, opts ...remote.Option) (v1.Image, error) {
    24  	ref, err := name.ParseReference(imageName)
    25  	if err != nil {
    26  		return nil, err
    27  	}
    28  
    29  	return remote.Image(ref, opts...)
    30  }
    31  
    32  // ComputeLayerHashes computes cryptographic digests of image layers and returns
    33  // them as slice of string hashes.
    34  func ComputeLayerHashes(img v1.Image) ([]string, error) {
    35  	imgLayers, err := img.Layers()
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	var layerHashes []string
    41  
    42  	for _, layer := range imgLayers {
    43  		r, err := layer.Uncompressed()
    44  		if err != nil {
    45  			return nil, err
    46  		}
    47  
    48  		hashString, err := tar2ext4.ConvertAndComputeRootDigest(r)
    49  		if err != nil {
    50  			return nil, err
    51  		}
    52  		layerHashes = append(layerHashes, hashString)
    53  	}
    54  	return layerHashes, nil
    55  }
    56  
    57  // ParseEnvFromImage inspects the image spec and adds security policy rules for
    58  // environment variables from the spec. Additionally, includes "TERM=xterm"
    59  // rule, which is added for linux containers by CRI.
    60  func ParseEnvFromImage(img v1.Image) ([]string, error) {
    61  	imgConfig, err := img.ConfigFile()
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	return imgConfig.Config.Env, nil
    67  }
    68  
    69  // DefaultContainerConfigs returns a hardcoded slice of container configs, which should
    70  // be included by default in the security policy.
    71  // The slice includes only a sandbox pause container.
    72  func DefaultContainerConfigs() []sp.ContainerConfig {
    73  	pause := sp.ContainerConfig{
    74  		ImageName: "k8s.gcr.io/pause:3.1",
    75  		Command:   []string{"/pause"},
    76  	}
    77  	return []sp.ContainerConfig{pause}
    78  }
    79  
    80  // ParseWorkingDirFromImage inspects the image spec and returns working directory if
    81  // one was set via CWD Docker directive, otherwise returns "/".
    82  func ParseWorkingDirFromImage(img v1.Image) (string, error) {
    83  	imgConfig, err := img.ConfigFile()
    84  	if err != nil {
    85  		return "", err
    86  	}
    87  
    88  	if imgConfig.Config.WorkingDir != "" {
    89  		return imgConfig.Config.WorkingDir, nil
    90  	}
    91  	return "/", nil
    92  }
    93  
    94  // ParseCommandFromImage inspects the image and returns the command args, which
    95  // is a combination of ENTRYPOINT and CMD Docker directives.
    96  func ParseCommandFromImage(img v1.Image) ([]string, error) {
    97  	imgConfig, err := img.ConfigFile()
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	cmdArgs := imgConfig.Config.Entrypoint
   103  	cmdArgs = append(cmdArgs, imgConfig.Config.Cmd...)
   104  	return cmdArgs, nil
   105  }
   106  
   107  // ParseUserFromImage inspects the image and returns the user and group
   108  func ParseUserFromImage(img v1.Image) (sp.IDNameConfig, sp.IDNameConfig, error) {
   109  	imgConfig, err := img.ConfigFile()
   110  	var user sp.IDNameConfig
   111  	var group sp.IDNameConfig
   112  	if err != nil {
   113  		return user, group, err
   114  	}
   115  
   116  	userString := imgConfig.Config.User
   117  	// valid values are "user", "user:group", "uid", "uid:gid", "user:gid", "uid:group"
   118  	// "" is also valid, and means any user
   119  	// we assume that any string that is not a number is a username, and thus
   120  	// that any string that is a number is a uid. It is possible to have a username
   121  	// that is a number, but that is not supported here.
   122  	if userString == "" {
   123  		// not specified, use any
   124  		user.Strategy = sp.IDNameStrategyAny
   125  		group.Strategy = sp.IDNameStrategyAny
   126  	} else {
   127  		parts := strings.Split(userString, ":")
   128  		if len(parts) == 1 {
   129  			// only user specified, use any
   130  			group.Strategy = sp.IDNameStrategyAny
   131  			user.Rule = parts[0]
   132  			_, err := strconv.ParseUint(parts[0], 10, 32)
   133  			if err == nil {
   134  				user.Strategy = sp.IDNameStrategyID
   135  			} else {
   136  				user.Strategy = sp.IDNameStrategyName
   137  			}
   138  		} else if len(parts) == 2 {
   139  			_, err := strconv.ParseUint(parts[0], 10, 32)
   140  			user.Rule = parts[0]
   141  			if err == nil {
   142  				user.Strategy = sp.IDNameStrategyID
   143  			} else {
   144  				user.Strategy = sp.IDNameStrategyName
   145  			}
   146  
   147  			_, err = strconv.ParseUint(parts[1], 10, 32)
   148  			group.Rule = parts[1]
   149  			if err == nil {
   150  				group.Strategy = sp.IDNameStrategyID
   151  			} else {
   152  				group.Strategy = sp.IDNameStrategyName
   153  			}
   154  		}
   155  	}
   156  
   157  	return user, group, nil
   158  }
   159  
   160  // PolicyContainersFromConfigs returns a slice of sp.Container generated
   161  // from a slice of sp.ContainerConfig's
   162  func PolicyContainersFromConfigs(containerConfigs []sp.ContainerConfig) ([]*sp.Container, error) {
   163  	var policyContainers []*sp.Container
   164  	for _, containerConfig := range containerConfigs {
   165  		var imageOptions []remote.Option
   166  
   167  		if containerConfig.Auth.Username != "" && containerConfig.Auth.Password != "" {
   168  			auth := authn.Basic{
   169  				Username: containerConfig.Auth.Username,
   170  				Password: containerConfig.Auth.Password}
   171  			c, _ := auth.Authorization()
   172  			authOption := remote.WithAuth(authn.FromConfig(*c))
   173  			imageOptions = append(imageOptions, authOption)
   174  		}
   175  
   176  		img, err := RemoteImageFromImageName(containerConfig.ImageName, imageOptions...)
   177  		if err != nil {
   178  			return nil, fmt.Errorf("unable to fetch image: %w", err)
   179  		}
   180  
   181  		layerHashes, err := ComputeLayerHashes(img)
   182  		if err != nil {
   183  			return nil, err
   184  		}
   185  
   186  		commandArgs := containerConfig.Command
   187  		if len(commandArgs) == 0 {
   188  			commandArgs, err = ParseCommandFromImage(img)
   189  			if err != nil {
   190  				return nil, err
   191  			}
   192  		}
   193  		// add rules for all known environment variables from the configuration
   194  		// these are in addition to "other rules" from the policy definition file
   195  		envVars, err := ParseEnvFromImage(img)
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  
   200  		// we want all environment variables which we've extracted from the
   201  		// image to be required
   202  		envRules := sp.NewEnvVarRules(envVars, true)
   203  
   204  		// cri adds TERM=xterm for all workload containers. we add to all containers
   205  		// to prevent any possible error
   206  		envRules = append(envRules, sp.EnvRuleConfig{
   207  			Rule:     "TERM=xterm",
   208  			Strategy: sp.EnvVarRuleString,
   209  			Required: false,
   210  		})
   211  
   212  		envRules = append(envRules, containerConfig.EnvRules...)
   213  
   214  		workingDir, err := ParseWorkingDirFromImage(img)
   215  		if err != nil {
   216  			return nil, err
   217  		}
   218  
   219  		if containerConfig.WorkingDir != "" {
   220  			workingDir = containerConfig.WorkingDir
   221  		}
   222  
   223  		user, group, err := ParseUserFromImage(img)
   224  		if err != nil {
   225  			return nil, err
   226  		}
   227  
   228  		container, err := sp.CreateContainerPolicy(
   229  			commandArgs,
   230  			layerHashes,
   231  			envRules,
   232  			workingDir,
   233  			containerConfig.Mounts,
   234  			containerConfig.AllowElevated,
   235  			containerConfig.ExecProcesses,
   236  			containerConfig.Signals,
   237  			containerConfig.AllowStdioAccess,
   238  			!containerConfig.AllowPrivilegeEscalation,
   239  			setDefaultUser(containerConfig.User, user, group),
   240  			setDefaultCapabilities(containerConfig.Capabilities),
   241  			setDefaultSeccomp(containerConfig.SeccompProfilePath),
   242  		)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  		policyContainers = append(policyContainers, container)
   247  	}
   248  
   249  	return policyContainers, nil
   250  }
   251  
   252  func setDefaultUser(config *sp.UserConfig, user, group sp.IDNameConfig) sp.UserConfig {
   253  	if config != nil {
   254  		return *config
   255  	}
   256  
   257  	// 0022 is the default umask for containers in docker
   258  	return sp.UserConfig{
   259  		UserIDName:   user,
   260  		GroupIDNames: []sp.IDNameConfig{group},
   261  		Umask:        "0022",
   262  	}
   263  }
   264  
   265  func setDefaultCapabilities(config *sp.CapabilitiesConfig) *sp.CapabilitiesConfig {
   266  	if config != nil {
   267  		// For any that is missing, we put an empty set to give the user the
   268  		// quickest path when they get a runtime error to figuring out the issue.
   269  		// Our only other reasonable option would be to bail here with an error
   270  		// message.
   271  		if config.Bounding == nil {
   272  			config.Bounding = make([]string, 0)
   273  		}
   274  		if config.Effective == nil {
   275  			config.Effective = make([]string, 0)
   276  		}
   277  		if config.Inheritable == nil {
   278  			config.Inheritable = make([]string, 0)
   279  		}
   280  		if config.Permitted == nil {
   281  			config.Permitted = make([]string, 0)
   282  		}
   283  		if config.Ambient == nil {
   284  			config.Ambient = make([]string, 0)
   285  		}
   286  	}
   287  
   288  	return config
   289  }
   290  
   291  func setDefaultSeccomp(seccompProfilePath string) string {
   292  	if len(seccompProfilePath) == 0 {
   293  		return ""
   294  	}
   295  
   296  	var seccomp specs.LinuxSeccomp
   297  
   298  	buff, err := os.ReadFile(seccompProfilePath)
   299  	if err != nil {
   300  		log.Fatalf("unable to read seccomp profile: %v", err)
   301  	}
   302  
   303  	err = json.Unmarshal(buff, &seccomp)
   304  	if err != nil {
   305  		log.Fatalf("unable to parse seccomp profile: %v", err)
   306  	}
   307  
   308  	profileSHA256, err := sp.MeasureSeccompProfile(&seccomp)
   309  	if err != nil {
   310  		log.Fatalf("unable to measure seccomp profile: %v", err)
   311  	}
   312  
   313  	return profileSHA256
   314  }
   315  

View as plain text