Support defining a KubeConfig Secret data key

Signed-off-by: Nick Stogner <nicholas.stogner@gmail.com>
This commit is contained in:
Nick Stogner 2022-04-10 21:44:06 -04:00
parent e0ba73f4be
commit 6aeff8c924
20 changed files with 171 additions and 50 deletions

View File

@ -4,7 +4,7 @@ go 1.17
require (
github.com/fluxcd/pkg/apis/kustomize v0.3.3
github.com/fluxcd/pkg/apis/meta v0.12.2
github.com/fluxcd/pkg/apis/meta v0.13.0
k8s.io/apiextensions-apiserver v0.23.5
k8s.io/apimachinery v0.23.5
sigs.k8s.io/controller-runtime v0.11.2

View File

@ -122,8 +122,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fluxcd/pkg/apis/kustomize v0.3.3 h1:bPN29SdVzWl0yhgivuf/83IAe2R6vUuDVcB3LzyVU8E=
github.com/fluxcd/pkg/apis/kustomize v0.3.3/go.mod h1:5HTOFZfQFVMMqR2rvuxpbZhpb+sQpcTT6RCQZOhjFzA=
github.com/fluxcd/pkg/apis/meta v0.12.2 h1:AiKAZxLyPtV150y63WC+mL1Qm4x5qWQmW6r4mLy1i8c=
github.com/fluxcd/pkg/apis/meta v0.12.2/go.mod h1:Z26X5uTU5LxAyWETGueRQY7TvdPaGfKU7Wye9bdUlho=
github.com/fluxcd/pkg/apis/meta v0.13.0 h1:0QuNKEExSjk+Rv0I6a85p2H3xOlWhdxZRsh10waEL/c=
github.com/fluxcd/pkg/apis/meta v0.13.0/go.mod h1:Z26X5uTU5LxAyWETGueRQY7TvdPaGfKU7Wye9bdUlho=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=

View File

@ -164,8 +164,9 @@ type Decryption struct {
// KubeConfig references a Kubernetes secret that contains a kubeconfig file.
type KubeConfig struct {
// SecretRef holds the name to a secret that contains a 'value' key with
// the kubeconfig file as the value. It must be in the same namespace as
// SecretRef holds the name of a secret that contains a key with
// the kubeconfig file as the value. If no key is set, the key will default
// to 'value'. The secret must be in the same namespace as
// the Kustomization.
// It is recommended that the kubeconfig is self-contained, and the secret
// is regularly updated if credentials such as a cloud-access-token expire.
@ -173,7 +174,7 @@ type KubeConfig struct {
// binaries and credentials to the Pod that is responsible for reconciling
// the Kustomization.
// +required
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
SecretRef meta.SecretKeyReference `json:"secretRef,omitempty"`
}
// PostBuild describes which actions to perform on the YAML manifest

View File

@ -698,17 +698,21 @@ spec:
is empty.
properties:
secretRef:
description: SecretRef holds the name to a secret that contains
a 'value' key with the kubeconfig file as the value. It must
be in the same namespace as the Kustomization. It is recommended
that the kubeconfig is self-contained, and the secret is regularly
updated if credentials such as a cloud-access-token expire.
Cloud specific `cmd-path` auth helpers will not function without
adding binaries and credentials to the Pod that is responsible
for reconciling the Kustomization.
description: SecretRef holds the name of a secret that contains
a key with the kubeconfig file as the value. If no key is set,
the key will default to 'value'. The secret must be in the same
namespace as the Kustomization. It is recommended that the kubeconfig
is self-contained, and the secret is regularly updated if credentials
such as a cloud-access-token expire. Cloud specific `cmd-path`
auth helpers will not function without adding binaries and credentials
to the Pod that is responsible for reconciling the Kustomization.
properties:
key:
description: Key in the Secret, when not specified an implementation-specific
default key is used.
type: string
name:
description: Name of the referent.
description: Name of the Secret.
type: string
required:
- name

View File

@ -89,7 +89,7 @@ stringData:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -146,7 +146,7 @@ func TestKustomizationReconciler_Decryptor(t *testing.T) {
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -143,7 +143,7 @@ spec:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -86,7 +86,7 @@ stringData:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -177,15 +177,20 @@ func (ki *KustomizeImpersonation) getKubeConfig(ctx context.Context) ([]byte, er
}
var kubeConfig []byte
for k := range secret.Data {
if k == "value" || k == "value.yaml" {
kubeConfig = secret.Data[k]
break
switch {
case ki.kustomization.Spec.KubeConfig.SecretRef.Key != "":
key := ki.kustomization.Spec.KubeConfig.SecretRef.Key
kubeConfig = secret.Data[key]
if kubeConfig == nil {
return nil, fmt.Errorf("KubeConfig secret '%s' does not contain a '%s' key with a kubeconfig", secretName, key)
}
}
if len(kubeConfig) == 0 {
return nil, fmt.Errorf("KubeConfig secret '%s' doesn't contain a 'value' key ", secretName.String())
case secret.Data["value"] != nil:
kubeConfig = secret.Data["value"]
case secret.Data["value.yaml"] != nil:
kubeConfig = secret.Data["value.yaml"]
default:
// User did not specify a key, and the 'value' key was not defined.
return nil, fmt.Errorf("KubeConfig secret '%s' does not contain a 'value' key with a kubeconfig", secretName)
}
return kubeConfig, nil

View File

@ -92,7 +92,7 @@ data:
Interval: metav1.Duration{Duration: time.Minute},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -208,3 +208,113 @@ data:
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
})
}
func TestKustomizationReconciler_KubeConfig(t *testing.T) {
g := NewWithT(t)
id := "kc-" + randStringRunes(5)
revision := "v1.0.0"
err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
manifests := func(name string, data string) []testserver.File {
return []testserver.File{
{
Name: "config.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: ConfigMap
metadata:
name: %[1]s
data:
key: "%[2]s"
`, name, data),
},
}
}
artifact, err := testServer.ArtifactFromFiles(manifests(id, randStringRunes(5)))
g.Expect(err).NotTo(HaveOccurred(), "failed to create artifact from files")
repositoryName := types.NamespacedName{
Name: randStringRunes(5),
Namespace: id,
}
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
const (
secretName = "user-defined-name"
secretKey = "user-defined-key"
)
kustomizationKey := types.NamespacedName{
Name: randStringRunes(5),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: time.Minute},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.SecretKeyReference{
Name: secretName,
Key: secretKey,
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: id,
Prune: true,
},
}
g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())
resultK := &kustomizev1.Kustomization{}
readyCondition := &metav1.Condition{}
t.Run("fails to reconcile with missing kubeconfig secret", func(t *testing.T) {
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
readyCondition = apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return apimeta.IsStatusConditionFalse(resultK.Status.Conditions, meta.ReadyCondition)
}, timeout, time.Second).Should(BeTrue())
g.Expect(readyCondition.Reason).To(Equal(kustomizev1.ReconciliationFailedReason))
g.Expect(readyCondition.Message).To(ContainSubstring(`Secret "%s" not found`, secretName))
})
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: id,
},
Data: map[string][]byte{
secretKey: kubeConfig,
},
}
g.Expect(k8sClient.Create(context.Background(), secret)).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
t.Run("reconciles successfully after secret is created", func(t *testing.T) {
revision = "v2.0.0"
err = applyGitRepository(repositoryName, artifact, revision)
g.Expect(err).NotTo(HaveOccurred())
g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
readyCondition = apimeta.FindStatusCondition(resultK.Status.Conditions, meta.ReadyCondition)
return resultK.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())
g.Expect(readyCondition.Reason).To(Equal(kustomizev1.ReconciliationSucceededReason))
})
}

View File

@ -97,7 +97,7 @@ stringData:
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -98,7 +98,7 @@ data:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -226,7 +226,7 @@ data:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -370,7 +370,7 @@ data:
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -74,7 +74,7 @@ func TestKustomizationReconciler_KustomizeTransformer(t *testing.T) {
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -197,7 +197,7 @@ func TestKustomizationReconciler_KustomizeTransformerFiles(t *testing.T) {
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -316,7 +316,7 @@ func TestKustomizationReconciler_FluxTransformers(t *testing.T) {
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -79,7 +79,7 @@ func TestKustomizationReconciler_Validation(t *testing.T) {
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -120,7 +120,7 @@ stringData:
},
Spec: kustomizev1.KustomizationSpec{
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},
@ -269,7 +269,7 @@ metadata:
},
Spec: kustomizev1.KustomizationSpec{
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -85,7 +85,7 @@ data:
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},

View File

@ -521,14 +521,15 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
<td>
<code>secretRef</code><br>
<em>
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#LocalObjectReference">
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
<a href="https://godoc.org/github.com/fluxcd/pkg/apis/meta#SecretKeyReference">
github.com/fluxcd/pkg/apis/meta.SecretKeyReference
</a>
</em>
</td>
<td>
<p>SecretRef holds the name to a secret that contains a &lsquo;value&rsquo; key with
the kubeconfig file as the value. It must be in the same namespace as
<p>SecretRef holds the name of a secret that contains a key with
the kubeconfig file as the value. If no key is set, the key will default
to &lsquo;value&rsquo;. The secret must be in the same namespace as
the Kustomization.
It is recommended that the kubeconfig is self-contained, and the secret
is regularly updated if credentials such as a cloud-access-token expire.

4
go.mod
View File

@ -15,9 +15,9 @@ require (
github.com/fluxcd/kustomize-controller/api v0.24.4
github.com/fluxcd/pkg/apis/acl v0.0.3
github.com/fluxcd/pkg/apis/kustomize v0.3.3
github.com/fluxcd/pkg/apis/meta v0.12.2
github.com/fluxcd/pkg/apis/meta v0.13.0
github.com/fluxcd/pkg/kustomize v0.3.0
github.com/fluxcd/pkg/runtime v0.14.1
github.com/fluxcd/pkg/runtime v0.14.2
github.com/fluxcd/pkg/ssa v0.15.2
github.com/fluxcd/pkg/testserver v0.2.0
github.com/fluxcd/pkg/untar v0.1.0

8
go.sum
View File

@ -274,12 +274,12 @@ github.com/fluxcd/pkg/apis/acl v0.0.3 h1:Lw0ZHdpnO4G7Zy9KjrzwwBmDZQuy4qEjaU/RvA6
github.com/fluxcd/pkg/apis/acl v0.0.3/go.mod h1:XPts6lRJ9C9fIF9xVWofmQwftvhY25n1ps7W9xw0XLU=
github.com/fluxcd/pkg/apis/kustomize v0.3.3 h1:bPN29SdVzWl0yhgivuf/83IAe2R6vUuDVcB3LzyVU8E=
github.com/fluxcd/pkg/apis/kustomize v0.3.3/go.mod h1:5HTOFZfQFVMMqR2rvuxpbZhpb+sQpcTT6RCQZOhjFzA=
github.com/fluxcd/pkg/apis/meta v0.12.2 h1:AiKAZxLyPtV150y63WC+mL1Qm4x5qWQmW6r4mLy1i8c=
github.com/fluxcd/pkg/apis/meta v0.12.2/go.mod h1:Z26X5uTU5LxAyWETGueRQY7TvdPaGfKU7Wye9bdUlho=
github.com/fluxcd/pkg/apis/meta v0.13.0 h1:0QuNKEExSjk+Rv0I6a85p2H3xOlWhdxZRsh10waEL/c=
github.com/fluxcd/pkg/apis/meta v0.13.0/go.mod h1:Z26X5uTU5LxAyWETGueRQY7TvdPaGfKU7Wye9bdUlho=
github.com/fluxcd/pkg/kustomize v0.3.0 h1:mrUsbONJxw1YapmIhim4C7yDGNILmQnAEFolrUdRx+s=
github.com/fluxcd/pkg/kustomize v0.3.0/go.mod h1:Qczvl7vNY9NJBpyaFrldsxfGjj6uaMcMmKGsSJ6hcxc=
github.com/fluxcd/pkg/runtime v0.14.1 h1:ZbS3RzR+f+wu1e6Y7GoCxY9PFZkOgX6/gL7Enr75CY0=
github.com/fluxcd/pkg/runtime v0.14.1/go.mod h1:eS4378ydLlWPt2fFjcrAAnJegGJNj3Q/iqYZqjBeWlM=
github.com/fluxcd/pkg/runtime v0.14.2 h1:ktyUjcX4pHoC8DRoBmhEP6eMHbmR6+/MYoARe4YulZY=
github.com/fluxcd/pkg/runtime v0.14.2/go.mod h1:NZr3PRK7xX2M1bl0LdtugvQyWkOmu2NcW3NrZH6U0is=
github.com/fluxcd/pkg/ssa v0.15.2 h1:hLEIh7Ymlt6ihfQHIEx7DjAa+FCndBpHW6wyELToVsI=
github.com/fluxcd/pkg/ssa v0.15.2/go.mod h1:wwDtLfi7a31/yQuC/oeHXZpaSHGrkZk835uhdHDNyik=
github.com/fluxcd/pkg/testserver v0.2.0 h1:Mj0TapmKaywI6Fi5wvt1LAZpakUHmtzWQpJNKQ0Krt4=

View File

@ -266,7 +266,7 @@ func FuzzControllers(data []byte) int {
Spec: kustomizev1.KustomizationSpec{
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},