From 95ab1aa3aafe0c22ec1ff5601dcc355ca135461b Mon Sep 17 00:00:00 2001 From: Arnaud Farbos Date: Thu, 19 Dec 2024 08:44:43 -0800 Subject: [PATCH 1/3] fix: Tiltfile and docs links --- Tiltfile | 4 +++- docs/book/src/developers/development.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Tiltfile b/Tiltfile index 233cfc4eb..8279d294e 100644 --- a/Tiltfile +++ b/Tiltfile @@ -102,7 +102,8 @@ FROM golang:1.18 as tilt-helper # Support live reloading with Tilt RUN wget --output-document /restart.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/restart.sh && \ wget --output-document /start.sh --quiet https://raw.githubusercontent.com/windmilleng/rerun-process-wrapper/master/start.sh && \ - chmod +x /start.sh && chmod +x /restart.sh + chmod +x /start.sh && chmod +x /restart.sh && \ + touch /process.txt && chmod 0666 /process.txt `# pre-create PID file to allow even non-root users to run the image` """ tilt_dockerfile_header = """ @@ -110,6 +111,7 @@ FROM gcr.io/distroless/base:debug as tilt WORKDIR / COPY --from=tilt-helper /start.sh . COPY --from=tilt-helper /restart.sh . +COPY --from=tilt-helper /process.txt . COPY manager . """ diff --git a/docs/book/src/developers/development.md b/docs/book/src/developers/development.md index 67f93e507..49a22930d 100644 --- a/docs/book/src/developers/development.md +++ b/docs/book/src/developers/development.md @@ -158,7 +158,7 @@ It will setup the network, if you already setup the network you can skip this st ``` By default, the Cluster API components deployed by Tilt have experimental features turned off. -If you would like to enable these features, add `extra_args` as specified in [The Cluster API Book](https://cluster-api.sigs.k8s.io/developer/tilt.html#create-a-tilt-settingsjson-file). +If you would like to enable these features, add `extra_args` as specified in [The Cluster API Book](https://cluster-api.sigs.k8s.io/developer/core/tilt.html?highlight=tilt#create-a-tilt-settings-file). Once your kind management cluster is up and running, you can [deploy a workload cluster](#deploying-a-workload-cluster). From b731a5cab313426567d50f937aeae278ebbe7f6f Mon Sep 17 00:00:00 2001 From: Arnaud Farbos Date: Thu, 19 Dec 2024 08:45:33 -0800 Subject: [PATCH 2/3] fix: add update identityServiceConfig logic --- cloud/services/container/clusters/reconcile.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloud/services/container/clusters/reconcile.go b/cloud/services/container/clusters/reconcile.go index 22a14dd8d..e385581a3 100644 --- a/cloud/services/container/clusters/reconcile.go +++ b/cloud/services/container/clusters/reconcile.go @@ -447,6 +447,12 @@ func (s *Service) checkDiffAndPrepareUpdate(existingCluster *containerpb.Cluster log.V(4).Info("Master authorized networks config update check", "desired", desiredMasterAuthorizedNetworksConfig) } + desiredEnableIdentityService := s.scope.GCPManagedControlPlane.Spec.EnableIdentityService + if desiredEnableIdentityService != existingCluster.GetIdentityServiceConfig().GetEnabled() { + needUpdate = true + clusterUpdate.DesiredIdentityServiceConfig = &containerpb.IdentityServiceConfig{Enabled: desiredEnableIdentityService} + } + updateClusterRequest := containerpb.UpdateClusterRequest{ Name: s.scope.ClusterFullName(), Update: &clusterUpdate, From 60fdfc28c76c7f65a1ebffd5178bb32e70a8ac68 Mon Sep 17 00:00:00 2001 From: Arnaud Farbos Date: Fri, 13 Dec 2024 16:19:42 -0800 Subject: [PATCH 3/3] feat: add support for identity service server in GKE controlplane status --- .../services/container/clusters/kubeconfig.go | 43 +++++----- .../services/container/clusters/reconcile.go | 79 ++++++++++++++++++- ...ster.x-k8s.io_gcpmanagedcontrolplanes.yaml | 4 + .../v1beta1/gcpmanagedcontrolplane_types.go | 4 + 4 files changed, 108 insertions(+), 22 deletions(-) diff --git a/cloud/services/container/clusters/kubeconfig.go b/cloud/services/container/clusters/kubeconfig.go index 978ead85c..a7529f011 100644 --- a/cloud/services/container/clusters/kubeconfig.go +++ b/cloud/services/container/clusters/kubeconfig.go @@ -41,34 +41,35 @@ const ( GkeScope = "https://www.googleapis.com/auth/cloud-platform" ) -func (s *Service) reconcileKubeconfig(ctx context.Context, cluster *containerpb.Cluster, log *logr.Logger) error { +func (s *Service) reconcileKubeconfig(ctx context.Context, cluster *containerpb.Cluster, log *logr.Logger) (clientcmd.ClientConfig, error) { log.Info("Reconciling kubeconfig") clusterRef := types.NamespacedName{ Name: s.scope.Cluster.Name, Namespace: s.scope.Cluster.Namespace, } + var kubeConfig *api.Config configSecret, err := secret.GetFromNamespacedName(ctx, s.scope.Client(), clusterRef, secret.Kubeconfig) if err != nil { if !apierrors.IsNotFound(err) { log.Error(err, "getting kubeconfig secret", "name", clusterRef) - return fmt.Errorf("getting kubeconfig secret %s: %w", clusterRef, err) + return nil, fmt.Errorf("getting kubeconfig secret %s: %w", clusterRef, err) } log.Info("kubeconfig secret not found, creating") - if createErr := s.createCAPIKubeconfigSecret( + if kubeConfig, err = s.createCAPIKubeconfigSecret( ctx, cluster, &clusterRef, log, - ); createErr != nil { - return fmt.Errorf("creating kubeconfig secret: %w", createErr) + ); err != nil { + return nil, fmt.Errorf("creating kubeconfig secret: %w", err) } - } else if updateErr := s.updateCAPIKubeconfigSecret(ctx, configSecret); updateErr != nil { - return fmt.Errorf("updating kubeconfig secret: %w", err) + } else if kubeConfig, err = s.updateCAPIKubeconfigSecret(ctx, configSecret); err != nil { + return nil, fmt.Errorf("updating kubeconfig secret: %w", err) } - return nil + return clientcmd.NewDefaultClientConfig(*kubeConfig, nil), nil } func (s *Service) reconcileAdditionalKubeconfigs(ctx context.Context, cluster *containerpb.Cluster, log *logr.Logger) error { @@ -133,7 +134,7 @@ func (s *Service) createUserKubeconfigSecret(ctx context.Context, cluster *conta return nil } -func (s *Service) createCAPIKubeconfigSecret(ctx context.Context, cluster *containerpb.Cluster, clusterRef *types.NamespacedName, log *logr.Logger) error { +func (s *Service) createCAPIKubeconfigSecret(ctx context.Context, cluster *containerpb.Cluster, clusterRef *types.NamespacedName, log *logr.Logger) (*api.Config, error) { controllerOwnerRef := *metav1.NewControllerRef(s.scope.GCPManagedControlPlane, infrav1exp.GroupVersion.WithKind("GCPManagedControlPlane")) contextName := s.getKubeConfigContextName(false) @@ -141,13 +142,13 @@ func (s *Service) createCAPIKubeconfigSecret(ctx context.Context, cluster *conta cfg, err := s.createBaseKubeConfig(contextName, cluster) if err != nil { log.Error(err, "failed creating base config") - return fmt.Errorf("creating base kubeconfig: %w", err) + return nil, fmt.Errorf("creating base kubeconfig: %w", err) } token, err := s.generateToken(ctx) if err != nil { log.Error(err, "failed generating token") - return err + return nil, err } cfg.AuthInfos = map[string]*api.AuthInfo{ contextName: { @@ -158,32 +159,32 @@ func (s *Service) createCAPIKubeconfigSecret(ctx context.Context, cluster *conta out, err := clientcmd.Write(*cfg) if err != nil { log.Error(err, "failed serializing kubeconfig to yaml") - return fmt.Errorf("serialize kubeconfig to yaml: %w", err) + return nil, fmt.Errorf("serialize kubeconfig to yaml: %w", err) } kubeconfigSecret := kubeconfig.GenerateSecretWithOwner(*clusterRef, out, controllerOwnerRef) if err := s.scope.Client().Create(ctx, kubeconfigSecret); err != nil { log.Error(err, "failed creating secret") - return fmt.Errorf("creating secret: %w", err) + return nil, fmt.Errorf("creating secret: %w", err) } - return nil + return cfg, nil } -func (s *Service) updateCAPIKubeconfigSecret(ctx context.Context, configSecret *corev1.Secret) error { +func (s *Service) updateCAPIKubeconfigSecret(ctx context.Context, configSecret *corev1.Secret) (*api.Config, error) { data, ok := configSecret.Data[secret.KubeconfigDataName] if !ok { - return errors.Errorf("missing key %q in secret data", secret.KubeconfigDataName) + return nil, errors.Errorf("missing key %q in secret data", secret.KubeconfigDataName) } config, err := clientcmd.Load(data) if err != nil { - return errors.Wrap(err, "failed to convert kubeconfig Secret into a clientcmdapi.Config") + return nil, errors.Wrap(err, "failed to convert kubeconfig Secret into a clientcmdapi.Config") } token, err := s.generateToken(ctx) if err != nil { - return err + return nil, err } contextName := s.getKubeConfigContextName(false) @@ -191,17 +192,17 @@ func (s *Service) updateCAPIKubeconfigSecret(ctx context.Context, configSecret * out, err := clientcmd.Write(*config) if err != nil { - return errors.Wrap(err, "failed to serialize config to yaml") + return nil, errors.Wrap(err, "failed to serialize config to yaml") } configSecret.Data[secret.KubeconfigDataName] = out err = s.scope.Client().Update(ctx, configSecret) if err != nil { - return fmt.Errorf("updating kubeconfig secret: %w", err) + return nil, fmt.Errorf("updating kubeconfig secret: %w", err) } - return nil + return config, nil } func (s *Service) getKubeConfigContextName(isUser bool) string { diff --git a/cloud/services/container/clusters/reconcile.go b/cloud/services/container/clusters/reconcile.go index e385581a3..8d6af931b 100644 --- a/cloud/services/container/clusters/reconcile.go +++ b/cloud/services/container/clusters/reconcile.go @@ -31,6 +31,11 @@ import ( "github.com/googleapis/gax-go/v2/apierror" "github.com/pkg/errors" "google.golang.org/grpc/codes" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/tools/clientcmd" infrav1exp "sigs.k8s.io/cluster-api-provider-gcp/exp/api/v1beta1" "sigs.k8s.io/cluster-api-provider-gcp/util/reconciler" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -155,7 +160,7 @@ func (s *Service) Reconcile(ctx context.Context) (ctrl.Result, error) { conditions.MarkFalse(s.scope.ConditionSetter(), infrav1exp.GKEControlPlaneUpdatingCondition, infrav1exp.GKEControlPlaneUpdatedReason, clusterv1.ConditionSeverityInfo, "") // Reconcile kubeconfig - err = s.reconcileKubeconfig(ctx, cluster, &log) + kubeConfig, err := s.reconcileKubeconfig(ctx, cluster, &log) if err != nil { log.Error(err, "Failed to reconcile CAPI kubeconfig") return ctrl.Result{}, err @@ -166,6 +171,11 @@ func (s *Service) Reconcile(ctx context.Context) (ctrl.Result, error) { return ctrl.Result{}, err } + err = s.reconcileIdentityService(ctx, kubeConfig, &log) + if err != nil { + return ctrl.Result{}, err + } + s.scope.SetEndpoint(cluster.GetEndpoint()) conditions.MarkTrue(s.scope.ConditionSetter(), clusterv1.ReadyCondition) conditions.MarkTrue(s.scope.ConditionSetter(), infrav1exp.GKEControlPlaneReadyCondition) @@ -488,3 +498,70 @@ func compareMasterAuthorizedNetworksConfig(a, b *containerpb.MasterAuthorizedNet } return true } + +// reconcileIdentityService set the identity service server in the status of the GCPManagedControlPlane. +func (s *Service) reconcileIdentityService(ctx context.Context, kubeConfig clientcmd.ClientConfig, log *logr.Logger) error { + identityServiceServer, err := s.getIdentityServiceServer(ctx, kubeConfig) + if err != nil { + err = fmt.Errorf("failed to retrieve identity service: %w", err) + log.Error(err, "Failed to retrieve identity service server") + return err + } + + s.scope.GCPManagedControlPlane.Status.IdentityServiceServer = identityServiceServer + + return nil +} + +// getIdentityServiceServer retrieve the server to use for authentication using the identity service. +func (s *Service) getIdentityServiceServer(ctx context.Context, kubeConfig clientcmd.ClientConfig) (string, error) { + /* + # Example of the ClientConfig (see https://cloud.google.com/kubernetes-engine/docs/how-to/oidc#configuring_on_a_cluster): + apiVersion: authentication.gke.io/v2alpha1 + kind: ClientConfig + metadata: + name: default + namespace: kube-public + spec: + server: https://192.168.0.1:6443 + */ + + if !s.scope.GCPManagedControlPlane.Spec.EnableIdentityService { + // Identity service is not enabled, skipping + return "", nil + } + + if kubeConfig == nil { + return "", errors.New("provided kubernetes configuration is nil") + } + + config, err := kubeConfig.ClientConfig() + if err != nil { + return "", err + } + + dynamicClient, err := dynamic.NewForConfig(config) + if err != nil { + return "", err + } + + resourceID := schema.GroupVersionResource{ + Group: "authentication.gke.io", + Version: "v2alpha1", + Resource: "clientconfigs", + } + + unstructured, err := dynamicClient.Resource(resourceID).Namespace("kube-public").Get(ctx, "default", metav1.GetOptions{}) + if err != nil { + return "", err + } + + gkeClientConfig := struct { + Spec struct { + Server string `json:"server"` + } `json:"spec"` + }{} + err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured.Object, &gkeClientConfig) + + return gkeClientConfig.Spec.Server, err +} diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml index 2c00db076..532c1f8e1 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml @@ -263,6 +263,10 @@ spec: description: CurrentVersion shows the current version of the GKE control plane. type: string + identityServiceServer: + description: IdentityServiceServer indicates when the identity service + is enabled, the server for external authentication. + type: string initialized: description: |- Initialized is true when the control plane is available for initial contact. diff --git a/exp/api/v1beta1/gcpmanagedcontrolplane_types.go b/exp/api/v1beta1/gcpmanagedcontrolplane_types.go index 0d2e68cfe..79ee51f46 100644 --- a/exp/api/v1beta1/gcpmanagedcontrolplane_types.go +++ b/exp/api/v1beta1/gcpmanagedcontrolplane_types.go @@ -171,6 +171,10 @@ type GCPManagedControlPlaneStatus struct { // CurrentVersion shows the current version of the GKE control plane. // +optional CurrentVersion string `json:"currentVersion,omitempty"` + + // IdentityServiceServer indicates when the identity service is enabled, the server for external authentication. + // +optional + IdentityServiceServer string `json:"identityServiceServer,omitempty"` } // +kubebuilder:object:root=true