1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-14 11:57:59 +00:00

Merge pull request #215 from external-secrets/fix/az-key-types

support more azure key types
This commit is contained in:
paul-the-alien[bot] 2021-06-24 18:37:20 +00:00 committed by GitHub
commit 662910fa7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 502 additions and 151 deletions

View file

@ -23,6 +23,8 @@ We provide a bunch of convenience functions that help you transform your secrets
| pkcs12certPass | extracts the certificate from a pkcs12 archive using the provided password | password `string`, data `[]byte` | `[]byte` | | pkcs12certPass | extracts the certificate from a pkcs12 archive using the provided password | password `string`, data `[]byte` | `[]byte` |
| pemPrivateKey | PEM encodes the provided bytes as private key | `[]byte` | `string` | | pemPrivateKey | PEM encodes the provided bytes as private key | `[]byte` | `string` |
| pemCertificate | PEM encodes the provided bytes as certificate | `[]byte` | `string` | | pemCertificate | PEM encodes the provided bytes as certificate | `[]byte` | `string` |
| jwkPublicKeyPem | takes an json-serialized JWK as `[]byte` and returns an PEM block of type `PUBLIC KEY` that contains the public key ([see here](https://golang.org/pkg/crypto/x509/#MarshalPKIXPublicKey)) for details | `[]byte` | `string` |
| jwkPrivateKeyPem | takes an json-serialized JWK as `[]byte` and returns an PEM block of type `PRIVATE KEY` that contains the private key in PKCS #8 format ([see here](https://golang.org/pkg/crypto/x509/#MarshalPKCS8PrivateKey)) for details | `[]byte` | `string` |
| base64decode | decodes the provided bytes as base64 | `[]byte` | `[]byte` | | base64decode | decodes the provided bytes as base64 | `[]byte` | `[]byte` |
| base64encode | encodes the provided bytes as base64 | `[]byte` | `[]byte` | | base64encode | encodes the provided bytes as base64 | `[]byte` | `[]byte` |
| fromJSON | parses the bytes as JSON so you can access individual properties | `[]byte` | `interface{}` | | fromJSON | parses the bytes as JSON so you can access individual properties | `[]byte` | `interface{}` |

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View file

@ -3,7 +3,7 @@
## Azure Key vault ## Azure Key vault
External Secrets Operator integrates with [Azure Key vault](https://azure.microsoft.com/en-us/services/key-vault/) for secrets , certificates and Keys management. External Secrets Operator integrates with [Azure Key vault](https://azure.microsoft.com/en-us/services/key-vault/) for secrets, certificates and Keys management.
### Authentication ### Authentication
@ -24,6 +24,17 @@ Be sure the `azkv` provider is listed in the `Kind=SecretStore`
{% include 'azkv-secret-store.yaml' %} {% include 'azkv-secret-store.yaml' %}
``` ```
### Object Types
Azure KeyVault manages different [object types](https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#object-types), we support `keys`, `secrets` and `certificates`. Simply prefix the key with `key`, `secret` or `cert` to retrieve the desired type (defaults to secret).
| Object Type | Return Value |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `secret` | the raw secret value. |
| `key` | A JWK which contains the public key. Azure KeyVault does **not** export the private key. You may want to use [template functions](guides-templating.md) to transform this JWK into PEM encoded PKIX ASN.1 DER format. |
| `certificate` | The raw CER contents of the x509 certificate. You may want to use [template functions](guides-templating.md) to transform this into your desired encoding |
### Creating external secret ### Creating external secret
To create a kubernetes secret from the Azure Key vault secret a `Kind=ExternalSecret` is needed. To create a kubernetes secret from the Azure Key vault secret a `Kind=ExternalSecret` is needed.
@ -40,4 +51,3 @@ The operator will fetch the Azure Key vault secret and inject it as a `Kind=Secr
``` ```
kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath='{.data.dev-secret-test}' | base64 -d kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath='{.data.dev-secret-test}' | base64 -d
``` ```

View file

@ -3,34 +3,39 @@ kind: ExternalSecret
metadata: metadata:
name: example-external-secret name: example-external-secret
spec: spec:
refreshInterval: 1h # rate SecretManager pulls Azure refreshInterval: 1h
secretStoreRef: secretStoreRef:
kind: SecretStore kind: SecretStore
name: example-secret-store # name of the SecretStore (or kind specified) name: example-secret-store
target: target:
name: secret-to-be-created # name of the k8s Secret to be created name: secret-to-be-created
creationPolicy: Owner creationPolicy: Owner
data: data:
- secretKey: dev-secret-test # name of the key to be created in the secret object # name of the SECRET in the Azure KV (no prefix is by default a SECRET)
- secretKey: dev-secret-test
remoteRef: remoteRef:
key: dev-secret-test #name of the SECRET in the Azure KV (no prefix => SECRET) key: dev-secret-test
- secretKey: dev-another-secret-test # name of the key to be created in the secret object # explicit type and name of secret in the Azure KV
- secretKey: dev-another-secret-test
remoteRef: remoteRef:
key: secret/dev-secret-test #type and name of secret in the Azure KV key: secret/dev-secret-test
- secretKey: dev-cert-test # name of the key to be created in the secret object # type/name of certificate in the Azure KV
# raw value will be returned, use templating features for data processing
- secretKey: dev-cert-test
remoteRef: remoteRef:
key: cert/dev-cert-test #type/name of certificate in the Azure KV key: cert/dev-cert-test
#raw value will be returned , use templating features for data processing
- secretKey: dev-key-test # name of the key to be created in the secret object # type/name of the public key in the Azure KV
# the key is returned PEM encoded
- secretKey: dev-key-test
remoteRef: remoteRef:
key: key/dev-key-test #type/name of the public key in the Azure KV key: key/dev-key-test
# dataFrom , return ALL secrets saved in the referenced secretStore # dataFrom , return ALL secrets saved in the referenced secretStore
# each secret name in the KV will be used as the secret key in the SECRET k8s target object # each secret name in the KV will be used as the secret key in the SECRET k8s target object
dataFrom: dataFrom:
- name: "*" - name: "*"

View file

@ -3,13 +3,17 @@ kind: SecretStore
metadata: metadata:
name: example-secret-store name: example-secret-store
spec: spec:
azurekv: #Provider type , azure keyvault # provider type: azure keyvault
tenantid: "d3bc2180-xxxx-xxxx-xxxx-154105743342" #azure tenant ID azurekv:
vaultUrl: "https://my-keyvault-name.vault.azure.net" #Keyvault URL # azure tenant ID, see: https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant
tenantId: "d3bc2180-xxxx-xxxx-xxxx-154105743342"
# URL of your vault instance, see: https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates
vaultUrl: "https://my-keyvault-name.vault.azure.net"
authSecretRef: authSecretRef:
#Secret created in the cluster holding the azure service principal with proper access rights # points to the secret that contains
clientID: # the azure service principal credentials
name: azure-secret-sp clientId:
name: azure-secret-sp
key: ClientID key: ClientID
clientSecret: clientSecret:
name: azure-secret-sp name: azure-secret-sp

View file

@ -180,6 +180,101 @@ see: <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.
</td> </td>
</tr></tbody> </tr></tbody>
</table> </table>
<h3 id="external-secrets.io/v1alpha1.AzureKVAuth">AzureKVAuth
</h3>
<p>
(<em>Appears on:</em>
<a href="#external-secrets.io/v1alpha1.AzureKVProvider">AzureKVProvider</a>)
</p>
<p>
<p>Configuration used to authenticate with Azure.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>clientId</code></br>
<em>
github.com/external-secrets/external-secrets/apis/meta/v1.SecretKeySelector
</em>
</td>
<td>
<p>The Azure clientId of the service principle used for authentication.</p>
</td>
</tr>
<tr>
<td>
<code>clientSecret</code></br>
<em>
github.com/external-secrets/external-secrets/apis/meta/v1.SecretKeySelector
</em>
</td>
<td>
<p>The Azure ClientSecret of the service principle used for authentication.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="external-secrets.io/v1alpha1.AzureKVProvider">AzureKVProvider
</h3>
<p>
(<em>Appears on:</em>
<a href="#external-secrets.io/v1alpha1.SecretStoreProvider">SecretStoreProvider</a>)
</p>
<p>
<p>Configures an store to sync secrets using Azure KV.</p>
</p>
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>vaultUrl</code></br>
<em>
string
</em>
</td>
<td>
<p>Vault Url from which the secrets to be fetched from.</p>
</td>
</tr>
<tr>
<td>
<code>tenantId</code></br>
<em>
string
</em>
</td>
<td>
<p>TenantID configures the Azure Tenant to send requests to.</p>
</td>
</tr>
<tr>
<td>
<code>authSecretRef</code></br>
<em>
<a href="#external-secrets.io/v1alpha1.AzureKVAuth">
AzureKVAuth
</a>
</em>
</td>
<td>
<p>Auth configures how the operator authenticates with Azure.</p>
</td>
</tr>
</tbody>
</table>
<h3 id="external-secrets.io/v1alpha1.ClusterSecretStore">ClusterSecretStore <h3 id="external-secrets.io/v1alpha1.ClusterSecretStore">ClusterSecretStore
</h3> </h3>
<p> <p>
@ -1132,6 +1227,20 @@ AWSProvider
</tr> </tr>
<tr> <tr>
<td> <td>
<code>azurekv</code></br>
<em>
<a href="#external-secrets.io/v1alpha1.AzureKVProvider">
AzureKVProvider
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>AzureKV configures this store to sync secrets using Azure Key Vault provider</p>
</td>
</tr>
<tr>
<td>
<code>vault</code></br> <code>vault</code></br>
<em> <em>
<a href="#external-secrets.io/v1alpha1.VaultProvider"> <a href="#external-secrets.io/v1alpha1.VaultProvider">

1
go.mod
View file

@ -56,6 +56,7 @@ require (
github.com/imdario/mergo v0.3.11 // indirect github.com/imdario/mergo v0.3.11 // indirect
github.com/kr/pretty v0.2.1 // indirect github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/lestrrat-go/jwx v1.2.1
github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/onsi/ginkgo v1.16.1 github.com/onsi/ginkgo v1.16.1

26
go.sum
View file

@ -140,6 +140,10 @@ github.com/crossplane/crossplane-runtime v0.13.0/go.mod h1:Bc54/KBvV9ld/tvervcnh
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 h1:sgNeV1VRMDzs6rzyPpxyM0jp317hnwiq58Filgag2xw=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
@ -211,6 +215,8 @@ github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3a
github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
github.com/gobuffalo/flect v0.2.2 h1:PAVD7sp0KOdfswjAw9BpLCU9hXo7wFSzgpQ+zNeks/A= github.com/gobuffalo/flect v0.2.2 h1:PAVD7sp0KOdfswjAw9BpLCU9hXo7wFSzgpQ+zNeks/A=
github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc=
github.com/goccy/go-json v0.4.8 h1:TfwOxfSp8hXH+ivoOk36RyDNmXATUETRdaNWDaZglf8=
github.com/goccy/go-json v0.4.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
@ -404,6 +410,22 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/backoff/v2 v2.0.7 h1:i2SeK33aOFJlUNJZzf2IpXRBvqBBnaGXfY5Xaop/GsE=
github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc=
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/jwx v1.2.1 h1:WJ/3tiPUz1wV24KiwMEanbENwHnYub9UqzCbQ82mv9c=
github.com/lestrrat-go/jwx v1.2.1/go.mod h1:Tg2uP7bpxEHUDtuWjap/PxroJ4okxGzkQznXiG+a5Dc=
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/pdebug/v3 v3.0.1 h1:3G5sX/aw/TbMTtVc9U7IHBWRZtMvwvBziF1e4HoQtv8=
github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -674,6 +696,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
@ -711,6 +734,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -905,8 +929,10 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4 h1:cYSqdOzmV9wJ7lWurRAws06Dmif0Wv6UL4gQLlz+im0= golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4 h1:cYSqdOzmV9wJ7lWurRAws06Dmif0Wv6UL4gQLlz+im0=
golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2-0.20210512205948-8287d5da45e4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=

View file

@ -21,15 +21,22 @@ import (
mock "github.com/stretchr/testify/mock" mock "github.com/stretchr/testify/mock"
) )
type secretData = struct { type secretData struct {
item keyvault.SecretItem item keyvault.SecretItem
secretVersions map[string]keyvault.SecretBundle secretVersions map[string]keyvault.SecretBundle
lastVersion string lastVersion string
} }
type keyData struct {
item keyvault.KeyItem
keyVersions map[string]keyvault.KeyBundle
lastVersion string
}
type AzureMock struct { type AzureMock struct {
mock.Mock mock.Mock
knownSecrets map[string]map[string]*secretData knownSecrets map[string]map[string]*secretData
knownKeys map[string]map[string]*keyData
} }
func (m *AzureMock) AddSecret(vaultBaseURL, secretName, secretContent string, enabled bool) string { func (m *AzureMock) AddSecret(vaultBaseURL, secretName, secretContent string, enabled bool) string {
@ -103,6 +110,75 @@ func (m *AzureMock) ExpectsGetSecretsComplete(ctx context.Context, vaultBaseURL
m.On("GetSecretsComplete", ctx, vaultBaseURL, maxresults).Return(returnValue, nil) m.On("GetSecretsComplete", ctx, vaultBaseURL, maxresults).Return(returnValue, nil)
} }
func (m *AzureMock) AddKey(vaultBaseURL, keyName string, key *keyvault.JSONWebKey, enabled bool) string {
uid := uuid.NewString()
m.AddKeyWithVersion(vaultBaseURL, keyName, uid, key, enabled)
return uid
}
func (m *AzureMock) AddKeyWithVersion(vaultBaseURL, keyName, keyVersion string, key *keyvault.JSONWebKey, enabled bool) {
if m.knownKeys == nil {
m.knownKeys = make(map[string]map[string]*keyData)
}
if m.knownKeys[vaultBaseURL] == nil {
m.knownKeys[vaultBaseURL] = make(map[string]*keyData)
}
keyItemID := vaultBaseURL + keyName
if m.knownKeys[vaultBaseURL][keyName] == nil {
m.knownKeys[vaultBaseURL][keyName] = &keyData{
item: newValidKeyItem(keyItemID, enabled),
keyVersions: make(map[string]keyvault.KeyBundle),
}
} else {
m.knownKeys[vaultBaseURL][keyName].item.Attributes.Enabled = &enabled
}
m.knownKeys[vaultBaseURL][keyName].keyVersions[keyVersion] = newValidKeyBundle(key)
m.knownKeys[vaultBaseURL][keyName].lastVersion = keyVersion
}
func newValidKeyBundle(key *keyvault.JSONWebKey) keyvault.KeyBundle {
return keyvault.KeyBundle{
Key: key,
}
}
func newValidKeyItem(keyItemID string, enabled bool) keyvault.KeyItem {
return keyvault.KeyItem{
Kid: &keyItemID,
Attributes: &keyvault.KeyAttributes{Enabled: &enabled},
}
}
func (m *AzureMock) ExpectsGetKey(ctx context.Context, vaultBaseURL, keyName, keyVersion string) {
data := m.knownKeys[vaultBaseURL][keyName]
version := keyVersion
if version == "" {
version = data.lastVersion
}
returnValue := data.keyVersions[version]
m.On("GetKey", ctx, vaultBaseURL, keyName, keyVersion).Return(returnValue, nil)
}
func (m *AzureMock) ExpectsGetKeysComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) {
keyMap := m.knownKeys[vaultBaseURL]
keyItems := make([]keyvault.KeyItem, len(keyMap))
i := 0
for _, value := range keyMap {
keyItems[i] = value.item
i++
}
firstPage := keyvault.KeyListResult{
Value: &keyItems,
NextLink: nil,
}
returnValue := keyvault.NewKeyListResultIterator(keyvault.NewKeyListResultPage(firstPage, func(context.Context, keyvault.KeyListResult) (keyvault.KeyListResult, error) {
return keyvault.KeyListResult{}, nil
}))
m.On("GetKeysComplete", ctx, vaultBaseURL, maxresults).Return(returnValue, nil)
}
func (m *AzureMock) GetKey(ctx context.Context, vaultBaseURL, keyName, keyVersion string) (result keyvault.KeyBundle, err error) { func (m *AzureMock) GetKey(ctx context.Context, vaultBaseURL, keyName, keyVersion string) (result keyvault.KeyBundle, err error) {
args := m.Called(ctx, vaultBaseURL, keyName, keyVersion) args := m.Called(ctx, vaultBaseURL, keyName, keyVersion)
return args.Get(0).(keyvault.KeyBundle), args.Error(1) return args.Get(0).(keyvault.KeyBundle), args.Error(1)
@ -121,3 +197,8 @@ func (m *AzureMock) GetSecretsComplete(ctx context.Context, vaultBaseURL string,
args := m.Called(ctx, vaultBaseURL, maxresults) args := m.Called(ctx, vaultBaseURL, maxresults)
return args.Get(0).(keyvault.SecretListResultIterator), args.Error(1) return args.Get(0).(keyvault.SecretListResultIterator), args.Error(1)
} }
func (m *AzureMock) GetKeysComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.KeyListResultIterator, err error) {
args := m.Called(ctx, vaultBaseURL, maxresults)
return args.Get(0).(keyvault.KeyListResultIterator), args.Error(1)
}

View file

@ -15,20 +15,14 @@ limitations under the License.
package keyvault package keyvault
import ( import (
"bytes"
"context" "context"
"crypto/rsa" "encoding/json"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt" "fmt"
"math/big"
"path" "path"
"strings" "strings"
"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault" "github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
kvauth "github.com/Azure/go-autorest/autorest/azure/auth" kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
"golang.org/x/crypto/pkcs12"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
@ -94,7 +88,6 @@ func (a *Azure) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretData
version := "" version := ""
objectType := "secret" objectType := "secret"
basicClient := a.baseClient basicClient := a.baseClient
var secretValue []byte // The value of the secret that will be set to the k8s secret object
if ref.Version != "" { if ref.Version != "" {
version = ref.Version version = ref.Version
@ -111,36 +104,33 @@ func (a *Azure) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretData
switch objectType { switch objectType {
case "secret": case "secret":
// returns a SecretBundle with the secret value
// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, version) secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
secretValue = []byte(*secretResp.Value) return []byte(*secretResp.Value), nil
case "cert": case "cert":
// returns a CertBundle. We return CER contents of x509 certificate
// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
secretResp, err := basicClient.GetCertificate(context.Background(), a.vaultURL, secretName, version) secretResp, err := basicClient.GetCertificate(context.Background(), a.vaultURL, secretName, version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
secretValue = *secretResp.Cer return *secretResp.Cer, nil
case "key": case "key":
// returns a KeyBundla that contains a jwk
// azure kv returns only public keys
// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
keyResp, err := basicClient.GetKey(context.Background(), a.vaultURL, secretName, version) keyResp, err := basicClient.GetKey(context.Background(), a.vaultURL, secretName, version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
jwk := *keyResp.Key return json.Marshal(keyResp.Key)
secretValue, err = getPublicKeyFromJwk(jwk)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName)
} }
return secretValue, nil return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName)
} }
// Implements store.Client.GetSecretMap Interface. // Implements store.Client.GetSecretMap Interface.
@ -177,74 +167,6 @@ func (a *Azure) GetSecretMap(ctx context.Context, _ esv1alpha1.ExternalSecretDat
return secretsMap, nil return secretsMap, nil
} }
// getCertBundle returns the certificate bundle.
func getCertBundleForPKCS(certificateRawVal string) (bundle string, err error) {
pfx, err := base64.StdEncoding.DecodeString(certificateRawVal)
if err != nil {
return bundle, err
}
blocks, _ := pkcs12.ToPEM(pfx, "")
for _, block := range blocks {
// no headers
if block.Type == "PRIVATE KEY" {
pkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
derStream := x509.MarshalPKCS1PrivateKey(pkey)
block = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: derStream,
}
}
block.Headers = nil
bundle += string(pem.EncodeToMemory(block))
}
return bundle, nil
}
func getPublicKeyFromJwk(jwk keyvault.JSONWebKey) (bundle []byte, err error) {
if jwk.Kty != "RSA" {
return nil, fmt.Errorf("invalid key type: %s", jwk.Kty)
}
// decode the base64 bytes for n
nb, err := base64.RawURLEncoding.DecodeString(*jwk.N)
if err != nil {
return nil, err
}
e := 0
// The default exponent is usually 65537, so just compare the
// base64 for [1,0,1] or [0,1,0,1]
if *jwk.E == "AQAB" || *jwk.E == "AAEAAQ" {
e = 65537
} else {
// need to decode "e" as a big-endian int
return nil, fmt.Errorf("need to deocde e: %s", *jwk.E)
}
pk := &rsa.PublicKey{
N: new(big.Int).SetBytes(nb),
E: e,
}
der, err := x509.MarshalPKIXPublicKey(pk)
if err != nil {
return nil, err
}
block := &pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: der,
}
var out bytes.Buffer
err = pem.Encode(&out, block)
if err != nil {
return nil, err
}
return out.Bytes(), nil
}
func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, string, error) { func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, string, error) {
spec := *a.store.GetSpec().Provider.AzureKV spec := *a.store.GetSpec().Provider.AzureKV
tenantID := *spec.TenantID tenantID := *spec.TenantID
@ -253,18 +175,18 @@ func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, strin
if spec.AuthSecretRef == nil { if spec.AuthSecretRef == nil {
return nil, "", fmt.Errorf("missing clientID/clientSecret in store config") return nil, "", fmt.Errorf("missing clientID/clientSecret in store config")
} }
scoped := true clusterScoped := false
if a.store.GetObjectMeta().String() == "ClusterSecretStore" { if a.store.GetObjectMeta().String() == esv1alpha1.ClusterSecretStoreKind {
scoped = false clusterScoped = true
} }
if spec.AuthSecretRef.ClientID == nil || spec.AuthSecretRef.ClientSecret == nil { if spec.AuthSecretRef.ClientID == nil || spec.AuthSecretRef.ClientSecret == nil {
return nil, "", fmt.Errorf("missing accessKeyID/secretAccessKey in store config") return nil, "", fmt.Errorf("missing accessKeyID/secretAccessKey in store config")
} }
cid, err := a.secretKeyRef(ctx, a.store.GetNamespacedName(), *spec.AuthSecretRef.ClientID, scoped) cid, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientID, clusterScoped)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
csec, err := a.secretKeyRef(ctx, a.store.GetNamespacedName(), *spec.AuthSecretRef.ClientSecret, scoped) csec, err := a.secretKeyRef(ctx, a.store.GetNamespace(), *spec.AuthSecretRef.ClientSecret, clusterScoped)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -283,18 +205,18 @@ func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, strin
return &basicClient, vaultURL, nil return &basicClient, vaultURL, nil
} }
func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, scoped bool) (string, error) { func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, clusterScoped bool) (string, error) {
var secret corev1.Secret var secret corev1.Secret
ref := types.NamespacedName{ ref := types.NamespacedName{
Namespace: namespace, Namespace: namespace,
Name: secretRef.Name, Name: secretRef.Name,
} }
if !scoped && secretRef.Namespace != nil { if clusterScoped && secretRef.Namespace != nil {
ref.Namespace = *secretRef.Namespace ref.Namespace = *secretRef.Namespace
} }
err := a.kube.Get(ctx, ref, &secret) err := a.kube.Get(ctx, ref, &secret)
if err != nil { if err != nil {
return "", err return "", fmt.Errorf("could not find secret %s/%s: %w", ref.Namespace, ref.Name, err)
} }
keyBytes, ok := secret.Data[secretRef.Key] keyBytes, ok := secret.Data[secretRef.Key]
if !ok { if !ok {

View file

@ -16,9 +16,12 @@ package keyvault
import ( import (
context "context" context "context"
"encoding/json"
"testing" "testing"
"github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault"
tassert "github.com/stretchr/testify/assert" tassert "github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake" clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1" esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
@ -41,6 +44,9 @@ func TestNewClientNoCreds(t *testing.T) {
vaultURL := "https://local.vault.url" vaultURL := "https://local.vault.url"
tenantID := "1234" tenantID := "1234"
store := esv1alpha1.SecretStore{ store := esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
},
Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{ Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{
VaultURL: &vaultURL, VaultURL: &vaultURL,
TenantID: &tenantID, TenantID: &tenantID,
@ -65,10 +71,55 @@ func TestNewClientNoCreds(t *testing.T) {
store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret = &v1.SecretKeySelector{Name: "password"} store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret = &v1.SecretKeySelector{Name: "password"}
secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace) secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
tassert.EqualError(t, err, "secrets \"user\" not found") tassert.EqualError(t, err, "could not find secret internal/user: secrets \"user\" not found")
tassert.Nil(t, secretClient) tassert.Nil(t, secretClient)
} }
const (
jwkPubRSA = `{"kid":"ex","kty":"RSA","key_ops":["sign","verify","wrapKey","unwrapKey","encrypt","decrypt"],"n":"p2VQo8qCfWAZmdWBVaYuYb-a-tWWm78K6Sr9poCvNcmv8rUPSLACxitQWR8gZaSH1DklVkqz-Ed8Cdlf8lkDg4Ex5tkB64jRdC1Uvn4CDpOH6cp-N2s8hTFLqy9_YaDmyQS7HiqthOi9oVjil1VMeWfaAbClGtFt6UnKD0Vb_DvLoWYQSqlhgBArFJi966b4E1pOq5Ad02K8pHBDThlIIx7unibLehhDU6q3DCwNH_OOLx6bgNtmvGYJDd1cywpkLQ3YzNCUPWnfMBJRP3iQP_WI21uP6cvo0DqBPBM4wvVzHbCT0vnIflwkbgEWkq1FprqAitZlop9KjLqzjp9vyQ","e":"AQAB"}`
jwkPubEC = `{"kid":"https://example.vault.azure.net/keys/ec-p-521/e3d0e9c179b54988860c69c6ae172c65","kty":"EC","key_ops":["sign","verify"],"crv":"P-521","x":"AedOAtb7H7Oz1C_cPKI_R4CN_eai5nteY6KFW07FOoaqgQfVCSkQDK22fCOiMT_28c8LZYJRsiIFz_IIbQUW7bXj","y":"AOnchHnmBphIWXvanmMAmcCDkaED6ycW8GsAl9fQ43BMVZTqcTkJYn6vGnhn7MObizmkNSmgZYTwG-vZkIg03HHs"}`
)
func TestGetKey(t *testing.T) {
testAzure, azureMock := newAzure()
ctx := context.Background()
tbl := []struct {
name string
kvName string
jwk *keyvault.JSONWebKey
out string
}{
{
name: "test public rsa key",
kvName: "my-rsa",
jwk: newKVJWK([]byte(jwkPubRSA)),
out: jwkPubRSA,
},
{
name: "test public ec key",
kvName: "my-ec",
jwk: newKVJWK([]byte(jwkPubEC)),
out: jwkPubEC,
},
}
for _, row := range tbl {
t.Run(row.name, func(t *testing.T) {
azureMock.AddKey(testAzure.vaultURL, row.kvName, row.jwk, true)
azureMock.ExpectsGetKey(ctx, testAzure.vaultURL, row.kvName, "")
rf := esv1alpha1.ExternalSecretDataRemoteRef{
Key: "key/" + row.kvName,
}
secret, err := testAzure.GetSecret(ctx, rf)
azureMock.AssertExpectations(t)
tassert.Nil(t, err, "the return err should be nil")
tassert.Equal(t, []byte(row.out), secret)
})
}
}
func TestGetSecretWithVersion(t *testing.T) { func TestGetSecretWithVersion(t *testing.T) {
testAzure, azureMock := newAzure() testAzure, azureMock := newAzure()
ctx := context.Background() ctx := context.Background()
@ -128,19 +179,11 @@ func TestGetSecretMapNotEnabled(t *testing.T) {
tassert.Empty(t, secretMap) tassert.Empty(t, secretMap)
} }
func TestGetCertBundleForPKCS(t *testing.T) { func newKVJWK(b []byte) *keyvault.JSONWebKey {
rawCertExample := "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURC" + var key keyvault.JSONWebKey
"VENDQWUyZ0F3SUJBZ0lFUnIxWTdEQU5CZ2txaGtpRzl3MEJBUVVGQURBeU1Rc3d" + err := json.Unmarshal(b, &key)
"DUVlEVlFRR0V3SkUKUlRFUU1BNEdBMVVFQ2hNSFFXMWhaR1YxY3pFUk1BOEdBMV" + if err != nil {
"VFQXhNSVUwRlFJRkp2YjNRd0hoY05NVE13TWpFMApNVE15TmpRNVdoY05NelV4T" + panic(err)
"WpNeE1UTXlOalE1V2pBeU1Rc3dDUVlEVlFRR0V3SkVSVEVRTUE0R0ExVUVDaE1I" + }
"CnFWUlE3NjNGODFwWnorNXgyejJ6NmZyd0JHNUF3YUZKL1RmTE9HQzZQWnl5bW1" + return &key
"pSlllL2tjUDdVeUhMQnBUUVkKLzloNTF5dDB5NlRBS1JmRk1wMlhuVUZBaWdyL0" +
"0xYVc1NjdORStQYzN5S0RWWlVHdU82UXZ0cExCZkpPS3pZSAowc3F3OElmYjRlN" +
"0R6TkJuTmRoVDhzbGdUYkh5K3RzZUtPb0xHNi9rUktmRmRvSmRoeHAzeGNnbm56" +
"ZkY0anUvCi9UZTRYaWsxNC9FMAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t"
c, ok := getCertBundleForPKCS(rawCertExample)
bundle := ""
tassert.Nil(t, ok)
tassert.Equal(t, c, bundle)
} }

View file

@ -15,6 +15,7 @@ package template
import ( import (
"bytes" "bytes"
"crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
@ -22,6 +23,7 @@ import (
"strings" "strings"
tpl "text/template" tpl "text/template"
"github.com/lestrrat-go/jwx/jwk"
"github.com/youmark/pkcs8" "github.com/youmark/pkcs8"
"golang.org/x/crypto/pkcs12" "golang.org/x/crypto/pkcs12"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -42,6 +44,9 @@ var tplFuncs = tpl.FuncMap{
"fromJSON": fromJSON, "fromJSON": fromJSON,
"toJSON": toJSON, "toJSON": toJSON,
"jwkPublicKeyPem": jwkPublicKeyPem,
"jwkPrivateKeyPem": jwkPrivateKeyPem,
"toString": toString, "toString": toString,
"toBytes": toBytes, "toBytes": toBytes,
"upper": strings.ToUpper, "upper": strings.ToUpper,
@ -119,22 +124,61 @@ func pkcs12cert(input []byte) ([]byte, error) {
return pkcs12certPass("", input) return pkcs12certPass("", input)
} }
func pemPrivateKey(key []byte) (string, error) { func jwkPublicKeyPem(jwkjson []byte) (string, error) {
buf := bytes.NewBuffer(nil) k, err := jwk.ParseKey(jwkjson)
err := pem.Encode(buf, &pem.Block{Type: "PRIVATE KEY", Bytes: key})
if err != nil { if err != nil {
return "", fmt.Errorf(errEncodePEMKey, err) return "", err
} }
var rawkey interface{}
err = k.Raw(&rawkey)
if err != nil {
return "", err
}
mpk, err := x509.MarshalPKIXPublicKey(rawkey)
if err != nil {
return "", err
}
return pemEncode(mpk, "PUBLIC KEY")
}
func jwkPrivateKeyPem(jwkjson []byte) (string, error) {
k, err := jwk.ParseKey(jwkjson)
if err != nil {
return "", err
}
var mpk []byte
var pk interface{}
err = k.Raw(&pk)
if err != nil {
return "", err
}
mpk, err = x509.MarshalPKCS8PrivateKey(pk)
if err != nil {
return "", err
}
return pemEncode(mpk, "PRIVATE KEY")
}
func pemEncode(thing []byte, kind string) (string, error) {
buf := bytes.NewBuffer(nil)
err := pem.Encode(buf, &pem.Block{Type: kind, Bytes: thing})
return buf.String(), err return buf.String(), err
} }
func pemCertificate(cert []byte) (string, error) { func pemPrivateKey(key []byte) (string, error) {
buf := bytes.NewBuffer(nil) res, err := pemEncode(key, "PRIVATE KEY")
err := pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
if err != nil { if err != nil {
return "", fmt.Errorf(errEncodePEMCert, err) return res, fmt.Errorf(errEncodePEMKey, err)
} }
return buf.String(), nil return res, nil
}
func pemCertificate(cert []byte) (string, error) {
res, err := pemEncode(cert, "CERTIFICATE")
if err != nil {
return res, fmt.Errorf(errEncodePEMCert, err)
}
return res, nil
} }
func base64decode(in []byte) ([]byte, error) { func base64decode(in []byte) ([]byte, error) {

View file

@ -74,6 +74,62 @@ qrWPv06wXD8dW2FlPpY5GXqA0l6erSK3YsQQToRmbem9ibPD7bd5P4gNbWfxwK4C
Wt+1Rcb8OrDhDJbYz85bXBnPecKp4EN0b9SHO0/dsCqn2w30emc+9T/4m1ZDkpBd Wt+1Rcb8OrDhDJbYz85bXBnPecKp4EN0b9SHO0/dsCqn2w30emc+9T/4m1ZDkpBd
BixHvI/EJ8YK3ta5WdJWKC6hnA== BixHvI/EJ8YK3ta5WdJWKC6hnA==
-----END PRIVATE KEY----- -----END PRIVATE KEY-----
`
jwkPubRSA = `{"kid":"ex","kty":"RSA","key_ops":["sign","verify","wrapKey","unwrapKey","encrypt","decrypt"],"n":"p2VQo8qCfWAZmdWBVaYuYb-a-tWWm78K6Sr9poCvNcmv8rUPSLACxitQWR8gZaSH1DklVkqz-Ed8Cdlf8lkDg4Ex5tkB64jRdC1Uvn4CDpOH6cp-N2s8hTFLqy9_YaDmyQS7HiqthOi9oVjil1VMeWfaAbClGtFt6UnKD0Vb_DvLoWYQSqlhgBArFJi966b4E1pOq5Ad02K8pHBDThlIIx7unibLehhDU6q3DCwNH_OOLx6bgNtmvGYJDd1cywpkLQ3YzNCUPWnfMBJRP3iQP_WI21uP6cvo0DqBPBM4wvVzHbCT0vnIflwkbgEWkq1FprqAitZlop9KjLqzjp9vyQ","e":"AQAB"}`
jwkPubRSAPKIX = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp2VQo8qCfWAZmdWBVaYu
Yb+a+tWWm78K6Sr9poCvNcmv8rUPSLACxitQWR8gZaSH1DklVkqz+Ed8Cdlf8lkD
g4Ex5tkB64jRdC1Uvn4CDpOH6cp+N2s8hTFLqy9/YaDmyQS7HiqthOi9oVjil1VM
eWfaAbClGtFt6UnKD0Vb/DvLoWYQSqlhgBArFJi966b4E1pOq5Ad02K8pHBDThlI
Ix7unibLehhDU6q3DCwNH/OOLx6bgNtmvGYJDd1cywpkLQ3YzNCUPWnfMBJRP3iQ
P/WI21uP6cvo0DqBPBM4wvVzHbCT0vnIflwkbgEWkq1FprqAitZlop9KjLqzjp9v
yQIDAQAB
-----END PUBLIC KEY-----
`
jwkPrivRSA = `{"kty" : "RSA","kid" : "cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df","use" : "sig","n" : "pjdss8ZaDfEH6K6U7GeW2nxDqR4IP049fk1fK0lndimbMMVBdPv_hSpm8T8EtBDxrUdi1OHZfMhUixGaut-3nQ4GG9nM249oxhCtxqqNvEXrmQRGqczyLxuh-fKn9Fg--hS9UpazHpfVAFnB5aCfXoNhPuI8oByyFKMKaOVgHNqP5NBEqabiLftZD3W_lsFCPGuzr4Vp0YS7zS2hDYScC2oOMu4rGU1LcMZf39p3153Cq7bS2Xh6Y-vw5pwzFYZdjQxDn8x8BG3fJ6j8TGLXQsbKH1218_HcUJRvMwdpbUQG5nvA2GXVqLqdwp054Lzk9_B_f1lVrmOKuHjTNHq48w","e" : "AQAB","d" : "ksDmucdMJXkFGZxiomNHnroOZxe8AmDLDGO1vhs-POa5PZM7mtUPonxwjVmthmpbZzla-kg55OFfO7YcXhg-Hm2OWTKwm73_rLh3JavaHjvBqsVKuorX3V3RYkSro6HyYIzFJ1Ek7sLxbjDRcDOj4ievSX0oN9l-JZhaDYlPlci5uJsoqro_YrE0PRRWVhtGynd-_aWgQv1YzkfZuMD-hJtDi1Im2humOWxA4eZrFs9eG-whXcOvaSwO4sSGbS99ecQZHM2TcdXeAs1PvjVgQ_dKnZlGN3lTWoWfQP55Z7Tgt8Nf1q4ZAKd-NlMe-7iqCFfsnFwXjSiaOa2CRGZn-Q","p" : "4A5nU4ahEww7B65yuzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ--wwfpRwHvSxtNU9qXb8ewo-BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3InKF4JvIlchyqs0RQ8wx7lULqwnn0","q" : "ven83GM6SfrmO-TBHbjTk6JhP_3CMsIvmSdo4KrbQNvp4vHO3w1_0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEBpxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA-k4UoH_eQmGKGK44TRzYj5hZYGWIC8","dp" : "lmmU_AG5SGxBhJqb8wxfNXDPJjf__i92BgJT2Vp4pskBbr5PGoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ-m0_XSWx13v9t9DIbheAtgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpE","dq" : "mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe__EjuCBbwHfcT8OG3hWOv8vpzokQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p-AF2p6Yfahscjtq-GY9cB85NxLy2IXCC0PF--Sq9LOrTE9QV988SJy_yUrAjcZ5MmECk","qi" : "ldHXIrEmMZVaNwGzDF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uYiqewXfCKw_UngrJt8Xwfq1Zruz0YY869zPN4GiE9-9rzdZB33RBw8kIOquY3MK74FMwCihYx_LiU2YTHkaoJ3ncvtvg"}`
jwkPrivRSAPKCS8 = `-----BEGIN PRIVATE KEY-----
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQCmN2yzxloN8Qfo
rpTsZ5bafEOpHgg/Tj1+TV8rSWd2KZswxUF0+/+FKmbxPwS0EPGtR2LU4dl8yFSL
EZq637edDgYb2czbj2jGEK3Gqo28ReuZBEapzPIvG6H58qf0WD76FL1SlrMel9UA
WcHloJ9eg2E+4jygHLIUowpo5WAc2o/k0ESppuIt+1kPdb+WwUI8a7OvhWnRhLvN
LaENhJwLag4y7isZTUtwxl/f2nfXncKrttLZeHpj6/DmnDMVhl2NDEOfzHwEbd8n
qPxMYtdCxsofXbXz8dxQlG8zB2ltRAbme8DYZdWoup3CnTngvOT38H9/WVWuY4q4
eNM0erjzAgMBAAECggEBAJLA5rnHTCV5BRmcYqJjR566DmcXvAJgywxjtb4bPjzm
uT2TO5rVD6J8cI1ZrYZqW2c5WvpIOeThXzu2HF4YPh5tjlkysJu9/6y4dyWr2h47
warFSrqK191d0WJEq6Oh8mCMxSdRJO7C8W4w0XAzo+Inr0l9KDfZfiWYWg2JT5XI
ubibKKq6P2KxND0UVlYbRsp3fv2loEL9WM5H2bjA/oSbQ4tSJtobpjlsQOHmaxbP
XhvsIV3Dr2ksDuLEhm0vfXnEGRzNk3HV3gLNT741YEP3Sp2ZRjd5U1qFn0D+eWe0
4LfDX9auGQCnfjZTHvu4qghX7JxcF40omjmtgkRmZ/kCgYEA4A5nU4ahEww7B65y
uzmGeCUUi8ikWzv1C81pSyUKvKzu8CX41hp9J6oRaLGesKImYiuVQK47FhZ++wwf
pRwHvSxtNU9qXb8ewo+BvadyO1eVrIk4tNV543QlSe7pQAoJGkxCia5rfznAE3In
KF4JvIlchyqs0RQ8wx7lULqwnn0CgYEAven83GM6SfrmO+TBHbjTk6JhP/3CMsIv
mSdo4KrbQNvp4vHO3w1/0zJ3URkmkYGhz2tgPlfd7v1l2I6QkIh4Bumdj6FyFZEB
pxjE4MpfdNVcNINvVj87cLyTRmIcaGxmfylY7QErP8GFA+k4UoH/eQmGKGK44TRz
Yj5hZYGWIC8CgYEAlmmU/AG5SGxBhJqb8wxfNXDPJjf//i92BgJT2Vp4pskBbr5P
GoyV0HbfUQVMnw977RONEurkR6O6gxZUeCclGt4kQlGZ+m0/XSWx13v9t9DIbheA
tgVJ2mQyVDvK4m7aRYlEceFh0PsX8vYDS5o1txgPwb3oXkPTtrmbAGMUBpECgYEA
mxRTU3QDyR2EnCv0Nl0TCF90oliJGAHR9HJmBe//EjuCBbwHfcT8OG3hWOv8vpzo
kQPRl5cQt3NckzX3fs6xlJN4Ai2Hh2zduKFVQ2p+AF2p6Yfahscjtq+GY9cB85Nx
Ly2IXCC0PF++Sq9LOrTE9QV988SJy/yUrAjcZ5MmECkCgYEAldHXIrEmMZVaNwGz
DF9WG8sHj2mOZmQpw9yrjLK9hAsmsNr5LTyqWAqJIYZSwPTYWhY4nu2O0EY9G9uY
iqewXfCKw/UngrJt8Xwfq1Zruz0YY869zPN4GiE9+9rzdZB33RBw8kIOquY3MK74
FMwCihYx/LiU2YTHkaoJ3ncvtvg=
-----END PRIVATE KEY-----
`
jwkPubEC = `{"kid":"https://kv-test-mj.vault.azure.net/keys/ec-p-521/e3d0e9c179b54988860c69c6ae172c65","kty":"EC","key_ops":["sign","verify"],"crv":"P-521","x":"AedOAtb7H7Oz1C_cPKI_R4CN_eai5nteY6KFW07FOoaqgQfVCSkQDK22fCOiMT_28c8LZYJRsiIFz_IIbQUW7bXj","y":"AOnchHnmBphIWXvanmMAmcCDkaED6ycW8GsAl9fQ43BMVZTqcTkJYn6vGnhn7MObizmkNSmgZYTwG-vZkIg03HHs"}`
jwkPubECPKIX = `-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB504C1vsfs7PUL9w8oj9HgI395qLm
e15jooVbTsU6hqqBB9UJKRAMrbZ8I6IxP/bxzwtlglGyIgXP8ghtBRbtteMA6dyE
eeYGmEhZe9qeYwCZwIORoQPrJxbwawCX19DjcExVlOpxOQlifq8aeGfsw5uLOaQ1
KaBlhPAb69mQiDTccew=
-----END PUBLIC KEY-----
`
jwkPrivEC = `{"kty": "EC","kid": "rie3pHe8u8gjSa0IaJfqk7_iEfHeYfDYx-Bqi7vQc0s","crv": "P-256","x": "fDjg3Nq4jPf8IOZ0277aPVal_8iXySnzLUJAZghUzZM","y": "d863PeyBOK_Q4duiSmWwgIRzi1RPlFZTR-vACMlPg-Q","d": "jJs5xsoHUetdMabtt8H2KyX5T92nGul1chFeMT5hlr0"}`
jwkPrivECPKCS8 = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjJs5xsoHUetdMabt
t8H2KyX5T92nGul1chFeMT5hlr2hRANCAAR8OODc2riM9/wg5nTbvto9VqX/yJfJ
KfMtQkBmCFTNk3fOtz3sgTiv0OHbokplsICEc4tUT5RWU0frwAjJT4Pk
-----END PRIVATE KEY-----
` `
) )
@ -237,6 +293,54 @@ func TestExecute(t *testing.T) {
data: map[string][]byte{}, data: map[string][]byte{},
expErr: "unable to parse template", expErr: "unable to parse template",
}, },
{
name: "jwk rsa pub pem",
tpl: &esv1alpha1.ExternalSecretTemplate{Data: map[string]string{
"fn": `{{ .secret | jwkPublicKeyPem }}`,
}},
data: map[string][]byte{
"secret": []byte(jwkPubRSA),
},
expetedData: map[string][]byte{
"fn": []byte(jwkPubRSAPKIX),
},
},
{
name: "jwk rsa priv pem",
tpl: &esv1alpha1.ExternalSecretTemplate{Data: map[string]string{
"fn": `{{ .secret | jwkPrivateKeyPem }}`,
}},
data: map[string][]byte{
"secret": []byte(jwkPrivRSA),
},
expetedData: map[string][]byte{
"fn": []byte(jwkPrivRSAPKCS8),
},
},
{
name: "jwk ecdsa pub pem",
tpl: &esv1alpha1.ExternalSecretTemplate{Data: map[string]string{
"fn": `{{ .secret | jwkPublicKeyPem }}`,
}},
data: map[string][]byte{
"secret": []byte(jwkPubEC),
},
expetedData: map[string][]byte{
"fn": []byte(jwkPubECPKIX),
},
},
{
name: "jwk ecdsa priv pem",
tpl: &esv1alpha1.ExternalSecretTemplate{Data: map[string]string{
"fn": `{{ .secret | jwkPrivateKeyPem }}`,
}},
data: map[string][]byte{
"secret": []byte(jwkPrivEC),
},
expetedData: map[string][]byte{
"fn": []byte(jwkPrivECPKCS8),
},
},
} }
for i := range tbl { for i := range tbl {