package services import ( "context" "encoding/json" "strings" "time" secretmanagerpb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" _ "github.com/doug-martin/goqu/v9/dialect/mysql" //needed for generating the correctly formatted sql statement gcperror "edge-infra.dev/pkg/edge/api/apierror/gcp" "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" "edge-infra.dev/pkg/edge/api/utils" workloadApi "edge-infra.dev/pkg/edge/constants/api/workload" secretMgrApi "edge-infra.dev/pkg/lib/gcp/secretmanager" gcputils "edge-infra.dev/pkg/lib/gcp/utils" ) const PlatformSecret = "platform" const EdgeOwner = "edge" //go:generate mockgen -destination=../mocks/mock_gcp_service.go -package=mocks edge-infra.dev/pkg/edge/api/services GCPService type GCPService interface { GetZones(ctx context.Context) ([]*string, error) GetGKEVersions(ctx context.Context, zone string) ([]*string, error) GetMachineTypes(ctx context.Context, zone string) ([]*model.MachineTypeInfo, error) AddSecret(ctx context.Context, name, owner, t string, secrets []*model.KeyValues, projectID string, workload *string, expireAt *time.Time) error GetMachineType(ctx context.Context, zone string, machineType string) (*model.MachineTypeInfo, error) GetSecrets(ctx context.Context, name *string, owner, t *string, getValues bool, projectID string) ([]*model.SecretManagerResponse, error) DeleteSecret(ctx context.Context, name string, projectID string) (bool, error) } type gcpService struct { GcpClientService GcpClientService TopLevelProjectID string BigQueryClient clients.BQClient } func (s *gcpService) GetZones(ctx context.Context) ([]*string, error) { computeClient, err := s.GcpClientService.GetComputeClient(ctx, s.TopLevelProjectID) if err != nil { return nil, gcperror.Wrap(err) } zoneList, err := computeClient.GetZones(ctx) if err != nil { return nil, gcperror.Wrap(err) } return mapper.ToZones(zoneList), nil } func (s *gcpService) GetGKEVersions(ctx context.Context, zone string) ([]*string, error) { containerClient, err := s.GcpClientService.GetContainerClient(ctx, s.TopLevelProjectID) if err != nil { return nil, gcperror.Wrap(err) } serverConfig, err := containerClient.GetServerConfig(ctx, zone) if err != nil { return nil, gcperror.Wrap(err) } return mapper.ToGkeVersions(serverConfig.ValidMasterVersions), nil } func (s *gcpService) GetMachineTypes(ctx context.Context, zone string) ([]*model.MachineTypeInfo, error) { computeClient, err := s.GcpClientService.GetComputeClient(ctx, s.TopLevelProjectID) if err != nil { return nil, gcperror.Wrap(err) } machineList, err := computeClient.GetMachineTypes(ctx, zone) if err != nil { return nil, gcperror.Wrap(err) } return mapper.ToMachineTypes(machineList), nil } func (s *gcpService) GetMachineType(ctx context.Context, zone string, machineType string) (*model.MachineTypeInfo, error) { computeClient, err := s.GcpClientService.GetComputeClient(ctx, s.TopLevelProjectID) if err != nil { return nil, gcperror.Wrap(err) } machineTypeInfo, err := computeClient.GetMachineType(ctx, zone, machineType) if err != nil { return nil, gcperror.Wrap(err) } return mapper.ToMachineType(machineTypeInfo), nil } func (s *gcpService) AddSecret(ctx context.Context, name, owner, t string, secrets []*model.KeyValues, projectID string, workload *string, expireAt *time.Time) error { secretService, err := s.GcpClientService.GetSecretClient(ctx, projectID) if err != nil { return gcperror.Wrap(err) } defer gcputils.CloseConnection(ctx, secretService) m := make(map[string]string) for _, s := range secrets { m[s.Key] = s.Value } strSecret, err := json.Marshal(m) if err != nil { return err } labels := map[string]string{ secretMgrApi.SecretLabel: string(workloadApi.Tenant), secretMgrApi.SecretTypeLabel: t, secretMgrApi.SecretOwnerLabel: owner, } if !utils.IsNullOrEmpty(workload) { // we need to save this to be able to compare labels labels[secretMgrApi.SecretNamespaceSelectorLabel] = *workload } err = secretService.AddSecret(ctx, name, strSecret, labels, false, expireAt, "") if err != nil { return gcperror.Wrap(err) } return nil } func (s *gcpService) GetSecrets(ctx context.Context, name *string, owner, t *string, getValues bool, projectID string) ([]*model.SecretManagerResponse, error) { secretService, err := s.GcpClientService.GetSecretClient(ctx, projectID) if err != nil { return nil, gcperror.Wrap(err) } defer gcputils.CloseConnection(ctx, secretService) secretsMap, err := getSecrets(ctx, name, owner, t, secretService) if err != nil { return nil, gcperror.Wrap(err) } res, err := mapSecretManagerToModel(ctx, secretsMap, getValues, secretService) if err != nil { return nil, gcperror.Wrap(err) } return res, nil } func getSecrets(ctx context.Context, name, owner, t *string, secretService types.SecretManagerService) ([]*secretmanagerpb.Secret, error) { var secrets []*secretmanagerpb.Secret var err error if name != nil { secret, err := secretService.GetSecret(ctx, *name) if err != nil { return nil, err } secrets = []*secretmanagerpb.Secret{secret} } else { secrets, err = secretService.ListSecrets(ctx, "") if err != nil { return nil, err } } return secretManagerSecretsFiltered(secrets, t, owner), nil } func (s *gcpService) DeleteSecret(ctx context.Context, name string, projectID string) (bool, error) { secretService, err := s.GcpClientService.GetSecretClient(ctx, projectID) if err != nil { return false, gcperror.Wrap(err) } defer gcputils.CloseConnection(ctx, secretService) err = secretService.DeleteSecret(ctx, name) if err != nil { return false, gcperror.Wrap(err) } return true, nil } func mapSecretManagerToModel(ctx context.Context, secrets []*secretmanagerpb.Secret, getValues bool, secretService types.SecretManagerService) ([]*model.SecretManagerResponse, error) { result := []*model.SecretManagerResponse{} for _, secret := range secrets { var updated *string var kv []*model.KeyValuesOutput name := getSecretName(secret.Name) if getValues { secretValue, err := secretService.GetLatestSecretValue(ctx, name) if err != nil { return nil, err } secretInfo, err := secretService.GetLatestSecretValueInfo(ctx, name) if err != nil { return nil, err } var keyMap map[string]string err = json.Unmarshal(secretValue, &keyMap) if err != nil { kv = []*model.KeyValuesOutput{{Key: name, Value: string(secretValue)}} } else { for k, v := range keyMap { kv = append(kv, &model.KeyValuesOutput{Key: k, Value: v}) } } u := secretInfo.CreateTime.AsTime().Format(mapper.TimeFormat) updated = &u } result = append(result, secretManagerToModel(name, secretService.GetProjectID(), secret, updated, kv)) } return result, nil } func getSecretName(val string) string { return val[strings.LastIndex(val, "/")+1:] } func secretManagerSecretsFiltered(secrets []*secretmanagerpb.Secret, filterType, owner *string) []*secretmanagerpb.Secret { var filteredSecrets []*secretmanagerpb.Secret for _, s := range secrets { if filterType != nil { if t, ok := s.Labels[secretMgrApi.SecretTypeLabel]; ok { if *filterType != t { continue } } else { continue } } if owner != nil { if o, ok := s.Labels[secretMgrApi.SecretOwnerLabel]; ok { if *owner != o { continue } } else { continue } } filteredSecrets = append(filteredSecrets, s) } return filteredSecrets } func secretManagerToModel(name string, organization string, secret *secretmanagerpb.Secret, updated *string, kv []*model.KeyValuesOutput) *model.SecretManagerResponse { var typ *string w := PlatformSecret o := EdgeOwner workload := &w owner := &o if t, ok := secret.Labels[secretMgrApi.SecretTypeLabel]; ok { typ = &t } if w, ok := secret.Labels[secretMgrApi.SecretLabel]; ok { workload = &w } if o, ok := secret.Labels[secretMgrApi.SecretOwnerLabel]; ok { owner = &o } created := secret.CreateTime.AsTime().Format(mapper.TimeFormat) return &model.SecretManagerResponse{ Name: name, Project: organization, Created: &created, Updated: updated, Values: kv, Type: typ, Workload: workload, Owner: owner, } } func NewGcpService(gcpClientService GcpClientService, topLevelProjectId string, bqClient clients.BQClient) *gcpService { //nolint stupid return &gcpService{ GcpClientService: gcpClientService, TopLevelProjectID: topLevelProjectId, BigQueryClient: bqClient, } }