1 package securitypolicy
2
3 import (
4 "crypto/sha256"
5 _ "embed"
6 "encoding/base64"
7 "encoding/json"
8 "fmt"
9 "regexp"
10 "strconv"
11 "strings"
12 "syscall"
13
14 "github.com/Microsoft/hcsshim/internal/guestpath"
15 "github.com/opencontainers/runtime-spec/specs-go"
16 "github.com/pkg/errors"
17 )
18
19
20 var frameworkCodeTemplate string
21
22
23 var apiCodeTemplate string
24
25 var APICode = strings.Replace(apiCodeTemplate, "@@API_VERSION@@", apiVersion, 1)
26 var FrameworkCode = strings.Replace(frameworkCodeTemplate, "@@FRAMEWORK_VERSION@@", frameworkVersion, 1)
27
28 var ErrInvalidOpenDoorPolicy = errors.New("allow_all cannot be set to 'true' when Containers are non-empty")
29
30 type EnvVarRule string
31
32 const (
33 EnvVarRuleString EnvVarRule = "string"
34 EnvVarRuleRegex EnvVarRule = "re2"
35 )
36
37 type IDNameStrategy string
38
39 const (
40 IDNameStrategyName IDNameStrategy = "name"
41 IDNameStrategyID IDNameStrategy = "id"
42 IDNameStrategyRegex IDNameStrategy = "re2"
43 IDNameStrategyAny IDNameStrategy = "any"
44 )
45
46 const plan9Prefix = "plan9://"
47
48 const (
49 SecurityContextDirTemplate = "security-context-*"
50 PolicyFilename = "security-policy-base64"
51 HostAMDCertFilename = "host-amd-cert-base64"
52 ReferenceInfoFilename = "reference-info-base64"
53 )
54
55
56 type PolicyConfig struct {
57 AllowAll bool `json:"allow_all" toml:"allow_all"`
58 Containers []ContainerConfig `json:"containers" toml:"container"`
59 ExternalProcesses []ExternalProcessConfig `json:"external_processes" toml:"external_process"`
60 Fragments []FragmentConfig `json:"fragments" toml:"fragment"`
61 AllowPropertiesAccess bool `json:"allow_properties_access" toml:"allow_properties_access"`
62 AllowDumpStacks bool `json:"allow_dump_stacks" toml:"allow_dump_stacks"`
63 AllowRuntimeLogging bool `json:"allow_runtime_logging" toml:"allow_runtime_logging"`
64 AllowEnvironmentVariableDropping bool `json:"allow_environment_variable_dropping" toml:"allow_environment_variable_dropping"`
65
66
67 AllowUnencryptedScratch bool `json:"allow_unencrypted_scratch" toml:"allow_unencrypted_scratch"`
68 AllowCapabilityDropping bool `json:"allow_capability_dropping" toml:"allow_capability_dropping"`
69 }
70
71 func NewPolicyConfig(opts ...PolicyConfigOpt) (*PolicyConfig, error) {
72 p := &PolicyConfig{}
73 for _, o := range opts {
74 if err := o(p); err != nil {
75 return nil, err
76 }
77 }
78 return p, nil
79 }
80
81
82 type ExternalProcessConfig struct {
83 Command []string `json:"command" toml:"command"`
84 WorkingDir string `json:"working_dir" toml:"working_dir"`
85 AllowStdioAccess bool `json:"allow_stdio_access" toml:"allow_stdio_access"`
86 }
87
88
89 type FragmentConfig struct {
90 Issuer string `json:"issuer" toml:"issuer"`
91 Feed string `json:"feed" toml:"feed"`
92 MinimumSVN string `json:"minimum_svn" toml:"minimum_svn"`
93 Includes []string `json:"includes" toml:"include"`
94 }
95
96
97 type AuthConfig struct {
98 Username string `json:"username" toml:"username"`
99 Password string `json:"password" toml:"password"`
100 }
101
102
103
104 type EnvRuleConfig struct {
105 Strategy EnvVarRule `json:"strategy" toml:"strategy"`
106 Rule string `json:"rule" toml:"rule"`
107 Required bool `json:"required" toml:"required"`
108 }
109
110 type IDNameConfig struct {
111 Strategy IDNameStrategy `json:"strategy" toml:"strategy"`
112 Rule string `json:"rule" toml:"rule"`
113 }
114
115 type UserConfig struct {
116 UserIDName IDNameConfig `json:"user_idname" toml:"user_idname"`
117 GroupIDNames []IDNameConfig `json:"group_idnames" toml:"group_idname"`
118 Umask string `json:"umask" toml:"umask"`
119 }
120
121 type IDName struct {
122 ID string
123 Name string
124 }
125
126 func MeasureSeccompProfile(seccomp *specs.LinuxSeccomp) (string, error) {
127 if seccomp == nil {
128 return "", nil
129 }
130
131 buf, err := json.Marshal(seccomp)
132 if err != nil {
133 return "", err
134 }
135
136 profileSHA256 := sha256.Sum256(buf)
137 return fmt.Sprintf("%x", profileSHA256), nil
138 }
139
140 const policyDecisionPattern = `policyDecision< %s >policyDecision`
141
142 func ExtractPolicyDecision(errorMessage string) (string, error) {
143 re := regexp.MustCompile(fmt.Sprintf(policyDecisionPattern, `(.*)`))
144 matches := re.FindStringSubmatch(errorMessage)
145 if len(matches) != 2 {
146 return "", errors.Errorf("unable to extract policy decision from error message: %s", errorMessage)
147 }
148
149 errorBytes, err := base64.StdEncoding.DecodeString(matches[1])
150 if err != nil {
151 return "", err
152 }
153
154 return string(errorBytes), nil
155 }
156
157
158
159 type ContainerConfig struct {
160 ImageName string `json:"image_name" toml:"image_name"`
161 Command []string `json:"command" toml:"command"`
162 Auth AuthConfig `json:"auth" toml:"auth"`
163 EnvRules []EnvRuleConfig `json:"env_rules" toml:"env_rule"`
164 WorkingDir string `json:"working_dir" toml:"working_dir"`
165 Mounts []MountConfig `json:"mounts" toml:"mount"`
166 AllowElevated bool `json:"allow_elevated" toml:"allow_elevated"`
167 ExecProcesses []ExecProcessConfig `json:"exec_processes" toml:"exec_process"`
168 Signals []syscall.Signal `json:"signals" toml:"signals"`
169 AllowStdioAccess bool `json:"allow_stdio_access" toml:"allow_stdio_access"`
170 AllowPrivilegeEscalation bool `json:"allow_privilege_escalation" toml:"allow_privilege_escalation"`
171 User *UserConfig `json:"user" toml:"user"`
172 Capabilities *CapabilitiesConfig `json:"capabilities" toml:"capabilities"`
173 SeccompProfilePath string `json:"seccomp_profile_path" toml:"seccomp_profile_path"`
174 }
175
176
177
178 type MountConfig struct {
179 HostPath string `json:"host_path" toml:"host_path"`
180 ContainerPath string `json:"container_path" toml:"container_path"`
181 Readonly bool `json:"readonly" toml:"readonly"`
182 }
183
184
185
186 type ExecProcessConfig struct {
187 Command []string `json:"command" toml:"command"`
188 Signals []syscall.Signal `json:"signals" toml:"signals"`
189 }
190
191
192
193 type CapabilitiesConfig struct {
194 Bounding []string `json:"bounding" toml:"bounding"`
195 Effective []string `json:"effective" toml:"effective"`
196 Inheritable []string `json:"inheritable" toml:"inheritable"`
197 Permitted []string `json:"permitted" toml:"permitted"`
198 Ambient []string `json:"ambient" toml:"ambient"`
199 }
200
201
202 var apiVersion string
203
204
205 var frameworkVersion string
206
207
208
209 func NewEnvVarRules(envVars []string, required bool) []EnvRuleConfig {
210 var rules []EnvRuleConfig
211 for _, env := range envVars {
212 r := EnvRuleConfig{
213 Strategy: EnvVarRuleString,
214 Rule: env,
215 Required: required,
216 }
217 rules = append(rules, r)
218 }
219 return rules
220 }
221
222
223 func NewOpenDoorPolicy() *SecurityPolicy {
224 return &SecurityPolicy{
225 AllowAll: true,
226 }
227 }
228
229
230
231 func NewSecurityPolicyDigest(base64policy string) ([]byte, error) {
232 jsonPolicy, err := base64.StdEncoding.DecodeString(base64policy)
233 if err != nil {
234 return nil, fmt.Errorf("failed to decode base64 security policy: %w", err)
235 }
236 digest := sha256.New()
237 digest.Write(jsonPolicy)
238 digestBytes := digest.Sum(nil)
239 return digestBytes, nil
240 }
241
242
243
244
245 type EncodedSecurityPolicy struct {
246 SecurityPolicy string `json:"SecurityPolicy,omitempty"`
247 }
248
249 type SecurityPolicy struct {
250
251
252
253
254
255
256 AllowAll bool `json:"allow_all"`
257
258 Containers Containers `json:"containers"`
259 }
260
261
262 func (sp *SecurityPolicy) EncodeToString() (string, error) {
263 jsn, err := json.Marshal(sp)
264 if err != nil {
265 return "", err
266 }
267 return base64.StdEncoding.EncodeToString(jsn), nil
268 }
269
270 type Containers struct {
271 Length int `json:"length"`
272 Elements map[string]Container `json:"elements"`
273 }
274
275 type Container struct {
276 Command CommandArgs `json:"command"`
277 EnvRules EnvRules `json:"env_rules"`
278 Layers Layers `json:"layers"`
279 WorkingDir string `json:"working_dir"`
280 Mounts Mounts `json:"mounts"`
281 AllowElevated bool `json:"allow_elevated"`
282 ExecProcesses []ExecProcessConfig `json:"-"`
283 Signals []syscall.Signal `json:"-"`
284 AllowStdioAccess bool `json:"-"`
285 NoNewPrivileges bool `json:"-"`
286 User UserConfig `json:"-"`
287 Capabilities *CapabilitiesConfig `json:"-"`
288 SeccompProfileSHA256 string `json:"-"`
289 }
290
291
292 type StringArrayMap struct {
293 Length int `json:"length"`
294 Elements map[string]string `json:"elements"`
295 }
296
297 type Layers StringArrayMap
298
299 type CommandArgs StringArrayMap
300
301 type Options StringArrayMap
302
303 type EnvRules struct {
304 Length int `json:"length"`
305 Elements map[string]EnvRuleConfig `json:"elements"`
306 }
307
308 type Mount struct {
309 Source string `json:"source"`
310 Destination string `json:"destination"`
311 Type string `json:"type"`
312 Options Options `json:"options"`
313 }
314
315 type Mounts struct {
316 Length int `json:"length"`
317 Elements map[string]Mount `json:"elements"`
318 }
319
320
321
322 func CreateContainerPolicy(
323 command, layers []string,
324 envRules []EnvRuleConfig,
325 workingDir string,
326 mounts []MountConfig,
327 allowElevated bool,
328 execProcesses []ExecProcessConfig,
329 signals []syscall.Signal,
330 allowStdioAccess bool,
331 noNewPrivileges bool,
332 user UserConfig,
333 capabilities *CapabilitiesConfig,
334 seccompProfileSHA256 string,
335 ) (*Container, error) {
336 if err := validateEnvRules(envRules); err != nil {
337 return nil, err
338 }
339 if err := validateMountConstraint(mounts); err != nil {
340 return nil, err
341 }
342 return &Container{
343 Command: newCommandArgs(command),
344 Layers: newLayers(layers),
345 EnvRules: newEnvRules(envRules),
346 WorkingDir: workingDir,
347 Mounts: newMountConstraints(mounts),
348 AllowElevated: allowElevated,
349 ExecProcesses: execProcesses,
350 Signals: signals,
351 AllowStdioAccess: allowStdioAccess,
352 NoNewPrivileges: noNewPrivileges,
353 User: user,
354 Capabilities: capabilities,
355 SeccompProfileSHA256: seccompProfileSHA256,
356 }, nil
357 }
358
359
360 func NewSecurityPolicy(allowAll bool, containers []*Container) *SecurityPolicy {
361 containersMap := map[string]Container{}
362 for i, c := range containers {
363 containersMap[strconv.Itoa(i)] = *c
364 }
365 return &SecurityPolicy{
366 AllowAll: allowAll,
367 Containers: Containers{
368 Elements: containersMap,
369 },
370 }
371 }
372
373 func validateEnvRules(rules []EnvRuleConfig) error {
374 for _, rule := range rules {
375 switch rule.Strategy {
376 case EnvVarRuleRegex:
377 if _, err := regexp.Compile(rule.Rule); err != nil {
378 return err
379 }
380 }
381 }
382 return nil
383 }
384
385 func validateMountConstraint(mounts []MountConfig) error {
386 for _, m := range mounts {
387 if _, err := regexp.Compile(m.HostPath); err != nil {
388 return err
389 }
390 }
391 return nil
392 }
393
394 func newCommandArgs(args []string) CommandArgs {
395 command := map[string]string{}
396 for i, arg := range args {
397 command[strconv.Itoa(i)] = arg
398 }
399 return CommandArgs{
400 Elements: command,
401 }
402 }
403
404 func newEnvRules(rs []EnvRuleConfig) EnvRules {
405 envRules := map[string]EnvRuleConfig{}
406 for i, r := range rs {
407 envRules[strconv.Itoa(i)] = r
408 }
409 return EnvRules{
410 Elements: envRules,
411 }
412 }
413
414 func newLayers(ls []string) Layers {
415 layers := map[string]string{}
416 for i, l := range ls {
417 layers[strconv.Itoa(i)] = l
418 }
419 return Layers{
420 Elements: layers,
421 }
422 }
423
424 func newMountOptions(opts []string) Options {
425 mountOpts := map[string]string{}
426 for i, o := range opts {
427 mountOpts[strconv.Itoa(i)] = o
428 }
429 return Options{
430 Elements: mountOpts,
431 }
432 }
433
434
435
436
437
438
439
440
441 func newOptionsFromConfig(mCfg *MountConfig) []string {
442 mountOpts := []string{"rbind"}
443
444 if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) ||
445 strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) {
446 mountOpts = append(mountOpts, "rshared")
447 } else {
448 mountOpts = append(mountOpts, "rprivate")
449 }
450
451 if mCfg.Readonly {
452 mountOpts = append(mountOpts, "ro")
453 } else {
454 mountOpts = append(mountOpts, "rw")
455 }
456 return mountOpts
457 }
458
459
460
461 func newMountTypeFromConfig(mCfg *MountConfig) string {
462 if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) ||
463 strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) ||
464 strings.HasPrefix(mCfg.HostPath, plan9Prefix) {
465 return "bind"
466 }
467 return "none"
468 }
469
470
471
472 func newMountFromConfig(mCfg *MountConfig) Mount {
473 opts := newOptionsFromConfig(mCfg)
474 return Mount{
475 Source: mCfg.HostPath,
476 Destination: mCfg.ContainerPath,
477 Type: newMountTypeFromConfig(mCfg),
478 Options: newMountOptions(opts),
479 }
480 }
481
482
483 func newMountConstraints(mountConfigs []MountConfig) Mounts {
484 mounts := map[string]Mount{}
485 for i, mc := range mountConfigs {
486 mounts[strconv.Itoa(i)] = newMountFromConfig(&mc)
487 }
488 return Mounts{
489 Elements: mounts,
490 }
491 }
492
493 func EmptyCapabiltiesSet() []string {
494 return make([]string, 0)
495 }
496
497 func DefaultUnprivilegedCapabilities() []string {
498 return []string{"CAP_CHOWN",
499 "CAP_DAC_OVERRIDE",
500 "CAP_FSETID",
501 "CAP_FOWNER",
502 "CAP_MKNOD",
503 "CAP_NET_RAW",
504 "CAP_SETGID",
505 "CAP_SETUID",
506 "CAP_SETFCAP",
507 "CAP_SETPCAP",
508 "CAP_NET_BIND_SERVICE",
509 "CAP_SYS_CHROOT",
510 "CAP_KILL",
511 "CAP_AUDIT_WRITE",
512 }
513 }
514
515 func DefaultPrivilegedCapabilities() []string {
516 return []string{"CAP_CHOWN",
517 "CAP_DAC_OVERRIDE",
518 "CAP_DAC_READ_SEARCH",
519 "CAP_FOWNER",
520 "CAP_FSETID",
521 "CAP_KILL",
522 "CAP_SETGID",
523 "CAP_SETUID",
524 "CAP_SETPCAP",
525 "CAP_LINUX_IMMUTABLE",
526 "CAP_NET_BIND_SERVICE",
527 "CAP_NET_BROADCAST",
528 "CAP_NET_ADMIN",
529 "CAP_NET_RAW",
530 "CAP_IPC_LOCK",
531 "CAP_IPC_OWNER",
532 "CAP_SYS_MODULE",
533 "CAP_SYS_RAWIO",
534 "CAP_SYS_CHROOT",
535 "CAP_SYS_PTRACE",
536 "CAP_SYS_PACCT",
537 "CAP_SYS_ADMIN",
538 "CAP_SYS_BOOT",
539 "CAP_SYS_NICE",
540 "CAP_SYS_RESOURCE",
541 "CAP_SYS_TIME",
542 "CAP_SYS_TTY_CONFIG",
543 "CAP_MKNOD",
544 "CAP_LEASE",
545 "CAP_AUDIT_WRITE",
546 "CAP_AUDIT_CONTROL",
547 "CAP_SETFCAP",
548 "CAP_MAC_OVERRIDE",
549 "CAP_MAC_ADMIN",
550 "CAP_SYSLOG",
551 "CAP_WAKE_ALARM",
552 "CAP_BLOCK_SUSPEND",
553 "CAP_AUDIT_READ",
554 "CAP_PERFMON",
555 "CAP_BPF",
556 "CAP_CHECKPOINT_RESTORE",
557 }
558 }
559
View as plain text