-
Notifications
You must be signed in to change notification settings - Fork 450
/
serviceaccount.go
165 lines (142 loc) · 7.47 KB
/
serviceaccount.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0
package access
import (
"context"
"fmt"
"time"
authenticationv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
"github.com/gardener/gardener/pkg/client/kubernetes"
"github.com/gardener/gardener/pkg/controllerutils"
"github.com/gardener/gardener/pkg/utils"
"github.com/gardener/gardener/pkg/utils/flow"
"github.com/gardener/gardener/pkg/utils/retry"
)
const namespaceE2ETestServiceAccountTokenAccess = metav1.NamespaceDefault
// labelsE2ETestDynamicServiceAccountTokenAccess is the set of labels added to all ServiceAccounts and
// ClusterRoleBindings for easy cleanup.
var labelsE2ETestDynamicServiceAccountTokenAccess = map[string]string{"e2e-test": "serviceaccount-dynamic-access"}
// CreateTargetClientFromDynamicServiceAccountToken creates a ServiceAccount, uses the kube-apiserver's TokenRequest API
// to request a token for it, and then creates a new target client from it.
// You should call CleanupObjectsFromDynamicServiceAccountTokenAccess to clean up the objects created by this function.
func CreateTargetClientFromDynamicServiceAccountToken(ctx context.Context, targetClient kubernetes.Interface, name string) (kubernetes.Interface, error) {
return createTargetClientFromServiceAccount(ctx, targetClient, name, labelsE2ETestDynamicServiceAccountTokenAccess, func(serviceAccount *corev1.ServiceAccount) (string, error) {
tokenRequest := &authenticationv1.TokenRequest{
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{v1beta1constants.GardenerAudience},
ExpirationSeconds: ptr.To[int64](3600),
},
}
if err := targetClient.Client().SubResource("token").Create(ctx, serviceAccount, tokenRequest); err != nil {
return "", err
}
return tokenRequest.Status.Token, nil
})
}
// CleanupObjectsFromDynamicServiceAccountTokenAccess cleans up all objects in the target created by all calls to
// CreateTargetClientFromDynamicServiceAccountToken.
func CleanupObjectsFromDynamicServiceAccountTokenAccess(ctx context.Context, targetClient kubernetes.Interface) error {
return flow.Parallel(func(ctx context.Context) error {
return targetClient.Client().DeleteAllOf(ctx, &corev1.ServiceAccount{}, client.InNamespace(namespaceE2ETestServiceAccountTokenAccess), client.MatchingLabels(labelsE2ETestDynamicServiceAccountTokenAccess))
}, func(ctx context.Context) error {
return targetClient.Client().DeleteAllOf(ctx, &rbacv1.ClusterRoleBinding{}, client.MatchingLabels(labelsE2ETestDynamicServiceAccountTokenAccess))
})(ctx)
}
// labelsE2ETestStaticServiceAccountToken is the set of labels added to all ServiceAccounts, Secrets, and
// ClusterRoleBindings for easy cleanup.
var labelsE2ETestStaticServiceAccountToken = map[string]string{"e2e-test": "serviceaccount-static-access"}
// CreateShootClientFromStaticServiceAccountToken creates a ServiceAccount, a corresponding static token secret (issued
// by kube-controller-manager), and then creates a new shoot client from it.
// You should call CleanupObjectsFromStaticServiceAccountTokenAccess to clean up the objects created by this function.
func CreateShootClientFromStaticServiceAccountToken(ctx context.Context, shootClient kubernetes.Interface, name string) (kubernetes.Interface, error) {
return createTargetClientFromServiceAccount(ctx, shootClient, name, labelsE2ETestStaticServiceAccountToken, func(serviceAccount *corev1.ServiceAccount) (string, error) {
secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: serviceAccount.Namespace}}
if _, err := controllerutils.GetAndCreateOrMergePatch(ctx, shootClient.Client(), secret, func() error {
secret.Labels = utils.MergeStringMaps(secret.Labels, labelsE2ETestStaticServiceAccountToken)
secret.Annotations = utils.MergeStringMaps(secret.Annotations, map[string]string{corev1.ServiceAccountNameKey: serviceAccount.Name})
secret.Type = corev1.SecretTypeServiceAccountToken
return nil
}); err != nil {
return "", err
}
timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
if err := retry.Until(timeoutCtx, 500*time.Millisecond, func(ctx context.Context) (bool, error) {
if err := shootClient.Client().Get(ctx, client.ObjectKeyFromObject(secret), secret); err != nil {
return retry.SevereError(err)
}
if len(secret.Data[corev1.ServiceAccountTokenKey]) == 0 {
return retry.MinorError(fmt.Errorf("token for secret %s not yet populated by kube-controller-manager", client.ObjectKeyFromObject(secret)))
}
return retry.Ok()
}); err != nil {
return "", err
}
return string(secret.Data[corev1.ServiceAccountTokenKey]), nil
})
}
// CleanupObjectsFromStaticServiceAccountTokenAccess cleans up all objects in the shoot created by all calls to
// CreateShootClientFromStaticServiceAccountToken.
func CleanupObjectsFromStaticServiceAccountTokenAccess(ctx context.Context, shootClient kubernetes.Interface) error {
return flow.Parallel(func(ctx context.Context) error {
return shootClient.Client().DeleteAllOf(ctx, &corev1.ServiceAccount{}, client.InNamespace(namespaceE2ETestServiceAccountTokenAccess), client.MatchingLabels(labelsE2ETestStaticServiceAccountToken))
}, func(ctx context.Context) error {
return shootClient.Client().DeleteAllOf(ctx, &corev1.Secret{}, client.InNamespace(namespaceE2ETestServiceAccountTokenAccess), client.MatchingLabels(labelsE2ETestStaticServiceAccountToken))
}, func(ctx context.Context) error {
return shootClient.Client().DeleteAllOf(ctx, &rbacv1.ClusterRoleBinding{}, client.MatchingLabels(labelsE2ETestStaticServiceAccountToken))
})(ctx)
}
func createTargetClientFromServiceAccount(
ctx context.Context,
targetClient kubernetes.Interface,
name string,
labels map[string]string,
getTokenForServiceAccount func(*corev1.ServiceAccount) (string, error),
) (
kubernetes.Interface,
error,
) {
serviceAccount := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespaceE2ETestServiceAccountTokenAccess}}
if _, err := controllerutils.GetAndCreateOrMergePatch(ctx, targetClient.Client(), serviceAccount, func() error {
serviceAccount.Labels = utils.MergeStringMaps(serviceAccount.Labels, labels)
return nil
}); err != nil {
return nil, err
}
clusterRoleBinding := &rbacv1.ClusterRoleBinding{ObjectMeta: metav1.ObjectMeta{Name: name}}
if _, err := controllerutils.GetAndCreateOrMergePatch(ctx, targetClient.Client(), clusterRoleBinding, func() error {
clusterRoleBinding.Labels = utils.MergeStringMaps(serviceAccount.Labels, labels)
clusterRoleBinding.RoleRef = rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: "cluster-admin",
}
clusterRoleBinding.Subjects = []rbacv1.Subject{{
Kind: rbacv1.ServiceAccountKind,
Name: serviceAccount.Name,
Namespace: serviceAccount.Namespace,
}}
return nil
}); err != nil {
return nil, err
}
token, err := getTokenForServiceAccount(serviceAccount)
if err != nil {
return nil, err
}
r := targetClient.RESTConfig()
restConfig := &rest.Config{
Host: r.Host,
TLSClientConfig: rest.TLSClientConfig{CAData: r.CAData},
BearerToken: token,
}
return kubernetes.NewWithConfig(kubernetes.WithRESTConfig(restConfig), kubernetes.WithDisabledCachedClient())
}