keyvault.go 11 KB

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