/
strategy.go
178 lines (155 loc) · 7.03 KB
/
strategy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package seccomp
import (
"fmt"
"strings"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
)
const (
// AllowAny is the wildcard used to allow any profile.
AllowAny = "*"
// DefaultProfileAnnotationKey specifies the default seccomp profile.
DefaultProfileAnnotationKey = "seccomp.security.alpha.kubernetes.io/defaultProfileName"
// AllowedProfilesAnnotationKey specifies the allowed seccomp profiles.
AllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames"
)
// Strategy defines the interface for all seccomp constraint strategies.
type Strategy interface {
// Generate returns a profile based on constraint rules.
Generate(annotations map[string]string, pod *api.Pod) (string, error)
// Validate ensures that the specified values fall within the range of the strategy.
ValidatePod(pod *api.Pod) field.ErrorList
// Validate ensures that the specified values fall within the range of the strategy.
ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList
}
type strategy struct {
defaultProfile string
allowedProfiles map[string]bool
// For printing error messages (preserves order).
allowedProfilesString string
// does the strategy allow any profile (wildcard)
allowAnyProfile bool
}
var _ Strategy = &strategy{}
// NewStrategy creates a new strategy that enforces seccomp profile constraints.
func NewStrategy(pspAnnotations map[string]string) Strategy {
var allowedProfiles map[string]bool
allowAnyProfile := false
if allowed, ok := pspAnnotations[AllowedProfilesAnnotationKey]; ok {
profiles := strings.Split(allowed, ",")
allowedProfiles = make(map[string]bool, len(profiles))
for _, p := range profiles {
if p == AllowAny {
allowAnyProfile = true
continue
}
// With the graduation of seccomp to GA we automatically convert
// the deprecated seccomp profile annotation `docker/default` to
// `runtime/default`. This means that we now have to automatically
// allow `runtime/default` if a user specifies `docker/default` and
// vice versa in a PSP.
if p == v1.DeprecatedSeccompProfileDockerDefault || p == v1.SeccompProfileRuntimeDefault {
allowedProfiles[v1.SeccompProfileRuntimeDefault] = true
allowedProfiles[v1.DeprecatedSeccompProfileDockerDefault] = true
}
allowedProfiles[p] = true
}
}
return &strategy{
defaultProfile: pspAnnotations[DefaultProfileAnnotationKey],
allowedProfiles: allowedProfiles,
allowedProfilesString: pspAnnotations[AllowedProfilesAnnotationKey],
allowAnyProfile: allowAnyProfile,
}
}
// Generate returns a profile based on constraint rules.
func (s *strategy) Generate(annotations map[string]string, pod *api.Pod) (string, error) {
if annotations[api.SeccompPodAnnotationKey] != "" {
// Profile already set, nothing to do.
return annotations[api.SeccompPodAnnotationKey], nil
}
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil {
// Profile field already set, translate to annotation
return podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile), nil
}
return s.defaultProfile, nil
}
// ValidatePod ensures that the specified values on the pod fall within the range
// of the strategy.
func (s *strategy) ValidatePod(pod *api.Pod) field.ErrorList {
allErrs := field.ErrorList{}
podSpecFieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompPodAnnotationKey)
podProfile := pod.Annotations[api.SeccompPodAnnotationKey]
// if the annotation is not set, see if the field is set and derive the corresponding annotation value
if len(podProfile) == 0 && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil {
podProfile = podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile)
}
if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && podProfile != "" {
allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, "seccomp may not be set"))
return allErrs
}
if !s.profileAllowed(podProfile) {
msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", podProfile, s.allowedProfilesString)
allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, msg))
}
return allErrs
}
// ValidateContainer ensures that the specified values on the container fall within
// the range of the strategy.
func (s *strategy) ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList {
allErrs := field.ErrorList{}
fieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompContainerAnnotationKeyPrefix + container.Name)
containerProfile := profileForContainer(pod, container)
if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && containerProfile != "" {
allErrs = append(allErrs, field.Forbidden(fieldPath, "seccomp may not be set"))
return allErrs
}
if !s.profileAllowed(containerProfile) {
msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", containerProfile, s.allowedProfilesString)
allErrs = append(allErrs, field.Forbidden(fieldPath, msg))
}
return allErrs
}
// profileAllowed checks if profile is in allowedProfiles or if allowedProfiles
// contains the wildcard.
func (s *strategy) profileAllowed(profile string) bool {
// for backwards compatibility and PSPs without a defined list of allowed profiles.
// If a PSP does not have allowedProfiles set then we should allow an empty profile.
// This will mean that the runtime default is used.
if len(s.allowedProfiles) == 0 && profile == "" {
return true
}
return s.allowAnyProfile || s.allowedProfiles[profile]
}
// profileForContainer returns the container profile if set, otherwise the pod profile.
func profileForContainer(pod *api.Pod, container *api.Container) string {
if container.SecurityContext != nil && container.SecurityContext.SeccompProfile != nil {
// derive the annotation value from the container field
return podutil.SeccompAnnotationForField(container.SecurityContext.SeccompProfile)
}
containerProfile, ok := pod.Annotations[api.SeccompContainerAnnotationKeyPrefix+container.Name]
if ok {
// return the existing container annotation
return containerProfile
}
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SeccompProfile != nil {
// derive the annotation value from the pod field
return podutil.SeccompAnnotationForField(pod.Spec.SecurityContext.SeccompProfile)
}
// return the existing pod annotation
return pod.Annotations[api.SeccompPodAnnotationKey]
}