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 'external-secrets:main' into main

This commit is contained in:
KianTigger 2021-08-03 11:49:55 +01:00 committed by GitHub
commit 78b0589a07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 330 additions and 3 deletions

View file

@ -62,7 +62,7 @@ type VaultProvider struct {
}
// VaultAuth is the configuration used to authenticate with a Vault server.
// Only one of `tokenSecretRef`, `appRole`, `kubernetes`, `ldap` or `jwt`
// Only one of `tokenSecretRef`, `appRole`, `kubernetes`, `ldap`, `jwt` or `cert`
// can be specified.
type VaultAuth struct {
// TokenSecretRef authenticates with Vault by presenting a token.
@ -88,6 +88,11 @@ type VaultAuth struct {
// JWT/OIDC authentication method
// +optional
Jwt *VaultJwtAuth `json:"jwt,omitempty"`
// Cert authenticates with TLS Certificates by passing client certificate, private key and ca certificate
// Cert authentication method
// +optional
Cert *VaultCertAuth `json:"cert,omitempty"`
}
// VaultAppRole authenticates with Vault using the App Role auth mechanism,
@ -161,3 +166,16 @@ type VaultJwtAuth struct {
// authenticate with Vault using the JWT/OIDC authentication method
SecretRef esmeta.SecretKeySelector `json:"secretRef,omitempty"`
}
// VaultJwtAuth authenticates with Vault using the JWT/OIDC authentication
// method, with the role name and token stored in a Kubernetes Secret resource.
type VaultCertAuth struct {
// ClientCert is a certificate to authenticate using the Cert Vault
// authentication method
// +optional
ClientCert esmeta.SecretKeySelector `json:"clientCert,omitempty"`
// SecretRef to a key in a Secret resource containing client private key to
// authenticate with Vault using the Cert authentication method
SecretRef esmeta.SecretKeySelector `json:"secretRef,omitempty"`
}

View file

@ -833,6 +833,11 @@ func (in *VaultAuth) DeepCopyInto(out *VaultAuth) {
*out = new(VaultJwtAuth)
(*in).DeepCopyInto(*out)
}
if in.Cert != nil {
in, out := &in.Cert, &out.Cert
*out = new(VaultCertAuth)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuth.
@ -845,6 +850,23 @@ func (in *VaultAuth) DeepCopy() *VaultAuth {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultCertAuth) DeepCopyInto(out *VaultCertAuth) {
*out = *in
in.ClientCert.DeepCopyInto(&out.ClientCert)
in.SecretRef.DeepCopyInto(&out.SecretRef)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultCertAuth.
func (in *VaultCertAuth) DeepCopy() *VaultCertAuth {
if in == nil {
return nil
}
out := new(VaultCertAuth)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VaultJwtAuth) DeepCopyInto(out *VaultJwtAuth) {
*out = *in

View file

@ -359,6 +359,59 @@ spec:
- roleId
- secretRef
type: object
cert:
description: Cert authenticates with TLS Certificates
by passing client certificate, private key and ca certificate
Cert authentication method
properties:
clientCert:
description: ClientCert is a certificate to authenticate
using the Cert Vault authentication method
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
required:
- name
type: object
secretRef:
description: SecretRef to a key in a Secret resource
containing client private key to authenticate with
Vault using the Cert authentication method
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
required:
- name
type: object
type: object
jwt:
description: Jwt authenticates with Vault by passing role
and JWT token using the JWT/OIDC authentication method

View file

@ -359,6 +359,59 @@ spec:
- roleId
- secretRef
type: object
cert:
description: Cert authenticates with TLS Certificates
by passing client certificate, private key and ca certificate
Cert authentication method
properties:
clientCert:
description: ClientCert is a certificate to authenticate
using the Cert Vault authentication method
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
required:
- name
type: object
secretRef:
description: SecretRef to a key in a Secret resource
containing client private key to authenticate with
Vault using the Cert authentication method
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
required:
- name
type: object
type: object
jwt:
description: Jwt authenticates with Vault by passing role
and JWT token using the JWT/OIDC authentication method

View file

@ -16,6 +16,7 @@ package vault
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
@ -66,6 +67,8 @@ const (
errGetKubeSecret = "cannot get Kubernetes secret %q: %w"
errSecretKeyFmt = "cannot find secret data for key: %q"
errClientTLSAuth = "error from Client TLS Auth: %q"
)
type Client interface {
@ -116,6 +119,7 @@ func (c *connector) NewClient(ctx context.Context, store esv1alpha1.GenericStore
}
cfg, err := vStore.newConfig()
if err != nil {
return nil, err
}
@ -129,7 +133,7 @@ func (c *connector) NewClient(ctx context.Context, store esv1alpha1.GenericStore
client.SetNamespace(*vaultSpec.Namespace)
}
if err := vStore.setAuth(ctx, client); err != nil {
if err := vStore.setAuth(ctx, client, cfg); err != nil {
return nil, err
}
@ -243,7 +247,7 @@ func (v *client) newConfig() (*vault.Config, error) {
return cfg, nil
}
func (v *client) setAuth(ctx context.Context, client Client) error {
func (v *client) setAuth(ctx context.Context, client Client, cfg *vault.Config) error {
tokenRef := v.store.Auth.TokenSecretRef
if tokenRef != nil {
token, err := v.secretKeyRef(ctx, tokenRef)
@ -294,6 +298,16 @@ func (v *client) setAuth(ctx context.Context, client Client) error {
return nil
}
certAuth := v.store.Auth.Cert
if certAuth != nil {
token, err := v.requestTokenWithCertAuth(ctx, client, certAuth, cfg)
if err != nil {
return err
}
client.SetToken(token)
return nil
}
return errors.New(errAuthFormat)
}
@ -550,3 +564,46 @@ func (v *client) requestTokenWithJwtAuth(ctx context.Context, client Client, jwt
return token, nil
}
func (v *client) requestTokenWithCertAuth(ctx context.Context, client Client, certAuth *esv1alpha1.VaultCertAuth, cfg *vault.Config) (string, error) {
clientKey, err := v.secretKeyRef(ctx, &certAuth.SecretRef)
if err != nil {
return "", err
}
clientCert, err := v.secretKeyRef(ctx, &certAuth.ClientCert)
if err != nil {
return "", err
}
cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
if err != nil {
return "", fmt.Errorf(errClientTLSAuth, err)
}
if transport, ok := cfg.HttpClient.Transport.(*http.Transport); ok {
transport.TLSClientConfig.Certificates = []tls.Certificate{cert}
}
url := strings.Join([]string{"/v1", "auth", "cert", "login"}, "/")
request := client.NewRequest("POST", url)
resp, err := client.RawRequestWithContext(ctx, request)
if err != nil {
return "", fmt.Errorf(errVaultRequest, err)
}
defer resp.Body.Close()
vaultResult := vault.Secret{}
if err = resp.DecodeJSON(&vaultResult); err != nil {
return "", fmt.Errorf(errVaultResponse, err)
}
token, err := vaultResult.TokenID()
if err != nil {
return "", fmt.Errorf(errVaultToken, err)
}
return token, nil
}

View file

@ -63,6 +63,36 @@ func makeValidSecretStore() *esv1alpha1.SecretStore {
}
}
func makeValidSecretStoreWithCerts() *esv1alpha1.SecretStore {
return &esv1alpha1.SecretStore{
ObjectMeta: metav1.ObjectMeta{
Name: "vault-store",
Namespace: "default",
},
Spec: esv1alpha1.SecretStoreSpec{
Provider: &esv1alpha1.SecretStoreProvider{
Vault: &esv1alpha1.VaultProvider{
Server: "vault.example.com",
Path: "secret",
Version: esv1alpha1.VaultKVStoreV2,
Auth: esv1alpha1.VaultAuth{
Cert: &esv1alpha1.VaultCertAuth{
ClientCert: esmeta.SecretKeySelector{
Name: "tls-auth-certs",
Key: "tls.crt",
},
SecretRef: esmeta.SecretKeySelector{
Name: "tls-auth-certs",
Key: "tls.key",
},
},
},
},
},
},
}
}
type secretStoreTweakFn func(s *esv1alpha1.SecretStore)
func makeSecretStore(tweaks ...secretStoreTweakFn) *esv1alpha1.SecretStore {
@ -95,6 +125,13 @@ func newVaultTokenIDResponse(token string) *vault.Response {
func TestNewVault(t *testing.T) {
errBoom := errors.New("boom")
secretData := []byte("some-creds")
secretClientKey := []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEArfZ4HV1obFVlVNiA24tX/UOakqRnEtWXpIvaOsMaPGvvODgGe4XnyJGO32idPv85sIr7vDH9p+OhactVlJV1fu5SZoZ7pg4jTCLqVDCb3IRD++yik2Sw58YayNe3HiaCTsJQWeMXLzfaqOeyk6bEpBCJo09+3QxUWxijgJ7YZCb+Gi8pf3ZWeSZG+rGNNvXHmTs1Yu1H849SYXu+uJOd/R3ZSTw8CxFe4eTLgbCnPf6tgA8Sg2hc+CAZxunPP2JLZWbiJXxjNRoypso6MAJ1FRkx5sTJiLg6UoLvd95/S/lCVOR2PDlM1hg7ox8VEd4QHky7tLx7gji/5hHQKJQSTwIDAQABAoIBAQCYPICQ8hVX+MNcpLrfZenycR7sBYNOMC0silbH5cUn6yzFfgHuRxi3pOnrCJnTb3cE0BvMbdMVAVdYReD2znSsR9NEdZvvjZ/GGSgH1SIQsI7t//+mDQ/jRLJb4KsXb4vJcLLwdpLrd22bMmhMXjzndrF8gSz8NLX9omozPM8RlLxjzPzYOdlX/Zw8V68qQH2Ic04KbtnCwyAUIgAJxYtn/uYB8lzILBkyzQqwhQKkDDZQ0wbZT0hP6z+HgsdifwQvHG1GZAgCuzzyXrL/4TgDaDhYdMVoBA4+HPmzqm5MkBvjH4oqroxjRofUroVix0OGXZJMI1OJ0z/ubzmwCq5BAoGBANqbwzAydUJs0P+GFL94K/Y6tXULKA2c9N0crbxoxheobRpuJvhpW1ZE/9UGpaYX1Rw3nW4x+Jwvt83YkgHAlR4LgEwDvdJPZobybfqifQDiraUO0t62Crn8mSxOsFCugtRIFniwnX67w3uKxiSdCZYbJGs9JEDTpxRG/PSWq3QlAoGBAMu3zOv1PJAhOky7VcxFxWQPEMY+t2PA/sneD01/qgGuhlTwL4QlpywmBqXcI070dcvcBkP0flnWI7y5cnuE1+55twmsrvfaS8s1+AYje0b35DsaF2vtKuJrXC0AGKP+/eiycd9cbvVW2GWOxE7Ui76Mj95MARK8ZNjt0wJagQhjAoGASm9dD80uhhadN1RFPkjB1054OMk6sx/tdFhug8e9I5MSyzwUguME2aQW5EcmIh7dToVVUo8rUqsgz7NdS8FyRM+vuLJRcQneJDbp4bxwCdwlOh2JCZI8psVutlp4yJATNgrxs9iXV+7BChDflNnvyK+nP+iKrpQiwNHHEdU3vg0CgYEAvEpwD4+loJn1psJn9NxwK6F5IaMKIhtZ4/9pKXpcCh3jb1JouL2MnFOxRVAJGor87aW57Mlol2RDt8W4OM56PqMlOL3xIokUEQka66GT6e5pdu8QwuJ9BrWwhq9WFw4yZQe6FHb836qbbJLegvYVC9QjjZW2UDjtBUwcAkrghH0CgYBUMmMOCwIfMEtMaWxZRGdxRabazLhn7TXhBpVTuv7WouPaXYd7ZGjCTMKAuVa/E4afBlxgemnqBuX90gHpK/dDmn9l+lp8GZey0grJ7G0x5HEMiKziaX5PrgAcKbQ70m9ZNZ1deYhsC05X8rHNexZB6ns7Yms9L7qnlAy51ZH2zw==
-----END RSA PRIVATE KEY-----`)
clientCrt := []byte(`-----BEGIN CERTIFICATE-----
MIICsTCCAZkCFEJJ4daz5sxkFlzq9n1djLEuG7bmMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMMCHZhdWx0LWNhMB4XDTIxMDcyMDA4MTQxM1oXDTIyMDcyMDA4MTQxM1owFzEVMBMGA1UEAwwMdmF1bHQtY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArfZ4HV1obFVlVNiA24tX/UOakqRnEtWXpIvaOsMaPGvvODgGe4XnyJGO32idPv85sIr7vDH9p+OhactVlJV1fu5SZoZ7pg4jTCLqVDCb3IRD++yik2Sw58YayNe3HiaCTsJQWeMXLzfaqOeyk6bEpBCJo09+3QxUWxijgJ7YZCb+Gi8pf3ZWeSZG+rGNNvXHmTs1Yu1H849SYXu+uJOd/R3ZSTw8CxFe4eTLgbCnPf6tgA8Sg2hc+CAZxunPP2JLZWbiJXxjNRoypso6MAJ1FRkx5sTJiLg6UoLvd95/S/lCVOR2PDlM1hg7ox8VEd4QHky7tLx7gji/5hHQKJQSTwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAsDYKtzScIA7bqIOmqF8rr+oLSjRhPt5OfT+KGNdXk8G3VAy1ED2tyCHaRNC7dPLq4EvcxbIXQnXPy1iZMofriGbFPAcQ2fyWUesAD6bYSpI+bYxwz6Ebb93hU5nc/FyXg8yh0kgiGbY3MrACPjxqP2+z5kcOC3u3hx3SZylgW7TeOXDTdqSbNfH1b+1rR/bVNgQQshjhU9d+c4Yv/t0u07uykBhHLWZDSnYiAeOZ8+mWuOSDkcZHE1zznx74fWgtN0zRDtr0L0w9evT9R2CnNSZGxXcEQxAlQ7SL/Jyw82TFCGEw0L4jj7jjvx0N5J8KX/DulUDE9vuVyQEJ88Epe
-----END CERTIFICATE-----
`)
type args struct {
newClientFunc func(c *vault.Config) (Client, error)
@ -217,6 +254,93 @@ func TestNewVault(t *testing.T) {
err: nil,
},
},
"SuccessfulVaultStoreWithCertAuth": {
reason: "Should return a Vault provider successfully",
args: args{
store: makeValidSecretStoreWithCerts(),
kube: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
if o, ok := obj.(*corev1.Secret); ok {
o.Data = map[string][]byte{
"tls.key": secretClientKey,
"tls.crt": clientCrt,
}
return nil
}
return nil
}),
},
newClientFunc: func(c *vault.Config) (Client, error) {
return &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
MockSetToken: fake.NewSetTokenFn(),
}, nil
},
},
want: want{
err: nil,
},
},
"GetCertificateFormatError": {
reason: "Should return error if client certificate is in wrong format.",
args: args{
store: makeValidSecretStoreWithCerts(),
kube: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
if o, ok := obj.(*corev1.Secret); ok {
o.Data = map[string][]byte{
"tls.key": secretClientKey,
"tls.crt": []byte("cert with mistak"),
}
return nil
}
return nil
}),
},
newClientFunc: func(c *vault.Config) (Client, error) {
return &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
MockSetToken: fake.NewSetTokenFn(),
}, nil
},
},
want: want{
err: fmt.Errorf(errClientTLSAuth, "tls: failed to find any PEM data in certificate input"),
},
},
"GetKeyFormatError": {
reason: "Should return error if client key is in wrong format.",
args: args{
store: makeValidSecretStoreWithCerts(),
kube: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(obj kclient.Object) error {
if o, ok := obj.(*corev1.Secret); ok {
o.Data = map[string][]byte{
"tls.key": []byte("key with mistake"),
"tls.crt": clientCrt,
}
return nil
}
return nil
}),
},
newClientFunc: func(c *vault.Config) (Client, error) {
return &fake.VaultClient{
MockNewRequest: fake.NewMockNewRequestFn(&vault.Request{}),
MockRawRequestWithContext: fake.NewMockRawRequestWithContextFn(
newVaultTokenIDResponse("test-token"), nil, func(got *vault.Request) error { return nil }),
MockSetToken: fake.NewSetTokenFn(),
}, nil
},
},
want: want{
err: fmt.Errorf(errClientTLSAuth, "tls: failed to find any PEM data in key input"),
},
},
}
for name, tc := range cases {