Files
podman/pkg/specgen/generate/container_create.go
cdoern 289270375a Pod Security Option support
Added support for pod security options. These are applied to infra and passed down to the
containers as added (unless overridden).

Modified the inheritance process from infra, creating a new function Inherit() which reads the config, and marshals the compatible options into an intermediate struct `InfraInherit`
This is then unmarshaled into a container config and all of this is added to the CtrCreateOptions. Removes the need (mostly) for special additons which complicate the Container_create
code and pod creation.

resolves #12173

Signed-off-by: cdoern <cdoern@redhat.com>
2021-12-27 13:39:36 -05:00

520 lines
16 KiB
Go

package generate
import (
"context"
"encoding/json"
"path/filepath"
"strings"
cdi "github.com/container-orchestrated-devices/container-device-interface/pkg"
"github.com/containers/common/libimage"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/namespaces"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// MakeContainer creates a container based on the SpecGenerator.
// Returns the created, container and any warnings resulting from creating the
// container, or an error.
func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator) (*spec.Spec, *specgen.SpecGenerator, []libpod.CtrCreateOption, error) {
rtc, err := rt.GetConfigNoCopy()
if err != nil {
return nil, nil, nil, err
}
// If joining a pod, retrieve the pod for use, and its infra container
var pod *libpod.Pod
var infra *libpod.Container
if s.Pod != "" {
pod, err = rt.LookupPod(s.Pod)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod)
}
if pod.HasInfraContainer() {
infra, err = pod.InfraContainer()
if err != nil {
return nil, nil, nil, err
}
}
}
options := []libpod.CtrCreateOption{}
compatibleOptions := &libpod.InfraInherit{}
var infraSpec *spec.Spec
if infra != nil {
options, infraSpec, compatibleOptions, err = Inherit(*infra)
if err != nil {
return nil, nil, nil, err
}
}
if err := FinishThrottleDevices(s); err != nil {
return nil, nil, nil, err
}
// Set defaults for unset namespaces
if s.PidNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("pid", rtc, pod)
if err != nil {
return nil, nil, nil, err
}
s.PidNS = defaultNS
}
if s.IpcNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("ipc", rtc, pod)
if err != nil {
return nil, nil, nil, err
}
s.IpcNS = defaultNS
}
if s.UtsNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("uts", rtc, pod)
if err != nil {
return nil, nil, nil, err
}
s.UtsNS = defaultNS
}
if s.UserNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("user", rtc, pod)
if err != nil {
return nil, nil, nil, err
}
s.UserNS = defaultNS
mappings, err := util.ParseIDMapping(namespaces.UsernsMode(s.UserNS.NSMode), nil, nil, "", "")
if err != nil {
return nil, nil, nil, err
}
s.IDMappings = mappings
}
if s.NetNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("net", rtc, pod)
if err != nil {
return nil, nil, nil, err
}
s.NetNS = defaultNS
}
if s.CgroupNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("cgroup", rtc, pod)
if err != nil {
return nil, nil, nil, err
}
s.CgroupNS = defaultNS
}
if s.ContainerCreateCommand != nil {
options = append(options, libpod.WithCreateCommand(s.ContainerCreateCommand))
}
if s.Rootfs != "" {
options = append(options, libpod.WithRootFS(s.Rootfs, s.RootfsOverlay))
}
newImage, resolvedImageName, imageData, err := getImageFromSpec(ctx, rt, s)
if err != nil {
return nil, nil, nil, err
}
if newImage != nil {
// If the input name changed, we could properly resolve the
// image. Otherwise, it must have been an ID where we're
// defaulting to the first name or an empty one if no names are
// present.
if strings.HasPrefix(newImage.ID(), resolvedImageName) {
names := newImage.Names()
if len(names) > 0 {
resolvedImageName = names[0]
}
}
options = append(options, libpod.WithRootFSFromImage(newImage.ID(), resolvedImageName, s.RawImageName))
}
if err := s.Validate(); err != nil {
return nil, nil, nil, errors.Wrap(err, "invalid config provided")
}
finalMounts, finalVolumes, finalOverlays, err := finalizeMounts(ctx, s, rt, rtc, newImage)
if err != nil {
return nil, nil, nil, err
}
if len(s.HostUsers) > 0 {
options = append(options, libpod.WithHostUsers(s.HostUsers))
}
command, err := makeCommand(ctx, s, imageData, rtc)
if err != nil {
return nil, nil, nil, err
}
infraVolumes := (len(compatibleOptions.InfraVolumes) > 0 || len(compatibleOptions.InfraUserVolumes) > 0 || len(compatibleOptions.InfraImageVolumes) > 0)
opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command, infraVolumes, *compatibleOptions)
if err != nil {
return nil, nil, nil, err
}
options = append(options, opts...)
if containerType := s.InitContainerType; len(containerType) > 0 {
options = append(options, libpod.WithInitCtrType(containerType))
}
if len(s.Name) > 0 {
logrus.Debugf("setting container name %s", s.Name)
options = append(options, libpod.WithName(s.Name))
}
if len(s.Devices) > 0 {
opts = ExtractCDIDevices(s)
options = append(options, opts...)
}
runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command, compatibleOptions)
if err != nil {
return nil, nil, nil, err
}
if len(s.HostDeviceList) > 0 {
options = append(options, libpod.WithHostDevice(s.HostDeviceList))
}
if infraSpec != nil && infraSpec.Linux != nil { // if we are inheriting Linux info from a pod...
// Pass Security annotations
if len(infraSpec.Annotations[define.InspectAnnotationLabel]) > 0 && len(runtimeSpec.Annotations[define.InspectAnnotationLabel]) == 0 {
runtimeSpec.Annotations[define.InspectAnnotationLabel] = infraSpec.Annotations[define.InspectAnnotationLabel]
}
if len(infraSpec.Annotations[define.InspectAnnotationSeccomp]) > 0 && len(runtimeSpec.Annotations[define.InspectAnnotationSeccomp]) == 0 {
runtimeSpec.Annotations[define.InspectAnnotationSeccomp] = infraSpec.Annotations[define.InspectAnnotationSeccomp]
}
if len(infraSpec.Annotations[define.InspectAnnotationApparmor]) > 0 && len(runtimeSpec.Annotations[define.InspectAnnotationApparmor]) == 0 {
runtimeSpec.Annotations[define.InspectAnnotationApparmor] = infraSpec.Annotations[define.InspectAnnotationApparmor]
}
}
return runtimeSpec, s, options, err
}
func ExecuteCreate(ctx context.Context, rt *libpod.Runtime, runtimeSpec *spec.Spec, s *specgen.SpecGenerator, infra bool, options ...libpod.CtrCreateOption) (*libpod.Container, error) {
ctr, err := rt.NewContainer(ctx, runtimeSpec, s, infra, options...)
if err != nil {
return ctr, err
}
return ctr, rt.PrepareVolumeOnCreateContainer(ctx, ctr)
}
func ExtractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption {
devs := make([]spec.LinuxDevice, 0, len(s.Devices))
var cdiDevs []string
var options []libpod.CtrCreateOption
for _, device := range s.Devices {
isCDIDevice, err := cdi.HasDevice(device.Path)
if err != nil {
logrus.Debugf("CDI HasDevice Error: %v", err)
}
if err == nil && isCDIDevice {
cdiDevs = append(cdiDevs, device.Path)
continue
}
devs = append(devs, device)
}
s.Devices = devs
if len(cdiDevs) > 0 {
options = append(options, libpod.WithCDI(cdiDevs))
}
return options
}
func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string, infraVolumes bool, compatibleOptions libpod.InfraInherit) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
if s.PreserveFDs > 0 {
options = append(options, libpod.WithPreserveFDs(s.PreserveFDs))
}
if s.Stdin {
options = append(options, libpod.WithStdin())
}
if s.Timezone != "" {
options = append(options, libpod.WithTimezone(s.Timezone))
}
if s.Umask != "" {
options = append(options, libpod.WithUmask(s.Umask))
}
if s.Volatile {
options = append(options, libpod.WithVolatile())
}
useSystemd := false
switch s.Systemd {
case "always":
useSystemd = true
case "false":
break
case "", "true":
if len(command) == 0 && imageData != nil {
command = imageData.Config.Cmd
}
if len(command) > 0 {
useSystemdCommands := map[string]bool{
"/sbin/init": true,
"/usr/sbin/init": true,
"/usr/local/sbin/init": true,
}
if useSystemdCommands[command[0]] || (filepath.Base(command[0]) == "systemd") {
useSystemd = true
}
}
default:
return nil, errors.Wrapf(err, "invalid value %q systemd option requires 'true, false, always'", s.Systemd)
}
logrus.Debugf("using systemd mode: %t", useSystemd)
if useSystemd {
// is StopSignal was not set by the user then set it to systemd
// expected StopSigal
if s.StopSignal == nil {
stopSignal, err := util.ParseSignal("RTMIN+3")
if err != nil {
return nil, errors.Wrapf(err, "error parsing systemd signal")
}
s.StopSignal = &stopSignal
}
options = append(options, libpod.WithSystemd())
}
if len(s.SdNotifyMode) > 0 {
options = append(options, libpod.WithSdNotifyMode(s.SdNotifyMode))
}
if pod != nil {
logrus.Debugf("adding container to pod %s", pod.Name())
options = append(options, rt.WithPod(pod))
}
destinations := []string{}
// Take all mount and named volume destinations.
for _, mount := range s.Mounts {
destinations = append(destinations, mount.Destination)
}
for _, volume := range volumes {
destinations = append(destinations, volume.Dest)
}
for _, overlayVolume := range overlays {
destinations = append(destinations, overlayVolume.Destination)
}
for _, imageVolume := range s.ImageVolumes {
destinations = append(destinations, imageVolume.Destination)
}
if len(destinations) > 0 || !infraVolumes {
options = append(options, libpod.WithUserVolumes(destinations))
}
if len(volumes) != 0 {
var vols []*libpod.ContainerNamedVolume
for _, v := range volumes {
vols = append(vols, &libpod.ContainerNamedVolume{
Name: v.Name,
Dest: v.Dest,
Options: v.Options,
})
}
options = append(options, libpod.WithNamedVolumes(vols))
}
if len(overlays) != 0 {
var vols []*libpod.ContainerOverlayVolume
for _, v := range overlays {
vols = append(vols, &libpod.ContainerOverlayVolume{
Dest: v.Destination,
Source: v.Source,
Options: v.Options,
})
}
options = append(options, libpod.WithOverlayVolumes(vols))
}
if len(s.ImageVolumes) != 0 {
var vols []*libpod.ContainerImageVolume
for _, v := range s.ImageVolumes {
vols = append(vols, &libpod.ContainerImageVolume{
Dest: v.Destination,
Source: v.Source,
ReadWrite: v.ReadWrite,
})
}
options = append(options, libpod.WithImageVolumes(vols))
}
if s.Command != nil {
options = append(options, libpod.WithCommand(s.Command))
}
if s.Entrypoint != nil {
options = append(options, libpod.WithEntrypoint(s.Entrypoint))
}
if len(s.ContainerStorageConfig.StorageOpts) > 0 {
options = append(options, libpod.WithStorageOpts(s.StorageOpts))
}
// If the user did not specify a workdir on the CLI, let's extract it
// from the image.
if s.WorkDir == "" && imageData != nil {
options = append(options, libpod.WithCreateWorkingDir())
s.WorkDir = imageData.Config.WorkingDir
}
if s.WorkDir == "" {
s.WorkDir = "/"
}
if s.CreateWorkingDir {
options = append(options, libpod.WithCreateWorkingDir())
}
if s.StopSignal != nil {
options = append(options, libpod.WithStopSignal(*s.StopSignal))
}
if s.StopTimeout != nil {
options = append(options, libpod.WithStopTimeout(*s.StopTimeout))
}
if s.Timeout != 0 {
options = append(options, libpod.WithTimeout(s.Timeout))
}
if s.LogConfiguration != nil {
if len(s.LogConfiguration.Path) > 0 {
options = append(options, libpod.WithLogPath(s.LogConfiguration.Path))
}
if s.LogConfiguration.Size > 0 {
options = append(options, libpod.WithMaxLogSize(s.LogConfiguration.Size))
}
if len(s.LogConfiguration.Options) > 0 && s.LogConfiguration.Options["tag"] != "" {
// Note: I'm really guessing here.
options = append(options, libpod.WithLogTag(s.LogConfiguration.Options["tag"]))
}
if len(s.LogConfiguration.Driver) > 0 {
options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver))
}
}
// Security options
if len(s.SelinuxOpts) > 0 {
options = append(options, libpod.WithSecLabels(s.SelinuxOpts))
} else {
if pod != nil && len(compatibleOptions.InfraLabels) == 0 {
// duplicate the security options from the pod
processLabel, err := pod.ProcessLabel()
if err != nil {
return nil, err
}
if processLabel != "" {
selinuxOpts, err := label.DupSecOpt(processLabel)
if err != nil {
return nil, err
}
options = append(options, libpod.WithSecLabels(selinuxOpts))
}
}
}
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
namespaceOpts, err := namespaceOptions(ctx, s, rt, pod, imageData)
if err != nil {
return nil, err
}
options = append(options, namespaceOpts...)
if len(s.ConmonPidFile) > 0 {
options = append(options, libpod.WithConmonPidFile(s.ConmonPidFile))
}
options = append(options, libpod.WithLabels(s.Labels))
if s.ShmSize != nil {
options = append(options, libpod.WithShmSize(*s.ShmSize))
}
if s.Rootfs != "" {
options = append(options, libpod.WithRootFS(s.Rootfs, s.RootfsOverlay))
}
// Default used if not overridden on command line
if s.RestartPolicy != "" {
if s.RestartRetries != nil {
options = append(options, libpod.WithRestartRetries(*s.RestartRetries))
}
options = append(options, libpod.WithRestartPolicy(s.RestartPolicy))
}
if s.ContainerHealthCheckConfig.HealthConfig != nil {
options = append(options, libpod.WithHealthCheck(s.ContainerHealthCheckConfig.HealthConfig))
logrus.Debugf("New container has a health check")
}
if len(s.Secrets) != 0 {
manager, err := rt.SecretsManager()
if err != nil {
return nil, err
}
var secrs []*libpod.ContainerSecret
for _, s := range s.Secrets {
secr, err := manager.Lookup(s.Source)
if err != nil {
return nil, err
}
secrs = append(secrs, &libpod.ContainerSecret{
Secret: secr,
UID: s.UID,
GID: s.GID,
Mode: s.Mode,
Target: s.Target,
})
}
options = append(options, libpod.WithSecrets(secrs))
}
if len(s.EnvSecrets) != 0 {
options = append(options, libpod.WithEnvSecrets(s.EnvSecrets))
}
if len(s.DependencyContainers) > 0 {
deps := make([]*libpod.Container, 0, len(s.DependencyContainers))
for _, ctr := range s.DependencyContainers {
depCtr, err := rt.LookupContainer(ctr)
if err != nil {
return nil, errors.Wrapf(err, "%q is not a valid container, cannot be used as a dependency", ctr)
}
deps = append(deps, depCtr)
}
options = append(options, libpod.WithDependencyCtrs(deps))
}
if s.PidFile != "" {
options = append(options, libpod.WithPidFile(s.PidFile))
}
options = append(options, libpod.WithSelectedPasswordManagement(s.Passwd))
return options, nil
}
func Inherit(infra libpod.Container) (opts []libpod.CtrCreateOption, infraS *spec.Spec, compat *libpod.InfraInherit, err error) {
options := []libpod.CtrCreateOption{}
compatibleOptions := &libpod.InfraInherit{}
infraConf := infra.Config()
infraSpec := infraConf.Spec
config, err := json.Marshal(infraConf)
if err != nil {
return nil, nil, nil, err
}
err = json.Unmarshal(config, compatibleOptions)
if err != nil {
return nil, nil, nil, err
}
if infraSpec.Linux != nil && infraSpec.Linux.Resources != nil {
resources, err := json.Marshal(infraSpec.Linux.Resources)
if err != nil {
return nil, nil, nil, err
}
err = json.Unmarshal(resources, &compatibleOptions.InfraResources)
if err != nil {
return nil, nil, nil, err
}
}
if compatibleOptions != nil {
options = append(options, libpod.WithInfraConfig(*compatibleOptions))
}
return options, infraSpec, compatibleOptions, nil
}