Skip to content

Commit 7c1e1d3

Browse files
committed
DRA e2e: use VAP to control "admin access" permissions
The advantages of using a validation admission policy (VAP) are that no changes are needed in Kubernetes and that admins have full flexibility if and how they want to control which users are allowed to use "admin access" in their requests. The downside is that without admins taking actions, the feature is enabled out-of-the-box in a cluster. Documentation for DRA will have to make it very clear that something needs to be done in multi-tenant clusters. The test/e2e/testing-manifests/dra/admin-access-policy.yaml shows how to do this. The corresponding E2E tests ensures that it actually works as intended. For some reason, adding the namespace to the message expression leads to a type check errors, so it's currently commented out.
1 parent a2e5c39 commit 7c1e1d3

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

test/e2e/dra/dra.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ import (
2929
"github.com/onsi/gomega"
3030
"github.com/onsi/gomega/gstruct"
3131

32+
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
3233
v1 "k8s.io/api/core/v1"
3334
resourceapi "k8s.io/api/resource/v1alpha3"
3435
apierrors "k8s.io/apimachinery/pkg/api/errors"
3536
"k8s.io/apimachinery/pkg/api/resource"
3637
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3738
"k8s.io/apimachinery/pkg/labels"
3839
"k8s.io/apimachinery/pkg/runtime"
40+
applyv1 "k8s.io/client-go/applyconfigurations/core/v1"
3941
"k8s.io/client-go/kubernetes"
4042
"k8s.io/dynamic-resource-allocation/controller"
4143
"k8s.io/klog/v2"
@@ -862,6 +864,63 @@ var _ = framework.SIGDescribe("node")("DRA", feature.DynamicResourceAllocation,
862864
driver := NewDriver(f, nodes, networkResources)
863865
b := newBuilder(f, driver)
864866

867+
ginkgo.It("support validating admission policy for admin access", func(ctx context.Context) {
868+
// Create VAP, after making it unique to the current test.
869+
adminAccessPolicyYAML := strings.ReplaceAll(adminAccessPolicyYAML, "dra.example.com", b.f.UniqueName)
870+
driver.createFromYAML(ctx, []byte(adminAccessPolicyYAML), "")
871+
872+
// Wait for both VAPs to be processed. This ensures that there are no check errors in the status.
873+
matchStatus := gomega.Equal(admissionregistrationv1.ValidatingAdmissionPolicyStatus{ObservedGeneration: 1, TypeChecking: &admissionregistrationv1.TypeChecking{}})
874+
gomega.Eventually(ctx, framework.ListObjects(b.f.ClientSet.AdmissionregistrationV1().ValidatingAdmissionPolicies().List, metav1.ListOptions{})).Should(gomega.HaveField("Items", gomega.ContainElements(
875+
gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
876+
"ObjectMeta": gomega.HaveField("Name", "resourceclaim-policy."+b.f.UniqueName),
877+
"Status": matchStatus,
878+
}),
879+
gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
880+
"ObjectMeta": gomega.HaveField("Name", "resourceclaimtemplate-policy."+b.f.UniqueName),
881+
"Status": matchStatus,
882+
}),
883+
)))
884+
885+
// Attempt to create claim and claim template with admin access. Must fail eventually.
886+
claim := b.externalClaim()
887+
claim.Spec.Devices.Requests[0].AdminAccess = true
888+
_, claimTemplate := b.podInline()
889+
claimTemplate.Spec.Spec.Devices.Requests[0].AdminAccess = true
890+
matchVAPError := gomega.MatchError(gomega.ContainSubstring("admin access to devices not enabled" /* in namespace " + b.f.Namespace.Name */))
891+
gomega.Eventually(ctx, func(ctx context.Context) error {
892+
// First delete, in case that it succeeded earlier.
893+
if err := b.f.ClientSet.ResourceV1alpha3().ResourceClaims(b.f.Namespace.Name).Delete(ctx, claim.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
894+
return err
895+
}
896+
_, err := b.f.ClientSet.ResourceV1alpha3().ResourceClaims(b.f.Namespace.Name).Create(ctx, claim, metav1.CreateOptions{})
897+
return err
898+
}).Should(matchVAPError)
899+
900+
gomega.Eventually(ctx, func(ctx context.Context) error {
901+
// First delete, in case that it succeeded earlier.
902+
if err := b.f.ClientSet.ResourceV1alpha3().ResourceClaimTemplates(b.f.Namespace.Name).Delete(ctx, claimTemplate.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
903+
return err
904+
}
905+
_, err := b.f.ClientSet.ResourceV1alpha3().ResourceClaimTemplates(b.f.Namespace.Name).Create(ctx, claimTemplate, metav1.CreateOptions{})
906+
return err
907+
}).Should(matchVAPError)
908+
909+
// After labeling the namespace, creation must (eventually...) succeed.
910+
_, err := b.f.ClientSet.CoreV1().Namespaces().Apply(ctx,
911+
applyv1.Namespace(b.f.Namespace.Name).WithLabels(map[string]string{"admin-access." + b.f.UniqueName: "on"}),
912+
metav1.ApplyOptions{FieldManager: b.f.UniqueName})
913+
framework.ExpectNoError(err)
914+
gomega.Eventually(ctx, func(ctx context.Context) error {
915+
_, err := b.f.ClientSet.ResourceV1alpha3().ResourceClaims(b.f.Namespace.Name).Create(ctx, claim, metav1.CreateOptions{})
916+
return err
917+
}).Should(gomega.Succeed())
918+
gomega.Eventually(ctx, func(ctx context.Context) error {
919+
_, err := b.f.ClientSet.ResourceV1alpha3().ResourceClaimTemplates(b.f.Namespace.Name).Create(ctx, claimTemplate, metav1.CreateOptions{})
920+
return err
921+
}).Should(gomega.Succeed())
922+
})
923+
865924
ginkgo.It("truncates the name of a generated resource claim", func(ctx context.Context) {
866925
pod, template := b.podInline()
867926
pod.Name = strings.Repeat("p", 63)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# This example shows how to use a validating admission policy (VAP)
2+
# to control who may use "admin access", a privileged mode which
3+
# grants access to devices which are currently in use, potentially
4+
# by some other user.
5+
#
6+
# The policy applies in any namespace which does not have the
7+
# "admin-access.dra.example.com" label. Other ways of making that decision are
8+
# also possible.
9+
#
10+
# Cluster administrators need to adapt at least the names and replace
11+
# "dra.example.com".
12+
13+
apiVersion: admissionregistration.k8s.io/v1
14+
kind: ValidatingAdmissionPolicy
15+
metadata:
16+
name: resourceclaim-policy.dra.example.com
17+
spec:
18+
failurePolicy: Fail
19+
matchConstraints:
20+
resourceRules:
21+
- apiGroups: ["resource.k8s.io"]
22+
apiVersions: ["v1alpha3"]
23+
operations: ["CREATE", "UPDATE"]
24+
resources: ["resourceclaims"]
25+
validations:
26+
- expression: '! object.spec.devices.requests.exists(e, has(e.adminAccess) && e.adminAccess)'
27+
reason: Forbidden
28+
messageExpression: '"admin access to devices not enabled"' # in namespace " + object.metadata.namespace' - need to use __namespace__, but somehow that also doesn't work.
29+
---
30+
apiVersion: admissionregistration.k8s.io/v1
31+
kind: ValidatingAdmissionPolicyBinding
32+
metadata:
33+
name: resourceclaim-binding.dra.example.com
34+
spec:
35+
policyName: resourceclaim-policy.dra.example.com
36+
validationActions: [Deny]
37+
matchResources:
38+
namespaceSelector:
39+
matchExpressions:
40+
- key: admin-access.dra.example.com
41+
operator: DoesNotExist
42+
---
43+
apiVersion: admissionregistration.k8s.io/v1
44+
kind: ValidatingAdmissionPolicy
45+
metadata:
46+
name: resourceclaimtemplate-policy.dra.example.com
47+
spec:
48+
failurePolicy: Fail
49+
matchConstraints:
50+
resourceRules:
51+
- apiGroups: ["resource.k8s.io"]
52+
apiVersions: ["v1alpha3"]
53+
operations: ["CREATE", "UPDATE"]
54+
resources: ["resourceclaimtemplates"]
55+
validations:
56+
- expression: '! object.spec.spec.devices.requests.exists(e, has(e.adminAccess) && e.adminAccess)'
57+
reason: Forbidden
58+
messageExpression: '"admin access to devices not enabled"' # in namespace " + object.metadata.namespace' - need to use __namespace__, but somehow that also doesn't work.
59+
---
60+
apiVersion: admissionregistration.k8s.io/v1
61+
kind: ValidatingAdmissionPolicyBinding
62+
metadata:
63+
name: resourceclaimtemplate-binding.dra.example.com
64+
spec:
65+
policyName: resourceclaimtemplate-policy.dra.example.com
66+
validationActions: [Deny]
67+
matchResources:
68+
namespaceSelector:
69+
matchExpressions:
70+
- key: admin-access.dra.example.com
71+
operator: DoesNotExist

0 commit comments

Comments
 (0)