k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/auth/bootstraptoken_test.go (about) 1 /* 2 Copyright 2017 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 auth 18 19 import ( 20 "bytes" 21 "fmt" 22 "io" 23 "net/http" 24 "testing" 25 "time" 26 27 corev1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/labels" 30 "k8s.io/apiserver/pkg/authentication/group" 31 "k8s.io/apiserver/pkg/authentication/request/bearertoken" 32 "k8s.io/client-go/rest" 33 bootstrapapi "k8s.io/cluster-bootstrap/token/api" 34 "k8s.io/kubernetes/cmd/kube-apiserver/app/options" 35 "k8s.io/kubernetes/pkg/controlplane" 36 "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/bootstrap" 37 "k8s.io/kubernetes/test/integration" 38 "k8s.io/kubernetes/test/integration/framework" 39 "k8s.io/kubernetes/test/utils/ktesting" 40 ) 41 42 type bootstrapSecrets []*corev1.Secret 43 44 func (b bootstrapSecrets) List(selector labels.Selector) (ret []*corev1.Secret, err error) { 45 return b, nil 46 } 47 48 func (b bootstrapSecrets) Get(name string) (*corev1.Secret, error) { 49 return b[0], nil 50 } 51 52 // TestBootstrapTokenAuth tests the bootstrap token auth provider 53 func TestBootstrapTokenAuth(t *testing.T) { 54 validTokenID := "token1" 55 validSecret := "validtokensecret" 56 var bootstrapSecretValid = &corev1.Secret{ 57 ObjectMeta: metav1.ObjectMeta{ 58 Namespace: metav1.NamespaceSystem, 59 Name: bootstrapapi.BootstrapTokenSecretPrefix, 60 }, 61 Type: corev1.SecretTypeBootstrapToken, 62 Data: map[string][]byte{ 63 bootstrapapi.BootstrapTokenIDKey: []byte(validTokenID), 64 bootstrapapi.BootstrapTokenSecretKey: []byte(validSecret), 65 bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"), 66 }, 67 } 68 var bootstrapSecretInvalid = &corev1.Secret{ 69 ObjectMeta: metav1.ObjectMeta{ 70 Namespace: metav1.NamespaceSystem, 71 Name: bootstrapapi.BootstrapTokenSecretPrefix, 72 }, 73 Type: corev1.SecretTypeBootstrapToken, 74 Data: map[string][]byte{ 75 bootstrapapi.BootstrapTokenIDKey: []byte(validTokenID), 76 bootstrapapi.BootstrapTokenSecretKey: []byte("invalid"), 77 bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"), 78 }, 79 } 80 tokenExpiredTime := time.Now().UTC().Add(-time.Hour).Format(time.RFC3339) 81 var expiredBootstrapToken = &corev1.Secret{ 82 ObjectMeta: metav1.ObjectMeta{ 83 Namespace: metav1.NamespaceSystem, 84 Name: bootstrapapi.BootstrapTokenSecretPrefix, 85 }, 86 Type: corev1.SecretTypeBootstrapToken, 87 Data: map[string][]byte{ 88 bootstrapapi.BootstrapTokenIDKey: []byte(validTokenID), 89 bootstrapapi.BootstrapTokenSecretKey: []byte("invalid"), 90 bootstrapapi.BootstrapTokenUsageAuthentication: []byte("true"), 91 bootstrapapi.BootstrapTokenExpirationKey: []byte(tokenExpiredTime), 92 }, 93 } 94 type request struct { 95 verb string 96 URL string 97 body string 98 statusCodes map[int]bool // Set of expected resp.StatusCode if all goes well. 99 } 100 tests := []struct { 101 name string 102 request request 103 secret *corev1.Secret 104 }{ 105 { 106 name: "valid token", 107 request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code200}, 108 secret: bootstrapSecretValid, 109 }, 110 { 111 name: "invalid token format", 112 request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code401}, 113 secret: bootstrapSecretInvalid, 114 }, 115 { 116 name: "invalid token expired", 117 request: request{verb: "GET", URL: path("pods", "", ""), body: "", statusCodes: integration.Code401}, 118 secret: expiredBootstrapToken, 119 }, 120 } 121 for _, test := range tests { 122 t.Run(test.name, func(t *testing.T) { 123 tCtx := ktesting.Init(t) 124 authenticator := group.NewAuthenticatedGroupAdder(bearertoken.New(bootstrap.NewTokenAuthenticator(bootstrapSecrets{test.secret}))) 125 126 kubeClient, kubeConfig, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{ 127 ModifyServerRunOptions: func(opts *options.ServerRunOptions) { 128 opts.Authorization.Modes = []string{"AlwaysAllow"} 129 }, 130 ModifyServerConfig: func(config *controlplane.Config) { 131 config.ControlPlane.Generic.Authentication.Authenticator = authenticator 132 }, 133 }) 134 defer tearDownFn() 135 136 ns := framework.CreateNamespaceOrDie(kubeClient, "auth-bootstrap-token", t) 137 defer framework.DeleteNamespaceOrDie(kubeClient, ns, t) 138 139 previousResourceVersion := make(map[string]float64) 140 transport, err := rest.TransportFor(kubeConfig) 141 if err != nil { 142 t.Fatal(err) 143 } 144 145 token := validTokenID + "." + validSecret 146 var bodyStr string 147 if test.request.body != "" { 148 sub := "" 149 if test.request.verb == "PUT" { 150 // For update operations, insert previous resource version 151 if resVersion := previousResourceVersion[getPreviousResourceVersionKey(test.request.URL, "")]; resVersion != 0 { 152 sub += fmt.Sprintf(",\r\n\"resourceVersion\": \"%v\"", resVersion) 153 } 154 sub += fmt.Sprintf(",\r\n\"namespace\": %q", ns.Name) 155 } 156 bodyStr = fmt.Sprintf(test.request.body, sub) 157 } 158 test.request.body = bodyStr 159 bodyBytes := bytes.NewReader([]byte(bodyStr)) 160 req, err := http.NewRequest(test.request.verb, kubeConfig.Host+test.request.URL, bodyBytes) 161 if err != nil { 162 t.Fatalf("unexpected error: %v", err) 163 } 164 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) 165 if test.request.verb == "PATCH" { 166 req.Header.Set("Content-Type", "application/merge-patch+json") 167 } 168 169 func() { 170 resp, err := transport.RoundTrip(req) 171 if err != nil { 172 t.Logf("case %v", test.name) 173 t.Fatalf("unexpected error: %v", err) 174 } 175 defer resp.Body.Close() 176 b, _ := io.ReadAll(resp.Body) 177 if _, ok := test.request.statusCodes[resp.StatusCode]; !ok { 178 t.Logf("case %v", test.name) 179 t.Errorf("Expected status one of %v, but got %v", test.request.statusCodes, resp.StatusCode) 180 t.Errorf("Body: %v", string(b)) 181 } else { 182 if test.request.verb == "POST" { 183 // For successful create operations, extract resourceVersion 184 id, currentResourceVersion, err := parseResourceVersion(b) 185 if err == nil { 186 key := getPreviousResourceVersionKey(test.request.URL, id) 187 previousResourceVersion[key] = currentResourceVersion 188 } 189 } 190 } 191 192 }() 193 }) 194 } 195 }