1
16
17 package approval
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "testing"
24
25 "k8s.io/apimachinery/pkg/runtime"
26 "k8s.io/apimachinery/pkg/runtime/schema"
27 "k8s.io/apiserver/pkg/admission"
28 "k8s.io/apiserver/pkg/authentication/user"
29 "k8s.io/apiserver/pkg/authorization/authorizer"
30
31 certificatesapi "k8s.io/kubernetes/pkg/apis/certificates"
32 )
33
34 func TestPlugin_Validate(t *testing.T) {
35 tests := map[string]struct {
36 attributes admission.Attributes
37 allowedName string
38 allowed bool
39 authzErr error
40 }{
41 "wrong type": {
42 attributes: &testAttributes{
43 resource: certificatesapi.Resource("certificatesigningrequests"),
44 subresource: "approval",
45 oldObj: &certificatesapi.CertificateSigningRequestList{},
46 operation: admission.Update,
47 },
48 allowed: false,
49 },
50 "reject requests if looking up permissions fails": {
51 attributes: &testAttributes{
52 resource: certificatesapi.Resource("certificatesigningrequests"),
53 subresource: "approval",
54 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
55 SignerName: "abc.com/xyz",
56 }},
57 operation: admission.Update,
58 },
59 authzErr: errors.New("forced error"),
60 allowed: false,
61 },
62 "should allow request if user is authorized for specific signerName": {
63 allowedName: "abc.com/xyz",
64 attributes: &testAttributes{
65 resource: certificatesapi.Resource("certificatesigningrequests"),
66 subresource: "approval",
67 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
68 SignerName: "abc.com/xyz",
69 }},
70 operation: admission.Update,
71 },
72 allowed: true,
73 },
74 "should allow request if user is authorized with wildcard": {
75 allowedName: "abc.com/*",
76 attributes: &testAttributes{
77 resource: certificatesapi.Resource("certificatesigningrequests"),
78 subresource: "approval",
79 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
80 SignerName: "abc.com/xyz",
81 }},
82 operation: admission.Update,
83 },
84 allowed: true,
85 },
86 "should deny request if user does not have permission for this signerName": {
87 allowedName: "notabc.com/xyz",
88 attributes: &testAttributes{
89 resource: certificatesapi.Resource("certificatesigningrequests"),
90 subresource: "approval",
91 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
92 SignerName: "abc.com/xyz",
93 }},
94 operation: admission.Update,
95 },
96 allowed: false,
97 },
98 "should deny request if user attempts to update signerName to a new value they *do* have permission to approve for": {
99 allowedName: "allowed.com/xyz",
100 attributes: &testAttributes{
101 resource: certificatesapi.Resource("certificatesigningrequests"),
102 subresource: "approval",
103 oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
104 SignerName: "notallowed.com/xyz",
105 }},
106 obj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
107 SignerName: "allowed.com/xyz",
108 }},
109 operation: admission.Update,
110 },
111 allowed: false,
112 },
113 }
114
115 for n, test := range tests {
116 t.Run(n, func(t *testing.T) {
117 p := Plugin{
118 authz: fakeAuthorizer{
119 t: t,
120 verb: "approve",
121 allowedName: test.allowedName,
122 decision: authorizer.DecisionAllow,
123 err: test.authzErr,
124 },
125 }
126 err := p.Validate(context.Background(), test.attributes, nil)
127 if err == nil && !test.allowed {
128 t.Errorf("Expected authorization policy to reject CSR but it was allowed")
129 }
130 if err != nil && test.allowed {
131 t.Errorf("Expected authorization policy to accept CSR but it was rejected: %v", err)
132 }
133 })
134 }
135 }
136
137 type fakeAuthorizer struct {
138 t *testing.T
139 verb string
140 allowedName string
141 decision authorizer.Decision
142 err error
143 }
144
145 func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
146 if f.err != nil {
147 return f.decision, "forced error", f.err
148 }
149 if a.GetVerb() != f.verb {
150 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised verb '%s'", a.GetVerb()), nil
151 }
152 if a.GetAPIGroup() != "certificates.k8s.io" {
153 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised groupName '%s'", a.GetAPIGroup()), nil
154 }
155 if a.GetAPIVersion() != "*" {
156 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised apiVersion '%s'", a.GetAPIVersion()), nil
157 }
158 if a.GetResource() != "signers" {
159 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource '%s'", a.GetResource()), nil
160 }
161 if a.GetName() != f.allowedName {
162 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource name '%s'", a.GetName()), nil
163 }
164 if !a.IsResourceRequest() {
165 return authorizer.DecisionDeny, fmt.Sprintf("unrecognised IsResourceRequest '%t'", a.IsResourceRequest()), nil
166 }
167 return f.decision, "", nil
168 }
169
170 type testAttributes struct {
171 resource schema.GroupResource
172 subresource string
173 operation admission.Operation
174 obj, oldObj runtime.Object
175 name string
176
177 admission.Attributes
178 }
179
180 func (t *testAttributes) GetResource() schema.GroupVersionResource {
181 return t.resource.WithVersion("ignored")
182 }
183
184 func (t *testAttributes) GetSubresource() string {
185 return t.subresource
186 }
187
188 func (t *testAttributes) GetObject() runtime.Object {
189 return t.obj
190 }
191
192 func (t *testAttributes) GetOldObject() runtime.Object {
193 return t.oldObj
194 }
195
196 func (t *testAttributes) GetName() string {
197 return t.name
198 }
199
200 func (t *testAttributes) GetOperation() admission.Operation {
201 return t.operation
202 }
203
204 func (t *testAttributes) GetUserInfo() user.Info {
205 return &user.DefaultInfo{Name: "ignored"}
206 }
207
View as plain text