mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
[PodSecurity] Add metrics test coverage
This commit is contained in:
parent
c3398729e0
commit
81661d5a34
@ -228,7 +228,7 @@ func (a *Admission) Validate(ctx context.Context, attrs api.Attributes) *admissi
|
||||
func (a *Admission) ValidateNamespace(ctx context.Context, attrs api.Attributes) *admissionv1.AdmissionResponse {
|
||||
// short-circuit on subresources
|
||||
if attrs.GetSubresource() != "" {
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
obj, err := attrs.GetObject()
|
||||
if err != nil {
|
||||
@ -249,7 +249,7 @@ func (a *Admission) ValidateNamespace(ctx context.Context, attrs api.Attributes)
|
||||
if len(newErrs) > 0 {
|
||||
return invalidResponse(attrs, newErrs)
|
||||
}
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
|
||||
case admissionv1.Update:
|
||||
// if update, check if policy labels changed
|
||||
@ -276,24 +276,24 @@ func (a *Admission) ValidateNamespace(ctx context.Context, attrs api.Attributes)
|
||||
// * if the new enforce is the same version and level was relaxed
|
||||
// * for exempt namespaces
|
||||
if newPolicy.Enforce == oldPolicy.Enforce {
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
if newPolicy.Enforce.Level == api.LevelPrivileged {
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
if newPolicy.Enforce.Version == oldPolicy.Enforce.Version &&
|
||||
api.CompareLevels(newPolicy.Enforce.Level, oldPolicy.Enforce.Level) < 1 {
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
if a.exemptNamespace(attrs.GetNamespace()) {
|
||||
return sharedAllowedByNamespaceExemptionResponse()
|
||||
return sharedAllowedByNamespaceExemptionResponse
|
||||
}
|
||||
response := allowedResponse()
|
||||
response.Warnings = a.EvaluatePodsInNamespace(ctx, namespace.Name, newPolicy.Enforce)
|
||||
return response
|
||||
|
||||
default:
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,17 +316,17 @@ var ignoredPodSubresources = map[string]bool{
|
||||
func (a *Admission) ValidatePod(ctx context.Context, attrs api.Attributes) *admissionv1.AdmissionResponse {
|
||||
// short-circuit on ignored subresources
|
||||
if ignoredPodSubresources[attrs.GetSubresource()] {
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
// short-circuit on exempt namespaces and users
|
||||
if a.exemptNamespace(attrs.GetNamespace()) {
|
||||
a.Metrics.RecordExemption(attrs)
|
||||
return sharedAllowedByNamespaceExemptionResponse()
|
||||
return sharedAllowedByNamespaceExemptionResponse
|
||||
}
|
||||
|
||||
if a.exemptUser(attrs.GetUserName()) {
|
||||
a.Metrics.RecordExemption(attrs)
|
||||
return sharedAllowedByUserExemptionResponse()
|
||||
return sharedAllowedByUserExemptionResponse
|
||||
}
|
||||
|
||||
// short-circuit on privileged enforce+audit+warn namespaces
|
||||
@ -339,7 +339,7 @@ func (a *Admission) ValidatePod(ctx context.Context, attrs api.Attributes) *admi
|
||||
nsPolicy, nsPolicyErrs := a.PolicyToEvaluate(namespace.Labels)
|
||||
if len(nsPolicyErrs) == 0 && nsPolicy.Enforce.Level == api.LevelPrivileged && nsPolicy.Warn.Level == api.LevelPrivileged && nsPolicy.Audit.Level == api.LevelPrivileged {
|
||||
a.Metrics.RecordEvaluation(metrics.DecisionAllow, nsPolicy.Enforce, metrics.ModeEnforce, attrs)
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedPrivilegedResponse
|
||||
}
|
||||
|
||||
obj, err := attrs.GetObject()
|
||||
@ -369,7 +369,7 @@ func (a *Admission) ValidatePod(ctx context.Context, attrs api.Attributes) *admi
|
||||
}
|
||||
if !isSignificantPodUpdate(pod, oldPod) {
|
||||
// Nothing we care about changed, so always allow the update.
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
}
|
||||
return a.EvaluatePod(ctx, nsPolicy, nsPolicyErrs.ToAggregate(), &pod.ObjectMeta, &pod.Spec, attrs, true)
|
||||
@ -380,17 +380,17 @@ func (a *Admission) ValidatePod(ctx context.Context, attrs api.Attributes) *admi
|
||||
func (a *Admission) ValidatePodController(ctx context.Context, attrs api.Attributes) *admissionv1.AdmissionResponse {
|
||||
// short-circuit on subresources
|
||||
if attrs.GetSubresource() != "" {
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
// short-circuit on exempt namespaces and users
|
||||
if a.exemptNamespace(attrs.GetNamespace()) {
|
||||
a.Metrics.RecordExemption(attrs)
|
||||
return sharedAllowedByNamespaceExemptionResponse()
|
||||
return sharedAllowedByNamespaceExemptionResponse
|
||||
}
|
||||
|
||||
if a.exemptUser(attrs.GetUserName()) {
|
||||
a.Metrics.RecordExemption(attrs)
|
||||
return sharedAllowedByUserExemptionResponse()
|
||||
return sharedAllowedByUserExemptionResponse
|
||||
}
|
||||
|
||||
// short-circuit on privileged audit+warn namespaces
|
||||
@ -406,7 +406,7 @@ func (a *Admission) ValidatePodController(ctx context.Context, attrs api.Attribu
|
||||
}
|
||||
nsPolicy, nsPolicyErrs := a.PolicyToEvaluate(namespace.Labels)
|
||||
if len(nsPolicyErrs) == 0 && nsPolicy.Warn.Level == api.LevelPrivileged && nsPolicy.Audit.Level == api.LevelPrivileged {
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
|
||||
obj, err := attrs.GetObject()
|
||||
@ -431,7 +431,7 @@ func (a *Admission) ValidatePodController(ctx context.Context, attrs api.Attribu
|
||||
}
|
||||
if podMetadata == nil && podSpec == nil {
|
||||
// if a controller with an optional pod spec does not contain a pod spec, skip validation
|
||||
return sharedAllowedResponse()
|
||||
return sharedAllowedResponse
|
||||
}
|
||||
return a.EvaluatePod(ctx, nsPolicy, nsPolicyErrs.ToAggregate(), podMetadata, podSpec, attrs, false)
|
||||
}
|
||||
@ -443,7 +443,7 @@ func (a *Admission) EvaluatePod(ctx context.Context, nsPolicy api.Policy, nsPoli
|
||||
// short-circuit on exempt runtimeclass
|
||||
if a.exemptRuntimeClass(podSpec.RuntimeClassName) {
|
||||
a.Metrics.RecordExemption(attrs)
|
||||
return sharedAllowedByRuntimeClassExemptionResponse()
|
||||
return sharedAllowedByRuntimeClassExemptionResponse
|
||||
}
|
||||
|
||||
auditAnnotations := map[string]string{}
|
||||
|
@ -602,9 +602,11 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
|
||||
exemptUser = "exempt-user"
|
||||
exemptRuntimeClass = "exempt-runtimeclass"
|
||||
|
||||
podName = "test-pod"
|
||||
)
|
||||
|
||||
objMetadata := metav1.ObjectMeta{Name: "test-pod", Labels: map[string]string{"foo": "bar"}}
|
||||
objMetadata := metav1.ObjectMeta{Name: podName, Labels: map[string]string{"foo": "bar"}}
|
||||
|
||||
restrictedPod, err := test.GetMinimalValidPod(api.LevelRestricted, api.MajorMinorVersion(1, 23))
|
||||
require.NoError(t, err)
|
||||
@ -662,7 +664,7 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
baselineNs: makeNs(api.LevelBaseline, api.LevelBaseline, api.LevelBaseline),
|
||||
baselineWarnNs: makeNs("", api.LevelBaseline, ""),
|
||||
baselineAuditNs: makeNs("", "", api.LevelBaseline),
|
||||
restrictedNs: makeNs(api.LevelRestricted, "", api.LevelRestricted),
|
||||
restrictedNs: makeNs(api.LevelRestricted, api.LevelRestricted, api.LevelRestricted),
|
||||
invalidNs: makeNs("not-a-valid-level", "", ""),
|
||||
}
|
||||
|
||||
@ -675,16 +677,6 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
evaluator, err := policy.NewEvaluator(policy.DefaultChecks())
|
||||
assert.NoError(t, err)
|
||||
|
||||
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
|
||||
|
||||
@ -707,11 +699,14 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
skipPod bool // Whether to skip the ValidatePod test case.
|
||||
skipDeployment bool // Whteher to skip the ValidatePodController test case.
|
||||
|
||||
expectAllowed bool
|
||||
expectReason metav1.StatusReason
|
||||
expectWarning bool
|
||||
expectEnforce bool // Whether to expect an enforcing evaluation (metric+annotation)
|
||||
expectedAuditAnnotationKeys []string
|
||||
expectAllowed bool
|
||||
expectReason metav1.StatusReason
|
||||
expectExempt bool
|
||||
expectError bool
|
||||
|
||||
expectEnforce api.Level
|
||||
expectWarning api.Level
|
||||
expectAudit api.Level
|
||||
}
|
||||
podCases := []testCase{
|
||||
{
|
||||
@ -722,85 +717,87 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
expectAllowed: true,
|
||||
},
|
||||
{
|
||||
desc: "exempt namespace",
|
||||
namespace: exemptNs,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectedAuditAnnotationKeys: []string{"exempt"},
|
||||
desc: "exempt namespace",
|
||||
namespace: exemptNs,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectExempt: true,
|
||||
},
|
||||
{
|
||||
desc: "exempt user",
|
||||
namespace: restrictedNs,
|
||||
username: exemptUser,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectedAuditAnnotationKeys: []string{"exempt"},
|
||||
desc: "exempt user",
|
||||
namespace: restrictedNs,
|
||||
username: exemptUser,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectExempt: true,
|
||||
},
|
||||
{
|
||||
desc: "exempt runtimeClass",
|
||||
namespace: restrictedNs,
|
||||
pod: exemptRCPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectedAuditAnnotationKeys: []string{"exempt"},
|
||||
desc: "exempt runtimeClass",
|
||||
namespace: restrictedNs,
|
||||
pod: exemptRCPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectExempt: true,
|
||||
},
|
||||
{
|
||||
desc: "namespace not found",
|
||||
namespace: "missing-ns",
|
||||
pod: restrictedPod.DeepCopy(),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonInternalError,
|
||||
expectedAuditAnnotationKeys: []string{"error"},
|
||||
desc: "namespace not found",
|
||||
namespace: "missing-ns",
|
||||
pod: restrictedPod.DeepCopy(),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonInternalError,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
desc: "short-circuit privileged:latest (implicit)",
|
||||
namespace: implicitNs,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectEnforce: api.LevelPrivileged,
|
||||
},
|
||||
{
|
||||
desc: "short-circuit privileged:latest (explicit)",
|
||||
namespace: privilegedNs,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectEnforce: api.LevelPrivileged,
|
||||
},
|
||||
{
|
||||
desc: "failed decode",
|
||||
namespace: baselineNs,
|
||||
objErr: fmt.Errorf("expected (failed decode)"),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonBadRequest,
|
||||
expectedAuditAnnotationKeys: []string{"error"},
|
||||
desc: "failed decode",
|
||||
namespace: baselineNs,
|
||||
objErr: fmt.Errorf("expected (failed decode)"),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonBadRequest,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid object",
|
||||
namespace: baselineNs,
|
||||
operation: admissionv1.Update,
|
||||
obj: &corev1.Namespace{},
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonBadRequest,
|
||||
expectedAuditAnnotationKeys: []string{"error"},
|
||||
desc: "invalid object",
|
||||
namespace: baselineNs,
|
||||
operation: admissionv1.Update,
|
||||
obj: &corev1.Namespace{},
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonBadRequest,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
desc: "failed decode old object",
|
||||
namespace: baselineNs,
|
||||
operation: admissionv1.Update,
|
||||
pod: restrictedPod.DeepCopy(),
|
||||
oldObjErr: fmt.Errorf("expected (failed decode)"),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonBadRequest,
|
||||
expectedAuditAnnotationKeys: []string{"error"},
|
||||
skipDeployment: true, // Updates aren't special cased for controller resources.
|
||||
desc: "failed decode old object",
|
||||
namespace: baselineNs,
|
||||
operation: admissionv1.Update,
|
||||
pod: restrictedPod.DeepCopy(),
|
||||
oldObjErr: fmt.Errorf("expected (failed decode)"),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonBadRequest,
|
||||
expectError: true,
|
||||
skipDeployment: true, // Updates aren't special cased for controller resources.
|
||||
},
|
||||
{
|
||||
desc: "invalid old object",
|
||||
namespace: baselineNs,
|
||||
operation: admissionv1.Update,
|
||||
pod: restrictedPod.DeepCopy(),
|
||||
oldObj: &corev1.Namespace{},
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonBadRequest,
|
||||
expectedAuditAnnotationKeys: []string{"error"},
|
||||
skipDeployment: true, // Updates aren't special cased for controller resources.
|
||||
desc: "invalid old object",
|
||||
namespace: baselineNs,
|
||||
operation: admissionv1.Update,
|
||||
pod: restrictedPod.DeepCopy(),
|
||||
oldObj: &corev1.Namespace{},
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonBadRequest,
|
||||
expectError: true,
|
||||
skipDeployment: true, // Updates aren't special cased for controller resources.
|
||||
},
|
||||
{
|
||||
desc: "insignificant update",
|
||||
@ -812,15 +809,16 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
skipDeployment: true, // Updates aren't special cased for controller resources.
|
||||
},
|
||||
{
|
||||
desc: "significant update denied",
|
||||
namespace: restrictedNs,
|
||||
operation: admissionv1.Update,
|
||||
pod: differentPrivilegedPod.DeepCopy(),
|
||||
oldPod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonForbidden,
|
||||
expectEnforce: true,
|
||||
expectedAuditAnnotationKeys: []string{"audit-violations"},
|
||||
desc: "significant update denied",
|
||||
namespace: restrictedNs,
|
||||
operation: admissionv1.Update,
|
||||
pod: differentPrivilegedPod.DeepCopy(),
|
||||
oldPod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonForbidden,
|
||||
expectEnforce: api.LevelRestricted,
|
||||
expectWarning: api.LevelRestricted,
|
||||
expectAudit: api.LevelRestricted,
|
||||
},
|
||||
{
|
||||
desc: "significant update allowed",
|
||||
@ -829,55 +827,56 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
pod: differentRestrictedPod.DeepCopy(),
|
||||
oldPod: restrictedPod,
|
||||
expectAllowed: true,
|
||||
expectEnforce: true,
|
||||
expectEnforce: api.LevelRestricted,
|
||||
},
|
||||
{
|
||||
desc: "invalid namespace labels",
|
||||
namespace: invalidNs,
|
||||
pod: baselinePod.DeepCopy(),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonForbidden,
|
||||
expectEnforce: true,
|
||||
expectedAuditAnnotationKeys: []string{"error"},
|
||||
desc: "invalid namespace labels",
|
||||
namespace: invalidNs,
|
||||
pod: baselinePod.DeepCopy(),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonForbidden,
|
||||
expectEnforce: api.LevelRestricted,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
desc: "enforce deny",
|
||||
namespace: restrictedNs,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonForbidden,
|
||||
expectEnforce: true,
|
||||
expectedAuditAnnotationKeys: []string{"audit-violations"},
|
||||
desc: "enforce deny",
|
||||
namespace: restrictedNs,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: false,
|
||||
expectReason: metav1.StatusReasonForbidden,
|
||||
expectEnforce: api.LevelRestricted,
|
||||
expectWarning: api.LevelRestricted,
|
||||
expectAudit: api.LevelRestricted,
|
||||
},
|
||||
{
|
||||
desc: "enforce allow",
|
||||
namespace: baselineNs,
|
||||
pod: baselinePod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectEnforce: true,
|
||||
expectEnforce: api.LevelBaseline,
|
||||
},
|
||||
{
|
||||
desc: "warn deny",
|
||||
namespace: baselineWarnNs,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectWarning: true,
|
||||
expectEnforce: true,
|
||||
expectEnforce: api.LevelPrivileged,
|
||||
expectWarning: api.LevelBaseline,
|
||||
},
|
||||
{
|
||||
desc: "audit deny",
|
||||
namespace: baselineAuditNs,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectEnforce: true,
|
||||
expectedAuditAnnotationKeys: []string{"audit-violations"},
|
||||
desc: "audit deny",
|
||||
namespace: baselineAuditNs,
|
||||
pod: privilegedPod.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectEnforce: api.LevelPrivileged,
|
||||
expectAudit: api.LevelBaseline,
|
||||
},
|
||||
{
|
||||
desc: "no pod template",
|
||||
namespace: restrictedNs,
|
||||
obj: emptyDeployment.DeepCopy(),
|
||||
expectAllowed: true,
|
||||
expectWarning: false, // No pod template skips validation.
|
||||
expectWarning: "", // No pod template skips validation.
|
||||
skipPod: true,
|
||||
},
|
||||
}
|
||||
@ -904,15 +903,17 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
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")
|
||||
if !tc.expectAllowed {
|
||||
podTest.expectWarning = "" // Warnings should only be returned when the request is allowed.
|
||||
}
|
||||
|
||||
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 // PodController validation is always non-enforcing.
|
||||
// PodController validation is always non-enforcing.
|
||||
deploymentTest.expectAllowed = true
|
||||
deploymentTest.expectEnforce = ""
|
||||
deploymentTest.expectReason = ""
|
||||
|
||||
if tc.pod != nil {
|
||||
@ -961,8 +962,21 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
attrs.Username = tc.username
|
||||
}
|
||||
|
||||
recorder := &FakeRecorder{}
|
||||
a := &Admission{
|
||||
PodLister: &testPodLister{},
|
||||
Evaluator: evaluator,
|
||||
Configuration: config,
|
||||
Metrics: recorder,
|
||||
NamespaceGetter: nsGetter,
|
||||
}
|
||||
require.NoError(t, a.CompleteConfiguration(), "CompleteConfiguration()")
|
||||
require.NoError(t, a.ValidateConfiguration(), "ValidateConfiguration()")
|
||||
|
||||
response := a.Validate(context.TODO(), attrs)
|
||||
|
||||
var expectedEvaluations []MetricsRecord
|
||||
var expectedAuditAnnotationKeys []string
|
||||
if tc.expectAllowed {
|
||||
assert.True(t, response.Allowed, "Allowed")
|
||||
assert.Nil(t, response.Result)
|
||||
@ -973,42 +987,72 @@ func TestValidatePodAndController(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if tc.expectWarning {
|
||||
if tc.expectWarning != "" {
|
||||
assert.NotEmpty(t, response.Warnings, "Warnings")
|
||||
} else {
|
||||
assert.Empty(t, response.Warnings, "Warnings")
|
||||
}
|
||||
|
||||
assert.Len(t, response.AuditAnnotations, len(tc.expectedAuditAnnotationKeys), "AuditAnnotations")
|
||||
for _, key := range tc.expectedAuditAnnotationKeys {
|
||||
if tc.expectEnforce != "" {
|
||||
expectedAuditAnnotationKeys = append(expectedAuditAnnotationKeys, "enforce-policy")
|
||||
record := MetricsRecord{podName, metrics.DecisionAllow, tc.expectEnforce, metrics.ModeEnforce}
|
||||
if !tc.expectAllowed {
|
||||
record.EvalDecision = metrics.DecisionDeny
|
||||
}
|
||||
expectedEvaluations = append(expectedEvaluations, record)
|
||||
}
|
||||
if tc.expectWarning != "" {
|
||||
expectedEvaluations = append(expectedEvaluations, MetricsRecord{podName, metrics.DecisionDeny, tc.expectWarning, metrics.ModeWarn})
|
||||
}
|
||||
if tc.expectAudit != "" {
|
||||
expectedEvaluations = append(expectedEvaluations, MetricsRecord{podName, metrics.DecisionDeny, tc.expectAudit, metrics.ModeAudit})
|
||||
expectedAuditAnnotationKeys = append(expectedAuditAnnotationKeys, "audit-violations")
|
||||
}
|
||||
if tc.expectError {
|
||||
expectedAuditAnnotationKeys = append(expectedAuditAnnotationKeys, "error")
|
||||
assert.ElementsMatch(t, []MetricsRecord{{ObjectName: podName}}, recorder.errors, "expected RecordError() calls")
|
||||
} else {
|
||||
assert.Empty(t, recorder.errors, "expected RecordError() calls")
|
||||
}
|
||||
if tc.expectExempt {
|
||||
expectedAuditAnnotationKeys = append(expectedAuditAnnotationKeys, "exempt")
|
||||
assert.ElementsMatch(t, []MetricsRecord{{ObjectName: podName}}, recorder.exemptions, "expected RecordExemption() calls")
|
||||
} else {
|
||||
assert.Empty(t, recorder.exemptions, "expected RecordExemption() calls")
|
||||
}
|
||||
|
||||
assert.Len(t, response.AuditAnnotations, len(expectedAuditAnnotationKeys), "AuditAnnotations")
|
||||
for _, key := range expectedAuditAnnotationKeys {
|
||||
assert.Contains(t, response.AuditAnnotations, key, "AuditAnnotations")
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, expectedEvaluations, recorder.evaluations, "expected RecordEvaluation() calls")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type FakeRecorder struct {
|
||||
evaluations []EvaluationRecord
|
||||
evaluations []MetricsRecord
|
||||
exemptions []MetricsRecord
|
||||
errors []MetricsRecord
|
||||
}
|
||||
|
||||
type EvaluationRecord struct {
|
||||
ObjectName string
|
||||
Decision metrics.Decision
|
||||
Policy api.LevelVersion
|
||||
Mode metrics.Mode
|
||||
type MetricsRecord struct {
|
||||
ObjectName string
|
||||
EvalDecision metrics.Decision
|
||||
EvalPolicy api.Level
|
||||
EvalMode metrics.Mode
|
||||
}
|
||||
|
||||
func (r *FakeRecorder) RecordEvaluation(decision metrics.Decision, policy api.LevelVersion, evalMode metrics.Mode, attrs api.Attributes) {
|
||||
r.evaluations = append(r.evaluations, EvaluationRecord{attrs.GetName(), decision, policy, evalMode})
|
||||
r.evaluations = append(r.evaluations, MetricsRecord{attrs.GetName(), decision, policy.Level, evalMode})
|
||||
}
|
||||
|
||||
func (r *FakeRecorder) RecordExemption(api.Attributes) {}
|
||||
func (r *FakeRecorder) RecordError(bool, api.Attributes) {}
|
||||
|
||||
// ExpectEvaluation asserts that the evaluation was recorded, and clears the record.
|
||||
func (r *FakeRecorder) ExpectEvaluations(t *testing.T, expected []EvaluationRecord) {
|
||||
t.Helper()
|
||||
assert.ElementsMatch(t, expected, r.evaluations)
|
||||
func (r *FakeRecorder) RecordExemption(attrs api.Attributes) {
|
||||
r.exemptions = append(r.exemptions, MetricsRecord{ObjectName: attrs.GetName()})
|
||||
}
|
||||
func (r *FakeRecorder) RecordError(_ bool, attrs api.Attributes) {
|
||||
r.errors = append(r.errors, MetricsRecord{ObjectName: attrs.GetName()})
|
||||
}
|
||||
|
||||
func TestPrioritizePods(t *testing.T) {
|
||||
|
@ -21,15 +21,30 @@ import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
sharedCopy := sharedAllowedResponse().DeepCopy()
|
||||
sharedResponses := map[string]*admissionv1.AdmissionResponse{
|
||||
"sharedAllowedResponse": sharedAllowedResponse,
|
||||
"sharedAllowedPrivilegedResponse": sharedAllowedPrivilegedResponse,
|
||||
"sharedAllowedByUserExemptionResponse": sharedAllowedByUserExemptionResponse,
|
||||
"sharedAllowedByNamespaceExemptionResponse": sharedAllowedByNamespaceExemptionResponse,
|
||||
"sharedAllowedByRuntimeClassExemptionResponse": sharedAllowedByRuntimeClassExemptionResponse,
|
||||
}
|
||||
sharedResponseCopies := map[string]*admissionv1.AdmissionResponse{}
|
||||
for name, response := range sharedResponses {
|
||||
sharedResponseCopies[name] = response.DeepCopy()
|
||||
}
|
||||
|
||||
rc := m.Run()
|
||||
|
||||
if !reflect.DeepEqual(sharedCopy, sharedAllowedResponse()) {
|
||||
fmt.Println("sharedAllowedReponse mutated")
|
||||
rc = 1
|
||||
for name := range sharedResponses {
|
||||
if !reflect.DeepEqual(sharedResponseCopies[name], sharedResponses[name]) {
|
||||
fmt.Fprintf(os.Stderr, "%s mutated\n", name)
|
||||
rc = 1
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(rc)
|
||||
|
@ -27,26 +27,20 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
_sharedAllowedResponse = allowedResponse()
|
||||
_sharedAllowedByUserExemptionResponse = allowedByExemptResponse("user")
|
||||
_sharedAllowedByNamespaceExemptionResponse = allowedByExemptResponse("namespace")
|
||||
_sharedAllowedByRuntimeClassExemptionResponse = allowedByExemptResponse("runtimeClass")
|
||||
sharedAllowedResponse = allowedResponse()
|
||||
sharedAllowedPrivilegedResponse = allowedResponse()
|
||||
sharedAllowedByUserExemptionResponse = allowedResponse()
|
||||
sharedAllowedByNamespaceExemptionResponse = allowedResponse()
|
||||
sharedAllowedByRuntimeClassExemptionResponse = allowedResponse()
|
||||
)
|
||||
|
||||
func sharedAllowedResponse() *admissionv1.AdmissionResponse {
|
||||
return _sharedAllowedResponse
|
||||
}
|
||||
|
||||
func sharedAllowedByUserExemptionResponse() *admissionv1.AdmissionResponse {
|
||||
return _sharedAllowedByUserExemptionResponse
|
||||
}
|
||||
|
||||
func sharedAllowedByNamespaceExemptionResponse() *admissionv1.AdmissionResponse {
|
||||
return _sharedAllowedByNamespaceExemptionResponse
|
||||
}
|
||||
|
||||
func sharedAllowedByRuntimeClassExemptionResponse() *admissionv1.AdmissionResponse {
|
||||
return _sharedAllowedByRuntimeClassExemptionResponse
|
||||
func init() {
|
||||
sharedAllowedPrivilegedResponse.AuditAnnotations = map[string]string{
|
||||
api.EnforcedPolicyAnnotationKey: api.LevelVersion{Level: api.LevelPrivileged, Version: api.LatestVersion()}.String(),
|
||||
}
|
||||
sharedAllowedByUserExemptionResponse.AuditAnnotations = map[string]string{api.ExemptionReasonAnnotationKey: "user"}
|
||||
sharedAllowedByNamespaceExemptionResponse.AuditAnnotations = map[string]string{api.ExemptionReasonAnnotationKey: "namespace"}
|
||||
sharedAllowedByRuntimeClassExemptionResponse.AuditAnnotations = map[string]string{api.ExemptionReasonAnnotationKey: "runtimeClass"}
|
||||
}
|
||||
|
||||
// allowedResponse is the response used when the admission decision is allow.
|
||||
@ -54,13 +48,6 @@ func allowedResponse() *admissionv1.AdmissionResponse {
|
||||
return &admissionv1.AdmissionResponse{Allowed: true}
|
||||
}
|
||||
|
||||
func allowedByExemptResponse(exemptionReason string) *admissionv1.AdmissionResponse {
|
||||
return &admissionv1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
AuditAnnotations: map[string]string{api.ExemptionReasonAnnotationKey: exemptionReason},
|
||||
}
|
||||
}
|
||||
|
||||
// forbiddenResponse is the response used when the admission decision is deny for policy violations.
|
||||
func forbiddenResponse(attrs api.Attributes, err error) *admissionv1.AdmissionResponse {
|
||||
return &admissionv1.AdmissionResponse{
|
||||
|
Loading…
Reference in New Issue
Block a user