Skip to content

Commit 3bad5b6

Browse files
committed
feat: add support for identity service server in GKE controlplane status
1 parent e7ce524 commit 3bad5b6

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

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)