mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
feat: add CAProvider to Bitwarden provider (#3699)
* feat: add CAProvider to bitwarden This change introduces a refactor as well since CAProvider was used by multiple providers with diverging implementations. The following providers were affected: - webhook - akeyless - vault - conjur - kubernetes Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * refactored the Kubernetes provider to use create ca Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * refactor webhook, vault and kubernetes provider Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * rename CreateCACert to FetchCACertFromSource Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * addressed comments and autodecoding base64 data Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> * check if the decoded value is a valid certificate Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> --------- Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
This commit is contained in:
parent
098d03792d
commit
82d419e2ee
24 changed files with 418 additions and 461 deletions
|
@ -23,8 +23,11 @@ type BitwardenSecretsManagerProvider struct {
|
|||
BitwardenServerSDKURL string `json:"bitwardenServerSDKURL,omitempty"`
|
||||
// Base64 encoded certificate for the bitwarden server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
|
||||
// can be performed.
|
||||
// +required
|
||||
CABundle string `json:"caBundle"`
|
||||
// +optional
|
||||
CABundle string `json:"caBundle,omitempty"`
|
||||
// see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider
|
||||
// +optional
|
||||
CAProvider *CAProvider `json:"caProvider,omitempty"`
|
||||
// OrganizationID determines which organization this secret store manages.
|
||||
OrganizationID string `json:"organizationID"`
|
||||
// ProjectID determines which project this secret store manages.
|
||||
|
|
|
@ -505,6 +505,11 @@ func (in *BitwardenSecretsManagerAuth) DeepCopy() *BitwardenSecretsManagerAuth {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BitwardenSecretsManagerProvider) DeepCopyInto(out *BitwardenSecretsManagerProvider) {
|
||||
*out = *in
|
||||
if in.CAProvider != nil {
|
||||
in, out := &in.CAProvider, &out.CAProvider
|
||||
*out = new(CAProvider)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.Auth.DeepCopyInto(&out.Auth)
|
||||
}
|
||||
|
||||
|
|
|
@ -2493,6 +2493,33 @@ spec:
|
|||
Base64 encoded certificate for the bitwarden server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
|
||||
can be performed.
|
||||
type: string
|
||||
caProvider:
|
||||
description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
|
||||
properties:
|
||||
key:
|
||||
description: The key where the CA certificate can be found
|
||||
in the Secret or ConfigMap.
|
||||
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.
|
||||
Can only be defined when used in a ClusterSecretStore.
|
||||
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
|
||||
identityURL:
|
||||
type: string
|
||||
organizationID:
|
||||
|
@ -2505,7 +2532,6 @@ spec:
|
|||
type: string
|
||||
required:
|
||||
- auth
|
||||
- caBundle
|
||||
- organizationID
|
||||
- projectID
|
||||
type: object
|
||||
|
|
|
@ -2493,6 +2493,33 @@ spec:
|
|||
Base64 encoded certificate for the bitwarden server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
|
||||
can be performed.
|
||||
type: string
|
||||
caProvider:
|
||||
description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
|
||||
properties:
|
||||
key:
|
||||
description: The key where the CA certificate can be found
|
||||
in the Secret or ConfigMap.
|
||||
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.
|
||||
Can only be defined when used in a ClusterSecretStore.
|
||||
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
|
||||
identityURL:
|
||||
type: string
|
||||
organizationID:
|
||||
|
@ -2505,7 +2532,6 @@ spec:
|
|||
type: string
|
||||
required:
|
||||
- auth
|
||||
- caBundle
|
||||
- organizationID
|
||||
- projectID
|
||||
type: object
|
||||
|
|
|
@ -2978,6 +2978,30 @@ spec:
|
|||
Base64 encoded certificate for the bitwarden server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
|
||||
can be performed.
|
||||
type: string
|
||||
caProvider:
|
||||
description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
|
||||
properties:
|
||||
key:
|
||||
description: The key where the CA certificate can be found in the Secret or ConfigMap.
|
||||
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.
|
||||
Can only be defined when used in a ClusterSecretStore.
|
||||
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
|
||||
identityURL:
|
||||
type: string
|
||||
organizationID:
|
||||
|
@ -2988,7 +3012,6 @@ spec:
|
|||
type: string
|
||||
required:
|
||||
- auth
|
||||
- caBundle
|
||||
- organizationID
|
||||
- projectID
|
||||
type: object
|
||||
|
@ -8740,6 +8763,30 @@ spec:
|
|||
Base64 encoded certificate for the bitwarden server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
|
||||
can be performed.
|
||||
type: string
|
||||
caProvider:
|
||||
description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
|
||||
properties:
|
||||
key:
|
||||
description: The key where the CA certificate can be found in the Secret or ConfigMap.
|
||||
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.
|
||||
Can only be defined when used in a ClusterSecretStore.
|
||||
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
|
||||
identityURL:
|
||||
type: string
|
||||
organizationID:
|
||||
|
@ -8750,7 +8797,6 @@ spec:
|
|||
type: string
|
||||
required:
|
||||
- auth
|
||||
- caBundle
|
||||
- organizationID
|
||||
- projectID
|
||||
type: object
|
||||
|
|
|
@ -1328,12 +1328,27 @@ string
|
|||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>Base64 encoded certificate for the bitwarden server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
|
||||
can be performed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>caProvider</code></br>
|
||||
<em>
|
||||
<a href="#external-secrets.io/v1beta1.CAProvider">
|
||||
CAProvider
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>see: <a href="https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider">https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>organizationID</code></br>
|
||||
<em>
|
||||
string
|
||||
|
@ -1407,6 +1422,7 @@ External Secrets meta/v1.SecretKeySelector
|
|||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#external-secrets.io/v1beta1.AkeylessProvider">AkeylessProvider</a>,
|
||||
<a href="#external-secrets.io/v1beta1.BitwardenSecretsManagerProvider">BitwardenSecretsManagerProvider</a>,
|
||||
<a href="#external-secrets.io/v1beta1.ConjurProvider">ConjurProvider</a>,
|
||||
<a href="#external-secrets.io/v1beta1.KubernetesServer">KubernetesServer</a>,
|
||||
<a href="#external-secrets.io/v1beta1.VaultProvider">VaultProvider</a>)
|
||||
|
|
|
@ -16,6 +16,8 @@ package webhook
|
|||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
)
|
||||
|
||||
type Spec struct {
|
||||
|
@ -55,31 +57,7 @@ type Spec struct {
|
|||
|
||||
// The provider for the CA bundle to use to validate webhook server certificate.
|
||||
// +optional
|
||||
CAProvider *CAProvider `json:"caProvider,omitempty"`
|
||||
}
|
||||
type CAProviderType string
|
||||
|
||||
const (
|
||||
CAProviderTypeSecret CAProviderType = "Secret"
|
||||
CAProviderTypeConfigMap CAProviderType = "ConfigMap"
|
||||
)
|
||||
|
||||
// Defines a location to fetch the cert for the webhook provider from.
|
||||
type CAProvider struct {
|
||||
// The type of provider to use such as "Secret", or "ConfigMap".
|
||||
// +kubebuilder:validation:Enum="Secret";"ConfigMap"
|
||||
Type CAProviderType `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"`
|
||||
CAProvider *esv1beta1.CAProvider `json:"caProvider,omitempty"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
|
|
|
@ -207,7 +207,7 @@ func (w *Webhook) GetWebhookData(ctx context.Context, provider *Spec, ref *esv1b
|
|||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func (w *Webhook) GetHTTPClient(provider *Spec) (*http.Client, error) {
|
||||
func (w *Webhook) GetHTTPClient(ctx context.Context, provider *Spec) (*http.Client, error) {
|
||||
client := &http.Client{}
|
||||
if provider.Timeout != nil {
|
||||
client.Timeout = provider.Timeout.Duration
|
||||
|
@ -216,7 +216,7 @@ func (w *Webhook) GetHTTPClient(provider *Spec) (*http.Client, error) {
|
|||
// No need to process ca stuff if it is not there
|
||||
return client, nil
|
||||
}
|
||||
caCertPool, err := w.GetCACertPool(provider)
|
||||
caCertPool, err := w.GetCACertPool(ctx, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -230,37 +230,23 @@ func (w *Webhook) GetHTTPClient(provider *Spec) (*http.Client, error) {
|
|||
return client, nil
|
||||
}
|
||||
|
||||
func (w *Webhook) GetCACertPool(provider *Spec) (*x509.CertPool, error) {
|
||||
func (w *Webhook) GetCACertPool(ctx context.Context, provider *Spec) (*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")
|
||||
}
|
||||
ca, err := utils.FetchCACertFromSource(ctx, utils.CreateCertOpts{
|
||||
CABundle: provider.CABundle,
|
||||
CAProvider: provider.CAProvider,
|
||||
StoreKind: w.StoreKind,
|
||||
Namespace: w.Namespace,
|
||||
Client: w.Kube,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ok := caCertPool.AppendCertsFromPEM(ca)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to append cabundle")
|
||||
}
|
||||
|
||||
if provider.CAProvider != nil {
|
||||
var cert []byte
|
||||
var err error
|
||||
|
||||
switch provider.CAProvider.Type {
|
||||
case CAProviderTypeSecret:
|
||||
cert, err = w.GetCertFromSecret(provider)
|
||||
case CAProviderTypeConfigMap:
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ func (w *Webhook) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, kc
|
|||
w.wh.Namespace = ns
|
||||
w.url = provider.URL
|
||||
w.wh.Kube = kclient
|
||||
w.wh.HTTP, err = w.wh.GetHTTPClient(provider)
|
||||
w.wh.HTTP, err = w.wh.GetHTTPClient(ctx, provider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare provider http client: %w", err)
|
||||
}
|
||||
|
|
|
@ -37,10 +37,8 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
"github.com/external-secrets/external-secrets/pkg/find"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -180,7 +178,7 @@ func (p *Provider) ValidateStore(store esv1beta1.GenericStore) (admission.Warnin
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func newClient(_ context.Context, store esv1beta1.GenericStore, kube client.Client, corev1 typedcorev1.CoreV1Interface, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, corev1 typedcorev1.CoreV1Interface, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
akl := &akeylessBase{
|
||||
kube: kube,
|
||||
store: store,
|
||||
|
@ -202,7 +200,7 @@ func newClient(_ context.Context, store esv1beta1.GenericStore, kube client.Clie
|
|||
return nil, fmt.Errorf("missing Auth in store config")
|
||||
}
|
||||
|
||||
client, err := akl.getAkeylessHTTPClient(spec)
|
||||
client, err := akl.getAkeylessHTTPClient(ctx, spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -406,16 +404,29 @@ func (a *Akeyless) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecre
|
|||
return secretData, nil
|
||||
}
|
||||
|
||||
func (a *akeylessBase) getAkeylessHTTPClient(provider *esv1beta1.AkeylessProvider) (*http.Client, error) {
|
||||
func (a *akeylessBase) getAkeylessHTTPClient(ctx context.Context, provider *esv1beta1.AkeylessProvider) (*http.Client, error) {
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
if len(provider.CABundle) == 0 && provider.CAProvider == nil {
|
||||
return client, nil
|
||||
}
|
||||
caCertPool, err := a.getCACertPool(provider)
|
||||
|
||||
cert, err := utils.FetchCACertFromSource(ctx, utils.CreateCertOpts{
|
||||
StoreKind: a.storeKind,
|
||||
Client: a.kube,
|
||||
Namespace: a.namespace,
|
||||
CABundle: provider.CABundle,
|
||||
CAProvider: provider.CAProvider,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
ok := caCertPool.AppendCertsFromPEM(cert)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to append caBundle")
|
||||
}
|
||||
|
||||
tlsConf := &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
|
@ -423,93 +434,3 @@ func (a *akeylessBase) getAkeylessHTTPClient(provider *esv1beta1.AkeylessProvide
|
|||
client.Transport = &http.Transport{TLSClientConfig: tlsConf}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (a *akeylessBase) getCACertPool(provider *esv1beta1.AkeylessProvider) (*x509.CertPool, error) {
|
||||
caCertPool := x509.NewCertPool()
|
||||
if len(provider.CABundle) > 0 {
|
||||
pem, err := base64decode(provider.CABundle)
|
||||
if err != nil {
|
||||
pem = provider.CABundle
|
||||
}
|
||||
ok := caCertPool.AppendCertsFromPEM(pem)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to append caBundle")
|
||||
}
|
||||
}
|
||||
|
||||
if provider.CAProvider != nil &&
|
||||
a.storeKind == esv1beta1.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 esv1beta1.CAProviderTypeSecret:
|
||||
cert, err = a.getCertFromSecret(provider)
|
||||
case esv1beta1.CAProviderTypeConfigMap:
|
||||
cert, err = a.getCertFromConfigMap(provider)
|
||||
default:
|
||||
err = fmt.Errorf("unknown CAProvider type: %s", provider.CAProvider.Type)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pem, err := base64decode(cert)
|
||||
if err != nil {
|
||||
pem = cert
|
||||
}
|
||||
ok := caCertPool.AppendCertsFromPEM(pem)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to append caBundle")
|
||||
}
|
||||
}
|
||||
return caCertPool, nil
|
||||
}
|
||||
|
||||
func (a *akeylessBase) getCertFromSecret(provider *esv1beta1.AkeylessProvider) ([]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()
|
||||
cert, err := resolvers.SecretKeyRef(ctx, a.kube, a.storeKind, a.namespace, &secretRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(cert), nil
|
||||
}
|
||||
|
||||
func (a *akeylessBase) getCertFromConfigMap(provider *esv1beta1.AkeylessProvider) ([]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 := a.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
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ limitations under the License.
|
|||
package akeyless
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -109,12 +108,3 @@ func sendReq(url string) string {
|
|||
body, _ := io.ReadAll(resp.Body)
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func base64decode(in []byte) ([]byte, error) {
|
||||
out := make([]byte, len(in))
|
||||
l, err := base64.StdEncoding.Decode(out, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out[:l], nil
|
||||
}
|
||||
|
|
|
@ -21,6 +21,10 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
)
|
||||
|
||||
// Defined Header Keys.
|
||||
|
@ -100,18 +104,18 @@ type SdkClient struct {
|
|||
client *http.Client
|
||||
}
|
||||
|
||||
func NewSdkClient(apiURL, identityURL, bitwardenURL, token string, caBundle []byte) (*SdkClient, error) {
|
||||
client, err := newHTTPSClient(caBundle)
|
||||
func NewSdkClient(ctx context.Context, c client.Client, storeKind, namespace string, provider *v1beta1.BitwardenSecretsManagerProvider, token string) (*SdkClient, error) {
|
||||
httpsClient, err := newHTTPSClient(ctx, c, storeKind, namespace, provider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating https client: %w", err)
|
||||
}
|
||||
|
||||
return &SdkClient{
|
||||
apiURL: apiURL,
|
||||
identityURL: identityURL,
|
||||
apiURL: provider.APIURL,
|
||||
identityURL: provider.IdentityURL,
|
||||
bitwardenSdkServerURL: provider.BitwardenServerSDKURL,
|
||||
token: token,
|
||||
client: client,
|
||||
bitwardenSdkServerURL: bitwardenURL,
|
||||
client: httpsClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -26,10 +26,6 @@ import (
|
|||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
errBadCertBundle = "caBundle failed to base64 decode: %w"
|
||||
)
|
||||
|
||||
const (
|
||||
// NoteMetadataKey defines the note for the pushed secret.
|
||||
NoteMetadataKey = "note"
|
||||
|
@ -249,16 +245,6 @@ func (p *Provider) Close(_ context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// getCABundle try retrieve the CA bundle from the provider CABundle.
|
||||
func (p *Provider) getCABundle(provider *esv1beta1.BitwardenSecretsManagerProvider) ([]byte, error) {
|
||||
certBytes, decodeErr := utils.Decode(esv1beta1.ExternalSecretDecodeBase64, []byte(provider.CABundle))
|
||||
if decodeErr != nil {
|
||||
return nil, fmt.Errorf(errBadCertBundle, decodeErr)
|
||||
}
|
||||
|
||||
return certBytes, nil
|
||||
}
|
||||
|
||||
func (p *Provider) findSecretByRef(ctx context.Context, key, projectID string) (*SecretResponse, error) {
|
||||
spec := p.store.GetSpec()
|
||||
if spec == nil || spec.Provider == nil {
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||
)
|
||||
|
||||
|
@ -58,17 +59,12 @@ func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore,
|
|||
return nil, fmt.Errorf("could not resolve auth credentials: %w", err)
|
||||
}
|
||||
|
||||
bundle, err := p.getCABundle(storeSpec.Provider.BitwardenSecretsManager)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve caBundle: %w", err)
|
||||
}
|
||||
|
||||
sdkClient, err := NewSdkClient(
|
||||
storeSpec.Provider.BitwardenSecretsManager.APIURL,
|
||||
storeSpec.Provider.BitwardenSecretsManager.IdentityURL,
|
||||
storeSpec.Provider.BitwardenSecretsManager.BitwardenServerSDKURL,
|
||||
sdkClient, err := NewSdkClient(ctx,
|
||||
kube,
|
||||
store.GetKind(),
|
||||
namespace,
|
||||
storeSpec.Provider.BitwardenSecretsManager,
|
||||
token,
|
||||
bundle,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create SdkClient: %w", err)
|
||||
|
@ -88,17 +84,49 @@ func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
|
|||
}
|
||||
|
||||
// ValidateStore validates the store.
|
||||
func (p *Provider) ValidateStore(_ esv1beta1.GenericStore) (admission.Warnings, error) {
|
||||
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
if storeSpec == nil {
|
||||
return admission.Warnings{}, fmt.Errorf("no store type or wrong store type")
|
||||
}
|
||||
|
||||
if storeSpec.Provider == nil {
|
||||
return admission.Warnings{}, fmt.Errorf("provider not configured")
|
||||
}
|
||||
|
||||
bitwardenSpec := storeSpec.Provider.BitwardenSecretsManager
|
||||
if bitwardenSpec == nil {
|
||||
return admission.Warnings{}, fmt.Errorf("bitwarden spec not configured")
|
||||
}
|
||||
|
||||
if bitwardenSpec.CAProvider == nil && bitwardenSpec.CABundle == "" {
|
||||
return admission.Warnings{
|
||||
"Neither CA nor CA bundle is configured; user is expected to provide certificate information via volume mount.",
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// newHTTPSClient creates a new HTTPS client with the given cert.
|
||||
func newHTTPSClient(cert []byte) (*http.Client, error) {
|
||||
func newHTTPSClient(ctx context.Context, c client.Client, storeKind, namespace string, provider *esv1beta1.BitwardenSecretsManagerProvider) (*http.Client, error) {
|
||||
cert, err := utils.FetchCACertFromSource(ctx, utils.CreateCertOpts{
|
||||
CABundle: []byte(provider.CABundle),
|
||||
CAProvider: provider.CAProvider,
|
||||
StoreKind: storeKind,
|
||||
Namespace: namespace,
|
||||
Client: c,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
ok := pool.AppendCertsFromPEM(cert)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't append Conjur SSL cert")
|
||||
return nil, fmt.Errorf("failed to append caBundle")
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12},
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ package conjur
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cyberark/conjur-api-go/conjurapi"
|
||||
"github.com/cyberark/conjur-api-go/conjurapi/authn"
|
||||
|
@ -26,24 +25,17 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/conjur/util"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||
)
|
||||
|
||||
var (
|
||||
errConjurClient = "cannot setup new Conjur client: %w"
|
||||
errBadCertBundle = "caBundle failed to base64 decode: %w"
|
||||
errBadServiceUser = "could not get Auth.Apikey.UserRef: %w"
|
||||
errBadServiceAPIKey = "could not get Auth.Apikey.ApiKeyRef: %w"
|
||||
|
||||
errConjurClient = "cannot setup new Conjur client: %w"
|
||||
errBadServiceUser = "could not get Auth.Apikey.UserRef: %w"
|
||||
errBadServiceAPIKey = "could not get Auth.Apikey.ApiKeyRef: %w"
|
||||
errGetKubeSATokenRequest = "cannot request Kubernetes service account token for service account %q: %w"
|
||||
|
||||
errUnableToFetchCAProviderCM = "unable to fetch Server.CAProvider ConfigMap: %w"
|
||||
errUnableToFetchCAProviderSecret = "unable to fetch Server.CAProvider Secret: %w"
|
||||
|
||||
errSecretKeyFmt = "cannot find secret data for key: %q"
|
||||
errSecretKeyFmt = "cannot find secret data for key: %q"
|
||||
)
|
||||
|
||||
// Client is a provider for Conjur.
|
||||
|
@ -68,14 +60,20 @@ func (c *Client) GetConjurClient(ctx context.Context) (SecretsClient, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
cert, getCertErr := c.getCA(ctx, prov)
|
||||
cert, getCertErr := utils.FetchCACertFromSource(ctx, utils.CreateCertOpts{
|
||||
CABundle: []byte(prov.CABundle),
|
||||
CAProvider: prov.CAProvider,
|
||||
StoreKind: c.store.GetKind(),
|
||||
Namespace: c.namespace,
|
||||
Client: c.kube,
|
||||
})
|
||||
if getCertErr != nil {
|
||||
return nil, getCertErr
|
||||
}
|
||||
|
||||
config := conjurapi.Config{
|
||||
ApplianceURL: prov.URL,
|
||||
SSLCert: cert,
|
||||
SSLCert: string(cert),
|
||||
}
|
||||
|
||||
if prov.Auth.APIKey != nil {
|
||||
|
@ -151,69 +149,3 @@ func (c *Client) Validate() (esv1beta1.ValidationResult, error) {
|
|||
func (c *Client) Close(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// configMapKeyRef returns the value of a key in a ConfigMap.
|
||||
func (c *Client) configMapKeyRef(ctx context.Context, cmRef *esmeta.SecretKeySelector) (string, error) {
|
||||
configMap := &corev1.ConfigMap{}
|
||||
ref := client.ObjectKey{
|
||||
Namespace: c.namespace,
|
||||
Name: cmRef.Name,
|
||||
}
|
||||
if (c.StoreKind == esv1beta1.ClusterSecretStoreKind) &&
|
||||
(cmRef.Namespace != nil) {
|
||||
ref.Namespace = *cmRef.Namespace
|
||||
}
|
||||
err := c.kube.Get(ctx, ref, configMap)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
keyBytes, ok := configMap.Data[cmRef.Key]
|
||||
if !ok {
|
||||
return "", err
|
||||
}
|
||||
|
||||
valueStr := strings.TrimSpace(keyBytes)
|
||||
return valueStr, nil
|
||||
}
|
||||
|
||||
// getCA try retrieve the CA bundle from the provider CABundle or from the CAProvider.
|
||||
func (c *Client) getCA(ctx context.Context, provider *esv1beta1.ConjurProvider) (string, error) {
|
||||
if provider.CAProvider != nil {
|
||||
var ca string
|
||||
var err error
|
||||
switch provider.CAProvider.Type {
|
||||
case esv1beta1.CAProviderTypeConfigMap:
|
||||
keySelector := esmeta.SecretKeySelector{
|
||||
Name: provider.CAProvider.Name,
|
||||
Namespace: provider.CAProvider.Namespace,
|
||||
Key: provider.CAProvider.Key,
|
||||
}
|
||||
ca, err = c.configMapKeyRef(ctx, &keySelector)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errUnableToFetchCAProviderCM, err)
|
||||
}
|
||||
case esv1beta1.CAProviderTypeSecret:
|
||||
keySelector := esmeta.SecretKeySelector{
|
||||
Name: provider.CAProvider.Name,
|
||||
Namespace: provider.CAProvider.Namespace,
|
||||
Key: provider.CAProvider.Key,
|
||||
}
|
||||
ca, err = resolvers.SecretKeyRef(
|
||||
ctx,
|
||||
c.kube,
|
||||
c.StoreKind,
|
||||
c.namespace,
|
||||
&keySelector)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(errUnableToFetchCAProviderSecret, err)
|
||||
}
|
||||
}
|
||||
return ca, nil
|
||||
}
|
||||
certBytes, decodeErr := utils.Decode(esv1beta1.ExternalSecretDecodeBase64, []byte(provider.CABundle))
|
||||
if decodeErr != nil {
|
||||
return "", fmt.Errorf(errBadCertBundle, decodeErr)
|
||||
}
|
||||
return string(certBytes), nil
|
||||
}
|
||||
|
|
|
@ -458,8 +458,21 @@ func TestGetCA(t *testing.T) {
|
|||
want want
|
||||
}
|
||||
|
||||
certData := "mycertdata"
|
||||
certDataEncoded := "bXljZXJ0ZGF0YQo="
|
||||
certData := `-----BEGIN CERTIFICATE-----
|
||||
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
|
||||
CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
|
||||
Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
|
||||
MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
|
||||
bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG
|
||||
ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS
|
||||
7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp
|
||||
0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS
|
||||
B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49
|
||||
BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ
|
||||
LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4
|
||||
DXZDjC5Ty3zfDBeWUA==
|
||||
-----END CERTIFICATE-----`
|
||||
certDataEncoded := "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNHVENDQVorZ0F3SUJBZ0lRQ2VDVFphejMyY2k1UGh3TEJDb3U4ekFLQmdncWhrak9QUVFEQXpCT01Rc3cKQ1FZRFZRUUdFd0pWVXpFWE1CVUdBMVVFQ2hNT1JHbG5hVU5sY25Rc0lFbHVZeTR4SmpBa0JnTlZCQU1USFVScApaMmxEWlhKMElGUk1VeUJGUTBNZ1VETTROQ0JTYjI5MElFYzFNQjRYRFRJeE1ERXhOVEF3TURBd01Gb1hEVFEyCk1ERXhOREl6TlRrMU9Wb3dUakVMTUFrR0ExVUVCaE1DVlZNeEZ6QVZCZ05WQkFvVERrUnBaMmxEWlhKMExDQkoKYm1NdU1TWXdKQVlEVlFRREV4MUVhV2RwUTJWeWRDQlVURk1nUlVORElGQXpPRFFnVW05dmRDQkhOVEIyTUJBRwpCeXFHU000OUFnRUdCU3VCQkFBaUEySUFCTUZFb2M4UmwxQ2EzaU9DTlFmTjBNc1luZEx4ZjNjMVR6dmRsSEpTCjdjSTcrT3o2ZTJ0WUlPeVpyc244YUxOMXVkc0o3TWdUOVU3R0NoMW1NRXk3SDBjS1BHRVFRaWw4cFFnTzRDTHAKMHpWb3pwdGpuNFMxbVUxWW9JNzFWT2VWeWFOQ01FQXdIUVlEVlIwT0JCWUVGTUZSUlZCWnF6N25MRnI2SUNJUwpCNENJZkJGcU1BNEdBMVVkRHdFQi93UUVBd0lCaGpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5CkJBTURBMmdBTUdVQ01RQ0phbzFINSt6OGJsVUQyV2RzSms2RHh2M0oreXNUdkxkNmpMUmwwbWxwWXhOak95WlEKTGdHaGVRYVJuVWkvd3I0Q01FZkRGWHV4b0pHWlNaT29QSHpvUmdhTExQSXhBSlNkWXNpSnZSbUVGT21sK3dHNApEWFpEakM1VHkzemZEQmVXVUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t"
|
||||
|
||||
cases := map[string]testCase{
|
||||
"UseCABundleSuccess": {
|
||||
|
|
|
@ -19,23 +19,18 @@ import (
|
|||
"fmt"
|
||||
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||
)
|
||||
|
||||
const (
|
||||
errInvalidClusterStoreMissingNamespace = "missing namespace"
|
||||
errFetchCredentials = "could not fetch credentials: %w"
|
||||
errMissingCredentials = "missing credentials: \"%s\""
|
||||
errEmptyKey = "key %s found but empty"
|
||||
errUnableCreateToken = "cannot create service account token: %q"
|
||||
errUnableCreateToken = "cannot create service account token: %q"
|
||||
)
|
||||
|
||||
func (c *Client) getAuth(ctx context.Context) (*rest.Config, error) {
|
||||
|
@ -48,7 +43,13 @@ func (c *Client) getAuth(ctx context.Context) (*rest.Config, error) {
|
|||
return clientcmd.RESTConfigFromKubeConfig(cfg)
|
||||
}
|
||||
|
||||
ca, err := c.getCA(ctx)
|
||||
ca, err := utils.FetchCACertFromSource(ctx, utils.CreateCertOpts{
|
||||
CABundle: c.store.Server.CABundle,
|
||||
CAProvider: c.store.Server.CAProvider,
|
||||
StoreKind: c.storeKind,
|
||||
Namespace: c.namespace,
|
||||
Client: c.ctrlClient,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -92,40 +93,6 @@ func (c *Client) getAuth(ctx context.Context) (*rest.Config, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) getCA(ctx context.Context) ([]byte, error) {
|
||||
if c.store.Server.CABundle != nil {
|
||||
return c.store.Server.CABundle, nil
|
||||
}
|
||||
if c.store.Server.CAProvider != nil {
|
||||
var ca []byte
|
||||
var err error
|
||||
switch c.store.Server.CAProvider.Type {
|
||||
case esv1beta1.CAProviderTypeConfigMap:
|
||||
keySelector := esmeta.SecretKeySelector{
|
||||
Name: c.store.Server.CAProvider.Name,
|
||||
Namespace: c.store.Server.CAProvider.Namespace,
|
||||
Key: c.store.Server.CAProvider.Key,
|
||||
}
|
||||
ca, err = c.fetchConfigMapKey(ctx, keySelector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch Server.CAProvider ConfigMap: %w", err)
|
||||
}
|
||||
case esv1beta1.CAProviderTypeSecret:
|
||||
keySelector := esmeta.SecretKeySelector{
|
||||
Name: c.store.Server.CAProvider.Name,
|
||||
Namespace: c.store.Server.CAProvider.Namespace,
|
||||
Key: c.store.Server.CAProvider.Key,
|
||||
}
|
||||
ca, err = c.fetchSecretKey(ctx, keySelector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch Server.CAProvider Secret: %w", err)
|
||||
}
|
||||
}
|
||||
return ca, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no Certificate Authority provided")
|
||||
}
|
||||
|
||||
func (c *Client) getClientKeyAndCert(ctx context.Context) ([]byte, []byte, error) {
|
||||
var err error
|
||||
cert, err := c.fetchSecretKey(ctx, c.store.Auth.Cert.ClientCert)
|
||||
|
@ -171,30 +138,3 @@ func (c *Client) fetchSecretKey(ctx context.Context, ref esmeta.SecretKeySelecto
|
|||
}
|
||||
return []byte(secret), nil
|
||||
}
|
||||
|
||||
func (c *Client) fetchConfigMapKey(ctx context.Context, key esmeta.SecretKeySelector) ([]byte, error) {
|
||||
configMap := &corev1.ConfigMap{}
|
||||
objectKey := types.NamespacedName{
|
||||
Name: key.Name,
|
||||
Namespace: c.namespace,
|
||||
}
|
||||
// only ClusterStore is allowed to set namespace (and then it's required)
|
||||
if c.storeKind == esv1beta1.ClusterSecretStoreKind {
|
||||
if key.Namespace == nil {
|
||||
return nil, fmt.Errorf(errInvalidClusterStoreMissingNamespace)
|
||||
}
|
||||
objectKey.Namespace = *key.Namespace
|
||||
}
|
||||
err := c.ctrlClient.Get(ctx, objectKey, configMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errFetchCredentials, err)
|
||||
}
|
||||
val, ok := configMap.Data[key.Key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(errMissingCredentials, key.Key)
|
||||
}
|
||||
if val == "" {
|
||||
return nil, fmt.Errorf(errEmptyKey, key.Key)
|
||||
}
|
||||
return []byte(val), nil
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
|
@ -99,7 +99,7 @@ func TestSetAuth(t *testing.T) {
|
|||
fields: fields{
|
||||
store: &esv1beta1.KubernetesProvider{
|
||||
Server: esv1beta1.KubernetesServer{
|
||||
CABundle: []byte("1234"),
|
||||
CABundle: []byte(caCert),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -116,7 +116,7 @@ func TestSetAuth(t *testing.T) {
|
|||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"cert": []byte("1234"),
|
||||
"cert": []byte(caCert),
|
||||
"token": []byte("mytoken"),
|
||||
},
|
||||
}).Build(),
|
||||
|
@ -144,7 +144,7 @@ func TestSetAuth(t *testing.T) {
|
|||
Host: "https://my.test.tld",
|
||||
BearerToken: "mytoken",
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
CAData: []byte("1234"),
|
||||
CAData: []byte(caCert),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
|
@ -215,7 +215,7 @@ func TestSetAuth(t *testing.T) {
|
|||
store: &esv1beta1.KubernetesProvider{
|
||||
Server: esv1beta1.KubernetesServer{
|
||||
URL: "https://my.test.tld",
|
||||
CABundle: []byte("1234"),
|
||||
CABundle: []byte(caCert),
|
||||
},
|
||||
Auth: esv1beta1.KubernetesAuth{
|
||||
Token: &esv1beta1.TokenAuth{
|
||||
|
@ -232,7 +232,7 @@ func TestSetAuth(t *testing.T) {
|
|||
Host: "https://my.test.tld",
|
||||
BearerToken: "mytoken",
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
CAData: []byte("1234"),
|
||||
CAData: []byte(caCert),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
|
@ -262,7 +262,7 @@ func TestSetAuth(t *testing.T) {
|
|||
store: &esv1beta1.KubernetesProvider{
|
||||
Server: esv1beta1.KubernetesServer{
|
||||
URL: "https://my.test.tld",
|
||||
CABundle: []byte("1234"),
|
||||
CABundle: []byte(caCert),
|
||||
},
|
||||
Auth: esv1beta1.KubernetesAuth{
|
||||
Token: &esv1beta1.TokenAuth{
|
||||
|
@ -289,7 +289,7 @@ func TestSetAuth(t *testing.T) {
|
|||
Host: "https://my.test.tld",
|
||||
BearerToken: "mytoken",
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
CAData: []byte("1234"),
|
||||
CAData: []byte(caCert),
|
||||
CertData: []byte("my-cert"),
|
||||
KeyData: []byte("my-key"),
|
||||
},
|
||||
|
@ -310,7 +310,7 @@ func TestSetAuth(t *testing.T) {
|
|||
store: &esv1beta1.KubernetesProvider{
|
||||
Server: esv1beta1.KubernetesServer{
|
||||
URL: "https://my.test.tld",
|
||||
CABundle: []byte("1234"),
|
||||
CABundle: []byte(caCert),
|
||||
},
|
||||
Auth: esv1beta1.KubernetesAuth{
|
||||
ServiceAccount: &v1.ServiceAccountSelector{
|
||||
|
@ -324,7 +324,7 @@ func TestSetAuth(t *testing.T) {
|
|||
Host: "https://my.test.tld",
|
||||
BearerToken: "my-sa-token",
|
||||
TLSClientConfig: rest.TLSClientConfig{
|
||||
CAData: []byte("1234"),
|
||||
CAData: []byte(caCert),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
|
@ -342,7 +342,7 @@ func TestSetAuth(t *testing.T) {
|
|||
kubeclientset: utilfake.NewCreateTokenMock().WithToken("my-sa-token"),
|
||||
store: &esv1beta1.KubernetesProvider{
|
||||
Server: esv1beta1.KubernetesServer{
|
||||
CABundle: []byte("1234"),
|
||||
CABundle: []byte(caCert),
|
||||
},
|
||||
Auth: esv1beta1.KubernetesAuth{
|
||||
ServiceAccount: &v1.ServiceAccountSelector{
|
||||
|
@ -399,9 +399,7 @@ func TestSetAuth(t *testing.T) {
|
|||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("BaseClient.setAuth() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !cmp.Equal(cfg, tt.want) {
|
||||
t.Errorf("unexpected value: expected %#v, got %#v", tt.want, cfg)
|
||||
}
|
||||
assert.Equal(t, tt.want, cfg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
approle "github.com/hashicorp/vault/api/auth/approle"
|
||||
"github.com/hashicorp/vault/api/auth/approle"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/constants"
|
||||
|
|
|
@ -25,13 +25,12 @@ import (
|
|||
"github.com/go-logr/logr"
|
||||
vault "github.com/hashicorp/vault/api"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/vault/util"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||
)
|
||||
|
||||
|
@ -56,39 +55,19 @@ func (c *client) newConfig(ctx context.Context) (*vault.Config, error) {
|
|||
|
||||
if len(c.store.CABundle) != 0 || c.store.CAProvider != nil {
|
||||
caCertPool := x509.NewCertPool()
|
||||
|
||||
if len(c.store.CABundle) > 0 {
|
||||
ok := caCertPool.AppendCertsFromPEM(c.store.CABundle)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(errVaultCert, errors.New("failed to parse certificates from CertPool"))
|
||||
}
|
||||
ca, err := utils.FetchCACertFromSource(ctx, utils.CreateCertOpts{
|
||||
CABundle: c.store.CABundle,
|
||||
CAProvider: c.store.CAProvider,
|
||||
StoreKind: c.storeKind,
|
||||
Namespace: c.namespace,
|
||||
Client: c.kube,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.store.CAProvider != nil && c.storeKind == esv1beta1.ClusterSecretStoreKind && c.store.CAProvider.Namespace == nil {
|
||||
return nil, errors.New(errCANamespace)
|
||||
}
|
||||
|
||||
if c.store.CAProvider != nil {
|
||||
var cert []byte
|
||||
var err error
|
||||
|
||||
switch c.store.CAProvider.Type {
|
||||
case esv1beta1.CAProviderTypeSecret:
|
||||
cert, err = getCertFromSecret(c)
|
||||
case esv1beta1.CAProviderTypeConfigMap:
|
||||
cert, err = getCertFromConfigMap(c)
|
||||
default:
|
||||
return nil, errors.New(errUnknownCAProvider)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ok := caCertPool.AppendCertsFromPEM(cert)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(errVaultCert, errors.New("failed to parse certificates from CertPool"))
|
||||
}
|
||||
ok := caCertPool.AppendCertsFromPEM(ca)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(errVaultCert, errors.New("failed to parse certificates from CertPool"))
|
||||
}
|
||||
|
||||
if transport, ok := cfg.HttpClient.Transport.(*http.Transport); ok {
|
||||
|
@ -138,50 +117,6 @@ func (c *client) configureClientTLS(ctx context.Context, cfg *vault.Config) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func getCertFromSecret(v *client) ([]byte, error) {
|
||||
secretRef := esmeta.SecretKeySelector{
|
||||
Name: v.store.CAProvider.Name,
|
||||
Namespace: &v.namespace,
|
||||
Key: v.store.CAProvider.Key,
|
||||
}
|
||||
|
||||
if v.store.CAProvider.Namespace != nil {
|
||||
secretRef.Namespace = v.store.CAProvider.Namespace
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
res, err := resolvers.SecretKeyRef(ctx, v.kube, v.storeKind, v.namespace, &secretRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errVaultCert, err)
|
||||
}
|
||||
|
||||
return []byte(res), nil
|
||||
}
|
||||
|
||||
func getCertFromConfigMap(v *client) ([]byte, error) {
|
||||
objKey := types.NamespacedName{
|
||||
Name: v.store.CAProvider.Name,
|
||||
Namespace: v.namespace,
|
||||
}
|
||||
|
||||
if v.store.CAProvider.Namespace != nil {
|
||||
objKey.Namespace = *v.store.CAProvider.Namespace
|
||||
}
|
||||
|
||||
configMapRef := &corev1.ConfigMap{}
|
||||
ctx := context.Background()
|
||||
err := v.kube.Get(ctx, objKey, configMapRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errVaultCert, err)
|
||||
}
|
||||
|
||||
val, ok := configMapRef.Data[v.store.CAProvider.Key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(errConfigMapFmt, v.store.CAProvider.Key)
|
||||
}
|
||||
return []byte(val), nil
|
||||
}
|
||||
|
||||
func (c *client) Close(ctx context.Context) error {
|
||||
// Revoke the token if we have one set, it wasn't sourced from a TokenSecretRef,
|
||||
// and token caching isn't enabled
|
||||
|
|
|
@ -43,13 +43,11 @@ var (
|
|||
)
|
||||
|
||||
const (
|
||||
errVaultStore = "received invalid Vault SecretStore resource: %w"
|
||||
errVaultClient = "cannot setup new vault client: %w"
|
||||
errVaultCert = "cannot set Vault CA certificate: %w"
|
||||
errConfigMapFmt = "cannot find config map data for key: %q"
|
||||
errClientTLSAuth = "error from Client TLS Auth: %q"
|
||||
errUnknownCAProvider = "unknown caProvider type given"
|
||||
errCANamespace = "cannot read secret for CAProvider due to missing namespace on kind ClusterSecretStore"
|
||||
errVaultStore = "received invalid Vault SecretStore resource: %w"
|
||||
errVaultClient = "cannot setup new vault client: %w"
|
||||
errVaultCert = "cannot set Vault CA certificate: %w"
|
||||
errClientTLSAuth = "error from Client TLS Auth: %q"
|
||||
errCANamespace = "missing namespace on caProvider secret"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
|
|
|
@ -306,7 +306,7 @@ MIIFkTCCA3mgAwIBAgIUBEUg3m/WqAsWHG4Q/II3IePFfuowDQYJKoZIhvcNAQELBQAwWDELMAkGA1UE
|
|||
}),
|
||||
},
|
||||
want: want{
|
||||
err: fmt.Errorf(errVaultCert, errors.New("failed to parse certificates from CertPool")),
|
||||
err: fmt.Errorf("failed to decode ca bundle: %w", errors.New("failed to parse the new certificate, not valid pem data")),
|
||||
},
|
||||
},
|
||||
"VaultAuthFormatError": {
|
||||
|
@ -419,7 +419,7 @@ MIIFkTCCA3mgAwIBAgIUBEUg3m/WqAsWHG4Q/II3IePFfuowDQYJKoZIhvcNAQELBQAwWDELMAkGA1UE
|
|||
newClientFunc: fake.ClientWithLoginMock,
|
||||
},
|
||||
want: want{
|
||||
err: fmt.Errorf(errVaultCert, errors.New(`cannot find secret data for key: "cert"`)),
|
||||
err: fmt.Errorf("failed to get cert from secret: %w", fmt.Errorf("failed to resolve secret key ref: %w", errors.New("cannot find secret data for key: \"cert\""))),
|
||||
},
|
||||
},
|
||||
"SuccessfulVaultStoreWithIamAuthSecret": {
|
||||
|
@ -491,7 +491,7 @@ MIIFkTCCA3mgAwIBAgIUBEUg3m/WqAsWHG4Q/II3IePFfuowDQYJKoZIhvcNAQELBQAwWDELMAkGA1UE
|
|||
newClientFunc: fake.ClientWithLoginMock,
|
||||
},
|
||||
want: want{
|
||||
err: fmt.Errorf(errConfigMapFmt, "cert"),
|
||||
err: fmt.Errorf("failed to get cert from configmap: %w", errors.New("failed to get caProvider configMap vault-cert -> cert")),
|
||||
},
|
||||
},
|
||||
"GetCertificateFormatError": {
|
||||
|
@ -506,7 +506,7 @@ MIIFkTCCA3mgAwIBAgIUBEUg3m/WqAsWHG4Q/II3IePFfuowDQYJKoZIhvcNAQELBQAwWDELMAkGA1UE
|
|||
},
|
||||
Data: map[string][]byte{
|
||||
tlsKey: secretClientKey,
|
||||
tlsCrt: []byte("cert with mistak"),
|
||||
tlsCrt: []byte("cert with mistake"),
|
||||
},
|
||||
}).Build(),
|
||||
newClientFunc: fake.ClientWithLoginMock,
|
||||
|
|
|
@ -60,7 +60,7 @@ func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
|
|||
return esv1beta1.SecretStoreReadOnly
|
||||
}
|
||||
|
||||
func (p *Provider) NewClient(_ context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
wh := webhook.Webhook{
|
||||
Kube: kube,
|
||||
Namespace: namespace,
|
||||
|
@ -80,7 +80,7 @@ func (p *Provider) NewClient(_ context.Context, store esv1beta1.GenericStore, ku
|
|||
}
|
||||
whClient.url = provider.URL
|
||||
|
||||
whClient.wh.HTTP, err = whClient.wh.GetHTTPClient(provider)
|
||||
whClient.wh.HTTP, err = whClient.wh.GetHTTPClient(ctx, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -113,12 +113,12 @@ func (w *WebHook) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRe
|
|||
return false, fmt.Errorf(errNotImplemented)
|
||||
}
|
||||
|
||||
// Not Implemented PushSecret.
|
||||
// PushSecret not implement.
|
||||
func (w *WebHook) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
|
||||
return fmt.Errorf(errNotImplemented)
|
||||
}
|
||||
|
||||
// Empty GetAllSecrets.
|
||||
// GetAllSecrets Empty .
|
||||
func (w *WebHook) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
// TO be implemented
|
||||
return nil, fmt.Errorf(errNotImplemented)
|
||||
|
|
|
@ -16,9 +16,12 @@ package utils
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5" //nolint:gosec
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
@ -31,12 +34,15 @@ import (
|
|||
"time"
|
||||
"unicode"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
"github.com/external-secrets/external-secrets/pkg/template/v2"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -139,7 +145,7 @@ func transform(val string, data map[string][]byte) ([]byte, error) {
|
|||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// DecodeValues decodes values from a secretMap.
|
||||
// DecodeMap decodes values from a secretMap.
|
||||
func DecodeMap(strategy esv1beta1.ExternalSecretDecodingStrategy, in map[string][]byte) (map[string][]byte, error) {
|
||||
out := make(map[string][]byte, len(in))
|
||||
for k, v := range in {
|
||||
|
@ -515,3 +521,123 @@ func CompareStringAndByteSlices(valueString *string, valueByte []byte) bool {
|
|||
|
||||
return bytes.Equal(valueByte, []byte(*valueString))
|
||||
}
|
||||
|
||||
// CreateCertOpts contains options for a cert pool creation.
|
||||
type CreateCertOpts struct {
|
||||
CABundle []byte
|
||||
CAProvider *esv1beta1.CAProvider
|
||||
StoreKind string
|
||||
Namespace string
|
||||
Client client.Client
|
||||
}
|
||||
|
||||
// FetchCACertFromSource creates a CertPool using either a CABundle directly, or
|
||||
// a ConfigMap / Secret.
|
||||
func FetchCACertFromSource(ctx context.Context, opts CreateCertOpts) ([]byte, error) {
|
||||
if len(opts.CABundle) == 0 && opts.CAProvider == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if len(opts.CABundle) > 0 {
|
||||
pem, err := base64decode(opts.CABundle)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode ca bundle: %w", err)
|
||||
}
|
||||
|
||||
return pem, nil
|
||||
}
|
||||
|
||||
if opts.CAProvider != nil &&
|
||||
opts.StoreKind == esv1beta1.ClusterSecretStoreKind &&
|
||||
opts.CAProvider.Namespace == nil {
|
||||
return nil, fmt.Errorf("missing namespace on caProvider secret")
|
||||
}
|
||||
|
||||
switch opts.CAProvider.Type {
|
||||
case esv1beta1.CAProviderTypeSecret:
|
||||
cert, err := getCertFromSecret(ctx, opts.Client, opts.CAProvider, opts.StoreKind, opts.Namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get cert from secret: %w", err)
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
case esv1beta1.CAProviderTypeConfigMap:
|
||||
cert, err := getCertFromConfigMap(ctx, opts.Namespace, opts.Client, opts.CAProvider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get cert from configmap: %w", err)
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported CA provider type: %s", opts.CAProvider.Type)
|
||||
}
|
||||
|
||||
func base64decode(cert []byte) ([]byte, error) {
|
||||
if c, err := parseCertificateBytes(cert); err == nil {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// try decoding and test for validity again...
|
||||
certificate, err := Decode(esv1beta1.ExternalSecretDecodeAuto, cert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode base64: %w", err)
|
||||
}
|
||||
|
||||
return parseCertificateBytes(certificate)
|
||||
}
|
||||
|
||||
func parseCertificateBytes(certBytes []byte) ([]byte, error) {
|
||||
block, _ := pem.Decode(certBytes)
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to parse the new certificate, not valid pem data")
|
||||
}
|
||||
|
||||
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
|
||||
return nil, fmt.Errorf("failed to validate certificate: %w", err)
|
||||
}
|
||||
|
||||
return certBytes, nil
|
||||
}
|
||||
|
||||
func getCertFromSecret(ctx context.Context, c client.Client, provider *esv1beta1.CAProvider, storeKind, namespace string) ([]byte, error) {
|
||||
secretRef := esmeta.SecretKeySelector{
|
||||
Name: provider.Name,
|
||||
Key: provider.Key,
|
||||
}
|
||||
|
||||
if provider.Namespace != nil {
|
||||
secretRef.Namespace = provider.Namespace
|
||||
}
|
||||
|
||||
cert, err := resolvers.SecretKeyRef(ctx, c, storeKind, namespace, &secretRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve secret key ref: %w", err)
|
||||
}
|
||||
|
||||
return []byte(cert), nil
|
||||
}
|
||||
|
||||
func getCertFromConfigMap(ctx context.Context, namespace string, c client.Client, provider *esv1beta1.CAProvider) ([]byte, error) {
|
||||
objKey := client.ObjectKey{
|
||||
Name: provider.Name,
|
||||
Namespace: namespace,
|
||||
}
|
||||
|
||||
if provider.Namespace != nil {
|
||||
objKey.Namespace = *provider.Namespace
|
||||
}
|
||||
|
||||
configMapRef := &corev1.ConfigMap{}
|
||||
err := c.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.Key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to get caProvider configMap %s -> %s", objKey.Name, provider.Key)
|
||||
}
|
||||
|
||||
return []byte(val), nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue