mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
feat: implement validating webhook
Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
This commit is contained in:
parent
ab4a1f3d05
commit
8fc4484cc6
52 changed files with 1903 additions and 402 deletions
|
@ -28,7 +28,7 @@ func (alpha *ExternalSecret) ConvertTo(betaRaw conversion.Hub) error {
|
|||
v1beta1DataFrom := make([]esv1beta1.ExternalSecretDataFromRemoteRef, 0)
|
||||
for _, v1alpha1RemoteRef := range alpha.Spec.DataFrom {
|
||||
v1beta1RemoteRef := esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
Extract: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Extract: &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: v1alpha1RemoteRef.Key,
|
||||
Property: v1alpha1RemoteRef.Property,
|
||||
Version: v1alpha1RemoteRef.Version,
|
||||
|
|
|
@ -183,7 +183,7 @@ func newExternalSecretV1Beta1() *esv1beta1.ExternalSecret {
|
|||
},
|
||||
DataFrom: []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Extract: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Extract: &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "key",
|
||||
Property: "property",
|
||||
Version: "version",
|
||||
|
|
|
@ -161,15 +161,19 @@ type ExternalSecretDataRemoteRef struct {
|
|||
Property string `json:"property,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:MinProperties=1
|
||||
// +kubebuilder:validation:MaxProperties=1
|
||||
type ExternalSecretDataFromRemoteRef struct {
|
||||
// Used to extract multiple key/value pairs from one secret
|
||||
// +optional
|
||||
Extract ExternalSecretDataRemoteRef `json:"extract,omitempty"`
|
||||
Extract *ExternalSecretDataRemoteRef `json:"extract,omitempty"`
|
||||
// Used to find secrets based on tags or regular expressions
|
||||
// +optional
|
||||
Find ExternalSecretFind `json:"find,omitempty"`
|
||||
Find *ExternalSecretFind `json:"find,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:MinProperties=1
|
||||
// +kubebuilder:validation:MaxProperties=1
|
||||
type ExternalSecretFind struct {
|
||||
// Finds secrets based on the name.
|
||||
// +optional
|
||||
|
|
|
@ -12,36 +12,47 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
)
|
||||
|
||||
// +kubebuilder:object:root=false
|
||||
// +kubebuilder:object:generate:false
|
||||
// +k8s:deepcopy-gen:interfaces=nil
|
||||
// +k8s:deepcopy-gen=nil
|
||||
|
||||
// Provider is a common interface for interacting with secret backends.
|
||||
type Provider interface {
|
||||
// NewClient constructs a SecretsManager Provider
|
||||
NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (SecretsClient, error)
|
||||
NewClient(ctx context.Context, store GenericStore, kube client.Client, namespace string) (SecretsClient, error)
|
||||
|
||||
// ValidateStore checks if the provided store is valid
|
||||
ValidateStore(store GenericStore) error
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=false
|
||||
// +kubebuilder:object:generate:false
|
||||
// +k8s:deepcopy-gen:interfaces=nil
|
||||
// +k8s:deepcopy-gen=nil
|
||||
|
||||
// SecretsClient provides access to secrets.
|
||||
type SecretsClient interface {
|
||||
// GetSecret returns a single secret from the provider
|
||||
GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error)
|
||||
GetSecret(ctx context.Context, ref ExternalSecretDataRemoteRef) ([]byte, error)
|
||||
|
||||
// Validate checks if the client is configured correctly
|
||||
// and is able to retrieve secrets from the provider
|
||||
Validate() error
|
||||
|
||||
// GetSecretMap returns multiple k/v pairs from the provider
|
||||
GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
|
||||
GetSecretMap(ctx context.Context, ref ExternalSecretDataRemoteRef) (map[string][]byte, error)
|
||||
|
||||
// GetAllSecrets returns multiple k/v pairs from the provider
|
||||
GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error)
|
||||
GetAllSecrets(ctx context.Context, ref ExternalSecretFind) (map[string][]byte, error)
|
||||
|
||||
Close(ctx context.Context) error
|
||||
}
|
|
@ -12,27 +12,24 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package schema
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
)
|
||||
|
||||
var builder map[string]provider.Provider
|
||||
var builder map[string]Provider
|
||||
var buildlock sync.RWMutex
|
||||
|
||||
func init() {
|
||||
builder = make(map[string]provider.Provider)
|
||||
builder = make(map[string]Provider)
|
||||
}
|
||||
|
||||
// Register a store backend type. Register panics if a
|
||||
// backend with the same store is already registered.
|
||||
func Register(s provider.Provider, storeSpec *esv1beta1.SecretStoreProvider) {
|
||||
func Register(s Provider, storeSpec *SecretStoreProvider) {
|
||||
storeName, err := getProviderName(storeSpec)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
|
||||
|
@ -50,7 +47,7 @@ func Register(s provider.Provider, storeSpec *esv1beta1.SecretStoreProvider) {
|
|||
|
||||
// ForceRegister adds to store schema, overwriting a store if
|
||||
// already registered. Should only be used for testing.
|
||||
func ForceRegister(s provider.Provider, storeSpec *esv1beta1.SecretStoreProvider) {
|
||||
func ForceRegister(s Provider, storeSpec *SecretStoreProvider) {
|
||||
storeName, err := getProviderName(storeSpec)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("store error registering schema: %s", err.Error()))
|
||||
|
@ -62,7 +59,7 @@ func ForceRegister(s provider.Provider, storeSpec *esv1beta1.SecretStoreProvider
|
|||
}
|
||||
|
||||
// GetProviderByName returns the provider implementation by name.
|
||||
func GetProviderByName(name string) (provider.Provider, bool) {
|
||||
func GetProviderByName(name string) (Provider, bool) {
|
||||
buildlock.RLock()
|
||||
f, ok := builder[name]
|
||||
buildlock.RUnlock()
|
||||
|
@ -70,8 +67,11 @@ func GetProviderByName(name string) (provider.Provider, bool) {
|
|||
}
|
||||
|
||||
// GetProvider returns the provider from the generic store.
|
||||
func GetProvider(s esv1beta1.GenericStore) (provider.Provider, error) {
|
||||
func GetProvider(s GenericStore) (Provider, error) {
|
||||
spec := s.GetSpec()
|
||||
if spec == nil {
|
||||
return nil, fmt.Errorf("no spec found in %#v", s)
|
||||
}
|
||||
storeName, err := getProviderName(spec.Provider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("store error for %s: %w", s.GetName(), err)
|
||||
|
@ -90,7 +90,7 @@ func GetProvider(s esv1beta1.GenericStore) (provider.Provider, error) {
|
|||
|
||||
// getProviderName returns the name of the configured provider
|
||||
// or an error if the provider is not configured.
|
||||
func getProviderName(storeSpec *esv1beta1.SecretStoreProvider) (string, error) {
|
||||
func getProviderName(storeSpec *SecretStoreProvider) (string, error) {
|
||||
storeBytes, err := json.Marshal(storeSpec)
|
||||
if err != nil || storeBytes == nil {
|
||||
return "", fmt.Errorf("failed to marshal store spec: %w", err)
|
|
@ -11,7 +11,7 @@ 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 schema
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -19,9 +19,6 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
)
|
||||
|
||||
type PP struct{}
|
||||
|
@ -29,22 +26,22 @@ type PP struct{}
|
|||
const shouldBeRegistered = "provider should be registered"
|
||||
|
||||
// New constructs a SecretsManager Provider.
|
||||
func (p *PP) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (p *PP) NewClient(ctx context.Context, store GenericStore, kube client.Client, namespace string) (SecretsClient, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// GetSecret returns a single secret from the provider.
|
||||
func (p *PP) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
func (p *PP) GetSecret(ctx context.Context, ref ExternalSecretDataRemoteRef) ([]byte, error) {
|
||||
return []byte("NOOP"), nil
|
||||
}
|
||||
|
||||
// GetSecretMap returns multiple k/v pairs from the provider.
|
||||
func (p *PP) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
func (p *PP) GetSecretMap(ctx context.Context, ref ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
return map[string][]byte{}, nil
|
||||
}
|
||||
|
||||
// Empty GetAllSecrets.
|
||||
func (p *PP) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
func (p *PP) GetAllSecrets(ctx context.Context, ref ExternalSecretFind) (map[string][]byte, error) {
|
||||
// TO be implemented
|
||||
return map[string][]byte{}, nil
|
||||
}
|
||||
|
@ -57,6 +54,10 @@ func (p *PP) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *PP) ValidateStore(store GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestRegister tests if the Register function
|
||||
// (1) panics if it tries to register something invalid
|
||||
// (2) stores the correct provider.
|
||||
|
@ -66,22 +67,22 @@ func TestRegister(t *testing.T) {
|
|||
name string
|
||||
expPanic bool
|
||||
expExists bool
|
||||
provider *esv1beta1.SecretStoreProvider
|
||||
provider *SecretStoreProvider
|
||||
}{
|
||||
{
|
||||
test: "should panic when given an invalid provider",
|
||||
name: "aws",
|
||||
expPanic: true,
|
||||
expExists: false,
|
||||
provider: &esv1beta1.SecretStoreProvider{},
|
||||
provider: &SecretStoreProvider{},
|
||||
},
|
||||
{
|
||||
test: "should register an correct provider",
|
||||
name: "aws",
|
||||
expExists: false,
|
||||
provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Service: esv1beta1.AWSServiceSecretsManager,
|
||||
provider: &SecretStoreProvider{
|
||||
AWS: &AWSProvider{
|
||||
Service: AWSServiceSecretsManager,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -90,9 +91,9 @@ func TestRegister(t *testing.T) {
|
|||
name: "aws",
|
||||
expPanic: true,
|
||||
expExists: true,
|
||||
provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Service: esv1beta1.AWSServiceSecretsManager,
|
||||
provider: &SecretStoreProvider{
|
||||
AWS: &AWSProvider{
|
||||
Service: AWSServiceSecretsManager,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -109,10 +110,10 @@ func TestRegister(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, name string, provider *esv1beta1.SecretStoreProvider, expPanic bool) {
|
||||
func runTest(t *testing.T, name string, provider *SecretStoreProvider, expPanic bool) {
|
||||
testProvider := &PP{}
|
||||
secretStore := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
secretStore := &SecretStore{
|
||||
Spec: SecretStoreSpec{
|
||||
Provider: provider,
|
||||
},
|
||||
}
|
||||
|
@ -135,19 +136,19 @@ func runTest(t *testing.T, name string, provider *esv1beta1.SecretStoreProvider,
|
|||
// ForceRegister is used by other tests, we should ensure it works as expected.
|
||||
func TestForceRegister(t *testing.T) {
|
||||
testProvider := &PP{}
|
||||
provider := &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Service: esv1beta1.AWSServiceParameterStore,
|
||||
provider := &SecretStoreProvider{
|
||||
AWS: &AWSProvider{
|
||||
Service: AWSServiceParameterStore,
|
||||
},
|
||||
}
|
||||
secretStore := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
secretStore := &SecretStore{
|
||||
Spec: SecretStoreSpec{
|
||||
Provider: provider,
|
||||
},
|
||||
}
|
||||
ForceRegister(testProvider, &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Service: esv1beta1.AWSServiceParameterStore,
|
||||
ForceRegister(testProvider, &SecretStoreProvider{
|
||||
AWS: &AWSProvider{
|
||||
Service: AWSServiceParameterStore,
|
||||
},
|
||||
})
|
||||
p1, ok := GetProviderByName("aws")
|
||||
|
@ -164,10 +165,10 @@ func TestRegisterGCP(t *testing.T) {
|
|||
assert.False(t, ok, "provider should not be registered")
|
||||
|
||||
testProvider := &PP{}
|
||||
secretStore := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
GCPSM: &esv1beta1.GCPSMProvider{},
|
||||
secretStore := &SecretStore{
|
||||
Spec: SecretStoreSpec{
|
||||
Provider: &SecretStoreProvider{
|
||||
GCPSM: &GCPSMProvider{},
|
||||
},
|
||||
},
|
||||
}
|
62
apis/externalsecrets/v1beta1/secretstore_validator.go
Normal file
62
apis/externalsecrets/v1beta1/secretstore_validator.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
)
|
||||
|
||||
var _ admission.CustomValidator = &GenericStoreValidator{}
|
||||
|
||||
const (
|
||||
errInvalidStore = "invalid store"
|
||||
)
|
||||
|
||||
type GenericStoreValidator struct{}
|
||||
|
||||
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type.
|
||||
func (r *GenericStoreValidator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
|
||||
st, ok := obj.(GenericStore)
|
||||
if !ok {
|
||||
return fmt.Errorf(errInvalidStore)
|
||||
}
|
||||
return validateStore(st)
|
||||
}
|
||||
|
||||
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type.
|
||||
func (r *GenericStoreValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) error {
|
||||
st, ok := newObj.(GenericStore)
|
||||
if !ok {
|
||||
return fmt.Errorf(errInvalidStore)
|
||||
}
|
||||
return validateStore(st)
|
||||
}
|
||||
|
||||
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type.
|
||||
func (r *GenericStoreValidator) ValidateDelete(ctx context.Context, obj runtime.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateStore(store GenericStore) error {
|
||||
provider, err := GetProvider(store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return provider.ValidateStore(store)
|
||||
}
|
|
@ -21,11 +21,13 @@ import (
|
|||
func (c *SecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewWebhookManagedBy(mgr).
|
||||
For(c).
|
||||
WithValidator(&GenericStoreValidator{}).
|
||||
Complete()
|
||||
}
|
||||
|
||||
func (c *ClusterSecretStore) SetupWebhookWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewWebhookManagedBy(mgr).
|
||||
For(c).
|
||||
WithValidator(&GenericStoreValidator{}).
|
||||
Complete()
|
||||
}
|
||||
|
|
|
@ -422,8 +422,16 @@ func (in *ExternalSecretData) DeepCopy() *ExternalSecretData {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExternalSecretDataFromRemoteRef) DeepCopyInto(out *ExternalSecretDataFromRemoteRef) {
|
||||
*out = *in
|
||||
out.Extract = in.Extract
|
||||
in.Find.DeepCopyInto(&out.Find)
|
||||
if in.Extract != nil {
|
||||
in, out := &in.Extract, &out.Extract
|
||||
*out = new(ExternalSecretDataRemoteRef)
|
||||
**out = **in
|
||||
}
|
||||
if in.Find != nil {
|
||||
in, out := &in.Find, &out.Find
|
||||
*out = new(ExternalSecretFind)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalSecretDataFromRemoteRef.
|
||||
|
@ -794,6 +802,21 @@ func (in *GCPWorkloadIdentity) DeepCopy() *GCPWorkloadIdentity {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericStoreValidator) DeepCopyInto(out *GenericStoreValidator) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericStoreValidator.
|
||||
func (in *GenericStoreValidator) DeepCopy() *GenericStoreValidator {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericStoreValidator)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GitlabAuth) DeepCopyInto(out *GitlabAuth) {
|
||||
*out = *in
|
||||
|
|
|
@ -28,12 +28,13 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/crds"
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/webhookconfig"
|
||||
)
|
||||
|
||||
var certcontrollerCmd = &cobra.Command{
|
||||
Use: "certcontroller",
|
||||
Short: "Controller to manage certificates for external secrets CRDs",
|
||||
Long: `Controller to manage certificates for external secrets CRDs.
|
||||
Short: "Controller to manage certificates for external secrets CRDs and ValidatingWebhookConfigs",
|
||||
Long: `Controller to manage certificates for external secrets CRDs and ValidatingWebhookConfigs.
|
||||
For more information visit https://external-secrets.io`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var lvl zapcore.Level
|
||||
|
@ -46,11 +47,12 @@ var certcontrollerCmd = &cobra.Command{
|
|||
ctrl.SetLogger(logger)
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
Port: 9443,
|
||||
LeaderElection: enableLeaderElection,
|
||||
LeaderElectionID: "crd-certs-controller",
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
HealthProbeBindAddress: healthzAddr,
|
||||
Port: 9443,
|
||||
LeaderElection: enableLeaderElection,
|
||||
LeaderElectionID: "crd-certs-controller",
|
||||
ClientDisableCacheFor: []client.Object{
|
||||
// the client creates a ListWatch for all resource kinds that
|
||||
// are requested with .Get().
|
||||
|
@ -59,31 +61,48 @@ var certcontrollerCmd = &cobra.Command{
|
|||
// that he owns.
|
||||
// see #721
|
||||
&v1.Secret{},
|
||||
}})
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
crds := &crds.Reconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("webhook-certs-updater"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
SvcName: serviceName,
|
||||
SvcNamespace: serviceNamespace,
|
||||
SecretName: secretName,
|
||||
SecretNamespace: secretNamespace,
|
||||
RequeueInterval: crdRequeueInterval,
|
||||
CrdResources: []string{"externalsecrets.external-secrets.io", "clustersecretstores.external-secrets.io", "secretstores.external-secrets.io"},
|
||||
CAName: "external-secrets",
|
||||
CAOrganization: "external-secrets",
|
||||
RestartOnSecretRefresh: false,
|
||||
}
|
||||
if err := crds.SetupWithManager(mgr, controller.Options{
|
||||
crdctrl := crds.New(mgr.GetClient(), mgr.GetScheme(),
|
||||
ctrl.Log.WithName("controllers").WithName("webhook-certs-updater"),
|
||||
crdRequeueInterval, serviceName, serviceNamespace, secretName, secretNamespace, []string{
|
||||
"externalsecrets.external-secrets.io",
|
||||
"clustersecretstores.external-secrets.io",
|
||||
"secretstores.external-secrets.io",
|
||||
})
|
||||
if err := crdctrl.SetupWithManager(mgr, controller.Options{
|
||||
MaxConcurrentReconciles: concurrent,
|
||||
}); err != nil {
|
||||
setupLog.Error(err, errCreateController, "controller", "CustomResourceDefinition")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
whc := webhookconfig.New(mgr.GetClient(), mgr.GetScheme(),
|
||||
ctrl.Log.WithName("controllers").WithName("webhook-certs-updater"),
|
||||
serviceName, serviceNamespace,
|
||||
secretName, secretNamespace, crdRequeueInterval)
|
||||
if err := whc.SetupWithManager(mgr, controller.Options{
|
||||
MaxConcurrentReconciles: concurrent,
|
||||
}); err != nil {
|
||||
setupLog.Error(err, errCreateController, "controller", "WebhookConfig")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = mgr.AddReadyzCheck("crd-inject", crdctrl.ReadyCheck)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to add crd readyz check")
|
||||
os.Exit(1)
|
||||
}
|
||||
err = mgr.AddReadyzCheck("validation-webhook-inject", whc.ReadyCheck)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to add webhook readyz check")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
|
@ -96,6 +115,7 @@ func init() {
|
|||
rootCmd.AddCommand(certcontrollerCmd)
|
||||
|
||||
certcontrollerCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
certcontrollerCmd.Flags().StringVar(&healthzAddr, "healthz-addr", ":8081", "The address the health endpoint binds to.")
|
||||
certcontrollerCmd.Flags().StringVar(&serviceName, "service-name", "external-secrets-webhook", "Webhook service name")
|
||||
certcontrollerCmd.Flags().StringVar(&serviceNamespace, "service-namespace", "default", "Webhook service namespace")
|
||||
certcontrollerCmd.Flags().StringVar(&secretName, "secret-name", "external-secrets-webhook", "Secret to store certs for webhook")
|
||||
|
|
|
@ -22,11 +22,12 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap/zapcore"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
|
||||
// To allow using gcp auth.
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
|
@ -44,6 +45,7 @@ var (
|
|||
dnsName string
|
||||
certDir string
|
||||
metricsAddr string
|
||||
healthzAddr string
|
||||
controllerClass string
|
||||
enableLeaderElection bool
|
||||
concurrent int
|
||||
|
@ -64,6 +66,7 @@ func init() {
|
|||
_ = clientgoscheme.AddToScheme(scheme)
|
||||
_ = esv1beta1.AddToScheme(scheme)
|
||||
_ = esv1alpha1.AddToScheme(scheme)
|
||||
_ = apiextensionsv1.AddToScheme(scheme)
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
|
|
|
@ -17,6 +17,7 @@ package cmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
@ -65,12 +66,12 @@ var webhookCmd = &cobra.Command{
|
|||
logger := zap.New(zap.Level(lvl))
|
||||
ctrl.SetLogger(logger)
|
||||
|
||||
setupLog.Info("validating certs")
|
||||
err = crds.CheckCerts(c, dnsName, time.Now().Add(time.Hour))
|
||||
err = waitForCerts(c, time.Minute*2)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "error checking certs")
|
||||
setupLog.Error(err, "unable to validate certificates")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func(c crds.CertInfo, dnsName string, every time.Duration) {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
|
@ -92,10 +93,11 @@ var webhookCmd = &cobra.Command{
|
|||
}(c, dnsName, certCheckInterval)
|
||||
|
||||
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
Port: 9443,
|
||||
CertDir: certDir,
|
||||
Scheme: scheme,
|
||||
MetricsBindAddress: metricsAddr,
|
||||
HealthProbeBindAddress: healthzAddr,
|
||||
Port: 9443,
|
||||
CertDir: certDir,
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
|
@ -125,6 +127,15 @@ var webhookCmd = &cobra.Command{
|
|||
setupLog.Error(err, errCreateWebhook, "webhook", "ClusterSecretStore-v1alpha1")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = mgr.AddReadyzCheck("certs", func(_ *http.Request) error {
|
||||
return crds.CheckCerts(c, dnsName, time.Now().Add(time.Hour))
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to add certs readyz check")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctx); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
|
@ -133,9 +144,33 @@ var webhookCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
// waitForCerts waits until the certificates become ready.
|
||||
// If they don't become ready within a given time duration
|
||||
// this function returns an error.
|
||||
// certs are generated by the certcontroller.
|
||||
func waitForCerts(c crds.CertInfo, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
for {
|
||||
setupLog.Info("validating certs")
|
||||
err := crds.CheckCerts(c, dnsName, time.Now().Add(time.Hour))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
setupLog.Error(err, "invalid certs. retrying...")
|
||||
<-time.After(time.Second * 10)
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(webhookCmd)
|
||||
webhookCmd.Flags().StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
webhookCmd.Flags().StringVar(&healthzAddr, "healthz-addr", ":8081", "The address the health endpoint binds to.")
|
||||
webhookCmd.Flags().StringVar(&dnsName, "dns-name", "localhost", "DNS name to validate certificates with")
|
||||
webhookCmd.Flags().StringVar(&certDir, "cert-dir", "/tmp/k8s-webhook-server/serving-certs", "path to check for certs")
|
||||
webhookCmd.Flags().StringVar(&loglevel, "loglevel", "info", "loglevel to use, one of: debug, info, warn, error, dpanic, panic, fatal")
|
||||
|
|
|
@ -320,6 +320,8 @@ spec:
|
|||
Provider data If multiple entries are specified, the Secret keys
|
||||
are merged in the specified order
|
||||
items:
|
||||
maxProperties: 1
|
||||
minProperties: 1
|
||||
properties:
|
||||
extract:
|
||||
description: Used to extract multiple key/value pairs from one
|
||||
|
@ -341,6 +343,8 @@ spec:
|
|||
type: object
|
||||
find:
|
||||
description: Used to find secrets based on tags or regular expressions
|
||||
maxProperties: 1
|
||||
minProperties: 1
|
||||
properties:
|
||||
name:
|
||||
description: Finds secrets based on the name.
|
||||
|
|
|
@ -61,6 +61,12 @@ spec:
|
|||
- containerPort: {{ .Values.certController.prometheus.service.port }}
|
||||
protocol: TCP
|
||||
name: metrics
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 8081
|
||||
path: /readyz
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
{{- with .Values.certController.extraEnv }}
|
||||
env:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
|
|
|
@ -16,6 +16,31 @@ rules:
|
|||
- "watch"
|
||||
- "update"
|
||||
- "patch"
|
||||
- apiGroups:
|
||||
- "admissionregistration.k8s.io"
|
||||
resources:
|
||||
- "validatingwebhookconfigurations"
|
||||
verbs:
|
||||
- "get"
|
||||
- "list"
|
||||
- "watch"
|
||||
- "update"
|
||||
- "patch"
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- "endpoints"
|
||||
verbs:
|
||||
- "list"
|
||||
- "get"
|
||||
- "watch"
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- "events"
|
||||
verbs:
|
||||
- "create"
|
||||
- "patch"
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
apiVersion: admissionregistration.k8s.io/v1
|
||||
kind: ValidatingWebhookConfiguration
|
||||
metadata:
|
||||
name: secretstore-validate
|
||||
labels:
|
||||
external-secrets.io/component: webhook
|
||||
webhooks:
|
||||
- name: "validate.secretstore.external-secrets.io"
|
||||
rules:
|
||||
- apiGroups: ["external-secrets.io"]
|
||||
apiVersions: ["v1beta1"]
|
||||
operations: ["CREATE", "UPDATE", "DELETE"]
|
||||
resources: ["secretstores"]
|
||||
scope: "Namespaced"
|
||||
clientConfig:
|
||||
service:
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
name: {{ include "external-secrets.fullname" . }}-webhook
|
||||
path: /validate-external-secrets-io-v1beta1-secretstore
|
||||
# will be set by controller
|
||||
caBundle: Cg==
|
||||
admissionReviewVersions: ["v1", "v1beta1"]
|
||||
sideEffects: None
|
||||
timeoutSeconds: 5
|
||||
|
||||
- name: "validate.clustersecretstore.external-secrets.io"
|
||||
rules:
|
||||
- apiGroups: ["external-secrets.io"]
|
||||
apiVersions: ["v1beta1"]
|
||||
operations: ["CREATE", "UPDATE", "DELETE"]
|
||||
resources: ["clustersecretstores"]
|
||||
scope: "Cluster"
|
||||
clientConfig:
|
||||
service:
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
name: {{ include "external-secrets.fullname" . }}-webhook
|
||||
path: /validate-external-secrets-io-v1beta1-clustersecretstore
|
||||
caBundle: Cg== # will be set by controller
|
||||
admissionReviewVersions: ["v1", "v1beta1"]
|
||||
sideEffects: None
|
||||
timeoutSeconds: 5
|
|
@ -63,8 +63,9 @@ spec:
|
|||
protocol: TCP
|
||||
name: webhook
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 9443
|
||||
httpGet:
|
||||
port: 8081
|
||||
path: /readyz
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
{{- with .Values.webhook.extraEnv }}
|
||||
|
|
|
@ -2,6 +2,7 @@ apiVersion: v1
|
|||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "external-secrets.fullname" . }}-webhook
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "external-secrets-webhook.labels" . | nindent 4 }}
|
||||
external-secrets.io/component : webhook
|
||||
|
|
|
@ -2,6 +2,7 @@ apiVersion: v1
|
|||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "external-secrets.fullname" . }}-webhook
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
labels:
|
||||
{{- include "external-secrets-webhook.labels" . | nindent 4 }}
|
||||
external-secrets.io/component : webhook
|
||||
|
|
|
@ -2244,6 +2244,8 @@ spec:
|
|||
dataFrom:
|
||||
description: DataFrom is used to fetch all properties from a specific Provider data If multiple entries are specified, the Secret keys are merged in the specified order
|
||||
items:
|
||||
maxProperties: 1
|
||||
minProperties: 1
|
||||
properties:
|
||||
extract:
|
||||
description: Used to extract multiple key/value pairs from one secret
|
||||
|
@ -2262,6 +2264,8 @@ spec:
|
|||
type: object
|
||||
find:
|
||||
description: Used to find secrets based on tags or regular expressions
|
||||
maxProperties: 1
|
||||
minProperties: 1
|
||||
properties:
|
||||
name:
|
||||
description: Finds secrets based on the name.
|
||||
|
|
|
@ -15,28 +15,22 @@ package crds
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
crdGroup = "apiextensions.k8s.io"
|
||||
crdKind = "CustomResourceDefinition"
|
||||
crdVersion = "v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
crd unstructured.Unstructured
|
||||
crd2 unstructured.Unstructured
|
||||
crd *apiextensions.CustomResourceDefinition
|
||||
crd2 *apiextensions.CustomResourceDefinition
|
||||
service corev1.Service
|
||||
secret corev1.Secret
|
||||
assert func()
|
||||
|
@ -50,31 +44,27 @@ var _ = Describe("CRD reconcile", func() {
|
|||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// To improve later on with proper clean up.
|
||||
ctx := context.Background()
|
||||
k8sClient.Delete(ctx, &test.secret)
|
||||
k8sClient.Delete(ctx, &test.service)
|
||||
deleteCRD(test.crd)
|
||||
deleteCRD(test.crd2)
|
||||
})
|
||||
|
||||
// a invalid provider config should be reflected
|
||||
// in the store status condition
|
||||
PatchesCRD := func(tc *testCase) {
|
||||
tc.assert = func() {
|
||||
Consistently(func() bool {
|
||||
ss := unstructured.Unstructured{}
|
||||
ss.SetGroupVersionKind(schema.GroupVersionKind{Kind: crdKind, Version: crdVersion, Group: crdGroup})
|
||||
Eventually(func() bool {
|
||||
crd := &apiextensions.CustomResourceDefinition{}
|
||||
err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: "secretstores.test.io",
|
||||
}, &ss)
|
||||
}, crd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
val, ok, err := unstructured.NestedString(ss.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
|
||||
if err != nil || !ok {
|
||||
return false
|
||||
}
|
||||
want, ok, err := unstructured.NestedString(tc.crd.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
|
||||
if err != nil || !ok {
|
||||
return false
|
||||
}
|
||||
return want != val
|
||||
return crd.Spec.Conversion.Webhook.ClientConfig.Service.Name !=
|
||||
tc.crd.Spec.Conversion.Webhook.ClientConfig.Service.Name
|
||||
}).
|
||||
WithTimeout(time.Second * 10).
|
||||
WithPolling(time.Second).
|
||||
|
@ -87,23 +77,15 @@ var _ = Describe("CRD reconcile", func() {
|
|||
ignoreNonTargetCRDs := func(tc *testCase) {
|
||||
tc.assert = func() {
|
||||
Consistently(func() bool {
|
||||
ss := unstructured.Unstructured{}
|
||||
ss.SetGroupVersionKind(schema.GroupVersionKind{Kind: crdKind, Version: crdVersion, Group: crdGroup})
|
||||
crd := &apiextensions.CustomResourceDefinition{}
|
||||
err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: "some-other.test.io",
|
||||
}, &ss)
|
||||
}, crd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
got, ok, err := unstructured.NestedString(ss.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
|
||||
if !ok || err != nil {
|
||||
return false
|
||||
}
|
||||
want, ok, err := unstructured.NestedString(tc.crd2.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
|
||||
if !ok || err != nil {
|
||||
return false
|
||||
}
|
||||
return got == want
|
||||
return crd.Spec.Conversion.Webhook.ClientConfig.Service.Name ==
|
||||
tc.crd2.Spec.Conversion.Webhook.ClientConfig.Service.Name
|
||||
}).
|
||||
WithTimeout(time.Second * 3).
|
||||
WithPolling(time.Millisecond * 500).
|
||||
|
@ -116,21 +98,47 @@ var _ = Describe("CRD reconcile", func() {
|
|||
mut(test)
|
||||
}
|
||||
ctx := context.Background()
|
||||
k8sClient.Create(ctx, &test.secret)
|
||||
k8sClient.Create(ctx, &test.service)
|
||||
k8sClient.Create(ctx, &test.crd)
|
||||
k8sClient.Create(ctx, &test.crd2)
|
||||
err := k8sClient.Create(ctx, &test.secret)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = k8sClient.Create(ctx, &test.service)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = k8sClient.Create(ctx, test.crd)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = k8sClient.Create(ctx, test.crd2)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
test.assert()
|
||||
},
|
||||
|
||||
Entry("[namespace] Ignore non Target CRDs", ignoreNonTargetCRDs),
|
||||
Entry("[namespace] Patch target CRDs", PatchesCRD),
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
func makeUnstructuredCRD(plural, group string) unstructured.Unstructured {
|
||||
crd := apiextensions.CustomResourceDefinition{
|
||||
func deleteCRD(crd *apiextensions.CustomResourceDefinition) {
|
||||
err := k8sClient.Delete(context.Background(), crd, client.GracePeriodSeconds(0))
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
Fail("unable to delete crd " + crd.Name)
|
||||
return
|
||||
}
|
||||
Eventually(func() bool {
|
||||
err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: crd.Name,
|
||||
}, crd)
|
||||
if err == nil {
|
||||
// force delete by removing finalizers
|
||||
// note: we can not delete a CRD with an invalid caBundle field
|
||||
cpy := crd.DeepCopy()
|
||||
controllerutil.RemoveFinalizer(cpy, "customresourcecleanup.apiextensions.k8s.io")
|
||||
p := client.MergeFrom(crd)
|
||||
k8sClient.Patch(context.Background(), cpy, p)
|
||||
return false
|
||||
}
|
||||
return apierrors.IsNotFound(err)
|
||||
}).WithTimeout(time.Second * 5).WithPolling(time.Second).Should(BeTrue())
|
||||
}
|
||||
|
||||
func makeCRD(plural, group string) *apiextensions.CustomResourceDefinition {
|
||||
return &apiextensions.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: plural + "." + group,
|
||||
},
|
||||
|
@ -160,7 +168,7 @@ func makeUnstructuredCRD(plural, group string) unstructured.Unstructured {
|
|||
Webhook: &apiextensions.WebhookConversion{
|
||||
ConversionReviewVersions: []string{"v1"},
|
||||
ClientConfig: &apiextensions.WebhookClientConfig{
|
||||
CABundle: []byte("foobar"),
|
||||
CABundle: []byte(`Cg==`),
|
||||
Service: &apiextensions.ServiceReference{
|
||||
Name: "webhook",
|
||||
Namespace: "default",
|
||||
|
@ -170,14 +178,6 @@ func makeUnstructuredCRD(plural, group string) unstructured.Unstructured {
|
|||
},
|
||||
},
|
||||
}
|
||||
marshal, _ := json.Marshal(crd)
|
||||
unmarshal := make(map[string]interface{})
|
||||
json.Unmarshal(marshal, &unmarshal)
|
||||
u := unstructured.Unstructured{
|
||||
Object: unmarshal,
|
||||
}
|
||||
u.SetGroupVersionKind(schema.GroupVersionKind{Kind: crdKind, Version: "v1", Group: crdGroup})
|
||||
return u
|
||||
}
|
||||
|
||||
func makeSecret() corev1.Secret {
|
||||
|
@ -217,8 +217,8 @@ func makeDefaultTestcase() *testCase {
|
|||
assert: func() {
|
||||
// this is a noop by default
|
||||
},
|
||||
crd: makeUnstructuredCRD("secretstores", "test.io"),
|
||||
crd2: makeUnstructuredCRD("some-other", "test.io"),
|
||||
crd: makeCRD("secretstores", "test.io"),
|
||||
crd2: makeCRD("some-other", "test.io"),
|
||||
secret: makeSecret(),
|
||||
service: makeService(),
|
||||
}
|
||||
|
|
|
@ -22,19 +22,20 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
@ -49,31 +50,49 @@ const (
|
|||
caKeyName = "ca.key"
|
||||
certValidityDuration = 10 * 365 * 24 * time.Hour
|
||||
LookaheadInterval = 90 * 24 * time.Hour
|
||||
)
|
||||
|
||||
type WebhookType int
|
||||
|
||||
const (
|
||||
Validating WebhookType = iota
|
||||
Mutating
|
||||
CRDConversion
|
||||
errResNotReady = "resource not ready: %s"
|
||||
errSubsetsNotReady = "subsets not ready"
|
||||
errAddressesNotReady = "addresses not ready"
|
||||
)
|
||||
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
recorder record.EventRecorder
|
||||
SvcName string
|
||||
SvcNamespace string
|
||||
SecretName string
|
||||
SecretNamespace string
|
||||
CrdResources []string
|
||||
dnsName string
|
||||
CAName string
|
||||
CAOrganization string
|
||||
RestartOnSecretRefresh bool
|
||||
RequeueInterval time.Duration
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
recorder record.EventRecorder
|
||||
SvcName string
|
||||
SvcNamespace string
|
||||
SecretName string
|
||||
SecretNamespace string
|
||||
CrdResources []string
|
||||
dnsName string
|
||||
CAName string
|
||||
CAOrganization string
|
||||
RequeueInterval time.Duration
|
||||
|
||||
// the controller is ready when all crds are injected
|
||||
rdyMu *sync.Mutex
|
||||
readyStatusMap map[string]bool
|
||||
}
|
||||
|
||||
func New(k8sClient client.Client, scheme *runtime.Scheme, logger logr.Logger,
|
||||
interval time.Duration, svcName, svcNamespace, secretName, secretNamespace string, resources []string) *Reconciler {
|
||||
return &Reconciler{
|
||||
Client: k8sClient,
|
||||
Log: logger,
|
||||
Scheme: scheme,
|
||||
SvcName: svcName,
|
||||
SvcNamespace: svcNamespace,
|
||||
SecretName: secretName,
|
||||
SecretNamespace: secretNamespace,
|
||||
RequeueInterval: interval,
|
||||
CrdResources: resources,
|
||||
CAName: "external-secrets",
|
||||
CAOrganization: "external-secrets",
|
||||
rdyMu: &sync.Mutex{},
|
||||
readyStatusMap: map[string]bool{},
|
||||
}
|
||||
}
|
||||
|
||||
type CertInfo struct {
|
||||
|
@ -82,10 +101,6 @@ type CertInfo struct {
|
|||
KeyName string
|
||||
CAName string
|
||||
}
|
||||
type WebhookInfo struct {
|
||||
Name string
|
||||
Type WebhookType
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
|
@ -95,44 +110,62 @@ func contains(s []string, e string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := r.Log.WithValues("CustomResourceDefinition", req.NamespacedName)
|
||||
if contains(r.CrdResources, req.NamespacedName.Name) {
|
||||
err := r.updateCRD(ctx, req)
|
||||
if err != nil {
|
||||
log.Error(err, "failed to inject conversion webhook")
|
||||
r.rdyMu.Lock()
|
||||
r.readyStatusMap[req.NamespacedName.Name] = false
|
||||
r.rdyMu.Unlock()
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
r.rdyMu.Lock()
|
||||
r.readyStatusMap[req.NamespacedName.Name] = true
|
||||
r.rdyMu.Unlock()
|
||||
}
|
||||
return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) ConvertToWebhookInfo() []WebhookInfo {
|
||||
info := make([]WebhookInfo, len(r.CrdResources))
|
||||
for p, v := range r.CrdResources {
|
||||
r := WebhookInfo{
|
||||
Name: v,
|
||||
Type: CRDConversion,
|
||||
// ReadyCheck reviews if all webhook configs have been injected into the CRDs
|
||||
// and if the referenced webhook service is ready.
|
||||
func (r *Reconciler) ReadyCheck(_ *http.Request) error {
|
||||
for _, res := range r.CrdResources {
|
||||
r.rdyMu.Lock()
|
||||
rdy := r.readyStatusMap[res]
|
||||
r.rdyMu.Unlock()
|
||||
if !rdy {
|
||||
return fmt.Errorf(errResNotReady, res)
|
||||
}
|
||||
info[p] = r
|
||||
}
|
||||
return info
|
||||
var eps corev1.Endpoints
|
||||
err := r.Get(context.TODO(), types.NamespacedName{
|
||||
Name: r.SvcName,
|
||||
Namespace: r.SvcNamespace,
|
||||
}, &eps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(eps.Subsets) == 0 {
|
||||
return fmt.Errorf(errSubsetsNotReady)
|
||||
}
|
||||
if len(eps.Subsets[0].Addresses) == 0 {
|
||||
return fmt.Errorf(errAddressesNotReady)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
|
||||
crdGVK := schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}
|
||||
res := &unstructured.Unstructured{}
|
||||
res.SetGroupVersionKind(crdGVK)
|
||||
r.recorder = mgr.GetEventRecorderFor("custom-resource-definition")
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
WithOptions(opts).
|
||||
For(res).
|
||||
For(&apiext.CustomResourceDefinition{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
|
||||
crdGVK := schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}
|
||||
|
||||
secret := corev1.Secret{}
|
||||
secretName := types.NamespacedName{
|
||||
Name: r.SecretName,
|
||||
|
@ -142,16 +175,15 @@ func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedResource := &unstructured.Unstructured{}
|
||||
updatedResource.SetGroupVersionKind(crdGVK)
|
||||
if err := r.Get(ctx, req.NamespacedName, updatedResource); err != nil {
|
||||
var updatedResource apiext.CustomResourceDefinition
|
||||
if err := r.Get(ctx, req.NamespacedName, &updatedResource); err != nil {
|
||||
return err
|
||||
}
|
||||
svc := types.NamespacedName{
|
||||
Name: r.SvcName,
|
||||
Namespace: r.SvcNamespace,
|
||||
}
|
||||
if err := injectSvcToConversionWebhook(updatedResource, svc); err != nil {
|
||||
if err := injectService(&updatedResource, svc); err != nil {
|
||||
return err
|
||||
}
|
||||
r.dnsName = fmt.Sprintf("%v.%v.svc", r.SvcName, r.SvcNamespace)
|
||||
|
@ -164,45 +196,35 @@ func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := injectCertToConversionWebhook(updatedResource, artifacts.CertPEM); err != nil {
|
||||
if err := injectCert(&updatedResource, artifacts.CertPEM); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := r.Update(ctx, updatedResource); err != nil {
|
||||
if err := r.Update(ctx, &updatedResource); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func injectSvcToConversionWebhook(crd *unstructured.Unstructured, svc types.NamespacedName) error {
|
||||
_, found, err := unstructured.NestedMap(crd.Object, "spec", "conversion", "webhook", "clientConfig")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
return errors.New("`conversion.webhook.clientConfig` field not found in CustomResourceDefinition")
|
||||
}
|
||||
if err := unstructured.SetNestedField(crd.Object, svc.Name, "spec", "conversion", "webhook", "clientConfig", "service", "name"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := unstructured.SetNestedField(crd.Object, svc.Namespace, "spec", "conversion", "webhook", "clientConfig", "service", "namespace"); err != nil {
|
||||
return err
|
||||
func injectService(crd *apiext.CustomResourceDefinition, svc types.NamespacedName) error {
|
||||
if crd.Spec.Conversion == nil ||
|
||||
crd.Spec.Conversion.Webhook == nil ||
|
||||
crd.Spec.Conversion.Webhook.ClientConfig == nil ||
|
||||
crd.Spec.Conversion.Webhook.ClientConfig.Service == nil {
|
||||
return fmt.Errorf("unexpected crd conversion webhook config")
|
||||
}
|
||||
crd.Spec.Conversion.Webhook.ClientConfig.Service.Namespace = svc.Namespace
|
||||
crd.Spec.Conversion.Webhook.ClientConfig.Service.Name = svc.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
func injectCertToConversionWebhook(crd *unstructured.Unstructured, certPem []byte) error {
|
||||
_, found, err := unstructured.NestedMap(crd.Object, "spec", "conversion", "webhook", "clientConfig")
|
||||
if err != nil {
|
||||
return err
|
||||
func injectCert(crd *apiext.CustomResourceDefinition, certPem []byte) error {
|
||||
if crd.Spec.Conversion == nil ||
|
||||
crd.Spec.Conversion.Webhook == nil ||
|
||||
crd.Spec.Conversion.Webhook.ClientConfig == nil {
|
||||
return fmt.Errorf("unexpected crd conversion webhook config")
|
||||
}
|
||||
if !found {
|
||||
return errors.New("`conversion.webhook.clientConfig` field not found in CustomResourceDefinition")
|
||||
}
|
||||
if err := unstructured.SetNestedField(crd.Object, base64.StdEncoding.EncodeToString(certPem), "spec", "conversion", "webhook", "clientConfig", "caBundle"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
crd.Spec.Conversion.Webhook.ClientConfig.CABundle = certPem
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -289,18 +311,12 @@ func (r *Reconciler) refreshCertIfNeeded(secret *corev1.Secret) (bool, error) {
|
|||
if err := r.refreshCerts(true, secret); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if r.RestartOnSecretRefresh {
|
||||
os.Exit(0)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
if !r.validServerCert(secret.Data[caCertName], secret.Data[certName], secret.Data[keyName]) {
|
||||
if err := r.refreshCerts(false, secret); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if r.RestartOnSecretRefresh {
|
||||
os.Exit(0)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return true, nil
|
||||
|
@ -450,21 +466,23 @@ func (r *Reconciler) writeSecret(cert, key []byte, caArtifacts *KeyPairArtifacts
|
|||
return r.Update(context.Background(), secret)
|
||||
}
|
||||
|
||||
// CheckCerts verifies that certificates exist in a given fs location
|
||||
// and if they're valid.
|
||||
func CheckCerts(c CertInfo, dnsName string, at time.Time) error {
|
||||
certFile := c.CertDir + "/" + c.CertName
|
||||
certFile := filepath.Join(c.CertDir, c.CertName)
|
||||
_, err := os.Stat(certFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ca, err := os.ReadFile(c.CertDir + "/" + c.CAName)
|
||||
ca, err := os.ReadFile(filepath.Join(c.CertDir, c.CAName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cert, err := os.ReadFile(c.CertDir + "/" + c.CertName)
|
||||
cert, err := os.ReadFile(filepath.Join(c.CertDir, c.CertName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := os.ReadFile(c.CertDir + "/" + c.KeyName)
|
||||
key, err := os.ReadFile(filepath.Join(c.CertDir, c.KeyName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import (
|
|||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -26,15 +25,12 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
client "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
const (
|
||||
setupError = "Could not setup test"
|
||||
errorSearchingField = "Error when searching for field"
|
||||
failedCreateCaCerts = "could not create ca certificates:%v"
|
||||
failedCreateServerCerts = "could not create server certificates:%v"
|
||||
invalidCerts = "generated certificates are invalid:%v,%v"
|
||||
|
@ -92,21 +88,6 @@ func newCRD() apiextensionsv1.CustomResourceDefinition {
|
|||
},
|
||||
}
|
||||
}
|
||||
func TestConvertToWebhookInfo(t *testing.T) {
|
||||
rec := newReconciler()
|
||||
info := rec.ConvertToWebhookInfo()
|
||||
if len(info) != 3 {
|
||||
t.Errorf("Convert to WebhookInfo failed. Total resources:%d", len(info))
|
||||
}
|
||||
for _, v := range info {
|
||||
if v.Type != CRDConversion {
|
||||
t.Errorf("Convert to WebhookInfo failed. wrong type:%v", v.Type)
|
||||
}
|
||||
if v.Name != "one" && v.Name != "two" && v.Name != "three" {
|
||||
t.Errorf("Convert to WebhookInfo failed. wrong name:%v", v.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateCRD(t *testing.T) {
|
||||
rec := newReconciler()
|
||||
|
@ -123,51 +104,26 @@ func TestUpdateCRD(t *testing.T) {
|
|||
}
|
||||
err := rec.updateCRD(ctx, req)
|
||||
if err != nil {
|
||||
t.Errorf("Failed updating CRD:%v", err)
|
||||
t.Errorf("Failed updating CRD: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInjectSvcToConversionWebhook(t *testing.T) {
|
||||
svc := newService()
|
||||
crd := newCRD()
|
||||
crdunmarshalled := make(map[string]interface{})
|
||||
crdJSON, err := json.Marshal(crd)
|
||||
if err != nil {
|
||||
t.Fatal(setupError)
|
||||
}
|
||||
err = json.Unmarshal(crdJSON, &crdunmarshalled)
|
||||
if err != nil {
|
||||
t.Fatal(setupError)
|
||||
}
|
||||
u := unstructured.Unstructured{
|
||||
Object: crdunmarshalled,
|
||||
}
|
||||
name := types.NamespacedName{
|
||||
Name: svc.Name,
|
||||
Namespace: svc.Namespace,
|
||||
}
|
||||
err = injectSvcToConversionWebhook(&u, name)
|
||||
err := injectService(&crd, name)
|
||||
if err != nil {
|
||||
t.Errorf("Failed: error when injecting: %v", err)
|
||||
}
|
||||
val, found, err := unstructured.NestedString(u.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
|
||||
if err != nil {
|
||||
t.Error(errorSearchingField)
|
||||
}
|
||||
if !found {
|
||||
t.Error("fieldNotFound")
|
||||
}
|
||||
val := crd.Spec.Conversion.Webhook.ClientConfig.Service.Name
|
||||
if val != "foo" {
|
||||
t.Errorf("Wrong service name injected: %v", val)
|
||||
}
|
||||
|
||||
val, found, err = unstructured.NestedString(u.Object, "spec", "conversion", "webhook", "clientConfig", "service", "namespace")
|
||||
if err != nil {
|
||||
t.Error(errorSearchingField)
|
||||
}
|
||||
if !found {
|
||||
t.Error("fieldNotFound")
|
||||
}
|
||||
val = crd.Spec.Conversion.Webhook.ClientConfig.Service.Namespace
|
||||
if val != "default" {
|
||||
t.Errorf("Wrong service namespace injected: %v", val)
|
||||
}
|
||||
|
@ -176,31 +132,12 @@ func TestInjectSvcToConversionWebhook(t *testing.T) {
|
|||
func TestInjectCertToConversionWebhook(t *testing.T) {
|
||||
certPEM := []byte("foobar")
|
||||
crd := newCRD()
|
||||
crdunmarshalled := make(map[string]interface{})
|
||||
crdJSON, err := json.Marshal(crd)
|
||||
if err != nil {
|
||||
t.Fatal(setupError)
|
||||
}
|
||||
err = json.Unmarshal(crdJSON, &crdunmarshalled)
|
||||
if err != nil {
|
||||
t.Fatal(setupError)
|
||||
}
|
||||
u := unstructured.Unstructured{
|
||||
Object: crdunmarshalled,
|
||||
}
|
||||
err = injectCertToConversionWebhook(&u, certPEM)
|
||||
err := injectCert(&crd, certPEM)
|
||||
if err != nil {
|
||||
t.Errorf("Failed: error when injecting: %v", err)
|
||||
}
|
||||
val, found, err := unstructured.NestedString(u.Object, "spec", "conversion", "webhook", "clientConfig", "caBundle")
|
||||
if err != nil {
|
||||
t.Error(errorSearchingField)
|
||||
}
|
||||
if !found {
|
||||
t.Error("fieldNotFound")
|
||||
}
|
||||
if val != "Zm9vYmFy" {
|
||||
t.Errorf("Wrong certificate name injected: %v", val)
|
||||
if string(crd.Spec.Conversion.Webhook.ClientConfig.CABundle) != "foobar" {
|
||||
t.Errorf("Wrong certificate name injected: %v", string(crd.Spec.Conversion.Webhook.ClientConfig.CABundle))
|
||||
}
|
||||
}
|
||||
func TestPopulateSecret(t *testing.T) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -73,19 +74,11 @@ var _ = BeforeSuite(func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
err = (&Reconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: k8sManager.GetScheme(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("CustomResourceDefinition"),
|
||||
SvcName: "foo",
|
||||
SvcNamespace: "default",
|
||||
SecretName: "foo",
|
||||
SecretNamespace: "default",
|
||||
CrdResources: []string{"externalsecrets.test.io", "secretstores.test.io", "clustersecretstores.test.io"},
|
||||
CAName: "external-secrets",
|
||||
CAOrganization: "external-secrets",
|
||||
RestartOnSecretRefresh: false,
|
||||
}).SetupWithManager(k8sManager, controller.Options{})
|
||||
rec := New(k8sClient, k8sManager.GetScheme(), log, time.Second*1,
|
||||
"foo", "default", "foo", "default", []string{
|
||||
"secretstores.test.io",
|
||||
})
|
||||
rec.SetupWithManager(k8sManager, controller.Options{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
go func() {
|
||||
|
|
|
@ -36,11 +36,9 @@ import (
|
|||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
|
||||
// Loading registered providers.
|
||||
_ "github.com/external-secrets/external-secrets/pkg/provider/register"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -135,7 +133,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
|||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
storeProvider, err := schema.GetProvider(store)
|
||||
storeProvider, err := esv1beta1.GetProvider(store)
|
||||
if err != nil {
|
||||
log.Error(err, errStoreProvider)
|
||||
syncCallsError.With(syncCallsMetricLabels).Inc()
|
||||
|
@ -393,19 +391,19 @@ func (r *Reconciler) getStore(ctx context.Context, externalSecret *esv1beta1.Ext
|
|||
}
|
||||
|
||||
// getProviderSecretData returns the provider's secret data with the provided ExternalSecret.
|
||||
func (r *Reconciler) getProviderSecretData(ctx context.Context, providerClient provider.SecretsClient, externalSecret *esv1beta1.ExternalSecret) (map[string][]byte, error) {
|
||||
func (r *Reconciler) getProviderSecretData(ctx context.Context, providerClient esv1beta1.SecretsClient, externalSecret *esv1beta1.ExternalSecret) (map[string][]byte, error) {
|
||||
providerData := make(map[string][]byte)
|
||||
|
||||
for _, remoteRef := range externalSecret.Spec.DataFrom {
|
||||
var secretMap map[string][]byte
|
||||
var err error
|
||||
if len(remoteRef.Find.Tags) > 0 || remoteRef.Find.Name != nil {
|
||||
secretMap, err = providerClient.GetAllSecrets(ctx, remoteRef.Find)
|
||||
if remoteRef.Find != nil {
|
||||
secretMap, err = providerClient.GetAllSecrets(ctx, *remoteRef.Find)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errGetSecretKey, remoteRef.Extract.Key, externalSecret.Name, err)
|
||||
}
|
||||
} else if remoteRef.Extract.Key != "" {
|
||||
secretMap, err = providerClient.GetSecretMap(ctx, remoteRef.Extract)
|
||||
} else if remoteRef.Extract != nil {
|
||||
secretMap, err = providerClient.GetSecretMap(ctx, *remoteRef.Extract)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(errGetSecretKey, remoteRef.Extract.Key, externalSecret.Name, err)
|
||||
}
|
||||
|
|
|
@ -30,8 +30,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/testing/fake"
|
||||
)
|
||||
|
||||
|
@ -187,7 +185,9 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
}
|
||||
return true
|
||||
},
|
||||
checkExternalSecret: func(es *esv1beta1.ExternalSecret) {},
|
||||
checkExternalSecret: func(es *esv1beta1.ExternalSecret) {
|
||||
// noop by default
|
||||
},
|
||||
secretStore: &esv1beta1.SecretStore{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ExternalSecretStore,
|
||||
|
@ -540,7 +540,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
}
|
||||
tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Extract: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Extract: &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: "datamap",
|
||||
},
|
||||
},
|
||||
|
@ -684,7 +684,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
tc.externalSecret.Spec.Data = []esv1beta1.ExternalSecretData{}
|
||||
tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Extract: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Extract: &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: remoteKey,
|
||||
},
|
||||
},
|
||||
|
@ -726,7 +726,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
tc.externalSecret.Spec.Data = []esv1beta1.ExternalSecretData{}
|
||||
tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Extract: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Extract: &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: remoteKey,
|
||||
},
|
||||
},
|
||||
|
@ -791,7 +791,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
tc.externalSecret.Spec.Data = nil
|
||||
tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Extract: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Extract: &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: remoteKey,
|
||||
},
|
||||
},
|
||||
|
@ -813,7 +813,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
tc.externalSecret.Spec.Data = nil
|
||||
tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Find: esv1beta1.ExternalSecretFind{
|
||||
Find: &esv1beta1.ExternalSecretFind{
|
||||
Name: &esv1beta1.FindName{
|
||||
RegExp: "foobar",
|
||||
},
|
||||
|
@ -844,7 +844,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
|
||||
tc.externalSecret.Spec.DataFrom = []esv1beta1.ExternalSecretDataFromRemoteRef{
|
||||
{
|
||||
Extract: esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Extract: &esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: remoteKey,
|
||||
},
|
||||
},
|
||||
|
@ -925,7 +925,7 @@ var _ = Describe("ExternalSecret controller", func() {
|
|||
// a SecretSyncedError status condition must be set
|
||||
storeConstructErrCondition := func(tc *testCase) {
|
||||
fakeProvider.WithNew(func(context.Context, esv1beta1.GenericStore, client.Client,
|
||||
string) (provider.SecretsClient, error) {
|
||||
string) (esv1beta1.SecretsClient, error) {
|
||||
return nil, fmt.Errorf("artificial constructor error")
|
||||
})
|
||||
tc.checkCondition = func(es *esv1beta1.ExternalSecret) bool {
|
||||
|
@ -1392,7 +1392,7 @@ func externalSecretConditionShouldBe(name, ns string, ct esv1beta1.ExternalSecre
|
|||
|
||||
func init() {
|
||||
fakeProvider = fake.New()
|
||||
schema.ForceRegister(fakeProvider, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.ForceRegister(fakeProvider, &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Service: esv1beta1.AWSServiceSecretsManager,
|
||||
},
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -77,7 +76,7 @@ func reconcile(ctx context.Context, req ctrl.Request, ss esapi.GenericStore, cl
|
|||
// if it fails sets a condition and writes events.
|
||||
func validateStore(ctx context.Context, namespace string, store esapi.GenericStore,
|
||||
client client.Client, recorder record.EventRecorder) error {
|
||||
storeProvider, err := schema.GetProvider(store)
|
||||
storeProvider, err := esapi.GetProvider(store)
|
||||
if err != nil {
|
||||
cond := NewSecretStoreCondition(esapi.SecretStoreReady, v1.ConditionFalse, esapi.ReasonInvalidStore, errUnableGetProvider)
|
||||
SetExternalSecretCondition(store, *cond)
|
||||
|
|
100
pkg/controllers/webhookconfig/suite_test.go
Normal file
100
pkg/controllers/webhookconfig/suite_test.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
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 webhookconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var cancel context.CancelFunc
|
||||
var reconciler *Reconciler
|
||||
|
||||
const (
|
||||
ctrlSvcName = "foo"
|
||||
ctrlSvcNamespace = "default"
|
||||
ctrlSecretName = "foo"
|
||||
ctrlSecretNamespace = "default"
|
||||
)
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Controller Suite")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
log := zap.New(zap.WriteTo(GinkgoWriter))
|
||||
logf.SetLogger(log)
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "deploy", "crds")},
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = esapi.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
|
||||
Scheme: scheme.Scheme,
|
||||
MetricsBindAddress: "0", // avoid port collision when testing
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
reconciler = New(k8sClient, k8sManager.GetScheme(), ctrl.Log, ctrlSvcName, ctrlSvcNamespace, ctrlSecretName, ctrlSecretNamespace, time.Second)
|
||||
reconciler.SetupWithManager(k8sManager, controller.Options{})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
Expect(k8sManager.Start(ctx)).ToNot(HaveOccurred())
|
||||
}()
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
cancel() // stop manager
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
186
pkg/controllers/webhookconfig/webhookconfig.go
Normal file
186
pkg/controllers/webhookconfig/webhookconfig.go
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
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 webhookconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
admissionregistration "k8s.io/api/admissionregistration/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
)
|
||||
|
||||
type Reconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
recorder record.EventRecorder
|
||||
RequeueDuration time.Duration
|
||||
SvcName string
|
||||
SvcNamespace string
|
||||
SecretName string
|
||||
SecretNamespace string
|
||||
|
||||
rdyMu *sync.Mutex
|
||||
ready bool
|
||||
}
|
||||
|
||||
func New(k8sClient client.Client, scheme *runtime.Scheme,
|
||||
log logr.Logger, svcName, svcNamespace, secretName, secretNamespace string,
|
||||
requeueInterval time.Duration) *Reconciler {
|
||||
return &Reconciler{
|
||||
Client: k8sClient,
|
||||
Scheme: scheme,
|
||||
Log: log,
|
||||
RequeueDuration: requeueInterval,
|
||||
SvcName: svcName,
|
||||
SvcNamespace: svcNamespace,
|
||||
SecretName: secretName,
|
||||
SecretNamespace: secretNamespace,
|
||||
rdyMu: &sync.Mutex{},
|
||||
ready: false,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
wellKnownLabelKey = "external-secrets.io/component"
|
||||
wellKnownLabelValue = "webhook"
|
||||
|
||||
ReasonUpdateFailed = "UpdateFailed"
|
||||
errWebhookNotReady = "webhook not ready"
|
||||
errSubsetsNotReady = "subsets not ready"
|
||||
errAddressesNotReady = "addresses not ready"
|
||||
errCACertNotReady = "ca cert not yet ready"
|
||||
|
||||
caCertName = "ca.crt"
|
||||
)
|
||||
|
||||
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := r.Log.WithValues("Webhookconfig", req.NamespacedName)
|
||||
var cfg admissionregistration.ValidatingWebhookConfiguration
|
||||
err := r.Get(ctx, req.NamespacedName, &cfg)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return ctrl.Result{}, nil
|
||||
} else if err != nil {
|
||||
log.Error(err, "unable to get Webhookconfig")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if cfg.Labels[wellKnownLabelKey] != wellKnownLabelValue {
|
||||
log.Info("ignoring webhook due to missing labels", wellKnownLabelKey, wellKnownLabelValue)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
log.Info("updating webhook config")
|
||||
err = r.updateConfig(ctx, &cfg)
|
||||
if err != nil {
|
||||
log.Error(err, "could not update webhook config")
|
||||
r.recorder.Eventf(&cfg, v1.EventTypeWarning, ReasonUpdateFailed, err.Error())
|
||||
return ctrl.Result{
|
||||
RequeueAfter: time.Minute,
|
||||
}, err
|
||||
}
|
||||
log.Info("updated webhook config")
|
||||
|
||||
// right now we only have one single
|
||||
// webhook config we care about
|
||||
r.rdyMu.Lock()
|
||||
defer r.rdyMu.Unlock()
|
||||
r.ready = true
|
||||
return ctrl.Result{
|
||||
RequeueAfter: r.RequeueDuration,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
|
||||
r.recorder = mgr.GetEventRecorderFor("validating-webhook-configuration")
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
WithOptions(opts).
|
||||
For(&admissionregistration.ValidatingWebhookConfiguration{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *Reconciler) ReadyCheck(_ *http.Request) error {
|
||||
r.rdyMu.Lock()
|
||||
defer r.rdyMu.Unlock()
|
||||
if !r.ready {
|
||||
return fmt.Errorf(errWebhookNotReady)
|
||||
}
|
||||
var eps v1.Endpoints
|
||||
err := r.Get(context.TODO(), types.NamespacedName{
|
||||
Name: r.SvcName,
|
||||
Namespace: r.SvcNamespace,
|
||||
}, &eps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(eps.Subsets) == 0 {
|
||||
return fmt.Errorf(errSubsetsNotReady)
|
||||
}
|
||||
if len(eps.Subsets[0].Addresses) == 0 {
|
||||
return fmt.Errorf(errAddressesNotReady)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// reads the ca cert and updates the webhook config.
|
||||
func (r *Reconciler) updateConfig(ctx context.Context, cfg *admissionregistration.ValidatingWebhookConfiguration) error {
|
||||
secret := v1.Secret{}
|
||||
secretName := types.NamespacedName{
|
||||
Name: r.SecretName,
|
||||
Namespace: r.SecretNamespace,
|
||||
}
|
||||
err := r.Get(context.Background(), secretName, &secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
crt, ok := secret.Data[caCertName]
|
||||
if !ok {
|
||||
return fmt.Errorf(errCACertNotReady)
|
||||
}
|
||||
if err := r.inject(cfg, r.SvcName, r.SvcNamespace, crt); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Update(ctx, cfg)
|
||||
}
|
||||
|
||||
func (r *Reconciler) inject(cfg *admissionregistration.ValidatingWebhookConfiguration, svcName, svcNamespace string, certData []byte) error {
|
||||
r.Log.Info("injecting ca certificate and service names", "cacrt", base64.StdEncoding.EncodeToString(certData), "name", cfg.Name)
|
||||
for idx, w := range cfg.Webhooks {
|
||||
if !strings.HasSuffix(w.Name, "external-secrets.io") {
|
||||
r.Log.Info("skipping webhook", "name", cfg.Name, "webhook-name", w.Name)
|
||||
continue
|
||||
}
|
||||
// we just patch the relevant fields
|
||||
cfg.Webhooks[idx].ClientConfig.Service.Name = svcName
|
||||
cfg.Webhooks[idx].ClientConfig.Service.Namespace = svcNamespace
|
||||
cfg.Webhooks[idx].ClientConfig.CABundle = certData
|
||||
}
|
||||
return nil
|
||||
}
|
324
pkg/controllers/webhookconfig/webhookconfig_test.go
Normal file
324
pkg/controllers/webhookconfig/webhookconfig_test.go
Normal file
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
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 webhookconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
admissionregistration "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
const defaultCACert = `-----BEGIN CERTIFICATE-----
|
||||
MIIDRjCCAi6gAwIBAgIBADANBgkqhkiG9w0BAQsFADA2MRkwFwYDVQQKExBleHRl
|
||||
cm5hbC1zZWNyZXRzMRkwFwYDVQQDExBleHRlcm5hbC1zZWNyZXRzMB4XDTIyMDIx
|
||||
NzEwMDYxMFoXDTMyMDIxNTExMDYxMFowNjEZMBcGA1UEChMQZXh0ZXJuYWwtc2Vj
|
||||
cmV0czEZMBcGA1UEAxMQZXh0ZXJuYWwtc2VjcmV0czCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAKSINgqU2dBdX8JpPjRHWSdpxuoltGl6xXmQHOhbTXAt
|
||||
/STDu7oi6eiFgepQ2QHuWLGwZgbbYnEhtLvw4dUwPcLyv6WIdeiUSA4pdFxL7asc
|
||||
WV4tjiRkRTJVrixJTxXpry/CsPqXBlvnu1YGESkrLOYCmA2xnDH8voEBbwYvXXB9
|
||||
3g5rOJncSh/7g+H55ZFFyWrIPyDUnfwE3CREjZXpsagFhRYpkuRlXTnU6t0OTEEh
|
||||
qLHlZ+ebUzL8NaegEgEHD32PrQPXpls1yurIrsA+I6McWkXGykykYHVK+1a1pL1g
|
||||
e+PBkegtwtX+EmB2ux7PVVeB4TTYqzCKbnObW4mJLZkCAwEAAaNfMF0wDgYDVR0P
|
||||
AQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHgSu/Im2gyu4TU0
|
||||
AWrMSFbtoVokMBsGA1UdEQQUMBKCEGV4dGVybmFsLXNlY3JldHMwDQYJKoZIhvcN
|
||||
AQELBQADggEBAJU88jCcPsAHN8DKLu+QMCoKYbeftX4gXxyoijGSde2w2O8NPtMP
|
||||
awu4Y5x3LNTwyIIxXi78UD0RI53GbUgHvS+X9v6CC2IZMS65xqKR+EsjzEh7Ldbm
|
||||
vZoF4ZDnfb2s5SK6MeYf67BE7XWpGfbHmjt6h80xsYjL6ovcik+dlu/AixMyLslS
|
||||
tDbMybAR8kR0zdQLYcZq7XEX5QsOO8qBn5rTfD6MiYik8ZrP7FqUMHyVpHiBuNio
|
||||
krnSOvynvuA9mlf2F2727dMt2Ij9uER+9QnhWBQex1h8CwALmm2k9G5Gt+RjB8oe
|
||||
lNjvmHAXUfOE/cbD7EP++X17kWt41FjmePc=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
type testCase struct {
|
||||
vwc *admissionregistration.ValidatingWebhookConfiguration
|
||||
service *corev1.Service
|
||||
endpoints *corev1.Endpoints
|
||||
secret *corev1.Secret
|
||||
assert func()
|
||||
}
|
||||
|
||||
var _ = Describe("ValidatingWebhookConfig reconcile", Ordered, func() {
|
||||
var test *testCase
|
||||
|
||||
BeforeEach(func() {
|
||||
test = makeDefaultTestcase()
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
ctx := context.Background()
|
||||
k8sClient.Delete(ctx, test.vwc)
|
||||
k8sClient.Delete(ctx, test.secret)
|
||||
k8sClient.Delete(ctx, test.service)
|
||||
k8sClient.Delete(ctx, test.endpoints)
|
||||
})
|
||||
|
||||
// Should patch VWC
|
||||
PatchAndReady := func(tc *testCase) {
|
||||
tc.endpoints.Subsets = nil
|
||||
|
||||
// endpoints become ready in a moment
|
||||
go func() {
|
||||
<-time.After(time.Second * 4)
|
||||
eps := makeEndpoints()
|
||||
err := k8sClient.Update(context.Background(), eps)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}()
|
||||
tc.assert = func() {
|
||||
Eventually(func() bool {
|
||||
// the controller should become ready at some point!
|
||||
err := reconciler.ReadyCheck(nil)
|
||||
return err == nil
|
||||
}).
|
||||
WithTimeout(time.Second * 10).
|
||||
WithPolling(time.Second).
|
||||
Should(BeTrue())
|
||||
|
||||
Eventually(func() bool {
|
||||
var vwc admissionregistration.ValidatingWebhookConfiguration
|
||||
err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tc.vwc.Name,
|
||||
}, &vwc)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, wc := range vwc.Webhooks {
|
||||
if !bytes.Equal(wc.ClientConfig.CABundle, []byte(defaultCACert)) {
|
||||
return false
|
||||
}
|
||||
if wc.ClientConfig.Service == nil {
|
||||
return false
|
||||
}
|
||||
if wc.ClientConfig.Service.Name != ctrlSvcName {
|
||||
return false
|
||||
}
|
||||
if wc.ClientConfig.Service.Namespace != ctrlSvcNamespace {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}).
|
||||
WithTimeout(time.Second * 10).
|
||||
WithPolling(time.Second).
|
||||
Should(BeTrue())
|
||||
}
|
||||
}
|
||||
|
||||
IgnoreNoMatch := func(tc *testCase) {
|
||||
delete(tc.vwc.ObjectMeta.Labels, wellKnownLabelKey)
|
||||
tc.assert = func() {
|
||||
Consistently(func() bool {
|
||||
var vwc admissionregistration.ValidatingWebhookConfiguration
|
||||
|
||||
err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tc.vwc.Name,
|
||||
}, &vwc)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, wc := range vwc.Webhooks {
|
||||
if bytes.Equal(wc.ClientConfig.CABundle, []byte(defaultCACert)) {
|
||||
return false
|
||||
}
|
||||
if wc.ClientConfig.Service.Name == ctrlSvcName {
|
||||
return false
|
||||
}
|
||||
if wc.ClientConfig.Service.Namespace == ctrlSvcNamespace {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}).
|
||||
WithTimeout(time.Second * 10).
|
||||
WithPolling(time.Second).
|
||||
Should(BeTrue())
|
||||
}
|
||||
}
|
||||
|
||||
// Should patch and update VWC after requeue duration has passed
|
||||
PatchAndUpdate := func(tc *testCase) {
|
||||
foobar := "new value"
|
||||
// ca cert will change after some time
|
||||
go func() {
|
||||
<-time.After(time.Second * 4)
|
||||
sec := makeSecret()
|
||||
sec.Data[caCertName] = []byte(foobar)
|
||||
err := k8sClient.Update(context.Background(), sec)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}()
|
||||
tc.assert = func() {
|
||||
|
||||
Eventually(func() bool {
|
||||
var vwc admissionregistration.ValidatingWebhookConfiguration
|
||||
err := k8sClient.Get(context.Background(), types.NamespacedName{
|
||||
Name: tc.vwc.Name,
|
||||
}, &vwc)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, wc := range vwc.Webhooks {
|
||||
if !bytes.Equal(wc.ClientConfig.CABundle, []byte(foobar)) {
|
||||
return false
|
||||
}
|
||||
if wc.ClientConfig.Service == nil {
|
||||
return false
|
||||
}
|
||||
if wc.ClientConfig.Service.Name != ctrlSvcName {
|
||||
return false
|
||||
}
|
||||
if wc.ClientConfig.Service.Namespace != ctrlSvcNamespace {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}).
|
||||
WithTimeout(time.Second * 10).
|
||||
WithPolling(time.Second).
|
||||
Should(BeTrue())
|
||||
}
|
||||
}
|
||||
|
||||
DescribeTable("Controller Reconcile logic", func(muts ...func(tc *testCase)) {
|
||||
for _, mut := range muts {
|
||||
mut(test)
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
err := k8sClient.Create(ctx, test.vwc)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = k8sClient.Create(ctx, test.secret)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = k8sClient.Create(ctx, test.service)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = k8sClient.Create(ctx, test.endpoints)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
test.assert()
|
||||
},
|
||||
|
||||
Entry("should patch matching webhook configs", PatchAndReady),
|
||||
Entry("should update vwc with new ca cert after requeue duration", PatchAndUpdate),
|
||||
Entry("should ignore when vwc labels are missing", IgnoreNoMatch),
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
func makeValidatingWebhookConfig() *admissionregistration.ValidatingWebhookConfiguration {
|
||||
return &admissionregistration.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "name-shouldnt-matter",
|
||||
Labels: map[string]string{
|
||||
wellKnownLabelKey: wellKnownLabelValue,
|
||||
},
|
||||
},
|
||||
Webhooks: []admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "secretstores.external-secrets.io",
|
||||
SideEffects: (*admissionregistration.SideEffectClass)(pointer.StringPtr(string(admissionregistration.SideEffectClassNone))),
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
CABundle: []byte("Cg=="),
|
||||
Service: &admissionregistration.ServiceReference{
|
||||
Name: "noop",
|
||||
Namespace: "noop",
|
||||
Path: pointer.StringPtr("/validate-secretstore"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "clustersecretstores.external-secrets.io",
|
||||
SideEffects: (*admissionregistration.SideEffectClass)(pointer.StringPtr(string(admissionregistration.SideEffectClassNone))),
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
ClientConfig: admissionregistration.WebhookClientConfig{
|
||||
CABundle: []byte("Cg=="),
|
||||
Service: &admissionregistration.ServiceReference{
|
||||
Name: "noop",
|
||||
Namespace: "noop",
|
||||
Path: pointer.StringPtr("/validate-clustersecretstore"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeSecret() *corev1.Secret {
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ctrlSecretName,
|
||||
Namespace: ctrlSecretNamespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
caCertName: []byte(defaultCACert),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeService() *corev1.Service {
|
||||
return &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ctrlSvcName,
|
||||
Namespace: ctrlSvcNamespace,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Ports: []corev1.ServicePort{
|
||||
{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeEndpoints() *corev1.Endpoints {
|
||||
return &corev1.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ctrlSvcName,
|
||||
Namespace: ctrlSvcNamespace,
|
||||
},
|
||||
Subsets: []corev1.EndpointSubset{
|
||||
{
|
||||
Addresses: []corev1.EndpointAddress{
|
||||
{
|
||||
IP: "1.2.3.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func makeDefaultTestcase() *testCase {
|
||||
return &testCase{
|
||||
assert: func() {
|
||||
// this is a noop by default
|
||||
},
|
||||
vwc: makeValidatingWebhookConfig(),
|
||||
secret: makeSecret(),
|
||||
service: makeService(),
|
||||
endpoints: makeEndpoints(),
|
||||
}
|
||||
}
|
|
@ -24,8 +24,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -56,17 +54,21 @@ type akeylessVaultInterface interface {
|
|||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
Akeyless: &esv1beta1.AkeylessProvider{},
|
||||
})
|
||||
}
|
||||
|
||||
// NewClient constructs a new secrets client based on the provided store.
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
return newClient(ctx, store, kube, namespace)
|
||||
}
|
||||
|
||||
func newClient(_ context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newClient(_ context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
akl := &akeylessBase{
|
||||
kube: kube,
|
||||
store: store,
|
||||
|
|
|
@ -26,9 +26,7 @@ import (
|
|||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -165,7 +163,7 @@ func (kms *KeyManagementService) GetSecretMap(ctx context.Context, ref esv1beta1
|
|||
}
|
||||
|
||||
// NewClient constructs a new secrets client based on the provided store.
|
||||
func (kms *KeyManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (kms *KeyManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
alibabaSpec := storeSpec.Provider.Alibaba
|
||||
iStore := &Client{
|
||||
|
@ -196,8 +194,12 @@ func (kms *KeyManagementService) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (kms *KeyManagementService) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&KeyManagementService{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&KeyManagementService{}, &esv1beta1.SecretStoreProvider{
|
||||
Alibaba: &esv1beta1.AlibabaProvider{},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,15 +18,15 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/endpoints"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
awsauth "github.com/external-secrets/external-secrets/pkg/provider/aws/auth"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/aws/parameterstore"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
// Provider satisfies the provider interface.
|
||||
|
@ -35,14 +35,62 @@ type Provider struct{}
|
|||
const (
|
||||
errUnableCreateSession = "unable to create session: %w"
|
||||
errUnknownProviderService = "unknown AWS Provider Service: %s"
|
||||
errRegionNotFound = "region not found: %s"
|
||||
)
|
||||
|
||||
// NewClient constructs a new secrets client based on the provided store.
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
return newClient(ctx, store, kube, namespace, awsauth.DefaultSTSProvider)
|
||||
}
|
||||
|
||||
func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string, assumeRoler awsauth.STSProvider) (provider.SecretsClient, error) {
|
||||
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
prov, err := util.GetAWSProvider(store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = validateRegion(prov)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// case: static credentials
|
||||
if prov.Auth.SecretRef != nil {
|
||||
if err := utils.ValidateSecretSelector(store, prov.Auth.SecretRef.AccessKeyID); err != nil {
|
||||
return fmt.Errorf("invalid Auth.SecretRef.AccessKeyID: %w", err)
|
||||
}
|
||||
if err := utils.ValidateSecretSelector(store, prov.Auth.SecretRef.SecretAccessKey); err != nil {
|
||||
return fmt.Errorf("invalid Auth.SecretRef.SecretAccessKey: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// case: jwt credentials
|
||||
if prov.Auth.JWTAuth != nil && prov.Auth.JWTAuth.ServiceAccountRef != nil {
|
||||
if err := utils.ValidateServiceAccountSelector(store, *prov.Auth.JWTAuth.ServiceAccountRef); err != nil {
|
||||
return fmt.Errorf("invalid Auth.JWT.ServiceAccountRef: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRegion(prov *esv1beta1.AWSProvider) error {
|
||||
resolver := endpoints.DefaultResolver()
|
||||
partitions := resolver.(endpoints.EnumPartitions).Partitions()
|
||||
found := false
|
||||
for _, p := range partitions {
|
||||
for id := range p.Regions() {
|
||||
if id == prov.Region {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf(errRegionNotFound, prov.Region)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string, assumeRoler awsauth.STSProvider) (esv1beta1.SecretsClient, error) {
|
||||
prov, err := util.GetAWSProvider(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -63,7 +111,7 @@ func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Cl
|
|||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import (
|
|||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
|
@ -146,3 +148,198 @@ func TestProvider(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
const validRegion = "eu-central-1"
|
||||
|
||||
func TestValidateStore(t *testing.T) {
|
||||
type args struct {
|
||||
store esv1beta1.GenericStore
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "invalid region",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Region: "noop.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid region",
|
||||
args: args{
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Region: validRegion,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid static creds auth / AccessKeyID",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Region: validRegion,
|
||||
Auth: esv1beta1.AWSAuth{
|
||||
SecretRef: &esv1beta1.AWSAuthSecretRef{
|
||||
AccessKeyID: esmeta.SecretKeySelector{
|
||||
Name: "foobar",
|
||||
Namespace: pointer.StringPtr("unacceptable"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid static creds auth / SecretAccessKey",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Region: validRegion,
|
||||
Auth: esv1beta1.AWSAuth{
|
||||
SecretRef: &esv1beta1.AWSAuthSecretRef{
|
||||
SecretAccessKey: esmeta.SecretKeySelector{
|
||||
Name: "foobar",
|
||||
Namespace: pointer.StringPtr("unacceptable"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid static creds auth / SecretAccessKey missing namespace",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
store: &esv1beta1.ClusterSecretStore{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: esv1beta1.ClusterSecretStoreKind,
|
||||
},
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Region: validRegion,
|
||||
Auth: esv1beta1.AWSAuth{
|
||||
SecretRef: &esv1beta1.AWSAuthSecretRef{
|
||||
SecretAccessKey: esmeta.SecretKeySelector{
|
||||
Name: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid static creds auth / AccessKeyID missing namespace",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
store: &esv1beta1.ClusterSecretStore{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: esv1beta1.ClusterSecretStoreKind,
|
||||
},
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Region: validRegion,
|
||||
Auth: esv1beta1.AWSAuth{
|
||||
SecretRef: &esv1beta1.AWSAuthSecretRef{
|
||||
AccessKeyID: esmeta.SecretKeySelector{
|
||||
Name: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid jwt auth: missing sa selector namespace",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
store: &esv1beta1.ClusterSecretStore{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: esv1beta1.ClusterSecretStoreKind,
|
||||
},
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Region: validRegion,
|
||||
Auth: esv1beta1.AWSAuth{
|
||||
JWTAuth: &esv1beta1.AWSJWTAuth{
|
||||
ServiceAccountRef: &esmeta.ServiceAccountSelector{
|
||||
Name: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid jwt auth: not allowed sa selector namespace",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
store: &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
AWS: &esv1beta1.AWSProvider{
|
||||
Region: validRegion,
|
||||
Auth: esv1beta1.AWSAuth{
|
||||
JWTAuth: &esv1beta1.AWSJWTAuth{
|
||||
ServiceAccountRef: &esmeta.ServiceAccountSelector{
|
||||
Name: "foobar",
|
||||
Namespace: pointer.StringPtr("unacceptable"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := &Provider{}
|
||||
if err := p.ValidateStore(tt.args.store); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Provider.ValidateStore() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,10 @@ func (sm *Client) GetSecretValue(in *awssm.GetSecretValueInput) (*awssm.GetSecre
|
|||
return nil, fmt.Errorf("test case not found")
|
||||
}
|
||||
|
||||
func (sm *Client) ListSecrets(*awssm.ListSecretsInput) (*awssm.ListSecretsOutput, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (sm *Client) cacheKeyForInput(in *awssm.GetSecretValueInput) string {
|
||||
var secretID, versionID string
|
||||
if in.SecretId != nil {
|
||||
|
|
|
@ -30,8 +30,7 @@ import (
|
|||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
smmeta "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/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -52,6 +51,13 @@ const (
|
|||
errMissingClientIDSecret = "missing accessKeyID/secretAccessKey in store config"
|
||||
errFindSecret = "could not find secret %s/%s: %w"
|
||||
errFindDataKey = "no data for %q in secret '%s/%s'"
|
||||
|
||||
errInvalidStore = "invalid store"
|
||||
errInvalidStoreSpec = "invalid store spec"
|
||||
errInvalidStoreProv = "invalid store provider"
|
||||
errInvalidAzureProv = "invalid azure keyvault provider"
|
||||
errInvalidSecRefClientID = "invalid AuthSecretRef.ClientID: %w"
|
||||
errInvalidSecRefClientSecret = "invalid AuthSecretRef.ClientSecret: %w"
|
||||
)
|
||||
|
||||
// interface to keyvault.BaseClient.
|
||||
|
@ -71,17 +77,17 @@ type Azure struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&Azure{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&Azure{}, &esv1beta1.SecretStoreProvider{
|
||||
AzureKV: &esv1beta1.AzureKVProvider{},
|
||||
})
|
||||
}
|
||||
|
||||
// NewClient constructs a new secrets client based on the provided store.
|
||||
func (a *Azure) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (a *Azure) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
return newClient(ctx, store, kube, namespace)
|
||||
}
|
||||
|
||||
func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func newClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
provider, err := getProvider(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -115,6 +121,36 @@ func getProvider(store esv1beta1.GenericStore) (*esv1beta1.AzureKVProvider, erro
|
|||
return spc.Provider.AzureKV, nil
|
||||
}
|
||||
|
||||
func (a *Azure) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
if store == nil {
|
||||
return fmt.Errorf(errInvalidStore)
|
||||
}
|
||||
spc := store.GetSpec()
|
||||
if spc == nil {
|
||||
return fmt.Errorf(errInvalidStoreSpec)
|
||||
}
|
||||
if spc.Provider == nil {
|
||||
return fmt.Errorf(errInvalidStoreProv)
|
||||
}
|
||||
p := spc.Provider.AzureKV
|
||||
if p == nil {
|
||||
return fmt.Errorf(errInvalidAzureProv)
|
||||
}
|
||||
if p.AuthSecretRef != nil {
|
||||
if p.AuthSecretRef.ClientID != nil {
|
||||
if err := utils.ValidateSecretSelector(store, *p.AuthSecretRef.ClientID); err != nil {
|
||||
return fmt.Errorf(errInvalidSecRefClientID, err)
|
||||
}
|
||||
}
|
||||
if p.AuthSecretRef.ClientSecret != nil {
|
||||
if err := utils.ValidateSecretSelector(store, *p.AuthSecretRef.ClientSecret); err != nil {
|
||||
return fmt.Errorf(errInvalidSecRefClientSecret, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Empty GetAllSecrets.
|
||||
func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
// TO be implemented
|
||||
|
|
|
@ -30,7 +30,6 @@ import (
|
|||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
fake "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault/fake"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
utils "github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -99,7 +98,7 @@ func TestNewClientManagedIdentityNoNeedForCredentials(t *testing.T) {
|
|||
}}},
|
||||
}
|
||||
|
||||
provider, err := schema.GetProvider(&store)
|
||||
provider, err := esv1beta1.GetProvider(&store)
|
||||
tassert.Nil(t, err, "the return err should be nil")
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
secretClient, err := provider.NewClient(context.Background(), &store, k8sClient, namespace)
|
||||
|
@ -127,7 +126,7 @@ func TestNewClientNoCreds(t *testing.T) {
|
|||
TenantID: &tenantID,
|
||||
}}},
|
||||
}
|
||||
provider, err := schema.GetProvider(&store)
|
||||
provider, err := esv1beta1.GetProvider(&store)
|
||||
tassert.Nil(t, err, "the return err should be nil")
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
_, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
|
||||
|
@ -381,3 +380,65 @@ func makeValidRef() *esv1beta1.ExternalSecretDataRemoteRef {
|
|||
Version: "default",
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateStore(t *testing.T) {
|
||||
type args struct {
|
||||
auth esv1beta1.AzureKVAuth
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty auth",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty client id",
|
||||
wantErr: false,
|
||||
args: args{
|
||||
auth: esv1beta1.AzureKVAuth{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid client id",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
auth: esv1beta1.AzureKVAuth{
|
||||
ClientID: &v1.SecretKeySelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid client secret",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
auth: esv1beta1.AzureKVAuth{
|
||||
ClientSecret: &v1.SecretKeySelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &Azure{}
|
||||
store := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
AzureKV: &esv1beta1.AzureKVProvider{
|
||||
AuthSecretRef: &tt.args.auth,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := a.ValidateStore(store); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Azure.ValidateStore() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -35,7 +33,7 @@ type Provider struct {
|
|||
config *esv1beta1.FakeProvider
|
||||
}
|
||||
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
cfg, err := getProvider(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -99,8 +97,12 @@ func (p *Provider) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
Fake: &esv1beta1.FakeProvider{},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -30,8 +30,6 @@ import (
|
|||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -52,6 +50,13 @@ const (
|
|||
errUninitalizedGCPProvider = "provider GCP is not initialized"
|
||||
errClientGetSecretAccess = "unable to access Secret from SecretManager Client: %w"
|
||||
errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
|
||||
|
||||
errInvalidStore = "invalid store"
|
||||
errInvalidStoreSpec = "invalid store spec"
|
||||
errInvalidStoreProv = "invalid store provider"
|
||||
errInvalidGCPProv = "invalid gcp secrets manager provider"
|
||||
errInvalidAuthSecretRef = "invalid auth secret ref: %w"
|
||||
errInvalidWISARef = "invalid workload identity service account reference: %w"
|
||||
)
|
||||
|
||||
type GoogleSecretManagerClient interface {
|
||||
|
@ -132,7 +137,7 @@ func serviceAccountTokenSource(ctx context.Context, store esv1beta1.GenericStore
|
|||
}
|
||||
|
||||
// NewClient constructs a GCP Provider.
|
||||
func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
|
||||
return nil, fmt.Errorf(errGCPSMStore)
|
||||
|
@ -270,8 +275,36 @@ func (sm *ProviderGCP) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (sm *ProviderGCP) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
if store == nil {
|
||||
return fmt.Errorf(errInvalidStore)
|
||||
}
|
||||
spc := store.GetSpec()
|
||||
if spc == nil {
|
||||
return fmt.Errorf(errInvalidStoreSpec)
|
||||
}
|
||||
if spc.Provider == nil {
|
||||
return fmt.Errorf(errInvalidStoreProv)
|
||||
}
|
||||
p := spc.Provider.GCPSM
|
||||
if p == nil {
|
||||
return fmt.Errorf(errInvalidGCPProv)
|
||||
}
|
||||
if p.Auth.SecretRef != nil {
|
||||
if err := utils.ValidateSecretSelector(store, p.Auth.SecretRef.SecretAccessKey); err != nil {
|
||||
return fmt.Errorf(errInvalidAuthSecretRef, err)
|
||||
}
|
||||
}
|
||||
if p.Auth.WorkloadIdentity != nil {
|
||||
if err := utils.ValidateServiceAccountSelector(store, p.Auth.WorkloadIdentity.ServiceAccountRef); err != nil {
|
||||
return fmt.Errorf(errInvalidWISARef, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&ProviderGCP{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&ProviderGCP{}, &esv1beta1.SecretStoreProvider{
|
||||
GCPSM: &esv1beta1.GCPSMProvider{},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -21,8 +21,10 @@ import (
|
|||
"testing"
|
||||
|
||||
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
fakesm "github.com/external-secrets/external-secrets/pkg/provider/gcp/secretmanager/fake"
|
||||
)
|
||||
|
||||
|
@ -210,3 +212,65 @@ func ErrorContains(out error, want string) bool {
|
|||
}
|
||||
return strings.Contains(out.Error(), want)
|
||||
}
|
||||
|
||||
func TestValidateStore(t *testing.T) {
|
||||
type args struct {
|
||||
auth esv1beta1.GCPSMAuth
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty auth",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid secret ref",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
auth: esv1beta1.GCPSMAuth{
|
||||
SecretRef: &esv1beta1.GCPSMAuthSecretRef{
|
||||
SecretAccessKey: v1.SecretKeySelector{
|
||||
Name: "foo",
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid wi sa ref",
|
||||
wantErr: true,
|
||||
args: args{
|
||||
auth: esv1beta1.GCPSMAuth{
|
||||
WorkloadIdentity: &esv1beta1.GCPWorkloadIdentity{
|
||||
ServiceAccountRef: v1.ServiceAccountSelector{
|
||||
Name: "foo",
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sm := &ProviderGCP{}
|
||||
store := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
GCPSM: &esv1beta1.GCPSMProvider{
|
||||
Auth: tt.args.auth,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := sm.ValidateStore(store); (err != nil) != tt.wantErr {
|
||||
t.Errorf("ProviderGCP.ValidateStore() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ import (
|
|||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/e2e/framework/log"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -63,7 +61,7 @@ type gClient struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&Gitlab{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&Gitlab{}, &esv1beta1.SecretStoreProvider{
|
||||
Gitlab: &esv1beta1.GitlabProvider{},
|
||||
})
|
||||
}
|
||||
|
@ -108,7 +106,7 @@ func NewGitlabProvider() *Gitlab {
|
|||
}
|
||||
|
||||
// Method on Gitlab Provider to set up client with credentials and populate projectID.
|
||||
func (g *Gitlab) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (g *Gitlab) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Gitlab == nil {
|
||||
return nil, fmt.Errorf("no store type or wrong store type")
|
||||
|
@ -220,3 +218,7 @@ func (g *Gitlab) Close(ctx context.Context) error {
|
|||
func (g *Gitlab) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Gitlab) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ import (
|
|||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -323,7 +321,11 @@ func (ibm *providerIBM) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (ibm *providerIBM) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
ibmSpec := storeSpec.Provider.IBM
|
||||
|
||||
|
@ -376,7 +378,7 @@ func (ibm *providerIBM) NewClient(ctx context.Context, store esv1beta1.GenericSt
|
|||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&providerIBM{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&providerIBM{}, &esv1beta1.SecretStoreProvider{
|
||||
IBM: &esv1beta1.IBMProvider{},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ import (
|
|||
|
||||
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"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -51,7 +49,7 @@ type ProviderKubernetes struct {
|
|||
Client KClient
|
||||
}
|
||||
|
||||
var _ provider.SecretsClient = &ProviderKubernetes{}
|
||||
var _ esv1beta1.SecretsClient = &ProviderKubernetes{}
|
||||
|
||||
type BaseClient struct {
|
||||
kube kclient.Client
|
||||
|
@ -65,13 +63,13 @@ type BaseClient struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&ProviderKubernetes{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&ProviderKubernetes{}, &esv1beta1.SecretStoreProvider{
|
||||
Kubernetes: &esv1beta1.KubernetesProvider{},
|
||||
})
|
||||
}
|
||||
|
||||
// NewClient constructs a Kubernetes Provider.
|
||||
func (k *ProviderKubernetes) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (k *ProviderKubernetes) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Kubernetes == nil {
|
||||
return nil, fmt.Errorf("no store type or wrong store type")
|
||||
|
@ -233,3 +231,7 @@ func (k *BaseClient) fetchSecretKey(ctx context.Context, key esmeta.SecretKeySel
|
|||
func (k *ProviderKubernetes) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *ProviderKubernetes) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -27,9 +27,7 @@ import (
|
|||
|
||||
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"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -124,7 +122,7 @@ func (vms *VaultManagementService) GetSecretMap(ctx context.Context, ref esv1bet
|
|||
}
|
||||
|
||||
// NewClient constructs a new secrets client based on the provided store.
|
||||
func (vms *VaultManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (vms *VaultManagementService) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
oracleSpec := storeSpec.Provider.Oracle
|
||||
|
||||
|
@ -225,8 +223,12 @@ func (vms *VaultManagementService) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (vms *VaultManagementService) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&VaultManagementService{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&VaultManagementService{}, &esv1beta1.SecretStoreProvider{
|
||||
Oracle: &esv1beta1.OracleProvider{},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,16 +20,14 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
)
|
||||
|
||||
var _ provider.Provider = &Client{}
|
||||
var _ esv1beta1.Provider = &Client{}
|
||||
|
||||
// Client is a fake client for testing.
|
||||
type Client struct {
|
||||
NewFn func(context.Context, esv1beta1.GenericStore, client.Client,
|
||||
string) (provider.SecretsClient, error)
|
||||
string) (esv1beta1.SecretsClient, error)
|
||||
GetSecretFn func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error)
|
||||
GetSecretMapFn func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error)
|
||||
GetAllSecretsFn func(context.Context, esv1beta1.ExternalSecretFind) (map[string][]byte, error)
|
||||
|
@ -49,7 +47,7 @@ func New() *Client {
|
|||
},
|
||||
}
|
||||
|
||||
v.NewFn = func(context.Context, esv1beta1.GenericStore, client.Client, string) (provider.SecretsClient, error) {
|
||||
v.NewFn = func(context.Context, esv1beta1.GenericStore, client.Client, string) (esv1beta1.SecretsClient, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
|
@ -58,7 +56,7 @@ func New() *Client {
|
|||
|
||||
// RegisterAs registers the fake client in the schema.
|
||||
func (v *Client) RegisterAs(provider *esv1beta1.SecretStoreProvider) {
|
||||
schema.ForceRegister(v, provider)
|
||||
esv1beta1.ForceRegister(v, provider)
|
||||
}
|
||||
|
||||
// GetSecret implements the provider.Provider interface.
|
||||
|
@ -92,6 +90,10 @@ func (v *Client) Validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (v *Client) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithGetSecretMap wraps the secret data map returned by this fake provider.
|
||||
func (v *Client) WithGetSecretMap(secData map[string][]byte, err error) *Client {
|
||||
v.GetSecretMapFn = func(context.Context, esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
||||
|
@ -110,13 +112,13 @@ func (v *Client) WithGetAllSecrets(secData map[string][]byte, err error) *Client
|
|||
|
||||
// WithNew wraps the fake provider factory function.
|
||||
func (v *Client) WithNew(f func(context.Context, esv1beta1.GenericStore, client.Client,
|
||||
string) (provider.SecretsClient, error)) *Client {
|
||||
string) (esv1beta1.SecretsClient, error)) *Client {
|
||||
v.NewFn = f
|
||||
return v
|
||||
}
|
||||
|
||||
// NewClient returns a new fake provider.
|
||||
func (v *Client) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (v *Client) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
c, err := v.NewFn(ctx, store, kube, namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -37,13 +37,12 @@ import (
|
|||
|
||||
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"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
_ provider.Provider = &connector{}
|
||||
_ provider.SecretsClient = &client{}
|
||||
_ esv1beta1.Provider = &connector{}
|
||||
_ esv1beta1.SecretsClient = &client{}
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -79,6 +78,19 @@ const (
|
|||
|
||||
errUnknownCAProvider = "unknown caProvider type given"
|
||||
errCANamespace = "cannot read secret for CAProvider due to missing namespace on kind ClusterSecretStore"
|
||||
|
||||
errInvalidStore = "invalid store"
|
||||
errInvalidStoreSpec = "invalid store spec"
|
||||
errInvalidStoreProv = "invalid store provider"
|
||||
errInvalidVaultProv = "invalid vault provider"
|
||||
errInvalidAppRoleSec = "invalid Auth.AppRole.SecretRef: %w"
|
||||
errInvalidClientCert = "invalid Auth.Cert.ClientCert: %w"
|
||||
errInvalidCertSec = "invalid Auth.Cert.SecretRef: %w"
|
||||
errInvalidJwtSec = "invalid Auth.Jwt.SecretRef: %w"
|
||||
errInvalidKubeSA = "invalid Auth.Kubernetes.ServiceAccountRef: %w"
|
||||
errInvalidKubeSec = "invalid Auth.Kubernetes.SecretRef: %w"
|
||||
errInvalidLdapSec = "invalid Auth.Ldap.SecretRef: %w"
|
||||
errInvalidTokenRef = "invalid Auth.TokenSecretRef: %w"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
|
@ -101,7 +113,7 @@ type client struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&connector{
|
||||
esv1beta1.Register(&connector{
|
||||
newVaultClient: newVaultClient,
|
||||
}, &esv1beta1.SecretStoreProvider{
|
||||
Vault: &esv1beta1.VaultProvider{},
|
||||
|
@ -116,7 +128,7 @@ type connector struct {
|
|||
newVaultClient func(c *vault.Config) (Client, error)
|
||||
}
|
||||
|
||||
func (c *connector) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (c *connector) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Vault == nil {
|
||||
return nil, errors.New(errVaultStore)
|
||||
|
@ -158,6 +170,64 @@ func (c *connector) NewClient(ctx context.Context, store esv1beta1.GenericStore,
|
|||
return vStore, nil
|
||||
}
|
||||
|
||||
func (c *connector) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
if store == nil {
|
||||
return fmt.Errorf(errInvalidStore)
|
||||
}
|
||||
spc := store.GetSpec()
|
||||
if spc == nil {
|
||||
return fmt.Errorf(errInvalidStoreSpec)
|
||||
}
|
||||
if spc.Provider == nil {
|
||||
return fmt.Errorf(errInvalidStoreProv)
|
||||
}
|
||||
p := spc.Provider.Vault
|
||||
if p == nil {
|
||||
return fmt.Errorf(errInvalidVaultProv)
|
||||
}
|
||||
if p.Auth.AppRole != nil {
|
||||
if err := utils.ValidateSecretSelector(store, p.Auth.AppRole.SecretRef); err != nil {
|
||||
return fmt.Errorf(errInvalidAppRoleSec, err)
|
||||
}
|
||||
}
|
||||
if p.Auth.Cert != nil {
|
||||
if err := utils.ValidateSecretSelector(store, p.Auth.Cert.ClientCert); err != nil {
|
||||
return fmt.Errorf(errInvalidClientCert, err)
|
||||
}
|
||||
if err := utils.ValidateSecretSelector(store, p.Auth.Cert.SecretRef); err != nil {
|
||||
return fmt.Errorf(errInvalidCertSec, err)
|
||||
}
|
||||
}
|
||||
if p.Auth.Jwt != nil {
|
||||
if err := utils.ValidateSecretSelector(store, p.Auth.Jwt.SecretRef); err != nil {
|
||||
return fmt.Errorf(errInvalidJwtSec, err)
|
||||
}
|
||||
}
|
||||
if p.Auth.Kubernetes != nil {
|
||||
if p.Auth.Kubernetes.ServiceAccountRef != nil {
|
||||
if err := utils.ValidateServiceAccountSelector(store, *p.Auth.Kubernetes.ServiceAccountRef); err != nil {
|
||||
return fmt.Errorf(errInvalidKubeSA, err)
|
||||
}
|
||||
}
|
||||
if p.Auth.Kubernetes.SecretRef != nil {
|
||||
if err := utils.ValidateSecretSelector(store, *p.Auth.Kubernetes.SecretRef); err != nil {
|
||||
return fmt.Errorf(errInvalidKubeSec, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if p.Auth.Ldap != nil {
|
||||
if err := utils.ValidateSecretSelector(store, p.Auth.Ldap.SecretRef); err != nil {
|
||||
return fmt.Errorf(errInvalidLdapSec, err)
|
||||
}
|
||||
}
|
||||
if p.Auth.TokenSecretRef != nil {
|
||||
if err := utils.ValidateSecretSelector(store, *p.Auth.TokenSecretRef); err != nil {
|
||||
return fmt.Errorf(errInvalidTokenRef, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Empty GetAllSecrets.
|
||||
func (v *client) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
|
||||
// TO be implemented
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
vault "github.com/hashicorp/vault/api"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
|
@ -1070,3 +1071,142 @@ func TestGetSecretPath(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateStore(t *testing.T) {
|
||||
type args struct {
|
||||
auth esv1beta1.VaultAuth
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty auth",
|
||||
args: args{},
|
||||
},
|
||||
|
||||
{
|
||||
name: "invalid approle with namespace",
|
||||
args: args{
|
||||
auth: esv1beta1.VaultAuth{
|
||||
AppRole: &esv1beta1.VaultAppRole{
|
||||
SecretRef: esmeta.SecretKeySelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid clientcert",
|
||||
args: args{
|
||||
auth: esv1beta1.VaultAuth{
|
||||
Cert: &esv1beta1.VaultCertAuth{
|
||||
ClientCert: esmeta.SecretKeySelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid cert secret",
|
||||
args: args{
|
||||
auth: esv1beta1.VaultAuth{
|
||||
Cert: &esv1beta1.VaultCertAuth{
|
||||
SecretRef: esmeta.SecretKeySelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid jwt secret",
|
||||
args: args{
|
||||
auth: esv1beta1.VaultAuth{
|
||||
Jwt: &esv1beta1.VaultJwtAuth{
|
||||
SecretRef: esmeta.SecretKeySelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid kubernetes sa",
|
||||
args: args{
|
||||
auth: esv1beta1.VaultAuth{
|
||||
Kubernetes: &esv1beta1.VaultKubernetesAuth{
|
||||
ServiceAccountRef: &esmeta.ServiceAccountSelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid kubernetes secret",
|
||||
args: args{
|
||||
auth: esv1beta1.VaultAuth{
|
||||
Kubernetes: &esv1beta1.VaultKubernetesAuth{
|
||||
SecretRef: &esmeta.SecretKeySelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid ldap secret",
|
||||
args: args{
|
||||
auth: esv1beta1.VaultAuth{
|
||||
Ldap: &esv1beta1.VaultLdapAuth{
|
||||
SecretRef: esmeta.SecretKeySelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid token secret",
|
||||
args: args{
|
||||
auth: esv1beta1.VaultAuth{
|
||||
TokenSecretRef: &esmeta.SecretKeySelector{
|
||||
Namespace: pointer.StringPtr("invalid"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &connector{
|
||||
newVaultClient: nil,
|
||||
}
|
||||
store := &esv1beta1.SecretStore{
|
||||
Spec: esv1beta1.SecretStoreSpec{
|
||||
Provider: &esv1beta1.SecretStoreProvider{
|
||||
Vault: &esv1beta1.VaultProvider{
|
||||
Auth: tt.args.auth,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := c.ValidateStore(store); (err != nil) != tt.wantErr {
|
||||
t.Errorf("connector.ValidateStore() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,8 +34,6 @@ import (
|
|||
|
||||
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"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/template/v2"
|
||||
)
|
||||
|
||||
|
@ -51,12 +49,12 @@ type WebHook struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
schema.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
|
||||
Webhook: &esv1beta1.WebhookProvider{},
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
whClient := &WebHook{
|
||||
kube: kube,
|
||||
store: store,
|
||||
|
@ -74,6 +72,10 @@ func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore,
|
|||
return whClient, nil
|
||||
}
|
||||
|
||||
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getProvider(store esv1beta1.GenericStore) (*esv1beta1.WebhookProvider, error) {
|
||||
spc := store.GetSpec()
|
||||
if spc == nil || spc.Provider == nil || spc.Provider.Webhook == nil {
|
||||
|
|
|
@ -28,7 +28,6 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
|
@ -268,7 +267,7 @@ func runTestCase(tc testCase, t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testGetSecretMap(tc testCase, t *testing.T, client provider.SecretsClient) {
|
||||
func testGetSecretMap(tc testCase, t *testing.T, client esv1beta1.SecretsClient) {
|
||||
testRef := esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: tc.Args.Key,
|
||||
Version: tc.Args.Version,
|
||||
|
@ -293,7 +292,7 @@ func testGetSecretMap(tc testCase, t *testing.T, client provider.SecretsClient)
|
|||
}
|
||||
}
|
||||
|
||||
func testGetSecret(tc testCase, t *testing.T, client provider.SecretsClient) {
|
||||
func testGetSecret(tc testCase, t *testing.T, client esv1beta1.SecretsClient) {
|
||||
testRef := esv1beta1.ExternalSecretDataRemoteRef{
|
||||
Key: tc.Args.Key,
|
||||
Version: tc.Args.Version,
|
||||
|
|
|
@ -30,8 +30,6 @@ import (
|
|||
kclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/grpc"
|
||||
)
|
||||
|
@ -66,7 +64,7 @@ func newLockboxProvider(yandexCloudCreator client.YandexCloudCreator) *lockboxPr
|
|||
}
|
||||
|
||||
// NewClient constructs a Yandex Lockbox Provider.
|
||||
func (p *lockboxProvider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
|
||||
func (p *lockboxProvider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
|
||||
storeSpec := store.GetSpec()
|
||||
if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.YandexLockbox == nil {
|
||||
return nil, fmt.Errorf("received invalid Yandex Lockbox SecretStore resource")
|
||||
|
@ -218,6 +216,10 @@ func (p *lockboxProvider) cleanUpIamTokenMap() {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *lockboxProvider) ValidateStore(store esv1beta1.GenericStore) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// lockboxSecretsClient is a secrets client for Yandex Lockbox.
|
||||
type lockboxSecretsClient struct {
|
||||
lockboxClient client.LockboxClient
|
||||
|
@ -327,7 +329,7 @@ func init() {
|
|||
}
|
||||
}()
|
||||
|
||||
schema.Register(
|
||||
esv1beta1.Register(
|
||||
lockboxProvider,
|
||||
&esv1beta1.SecretStoreProvider{
|
||||
YandexLockbox: &esv1beta1.YandexLockboxProvider{},
|
||||
|
|
|
@ -34,7 +34,6 @@ import (
|
|||
|
||||
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/schema"
|
||||
"github.com/external-secrets/external-secrets/pkg/provider/yandex/lockbox/client/fake"
|
||||
)
|
||||
|
||||
|
@ -58,7 +57,7 @@ func TestNewClient(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
provider, err := schema.GetProvider(store)
|
||||
provider, err := esv1beta1.GetProvider(store)
|
||||
tassert.Nil(t, err)
|
||||
|
||||
k8sClient := clientfake.NewClientBuilder().Build()
|
||||
|
|
|
@ -21,6 +21,9 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
|
||||
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
|
||||
)
|
||||
|
||||
// MergeByteMap merges map of byte slices.
|
||||
|
@ -66,3 +69,31 @@ func ErrorContains(out error, want string) bool {
|
|||
}
|
||||
return strings.Contains(out.Error(), want)
|
||||
}
|
||||
|
||||
// ValidateSecretSelector just checks if the namespace field is present/absent
|
||||
// depending on the secret store type.
|
||||
// We MUST NOT check the name or key property here. It MAY be defaulted by the provider.
|
||||
func ValidateSecretSelector(store esv1beta1.GenericStore, ref esmeta.SecretKeySelector) error {
|
||||
clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
|
||||
if clusterScope && ref.Namespace == nil {
|
||||
return fmt.Errorf("cluster scope requires namespace")
|
||||
}
|
||||
if !clusterScope && ref.Namespace != nil {
|
||||
return fmt.Errorf("namespace not allowed with namespaced SecretStore")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateServiceAccountSelector just checks if the namespace field is present/absent
|
||||
// depending on the secret store type.
|
||||
// We MUST NOT check the name or key property here. It MAY be defaulted by the provider.
|
||||
func ValidateServiceAccountSelector(store esv1beta1.GenericStore, ref esmeta.ServiceAccountSelector) error {
|
||||
clusterScope := store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind
|
||||
if clusterScope && ref.Namespace == nil {
|
||||
return fmt.Errorf("cluster scope requires namespace")
|
||||
}
|
||||
if !clusterScope && ref.Namespace != nil {
|
||||
return fmt.Errorf("namespace not allowed with namespaced SecretStore")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue