Skip to content

Commit ccf9d4f

Browse files
authored
Merge pull request #187 from projectsyn/feat/get-sa-k8s-1.24
Add support for Kubernetes 1.24
2 parents ac6cc78 + 2e98074 commit ccf9d4f

4 files changed

Lines changed: 216 additions & 54 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ build: generate
6666

6767
.PHONY: test
6868
test: generate
69-
$(GOTEST) -v -cover ./...
69+
$(GOTEST) -cover ./...
7070

7171
.PHONY: run
7272
run: generate

pkg/service/api_service_test.go

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,28 +122,76 @@ var (
122122
},
123123
},
124124
}
125+
// A secret that seems to have the correct annotation and data, but is not of type service account token
126+
wrongSecret = &corev1.Secret{
127+
ObjectMeta: metav1.ObjectMeta{
128+
Name: "bootstrap-token",
129+
Namespace: clusterA.Namespace,
130+
Annotations: map[string]string{
131+
corev1.ServiceAccountNameKey: clusterA.Name,
132+
},
133+
},
134+
Type: corev1.SecretTypeBootstrapToken,
135+
Data: map[string][]byte{"token": []byte("notAtoken")},
136+
}
137+
clusterBSecret = &corev1.Secret{
138+
ObjectMeta: metav1.ObjectMeta{
139+
Name: "anotherName", // We do not have guarantees that the secret name matches any fixed naming scheme
140+
Namespace: clusterB.Namespace,
141+
CreationTimestamp: metav1.NewTime(time.Now().Add(-1 * time.Hour)),
142+
Annotations: map[string]string{
143+
corev1.ServiceAccountNameKey: clusterB.Name,
144+
},
145+
},
146+
Type: corev1.SecretTypeServiceAccountToken,
147+
Data: map[string][]byte{"token": []byte("someothertoken")},
148+
}
149+
125150
clusterASecret = &corev1.Secret{
151+
ObjectMeta: metav1.ObjectMeta{
152+
Name: clusterA.Name,
153+
Namespace: clusterA.Namespace,
154+
CreationTimestamp: metav1.NewTime(time.Now().Add(-1 * time.Hour)),
155+
Annotations: map[string]string{
156+
corev1.ServiceAccountNameKey: clusterA.Name,
157+
},
158+
},
159+
Type: corev1.SecretTypeServiceAccountToken,
160+
Data: map[string][]byte{"token": []byte("sometoken")},
161+
}
162+
newClusterASecret = &corev1.Secret{
163+
ObjectMeta: metav1.ObjectMeta{
164+
Name: "new-secret",
165+
Namespace: clusterA.Namespace,
166+
CreationTimestamp: metav1.NewTime(time.Now()),
167+
Annotations: map[string]string{
168+
corev1.ServiceAccountNameKey: clusterA.Name,
169+
},
170+
},
171+
Type: corev1.SecretTypeServiceAccountToken,
172+
Data: map[string][]byte{"token": []byte("newtoken")},
173+
}
174+
clusterASA = &corev1.ServiceAccount{
126175
ObjectMeta: metav1.ObjectMeta{
127176
Name: clusterA.Name,
128177
Namespace: clusterA.Namespace,
129178
},
130-
Data: map[string][]byte{"token": []byte("sometoken")},
131179
}
132180
testObjects = []client.Object{
133181
tenantA,
134182
tenantB,
135183
clusterA,
184+
wrongSecret,
185+
clusterBSecret,
186+
newClusterASecret,
136187
clusterASecret,
137188
clusterB,
189+
clusterASA,
138190
&corev1.ServiceAccount{
139191
ObjectMeta: metav1.ObjectMeta{
140-
Name: clusterA.Name,
141-
Namespace: clusterA.Namespace,
192+
Name: clusterB.Name,
193+
Namespace: clusterB.Namespace,
142194
},
143-
Secrets: []corev1.ObjectReference{{
144-
Name: clusterASecret.Name,
145-
Namespace: clusterASecret.Namespace,
146-
}},
147195
},
148196
}
149197
)
@@ -167,8 +215,12 @@ func TestNewServer(t *testing.T) {
167215
}
168216

169217
func setupTest(t *testing.T, _ ...[]runtime.Object) (*echo.Echo, client.Client) {
218+
return rawSetupTest(t, testObjects...)
219+
}
220+
221+
func rawSetupTest(t *testing.T, obj ...client.Object) (*echo.Echo, client.Client) {
170222

171-
f := fake.NewClientBuilder().WithScheme(scheme).WithObjects(testObjects...).Build()
223+
f := fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj...).Build()
172224
testMiddleWare := KubernetesAuth{
173225
CreateClientFunc: func(token string) (client.Client, error) {
174226
return f, nil

pkg/service/steward.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"k8s.io/apimachinery/pkg/api/resource"
1515
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1616
"k8s.io/apimachinery/pkg/runtime"
17-
"k8s.io/apimachinery/pkg/types"
1817
"sigs.k8s.io/controller-runtime/pkg/client"
1918

2019
"github.com/projectsyn/lieutenant-api/pkg/api"
@@ -96,25 +95,39 @@ func (s *APIImpl) InstallSteward(c echo.Context, params api.InstallStewardParams
9695
}
9796

9897
func (s *APIImpl) getServiceAccountToken(ctx *APIContext, saName string) (string, error) {
99-
serviceAccount := &corev1.ServiceAccount{}
100-
if err := ctx.client.Get(ctx.Request().Context(), types.NamespacedName{Name: saName, Namespace: s.namespace}, serviceAccount); err != nil {
101-
return "", err
102-
}
10398

104-
if len(serviceAccount.Secrets) < 1 {
105-
return "", echo.NewHTTPError(http.StatusInternalServerError, "No secret found for ServiceAccount: '%s'", saName)
106-
}
107-
secretName := serviceAccount.Secrets[0]
108-
secret := &corev1.Secret{}
109-
if err := ctx.client.Get(ctx.Request().Context(), types.NamespacedName{Name: secretName.Name, Namespace: serviceAccount.Namespace}, secret); err != nil {
99+
secrets := &corev1.SecretList{}
100+
if err := ctx.client.List(
101+
ctx.Request().Context(),
102+
secrets,
103+
client.InNamespace(s.namespace),
104+
client.MatchingFields{"type": string(corev1.SecretTypeServiceAccountToken)},
105+
); err != nil {
110106
return "", err
111107
}
112108

113-
if len(secret.Data["token"]) < 1 {
114-
return "", echo.NewHTTPError(http.StatusInternalServerError, "Secret doesn't contain a token: '%s'", secretName.Name)
109+
token := findOldestSAToken(secrets.Items, saName)
110+
if token == "" {
111+
return "", echo.NewHTTPError(http.StatusServiceUnavailable, "Unable to find token for Cluster. This error might be transient, please try again.")
115112
}
113+
return token, nil
114+
}
116115

117-
return string(secret.Data["token"]), nil
116+
func findOldestSAToken(secrets []corev1.Secret, saName string) string {
117+
token := ""
118+
var created *metav1.Time
119+
120+
for i, secret := range secrets {
121+
if secret.Type == corev1.SecretTypeServiceAccountToken && // Not strictly necessary but our testing framework can't handle field selectors
122+
secret.Annotations[corev1.ServiceAccountNameKey] == saName &&
123+
len(secret.Data[corev1.ServiceAccountTokenKey]) > 0 &&
124+
!created.Before(&secret.CreationTimestamp) {
125+
126+
token = string(secret.Data[corev1.ServiceAccountTokenKey])
127+
created = &secrets[i].CreationTimestamp
128+
}
129+
}
130+
return token
118131
}
119132

120133
func createRBAC() []runtime.RawExtension {

pkg/service/steward_test.go

Lines changed: 128 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,148 @@ package service
33
import (
44
"net/http"
55
"testing"
6+
"time"
67

78
"github.com/deepmap/oapi-codegen/pkg/testutil"
89
"github.com/stretchr/testify/assert"
910
appsv1 "k8s.io/api/apps/v1"
1011
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1113
"k8s.io/apimachinery/pkg/runtime"
1214
"k8s.io/apimachinery/pkg/runtime/serializer/json"
15+
"sigs.k8s.io/controller-runtime/pkg/client"
1316

1417
"github.com/projectsyn/lieutenant-api/pkg/api"
1518
)
1619

1720
func TestInstallSteward(t *testing.T) {
18-
e, _ := setupTest(t)
1921

20-
result := testutil.NewRequest().
21-
WithHeader("X-Forwarded-Proto", "https").
22-
Get("/install/steward.json?token="+clusterA.Status.BootstrapToken.Token).
23-
Go(t, e)
24-
assert.Equal(t, http.StatusOK, result.Code())
25-
manifests := &corev1.List{}
26-
err := result.UnmarshalJsonToObject(&manifests)
27-
assert.NoError(t, err)
28-
assert.Len(t, manifests.Items, 6)
29-
decoder := json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, true)
30-
foundSecret := false
31-
foundDeployment := false
32-
for i, item := range manifests.Items {
33-
obj, err := runtime.Decode(decoder, item.Raw)
34-
assert.NoError(t, err)
35-
if i == 0 {
36-
_, ok := obj.(*corev1.Namespace)
37-
assert.True(t, ok, "First object needs to be a namespace")
38-
}
39-
if secret, ok := obj.(*corev1.Secret); ok {
40-
foundSecret = true
41-
assert.Equal(t, secret.StringData["token"], string(clusterASecret.Data["token"]))
42-
}
43-
if deployment, ok := obj.(*appsv1.Deployment); ok {
44-
foundDeployment = true
45-
assert.Equal(t, "https://example.com", deployment.Spec.Template.Spec.Containers[0].Env[0].Value)
46-
assert.Equal(t, clusterA.Name, deployment.Spec.Template.Spec.Containers[0].Env[1].Value)
47-
}
22+
tcs := map[string]struct {
23+
bootstrapToken string
24+
objs []client.Object
25+
saToken string
26+
clusterName string
27+
}{
28+
"default": {
29+
bootstrapToken: clusterA.Status.BootstrapToken.Token,
30+
objs: testObjects,
31+
saToken: "sometoken",
32+
clusterName: clusterA.Name,
33+
},
34+
"reordered": {
35+
bootstrapToken: clusterA.Status.BootstrapToken.Token,
36+
objs: []client.Object{
37+
newClusterASecret,
38+
clusterA,
39+
tenantA,
40+
wrongSecret,
41+
clusterASA,
42+
clusterASecret,
43+
},
44+
saToken: "sometoken",
45+
clusterName: clusterA.Name,
46+
},
47+
"older secret": {
48+
bootstrapToken: clusterA.Status.BootstrapToken.Token,
49+
objs: []client.Object{
50+
newClusterASecret,
51+
tenantA,
52+
clusterASecret,
53+
&corev1.Secret{
54+
ObjectMeta: metav1.ObjectMeta{
55+
Name: "old-secret",
56+
Namespace: clusterA.Namespace,
57+
CreationTimestamp: metav1.NewTime(time.Now().Add(-24 * time.Hour)),
58+
Annotations: map[string]string{
59+
corev1.ServiceAccountNameKey: clusterA.Name,
60+
},
61+
},
62+
Type: corev1.SecretTypeServiceAccountToken,
63+
Data: map[string][]byte{"token": []byte("someoldertoken")},
64+
},
65+
clusterA,
66+
wrongSecret,
67+
clusterASA,
68+
},
69+
saToken: "someoldertoken",
70+
clusterName: clusterA.Name,
71+
},
72+
"even older secret": {
73+
bootstrapToken: clusterA.Status.BootstrapToken.Token,
74+
objs: []client.Object{
75+
tenantA,
76+
&corev1.Secret{
77+
ObjectMeta: metav1.ObjectMeta{
78+
Name: "old-secret",
79+
Namespace: clusterA.Namespace,
80+
CreationTimestamp: metav1.NewTime(time.Now().Add(-24 * time.Hour)),
81+
Annotations: map[string]string{
82+
corev1.ServiceAccountNameKey: clusterA.Name,
83+
},
84+
},
85+
Type: corev1.SecretTypeServiceAccountToken,
86+
Data: map[string][]byte{"token": []byte("someoldertoken")},
87+
},
88+
clusterA,
89+
wrongSecret,
90+
clusterASA,
91+
&corev1.Secret{
92+
ObjectMeta: metav1.ObjectMeta{
93+
Name: "arcane-secret",
94+
Namespace: clusterA.Namespace,
95+
CreationTimestamp: metav1.NewTime(time.Unix(0, 0)),
96+
Annotations: map[string]string{
97+
corev1.ServiceAccountNameKey: clusterA.Name,
98+
},
99+
},
100+
Type: corev1.SecretTypeServiceAccountToken,
101+
Data: map[string][]byte{"token": []byte("mysterytoken")},
102+
},
103+
newClusterASecret,
104+
clusterASecret,
105+
},
106+
saToken: "mysterytoken",
107+
clusterName: clusterA.Name,
108+
},
109+
}
110+
111+
for n, tc := range tcs {
112+
t.Run(n, func(t *testing.T) {
113+
e, _ := rawSetupTest(t, tc.objs...)
114+
115+
result := testutil.NewRequest().
116+
WithHeader("X-Forwarded-Proto", "https").
117+
Get("/install/steward.json?token="+tc.bootstrapToken).
118+
Go(t, e)
119+
assert.Equal(t, http.StatusOK, result.Code())
120+
manifests := &corev1.List{}
121+
err := result.UnmarshalJsonToObject(&manifests)
122+
assert.NoError(t, err)
123+
assert.Len(t, manifests.Items, 6)
124+
decoder := json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, true)
125+
foundSecret := false
126+
foundDeployment := false
127+
for i, item := range manifests.Items {
128+
obj, err := runtime.Decode(decoder, item.Raw)
129+
assert.NoError(t, err)
130+
if i == 0 {
131+
_, ok := obj.(*corev1.Namespace)
132+
assert.True(t, ok, "First object needs to be a namespace")
133+
}
134+
if secret, ok := obj.(*corev1.Secret); ok {
135+
foundSecret = true
136+
assert.Equal(t, tc.saToken, secret.StringData["token"])
137+
}
138+
if deployment, ok := obj.(*appsv1.Deployment); ok {
139+
foundDeployment = true
140+
assert.Equal(t, "https://example.com", deployment.Spec.Template.Spec.Containers[0].Env[0].Value)
141+
assert.Equal(t, tc.clusterName, deployment.Spec.Template.Spec.Containers[0].Env[1].Value)
142+
}
143+
}
144+
assert.True(t, foundSecret, "Could not find secret with steward token")
145+
assert.True(t, foundDeployment, "Could not find deployment for steward")
146+
})
48147
}
49-
assert.True(t, foundSecret, "Could not find secret with steward token")
50-
assert.True(t, foundDeployment, "Could not find deployment for steward")
51148
}
52149

53150
func TestInstallStewardNoToken(t *testing.T) {

0 commit comments

Comments
 (0)