provider.go 11 KB

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