Skip to content

Commit

Permalink
Add serviceAccount CredentialsSource
Browse files Browse the repository at this point in the history
Signed-off-by: Barrera, Angel <[email protected]>
  • Loading branch information
angelbarrera92 committed Jan 21, 2023
1 parent 4fafb7e commit 227b880
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 7 deletions.
76 changes: 73 additions & 3 deletions apis/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,80 @@ type ProviderConfigSpec struct {
// ProviderCredentials required to authenticate.
type ProviderCredentials struct {
// Source of the provider credentials.
// +kubebuilder:validation:Enum=None;Secret;InjectedIdentity;Environment;Filesystem
Source xpv1.CredentialsSource `json:"source"`
// +kubebuilder:validation:Enum=None;Secret;ServiceAccount;InjectedIdentity;Environment;Filesystem
Source CredentialsSource `json:"source"`

xpv1.CommonCredentialSelectors `json:",inline"`
KubernetesCredentialSelectors `json:",inline"`
}

// A CredentialsSource is a source from which provider credentials may be
// acquired.
type CredentialsSource string

const (
// CredentialsSourceNone indicates that a provider does not require
// credentials.
CredentialsSourceNone CredentialsSource = "None"

// CredentialsSourceSecret indicates that a provider should acquire
// credentials from a secret.
CredentialsSourceSecret CredentialsSource = "Secret"

// CredentialsSourceServiceAccount indicates that a provider should acquire
// credentials from a serviceaccount token.
CredentialsSourceServiceAccount CredentialsSource = "ServiceAccount"

// CredentialsSourceInjectedIdentity indicates that a provider should use
// credentials via its (pod's) identity; i.e. via IRSA for AWS,
// Workload Identity for GCP, Pod Identity for Azure, or in-cluster
// authentication for the Kubernetes API.
CredentialsSourceInjectedIdentity CredentialsSource = "InjectedIdentity"

// CredentialsSourceEnvironment indicates that a provider should acquire
// credentials from an environment variable.
CredentialsSourceEnvironment CredentialsSource = "Environment"

// CredentialsSourceFilesystem indicates that a provider should acquire
// credentials from the filesystem.
CredentialsSourceFilesystem CredentialsSource = "Filesystem"
)

// KubernetesCredentialSelectors provides common selectors for extracting
// credentials.
type KubernetesCredentialSelectors struct {
// Fs is a reference to a filesystem location that contains credentials that
// must be used to connect to the provider.
// +optional
Fs *xpv1.FsSelector `json:"fs,omitempty"`

// Env is a reference to an environment variable that contains credentials
// that must be used to connect to the provider.
// +optional
Env *xpv1.EnvSelector `json:"env,omitempty"`

// A SecretRef is a reference to a secret key that contains the credentials
// that must be used to connect to the provider.
// +optional
SecretRef *xpv1.SecretKeySelector `json:"secretRef,omitempty"`

// A ServiceAccountRef is a reference to a serviceaccount that contains the grants
// that must be used to connect to the provider.
// +optional
ServiceAccountRef *ServiceAccountSelector `json:"serviceAccountRef,omitempty"`
}

// A ServiceAccountSelector is a reference to a serviceaccount in an arbitrary namespace.
type ServiceAccountSelector struct {
ServiceAccountReference `json:",inline"`
}

// A ServiceAccountReference is a reference to a serviceaccount in an arbitrary namespace.
type ServiceAccountReference struct {
// Name of the serviceaccount.
Name string `json:"name"`

// Namespace of the serviceaccount.
Namespace string `json:"namespace"`
}

// IdentityType used to authenticate to the Kubernetes API.
Expand Down
69 changes: 68 additions & 1 deletion apis/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 33 additions & 3 deletions internal/controller/object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"

"k8s.io/apimachinery/pkg/api/equality"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand All @@ -39,6 +40,8 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
"github.com/crossplane/crossplane-runtime/pkg/resource"

internalresource "github.com/crossplane-contrib/provider-kubernetes/internal/resource"

"github.com/crossplane-contrib/provider-kubernetes/apis/object/v1alpha1"
apisv1alpha1 "github.com/crossplane-contrib/provider-kubernetes/apis/v1alpha1"
"github.com/crossplane-contrib/provider-kubernetes/internal/clients"
Expand Down Expand Up @@ -141,13 +144,32 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
var err error

switch cd := pc.Spec.Credentials; cd.Source { //nolint:exhaustive
case xpv1.CredentialsSourceInjectedIdentity:
case apisv1alpha1.CredentialsSourceInjectedIdentity:
rc, err = rest.InClusterConfig()
if err != nil {
return nil, errors.Wrap(err, errFailedToCreateRestConfig)
}
case apisv1alpha1.CredentialsSourceServiceAccount:
token, err := internalresource.ExtractServiceAccount(ctx, c.kube, cd.KubernetesCredentialSelectors)
if err != nil {
return nil, errors.Wrap(err, errGetCreds)
}
rc, err = rest.InClusterConfig()
if err != nil {
return nil, errors.Wrap(err, errFailedToCreateRestConfig)
}
rc.BearerToken = string(token)
rc.BearerTokenFile = ""
default:
kc, err := c.kcfgExtractorFn(ctx, cd.Source, c.kube, cd.CommonCredentialSelectors)
// Needed to use upstream methods for extracting credentials.
ccs := xpv1.CommonCredentialSelectors{
Fs: cd.KubernetesCredentialSelectors.Fs,
Env: cd.KubernetesCredentialSelectors.Env,
SecretRef: cd.KubernetesCredentialSelectors.SecretRef,
}
src := xpv1.CredentialsSource(cd.Source)

kc, err := c.kcfgExtractorFn(ctx, src, c.kube, ccs)
if err != nil {
return nil, errors.Wrap(err, errGetCreds)
}
Expand All @@ -161,7 +183,15 @@ func (c *connector) Connect(ctx context.Context, mg resource.Managed) (managed.E
// time of writing there's only one valid value (Google App Creds), and
// that value is required.
if id := pc.Spec.Identity; id != nil {
creds, err := c.gcpExtractorFn(ctx, id.Source, c.kube, id.CommonCredentialSelectors)
// Needed to use upstream methods for extracting credentials.
ccs := xpv1.CommonCredentialSelectors{
Fs: id.ProviderCredentials.KubernetesCredentialSelectors.Fs,
Env: id.ProviderCredentials.KubernetesCredentialSelectors.Env,
SecretRef: id.ProviderCredentials.KubernetesCredentialSelectors.SecretRef,
}
src := xpv1.CredentialsSource(id.ProviderCredentials.Source)

creds, err := c.gcpExtractorFn(ctx, src, c.kube, ccs)
if err != nil {
return nil, errors.Wrap(err, errFailedToExtractGoogleCredentials)
}
Expand Down
50 changes: 50 additions & 0 deletions internal/resource/providerconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package resource

import (
"context"

"github.com/google/uuid"
"github.com/pkg/errors"
authenticationv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"

apisv1alpha1 "github.com/crossplane-contrib/provider-kubernetes/apis/v1alpha1"
)

const (
errExtractServiceAccount = "cannot extract from service account when none specified"
errGetServiceAccount = "cannot get service account"
errTokenRequest = "cannot create token request"
)

// ExtractServiceAccount extracts credentials from a Kubernetes service account.
func ExtractServiceAccount(ctx context.Context, client client.Client, s apisv1alpha1.KubernetesCredentialSelectors) ([]byte, error) {
if s.ServiceAccountRef == nil {
return nil, errors.New(errExtractServiceAccount)
}
sa := &corev1.ServiceAccount{}
if err := client.Get(ctx, types.NamespacedName{Namespace: s.ServiceAccountRef.Namespace, Name: s.ServiceAccountRef.Name}, sa); err != nil {
return nil, errors.Wrap(err, errGetServiceAccount)
}
// Create a TokenRequest for the service account.
// The name must be the same as the service account name + "-token-<random suffix>".
tr := &authenticationv1.TokenRequest{
ObjectMeta: metav1.ObjectMeta{
Namespace: sa.Namespace,
Name: sa.Name + "-token-" + uuid.New().String()[0:5],
},
Spec: authenticationv1.TokenRequestSpec{
Audiences: []string{"https://kubernetes.default.svc"},
ExpirationSeconds: pointer.Int64Ptr(3600), // 1 hour. TODO: make this configurable.
},
}
if err := client.Create(ctx, tr); err != nil {
return nil, errors.Wrap(err, errTokenRequest)
}
// Return the token.
return []byte(tr.Status.Token), nil
}
32 changes: 32 additions & 0 deletions package/crds/kubernetes.crossplane.io_providerconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,27 @@ spec:
- name
- namespace
type: object
serviceAccountRef:
description: A ServiceAccountRef is a reference to a serviceaccount
that contains the grants that must be used to connect to the
provider.
properties:
name:
description: Name of the serviceaccount.
type: string
namespace:
description: Namespace of the serviceaccount.
type: string
required:
- name
- namespace
type: object
source:
description: Source of the provider credentials.
enum:
- None
- Secret
- ServiceAccount
- InjectedIdentity
- Environment
- Filesystem
Expand Down Expand Up @@ -144,11 +160,27 @@ spec:
- name
- namespace
type: object
serviceAccountRef:
description: A ServiceAccountRef is a reference to a serviceaccount
that contains the grants that must be used to connect to the
provider.
properties:
name:
description: Name of the serviceaccount.
type: string
namespace:
description: Namespace of the serviceaccount.
type: string
required:
- name
- namespace
type: object
source:
description: Source of the provider credentials.
enum:
- None
- Secret
- ServiceAccount
- InjectedIdentity
- Environment
- Filesystem
Expand Down

0 comments on commit 227b880

Please sign in to comment.