Skip to content

Commit

Permalink
feat: support deny action in bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
f1ames committed Oct 31, 2023
1 parent 480ac3f commit c991311
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 5 deletions.
27 changes: 23 additions & 4 deletions admission-controller/server/src/utils/validation-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ export class ValidationServer {
}
}

this._logger.trace({resourceForValidation, validationResponses});

if (violations.length === 0) {
this._logger.debug({msg: 'No violations', response});
return response;
}

const violationsByAction = violations.reduce((acc: Record<string, Violation[]>, violation: Violation) => {
const actions = violation.actions;

Expand All @@ -191,7 +198,6 @@ export class ValidationServer {
const responseFull = this._handleViolationsByAction(violationsByAction, resourceForValidation, response);

this._logger.debug({response});
this._logger.trace({resourceForValidation, validationResponses});

return responseFull;
});
Expand Down Expand Up @@ -221,13 +227,27 @@ export class ValidationServer {
// - https://kubernetes.io/blog/2020/09/03/warnings/
if (action.toLowerCase() === 'warn') {
response = this._handleViolationsAsWarn(violationsByAction[action], resource, response);
} else if (action.toLowerCase() === 'deny') {
const violationMessages = this._getViolationsMessages(violationsByAction[action], resource);

response.response.allowed = false;
response.response.status.message = violationMessages.join("\n");
}
}

return response;
}

private _handleViolationsAsWarn(violations: Violation[], resource: Resource, response: AdmissionResponse) {
const violationMessages = this._getViolationsMessages(violations, resource);
if (violationMessages.length > 0) {
response.response.warnings = violationMessages;
}

return response;
}

private _getViolationsMessages(violations: Violation[], resource: Resource): string[] {
const errors = violations
.filter((v) => v.level === 'error')
.map((e) => this._formatViolationMessage(e, resource));
Expand All @@ -236,17 +256,16 @@ export class ValidationServer {
.filter((v) => v.level === 'warning')
.map((e) => this._formatViolationMessage(e, resource));


if (errors.length > 0 || warnings.length > 0) {
response.response.warnings = [
return [
`Monokle Admission Controller found ${errors.length} errors and ${warnings.length} warnings:`,
...errors,
...warnings,
'You can use Monokle Cloud (https://monokle.io/) to fix those errors easily.',
];
}

return response;
return [];
}

private _getFullyQualifiedName(result: ValidationResult) {
Expand Down
14 changes: 14 additions & 0 deletions examples/policy-binding-sample-6.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: monokle.io/v1alpha1
kind: MonoklePolicyBinding
metadata:
name: policy-binding-sample-5
spec:
policyName: "policy-sample-1"
validationActions: [Deny]
matchResources:
namespaceSelector:
matchExpressions:
- key: name
operator: In
values:
- default
2 changes: 1 addition & 1 deletion helm/templates/monokle-policy-binding-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ spec:
type: array
items:
type: string
enum: [Warn]
enum: [Warn, Deny]
matchResources:
type: object
properties:
Expand Down
31 changes: 31 additions & 0 deletions tests/src/standalone.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,36 @@ describe(`Standalone (dir: ${mainDir})`, () => {
assert.equal(warningsCount, 8);
assert.equal(errorsCount, 4);
});

it('creates resource (valid) when "Deny" policy defined', async () => {
await run(`cd "${mainDir}" && kubectl apply -f examples/policy-sample-1.yaml`);
await run(`cd "${mainDir}" && kubectl apply -f examples/policy-binding-sample-6.yaml`, 500);

const output = await run(`cd "${mainDir}" && kubectl -n default apply -f examples/pod-valid.yaml`);

assert.match(output, /pod\/pod-valid created/);
assert.notMatch(output, /\(warning\)/gi);
assert.notMatch(output, /\(error\)/gi);
});

it('blocks creating resource (misconfigured) with list of violations as output when "Deny" policy defined', async () => {
await run(`cd "${mainDir}" && kubectl apply -f examples/policy-sample-1.yaml`);
await run(`cd "${mainDir}" && kubectl apply -f examples/policy-binding-sample-6.yaml`, 500);

try {
const output = await run(`cd "${mainDir}" && kubectl -n default apply -f examples/pod-warning.yaml`);
assert.fail(`Expected error but got success: ${output}`);
} catch (err: any) {
assert.notMatch(err, /pod\/pod-warning created/);
assert.match(err, /\(warning\)/gi);

const warningsCount = (err.match(/\(warning\)/gi) || []).length;
const errorsCount = (err.match(/\(error\)/gi) || []).length;

assert.equal(warningsCount, 8);
assert.equal(errorsCount, 3);
}
});
});

const run = async (command: string, timeoutMs?: number): Promise<string> => {
Expand Down Expand Up @@ -221,6 +251,7 @@ const cleanup = async () => {
run(`cd "${mainDir}" && kubectl delete -f examples/policy-binding-sample-3.yaml`),
run(`cd "${mainDir}" && kubectl delete -f examples/policy-binding-sample-4.yaml`),
run(`cd "${mainDir}" && kubectl delete -f examples/policy-binding-sample-5.yaml`),
run(`cd "${mainDir}" && kubectl delete -f examples/policy-binding-sample-6.yaml`),
run(`cd "${mainDir}" && kubectl delete -f examples/pod-valid.yaml -n default`),
run(`cd "${mainDir}" && kubectl delete -f examples/pod-valid.yaml -n nstest1`),
run(`cd "${mainDir}" && kubectl delete -f examples/pod-valid.yaml -n nstest2`),
Expand Down

0 comments on commit c991311

Please sign in to comment.