...

Source file src/edge-infra.dev/pkg/f8n/gcp/k8s/controllers/projectinit/project_controller.go

Documentation: edge-infra.dev/pkg/f8n/gcp/k8s/controllers/projectinit

     1  package projectinit
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"time"
     9  
    10  	ar "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/artifactregistry/v1beta1"
    11  	compute "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/compute/v1beta1"
    12  	iam "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/iam/v1beta1"
    13  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
    14  	kcc "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
    15  	resourcemgr "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/resourcemanager/v1beta1"
    16  	secretmgr "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/secretmanager/v1beta1"
    17  	v1 "k8s.io/api/core/v1"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    20  	ctrl "sigs.k8s.io/controller-runtime"
    21  	"sigs.k8s.io/controller-runtime/pkg/client"
    22  
    23  	"edge-infra.dev/pkg/k8s/konfigkonnector/apis/meta"
    24  	"edge-infra.dev/pkg/k8s/runtime/inventory"
    25  	"edge-infra.dev/pkg/k8s/runtime/sap"
    26  	unstructuredutil "edge-infra.dev/pkg/k8s/unstructured"
    27  	iamutil "edge-infra.dev/pkg/lib/gcp/iam"
    28  	"edge-infra.dev/pkg/lib/gcp/iam/roles"
    29  	"edge-infra.dev/pkg/lib/ncr/gcp/security"
    30  )
    31  
    32  // +kubebuilder:rbac:groups="compute.cnrm.cloud.google.com",resources=computefirewalls;computerouternats;computerouters;computesslpolicies,verbs=create;get;list;update;patch;watch;delete
    33  // +kubebuilder:rbac:groups="compute.cnrm.cloud.google.com",resources=computefirewalls/status;computerouternats/status;computerouters/status;computesslpolicies/status,verbs=get
    34  // +kubebuilder:rbac:groups="resourcemanager.cnrm.cloud.google.com",resources=projects,verbs=get;list;watch
    35  // +kubebuilder:rbac:groups="resourcemanager.cnrm.cloud.google.com",resources=projects/status,verbs=get;watch
    36  // +kubebuilder:rbac:groups="secretmanager.cnrm.cloud.google.com",resources=secretmanagersecrets;secretmanagersecretversions,verbs=create;get;list;update;patch;watch;delete
    37  // +kubebuilder:rbac:groups="secretmanager.cnrm.cloud.google.com",resources=secretmanagersecrets/status;secretmanagersecretversions/status,verbs=get;list;watch
    38  // +kubebuilder:rbac:groups="iam.cnrm.cloud.google.com",resources=iamserviceaccounts;iampolicymembers;iamserviceaccountkeys,verbs=create;get;list;update;patch;watch;delete
    39  // +kubebuilder:rbac:groups="iam.cnrm.cloud.google.com",resources=iamserviceaccounts/status;iampolicymembers/status;iamserviceaccountkeys/status,verbs=get;watch
    40  // +kubebuilder:rbac:groups="compute.cnrm.cloud.google.com",resources=computenetworks,verbs=create;get;list;update;patch;watch;delete
    41  // +kubebuilder:rbac:groups="compute.cnrm.cloud.google.com",resources=computenetworks/status,verbs=get;watch
    42  // +kubebuilder:rbac:groups="",resources=configmaps;secrets,verbs=create;get;list;update;watch;patch;delete
    43  
    44  // TODO: Update name once Edge vs Foundation is settled
    45  const (
    46  	DockerPullSA           = "docker-pull-sa"
    47  	PltfDockerPullCfgSAKey = "pltf-pull-cfg-sa-key"
    48  	K8sPltfDockerPullCfg   = "platform-docker-pull-config"
    49  	PltfDockerPullCfg      = "platform-docker-pull-cfg"
    50  	DefaultRouterName      = "default-router"
    51  	DefaultNATGatewayName  = "default-nat-gateway"
    52  )
    53  
    54  var (
    55  	defaultNetworkType = "REGIONAL"
    56  	defaultNetworkName = "default"
    57  )
    58  
    59  // Reconciler reconciles Project objects to in order to
    60  // create and configure required infrastructure in GCP, e.g.:
    61  //
    62  // - Firewall configurations
    63  type Reconciler struct {
    64  	client.Client
    65  	ResourceManager *sap.ResourceManager
    66  
    67  	FirewallConfig     Firewall
    68  	ArtifactRegistries []ArtifactRegistry
    69  
    70  	// Inventories is the ConfigMap backed-storage listing objects that this
    71  	// controller creates, to be used for pruning.
    72  	Inventories *inventory.Storage
    73  
    74  	// Name is the controller's name, used to consistently represent the controller
    75  	// in various cluster interactions, e.g., as field manager
    76  	Name string
    77  
    78  	// Namespace is the namespace this controller is deployed to
    79  	Namespace string
    80  	GCPRegion string
    81  
    82  	retryInterval time.Duration
    83  }
    84  
    85  // SetupWithManager sets up Reconciler with the manager, such that the Reconciler
    86  // will be triggered for updates to
    87  // If configuration hasn't been explicitly set on the Reconciler struct
    88  // (e.g., FirewallConfig, controller name), defaults will be set by this function.
    89  func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
    90  	// instantiate server-side apply client
    91  	var err error
    92  	r.ResourceManager, err = sap.NewResourceManagerFromConfig(
    93  		mgr.GetConfig(),
    94  		client.Options{},
    95  		sap.Owner{Field: r.Name},
    96  	)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	return ctrl.NewControllerManagedBy(mgr).
   102  		For(&resourcemgr.Project{}).
   103  		Owns(&compute.ComputeFirewall{}).
   104  		Owns(&compute.ComputeSSLPolicy{}).
   105  		Owns(&iam.IAMPolicyMember{}).
   106  		Owns(&iam.IAMServiceAccountKey{}).
   107  		Owns(&v1.Secret{}).
   108  		Owns(&secretmgr.SecretManagerSecret{}).
   109  		Owns(&secretmgr.SecretManagerSecretVersion{}).
   110  		Complete(r)
   111  }
   112  
   113  func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
   114  	log := ctrl.LoggerFrom(ctx)
   115  
   116  	// instantiate inventories if we have not done so already
   117  	if r.Inventories == nil {
   118  		var err error
   119  		r.Inventories, err = inventory.NewStorage(ctx, r.Client, r.Name, r.Namespace)
   120  		if err != nil {
   121  			return ctrl.Result{}, fmt.Errorf("failed to instantiate inventory: %w", err)
   122  		}
   123  	}
   124  
   125  	p := resourcemgr.Project{}
   126  	if err := r.Get(ctx, req.NamespacedName, &p); err != nil {
   127  		// do nothing if object isnt found
   128  		return ctrl.Result{}, client.IgnoreNotFound(err)
   129  	}
   130  
   131  	if ready, reason := meta.IsReady(p.Status.Conditions); !ready {
   132  		log.Info("waiting for project to become ready", "reason", reason)
   133  		return ctrl.Result{Requeue: true, RequeueAfter: r.retryInterval}, nil
   134  	}
   135  
   136  	if p.Spec.ResourceID == nil {
   137  		log.Info("waiting for spec.resourceID to be set")
   138  		return ctrl.Result{Requeue: true, RequeueAfter: r.retryInterval}, nil
   139  	}
   140  
   141  	if p.Status.Number == nil {
   142  		log.Info("waiting for status.projectNumber to be set")
   143  		return ctrl.Result{Requeue: true, RequeueAfter: r.retryInterval}, nil
   144  	}
   145  
   146  	if err := r.reconcile(ctx, req, p); err != nil {
   147  		log.Error(err, "failed to reconcile")
   148  		return ctrl.Result{}, err
   149  	}
   150  
   151  	return ctrl.Result{}, nil
   152  }
   153  
   154  // for the reconciled gcp project:
   155  // - disables the default rules allowing SSH and RDP
   156  // - creates new firewall rules that only allow zscaler traffic for SSH and RDP
   157  func (r *Reconciler) reconcile(ctx context.Context, req ctrl.Request, project resourcemgr.Project) error {
   158  	mgr := r.ResourceManager
   159  	log := ctrl.LoggerFrom(ctx).WithName("reconcile")
   160  	computeSAMember := iamutil.ComputeEngineSvcAccountMember(*project.Status.Number)
   161  	dockerPullSAMember := iamutil.StandardSvcAccountMember(DockerPullSA, *project.Spec.ResourceID)
   162  
   163  	var objs []client.Object
   164  
   165  	objs = append(objs, CreateDefaultNetwork(project))
   166  	objs = append(objs, GenerateFirewallRules(project, r.FirewallConfig)...)
   167  	objs = append(objs, GenerateSSLPolicies(project)...)
   168  	objs = append(objs, GenerateIAMServiceAccount(project)...)
   169  	objs = append(objs, GenerateArtifactRegistryPermissions(project, computeSAMember, r.ArtifactRegistries)...)
   170  	objs = append(objs, GenerateArtifactRegistryPermissions(project, dockerPullSAMember, r.ArtifactRegistries)...)
   171  	objs = append(objs, GenerateIAMServiceAccountKey(project)...)
   172  	objs = append(objs, GenerateDefaultRouter(project, r.GCPRegion)...)
   173  	objs = append(objs, GenerateDefaultNATGateway(project, r.GCPRegion)...)
   174  
   175  	var unstructuredObjs []*unstructured.Unstructured
   176  	for _, obj := range objs {
   177  		uobj, err := unstructuredutil.ToUnstructured(obj)
   178  		if err != nil {
   179  			return fmt.Errorf("failed to convert %s/%s/%s to unstructured: %w", obj.GetObjectKind(), obj.GetNamespace(), obj.GetName(), err)
   180  		}
   181  		unstructuredObjs = append(unstructuredObjs, uobj)
   182  	}
   183  
   184  	changeSet, err := mgr.ApplyAll(ctx, unstructuredObjs, sap.ApplyOptions{Force: true})
   185  	if err != nil {
   186  		return fmt.Errorf("failed to apply resources: %w", err)
   187  	}
   188  	log.Info("resources applied", "changeset", changeSet)
   189  
   190  	// retrieve newly created IAMServiceAccountKey and confirm its readiness
   191  	svcAcc := iam.IAMServiceAccountKey{}
   192  	objKey := client.ObjectKey{Namespace: project.Namespace, Name: NameWithProjectPrefix(PltfDockerPullCfgSAKey, project.Name)}
   193  	if err := r.Get(ctx, objKey, &svcAcc); err != nil {
   194  		return fmt.Errorf("failed to retrieve iamSAKey: %w", err)
   195  	}
   196  
   197  	if ready, reason := meta.IsReady(svcAcc.Status.Conditions); !ready {
   198  		return fmt.Errorf("waiting for IAMSAKey to become ready: %s", reason)
   199  	}
   200  
   201  	// Create pull secret object that depends on the IAMServiceAccountKey
   202  	pullSecretObj, err := GenerateDockerPullSecret(project, svcAcc, r.ArtifactRegistries)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	// Apply pull secret object
   208  	pullSecretUnstructuredObj, err := unstructuredutil.ToUnstructured(pullSecretObj)
   209  	if err != nil {
   210  		return fmt.Errorf("failed to convert %s/%s/%s to unstructured: %w", pullSecretObj.GetObjectKind(), pullSecretObj.GetNamespace(), pullSecretObj.GetName(), err)
   211  	}
   212  	changeSetEntry, err := mgr.Apply(ctx, pullSecretUnstructuredObj, sap.ApplyOptions{Force: true})
   213  	if err != nil {
   214  		return fmt.Errorf("failed to apply pull secret resources: %w", err)
   215  	}
   216  
   217  	log.Info("pull secret resources applied", "changeset", changeSetEntry)
   218  
   219  	// Add pull secret object to changeSet
   220  	changeSet.Add(*changeSetEntry)
   221  
   222  	// Create SecretManager objects that depend on the newly created pull secret
   223  	secret := v1.Secret{}
   224  	objKey = client.ObjectKey{Namespace: project.Namespace, Name: NameWithProjectPrefix(K8sPltfDockerPullCfg, project.Name)}
   225  	if err := r.Get(ctx, objKey, &secret); err != nil {
   226  		return fmt.Errorf("failed to retrieve v1.Secret: %w", err)
   227  	}
   228  
   229  	secretManagerObjs := GenerateSecretManagerObjects(project, secret)
   230  	var unstructuredSecretManagerObjs []*unstructured.Unstructured
   231  	for _, obj := range secretManagerObjs {
   232  		uobj, err := unstructuredutil.ToUnstructured(obj)
   233  		if err != nil {
   234  			return fmt.Errorf("failed to convert %s/%s/%s to unstructured: %w", obj.GetObjectKind(), obj.GetNamespace(), obj.GetName(), err)
   235  		}
   236  		unstructuredSecretManagerObjs = append(unstructuredSecretManagerObjs, uobj)
   237  	}
   238  
   239  	// Apply Secret Manager unstructured objects
   240  	secretManagerChangeSet, err := mgr.ApplyAll(ctx, unstructuredSecretManagerObjs, sap.ApplyOptions{Force: true})
   241  	if err != nil {
   242  		return fmt.Errorf("failed to apply secretManager resources: %w", err)
   243  	}
   244  	log.Info("secretManager resources applied", "changeset", secretManagerChangeSet)
   245  
   246  	newInventory := inventory.New(inventory.FromSapChangeSet(changeSet))
   247  	newInventory.AddSapObjects(secretManagerChangeSet)
   248  
   249  	if inv := r.Inventories.Get(req.NamespacedName); inv != nil {
   250  		diff, err := inv.Diff(newInventory)
   251  		if err != nil {
   252  			return err
   253  		}
   254  
   255  		if len(diff) > 0 {
   256  			deleted, err := mgr.DeleteAll(ctx, diff, sap.DefaultDeleteOptions())
   257  			if err != nil {
   258  				return fmt.Errorf("failed to prune resources: %w", err)
   259  			}
   260  			log.Info("pruned", "changeset", deleted)
   261  		}
   262  	}
   263  
   264  	log.Info("updating inventory")
   265  	return r.Inventories.Set(ctx, req.NamespacedName, newInventory)
   266  }
   267  
   268  func CreateDefaultNetwork(project resourcemgr.Project) *compute.ComputeNetwork {
   269  	shouldCreateSubnets := true
   270  	return &compute.ComputeNetwork{
   271  		ObjectMeta: metav1.ObjectMeta{
   272  			Name:      fmt.Sprintf("%s-default", *project.Spec.ResourceID),
   273  			Namespace: project.Namespace,
   274  			Annotations: map[string]string{
   275  				meta.DeletionPolicyAnnotation: meta.DeletionPolicyAbandon,
   276  			},
   277  			OwnerReferences: ownerRef(&project),
   278  		},
   279  		TypeMeta: metav1.TypeMeta{
   280  			Kind:       compute.ComputeNetworkGVK.Kind,
   281  			APIVersion: compute.SchemeGroupVersion.String(),
   282  		},
   283  		Spec: compute.ComputeNetworkSpec{
   284  			RoutingMode:           &defaultNetworkType,
   285  			AutoCreateSubnetworks: &shouldCreateSubnets,
   286  			ResourceID:            &defaultNetworkName,
   287  		},
   288  	}
   289  }
   290  
   291  func GenerateFirewallRules(project resourcemgr.Project, cfg Firewall) []client.Object {
   292  	projectID := *project.Spec.ResourceID
   293  	disabled := true
   294  	defaultAllowSSH := "default-allow-ssh"
   295  	defaultAllowRDP := "default-allow-rdp"
   296  
   297  	return []client.Object{
   298  		// deny all SSH/RDP by default
   299  		computeFirewall(
   300  			objMeta("deny-ssh-rdp", project.Namespace, project),
   301  			compute.ComputeFirewallSpec{
   302  				Deny: []compute.FirewallDeny{
   303  					{Protocol: "tcp", Ports: []string{"22", "3389"}},
   304  				},
   305  				Priority:   &cfg.DenyPriority,
   306  				NetworkRef: NetworkRef(projectID),
   307  			},
   308  		),
   309  		// allow SSH/RDP from Zscaler IPs
   310  		computeFirewall(
   311  			objMeta("allow-zscaler-ssh-rdp", project.Namespace, project),
   312  			compute.ComputeFirewallSpec{
   313  				Allow: []compute.FirewallAllow{
   314  					{Protocol: "tcp", Ports: []string{"22", "3398"}},
   315  				},
   316  				SourceRanges: security.ZscalerIPs(),
   317  				Priority:     &cfg.ZScalerAllowPriority,
   318  				NetworkRef:   NetworkRef(projectID),
   319  			},
   320  		),
   321  		// allow SSH from IAP forwarding
   322  		computeFirewall(
   323  			objMeta("allow-iap-ssh", project.Namespace, project),
   324  			compute.ComputeFirewallSpec{
   325  				Allow: []compute.FirewallAllow{
   326  					{Protocol: "tcp", Ports: []string{"22"}},
   327  				},
   328  				SourceRanges: security.GoogleIAPIPs(),
   329  				Priority:     &cfg.ZScalerAllowPriority,
   330  				NetworkRef:   NetworkRef(projectID),
   331  			},
   332  		),
   333  		// allow proxy port from IAP forwarding to k8s control plane
   334  		computeFirewall(
   335  			objMeta("allow-iap-k8s-proxy", project.Namespace, project),
   336  			compute.ComputeFirewallSpec{
   337  				Allow: []compute.FirewallAllow{
   338  					{Protocol: "tcp", Ports: []string{"30443"}},
   339  				},
   340  				SourceRanges: security.GoogleIAPIPs(),
   341  				Priority:     &cfg.ZScalerAllowPriority,
   342  				NetworkRef:   NetworkRef(projectID),
   343  			},
   344  		),
   345  		// disable default SSH/RDP rules, created for all GCP projects by default
   346  		computeFirewall(
   347  			objMeta(defaultAllowSSH, project.Namespace, project),
   348  			compute.ComputeFirewallSpec{
   349  				Disabled:     &disabled,
   350  				NetworkRef:   NetworkRef(projectID),
   351  				Allow:        []compute.FirewallAllow{{Ports: []string{"22"}, Protocol: "tcp"}},
   352  				SourceRanges: []string{"0.0.0.0/0"},
   353  				ResourceID:   &defaultAllowSSH,
   354  			},
   355  		),
   356  		computeFirewall(
   357  			objMeta(defaultAllowRDP, project.Namespace, project),
   358  			compute.ComputeFirewallSpec{
   359  				Disabled:     &disabled,
   360  				NetworkRef:   NetworkRef(projectID),
   361  				Allow:        []compute.FirewallAllow{{Ports: []string{"3389"}, Protocol: "tcp"}},
   362  				SourceRanges: []string{"0.0.0.0/0"},
   363  				ResourceID:   &defaultAllowRDP,
   364  			},
   365  		),
   366  	}
   367  }
   368  
   369  func GenerateSSLPolicies(project resourcemgr.Project) []client.Object {
   370  	profile := "MODERN"
   371  	return []client.Object{
   372  		computeSSLPolicy(objMeta(security.DefaultSSLPolicyName, project.Namespace, project),
   373  			compute.ComputeSSLPolicySpec{
   374  				MinTlsVersion: &security.MinTLSVersion,
   375  				Profile:       &profile,
   376  				ResourceID:    &security.DefaultSSLPolicyName,
   377  			}),
   378  	}
   379  }
   380  
   381  func GenerateIAMServiceAccount(project resourcemgr.Project) []client.Object {
   382  	displayName := NameWithProjectPrefix(DockerPullSA, project.Name)
   383  	return []client.Object{&iam.IAMServiceAccount{
   384  		TypeMeta: metav1.TypeMeta{
   385  			Kind:       iam.IAMServiceAccountGVK.Kind,
   386  			APIVersion: iam.SchemeGroupVersion.String(),
   387  		},
   388  		// Not using objMeta here since it returns a relatively long name. We don't want that here since
   389  		// the SA's resourceID will default to ObjectMeta.Name, and we need to stay between 6-30 chars.
   390  		//
   391  		// See https://cloud.google.com/iam/docs/service-accounts-create#creating
   392  		ObjectMeta: metav1.ObjectMeta{
   393  			Name:            DockerPullSA,
   394  			Namespace:       project.Namespace,
   395  			Annotations:     map[string]string{meta.ProjectAnnotation: *project.Spec.ResourceID},
   396  			OwnerReferences: ownerRef(&project),
   397  		},
   398  		Spec: iam.IAMServiceAccountSpec{
   399  			DisplayName: &displayName,
   400  		},
   401  	}}
   402  }
   403  
   404  func GenerateArtifactRegistryPermissions(project resourcemgr.Project, member string, cfgs []ArtifactRegistry) []client.Object {
   405  	var policyMembers []client.Object
   406  	for _, cfg := range cfgs {
   407  		var policyName string
   408  		if member == iamutil.ComputeEngineSvcAccountMember(*project.Status.Number) {
   409  			policyName = cfg.ArtifactRegistryBindingNameCompute()
   410  		} else {
   411  			policyName = cfg.ArtifactRegistryBindingName()
   412  		}
   413  
   414  		policyMember := &iam.IAMPolicyMember{
   415  			TypeMeta: metav1.TypeMeta{
   416  				Kind:       iam.IAMPolicyMemberGVK.Kind,
   417  				APIVersion: iam.SchemeGroupVersion.String(),
   418  			},
   419  			// dont use objMeta here because technically the project annotation is
   420  			// not valid for policy member objects
   421  			ObjectMeta: metav1.ObjectMeta{
   422  				Name:            policyName,
   423  				Namespace:       project.Namespace,
   424  				OwnerReferences: ownerRef(&project),
   425  			},
   426  			Spec: iam.IAMPolicyMemberSpec{
   427  				Member: &member,
   428  				ResourceRef: kcc.IAMResourceRef{
   429  					APIVersion: ar.SchemeGroupVersion.String(),
   430  					Kind:       ar.ArtifactRegistryRepositoryGVK.Kind,
   431  					External:   cfg.ExternalRef(),
   432  				},
   433  				Role: roles.ArtifactoryReader,
   434  			},
   435  		}
   436  		policyMembers = append(policyMembers, policyMember)
   437  	}
   438  
   439  	return policyMembers
   440  }
   441  
   442  func GenerateIAMServiceAccountKey(project resourcemgr.Project) []client.Object {
   443  	svcAcc := iamutil.SvcAccountEmail(DockerPullSA, *project.Spec.ResourceID)
   444  	return []client.Object{
   445  		&iam.IAMServiceAccountKey{
   446  			TypeMeta: metav1.TypeMeta{
   447  				Kind:       iam.IAMServiceAccountKeyGVK.Kind,
   448  				APIVersion: iam.SchemeGroupVersion.String(),
   449  			},
   450  			ObjectMeta: objMeta(PltfDockerPullCfgSAKey, project.Namespace, project),
   451  			Spec: iam.IAMServiceAccountKeySpec{
   452  				ServiceAccountRef: kcc.ResourceRef{
   453  					External: svcAcc,
   454  				},
   455  			},
   456  		},
   457  	}
   458  }
   459  
   460  func GenerateDefaultRouter(project resourcemgr.Project, gcpregion string) []client.Object {
   461  	projectID := *project.Spec.ResourceID
   462  
   463  	return []client.Object{
   464  		&compute.ComputeRouter{
   465  			ObjectMeta: metav1.ObjectMeta{
   466  				Name:      DefaultRouterName,
   467  				Namespace: project.Namespace,
   468  				Annotations: map[string]string{
   469  					meta.DeletionPolicyAnnotation: meta.DeletionPolicyAbandon,
   470  					meta.ProjectAnnotation:        projectID,
   471  				},
   472  				OwnerReferences: ownerRef(&project),
   473  			},
   474  			TypeMeta: metav1.TypeMeta{
   475  				Kind:       compute.ComputeRouterGVK.Kind,
   476  				APIVersion: compute.SchemeGroupVersion.String(),
   477  			},
   478  			Spec: compute.ComputeRouterSpec{
   479  				NetworkRef: NetworkRef(projectID),
   480  				Region:     gcpregion,
   481  			},
   482  		},
   483  	}
   484  }
   485  
   486  func GenerateDefaultNATGateway(project resourcemgr.Project, gcpregion string) []client.Object {
   487  	projectID := *project.Spec.ResourceID
   488  
   489  	return []client.Object{
   490  		&compute.ComputeRouterNAT{
   491  			ObjectMeta: metav1.ObjectMeta{
   492  				Name:      DefaultNATGatewayName,
   493  				Namespace: project.Namespace,
   494  				Annotations: map[string]string{
   495  					meta.DeletionPolicyAnnotation: meta.DeletionPolicyAbandon,
   496  					meta.ProjectAnnotation:        projectID,
   497  				},
   498  				OwnerReferences: ownerRef(&project),
   499  			},
   500  			TypeMeta: metav1.TypeMeta{
   501  				Kind:       compute.ComputeRouterNATGVK.Kind,
   502  				APIVersion: compute.SchemeGroupVersion.String(),
   503  			},
   504  			Spec: compute.ComputeRouterNATSpec{
   505  				NatIpAllocateOption: "AUTO_ONLY",
   506  				Region:              gcpregion,
   507  				RouterRef: v1alpha1.ResourceRef{
   508  					Name:      DefaultRouterName,
   509  					Namespace: project.Namespace,
   510  				},
   511  				SourceSubnetworkIpRangesToNat: "ALL_SUBNETWORKS_ALL_IP_RANGES",
   512  			},
   513  		},
   514  	}
   515  }
   516  
   517  func GenerateDockerPullSecret(project resourcemgr.Project, svcAcc iam.IAMServiceAccountKey, cfgs []ArtifactRegistry) (client.Object, error) {
   518  	svcAccKey := svcAcc.Status.PrivateKey
   519  	jsonKey := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("_json_key_base64:%s", *svcAccKey)))
   520  
   521  	authMap := make(map[string]interface{})
   522  	authMap["auth"] = jsonKey
   523  
   524  	secrets := make(map[string]interface{})
   525  	for _, cfg := range cfgs {
   526  		registry := fmt.Sprintf("%s-docker.pkg.dev", cfg.Location)
   527  		if _, exists := secrets[registry]; exists {
   528  			continue
   529  		}
   530  		secrets[registry] = authMap
   531  	}
   532  
   533  	auths := make(map[string]interface{})
   534  	auths["auths"] = secrets
   535  	secretJSON, err := json.Marshal(auths)
   536  	if err != nil {
   537  		return nil, fmt.Errorf("failed to marshal secret into JSON: %w", err)
   538  	}
   539  
   540  	return &v1.Secret{
   541  		TypeMeta: metav1.TypeMeta{
   542  			Kind:       "Secret",
   543  			APIVersion: v1.SchemeGroupVersion.String(),
   544  		},
   545  		ObjectMeta: objMeta(K8sPltfDockerPullCfg, project.Namespace, project),
   546  		Data: map[string][]byte{
   547  			".dockerconfigjson": secretJSON,
   548  		},
   549  		Type: "kubernetes.io/dockerconfigjson",
   550  	}, nil
   551  }
   552  
   553  func GenerateSecretManagerObjects(project resourcemgr.Project, secret v1.Secret) []client.Object {
   554  	automatic := true
   555  	secretData := secret.Data[".dockerconfigjson"]
   556  	data := string(secretData)
   557  	resource := PltfDockerPullCfg
   558  
   559  	return []client.Object{
   560  		&secretmgr.SecretManagerSecret{
   561  			TypeMeta: metav1.TypeMeta{
   562  				Kind:       secretmgr.SecretManagerSecretGVK.Kind,
   563  				APIVersion: secretmgr.SchemeGroupVersion.String(),
   564  			},
   565  			ObjectMeta: objMeta(PltfDockerPullCfg, project.Namespace, project),
   566  			Spec: secretmgr.SecretManagerSecretSpec{
   567  				Replication: secretmgr.SecretReplication{
   568  					Automatic: &automatic,
   569  				},
   570  				ResourceID: &resource,
   571  			},
   572  		},
   573  		&secretmgr.SecretManagerSecretVersion{
   574  			TypeMeta: metav1.TypeMeta{
   575  				Kind:       secretmgr.SecretManagerSecretVersionGVK.Kind,
   576  				APIVersion: secretmgr.SchemeGroupVersion.String(),
   577  			},
   578  			ObjectMeta: objMeta(PltfDockerPullCfg, project.Namespace, project),
   579  			Spec: secretmgr.SecretManagerSecretVersionSpec{
   580  				SecretRef: kcc.ResourceRef{
   581  					Name: NameWithProjectPrefix(PltfDockerPullCfg, project.Name),
   582  				},
   583  				SecretData: secretmgr.SecretversionSecretData{
   584  					// TODO: Replace Value with ValueFrom once Key field is available in ResourceRef
   585  					Value: &data,
   586  				},
   587  			},
   588  		},
   589  	}
   590  }
   591  
   592  func ownerRef(p *resourcemgr.Project) []metav1.OwnerReference {
   593  	return []metav1.OwnerReference{
   594  		*metav1.NewControllerRef(
   595  			p,
   596  			resourcemgr.ProjectGVK,
   597  		),
   598  	}
   599  }
   600  

View as plain text