| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- /*
- Copyright © The ESO Authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- // Package beyondtrustworkloadcredentialsdynamic provides a generator for BeyondTrust Workload Credentials dynamic credentials.
- package beyondtrustworkloadcredentialsdynamic
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "strings"
- apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/yaml"
- genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
- beyondtrustworkloadcredentialsprovider "github.com/external-secrets/external-secrets/providers/v1/beyondtrustworkloadcredentials"
- "github.com/external-secrets/external-secrets/providers/v1/beyondtrustworkloadcredentials/httpclient"
- btwcutil "github.com/external-secrets/external-secrets/providers/v1/beyondtrustworkloadcredentials/util"
- )
- // Generator implements BeyondtrustWorkloadCredentials dynamic generator.
- type Generator struct {
- // NewBeyondtrustWorkloadCredentialsClient is a factory function to create a BeyondtrustWorkloadCredentials client.
- // If nil, defaults to httpclient.NewBeyondtrustWorkloadCredentialsClient.
- NewBeyondtrustWorkloadCredentialsClient func(server, token string) (btwcutil.Client, error)
- }
- const (
- errNoSpec = "no config spec provided"
- errParseSpec = "unable to parse spec: %w"
- errMissingConfig = "no beyondtrustworkloadcredentials provider config in spec"
- errNoPath = "path is required in spec"
- errGetSecret = "unable to generate dynamic secret: %w"
- )
- // Generate creates the dynamic credentials by calling BeyondtrustWorkloadCredentials generate endpoint.
- func (g *Generator) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, kube client.Client, namespace string) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
- spec, err := getDynamicSecretSpec(jsonSpec)
- if err != nil {
- return nil, nil, err
- }
- provider := spec.Spec.Provider
- // parse and validate folder path and secret name early
- fullPath := spec.Spec.Provider.FolderPath
- folderPath, secretName := parsePath(fullPath)
- if secretName == "" {
- return nil, nil, fmt.Errorf("invalid path: missing secret name in %q", fullPath)
- }
- // create BeyondtrustWorkloadCredentials provider and initialize a client for generator controller
- clientFactory := g.NewBeyondtrustWorkloadCredentialsClient
- if clientFactory == nil {
- clientFactory = httpclient.NewBeyondtrustWorkloadCredentialsClient
- }
- prov := beyondtrustworkloadcredentialsprovider.Provider{
- NewBeyondtrustWorkloadCredentialsClient: clientFactory,
- }
- cl, err := prov.NewGeneratorClient(ctx, kube, provider, namespace)
- if err != nil {
- return nil, nil, fmt.Errorf("failed to create BeyondtrustWorkloadCredentials client: %w", err)
- }
- // call generate
- generatedSecret, err := cl.GenerateDynamicSecret(ctx, secretName, folderPath)
- if err != nil {
- return nil, nil, fmt.Errorf(errGetSecret, err)
- }
- if generatedSecret == nil {
- return nil, nil, fmt.Errorf("generated secret is nil")
- }
- out := convertToByteMap(generatedSecret)
- // prepare provider state
- state := &struct {
- Path string `json:"path,omitempty"`
- }{Path: spec.Spec.Provider.FolderPath}
- stateJSON, _ := json.Marshal(state)
- gpState := genv1alpha1.GeneratorProviderState(&apiextensions.JSON{Raw: stateJSON})
- return out, gpState, nil
- }
- // Cleanup is a no-op for BeyondtrustWorkloadCredentials dynamic generator.
- func (g *Generator) Cleanup(_ context.Context, _ *apiextensions.JSON, _ genv1alpha1.GeneratorProviderState, _ client.Client, _ string) error {
- return nil
- }
- // getDynamicSecretSpec checks if the provided spec is valid.
- func getDynamicSecretSpec(jsonSpec *apiextensions.JSON) (*genv1alpha1.BeyondtrustWorkloadCredentialsDynamicSecret, error) {
- if jsonSpec == nil {
- return nil, errors.New(errNoSpec)
- }
- spec, err := parseSpec(jsonSpec.Raw)
- if err != nil {
- return nil, fmt.Errorf(errParseSpec, err)
- }
- if spec == nil || spec.Spec.Provider == nil {
- return nil, errors.New(errMissingConfig)
- }
- if spec.Spec.Provider.FolderPath == "" {
- return nil, errors.New(errNoPath)
- }
- return spec, nil
- }
- // parseSpec unmarshals the JSON spec into a BeyondtrustWorkloadCredentialsDynamicSecret struct.
- func parseSpec(data []byte) (*genv1alpha1.BeyondtrustWorkloadCredentialsDynamicSecret, error) {
- var spec genv1alpha1.BeyondtrustWorkloadCredentialsDynamicSecret
- if err := yaml.Unmarshal(data, &spec); err != nil {
- return nil, err
- }
- return &spec, nil
- }
- // Parse the path to extract folder and secret name.
- // Path format: "folder/subfolder/secretname" or just "secretname".
- func parsePath(fullPath string) (*string, string) {
- var folderPath *string
- var secretName string
- lastSlash := strings.LastIndex(fullPath, "/")
- if lastSlash >= 0 {
- folder := fullPath[:lastSlash]
- folderPath = &folder
- secretName = fullPath[lastSlash+1:]
- } else {
- secretName = fullPath
- }
- return folderPath, secretName
- }
- // Convert generatedSecret to map[string][]byte.
- func convertToByteMap(generatedSecret *btwcutil.GeneratedSecret) map[string][]byte {
- out := make(map[string][]byte)
- // Defensive nil check
- if generatedSecret == nil {
- return out
- }
- out["accessKeyId"] = []byte(generatedSecret.AccessKeyID)
- out["secretAccessKey"] = []byte(generatedSecret.SecretAccessKey)
- out["leaseId"] = []byte(generatedSecret.LeaseID)
- out["expiration"] = []byte(generatedSecret.Expiration)
- if generatedSecret.SessionToken != "" {
- out["sessionToken"] = []byte(generatedSecret.SessionToken)
- }
- return out
- }
- // NewGenerator creates a new BeyondtrustWorkloadCredentials generator instance.
- func NewGenerator() genv1alpha1.Generator {
- return &Generator{}
- }
- // Kind returns the generator kind string.
- func Kind() string {
- return string(genv1alpha1.GeneratorKindBeyondtrustWorkloadCredentialsDynamicSecret)
- }
|