package services import ( "context" "encoding/json" goerrors "errors" "fmt" iamAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/iam/v1beta1" "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1" secretmanagerAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/secretmanager/v1beta1" "github.com/rs/zerolog/log" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "edge-infra.dev/pkg/edge/api/clients" "edge-infra.dev/pkg/edge/api/graph/mapper" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/types" chariotClientApi "edge-infra.dev/pkg/edge/chariot/client" extScrt "edge-infra.dev/pkg/edge/externalsecrets" "edge-infra.dev/pkg/lib/gcp/iam/roles" "edge-infra.dev/pkg/lib/uuid" ) //go:generate mockgen -destination=../mocks/mock_secret_service.go -package=mocks edge-infra.dev/pkg/edge/api/services SecretService type SecretService interface { CreateSecret(ctx context.Context, cluster *model.Cluster, name, namespace string, description *string, values []*model.KeyValues) (bool, error) GetSecrets(ctx context.Context, cluster *model.Cluster, namespace string, name *string) (*corev1.SecretList, error) DeleteSecret(ctx context.Context, cluster *model.Cluster, name, namespace string) (bool, error) GetClusterDefaultSecret(ctx context.Context, cluster *types.GkeCluster) (*corev1.Secret, error) CreateExternalSecret(ctx context.Context, projectID string, name string, namespace string, values []*model.KeyValues, cluster *model.Cluster, clusterInfra *model.Cluster, secretType corev1.SecretType, k8sSecretName string) error DeleteExternalSecret(ctx context.Context, name string, namespace string, projectID string, cluster *model.Cluster, clusterInfra *model.Cluster, k8sSecretName string) error } type secretService struct { GkeService GkeClient ChariotService ChariotService GCPService GCPService BQClient clients.BQClient } func (scrt *secretService) CreateExternalSecret(ctx context.Context, projectID string, name string, namespace string, values []*model.KeyValues, cluster *model.Cluster, clusterInfra *model.Cluster, secretType corev1.SecretType, k8sSecretName string) error { m := make(map[string]string) for _, v := range values { m[v.Key] = v.Key } es := extScrt.DefaultExternalSecret(). Name(name). K8sSecretName(k8sSecretName). ProjectID(projectID) if secretType != "" { es.SetSecretType(secretType) } // if its namespace-scoped, create external secret, otherwise create cluster external secret isClusterExternalSecret := namespace == "" if !isClusterExternalSecret { es = es.Namespace(namespace) } for k8sSecretKey, secretManagerProp := range m { es = es.MapSecretFieldToK8sSecretKey(name, k8sSecretKey, secretManagerProp) } externalSecret, err := buildSecretMessage(es, isClusterExternalSecret) if err != nil { return err } err = scrt.sendChariotMessage(ctx, projectID, cluster, chariotClientApi.Create, externalSecret) if err != nil { return err } externalSecretPermission := createExternalSecretPermissions(projectID, name, cluster.ClusterEdgeID) err = scrt.sendChariotMessage(ctx, projectID, clusterInfra, chariotClientApi.Create, externalSecretPermission) if err != nil { return err } return nil } func (scrt *secretService) DeleteExternalSecret(ctx context.Context, name string, namespace string, projectID string, cluster *model.Cluster, clusterInfra *model.Cluster, k8sSecretName string) error { es := extScrt.DefaultExternalSecret(). Name(name). K8sSecretName(k8sSecretName). ProjectID(projectID) isClusterExternalSecret := namespace == "" if !isClusterExternalSecret { es = es.Namespace(namespace) } externalSecret, err := buildSecretMessage(es, isClusterExternalSecret) if err != nil { return err } err = scrt.sendChariotMessage(ctx, projectID, cluster, chariotClientApi.Delete, externalSecret) if err != nil { return err } if !isClusterExternalSecret { externalSecretPermission := createExternalSecretPermissions(projectID, name, cluster.ClusterEdgeID) err = scrt.sendChariotMessage(ctx, projectID, clusterInfra, chariotClientApi.Delete, externalSecretPermission) if err != nil { return err } } return nil } func (scrt *secretService) CreateSecret(ctx context.Context, cluster *model.Cluster, name, namespace string, _ *string, values []*model.KeyValues) (bool, error) { var objects []client.Object namespace = mapper.ConvertK8sName(namespace) secret, err := mapper.ToCreateSecretObject(name, namespace, values) if err != nil { return false, err } secret.APIVersion = "v1" secret.Type = "Opaque" secret.Kind = "Secret" objects = append(objects, secret) err = scrt.sendChariotMessage(ctx, cluster.ProjectID, cluster, chariotClientApi.Create, objects...) if err != nil { return false, err } return true, nil } func (scrt *secretService) GetSecrets(ctx context.Context, cluster *model.Cluster, namespace string, name *string) (*corev1.SecretList, error) { res, err := scrt.BQClient.GetKubeResource(ctx, cluster.ProjectID, cluster, mapper.GetSecrets(name, &namespace)) if err != nil { return nil, err } var list []corev1.Secret for i := range res { secret := &corev1.Secret{} err = json.Unmarshal([]byte(res[i]), secret) if err != nil { log.Ctx(ctx).Err(err).Msg("Unable to unmarshal secret release resource") } list = append(list, *secret) } if len(list) > 0 { return &corev1.SecretList{Items: list}, nil } return nil, goerrors.New("error fetching Secret") } func (scrt *secretService) DeleteSecret(ctx context.Context, cluster *model.Cluster, name, namespace string) (bool, error) { var object []client.Object namespace = mapper.ConvertK8sName(namespace) var emptyvals []*model.KeyValues secret, err := mapper.ToCreateSecretObject(name, namespace, emptyvals) if err != nil { return false, err } secret.APIVersion = "v1" secret.Type = "Opaque" secret.Kind = "Secret" object = append(object, secret) err = scrt.sendChariotMessage(ctx, cluster.ProjectID, cluster, chariotClientApi.Delete, object...) if err != nil { return false, err } return true, nil } func (scrt *secretService) GetClusterDefaultSecret(ctx context.Context, cluster *types.GkeCluster) (*corev1.Secret, error) { cl, err := scrt.GkeService.GetRuntimeClient(ctx, cluster) if err != nil { return nil, err } sa := &corev1.ServiceAccount{} if err := cl.Get(ctx, client.ObjectKey{Namespace: "default", Name: "default"}, sa); err != nil { log.Ctx(ctx).Err(err).Msg("error in retrieving service account") return nil, err } secret := &corev1.Secret{} if err := cl.Get(ctx, client.ObjectKey{Namespace: "default", Name: sa.Secrets[0].Name}, secret); err != nil { log.Ctx(ctx).Err(err).Msg("error in retrieving secret") return nil, err } return secret, nil } func (scrt *secretService) sendChariotMessage(ctx context.Context, projectID string, cluster *model.Cluster, operation chariotClientApi.Operation, object ...client.Object) error { chariotMessage, err := chariotClientApi. NewChariotMessage(). SetBanner(projectID). SetOperation(operation). SetOwner(ComponentOwner). AddK8sBase64Object(object...) if err != nil { return err } if err := scrt.ChariotService.CheckClusterAndInvokeChariotPubsub(ctx, cluster, chariotMessage, nil); err != nil { return fmt.Errorf("error calling chariot v2: %w", err) } return nil } func buildClusterExternalSecret(es *extScrt.ExternalSecret) (client.Object, error) { externalSecret, err := es.BuildClusterExternalSecret() if err != nil { return nil, err } return externalSecret, nil } func buildExternalSecret(es *extScrt.ExternalSecret) (client.Object, error) { externalSecret, err := es.Build() if err != nil { return nil, err } return externalSecret, nil } func buildSecretMessage(es *extScrt.ExternalSecret, isClusterExternalSecret bool) (client.Object, error) { if isClusterExternalSecret { return buildClusterExternalSecret(es) } return buildExternalSecret(es) } func createExternalSecretPermissions(projectID, name, namespace string) *iamAPI.IAMPolicyMember { member := fmt.Sprintf("serviceAccount:ext-sec-%s@%s.iam.gserviceaccount.com", uuid.FromUUID(namespace).Hash(), projectID) return &iamAPI.IAMPolicyMember{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("essa-%s", name), Namespace: namespace, }, TypeMeta: metav1.TypeMeta{ APIVersion: iamAPI.IAMPolicyMemberGVK.GroupVersion().String(), Kind: iamAPI.IAMPolicyMemberGVK.Kind, }, Spec: iamAPI.IAMPolicyMemberSpec{ Member: &member, ResourceRef: v1alpha1.IAMResourceRef{ APIVersion: secretmanagerAPI.SecretManagerSecretGVK.GroupVersion().String(), Kind: secretmanagerAPI.SecretManagerSecretGVK.Kind, External: fmt.Sprintf("projects/%s/secrets/%s", projectID, name), }, Role: roles.SecretAccessor, }, } } func NewSecretService(gkeService GkeClient, chariotService ChariotService, gcpService GCPService, bqClient clients.BQClient) *secretService { //nolint stupid return &secretService{ GkeService: gkeService, ChariotService: chariotService, GCPService: gcpService, BQClient: bqClient, } }