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 pod identity support for namespaces and per-resource scoped auth #3187

Merged
merged 14 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from 10 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
159 changes: 141 additions & 18 deletions docs/hugo/content/guide/authentication/credential-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ metadata:
namespace: my-namespace
stringData:
AZURE_SUBSCRIPTION_ID: "$AZURE_SUBSCRIPTION_ID"
AZURE_TENANT_ID: "$AZURE_TENANT_ID"
AZURE_CLIENT_ID: "$AZURE_CLIENT_ID"
AZURE_TENANT_ID: "$AZURE_TENANT_ID"
AZURE_CLIENT_ID: "$AZURE_CLIENT_ID"
AUTH_MODE: "workloadidentity"
EOF
```

Expand All @@ -160,8 +161,9 @@ metadata:
namespace: my-namespace
stringData:
AZURE_SUBSCRIPTION_ID: "$AZURE_SUBSCRIPTION_ID"
AZURE_TENANT_ID: "$AZURE_TENANT_ID"
AZURE_CLIENT_ID: "$AZURE_CLIENT_ID"
AZURE_TENANT_ID: "$AZURE_TENANT_ID"
AZURE_CLIENT_ID: "$AZURE_CLIENT_ID"
AUTH_MODE: "workloadidentity"
EOF
```

Expand Down Expand Up @@ -448,7 +450,27 @@ export IDENTITY_CLIENT_ID="$(az identity show -g ${IDENTITY_RESOURCE_GROUP} -n $
export IDENTITY_RESOURCE_ID="$(az identity show -g ${IDENTITY_RESOURCE_GROUP} -n ${IDENTITY_NAME} --query id -otsv)"
```

#### Manual Deploy
### Create the secret

{{< tabpane text=true left=true >}}
{{% tab header="**Scope**:" disabled=true /%}}
{{% tab header="Global" %}}

If installing ASO for the first time, you can pass these values via Helm arguments:
```bash
helm upgrade --install --devel aso2 aso2/azure-service-operator \
--create-namespace \
--namespace=azureserviceoperator-system \
--set azureSubscriptionID=$AZURE_SUBSCRIPTION_ID \
--set aadPodIdentity.enable=true \
--set aadPodIdentity.azureManagedIdentityResourceId=${IDENTITY_RESOURCE_ID} \
--set azureClientID=${IDENTITY_CLIENT_ID} \
--set crdPattern='resources.azure.com/*;containerservice.azure.com/*;keyvault.azure.com/*;managedidentity.azure.com/*;eventhub.azure.com/*'
```

See [CRD management]( {{< relref "crd-management" >}} ) for more details about `crdPattern`.
matthchr marked this conversation as resolved.
Show resolved Hide resolved

Otherwise, if deploying manually:

Deploy an `AzureIdentity`:
```bash
Expand Down Expand Up @@ -479,7 +501,7 @@ spec:
EOF
```

Deploy the `aso-controller-settings` secret, configured to use the identity:
Create or update the `aso-controller-settings` secret:
```bash
cat <<EOF | kubectl apply -f -
apiVersion: v1
Expand All @@ -494,20 +516,121 @@ stringData:
EOF
```

#### Helm Chart Deploy
**Note:** The `aso-controller-settings` secret contains more configuration than just the global credential.
If ASO was already installed on your cluster and you are updating the `aso-controller-settings` secret, ensure that
[other values]( {{< relref "aso-controller-settings-options" >}} ) in that secret are not being overwritten.

{{% /tab %}}
{{% tab header="Namespace" %}}

Deploy an `AzureIdentity`:
```bash
helm repo add aso2 https://raw.githubusercontent.com/Azure/azure-service-operator/main/v2/charts
helm repo update
cat <<EOF | kubectl apply -f -
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentity
metadata:
name: aso-identity
namespace: azureserviceoperator-system
spec:
type: 0
resourceID: ${IDENTITY_RESOURCE_ID}
clientID: ${IDENTITY_CLIENT_ID}
EOF
```

helm upgrade --install --devel aso2 aso2/azure-service-operator \
--create-namespace \
--namespace=azureserviceoperator-system \
--set azureSubscriptionID=$AZURE_SUBSCRIPTION_ID \
--set aadPodIdentity.enable=true \
--set aadPodIdentity.azureManagedIdentityResourceId=${IDENTITY_RESOURCE_ID} \
--set azureClientID=${IDENTITY_CLIENT_ID} \
--set crdPattern='resources.azure.com/*;containerservice.azure.com/*;keyvault.azure.com/*;managedidentity.azure.com/*;eventhub.azure.com/*'
Comment on lines -503 to -510
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this helm command present below this point - should it have been removed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have moved it above on line 455

Deploy an `AzureIdentityBinding` to bind this identity to the Azure Service Operator manager pod:
```bash
cat <<EOF | kubectl apply -f -
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentityBinding
metadata:
name: aso-identity-binding
namespace: azureserviceoperator-system
spec:
azureIdentity: aso-identity
selector: aso-manager-binding
EOF
```

See [CRD management]( {{< relref "crd-management" >}} ) for more details about `crdPattern`.
Create the `aso-credential` secret in your namespace:
```bash
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: aso-credential
namespace: my-namespace
stringData:
AZURE_SUBSCRIPTION_ID: "$AZURE_SUBSCRIPTION_ID"
AZURE_TENANT_ID: "$AZURE_TENANT_ID"
AZURE_CLIENT_ID: "$IDENTITY_CLIENT_ID"
AUTH_MODE: "podidentity"
EOF
```

{{% /tab %}}
{{% tab header="Resource" %}}

Deploy an `AzureIdentity`:
```bash
cat <<EOF | kubectl apply -f -
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentity
metadata:
name: aso-identity
namespace: azureserviceoperator-system
spec:
type: 0
resourceID: ${IDENTITY_RESOURCE_ID}
clientID: ${IDENTITY_CLIENT_ID}
EOF
```

Deploy an `AzureIdentityBinding` to bind this identity to the Azure Service Operator manager pod:
```bash
cat <<EOF | kubectl apply -f -
apiVersion: "aadpodidentity.k8s.io/v1"
kind: AzureIdentityBinding
metadata:
name: aso-identity-binding
namespace: azureserviceoperator-system
spec:
azureIdentity: aso-identity
selector: aso-manager-binding
EOF
```

Create a per-resource secret. We'll use `my-resource-secret`:
```bash
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: my-resource-secret
namespace: my-namespace
stringData:
AZURE_SUBSCRIPTION_ID: "$AZURE_SUBSCRIPTION_ID"
AZURE_TENANT_ID: "$AZURE_TENANT_ID"
AZURE_CLIENT_ID: "$IDENTITY_CLIENT_ID"
AUTH_MODE: "podidentity"
EOF
```

Create the ASO resource referring to `my-resource-secret`. We show a `ResourceGroup` here, but any ASO resource will work.

```bash
cat <<EOF | kubectl apply -f -
apiVersion: resources.azure.com/v1api20200601
kind: ResourceGroup
metadata:
name: aso-sample-rg
namespace: default
annotations:
serviceoperator.azure.com/credential-from: my-resource-secret
spec:
location: westcentralus
EOF
```

{{% /tab %}}
{{< /tabpane >}}
51 changes: 50 additions & 1 deletion v2/internal/identity/credential_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ const (
FederatedTokenFilePath = "/var/run/secrets/tokens/azure-identity"
)

type AuthModeOption string

const (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could these constants be made available to import like #3171?

podIdentity AuthModeOption = "podidentity"
workloadIdentity AuthModeOption = "workloadidentity"

// AuthMode enum is used to determine if we're using Pod Identity or Workload Identity
//authentication for namespace and per-resource scoped credentials
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//authentication for namespace and per-resource scoped credentials
// authentication for namespace and per-resource scoped credentials

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not actually updated?

AuthMode = "AUTH_MODE"
)

// Credential describes a credential used to connect to Azure
type Credential struct {
tokenCredential azcore.TokenCredential
Expand Down Expand Up @@ -258,7 +269,33 @@ func (c *credentialProvider) newCredentialFromSecret(secret *v1.Secret) (*Creden
}, nil
}

// Here we check for workload identity if client secret is not provided.
if value, hasAuthMode := secret.Data[AuthMode]; hasAuthMode {
authMode, err := authModeOrDefault(string(value))
if err != nil {
return nil, errors.Wrap(err, errors.Errorf("invalid identity auth mode for %q encountered", nsName).Error())

}

if authMode == podIdentity {
tokenCredential, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{
ClientOptions: azcore.ClientOptions{},
ID: azidentity.ClientID(clientID),
})

if err != nil {
return nil, errors.Wrap(err, errors.Errorf("invalid Managed Identity for %q encountered", nsName).Error())
}

return &Credential{
tokenCredential: tokenCredential,
subscriptionID: string(subscriptionID),
credentialFrom: nsName,
secretData: secret.Data,
}, nil
}
}

// Default to Workload Identity
tokenCredential, err := azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{
ClientID: string(clientID),
TenantID: string(tenantID),
Expand Down Expand Up @@ -300,3 +337,15 @@ func (c *credentialProvider) getSecret(ctx context.Context, namespace string, se
func getSecretNameFromAnnotation(credentialFrom string, resourceNamespace string) types.NamespacedName {
return types.NamespacedName{Namespace: resourceNamespace, Name: credentialFrom}
}

func authModeOrDefault(mode string) (AuthModeOption, error) {
if strings.EqualFold(mode, string(workloadIdentity)) || mode == "" {
return workloadIdentity, nil
}

if strings.EqualFold(mode, string(podIdentity)) {
return podIdentity, nil
}

return "", errors.Errorf("authorization mode %q not valid", mode)
}