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:
commit
78b0589a07
6 changed files with 330 additions and 3 deletions
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue