...

Source file src/edge-infra.dev/pkg/edge/api/services/secret_service.go

Documentation: edge-infra.dev/pkg/edge/api/services

     1  package services
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	goerrors "errors"
     7  	"fmt"
     8  
     9  	iamAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/iam/v1beta1"
    10  	"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
    11  	secretmanagerAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/secretmanager/v1beta1"
    12  	"github.com/rs/zerolog/log"
    13  	corev1 "k8s.io/api/core/v1"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"sigs.k8s.io/controller-runtime/pkg/client"
    16  
    17  	"edge-infra.dev/pkg/edge/api/clients"
    18  	"edge-infra.dev/pkg/edge/api/graph/mapper"
    19  	"edge-infra.dev/pkg/edge/api/graph/model"
    20  	"edge-infra.dev/pkg/edge/api/types"
    21  	chariotClientApi "edge-infra.dev/pkg/edge/chariot/client"
    22  	extScrt "edge-infra.dev/pkg/edge/externalsecrets"
    23  	"edge-infra.dev/pkg/lib/gcp/iam/roles"
    24  	"edge-infra.dev/pkg/lib/uuid"
    25  )
    26  
    27  //go:generate mockgen -destination=../mocks/mock_secret_service.go -package=mocks edge-infra.dev/pkg/edge/api/services SecretService
    28  type SecretService interface {
    29  	CreateSecret(ctx context.Context, cluster *model.Cluster, name, namespace string, description *string, values []*model.KeyValues) (bool, error)
    30  	GetSecrets(ctx context.Context, cluster *model.Cluster, namespace string, name *string) (*corev1.SecretList, error)
    31  	DeleteSecret(ctx context.Context, cluster *model.Cluster, name, namespace string) (bool, error)
    32  	GetClusterDefaultSecret(ctx context.Context, cluster *types.GkeCluster) (*corev1.Secret, error)
    33  	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
    34  	DeleteExternalSecret(ctx context.Context, name string, namespace string, projectID string, cluster *model.Cluster, clusterInfra *model.Cluster, k8sSecretName string) error
    35  }
    36  
    37  type secretService struct {
    38  	GkeService     GkeClient
    39  	ChariotService ChariotService
    40  	GCPService     GCPService
    41  	BQClient       clients.BQClient
    42  }
    43  
    44  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 {
    45  	m := make(map[string]string)
    46  	for _, v := range values {
    47  		m[v.Key] = v.Key
    48  	}
    49  
    50  	es := extScrt.DefaultExternalSecret().
    51  		Name(name).
    52  		K8sSecretName(k8sSecretName).
    53  		ProjectID(projectID)
    54  
    55  	if secretType != "" {
    56  		es.SetSecretType(secretType)
    57  	}
    58  
    59  	// if its namespace-scoped, create external secret, otherwise create cluster external secret
    60  	isClusterExternalSecret := namespace == ""
    61  	if !isClusterExternalSecret {
    62  		es = es.Namespace(namespace)
    63  	}
    64  
    65  	for k8sSecretKey, secretManagerProp := range m {
    66  		es = es.MapSecretFieldToK8sSecretKey(name, k8sSecretKey, secretManagerProp)
    67  	}
    68  
    69  	externalSecret, err := buildSecretMessage(es, isClusterExternalSecret)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	err = scrt.sendChariotMessage(ctx, projectID, cluster, chariotClientApi.Create, externalSecret)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	externalSecretPermission := createExternalSecretPermissions(projectID, name, cluster.ClusterEdgeID)
    80  	err = scrt.sendChariotMessage(ctx, projectID, clusterInfra, chariotClientApi.Create, externalSecretPermission)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  func (scrt *secretService) DeleteExternalSecret(ctx context.Context, name string, namespace string, projectID string, cluster *model.Cluster, clusterInfra *model.Cluster, k8sSecretName string) error {
    89  	es := extScrt.DefaultExternalSecret().
    90  		Name(name).
    91  		K8sSecretName(k8sSecretName).
    92  		ProjectID(projectID)
    93  
    94  	isClusterExternalSecret := namespace == ""
    95  	if !isClusterExternalSecret {
    96  		es = es.Namespace(namespace)
    97  	}
    98  
    99  	externalSecret, err := buildSecretMessage(es, isClusterExternalSecret)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	err = scrt.sendChariotMessage(ctx, projectID, cluster, chariotClientApi.Delete, externalSecret)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	if !isClusterExternalSecret {
   110  		externalSecretPermission := createExternalSecretPermissions(projectID, name, cluster.ClusterEdgeID)
   111  		err = scrt.sendChariotMessage(ctx, projectID, clusterInfra, chariotClientApi.Delete, externalSecretPermission)
   112  		if err != nil {
   113  			return err
   114  		}
   115  	}
   116  	return nil
   117  }
   118  
   119  func (scrt *secretService) CreateSecret(ctx context.Context, cluster *model.Cluster, name, namespace string, _ *string, values []*model.KeyValues) (bool, error) {
   120  	var objects []client.Object
   121  	namespace = mapper.ConvertK8sName(namespace)
   122  	secret, err := mapper.ToCreateSecretObject(name, namespace, values)
   123  	if err != nil {
   124  		return false, err
   125  	}
   126  	secret.APIVersion = "v1"
   127  	secret.Type = "Opaque"
   128  	secret.Kind = "Secret"
   129  
   130  	objects = append(objects, secret)
   131  
   132  	err = scrt.sendChariotMessage(ctx, cluster.ProjectID, cluster, chariotClientApi.Create, objects...)
   133  	if err != nil {
   134  		return false, err
   135  	}
   136  
   137  	return true, nil
   138  }
   139  
   140  func (scrt *secretService) GetSecrets(ctx context.Context, cluster *model.Cluster, namespace string, name *string) (*corev1.SecretList, error) {
   141  	res, err := scrt.BQClient.GetKubeResource(ctx, cluster.ProjectID, cluster, mapper.GetSecrets(name, &namespace))
   142  
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	var list []corev1.Secret
   147  
   148  	for i := range res {
   149  		secret := &corev1.Secret{}
   150  		err = json.Unmarshal([]byte(res[i]), secret)
   151  		if err != nil {
   152  			log.Ctx(ctx).Err(err).Msg("Unable to unmarshal secret release resource")
   153  		}
   154  		list = append(list, *secret)
   155  	}
   156  
   157  	if len(list) > 0 {
   158  		return &corev1.SecretList{Items: list}, nil
   159  	}
   160  	return nil, goerrors.New("error fetching Secret")
   161  }
   162  
   163  func (scrt *secretService) DeleteSecret(ctx context.Context, cluster *model.Cluster, name, namespace string) (bool, error) {
   164  	var object []client.Object
   165  	namespace = mapper.ConvertK8sName(namespace)
   166  
   167  	var emptyvals []*model.KeyValues
   168  	secret, err := mapper.ToCreateSecretObject(name, namespace, emptyvals)
   169  
   170  	if err != nil {
   171  		return false, err
   172  	}
   173  
   174  	secret.APIVersion = "v1"
   175  	secret.Type = "Opaque"
   176  	secret.Kind = "Secret"
   177  	object = append(object, secret)
   178  
   179  	err = scrt.sendChariotMessage(ctx, cluster.ProjectID, cluster, chariotClientApi.Delete, object...)
   180  	if err != nil {
   181  		return false, err
   182  	}
   183  
   184  	return true, nil
   185  }
   186  
   187  func (scrt *secretService) GetClusterDefaultSecret(ctx context.Context, cluster *types.GkeCluster) (*corev1.Secret, error) {
   188  	cl, err := scrt.GkeService.GetRuntimeClient(ctx, cluster)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	sa := &corev1.ServiceAccount{}
   194  	if err := cl.Get(ctx, client.ObjectKey{Namespace: "default", Name: "default"}, sa); err != nil {
   195  		log.Ctx(ctx).Err(err).Msg("error in retrieving service account")
   196  		return nil, err
   197  	}
   198  
   199  	secret := &corev1.Secret{}
   200  	if err := cl.Get(ctx, client.ObjectKey{Namespace: "default", Name: sa.Secrets[0].Name}, secret); err != nil {
   201  		log.Ctx(ctx).Err(err).Msg("error in retrieving secret")
   202  		return nil, err
   203  	}
   204  
   205  	return secret, nil
   206  }
   207  
   208  func (scrt *secretService) sendChariotMessage(ctx context.Context, projectID string, cluster *model.Cluster, operation chariotClientApi.Operation, object ...client.Object) error {
   209  	chariotMessage, err := chariotClientApi.
   210  		NewChariotMessage().
   211  		SetBanner(projectID).
   212  		SetOperation(operation).
   213  		SetOwner(ComponentOwner).
   214  		AddK8sBase64Object(object...)
   215  
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	if err := scrt.ChariotService.CheckClusterAndInvokeChariotPubsub(ctx, cluster, chariotMessage, nil); err != nil {
   221  		return fmt.Errorf("error calling chariot v2: %w", err)
   222  	}
   223  	return nil
   224  }
   225  
   226  func buildClusterExternalSecret(es *extScrt.ExternalSecret) (client.Object, error) {
   227  	externalSecret, err := es.BuildClusterExternalSecret()
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	return externalSecret, nil
   232  }
   233  
   234  func buildExternalSecret(es *extScrt.ExternalSecret) (client.Object, error) {
   235  	externalSecret, err := es.Build()
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	return externalSecret, nil
   240  }
   241  
   242  func buildSecretMessage(es *extScrt.ExternalSecret, isClusterExternalSecret bool) (client.Object, error) {
   243  	if isClusterExternalSecret {
   244  		return buildClusterExternalSecret(es)
   245  	}
   246  	return buildExternalSecret(es)
   247  }
   248  
   249  func createExternalSecretPermissions(projectID, name, namespace string) *iamAPI.IAMPolicyMember {
   250  	member := fmt.Sprintf("serviceAccount:ext-sec-%s@%s.iam.gserviceaccount.com", uuid.FromUUID(namespace).Hash(), projectID)
   251  	return &iamAPI.IAMPolicyMember{
   252  		ObjectMeta: metav1.ObjectMeta{
   253  			Name:      fmt.Sprintf("essa-%s", name),
   254  			Namespace: namespace,
   255  		},
   256  		TypeMeta: metav1.TypeMeta{
   257  			APIVersion: iamAPI.IAMPolicyMemberGVK.GroupVersion().String(),
   258  			Kind:       iamAPI.IAMPolicyMemberGVK.Kind,
   259  		},
   260  		Spec: iamAPI.IAMPolicyMemberSpec{
   261  			Member: &member,
   262  			ResourceRef: v1alpha1.IAMResourceRef{
   263  				APIVersion: secretmanagerAPI.SecretManagerSecretGVK.GroupVersion().String(),
   264  				Kind:       secretmanagerAPI.SecretManagerSecretGVK.Kind,
   265  				External:   fmt.Sprintf("projects/%s/secrets/%s", projectID, name),
   266  			},
   267  			Role: roles.SecretAccessor,
   268  		},
   269  	}
   270  }
   271  
   272  func NewSecretService(gkeService GkeClient, chariotService ChariotService, gcpService GCPService, bqClient clients.BQClient) *secretService { //nolint stupid
   273  	return &secretService{
   274  		GkeService:     gkeService,
   275  		ChariotService: chariotService,
   276  		GCPService:     gcpService,
   277  		BQClient:       bqClient,
   278  	}
   279  }
   280  

View as plain text