Skip to content

Commit

Permalink
feat: add support for identity service server in GKE controlplane status
Browse files Browse the repository at this point in the history
  • Loading branch information
afarbos committed Dec 19, 2024
1 parent b731a5c commit 3cbacd9
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 22 deletions.
43 changes: 22 additions & 21 deletions cloud/services/container/clusters/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -133,21 +134,21 @@ 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)

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: {
Expand All @@ -158,50 +159,50 @@ 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)
config.AuthInfos[contextName].Token = token

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 {
Expand Down
79 changes: 78 additions & 1 deletion cloud/services/container/clusters/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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, log)
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, log *logr.Logger) (string, error) {

Check failure on line 517 in cloud/services/container/clusters/reconcile.go

View workflow job for this annotation

GitHub Actions / lint

unused-parameter: parameter 'log' seems to be unused, consider removing or renaming it as _ (revive)
/*
# 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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions exp/api/v1beta1/gcpmanagedcontrolplane_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3cbacd9

Please sign in to comment.