Skip to content

Commit

Permalink
Fetch cluster CA via ConfigMap
Browse files Browse the repository at this point in the history
  • Loading branch information
petersutter committed Feb 7, 2024
1 parent efbd79d commit 8d49095
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 56 deletions.
130 changes: 91 additions & 39 deletions internal/client/garden/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,10 @@ var _ = Describe("Client", func() {
k8sVersionLegacy = "1.19.0" // legacy kubeconfig should be rendered
)
var (
testShoot1 *gardencorev1beta1.Shoot
caSecret *corev1.Secret
ca *secrets.Certificate
testShoot1 *gardencorev1beta1.Shoot
caConfigMap *corev1.ConfigMap
caSecret *corev1.Secret
ca *secrets.Certificate
)

BeforeEach(func() {
Expand Down Expand Up @@ -188,6 +189,16 @@ var _ = Describe("Client", func() {
ca, err = csc.GenerateCertificate()
Expect(err).NotTo(HaveOccurred())

caConfigMap = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: testShoot1.Name + ".ca-cluster",
Namespace: testShoot1.Namespace,
},
Data: map[string]string{
"ca.crt": string(ca.CertificatePEM),
},
}

caSecret = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: testShoot1.Name + ".ca-cluster",
Expand All @@ -203,46 +214,87 @@ var _ = Describe("Client", func() {
JustBeforeEach(func() {
gardenClient = clientgarden.NewClient(
nil,
fake.NewClientWithObjects(testShoot1, caSecret),
fake.NewClientWithObjects(testShoot1, caConfigMap),
gardenName,
)
})

It("it should return the client config", func() {
gardenClient = clientgarden.NewClient(
nil,
fake.NewClientWithObjects(testShoot1, caSecret),
gardenName,
)
Context("when ca-cluster configmap exists", func() {
It("it should return the client config", func() {
gardenClient = clientgarden.NewClient(
nil,
fake.NewClientWithObjects(testShoot1, caSecret),
gardenName,
)

clientConfig, err := gardenClient.GetShootClientConfig(ctx, namespace, shootName)
Expect(err).NotTo(HaveOccurred())

rawConfig, err := clientConfig.RawConfig()
Expect(err).NotTo(HaveOccurred())
Expect(rawConfig.Clusters).To(HaveLen(2))
context := rawConfig.Contexts[rawConfig.CurrentContext]
cluster := rawConfig.Clusters[context.Cluster]
Expect(cluster.Server).To(Equal("https://api." + domain))
Expect(cluster.CertificateAuthorityData).To(Equal(ca.CertificatePEM))

extension := &clientgarden.ExecPluginConfig{}
extension.GardenClusterIdentity = gardenName
extension.ShootRef.Namespace = namespace
extension.ShootRef.Name = shootName

Expect(cluster.Extensions["client.authentication.k8s.io/exec"]).To(Equal(extension.ToRuntimeObject()))

Expect(rawConfig.Contexts).To(HaveLen(2))

Expect(rawConfig.AuthInfos).To(HaveLen(1))
authInfo := rawConfig.AuthInfos[context.AuthInfo]
Expect(authInfo.Exec.APIVersion).To(Equal(clientauthenticationv1.SchemeGroupVersion.String()))
Expect(authInfo.Exec.Command).To(Equal("kubectl-gardenlogin"))
Expect(authInfo.Exec.Args).To(Equal([]string{
"get-client-certificate",
}))
Expect(authInfo.Exec.InstallHint).ToNot(BeEmpty())
})
})

clientConfig, err := gardenClient.GetShootClientConfig(ctx, namespace, shootName)
Expect(err).NotTo(HaveOccurred())

rawConfig, err := clientConfig.RawConfig()
Expect(err).NotTo(HaveOccurred())
Expect(rawConfig.Clusters).To(HaveLen(2))
context := rawConfig.Contexts[rawConfig.CurrentContext]
cluster := rawConfig.Clusters[context.Cluster]
Expect(cluster.Server).To(Equal("https://api." + domain))
Expect(cluster.CertificateAuthorityData).To(Equal(ca.CertificatePEM))

extension := &clientgarden.ExecPluginConfig{}
extension.GardenClusterIdentity = gardenName
extension.ShootRef.Namespace = namespace
extension.ShootRef.Name = shootName

Expect(cluster.Extensions["client.authentication.k8s.io/exec"]).To(Equal(extension.ToRuntimeObject()))

Expect(rawConfig.Contexts).To(HaveLen(2))

Expect(rawConfig.AuthInfos).To(HaveLen(1))
authInfo := rawConfig.AuthInfos[context.AuthInfo]
Expect(authInfo.Exec.APIVersion).To(Equal(clientauthenticationv1.SchemeGroupVersion.String()))
Expect(authInfo.Exec.Command).To(Equal("kubectl-gardenlogin"))
Expect(authInfo.Exec.Args).To(Equal([]string{
"get-client-certificate",
}))
Expect(authInfo.Exec.InstallHint).ToNot(BeEmpty())
Context("when ca-cluster secret exists", func() {
It("it should return the client config", func() {
gardenClient = clientgarden.NewClient(
nil,
fake.NewClientWithObjects(testShoot1, caSecret),
gardenName,
)

clientConfig, err := gardenClient.GetShootClientConfig(ctx, namespace, shootName)
Expect(err).NotTo(HaveOccurred())

rawConfig, err := clientConfig.RawConfig()
Expect(err).NotTo(HaveOccurred())
Expect(rawConfig.Clusters).To(HaveLen(2))
context := rawConfig.Contexts[rawConfig.CurrentContext]
cluster := rawConfig.Clusters[context.Cluster]
Expect(cluster.Server).To(Equal("https://api." + domain))
Expect(cluster.CertificateAuthorityData).To(Equal(ca.CertificatePEM))

extension := &clientgarden.ExecPluginConfig{}
extension.GardenClusterIdentity = gardenName
extension.ShootRef.Namespace = namespace
extension.ShootRef.Name = shootName

Expect(cluster.Extensions["client.authentication.k8s.io/exec"]).To(Equal(extension.ToRuntimeObject()))

Expect(rawConfig.Contexts).To(HaveLen(2))

Expect(rawConfig.AuthInfos).To(HaveLen(1))
authInfo := rawConfig.AuthInfos[context.AuthInfo]
Expect(authInfo.Exec.APIVersion).To(Equal(clientauthenticationv1.SchemeGroupVersion.String()))
Expect(authInfo.Exec.Command).To(Equal("kubectl-gardenlogin"))
Expect(authInfo.Exec.Args).To(Equal([]string{
"get-client-certificate",
}))
Expect(authInfo.Exec.InstallHint).ToNot(BeEmpty())
})
})

Context("legacy kubeconfig", func() {
Expand Down Expand Up @@ -281,7 +333,7 @@ var _ = Describe("Client", func() {
})
})

Context("when the ca-cluster secret does not exist", func() {
Context("when the ca-cluster does not exist", func() {
BeforeEach(func() {
gardenClient = clientgarden.NewClient(
nil,
Expand Down
38 changes: 29 additions & 9 deletions internal/client/garden/shoot_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
seedmanagementv1alpha1 "github.com/gardener/gardener/pkg/apis/seedmanagement/v1alpha1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -32,9 +33,12 @@ func init() {
}

const (
// ShootProjectConfigMapSuffixCACluster is a constant for a shoot project config map with suffix 'ca-cluster'.
ShootProjectConfigMapSuffixCACluster = "ca-cluster"
// ShootProjectSecretSuffixCACluster is a constant for a shoot project secret with suffix 'ca-cluster'.
// Deprecated: This constant is deprecated in favor of ShootProjectConfigMapSuffixCACluster.
ShootProjectSecretSuffixCACluster = "ca-cluster"
// DataKeyCertificateCA is the key in a secret data holding the CA certificate.
// DataKeyCertificateCA is the key in a secret or config map data holding the CA certificate.
DataKeyCertificateCA = "ca.crt"
)

Expand Down Expand Up @@ -219,16 +223,32 @@ func (g *clientImpl) GetShootClientConfig(ctx context.Context, namespace, name s
}

// fetch cluster ca
caClusterSecret := corev1.Secret{}
caClusterSecretName := fmt.Sprintf("%s.%s", name, ShootProjectSecretSuffixCACluster)
caClusterConfigMap := corev1.ConfigMap{}
caClusterConfigName := fmt.Sprintf("%s.%s", name, ShootProjectConfigMapSuffixCACluster)

if err := g.c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: caClusterSecretName}, &caClusterSecret); err != nil {
return nil, err
}
err := g.c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: caClusterConfigName}, &caClusterConfigMap)

var caCert []byte
// TODO(petersutter): Remove this fallback of reading the `<shoot-name>.ca-cluster` Secret when Gardener no longer reconciles it, presumably with Gardener v1.97.
if apierrors.IsNotFound(err) { //nolint:gocritic // Rewriting the if-else to a switch statement does not provide significant improvement in this case. We will soon remove the switch once we stop reading the Secret.
caClusterSecret := corev1.Secret{}
caClusterSecretName := fmt.Sprintf("%s.%s", name, ShootProjectSecretSuffixCACluster)

if err := g.c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: caClusterSecretName}, &caClusterSecret); err != nil {
return nil, fmt.Errorf("could not get cluster CA secret: %w", err)
}

caCert, ok := caClusterSecret.Data[DataKeyCertificateCA]
if !ok || len(caCert) == 0 {
return nil, fmt.Errorf("%s of secret %s is empty", DataKeyCertificateCA, caClusterSecretName)
caCert = caClusterSecret.Data[DataKeyCertificateCA]
if len(caCert) == 0 {
return nil, fmt.Errorf("%s of secret %s is empty", DataKeyCertificateCA, caClusterSecretName)
}
} else if err != nil {
return nil, fmt.Errorf("could not get cluster CA config map: %w", err)
} else {
caCert = []byte(caClusterConfigMap.Data[DataKeyCertificateCA])
if len(caCert) == 0 {
return nil, fmt.Errorf("%s of config map %s is empty", DataKeyCertificateCA, caClusterConfigName)
}
}

kubeconfigRequest := shootKubeconfigRequest{
Expand Down
8 changes: 4 additions & 4 deletions pkg/cmd/ssh/ssh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,13 @@ var _ = Describe("SSH Command", func() {
ca, err := csc.GenerateCertificate()
Expect(err).NotTo(HaveOccurred())

caSecret := &corev1.Secret{
caConfigMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: testShoot.Name + ".ca-cluster",
Namespace: testShoot.Namespace,
},
Data: map[string][]byte{
"ca.crt": ca.CertificatePEM,
Data: map[string]string{
"ca.crt": string(ca.CertificatePEM),
},
}

Expand All @@ -251,7 +251,7 @@ var _ = Describe("SSH Command", func() {
testShoot,
testShootKeypair,
seedKubeconfigSecret,
caSecret,
caConfigMap,
).
WithStatusSubresource(&operationsv1alpha1.Bastion{}).
Build())
Expand Down
8 changes: 4 additions & 4 deletions pkg/target/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,13 @@ var _ = Describe("Target Manager", func() {
ca, err := csc.GenerateCertificate()
Expect(err).NotTo(HaveOccurred())

prod1GoldenShootCaSecret := &corev1.Secret{
prod1GoldenShootCaConfigMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: prod1GoldenShoot.Name + ".ca-cluster",
Namespace: *prod1Project.Spec.Namespace,
},
Data: map[string][]byte{
"ca.crt": ca.CertificatePEM,
Data: map[string]string{
"ca.crt": string(ca.CertificatePEM),
},
}

Expand All @@ -209,7 +209,7 @@ var _ = Describe("Target Manager", func() {
prod2AmbiguousShoot,
prod1PendingShoot,
namespace,
prod1GoldenShootCaSecret,
prod1GoldenShootCaConfigMap,
)

ctrl = gomock.NewController(GinkgoT())
Expand Down

0 comments on commit 8d49095

Please sign in to comment.