Browse Source

feat: Oracle provider service account masquerade (#2817)

* feat: Oracle provider service account masquerade

Signed-off-by: anders-swanson <anders.swanson@oracle.com>
Anders Swanson 2 years ago
parent
commit
8dd934ceed

+ 5 - 0
apis/externalsecrets/v1alpha1/secretstore_oracle_types.go

@@ -47,6 +47,11 @@ type OracleProvider struct {
 	// and/or user data may be supplied for the use of workload identity and user principal.
 	// +optional
 	Auth *OracleAuth `json:"auth,omitempty"`
+
+	// ServiceAccountRef specified the service account
+	// that should be used when authenticating with WorkloadIdentity.
+	// +optional
+	ServiceAccountRef *esmeta.ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
 }
 
 type OracleAuth struct {

+ 5 - 0
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

@@ -996,6 +996,11 @@ func (in *OracleProvider) DeepCopyInto(out *OracleProvider) {
 		*out = new(OracleAuth)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.ServiceAccountRef != nil {
+		in, out := &in.ServiceAccountRef, &out.ServiceAccountRef
+		*out = new(metav1.ServiceAccountSelector)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleProvider.

+ 5 - 0
apis/externalsecrets/v1beta1/secretstore_oracle_types.go

@@ -46,6 +46,11 @@ type OracleProvider struct {
 	// If empty, use the instance principal, otherwise the user credentials specified in Auth.
 	// +optional
 	Auth *OracleAuth `json:"auth,omitempty"`
+
+	// ServiceAccountRef specified the service account
+	// that should be used when authenticating with WorkloadIdentity.
+	// +optional
+	ServiceAccountRef *esmeta.ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
 }
 
 type OracleAuth struct {

+ 5 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -1741,6 +1741,11 @@ func (in *OracleProvider) DeepCopyInto(out *OracleProvider) {
 		*out = new(OracleAuth)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.ServiceAccountRef != nil {
+		in, out := &in.ServiceAccountRef, &out.ServiceAccountRef
+		*out = new(metav1.ServiceAccountSelector)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OracleProvider.

+ 50 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -952,6 +952,31 @@ spec:
                       region:
                         description: Region is the region where vault is located.
                         type: string
+                      serviceAccountRef:
+                        description: ServiceAccountRef specified the service account
+                          that should be used when authenticating with WorkloadIdentity.
+                        properties:
+                          audiences:
+                            description: Audience specifies the `aud` claim for the
+                              service account token If the service account uses a
+                              well-known annotation for e.g. IRSA or GCP Workload
+                              Identity then this audiences will be appended to the
+                              list
+                            items:
+                              type: string
+                            type: array
+                          name:
+                            description: The name of the ServiceAccount resource being
+                              referred to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        required:
+                        - name
+                        type: object
                       vault:
                         description: Vault is the vault's OCID of the specific vault
                           where secret is located.
@@ -3032,6 +3057,31 @@ spec:
                       region:
                         description: Region is the region where vault is located.
                         type: string
+                      serviceAccountRef:
+                        description: ServiceAccountRef specified the service account
+                          that should be used when authenticating with WorkloadIdentity.
+                        properties:
+                          audiences:
+                            description: Audience specifies the `aud` claim for the
+                              service account token If the service account uses a
+                              well-known annotation for e.g. IRSA or GCP Workload
+                              Identity then this audiences will be appended to the
+                              list
+                            items:
+                              type: string
+                            type: array
+                          name:
+                            description: The name of the ServiceAccount resource being
+                              referred to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        required:
+                        - name
+                        type: object
                       vault:
                         description: Vault is the vault's OCID of the specific vault
                           where secret is located.

+ 50 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -952,6 +952,31 @@ spec:
                       region:
                         description: Region is the region where vault is located.
                         type: string
+                      serviceAccountRef:
+                        description: ServiceAccountRef specified the service account
+                          that should be used when authenticating with WorkloadIdentity.
+                        properties:
+                          audiences:
+                            description: Audience specifies the `aud` claim for the
+                              service account token If the service account uses a
+                              well-known annotation for e.g. IRSA or GCP Workload
+                              Identity then this audiences will be appended to the
+                              list
+                            items:
+                              type: string
+                            type: array
+                          name:
+                            description: The name of the ServiceAccount resource being
+                              referred to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        required:
+                        - name
+                        type: object
                       vault:
                         description: Vault is the vault's OCID of the specific vault
                           where secret is located.
@@ -3032,6 +3057,31 @@ spec:
                       region:
                         description: Region is the region where vault is located.
                         type: string
+                      serviceAccountRef:
+                        description: ServiceAccountRef specified the service account
+                          that should be used when authenticating with WorkloadIdentity.
+                        properties:
+                          audiences:
+                            description: Audience specifies the `aud` claim for the
+                              service account token If the service account uses a
+                              well-known annotation for e.g. IRSA or GCP Workload
+                              Identity then this audiences will be appended to the
+                              list
+                            items:
+                              type: string
+                            type: array
+                          name:
+                            description: The name of the ServiceAccount resource being
+                              referred to.
+                            type: string
+                          namespace:
+                            description: Namespace of the resource being referred
+                              to. Ignored if referent is not cluster-scoped. cluster-scoped
+                              defaults to the namespace of the referent.
+                            type: string
+                        required:
+                        - name
+                        type: object
                       vault:
                         description: Vault is the vault's OCID of the specific vault
                           where secret is located.

+ 68 - 0
deploy/crds/bundle.yaml

@@ -1232,6 +1232,23 @@ spec:
                         region:
                           description: Region is the region where vault is located.
                           type: string
+                        serviceAccountRef:
+                          description: ServiceAccountRef specified the service account that should be used when authenticating with WorkloadIdentity.
+                          properties:
+                            audiences:
+                              description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
+                              items:
+                                type: string
+                              type: array
+                            name:
+                              description: The name of the ServiceAccount resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          required:
+                            - name
+                          type: object
                         vault:
                           description: Vault is the vault's OCID of the specific vault where secret is located.
                           type: string
@@ -2753,6 +2770,23 @@ spec:
                         region:
                           description: Region is the region where vault is located.
                           type: string
+                        serviceAccountRef:
+                          description: ServiceAccountRef specified the service account that should be used when authenticating with WorkloadIdentity.
+                          properties:
+                            audiences:
+                              description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
+                              items:
+                                type: string
+                              type: array
+                            name:
+                              description: The name of the ServiceAccount resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          required:
+                            - name
+                          type: object
                         vault:
                           description: Vault is the vault's OCID of the specific vault where secret is located.
                           type: string
@@ -5109,6 +5143,23 @@ spec:
                         region:
                           description: Region is the region where vault is located.
                           type: string
+                        serviceAccountRef:
+                          description: ServiceAccountRef specified the service account that should be used when authenticating with WorkloadIdentity.
+                          properties:
+                            audiences:
+                              description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
+                              items:
+                                type: string
+                              type: array
+                            name:
+                              description: The name of the ServiceAccount resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          required:
+                            - name
+                          type: object
                         vault:
                           description: Vault is the vault's OCID of the specific vault where secret is located.
                           type: string
@@ -6630,6 +6681,23 @@ spec:
                         region:
                           description: Region is the region where vault is located.
                           type: string
+                        serviceAccountRef:
+                          description: ServiceAccountRef specified the service account that should be used when authenticating with WorkloadIdentity.
+                          properties:
+                            audiences:
+                              description: Audience specifies the `aud` claim for the service account token If the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity then this audiences will be appended to the list
+                              items:
+                                type: string
+                              type: array
+                            name:
+                              description: The name of the ServiceAccount resource being referred to.
+                              type: string
+                            namespace:
+                              description: Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults to the namespace of the referent.
+                              type: string
+                          required:
+                            - name
+                          type: object
                         vault:
                           description: Vault is the vault's OCID of the specific vault where secret is located.
                           type: string

+ 15 - 0
docs/api/spec.md

@@ -4571,6 +4571,21 @@ OracleAuth
 If empty, use the instance principal, otherwise the user credentials specified in Auth.</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>serviceAccountRef</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#ServiceAccountSelector">
+External Secrets meta/v1.ServiceAccountSelector
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>ServiceAccountRef specified the service account
+that should be used when authenticating with WorkloadIdentity.</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1beta1.OracleSecretRef">OracleSecretRef

+ 48 - 19
pkg/provider/oracle/oracle.go

@@ -17,6 +17,7 @@ import (
 	"context"
 	"encoding/base64"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"os"
 	"sync"
@@ -30,7 +31,9 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/kubernetes"
 	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+	ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
@@ -167,25 +170,7 @@ func (vms *VaultManagementService) NewClient(ctx context.Context, store esv1beta
 	)
 
 	if oracleSpec.PrincipalType == esv1beta1.WorkloadPrincipal {
-		defer vms.workloadIdentityMutex.Unlock()
-		vms.workloadIdentityMutex.Lock()
-		// OCI SDK requires specific environment variables for workload identity.
-		if err := os.Setenv(auth.ResourcePrincipalVersionEnvVar, auth.ResourcePrincipalVersion2_2); err != nil {
-			return nil, fmt.Errorf("unable to set OCI SDK environment variable %s: %w", auth.ResourcePrincipalVersionEnvVar, err)
-		}
-		if err := os.Setenv(auth.ResourcePrincipalRegionEnvVar, oracleSpec.Region); err != nil {
-			return nil, fmt.Errorf("unable to set OCI SDK environment variable %s: %w", auth.ResourcePrincipalRegionEnvVar, err)
-		}
-		configurationProvider, err = auth.OkeWorkloadIdentityConfigurationProvider()
-		if err := os.Unsetenv(auth.ResourcePrincipalVersionEnvVar); err != nil {
-			return nil, fmt.Errorf("unabled to unset OCI SDK environment variable %s: %w", auth.ResourcePrincipalVersionEnvVar, err)
-		}
-		if err := os.Unsetenv(auth.ResourcePrincipalRegionEnvVar); err != nil {
-			return nil, fmt.Errorf("unabled to unset OCI SDK environment variable %s: %w", auth.ResourcePrincipalRegionEnvVar, err)
-		}
-		if err != nil {
-			return nil, err
-		}
+		configurationProvider, err = vms.getWorkloadIdentityProvider(store, oracleSpec.ServiceAccountRef, oracleSpec.Region, namespace)
 	} else if oracleSpec.PrincipalType == esv1beta1.InstancePrincipal || oracleSpec.Auth == nil {
 		configurationProvider, err = auth.InstancePrincipalConfigurationProvider()
 	} else {
@@ -394,9 +379,53 @@ func (vms *VaultManagementService) ValidateStore(store esv1beta1.GenericStore) e
 		return err
 	}
 
+	if oracleSpec.ServiceAccountRef != nil {
+		if err := utils.ValidateReferentServiceAccountSelector(store, *oracleSpec.ServiceAccountRef); err != nil {
+			return fmt.Errorf("invalid ServiceAccountRef: %w", err)
+		}
+	}
+
 	return nil
 }
 
+func (vms *VaultManagementService) getWorkloadIdentityProvider(store esv1beta1.GenericStore, serviceAcccountRef *esmeta.ServiceAccountSelector, region, namespace string) (configurationProvider common.ConfigurationProvider, err error) {
+	defer func() {
+		if uerr := os.Unsetenv(auth.ResourcePrincipalVersionEnvVar); uerr != nil {
+			err = errors.Join(err, fmt.Errorf("unable to set OCI SDK environment variable %s: %w", auth.ResourcePrincipalRegionEnvVar, uerr))
+		}
+		if uerr := os.Unsetenv(auth.ResourcePrincipalRegionEnvVar); uerr != nil {
+			err = errors.Join(err, fmt.Errorf("unabled to unset OCI SDK environment variable %s: %w", auth.ResourcePrincipalVersionEnvVar, uerr))
+		}
+		vms.workloadIdentityMutex.Unlock()
+	}()
+	vms.workloadIdentityMutex.Lock()
+	// OCI SDK requires specific environment variables for workload identity.
+	if err := os.Setenv(auth.ResourcePrincipalVersionEnvVar, auth.ResourcePrincipalVersion2_2); err != nil {
+		return nil, fmt.Errorf("unable to set OCI SDK environment variable %s: %w", auth.ResourcePrincipalVersionEnvVar, err)
+	}
+	if err := os.Setenv(auth.ResourcePrincipalRegionEnvVar, region); err != nil {
+		return nil, fmt.Errorf("unable to set OCI SDK environment variable %s: %w", auth.ResourcePrincipalRegionEnvVar, err)
+	}
+	// If no service account is specified, use the pod service account to create the Workload Identity provider.
+	if serviceAcccountRef == nil {
+		return auth.OkeWorkloadIdentityConfigurationProvider()
+	}
+	// Ensure the service account ref is being used appropriately, so arbitrary tokens are not minted by the provider.
+	if err = utils.ValidateServiceAccountSelector(store, *serviceAcccountRef); err != nil {
+		return nil, fmt.Errorf("invalid ServiceAccountRef: %w", err)
+	}
+	cfg, err := ctrlcfg.GetConfig()
+	if err != nil {
+		return nil, err
+	}
+	clientset, err := kubernetes.NewForConfig(cfg)
+	if err != nil {
+		return nil, err
+	}
+	tokenProvider := NewTokenProvider(clientset, serviceAcccountRef, namespace)
+	return auth.OkeWorkloadIdentityConfigurationProviderWithServiceAccountTokenProvider(tokenProvider)
+}
+
 func init() {
 	esv1beta1.Register(&VaultManagementService{}, &esv1beta1.SecretStoreProvider{
 		Oracle: &esv1beta1.OracleProvider{},

+ 66 - 0
pkg/provider/oracle/serviceaccount.go

@@ -0,0 +1,66 @@
+/*
+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 oracle
+
+import (
+	"context"
+
+	"github.com/oracle/oci-go-sdk/v65/common/auth"
+	authv1 "k8s.io/api/authentication/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+// TokenProvider implements the ServiceAccountTokenProvider interface to create service account tokens for OCI authentication.
+type TokenProvider struct {
+	Name      string
+	Namespace string
+	Audiences []string
+	Clientset kubernetes.Interface
+}
+
+var _ auth.ServiceAccountTokenProvider = &TokenProvider{}
+
+// NewTokenProvider creates a new TokenProvider for a given service account.
+func NewTokenProvider(clientset kubernetes.Interface, serviceAccountRef *esmeta.ServiceAccountSelector, namespace string) *TokenProvider {
+	// "api" is the default OCI workload identity audience.
+	audiences := []string{"api"}
+	if len(serviceAccountRef.Audiences) > 0 {
+		audiences = append(audiences, serviceAccountRef.Audiences...)
+	}
+	if serviceAccountRef.Namespace != nil {
+		namespace = *serviceAccountRef.Namespace
+	}
+	return &TokenProvider{
+		Name:      serviceAccountRef.Name,
+		Namespace: namespace,
+		Audiences: audiences,
+		Clientset: clientset,
+	}
+}
+
+// ServiceAccountToken creates a new service account token for OCI authentication.
+func (t *TokenProvider) ServiceAccountToken() (string, error) {
+	tok, err := t.Clientset.CoreV1().ServiceAccounts(t.Namespace).CreateToken(context.Background(), t.Name, &authv1.TokenRequest{
+		Spec: authv1.TokenRequestSpec{
+			Audiences: t.Audiences,
+		},
+	}, metav1.CreateOptions{})
+	if err != nil {
+		return "", err
+	}
+	return tok.Status.Token, nil
+}