package externalsecrets import ( "errors" "fmt" "time" goext "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/kustomize/kyaml/kio/kioutil" "edge-infra.dev/pkg/edge/constants" workloads "edge-infra.dev/pkg/edge/constants/api/workload" ) const ( SecretName = "gcp-creds" //nolint: gosec SecretNamespace = "external-secrets" SecretKey = "key.json" gcpProvider = "gcp-provider" clusterSecretStore = "ClusterSecretStore" DockerPullSecretType = "docker-registry" ) var ( defaultInterval = time.Minute ErrNoName = errors.New("external secret name must be set") ErrNoNamespace = errors.New("external secret namespace or namespace selector must be set") ErrNameSpaceConflict = errors.New("cannot set both namesapce and namesapce selector for an external secret") ErrNoPath = errors.New("external secret path must be set") ErrNoProjectID = errors.New("external secret projectID must be set") ) // ExternalSecret type ExternalSecret struct { name string namespace string secretStoreKind string secretStoreName string path string k8sSecretName string projectID string template *goext.ExternalSecretTemplate dataFieldMap map[string]map[string]string dataMap map[string]string refreshInterval time.Duration creationPolicy goext.ExternalSecretCreationPolicy labels map[string]string } func DefaultExternalSecret() *ExternalSecret { return &ExternalSecret{ dataMap: map[string]string{}, dataFieldMap: map[string]map[string]string{}, secretStoreKind: clusterSecretStore, secretStoreName: gcpProvider, refreshInterval: defaultInterval, creationPolicy: goext.CreatePolicyOwner, } } // DockerConfig the secretKey would be the secret manager's key without a . // Where field is dockerconfigjson // secretRef is dockerconfigjson func (p *ExternalSecret) DockerConfig(secretName, field string) *ExternalSecret { templateString := fmt.Sprintf("{{ .%s }}", field) p.template = &goext.ExternalSecretTemplate{ EngineVersion: goext.TemplateEngineV2, Type: corev1.SecretTypeDockerConfigJson, Data: map[string]string{".dockerconfigjson": templateString}, } return p.MapSecretFieldToK8sSecretKey(secretName, field, field) } func (p *ExternalSecret) SetSecretType(secretType corev1.SecretType) *ExternalSecret { p.template = &goext.ExternalSecretTemplate{ Type: secretType, } return p } func (p *ExternalSecret) Namespace(ns string) *ExternalSecret { p.namespace = ns return p } func (p *ExternalSecret) Name(name string) *ExternalSecret { p.name = name return p } func (p *ExternalSecret) Path(path string) *ExternalSecret { p.path = path return p } func (p *ExternalSecret) K8sSecretName(k8sSecretName string) *ExternalSecret { p.k8sSecretName = k8sSecretName return p } func (p *ExternalSecret) ProjectID(projectID string) *ExternalSecret { p.projectID = projectID return p } func (p *ExternalSecret) Labels(labels map[string]string) *ExternalSecret { if labels == nil { p.labels = map[string]string{} return p } p.labels = labels return p } func (p *ExternalSecret) MapSecretFieldToK8sSecretKey(secretManagerSecretName, secretManagerField, k8sSecretKey string) *ExternalSecret { if secretManagerSecret, ok := p.dataFieldMap[secretManagerSecretName]; ok { secretManagerSecret[secretManagerField] = k8sSecretKey } else { p.dataFieldMap[secretManagerSecretName] = map[string]string{secretManagerField: k8sSecretKey} } return p } // MapSecretToK8sSecretKey is used to map the entire secret manager secret to the this field in the k8s secret func (p *ExternalSecret) MapSecretToK8sSecretKey(secretManagerSecretName, k8sSecretKey string) *ExternalSecret { p.dataMap[secretManagerSecretName] = k8sSecretKey return p } func (p *ExternalSecret) Validate() error { if p.name == "" { return ErrNoName } if p.k8sSecretName == "" { p.k8sSecretName = p.name } if p.projectID == "" { return ErrNoProjectID } return nil } func (p *ExternalSecret) BuildClusterExternalSecret() (*goext.ClusterExternalSecret, error) { err := p.Validate() if err != nil { return nil, err } matchLabels := []string{"tenant"} es := &goext.ClusterExternalSecret{ TypeMeta: metav1.TypeMeta{ APIVersion: goext.ExtSecretGroupVersionKind.GroupVersion().String(), Kind: goext.ClusterExtSecretGroupVersionKind.Kind, }, ObjectMeta: metav1.ObjectMeta{ Name: p.name, Labels: mergeStringMaps(p.labels, map[string]string{ constants.Tenant: p.projectID, }), Annotations: map[string]string{ // e,g,: manifests/namespaces/acme.yaml kioutil.PathAnnotation: p.path, }, }, Spec: goext.ClusterExternalSecretSpec{ ExternalSecretName: p.k8sSecretName, ExternalSecretSpec: goext.ExternalSecretSpec{ SecretStoreRef: goext.SecretStoreRef{ Name: p.secretStoreName, Kind: p.secretStoreKind, }, Target: goext.ExternalSecretTarget{ Name: p.k8sSecretName, CreationPolicy: p.creationPolicy, }, RefreshInterval: &metav1.Duration{ Duration: p.refreshInterval, }, }, NamespaceSelectors: []*metav1.LabelSelector{ { MatchExpressions: []metav1.LabelSelectorRequirement{ {Key: workloads.Label, Operator: metav1.LabelSelectorOpIn, Values: matchLabels}, }, }, }, }, } var externalSecretData []goext.ExternalSecretData for secretManagerSecretName, k8sSecretKey := range p.dataMap { externalSecretData = append(externalSecretData, goext.ExternalSecretData{RemoteRef: goext.ExternalSecretDataRemoteRef{Key: secretManagerSecretName}, SecretKey: k8sSecretKey}) } for secretManagerSecretName, secretManagerFieldToKeyMap := range p.dataFieldMap { for secretManagerSecretField, k8sSecretKey := range secretManagerFieldToKeyMap { externalSecretData = append(externalSecretData, goext.ExternalSecretData{RemoteRef: goext.ExternalSecretDataRemoteRef{Key: secretManagerSecretName, Property: secretManagerSecretField}, SecretKey: k8sSecretKey}) } } es.Spec.ExternalSecretSpec.Data = externalSecretData es.Spec.ExternalSecretSpec.Target.Template = p.template if p.namespace != "" { es.ObjectMeta.Namespace = p.namespace } return es, nil } func (p *ExternalSecret) Build() (*goext.ExternalSecret, error) { err := p.Validate() if err != nil { return nil, err } es := &goext.ExternalSecret{ TypeMeta: metav1.TypeMeta{ APIVersion: goext.ExtSecretGroupVersionKind.GroupVersion().String(), Kind: goext.ExtSecretGroupVersionKind.Kind, }, ObjectMeta: metav1.ObjectMeta{ Name: p.name, Labels: mergeStringMaps(p.labels, map[string]string{ constants.Tenant: p.projectID, }), Annotations: map[string]string{ // e,g,: manifests/namespaces/acme.yaml kioutil.PathAnnotation: p.path, }, }, Spec: goext.ExternalSecretSpec{ SecretStoreRef: goext.SecretStoreRef{ Name: p.secretStoreName, Kind: p.secretStoreKind, }, Target: goext.ExternalSecretTarget{ Name: p.k8sSecretName, CreationPolicy: p.creationPolicy, }, RefreshInterval: &metav1.Duration{ Duration: p.refreshInterval, }, }, } var externalSecretData []goext.ExternalSecretData for secretManagerSecretName, k8sSecretKey := range p.dataMap { externalSecretData = append(externalSecretData, goext.ExternalSecretData{RemoteRef: goext.ExternalSecretDataRemoteRef{Key: secretManagerSecretName}, SecretKey: k8sSecretKey}) } for secretManagerSecretName, secretManagerFieldToKeyMap := range p.dataFieldMap { for secretManagerSecretField, k8sSecretKey := range secretManagerFieldToKeyMap { externalSecretData = append(externalSecretData, goext.ExternalSecretData{RemoteRef: goext.ExternalSecretDataRemoteRef{Key: secretManagerSecretName, Property: secretManagerSecretField}, SecretKey: k8sSecretKey}) } } es.Spec.Data = externalSecretData es.Spec.Target.Template = p.template if p.namespace != "" { es.ObjectMeta.Namespace = p.namespace } return es, nil } // Merges mapOne into mapTwo and returns mapTwo func mergeStringMaps(mapOne, mapTwo map[string]string) map[string]string { for k, v := range mapOne { mapTwo[k] = v } return mapTwo }