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

Merge branch 'main' into feature/conversion-webhook

Fixed conflicts and implemented necessary changes for v1beta1
This commit is contained in:
Gustavo Carvalho 2022-02-16 16:00:32 -03:00
commit 40ec693479
63 changed files with 2235 additions and 145 deletions

View file

@ -49,7 +49,7 @@ jobs:
steps:
# create new status check for this specific provider
- uses: actions/github-script@v1
- uses: actions/github-script@v6
if: ${{ always() }}
env:
number: ${{ github.event.client_payload.pull_request.number }}
@ -58,13 +58,13 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { data: pull } = await github.pulls.get({
const { data: pull } = await github.rest.pulls.get({
...context.repo,
pull_number: process.env.number
});
const ref = pull.head.sha;
console.log("\n\nPR sha: " + ref)
const { data: checks } = await github.checks.listForRef({
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref
});
@ -74,14 +74,14 @@ jobs:
console.log("\n\nPR Filtered CHECK: " + check)
console.log(check)
if(check && check.length > 0){
const { data: result } = await github.checks.update({
const { data: result } = await github.rest.checks.update({
...context.repo,
check_run_id: check[0].id,
status: 'in_progress',
});
return result;
}
const { data: result } = await github.checks.create({
const { data: result } = await github.rest.checks.create({
...context.repo,
name: job_name,
head_sha: pull.head.sha,
@ -201,7 +201,7 @@ jobs:
make tf.destroy.${PROVIDER}
# set status=completed
- uses: actions/github-script@v1
- uses: actions/github-script@v6
if: ${{ always() }}
env:
number: ${{ github.event.client_payload.pull_request.number }}
@ -212,13 +212,13 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { data: pull } = await github.pulls.get({
const { data: pull } = await github.rest.pulls.get({
...context.repo,
pull_number: process.env.number
});
const ref = pull.head.sha;
console.log("\n\nPR sha: " + ref)
const { data: checks } = await github.checks.listForRef({
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref
});
@ -227,7 +227,7 @@ jobs:
const check = checks.check_runs.filter(c => c.name === job_name);
console.log("\n\nPR Filtered CHECK: " + check)
console.log(check)
const { data: result } = await github.checks.update({
const { data: result } = await github.rest.checks.update({
...context.repo,
check_run_id: check[0].id,
status: 'completed',

View file

@ -156,7 +156,7 @@ jobs:
make test.e2e
# Update check run called "integration-fork"
- uses: actions/github-script@v1
- uses: actions/github-script@v6
id: update-check-run
if: ${{ always() }}
env:
@ -167,13 +167,13 @@ jobs:
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { data: pull } = await github.pulls.get({
const { data: pull } = await github.rest.pulls.get({
...context.repo,
pull_number: process.env.number
});
const ref = pull.head.sha;
console.log("\n\nPR sha: " + ref)
const { data: checks } = await github.checks.listForRef({
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref
});
@ -181,7 +181,7 @@ jobs:
const check = checks.check_runs.filter(c => c.name === process.env.job);
console.log("\n\nPR Filtered CHECK: " + check)
console.log(check)
const { data: result } = await github.checks.update({
const { data: result } = await github.rest.checks.update({
...context.repo,
check_run_id: check[0].id,
status: 'completed',

View file

@ -2,6 +2,7 @@
<!-- Add yourself here if you are using ESO in your company or your project! -->
- [Polarpoint](https://www.polarpoint.io/)
- [Pento](https://www.pento.io/)
- [Mixpanel](https://mixpanel.com)
- [K8S Website Infra](https://k8s.io/)

View file

@ -196,8 +196,8 @@ docs: generate ## Generate docs
docs.publish: generate ## Generate and deploys docs
$(MAKE) -C ./hack/api-docs build.publish
.PHONY: serve-docs
serve-docs: ## Serve docs
.PHONY: docs.serve
docs.serve: ## Serve docs
$(MAKE) -C ./hack/api-docs serve
# ====================================================================================

View file

@ -30,8 +30,8 @@ Multiple people and organizations are joining efforts to create a single Externa
| Provider | Stability | Contact |
| ------------------------------------------------------------------------ | :-------: | ---------------------------------------------: |
| [AWS SM](https://external-secrets.io/provider-aws-secrets-manager/) | beta | [ESO Org](https://github.com/external-secrets) |
| [AWS PS](https://external-secrets.io/provider-aws-parameter-store/) | beta | [ESO Org](https://github.com/external-secrets) |
| [AWS SM](https://external-secrets.io/provider-aws-secrets-manager/) | stable | [ESO Org](https://github.com/external-secrets) |
| [AWS PS](https://external-secrets.io/provider-aws-parameter-store/) | stable | [ESO Org](https://github.com/external-secrets) |
| [Hashicorp Vault](https://external-secrets.io/provider-hashicorp-vault/) | stable | [ESO Org](https://github.com/external-secrets) |
| [GCP SM](https://external-secrets.io/provider-google-secrets-manager/) | stable | [ESO Org](https://github.com/external-secrets) |
@ -39,7 +39,7 @@ Multiple people and organizations are joining efforts to create a single Externa
| Provider | Stability | Contact |
| ------------------------------------------------------------------- | :-------: | :----------------------------------------: |
| [Azure KV](https://external-secrets.io/provider-azure-key-vault/) | alpha | [@ahmedmus-1A](https://github.com/ahmedmus-1A) [@asnowfix](https://github.com/asnowfix) [@ncourbet-1A](https://github.com/ncourbet-1A) [@1A-mj](https://github.com/1A-mj) |
| [Azure KV](https://external-secrets.io/provider-azure-key-vault/) | beta | [@ahmedmus-1A](https://github.com/ahmedmus-1A) [@asnowfix](https://github.com/asnowfix) [@ncourbet-1A](https://github.com/ncourbet-1A) [@1A-mj](https://github.com/1A-mj) |
| [IBM SM](https://external-secrets.io/provider-ibm-secrets-manager/) | alpha | [@knelasevero](https://github.com/knelasevero) [@sebagomez](https://github.com/sebagomez) [@ricardoptcosta](https://github.com/ricardoptcosta) |
| [Yandex Lockbox](https://external-secrets.io/provider-yandex-lockbox/) | alpha | [@AndreyZamyslov](https://github.com/AndreyZamyslov) [@knelasevero](https://github.com/knelasevero) |
| [Gitlab Project Variables](https://external-secrets.io/provider-gitlab-project-variables/) | alpha | [@Jabray5](https://github.com/Jabray5) |

View file

@ -59,6 +59,12 @@ type ExternalSecretTemplate struct {
// +optional
Type corev1.SecretType `json:"type,omitempty"`
// EngineVersion specifies the template engine version
// that should be used to compile/execute the
// template specified in .data and .templateFrom[].
// +kubebuilder:default="v1"
EngineVersion TemplateEngineVersion `json:"engineVersion,omitempty"`
// +optional
Metadata ExternalSecretTemplateMetadata `json:"metadata,omitempty"`
@ -69,6 +75,13 @@ type ExternalSecretTemplate struct {
TemplateFrom []TemplateFrom `json:"templateFrom,omitempty"`
}
type TemplateEngineVersion string
const (
TemplateEngineV1 TemplateEngineVersion = "v1"
TemplateEngineV2 TemplateEngineVersion = "v2"
)
// +kubebuilder:validation:MinProperties=1
// +kubebuilder:validation:MaxProperties=1
type TemplateFrom struct {

View file

@ -44,6 +44,20 @@ const (
None ExternalSecretCreationPolicy = "None"
)
// ExternalSecretDeletionPolicy defines rules on how to delete the resulting Secret.
type ExternalSecretDeletionPolicy string
const (
// Owner creates the Secret and sets .metadata.ownerReferences to the ExternalSecret resource.
DeletionOwner ExternalSecretDeletionPolicy = "Owner"
// Merge does not create the Secret, but merges the data fields to the Secret.
DeletionMerge ExternalSecretDeletionPolicy = "Merge"
// None does not create a Secret (future use with injector).
DeletionNone ExternalSecretDeletionPolicy = "None"
)
// ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint.
type ExternalSecretTemplateMetadata struct {
// +optional
@ -59,6 +73,12 @@ type ExternalSecretTemplate struct {
// +optional
Type corev1.SecretType `json:"type,omitempty"`
// EngineVersion specifies the template engine version
// that should be used to compile/execute the
// template specified in .data and .templateFrom[].
// +kubebuilder:default="v2"
EngineVersion TemplateEngineVersion `json:"engineVersion,omitempty"`
// +optional
Metadata ExternalSecretTemplateMetadata `json:"metadata,omitempty"`
@ -69,6 +89,13 @@ type ExternalSecretTemplate struct {
TemplateFrom []TemplateFrom `json:"templateFrom,omitempty"`
}
type TemplateEngineVersion string
const (
TemplateEngineV1 TemplateEngineVersion = "v1"
TemplateEngineV2 TemplateEngineVersion = "v2"
)
// +kubebuilder:validation:MinProperties=1
// +kubebuilder:validation:MaxProperties=1
type TemplateFrom struct {
@ -99,7 +126,11 @@ type ExternalSecretTarget struct {
// +optional
// +kubebuilder:default="Owner"
CreationPolicy ExternalSecretCreationPolicy `json:"creationPolicy,omitempty"`
// DeletionPolicy defines rules on how to delete the resulting Secret
// Defaults to 'None'
// +optional
// +kubebuilder:default="None"
DeletionPolicy ExternalSecretDeletionPolicy `json:"deletionPolicy,omitempty"`
// Template defines a blueprint for the created Secret resource.
// +optional
Template *ExternalSecretTemplate `json:"template,omitempty"`

View file

@ -148,6 +148,12 @@ spec:
additionalProperties:
type: string
type: object
engineVersion:
default: v1
description: EngineVersion specifies the template engine version
that should be used to compile/execute the template specified
in .data and .templateFrom[].
type: string
metadata:
description: ExternalSecretTemplateMetadata defines metadata
fields for the Secret blueprint.
@ -381,6 +387,11 @@ spec:
description: CreationPolicy defines rules on how to create the
resulting Secret Defaults to 'Owner'
type: string
deletionPolicy:
default: None
description: DeletionPolicy defines rules on how to delete the
resulting Secret Defaults to 'None'
type: string
immutable:
description: Immutable defines if the final secret will be immutable
type: boolean
@ -397,6 +408,9 @@ spec:
additionalProperties:
type: string
type: object
engineVersion:
default: v2
type: string
metadata:
description: ExternalSecretTemplateMetadata defines metadata
fields for the Secret blueprint.

View file

@ -1859,6 +1859,10 @@ spec:
additionalProperties:
type: string
type: object
engineVersion:
default: v1
description: EngineVersion specifies the template engine version that should be used to compile/execute the template specified in .data and .templateFrom[].
type: string
metadata:
description: ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint.
properties:
@ -2068,6 +2072,10 @@ spec:
default: Owner
description: CreationPolicy defines rules on how to create the resulting Secret Defaults to 'Owner'
type: string
deletionPolicy:
default: None
description: DeletionPolicy defines rules on how to delete the resulting Secret Defaults to 'None'
type: string
immutable:
description: Immutable defines if the final secret will be immutable
type: boolean
@ -2081,6 +2089,9 @@ spec:
additionalProperties:
type: string
type: object
engineVersion:
default: v2
type: string
metadata:
description: ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint.
properties:

View file

@ -0,0 +1,50 @@
# Advanced Templating v1
!!! warning
Templating Engine v1 is **deprecated** and will be removed in the future. Please migrate to engine v2 and take a look at our [upgrade guide](guides-templating.md#migrating-from-v1) for changes.
With External Secrets Operator you can transform the data from the external secret provider before it is stored as `Kind=Secret`. You can do this with the `Spec.Target.Template`. Each data value is interpreted as a [golang template](https://golang.org/pkg/text/template/).
## Examples
You can use templates to inject your secrets into a configuration file that you mount into your pod:
``` yaml
{% include 'multiline-template-v1-external-secret.yaml' %}
```
You can also use pre-defined functions to extract data from your secrets. Here: extract key/cert from a pkcs12 archive and store it as PEM.
``` yaml
{% include 'pkcs12-template-v1-external-secret.yaml' %}
```
### TemplateFrom
You do not have to define your templates inline in an ExternalSecret but you can pull `ConfigMaps` or other Secrets that contain a template. Consider the following example:
``` yaml
{% include 'template-v1-from-secret.yaml' %}
```
## Helper functions
We provide a bunch of convenience functions that help you transform your secrets. A secret value is a `[]byte`.
| Function | Description | Input | Output |
| -------------- | -------------------------------------------------------------------------- | -------------------------------- | ------------- |
| pkcs12key | extracts the private key from a pkcs12 archive | `[]byte` | `[]byte` |
| pkcs12keyPass | extracts the private key from a pkcs12 archive using the provided password | password `string`, data `[]byte` | `[]byte` |
| pkcs12cert | extracts the certificate from a pkcs12 archive | `[]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` |
| 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` |
| base64encode | encodes the provided bytes as base64 | `[]byte` | `[]byte` |
| fromJSON | parses the bytes as JSON so you can access individual properties | `[]byte` | `interface{}` |
| toJSON | encodes the provided object as json string | `interface{}` | `string` |
| toString | converts bytes to string | `[]byte` | `string` |
| toBytes | converts string to bytes | `string` | `[]byte` |
| upper | converts all characters to their upper case | `string` | `string` |
| lower | converts all character to their lower case | `string` | `string` |

View file

@ -1,43 +1,145 @@
# Advanced Templating v2
With External Secrets Operator you can transform the data from the external secret provider before it is stored as `Kind=Secret`. You can do this with the `Spec.Target.Template`. Each data value is interpreted as a [golang template](https://golang.org/pkg/text/template/).
## Examples
You can use templates to inject your secrets into a configuration file that you mount into your pod:
``` yaml
{% include 'multiline-template-external-secret.yaml' %}
```
You can also use pre-defined functions to extract data from your secrets. Here: extract key/cert from a pkcs12 archive and store it as PEM.
``` yaml
{% include 'pkcs12-template-external-secret.yaml' %}
```yaml
{% include 'multiline-template-v2-external-secret.yaml' %}
```
### TemplateFrom
You do not have to define your templates inline in an ExternalSecret but you can pull `ConfigMaps` or other Secrets that contain a template. Consider the following example:
``` yaml
{% include 'template-from-secret.yaml' %}
```yaml
{% include 'template-v2-from-secret.yaml' %}
```
### Extract Keys and Certificates from PKCS#12 Archive
You can use pre-defined functions to extract data from your secrets. Here: extract keys and certificates from a PKCS#12 archive and store it as PEM.
```yaml
{% include 'pkcs12-template-v2-external-secret.yaml' %}
```
### Extract from JWK
You can extract the public or private key parts of a JWK and use them as [PKCS#8](https://pkg.go.dev/crypto/x509#ParsePKCS8PrivateKey) private key or PEM-encoded [PKIX](https://pkg.go.dev/crypto/x509#MarshalPKIXPublicKey) public key.
A JWK looks similar to this:
```json
{
"kty": "RSA",
"kid": "cc34c0a0-bd5a-4a3c-a50d-a2a7db7643df",
"use": "sig",
"n": "pjdss...",
"e": "AQAB"
// ...
}
```
And what you want may be a PEM-encoded public or private key portion of it. Take a look at this example on how to transform it into the desired format:
```yaml
{% include 'jwk-template-v2-external-secret.yaml' %}
```
### Filter PEM blocks
Consider you have a secret that contains both a certificate and a private key encoded in PEM format and it is your goal to use only the certificate from that secret.
```
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvxGZOW4IXvGlh
. . .
m8JCpbJXDfSSVxKHgK1Siw4K6pnTsIA2e/Z+Ha2fvtocERjq7VQMAJFaIZSTKo9Q
JwwY+vj0yxWjyzHUzZB33tg=
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDMDCCAhigAwIBAgIQabPaXuZCQaCg+eQAVptGGDANBgkqhkiG9w0BAQsFADAV
. . .
NtFUGA95RGN9s+pl6XY0YARPHf5O76ErC1OZtDTR5RdyQfcM+94gYZsexsXl0aQO
9YD3Wg==
-----END CERTIFICATE-----
```
You can achieve that by using the `filterPEM` function to extract a specific type of PEM block from that secret. If multiple blocks of that type (here: `CERTIFICATE`) exist then all of them are returned in the order they are specified.
```yaml
{% include 'pem-filter-template-v2-external-secret.yaml' %}
```
## Helper functions
We provide a bunch of convenience functions that help you transform your secrets. A secret value is a `[]byte`.
| Function | Description | Input | Output |
| -------------- | -------------------------------------------------------------------------- | -------------------------------- | ------------- |
| pkcs12key | extracts the private key from a pkcs12 archive | `[]byte` | `[]byte` |
| pkcs12keyPass | extracts the private key from a pkcs12 archive using the provided password | password `string`, data `[]byte` | `[]byte` |
| pkcs12cert | extracts the certificate from a pkcs12 archive | `[]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` |
| 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` |
| base64encode | encodes the provided bytes as base64 | `[]byte` | `[]byte` |
| fromJSON | parses the bytes as JSON so you can access individual properties | `[]byte` | `interface{}` |
| toJSON | encodes the provided object as json string | `interface{}` | `string` |
| toString | converts bytes to string | `[]byte` | `string` |
| toBytes | converts string to bytes | `string` | `[]byte` |
| upper | converts all characters to their upper case | `string` | `string` |
| lower | converts all character to their lower case | `string` | `string` |
!!! info inline end
Note: we removed `env` and `expandenv` from sprig functions for security reasons.
We provide a couple of convenience functions that help you transform your secrets. This is useful when dealing with PKCS#12 archives or JSON Web Keys (JWK).
In addition to that you can use over 200+ [sprig functions](http://masterminds.github.io/sprig/). If you feel a function is missing or might be valuable feel free to open an issue and submit a [pull request](contributing-process.md#submitting-a-pull-request).
<br/>
| Function | Description |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| pkcs12key | Extracts all private keys from a PKCS#12 archive and encodes them in **PKCS#8 PEM** format. |
| pkcs12keyPass | Same as `pkcs12key`. Uses the provided password to decrypt the PKCS#12 archive. |
| pkcs12cert | Extracts all certificates from a PKCS#12 archive and orders them if possible. If disjunct or multiple leaf certs are provided they are returned as-is. <br/> Sort order: `leaf / intermediate(s) / root`. |
| pkcs12certPass | Same as `pkcs12cert`. Uses the provided password to decrypt the PKCS#12 archive. |
| filterPEM | Filters PEM blocks with a specific type from a list of PEM blocks. |
| jwkPublicKeyPem | Takes an json-serialized JWK 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. |
| jwkPrivateKeyPem | Takes an json-serialized JWK as `string` 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. |
## Migrating from v1
You have to opt-in to use the new engine version by specifying `template.engineVersion=v2`:
```yaml
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: secret
spec:
# ...
target:
template:
engineVersion: v2
# ...
```
The biggest change was that basically all function parameter types were changed from accepting/returning `[]byte` to `string`. This is relevant for you because now you don't need to specify `toString` all the time at the end of a template pipeline.
```yaml
{% raw %}
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
# ...
spec:
target:
template:
engineVersion: v2
data:
# this used to be {{ .foobar | toString }}
egg: "new: {{ .foobar }}"
{% endraw %}
```
##### Functions removed/replaced
- `base64encode` was renamed to `b64enc`.
- `base64decode` was renamed to `b64dec`. Any errors that occurr during decoding are silenced.
- `fromJSON` was renamed to `fromJson`. Any errors that occurr during unmarshalling are silenced.
- `toJSON` was renamed to `toJson`. Any errors that occurr during marshalling are silenced.
- `pkcs12key` and `pkcs12keyPass` encode the PKCS#8 key directly into PEM format. There is no need to call `pemPrivateKey` anymore. Also, these functions do extract all private keys from the PKCS#12 archive not just the first one.
- `pkcs12cert` and `pkcs12certPass` encode the certs directly into PEM format. There is no need to call `pemCertificate` anymore. These functions now **extract all certificates** from the PKCS#12 archive not just the first one.
- `toString` implementation was replaced by the `sprig` implementation and should be api-compatible.
- `toBytes` was removed.
- `pemPrivateKey` was removed. It's now implemented within the `pkcs12*` functions.
- `pemCertificate` was removed. It's now implemented within the `pkcs12*` functions.

View file

@ -0,0 +1,25 @@
{% raw %}
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: template
spec:
# ...
target:
template:
engineVersion: v2
data:
# .myjwk is a json-encoded JWK string.
#
# this template will produce for jwk_pub a PEM encoded public key:
# -----BEGIN PUBLIC KEY-----
# MIIBI...
# ...
# ...AQAB
# -----END PUBLIC KEY-----
jwk_pub: "{{ .myjwk | jwkPublicKeyPem }}"
# private key is a pem-encoded PKCS#8 private key
jwk_priv: "{{ .myjwk | jwkPrivateKeyPem }}"
{% endraw %}

View file

@ -10,6 +10,10 @@ spec:
kind: SecretStore
target:
name: secret-to-be-created
# v1 is the default version
engineVersion: v1
# this is how the Kind=Secret will look like
template:
type: kubernetes.io/tls

View file

@ -0,0 +1,32 @@
{% raw %}
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: template
spec:
# ...
target:
name: secret-to-be-created
# this is how the Kind=Secret will look like
template:
type: kubernetes.io/tls
engineVersion: v2
data:
# multiline string
config: |
datasources:
- name: Graphite
type: graphite
access: proxy
url: http://localhost:8080
password: "{{ .password }}"
user: "{{ .user }}"
data:
- secretKey: user
remoteRef:
key: /grafana/user
- secretKey: password
remoteRef:
key: /grafana/password
{% endraw %}

View file

@ -0,0 +1,19 @@
{% raw %}
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: template
spec:
# ...
target:
template:
type: kubernetes.io/tls
engineVersion: v2
data:
tls.crt: "{{ .mysecret | pkcs12cert }}"
tls.key: "{{ .mysecret | pkcs12key }}"
# if needed unlock the pkcs12 with the password
tls.crt: "{{ .mysecret | pkcs12certPass "my-password" }}"
{% endraw %}

View file

@ -0,0 +1,41 @@
{% raw %}
# define your template in a config map
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-config-tpl
data:
config.yaml: |
datasources:
- name: Graphite
type: graphite
access: proxy
url: http://localhost:8080
password: "{{ .password }}"
user: "{{ .user }}"
---
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
name: my-template-example
spec:
# ...
target:
name: secret-to-be-created
template:
engineVersion: v2
templateFrom:
- configMap:
# name of the configmap to pull in
name: grafana-config-tpl
# here you define the keys that should be used as template
items:
- key: config.yaml
data:
- secretKey: user
remoteRef:
key: /grafana/user
- secretKey: password
remoteRef:
key: /grafana/password
{% endraw %}

View file

@ -1408,6 +1408,21 @@ Kubernetes core/v1.SecretType
</tr>
<tr>
<td>
<code>engineVersion</code></br>
<em>
<a href="#external-secrets.io/v1alpha1.TemplateEngineVersion">
TemplateEngineVersion
</a>
</em>
</td>
<td>
<p>EngineVersion specifies the template engine version
that should be used to compile/execute the
template specified in .data and .templateFrom[].</p>
</td>
</tr>
<tr>
<td>
<code>metadata</code></br>
<em>
<a href="#external-secrets.io/v1alpha1.ExternalSecretTemplateMetadata">
@ -2666,6 +2681,27 @@ Kubernetes meta/v1.Time
</tr>
</tbody>
</table>
<h3 id="external-secrets.io/v1alpha1.TemplateEngineVersion">TemplateEngineVersion
(<code>string</code> alias)</p></h3>
<p>
(<em>Appears on:</em>
<a href="#external-secrets.io/v1alpha1.ExternalSecretTemplate">ExternalSecretTemplate</a>)
</p>
<p>
</p>
<table>
<thead>
<tr>
<th>Value</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr><td><p>&#34;v1&#34;</p></td>
<td></td>
</tr><tr><td><p>&#34;v2&#34;</p></td>
<td></td>
</tr></tbody>
</table>
<h3 id="external-secrets.io/v1alpha1.TemplateFrom">TemplateFrom
</h3>
<p>

View file

@ -110,8 +110,9 @@ func (f *Framework) Install(a addon.Addon) {
// Compose helps define multiple testcases with same/different auth methods.
func Compose(descAppend string, f *Framework, fn func(f *Framework) (string, func(*TestCase)), tweaks ...func(*TestCase)) TableEntry {
desc, tfn := fn(f)
tweaks = append(tweaks, tfn)
// prepend common fn to tweaks
desc, cfn := fn(f)
tweaks = append([]func(*TestCase){cfn}, tweaks...)
// need to convert []func to []interface{}
ifs := make([]interface{}, len(tweaks))

View file

@ -20,5 +20,6 @@ import (
_ "github.com/external-secrets/external-secrets/e2e/suite/aws/secretsmanager"
_ "github.com/external-secrets/external-secrets/e2e/suite/azure"
_ "github.com/external-secrets/external-secrets/e2e/suite/gcp"
_ "github.com/external-secrets/external-secrets/e2e/suite/template"
_ "github.com/external-secrets/external-secrets/e2e/suite/vault"
)

View file

@ -0,0 +1,89 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
import (
"context"
// nolint
. "github.com/onsi/ginkgo/v2"
// nolint
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
"github.com/external-secrets/external-secrets/e2e/framework"
)
type templateProvider struct {
framework *framework.Framework
}
func newProvider(f *framework.Framework) *templateProvider {
prov := &templateProvider{
framework: f,
}
BeforeEach(prov.BeforeEach)
return prov
}
func (s *templateProvider) CreateSecret(key, val string) {
// noop: this provider implements static key/value pairs
}
func (s *templateProvider) DeleteSecret(key string) {
// noop: this provider implements static key/value pairs
}
func (s *templateProvider) BeforeEach() {
// Create a secret store - change these values to match YAML
By("creating a secret store for credentials")
secretStore := &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: s.framework.Namespace.Name,
Namespace: s.framework.Namespace.Name,
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
Fake: &esv1alpha1.FakeProvider{
Data: []esv1alpha1.FakeProviderData{
{
Key: "foo",
Value: "bar",
},
{
Key: "baz",
Value: "bang",
},
{
Key: "map",
ValueMap: map[string]string{
"foo": "barmap",
"bar": "bangmap",
},
},
{
Key: "json",
Value: `{"foo":{"bar":"baz"}}`,
},
},
},
},
},
}
err := s.framework.CRClient.Create(context.Background(), secretStore)
Expect(err).ToNot(HaveOccurred())
}

View file

@ -0,0 +1,101 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
limitations under the License.
*/
package template
import (
// nolint
. "github.com/onsi/ginkgo/v2"
v1 "k8s.io/api/core/v1"
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
"github.com/external-secrets/external-secrets/e2e/framework"
)
var _ = Describe("[template]", Label("template"), func() {
f := framework.New("eso-template")
prov := newProvider(f)
DescribeTable("sync secrets", framework.TableFunc(f, prov),
framework.Compose("template v1", f, genericTemplate, useTemplateV1),
framework.Compose("template v2", f, genericTemplate, useTemplateV2),
)
})
// useTemplateV1 specifies a test case which uses the template engine v1.
func useTemplateV1(tc *framework.TestCase) {
tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{
EngineVersion: esv1alpha1.TemplateEngineV1,
Data: map[string]string{
"tplv1": "executed: {{ .singlefoo | toString }}|{{ .singlebaz | toString }}",
"other": `{{ .foo | toString }}|{{ .bar | toString }}`,
},
}
tc.ExpectedSecret.Data = map[string][]byte{
"tplv1": []byte(`executed: bar|bang`),
"other": []byte(`barmap|bangmap`),
}
}
// useTemplateV2 specifies a test case which uses the template engine v2.
func useTemplateV2(tc *framework.TestCase) {
tc.ExternalSecret.Spec.Target.Template = &esv1alpha1.ExternalSecretTemplate{
EngineVersion: esv1alpha1.TemplateEngineV2,
Data: map[string]string{
"tplv2": "executed: {{ .singlefoo }}|{{ .singlebaz }}",
"other": `{{ .foo }}|{{ .bar }}`,
"sprig-str": `{{ .foo | upper }}`,
"json-ex": `{{ $var := .singlejson | fromJson }}{{ $var.foo | toJson }}`,
},
}
tc.ExpectedSecret.Data = map[string][]byte{
"tplv2": []byte(`executed: bar|bang`),
"other": []byte(`barmap|bangmap`),
"sprig-str": []byte(`BARMAP`),
"json-ex": []byte(`{"bar":"baz"}`),
}
}
// This case uses template engine v1.
func genericTemplate(f *framework.Framework) (string, func(*framework.TestCase)) {
return "[template] should execute template v1", func(tc *framework.TestCase) {
tc.ExpectedSecret = &v1.Secret{
Type: v1.SecretTypeOpaque,
}
tc.ExternalSecret.Spec.Data = []esv1alpha1.ExternalSecretData{
{
SecretKey: "singlefoo",
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: "foo",
},
},
{
SecretKey: "singlebaz",
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: "baz",
},
},
{
SecretKey: "singlejson",
RemoteRef: esv1alpha1.ExternalSecretDataRemoteRef{
Key: "json",
},
},
}
tc.ExternalSecret.Spec.DataFrom = []esv1alpha1.ExternalSecretDataRemoteRef{
{
Key: "map",
},
}
}
}

29
go.mod
View file

@ -35,13 +35,12 @@ replace (
require (
cloud.google.com/go v0.100.2 // indirect
cloud.google.com/go/secretmanager v1.0.0
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible
github.com/Azure/go-autorest/autorest/azure/auth v0.5.7
github.com/Azure/azure-sdk-for-go v61.5.0+incompatible
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
github.com/IBM/go-sdk-core/v5 v5.9.1
github.com/IBM/secrets-manager-go-sdk v1.0.31
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/Masterminds/sprig/v3 v3.2.2
github.com/PaesslerAG/jsonpath v0.1.1
github.com/ahmetb/gen-crd-api-reference-docs v0.3.0
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2
@ -57,7 +56,7 @@ require (
github.com/hashicorp/vault/api v1.3.1
github.com/huandu/xstrings v1.3.2 // indirect
github.com/lestrrat-go/jwx v1.2.1
github.com/onsi/ginkgo/v2 v2.1.1
github.com/onsi/ginkgo/v2 v2.1.2
github.com/onsi/gomega v1.18.1
github.com/oracle/oci-go-sdk/v56 v56.1.0
github.com/prometheus/client_golang v1.12.1
@ -68,11 +67,11 @@ require (
github.com/yandex-cloud/go-genproto v0.0.0-20210809082946-a97da516c588
github.com/yandex-cloud/go-sdk v0.0.0-20210809100642-c13c40a429fa
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
go.uber.org/zap v1.20.0
go.uber.org/zap v1.21.0
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
google.golang.org/api v0.64.0
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5
google.golang.org/api v0.68.0
google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e
google.golang.org/grpc v1.44.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919
@ -92,16 +91,17 @@ require (
)
require (
cloud.google.com/go/compute v0.1.0 // indirect
cloud.google.com/go/compute v1.2.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.18 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 // indirect
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/PaesslerAG/gval v1.0.0 // indirect
github.com/armon/go-metrics v0.3.10 // indirect
github.com/armon/go-radix v1.0.0 // indirect
@ -116,7 +116,6 @@ require (
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-logr/zapr v1.2.0 // indirect
@ -184,7 +183,9 @@ require (
github.com/prometheus/procfs v0.7.3 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sony/gobreaker v0.4.2-0.20210216022020-dd874f9dd33b // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.2.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
@ -195,7 +196,7 @@ require (
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.5.0 // indirect
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect

66
go.sum
View file

@ -36,8 +36,9 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0 h1:rSUBvAyVwNJ5uQCKNJFMwPtTvJkfN38b6Pvb9zZoqJ8=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.2.0 h1:EKki8sSdvDU0OO9mAXGwPXOTOgPz2l08R0/IutDH11I=
cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
@ -56,23 +57,22 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible h1:BF2Pm3aQWIa6q9KmxyF1JYKYXtVw67vtvu2Wd54NGuY=
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v61.5.0+incompatible h1:OSHSFeNm7D1InGsQrFjyN9hpxD5Ec60PdsWWudCpah4=
github.com/Azure/azure-sdk-for-go v61.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.7 h1:8DQB8yl7aLQuP+nuR5e2RO6454OvFlSTXXaNHshc16s=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.7/go.mod h1:AkzUsqkrdmNhfP2i54HqINVQopw0CLDnvHpJ88Zz1eI=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2 h1:dMOmEJfkLKW/7JsokJqkyoYSgmR08hi9KrhjZb+JALY=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
@ -81,7 +81,6 @@ github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+X
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
@ -96,10 +95,10 @@ github.com/IBM/secrets-manager-go-sdk v1.0.31 h1:KRRyeEvlKkkZb90njgReOrK92+IyS6L
github.com/IBM/secrets-manager-go-sdk v1.0.31/go.mod h1:0Juj6ER/LpDqJ49nw705MNyXSHsHodgztFdkXz5ttxs=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@ -215,7 +214,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
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/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
@ -249,7 +247,6 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
@ -340,6 +337,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -537,12 +535,14 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I=
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@ -706,8 +706,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.1 h1:LCnPB85AvFNr91s0B2aDzEiiIg6MUwLYbryC1NSlWi8=
github.com/onsi/ginkgo/v2 v2.1.1/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.2 h1:QUvZA5LiZ5EMDS0dVTQbjOvYLFs3wzcztqFU/mfR70c=
github.com/onsi/ginkgo/v2 v2.1.2/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
@ -789,6 +789,8 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@ -810,6 +812,7 @@ github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY52
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
@ -922,8 +925,8 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc=
go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -935,13 +938,15 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/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-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-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1159,8 +1164,9 @@ golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a h1:ppl5mZgokTT8uPkmYOyEUmPTr3ypaKkg5eFOGrAmxxE=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
@ -1297,8 +1303,10 @@ google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUb
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.64.0 h1:l3pi8ncrQgB9+ncFw3A716L8lWujnXniBYbxWqqy6tE=
google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM=
google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
google.golang.org/api v0.68.0 h1:9eJiHhwJKIYX6sX2fUZxQLi7pDRA/MYu8c12q6WbJik=
google.golang.org/api v0.68.0/go.mod h1:sOM8pTpwgflXRhz+oC8H2Dr+UcbMqkPPWNJo88Q7TH8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1381,8 +1389,10 @@ google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ6
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 h1:zzNejm+EgrbLfDZ6lu9Uud2IVvHySPl8vQzf04laR5Q=
google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e h1:hXl9hnyOkeznztYpYxVPAVZfPzcbO6Q0C+nLXodza8k=
google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=

View file

@ -23,4 +23,4 @@ RUN apk add -U --no-cache \
bash \
gcc \
diffutils \
&& pip3 install -r /requirements.txt
&& pip3 install -r /requirements.txt

View file

@ -53,8 +53,7 @@ build: image generate $(SOURCES)
--rm \
--user $(UID):$(GID) \
$(MKDOCS_IMAGE) \
/bin/bash -c "cd /repo && git config user.email "docs@external-secrets.io" && git config user.name "Docs" && $(MIKE) deploy --update-aliases -F hack/api-docs/mkdocs.yml $(DOCS_VERSION) $(DOCS_ALIAS);"
/bin/bash -c "cd /repo && git config user.email "docs@external-secrets.io" && git config user.name "Docs" && $(MIKE) deploy --ignore --update-aliases -F hack/api-docs/mkdocs.yml $(DOCS_VERSION) $(DOCS_ALIAS);"
.PHONY: build.publish
build.publish: image generate $(SOURCES)
mkdir -p $(GENROOT)
@ -65,7 +64,6 @@ build.publish: image generate $(SOURCES)
--user $(UID):$(GID) \
$(MKDOCS_IMAGE) \
/bin/bash -c "cd /repo && git config user.email "docs@external-secrets.io" && git config user.name "Docs" && $(MIKE) deploy --update-aliases -p -F hack/api-docs/mkdocs.yml $(DOCS_VERSION) $(DOCS_ALIAS);"
.PHONY: generate
generate:
./generate.sh $(SRCDIR)/spec.md
@ -86,4 +84,4 @@ serve:
-p $(SERVE_BIND_ADDRESS):8000:8000 \
--rm \
$(MKDOCS_IMAGE) \
/bin/bash -c "cd /repo && $(MIKE) serve -F hack/api-docs/mkdocs.yml -a 0.0.0.0:8000"
/bin/bash -c "cd /repo && mkdocs serve -f hack/api-docs/mkdocs.yml -a 0.0.0.0:8000"

View file

@ -34,7 +34,9 @@ nav:
- Guides:
- Introduction: guides-introduction.md
- Getting started: guides-getting-started.md
- Advanced Templating: guides-templating.md
- Advanced Templating:
v2: guides-templating.md
v1: guides-templating-v1.md
- Controller Classes: guides-controller-class.md
- All keys, One secret: guides-all-keys-one-secret.md
- Common K8S Secret Types: guides-common-k8s-secret-types.md

View file

@ -1,18 +1,18 @@
Click==7.0
Click==8.0.3
htmlmin==0.1.12
Jinja2==2.11.1
jsmin==2.2.2
livereload==2.6.1
Markdown==3.2.1
MarkupSafe==1.1.1
Jinja2==3.0.3
jsmin==3.0.1
livereload==2.6.3
Markdown==3.3.6
MarkupSafe==2.0.1
mkdocs==1.2.3
mike==1.1.2
mkdocs-material==8.1.9
mkdocs-minify-plugin==0.2.1
pep562==1.0
Pygments==2.10.0
pymdown-extensions==9.0
PyYAML==5.3
six==1.14.0
tornado==6.0.3
mkdocs-macros-plugin==0.4.18
mkdocs-material==8.1.10
mkdocs-minify-plugin==0.5.0
pep562==1.1
Pygments==2.11.2
pymdown-extensions==9.1
PyYAML==6.0
six==1.16.0
tornado==6.1
mkdocs-macros-plugin==0.6.4

View file

@ -56,7 +56,11 @@ func (r *Reconciler) applyTemplate(ctx context.Context, es *esv1beta1.ExternalSe
}
r.Log.V(1).Info("found template data", "tpl_data", tplMap)
err = template.Execute(tplMap, dataMap, secret)
execute, err := template.EngineForVersion(es.Spec.Target.Template.EngineVersion)
if err != nil {
return err
}
err = execute(tplMap, dataMap, secret)
if err != nil {
return fmt.Errorf(errExecTpl, err)
}

View file

@ -432,7 +432,8 @@ var _ = Describe("ExternalSecret controller", func() {
"hihi": "ga",
},
},
Type: v1.SecretTypeOpaque,
Type: v1.SecretTypeOpaque,
EngineVersion: esv1beta1.TemplateEngineV1,
Data: map[string]string{
targetProp: targetPropObj,
tplStaticKey: tplStaticVal,
@ -452,6 +453,24 @@ var _ = Describe("ExternalSecret controller", func() {
}
}
// when using a v2 template it should use the v2 engine version
syncWithTemplateV2 := func(tc *testCase) {
const secretVal = "someValue"
tc.externalSecret.Spec.Target.Template = &esv1beta1.ExternalSecretTemplate{
Type: v1.SecretTypeOpaque,
// it should default to v2 for beta11
// EngineVersion: esv1beta1.TemplateEngineV2,
Data: map[string]string{
targetProp: "{{ .targetProperty | upper }} was templated",
},
}
fakeProvider.WithGetSecret([]byte(secretVal), nil)
tc.checkSecret = func(es *esv1beta1.ExternalSecret, secret *v1.Secret) {
// check values
Expect(string(secret.Data[targetProp])).To(Equal(expectedSecretVal))
}
}
// secret should be synced with correct value precedence:
// * template
// * templateFrom
@ -1079,6 +1098,7 @@ var _ = Describe("ExternalSecret controller", func() {
Entry("should not resolve conflicts with creationPolicy=Merge", mergeWithConflict),
Entry("should not update unchanged secret using creationPolicy=Merge", mergeWithSecretNoChange),
Entry("should sync with template", syncWithTemplate),
Entry("should sync with template engine v2", syncWithTemplateV2),
Entry("should sync template with correct value precedence", syncWithTemplatePrecedence),
Entry("should refresh secret from template", refreshWithTemplate),
Entry("should be able to use only metadata from template", onlyMetadataFromTemplate),

View file

@ -63,6 +63,7 @@ type GoogleSecretManagerClient interface {
type ProviderGCP struct {
projectID string
SecretManagerClient GoogleSecretManagerClient
gClient *gClient
}
type gClient struct {
@ -86,6 +87,10 @@ func (c *gClient) getTokenSource(ctx context.Context, store esv1beta1.GenericSto
return google.DefaultTokenSource(ctx, CloudPlatformRole)
}
func (c *gClient) Close() error {
return c.workloadIdentity.Close()
}
func serviceAccountTokenSource(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
spec := store.GetSpec()
if spec == nil || spec.Provider.GCPSM == nil {
@ -146,6 +151,13 @@ func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1beta1.GenericSto
storeKind: store.GetObjectKind().GroupVersionKind().Kind,
workloadIdentity: wi,
}
sm.gClient = &cliStore
defer func() {
// closes IAMClient to prevent gRPC connection leak in case of an error.
if sm.SecretManagerClient == nil {
_ = sm.gClient.Close()
}
}()
sm.projectID = cliStore.store.ProjectID
@ -245,6 +257,9 @@ func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalS
func (sm *ProviderGCP) Close(ctx context.Context) error {
err := sm.SecretManagerClient.Close()
if sm.gClient != nil {
err = sm.gClient.Close()
}
if err != nil {
return fmt.Errorf(errClientClose, err)
}

View file

@ -62,6 +62,7 @@ type workloadIdentity struct {
// interface to GCP IAM API.
type IamClient interface {
GenerateAccessToken(ctx context.Context, req *credentialspb.GenerateAccessTokenRequest, opts ...gax.CallOption) (*credentialspb.GenerateAccessTokenResponse, error)
Close() error
}
// interface to securetoken/identitybindingtoken API.
@ -154,6 +155,10 @@ func (w *workloadIdentity) TokenSource(ctx context.Context, store esv1beta1.Gene
}), nil
}
func (w *workloadIdentity) Close() error {
return w.iamClient.Close()
}
func newIAMClient(ctx context.Context) (IamClient, error) {
iamOpts := []option.ClientOption{
option.WithUserAgent("external-secrets-operator"),

View file

@ -357,6 +357,10 @@ func (f *fakeIAMClient) GenerateAccessToken(ctx context.Context, req *credential
return f.generateAccessTokenFunc(ctx, req, opts...)
}
func (f *fakeIAMClient) Close() error {
return nil
}
// fake SA Token Generator.
type fakeSATokenGen struct {
GenerateFunc func(context.Context, string, string, string) (*authv1.TokenRequest, error)

View file

@ -58,6 +58,7 @@ const (
errDataField = "failed to find data field"
errJSONUnmarshall = "failed to unmarshall JSON"
errSecretFormat = "secret data not in expected format"
errUnexpectedKey = "unexpected key in data: %s"
errVaultToken = "cannot parse Vault authentication token: %w"
errVaultReqParams = "cannot set Vault request parameters: %w"
errVaultRequest = "error from Vault request: %w"
@ -163,24 +164,43 @@ func (v *client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecret
return nil, fmt.Errorf("GetAllSecrets not implemented")
}
// GetSecret supports two types:
// 1. get the full secret as json-encoded value
// by leaving the ref.Property empty.
// 2. get a key from the secret.
// Nested values are supported by specifying a gjson expression
func (v *client) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
data, err := v.readSecret(ctx, ref.Key, ref.Version)
if err != nil {
return nil, err
}
// return raw json if no property is defined
jsonStr, err := json.Marshal(data)
if err != nil {
return nil, err
}
// (1): return raw json if no property is defined
if ref.Property == "" {
return data, nil
return jsonStr, nil
}
val := gjson.Get(string(data), ref.Property)
// For backwards compatibility we want the
// actual keys to take precedence over gjson syntax
// (2): extract key from secret with property
if _, ok := data[ref.Property]; ok {
return getTypedKey(data, ref.Property)
}
// (3): extract key from secret using gjson
val := gjson.Get(string(jsonStr), ref.Property)
if !val.Exists() {
return nil, fmt.Errorf(errSecretKeyFmt, ref.Property)
}
return []byte(val.String()), nil
}
// GetSecretMap supports two modes of operation:
// 1. get the full secret from the vault data payload (by leaving .property empty).
// 2. extract key/value pairs from a (nested) object.
func (v *client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
data, err := v.GetSecret(ctx, ref)
if err != nil {
@ -193,33 +213,40 @@ func (v *client) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretD
return nil, err
}
byteMap := make(map[string][]byte, len(secretData))
for k, v := range secretData {
switch t := v.(type) {
case string:
byteMap[k] = []byte(t)
case map[string]interface{}:
jsonData, err := json.Marshal(t)
if err != nil {
return nil, err
}
byteMap[k] = jsonData
case []byte:
byteMap[k] = t
// also covers int and float32 due to json.Marshal
case float64:
byteMap[k] = []byte(strconv.FormatFloat(t, 'f', -1, 64))
case bool:
byteMap[k] = []byte(strconv.FormatBool(t))
case nil:
byteMap[k] = []byte(nil)
default:
return nil, errors.New(errSecretFormat)
for k := range secretData {
byteMap[k], err = getTypedKey(secretData, k)
if err != nil {
return nil, err
}
}
return byteMap, nil
}
func getTypedKey(data map[string]interface{}, key string) ([]byte, error) {
v, ok := data[key]
if !ok {
return nil, fmt.Errorf(errUnexpectedKey, key)
}
switch t := v.(type) {
case string:
return []byte(t), nil
case map[string]interface{}:
return json.Marshal(t)
case []byte:
return t, nil
// also covers int and float32 due to json.Marshal
case float64:
return []byte(strconv.FormatFloat(t, 'f', -1, 64)), nil
case bool:
return []byte(strconv.FormatBool(t)), nil
case nil:
return []byte(nil), nil
default:
return nil, errors.New(errSecretFormat)
}
}
func (v *client) Close(ctx context.Context) error {
// Revoke the token if we have one set and it wasn't sourced from a TokenSecretRef
if v.client.Token() != "" && v.store.Auth.TokenSecretRef == nil {
@ -268,7 +295,7 @@ func (v *client) buildPath(path string) string {
return returnPath
}
func (v *client) readSecret(ctx context.Context, path, version string) ([]byte, error) {
func (v *client) readSecret(ctx context.Context, path, version string) (map[string]interface{}, error) {
dataPath := v.buildPath(path)
// path formated according to vault docs for v1 and v2 API
@ -304,8 +331,7 @@ func (v *client) readSecret(ctx context.Context, path, version string) ([]byte,
}
}
// return json string
return json.Marshal(secretData)
return secretData, nil
}
func (v *client) newConfig() (*vault.Config, error) {

View file

@ -565,8 +565,10 @@ func TestGetSecret(t *testing.T) {
secretWithNestedVal := map[string]interface{}{
"access_key": "access_key",
"access_secret": "access_secret",
"nested.bar": "something different",
"nested": map[string]string{
"foo": "oke",
"bar": "also ok?",
},
}
@ -662,6 +664,26 @@ func TestGetSecret(t *testing.T) {
val: []byte("oke"),
},
},
"ReadSecretWithNestedValueFromData": {
reason: "Should return a nested property",
args: args{
store: makeValidSecretStoreWithVersion(esv1beta1.VaultKVStoreV1).Spec.Provider.Vault,
data: esv1beta1.ExternalSecretDataRemoteRef{
//
Property: "nested.bar",
},
vClient: &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultResponseWithData(secretWithNestedVal), nil,
),
},
},
want: want{
err: nil,
val: []byte("something different"),
},
},
"NonexistentProperty": {
reason: "Should return error property does not exist.",
args: args{

View file

@ -26,7 +26,7 @@ import (
"strings"
tpl "text/template"
"github.com/Masterminds/sprig"
"github.com/Masterminds/sprig/v3"
"github.com/PaesslerAG/jsonpath"
"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
@ -36,7 +36,7 @@ import (
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
"github.com/external-secrets/external-secrets/pkg/provider"
"github.com/external-secrets/external-secrets/pkg/provider/schema"
"github.com/external-secrets/external-secrets/pkg/template"
"github.com/external-secrets/external-secrets/pkg/template/v2"
)
// Provider satisfies the provider interface.

36
pkg/template/engine.go Normal file
View file

@ -0,0 +1,36 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
limitations under the License.
*/
package template
import (
corev1 "k8s.io/api/core/v1"
esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
v1 "github.com/external-secrets/external-secrets/pkg/template/v1"
v2 "github.com/external-secrets/external-secrets/pkg/template/v2"
)
type ExecFunc func(tpl, data map[string][]byte, secret *corev1.Secret) error
func EngineForVersion(version esapi.TemplateEngineVersion) (ExecFunc, error) {
switch version {
case esapi.TemplateEngineV1:
return v1.Execute, nil
case esapi.TemplateEngineV2:
return v2.Execute, nil
}
// in case we run with a old v1alpha1 CRD
// we must return v1 as default
return v1.Execute, nil
}

View file

@ -2,9 +2,7 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View file

@ -0,0 +1,80 @@
# prerequisite:
# install step cli
# from: https://github.com/smallstep/cli
all: ca disjunct-ca intermediate leaf \
pkcs12-nopass pkcs12-disjunct pkcs12-multibag pkcs12-withpass-1234
clean:
rm *.{pfx,crt,key,pem}
ca:
step certificate create root-ca \
root-ca.crt root-ca.key \
--profile root-ca --kty OKP --curve Ed25519 \
--no-password --insecure -f
disjunct-ca:
step certificate create disjunct-root-ca \
disjunct-root-ca.crt disjunct-root-ca.key \
--profile root-ca --kty OKP --curve Ed25519 \
--no-password --insecure -f
intermediate:
step certificate create intermediate-ca \
intermediate-ca.crt intermediate-ca.key \
--profile intermediate-ca \
--ca ./root-ca.crt \
--ca-key ./root-ca.key \
--kty EC --curve P-256 \
--no-password --insecure -f
leaf:
step certificate create foo \
foo.crt foo.key --profile leaf \
--ca ./intermediate-ca.crt \
--ca-key ./intermediate-ca.key \
--no-password --insecure -f
pkcs12-nopass: ca intermediate leaf
# deliberately in wrong order
cat foo.crt root-ca.crt intermediate-ca.crt > chain.pem
# create pkcs12
openssl pkcs12 -export \
-in chain.pem \
-inkey foo.key \
-out foo-nopass.pfx \
-password pass:
pkcs12-disjunct: ca intermediate disjunct-ca leaf
cat root-ca.crt intermediate-ca.crt disjunct-root-ca.crt > disjunct-chain.pem
openssl pkcs12 -export \
-in foo.crt \
-certfile disjunct-chain.pem \
-inkey foo.key \
-out foo-disjunct-nopass.pfx \
-password pass:
pkcs12-multibag: ca intermediate leaf
# deliberately in wrong order, we're missing the leaf cert here
cat root-ca.crt intermediate-ca.crt > intermediate-chain.pem
openssl pkcs12 -export \
-in foo.crt \
-certfile intermediate-chain.pem \
-inkey foo.key \
-out foo-multibag-nopass.pfx \
-password pass:
pkcs12-withpass-1234: ca intermediate leaf
# deliberately in the wrong order
cat foo.crt root-ca.crt intermediate-ca.crt > chain.pem
# create pkcs12
openssl pkcs12 -export \
-in chain.pem \
-inkey foo.key \
-out foo-withpass-1234.pfx \
-password pass:1234

View file

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIBqjCCAU+gAwIBAgIRAPnGGsBUMbZhmh5QdnYdBmUwCgYIKoZIzj0EAwIwGjEY
MBYGA1UEAxMPaW50ZXJtZWRpYXRlLWNhMB4XDTIyMDIwOTEwMjUzMVoXDTIyMDIx
MDEwMjUzMVowDjEMMAoGA1UEAxMDZm9vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
QgAEqnxdeInykx8JZsLi13rZLekoG2cosQ3F+2InVNy7hCQ7soMqdaJsGQ6LFtov
ogUFtOOTRWrunblqNWGZsowHbKOBgTB/MA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUE
FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLtundVbuKd73OWzo6SY
by0Ajeb2MB8GA1UdIwQYMBaAFCLg80J/bZBbOd+Y8+V94l5xM2zEMA4GA1UdEQQH
MAWCA2ZvbzAKBggqhkjOPQQDAgNJADBGAiEA4K4SbVNqrEtl7RfwBfJFMnWI+X8D
zMPMc4Xqzp2qTxcCIQDsySgtiakypZfWakpB49zJph0kLwGK8xhWvGMUw1N1/w==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBJzCB2qADAgECAhEArvunrLoYXTmwMROkmbAlBTAFBgMrZXAwEjEQMA4GA1UE
AxMHcm9vdC1jYTAeFw0yMjAyMDkxMDI1MzBaFw0zMjAyMDcxMDI1MzBaMBIxEDAO
BgNVBAMTB3Jvb3QtY2EwKjAFBgMrZXADIQDSw5uQ1io+jcKevCH0sl+tGTB6/BQs
Bu84ibw13QoP36NFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
AQEwHQYDVR0OBBYEFGtn1GnNjmd6cxOhMnVau1L8IkGtMAUGAytlcANBAOHSAS4z
/6ctcvRwlGr9Hyt7vVLROImD2t3rFdDDHLLL1znikK3JZvVbETyMFOMbOMQS33C/
4FtLGenZFXySjQw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBgDCCATKgAwIBAgIRAOzjpCdp42oW5MoccLpRXpAwBQYDK2VwMBIxEDAOBgNV
BAMTB3Jvb3QtY2EwHhcNMjIwMjA5MTAyNTMxWhcNMzIwMjA3MTAyNTMxWjAaMRgw
FgYDVQQDEw9pbnRlcm1lZGlhdGUtY2EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AATekdyX6cZe0Ajmme363TQoWnrQwXnARzeWEf4FRQE8BGWgf8z7wljjpb4M4S4f
+CJAYYY/6x38UnlsxXEeBTofo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/
BAgwBgEB/wIBADAdBgNVHQ4EFgQUIuDzQn9tkFs535jz5X3iXnEzbMQwHwYDVR0j
BBgwFoAUa2fUac2OZ3pzE6EydVq7UvwiQa0wBQYDK2VwA0EA4gntaGs/3ME6q1y9
gO4ntri2qwoC25l3q7q9BiFBmeBmvS6I1w9HCZHtB3JnVC/IYDTCYDNTbpGWEOjl
aCKLCA==
-----END CERTIFICATE-----

View file

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIBJzCB2qADAgECAhEArvunrLoYXTmwMROkmbAlBTAFBgMrZXAwEjEQMA4GA1UE
AxMHcm9vdC1jYTAeFw0yMjAyMDkxMDI1MzBaFw0zMjAyMDcxMDI1MzBaMBIxEDAO
BgNVBAMTB3Jvb3QtY2EwKjAFBgMrZXADIQDSw5uQ1io+jcKevCH0sl+tGTB6/BQs
Bu84ibw13QoP36NFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
AQEwHQYDVR0OBBYEFGtn1GnNjmd6cxOhMnVau1L8IkGtMAUGAytlcANBAOHSAS4z
/6ctcvRwlGr9Hyt7vVLROImD2t3rFdDDHLLL1znikK3JZvVbETyMFOMbOMQS33C/
4FtLGenZFXySjQw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBgDCCATKgAwIBAgIRAOzjpCdp42oW5MoccLpRXpAwBQYDK2VwMBIxEDAOBgNV
BAMTB3Jvb3QtY2EwHhcNMjIwMjA5MTAyNTMxWhcNMzIwMjA3MTAyNTMxWjAaMRgw
FgYDVQQDEw9pbnRlcm1lZGlhdGUtY2EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AATekdyX6cZe0Ajmme363TQoWnrQwXnARzeWEf4FRQE8BGWgf8z7wljjpb4M4S4f
+CJAYYY/6x38UnlsxXEeBTofo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/
BAgwBgEB/wIBADAdBgNVHQ4EFgQUIuDzQn9tkFs535jz5X3iXnEzbMQwHwYDVR0j
BBgwFoAUa2fUac2OZ3pzE6EydVq7UvwiQa0wBQYDK2VwA0EA4gntaGs/3ME6q1y9
gO4ntri2qwoC25l3q7q9BiFBmeBmvS6I1w9HCZHtB3JnVC/IYDTCYDNTbpGWEOjl
aCKLCA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBOTCB7KADAgECAhEA0cArEY0s5JoP8JfqIDv2BTAFBgMrZXAwGzEZMBcGA1UE
AxMQZGlzanVuY3Qtcm9vdC1jYTAeFw0yMjAyMDkxMDI1MzBaFw0zMjAyMDcxMDI1
MzBaMBsxGTAXBgNVBAMTEGRpc2p1bmN0LXJvb3QtY2EwKjAFBgMrZXADIQAJER3w
QFZH1DBmxZm9IkaZ5noangcg/CYNP+GtcyQPL6NFMEMwDgYDVR0PAQH/BAQDAgEG
MBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMddJG4lWXTN/x9InsiJUTxu
d4+HMAUGAytlcANBABcyLV4om9LPV0TGDf0jiM+JxH1R+ATvAE8FHDtd8L66BrzA
Id656nPz3fz9ZMB9VZr7iGcghXYlTHxu6NkQEgA=
-----END CERTIFICATE-----

View file

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBOTCB7KADAgECAhEA0cArEY0s5JoP8JfqIDv2BTAFBgMrZXAwGzEZMBcGA1UE
AxMQZGlzanVuY3Qtcm9vdC1jYTAeFw0yMjAyMDkxMDI1MzBaFw0zMjAyMDcxMDI1
MzBaMBsxGTAXBgNVBAMTEGRpc2p1bmN0LXJvb3QtY2EwKjAFBgMrZXADIQAJER3w
QFZH1DBmxZm9IkaZ5noangcg/CYNP+GtcyQPL6NFMEMwDgYDVR0PAQH/BAQDAgEG
MBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMddJG4lWXTN/x9InsiJUTxu
d4+HMAUGAytlcANBABcyLV4om9LPV0TGDf0jiM+JxH1R+ATvAE8FHDtd8L66BrzA
Id656nPz3fz9ZMB9VZr7iGcghXYlTHxu6NkQEgA=
-----END CERTIFICATE-----

View file

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIDWjPU0WTbgryudXkq5qduRP5utcq8yEsmYPizQ0J1vW
-----END PRIVATE KEY-----

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBqjCCAU+gAwIBAgIRAPnGGsBUMbZhmh5QdnYdBmUwCgYIKoZIzj0EAwIwGjEY
MBYGA1UEAxMPaW50ZXJtZWRpYXRlLWNhMB4XDTIyMDIwOTEwMjUzMVoXDTIyMDIx
MDEwMjUzMVowDjEMMAoGA1UEAxMDZm9vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
QgAEqnxdeInykx8JZsLi13rZLekoG2cosQ3F+2InVNy7hCQ7soMqdaJsGQ6LFtov
ogUFtOOTRWrunblqNWGZsowHbKOBgTB/MA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUE
FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLtundVbuKd73OWzo6SY
by0Ajeb2MB8GA1UdIwQYMBaAFCLg80J/bZBbOd+Y8+V94l5xM2zEMA4GA1UdEQQH
MAWCA2ZvbzAKBggqhkjOPQQDAgNJADBGAiEA4K4SbVNqrEtl7RfwBfJFMnWI+X8D
zMPMc4Xqzp2qTxcCIQDsySgtiakypZfWakpB49zJph0kLwGK8xhWvGMUw1N1/w==
-----END CERTIFICATE-----

View file

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMuAjEwBeXznOjx3V7viagAznflfL+p64CXkm++xlXhkoAoGCCqGSM49
AwEHoUQDQgAEqnxdeInykx8JZsLi13rZLekoG2cosQ3F+2InVNy7hCQ7soMqdaJs
GQ6LFtovogUFtOOTRWrunblqNWGZsowHbA==
-----END EC PRIVATE KEY-----

View file

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBgDCCATKgAwIBAgIRAOzjpCdp42oW5MoccLpRXpAwBQYDK2VwMBIxEDAOBgNV
BAMTB3Jvb3QtY2EwHhcNMjIwMjA5MTAyNTMxWhcNMzIwMjA3MTAyNTMxWjAaMRgw
FgYDVQQDEw9pbnRlcm1lZGlhdGUtY2EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AATekdyX6cZe0Ajmme363TQoWnrQwXnARzeWEf4FRQE8BGWgf8z7wljjpb4M4S4f
+CJAYYY/6x38UnlsxXEeBTofo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/
BAgwBgEB/wIBADAdBgNVHQ4EFgQUIuDzQn9tkFs535jz5X3iXnEzbMQwHwYDVR0j
BBgwFoAUa2fUac2OZ3pzE6EydVq7UvwiQa0wBQYDK2VwA0EA4gntaGs/3ME6q1y9
gO4ntri2qwoC25l3q7q9BiFBmeBmvS6I1w9HCZHtB3JnVC/IYDTCYDNTbpGWEOjl
aCKLCA==
-----END CERTIFICATE-----

View file

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIYmlQRczt7PR7VN9jjKf5MGcgKHgohcBbrPbikYVWNqoAoGCCqGSM49
AwEHoUQDQgAE3pHcl+nGXtAI5pnt+t00KFp60MF5wEc3lhH+BUUBPARloH/M+8JY
46W+DOEuH/giQGGGP+sd/FJ5bMVxHgU6Hw==
-----END EC PRIVATE KEY-----

View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIBJzCB2qADAgECAhEArvunrLoYXTmwMROkmbAlBTAFBgMrZXAwEjEQMA4GA1UE
AxMHcm9vdC1jYTAeFw0yMjAyMDkxMDI1MzBaFw0zMjAyMDcxMDI1MzBaMBIxEDAO
BgNVBAMTB3Jvb3QtY2EwKjAFBgMrZXADIQDSw5uQ1io+jcKevCH0sl+tGTB6/BQs
Bu84ibw13QoP36NFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
AQEwHQYDVR0OBBYEFGtn1GnNjmd6cxOhMnVau1L8IkGtMAUGAytlcANBAOHSAS4z
/6ctcvRwlGr9Hyt7vVLROImD2t3rFdDDHLLL1znikK3JZvVbETyMFOMbOMQS33C/
4FtLGenZFXySjQw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBgDCCATKgAwIBAgIRAOzjpCdp42oW5MoccLpRXpAwBQYDK2VwMBIxEDAOBgNV
BAMTB3Jvb3QtY2EwHhcNMjIwMjA5MTAyNTMxWhcNMzIwMjA3MTAyNTMxWjAaMRgw
FgYDVQQDEw9pbnRlcm1lZGlhdGUtY2EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AATekdyX6cZe0Ajmme363TQoWnrQwXnARzeWEf4FRQE8BGWgf8z7wljjpb4M4S4f
+CJAYYY/6x38UnlsxXEeBTofo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/
BAgwBgEB/wIBADAdBgNVHQ4EFgQUIuDzQn9tkFs535jz5X3iXnEzbMQwHwYDVR0j
BBgwFoAUa2fUac2OZ3pzE6EydVq7UvwiQa0wBQYDK2VwA0EA4gntaGs/3ME6q1y9
gO4ntri2qwoC25l3q7q9BiFBmeBmvS6I1w9HCZHtB3JnVC/IYDTCYDNTbpGWEOjl
aCKLCA==
-----END CERTIFICATE-----

View file

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBJzCB2qADAgECAhEArvunrLoYXTmwMROkmbAlBTAFBgMrZXAwEjEQMA4GA1UE
AxMHcm9vdC1jYTAeFw0yMjAyMDkxMDI1MzBaFw0zMjAyMDcxMDI1MzBaMBIxEDAO
BgNVBAMTB3Jvb3QtY2EwKjAFBgMrZXADIQDSw5uQ1io+jcKevCH0sl+tGTB6/BQs
Bu84ibw13QoP36NFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8C
AQEwHQYDVR0OBBYEFGtn1GnNjmd6cxOhMnVau1L8IkGtMAUGAytlcANBAOHSAS4z
/6ctcvRwlGr9Hyt7vVLROImD2t3rFdDDHLLL1znikK3JZvVbETyMFOMbOMQS33C/
4FtLGenZFXySjQw=
-----END CERTIFICATE-----

View file

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEICJ7FAxZXElbLQe/yrvr+ZqYQHKf9oGtzsasBZ8a32nk
-----END PRIVATE KEY-----

55
pkg/template/v2/jwk.go Normal file
View file

@ -0,0 +1,55 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
import (
"crypto/x509"
"github.com/lestrrat-go/jwx/jwk"
)
func jwkPublicKeyPem(jwkjson string) (string, error) {
k, err := jwk.ParseKey([]byte(jwkjson))
if err != nil {
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(string(mpk), "PUBLIC KEY")
}
func jwkPrivateKeyPem(jwkjson string) (string, error) {
k, err := jwk.ParseKey([]byte(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(string(mpk), "PRIVATE KEY")
}

62
pkg/template/v2/pem.go Normal file
View file

@ -0,0 +1,62 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
import (
"bytes"
"encoding/pem"
"errors"
"strings"
)
const (
errJunk = "error filtering pem: found junk"
)
func filterPEM(pemType, input string) (string, error) {
data := []byte(input)
var blocks []byte
var block *pem.Block
var rest []byte
for {
block, rest = pem.Decode(data)
data = rest
if block == nil {
break
}
if !strings.EqualFold(block.Type, pemType) {
continue
}
var buf bytes.Buffer
err := pem.Encode(&buf, block)
if err != nil {
return "", err
}
blocks = append(blocks, buf.Bytes()...)
}
if len(blocks) == 0 && len(rest) != 0 {
return "", errors.New(errJunk)
}
return string(blocks), nil
}
func pemEncode(thing, kind string) (string, error) {
buf := bytes.NewBuffer(nil)
err := pem.Encode(buf, &pem.Block{Type: kind, Bytes: []byte(thing)})
return buf.String(), err
}

View file

@ -0,0 +1,141 @@
/*
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
Original Author: Anish Ramasekar https://github.com/aramase
In: https://github.com/Azure/secrets-store-csi-driver-provider-azure/pull/332
*/
package template
import (
"crypto/x509"
"encoding/pem"
"fmt"
)
const (
errNilCert = "certificate is nil"
errFoundDisjunctCert = "found multiple leaf or disjunct certificates"
errNoLeafFound = "no leaf certificate found"
errChainCycle = "constructing chain resulted in cycle"
)
type node struct {
cert *x509.Certificate
parent *node
isParent bool
}
func fetchCertChains(data []byte) ([]byte, error) {
var newCertChain []*x509.Certificate
var pemData []byte
nodes, err := pemToNodes(data)
if err != nil {
return nil, err
}
// at the end of this computation, the output will be a single linked list
// the tail of the list will be the root node (which has no parents)
// the head of the list will be the leaf node (whose parent will be intermediate certs)
// (head) leaf -> intermediates -> root (tail)
for i := range nodes {
for j := range nodes {
// ignore same node to prevent generating a cycle
if i == j {
continue
}
// if ith node AuthorityKeyId is same as jth node SubjectKeyId, jth node was used
// to sign the ith certificate
if string(nodes[i].cert.AuthorityKeyId) == string(nodes[j].cert.SubjectKeyId) {
nodes[j].isParent = true
nodes[i].parent = nodes[j]
break
}
}
}
var foundLeaf bool
var leaf *node
for i := range nodes {
if !nodes[i].isParent {
if foundLeaf {
return nil, fmt.Errorf(errFoundDisjunctCert)
}
// this is the leaf node as it's not a parent for any other node
leaf = nodes[i]
foundLeaf = true
}
}
if leaf == nil {
return nil, fmt.Errorf(errNoLeafFound)
}
processedNodes := 0
// iterate through the directed list and append the nodes to new cert chain
for leaf != nil {
processedNodes++
// ensure we aren't stuck in a cyclic loop
if processedNodes > len(nodes) {
return pemData, fmt.Errorf(errChainCycle)
}
newCertChain = append(newCertChain, leaf.cert)
leaf = leaf.parent
}
for _, cert := range newCertChain {
b := &pem.Block{
Type: pemTypeCertificate,
Bytes: cert.Raw,
}
pemData = append(pemData, pem.EncodeToMemory(b)...)
}
return pemData, nil
}
func pemToNodes(data []byte) ([]*node, error) {
nodes := make([]*node, 0)
for {
// decode pem to der first
block, rest := pem.Decode(data)
data = rest
if block == nil {
break
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
// this should not be the case because ParseCertificate should return a non nil
// certificate when there is no error.
if cert == nil {
return nil, fmt.Errorf(errNilCert)
}
nodes = append(nodes, &node{
cert: cert,
parent: nil,
isParent: false,
})
}
return nodes, nil
}

180
pkg/template/v2/pem_test.go Normal file
View file

@ -0,0 +1,180 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
import "testing"
const (
certData = `-----BEGIN CERTIFICATE-----
MIIDHTCCAgWgAwIBAgIRAKC4yxy9QGocND+6avTf7BgwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yMTAzMjAyMDA4MDhaFw0yMTAzMjAyMDM4
MDhaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQC3o6/JdZEqNbqNRkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4Nzj
aG15owr92/11W0pxPUliRLti3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvW
Y8jh8A0LQALZiV/9QsrJdXZdS47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE
1gEDqnKfRxXI8DEQKXr+CKPUwCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4e
ugHe52vXHdh/HJ9VjNp0xOH1waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJa
YOOonQSEswveSv6PcG9AHvpNPot2Xs6hAgMBAAGjbjBsMA4GA1UdDwEB/wQEAwIC
pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBR00805mrpoonp95RmC3B6oLl+cGTAVBgNVHREEDjAMggpnb29ibGUuY29tMA0G
CSqGSIb3DQEBCwUAA4IBAQAipc1b6JrEDayPjpz5GM5krcI8dCWVd8re0a9bGjjN
ioWGlu/eTr5El0ffwCNZ2WLmL9rewfHf/bMvYz3ioFZJ2OTxfazqYXNggQz6cMfa
lbedDCdt5XLVX2TyerGvFram+9Uyvk3l0uM7rZnwAmdirG4Tv94QRaD3q4xTj/c0
mv+AggtK0aRFb9o47z/BypLdk5mhbf3Mmr88C8XBzEnfdYyf4JpTlZrYLBmDCu5d
9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC
QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
-----END CERTIFICATE-----
`
otherCert = `-----BEGIN CERTIFICATE-----
MIIBqjCCAU+gAwIBAgIRAPnGGsBUMbZhmh5QdnYdBmUwCgYIKoZIzj0EAwIwGjEY
MBYGA1UEAxMPaW50ZXJtZWRpYXRlLWNhMB4XDTIyMDIwOTEwMjUzMVoXDTIyMDIx
MDEwMjUzMVowDjEMMAoGA1UEAxMDZm9vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
QgAEqnxdeInykx8JZsLi13rZLekoG2cosQ3F+2InVNy7hCQ7soMqdaJsGQ6LFtov
ogUFtOOTRWrunblqNWGZsowHbKOBgTB/MA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUE
FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLtundVbuKd73OWzo6SY
by0Ajeb2MB8GA1UdIwQYMBaAFCLg80J/bZBbOd+Y8+V94l5xM2zEMA4GA1UdEQQH
MAWCA2ZvbzAKBggqhkjOPQQDAgNJADBGAiEA4K4SbVNqrEtl7RfwBfJFMnWI+X8D
zMPMc4Xqzp2qTxcCIQDsySgtiakypZfWakpB49zJph0kLwGK8xhWvGMUw1N1/w==
-----END CERTIFICATE-----
`
keyData = `-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3o6/JdZEqNbqN
RkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4NzjaG15owr92/11W0pxPUliRLti
3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvWY8jh8A0LQALZiV/9QsrJdXZd
S47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE1gEDqnKfRxXI8DEQKXr+CKPU
wCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4eugHe52vXHdh/HJ9VjNp0xOH1
waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJaYOOonQSEswveSv6PcG9AHvpN
Pot2Xs6hAgMBAAECggEACTGPrmVNZDCWa1Y2hkJ0J7SoNcw+9O4M/jwMp4l/PD6P
I98S78LYLCZhPLK17SmjUcnFO1AXKW1JeFS2D/fjfP256guvcqQNjLFoioxcOhVb
ZGyd1Mi8JPqP5wfOj16gBeYDwTkjz9wqldcfiZaL9XoXetkZecbzR2JwC2FtIVuC
0njTjMNYpaBKnoLb8OTR0EQz7lYEo2MkQiWryz8wseONnFmdfh18p+p10YgCbuCH
qesrWfDLLxaxZelNtDhDngg9LoCLmarYy7BgShacmUEgJTZ/x3xFC75thK3ln0OY
+ktTgvVotYYaZi7qAjQiEsTvkTAPg5RMpQLd2UIWsQKBgQDCBp+1vURbwGzmTNUg
HMipD6WDFdLc9DCacx6+ZqsEPTMWQbCpVZrDKiY0Rjt5F+xOCyMr00J5RDJXRC0G
+L7NcJdywOFutT7vB+cmETg7l/6PHweNYBnE66706eTL/KVYZMi4tEinarPWhHmL
jasfdLANtpDjdWkRt299TkPRbQKBgQDyS8Rr7KZdv04Csqkf+ASmiJpT5R6Y72kc
3XYpKETyB2FyPZkuh/zInMut9SkkSI9O/jA3zf956jj6sF1DHvp7T8KkIp5OAQeD
J9AF65m2MnZfHFUeJ6ZQsggwMWqrD0ycIWP7YWtiBHH+D1wGkjYrssq+bvG/yNpA
LtqdKq9lhQKBgQCZA2hIhy61vRckuEsLvCdzTGeW7UsR/XGnHEqOlaEhArKbRsrv
gBdA+qiOaSTV5svw8E+YbE7sG6AnuhhYeyreEYEeeoZOLJmpIG5mUwYp2UBj1nC6
SaOI7OVZOGu7g09SWokBQQxbG4cgEfFY4Sym7fs5lVTGTP3Dfwppo6NQMQKBgQCo
J5NDP3Lafwk58BpV+H/pv8YzUUDh7M2rXbtCpxLqUdr8OOnVlEUISWFF8m5CIyVq
MhjuscWLK9Wtjba7/YTjDaDM3sW05xv6lyfU5ATCoNTr/zLHgcb4HAZ4w+L+otiN
RtMnxB2NYf5mzuwUF2cG/secUEzwyAlIH/xStSwTLQKBgQCRvqF+rqxnegoOgwVW
qrWPv06wXD8dW2FlPpY5GXqA0l6erSK3YsQQToRmbem9ibPD7bd5P4gNbWfxwK4C
Wt+1Rcb8OrDhDJbYz85bXBnPecKp4EN0b9SHO0/dsCqn2w30emc+9T/4m1ZDkpBd
BixHvI/EJ8YK3ta5WdJWKC6hnA==
-----END PRIVATE KEY-----
`
)
const (
filterPrivateKey = "private key"
filterCert = "certificate"
)
func TestFilterPEM(t *testing.T) {
type args struct {
input string
pemType string
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "extract cert / cert first",
args: args{
input: certData + keyData,
pemType: filterCert,
},
want: certData,
},
{
name: "extract cert / key first",
args: args{
input: keyData + certData,
pemType: filterCert,
},
want: certData,
},
{
name: "extract multiple certs",
args: args{
input: keyData + certData + keyData + otherCert,
pemType: filterCert,
},
want: certData + otherCert,
},
{
name: "extract key",
args: args{
input: keyData + certData,
pemType: filterPrivateKey,
},
want: keyData,
},
{
name: "key with junk",
args: args{
input: certData + keyData + "some ---junk---",
pemType: filterPrivateKey,
},
want: keyData,
},
{
name: "begin/end with junk",
args: args{
// pem.Decode trims junk from the beginning of the input
// so we are able to decode both cert & key
input: "some junk" + certData + keyData + "some ---junk---",
pemType: filterPrivateKey,
},
want: keyData,
},
{
name: "interleaved junk",
args: args{
// can parse cert but not key due to junk
input: certData + "some junk" + keyData,
pemType: filterPrivateKey,
},
wantErr: true,
},
{
name: "err when junk",
args: args{
input: "---junk---",
pemType: filterPrivateKey,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := filterPEM(tt.args.pemType, tt.args.input)
if (err != nil) != tt.wantErr {
t.Errorf("filterPEM() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("filterPEM() = %v, want %v", got, tt.want)
}
})
}
}

109
pkg/template/v2/pkcs12.go Normal file
View file

@ -0,0 +1,109 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
import (
"bytes"
"crypto/x509"
"encoding/pem"
"fmt"
"golang.org/x/crypto/pkcs12"
)
func pkcs12keyPass(pass, input string) (string, error) {
blocks, err := pkcs12.ToPEM([]byte(input), pass)
if err != nil {
return "", fmt.Errorf(errDecodePKCS12WithPass, err)
}
var pemData []byte
for _, block := range blocks {
// remove bag attributes like localKeyID, friendlyName
block.Headers = nil
if block.Type == pemTypeCertificate {
continue
}
key, err := parsePrivateKey(block.Bytes)
if err != nil {
return "", err
}
// we use pkcs8 because it supports more key types (ecdsa, ed25519), not just RSA
block.Bytes, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return "", err
}
// report error if encode fails
var buf bytes.Buffer
if err := pem.Encode(&buf, block); err != nil {
return "", err
}
pemData = append(pemData, buf.Bytes()...)
}
return string(pemData), nil
}
func parsePrivateKey(block []byte) (interface{}, error) {
if k, err := x509.ParsePKCS1PrivateKey(block); err == nil {
return k, nil
}
if k, err := x509.ParsePKCS8PrivateKey(block); err == nil {
return k, nil
}
if k, err := x509.ParseECPrivateKey(block); err == nil {
return k, nil
}
return nil, fmt.Errorf(errParsePrivKey)
}
func pkcs12key(input string) (string, error) {
return pkcs12keyPass("", input)
}
func pkcs12certPass(pass, input string) (string, error) {
blocks, err := pkcs12.ToPEM([]byte(input), pass)
if err != nil {
return "", fmt.Errorf(errDecodeCertWithPass, err)
}
var pemData []byte
for _, block := range blocks {
if block.Type != pemTypeCertificate {
continue
}
// remove bag attributes like localKeyID, friendlyName
block.Headers = nil
// report error if encode fails
var buf bytes.Buffer
if err := pem.Encode(&buf, block); err != nil {
return "", err
}
pemData = append(pemData, buf.Bytes()...)
}
// try to order certificate chain. If it fails we return
// the unordered raw pem data.
// This fails if multiple leaf or disjunct certs are provided.
ordered, err := fetchCertChains(pemData)
if err != nil {
return string(pemData), nil
}
return string(ordered), nil
}
func pkcs12cert(input string) (string, error) {
return pkcs12certPass("", input)
}

View file

@ -0,0 +1,95 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
import (
"bytes"
"fmt"
tpl "text/template"
"github.com/Masterminds/sprig/v3"
corev1 "k8s.io/api/core/v1"
)
var tplFuncs = tpl.FuncMap{
"pkcs12key": pkcs12key,
"pkcs12keyPass": pkcs12keyPass,
"pkcs12cert": pkcs12cert,
"pkcs12certPass": pkcs12certPass,
"filterPEM": filterPEM,
"jwkPublicKeyPem": jwkPublicKeyPem,
"jwkPrivateKeyPem": jwkPrivateKeyPem,
}
// So other templating calls can use the same extra functions.
func FuncMap() tpl.FuncMap {
return tplFuncs
}
const (
errParse = "unable to parse template at key %s: %s"
errExecute = "unable to execute template at key %s: %s"
errDecodePKCS12WithPass = "unable to decode pkcs12 with password: %s"
errDecodeCertWithPass = "unable to decode pkcs12 certificate with password: %s"
errParsePrivKey = "unable to parse private key type"
pemTypeCertificate = "CERTIFICATE"
)
func init() {
sprigFuncs := sprig.TxtFuncMap()
delete(sprigFuncs, "env")
delete(sprigFuncs, "expandenv")
for k, v := range sprigFuncs {
tplFuncs[k] = v
}
}
// Execute renders the secret data as template. If an error occurs processing is stopped immediately.
func Execute(tpl, data map[string][]byte, secret *corev1.Secret) error {
if tpl == nil {
return nil
}
for k, v := range tpl {
val, err := execute(k, string(v), data)
if err != nil {
return fmt.Errorf(errExecute, k, err)
}
secret.Data[k] = val
}
return nil
}
func execute(k, val string, data map[string][]byte) ([]byte, error) {
strValData := make(map[string]string, len(data))
for k := range data {
strValData[k] = string(data[k])
}
t, err := tpl.New(k).
Funcs(tplFuncs).
Parse(val)
if err != nil {
return nil, fmt.Errorf(errParse, k, err)
}
buf := bytes.NewBuffer(nil)
err = t.Execute(buf, strValData)
if err != nil {
return nil, fmt.Errorf(errExecute, k, err)
}
return buf.Bytes(), nil
}

View file

@ -0,0 +1,520 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package template
import (
"os"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)
const (
pkcs12ContentNoPass = `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`
pkcs12ContentWithPass = `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`
pkcs12Cert = `-----BEGIN CERTIFICATE-----
MIIDHTCCAgWgAwIBAgIRAKC4yxy9QGocND+6avTf7BgwDQYJKoZIhvcNAQELBQAw
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yMTAzMjAyMDA4MDhaFw0yMTAzMjAyMDM4
MDhaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQC3o6/JdZEqNbqNRkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4Nzj
aG15owr92/11W0pxPUliRLti3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvW
Y8jh8A0LQALZiV/9QsrJdXZdS47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE
1gEDqnKfRxXI8DEQKXr+CKPUwCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4e
ugHe52vXHdh/HJ9VjNp0xOH1waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJa
YOOonQSEswveSv6PcG9AHvpNPot2Xs6hAgMBAAGjbjBsMA4GA1UdDwEB/wQEAwIC
pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBR00805mrpoonp95RmC3B6oLl+cGTAVBgNVHREEDjAMggpnb29ibGUuY29tMA0G
CSqGSIb3DQEBCwUAA4IBAQAipc1b6JrEDayPjpz5GM5krcI8dCWVd8re0a9bGjjN
ioWGlu/eTr5El0ffwCNZ2WLmL9rewfHf/bMvYz3ioFZJ2OTxfazqYXNggQz6cMfa
lbedDCdt5XLVX2TyerGvFram+9Uyvk3l0uM7rZnwAmdirG4Tv94QRaD3q4xTj/c0
mv+AggtK0aRFb9o47z/BypLdk5mhbf3Mmr88C8XBzEnfdYyf4JpTlZrYLBmDCu5d
9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC
QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
-----END CERTIFICATE-----
`
pkcs12Key = `-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3o6/JdZEqNbqN
RkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4NzjaG15owr92/11W0pxPUliRLti
3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvWY8jh8A0LQALZiV/9QsrJdXZd
S47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE1gEDqnKfRxXI8DEQKXr+CKPU
wCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4eugHe52vXHdh/HJ9VjNp0xOH1
waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJaYOOonQSEswveSv6PcG9AHvpN
Pot2Xs6hAgMBAAECggEACTGPrmVNZDCWa1Y2hkJ0J7SoNcw+9O4M/jwMp4l/PD6P
I98S78LYLCZhPLK17SmjUcnFO1AXKW1JeFS2D/fjfP256guvcqQNjLFoioxcOhVb
ZGyd1Mi8JPqP5wfOj16gBeYDwTkjz9wqldcfiZaL9XoXetkZecbzR2JwC2FtIVuC
0njTjMNYpaBKnoLb8OTR0EQz7lYEo2MkQiWryz8wseONnFmdfh18p+p10YgCbuCH
qesrWfDLLxaxZelNtDhDngg9LoCLmarYy7BgShacmUEgJTZ/x3xFC75thK3ln0OY
+ktTgvVotYYaZi7qAjQiEsTvkTAPg5RMpQLd2UIWsQKBgQDCBp+1vURbwGzmTNUg
HMipD6WDFdLc9DCacx6+ZqsEPTMWQbCpVZrDKiY0Rjt5F+xOCyMr00J5RDJXRC0G
+L7NcJdywOFutT7vB+cmETg7l/6PHweNYBnE66706eTL/KVYZMi4tEinarPWhHmL
jasfdLANtpDjdWkRt299TkPRbQKBgQDyS8Rr7KZdv04Csqkf+ASmiJpT5R6Y72kc
3XYpKETyB2FyPZkuh/zInMut9SkkSI9O/jA3zf956jj6sF1DHvp7T8KkIp5OAQeD
J9AF65m2MnZfHFUeJ6ZQsggwMWqrD0ycIWP7YWtiBHH+D1wGkjYrssq+bvG/yNpA
LtqdKq9lhQKBgQCZA2hIhy61vRckuEsLvCdzTGeW7UsR/XGnHEqOlaEhArKbRsrv
gBdA+qiOaSTV5svw8E+YbE7sG6AnuhhYeyreEYEeeoZOLJmpIG5mUwYp2UBj1nC6
SaOI7OVZOGu7g09SWokBQQxbG4cgEfFY4Sym7fs5lVTGTP3Dfwppo6NQMQKBgQCo
J5NDP3Lafwk58BpV+H/pv8YzUUDh7M2rXbtCpxLqUdr8OOnVlEUISWFF8m5CIyVq
MhjuscWLK9Wtjba7/YTjDaDM3sW05xv6lyfU5ATCoNTr/zLHgcb4HAZ4w+L+otiN
RtMnxB2NYf5mzuwUF2cG/secUEzwyAlIH/xStSwTLQKBgQCRvqF+rqxnegoOgwVW
qrWPv06wXD8dW2FlPpY5GXqA0l6erSK3YsQQToRmbem9ibPD7bd5P4gNbWfxwK4C
Wt+1Rcb8OrDhDJbYz85bXBnPecKp4EN0b9SHO0/dsCqn2w30emc+9T/4m1ZDkpBd
BixHvI/EJ8YK3ta5WdJWKC6hnA==
-----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-----
`
)
func TestExecute(t *testing.T) {
tbl := []struct {
name string
tpl map[string][]byte
data map[string][]byte
expetedData map[string][]byte
expErr string
}{
{
name: "test empty",
tpl: nil,
data: nil,
},
{
name: "b64dec func",
tpl: map[string][]byte{
"foo": []byte("{{ .secret | b64dec }}"),
},
data: map[string][]byte{
"secret": []byte("MTIzNA=="),
},
expetedData: map[string][]byte{
"foo": []byte("1234"),
},
},
{
name: "fromJSON func",
tpl: map[string][]byte{
"foo": []byte("{{ $var := .secret | fromJson }}{{ $var.foo }}"),
},
data: map[string][]byte{
"secret": []byte(`{"foo": "bar"}`),
},
expetedData: map[string][]byte{
"foo": []byte("bar"),
},
},
{
name: "from & toJSON func",
tpl: map[string][]byte{
"foo": []byte("{{ $var := .secret | fromJson }}{{ $var.foo | toJson }}"),
},
data: map[string][]byte{
"secret": []byte(`{"foo": {"baz":"bang"}}`),
},
expetedData: map[string][]byte{
"foo": []byte(`{"baz":"bang"}`),
},
},
{
name: "use sprig functions",
tpl: map[string][]byte{
"foo": []byte(`{{ .path | ext }}`),
},
data: map[string][]byte{
"path": []byte(`foo/bar/baz.exe`),
},
expetedData: map[string][]byte{
"foo": []byte(`.exe`),
},
},
{
name: "multiline template",
tpl: map[string][]byte{
"cfg": []byte(`
datasources:
- name: Graphite
type: graphite
access: proxy
url: http://localhost:8080
password: "{{ .password }}"
user: "{{ .user }}"`),
},
data: map[string][]byte{
"user": []byte(`foobert`),
"password": []byte("harharhar"),
},
expetedData: map[string][]byte{
"cfg": []byte(`
datasources:
- name: Graphite
type: graphite
access: proxy
url: http://localhost:8080
password: "harharhar"
user: "foobert"`),
},
},
{
name: "base64 pipeline",
tpl: map[string][]byte{
"foo": []byte(`{{ "123412341234" | b64enc | b64dec }}`),
},
data: map[string][]byte{},
expetedData: map[string][]byte{
"foo": []byte("123412341234"),
},
},
{
name: "base64 pkcs12 extract",
tpl: map[string][]byte{
"key": []byte(`{{ .secret | b64dec | pkcs12key }}`),
"cert": []byte(`{{ .secret | b64dec | pkcs12cert }}`),
},
data: map[string][]byte{
"secret": []byte(pkcs12ContentNoPass),
},
expetedData: map[string][]byte{
"key": []byte(pkcs12Key),
"cert": []byte(pkcs12Cert),
},
},
{
name: "base64 pkcs12 extract with password",
tpl: map[string][]byte{
"key": []byte(`{{ .secret | b64dec | pkcs12keyPass "123456" }}`),
"cert": []byte(`{{ .secret | b64dec | pkcs12certPass "123456" }}`),
},
data: map[string][]byte{
"secret": []byte(pkcs12ContentWithPass),
},
expetedData: map[string][]byte{
"key": []byte(pkcs12Key),
"cert": []byte(pkcs12Cert),
},
},
{
name: "base64 decode error",
tpl: map[string][]byte{
"key": []byte(`{{ .example | b64dec }}`),
},
data: map[string][]byte{
"example": []byte("iam_no_base64"),
},
expErr: "", // silent error
},
{
name: "pkcs12 key wrong password",
tpl: map[string][]byte{
"key": []byte(`{{ .secret | b64dec | pkcs12keyPass "wrong" }}`),
},
data: map[string][]byte{
"secret": []byte(pkcs12ContentWithPass),
},
expErr: "unable to decode pkcs12",
},
{
name: "pkcs12 cert wrong password",
tpl: map[string][]byte{
"cert": []byte(`{{ .secret | b64dec | pkcs12certPass "wrong" }}`),
},
data: map[string][]byte{
"secret": []byte(pkcs12ContentWithPass),
},
expErr: "unable to decode pkcs12",
},
{
name: "fromJSON error",
tpl: map[string][]byte{
"key": []byte(`{{ "{ # no json # }" | fromJson }}`),
},
data: map[string][]byte{},
expErr: "", // silent error
},
{
name: "template syntax error",
tpl: map[string][]byte{
"key": []byte(`{{ #xx }}`),
},
data: map[string][]byte{},
expErr: "unable to parse template",
},
{
name: "jwk rsa pub pem",
tpl: map[string][]byte{
"fn": []byte(`{{ .secret | jwkPublicKeyPem }}`),
},
data: map[string][]byte{
"secret": []byte(jwkPubRSA),
},
expetedData: map[string][]byte{
"fn": []byte(jwkPubRSAPKIX),
},
},
{
name: "jwk rsa priv pem",
tpl: map[string][]byte{
"fn": []byte(`{{ .secret | jwkPrivateKeyPem }}`),
},
data: map[string][]byte{
"secret": []byte(jwkPrivRSA),
},
expetedData: map[string][]byte{
"fn": []byte(jwkPrivRSAPKCS8),
},
},
{
name: "jwk ecdsa pub pem",
tpl: map[string][]byte{
"fn": []byte(`{{ .secret | jwkPublicKeyPem }}`),
},
data: map[string][]byte{
"secret": []byte(jwkPubEC),
},
expetedData: map[string][]byte{
"fn": []byte(jwkPubECPKIX),
},
},
{
name: "jwk ecdsa priv pem",
tpl: map[string][]byte{
"fn": []byte(`{{ .secret | jwkPrivateKeyPem }}`),
},
data: map[string][]byte{
"secret": []byte(jwkPrivEC),
},
expetedData: map[string][]byte{
"fn": []byte(jwkPrivECPKCS8),
},
},
{
name: "filter pem certificate",
tpl: map[string][]byte{
"fn": []byte(`{{ .secret | filterPEM "CERTIFICATE" }}`),
},
data: map[string][]byte{
"secret": []byte(jwkPrivRSAPKCS8 + pkcs12Cert),
},
expetedData: map[string][]byte{
"fn": []byte(pkcs12Cert),
},
},
}
for i := range tbl {
row := tbl[i]
t.Run(row.name, func(t *testing.T) {
sec := &corev1.Secret{
Data: make(map[string][]byte),
}
err := Execute(row.tpl, row.data, sec)
if !ErrorContains(err, row.expErr) {
t.Errorf("unexpected error: %s, expected: %s", err, row.expErr)
}
if row.expetedData == nil {
return
}
assert.EqualValues(t, row.expetedData, sec.Data)
})
}
}
func ErrorContains(out error, want string) bool {
if out == nil {
return want == ""
}
if want == "" {
return false
}
return strings.Contains(out.Error(), want)
}
func TestPkcs12certPass(t *testing.T) {
const (
leafCertPath = "_testdata/foo.crt"
intermediateCertPath = "_testdata/intermediate-ca.crt"
rootCertPath = "_testdata/root-ca.crt"
disjunctCertPath = "_testdata/disjunct-root-ca.crt"
)
type args struct {
pass string
filename string
}
type testCase struct {
name string
args args
want []string
wantErr bool
}
tests := []testCase{
{
// this case expects the whole chain to be stored
// in a single bag.
// bag(1): leaf/root/intermediate cert
// bag(2): private key
name: "read file without password",
args: args{
pass: "",
filename: "_testdata/foo-nopass.pfx",
},
want: []string{
// this order is important
leafCertPath,
intermediateCertPath,
rootCertPath,
},
},
{
// same as above but with password
name: "read file with password",
args: args{
pass: "1234",
filename: "_testdata/foo-withpass-1234.pfx",
},
want: []string{
// this order is important
leafCertPath,
intermediateCertPath,
rootCertPath,
},
},
{
// cert chain may be stored in different bags
// this test case uses a pfx that has the following structure:
// bag(1): leaf certificate
// bag(2): root + intermediate cert
// bag(3): private key
name: "read multibag cert chain",
args: args{
pass: "",
filename: "_testdata/foo-multibag-nopass.pfx",
},
want: []string{
// this order is important
leafCertPath,
intermediateCertPath,
rootCertPath,
},
},
{
// cert chain may contain a disjunct cert
// bag(1): leaf/root/intermediate/disjunct
// bag(2): private key
name: "read disjunct cert chain",
args: args{
pass: "",
filename: "_testdata/foo-disjunct-nopass.pfx",
},
want: []string{
// this order is important
leafCertPath,
rootCertPath,
intermediateCertPath,
disjunctCertPath,
},
},
{
name: "read file wrong password",
args: args{
pass: "wrongpass",
filename: "_testdata/foo-withpass-1234.pfx",
},
wantErr: true,
},
}
testFunc := func(t *testing.T, tc testCase) {
archive, err := os.ReadFile(tc.args.filename)
if err != nil {
t.Error(err)
}
var expOut []byte
for _, w := range tc.want {
c, err := os.ReadFile(w)
if err != nil {
t.Error(err)
}
expOut = append(expOut, c...)
}
got, err := pkcs12certPass(tc.args.pass, string(archive))
if (err != nil) != tc.wantErr {
t.Errorf("pkcs12certPass() error = %v, wantErr %v", err, tc.wantErr)
return
}
if diff := cmp.Diff(string(expOut), got); diff != "" {
t.Errorf("pkcs12certPass() = diff:\n%s", diff)
}
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testFunc(t, tt)
})
}
}