github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/container/run_security_linux.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package container 18 19 import ( 20 "errors" 21 "strings" 22 "sync" 23 24 "github.com/containerd/containerd/contrib/apparmor" 25 "github.com/containerd/containerd/contrib/seccomp" 26 "github.com/containerd/containerd/oci" 27 "github.com/containerd/containerd/pkg/cap" 28 "github.com/containerd/log" 29 "github.com/containerd/nerdctl/v2/pkg/apparmorutil" 30 "github.com/containerd/nerdctl/v2/pkg/defaults" 31 "github.com/containerd/nerdctl/v2/pkg/maputil" 32 "github.com/containerd/nerdctl/v2/pkg/strutil" 33 ) 34 35 var privilegedOpts = []oci.SpecOpts{ 36 oci.WithPrivileged, 37 oci.WithAllDevicesAllowed, 38 oci.WithHostDevices, 39 oci.WithNewPrivileges, 40 } 41 42 var privilegedWithoutDevicesOpts = []oci.SpecOpts{ 43 oci.WithPrivileged, 44 oci.WithNewPrivileges, 45 } 46 47 func generateSecurityOpts(privileged bool, securityOptsMap map[string]string) ([]oci.SpecOpts, error) { 48 for k := range securityOptsMap { 49 switch k { 50 case "seccomp", "apparmor", "no-new-privileges", "privileged-without-host-devices": 51 default: 52 log.L.Warnf("unknown security-opt: %q", k) 53 } 54 } 55 var opts []oci.SpecOpts 56 if seccompProfile, ok := securityOptsMap["seccomp"]; ok && seccompProfile != defaults.SeccompProfileName { 57 if seccompProfile == "" { 58 return nil, errors.New("invalid security-opt \"seccomp\"") 59 } 60 61 if seccompProfile != "unconfined" { 62 opts = append(opts, seccomp.WithProfile(seccompProfile)) 63 } 64 } else { 65 opts = append(opts, seccomp.WithDefaultProfile()) 66 } 67 68 canLoadNewAppArmor := apparmorutil.CanLoadNewProfile() 69 canApplyExistingProfile := apparmorutil.CanApplyExistingProfile() 70 if aaProfile, ok := securityOptsMap["apparmor"]; ok { 71 if aaProfile == "" { 72 return nil, errors.New("invalid security-opt \"apparmor\"") 73 } 74 if aaProfile != "unconfined" { 75 if !canApplyExistingProfile { 76 log.L.Warnf("the host does not support AppArmor. Ignoring profile %q", aaProfile) 77 } else { 78 opts = append(opts, apparmor.WithProfile(aaProfile)) 79 } 80 } 81 } else { 82 if canLoadNewAppArmor { 83 if err := apparmor.LoadDefaultProfile(defaults.AppArmorProfileName); err != nil { 84 return nil, err 85 } 86 } 87 if apparmorutil.CanApplySpecificExistingProfile(defaults.AppArmorProfileName) { 88 opts = append(opts, apparmor.WithProfile(defaults.AppArmorProfileName)) 89 } 90 } 91 92 nnp, err := maputil.MapBoolValueAsOpt(securityOptsMap, "no-new-privileges") 93 if err != nil { 94 return nil, err 95 } 96 97 if !nnp { 98 opts = append(opts, oci.WithNewPrivileges) 99 } 100 101 privilegedWithoutHostDevices, err := maputil.MapBoolValueAsOpt(securityOptsMap, "privileged-without-host-devices") 102 if err != nil { 103 return nil, err 104 } 105 106 if privilegedWithoutHostDevices && !privileged { 107 return nil, errors.New("flag `--security-opt privileged-without-host-devices` can't be used without `--privileged` enabled") 108 } 109 110 if privileged { 111 if privilegedWithoutHostDevices { 112 opts = append(opts, privilegedWithoutDevicesOpts...) 113 } else { 114 opts = append(opts, privilegedOpts...) 115 } 116 } 117 118 return opts, nil 119 } 120 121 func canonicalizeCapName(s string) string { 122 if s == "" { 123 return "" 124 } 125 s = strings.ToUpper(s) 126 if !strings.HasPrefix(s, "CAP_") { 127 s = "CAP_" + s 128 } 129 if !isKnownCapName(s) { 130 log.L.Warnf("unknown capability name %q", s) 131 // Not a fatal error, because runtime might be aware of this cap 132 } 133 return s 134 } 135 136 var ( 137 knownCapNames map[string]struct{} 138 knownCapNamesOnce sync.Once 139 ) 140 141 func isKnownCapName(s string) bool { 142 knownCapNamesOnce.Do(func() { 143 known := cap.Known() 144 knownCapNames = make(map[string]struct{}, len(known)) 145 for _, f := range known { 146 knownCapNames[f] = struct{}{} 147 } 148 }) 149 _, ok := knownCapNames[s] 150 return ok 151 } 152 153 func generateCapOpts(capAdd, capDrop []string) ([]oci.SpecOpts, error) { 154 if len(capAdd) == 0 && len(capDrop) == 0 { 155 return nil, nil 156 } 157 158 var opts []oci.SpecOpts 159 if strutil.InStringSlice(capDrop, "ALL") { 160 opts = append(opts, oci.WithCapabilities(nil)) 161 } 162 163 if strutil.InStringSlice(capAdd, "ALL") { 164 opts = append(opts, oci.WithAllCurrentCapabilities) 165 } else { 166 var capsAdd []string 167 for _, c := range capAdd { 168 capsAdd = append(capsAdd, canonicalizeCapName(c)) 169 } 170 opts = append(opts, oci.WithAddedCapabilities(capsAdd)) 171 } 172 173 if !strutil.InStringSlice(capDrop, "ALL") { 174 var capsDrop []string 175 for _, c := range capDrop { 176 capsDrop = append(capsDrop, canonicalizeCapName(c)) 177 } 178 opts = append(opts, oci.WithDroppedCapabilities(capsDrop)) 179 } 180 return opts, nil 181 }