Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serviceAccount CredentialsSource #93

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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