mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 18:02:01 +00:00
* add superuser fallback to authorizer * change the order of authorizers * change the order of authorizers * remove the duplicate superuser authorizer * add integration test for superuser permissions
163 lines
7.2 KiB
Go
163 lines
7.2 KiB
Go
/*
|
|
Copyright 2020 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package certificates
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
certv1 "k8s.io/api/certificates/v1"
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
clientset "k8s.io/client-go/kubernetes"
|
|
restclient "k8s.io/client-go/rest"
|
|
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
|
"k8s.io/kubernetes/test/integration/authutil"
|
|
"k8s.io/kubernetes/test/integration/framework"
|
|
)
|
|
|
|
// Verifies that the CSR approval admission plugin correctly enforces that a
|
|
// user has permission to sign CSRs for the named signer
|
|
func TestCSRSignerNameSigningPlugin(t *testing.T) {
|
|
tests := map[string]struct {
|
|
allowedSignerName string
|
|
signerName string
|
|
error string
|
|
}{
|
|
"should admit when a user has permission for the exact signerName": {
|
|
allowedSignerName: "example.com/something",
|
|
signerName: "example.com/something",
|
|
},
|
|
"should admit when a user has permission for the wildcard-suffixed signerName": {
|
|
allowedSignerName: "example.com/*",
|
|
signerName: "example.com/something",
|
|
},
|
|
"should deny if a user does not have permission for the given signerName": {
|
|
allowedSignerName: "example.com/not-something",
|
|
signerName: "example.com/something",
|
|
error: `certificatesigningrequests.certificates.k8s.io "csr" is forbidden: user not permitted to sign requests with signerName "example.com/something"`,
|
|
},
|
|
}
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
// Run an apiserver with the default configuration options.
|
|
s := kubeapiservertesting.StartTestServerOrDie(t, kubeapiservertesting.NewDefaultTestServerOptions(), []string{"--authorization-mode=RBAC"}, framework.SharedEtcd())
|
|
defer s.TearDownFn()
|
|
client := clientset.NewForConfigOrDie(s.ClientConfig)
|
|
|
|
// Drop the default RBAC superuser permissions to rely on the internal superuser authorizer
|
|
if err := client.RbacV1().ClusterRoleBindings().Delete(context.TODO(), "cluster-admin", metav1.DeleteOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Grant 'test-user' permission to sign CertificateSigningRequests with the specified signerName.
|
|
const username = "test-user"
|
|
grantUserPermissionToSignFor(t, client, username, test.allowedSignerName)
|
|
// Create a CSR to attempt to sign.
|
|
csr := createTestingCSR(t, client.CertificatesV1().CertificateSigningRequests(), "csr", test.signerName, "")
|
|
|
|
// Create a second client, impersonating the 'test-user' for us to test with.
|
|
testuserConfig := restclient.CopyConfig(s.ClientConfig)
|
|
testuserConfig.Impersonate = restclient.ImpersonationConfig{UserName: username}
|
|
testuserClient := clientset.NewForConfigOrDie(testuserConfig)
|
|
|
|
// Attempt to 'sign' the certificate.
|
|
// random valid pem
|
|
csr.Status.Certificate = []byte(`
|
|
Leading non-PEM content
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
|
|
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
|
|
MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
|
|
BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
|
|
KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
|
|
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
|
|
K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
|
|
a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
|
|
MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
|
|
-----END CERTIFICATE-----
|
|
Intermediate non-PEM content
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBqDCCAU6gAwIBAgIUfqZtjoFgczZ+oQZbEC/BDSS2J6wwCgYIKoZIzj0EAwIw
|
|
EjEQMA4GA1UEAxMHUm9vdC1DQTAgFw0xNjEwMTEwNTA2MDBaGA8yMTE2MDkxNzA1
|
|
MDYwMFowGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMFkwEwYHKoZIzj0CAQYI
|
|
KoZIzj0DAQcDQgAEyWHEMMCctJg8Xa5YWLqaCPbk3MjB+uvXac42JM9pj4k9jedD
|
|
kpUJRkWIPzgJI8Zk/3cSzluUTixP6JBSDKtwwaN4MHYwDgYDVR0PAQH/BAQDAgGm
|
|
MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
|
|
FF+p0JcY31pz+mjNZnjv0Gum92vZMB8GA1UdIwQYMBaAFB7P6+i4/pfNjqZgJv/b
|
|
dgA7Fe4tMAoGCCqGSM49BAMCA0gAMEUCIQCTT1YWQZaAqfQ2oBxzOkJE2BqLFxhz
|
|
3smQlrZ5gCHddwIgcvT7puhYOzAgcvMn9+SZ1JOyZ7edODjshCVCRnuHK2c=
|
|
-----END CERTIFICATE-----
|
|
Trailing non-PEM content
|
|
`)
|
|
|
|
// superuser should always have permission to sign; dry-run so we don't actually modify the CSR so the non-superuser can attempt as well
|
|
if _, err := client.CertificatesV1().CertificateSigningRequests().UpdateStatus(context.TODO(), csr, metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}}); err != nil {
|
|
t.Errorf("expected no superuser error but got: %v", err)
|
|
}
|
|
|
|
_, err := testuserClient.CertificatesV1().CertificateSigningRequests().UpdateStatus(context.TODO(), csr, metav1.UpdateOptions{})
|
|
if err != nil && test.error != err.Error() {
|
|
t.Errorf("expected error %q but got: %v", test.error, err)
|
|
}
|
|
if err == nil && test.error != "" {
|
|
t.Errorf("expected to get an error %q but got none", test.error)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func grantUserPermissionToSignFor(t *testing.T, client clientset.Interface, username string, signerNames ...string) {
|
|
resourceName := "signername-" + username
|
|
cr := buildSigningClusterRoleForSigners(resourceName, signerNames...)
|
|
crb := buildClusterRoleBindingForUser(resourceName, username, cr.Name)
|
|
if _, err := client.RbacV1().ClusterRoles().Create(context.TODO(), cr, metav1.CreateOptions{}); err != nil {
|
|
t.Fatalf("failed to create test fixtures: %v", err)
|
|
}
|
|
if _, err := client.RbacV1().ClusterRoleBindings().Create(context.TODO(), crb, metav1.CreateOptions{}); err != nil {
|
|
t.Fatalf("failed to create test fixtures: %v", err)
|
|
}
|
|
signRule := cr.Rules[0]
|
|
statusRule := cr.Rules[1]
|
|
authutil.WaitForNamedAuthorizationUpdate(t, context.TODO(), client.AuthorizationV1(), username, "", signRule.Verbs[0], signRule.ResourceNames[0], schema.GroupResource{Group: signRule.APIGroups[0], Resource: signRule.Resources[0]}, true)
|
|
authutil.WaitForNamedAuthorizationUpdate(t, context.TODO(), client.AuthorizationV1(), username, "", statusRule.Verbs[0], "", schema.GroupResource{Group: statusRule.APIGroups[0], Resource: statusRule.Resources[0]}, true)
|
|
}
|
|
|
|
func buildSigningClusterRoleForSigners(name string, signerNames ...string) *rbacv1.ClusterRole {
|
|
return &rbacv1.ClusterRole{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Rules: []rbacv1.PolicyRule{
|
|
// must have permission to 'approve' the 'certificatesigners' named
|
|
// 'signerName' to approve CSRs with the given signerName.
|
|
{
|
|
Verbs: []string{"sign"},
|
|
APIGroups: []string{certv1.SchemeGroupVersion.Group},
|
|
Resources: []string{"signers"},
|
|
ResourceNames: signerNames,
|
|
},
|
|
{
|
|
Verbs: []string{"update"},
|
|
APIGroups: []string{certv1.SchemeGroupVersion.Group},
|
|
Resources: []string{"certificatesigningrequests/status"},
|
|
},
|
|
},
|
|
}
|
|
}
|