Skip to content

Azure Deployment

DeepIntShield Enterprise images for Azure customers are distributed through GCP Artifact Registry, using Azure Workload Identity Federation for secure, credential-less authentication.

flowchart LR
subgraph Azure[Azure Subscription]
subgraph AKS[AKS Cluster]
Pod[DeepIntShield 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/>DeepIntShield Images]
end
KSA -->|Federated| MI
MI -->|OIDC Token| WIF
WIF -->|Exchange| GSA
GSA -->|Pull Permission| AR
AR -->|Image| Pod

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
  • 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 DeepIntShield team

If not already enabled, enable Workload Identity on your AKS cluster:

Terminal window
# 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
Terminal window
# Create Managed Identity
az identity create \
--name deepintshield-pull-identity \
--resource-group YOUR_RESOURCE_GROUP \
--location YOUR_LOCATION
# Get the Client ID
CLIENT_ID=$(az identity show \
--name deepintshield-pull-identity \
--resource-group YOUR_RESOURCE_GROUP \
--query clientId -o tsv)
echo "Client ID: $CLIENT_ID"

Link the Kubernetes ServiceAccount to the Azure Managed Identity:

Terminal window
# 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 deepintshield-federated-credential \
--identity-name deepintshield-pull-identity \
--resource-group YOUR_RESOURCE_GROUP \
--issuer "$AKS_OIDC_ISSUER" \
--subject "system:serviceaccount:deepintshield:deepintshield-sa" \
--audience "api://AzureADTokenExchange"

Step 4: Provide Details to DeepIntShield Team

Section titled “Step 4: Provide Details to DeepIntShield Team”

Send the following information to the DeepIntShield team:

Terminal window
# Get Tenant ID
az account show --query tenantId -o tsv
# Get Client ID
az identity show \
--name deepintshield-pull-identity \
--resource-group YOUR_RESOURCE_GROUP \
--query clientId -o tsv

The DeepIntShield team will configure GCP Workload Identity Federation to trust your Azure Managed Identity.

Step 5: Store GCP Credential Configuration

Section titled “Step 5: Store GCP Credential Configuration”

After the DeepIntShield team configures access, they will provide a credential configuration. Store it as a ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
name: gcp-credential-config
namespace: deepintshield
data:
credential-config.json: |
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/DEEPINTSHIELD_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/DEEPINTSHIELD_SA@DEEPINTSHIELD_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"
}
}
}
apiVersion: v1
kind: ServiceAccount
metadata:
name: deepintshield-sa
namespace: deepintshield
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

Section titled “Step 7: Create Image Pull Secret with Token Refresh”

Create a CronJob to refresh the imagePullSecret using the federated identity:

apiVersion: batch/v1
kind: CronJob
metadata:
name: refresh-ar-secret
namespace: deepintshield
spec:
schedule: "*/30 * * * *" # Every 30 minutes
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: deepintshield-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 deepintshield
# Create new imagePullSecret
kubectl create secret docker-registry ar-pull-secret \
--docker-server=REGION-docker.pkg.dev \
--docker-username=oauth2accesstoken \
--docker-password="$TOKEN" \
-n deepintshield
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: deepintshield
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: secret-manager-binding
namespace: deepintshield
subjects:
- kind: ServiceAccount
name: deepintshield-sa
namespace: deepintshield
roleRef:
kind: Role
name: secret-manager
apiGroup: rbac.authorization.k8s.io
apiVersion: apps/v1
kind: Deployment
metadata:
name: deepintshield
namespace: deepintshield
spec:
replicas: 2
selector:
matchLabels:
app: deepintshield
template:
metadata:
labels:
app: deepintshield
azure.workload.identity/use: "true"
spec:
serviceAccountName: deepintshield-sa
imagePullSecrets:
- name: ar-pull-secret
containers:
- name: deepintshield
image: REGION-docker.pkg.dev/DEEPINTSHIELD_PROJECT/YOUR_HUB_SLUG/deepintshield: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: deepintshield-config
---
apiVersion: v1
kind: Service
metadata:
name: deepintshield
namespace: deepintshield
spec:
selector:
app: deepintshield
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP

Before the first deployment, manually trigger the CronJob or create the secret:

Terminal window
# Create namespace
kubectl create namespace deepintshield
# 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 deepintshield
# Wait for completion
kubectl wait --for=condition=complete job/initial-refresh -n deepintshield --timeout=120s
# Verify secret was created
kubectl get secret ar-pull-secret -n deepintshield
Terminal window
# 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 deepintshield-federated-credential \
--identity-name deepintshield-pull-identity \
--resource-group YOUR_RESOURCE_GROUP
Terminal window
# Check CronJob ran successfully
kubectl get jobs -n deepintshield
# View CronJob logs
kubectl logs -l job-name=refresh-ar-secret -n deepintshield
# Verify imagePullSecret exists
kubectl get secret ar-pull-secret -n deepintshield -o yaml
  1. Check imagePullSecret exists: kubectl get secret ar-pull-secret -n deepintshield
  2. Verify CronJob succeeded: kubectl get jobs -n deepintshield
  3. Check Azure Workload Identity: Ensure labels are set correctly
Terminal window
# Check pod events
kubectl describe pod -l app=deepintshield -n deepintshield
# Check ServiceAccount has correct annotations
kubectl get sa deepintshield-sa -n deepintshield -o yaml
Terminal window
# Check CronJob logs for errors
kubectl logs -l job-name=refresh-ar-secret -n deepintshield
# Common issues:
# - "audience mismatch": Check credential-config.json audience field
# - "subject mismatch": Verify federated credential subject matches SA
# - "permission denied": Contact DeepIntShield team to verify WIF configuration
Terminal window
# Verify Managed Identity exists
az identity show \
--name deepintshield-pull-identity \
--resource-group YOUR_RESOURCE_GROUP
# Check federated credentials
az identity federated-credential list \
--identity-name deepintshield-pull-identity \
--resource-group YOUR_RESOURCE_GROUP
# Verify pod has identity token mounted
kubectl exec -it deployment/deepintshield -n deepintshield -- \
ls -la /var/run/secrets/azure/tokens/
ComponentValue
RegistryGCP Artifact Registry
AuthenticationAzure WIF -> GCP WIF -> GCP SA
Token Lifetime60 minutes (auto-refreshed every 30 min)
Secret Namear-pull-secret