github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/wrapper/kubernetes_authentication_test.go (about) 1 package wrapper_test 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "encoding/pem" 7 "errors" 8 "io/ioutil" 9 "net/http" 10 "os" 11 "strings" 12 "time" 13 14 "code.cloudfoundry.org/cli/actor/v7action/v7actionfakes" 15 "code.cloudfoundry.org/cli/api/cloudcontroller" 16 "code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/ccv3fakes" 17 "code.cloudfoundry.org/cli/api/cloudcontroller/wrapper" 18 "code.cloudfoundry.org/cli/command/commandfakes" 19 "code.cloudfoundry.org/cli/integration/helpers" 20 "github.com/SermoDigital/jose/crypto" 21 "github.com/SermoDigital/jose/jws" 22 . "github.com/onsi/ginkgo" 23 . "github.com/onsi/gomega" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" 26 "k8s.io/client-go/tools/clientcmd/api" 27 ) 28 29 const ( 30 clientCertData = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJVk9iMUFIckxNUjh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TVRFd01EVXhOVEExTURsYUZ3MHlNakV3TURVeE5UQTFNVEZhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXJrdWxLbS9qTTJhZWZsdjkKK00zQk9Jc2QvVXZrRTBONGhWb3hSeWRBbE0xQXhWd3REYUdzL3dmUzRzb0xuNHJENTF3UE1SRlNJaitwSzdGYQprRGdaR0x4UFhrai96UkZOTzcvU3J2RHYwVGxjYjJENzNCS21qaXArQ2hBWkpQdWhMQlY2VnlTN0pXSWhOM1lOCktyamR5TnB5MHN3SjI1TW9CbW1saUpFc3V2dCtDaEhseERqWE9KenF1U2owa1hPQVVsWUFTN1dKK09JMU9HbzQKUjcvdHdHZlFTNW9oYXpRVVlDR2lZSllYcjVRNkVKTmJOVVI0RjdpRSthY1I5Rm9GNnNKSmkrQStET1VDUFFSKwptbjQ5Zm1pcFVHSGtMc3BicTNFZ0FEME40VW5jcmIyeUJEMFNVTmdLQmJjclY1S2hybFA2SzkwNkY5NEpubzNHCm1Id1JwUUlEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JUV2VNZ1ZBRkRhbWcraDRqS3hoRUh2Q1l5egp5akFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBUUxMWWFXQTRva1M2b3ZEWjQ1Z28ybkVZdUR4MklmRXZwYnh3CkNmYkFTNDY4M3lLT3FiYVBHOEpTVGhSbkh3TWlMcVBrbGFsdkJvV2R3aFB5Vkk0d2tVNHI4Y2c0UEpxNEZwWnQKVkNUQzFPZWVwRGpTMFpVQjRwSDVIZVlNQUxqSDBqcFV3RU96djFpaEtid05IMHFoZ2pGeUNTdld5TG9oZHdzbApJWXIvV1NEZm50NlBETC84TjFpcEJJbEN5Z1JHVGdoSFhPemhHUklPWG4rYWVOR29yWm9YWm0xbHErc1hyUnc5CktNdVZhRmdhaWVjSm0vbytyemFFSG9VZjRYOERKeVNubmVTa3ViaEx6ZERNc2o5eEs1cEJpdFgvaDlQMUQrMkcKeW5rcWdJVTJSWTM0SjBRcnU4Z0syNlJVT2pOcHIvRWJHQ0dUQUxiMXJnSDM0K2NFdlE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" 31 clientKeyData = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQ3VTNlVxYitNelpwNSsKVy8zNHpjRTRpeDM5UytRVFEzaUZXakZISjBDVXpVREZYQzBOb2F6L0I5TGl5Z3VmaXNQblhBOHhFVklpUDZrcgpzVnFRT0JrWXZFOWVTUC9ORVUwN3Y5S3U4Ty9ST1Z4dllQdmNFcWFPS240S0VCa2srNkVzRlhwWEpMc2xZaUUzCmRnMHF1TjNJMm5MU3pBbmJreWdHYWFXSWtTeTYrMzRLRWVYRU9OYzRuT3E1S1BTUmM0QlNWZ0JMdFluNDRqVTQKYWpoSHYrM0FaOUJMbWlGck5CUmdJYUpnbGhldmxEb1FrMXMxUkhnWHVJVDVweEgwV2dYcXdrbUw0RDRNNVFJOQpCSDZhZmoxK2FLbFFZZVF1eWx1cmNTQUFQUTNoU2R5dHZiSUVQUkpRMkFvRnR5dFhrcUd1VS9vcjNUb1gzZ21lCmpjYVlmQkdsQWdNQkFBRUNnZ0VBZG80WndLM3VteTM0TFBjaDM3VUU4eE1keVFkd0VmSlk3a3dWTE5MMFNNTDgKaGNKWEd1aVlKYmtLcHh6TG55L2laV0xuS25jZnFSQW9ZQUg1R2hRdWJmYlkvY2NseURVMmxhZTdCU2Y1MkJUdQpYUXhaQks3aS85ekRjdERVYWFXSFVkY2lLbGhmdStQdHVDM2ljdWJnWlJqQjljUzRCOVVtNm9XK0JSREtuandICkduQ0lEZlNNQWt4VXdTaUwwa2NXelNpZ1BYMVN3UHcxOEZvZWgzTmJEd1VXTHhxUWZLVThydVlSTUsxYUg5M3cKcjFtbjlDWUwvd0hiVWRqcmtZMlIxTjVUR21ab2Vldm5qUXgyQVc2NkYzdEg1cGg4RTF6TEFQVTl4TFdRTW9KcwpXM0gzSTdUaEYvRnJuNERQa3hQbThUUVVhQUdvQ09SSWFUQkN5VlgxQVFLQmdRREkxbkRmNWYySHdHaldrTStpCk9YbGE1R1VnRUtXaGZpeGhidE5OclNpMDU5VnZQUEJwNXdtbGQzMHJKUDhWem8vbnFnUW5ISmpmaEQ2Y3NSMTQKL2VlMHZ1Um0zYzZwZzMrODdwOHhWY3lLNHhDd0JmdFFuMGRZWWFLMkRMOEtYb0liYThpN09EQmFoNW5OZWQwcgpKa1RPcE5NRGRkL0p0bEpPZ25jRXBlUk9oUUtCZ1FEZUt1L0R1MXU1QVR3Y3p5STRXOWV1L1YwTXRwMHdqM3RpClF2MmpObW83QU1zS3BwK0ZKVDFqWFhUKzZCTm02OWpxUVJwdlAyd2RhVUdqV1dLa1lHVEVpbUZCc2ZuKzJDOFAKOEc3Uk50YWpRdEV2QlR1ZDZPN0tZUkFoTU56dm9RcDkrZmJKY1ZsRG13Nlk0bUYzUTJXS3NmZU51TGtpY3VqNQpYVFV1ekVMd29RS0JnUUREU0IvQTFYVEx4cjhwd3V6aHBGam5sQ1R3Skwrb1kzTHIya01EeUZkSWNCUU1jWWlpCnNNK2tZS2NJaUpTdnM0WWhrQ014bEpEZzVVbXNPbHVhQmVpQ3l3cHpLMEdEZWlWK285ZU90UXFLRVhkc2NLU0oKSkJiUFRVQlZHOWUyVVdiWkd0aTNrazhSOThBSkYzR0NQMWV3Um53WFpVb1FiSU5qYTJBbTJOZEJzUUtCZ0Q4eApOVXVXTWl1NE56SDJsTVExRTI4NXI4cmE4bkVLanN6UFF6ZTJWWmI4emNQMHl2RGpPOGZVb0YrVkFWZklBOFgxCnlLQVdDUm1BZytRRG03UW5tdUh3Zm1OaVRUcDRvVUpHWUM3d0N6TWE0VWNmbE9xQWc5TmFzbXpPYWpsYXRCSkwKRkRBT0pwYTlOdlN6aDRlVnl2OGRTYzJzMmpQN1BWc1ljUFVqc25LaEFvR0JBSy9kQjlnVEFpME5nczVmaVNtWQovWkp3Yk52MjcyTHdKbWV4Vit2eWtjN3J5LzRraTRQb2xRd1BHNzQ5eFZ0T2NNc2FhRlVNMVVkclN2NlIwbjlkCmpTbXhCeTl2YWdzc1FmVDNSc3BvUUJKM0w5YWxiNHM2V2ZtUEpzNkFrQkhIZHNpVXFaaElYT2J2WE1lQ0k2aVMKOTQ2R0toekFxMlVGbjhFUGxXaFVNeEFiCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K" 32 ) 33 34 var _ = Describe("KubernetesAuthentication", func() { 35 var ( 36 k8sAuthWrapper *wrapper.KubernetesAuthentication 37 config *commandfakes.FakeConfig 38 k8sConfigGetter *v7actionfakes.FakeKubernetesConfigGetter 39 wrappedConnection *ccv3fakes.FakeConnectionWrapper 40 req *cloudcontroller.Request 41 resp *cloudcontroller.Response 42 kubeConfig *api.Config 43 makeErr error 44 ) 45 46 BeforeEach(func() { 47 kubeConfig = &api.Config{ 48 Kind: "Config", 49 APIVersion: "v1", 50 Clusters: map[string]*api.Cluster{ 51 "my-cluster": { 52 Server: "https://example.org", 53 }, 54 }, 55 Contexts: map[string]*api.Context{ 56 "my-context": { 57 Cluster: "my-cluster", 58 AuthInfo: "my-auth-info", 59 Namespace: "my-namespace", 60 }, 61 }, 62 CurrentContext: "my-context", 63 AuthInfos: map[string]*api.AuthInfo{}, 64 } 65 66 k8sConfigGetter = new(v7actionfakes.FakeKubernetesConfigGetter) 67 k8sConfigGetter.GetReturns(kubeConfig, nil) 68 69 config = new(commandfakes.FakeConfig) 70 config.CurrentUserNameReturns("auth-test", nil) 71 72 wrappedConnection = new(ccv3fakes.FakeConnectionWrapper) 73 74 httpReq, err := http.NewRequest(http.MethodPost, "", strings.NewReader("hello")) 75 Expect(err).NotTo(HaveOccurred()) 76 req = cloudcontroller.NewRequest(httpReq, nil) 77 78 resp = &cloudcontroller.Response{ 79 HTTPResponse: &http.Response{ 80 StatusCode: http.StatusTeapot, 81 }, 82 } 83 }) 84 85 JustBeforeEach(func() { 86 k8sAuthWrapper = wrapper.NewKubernetesAuthentication(config, k8sConfigGetter) 87 k8sAuthWrapper.Wrap(wrappedConnection) 88 89 makeErr = k8sAuthWrapper.Make(req, resp) 90 }) 91 92 When("getting the k8s config fails", func() { 93 BeforeEach(func() { 94 k8sConfigGetter.GetReturns(nil, errors.New("boom!")) 95 }) 96 97 It("returns the error", func() { 98 Expect(makeErr).To(MatchError("boom!")) 99 }) 100 }) 101 102 When("no user is set in the config", func() { 103 BeforeEach(func() { 104 config.CurrentUserNameReturns("", nil) 105 }) 106 107 It("errors", func() { 108 Expect(makeErr).To(MatchError(ContainSubstring("current user not set"))) 109 }) 110 }) 111 112 When("there is an error getting the current user from the config", func() { 113 BeforeEach(func() { 114 config.CurrentUserNameReturns("", errors.New("boom")) 115 }) 116 117 It("errors", func() { 118 Expect(makeErr).To(MatchError(ContainSubstring("boom"))) 119 }) 120 }) 121 122 When("the chosen kubernetes auth info is not present in kubeconfig", func() { 123 BeforeEach(func() { 124 config.CurrentUserNameReturns("not-present", nil) 125 }) 126 127 It("errors", func() { 128 Expect(makeErr).To(MatchError(ContainSubstring(`auth info "not-present" does not exist`))) 129 }) 130 }) 131 132 checkCalls := func() *cloudcontroller.Request { 133 Expect(makeErr).NotTo(HaveOccurred()) 134 Expect(wrappedConnection.MakeCallCount()).To(Equal(1)) 135 136 actualReq, actualResp := wrappedConnection.MakeArgsForCall(0) 137 Expect(actualResp.HTTPResponse).To(HaveHTTPStatus(http.StatusTeapot)) 138 139 body, err := ioutil.ReadAll(actualReq.Body) 140 Expect(err).NotTo(HaveOccurred()) 141 Expect(string(body)).To(Equal("hello")) 142 143 return actualReq 144 } 145 146 checkBearerTokenInAuthHeader := func() { 147 actualReq := checkCalls() 148 149 token, err := jws.ParseJWTFromRequest(actualReq.Request) 150 Expect(err).NotTo(HaveOccurred()) 151 Expect(token.Validate(keyPair.Public(), crypto.SigningMethodRS256)).To(Succeed()) 152 153 claims := token.Claims() 154 Expect(claims).To(HaveKeyWithValue("another", "thing")) 155 } 156 157 checkClientCertInAuthHeader := func() { 158 actualReq := checkCalls() 159 160 Expect(actualReq.Header).To(HaveKeyWithValue("Authorization", ConsistOf(HavePrefix("ClientCert ")))) 161 162 certAndKeyPEMBase64 := actualReq.Header.Get("Authorization")[11:] 163 certAndKeyPEM, err := base64.StdEncoding.DecodeString(certAndKeyPEMBase64) 164 Expect(err).NotTo(HaveOccurred()) 165 166 cert, rest := pem.Decode(certAndKeyPEM) 167 Expect(cert.Type).To(Equal(pemDecodeKubeConfigCertData(clientCertData).Type)) 168 Expect(cert.Bytes).To(Equal(pemDecodeKubeConfigCertData(clientCertData).Bytes)) 169 170 var key *pem.Block 171 key, rest = pem.Decode(rest) 172 Expect(key.Bytes).To(Equal(pemDecodeKubeConfigCertData(clientKeyData).Bytes)) 173 174 Expect(rest).To(BeEmpty()) 175 } 176 177 Describe("auth-provider", func() { 178 var token []byte 179 180 BeforeEach(func() { 181 jwt := jws.NewJWT(jws.Claims{ 182 "exp": time.Now().Add(time.Hour).Unix(), 183 "another": "thing", 184 }, crypto.SigningMethodRS256) 185 var err error 186 token, err = jwt.Serialize(keyPair) 187 Expect(err).NotTo(HaveOccurred()) 188 189 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 190 AuthProvider: &api.AuthProviderConfig{ 191 Name: "oidc", 192 Config: map[string]string{ 193 "id-token": string(token), 194 "idp-issuer-url": "-", 195 "client-id": "-", 196 }, 197 }, 198 } 199 }) 200 201 It("uses the auth-provider to generate the Bearer token", func() { 202 checkBearerTokenInAuthHeader() 203 }) 204 }) 205 206 Describe("client certs", func() { 207 var ( 208 certFilePath string 209 keyFilePath string 210 ) 211 212 BeforeEach(func() { 213 certFilePath = writeToFile(clientCertData) 214 keyFilePath = writeToFile(clientKeyData) 215 }) 216 217 AfterEach(func() { 218 Expect(os.RemoveAll(certFilePath)).To(Succeed()) 219 Expect(os.RemoveAll(keyFilePath)).To(Succeed()) 220 }) 221 222 When("inline cert and key are provided", func() { 223 BeforeEach(func() { 224 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 225 ClientCertificateData: []byte(base64Decode(clientCertData)), 226 ClientKeyData: []byte(base64Decode(clientKeyData)), 227 } 228 }) 229 230 It("puts concatenated client ceritificate and key data into the Authorization header", func() { 231 checkClientCertInAuthHeader() 232 }) 233 }) 234 235 When("cert and key are provided in files", func() { 236 BeforeEach(func() { 237 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 238 ClientCertificate: certFilePath, 239 ClientKey: keyFilePath, 240 } 241 }) 242 243 It("puts concatenated client ceritificate and key data into the Authorization header", func() { 244 checkClientCertInAuthHeader() 245 }) 246 247 When("cert file cannot be read", func() { 248 BeforeEach(func() { 249 Expect(os.Remove(certFilePath)).To(Succeed()) 250 }) 251 252 It("returns an error", func() { 253 Expect(makeErr).To(MatchError(ContainSubstring(certFilePath))) 254 }) 255 }) 256 257 When("key file cannot be read", func() { 258 BeforeEach(func() { 259 Expect(os.Remove(keyFilePath)).To(Succeed()) 260 }) 261 262 It("returns an error", func() { 263 Expect(makeErr).To(MatchError(ContainSubstring(keyFilePath))) 264 }) 265 }) 266 }) 267 268 When("file and inline cert is provided", func() { 269 BeforeEach(func() { 270 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 271 ClientCertificate: certFilePath, 272 ClientCertificateData: []byte(base64Decode(clientCertData)), 273 ClientKeyData: []byte(base64Decode(clientKeyData)), 274 } 275 }) 276 277 It("complains about invalid configuration", func() { 278 Expect(makeErr).To(MatchError(ContainSubstring("client-cert-data and client-cert are both specified"))) 279 }) 280 }) 281 282 When("file and inline key is provided", func() { 283 BeforeEach(func() { 284 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 285 ClientCertificateData: []byte(base64Decode(clientCertData)), 286 ClientKeyData: []byte(base64Decode(clientKeyData)), 287 ClientKey: keyFilePath, 288 } 289 }) 290 291 It("complains about invalid configuration", func() { 292 Expect(makeErr).To(MatchError(ContainSubstring("client-key-data and client-key are both specified"))) 293 }) 294 }) 295 296 When("inline cert and key file are provided", func() { 297 BeforeEach(func() { 298 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 299 ClientCertificateData: []byte(base64Decode(clientCertData)), 300 ClientKey: keyFilePath, 301 } 302 }) 303 304 It("uses the inline key", func() { 305 checkClientCertInAuthHeader() 306 }) 307 }) 308 309 When("cert file and inline key are provided", func() { 310 BeforeEach(func() { 311 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 312 ClientCertificate: certFilePath, 313 ClientKeyData: []byte(base64Decode(clientKeyData)), 314 } 315 }) 316 317 It("uses the inline key", func() { 318 checkClientCertInAuthHeader() 319 }) 320 }) 321 }) 322 323 Describe("exec", func() { 324 BeforeEach(func() { 325 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 326 Exec: &api.ExecConfig{ 327 APIVersion: "client.authentication.k8s.io/v1beta1", 328 InteractiveMode: "Never", 329 Command: "echo", 330 }, 331 } 332 }) 333 334 When("the command returns a token", func() { 335 BeforeEach(func() { 336 kubeConfig.AuthInfos["auth-test"].Exec.Args = []string{execCredential(&clientauthenticationv1beta1.ExecCredentialStatus{ 337 Token: "a-token", 338 })} 339 }) 340 341 It("uses the exec command to generate the Bearer token", func() { 342 helpers.SkipIfWindows() 343 344 Expect(makeErr).NotTo(HaveOccurred()) 345 Expect(wrappedConnection.MakeCallCount()).To(Equal(1)) 346 347 actualReq, actualResp := wrappedConnection.MakeArgsForCall(0) 348 Expect(actualResp.HTTPResponse).To(HaveHTTPStatus(http.StatusTeapot)) 349 Expect(actualReq.Header.Get("Authorization")).To(Equal("Bearer a-token")) 350 }) 351 }) 352 353 When("the command returns a client cert and key", func() { 354 BeforeEach(func() { 355 kubeConfig.AuthInfos["auth-test"].Exec.Args = []string{execCredential(&clientauthenticationv1beta1.ExecCredentialStatus{ 356 ClientCertificateData: base64Decode(clientCertData), 357 ClientKeyData: base64Decode(clientKeyData), 358 })} 359 }) 360 361 It("uses the exec command to generate client certs", func() { 362 checkClientCertInAuthHeader() 363 }) 364 }) 365 }) 366 367 Describe("tokens provided in config", func() { 368 var token []byte 369 370 BeforeEach(func() { 371 jwt := jws.NewJWT(jws.Claims{ 372 "exp": time.Now().Add(time.Hour).Unix(), 373 "another": "thing", 374 }, crypto.SigningMethodRS256) 375 var err error 376 token, err = jwt.Serialize(keyPair) 377 Expect(err).NotTo(HaveOccurred()) 378 }) 379 380 Context("inline tokens", func() { 381 BeforeEach(func() { 382 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 383 Token: string(token), 384 } 385 }) 386 387 It("inserts the token in the authorization header", func() { 388 checkBearerTokenInAuthHeader() 389 }) 390 }) 391 392 Context("token file paths", func() { 393 var tokenFilePath string 394 395 BeforeEach(func() { 396 tokenFile, err := ioutil.TempFile("", "") 397 Expect(err).NotTo(HaveOccurred()) 398 defer tokenFile.Close() 399 _, err = tokenFile.Write(token) 400 Expect(err).NotTo(HaveOccurred()) 401 tokenFilePath = tokenFile.Name() 402 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 403 TokenFile: tokenFilePath, 404 } 405 }) 406 407 AfterEach(func() { 408 Expect(os.RemoveAll(tokenFilePath)).To(Succeed()) 409 }) 410 411 It("inserts the token in the authorization header", func() { 412 checkBearerTokenInAuthHeader() 413 }) 414 }) 415 416 When("both file and inline token are provided", func() { 417 BeforeEach(func() { 418 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 419 Token: string(token), 420 TokenFile: "some-path", 421 } 422 }) 423 424 It("the inline token takes precedence", func() { 425 checkBearerTokenInAuthHeader() 426 }) 427 }) 428 }) 429 }) 430 431 func pemDecodeKubeConfigCertData(data string) *pem.Block { 432 decodedData, err := base64.StdEncoding.DecodeString(data) 433 Expect(err).NotTo(HaveOccurred()) 434 pemDecodedBlock, rest := pem.Decode(decodedData) 435 Expect(rest).To(BeEmpty()) 436 return pemDecodedBlock 437 } 438 439 func base64Decode(encoded string) string { 440 decoded, err := base64.StdEncoding.DecodeString(encoded) 441 Expect(err).NotTo(HaveOccurred()) 442 return string(decoded) 443 } 444 445 func writeToFile(base64Data string) string { 446 file, err := ioutil.TempFile("", "") 447 Expect(err).NotTo(HaveOccurred()) 448 file.WriteString(base64Decode(base64Data)) 449 Expect(file.Close()).To(Succeed()) 450 return file.Name() 451 } 452 453 func execCredential(status *clientauthenticationv1beta1.ExecCredentialStatus) string { 454 execCred, err := json.Marshal(clientauthenticationv1beta1.ExecCredential{ 455 TypeMeta: metav1.TypeMeta{ 456 APIVersion: "client.authentication.k8s.io/v1beta1", 457 }, 458 Status: status, 459 }) 460 Expect(err).NotTo(HaveOccurred()) 461 return string(execCred) 462 }