github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/shared/wrap_for_cf_on_k8s_test.go (about)

     1  package shared_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/shared"
    16  	"code.cloudfoundry.org/cli/api/shared/sharedfakes"
    17  	"code.cloudfoundry.org/cli/command/commandfakes"
    18  	"code.cloudfoundry.org/cli/integration/helpers"
    19  
    20  	"github.com/SermoDigital/jose/crypto"
    21  	"github.com/SermoDigital/jose/jws"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
    24  	"k8s.io/client-go/tools/clientcmd/api"
    25  
    26  	. "github.com/onsi/ginkgo"
    27  	. "github.com/onsi/gomega"
    28  )
    29  
    30  const (
    31  	clientCertData = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJVk9iMUFIckxNUjh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TVRFd01EVXhOVEExTURsYUZ3MHlNakV3TURVeE5UQTFNVEZhTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXJrdWxLbS9qTTJhZWZsdjkKK00zQk9Jc2QvVXZrRTBONGhWb3hSeWRBbE0xQXhWd3REYUdzL3dmUzRzb0xuNHJENTF3UE1SRlNJaitwSzdGYQprRGdaR0x4UFhrai96UkZOTzcvU3J2RHYwVGxjYjJENzNCS21qaXArQ2hBWkpQdWhMQlY2VnlTN0pXSWhOM1lOCktyamR5TnB5MHN3SjI1TW9CbW1saUpFc3V2dCtDaEhseERqWE9KenF1U2owa1hPQVVsWUFTN1dKK09JMU9HbzQKUjcvdHdHZlFTNW9oYXpRVVlDR2lZSllYcjVRNkVKTmJOVVI0RjdpRSthY1I5Rm9GNnNKSmkrQStET1VDUFFSKwptbjQ5Zm1pcFVHSGtMc3BicTNFZ0FEME40VW5jcmIyeUJEMFNVTmdLQmJjclY1S2hybFA2SzkwNkY5NEpubzNHCm1Id1JwUUlEQVFBQm8xWXdWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RBWURWUjBUQVFIL0JBSXdBREFmQmdOVkhTTUVHREFXZ0JUV2VNZ1ZBRkRhbWcraDRqS3hoRUh2Q1l5egp5akFOQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBUUxMWWFXQTRva1M2b3ZEWjQ1Z28ybkVZdUR4MklmRXZwYnh3CkNmYkFTNDY4M3lLT3FiYVBHOEpTVGhSbkh3TWlMcVBrbGFsdkJvV2R3aFB5Vkk0d2tVNHI4Y2c0UEpxNEZwWnQKVkNUQzFPZWVwRGpTMFpVQjRwSDVIZVlNQUxqSDBqcFV3RU96djFpaEtid05IMHFoZ2pGeUNTdld5TG9oZHdzbApJWXIvV1NEZm50NlBETC84TjFpcEJJbEN5Z1JHVGdoSFhPemhHUklPWG4rYWVOR29yWm9YWm0xbHErc1hyUnc5CktNdVZhRmdhaWVjSm0vbytyemFFSG9VZjRYOERKeVNubmVTa3ViaEx6ZERNc2o5eEs1cEJpdFgvaDlQMUQrMkcKeW5rcWdJVTJSWTM0SjBRcnU4Z0syNlJVT2pOcHIvRWJHQ0dUQUxiMXJnSDM0K2NFdlE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
    32  	clientKeyData  = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQ3VTNlVxYitNelpwNSsKVy8zNHpjRTRpeDM5UytRVFEzaUZXakZISjBDVXpVREZYQzBOb2F6L0I5TGl5Z3VmaXNQblhBOHhFVklpUDZrcgpzVnFRT0JrWXZFOWVTUC9ORVUwN3Y5S3U4Ty9ST1Z4dllQdmNFcWFPS240S0VCa2srNkVzRlhwWEpMc2xZaUUzCmRnMHF1TjNJMm5MU3pBbmJreWdHYWFXSWtTeTYrMzRLRWVYRU9OYzRuT3E1S1BTUmM0QlNWZ0JMdFluNDRqVTQKYWpoSHYrM0FaOUJMbWlGck5CUmdJYUpnbGhldmxEb1FrMXMxUkhnWHVJVDVweEgwV2dYcXdrbUw0RDRNNVFJOQpCSDZhZmoxK2FLbFFZZVF1eWx1cmNTQUFQUTNoU2R5dHZiSUVQUkpRMkFvRnR5dFhrcUd1VS9vcjNUb1gzZ21lCmpjYVlmQkdsQWdNQkFBRUNnZ0VBZG80WndLM3VteTM0TFBjaDM3VUU4eE1keVFkd0VmSlk3a3dWTE5MMFNNTDgKaGNKWEd1aVlKYmtLcHh6TG55L2laV0xuS25jZnFSQW9ZQUg1R2hRdWJmYlkvY2NseURVMmxhZTdCU2Y1MkJUdQpYUXhaQks3aS85ekRjdERVYWFXSFVkY2lLbGhmdStQdHVDM2ljdWJnWlJqQjljUzRCOVVtNm9XK0JSREtuandICkduQ0lEZlNNQWt4VXdTaUwwa2NXelNpZ1BYMVN3UHcxOEZvZWgzTmJEd1VXTHhxUWZLVThydVlSTUsxYUg5M3cKcjFtbjlDWUwvd0hiVWRqcmtZMlIxTjVUR21ab2Vldm5qUXgyQVc2NkYzdEg1cGg4RTF6TEFQVTl4TFdRTW9KcwpXM0gzSTdUaEYvRnJuNERQa3hQbThUUVVhQUdvQ09SSWFUQkN5VlgxQVFLQmdRREkxbkRmNWYySHdHaldrTStpCk9YbGE1R1VnRUtXaGZpeGhidE5OclNpMDU5VnZQUEJwNXdtbGQzMHJKUDhWem8vbnFnUW5ISmpmaEQ2Y3NSMTQKL2VlMHZ1Um0zYzZwZzMrODdwOHhWY3lLNHhDd0JmdFFuMGRZWWFLMkRMOEtYb0liYThpN09EQmFoNW5OZWQwcgpKa1RPcE5NRGRkL0p0bEpPZ25jRXBlUk9oUUtCZ1FEZUt1L0R1MXU1QVR3Y3p5STRXOWV1L1YwTXRwMHdqM3RpClF2MmpObW83QU1zS3BwK0ZKVDFqWFhUKzZCTm02OWpxUVJwdlAyd2RhVUdqV1dLa1lHVEVpbUZCc2ZuKzJDOFAKOEc3Uk50YWpRdEV2QlR1ZDZPN0tZUkFoTU56dm9RcDkrZmJKY1ZsRG13Nlk0bUYzUTJXS3NmZU51TGtpY3VqNQpYVFV1ekVMd29RS0JnUUREU0IvQTFYVEx4cjhwd3V6aHBGam5sQ1R3Skwrb1kzTHIya01EeUZkSWNCUU1jWWlpCnNNK2tZS2NJaUpTdnM0WWhrQ014bEpEZzVVbXNPbHVhQmVpQ3l3cHpLMEdEZWlWK285ZU90UXFLRVhkc2NLU0oKSkJiUFRVQlZHOWUyVVdiWkd0aTNrazhSOThBSkYzR0NQMWV3Um53WFpVb1FiSU5qYTJBbTJOZEJzUUtCZ0Q4eApOVXVXTWl1NE56SDJsTVExRTI4NXI4cmE4bkVLanN6UFF6ZTJWWmI4emNQMHl2RGpPOGZVb0YrVkFWZklBOFgxCnlLQVdDUm1BZytRRG03UW5tdUh3Zm1OaVRUcDRvVUpHWUM3d0N6TWE0VWNmbE9xQWc5TmFzbXpPYWpsYXRCSkwKRkRBT0pwYTlOdlN6aDRlVnl2OGRTYzJzMmpQN1BWc1ljUFVqc25LaEFvR0JBSy9kQjlnVEFpME5nczVmaVNtWQovWkp3Yk52MjcyTHdKbWV4Vit2eWtjN3J5LzRraTRQb2xRd1BHNzQ5eFZ0T2NNc2FhRlVNMVVkclN2NlIwbjlkCmpTbXhCeTl2YWdzc1FmVDNSc3BvUUJKM0w5YWxiNHM2V2ZtUEpzNkFrQkhIZHNpVXFaaElYT2J2WE1lQ0k2aVMKOTQ2R0toekFxMlVGbjhFUGxXaFVNeEFiCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"
    33  )
    34  
    35  var _ = Describe("WrapForCFOnK8sAuth", func() {
    36  	var (
    37  		config              *commandfakes.FakeConfig
    38  		k8sConfigGetter     *v7actionfakes.FakeKubernetesConfigGetter
    39  		req                 *http.Request
    40  		res                 *http.Response
    41  		actualRes           *http.Response
    42  		kubeConfig          *api.Config
    43  		wrapErr             error
    44  		wrappedRoundTripper *sharedfakes.FakeRoundTripper
    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  		var err error
    74  		req, err = http.NewRequest(http.MethodPost, "", strings.NewReader("hello"))
    75  		Expect(err).NotTo(HaveOccurred())
    76  
    77  		wrappedRoundTripper = new(sharedfakes.FakeRoundTripper)
    78  		res = &http.Response{StatusCode: http.StatusTeapot}
    79  
    80  		wrappedRoundTripper.RoundTripReturns(res, nil)
    81  		actualRes = nil
    82  	})
    83  
    84  	JustBeforeEach(func() {
    85  		var roundTripper http.RoundTripper
    86  		roundTripper, wrapErr = shared.WrapForCFOnK8sAuth(config, k8sConfigGetter, wrappedRoundTripper)
    87  
    88  		if wrapErr == nil {
    89  			actualRes, wrapErr = roundTripper.RoundTrip(req)
    90  		}
    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(wrapErr).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(wrapErr).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(wrapErr).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(wrapErr).To(MatchError(ContainSubstring(`auth info "not-present" does not exist`)))
   130  		})
   131  	})
   132  
   133  	checkCalls := func() *http.Request {
   134  		Expect(wrapErr).NotTo(HaveOccurred())
   135  		Expect(wrappedRoundTripper.RoundTripCallCount()).To(Equal(1))
   136  
   137  		actualReq := wrappedRoundTripper.RoundTripArgsForCall(0)
   138  
   139  		body, err := ioutil.ReadAll(actualReq.Body)
   140  		Expect(err).NotTo(HaveOccurred())
   141  		Expect(string(body)).To(Equal("hello"))
   142  
   143  		Expect(actualRes).To(Equal(res))
   144  
   145  		return actualReq
   146  	}
   147  
   148  	checkBearerTokenInAuthHeader := func() {
   149  		actualReq := checkCalls()
   150  
   151  		token, err := jws.ParseJWTFromRequest(actualReq)
   152  		Expect(err).NotTo(HaveOccurred())
   153  		Expect(token.Validate(keyPair.Public(), crypto.SigningMethodRS256)).To(Succeed())
   154  
   155  		claims := token.Claims()
   156  		Expect(claims).To(HaveKeyWithValue("another", "thing"))
   157  	}
   158  
   159  	checkClientCertInAuthHeader := func() {
   160  		actualReq := checkCalls()
   161  
   162  		Expect(actualReq.Header).To(HaveKeyWithValue("Authorization", ConsistOf(HavePrefix("ClientCert "))))
   163  
   164  		certAndKeyPEMBase64 := actualReq.Header.Get("Authorization")[11:]
   165  		certAndKeyPEM, err := base64.StdEncoding.DecodeString(certAndKeyPEMBase64)
   166  		Expect(err).NotTo(HaveOccurred())
   167  
   168  		cert, rest := pem.Decode(certAndKeyPEM)
   169  		Expect(cert.Type).To(Equal(pemDecodeKubeConfigCertData(clientCertData).Type))
   170  		Expect(cert.Bytes).To(Equal(pemDecodeKubeConfigCertData(clientCertData).Bytes))
   171  
   172  		var key *pem.Block
   173  		key, rest = pem.Decode(rest)
   174  		Expect(key.Bytes).To(Equal(pemDecodeKubeConfigCertData(clientKeyData).Bytes))
   175  
   176  		Expect(rest).To(BeEmpty())
   177  	}
   178  
   179  	Describe("auth-provider", func() {
   180  		var token []byte
   181  
   182  		BeforeEach(func() {
   183  			jwt := jws.NewJWT(jws.Claims{
   184  				"exp":     time.Now().Add(time.Hour).Unix(),
   185  				"another": "thing",
   186  			}, crypto.SigningMethodRS256)
   187  			var err error
   188  			token, err = jwt.Serialize(keyPair)
   189  			Expect(err).NotTo(HaveOccurred())
   190  
   191  			kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   192  				AuthProvider: &api.AuthProviderConfig{
   193  					Name: "oidc",
   194  					Config: map[string]string{
   195  						"id-token":       string(token),
   196  						"idp-issuer-url": "-",
   197  						"client-id":      "-",
   198  					},
   199  				},
   200  			}
   201  		})
   202  
   203  		It("uses the auth-provider to generate the Bearer token", func() {
   204  			checkBearerTokenInAuthHeader()
   205  		})
   206  	})
   207  
   208  	Describe("client certs", func() {
   209  		var (
   210  			certFilePath string
   211  			keyFilePath  string
   212  		)
   213  
   214  		BeforeEach(func() {
   215  			certFilePath = writeToFile(clientCertData)
   216  			keyFilePath = writeToFile(clientKeyData)
   217  		})
   218  
   219  		AfterEach(func() {
   220  			Expect(os.RemoveAll(certFilePath)).To(Succeed())
   221  			Expect(os.RemoveAll(keyFilePath)).To(Succeed())
   222  		})
   223  
   224  		When("inline cert and key are provided", func() {
   225  			BeforeEach(func() {
   226  				kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   227  					ClientCertificateData: []byte(base64Decode(clientCertData)),
   228  					ClientKeyData:         []byte(base64Decode(clientKeyData)),
   229  				}
   230  			})
   231  
   232  			It("puts concatenated client ceritificate and key data into the Authorization header", func() {
   233  				checkClientCertInAuthHeader()
   234  			})
   235  		})
   236  
   237  		When("cert and key are provided in files", func() {
   238  			BeforeEach(func() {
   239  				kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   240  					ClientCertificate: certFilePath,
   241  					ClientKey:         keyFilePath,
   242  				}
   243  			})
   244  
   245  			It("puts concatenated client ceritificate and key data into the Authorization header", func() {
   246  				checkClientCertInAuthHeader()
   247  			})
   248  
   249  			When("cert file cannot be read", func() {
   250  				BeforeEach(func() {
   251  					Expect(os.Remove(certFilePath)).To(Succeed())
   252  				})
   253  
   254  				It("returns an error", func() {
   255  					Expect(wrapErr).To(MatchError(ContainSubstring(certFilePath)))
   256  				})
   257  			})
   258  
   259  			When("key file cannot be read", func() {
   260  				BeforeEach(func() {
   261  					Expect(os.Remove(keyFilePath)).To(Succeed())
   262  				})
   263  
   264  				It("returns an error", func() {
   265  					Expect(wrapErr).To(MatchError(ContainSubstring(keyFilePath)))
   266  				})
   267  			})
   268  		})
   269  
   270  		When("file and inline cert is provided", func() {
   271  			BeforeEach(func() {
   272  				kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   273  					ClientCertificate:     certFilePath,
   274  					ClientCertificateData: []byte(base64Decode(clientCertData)),
   275  					ClientKeyData:         []byte(base64Decode(clientKeyData)),
   276  				}
   277  			})
   278  
   279  			It("complains about invalid configuration", func() {
   280  				Expect(wrapErr).To(MatchError(ContainSubstring("client-cert-data and client-cert are both specified")))
   281  			})
   282  		})
   283  
   284  		When("file and inline key is provided", func() {
   285  			BeforeEach(func() {
   286  				kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   287  					ClientCertificateData: []byte(base64Decode(clientCertData)),
   288  					ClientKeyData:         []byte(base64Decode(clientKeyData)),
   289  					ClientKey:             keyFilePath,
   290  				}
   291  			})
   292  
   293  			It("complains about invalid configuration", func() {
   294  				Expect(wrapErr).To(MatchError(ContainSubstring("client-key-data and client-key are both specified")))
   295  			})
   296  		})
   297  
   298  		When("inline cert and key file are provided", func() {
   299  			BeforeEach(func() {
   300  				kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   301  					ClientCertificateData: []byte(base64Decode(clientCertData)),
   302  					ClientKey:             keyFilePath,
   303  				}
   304  			})
   305  
   306  			It("uses the inline key", func() {
   307  				checkClientCertInAuthHeader()
   308  			})
   309  		})
   310  
   311  		When("cert file and inline key are provided", func() {
   312  			BeforeEach(func() {
   313  				kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   314  					ClientCertificate: certFilePath,
   315  					ClientKeyData:     []byte(base64Decode(clientKeyData)),
   316  				}
   317  			})
   318  
   319  			It("uses the inline key", func() {
   320  				checkClientCertInAuthHeader()
   321  			})
   322  		})
   323  	})
   324  
   325  	Describe("exec", func() {
   326  		BeforeEach(func() {
   327  			kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   328  				Exec: &api.ExecConfig{
   329  					APIVersion:      "client.authentication.k8s.io/v1beta1",
   330  					InteractiveMode: "Never",
   331  					Command:         "echo",
   332  				},
   333  			}
   334  		})
   335  
   336  		When("the command returns a token", func() {
   337  			BeforeEach(func() {
   338  				kubeConfig.AuthInfos["auth-test"].Exec.Args = []string{execCredential(&clientauthenticationv1beta1.ExecCredentialStatus{
   339  					Token: "a-token",
   340  				})}
   341  			})
   342  
   343  			It("uses the exec command to generate the Bearer token", func() {
   344  				helpers.SkipIfWindows() // We're getting "plugin returned version client.authentication.k8s.io/__internal" on Windows. This issue is unresolved in upstream library.
   345  
   346  				Expect(wrapErr).NotTo(HaveOccurred())
   347  				Expect(wrappedRoundTripper.RoundTripCallCount()).To(Equal(1))
   348  
   349  				actualReq := wrappedRoundTripper.RoundTripArgsForCall(0)
   350  				Expect(actualReq.Header.Get("Authorization")).To(Equal("Bearer a-token"))
   351  
   352  				Expect(actualRes).To(Equal(res))
   353  			})
   354  		})
   355  
   356  		When("the command returns a client cert and key", func() {
   357  			BeforeEach(func() {
   358  				kubeConfig.AuthInfos["auth-test"].Exec.Args = []string{execCredential(&clientauthenticationv1beta1.ExecCredentialStatus{
   359  					ClientCertificateData: base64Decode(clientCertData),
   360  					ClientKeyData:         base64Decode(clientKeyData),
   361  				})}
   362  			})
   363  
   364  			It("uses the exec command to generate client certs", func() {
   365  				checkClientCertInAuthHeader()
   366  			})
   367  		})
   368  	})
   369  
   370  	Describe("tokens provided in config", func() {
   371  		var token []byte
   372  
   373  		BeforeEach(func() {
   374  			jwt := jws.NewJWT(jws.Claims{
   375  				"exp":     time.Now().Add(time.Hour).Unix(),
   376  				"another": "thing",
   377  			}, crypto.SigningMethodRS256)
   378  			var err error
   379  			token, err = jwt.Serialize(keyPair)
   380  			Expect(err).NotTo(HaveOccurred())
   381  		})
   382  
   383  		Context("inline tokens", func() {
   384  			BeforeEach(func() {
   385  				kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   386  					Token: string(token),
   387  				}
   388  			})
   389  
   390  			It("inserts the token in the authorization header", func() {
   391  				checkBearerTokenInAuthHeader()
   392  			})
   393  		})
   394  
   395  		Context("token file paths", func() {
   396  			var tokenFilePath string
   397  
   398  			BeforeEach(func() {
   399  				tokenFile, err := ioutil.TempFile("", "")
   400  				Expect(err).NotTo(HaveOccurred())
   401  				defer tokenFile.Close()
   402  				_, err = tokenFile.Write(token)
   403  				Expect(err).NotTo(HaveOccurred())
   404  				tokenFilePath = tokenFile.Name()
   405  				kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   406  					TokenFile: tokenFilePath,
   407  				}
   408  			})
   409  
   410  			AfterEach(func() {
   411  				Expect(os.RemoveAll(tokenFilePath)).To(Succeed())
   412  			})
   413  
   414  			It("inserts the token in the authorization header", func() {
   415  				checkBearerTokenInAuthHeader()
   416  			})
   417  		})
   418  
   419  		When("both file and inline token are provided", func() {
   420  			BeforeEach(func() {
   421  				kubeConfig.AuthInfos["auth-test"] = &api.AuthInfo{
   422  					Token:     string(token),
   423  					TokenFile: "some-path",
   424  				}
   425  			})
   426  
   427  			It("the inline token takes precedence", func() {
   428  				checkBearerTokenInAuthHeader()
   429  			})
   430  		})
   431  	})
   432  })
   433  
   434  func pemDecodeKubeConfigCertData(data string) *pem.Block {
   435  	decodedData, err := base64.StdEncoding.DecodeString(data)
   436  	Expect(err).NotTo(HaveOccurred())
   437  	pemDecodedBlock, rest := pem.Decode(decodedData)
   438  	Expect(rest).To(BeEmpty())
   439  	return pemDecodedBlock
   440  }
   441  
   442  func base64Decode(encoded string) string {
   443  	decoded, err := base64.StdEncoding.DecodeString(encoded)
   444  	Expect(err).NotTo(HaveOccurred())
   445  	return string(decoded)
   446  }
   447  
   448  func writeToFile(base64Data string) string {
   449  	file, err := ioutil.TempFile("", "")
   450  	Expect(err).NotTo(HaveOccurred())
   451  	file.WriteString(base64Decode(base64Data))
   452  	Expect(file.Close()).To(Succeed())
   453  	return file.Name()
   454  }
   455  
   456  func execCredential(status *clientauthenticationv1beta1.ExecCredentialStatus) string {
   457  	execCred, err := json.Marshal(clientauthenticationv1beta1.ExecCredential{
   458  		TypeMeta: metav1.TypeMeta{
   459  			APIVersion: "client.authentication.k8s.io/v1beta1",
   460  		},
   461  		Status: status,
   462  	})
   463  	Expect(err).NotTo(HaveOccurred())
   464  	return string(execCred)
   465  }