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

Allow Azure Workload identity authentication #210

Merged
merged 9 commits into from
May 23, 2024
4 changes: 3 additions & 1 deletion apis/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ const (
IdentityTypeGoogleApplicationCredentials = "GoogleApplicationCredentials"

IdentityTypeAzureServicePrincipalCredentials = "AzureServicePrincipalCredentials"

IdentityTypeAzureWorkloadIdentityCredentials = "AzureWorkloadIdentityCredentials"
)

// Identity used to authenticate.
type Identity struct {
// Type of identity.
// +kubebuilder:validation:Enum=GoogleApplicationCredentials;AzureServicePrincipalCredentials
// +kubebuilder:validation:Enum=GoogleApplicationCredentials;AzureServicePrincipalCredentials;AzureWorkloadIdentityCredentials
Type IdentityType `json:"type"`

ProviderCredentials `json:",inline"`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: kubernetes.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: kubernetes-provider
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: cluster-config
key: kubeconfig
identity:
type: AzureWorkloadIdentityCredentials
source: Secret
secretRef:
name: azure-credentials
namespace: crossplane-system
key: credentials.json
---
apiVersion: v1
kind: Secret
metadata:
name: azure-credentials
namespace: crossplane-system
stringData:
# serverId hardcoded to AKS ID, see https://azure.github.io/kubelogin/concepts/aks.html#azure-kubernetes-service-aad-server
credentials.json: |
{
"tenantId": "<aad-tenant-id>",
"serverId": "6dae42f8-4368-4678-94ff-3960e28e3630",
"clientId": "<client-id>",
"federatedTokenFile": "/var/run/secrets/azure/tokens/azure-identity-token",
"authorityHost": "https://login.microsoftonline.com/"
}
46 changes: 30 additions & 16 deletions internal/clients/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@ import (
"github.com/pkg/errors"
"github.com/spf13/pflag"
"k8s.io/client-go/rest"

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

// Credentials Secret content is a json whose keys are below.
const (
CredentialsKeyClientID = "clientId"
CredentialsKeyClientSecret = "clientSecret"
CredentialsKeyTenantID = "tenantId"
CredentialsKeyClientCert = "clientCertificate"
CredentialsKeyClientCertPass = "clientCertificatePassword"
CredentialsKeyClientID = "clientId"
CredentialsKeyClientSecret = "clientSecret"
CredentialsKeyTenantID = "tenantId"
CredentialsKeyClientCert = "clientCertificate"
CredentialsKeyClientCertPass = "clientCertificatePassword"
CredentialsKeyFederatedTokenFile = "federatedTokenFile"
CredentialsKeyAuthorityHost = "authorityHost"
CredentialsKeyServerID = "serverId"
)

// WrapRESTConfig configures the supplied REST config to use OAuth2 bearer
// tokens fetched using the supplied Azure Credentials.
func WrapRESTConfig(_ context.Context, rc *rest.Config, credentials []byte, _ ...string) error {
func WrapRESTConfig(_ context.Context, rc *rest.Config, credentials []byte, identityType apisv1alpha1.IdentityType, _ ...string) error { // nolint:gocyclo // todo: refactor
m := map[string]string{}
if err := json.Unmarshal(credentials, &m); err != nil {
return err
Expand All @@ -44,21 +49,30 @@ func WrapRESTConfig(_ context.Context, rc *rest.Config, credentials []byte, _ ..
return errors.Wrap(err, "could not parse execProvider arguments in kubeconfig")
}
rc.ExecProvider = nil
// TODO: support other login methods like MSI, Workload Identity in the future
opts.LoginMethod = token.ServicePrincipalLogin
opts.ClientID = m[CredentialsKeyClientID]
opts.ClientSecret = m[CredentialsKeyClientSecret]
opts.TenantID = m[CredentialsKeyTenantID]
if cert, ok := m[CredentialsKeyClientCert]; ok {
opts.ClientCert = cert
if certpass, ok2 := m[CredentialsKeyClientCertPass]; ok2 {
opts.ClientCertPassword = certpass
switch identityType {
case apisv1alpha1.IdentityTypeAzureServicePrincipalCredentials:
opts.LoginMethod = token.ServicePrincipalLogin
opts.ClientID = m[CredentialsKeyClientID]
opts.ClientSecret = m[CredentialsKeyClientSecret]
opts.TenantID = m[CredentialsKeyTenantID]
if cert, ok := m[CredentialsKeyClientCert]; ok {
opts.ClientCert = cert
if certpass, ok2 := m[CredentialsKeyClientCertPass]; ok2 {
opts.ClientCertPassword = certpass
}
}
case apisv1alpha1.IdentityTypeAzureWorkloadIdentityCredentials:
opts.LoginMethod = token.WorkloadIdentityLogin
opts.ClientID = m[CredentialsKeyClientID]
opts.TenantID = m[CredentialsKeyTenantID]
opts.ServerID = m[CredentialsKeyServerID]
opts.FederatedTokenFile = m[CredentialsKeyFederatedTokenFile]
opts.AuthorityHost = m[CredentialsKeyAuthorityHost]
}

p, err := token.NewTokenProvider(&opts)
if err != nil {
return errors.New("cannot build azure token provider")
return errors.Wrap(err, "cannot build azure token provider")
}

rc.Wrap(func(rt http.RoundTripper) http.RoundTripper {
Expand Down
4 changes: 2 additions & 2 deletions internal/clients/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func configForProvider(ctx context.Context, local client.Client, providerConfigN
return nil, errors.Wrap(err, errInjectGoogleCredentials)
}
}
case v1alpha1.IdentityTypeAzureServicePrincipalCredentials:
case v1alpha1.IdentityTypeAzureServicePrincipalCredentials, v1alpha1.IdentityTypeAzureWorkloadIdentityCredentials:
switch id.Source { //nolint:exhaustive
case xpv1.CredentialsSourceInjectedIdentity:
return nil, errors.Errorf("%s is not supported as identity source for identity type %s",
Expand All @@ -116,7 +116,7 @@ func configForProvider(ctx context.Context, local client.Client, providerConfigN
return nil, errors.Wrap(err, errExtractAzureCredentials)
}

if err := azure.WrapRESTConfig(ctx, rc, creds); err != nil {
if err := azure.WrapRESTConfig(ctx, rc, creds, id.Type); err != nil {
return nil, errors.Wrap(err, errInjectAzureCredentials)
}
}
Expand Down
1 change: 1 addition & 0 deletions package/crds/kubernetes.crossplane.io_providerconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ spec:
enum:
- GoogleApplicationCredentials
- AzureServicePrincipalCredentials
- AzureWorkloadIdentityCredentials
type: string
required:
- source
Expand Down
Loading