Skip to content

Commit 555a3bb

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 b508091 commit 555a3bb

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

test/e2e/dra/dra.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ import (
3232
"github.com/onsi/gomega/gstruct"
3333
"github.com/onsi/gomega/types"
3434

35+
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
3536
v1 "k8s.io/api/core/v1"
3637
resourceapi "k8s.io/api/resource/v1alpha3"
3738
apierrors "k8s.io/apimachinery/pkg/api/errors"
3839
"k8s.io/apimachinery/pkg/api/resource"
3940
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4041
"k8s.io/apimachinery/pkg/labels"
4142
"k8s.io/apimachinery/pkg/runtime"
43+
applyv1 "k8s.io/client-go/applyconfigurations/core/v1"
4244
"k8s.io/client-go/kubernetes"
4345
"k8s.io/dynamic-resource-allocation/controller"
4446
"k8s.io/klog/v2"
@@ -56,6 +58,9 @@ const (
5658
podStartTimeout = 5 * time.Minute
5759
)
5860

61+
//go:embed test-driver/deploy/example/admin-access-policy.yaml
62+
var adminAccessPolicyYAML string
63+
5964
// networkResources can be passed to NewDriver directly.
6065
func networkResources() app.Resources {
6166
return app.Resources{}
@@ -966,6 +971,63 @@ var _ = framework.SIGDescribe("node")("DRA", feature.DynamicResourceAllocation,
966971
driver := NewDriver(f, nodes, networkResources)
967972
b := newBuilder(f, driver)
968973

974+
ginkgo.It("support validating admission policy for admin access", func(ctx context.Context) {
975+
// Create VAP, after making it unique to the current test.
976+
adminAccessPolicyYAML := strings.ReplaceAll(adminAccessPolicyYAML, "dra.example.com", b.f.UniqueName)
977+
driver.createFromYAML(ctx, []byte(adminAccessPolicyYAML), "")
978+
979+
// Wait for both VAPs to be processed. This ensures that there are no check errors in the status.
980+
matchStatus := gomega.Equal(admissionregistrationv1.ValidatingAdmissionPolicyStatus{ObservedGeneration: 1, TypeChecking: &admissionregistrationv1.TypeChecking{}})
981+
gomega.Eventually(ctx, framework.ListObjects(b.f.ClientSet.AdmissionregistrationV1().ValidatingAdmissionPolicies().List, metav1.ListOptions{})).Should(gomega.HaveField("Items", gomega.ContainElements(
982+
gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
983+
"ObjectMeta": gomega.HaveField("Name", "resourceclaim-policy."+b.f.UniqueName),
984+
"Status": matchStatus,
985+
}),
986+
gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
987+
"ObjectMeta": gomega.HaveField("Name", "resourceclaimtemplate-policy."+b.f.UniqueName),
988+
"Status": matchStatus,
989+
}),
990+
)))
991+
992+
// Attempt to create claim and claim template with admin access. Must fail eventually.
993+
claim := b.externalClaim()
994+
claim.Spec.Devices.Requests[0].AdminAccess = true
995+
_, claimTemplate := b.podInline()
996+
claimTemplate.Spec.Spec.Devices.Requests[0].AdminAccess = true
997+
matchVAPError := gomega.MatchError(gomega.ContainSubstring("admin access to devices not enabled" /* in namespace " + b.f.Namespace.Name */))
998+
gomega.Eventually(ctx, func(ctx context.Context) error {
999+
// First delete, in case that it succeeded earlier.
1000+
if err := b.f.ClientSet.ResourceV1alpha3().ResourceClaims(b.f.Namespace.Name).Delete(ctx, claim.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
1001+
return err
1002+
}
1003+
_, err := b.f.ClientSet.ResourceV1alpha3().ResourceClaims(b.f.Namespace.Name).Create(ctx, claim, metav1.CreateOptions{})
1004+
return err
1005+
}).Should(matchVAPError)
1006+
1007+
gomega.Eventually(ctx, func(ctx context.Context) error {
1008+
// First delete, in case that it succeeded earlier.
1009+
if err := b.f.ClientSet.ResourceV1alpha3().ResourceClaimTemplates(b.f.Namespace.Name).Delete(ctx, claimTemplate.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
1010+
return err
1011+
}
1012+
_, err := b.f.ClientSet.ResourceV1alpha3().ResourceClaimTemplates(b.f.Namespace.Name).Create(ctx, claimTemplate, metav1.CreateOptions{})
1013+
return err
1014+
}).Should(matchVAPError)
1015+
1016+
// After labeling the namespace, creation must (eventually...) succeed.
1017+
_, err := b.f.ClientSet.CoreV1().Namespaces().Apply(ctx,
1018+
applyv1.Namespace(b.f.Namespace.Name).WithLabels(map[string]string{"admin-access." + b.f.UniqueName: "on"}),
1019+
metav1.ApplyOptions{FieldManager: b.f.UniqueName})
1020+
framework.ExpectNoError(err)
1021+
gomega.Eventually(ctx, func(ctx context.Context) error {
1022+
_, err := b.f.ClientSet.ResourceV1alpha3().ResourceClaims(b.f.Namespace.Name).Create(ctx, claim, metav1.CreateOptions{})
1023+
return err
1024+
}).Should(gomega.Succeed())
1025+
gomega.Eventually(ctx, func(ctx context.Context) error {
1026+
_, err := b.f.ClientSet.ResourceV1alpha3().ResourceClaimTemplates(b.f.Namespace.Name).Create(ctx, claimTemplate, metav1.CreateOptions{})
1027+
return err
1028+
}).Should(gomega.Succeed())
1029+
})
1030+
9691031
ginkgo.It("truncates the name of a generated resource claim", func(ctx context.Context) {
9701032
pod, template := b.podInline()
9711033
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)