beyondtrustworkloadcredentials.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package beyondtrustworkloadcredentialsdynamic provides a generator for BeyondTrust Workload Credentials dynamic credentials.
  14. package beyondtrustworkloadcredentialsdynamic
  15. import (
  16. "context"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "strings"
  21. apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  22. "sigs.k8s.io/controller-runtime/pkg/client"
  23. "sigs.k8s.io/yaml"
  24. genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
  25. beyondtrustworkloadcredentialsprovider "github.com/external-secrets/external-secrets/providers/v1/beyondtrustworkloadcredentials"
  26. "github.com/external-secrets/external-secrets/providers/v1/beyondtrustworkloadcredentials/httpclient"
  27. btwcutil "github.com/external-secrets/external-secrets/providers/v1/beyondtrustworkloadcredentials/util"
  28. )
  29. // Generator implements BeyondtrustWorkloadCredentials dynamic generator.
  30. type Generator struct {
  31. // NewBeyondtrustWorkloadCredentialsClient is a factory function to create a BeyondtrustWorkloadCredentials client.
  32. // If nil, defaults to httpclient.NewBeyondtrustWorkloadCredentialsClient.
  33. NewBeyondtrustWorkloadCredentialsClient func(server, token string) (btwcutil.Client, error)
  34. }
  35. const (
  36. errNoSpec = "no config spec provided"
  37. errParseSpec = "unable to parse spec: %w"
  38. errMissingConfig = "no beyondtrustworkloadcredentials provider config in spec"
  39. errNoPath = "path is required in spec"
  40. errGetSecret = "unable to generate dynamic secret: %w"
  41. )
  42. // Generate creates the dynamic credentials by calling BeyondtrustWorkloadCredentials generate endpoint.
  43. func (g *Generator) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, kube client.Client, namespace string) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
  44. spec, err := getDynamicSecretSpec(jsonSpec)
  45. if err != nil {
  46. return nil, nil, err
  47. }
  48. provider := spec.Spec.Provider
  49. // parse and validate folder path and secret name early
  50. fullPath := spec.Spec.Provider.FolderPath
  51. folderPath, secretName := parsePath(fullPath)
  52. if secretName == "" {
  53. return nil, nil, fmt.Errorf("invalid path: missing secret name in %q", fullPath)
  54. }
  55. // create BeyondtrustWorkloadCredentials provider and initialize a client for generator controller
  56. clientFactory := g.NewBeyondtrustWorkloadCredentialsClient
  57. if clientFactory == nil {
  58. clientFactory = httpclient.NewBeyondtrustWorkloadCredentialsClient
  59. }
  60. prov := beyondtrustworkloadcredentialsprovider.Provider{
  61. NewBeyondtrustWorkloadCredentialsClient: clientFactory,
  62. }
  63. cl, err := prov.NewGeneratorClient(ctx, kube, provider, namespace)
  64. if err != nil {
  65. return nil, nil, fmt.Errorf("failed to create BeyondtrustWorkloadCredentials client: %w", err)
  66. }
  67. // call generate
  68. generatedSecret, err := cl.GenerateDynamicSecret(ctx, secretName, folderPath)
  69. if err != nil {
  70. return nil, nil, fmt.Errorf(errGetSecret, err)
  71. }
  72. if generatedSecret == nil {
  73. return nil, nil, fmt.Errorf("generated secret is nil")
  74. }
  75. out := convertToByteMap(generatedSecret)
  76. // prepare provider state
  77. state := &struct {
  78. Path string `json:"path,omitempty"`
  79. }{Path: spec.Spec.Provider.FolderPath}
  80. stateJSON, _ := json.Marshal(state)
  81. gpState := genv1alpha1.GeneratorProviderState(&apiextensions.JSON{Raw: stateJSON})
  82. return out, gpState, nil
  83. }
  84. // Cleanup is a no-op for BeyondtrustWorkloadCredentials dynamic generator.
  85. func (g *Generator) Cleanup(_ context.Context, _ *apiextensions.JSON, _ genv1alpha1.GeneratorProviderState, _ client.Client, _ string) error {
  86. return nil
  87. }
  88. // getDynamicSecretSpec checks if the provided spec is valid.
  89. func getDynamicSecretSpec(jsonSpec *apiextensions.JSON) (*genv1alpha1.BeyondtrustWorkloadCredentialsDynamicSecret, error) {
  90. if jsonSpec == nil {
  91. return nil, errors.New(errNoSpec)
  92. }
  93. spec, err := parseSpec(jsonSpec.Raw)
  94. if err != nil {
  95. return nil, fmt.Errorf(errParseSpec, err)
  96. }
  97. if spec == nil || spec.Spec.Provider == nil {
  98. return nil, errors.New(errMissingConfig)
  99. }
  100. if spec.Spec.Provider.FolderPath == "" {
  101. return nil, errors.New(errNoPath)
  102. }
  103. return spec, nil
  104. }
  105. // parseSpec unmarshals the JSON spec into a BeyondtrustWorkloadCredentialsDynamicSecret struct.
  106. func parseSpec(data []byte) (*genv1alpha1.BeyondtrustWorkloadCredentialsDynamicSecret, error) {
  107. var spec genv1alpha1.BeyondtrustWorkloadCredentialsDynamicSecret
  108. if err := yaml.Unmarshal(data, &spec); err != nil {
  109. return nil, err
  110. }
  111. return &spec, nil
  112. }
  113. // Parse the path to extract folder and secret name.
  114. // Path format: "folder/subfolder/secretname" or just "secretname".
  115. func parsePath(fullPath string) (*string, string) {
  116. var folderPath *string
  117. var secretName string
  118. lastSlash := strings.LastIndex(fullPath, "/")
  119. if lastSlash >= 0 {
  120. folder := fullPath[:lastSlash]
  121. folderPath = &folder
  122. secretName = fullPath[lastSlash+1:]
  123. } else {
  124. secretName = fullPath
  125. }
  126. return folderPath, secretName
  127. }
  128. // Convert generatedSecret to map[string][]byte.
  129. func convertToByteMap(generatedSecret *btwcutil.GeneratedSecret) map[string][]byte {
  130. out := make(map[string][]byte)
  131. // Defensive nil check
  132. if generatedSecret == nil {
  133. return out
  134. }
  135. out["accessKeyId"] = []byte(generatedSecret.AccessKeyID)
  136. out["secretAccessKey"] = []byte(generatedSecret.SecretAccessKey)
  137. out["leaseId"] = []byte(generatedSecret.LeaseID)
  138. out["expiration"] = []byte(generatedSecret.Expiration)
  139. if generatedSecret.SessionToken != "" {
  140. out["sessionToken"] = []byte(generatedSecret.SessionToken)
  141. }
  142. return out
  143. }
  144. // NewGenerator creates a new BeyondtrustWorkloadCredentials generator instance.
  145. func NewGenerator() genv1alpha1.Generator {
  146. return &Generator{}
  147. }
  148. // Kind returns the generator kind string.
  149. func Kind() string {
  150. return string(genv1alpha1.GeneratorKindBeyondtrustWorkloadCredentialsDynamicSecret)
  151. }