Browse Source

Merge pull request #896 from burak-yuksel/feature/validate-kubernetes-provider

Validate for Kubernetes Provider
paul-the-alien[bot] 4 years ago
parent
commit
8527fe1d13

+ 2 - 0
docs/provider-kubernetes.md

@@ -4,6 +4,8 @@ External Secrets Operator allows to retrieve in-cluster secrets or from a remote
 
 
 It's possible to authenticate against the Kubernetes API using client certificates, a bearer token or a service account (not implemented yet). The operator enforces that exactly one authentication method is used.
 It's possible to authenticate against the Kubernetes API using client certificates, a bearer token or a service account (not implemented yet). The operator enforces that exactly one authentication method is used.
 
 
+**NOTE:** `SelfSubjectAccessReview` permission is required for the service account in order to validation work properly.
+
 ## Example
 ## Example
 
 
 ### In-cluster secrets using Client certificates
 ### In-cluster secrets using Client certificates

+ 30 - 1
pkg/provider/kubernetes/kubernetes.go

@@ -18,6 +18,7 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 
 
+	authv1 "k8s.io/api/authorization/v1"
 	corev1 "k8s.io/api/core/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/types"
@@ -44,9 +45,15 @@ type KClient interface {
 	Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Secret, error)
 	Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Secret, error)
 }
 }
 
 
+type RClient interface {
+	Create(ctx context.Context, SelfSubjectAccessReview *authv1.SelfSubjectAccessReview, opts metav1.CreateOptions) (*authv1.SelfSubjectAccessReview, error)
+}
+
 // ProviderKubernetes is a provider for Kubernetes.
 // ProviderKubernetes is a provider for Kubernetes.
 type ProviderKubernetes struct {
 type ProviderKubernetes struct {
-	Client KClient
+	Client       KClient
+	ReviewClient RClient
+	Namespace    string
 }
 }
 
 
 var _ esv1beta1.SecretsClient = &ProviderKubernetes{}
 var _ esv1beta1.SecretsClient = &ProviderKubernetes{}
@@ -104,6 +111,8 @@ func (k *ProviderKubernetes) NewClient(ctx context.Context, store esv1beta1.Gene
 	}
 	}
 
 
 	k.Client = kubeClientSet.CoreV1().Secrets(bStore.store.RemoteNamespace)
 	k.Client = kubeClientSet.CoreV1().Secrets(bStore.store.RemoteNamespace)
+	k.Namespace = bStore.store.RemoteNamespace
+	k.ReviewClient = kubeClientSet.AuthorizationV1().SelfSubjectAccessReviews()
 
 
 	return k, nil
 	return k, nil
 }
 }
@@ -229,6 +238,26 @@ func (k *BaseClient) fetchSecretKey(ctx context.Context, key esmeta.SecretKeySel
 }
 }
 
 
 func (k *ProviderKubernetes) Validate() error {
 func (k *ProviderKubernetes) Validate() error {
+	ctx := context.Background()
+
+	authReview, err := k.ReviewClient.Create(ctx, &authv1.SelfSubjectAccessReview{
+		Spec: authv1.SelfSubjectAccessReviewSpec{
+			ResourceAttributes: &authv1.ResourceAttributes{
+				Resource:  "secrets",
+				Namespace: k.Namespace,
+				Verb:      "get",
+			},
+		},
+	}, metav1.CreateOptions{})
+
+	if err != nil {
+		return fmt.Errorf("could not verify if client is valid: %w", err)
+	}
+
+	if !authReview.Status.Allowed {
+		return fmt.Errorf("client is not allowed to get secrets")
+	}
+
 	return nil
 	return nil
 }
 }
 
 

+ 47 - 2
pkg/provider/kubernetes/kubernetes_test.go

@@ -21,6 +21,7 @@ import (
 	"strings"
 	"strings"
 	"testing"
 	"testing"
 
 
+	authv1 "k8s.io/api/authorization/v1"
 	corev1 "k8s.io/api/core/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	fclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
 	fclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -32,6 +33,7 @@ import (
 const (
 const (
 	errTestFetchCredentialsSecret = "test could not fetch Credentials secret failed"
 	errTestFetchCredentialsSecret = "test could not fetch Credentials secret failed"
 	errTestAuthValue              = "test failed key didn't match expected value"
 	errTestAuthValue              = "test failed key didn't match expected value"
+	errSomethingWentWrong         = "Something went wrong"
 )
 )
 
 
 type fakeClient struct {
 type fakeClient struct {
@@ -42,11 +44,22 @@ func (fk fakeClient) Get(ctx context.Context, name string, opts metav1.GetOption
 	secret, ok := fk.secretMap[name]
 	secret, ok := fk.secretMap[name]
 
 
 	if !ok {
 	if !ok {
-		return nil, errors.New("Something went wrong")
+		return nil, errors.New(errSomethingWentWrong)
 	}
 	}
 	return &secret, nil
 	return &secret, nil
 }
 }
 
 
+type fakeReviewClient struct {
+	authReview *authv1.SelfSubjectAccessReview
+}
+
+func (fk fakeReviewClient) Create(ctx context.Context, selfSubjectAccessReview *authv1.SelfSubjectAccessReview, opts metav1.CreateOptions) (*authv1.SelfSubjectAccessReview, error) {
+	if fk.authReview == nil {
+		return nil, errors.New(errSomethingWentWrong)
+	}
+	return fk.authReview, nil
+}
+
 func TestKubernetesSecretManagerGetSecret(t *testing.T) {
 func TestKubernetesSecretManagerGetSecret(t *testing.T) {
 	expected := make(map[string][]byte)
 	expected := make(map[string][]byte)
 	value := "bar"
 	value := "bar"
@@ -70,7 +83,7 @@ func TestKubernetesSecretManagerGetSecret(t *testing.T) {
 	ref = esv1beta1.ExternalSecretDataRemoteRef{Key: "Key2", Property: "foo"}
 	ref = esv1beta1.ExternalSecretDataRemoteRef{Key: "Key2", Property: "foo"}
 	_, err := kp.GetSecret(ctx, ref)
 	_, err := kp.GetSecret(ctx, ref)
 
 
-	if err.Error() != "Something went wrong" {
+	if err.Error() != errSomethingWentWrong {
 		t.Error("test failed")
 		t.Error("test failed")
 	}
 	}
 
 
@@ -258,3 +271,35 @@ func ErrorContains(out error, want string) bool {
 	}
 	}
 	return strings.Contains(out.Error(), want)
 	return strings.Contains(out.Error(), want)
 }
 }
+
+func TestValidate(t *testing.T) {
+	authReview := authv1.SelfSubjectAccessReview{
+		Status: authv1.SubjectAccessReviewStatus{
+			Allowed: true,
+		},
+	}
+	fakeClient := fakeReviewClient{authReview: &authReview}
+	k := ProviderKubernetes{ReviewClient: fakeClient}
+	err := k.Validate()
+	if err != nil {
+		t.Errorf("Test Failed! %v", err)
+	}
+	authReview = authv1.SelfSubjectAccessReview{
+		Status: authv1.SubjectAccessReviewStatus{
+			Allowed: false,
+		},
+	}
+	fakeClient = fakeReviewClient{authReview: &authReview}
+	k = ProviderKubernetes{ReviewClient: fakeClient}
+	err = k.Validate()
+	if err.Error() != "client is not allowed to get secrets" {
+		t.Errorf("Test Failed! Wanted client is not allowed to get secrets got: %v", err)
+	}
+
+	fakeClient = fakeReviewClient{}
+	k = ProviderKubernetes{ReviewClient: fakeClient}
+	err = k.Validate()
+	if err.Error() != "could not verify if client is valid: Something went wrong" {
+		t.Errorf("Test Failed! Wanted could not verify if client is valid: Something went wrong got: %v", err)
+	}
+}