Unified Identity and Access
Managing identities and permissions consistently across multiple cloud providers is critical for security and operational efficiency. Each provider has different identity models, but you can create unified patterns that provide consistent access control while leveraging each platform’s strengths.
Cross-Cloud Service Accounts
Create service accounts that can access multiple cloud providers:
# AWS IAM role for cross-cloud access
resource "aws_iam_role" "cross_cloud_service" {
name = "cross-cloud-service-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
},
{
Action = "sts:AssumeRoleWithWebIdentity"
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.azure_ad.arn
}
Condition = {
StringEquals = {
"${aws_iam_openid_connect_provider.azure_ad.url}:aud" = var.azure_application_id
}
}
}
]
})
}
resource "aws_iam_policy" "cross_cloud_policy" {
name = "cross-cloud-access-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject",
"secretsmanager:GetSecretValue"
]
Resource = "*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "cross_cloud" {
role = aws_iam_role.cross_cloud_service.name
policy_arn = aws_iam_policy.cross_cloud_policy.arn
}
# Azure AD application for cross-cloud identity
resource "azuread_application" "cross_cloud" {
display_name = "cross-cloud-service"
web {
redirect_uris = ["https://signin.aws.amazon.com/saml"]
}
}
resource "azuread_service_principal" "cross_cloud" {
application_id = azuread_application.cross_cloud.application_id
}
resource "azuread_service_principal_password" "cross_cloud" {
service_principal_id = azuread_service_principal.cross_cloud.object_id
}
# GCP service account
resource "google_service_account" "cross_cloud" {
account_id = "cross-cloud-service"
display_name = "Cross-Cloud Service Account"
}
resource "google_service_account_key" "cross_cloud" {
service_account_id = google_service_account.cross_cloud.name
}
resource "google_project_iam_member" "cross_cloud_storage" {
project = var.gcp_project_id
role = "roles/storage.admin"
member = "serviceAccount:${google_service_account.cross_cloud.email}"
}
Federated Identity Setup
Configure identity federation between providers:
# AWS OIDC provider for Azure AD
resource "aws_iam_openid_connect_provider" "azure_ad" {
url = "https://sts.windows.net/${var.azure_tenant_id}/"
client_id_list = [
var.azure_application_id
]
thumbprint_list = [
"626d44e704d1ceabe3bf0d53397464ac8080142c"
]
}
# Azure AD SAML configuration for AWS
resource "azuread_application" "aws_sso" {
display_name = "AWS-SSO"
web {
redirect_uris = ["https://signin.aws.amazon.com/saml"]
}
app_role {
allowed_member_types = ["User"]
description = "AWS SSO Access"
display_name = "AWS Access"
enabled = true
id = "b9632174-c057-4f7e-951b-b3adc3ddb778"
value = "AWSAccess"
}
}
# GCP Workload Identity for cross-cloud access
resource "google_iam_workload_identity_pool" "cross_cloud" {
workload_identity_pool_id = "cross-cloud-pool"
display_name = "Cross-Cloud Identity Pool"
description = "Identity pool for cross-cloud access"
}
resource "google_iam_workload_identity_pool_provider" "aws" {
workload_identity_pool_id = google_iam_workload_identity_pool.cross_cloud.workload_identity_pool_id
workload_identity_pool_provider_id = "aws-provider"
display_name = "AWS Provider"
aws {
account_id = var.aws_account_id
}
attribute_mapping = {
"google.subject" = "assertion.arn"
"attribute.aws_role" = "assertion.arn.contains('role') ? assertion.arn.extract('{account_arn}role/') : ''"
"attribute.account_id" = "assertion.account"
}
}
Unified RBAC Implementation
Create consistent role-based access control across providers:
# Define common roles
locals {
common_roles = {
admin = {
description = "Full administrative access"
permissions = ["*"]
}
developer = {
description = "Development environment access"
permissions = ["read", "write", "deploy"]
}
readonly = {
description = "Read-only access"
permissions = ["read"]
}
}
}
# AWS IAM roles based on common roles
resource "aws_iam_role" "common_roles" {
for_each = local.common_roles
name = "multi-cloud-${each.key}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.azure_ad.arn
}
}
]
})
}
resource "aws_iam_policy" "common_role_policies" {
for_each = local.common_roles
name = "multi-cloud-${each.key}-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = each.value.permissions
Resource = "*"
}
]
})
}
# Azure AD groups for common roles
resource "azuread_group" "common_roles" {
for_each = local.common_roles
display_name = "MultiCloud-${title(each.key)}"
description = each.value.description
security_enabled = true
}
# GCP IAM custom roles
resource "google_project_iam_custom_role" "common_roles" {
for_each = local.common_roles
role_id = "multiCloud${title(each.key)}"
title = "Multi-Cloud ${title(each.key)}"
description = each.value.description
permissions = [
for perm in each.value.permissions :
"storage.objects.${perm}" if perm != "*"
]
}
Secret Management Across Clouds
Implement unified secret management:
#!/usr/bin/env python3
# scripts/cross_cloud_secrets.py
import boto3
import json
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
from google.cloud import secretmanager
class CrossCloudSecretManager:
def __init__(self, aws_region: str, azure_vault_url: str, gcp_project_id: str):
self.aws_secrets = boto3.client('secretsmanager', region_name=aws_region)
self.azure_secrets = SecretClient(vault_url=azure_vault_url, credential=DefaultAzureCredential())
self.gcp_secrets = secretmanager.SecretManagerServiceClient()
self.gcp_project_id = gcp_project_id
def create_secret_everywhere(self, secret_name: str, secret_value: str) -> dict:
"""Create the same secret in all three cloud providers"""
results = {}
# AWS Secrets Manager
try:
self.aws_secrets.create_secret(
Name=secret_name,
SecretString=secret_value,
Description=f"Cross-cloud secret: {secret_name}"
)
results['aws'] = 'success'
except Exception as e:
results['aws'] = f'error: {str(e)}'
# Azure Key Vault
try:
self.azure_secrets.set_secret(secret_name, secret_value)
results['azure'] = 'success'
except Exception as e:
results['azure'] = f'error: {str(e)}'
# GCP Secret Manager
try:
parent = f"projects/{self.gcp_project_id}"
# Create secret
secret = self.gcp_secrets.create_secret(
request={
"parent": parent,
"secret_id": secret_name,
"secret": {"replication": {"automatic": {}}},
}
)
# Add secret version
self.gcp_secrets.add_secret_version(
request={
"parent": secret.name,
"payload": {"data": secret_value.encode("UTF-8")},
}
)
results['gcp'] = 'success'
except Exception as e:
results['gcp'] = f'error: {str(e)}'
return results
def get_secret_from_all(self, secret_name: str) -> dict:
"""Retrieve secret from all providers for comparison"""
secrets = {}
# AWS
try:
response = self.aws_secrets.get_secret_value(SecretId=secret_name)
secrets['aws'] = response['SecretString']
except Exception as e:
secrets['aws'] = f'error: {str(e)}'
# Azure
try:
secret = self.azure_secrets.get_secret(secret_name)
secrets['azure'] = secret.value
except Exception as e:
secrets['azure'] = f'error: {str(e)}'
# GCP
try:
name = f"projects/{self.gcp_project_id}/secrets/{secret_name}/versions/latest"
response = self.gcp_secrets.access_secret_version(request={"name": name})
secrets['gcp'] = response.payload.data.decode("UTF-8")
except Exception as e:
secrets['gcp'] = f'error: {str(e)}'
return secrets
def sync_secrets(self, secret_mappings: dict) -> dict:
"""Sync secrets across providers based on mapping"""
sync_results = {}
for secret_name, config in secret_mappings.items():
source_provider = config['source']
target_providers = config['targets']
# Get secret from source
if source_provider == 'aws':
try:
response = self.aws_secrets.get_secret_value(SecretId=secret_name)
secret_value = response['SecretString']
except Exception as e:
sync_results[secret_name] = f'Failed to read from AWS: {e}'
continue
# Sync to targets
for target in target_providers:
if target == 'azure':
try:
self.azure_secrets.set_secret(secret_name, secret_value)
sync_results[f'{secret_name}_to_azure'] = 'success'
except Exception as e:
sync_results[f'{secret_name}_to_azure'] = f'error: {e}'
elif target == 'gcp':
try:
name = f"projects/{self.gcp_project_id}/secrets/{secret_name}/versions/latest"
self.gcp_secrets.add_secret_version(
request={
"parent": f"projects/{self.gcp_project_id}/secrets/{secret_name}",
"payload": {"data": secret_value.encode("UTF-8")},
}
)
sync_results[f'{secret_name}_to_gcp'] = 'success'
except Exception as e:
sync_results[f'{secret_name}_to_gcp'] = f'error: {e}'
return sync_results
def main():
import argparse
parser = argparse.ArgumentParser(description='Cross-Cloud Secret Manager')
parser.add_argument('--aws-region', default='us-west-2', help='AWS region')
parser.add_argument('--azure-vault-url', required=True, help='Azure Key Vault URL')
parser.add_argument('--gcp-project-id', required=True, help='GCP Project ID')
parser.add_argument('--action', choices=['create', 'get', 'sync'], required=True)
parser.add_argument('--secret-name', help='Secret name')
parser.add_argument('--secret-value', help='Secret value')
parser.add_argument('--config-file', help='JSON config file for sync operation')
args = parser.parse_args()
manager = CrossCloudSecretManager(
args.aws_region,
args.azure_vault_url,
args.gcp_project_id
)
if args.action == 'create':
if not args.secret_name or not args.secret_value:
print("Error: --secret-name and --secret-value required for create")
return
results = manager.create_secret_everywhere(args.secret_name, args.secret_value)
print(json.dumps(results, indent=2))
elif args.action == 'get':
if not args.secret_name:
print("Error: --secret-name required for get")
return
secrets = manager.get_secret_from_all(args.secret_name)
print(json.dumps(secrets, indent=2))
elif args.action == 'sync':
if not args.config_file:
print("Error: --config-file required for sync")
return
with open(args.config_file, 'r') as f:
config = json.load(f)
results = manager.sync_secrets(config)
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()
Access Control Automation
Automate user provisioning across all providers:
#!/bin/bash
# scripts/provision-user.sh
set -e
USER_EMAIL=${1:-""}
ROLE=${2:-"readonly"}
GROUPS=${3:-""}
if [ -z "$USER_EMAIL" ]; then
echo "Usage: $0 <user_email> [role] [additional_groups]"
exit 1
fi
provision_aws_access() {
echo "Provisioning AWS access for $USER_EMAIL..."
# Create IAM user
aws iam create-user --user-name "$USER_EMAIL" || true
# Attach role policy
aws iam attach-user-policy \
--user-name "$USER_EMAIL" \
--policy-arn "arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/multi-cloud-${ROLE}-policy"
# Generate access keys
aws iam create-access-key --user-name "$USER_EMAIL" --output table
}
provision_azure_access() {
echo "Provisioning Azure access for $USER_EMAIL..."
# Create user (if external, invite as guest)
az ad user create \
--display-name "$USER_EMAIL" \
--user-principal-name "$USER_EMAIL" \
--password "TempPassword123!" \
--force-change-password-next-sign-in true || \
az ad user invite --invited-user-email-address "$USER_EMAIL"
# Add to role group
GROUP_ID=$(az ad group show --group "MultiCloud-$(echo $ROLE | sed 's/.*/\u&/')" --query objectId --output tsv)
USER_ID=$(az ad user show --id "$USER_EMAIL" --query objectId --output tsv)
az ad group member add --group "$GROUP_ID" --member-id "$USER_ID"
}
provision_gcp_access() {
echo "Provisioning GCP access for $USER_EMAIL..."
# Add IAM policy binding
gcloud projects add-iam-policy-binding "$GCP_PROJECT_ID" \
--member="user:$USER_EMAIL" \
--role="projects/$GCP_PROJECT_ID/roles/multiCloud$(echo $ROLE | sed 's/.*/\u&/')"
}
# Execute provisioning
provision_aws_access
provision_azure_access
provision_gcp_access
echo "✅ User $USER_EMAIL provisioned with $ROLE access across all clouds"
What’s Next
Unified identity and access management provides the security foundation for multi-cloud operations. In the next part, we’ll explore provider abstraction patterns that allow you to create modules and configurations that work consistently across different cloud providers.