mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
Merge pull request #559 from willemm/feat/generic_webhook
Add generic webhook provider
This commit is contained in:
commit
44d4cf061b
12 changed files with 1297 additions and 2 deletions
|
@ -77,6 +77,10 @@ type SecretStoreProvider struct {
|
||||||
// Alibaba configures this store to sync secrets using Alibaba Cloud provider
|
// Alibaba configures this store to sync secrets using Alibaba Cloud provider
|
||||||
// +optional
|
// +optional
|
||||||
Alibaba *AlibabaProvider `json:"alibaba,omitempty"`
|
Alibaba *AlibabaProvider `json:"alibaba,omitempty"`
|
||||||
|
|
||||||
|
// Webhook configures this store to sync secrets using a generic templated webhook
|
||||||
|
// +optional
|
||||||
|
Webhook *WebhookProvider `json:"webhook,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecretStoreRetrySettings struct {
|
type SecretStoreRetrySettings struct {
|
||||||
|
|
101
apis/externalsecrets/v1alpha1/secretstore_webhook_types.go
Normal file
101
apis/externalsecrets/v1alpha1/secretstore_webhook_types.go
Normal 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.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AkeylessProvider Configures an store to sync secrets using Akeyless KV.
|
||||||
|
type WebhookProvider struct {
|
||||||
|
// Webhook Method
|
||||||
|
// +optional, default GET
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
|
||||||
|
// Webhook url to call
|
||||||
|
URL string `json:"url"`
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
// +optional
|
||||||
|
Headers map[string]string `json:"headers,omitempty"`
|
||||||
|
|
||||||
|
// Body
|
||||||
|
// +optional
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
|
||||||
|
// Timeout
|
||||||
|
// +optional
|
||||||
|
Timeout *metav1.Duration `json:"timeout,omitempty"`
|
||||||
|
|
||||||
|
// Result formatting
|
||||||
|
Result WebhookResult `json:"result"`
|
||||||
|
|
||||||
|
// Secrets to fill in templates
|
||||||
|
// These secrets will be passed to the templating function as key value pairs under the given name
|
||||||
|
// +optional
|
||||||
|
Secrets []WebhookSecret `json:"secrets,omitempty"`
|
||||||
|
|
||||||
|
// PEM encoded CA bundle used to validate webhook server certificate. Only used
|
||||||
|
// if the Server URL is using HTTPS protocol. This parameter is ignored for
|
||||||
|
// plain HTTP protocol connection. If not set the system root certificates
|
||||||
|
// are used to validate the TLS connection.
|
||||||
|
// +optional
|
||||||
|
CABundle []byte `json:"caBundle,omitempty"`
|
||||||
|
|
||||||
|
// The provider for the CA bundle to use to validate webhook server certificate.
|
||||||
|
// +optional
|
||||||
|
CAProvider *WebhookCAProvider `json:"caProvider,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookCAProviderType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
WebhookCAProviderTypeSecret WebhookCAProviderType = "Secret"
|
||||||
|
WebhookCAProviderTypeConfigMap WebhookCAProviderType = "ConfigMap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines a location to fetch the cert for the webhook provider from.
|
||||||
|
type WebhookCAProvider struct {
|
||||||
|
// The type of provider to use such as "Secret", or "ConfigMap".
|
||||||
|
// +kubebuilder:validation:Enum="Secret";"ConfigMap"
|
||||||
|
Type WebhookCAProviderType `json:"type"`
|
||||||
|
|
||||||
|
// The name of the object located at the provider type.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// The key the value inside of the provider type to use, only used with "Secret" type
|
||||||
|
// +kubebuilder:validation:Optional
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
|
||||||
|
// The namespace the Provider type is in.
|
||||||
|
// +optional
|
||||||
|
Namespace *string `json:"namespace,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookResult struct {
|
||||||
|
// Json path of return value
|
||||||
|
// +optional
|
||||||
|
JSONPath string `json:"jsonPath,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookSecret struct {
|
||||||
|
// Name of this secret in templates
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Secret ref to fill in credentials
|
||||||
|
SecretRef esmeta.SecretKeySelector `json:"secretRef"`
|
||||||
|
}
|
|
@ -934,6 +934,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
|
||||||
*out = new(AlibabaProvider)
|
*out = new(AlibabaProvider)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.Webhook != nil {
|
||||||
|
in, out := &in.Webhook, &out.Webhook
|
||||||
|
*out = new(WebhookProvider)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretStoreProvider.
|
||||||
|
@ -1275,6 +1280,102 @@ func (in *VaultProvider) DeepCopy() *VaultProvider {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *WebhookCAProvider) DeepCopyInto(out *WebhookCAProvider) {
|
||||||
|
*out = *in
|
||||||
|
if in.Namespace != nil {
|
||||||
|
in, out := &in.Namespace, &out.Namespace
|
||||||
|
*out = new(string)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookCAProvider.
|
||||||
|
func (in *WebhookCAProvider) DeepCopy() *WebhookCAProvider {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(WebhookCAProvider)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *WebhookProvider) DeepCopyInto(out *WebhookProvider) {
|
||||||
|
*out = *in
|
||||||
|
if in.Headers != nil {
|
||||||
|
in, out := &in.Headers, &out.Headers
|
||||||
|
*out = make(map[string]string, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.Timeout != nil {
|
||||||
|
in, out := &in.Timeout, &out.Timeout
|
||||||
|
*out = new(v1.Duration)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
out.Result = in.Result
|
||||||
|
if in.Secrets != nil {
|
||||||
|
in, out := &in.Secrets, &out.Secrets
|
||||||
|
*out = make([]WebhookSecret, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.CABundle != nil {
|
||||||
|
in, out := &in.CABundle, &out.CABundle
|
||||||
|
*out = make([]byte, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.CAProvider != nil {
|
||||||
|
in, out := &in.CAProvider, &out.CAProvider
|
||||||
|
*out = new(WebhookCAProvider)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookProvider.
|
||||||
|
func (in *WebhookProvider) DeepCopy() *WebhookProvider {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(WebhookProvider)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *WebhookResult) DeepCopyInto(out *WebhookResult) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookResult.
|
||||||
|
func (in *WebhookResult) DeepCopy() *WebhookResult {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(WebhookResult)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *WebhookSecret) DeepCopyInto(out *WebhookSecret) {
|
||||||
|
*out = *in
|
||||||
|
in.SecretRef.DeepCopyInto(&out.SecretRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookSecret.
|
||||||
|
func (in *WebhookSecret) DeepCopy() *WebhookSecret {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(WebhookSecret)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *YandexLockboxAuth) DeepCopyInto(out *YandexLockboxAuth) {
|
func (in *YandexLockboxAuth) DeepCopyInto(out *YandexLockboxAuth) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
|
@ -927,6 +927,106 @@ spec:
|
||||||
- path
|
- path
|
||||||
- server
|
- server
|
||||||
type: object
|
type: object
|
||||||
|
webhook:
|
||||||
|
description: Webhook configures this store to sync secrets using
|
||||||
|
a generic templated webhook
|
||||||
|
properties:
|
||||||
|
body:
|
||||||
|
description: Body
|
||||||
|
type: string
|
||||||
|
caBundle:
|
||||||
|
description: PEM encoded CA bundle used to validate webhook
|
||||||
|
server certificate. Only used if the Server URL is using
|
||||||
|
HTTPS protocol. This parameter is ignored for plain HTTP
|
||||||
|
protocol connection. If not set the system root certificates
|
||||||
|
are used to validate the TLS connection.
|
||||||
|
format: byte
|
||||||
|
type: string
|
||||||
|
caProvider:
|
||||||
|
description: The provider for the CA bundle to use to validate
|
||||||
|
webhook server certificate.
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key the value inside of the provider
|
||||||
|
type to use, only used with "Secret" type
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: The name of the object located at the provider
|
||||||
|
type.
|
||||||
|
type: string
|
||||||
|
namespace:
|
||||||
|
description: The namespace the Provider type is in.
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The type of provider to use such as "Secret",
|
||||||
|
or "ConfigMap".
|
||||||
|
enum:
|
||||||
|
- Secret
|
||||||
|
- ConfigMap
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
|
headers:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: Headers
|
||||||
|
type: object
|
||||||
|
method:
|
||||||
|
description: Webhook Method
|
||||||
|
type: string
|
||||||
|
result:
|
||||||
|
description: Result formatting
|
||||||
|
properties:
|
||||||
|
jsonPath:
|
||||||
|
description: Json path of return value
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
secrets:
|
||||||
|
description: Secrets to fill in templates These secrets will
|
||||||
|
be passed to the templating function as key value pairs
|
||||||
|
under the given name
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: Name of this secret in templates
|
||||||
|
type: string
|
||||||
|
secretRef:
|
||||||
|
description: Secret ref to fill in credentials
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key of the entry in the Secret
|
||||||
|
resource's `data` field to be used. Some instances
|
||||||
|
of this field may be defaulted, in others it may
|
||||||
|
be required.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: The name of the Secret resource being
|
||||||
|
referred to.
|
||||||
|
type: string
|
||||||
|
namespace:
|
||||||
|
description: Namespace of the resource being referred
|
||||||
|
to. Ignored if referent is not cluster-scoped.
|
||||||
|
cluster-scoped defaults to the namespace of the
|
||||||
|
referent.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- secretRef
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
timeout:
|
||||||
|
description: Timeout
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Webhook url to call
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- result
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
yandexlockbox:
|
yandexlockbox:
|
||||||
description: YandexLockbox configures this store to sync secrets
|
description: YandexLockbox configures this store to sync secrets
|
||||||
using Yandex Lockbox provider
|
using Yandex Lockbox provider
|
||||||
|
|
|
@ -927,6 +927,106 @@ spec:
|
||||||
- path
|
- path
|
||||||
- server
|
- server
|
||||||
type: object
|
type: object
|
||||||
|
webhook:
|
||||||
|
description: Webhook configures this store to sync secrets using
|
||||||
|
a generic templated webhook
|
||||||
|
properties:
|
||||||
|
body:
|
||||||
|
description: Body
|
||||||
|
type: string
|
||||||
|
caBundle:
|
||||||
|
description: PEM encoded CA bundle used to validate webhook
|
||||||
|
server certificate. Only used if the Server URL is using
|
||||||
|
HTTPS protocol. This parameter is ignored for plain HTTP
|
||||||
|
protocol connection. If not set the system root certificates
|
||||||
|
are used to validate the TLS connection.
|
||||||
|
format: byte
|
||||||
|
type: string
|
||||||
|
caProvider:
|
||||||
|
description: The provider for the CA bundle to use to validate
|
||||||
|
webhook server certificate.
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key the value inside of the provider
|
||||||
|
type to use, only used with "Secret" type
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: The name of the object located at the provider
|
||||||
|
type.
|
||||||
|
type: string
|
||||||
|
namespace:
|
||||||
|
description: The namespace the Provider type is in.
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
description: The type of provider to use such as "Secret",
|
||||||
|
or "ConfigMap".
|
||||||
|
enum:
|
||||||
|
- Secret
|
||||||
|
- ConfigMap
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
|
headers:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: Headers
|
||||||
|
type: object
|
||||||
|
method:
|
||||||
|
description: Webhook Method
|
||||||
|
type: string
|
||||||
|
result:
|
||||||
|
description: Result formatting
|
||||||
|
properties:
|
||||||
|
jsonPath:
|
||||||
|
description: Json path of return value
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
secrets:
|
||||||
|
description: Secrets to fill in templates These secrets will
|
||||||
|
be passed to the templating function as key value pairs
|
||||||
|
under the given name
|
||||||
|
items:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
description: Name of this secret in templates
|
||||||
|
type: string
|
||||||
|
secretRef:
|
||||||
|
description: Secret ref to fill in credentials
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key of the entry in the Secret
|
||||||
|
resource's `data` field to be used. Some instances
|
||||||
|
of this field may be defaulted, in others it may
|
||||||
|
be required.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: The name of the Secret resource being
|
||||||
|
referred to.
|
||||||
|
type: string
|
||||||
|
namespace:
|
||||||
|
description: Namespace of the resource being referred
|
||||||
|
to. Ignored if referent is not cluster-scoped.
|
||||||
|
cluster-scoped defaults to the namespace of the
|
||||||
|
referent.
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- secretRef
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
timeout:
|
||||||
|
description: Timeout
|
||||||
|
type: string
|
||||||
|
url:
|
||||||
|
description: Webhook url to call
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- result
|
||||||
|
- url
|
||||||
|
type: object
|
||||||
yandexlockbox:
|
yandexlockbox:
|
||||||
description: YandexLockbox configures this store to sync secrets
|
description: YandexLockbox configures this store to sync secrets
|
||||||
using Yandex Lockbox provider
|
using Yandex Lockbox provider
|
||||||
|
|
118
docs/provider-webhook.md
Normal file
118
docs/provider-webhook.md
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
## Generic Webhook
|
||||||
|
|
||||||
|
External Secrets Operator can integrate with simple web apis by specifying the endpoint
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
First, create a SecretStore with a webhook backend. We'll use a static user/password `root`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1alpha1
|
||||||
|
kind: SecretStore
|
||||||
|
metadata:
|
||||||
|
name: webhook-backend
|
||||||
|
spec:
|
||||||
|
provider:
|
||||||
|
webhook:
|
||||||
|
url: "http://httpbin.org/get?parameter={{ .remoteRef.key }}"
|
||||||
|
result:
|
||||||
|
jsonPath: "$.args.parameter"
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Basic {{ print .auth.username ":" .auth.password | b64enc }}
|
||||||
|
secrets:
|
||||||
|
- name: auth
|
||||||
|
secretRef:
|
||||||
|
name: webhook-credentials
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: webhook-credentials
|
||||||
|
data:
|
||||||
|
username: dGVzdA== # "test"
|
||||||
|
password: dGVzdA== # "test"
|
||||||
|
```
|
||||||
|
|
||||||
|
NB: This is obviously not practical because it just returns the key as the result, but it shows how it works
|
||||||
|
|
||||||
|
Now create an ExternalSecret that uses the above SecretStore:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1alpha1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: webhook-example
|
||||||
|
spec:
|
||||||
|
refreshInterval: "15s"
|
||||||
|
secretStoreRef:
|
||||||
|
name: webhook-backend
|
||||||
|
kind: SecretStore
|
||||||
|
target:
|
||||||
|
name: example-sync
|
||||||
|
data:
|
||||||
|
- secretKey: foobar
|
||||||
|
remoteRef:
|
||||||
|
key: secret
|
||||||
|
---
|
||||||
|
# will create a secret with:
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: example-sync
|
||||||
|
data:
|
||||||
|
foobar: c2VjcmV0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Limitations
|
||||||
|
|
||||||
|
Webhook does not support authorization, other than what can be sent by generating http headers
|
||||||
|
|
||||||
|
### Templating
|
||||||
|
|
||||||
|
Generic WebHook provider uses the templating engine to generate the API call. It can be used in the url, headers, body and result.jsonPath fields.
|
||||||
|
|
||||||
|
The provider inserts the secret to be retrieved in the object named `remoteRef`.
|
||||||
|
|
||||||
|
In addition, secrets can be added as named objects, for example to use in authorization headers.
|
||||||
|
Each secret has a `name` property which determines the name of the object in the templating engine.
|
||||||
|
|
||||||
|
### All Parameters
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: external-secrets.io/v1alpha1
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
metadata:
|
||||||
|
name: statervault
|
||||||
|
spec:
|
||||||
|
provider:
|
||||||
|
webhook:
|
||||||
|
# Url to call. Use templating engine to fill in the request parameters
|
||||||
|
url: <url>
|
||||||
|
# http method, defaults to GET
|
||||||
|
method: <method>
|
||||||
|
# Timeout in duration (1s, 1m, etc)
|
||||||
|
timeout: 1s
|
||||||
|
result:
|
||||||
|
# [jsonPath](https://jsonpath.com) syntax, which also can be templated
|
||||||
|
jsonPath: <jsonPath>
|
||||||
|
# Map of headers, can be templated
|
||||||
|
headers:
|
||||||
|
<Header-Name>: <header contents>
|
||||||
|
# Body to sent as request, can be templated (optional)
|
||||||
|
body: <body>
|
||||||
|
# List of secrets to expose to the templating engine
|
||||||
|
secrets:
|
||||||
|
# Use this name to refer to this secret in templating, above
|
||||||
|
- name: <name>
|
||||||
|
secretRef:
|
||||||
|
namespace: <namespace>
|
||||||
|
name: <name>
|
||||||
|
# Add CAs here for the TLS handshake
|
||||||
|
caBundle: <base64 encoded cabundle>
|
||||||
|
caProvider:
|
||||||
|
type: Secret or COnfigMap
|
||||||
|
name: <name of secret or configmap>
|
||||||
|
namespace: <namespace>
|
||||||
|
key: <key inside secret>
|
||||||
|
```
|
||||||
|
|
13
go.mod
13
go.mod
|
@ -4,6 +4,7 @@ go 1.17
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1 => ./apis/externalsecrets/v1alpha1
|
github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1 => ./apis/externalsecrets/v1alpha1
|
||||||
|
github.com/external-secrets/external-secrets/e2e/framework/log => ./e2e/framework/log
|
||||||
github.com/external-secrets/external-secrets/pkg/provider/gitlab => ./pkg/provider/gitlab
|
github.com/external-secrets/external-secrets/pkg/provider/gitlab => ./pkg/provider/gitlab
|
||||||
google.golang.org/grpc => google.golang.org/grpc v1.27.0
|
google.golang.org/grpc => google.golang.org/grpc v1.27.0
|
||||||
k8s.io/api => k8s.io/api v0.21.2
|
k8s.io/api => k8s.io/api v0.21.2
|
||||||
|
@ -38,6 +39,10 @@ require (
|
||||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.7
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.7
|
||||||
github.com/IBM/go-sdk-core/v5 v5.5.0
|
github.com/IBM/go-sdk-core/v5 v5.5.0
|
||||||
github.com/IBM/secrets-manager-go-sdk v1.0.23
|
github.com/IBM/secrets-manager-go-sdk v1.0.23
|
||||||
|
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/PaesslerAG/jsonpath v0.1.1
|
||||||
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2
|
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2
|
||||||
github.com/akeylesslabs/akeyless-go/v2 v2.5.11
|
github.com/akeylesslabs/akeyless-go/v2 v2.5.11
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1192
|
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1192
|
||||||
|
@ -49,6 +54,8 @@ require (
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/googleapis/gax-go v1.0.3
|
github.com/googleapis/gax-go v1.0.3
|
||||||
github.com/hashicorp/vault/api v1.0.5-0.20210224012239-b540be4b7ec4
|
github.com/hashicorp/vault/api v1.0.5-0.20210224012239-b540be4b7ec4
|
||||||
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
|
github.com/kr/pretty v0.2.1 // indirect
|
||||||
github.com/lestrrat-go/jwx v1.2.1
|
github.com/lestrrat-go/jwx v1.2.1
|
||||||
github.com/onsi/ginkgo v1.16.5
|
github.com/onsi/ginkgo v1.16.5
|
||||||
github.com/onsi/gomega v1.16.0
|
github.com/onsi/gomega v1.16.0
|
||||||
|
@ -67,6 +74,7 @@ require (
|
||||||
google.golang.org/api v0.45.0
|
google.golang.org/api v0.45.0
|
||||||
google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3
|
google.golang.org/genproto v0.0.0-20210413151531-c14fb6ef47c3
|
||||||
google.golang.org/grpc v1.43.0
|
google.golang.org/grpc v1.43.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919
|
||||||
k8s.io/api v0.21.3
|
k8s.io/api v0.21.3
|
||||||
k8s.io/apimachinery v0.21.3
|
k8s.io/apimachinery v0.21.3
|
||||||
|
@ -88,6 +96,7 @@ require (
|
||||||
github.com/Azure/go-autorest/logger v0.2.0 // indirect
|
github.com/Azure/go-autorest/logger v0.2.0 // indirect
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||||
|
github.com/PaesslerAG/gval v1.0.0 // indirect
|
||||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v0.23.0 // indirect
|
github.com/aws/aws-sdk-go-v2 v0.23.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
@ -133,7 +142,6 @@ require (
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.11 // indirect
|
github.com/json-iterator/go v1.1.11 // indirect
|
||||||
github.com/kr/pretty v0.2.1 // indirect
|
|
||||||
github.com/leodido/go-urn v1.2.0 // indirect
|
github.com/leodido/go-urn v1.2.0 // indirect
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect
|
github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect
|
||||||
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
|
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
|
||||||
|
@ -143,8 +151,10 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
|
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.0 // indirect
|
||||||
github.com/moby/spdystream v0.2.0 // indirect
|
github.com/moby/spdystream v0.2.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
|
@ -184,7 +194,6 @@ require (
|
||||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
|
||||||
honnef.co/go/tools v0.1.4 // indirect
|
honnef.co/go/tools v0.1.4 // indirect
|
||||||
k8s.io/apiextensions-apiserver v0.21.2 // indirect
|
k8s.io/apiextensions-apiserver v0.21.2 // indirect
|
||||||
k8s.io/component-base v0.21.2 // indirect
|
k8s.io/component-base v0.21.2 // indirect
|
||||||
|
|
15
go.sum
15
go.sum
|
@ -72,9 +72,20 @@ github.com/IBM/go-sdk-core/v5 v5.5.0 h1:etP4m0kzMCxjZRI4Bu6cRTfK9YDvY3xFuagXugkC
|
||||||
github.com/IBM/go-sdk-core/v5 v5.5.0/go.mod h1:Sn+z+qTDREQvCr+UFa22TqqfXNxx3o723y8GsfLV8e0=
|
github.com/IBM/go-sdk-core/v5 v5.5.0/go.mod h1:Sn+z+qTDREQvCr+UFa22TqqfXNxx3o723y8GsfLV8e0=
|
||||||
github.com/IBM/secrets-manager-go-sdk v1.0.23 h1:YvRB2jmCfXVwTiTozCNVIRfl6q9Qcl2JiL4x6chOSI4=
|
github.com/IBM/secrets-manager-go-sdk v1.0.23 h1:YvRB2jmCfXVwTiTozCNVIRfl6q9Qcl2JiL4x6chOSI4=
|
||||||
github.com/IBM/secrets-manager-go-sdk v1.0.23/go.mod h1:ruP6eQ0/J/zHBbnMfUyWeMsTe9vgnGL4rDeLiSKhZhU=
|
github.com/IBM/secrets-manager-go-sdk v1.0.23/go.mod h1:ruP6eQ0/J/zHBbnMfUyWeMsTe9vgnGL4rDeLiSKhZhU=
|
||||||
|
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/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
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/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/PaesslerAG/gval v1.0.0 h1:GEKnRwkWDdf9dOmKcNrar9EA1bz1z9DqPIO1+iLzhd8=
|
||||||
|
github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I=
|
||||||
|
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
|
||||||
|
github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
|
||||||
|
github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2 h1:1h4udX3Y5KgSG0m4Th2bHfaYxZB9fbngiij9PrKEp6c=
|
github.com/akeylesslabs/akeyless-go-cloud-id v0.3.2 h1:1h4udX3Y5KgSG0m4Th2bHfaYxZB9fbngiij9PrKEp6c=
|
||||||
|
@ -414,6 +425,8 @@ github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 h1:e1ok06zG
|
||||||
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
|
github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10=
|
||||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
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/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
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/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.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
|
@ -507,6 +520,7 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
@ -522,6 +536,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
||||||
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
||||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||||
|
|
|
@ -26,5 +26,6 @@ import (
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/ibm"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/ibm"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/oracle"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/oracle"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/vault"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/vault"
|
||||||
|
_ "github.com/external-secrets/external-secrets/pkg/provider/webhook"
|
||||||
_ "github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox"
|
_ "github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox"
|
||||||
)
|
)
|
||||||
|
|
401
pkg/provider/webhook/webhook.go
Normal file
401
pkg/provider/webhook/webhook.go
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
/*
|
||||||
|
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 webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
tpl "text/template"
|
||||||
|
|
||||||
|
"github.com/Masterminds/sprig"
|
||||||
|
"github.com/PaesslerAG/jsonpath"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
|
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider satisfies the provider interface.
|
||||||
|
type Provider struct{}
|
||||||
|
|
||||||
|
type WebHook struct {
|
||||||
|
kube client.Client
|
||||||
|
store esv1alpha1.GenericStore
|
||||||
|
namespace string
|
||||||
|
storeKind string
|
||||||
|
http *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
schema.Register(&Provider{}, &esv1alpha1.SecretStoreProvider{
|
||||||
|
Webhook: &esv1alpha1.WebhookProvider{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||||
|
whClient := &WebHook{
|
||||||
|
kube: kube,
|
||||||
|
store: store,
|
||||||
|
namespace: namespace,
|
||||||
|
storeKind: store.GetObjectKind().GroupVersionKind().Kind,
|
||||||
|
}
|
||||||
|
provider, err := getProvider(store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
whClient.http, err = whClient.getHTTPClient(provider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return whClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProvider(store esv1alpha1.GenericStore) (*esv1alpha1.WebhookProvider, error) {
|
||||||
|
spc := store.GetSpec()
|
||||||
|
if spc == nil || spc.Provider == nil || spc.Provider.Webhook == nil {
|
||||||
|
return nil, fmt.Errorf("missing store provider webhook")
|
||||||
|
}
|
||||||
|
return spc.Provider.Webhook, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) getStoreSecret(ctx context.Context, ref esmeta.SecretKeySelector) (*corev1.Secret, error) {
|
||||||
|
ke := client.ObjectKey{
|
||||||
|
Name: ref.Name,
|
||||||
|
Namespace: w.namespace,
|
||||||
|
}
|
||||||
|
if w.storeKind == esv1alpha1.ClusterSecretStoreKind {
|
||||||
|
if ref.Namespace == nil {
|
||||||
|
return nil, fmt.Errorf("no namespace on ClusterSecretStore webhook secret %s", ref.Name)
|
||||||
|
}
|
||||||
|
ke.Namespace = *ref.Namespace
|
||||||
|
}
|
||||||
|
secret := &corev1.Secret{}
|
||||||
|
if err := w.kube.Get(ctx, ke, secret); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get clustersecretstore webhook secret %s: %w", ref.Name, err)
|
||||||
|
}
|
||||||
|
return secret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||||
|
provider, err := getProvider(w.store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get store: %w", err)
|
||||||
|
}
|
||||||
|
result, err := w.getWebhookData(ctx, provider, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Only parse as json if we have a jsonpath set
|
||||||
|
if provider.Result.JSONPath != "" {
|
||||||
|
jsondata := interface{}(nil)
|
||||||
|
if err := yaml.Unmarshal(result, &jsondata); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse response json: %w", err)
|
||||||
|
}
|
||||||
|
jsondata, err = jsonpath.Get(provider.Result.JSONPath, jsondata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get response path %s: %w", provider.Result.JSONPath, err)
|
||||||
|
}
|
||||||
|
jsonvalue, ok := jsondata.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to get response (wrong type: %T)", jsondata)
|
||||||
|
}
|
||||||
|
return []byte(jsonvalue), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||||
|
provider, err := getProvider(w.store)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get store: %w", err)
|
||||||
|
}
|
||||||
|
result, err := w.getWebhookData(ctx, provider, ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We always want json here, so just parse it out
|
||||||
|
jsondata := interface{}(nil)
|
||||||
|
if err := yaml.Unmarshal(result, &jsondata); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse response json: %w", err)
|
||||||
|
}
|
||||||
|
// Get subdata via jsonpath, if given
|
||||||
|
if provider.Result.JSONPath != "" {
|
||||||
|
jsondata, err = jsonpath.Get(provider.Result.JSONPath, jsondata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get response path %s: %w", provider.Result.JSONPath, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the value is a string, try to parse it as json
|
||||||
|
jsonstring, ok := jsondata.(string)
|
||||||
|
if ok {
|
||||||
|
// This could also happen if the response was a single json-encoded string
|
||||||
|
// but that is an extremely unlikely scenario
|
||||||
|
if err := yaml.Unmarshal([]byte(jsonstring), &jsondata); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse response json from jsonpath: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Use the data as a key-value map
|
||||||
|
jsonvalue, ok := jsondata.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to get response (wrong type: %T)", jsondata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the map of generic objects to a map of byte arrays
|
||||||
|
values := make(map[string][]byte)
|
||||||
|
for rKey, rValue := range jsonvalue {
|
||||||
|
jVal, ok := rValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to get response (wrong type in key '%s': %T)", rKey, rValue)
|
||||||
|
}
|
||||||
|
values[rKey] = []byte(jVal)
|
||||||
|
}
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) getTemplateData(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef, secrets []esv1alpha1.WebhookSecret) (map[string]map[string]string, error) {
|
||||||
|
data := map[string]map[string]string{
|
||||||
|
"remoteRef": {
|
||||||
|
"key": url.QueryEscape(ref.Key),
|
||||||
|
"version": url.QueryEscape(ref.Version),
|
||||||
|
"property": url.QueryEscape(ref.Property),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, secref := range secrets {
|
||||||
|
if _, ok := data[secref.Name]; !ok {
|
||||||
|
data[secref.Name] = make(map[string]string)
|
||||||
|
}
|
||||||
|
secret, err := w.getStoreSecret(ctx, secref.SecretRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for sKey, sVal := range secret.Data {
|
||||||
|
data[secref.Name][sKey] = string(sVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) getWebhookData(ctx context.Context, provider *esv1alpha1.WebhookProvider, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||||
|
if w.http == nil {
|
||||||
|
return nil, fmt.Errorf("http client not initialized")
|
||||||
|
}
|
||||||
|
data, err := w.getTemplateData(ctx, ref, provider.Secrets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
method := provider.Method
|
||||||
|
if method == "" {
|
||||||
|
method = http.MethodGet
|
||||||
|
}
|
||||||
|
url, err := executeTemplateString(provider.URL, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse url: %w", err)
|
||||||
|
}
|
||||||
|
body, err := executeTemplate(provider.Body, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, method, url, &body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
for hKey, hValueTpl := range provider.Headers {
|
||||||
|
hValue, err := executeTemplateString(hValueTpl, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse header %s: %w", hKey, err)
|
||||||
|
}
|
||||||
|
req.Header.Add(hKey, hValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := w.http.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to call endpoint: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return nil, fmt.Errorf("endpoint gave error %s", resp.Status)
|
||||||
|
}
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) getHTTPClient(provider *esv1alpha1.WebhookProvider) (*http.Client, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
if provider.Timeout != nil {
|
||||||
|
client.Timeout = provider.Timeout.Duration
|
||||||
|
}
|
||||||
|
if len(provider.CABundle) == 0 && provider.CAProvider == nil {
|
||||||
|
// No need to process ca stuff if it is not there
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
caCertPool, err := w.getCACertPool(provider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConf := &tls.Config{
|
||||||
|
RootCAs: caCertPool,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
}
|
||||||
|
client.Transport = &http.Transport{TLSClientConfig: tlsConf}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) getCACertPool(provider *esv1alpha1.WebhookProvider) (*x509.CertPool, error) {
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
if len(provider.CABundle) > 0 {
|
||||||
|
ok := caCertPool.AppendCertsFromPEM(provider.CABundle)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to append cabundle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.CAProvider != nil && w.storeKind == esv1alpha1.ClusterSecretStoreKind && provider.CAProvider.Namespace == nil {
|
||||||
|
return nil, fmt.Errorf("missing namespace on CAProvider secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.CAProvider != nil {
|
||||||
|
var cert []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch provider.CAProvider.Type {
|
||||||
|
case esv1alpha1.WebhookCAProviderTypeSecret:
|
||||||
|
cert, err = w.getCertFromSecret(provider)
|
||||||
|
case esv1alpha1.WebhookCAProviderTypeConfigMap:
|
||||||
|
cert, err = w.getCertFromConfigMap(provider)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unknown caprovider type: %s", provider.CAProvider.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := caCertPool.AppendCertsFromPEM(cert)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to append cabundle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return caCertPool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) getCertFromSecret(provider *esv1alpha1.WebhookProvider) ([]byte, error) {
|
||||||
|
secretRef := esmeta.SecretKeySelector{
|
||||||
|
Name: provider.CAProvider.Name,
|
||||||
|
Key: provider.CAProvider.Key,
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.CAProvider.Namespace != nil {
|
||||||
|
secretRef.Namespace = provider.CAProvider.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
res, err := w.secretKeyRef(ctx, &secretRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKeySelector) (string, error) {
|
||||||
|
secret := &corev1.Secret{}
|
||||||
|
ref := client.ObjectKey{
|
||||||
|
Namespace: w.namespace,
|
||||||
|
Name: secretRef.Name,
|
||||||
|
}
|
||||||
|
if (w.storeKind == esv1alpha1.ClusterSecretStoreKind) &&
|
||||||
|
(secretRef.Namespace != nil) {
|
||||||
|
ref.Namespace = *secretRef.Namespace
|
||||||
|
}
|
||||||
|
err := w.kube.Get(ctx, ref, secret)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBytes, ok := secret.Data[secretRef.Key]
|
||||||
|
if !ok {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := string(keyBytes)
|
||||||
|
valueStr := strings.TrimSpace(value)
|
||||||
|
return valueStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) getCertFromConfigMap(provider *esv1alpha1.WebhookProvider) ([]byte, error) {
|
||||||
|
objKey := client.ObjectKey{
|
||||||
|
Name: provider.CAProvider.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.CAProvider.Namespace != nil {
|
||||||
|
objKey.Namespace = *provider.CAProvider.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
configMapRef := &corev1.ConfigMap{}
|
||||||
|
ctx := context.Background()
|
||||||
|
err := w.kube.Get(ctx, objKey, configMapRef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get caprovider secret %s: %w", objKey.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := configMapRef.Data[provider.CAProvider.Key]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to get caprovider configmap %s -> %s", objKey.Name, provider.CAProvider.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(val), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WebHook) Close(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeTemplateString(tmpl string, data map[string]map[string]string) (string, error) {
|
||||||
|
result, err := executeTemplate(tmpl, data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return result.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeTemplate(tmpl string, data map[string]map[string]string) (bytes.Buffer, error) {
|
||||||
|
var result bytes.Buffer
|
||||||
|
if tmpl == "" {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
urlt, err := tpl.New("webhooktemplate").Funcs(sprig.TxtFuncMap()).Funcs(template.FuncMap()).Parse(tmpl)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
if err := urlt.Execute(&result, data); err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
340
pkg/provider/webhook/webhook_test.go
Normal file
340
pkg/provider/webhook/webhook_test.go
Normal file
|
@ -0,0 +1,340 @@
|
||||||
|
/*
|
||||||
|
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 webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||||
|
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
Case string `json:"case,omitempty"`
|
||||||
|
Args args `json:"args"`
|
||||||
|
Want want `json:"want"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
Timeout string `json:"timeout,omitempty"`
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
JSONPath string `json:"jsonpath,omitempty"`
|
||||||
|
Response string `json:"response,omitempty"`
|
||||||
|
StatusCode int `json:"statuscode,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type want struct {
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
Err string `json:"err,omitempty"`
|
||||||
|
Result string `json:"result,omitempty"`
|
||||||
|
ResultMap map[string]string `json:"resultmap,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var testCases = `
|
||||||
|
case: error url
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .unclosed.template
|
||||||
|
want:
|
||||||
|
err: failed to parse url
|
||||||
|
---
|
||||||
|
case: error body
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
body: Body error {{ .unclosed.template
|
||||||
|
want:
|
||||||
|
err: failed to parse body
|
||||||
|
---
|
||||||
|
case: error connection
|
||||||
|
args:
|
||||||
|
url: 1/api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
want:
|
||||||
|
err: failed to call endpoint
|
||||||
|
---
|
||||||
|
case: error not found
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
statuscode: 404
|
||||||
|
response: not found
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
err: endpoint gave error 404
|
||||||
|
---
|
||||||
|
case: error bad json
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
jsonpath: $.result.thesecret
|
||||||
|
response: '{"result":{"thesecret":"secret-value"}'
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
err: failed to parse response json
|
||||||
|
---
|
||||||
|
case: error bad jsonpath
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
jsonpath: $.result.thesecret
|
||||||
|
response: '{"result":{"nosecret":"secret-value"}}'
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
err: failed to get response path
|
||||||
|
---
|
||||||
|
case: error bad json data
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
jsonpath: $.result.thesecret
|
||||||
|
response: '{"result":{"thesecret":{"one":"secret-value"}}}'
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
err: failed to get response (wrong type
|
||||||
|
---
|
||||||
|
case: error timeout
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
response: secret-value
|
||||||
|
timeout: 0.01ms
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
err: context deadline exceeded
|
||||||
|
---
|
||||||
|
case: good plaintext
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
response: secret-value
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
result: secret-value
|
||||||
|
---
|
||||||
|
case: good json
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
jsonpath: $.result.thesecret
|
||||||
|
response: '{"result":{"thesecret":"secret-value"}}'
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
result: secret-value
|
||||||
|
---
|
||||||
|
case: good json map
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
jsonpath: $.result
|
||||||
|
response: '{"result":{"thesecret":"secret-value","alsosecret":"another-value"}}'
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
resultmap:
|
||||||
|
thesecret: secret-value
|
||||||
|
alsosecret: another-value
|
||||||
|
---
|
||||||
|
case: good json map string
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
response: '{"thesecret":"secret-value","alsosecret":"another-value"}'
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
resultmap:
|
||||||
|
thesecret: secret-value
|
||||||
|
alsosecret: another-value
|
||||||
|
---
|
||||||
|
case: error json map string
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
response: 'some simple string'
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
err: failed to get response (wrong type
|
||||||
|
resultmap:
|
||||||
|
thesecret: secret-value
|
||||||
|
alsosecret: another-value
|
||||||
|
---
|
||||||
|
case: error json map
|
||||||
|
args:
|
||||||
|
url: /api/getsecret?id={{ .remoteRef.key }}&version={{ .remoteRef.version }}
|
||||||
|
key: testkey
|
||||||
|
version: 1
|
||||||
|
jsonpath: $.result.thesecret
|
||||||
|
response: '{"result":{"thesecret":"secret-value","alsosecret":"another-value"}}'
|
||||||
|
want:
|
||||||
|
path: /api/getsecret?id=testkey&version=1
|
||||||
|
err: failed to get response (wrong type
|
||||||
|
resultmap:
|
||||||
|
thesecret: secret-value
|
||||||
|
alsosecret: another-value
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestWebhookGetSecret(t *testing.T) {
|
||||||
|
ydec := yaml.NewDecoder(bytes.NewReader([]byte(testCases)))
|
||||||
|
for {
|
||||||
|
var tc testCase
|
||||||
|
if err := ydec.Decode(&tc); err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
t.Errorf("testcase decode error %w", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
runTestCase(tc, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCaseServer(tc testCase, t *testing.T) *httptest.Server {
|
||||||
|
// Start a new server for every test case because the server wants to check the expected api path
|
||||||
|
return httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if tc.Want.Path != "" && req.URL.String() != tc.Want.Path {
|
||||||
|
t.Errorf("%s: unexpected api path: %s, expected %s", tc.Case, req.URL.String(), tc.Want.Path)
|
||||||
|
}
|
||||||
|
if tc.Args.StatusCode != 0 {
|
||||||
|
rw.WriteHeader(tc.Args.StatusCode)
|
||||||
|
}
|
||||||
|
rw.Write([]byte(tc.Args.Response))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTimeout(timeout string) (*metav1.Duration, error) {
|
||||||
|
if timeout == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
dur, err := time.ParseDuration(timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &metav1.Duration{Duration: dur}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTestCase(tc testCase, t *testing.T) {
|
||||||
|
ts := testCaseServer(tc, t)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
testStore := makeClusterSecretStore(ts.URL, tc.Args)
|
||||||
|
var err error
|
||||||
|
timeout, err := parseTimeout(tc.Args.Timeout)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: error parsing timeout '%s': %s", tc.Case, tc.Args.Timeout, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
testStore.Spec.Provider.Webhook.Timeout = timeout
|
||||||
|
testProv := &Provider{}
|
||||||
|
client, err := testProv.NewClient(context.Background(), testStore, nil, "testnamespace")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: error creating client: %s", tc.Case, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.Want.ResultMap != nil {
|
||||||
|
testGetSecretMap(tc, t, client)
|
||||||
|
} else {
|
||||||
|
testGetSecret(tc, t, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGetSecretMap(tc testCase, t *testing.T, client provider.SecretsClient) {
|
||||||
|
testRef := esv1alpha1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: tc.Args.Key,
|
||||||
|
Version: tc.Args.Version,
|
||||||
|
}
|
||||||
|
secretmap, err := client.GetSecretMap(context.Background(), testRef)
|
||||||
|
errStr := ""
|
||||||
|
if err != nil {
|
||||||
|
errStr = err.Error()
|
||||||
|
}
|
||||||
|
if (tc.Want.Err == "") != (errStr == "") || !strings.Contains(errStr, tc.Want.Err) {
|
||||||
|
t.Errorf("%s: unexpected error: '%s' (expected '%s')", tc.Case, errStr, tc.Want.Err)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
for wantkey, wantval := range tc.Want.ResultMap {
|
||||||
|
gotval, ok := secretmap[wantkey]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s: unexpected response: wanted key '%s' not found", tc.Case, wantkey)
|
||||||
|
} else if string(gotval) != wantval {
|
||||||
|
t.Errorf("%s: unexpected response: key '%s' = '%s' (expected '%s')", tc.Case, wantkey, wantval, gotval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGetSecret(tc testCase, t *testing.T, client provider.SecretsClient) {
|
||||||
|
testRef := esv1alpha1.ExternalSecretDataRemoteRef{
|
||||||
|
Key: tc.Args.Key,
|
||||||
|
Version: tc.Args.Version,
|
||||||
|
}
|
||||||
|
secret, err := client.GetSecret(context.Background(), testRef)
|
||||||
|
errStr := ""
|
||||||
|
if err != nil {
|
||||||
|
errStr = err.Error()
|
||||||
|
}
|
||||||
|
if !strings.Contains(errStr, tc.Want.Err) {
|
||||||
|
t.Errorf("%s: unexpected error: '%s' (expected '%s')", tc.Case, errStr, tc.Want.Err)
|
||||||
|
}
|
||||||
|
if err == nil && string(secret) != tc.Want.Result {
|
||||||
|
t.Errorf("%s: unexpected response: '%s' (expected '%s')", tc.Case, secret, tc.Want.Result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeClusterSecretStore(url string, args args) *esv1alpha1.ClusterSecretStore {
|
||||||
|
store := &esv1alpha1.ClusterSecretStore{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "ClusterSecretStore",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "wehbook-store",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Spec: esv1alpha1.SecretStoreSpec{
|
||||||
|
Provider: &esv1alpha1.SecretStoreProvider{
|
||||||
|
Webhook: &esv1alpha1.WebhookProvider{
|
||||||
|
URL: url + args.URL,
|
||||||
|
Body: args.Body,
|
||||||
|
Headers: map[string]string{
|
||||||
|
"Content-Type": "application.json",
|
||||||
|
"X-SecretKey": "{{ .remoteRef.key }}",
|
||||||
|
},
|
||||||
|
Result: esv1alpha1.WebhookResult{
|
||||||
|
JSONPath: args.JSONPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return store
|
||||||
|
}
|
|
@ -51,6 +51,11 @@ var tplFuncs = tpl.FuncMap{
|
||||||
"lower": strings.ToLower,
|
"lower": strings.ToLower,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// So other templating calls can use the same extra functions.
|
||||||
|
func FuncMap() tpl.FuncMap {
|
||||||
|
return tplFuncs
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
errParse = "unable to parse template at key %s: %s"
|
errParse = "unable to parse template at key %s: %s"
|
||||||
errExecute = "unable to execute template at key %s: %s"
|
errExecute = "unable to execute template at key %s: %s"
|
||||||
|
|
Loading…
Reference in a new issue