Skip to content

Commit 27e0d3a

Browse files
GIT-2731: enable TokenRequest for ServiceAccount token generation
1 parent 2577d1c commit 27e0d3a

File tree

4 files changed

+92
-74
lines changed

4 files changed

+92
-74
lines changed

test/e2e/kind-config-v1.18.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2022 The Kubernetes Authors.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
kind: Cluster
16+
apiVersion: kind.x-k8s.io/v1alpha4
17+
nodes:
18+
- role: control-plane
19+
kubeadmConfigPatches:
20+
- |
21+
kind: ClusterConfiguration
22+
metadata:
23+
name: config
24+
apiServer:
25+
extraArgs:
26+
"service-account-signing-key-file": /etc/kubernetes/pki/sa.key
27+
"service-account-key-file": /etc/kubernetes/pki/sa.pub
28+
"service-account-issuer": api
29+
"service-account-api-audiences": api,vault,factors
30+

test/e2e/kind-config-v1.19.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
kind-config-v1.18.yaml

test/e2e/setup.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,16 @@ install_kind
2727
# export KIND_CLUSTER=<kind cluster name>
2828
# create_cluster <k8s version>
2929
function create_cluster {
30+
KIND_VERSION=$1
3031
: ${KIND_CLUSTER:?"KIND_CLUSTER must be set"}
3132
: ${1:?"k8s version must be set as arg 1"}
3233
if ! kind get clusters | grep -q $KIND_CLUSTER ; then
33-
kind create cluster -v 4 --name $KIND_CLUSTER --retain --wait=1m --config $(dirname "$0")/kind-config.yaml --image=kindest/node:$1
34+
version_prefix="${KIND_VERSION%.*}"
35+
kind_config=$(dirname "$0")/kind-config.yaml
36+
if test -f $(dirname "$0")/kind-config-${version_prefix}.yaml; then
37+
kind_config=$(dirname "$0")/kind-config-${version_prefix}.yaml
38+
fi
39+
kind create cluster -v 4 --name $KIND_CLUSTER --retain --wait=1m --config ${kind_config} --image=kindest/node:$1
3440
fi
3541
}
3642

test/e2e/v3/plugin_cluster_test.go

Lines changed: 54 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ limitations under the License.
1717
package v3
1818

1919
import (
20-
"encoding/base64"
20+
"encoding/json"
2121
"fmt"
2222
"os"
2323
"path/filepath"
@@ -38,11 +38,22 @@ import (
3838
"sigs.k8s.io/kubebuilder/v3/test/e2e/utils"
3939
)
4040

41+
const (
42+
tokenRequestRawString = `{"apiVersion": "authentication.k8s.io/v1", "kind": "TokenRequest"}`
43+
)
44+
45+
// tokenRequest is a trimmed down version of the authentication.k8s.io/v1/TokenRequest Type
46+
// that we want to use for extracting the token.
47+
type tokenRequest struct {
48+
Status struct {
49+
Token string `json:"token"`
50+
} `json:"status"`
51+
}
52+
4153
var _ = Describe("kubebuilder", func() {
4254
Context("project version 3", func() {
4355
var (
4456
kbc *utils.TestContext
45-
sat bool
4657
)
4758

4859
BeforeEach(func() {
@@ -53,8 +64,6 @@ var _ = Describe("kubebuilder", func() {
5364

5465
By("installing the Prometheus operator")
5566
Expect(kbc.InstallPrometheusOperManager()).To(Succeed())
56-
57-
sat = false
5867
})
5968

6069
AfterEach(func() {
@@ -94,7 +103,7 @@ var _ = Describe("kubebuilder", func() {
94103
kbc.Kubectl.ServiceAccount = "default"
95104
defer func() { kbc.Kubectl.ServiceAccount = tmp }()
96105
GenerateV2(kbc)
97-
Run(kbc, sat)
106+
Run(kbc)
98107
})
99108
})
100109

@@ -119,15 +128,7 @@ var _ = Describe("kubebuilder", func() {
119128
}
120129

121130
GenerateV3(kbc, "v1")
122-
123-
// only if running on Kubernetes >= 1.24 do we need to generate the ServiceAccount token Secret
124-
// TODO: Remove this once a better implementation using something like the TokenRequest API
125-
// is used in the e2e tests
126-
if srvVer := kbc.K8sVersion.ServerVersion; srvVer.GetMajorInt() == 1 && srvVer.GetMinorInt() >= 24 {
127-
sat = true
128-
}
129-
130-
Run(kbc, sat)
131+
Run(kbc)
131132
})
132133
It("should generate a runnable project with the golang base plugin v3 and kustomize v4-alpha", func() {
133134
// Skip if cluster version < 1.16, when v1 CRDs and webhooks did not exist.
@@ -139,15 +140,7 @@ var _ = Describe("kubebuilder", func() {
139140
}
140141

141142
GenerateV3WithKustomizeV2(kbc, "v1")
142-
143-
// only if running on Kubernetes >= 1.24 do we need to generate the ServiceAccount token Secret
144-
// TODO: Remove this once a better implementation using something like the TokenRequest API
145-
// is used in the e2e tests
146-
if srvVer := kbc.K8sVersion.ServerVersion; srvVer.GetMajorInt() == 1 && srvVer.GetMinorInt() >= 24 {
147-
sat = true
148-
}
149-
150-
Run(kbc, sat)
143+
Run(kbc)
151144
})
152145
It("should generate a runnable project with v1beta1 CRDs and Webhooks", func() {
153146
// Skip if cluster version < 1.15, when `.spec.preserveUnknownFields` was not a v1beta1 CRD field.
@@ -161,14 +154,14 @@ var _ = Describe("kubebuilder", func() {
161154
}
162155

163156
GenerateV3(kbc, "v1beta1")
164-
Run(kbc, sat)
157+
Run(kbc)
165158
})
166159
})
167160
})
168161
})
169162

170163
// Run runs a set of e2e tests for a scaffolded project defined by a TestContext.
171-
func Run(kbc *utils.TestContext, sat bool) {
164+
func Run(kbc *utils.TestContext) {
172165
var controllerPodName string
173166
var err error
174167

@@ -233,10 +226,6 @@ func Run(kbc *utils.TestContext, sat bool) {
233226
fmt.Sprintf("--serviceaccount=%s:%s", kbc.Kubectl.Namespace, kbc.Kubectl.ServiceAccount))
234227
ExpectWithOffset(1, err).NotTo(HaveOccurred())
235228

236-
if sat {
237-
ServiceAccountToken(kbc)
238-
}
239-
240229
_ = curlMetrics(kbc)
241230

242231
By("validating that cert-manager has provisioned the certificate Secret")
@@ -347,21 +336,14 @@ func Run(kbc *utils.TestContext, sat bool) {
347336
func curlMetrics(kbc *utils.TestContext) string {
348337
By("reading the metrics token")
349338
// Filter token query by service account in case more than one exists in a namespace.
350-
// TODO: Parsing a token this way is not ideal or best practice and should not be used.
351-
// Instead, a TokenRequest should be created to get a token to use for this step.
352-
query := fmt.Sprintf(`{.items[?(@.metadata.annotations.kubernetes\.io/service-account\.name=="%s")].data.token}`,
353-
kbc.Kubectl.ServiceAccount,
354-
)
355-
b64Token, err := kbc.Kubectl.Get(true, "secrets", "-o=jsonpath="+query)
356-
ExpectWithOffset(2, err).NotTo(HaveOccurred())
357-
token, err := base64.StdEncoding.DecodeString(strings.TrimSpace(b64Token))
339+
token, err := ServiceAccountToken(kbc)
358340
ExpectWithOffset(2, err).NotTo(HaveOccurred())
359341
ExpectWithOffset(2, len(token)).To(BeNumerically(">", 0))
360342

361343
By("creating a curl pod")
362344
cmdOpts := []string{
363345
"run", "curl", "--image=curlimages/curl:7.68.0", "--restart=OnFailure", "--",
364-
"curl", "-v", "-k", "-H", fmt.Sprintf(`Authorization: Bearer %s`, token),
346+
"curl", "-v", "-k", "-H", fmt.Sprintf(`Authorization: Bearer %s`, strings.TrimSpace(token)),
365347
fmt.Sprintf("https://e2e-%s-controller-manager-metrics-service.%s.svc:8443/metrics",
366348
kbc.TestSuffix, kbc.Kubectl.Namespace),
367349
}
@@ -398,43 +380,42 @@ func curlMetrics(kbc *utils.TestContext) string {
398380
return metricsOutput
399381
}
400382

401-
// TODO: Remove this when and other related functions when
402-
// tests have been changed to use a better implementation.
403-
// ServiceAccountToken creates the ServiceAccount token Secret.
404-
// This is to be used when Kubernetes cluster version is >= 1.24.
405-
// In k8s 1.24+ a ServiceAccount token Secret is no longer
406-
// automatically generated
407-
func ServiceAccountToken(kbc *utils.TestContext) {
408-
// As of Kubernetes 1.24 a ServiceAccount no longer has a ServiceAccount token
409-
// secret autogenerated. We have to create it manually here
383+
// ServiceAccountToken provides a helper function that can provide you with a service account
384+
// token that you can use to interact with the service. This function leverages the k8s'
385+
// TokenRequest API in raw format in order to make it generic for all version of the k8s that
386+
// is currently being supported in kubebuilder test infra.
387+
// TokenRequest API returns the token in raw JWT format itself. There is no conversion required.
388+
func ServiceAccountToken(kbc *utils.TestContext) (out string, err error) {
410389
By("Creating the ServiceAccount token")
411-
secretFile, err := createServiceAccountToken(kbc.Kubectl.ServiceAccount, kbc.Dir)
412-
Expect(err).NotTo(HaveOccurred())
413-
Eventually(func() error {
414-
_, err = kbc.Kubectl.Apply(true, "-f", secretFile)
415-
return err
416-
}, time.Minute, time.Second).Should(Succeed())
417-
}
418-
419-
var saSecretTemplate = `---
420-
apiVersion: v1
421-
kind: Secret
422-
type: kubernetes.io/service-account-token
423-
metadata:
424-
name: %s
425-
annotations:
426-
kubernetes.io/service-account.name: "%s"
427-
`
428-
429-
// createServiceAccountToken writes a service account token secret to a file.
430-
// It returns a string to the file or an error if it fails to write the file
431-
func createServiceAccountToken(name string, dir string) (string, error) {
432-
secretName := name + "-secret"
433-
fileName := dir + "/" + secretName + ".yaml"
434-
err := os.WriteFile(fileName, []byte(fmt.Sprintf(saSecretTemplate, secretName, name)), 0777)
390+
secretName := fmt.Sprintf("%s-token-request", kbc.Kubectl.ServiceAccount)
391+
tokenRequestFile := filepath.Join(kbc.Dir, secretName)
392+
err = os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0755))
435393
if err != nil {
436-
return "", err
394+
return out, err
437395
}
396+
var rawJson string
397+
Eventually(func() error {
398+
// Output of this is already a valid JWT token. No need to covert this from base64 to string format
399+
rawJson, err = kbc.Kubectl.Command(
400+
"create",
401+
"--raw", fmt.Sprintf(
402+
"/api/v1/namespaces/%s/serviceaccounts/%s/token",
403+
kbc.Kubectl.Namespace,
404+
kbc.Kubectl.ServiceAccount,
405+
),
406+
"-f", tokenRequestFile,
407+
)
408+
if err != nil {
409+
return err
410+
}
411+
var token tokenRequest
412+
err = json.Unmarshal([]byte(rawJson), &token)
413+
if err != nil {
414+
return err
415+
}
416+
out = token.Status.Token
417+
return nil
418+
}, time.Minute, time.Second).Should(Succeed())
438419

439-
return fileName, nil
420+
return out, err
440421
}

0 commit comments

Comments
 (0)