github.com/DaAlbrecht/cf-cli@v0.0.0-20231128151943-1fe19bb400b9/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 21 "github.com/SermoDigital/jose/crypto" 22 "github.com/SermoDigital/jose/jws" 23 . "github.com/onsi/ginkgo" 24 . "github.com/onsi/gomega" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" 27 "k8s.io/client-go/tools/clientcmd/api" 28 ) 29 30 const ( 31 clientCertData = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJVk9iMUFIckxNUjh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TVRFd01EVXhOVEExTURsYUZ3MHlNakV3TURVeE5UQTFNVEZhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXJrdWxLbS9qTTJhZWZsdjkKK00zQk9Jc2QvVXZrRTBONGhWb3hSeWRBbE0xQXhWd3REYUdzL3dmUzRzb0xuNHJENTF3UE1SRlNJaitwSzdGYQprRGdaR0x4UFhrai96UkZOTzcvU3J2RHYwVGxjYjJENzNCS21qaXArQ2hBWkpQdWhMQlY2VnlTN0pXSWhOM1lOCktyamR5TnB5MHN3SjI1TW9CbW1saUpFc3V2dCtDaEhseERqWE9KenF1U2owa1hPQVVsWUFTN1dKK09JMU9HbzQKUjcvdHdHZlFTNW9oYXpRVVlDR2lZSllYcjVRNkVKTmJOVVI0RjdpRSthY1I5Rm9GNnNKSmkrQStET1VDUFFSKwptbjQ5Zm1pcFVHSGtMc3BicTNFZ0FEME40VW5jcmIyeUJEMFNVTmdLQmJjclY1S2hybFA2SzkwNkY5NEpubzNHCm1Id1JwUUlEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JUV2VNZ1ZBRkRhbWcraDRqS3hoRUh2Q1l5egp5akFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBUUxMWWFXQTRva1M2b3ZEWjQ1Z28ybkVZdUR4MklmRXZwYnh3CkNmYkFTNDY4M3lLT3FiYVBHOEpTVGhSbkh3TWlMcVBrbGFsdkJvV2R3aFB5Vkk0d2tVNHI4Y2c0UEpxNEZwWnQKVkNUQzFPZWVwRGpTMFpVQjRwSDVIZVlNQUxqSDBqcFV3RU96djFpaEtid05IMHFoZ2pGeUNTdld5TG9oZHdzbApJWXIvV1NEZm50NlBETC84TjFpcEJJbEN5Z1JHVGdoSFhPemhHUklPWG4rYWVOR29yWm9YWm0xbHErc1hyUnc5CktNdVZhRmdhaWVjSm0vbytyemFFSG9VZjRYOERKeVNubmVTa3ViaEx6ZERNc2o5eEs1cEJpdFgvaDlQMUQrMkcKeW5rcWdJVTJSWTM0SjBRcnU4Z0syNlJVT2pOcHIvRWJHQ0dUQUxiMXJnSDM0K2NFdlE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" 32 clientKeyData = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQ3VTNlVxYitNelpwNSsKVy8zNHpjRTRpeDM5UytRVFEzaUZXakZISjBDVXpVREZYQzBOb2F6L0I5TGl5Z3VmaXNQblhBOHhFVklpUDZrcgpzVnFRT0JrWXZFOWVTUC9ORVUwN3Y5S3U4Ty9ST1Z4dllQdmNFcWFPS240S0VCa2srNkVzRlhwWEpMc2xZaUUzCmRnMHF1TjNJMm5MU3pBbmJreWdHYWFXSWtTeTYrMzRLRWVYRU9OYzRuT3E1S1BTUmM0QlNWZ0JMdFluNDRqVTQKYWpoSHYrM0FaOUJMbWlGck5CUmdJYUpnbGhldmxEb1FrMXMxUkhnWHVJVDVweEgwV2dYcXdrbUw0RDRNNVFJOQpCSDZhZmoxK2FLbFFZZVF1eWx1cmNTQUFQUTNoU2R5dHZiSUVQUkpRMkFvRnR5dFhrcUd1VS9vcjNUb1gzZ21lCmpjYVlmQkdsQWdNQkFBRUNnZ0VBZG80WndLM3VteTM0TFBjaDM3VUU4eE1keVFkd0VmSlk3a3dWTE5MMFNNTDgKaGNKWEd1aVlKYmtLcHh6TG55L2laV0xuS25jZnFSQW9ZQUg1R2hRdWJmYlkvY2NseURVMmxhZTdCU2Y1MkJUdQpYUXhaQks3aS85ekRjdERVYWFXSFVkY2lLbGhmdStQdHVDM2ljdWJnWlJqQjljUzRCOVVtNm9XK0JSREtuandICkduQ0lEZlNNQWt4VXdTaUwwa2NXelNpZ1BYMVN3UHcxOEZvZWgzTmJEd1VXTHhxUWZLVThydVlSTUsxYUg5M3cKcjFtbjlDWUwvd0hiVWRqcmtZMlIxTjVUR21ab2Vldm5qUXgyQVc2NkYzdEg1cGg4RTF6TEFQVTl4TFdRTW9KcwpXM0gzSTdUaEYvRnJuNERQa3hQbThUUVVhQUdvQ09SSWFUQkN5VlgxQVFLQmdRREkxbkRmNWYySHdHaldrTStpCk9YbGE1R1VnRUtXaGZpeGhidE5OclNpMDU5VnZQUEJwNXdtbGQzMHJKUDhWem8vbnFnUW5ISmpmaEQ2Y3NSMTQKL2VlMHZ1Um0zYzZwZzMrODdwOHhWY3lLNHhDd0JmdFFuMGRZWWFLMkRMOEtYb0liYThpN09EQmFoNW5OZWQwcgpKa1RPcE5NRGRkL0p0bEpPZ25jRXBlUk9oUUtCZ1FEZUt1L0R1MXU1QVR3Y3p5STRXOWV1L1YwTXRwMHdqM3RpClF2MmpObW83QU1zS3BwK0ZKVDFqWFhUKzZCTm02OWpxUVJwdlAyd2RhVUdqV1dLa1lHVEVpbUZCc2ZuKzJDOFAKOEc3Uk50YWpRdEV2QlR1ZDZPN0tZUkFoTU56dm9RcDkrZmJKY1ZsRG13Nlk0bUYzUTJXS3NmZU51TGtpY3VqNQpYVFV1ekVMd29RS0JnUUREU0IvQTFYVEx4cjhwd3V6aHBGam5sQ1R3Skwrb1kzTHIya01EeUZkSWNCUU1jWWlpCnNNK2tZS2NJaUpTdnM0WWhrQ014bEpEZzVVbXNPbHVhQmVpQ3l3cHpLMEdEZWlWK285ZU90UXFLRVhkc2NLU0oKSkJiUFRVQlZHOWUyVVdiWkd0aTNrazhSOThBSkYzR0NQMWV3Um53WFpVb1FiSU5qYTJBbTJOZEJzUUtCZ0Q4eApOVXVXTWl1NE56SDJsTVExRTI4NXI4cmE4bkVLanN6UFF6ZTJWWmI4emNQMHl2RGpPOGZVb0YrVkFWZklBOFgxCnlLQVdDUm1BZytRRG03UW5tdUh3Zm1OaVRUcDRvVUpHWUM3d0N6TWE0VWNmbE9xQWc5TmFzbXpPYWpsYXRCSkwKRkRBT0pwYTlOdlN6aDRlVnl2OGRTYzJzMmpQN1BWc1ljUFVqc25LaEFvR0JBSy9kQjlnVEFpME5nczVmaVNtWQovWkp3Yk52MjcyTHdKbWV4Vit2eWtjN3J5LzRraTRQb2xRd1BHNzQ5eFZ0T2NNc2FhRlVNMVVkclN2NlIwbjlkCmpTbXhCeTl2YWdzc1FmVDNSc3BvUUJKM0w5YWxiNHM2V2ZtUEpzNkFrQkhIZHNpVXFaaElYT2J2WE1lQ0k2aVMKOTQ2R0toekFxMlVGbjhFUGxXaFVNeEFiCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K" 33 ) 34 35 var _ = Describe("KubernetesAuthentication", func() { 36 var ( 37 k8sAuthWrapper *wrapper.KubernetesAuthentication 38 config *commandfakes.FakeConfig 39 k8sConfigGetter *v7actionfakes.FakeKubernetesConfigGetter 40 wrappedConnection *ccv3fakes.FakeConnectionWrapper 41 req *cloudcontroller.Request 42 resp *cloudcontroller.Response 43 kubeConfig *api.Config 44 makeErr error 45 ) 46 47 BeforeEach(func() { 48 kubeConfig = &api.Config{ 49 Kind: "Config", 50 APIVersion: "v1", 51 Clusters: map[string]*api.Cluster{ 52 "my-cluster": { 53 Server: "https://example.org", 54 }, 55 }, 56 Contexts: map[string]*api.Context{ 57 "my-context": { 58 Cluster: "my-cluster", 59 AuthInfo: "my-auth-info", 60 Namespace: "my-namespace", 61 }, 62 }, 63 CurrentContext: "my-context", 64 AuthInfos: map[string]*api.AuthInfo{}, 65 } 66 67 k8sConfigGetter = new(v7actionfakes.FakeKubernetesConfigGetter) 68 k8sConfigGetter.GetReturns(kubeConfig, nil) 69 70 config = new(commandfakes.FakeConfig) 71 config.CurrentUserNameReturns("auth-test", nil) 72 73 wrappedConnection = new(ccv3fakes.FakeConnectionWrapper) 74 75 httpReq, err := http.NewRequest(http.MethodPost, "", strings.NewReader("hello")) 76 Expect(err).NotTo(HaveOccurred()) 77 req = cloudcontroller.NewRequest(httpReq, nil) 78 79 resp = &cloudcontroller.Response{ 80 HTTPResponse: &http.Response{ 81 StatusCode: http.StatusTeapot, 82 }, 83 } 84 }) 85 86 JustBeforeEach(func() { 87 k8sAuthWrapper = wrapper.NewKubernetesAuthentication(config, k8sConfigGetter) 88 k8sAuthWrapper.Wrap(wrappedConnection) 89 90 makeErr = k8sAuthWrapper.Make(req, resp) 91 }) 92 93 When("getting the k8s config fails", func() { 94 BeforeEach(func() { 95 k8sConfigGetter.GetReturns(nil, errors.New("boom!")) 96 }) 97 98 It("returns the error", func() { 99 Expect(makeErr).To(MatchError("boom!")) 100 }) 101 }) 102 103 When("no user is set in the config", func() { 104 BeforeEach(func() { 105 config.CurrentUserNameReturns("", nil) 106 }) 107 108 It("errors", func() { 109 Expect(makeErr).To(MatchError(ContainSubstring("current user not set"))) 110 }) 111 }) 112 113 When("there is an error getting the current user from the config", func() { 114 BeforeEach(func() { 115 config.CurrentUserNameReturns("", errors.New("boom")) 116 }) 117 118 It("errors", func() { 119 Expect(makeErr).To(MatchError(ContainSubstring("boom"))) 120 }) 121 }) 122 123 When("the chosen kubernetes auth info is not present in kubeconfig", func() { 124 BeforeEach(func() { 125 config.CurrentUserNameReturns("not-present", nil) 126 }) 127 128 It("errors", func() { 129 Expect(makeErr).To(MatchError(ContainSubstring(`auth info "not-present" does not exist`))) 130 }) 131 }) 132 133 checkCalls := func() *cloudcontroller.Request { 134 Expect(makeErr).NotTo(HaveOccurred()) 135 Expect(wrappedConnection.MakeCallCount()).To(Equal(1)) 136 137 actualReq, actualResp := wrappedConnection.MakeArgsForCall(0) 138 Expect(actualResp.HTTPResponse).To(HaveHTTPStatus(http.StatusTeapot)) 139 140 body, err := ioutil.ReadAll(actualReq.Body) 141 Expect(err).NotTo(HaveOccurred()) 142 Expect(string(body)).To(Equal("hello")) 143 144 return actualReq 145 } 146 147 checkBearerTokenInAuthHeader := func() { 148 actualReq := checkCalls() 149 150 token, err := jws.ParseJWTFromRequest(actualReq.Request) 151 Expect(err).NotTo(HaveOccurred()) 152 Expect(token.Validate(keyPair.Public(), crypto.SigningMethodRS256)).To(Succeed()) 153 154 claims := token.Claims() 155 Expect(claims).To(HaveKeyWithValue("another", "thing")) 156 } 157 158 checkClientCertInAuthHeader := func() { 159 actualReq := checkCalls() 160 161 Expect(actualReq.Header).To(HaveKeyWithValue("Authorization", ConsistOf(HavePrefix("ClientCert ")))) 162 163 certAndKeyPEMBase64 := actualReq.Header.Get("Authorization")[11:] 164 certAndKeyPEM, err := base64.StdEncoding.DecodeString(certAndKeyPEMBase64) 165 Expect(err).NotTo(HaveOccurred()) 166 167 cert, rest := pem.Decode(certAndKeyPEM) 168 Expect(cert.Type).To(Equal(pemDecodeKubeConfigCertData(clientCertData).Type)) 169 Expect(cert.Bytes).To(Equal(pemDecodeKubeConfigCertData(clientCertData).Bytes)) 170 171 var key *pem.Block 172 key, rest = pem.Decode(rest) 173 Expect(key.Bytes).To(Equal(pemDecodeKubeConfigCertData(clientKeyData).Bytes)) 174 175 Expect(rest).To(BeEmpty()) 176 } 177 178 Describe("auth-provider", func() { 179 var token []byte 180 181 BeforeEach(func() { 182 jwt := jws.NewJWT(jws.Claims{ 183 "exp": time.Now().Add(time.Hour).Unix(), 184 "another": "thing", 185 }, crypto.SigningMethodRS256) 186 var err error 187 token, err = jwt.Serialize(keyPair) 188 Expect(err).NotTo(HaveOccurred()) 189 190 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 191 AuthProvider: &api.AuthProviderConfig{ 192 Name: "oidc", 193 Config: map[string]string{ 194 "id-token": string(token), 195 "idp-issuer-url": "-", 196 "client-id": "-", 197 }, 198 }, 199 } 200 }) 201 202 It("uses the auth-provider to generate the Bearer token", func() { 203 checkBearerTokenInAuthHeader() 204 }) 205 }) 206 207 Describe("client certs", func() { 208 var ( 209 certFilePath string 210 keyFilePath string 211 ) 212 213 BeforeEach(func() { 214 certFilePath = writeToFile(clientCertData) 215 keyFilePath = writeToFile(clientKeyData) 216 }) 217 218 AfterEach(func() { 219 Expect(os.RemoveAll(certFilePath)).To(Succeed()) 220 Expect(os.RemoveAll(keyFilePath)).To(Succeed()) 221 }) 222 223 When("inline cert and key are provided", func() { 224 BeforeEach(func() { 225 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 226 ClientCertificateData: []byte(base64Decode(clientCertData)), 227 ClientKeyData: []byte(base64Decode(clientKeyData)), 228 } 229 }) 230 231 It("puts concatenated client ceritificate and key data into the Authorization header", func() { 232 checkClientCertInAuthHeader() 233 }) 234 }) 235 236 When("cert and key are provided in files", func() { 237 BeforeEach(func() { 238 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 239 ClientCertificate: certFilePath, 240 ClientKey: keyFilePath, 241 } 242 }) 243 244 It("puts concatenated client ceritificate and key data into the Authorization header", func() { 245 checkClientCertInAuthHeader() 246 }) 247 248 When("cert file cannot be read", func() { 249 BeforeEach(func() { 250 Expect(os.Remove(certFilePath)).To(Succeed()) 251 }) 252 253 It("returns an error", func() { 254 Expect(makeErr).To(MatchError(ContainSubstring(certFilePath))) 255 }) 256 }) 257 258 When("key file cannot be read", func() { 259 BeforeEach(func() { 260 Expect(os.Remove(keyFilePath)).To(Succeed()) 261 }) 262 263 It("returns an error", func() { 264 Expect(makeErr).To(MatchError(ContainSubstring(keyFilePath))) 265 }) 266 }) 267 }) 268 269 When("file and inline cert is provided", func() { 270 BeforeEach(func() { 271 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 272 ClientCertificate: certFilePath, 273 ClientCertificateData: []byte(base64Decode(clientCertData)), 274 ClientKeyData: []byte(base64Decode(clientKeyData)), 275 } 276 }) 277 278 It("complains about invalid configuration", func() { 279 Expect(makeErr).To(MatchError(ContainSubstring("client-cert-data and client-cert are both specified"))) 280 }) 281 }) 282 283 When("file and inline key is provided", func() { 284 BeforeEach(func() { 285 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 286 ClientCertificateData: []byte(base64Decode(clientCertData)), 287 ClientKeyData: []byte(base64Decode(clientKeyData)), 288 ClientKey: keyFilePath, 289 } 290 }) 291 292 It("complains about invalid configuration", func() { 293 Expect(makeErr).To(MatchError(ContainSubstring("client-key-data and client-key are both specified"))) 294 }) 295 }) 296 297 When("inline cert and key file are provided", func() { 298 BeforeEach(func() { 299 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 300 ClientCertificateData: []byte(base64Decode(clientCertData)), 301 ClientKey: keyFilePath, 302 } 303 }) 304 305 It("uses the inline key", func() { 306 checkClientCertInAuthHeader() 307 }) 308 }) 309 310 When("cert file and inline key are provided", func() { 311 BeforeEach(func() { 312 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 313 ClientCertificate: certFilePath, 314 ClientKeyData: []byte(base64Decode(clientKeyData)), 315 } 316 }) 317 318 It("uses the inline key", func() { 319 checkClientCertInAuthHeader() 320 }) 321 }) 322 }) 323 324 Describe("exec", func() { 325 BeforeEach(func() { 326 kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{ 327 Exec: &api.ExecConfig{ 328 APIVersion: "client.authentication.k8s.io/v1beta1", 329 InteractiveMode: "Never", 330 Command: "echo", 331 }, 332 } 333 }) 334 335 When("the command returns a token", func() { 336 BeforeEach(func() { 337 kubeConfig.AuthInfos["auth-test"].Exec.Args = []string{execCredential(&clientauthenticationv1beta1.ExecCredentialStatus{ 338 Token: "a-token", 339 })} 340 }) 341 342 It("uses the exec command to generate the Bearer token", func() { 343 helpers.SkipIfWindows() // We're getting "plugin returned version client.authentication.k8s.io/__internal" on Windows. This issue is unresolved in upstream library. 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 }