provider.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implieclient.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package beyondtrust
  13. import (
  14. "context"
  15. "errors"
  16. "fmt"
  17. "net/url"
  18. "strings"
  19. "time"
  20. auth "github.com/BeyondTrust/go-client-library-passwordsafe/api/authentication"
  21. "github.com/BeyondTrust/go-client-library-passwordsafe/api/logging"
  22. managed_account "github.com/BeyondTrust/go-client-library-passwordsafe/api/managed_account"
  23. "github.com/BeyondTrust/go-client-library-passwordsafe/api/secrets"
  24. "github.com/BeyondTrust/go-client-library-passwordsafe/api/utils"
  25. "github.com/cenkalti/backoff/v4"
  26. v1 "k8s.io/api/core/v1"
  27. ctrl "sigs.k8s.io/controller-runtime"
  28. "sigs.k8s.io/controller-runtime/pkg/client"
  29. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  30. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  31. esoClient "github.com/external-secrets/external-secrets/pkg/utils"
  32. )
  33. const (
  34. errNilStore = "nil store found"
  35. errMissingStoreSpec = "store is missing spec"
  36. errMissingProvider = "storeSpec is missing provider"
  37. errInvalidProvider = "invalid provider spec. Missing field in store %s"
  38. errInvalidHostURL = "invalid host URL"
  39. errNoSuchKeyFmt = "no such key in secret: %q"
  40. errInvalidRetrievalPath = "invalid retrieval path. Provide one path, separator and name"
  41. errNotImplemented = "not implemented"
  42. )
  43. var (
  44. errSecretRefAndValueConflict = errors.New("cannot specify both secret reference and value")
  45. errMissingSecretName = errors.New("must specify a secret name")
  46. errMissingSecretKey = errors.New("must specify a secret key")
  47. ESOLogger = ctrl.Log.WithName("provider").WithName("beyondtrust")
  48. maxFileSecretSizeBytes = 5000000
  49. )
  50. // Provider is a Password Safe secrets provider implementing NewClient and ValidateStore for the esv1beta1.Provider interface.
  51. type Provider struct {
  52. apiURL string
  53. retrievaltype string
  54. authenticate auth.AuthenticationObj
  55. log logging.LogrLogger
  56. separator string
  57. }
  58. // Capabilities implements v1beta1.Provider.
  59. func (*Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
  60. return esv1beta1.SecretStoreReadOnly
  61. }
  62. // Close implements v1beta1.SecretsClient.
  63. func (*Provider) Close(_ context.Context) error {
  64. return nil
  65. }
  66. // DeleteSecret implements v1beta1.SecretsClient.
  67. func (*Provider) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
  68. return errors.New(errNotImplemented)
  69. }
  70. // GetSecretMap implements v1beta1.SecretsClient.
  71. func (*Provider) GetSecretMap(_ context.Context, _ esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  72. return make(map[string][]byte), errors.New(errNotImplemented)
  73. }
  74. // PushSecret implements v1beta1.SecretsClient.
  75. func (*Provider) PushSecret(_ context.Context, _ *v1.Secret, _ esv1beta1.PushSecretData) error {
  76. return errors.New(errNotImplemented)
  77. }
  78. // Validate implements v1beta1.SecretsClient.
  79. func (p *Provider) Validate() (esv1beta1.ValidationResult, error) {
  80. timeout := 15 * time.Second
  81. clientURL := p.apiURL
  82. if err := esoClient.NetworkValidate(clientURL, timeout); err != nil {
  83. ESOLogger.Error(err, "Network Validate", "clientURL:", clientURL)
  84. return esv1beta1.ValidationResultError, err
  85. }
  86. return esv1beta1.ValidationResultReady, nil
  87. }
  88. func (*Provider) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
  89. return false, errors.New(errNotImplemented)
  90. }
  91. // NewClient this is where we initialize the SecretClient and return it for the controller to use.
  92. func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  93. config := store.GetSpec().Provider.Beyondtrust
  94. logger := logging.NewLogrLogger(&ESOLogger)
  95. apiURL := config.Server.APIURL
  96. certificate := ""
  97. certificateKey := ""
  98. clientTimeOutInSeconds := 45
  99. retryMaxElapsedTimeMinutes := 15
  100. separator := "/"
  101. if config.Server.Separator != "" {
  102. separator = config.Server.Separator
  103. }
  104. if config.Server.ClientTimeOutSeconds != 0 {
  105. clientTimeOutInSeconds = config.Server.ClientTimeOutSeconds
  106. }
  107. backoffDefinition := backoff.NewExponentialBackOff()
  108. backoffDefinition.InitialInterval = 1 * time.Second
  109. backoffDefinition.MaxElapsedTime = time.Duration(retryMaxElapsedTimeMinutes) * time.Second
  110. backoffDefinition.RandomizationFactor = 0.5
  111. clientID, err := loadConfigSecret(ctx, config.Auth.ClientID, kube, namespace)
  112. if err != nil {
  113. return nil, fmt.Errorf("error loading clientID: %w", err)
  114. }
  115. clientSecret, err := loadConfigSecret(ctx, config.Auth.ClientSecret, kube, namespace)
  116. if err != nil {
  117. return nil, fmt.Errorf("error loading clientSecret: %w", err)
  118. }
  119. if config.Auth.Certificate != nil && config.Auth.CertificateKey != nil {
  120. loadedCertificate, err := loadConfigSecret(ctx, config.Auth.Certificate, kube, namespace)
  121. if err != nil {
  122. return nil, fmt.Errorf("error loading Certificate: %w", err)
  123. }
  124. certificate = loadedCertificate
  125. loadedCertificateKey, err := loadConfigSecret(ctx, config.Auth.CertificateKey, kube, namespace)
  126. if err != nil {
  127. return nil, fmt.Errorf("error loading Certificate Key: %w", err)
  128. }
  129. certificateKey = loadedCertificateKey
  130. }
  131. // Create an instance of ValidationParams
  132. params := utils.ValidationParams{
  133. ClientID: clientID,
  134. ClientSecret: clientSecret,
  135. ApiUrl: &apiURL,
  136. ClientTimeOutInSeconds: clientTimeOutInSeconds,
  137. Separator: &separator,
  138. VerifyCa: config.Server.VerifyCA,
  139. Logger: logger,
  140. Certificate: certificate,
  141. CertificateKey: certificateKey,
  142. RetryMaxElapsedTimeMinutes: &retryMaxElapsedTimeMinutes,
  143. MaxFileSecretSizeBytes: &maxFileSecretSizeBytes,
  144. }
  145. errorsInInputs := utils.ValidateInputs(params)
  146. if errorsInInputs != nil {
  147. return nil, fmt.Errorf("error in Inputs: %w", errorsInInputs)
  148. }
  149. // creating a http client
  150. httpClientObj, err := utils.GetHttpClient(clientTimeOutInSeconds, config.Server.VerifyCA, certificate, certificateKey, logger)
  151. if err != nil {
  152. return nil, fmt.Errorf("error creating http client: %w", err)
  153. }
  154. // instantiating authenticate obj, injecting httpClient object
  155. authenticate, _ := auth.Authenticate(*httpClientObj, backoffDefinition, apiURL, clientID, clientSecret, logger, retryMaxElapsedTimeMinutes)
  156. return &Provider{
  157. apiURL: config.Server.APIURL,
  158. retrievaltype: config.Server.RetrievalType,
  159. authenticate: *authenticate,
  160. log: *logger,
  161. separator: separator,
  162. }, nil
  163. }
  164. func loadConfigSecret(ctx context.Context, ref *esv1beta1.BeyondTrustProviderSecretRef, kube client.Client, defaultNamespace string) (string, error) {
  165. if ref.SecretRef == nil {
  166. return ref.Value, nil
  167. }
  168. if err := validateSecretRef(ref); err != nil {
  169. return "", err
  170. }
  171. namespace := defaultNamespace
  172. if ref.SecretRef.Namespace != nil {
  173. namespace = *ref.SecretRef.Namespace
  174. }
  175. ESOLogger.Info("using k8s secret", "name:", ref.SecretRef.Name, "namespace:", namespace)
  176. objKey := client.ObjectKey{Namespace: namespace, Name: ref.SecretRef.Name}
  177. secret := v1.Secret{}
  178. err := kube.Get(ctx, objKey, &secret)
  179. if err != nil {
  180. return "", err
  181. }
  182. value, ok := secret.Data[ref.SecretRef.Key]
  183. if !ok {
  184. return "", fmt.Errorf(errNoSuchKeyFmt, ref.SecretRef.Key)
  185. }
  186. return string(value), nil
  187. }
  188. func validateSecretRef(ref *esv1beta1.BeyondTrustProviderSecretRef) error {
  189. if ref.SecretRef != nil {
  190. if ref.Value != "" {
  191. return errSecretRefAndValueConflict
  192. }
  193. if ref.SecretRef.Name == "" {
  194. return errMissingSecretName
  195. }
  196. if ref.SecretRef.Key == "" {
  197. return errMissingSecretKey
  198. }
  199. }
  200. return nil
  201. }
  202. func (p *Provider) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  203. return nil, errors.New("GetAllSecrets not implemented")
  204. }
  205. // GetSecret reads the secret from the Password Safe server and returns it. The controller uses the value here to
  206. // create the Kubernetes secret.
  207. func (p *Provider) GetSecret(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  208. managedAccountType := !strings.EqualFold(p.retrievaltype, "SECRET")
  209. retrievalPaths := utils.ValidatePaths([]string{ref.Key}, managedAccountType, p.separator, &p.log)
  210. if len(retrievalPaths) != 1 {
  211. return nil, errors.New(errInvalidRetrievalPath)
  212. }
  213. retrievalPath := retrievalPaths[0]
  214. _, err := p.authenticate.GetPasswordSafeAuthentication()
  215. if err != nil {
  216. return nil, fmt.Errorf("error getting authentication: %w", err)
  217. }
  218. managedFetch := func() (string, error) {
  219. ESOLogger.Info("retrieve managed account value", "retrievalPath:", retrievalPath)
  220. manageAccountObj, _ := managed_account.NewManagedAccountObj(p.authenticate, &p.log)
  221. return manageAccountObj.GetSecret(retrievalPath, p.separator)
  222. }
  223. unmanagedFetch := func() (string, error) {
  224. ESOLogger.Info("retrieve secrets safe value", "retrievalPath:", retrievalPath)
  225. secretObj, _ := secrets.NewSecretObj(p.authenticate, &p.log, maxFileSecretSizeBytes)
  226. return secretObj.GetSecret(retrievalPath, p.separator)
  227. }
  228. fetch := unmanagedFetch
  229. if managedAccountType {
  230. fetch = managedFetch
  231. }
  232. returnSecret, err := fetch()
  233. if err != nil {
  234. if serr := p.authenticate.SignOut(); serr != nil {
  235. return nil, errors.Join(err, serr)
  236. }
  237. return nil, fmt.Errorf("error getting secret/managed account: %w", err)
  238. }
  239. return []byte(returnSecret), nil
  240. }
  241. // ValidateStore validates the store configuration to prevent unexpected errors.
  242. func (p *Provider) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) {
  243. if store == nil {
  244. return nil, errors.New(errNilStore)
  245. }
  246. spec := store.GetSpec()
  247. if spec == nil {
  248. return nil, errors.New(errMissingStoreSpec)
  249. }
  250. if spec.Provider == nil {
  251. return nil, errors.New(errMissingProvider)
  252. }
  253. provider := spec.Provider.Beyondtrust
  254. if provider == nil {
  255. return nil, fmt.Errorf(errInvalidProvider, store.GetObjectMeta().String())
  256. }
  257. apiURL, err := url.Parse(provider.Server.APIURL)
  258. if err != nil {
  259. return nil, errors.New(errInvalidHostURL)
  260. }
  261. if provider.Auth.ClientID.SecretRef != nil {
  262. return nil, err
  263. }
  264. if provider.Auth.ClientSecret.SecretRef != nil {
  265. return nil, err
  266. }
  267. if apiURL.Host == "" {
  268. return nil, errors.New(errInvalidHostURL)
  269. }
  270. return nil, nil
  271. }
  272. // registers the provider object to process on each reconciliation loop.
  273. func init() {
  274. esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
  275. Beyondtrust: &esv1beta1.BeyondtrustProvider{},
  276. })
  277. }