...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/certificates/ctbattest/admission_test.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/certificates/ctbattest

     1  /*
     2  Copyright 2022 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 ctbattest
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"testing"
    24  
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	"k8s.io/apiserver/pkg/admission"
    29  	"k8s.io/apiserver/pkg/authentication/user"
    30  	"k8s.io/apiserver/pkg/authorization/authorizer"
    31  	"k8s.io/apiserver/pkg/util/feature"
    32  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    33  	certificatesapi "k8s.io/kubernetes/pkg/apis/certificates"
    34  	"k8s.io/kubernetes/pkg/features"
    35  )
    36  
    37  func TestPluginValidate(t *testing.T) {
    38  	tests := []struct {
    39  		description                      string
    40  		clusterTrustBundleFeatureEnabled bool
    41  		attributes                       admission.Attributes
    42  		allowedName                      string
    43  		allowed                          bool
    44  		authzErr                         error
    45  	}{
    46  		{
    47  			description:                      "wrong type on create",
    48  			clusterTrustBundleFeatureEnabled: true,
    49  			attributes: &testAttributes{
    50  				resource:  certificatesapi.Resource("clustertrustbundles"),
    51  				obj:       &certificatesapi.ClusterTrustBundleList{},
    52  				operation: admission.Create,
    53  			},
    54  			allowed: false,
    55  		},
    56  		{
    57  			description:                      "wrong type on update",
    58  			clusterTrustBundleFeatureEnabled: true,
    59  			attributes: &testAttributes{
    60  				resource:  certificatesapi.Resource("clustertrustbundles"),
    61  				obj:       &certificatesapi.ClusterTrustBundleList{},
    62  				operation: admission.Update,
    63  			},
    64  			allowed: false,
    65  		},
    66  		{
    67  			description:                      "reject requests if looking up permissions fails",
    68  			clusterTrustBundleFeatureEnabled: true,
    69  			attributes: &testAttributes{
    70  				resource: certificatesapi.Resource("clustertrustbundles"),
    71  				obj: &certificatesapi.ClusterTrustBundle{
    72  					Spec: certificatesapi.ClusterTrustBundleSpec{
    73  						SignerName: "abc.com/xyz",
    74  					},
    75  				},
    76  				operation: admission.Update,
    77  			},
    78  			authzErr: errors.New("forced error"),
    79  			allowed:  false,
    80  		},
    81  		{
    82  			description:                      "should allow create if no signer name is specified",
    83  			clusterTrustBundleFeatureEnabled: true,
    84  			allowedName:                      "abc.com/xyz",
    85  			attributes: &testAttributes{
    86  				resource: certificatesapi.Resource("clustertrustbundles"),
    87  				obj: &certificatesapi.ClusterTrustBundle{
    88  					Spec: certificatesapi.ClusterTrustBundleSpec{},
    89  				},
    90  				operation: admission.Create,
    91  			},
    92  			allowed: true,
    93  		},
    94  		{
    95  			description:                      "should allow update if no signer name is specified",
    96  			clusterTrustBundleFeatureEnabled: true,
    97  			allowedName:                      "abc.com/xyz",
    98  			attributes: &testAttributes{
    99  				resource: certificatesapi.Resource("clustertrustbundles"),
   100  				oldObj: &certificatesapi.ClusterTrustBundle{
   101  					Spec: certificatesapi.ClusterTrustBundleSpec{},
   102  				},
   103  				obj: &certificatesapi.ClusterTrustBundle{
   104  					Spec: certificatesapi.ClusterTrustBundleSpec{},
   105  				},
   106  				operation: admission.Update,
   107  			},
   108  			allowed: true,
   109  		},
   110  		{
   111  			description:                      "should allow create if user is authorized for specific signerName",
   112  			clusterTrustBundleFeatureEnabled: true,
   113  			allowedName:                      "abc.com/xyz",
   114  			attributes: &testAttributes{
   115  				resource: certificatesapi.Resource("clustertrustbundles"),
   116  				obj: &certificatesapi.ClusterTrustBundle{
   117  					Spec: certificatesapi.ClusterTrustBundleSpec{
   118  						SignerName: "abc.com/xyz",
   119  					},
   120  				},
   121  				operation: admission.Create,
   122  			},
   123  			allowed: true,
   124  		},
   125  		{
   126  			description:                      "should allow update if user is authorized for specific signerName",
   127  			clusterTrustBundleFeatureEnabled: true,
   128  			allowedName:                      "abc.com/xyz",
   129  			attributes: &testAttributes{
   130  				resource: certificatesapi.Resource("clustertrustbundles"),
   131  				oldObj: &certificatesapi.ClusterTrustBundle{
   132  					Spec: certificatesapi.ClusterTrustBundleSpec{
   133  						SignerName: "abc.com/xyz",
   134  					},
   135  				},
   136  				obj: &certificatesapi.ClusterTrustBundle{
   137  					Spec: certificatesapi.ClusterTrustBundleSpec{
   138  						SignerName: "abc.com/xyz",
   139  					},
   140  				},
   141  				operation: admission.Update,
   142  			},
   143  			allowed: true,
   144  		},
   145  		{
   146  			description:                      "should allow create if user is authorized with wildcard",
   147  			clusterTrustBundleFeatureEnabled: true,
   148  			allowedName:                      "abc.com/*",
   149  			attributes: &testAttributes{
   150  				resource: certificatesapi.Resource("clustertrustbundles"),
   151  				obj: &certificatesapi.ClusterTrustBundle{
   152  					Spec: certificatesapi.ClusterTrustBundleSpec{
   153  						SignerName: "abc.com/xyz",
   154  					},
   155  				},
   156  				operation: admission.Create,
   157  			},
   158  			allowed: true,
   159  		},
   160  		{
   161  			description:                      "should allow update if user is authorized with wildcard",
   162  			clusterTrustBundleFeatureEnabled: true,
   163  			allowedName:                      "abc.com/*",
   164  			attributes: &testAttributes{
   165  				resource: certificatesapi.Resource("clustertrustbundles"),
   166  				oldObj: &certificatesapi.ClusterTrustBundle{
   167  					Spec: certificatesapi.ClusterTrustBundleSpec{
   168  						SignerName: "abc.com/xyz",
   169  					},
   170  				},
   171  				obj: &certificatesapi.ClusterTrustBundle{
   172  					Spec: certificatesapi.ClusterTrustBundleSpec{
   173  						SignerName: "abc.com/xyz",
   174  					},
   175  				},
   176  				operation: admission.Update,
   177  			},
   178  			allowed: true,
   179  		},
   180  		{
   181  			description:                      "should deny create if user does not have permission for this signerName",
   182  			clusterTrustBundleFeatureEnabled: true,
   183  			allowedName:                      "notabc.com/xyz",
   184  			attributes: &testAttributes{
   185  				resource: certificatesapi.Resource("clustertrustbundles"),
   186  				obj: &certificatesapi.ClusterTrustBundle{
   187  					Spec: certificatesapi.ClusterTrustBundleSpec{
   188  						SignerName: "abc.com/xyz",
   189  					},
   190  				},
   191  				operation: admission.Create,
   192  			},
   193  			allowed: false,
   194  		},
   195  		{
   196  			description:                      "should deny update if user does not have permission for this signerName",
   197  			clusterTrustBundleFeatureEnabled: true,
   198  			allowedName:                      "notabc.com/xyz",
   199  			attributes: &testAttributes{
   200  				resource: certificatesapi.Resource("clustertrustbundles"),
   201  				obj: &certificatesapi.ClusterTrustBundle{
   202  					Spec: certificatesapi.ClusterTrustBundleSpec{
   203  						SignerName: "abc.com/xyz",
   204  					},
   205  				},
   206  				operation: admission.Update,
   207  			},
   208  			allowed: false,
   209  		},
   210  		{
   211  			description:                      "should always allow no-op update",
   212  			clusterTrustBundleFeatureEnabled: true,
   213  			authzErr:                         errors.New("broken"),
   214  			attributes: &testAttributes{
   215  				resource: certificatesapi.Resource("clustertrustbundles"),
   216  				oldObj: &certificatesapi.ClusterTrustBundle{
   217  					Spec: certificatesapi.ClusterTrustBundleSpec{
   218  						SignerName: "panda.com/foo",
   219  					},
   220  				},
   221  				obj: &certificatesapi.ClusterTrustBundle{
   222  					Spec: certificatesapi.ClusterTrustBundleSpec{
   223  						SignerName: "panda.com/foo",
   224  					},
   225  				},
   226  				operation: admission.Update,
   227  			},
   228  			allowed: true,
   229  		},
   230  		{
   231  			description:                      "should always allow finalizer update",
   232  			clusterTrustBundleFeatureEnabled: true,
   233  			authzErr:                         errors.New("broken"),
   234  			attributes: &testAttributes{
   235  				resource: certificatesapi.Resource("clustertrustbundles"),
   236  				oldObj: &certificatesapi.ClusterTrustBundle{
   237  					Spec: certificatesapi.ClusterTrustBundleSpec{
   238  						SignerName: "panda.com/foo",
   239  					},
   240  				},
   241  				obj: &certificatesapi.ClusterTrustBundle{
   242  					ObjectMeta: metav1.ObjectMeta{
   243  						OwnerReferences: []metav1.OwnerReference{
   244  							{APIVersion: "something"},
   245  						},
   246  					},
   247  					Spec: certificatesapi.ClusterTrustBundleSpec{
   248  						SignerName: "panda.com/foo",
   249  					},
   250  				},
   251  				operation: admission.Update,
   252  			},
   253  			allowed: true,
   254  		},
   255  	}
   256  
   257  	for _, tc := range tests {
   258  		t.Run(tc.description, func(t *testing.T) {
   259  			p := Plugin{
   260  				authz: fakeAuthorizer{
   261  					t:           t,
   262  					verb:        "attest",
   263  					allowedName: tc.allowedName,
   264  					decision:    authorizer.DecisionAllow,
   265  					err:         tc.authzErr,
   266  				},
   267  			}
   268  
   269  			defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.ClusterTrustBundle, tc.clusterTrustBundleFeatureEnabled)()
   270  			p.InspectFeatureGates(feature.DefaultFeatureGate)
   271  
   272  			err := p.Validate(context.Background(), tc.attributes, nil)
   273  			if err == nil && !tc.allowed {
   274  				t.Errorf("Expected authorization policy to reject ClusterTrustBundle but it was allowed")
   275  			}
   276  			if err != nil && tc.allowed {
   277  				t.Errorf("Expected authorization policy to accept ClusterTrustBundle but it was rejected: %v", err)
   278  			}
   279  		})
   280  	}
   281  }
   282  
   283  type fakeAuthorizer struct {
   284  	t           *testing.T
   285  	verb        string
   286  	allowedName string
   287  	decision    authorizer.Decision
   288  	err         error
   289  }
   290  
   291  func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
   292  	if f.err != nil {
   293  		return f.decision, "forced error", f.err
   294  	}
   295  	if a.GetVerb() != f.verb {
   296  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised verb '%s'", a.GetVerb()), nil
   297  	}
   298  	if a.GetAPIGroup() != "certificates.k8s.io" {
   299  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised groupName '%s'", a.GetAPIGroup()), nil
   300  	}
   301  	if a.GetAPIVersion() != "*" {
   302  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised apiVersion '%s'", a.GetAPIVersion()), nil
   303  	}
   304  	if a.GetResource() != "signers" {
   305  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource '%s'", a.GetResource()), nil
   306  	}
   307  	if a.GetName() != f.allowedName {
   308  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised resource name '%s'", a.GetName()), nil
   309  	}
   310  	if !a.IsResourceRequest() {
   311  		return authorizer.DecisionDeny, fmt.Sprintf("unrecognised IsResourceRequest '%t'", a.IsResourceRequest()), nil
   312  	}
   313  	return f.decision, "", nil
   314  }
   315  
   316  type testAttributes struct {
   317  	resource    schema.GroupResource
   318  	subresource string
   319  	operation   admission.Operation
   320  	obj, oldObj runtime.Object
   321  	name        string
   322  
   323  	admission.Attributes // nil panic if any other methods called
   324  }
   325  
   326  func (t *testAttributes) GetResource() schema.GroupVersionResource {
   327  	return t.resource.WithVersion("ignored")
   328  }
   329  
   330  func (t *testAttributes) GetSubresource() string {
   331  	return t.subresource
   332  }
   333  
   334  func (t *testAttributes) GetObject() runtime.Object {
   335  	return t.obj
   336  }
   337  
   338  func (t *testAttributes) GetOldObject() runtime.Object {
   339  	return t.oldObj
   340  }
   341  
   342  func (t *testAttributes) GetName() string {
   343  	return t.name
   344  }
   345  
   346  func (t *testAttributes) GetOperation() admission.Operation {
   347  	return t.operation
   348  }
   349  
   350  func (t *testAttributes) GetUserInfo() user.Info {
   351  	return &user.DefaultInfo{Name: "ignored"}
   352  }
   353  

View as plain text