Skip to content

Commit f1f8dc7

Browse files
committed
feat: add support for identity service server in GKE controlplane status
1 parent 0211689 commit f1f8dc7

File tree

4 files changed

+108
-22
lines changed

4 files changed

+108
-22
lines changed

cloud/services/container/clusters/kubeconfig.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,35 @@ const (
4141
GkeScope = "https://www.googleapis.com/auth/cloud-platform"
4242
)
4343

44-
func (s *Service) reconcileKubeconfig(ctx context.Context, cluster *containerpb.Cluster, log *logr.Logger) error {
44+
func (s *Service) reconcileKubeconfig(ctx context.Context, cluster *containerpb.Cluster, log *logr.Logger) (clientcmd.ClientConfig, error) {
4545
log.Info("Reconciling kubeconfig")
4646
clusterRef := types.NamespacedName{
4747
Name: s.scope.Cluster.Name,
4848
Namespace: s.scope.Cluster.Namespace,
4949
}
50+
var kubeConfig *api.Config
5051

5152
configSecret, err := secret.GetFromNamespacedName(ctx, s.scope.Client(), clusterRef, secret.Kubeconfig)
5253
if err != nil {
5354
if !apierrors.IsNotFound(err) {
5455
log.Error(err, "getting kubeconfig secret", "name", clusterRef)
55-
return fmt.Errorf("getting kubeconfig secret %s: %w", clusterRef, err)
56+
return nil, fmt.Errorf("getting kubeconfig secret %s: %w", clusterRef, err)
5657
}
5758
log.Info("kubeconfig secret not found, creating")
5859

59-
if createErr := s.createCAPIKubeconfigSecret(
60+
if kubeConfig, err = s.createCAPIKubeconfigSecret(
6061
ctx,
6162
cluster,
6263
&clusterRef,
6364
log,
64-
); createErr != nil {
65-
return fmt.Errorf("creating kubeconfig secret: %w", createErr)
65+
); err != nil {
66+
return nil, fmt.Errorf("creating kubeconfig secret: %w", err)
6667
}
67-
} else if updateErr := s.updateCAPIKubeconfigSecret(ctx, configSecret); updateErr != nil {
68-
return fmt.Errorf("updating kubeconfig secret: %w", err)
68+
} else if kubeConfig, err = s.updateCAPIKubeconfigSecret(ctx, configSecret); err != nil {
69+
return nil, fmt.Errorf("updating kubeconfig secret: %w", err)
6970
}
7071

71-
return nil
72+
return clientcmd.NewDefaultClientConfig(*kubeConfig, nil), nil
7273
}
7374

7475
func (s *Service) reconcileAdditionalKubeconfigs(ctx context.Context, cluster *containerpb.Cluster, log *logr.Logger) error {
@@ -133,21 +134,21 @@ func (s *Service) createUserKubeconfigSecret(ctx context.Context, cluster *conta
133134
return nil
134135
}
135136

136-
func (s *Service) createCAPIKubeconfigSecret(ctx context.Context, cluster *containerpb.Cluster, clusterRef *types.NamespacedName, log *logr.Logger) error {
137+
func (s *Service) createCAPIKubeconfigSecret(ctx context.Context, cluster *containerpb.Cluster, clusterRef *types.NamespacedName, log *logr.Logger) (*api.Config, error) {
137138
controllerOwnerRef := *metav1.NewControllerRef(s.scope.GCPManagedControlPlane, infrav1exp.GroupVersion.WithKind("GCPManagedControlPlane"))
138139

139140
contextName := s.getKubeConfigContextName(false)
140141

141142
cfg, err := s.createBaseKubeConfig(contextName, cluster)
142143
if err != nil {
143144
log.Error(err, "failed creating base config")
144-
return fmt.Errorf("creating base kubeconfig: %w", err)
145+
return nil, fmt.Errorf("creating base kubeconfig: %w", err)
145146
}
146147

147148
token, err := s.generateToken(ctx)
148149
if err != nil {
149150
log.Error(err, "failed generating token")
150-
return err
151+
return nil, err
151152
}
152153
cfg.AuthInfos = map[string]*api.AuthInfo{
153154
contextName: {
@@ -158,50 +159,50 @@ func (s *Service) createCAPIKubeconfigSecret(ctx context.Context, cluster *conta
158159
out, err := clientcmd.Write(*cfg)
159160
if err != nil {
160161
log.Error(err, "failed serializing kubeconfig to yaml")
161-
return fmt.Errorf("serialize kubeconfig to yaml: %w", err)
162+
return nil, fmt.Errorf("serialize kubeconfig to yaml: %w", err)
162163
}
163164

164165
kubeconfigSecret := kubeconfig.GenerateSecretWithOwner(*clusterRef, out, controllerOwnerRef)
165166
if err := s.scope.Client().Create(ctx, kubeconfigSecret); err != nil {
166167
log.Error(err, "failed creating secret")
167-
return fmt.Errorf("creating secret: %w", err)
168+
return nil, fmt.Errorf("creating secret: %w", err)
168169
}
169170

170-
return nil
171+
return cfg, nil
171172
}
172173

173-
func (s *Service) updateCAPIKubeconfigSecret(ctx context.Context, configSecret *corev1.Secret) error {
174+
func (s *Service) updateCAPIKubeconfigSecret(ctx context.Context, configSecret *corev1.Secret) (*api.Config, error) {
174175
data, ok := configSecret.Data[secret.KubeconfigDataName]
175176
if !ok {
176-
return errors.Errorf("missing key %q in secret data", secret.KubeconfigDataName)
177+
return nil, errors.Errorf("missing key %q in secret data", secret.KubeconfigDataName)
177178
}
178179

179180
config, err := clientcmd.Load(data)
180181
if err != nil {
181-
return errors.Wrap(err, "failed to convert kubeconfig Secret into a clientcmdapi.Config")
182+
return nil, errors.Wrap(err, "failed to convert kubeconfig Secret into a clientcmdapi.Config")
182183
}
183184

184185
token, err := s.generateToken(ctx)
185186
if err != nil {
186-
return err
187+
return nil, err
187188
}
188189

189190
contextName := s.getKubeConfigContextName(false)
190191
config.AuthInfos[contextName].Token = token
191192

192193
out, err := clientcmd.Write(*config)
193194
if err != nil {
194-
return errors.Wrap(err, "failed to serialize config to yaml")
195+
return nil, errors.Wrap(err, "failed to serialize config to yaml")
195196
}
196197

197198
configSecret.Data[secret.KubeconfigDataName] = out
198199

199200
err = s.scope.Client().Update(ctx, configSecret)
200201
if err != nil {
201-
return fmt.Errorf("updating kubeconfig secret: %w", err)
202+
return nil, fmt.Errorf("updating kubeconfig secret: %w", err)
202203
}
203204

204-
return nil
205+
return config, nil
205206
}
206207

207208
func (s *Service) getKubeConfigContextName(isUser bool) string {

cloud/services/container/clusters/reconcile.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ import (
3131
"github.com/googleapis/gax-go/v2/apierror"
3232
"github.com/pkg/errors"
3333
"google.golang.org/grpc/codes"
34+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
35+
"k8s.io/apimachinery/pkg/runtime"
36+
"k8s.io/apimachinery/pkg/runtime/schema"
37+
"k8s.io/client-go/dynamic"
38+
"k8s.io/client-go/tools/clientcmd"
3439
infrav1exp "sigs.k8s.io/cluster-api-provider-gcp/exp/api/v1beta1"
3540
"sigs.k8s.io/cluster-api-provider-gcp/util/reconciler"
3641
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -157,7 +162,7 @@ func (s *Service) Reconcile(ctx context.Context) (ctrl.Result, error) {
157162
conditions.MarkFalse(s.scope.ConditionSetter(), infrav1exp.GKEControlPlaneUpdatingCondition, infrav1exp.GKEControlPlaneUpdatedReason, clusterv1.ConditionSeverityInfo, "")
158163

159164
// Reconcile kubeconfig
160-
err = s.reconcileKubeconfig(ctx, cluster, &log)
165+
kubeConfig, err := s.reconcileKubeconfig(ctx, cluster, &log)
161166
if err != nil {
162167
log.Error(err, "Failed to reconcile CAPI kubeconfig")
163168
return ctrl.Result{}, err
@@ -168,6 +173,11 @@ func (s *Service) Reconcile(ctx context.Context) (ctrl.Result, error) {
168173
return ctrl.Result{}, err
169174
}
170175

176+
err = s.reconcileIdentityService(ctx, kubeConfig, &log)
177+
if err != nil {
178+
return ctrl.Result{}, err
179+
}
180+
171181
s.scope.SetEndpoint(cluster.GetEndpoint())
172182
conditions.MarkTrue(s.scope.ConditionSetter(), clusterv1.ReadyCondition)
173183
conditions.MarkTrue(s.scope.ConditionSetter(), infrav1exp.GKEControlPlaneReadyCondition)
@@ -518,3 +528,70 @@ func compareMasterAuthorizedNetworksConfig(a, b *containerpb.MasterAuthorizedNet
518528
}
519529
return true
520530
}
531+
532+
// reconcileIdentityService set the identity service server in the status of the GCPManagedControlPlane.
533+
func (s *Service) reconcileIdentityService(ctx context.Context, kubeConfig clientcmd.ClientConfig, log *logr.Logger) error {
534+
identityServiceServer, err := s.getIdentityServiceServer(ctx, kubeConfig)
535+
if err != nil {
536+
err = fmt.Errorf("failed to retrieve identity service: %w", err)
537+
log.Error(err, "Failed to retrieve identity service server")
538+
return err
539+
}
540+
541+
s.scope.GCPManagedControlPlane.Status.IdentityServiceServer = identityServiceServer
542+
543+
return nil
544+
}
545+
546+
// getIdentityServiceServer retrieve the server to use for authentication using the identity service.
547+
func (s *Service) getIdentityServiceServer(ctx context.Context, kubeConfig clientcmd.ClientConfig) (string, error) {
548+
/*
549+
# Example of the ClientConfig (see https://cloud.google.com/kubernetes-engine/docs/how-to/oidc#configuring_on_a_cluster):
550+
apiVersion: authentication.gke.io/v2alpha1
551+
kind: ClientConfig
552+
metadata:
553+
name: default
554+
namespace: kube-public
555+
spec:
556+
server: https://192.168.0.1:6443
557+
*/
558+
559+
if !s.scope.GCPManagedControlPlane.Spec.EnableIdentityService {
560+
// Identity service is not enabled, skipping
561+
return "", nil
562+
}
563+
564+
if kubeConfig == nil {
565+
return "", errors.New("provided kubernetes configuration is nil")
566+
}
567+
568+
config, err := kubeConfig.ClientConfig()
569+
if err != nil {
570+
return "", err
571+
}
572+
573+
dynamicClient, err := dynamic.NewForConfig(config)
574+
if err != nil {
575+
return "", err
576+
}
577+
578+
resourceID := schema.GroupVersionResource{
579+
Group: "authentication.gke.io",
580+
Version: "v2alpha1",
581+
Resource: "clientconfigs",
582+
}
583+
584+
unstructured, err := dynamicClient.Resource(resourceID).Namespace("kube-public").Get(ctx, "default", metav1.GetOptions{})
585+
if err != nil {
586+
return "", err
587+
}
588+
589+
gkeClientConfig := struct {
590+
Spec struct {
591+
Server string `json:"server"`
592+
} `json:"spec"`
593+
}{}
594+
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, &gkeClientConfig)
595+
596+
return gkeClientConfig.Spec.Server, err
597+
}

config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,10 @@ spec:
289289
290290
Deprecated: This field will soon be removed and you are expected to use Version instead.
291291
type: string
292+
identityServiceServer:
293+
description: IdentityServiceServer indicates when the identity service
294+
is enabled, the server for external authentication.
295+
type: string
292296
initialized:
293297
description: |-
294298
Initialized is true when the control plane is available for initial contact.

exp/api/v1beta1/gcpmanagedcontrolplane_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ type GCPManagedControlPlaneStatus struct {
200200
// Version represents the version of the GKE control plane.
201201
// +optional
202202
Version *string `json:"version,omitempty"`
203+
204+
// IdentityServiceServer indicates when the identity service is enabled, the server for external authentication.
205+
// +optional
206+
IdentityServiceServer string `json:"identityServiceServer,omitempty"`
203207
}
204208

205209
// +kubebuilder:object:root=true

0 commit comments

Comments
 (0)