keyvault.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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 implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package keyvault
  13. import (
  14. "context"
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "path"
  19. "regexp"
  20. "strings"
  21. "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
  22. kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
  23. "github.com/tidwall/gjson"
  24. corev1 "k8s.io/api/core/v1"
  25. "k8s.io/apimachinery/pkg/types"
  26. "sigs.k8s.io/controller-runtime/pkg/client"
  27. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  28. smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  29. "github.com/external-secrets/external-secrets/pkg/utils"
  30. )
  31. const (
  32. defaultObjType = "secret"
  33. objectTypeCert = "cert"
  34. objectTypeKey = "key"
  35. vaultResource = "https://vault.azure.net"
  36. errUnexpectedStoreSpec = "unexpected store spec"
  37. errMissingAuthType = "cannot initialize Azure Client: no valid authType was specified"
  38. errPropNotExist = "property %s does not exist in key %s"
  39. errUnknownObjectType = "unknown Azure Keyvault object Type for %s"
  40. errUnmarshalJSONData = "error unmarshalling json data: %w"
  41. errDataFromCert = "cannot get use dataFrom to get certificate secret"
  42. errDataFromKey = "cannot get use dataFrom to get key secret"
  43. errMissingTenant = "missing tenantID in store config"
  44. errMissingSecretRef = "missing secretRef in provider config"
  45. errMissingClientIDSecret = "missing accessKeyID/secretAccessKey in store config"
  46. errFindSecret = "could not find secret %s/%s: %w"
  47. errFindDataKey = "no data for %q in secret '%s/%s'"
  48. errInvalidStore = "invalid store"
  49. errInvalidStoreSpec = "invalid store spec"
  50. errInvalidStoreProv = "invalid store provider"
  51. errInvalidAzureProv = "invalid azure keyvault provider"
  52. errInvalidSecRefClientID = "invalid AuthSecretRef.ClientID: %w"
  53. errInvalidSecRefClientSecret = "invalid AuthSecretRef.ClientSecret: %w"
  54. )
  55. // interface to keyvault.BaseClient.
  56. type SecretClient interface {
  57. GetKey(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string) (result keyvault.KeyBundle, err error)
  58. GetSecret(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
  59. GetSecretsComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error)
  60. GetCertificate(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
  61. }
  62. type Azure struct {
  63. kube client.Client
  64. store esv1beta1.GenericStore
  65. provider *esv1beta1.AzureKVProvider
  66. baseClient SecretClient
  67. namespace string
  68. }
  69. func init() {
  70. esv1beta1.Register(&Azure{}, &esv1beta1.SecretStoreProvider{
  71. AzureKV: &esv1beta1.AzureKVProvider{},
  72. })
  73. }
  74. // NewClient constructs a new secrets client based on the provided store.
  75. func (a *Azure) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  76. return newClient(ctx, store, kube, namespace)
  77. }
  78. func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  79. provider, err := getProvider(store)
  80. if err != nil {
  81. return nil, err
  82. }
  83. az := &Azure{
  84. kube: kube,
  85. store: store,
  86. namespace: namespace,
  87. provider: provider,
  88. }
  89. ok, err := az.setAzureClientWithManagedIdentity()
  90. if ok {
  91. return az, err
  92. }
  93. ok, err = az.setAzureClientWithServicePrincipal(ctx)
  94. if ok {
  95. return az, err
  96. }
  97. return nil, fmt.Errorf(errMissingAuthType)
  98. }
  99. func getProvider(store esv1beta1.GenericStore) (*esv1beta1.AzureKVProvider, error) {
  100. spc := store.GetSpec()
  101. if spc == nil || spc.Provider.AzureKV == nil {
  102. return nil, errors.New(errUnexpectedStoreSpec)
  103. }
  104. return spc.Provider.AzureKV, nil
  105. }
  106. func (a *Azure) ValidateStore(store esv1beta1.GenericStore) error {
  107. if store == nil {
  108. return fmt.Errorf(errInvalidStore)
  109. }
  110. spc := store.GetSpec()
  111. if spc == nil {
  112. return fmt.Errorf(errInvalidStoreSpec)
  113. }
  114. if spc.Provider == nil {
  115. return fmt.Errorf(errInvalidStoreProv)
  116. }
  117. p := spc.Provider.AzureKV
  118. if p == nil {
  119. return fmt.Errorf(errInvalidAzureProv)
  120. }
  121. if p.AuthSecretRef != nil {
  122. if p.AuthSecretRef.ClientID != nil {
  123. if err := utils.ValidateSecretSelector(store, *p.AuthSecretRef.ClientID); err != nil {
  124. return fmt.Errorf(errInvalidSecRefClientID, err)
  125. }
  126. }
  127. if p.AuthSecretRef.ClientSecret != nil {
  128. if err := utils.ValidateSecretSelector(store, *p.AuthSecretRef.ClientSecret); err != nil {
  129. return fmt.Errorf(errInvalidSecRefClientSecret, err)
  130. }
  131. }
  132. }
  133. return nil
  134. }
  135. // Implements store.Client.GetAllSecrets Interface.
  136. // Retrieves a map[string][]byte with the secret names as key and the secret itself as the calue.
  137. func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  138. basicClient := a.baseClient
  139. secretsMap := make(map[string][]byte)
  140. checkTags := len(ref.Tags) > 0
  141. checkName := ref.Name != nil && len(ref.Name.RegExp) > 0
  142. secretListIter, err := basicClient.GetSecretsComplete(context.Background(), *a.provider.VaultURL, nil)
  143. if err != nil {
  144. return nil, err
  145. }
  146. for secretListIter.NotDone() {
  147. secretList := secretListIter.Response().Value
  148. for _, secret := range *secretList {
  149. ok, secretName := isValidSecret(checkTags, checkName, ref, secret)
  150. if !ok {
  151. continue
  152. }
  153. secretResp, err := basicClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, "")
  154. if err != nil {
  155. return nil, err
  156. }
  157. secretValue := *secretResp.Value
  158. secretsMap[secretName] = []byte(secretValue)
  159. }
  160. err = secretListIter.Next()
  161. if err != nil {
  162. return nil, err
  163. }
  164. }
  165. return secretsMap, nil
  166. }
  167. // Implements store.Client.GetSecret Interface.
  168. // Retrieves a secret/Key/Certificate with the secret name defined in ref.Name
  169. // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
  170. func (a *Azure) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  171. version := ""
  172. objectType, secretName := getObjType(ref)
  173. if ref.Version != "" {
  174. version = ref.Version
  175. }
  176. switch objectType {
  177. case defaultObjType:
  178. // returns a SecretBundle with the secret value
  179. // https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
  180. secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, version)
  181. if err != nil {
  182. return nil, err
  183. }
  184. if ref.Property == "" {
  185. return []byte(*secretResp.Value), nil
  186. }
  187. res := gjson.Get(*secretResp.Value, ref.Property)
  188. if !res.Exists() {
  189. return nil, fmt.Errorf(errPropNotExist, ref.Property, ref.Key)
  190. }
  191. return []byte(res.String()), err
  192. case objectTypeCert:
  193. // returns a CertBundle. We return CER contents of x509 certificate
  194. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
  195. secretResp, err := a.baseClient.GetCertificate(context.Background(), *a.provider.VaultURL, secretName, version)
  196. if err != nil {
  197. return nil, err
  198. }
  199. return *secretResp.Cer, nil
  200. case objectTypeKey:
  201. // returns a KeyBundle that contains a jwk
  202. // azure kv returns only public keys
  203. // see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
  204. keyResp, err := a.baseClient.GetKey(context.Background(), *a.provider.VaultURL, secretName, version)
  205. if err != nil {
  206. return nil, err
  207. }
  208. return json.Marshal(keyResp.Key)
  209. }
  210. return nil, fmt.Errorf(errUnknownObjectType, secretName)
  211. }
  212. // Implements store.Client.GetSecretMap Interface.
  213. // New version of GetSecretMap.
  214. func (a *Azure) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  215. objectType, secretName := getObjType(ref)
  216. switch objectType {
  217. case defaultObjType:
  218. data, err := a.GetSecret(ctx, ref)
  219. if err != nil {
  220. return nil, err
  221. }
  222. kv := make(map[string]string)
  223. err = json.Unmarshal(data, &kv)
  224. if err != nil {
  225. return nil, fmt.Errorf(errUnmarshalJSONData, err)
  226. }
  227. secretData := make(map[string][]byte)
  228. for k, v := range kv {
  229. secretData[k] = []byte(v)
  230. }
  231. return secretData, nil
  232. case objectTypeCert:
  233. return nil, fmt.Errorf(errDataFromCert)
  234. case objectTypeKey:
  235. return nil, fmt.Errorf(errDataFromKey)
  236. }
  237. return nil, fmt.Errorf(errUnknownObjectType, secretName)
  238. }
  239. func (a *Azure) setAzureClientWithManagedIdentity() (bool, error) {
  240. if *a.provider.AuthType != esv1beta1.ManagedIdentity {
  241. return false, nil
  242. }
  243. msiConfig := kvauth.NewMSIConfig()
  244. msiConfig.Resource = vaultResource
  245. if a.provider.IdentityID != nil {
  246. msiConfig.ClientID = *a.provider.IdentityID
  247. }
  248. authorizer, err := msiConfig.Authorizer()
  249. if err != nil {
  250. return true, err
  251. }
  252. cl := keyvault.New()
  253. cl.Authorizer = authorizer
  254. a.baseClient = &cl
  255. return true, nil
  256. }
  257. func (a *Azure) setAzureClientWithServicePrincipal(ctx context.Context) (bool, error) {
  258. if *a.provider.AuthType != esv1beta1.ServicePrincipal {
  259. return false, nil
  260. }
  261. if a.provider.TenantID == nil {
  262. return true, fmt.Errorf(errMissingTenant)
  263. }
  264. if a.provider.AuthSecretRef == nil {
  265. return true, fmt.Errorf(errMissingSecretRef)
  266. }
  267. if a.provider.AuthSecretRef.ClientID == nil || a.provider.AuthSecretRef.ClientSecret == nil {
  268. return true, fmt.Errorf(errMissingClientIDSecret)
  269. }
  270. clusterScoped := false
  271. if a.store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind {
  272. clusterScoped = true
  273. }
  274. cid, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *a.provider.AuthSecretRef.ClientID, clusterScoped)
  275. if err != nil {
  276. return true, err
  277. }
  278. csec, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *a.provider.AuthSecretRef.ClientSecret, clusterScoped)
  279. if err != nil {
  280. return true, err
  281. }
  282. clientCredentialsConfig := kvauth.NewClientCredentialsConfig(cid, csec, *a.provider.TenantID)
  283. clientCredentialsConfig.Resource = vaultResource
  284. authorizer, err := clientCredentialsConfig.Authorizer()
  285. if err != nil {
  286. return true, err
  287. }
  288. cl := keyvault.New()
  289. cl.Authorizer = authorizer
  290. a.baseClient = &cl
  291. return true, nil
  292. }
  293. func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, clusterScoped bool) (string, error) {
  294. var secret corev1.Secret
  295. ref := types.NamespacedName{
  296. Namespace: namespace,
  297. Name: secretRef.Name,
  298. }
  299. if clusterScoped && secretRef.Namespace != nil {
  300. ref.Namespace = *secretRef.Namespace
  301. }
  302. err := a.kube.Get(ctx, ref, &secret)
  303. if err != nil {
  304. return "", fmt.Errorf(errFindSecret, ref.Namespace, ref.Name, err)
  305. }
  306. keyBytes, ok := secret.Data[secretRef.Key]
  307. if !ok {
  308. return "", fmt.Errorf(errFindDataKey, secretRef.Key, secretRef.Name, namespace)
  309. }
  310. value := strings.TrimSpace(string(keyBytes))
  311. return value, nil
  312. }
  313. func (a *Azure) Close(ctx context.Context) error {
  314. return nil
  315. }
  316. func (a *Azure) Validate() error {
  317. return nil
  318. }
  319. func getObjType(ref esv1beta1.ExternalSecretDataRemoteRef) (string, string) {
  320. objectType := defaultObjType
  321. secretName := ref.Key
  322. nameSplitted := strings.Split(secretName, "/")
  323. if len(nameSplitted) > 1 {
  324. objectType = nameSplitted[0]
  325. secretName = nameSplitted[1]
  326. // TODO: later tokens can be used to read the secret tags
  327. }
  328. return objectType, secretName
  329. }
  330. func isValidSecret(checkTags, checkName bool, ref esv1beta1.ExternalSecretFind, secret keyvault.SecretItem) (bool, string) {
  331. if secret.ID == nil || !*secret.Attributes.Enabled {
  332. return false, ""
  333. }
  334. if checkTags && !okByTags(ref, secret) {
  335. return false, ""
  336. }
  337. secretName := path.Base(*secret.ID)
  338. if checkName && !okByName(ref, secretName) {
  339. return false, ""
  340. }
  341. return true, secretName
  342. }
  343. func okByName(ref esv1beta1.ExternalSecretFind, secretName string) bool {
  344. matches, _ := regexp.MatchString(ref.Name.RegExp, secretName)
  345. return matches
  346. }
  347. func okByTags(ref esv1beta1.ExternalSecretFind, secret keyvault.SecretItem) bool {
  348. tagsFound := true
  349. for k, v := range ref.Tags {
  350. if val, ok := secret.Tags[k]; !ok || *val != v {
  351. tagsFound = false
  352. break
  353. }
  354. }
  355. return tagsFound
  356. }