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.