452 lines
12 KiB
Plaintext
452 lines
12 KiB
Plaintext
---
|
|
title: "Azure Deployment"
|
|
description: "Deploy Bifrost Enterprise on Azure AKS using Workload Identity Federation to GCP Artifact Registry"
|
|
icon: "microsoft"
|
|
---
|
|
|
|
Bifrost Enterprise images for Azure customers are distributed through GCP Artifact Registry, using Azure Workload Identity Federation for secure, credential-less authentication.
|
|
|
|
## Architecture
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
subgraph Azure[Azure Subscription]
|
|
subgraph AKS[AKS Cluster]
|
|
Pod[Bifrost Pod]
|
|
KSA[K8s ServiceAccount]
|
|
end
|
|
MI[Managed Identity]
|
|
end
|
|
|
|
subgraph GCP[GCP Project]
|
|
WIF[Workload Identity<br/>Federation Pool]
|
|
GSA[GCP Service Account]
|
|
AR[Artifact Registry<br/>Bifrost Images]
|
|
end
|
|
|
|
KSA -->|Federated| MI
|
|
MI -->|OIDC Token| WIF
|
|
WIF -->|Exchange| GSA
|
|
GSA -->|Pull Permission| AR
|
|
AR -->|Image| Pod
|
|
```
|
|
|
|
## How It Works
|
|
|
|
Azure Workload Identity Federation allows Azure Managed Identities to authenticate to GCP without exchanging credentials:
|
|
|
|
1. **AKS Pod** requests a token using its Kubernetes ServiceAccount
|
|
2. **Azure AD** issues an OIDC token for the Managed Identity
|
|
3. **GCP Workload Identity Federation** validates the Azure token
|
|
4. **GCP STS** exchanges it for a GCP access token
|
|
5. **Pod** uses the GCP token to pull images from Artifact Registry
|
|
|
|
## Prerequisites
|
|
|
|
- AKS cluster (v1.24+) with Workload Identity enabled
|
|
- Azure CLI configured with appropriate permissions
|
|
- `kubectl` configured for your AKS cluster
|
|
- Your Azure Tenant ID and Managed Identity Client ID provided to Bifrost team
|
|
|
|
<Note>
|
|
Contact the Bifrost team with your Azure Tenant ID and Managed Identity Client IDs to get access configured.
|
|
</Note>
|
|
|
|
## Step 1: Enable Workload Identity on AKS
|
|
|
|
If not already enabled, enable Workload Identity on your AKS cluster:
|
|
|
|
```bash
|
|
# For existing cluster
|
|
az aks update \
|
|
--resource-group YOUR_RESOURCE_GROUP \
|
|
--name YOUR_CLUSTER_NAME \
|
|
--enable-oidc-issuer \
|
|
--enable-workload-identity
|
|
|
|
# Get the OIDC issuer URL
|
|
az aks show \
|
|
--resource-group YOUR_RESOURCE_GROUP \
|
|
--name YOUR_CLUSTER_NAME \
|
|
--query "oidcIssuerProfile.issuerUrl" -o tsv
|
|
```
|
|
|
|
## Step 2: Create Azure Managed Identity
|
|
|
|
```bash
|
|
# Create Managed Identity
|
|
az identity create \
|
|
--name bifrost-pull-identity \
|
|
--resource-group YOUR_RESOURCE_GROUP \
|
|
--location YOUR_LOCATION
|
|
|
|
# Get the Client ID
|
|
CLIENT_ID=$(az identity show \
|
|
--name bifrost-pull-identity \
|
|
--resource-group YOUR_RESOURCE_GROUP \
|
|
--query clientId -o tsv)
|
|
|
|
echo "Client ID: $CLIENT_ID"
|
|
```
|
|
|
|
## Step 3: Create Federated Credential
|
|
|
|
Link the Kubernetes ServiceAccount to the Azure Managed Identity:
|
|
|
|
```bash
|
|
# Get AKS OIDC issuer
|
|
AKS_OIDC_ISSUER=$(az aks show \
|
|
--resource-group YOUR_RESOURCE_GROUP \
|
|
--name YOUR_CLUSTER_NAME \
|
|
--query "oidcIssuerProfile.issuerUrl" -o tsv)
|
|
|
|
# Create federated credential
|
|
az identity federated-credential create \
|
|
--name bifrost-federated-credential \
|
|
--identity-name bifrost-pull-identity \
|
|
--resource-group YOUR_RESOURCE_GROUP \
|
|
--issuer "$AKS_OIDC_ISSUER" \
|
|
--subject "system:serviceaccount:bifrost:bifrost-sa" \
|
|
--audience "api://AzureADTokenExchange"
|
|
```
|
|
|
|
## Step 4: Provide Details to Bifrost Team
|
|
|
|
Send the following information to the Bifrost team:
|
|
|
|
```bash
|
|
# Get Tenant ID
|
|
az account show --query tenantId -o tsv
|
|
|
|
# Get Client ID
|
|
az identity show \
|
|
--name bifrost-pull-identity \
|
|
--resource-group YOUR_RESOURCE_GROUP \
|
|
--query clientId -o tsv
|
|
```
|
|
|
|
The Bifrost team will configure GCP Workload Identity Federation to trust your Azure Managed Identity.
|
|
|
|
## Step 5: Store GCP Credential Configuration
|
|
|
|
After the Bifrost team configures access, they will provide a credential configuration. Store it as a ConfigMap:
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: gcp-credential-config
|
|
namespace: bifrost
|
|
data:
|
|
credential-config.json: |
|
|
{
|
|
"type": "external_account",
|
|
"audience": "//iam.googleapis.com/projects/BIFROST_PROJECT_NUMBER/locations/global/workloadIdentityPools/YOUR_HUB_SLUG-azure-pool/providers/YOUR_HUB_SLUG-azure-provider",
|
|
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
|
|
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/BIFROST_SA@BIFROST_PROJECT.iam.gserviceaccount.com:generateAccessToken",
|
|
"token_url": "https://sts.googleapis.com/v1/token",
|
|
"credential_source": {
|
|
"file": "/var/run/secrets/azure/tokens/azure-identity-token",
|
|
"format": {
|
|
"type": "text"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
<Warning>
|
|
The Bifrost team will provide the exact values for `BIFROST_PROJECT_NUMBER`, `YOUR_HUB_SLUG`, and `BIFROST_SA`.
|
|
</Warning>
|
|
|
|
## Step 6: Create Kubernetes ServiceAccount
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: ServiceAccount
|
|
metadata:
|
|
name: bifrost-sa
|
|
namespace: bifrost
|
|
annotations:
|
|
azure.workload.identity/client-id: YOUR_MANAGED_IDENTITY_CLIENT_ID
|
|
labels:
|
|
azure.workload.identity/use: "true"
|
|
```
|
|
|
|
## Step 7: Create Image Pull Secret with Token Refresh
|
|
|
|
Create a CronJob to refresh the imagePullSecret using the federated identity:
|
|
|
|
```yaml
|
|
apiVersion: batch/v1
|
|
kind: CronJob
|
|
metadata:
|
|
name: refresh-ar-secret
|
|
namespace: bifrost
|
|
spec:
|
|
schedule: "*/30 * * * *" # Every 30 minutes
|
|
successfulJobsHistoryLimit: 1
|
|
failedJobsHistoryLimit: 3
|
|
jobTemplate:
|
|
spec:
|
|
template:
|
|
metadata:
|
|
labels:
|
|
azure.workload.identity/use: "true"
|
|
spec:
|
|
serviceAccountName: bifrost-sa
|
|
containers:
|
|
- name: token-refresh
|
|
image: google/cloud-sdk:slim
|
|
command: ["/bin/bash", "-c"]
|
|
args:
|
|
- |
|
|
set -e
|
|
|
|
# Set GCP credential config
|
|
export GOOGLE_APPLICATION_CREDENTIALS=/etc/gcp/credential-config.json
|
|
|
|
# Get GCP access token via federation
|
|
TOKEN=$(gcloud auth print-access-token)
|
|
|
|
# Delete existing secret if it exists
|
|
kubectl delete secret ar-pull-secret --ignore-not-found -n bifrost
|
|
|
|
# Create new imagePullSecret
|
|
kubectl create secret docker-registry ar-pull-secret \
|
|
--docker-server=REGION-docker.pkg.dev \
|
|
--docker-username=oauth2accesstoken \
|
|
--docker-password="$TOKEN" \
|
|
-n bifrost
|
|
|
|
echo "Secret refreshed at $(date)"
|
|
volumeMounts:
|
|
- name: gcp-credential-config
|
|
mountPath: /etc/gcp
|
|
readOnly: true
|
|
- name: azure-identity-token
|
|
mountPath: /var/run/secrets/azure/tokens
|
|
readOnly: true
|
|
volumes:
|
|
- name: gcp-credential-config
|
|
configMap:
|
|
name: gcp-credential-config
|
|
- name: azure-identity-token
|
|
projected:
|
|
sources:
|
|
- serviceAccountToken:
|
|
path: azure-identity-token
|
|
expirationSeconds: 3600
|
|
audience: api://AzureADTokenExchange
|
|
restartPolicy: OnFailure
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: Role
|
|
metadata:
|
|
name: secret-manager
|
|
namespace: bifrost
|
|
rules:
|
|
- apiGroups: [""]
|
|
resources: ["secrets"]
|
|
verbs: ["get", "create", "delete"]
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: RoleBinding
|
|
metadata:
|
|
name: secret-manager-binding
|
|
namespace: bifrost
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: bifrost-sa
|
|
namespace: bifrost
|
|
roleRef:
|
|
kind: Role
|
|
name: secret-manager
|
|
apiGroup: rbac.authorization.k8s.io
|
|
```
|
|
|
|
## Step 8: Deploy Bifrost
|
|
|
|
```yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: bifrost
|
|
namespace: bifrost
|
|
spec:
|
|
replicas: 2
|
|
selector:
|
|
matchLabels:
|
|
app: bifrost
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: bifrost
|
|
azure.workload.identity/use: "true"
|
|
spec:
|
|
serviceAccountName: bifrost-sa
|
|
imagePullSecrets:
|
|
- name: ar-pull-secret
|
|
containers:
|
|
- name: bifrost
|
|
image: REGION-docker.pkg.dev/BIFROST_PROJECT/YOUR_HUB_SLUG/bifrost:latest
|
|
ports:
|
|
- containerPort: 8080
|
|
name: http
|
|
resources:
|
|
requests:
|
|
cpu: "250m"
|
|
memory: "512Mi"
|
|
limits:
|
|
cpu: "1000m"
|
|
memory: "2Gi"
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /health
|
|
port: 8080
|
|
initialDelaySeconds: 30
|
|
periodSeconds: 10
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /health
|
|
port: 8080
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 5
|
|
volumeMounts:
|
|
- name: config
|
|
mountPath: /app/data/config.json
|
|
subPath: config.json
|
|
volumes:
|
|
- name: config
|
|
secret:
|
|
secretName: bifrost-config
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: bifrost
|
|
namespace: bifrost
|
|
spec:
|
|
selector:
|
|
app: bifrost
|
|
ports:
|
|
- port: 80
|
|
targetPort: 8080
|
|
protocol: TCP
|
|
type: ClusterIP
|
|
```
|
|
|
|
## Bootstrap: Initial Secret Creation
|
|
|
|
Before the first deployment, manually trigger the CronJob or create the secret:
|
|
|
|
```bash
|
|
# Create namespace
|
|
kubectl create namespace bifrost
|
|
|
|
# Apply all configurations
|
|
kubectl apply -f configmap.yaml
|
|
kubectl apply -f serviceaccount.yaml
|
|
kubectl apply -f cronjob.yaml
|
|
|
|
# Manually trigger the CronJob
|
|
kubectl create job --from=cronjob/refresh-ar-secret initial-refresh -n bifrost
|
|
|
|
# Wait for completion
|
|
kubectl wait --for=condition=complete job/initial-refresh -n bifrost --timeout=120s
|
|
|
|
# Verify secret was created
|
|
kubectl get secret ar-pull-secret -n bifrost
|
|
```
|
|
|
|
## Verifying Access
|
|
|
|
### Check Workload Identity Configuration
|
|
|
|
```bash
|
|
# Verify AKS has Workload Identity enabled
|
|
az aks show \
|
|
--resource-group YOUR_RESOURCE_GROUP \
|
|
--name YOUR_CLUSTER_NAME \
|
|
--query "oidcIssuerProfile.enabled" -o tsv
|
|
|
|
# Check federated credential
|
|
az identity federated-credential show \
|
|
--name bifrost-federated-credential \
|
|
--identity-name bifrost-pull-identity \
|
|
--resource-group YOUR_RESOURCE_GROUP
|
|
```
|
|
|
|
### Verify Token Exchange
|
|
|
|
```bash
|
|
# Check CronJob ran successfully
|
|
kubectl get jobs -n bifrost
|
|
|
|
# View CronJob logs
|
|
kubectl logs -l job-name=refresh-ar-secret -n bifrost
|
|
|
|
# Verify imagePullSecret exists
|
|
kubectl get secret ar-pull-secret -n bifrost -o yaml
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### ImagePullBackOff Errors
|
|
|
|
1. **Check imagePullSecret exists**: `kubectl get secret ar-pull-secret -n bifrost`
|
|
2. **Verify CronJob succeeded**: `kubectl get jobs -n bifrost`
|
|
3. **Check Azure Workload Identity**: Ensure labels are set correctly
|
|
|
|
```bash
|
|
# Check pod events
|
|
kubectl describe pod -l app=bifrost -n bifrost
|
|
|
|
# Check ServiceAccount has correct annotations
|
|
kubectl get sa bifrost-sa -n bifrost -o yaml
|
|
```
|
|
|
|
### Token Exchange Failures
|
|
|
|
```bash
|
|
# Check CronJob logs for errors
|
|
kubectl logs -l job-name=refresh-ar-secret -n bifrost
|
|
|
|
# Common issues:
|
|
# - "audience mismatch": Check credential-config.json audience field
|
|
# - "subject mismatch": Verify federated credential subject matches SA
|
|
# - "permission denied": Contact Bifrost team to verify WIF configuration
|
|
```
|
|
|
|
### Azure Workload Identity Issues
|
|
|
|
```bash
|
|
# Verify Managed Identity exists
|
|
az identity show \
|
|
--name bifrost-pull-identity \
|
|
--resource-group YOUR_RESOURCE_GROUP
|
|
|
|
# Check federated credentials
|
|
az identity federated-credential list \
|
|
--identity-name bifrost-pull-identity \
|
|
--resource-group YOUR_RESOURCE_GROUP
|
|
|
|
# Verify pod has identity token mounted
|
|
kubectl exec -it deployment/bifrost -n bifrost -- \
|
|
ls -la /var/run/secrets/azure/tokens/
|
|
```
|
|
|
|
## Summary
|
|
|
|
| Component | Value |
|
|
|-----------|-------|
|
|
| Registry | GCP Artifact Registry |
|
|
| Authentication | Azure WIF -> GCP WIF -> GCP SA |
|
|
| Token Lifetime | 60 minutes (auto-refreshed every 30 min) |
|
|
| Secret Name | `ar-pull-secret` |
|
|
|
|
## Next Steps
|
|
|
|
- Configure [Bifrost settings](/quickstart/gateway/setting-up) for your use case
|
|
- Set up [observability](/features/observability/default) for monitoring
|
|
- Enable [clustering](/enterprise/clustering) for high availability
|