mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 04:33:26 +00:00
[PodSecurity] Errors validating PodControllers are non blocking
This commit is contained in:
parent
e1c4e85b52
commit
98c86b350c
@ -399,7 +399,11 @@ func (a *Admission) ValidatePodController(ctx context.Context, attrs api.Attribu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
klog.ErrorS(err, "failed to fetch pod namespace", "namespace", attrs.GetNamespace())
|
klog.ErrorS(err, "failed to fetch pod namespace", "namespace", attrs.GetNamespace())
|
||||||
a.Metrics.RecordError(true, attrs)
|
a.Metrics.RecordError(true, attrs)
|
||||||
return internalErrorResponse(fmt.Sprintf("failed to lookup namespace %s", attrs.GetNamespace()))
|
response := allowedResponse()
|
||||||
|
response.AuditAnnotations = map[string]string{
|
||||||
|
"error": fmt.Sprintf("failed to lookup namespace %s", attrs.GetNamespace()),
|
||||||
|
}
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
nsPolicy, nsPolicyErrs := a.PolicyToEvaluate(namespace.Labels)
|
nsPolicy, nsPolicyErrs := a.PolicyToEvaluate(namespace.Labels)
|
||||||
if len(nsPolicyErrs) == 0 && nsPolicy.Warn.Level == api.LevelPrivileged && nsPolicy.Audit.Level == api.LevelPrivileged {
|
if len(nsPolicyErrs) == 0 && nsPolicy.Warn.Level == api.LevelPrivileged && nsPolicy.Audit.Level == api.LevelPrivileged {
|
||||||
@ -410,13 +414,21 @@ func (a *Admission) ValidatePodController(ctx context.Context, attrs api.Attribu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
klog.ErrorS(err, "failed to decode object")
|
klog.ErrorS(err, "failed to decode object")
|
||||||
a.Metrics.RecordError(true, attrs)
|
a.Metrics.RecordError(true, attrs)
|
||||||
return badRequestResponse("failed to decode object")
|
response := allowedResponse()
|
||||||
|
response.AuditAnnotations = map[string]string{
|
||||||
|
"error": "failed to decode object",
|
||||||
|
}
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
podMetadata, podSpec, err := a.PodSpecExtractor.ExtractPodSpec(obj)
|
podMetadata, podSpec, err := a.PodSpecExtractor.ExtractPodSpec(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.ErrorS(err, "failed to extract pod spec")
|
klog.ErrorS(err, "failed to extract pod spec")
|
||||||
a.Metrics.RecordError(true, attrs)
|
a.Metrics.RecordError(true, attrs)
|
||||||
return badRequestResponse("failed to extract pod template")
|
response := allowedResponse()
|
||||||
|
response.AuditAnnotations = map[string]string{
|
||||||
|
"error": "failed to extract pod template",
|
||||||
|
}
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
if podMetadata == nil && podSpec == nil {
|
if podMetadata == nil && podSpec == nil {
|
||||||
// if a controller with an optional pod spec does not contain a pod spec, skip validation
|
// if a controller with an optional pod spec does not contain a pod spec, skip validation
|
||||||
|
@ -589,206 +589,7 @@ func TestValidateNamespace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePodController(t *testing.T) {
|
func TestValidatePodAndController(t *testing.T) {
|
||||||
testName, testNamespace := "testname", "default"
|
|
||||||
objMetadata := metav1.ObjectMeta{Name: testName, Namespace: testNamespace, Labels: map[string]string{"foo": "bar"}}
|
|
||||||
// One of the pod-template objects
|
|
||||||
goodDeploy := appsv1.Deployment{
|
|
||||||
ObjectMeta: objMetadata,
|
|
||||||
Spec: appsv1.DeploymentSpec{
|
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
|
|
||||||
Template: corev1.PodTemplateSpec{
|
|
||||||
ObjectMeta: objMetadata,
|
|
||||||
Spec: corev1.PodSpec{
|
|
||||||
RuntimeClassName: pointer.String("containerd"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
badDeploy := appsv1.Deployment{
|
|
||||||
ObjectMeta: objMetadata,
|
|
||||||
Spec: appsv1.DeploymentSpec{
|
|
||||||
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
|
|
||||||
Template: corev1.PodTemplateSpec{
|
|
||||||
ObjectMeta: objMetadata,
|
|
||||||
Spec: corev1.PodSpec{
|
|
||||||
SecurityContext: &corev1.PodSecurityContext{
|
|
||||||
// out of allowed sysctls to return auditAnnotation or warning
|
|
||||||
Sysctls: []corev1.Sysctl{{Name: "unknown", Value: "unknown"}},
|
|
||||||
},
|
|
||||||
RuntimeClassName: pointer.String("containerd"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that under the baseline policy,
|
|
||||||
// the pod-template object of all tests returns correct information or is exempted
|
|
||||||
nsLabels := map[string]string{
|
|
||||||
api.EnforceLevelLabel: string(api.LevelBaseline),
|
|
||||||
api.WarnLevelLabel: string(api.LevelBaseline),
|
|
||||||
api.AuditLevelLabel: string(api.LevelBaseline),
|
|
||||||
}
|
|
||||||
nsLevelVersion := api.LevelVersion{api.LevelBaseline, api.LatestVersion()}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
exemptNamespaces []string
|
|
||||||
exemptRuntimeClasses []string
|
|
||||||
exemptUsers []string
|
|
||||||
// request subresource
|
|
||||||
subresource string
|
|
||||||
// for create
|
|
||||||
newObject runtime.Object
|
|
||||||
// for update
|
|
||||||
oldObject runtime.Object
|
|
||||||
gvk schema.GroupVersionKind
|
|
||||||
gvr schema.GroupVersionResource
|
|
||||||
|
|
||||||
expectWarnings []string
|
|
||||||
expectAuditAnnotations map[string]string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "subresource(status) updates don't produce warnings",
|
|
||||||
subresource: "status",
|
|
||||||
newObject: &badDeploy,
|
|
||||||
oldObject: &goodDeploy,
|
|
||||||
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "namespace in exemptNamespaces will be exempted",
|
|
||||||
newObject: &badDeploy,
|
|
||||||
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
|
|
||||||
exemptNamespaces: []string{testNamespace},
|
|
||||||
expectAuditAnnotations: map[string]string{"exempt": "namespace"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "runtimeClass in exemptRuntimeClasses will be exempted",
|
|
||||||
newObject: &badDeploy,
|
|
||||||
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
|
|
||||||
exemptRuntimeClasses: []string{"containerd"},
|
|
||||||
expectAuditAnnotations: map[string]string{"exempt": "runtimeClass"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "user in exemptUsers will be exempted",
|
|
||||||
newObject: &badDeploy,
|
|
||||||
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
|
|
||||||
exemptUsers: []string{"testuser"},
|
|
||||||
expectAuditAnnotations: map[string]string{"exempt": "user"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "podMetadata == nil && podSpec == nil will skip verification",
|
|
||||||
newObject: &corev1.ReplicationController{ObjectMeta: metav1.ObjectMeta{Name: "foo-rc"}},
|
|
||||||
gvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ReplicationController"},
|
|
||||||
gvr: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "replicationcontrollers"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "good deploy creates and produce nothing",
|
|
||||||
newObject: &goodDeploy,
|
|
||||||
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
|
|
||||||
expectAuditAnnotations: map[string]string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "bad deploy creates produce correct user-visible warnings and correct auditAnnotations",
|
|
||||||
newObject: &badDeploy,
|
|
||||||
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
|
|
||||||
expectAuditAnnotations: map[string]string{"audit-violations": "would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"},
|
|
||||||
expectWarnings: []string{"would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "bad spec updates don't block on enforce failures and returns correct information",
|
|
||||||
newObject: &badDeploy,
|
|
||||||
oldObject: &goodDeploy,
|
|
||||||
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
|
|
||||||
gvr: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
|
|
||||||
expectAuditAnnotations: map[string]string{"audit-violations": "would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"},
|
|
||||||
expectWarnings: []string{"would violate PodSecurity \"baseline:latest\": forbidden sysctls (unknown)"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
|
||||||
var operation = admissionv1.Create
|
|
||||||
if tc.oldObject != nil {
|
|
||||||
operation = admissionv1.Update
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs := &api.AttributesRecord{
|
|
||||||
testName,
|
|
||||||
testNamespace,
|
|
||||||
tc.gvk,
|
|
||||||
tc.gvr,
|
|
||||||
tc.subresource,
|
|
||||||
operation,
|
|
||||||
tc.newObject,
|
|
||||||
tc.oldObject,
|
|
||||||
"testuser",
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultPolicy := api.Policy{
|
|
||||||
Enforce: api.LevelVersion{Level: api.LevelPrivileged, Version: api.LatestVersion()},
|
|
||||||
Audit: api.LevelVersion{Level: api.LevelPrivileged, Version: api.LatestVersion()},
|
|
||||||
Warn: api.LevelVersion{Level: api.LevelPrivileged, Version: api.LatestVersion()},
|
|
||||||
}
|
|
||||||
|
|
||||||
podLister := &testPodLister{}
|
|
||||||
evaluator, err := policy.NewEvaluator(policy.DefaultChecks())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
nsGetter := &testNamespaceGetter{
|
|
||||||
testNamespace: &corev1.Namespace{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: testName,
|
|
||||||
Labels: nsLabels}},
|
|
||||||
}
|
|
||||||
PodSpecExtractor := &DefaultPodSpecExtractor{}
|
|
||||||
recorder := &FakeRecorder{}
|
|
||||||
a := &Admission{
|
|
||||||
PodLister: podLister,
|
|
||||||
Evaluator: evaluator,
|
|
||||||
PodSpecExtractor: PodSpecExtractor,
|
|
||||||
Configuration: &admissionapi.PodSecurityConfiguration{
|
|
||||||
Exemptions: admissionapi.PodSecurityExemptions{
|
|
||||||
Namespaces: tc.exemptNamespaces,
|
|
||||||
RuntimeClasses: tc.exemptRuntimeClasses,
|
|
||||||
Usernames: tc.exemptUsers,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Metrics: recorder,
|
|
||||||
defaultPolicy: defaultPolicy,
|
|
||||||
NamespaceGetter: nsGetter,
|
|
||||||
}
|
|
||||||
|
|
||||||
result := a.ValidatePodController(context.TODO(), attrs)
|
|
||||||
// podContorller will not return an error due to correct evaluation
|
|
||||||
resultError := ""
|
|
||||||
if result.Result != nil {
|
|
||||||
resultError = result.Result.Message
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, true, result.Allowed)
|
|
||||||
assert.Empty(t, resultError)
|
|
||||||
assert.Equal(t, tc.expectAuditAnnotations, result.AuditAnnotations, "unexpected AuditAnnotations")
|
|
||||||
assert.Equal(t, tc.expectWarnings, result.Warnings, "unexpected Warnings")
|
|
||||||
|
|
||||||
expectedEvaluations := []EvaluationRecord{}
|
|
||||||
if _, ok := tc.expectAuditAnnotations["audit-violations"]; ok {
|
|
||||||
expectedEvaluations = append(expectedEvaluations, EvaluationRecord{testName, metrics.DecisionDeny, nsLevelVersion, metrics.ModeAudit})
|
|
||||||
}
|
|
||||||
if len(tc.expectWarnings) > 0 {
|
|
||||||
expectedEvaluations = append(expectedEvaluations, EvaluationRecord{testName, metrics.DecisionDeny, nsLevelVersion, metrics.ModeWarn})
|
|
||||||
}
|
|
||||||
recorder.ExpectEvaluations(t, expectedEvaluations)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidatePod(t *testing.T) {
|
|
||||||
const (
|
const (
|
||||||
exemptNs = "exempt-ns"
|
exemptNs = "exempt-ns"
|
||||||
implicitNs = "implicit-ns"
|
implicitNs = "implicit-ns"
|
||||||
@ -832,14 +633,9 @@ func TestValidatePod(t *testing.T) {
|
|||||||
differentRestrictedPod := *restrictedPod.DeepCopy()
|
differentRestrictedPod := *restrictedPod.DeepCopy()
|
||||||
differentRestrictedPod.Spec.Containers[0].Image = "https://example.com/a-different-image"
|
differentRestrictedPod.Spec.Containers[0].Image = "https://example.com/a-different-image"
|
||||||
|
|
||||||
deployment := appsv1.Deployment{
|
emptyDeployment := appsv1.Deployment{
|
||||||
ObjectMeta: *objMetadata.DeepCopy(),
|
ObjectMeta: *objMetadata.DeepCopy(),
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{},
|
||||||
Template: corev1.PodTemplateSpec{
|
|
||||||
ObjectMeta: *objMetadata.DeepCopy(),
|
|
||||||
Spec: *restrictedPod.Spec.DeepCopy(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
makeNs := func(enforceLevel, warnLevel, auditLevel api.Level) *corev1.Namespace {
|
makeNs := func(enforceLevel, warnLevel, auditLevel api.Level) *corev1.Namespace {
|
||||||
@ -863,10 +659,10 @@ func TestValidatePod(t *testing.T) {
|
|||||||
exemptNs: makeNs(api.LevelRestricted, api.LevelRestricted, api.LevelRestricted),
|
exemptNs: makeNs(api.LevelRestricted, api.LevelRestricted, api.LevelRestricted),
|
||||||
implicitNs: makeNs("", "", ""),
|
implicitNs: makeNs("", "", ""),
|
||||||
privilegedNs: makeNs(api.LevelPrivileged, api.LevelPrivileged, api.LevelPrivileged),
|
privilegedNs: makeNs(api.LevelPrivileged, api.LevelPrivileged, api.LevelPrivileged),
|
||||||
baselineNs: makeNs(api.LevelBaseline, "", ""),
|
baselineNs: makeNs(api.LevelBaseline, api.LevelBaseline, api.LevelBaseline),
|
||||||
baselineWarnNs: makeNs("", api.LevelBaseline, ""),
|
baselineWarnNs: makeNs("", api.LevelBaseline, ""),
|
||||||
baselineAuditNs: makeNs("", "", api.LevelBaseline),
|
baselineAuditNs: makeNs("", "", api.LevelBaseline),
|
||||||
restrictedNs: makeNs(api.LevelRestricted, api.LevelRestricted, api.LevelRestricted),
|
restrictedNs: makeNs(api.LevelRestricted, "", api.LevelRestricted),
|
||||||
invalidNs: makeNs("not-a-valid-level", "", ""),
|
invalidNs: makeNs("not-a-valid-level", "", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -879,36 +675,56 @@ func TestValidatePod(t *testing.T) {
|
|||||||
evaluator, err := policy.NewEvaluator(policy.DefaultChecks())
|
evaluator, err := policy.NewEvaluator(policy.DefaultChecks())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
testCases := []struct {
|
a := &Admission{
|
||||||
|
PodLister: &testPodLister{},
|
||||||
|
Evaluator: evaluator,
|
||||||
|
Configuration: config,
|
||||||
|
Metrics: &FakeRecorder{},
|
||||||
|
NamespaceGetter: nsGetter,
|
||||||
|
}
|
||||||
|
require.NoError(t, a.CompleteConfiguration(), "CompleteConfiguration()")
|
||||||
|
require.NoError(t, a.ValidateConfiguration(), "ValidateConfiguration()")
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
||||||
namespace string
|
namespace string
|
||||||
username string
|
username string
|
||||||
runtimeClass string
|
runtimeClass string
|
||||||
|
|
||||||
operation admissionv1.Operation
|
operation admissionv1.Operation
|
||||||
|
pod *corev1.Pod
|
||||||
|
oldPod *corev1.Pod
|
||||||
|
|
||||||
|
resource schema.GroupVersionResource
|
||||||
|
kind schema.GroupVersionKind
|
||||||
obj runtime.Object
|
obj runtime.Object
|
||||||
objErr error // Error to return instead of obj by attrs.GetObject()
|
|
||||||
oldObj runtime.Object
|
oldObj runtime.Object
|
||||||
|
objErr error // Error to return instead of obj by attrs.GetObject()
|
||||||
oldObjErr error // Error to return instead of oldObj by attrs.GetOldObject()
|
oldObjErr error // Error to return instead of oldObj by attrs.GetOldObject()
|
||||||
subresource string
|
subresource string
|
||||||
|
|
||||||
|
skipPod bool // Whether to skip the ValidatePod test case.
|
||||||
|
skipDeployment bool // Whteher to skip the ValidatePodController test case.
|
||||||
|
|
||||||
expectAllowed bool
|
expectAllowed bool
|
||||||
expectReason metav1.StatusReason
|
expectReason metav1.StatusReason
|
||||||
expectWarning bool
|
expectWarning bool
|
||||||
|
expectEnforce bool // Whether to expect an enforcing evaluation (metric+annotation)
|
||||||
expectedAuditAnnotationKeys []string
|
expectedAuditAnnotationKeys []string
|
||||||
}{
|
}
|
||||||
|
podCases := []testCase{
|
||||||
{
|
{
|
||||||
desc: "ignored subresource",
|
desc: "ignored subresource",
|
||||||
namespace: restrictedNs,
|
namespace: restrictedNs,
|
||||||
obj: privilegedPod.DeepCopy(),
|
pod: privilegedPod.DeepCopy(),
|
||||||
subresource: "status",
|
subresource: "status",
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "exempt namespace",
|
desc: "exempt namespace",
|
||||||
namespace: exemptNs,
|
namespace: exemptNs,
|
||||||
obj: privilegedPod.DeepCopy(),
|
pod: privilegedPod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
expectedAuditAnnotationKeys: []string{"exempt"},
|
expectedAuditAnnotationKeys: []string{"exempt"},
|
||||||
},
|
},
|
||||||
@ -916,34 +732,34 @@ func TestValidatePod(t *testing.T) {
|
|||||||
desc: "exempt user",
|
desc: "exempt user",
|
||||||
namespace: restrictedNs,
|
namespace: restrictedNs,
|
||||||
username: exemptUser,
|
username: exemptUser,
|
||||||
obj: privilegedPod.DeepCopy(),
|
pod: privilegedPod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
expectedAuditAnnotationKeys: []string{"exempt"},
|
expectedAuditAnnotationKeys: []string{"exempt"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "exempt runtimeClass",
|
desc: "exempt runtimeClass",
|
||||||
namespace: restrictedNs,
|
namespace: restrictedNs,
|
||||||
obj: exemptRCPod.DeepCopy(),
|
pod: exemptRCPod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
expectedAuditAnnotationKeys: []string{"exempt"},
|
expectedAuditAnnotationKeys: []string{"exempt"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "namespace not found",
|
desc: "namespace not found",
|
||||||
namespace: "missing-ns",
|
namespace: "missing-ns",
|
||||||
obj: restrictedPod.DeepCopy(),
|
pod: restrictedPod.DeepCopy(),
|
||||||
expectAllowed: false,
|
expectAllowed: false,
|
||||||
expectReason: metav1.StatusReasonInternalError,
|
expectReason: metav1.StatusReasonInternalError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "short-circuit privileged:latest (implicit)",
|
desc: "short-circuit privileged:latest (implicit)",
|
||||||
namespace: implicitNs,
|
namespace: implicitNs,
|
||||||
obj: privilegedPod.DeepCopy(),
|
pod: privilegedPod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "short-circuit privileged:latest (explicit)",
|
desc: "short-circuit privileged:latest (explicit)",
|
||||||
namespace: privilegedNs,
|
namespace: privilegedNs,
|
||||||
obj: privilegedPod.DeepCopy(),
|
pod: privilegedPod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -956,93 +772,162 @@ func TestValidatePod(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "invalid object",
|
desc: "invalid object",
|
||||||
namespace: baselineNs,
|
namespace: baselineNs,
|
||||||
obj: deployment.DeepCopy(),
|
operation: admissionv1.Update,
|
||||||
|
obj: &corev1.Namespace{},
|
||||||
expectAllowed: false,
|
expectAllowed: false,
|
||||||
expectReason: metav1.StatusReasonBadRequest,
|
expectReason: metav1.StatusReasonBadRequest,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "failed decode old object",
|
desc: "failed decode old object",
|
||||||
namespace: baselineNs,
|
namespace: baselineNs,
|
||||||
operation: admissionv1.Update,
|
operation: admissionv1.Update,
|
||||||
obj: restrictedPod.DeepCopy(),
|
pod: restrictedPod.DeepCopy(),
|
||||||
oldObjErr: fmt.Errorf("expected (failed decode)"),
|
oldObjErr: fmt.Errorf("expected (failed decode)"),
|
||||||
expectAllowed: false,
|
expectAllowed: false,
|
||||||
expectReason: metav1.StatusReasonBadRequest,
|
expectReason: metav1.StatusReasonBadRequest,
|
||||||
|
skipDeployment: true, // Updates aren't special cased for controller resources.
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "invalid old object",
|
desc: "invalid old object",
|
||||||
namespace: baselineNs,
|
namespace: baselineNs,
|
||||||
operation: admissionv1.Update,
|
operation: admissionv1.Update,
|
||||||
obj: restrictedPod.DeepCopy(),
|
pod: restrictedPod.DeepCopy(),
|
||||||
oldObj: deployment.DeepCopy(),
|
oldObj: &corev1.Namespace{},
|
||||||
expectAllowed: false,
|
expectAllowed: false,
|
||||||
expectReason: metav1.StatusReasonBadRequest,
|
expectReason: metav1.StatusReasonBadRequest,
|
||||||
|
skipDeployment: true, // Updates aren't special cased for controller resources.
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "insignificant pod update",
|
desc: "insignificant update",
|
||||||
namespace: restrictedNs,
|
namespace: restrictedNs,
|
||||||
operation: admissionv1.Update,
|
operation: admissionv1.Update,
|
||||||
obj: tolerantPod.DeepCopy(),
|
pod: tolerantPod.DeepCopy(),
|
||||||
oldObj: privilegedPod.DeepCopy(),
|
oldPod: privilegedPod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
|
skipDeployment: true, // Updates aren't special cased for controller resources.
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "significant pod update - denied",
|
desc: "significant update denied",
|
||||||
namespace: baselineNs,
|
|
||||||
operation: admissionv1.Update,
|
|
||||||
obj: differentPrivilegedPod.DeepCopy(),
|
|
||||||
oldObj: privilegedPod.DeepCopy(),
|
|
||||||
expectAllowed: false,
|
|
||||||
expectReason: metav1.StatusReasonForbidden,
|
|
||||||
expectedAuditAnnotationKeys: []string{"enforce-policy"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "significant pod update - allowed",
|
|
||||||
namespace: restrictedNs,
|
namespace: restrictedNs,
|
||||||
operation: admissionv1.Update,
|
operation: admissionv1.Update,
|
||||||
obj: differentRestrictedPod.DeepCopy(),
|
pod: differentPrivilegedPod.DeepCopy(),
|
||||||
oldObj: restrictedPod,
|
oldPod: privilegedPod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: false,
|
||||||
expectedAuditAnnotationKeys: []string{"enforce-policy"},
|
expectReason: metav1.StatusReasonForbidden,
|
||||||
|
expectEnforce: true,
|
||||||
|
expectedAuditAnnotationKeys: []string{"audit-violations"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "significant update allowed",
|
||||||
|
namespace: restrictedNs,
|
||||||
|
operation: admissionv1.Update,
|
||||||
|
pod: differentRestrictedPod.DeepCopy(),
|
||||||
|
oldPod: restrictedPod,
|
||||||
|
expectAllowed: true,
|
||||||
|
expectEnforce: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "invalid namespace labels",
|
desc: "invalid namespace labels",
|
||||||
namespace: invalidNs,
|
namespace: invalidNs,
|
||||||
obj: baselinePod.DeepCopy(),
|
pod: baselinePod.DeepCopy(),
|
||||||
expectAllowed: false,
|
expectAllowed: false,
|
||||||
expectReason: metav1.StatusReasonForbidden,
|
expectReason: metav1.StatusReasonForbidden,
|
||||||
expectedAuditAnnotationKeys: []string{"enforce-policy", "error"},
|
expectEnforce: true,
|
||||||
|
expectedAuditAnnotationKeys: []string{"error"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "enforce deny",
|
desc: "enforce deny",
|
||||||
namespace: baselineNs,
|
namespace: restrictedNs,
|
||||||
obj: privilegedPod.DeepCopy(),
|
pod: privilegedPod.DeepCopy(),
|
||||||
expectAllowed: false,
|
expectAllowed: false,
|
||||||
expectReason: metav1.StatusReasonForbidden,
|
expectReason: metav1.StatusReasonForbidden,
|
||||||
expectedAuditAnnotationKeys: []string{"enforce-policy"},
|
expectEnforce: true,
|
||||||
|
expectedAuditAnnotationKeys: []string{"audit-violations"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "enforce allow",
|
desc: "enforce allow",
|
||||||
namespace: baselineNs,
|
namespace: baselineNs,
|
||||||
obj: baselinePod.DeepCopy(),
|
pod: baselinePod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
expectedAuditAnnotationKeys: []string{"enforce-policy"},
|
expectEnforce: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "warn deny",
|
desc: "warn deny",
|
||||||
namespace: baselineWarnNs,
|
namespace: baselineWarnNs,
|
||||||
obj: privilegedPod.DeepCopy(),
|
pod: privilegedPod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
expectWarning: true,
|
expectWarning: true,
|
||||||
expectedAuditAnnotationKeys: []string{"enforce-policy"},
|
expectEnforce: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "audit deny",
|
desc: "audit deny",
|
||||||
namespace: baselineAuditNs,
|
namespace: baselineAuditNs,
|
||||||
obj: privilegedPod.DeepCopy(),
|
pod: privilegedPod.DeepCopy(),
|
||||||
expectAllowed: true,
|
expectAllowed: true,
|
||||||
expectedAuditAnnotationKeys: []string{"enforce-policy", "audit-violations"},
|
expectEnforce: true,
|
||||||
|
expectedAuditAnnotationKeys: []string{"audit-violations"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "no pod template",
|
||||||
|
namespace: restrictedNs,
|
||||||
|
obj: emptyDeployment.DeepCopy(),
|
||||||
|
expectAllowed: true,
|
||||||
|
expectWarning: false, // No pod template skips validation.
|
||||||
|
skipPod: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
podToDeployment := func(pod *corev1.Pod) *appsv1.Deployment {
|
||||||
|
if pod == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &appsv1.Deployment{
|
||||||
|
ObjectMeta: pod.ObjectMeta,
|
||||||
|
Spec: appsv1.DeploymentSpec{
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
ObjectMeta: pod.ObjectMeta,
|
||||||
|
Spec: pod.Spec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert "pod cases" into pod test cases & deployment test cases.
|
||||||
|
testCases := []testCase{}
|
||||||
|
for _, tc := range podCases {
|
||||||
|
podTest := tc
|
||||||
|
podTest.desc = "pod:" + tc.desc
|
||||||
|
podTest.resource = schema.GroupVersionResource{Version: "v1", Resource: "pods"}
|
||||||
|
podTest.kind = schema.GroupVersionKind{Version: "v1", Kind: "Pod"}
|
||||||
|
if tc.expectEnforce {
|
||||||
|
podTest.expectedAuditAnnotationKeys = append(podTest.expectedAuditAnnotationKeys, "enforce-policy")
|
||||||
|
}
|
||||||
|
|
||||||
|
deploymentTest := tc
|
||||||
|
deploymentTest.desc = "deployment:" + tc.desc
|
||||||
|
deploymentTest.resource = schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
|
||||||
|
deploymentTest.kind = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
|
||||||
|
deploymentTest.expectAllowed = true // Deployments policies are non-enforcing.
|
||||||
|
deploymentTest.expectReason = ""
|
||||||
|
if tc.expectReason != "" && tc.expectReason != metav1.StatusReasonForbidden {
|
||||||
|
// Error case, expect an error annotation.
|
||||||
|
deploymentTest.expectedAuditAnnotationKeys = append(deploymentTest.expectedAuditAnnotationKeys, "error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.pod != nil {
|
||||||
|
podTest.obj = tc.pod
|
||||||
|
deploymentTest.obj = podToDeployment(tc.pod)
|
||||||
|
}
|
||||||
|
if tc.oldPod != nil {
|
||||||
|
podTest.oldObj = tc.oldPod
|
||||||
|
deploymentTest.oldObj = podToDeployment(tc.oldPod)
|
||||||
|
}
|
||||||
|
if !tc.skipPod {
|
||||||
|
testCases = append(testCases, podTest)
|
||||||
|
}
|
||||||
|
if !tc.skipDeployment {
|
||||||
|
testCases = append(testCases, deploymentTest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@ -1057,8 +942,8 @@ func TestValidatePod(t *testing.T) {
|
|||||||
AttributesRecord: api.AttributesRecord{
|
AttributesRecord: api.AttributesRecord{
|
||||||
Name: "test-pod",
|
Name: "test-pod",
|
||||||
Namespace: tc.namespace,
|
Namespace: tc.namespace,
|
||||||
Kind: schema.GroupVersionKind{Version: "v1", Kind: "Pod"},
|
Kind: tc.kind,
|
||||||
Resource: schema.GroupVersionResource{Version: "v1", Resource: "pods"},
|
Resource: tc.resource,
|
||||||
Subresource: tc.subresource,
|
Subresource: tc.subresource,
|
||||||
Operation: admissionv1.Create,
|
Operation: admissionv1.Create,
|
||||||
Object: tc.obj,
|
Object: tc.obj,
|
||||||
@ -1075,16 +960,6 @@ func TestValidatePod(t *testing.T) {
|
|||||||
attrs.Username = tc.username
|
attrs.Username = tc.username
|
||||||
}
|
}
|
||||||
|
|
||||||
a := &Admission{
|
|
||||||
PodLister: &testPodLister{},
|
|
||||||
Evaluator: evaluator,
|
|
||||||
Configuration: config,
|
|
||||||
Metrics: &FakeRecorder{},
|
|
||||||
NamespaceGetter: nsGetter,
|
|
||||||
}
|
|
||||||
require.NoError(t, a.CompleteConfiguration(), "CompleteConfiguration()")
|
|
||||||
require.NoError(t, a.ValidateConfiguration(), "ValidateConfiguration()")
|
|
||||||
|
|
||||||
response := a.Validate(context.TODO(), attrs)
|
response := a.Validate(context.TODO(), attrs)
|
||||||
|
|
||||||
if tc.expectAllowed {
|
if tc.expectAllowed {
|
||||||
|
Loading…
Reference in New Issue
Block a user