k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/certificates/signing/admission_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package signing
    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: "status",
    45  				oldObj:      &certificatesapi.CertificateSigningRequestList{},
    46  				obj:         &certificatesapi.CertificateSigningRequestList{},
    47  				operation:   admission.Update,
    48  			},
    49  			allowed: false,
    50  		},
    51  		"allowed if the 'certificate' and conditions field has not changed": {
    52  			attributes: &testAttributes{
    53  				resource:    certificatesapi.Resource("certificatesigningrequests"),
    54  				subresource: "status",
    55  				oldObj: &certificatesapi.CertificateSigningRequest{Status: certificatesapi.CertificateSigningRequestStatus{
    56  					Certificate: []byte("data"),
    57  				}},
    58  				obj: &certificatesapi.CertificateSigningRequest{Status: certificatesapi.CertificateSigningRequestStatus{
    59  					Certificate: []byte("data"),
    60  				}},
    61  				operation: admission.Update,
    62  			},
    63  			allowed:  true,
    64  			authzErr: errors.New("faked error"),
    65  		},
    66  		"deny request if authz lookup fails on certificate change": {
    67  			allowedName: "abc.com/xyz",
    68  			attributes: &testAttributes{
    69  				resource:    certificatesapi.Resource("certificatesigningrequests"),
    70  				subresource: "status",
    71  				oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
    72  					SignerName: "abc.com/xyz",
    73  				}},
    74  				obj: &certificatesapi.CertificateSigningRequest{
    75  					Spec: certificatesapi.CertificateSigningRequestSpec{
    76  						SignerName: "abc.com/xyz",
    77  					},
    78  					Status: certificatesapi.CertificateSigningRequestStatus{
    79  						Certificate: []byte("data"),
    80  					},
    81  				},
    82  				operation: admission.Update,
    83  			},
    84  			authzErr: errors.New("test"),
    85  			allowed:  false,
    86  		},
    87  		"deny request if authz lookup fails on condition change": {
    88  			allowedName: "abc.com/xyz",
    89  			attributes: &testAttributes{
    90  				resource:    certificatesapi.Resource("certificatesigningrequests"),
    91  				subresource: "status",
    92  				oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
    93  					SignerName: "abc.com/xyz",
    94  				}},
    95  				obj: &certificatesapi.CertificateSigningRequest{
    96  					Spec: certificatesapi.CertificateSigningRequestSpec{
    97  						SignerName: "abc.com/xyz",
    98  					},
    99  					Status: certificatesapi.CertificateSigningRequestStatus{
   100  						Conditions: []certificatesapi.CertificateSigningRequestCondition{{Type: certificatesapi.CertificateFailed}},
   101  					},
   102  				},
   103  				operation: admission.Update,
   104  			},
   105  			authzErr: errors.New("test"),
   106  			allowed:  false,
   107  		},
   108  		"allow request if user is authorized for specific signerName": {
   109  			allowedName: "abc.com/xyz",
   110  			attributes: &testAttributes{
   111  				resource:    certificatesapi.Resource("certificatesigningrequests"),
   112  				subresource: "status",
   113  				oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
   114  					SignerName: "abc.com/xyz",
   115  				}},
   116  				obj: &certificatesapi.CertificateSigningRequest{
   117  					Spec: certificatesapi.CertificateSigningRequestSpec{
   118  						SignerName: "abc.com/xyz",
   119  					},
   120  					Status: certificatesapi.CertificateSigningRequestStatus{
   121  						Certificate: []byte("data"),
   122  					},
   123  				},
   124  				operation: admission.Update,
   125  			},
   126  			allowed: true,
   127  		},
   128  		"allow request if user is authorized with wildcard": {
   129  			allowedName: "abc.com/*",
   130  			attributes: &testAttributes{
   131  				resource:    certificatesapi.Resource("certificatesigningrequests"),
   132  				subresource: "status",
   133  				oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
   134  					SignerName: "abc.com/xyz",
   135  				}},
   136  				obj: &certificatesapi.CertificateSigningRequest{
   137  					Spec: certificatesapi.CertificateSigningRequestSpec{
   138  						SignerName: "abc.com/xyz",
   139  					},
   140  					Status: certificatesapi.CertificateSigningRequestStatus{
   141  						Certificate: []byte("data"),
   142  					},
   143  				},
   144  				operation: admission.Update,
   145  			},
   146  			allowed: true,
   147  		},
   148  		"should deny request if user does not have permission for this signerName": {
   149  			allowedName: "notabc.com/xyz",
   150  			attributes: &testAttributes{
   151  				resource:    certificatesapi.Resource("certificatesigningrequests"),
   152  				subresource: "status",
   153  				oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
   154  					SignerName: "abc.com/xyz",
   155  				}},
   156  				obj: &certificatesapi.CertificateSigningRequest{
   157  					Spec: certificatesapi.CertificateSigningRequestSpec{
   158  						SignerName: "abc.com/xyz",
   159  					},
   160  					Status: certificatesapi.CertificateSigningRequestStatus{
   161  						Certificate: []byte("data"),
   162  					},
   163  				},
   164  				operation: admission.Update,
   165  			},
   166  			allowed: false,
   167  		},
   168  		"should deny request if user attempts to update signerName to a new value they *do* have permission to sign for": {
   169  			allowedName: "allowed.com/xyz",
   170  			attributes: &testAttributes{
   171  				resource:    certificatesapi.Resource("certificatesigningrequests"),
   172  				subresource: "status",
   173  				oldObj: &certificatesapi.CertificateSigningRequest{Spec: certificatesapi.CertificateSigningRequestSpec{
   174  					SignerName: "notallowed.com/xyz",
   175  				}},
   176  				obj: &certificatesapi.CertificateSigningRequest{
   177  					Spec: certificatesapi.CertificateSigningRequestSpec{
   178  						SignerName: "allowed.com/xyz",
   179  					},
   180  					Status: certificatesapi.CertificateSigningRequestStatus{
   181  						Certificate: []byte("data"),
   182  					},
   183  				},
   184  				operation: admission.Update,
   185  			},
   186  			allowed: false,
   187  		},
   188  	}
   189  
   190  	for n, test := range tests {
   191  		t.Run(n, func(t *testing.T) {
   192  			p := Plugin{
   193  				authz: fakeAuthorizer{
   194  					t:           t,
   195  					verb:        "sign",
   196  					allowedName: test.allowedName,
   197  					decision:    authorizer.DecisionAllow,
   198  					err:         test.authzErr,
   199  				},
   200  			}
   201  			err := p.Validate(context.Background(), test.attributes, nil)
   202  			if err == nil && !test.allowed {
   203  				t.Errorf("Expected authorization policy to reject CSR but it was allowed")
   204  			}
   205  			if err != nil && test.allowed {
   206  				t.Errorf("Expected authorization policy to accept CSR but it was rejected: %v", err)
   207  			}
   208  		})
   209  	}
   210  }
   211  
   212  type fakeAuthorizer struct {
   213  	t           *testing.T
   214  	verb        string
   215  	allowedName string
   216  	decision    authorizer.Decision
   217  	err         error
   218  }
   219  
   220  func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
   221  	if f.err != nil {
   222  		return f.decision, "forced error", f.err
   223  	}
   224  	if a.GetVerb() != f.verb {
   225  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised verb '%s'", a.GetVerb()), nil
   226  	}
   227  	if a.GetAPIGroup() != "certificates.k8s.io" {
   228  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised groupName '%s'", a.GetAPIGroup()), nil
   229  	}
   230  	if a.GetAPIVersion() != "*" {
   231  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised apiVersion '%s'", a.GetAPIVersion()), nil
   232  	}
   233  	if a.GetResource() != "signers" {
   234  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource '%s'", a.GetResource()), nil
   235  	}
   236  	if a.GetName() != f.allowedName {
   237  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource name '%s'", a.GetName()), nil
   238  	}
   239  	if !a.IsResourceRequest() {
   240  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised IsResourceRequest '%t'", a.IsResourceRequest()), nil
   241  	}
   242  	return f.decision, "", nil
   243  }
   244  
   245  type testAttributes struct {
   246  	resource    schema.GroupResource
   247  	subresource string
   248  	operation   admission.Operation
   249  	oldObj, obj runtime.Object
   250  	name        string
   251  
   252  	admission.Attributes // nil panic if any other methods called
   253  }
   254  
   255  func (t *testAttributes) GetResource() schema.GroupVersionResource {
   256  	return t.resource.WithVersion("ignored")
   257  }
   258  
   259  func (t *testAttributes) GetSubresource() string {
   260  	return t.subresource
   261  }
   262  
   263  func (t *testAttributes) GetOldObject() runtime.Object {
   264  	return t.oldObj
   265  }
   266  
   267  func (t *testAttributes) GetObject() runtime.Object {
   268  	return t.obj
   269  }
   270  
   271  func (t *testAttributes) GetName() string {
   272  	return t.name
   273  }
   274  
   275  func (t *testAttributes) GetOperation() admission.Operation {
   276  	return t.operation
   277  }
   278  
   279  func (t *testAttributes) GetUserInfo() user.Info {
   280  	return &user.DefaultInfo{Name: "ignored"}
   281  }