Skip to content

Commit 455e334

Browse files
authored
Merge pull request #15403 from iastewar/gcp-auth-watch-namespaces
gcp-auth addon now adds the image registry pull secret to newly created namespaces
2 parents c88bc8b + a6bfa55 commit 455e334

File tree

14 files changed

+119
-119
lines changed

14 files changed

+119
-119
lines changed

deploy/addons/gcp-auth/gcp-auth-webhook.yaml.tmpl

+20
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ rules:
3838
verbs:
3939
- get
4040
- update
41+
- apiGroups:
42+
- ''
43+
resources:
44+
- namespaces
45+
verbs:
46+
- list
47+
- watch
4148

4249
---
4350
apiVersion: rbac.authorization.k8s.io/v1
@@ -92,10 +99,16 @@ spec:
9299
app: gcp-auth
93100
kubernetes.io/minikube-addons: gcp-auth
94101
spec:
102+
serviceAccountName: minikube-gcp-auth-certs
95103
containers:
96104
- name: gcp-auth
97105
image: {{.CustomRegistries.GCPAuthWebhook | default .ImageRepository | default .Registries.GCPAuthWebhook}}{{.Images.GCPAuthWebhook}}
98106
imagePullPolicy: IfNotPresent
107+
env:
108+
- name: GOOGLE_APPLICATION_CREDENTIALS
109+
value: /google-app-creds.json
110+
- name: MOCK_GOOGLE_TOKEN
111+
value: "{{.Environment.MockGoogleToken}}"
99112
ports:
100113
- containerPort: 8443
101114
volumeMounts:
@@ -105,6 +118,9 @@ spec:
105118
- name: gcp-project
106119
mountPath: /var/lib/minikube/google_cloud_project
107120
readOnly: true
121+
- name: gcp-creds
122+
mountPath: /google-app-creds.json
123+
readOnly: true
108124
volumes:
109125
- name: webhook-certs
110126
secret:
@@ -113,6 +129,10 @@ spec:
113129
hostPath:
114130
path: /var/lib/minikube/google_cloud_project
115131
type: File
132+
- name: gcp-creds
133+
hostPath:
134+
path: /var/lib/minikube/google_application_credentials.json
135+
type: File
116136
---
117137
apiVersion: batch/v1
118138
kind: Job

pkg/addons/addons_gcpauth.go

+32-95
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,12 @@ package addons
1919
import (
2020
"bytes"
2121
"context"
22-
"fmt"
2322
"os"
2423
"os/exec"
2524
"path"
2625
"strconv"
27-
"strings"
2826
"time"
2927

30-
gcr_config "github.com/GoogleCloudPlatform/docker-credential-gcr/config"
3128
corev1 "k8s.io/api/core/v1"
3229
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3330

@@ -88,10 +85,10 @@ func enableAddonGCPAuth(cfg *config.ClusterConfig) error {
8885
}
8986
}
9087

91-
// Create a registry secret in every namespace we can find
92-
// Always create the pull secret, no matter where we are
93-
if err := createPullSecret(cfg, creds); err != nil {
94-
return errors.Wrap(err, "pull secret")
88+
// Patch service accounts for all namespaces to include the image pull secret.
89+
// The image registry pull secret is added to the namespaces in the webhook.
90+
if err := patchServiceAccounts(cfg); err != nil {
91+
return errors.Wrap(err, "patching service accounts")
9592
}
9693

9794
// If the env var is explicitly set, even in GCE, then defer to the user and continue
@@ -101,7 +98,7 @@ func enableAddonGCPAuth(cfg *config.ClusterConfig) error {
10198
}
10299

103100
if creds.JSON == nil {
104-
out.WarningT("You have authenticated with a service account that does not have an associated JSON file. The GCP Auth addon requires credentials with a JSON file in order to continue. The image pull secret has been imported.")
101+
out.WarningT("You have authenticated with a service account that does not have an associated JSON file. The GCP Auth addon requires credentials with a JSON file in order to continue.")
105102
return nil
106103
}
107104

@@ -139,110 +136,50 @@ or set the GOOGLE_CLOUD_PROJECT environment variable.`)
139136

140137
}
141138

142-
func createPullSecret(cc *config.ClusterConfig, creds *google.Credentials) error {
143-
if creds == nil {
144-
return errors.New("no credentials, skipping creating pull secret")
139+
func patchServiceAccounts(cc *config.ClusterConfig) error {
140+
client, err := service.K8s.GetCoreClient(cc.Name)
141+
if err != nil {
142+
return err
145143
}
146144

147-
token, err := creds.TokenSource.Token()
148-
// Only try to add secret if Token was found
149-
if err == nil {
150-
client, err := service.K8s.GetCoreClient(cc.Name)
151-
if err != nil {
152-
return err
153-
}
145+
namespaces, err := client.Namespaces().List(context.TODO(), metav1.ListOptions{})
146+
if err != nil {
147+
return err
148+
}
154149

155-
namespaces, err := client.Namespaces().List(context.TODO(), metav1.ListOptions{})
150+
for _, n := range namespaces.Items {
151+
// Now patch the secret into all the service accounts we can find
152+
serviceaccounts := client.ServiceAccounts(n.Name)
153+
salist, err := serviceaccounts.List(context.TODO(), metav1.ListOptions{})
156154
if err != nil {
157155
return err
158156
}
159157

160-
var dockercfg string
161-
registries := append(gcr_config.DefaultGCRRegistries[:], gcr_config.DefaultARRegistries[:]...)
162-
for _, reg := range registries {
163-
dockercfg += fmt.Sprintf(`"https://%s":{"username":"oauth2accesstoken","password":"%s","email":"none"},`, reg, token.AccessToken)
164-
}
165-
166-
dockercfg = strings.TrimSuffix(dockercfg, ",")
167-
168-
data := map[string][]byte{
169-
".dockercfg": []byte(fmt.Sprintf(`{%s}`, dockercfg)),
170-
}
171-
172-
for _, n := range namespaces.Items {
173-
if skipNamespace(n.Name) {
174-
continue
175-
}
176-
secrets := client.Secrets(n.Name)
177-
178-
exists := false
179-
secList, err := secrets.List(context.TODO(), metav1.ListOptions{})
158+
// Let's make sure we at least find the default service account
159+
for len(salist.Items) == 0 {
160+
salist, err = serviceaccounts.List(context.TODO(), metav1.ListOptions{})
180161
if err != nil {
181162
return err
182163
}
183-
for _, s := range secList.Items {
184-
if s.Name == secretName {
185-
exists = true
186-
break
187-
}
188-
}
189-
190-
if !exists || Refresh {
191-
secretObj := &corev1.Secret{
192-
ObjectMeta: metav1.ObjectMeta{
193-
Name: secretName,
194-
},
195-
Data: data,
196-
Type: "kubernetes.io/dockercfg",
197-
}
164+
time.Sleep(1 * time.Second)
165+
}
198166

199-
if exists && Refresh {
200-
_, err := secrets.Update(context.TODO(), secretObj, metav1.UpdateOptions{})
201-
if err != nil {
202-
return err
203-
}
204-
} else {
205-
_, err = secrets.Create(context.TODO(), secretObj, metav1.CreateOptions{})
206-
if err != nil {
207-
return err
208-
}
167+
ips := corev1.LocalObjectReference{Name: secretName}
168+
for _, sa := range salist.Items {
169+
add := true
170+
for _, ps := range sa.ImagePullSecrets {
171+
if ps.Name == secretName {
172+
add = false
173+
break
209174
}
210175
}
211-
212-
// Now patch the secret into all the service accounts we can find
213-
serviceaccounts := client.ServiceAccounts(n.Name)
214-
salist, err := serviceaccounts.List(context.TODO(), metav1.ListOptions{})
215-
if err != nil {
216-
return err
217-
}
218-
219-
// Let's make sure we at least find the default service account
220-
for len(salist.Items) == 0 {
221-
salist, err = serviceaccounts.List(context.TODO(), metav1.ListOptions{})
176+
if add {
177+
sa.ImagePullSecrets = append(sa.ImagePullSecrets, ips)
178+
_, err := serviceaccounts.Update(context.TODO(), &sa, metav1.UpdateOptions{})
222179
if err != nil {
223180
return err
224181
}
225-
time.Sleep(1 * time.Second)
226182
}
227-
228-
ips := corev1.LocalObjectReference{Name: secretName}
229-
for _, sa := range salist.Items {
230-
add := true
231-
for _, ps := range sa.ImagePullSecrets {
232-
if ps.Name == secretName {
233-
add = false
234-
break
235-
}
236-
}
237-
if add {
238-
sa.ImagePullSecrets = append(sa.ImagePullSecrets, ips)
239-
_, err := serviceaccounts.Update(context.TODO(), &sa, metav1.UpdateOptions{})
240-
if err != nil {
241-
return err
242-
}
243-
}
244-
}
245-
246183
}
247184
}
248185
return nil

pkg/minikube/assets/addons.go

+21-16
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package assets
1818

1919
import (
2020
"fmt"
21+
"os"
2122
"runtime"
2223
"strings"
2324

@@ -577,7 +578,7 @@ var Addons = map[string]*Addon{
577578
"0640"),
578579
}, false, "gcp-auth", "Google", "", "https://minikube.sigs.k8s.io/docs/handbook/addons/gcp-auth/", map[string]string{
579580
"KubeWebhookCertgen": "ingress-nginx/kube-webhook-certgen:v1.0@sha256:f3b6b39a6062328c095337b4cadcefd1612348fdd5190b1dcbcb9b9e90bd8068",
580-
"GCPAuthWebhook": "k8s-minikube/gcp-auth-webhook:v0.0.11@sha256:82efb346863dc47701586bebadd4cef998d4c6692d802ec3de68d451c87fb613",
581+
"GCPAuthWebhook": "k8s-minikube/gcp-auth-webhook:v0.0.13@sha256:08a49cb7a588d81723b7e02c16082c75418b6e0a54cf2e44668bd77f79a41a40",
581582
}, map[string]string{
582583
"GCPAuthWebhook": "gcr.io",
583584
"KubeWebhookCertgen": "k8s.gcr.io",
@@ -884,24 +885,28 @@ func GenerateTemplateData(addon *Addon, cc *config.ClusterConfig, netInfo Networ
884885
Registries map[string]string
885886
CustomRegistries map[string]string
886887
NetworkInfo map[string]string
888+
Environment map[string]string
887889
LegacyPodSecurityPolicy bool
888890
LegacyRuntimeClass bool
889891
}{
890-
KubernetesVersion: make(map[string]uint64),
891-
PreOneTwentyKubernetes: false,
892-
Arch: a,
893-
ExoticArch: ea,
894-
ImageRepository: cfg.ImageRepository,
895-
LoadBalancerStartIP: cfg.LoadBalancerStartIP,
896-
LoadBalancerEndIP: cfg.LoadBalancerEndIP,
897-
CustomIngressCert: cfg.CustomIngressCert,
898-
RegistryAliases: cfg.RegistryAliases,
899-
IngressAPIVersion: "v1", // api version for ingress (eg, "v1beta1"; defaults to "v1" for k8s 1.19+)
900-
ContainerRuntime: cfg.ContainerRuntime,
901-
Images: images,
902-
Registries: addon.Registries,
903-
CustomRegistries: customRegistries,
904-
NetworkInfo: make(map[string]string),
892+
KubernetesVersion: make(map[string]uint64),
893+
PreOneTwentyKubernetes: false,
894+
Arch: a,
895+
ExoticArch: ea,
896+
ImageRepository: cfg.ImageRepository,
897+
LoadBalancerStartIP: cfg.LoadBalancerStartIP,
898+
LoadBalancerEndIP: cfg.LoadBalancerEndIP,
899+
CustomIngressCert: cfg.CustomIngressCert,
900+
RegistryAliases: cfg.RegistryAliases,
901+
IngressAPIVersion: "v1", // api version for ingress (eg, "v1beta1"; defaults to "v1" for k8s 1.19+)
902+
ContainerRuntime: cfg.ContainerRuntime,
903+
Images: images,
904+
Registries: addon.Registries,
905+
CustomRegistries: customRegistries,
906+
NetworkInfo: make(map[string]string),
907+
Environment: map[string]string{
908+
"MockGoogleToken": os.Getenv("MOCK_GOOGLE_TOKEN"),
909+
},
905910
LegacyPodSecurityPolicy: v.LT(semver.Version{Major: 1, Minor: 25}),
906911
LegacyRuntimeClass: v.LT(semver.Version{Major: 1, Minor: 25}),
907912
}

test/integration/addons_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"bytes"
2323
"context"
2424
"encoding/json"
25+
"errors"
2526
"fmt"
2627
"net/http"
2728
"net/url"
@@ -66,6 +67,9 @@ func TestAddons(t *testing.T) {
6667
t.Fatalf("Failed setting GOOGLE_CLOUD_PROJECT env var: %v", err)
6768
}
6869

70+
// MOCK_GOOGLE_TOKEN forces the gcp-auth webhook to use a mock token instead of trying to get a valid one from the credentials.
71+
t.Setenv("MOCK_GOOGLE_TOKEN", "true")
72+
6973
args := append([]string{"start", "-p", profile, "--wait=true", "--memory=4000", "--alsologtostderr", "--addons=registry", "--addons=metrics-server", "--addons=volumesnapshots", "--addons=csi-hostpath-driver", "--addons=gcp-auth", "--addons=cloud-spanner"}, StartArgs()...)
7074
if !NoneDriver() { // none driver does not support ingress
7175
args = append(args, "--addons=ingress", "--addons=ingress-dns")
@@ -598,10 +602,42 @@ func validateCSIDriverAndSnapshots(ctx context.Context, t *testing.T, profile st
598602
}
599603
}
600604

605+
// validateGCPAuthNamespaces validates that newly created namespaces contain the gcp-auth secret.
606+
func validateGCPAuthNamespaces(ctx context.Context, t *testing.T, profile string) {
607+
rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "create", "ns", "new-namespace"))
608+
if err != nil {
609+
t.Fatalf("%s failed: %v", rr.Command(), err)
610+
}
611+
612+
logsAsError := func() error {
613+
rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "logs", "-l", "app=gcp-auth", "-n", "gcp-auth"))
614+
if err != nil {
615+
return err
616+
}
617+
return errors.New(rr.Output())
618+
}
619+
620+
getSecret := func() error {
621+
_, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "secret", "gcp-auth", "-n", "new-namespace"))
622+
if err != nil {
623+
err = fmt.Errorf("%w: gcp-auth container logs: %v", err, logsAsError())
624+
}
625+
return err
626+
}
627+
628+
if err := retry.Expo(getSecret, Seconds(2), Minutes(1)); err != nil {
629+
t.Errorf("failed to get secret: %v", err)
630+
}
631+
}
632+
601633
// validateGCPAuthAddon tests the GCP Auth addon with either phony or real credentials and makes sure the files are mounted into pods correctly
602634
func validateGCPAuthAddon(ctx context.Context, t *testing.T, profile string) {
603635
defer PostMortemLogs(t, profile)
604636

637+
t.Run("Namespaces", func(t *testing.T) {
638+
validateGCPAuthNamespaces(ctx, t, profile)
639+
})
640+
605641
// schedule a pod to check environment variables
606642
rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "create", "-f", filepath.Join(*testdataDir, "busybox.yaml")))
607643
if err != nil {

test/integration/main_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func Minutes(n int) time.Duration {
186186
return time.Duration(*timeOutMultiplier) * time.Duration(n) * time.Minute
187187
}
188188

189-
// Seconds will return timeout in minutes based on how slow the machine is
189+
// Seconds will return timeout in seconds based on how slow the machine is
190190
func Seconds(n int) time.Duration {
191191
return time.Duration(*timeOutMultiplier) * time.Duration(n) * time.Second
192192
}

translations/de.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@
899899
"You cannot change the CPUs for an existing minikube cluster. Please first delete the cluster.": "Die Anzahl der CPUs eines existierenden Minikube Clusters kann nicht geändert werden. Bitte löschen Sie den Cluster zuerst.",
900900
"You cannot change the disk size for an existing minikube cluster. Please first delete the cluster.": "Die Plattengröße eines existierenden Minikube Clusters kann nicht geändert werden. Bitte löschen Sie den Cluster zuerst.",
901901
"You cannot change the memory size for an existing minikube cluster. Please first delete the cluster.": "Die Speichergröße eines existierenden Minikube Clusters kann nicht geändert werden. Bitte löschen Sie den Cluster zuerst.",
902-
"You have authenticated with a service account that does not have an associated JSON file. The GCP Auth addon requires credentials with a JSON file in order to continue. The image pull secret has been imported.": "",
902+
"You have authenticated with a service account that does not have an associated JSON file. The GCP Auth addon requires credentials with a JSON file in order to continue.": "",
903903
"You have authenticated with a service account that does not have an associated JSON. The GCP Auth requires credentials with a JSON file to in order to continue. The image pull secret has been imported.": "Sie haben sich mit einem Service Account authentifiziert, welcher kein zugehöriges JSON besitzt. GCP Auth benötigt Zugangsdaten in einer JSON-Datei um weitermachen zu können. Das Image Pull Secret wurde importiert.",
904904
"You have chosen to disable the CNI but the \"{{.name}}\" container runtime requires CNI": "Sie haben den CNI Treiber deaktiviert, aber die \"{{.name}}\" Container Laufzeitumgebung benötigt ein CNI",
905905
"You have selected \"virtualbox\" driver, but there are better options !\nFor better performance and support consider using a different driver: {{.drivers}}\n\nTo turn off this warning run:\n\n\t$ minikube config set WantVirtualBoxDriverWarning false\n\n\nTo learn more about on minikube drivers checkout https://minikube.sigs.k8s.io/docs/drivers/\nTo see benchmarks checkout https://minikube.sigs.k8s.io/docs/benchmarks/cpuusage/\n\n": "Sie haben den \"virtualbox\" Treiber ausgewählt, aber es existieren bessere Möglichkeiten !\nFür eine bessere Performanz und besseren Support erwägen Sie die Verwendung eines anderen Treibers: {{.drivers}}\n\nUm diese Warnung zu deaktivieren, führen Sie folgendes aus:\n\n\t$ minikube config set WantVirtualBoxDriverWarning false\n\n\nUm mehr über die Minikube-Treiber zu erfahren, lesen Sie https://minikube.sigs.k8s.io/docs/drivers/\nZu Benchmarks lesen Sie https://minikube.sigs.k8s.io/docs/benchmarks/cpuusage/\n\n",

translations/es.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@
899899
"You cannot change the CPUs for an existing minikube cluster. Please first delete the cluster.": "",
900900
"You cannot change the disk size for an existing minikube cluster. Please first delete the cluster.": "",
901901
"You cannot change the memory size for an existing minikube cluster. Please first delete the cluster.": "",
902-
"You have authenticated with a service account that does not have an associated JSON file. The GCP Auth addon requires credentials with a JSON file in order to continue. The image pull secret has been imported.": "",
902+
"You have authenticated with a service account that does not have an associated JSON file. The GCP Auth addon requires credentials with a JSON file in order to continue.": "",
903903
"You have chosen to disable the CNI but the \"{{.name}}\" container runtime requires CNI": "",
904904
"You have selected \"virtualbox\" driver, but there are better options !\nFor better performance and support consider using a different driver: {{.drivers}}\n\nTo turn off this warning run:\n\n\t$ minikube config set WantVirtualBoxDriverWarning false\n\n\nTo learn more about on minikube drivers checkout https://minikube.sigs.k8s.io/docs/drivers/\nTo see benchmarks checkout https://minikube.sigs.k8s.io/docs/benchmarks/cpuusage/\n\n": "",
905905
"You may need to manually remove the \"{{.name}}\" VM from your hypervisor": "Puede que tengas que retirar manualmente la VM \"{{.name}}\" de tu hipervisor",

translations/fr.json

+1
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,7 @@
872872
"You cannot change the CPUs for an existing minikube cluster. Please first delete the cluster.": "Vous ne pouvez pas modifier les processeurs d'un cluster minikube existant. Veuillez d'abord supprimer le cluster.",
873873
"You cannot change the disk size for an existing minikube cluster. Please first delete the cluster.": "Vous ne pouvez pas modifier la taille du disque pour un cluster minikube existant. Veuillez d'abord supprimer le cluster.",
874874
"You cannot change the memory size for an existing minikube cluster. Please first delete the cluster.": "Vous ne pouvez pas modifier la taille de la mémoire d'un cluster minikube existant. Veuillez d'abord supprimer le cluster.",
875+
"You have authenticated with a service account that does not have an associated JSON file. The GCP Auth addon requires credentials with a JSON file in order to continue.": "",
875876
"You have authenticated with a service account that does not have an associated JSON file. The GCP Auth addon requires credentials with a JSON file in order to continue. The image pull secret has been imported.": "Vous vous êtes authentifié avec un compte de service qui n'a pas de fichier JSON associé. Le module complémentaire GCP Auth nécessite des informations d'identification avec un fichier JSON pour continuer. Le secret d'extraction d'image a été importé.",
876877
"You have authenticated with a service account that does not have an associated JSON. The GCP Auth requires credentials with a JSON file in order to continue. The image pull secret has been imported.": "Vous vous êtes authentifié avec un compte de service qui n'a pas de fichier JSON associé. L'authentification GCP nécessite des informations d'identification avec un fichier JSON pour continuer. Le secret d'extraction d'image a été importé.",
877878
"You have authenticated with a service account that does not have an associated JSON. The GCP Auth requires credentials with a JSON file to in order to continue. The image pull secret has been imported.": "Vous vous êtes authentifié avec un compte de service qui n'a pas de fichier JSON associé. L'authentification GCP nécessite des informations d'identification avec un fichier JSON pour continuer. Le secret d'extraction d'image a été importé.",

0 commit comments

Comments
 (0)