HACKADEMICS

Cloud Security Configuration Showdown: Azure vs. Google Cloud for Enterprise DevOps

Azure and Google Cloud will both let you deploy insecure infrastructure at enterprise scale—they just give you different ways to screw it up. The question isn't which cloud is "more secure" (spoiler: neither is, out of the box), but which platform's security model aligns with how your DevOps team actually works when deadlines are tight and production is on fire.

IAM: Where Your Cloud Security Lives or Dies

Most cloud breaches don't exploit zero-days. They exploit an overly permissive service account that someone created at 11 PM because "we'll fix the permissions later." Let's talk about how each platform makes this easier or harder to avoid.

Azure AD/Entra ID: Role-Based Everything

Azure's identity model centers on Azure Active Directory (now branded as "Entra ID" because Microsoft can't stop renaming things). You assign roles at different scopes: management group, subscription, resource group, or individual resource.

# Assign Storage Blob Data Contributor to a managed identity
az role assignment create \
  --assignee <managed-identity-principal-id> \
  --role "Storage Blob Data Contributor" \
  --scope "/subscriptions/<sub-id>/resourceGroups/prod-rg/providers/Microsoft.Storage/storageAccounts/prodstore"

The good: Role-based access is conceptually simpler than AWS-style policy documents. You pick from built-in roles or create custom ones.

The bad: Built-in roles are often way too broad. "Contributor" sounds reasonable until you realize it grants nearly full control. "Reader" seems safe until you discover it can read secrets in some configurations.

Azure's killer feature: Privileged Identity Management (PIM)

PIM provides just-in-time (JIT) access with approval workflows. Instead of granting permanent admin rights, users request temporary elevation with justification:

# Request elevation through PIM (requires Azure Portal or API)
# Time-limited, requires approval, auto-revokes

This is built-in on Azure. On GCP you need to build it yourself or buy a third-party tool. For enterprises with compliance requirements, PIM alone can justify Azure.

Azure's gotcha: Service principal vs. managed identity

Service principals are essentially non-human accounts with passwords. Managed identities are Azure-managed identities attached to resources. Always use managed identities when possible:

# Enable system-assigned managed identity on VM
az vm identity assign \
  --name prod-vm \
  --resource-group prod-rg

# Now the VM can authenticate to Azure services without credentials

I still find service principal credentials in GitHub repos. Don't be that person.

Google Cloud IAM: Predicate-Based Precision

GCP's IAM is permission-based rather than role-based. Roles are collections of permissions, and you can get incredibly granular:

# Grant specific permission on specific bucket
gcloud storage buckets add-iam-policy-binding gs://prod-data \
  --member=serviceAccount:app@project.iam.gserviceaccount.com \
  --role=roles/storage.objectViewer

GCP's predefined roles are generally more granular than Azure's. roles/storage.objectViewer is read-only for objects. roles/storage.objectAdmin can create/delete objects but not change bucket settings. Azure's equivalent roles are broader.

Custom roles are easier on GCP:

# Create custom role with exact permissions needed
gcloud iam roles create limitedComputeRole \
  --project=my-project \
  --title="Limited Compute Access" \
  --description="Can start/stop VMs only" \
  --permissions=compute.instances.start,compute.instances.stop,compute.instances.get

GCP's killer feature: Workload Identity Federation

This lets external workloads (GitHub Actions, on-prem services, etc.) authenticate to GCP without long-lived service account keys:

# Configure GitHub Actions to authenticate via OIDC
gcloud iam workload-identity-pools create github-pool \
  --location=global \
  --display-name="GitHub Actions Pool"

# No service account keys needed - GitHub's OIDC token is exchanged for GCP access

Azure has a similar feature (federated credentials for managed identities), but GCP's implementation is cleaner and better documented.

GCP's gotcha: Service account keys

Service accounts can have JSON key files. These are long-lived credentials that get leaked constantly:

# This creates a credential that never expires by default
gcloud iam service-accounts keys create key.json \
  --iam-account=app@project.iam.gserviceaccount.com

If you're creating service account keys, you're probably doing it wrong. Use Workload Identity Federation or Application Default Credentials instead.

Network Security: Firewalls and Flow Control

Azure: NSGs and ASGs

Network Security Groups (NSGs) in Azure are stateful firewalls with priority-based rule evaluation. Lower priority number = evaluated first.

# Create NSG rule allowing HTTPS from specific source
az network nsg rule create \
  --resource-group prod-rg \
  --nsg-name prod-nsg \
  --name allow-https \
  --priority 100 \
  --source-address-prefixes 10.0.1.0/24 \
  --destination-port-ranges 443 \
  --access Allow \
  --protocol Tcp

# Block everything else at lower priority
az network nsg rule create \
  --nsg-name prod-nsg \
  --name deny-all \
  --priority 4096 \
  --access Deny \
  --protocol '*'

Application Security Groups (ASGs) are underused but powerful:

# Create logical groupings
az network asg create --name web-tier --resource-group prod-rg
az network asg create --name db-tier --resource-group prod-rg

# Rules reference ASGs instead of IP ranges
az network nsg rule create \
  --nsg-name prod-nsg \
  --name web-to-db \
  --source-asgs web-tier \
  --destination-asgs db-tier \
  --destination-port-ranges 5432 \
  --access Allow

As your infrastructure scales, ASGs scale with it. No hardcoded IP ranges to update.

Azure's default problem: Outbound is wide open

By default, NSGs allow all outbound traffic. Your compromised VM can beacon to command-and-control all day:

# Fix: Explicitly deny all outbound at low priority
az network nsg rule create \
  --nsg-name prod-nsg \
  --name deny-all-outbound \
  --priority 4096 \
  --direction Outbound \
  --access Deny \
  --protocol '*'

# Then add explicit allows for what you need
az network nsg rule create \
  --nsg-name prod-nsg \
  --name allow-https-outbound \
  --priority 100 \
  --direction Outbound \
  --destination-address-prefixes AzureCloud \
  --destination-port-ranges 443 \
  --access Allow

Google Cloud: VPC Firewall Rules and Hierarchical Policies

GCP firewall rules are applied at the VPC level. They're stateful and support tags for logical grouping:

# Create firewall rule allowing SSH from IAP only
gcloud compute firewall-rules create allow-iap-ssh \
  --network=prod-vpc \
  --allow=tcp:22 \
  --source-ranges=35.235.240.0/20 \
  --description="Allow SSH from Identity-Aware Proxy"

# Block all ingress by default (implied deny)
# GCP has implicit deny-all for ingress

GCP's killer feature: Hierarchical firewall policies

You can set organization-wide or folder-wide firewall rules that can't be overridden by individual projects:

# Create org-wide policy blocking risky ports
gcloud compute firewall-policies create block-risky-ports \
  --organization=123456789

gcloud compute firewall-policies rules create 100 \
  --firewall-policy=block-risky-ports \
  --action=deny \
  --direction=ingress \
  --layer4-configs=tcp:23,tcp:135-139,tcp:445

This prevents developers from accidentally exposing SMB or Telnet to the internet. Azure has similar capabilities with Azure Firewall and Azure Policy, but it's more complex to set up.

Service perimeters for data exfiltration prevention:

VPC Service Controls create security perimeters around GCP resources:

# Create perimeter preventing data exfiltration
gcloud access-context-manager perimeters create prod_perimeter \
  --policy=<policy-id> \
  --resources=projects/123456 \
  --restricted-services=storage.googleapis.com,bigquery.googleapis.com \
  --access-levels=<access-level>

This blocks resources inside the perimeter from being accessed outside it. If an attacker compromises a service account, they can't exfiltrate data to external buckets.

Azure has similar capabilities with Private Endpoints and Service Endpoints, but the conceptual model is different and often more complex.

Secrets Management: Stop Hardcoding Credentials

Azure Key Vault: Everything in One Place

Key Vault stores secrets, keys, and certificates with RBAC or access policies:

# Store database password
az keyvault secret set \
  --vault-name prod-keyvault \
  --name db-password \
  --value "super-secret-password"

# Grant managed identity access
az keyvault set-policy \
  --name prod-keyvault \
  --object-id <managed-identity-id> \
  --secret-permissions get list

Access from code using managed identity:

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

var client = new SecretClient(
    new Uri("https://prod-keyvault.vault.azure.net"),
    new DefaultAzureCredential());

var secret = await client.GetSecretAsync("db-password");
string password = secret.Value.Value;

No credentials in code. The managed identity handles authentication automatically.

Key Vault's gotcha: Access policies vs. RBAC

Key Vault supports two access models. Pick one and stick with it. Mixing them causes confusion and potential security gaps.

Google Secret Manager: Purpose-Built for Secrets

Secret Manager is Google's dedicated secrets service:

# Create secret
echo -n "super-secret-password" | gcloud secrets create db-password \
  --data-file=-

# Grant service account access
gcloud secrets add-iam-policy-binding db-password \
  --member=serviceAccount:app@project.iam.gserviceaccount.com \
  --role=roles/secretmanager.secretAccessor

Access from code:

from google.cloud import secretmanager

client = secretmanager.SecretManagerServiceClient()
name = "projects/123456/secrets/db-password/versions/latest"

response = client.access_secret_version(request={"name": name})
password = response.payload.data.decode("UTF-8")

Secret Manager's advantage: Versioning is built-in

Every secret update creates a new version. You can roll back or access old versions:

# Access specific version
gcloud secrets versions access 3 --secret=db-password

# Enable automatic rotation (requires Cloud Functions or Cloud Run)

Azure Key Vault also has versioning, but Secret Manager's UI and CLI make it more discoverable.

Compliance and Governance: Keeping Auditors Happy

Azure Policy: Enforce Standards

Azure Policy lets you enforce compliance requirements across subscriptions:

{
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Storage/storageAccounts"
        },
        {
          "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
          "notEquals": true
        }
      ]
    },
    "then": {
      "effect": "deny"
    }
  }
}

This prevents creating storage accounts without HTTPS enforcement. You can audit existing resources and deny non-compliant new ones.

Azure Blueprints package policies with role assignments and ARM templates for repeatable deployments. Good for enterprises with strict standards.

GCP Organization Policies: Constraints from Above

Organization policies work at the org, folder, or project level:

# Disable service account key creation across organization
gcloud resource-manager org-policies set-policy <policy.yaml> \
  --organization=123456789

# policy.yaml
constraint: iam.disableServiceAccountKeyCreation
listPolicy:
  deniedValues:
    - "all"

This prevents anyone in the organization from creating service account keys. It's a blunt instrument but effective.

GCP's advantage: Binary Authorization

Require images to be signed before deploying to GKE:

# Create attestor
gcloud container binauthz attestors create prod-attestor \
  --project=my-project

# Require attestation for prod cluster
gcloud container binauthz policy import <policy.yaml>

This enforces supply chain security for containers. Azure has similar capabilities through Azure Policy and Container Registry, but Binary Authorization is more mature.

Logging and Monitoring: CYA When Things Go Wrong

Azure Monitor and Log Analytics

Activity Log captures subscription-level events automatically. Export to Log Analytics for long-term retention:

az monitor diagnostic-settings create \
  --name export-activity \
  --resource /subscriptions/<sub-id> \
  --logs '[{"category": "Administrative", "enabled": true}]' \
  --workspace /subscriptions/<sub-id>/resourceGroups/monitoring/providers/Microsoft.OperationalInsights/workspaces/prod-logs

Query with KQL:

AzureActivity
| where OperationNameValue contains "delete"
| where Caller !contains "system"
| project TimeGenerated, Caller, OperationNameValue, ResourceGroup
| order by TimeGenerated desc

Azure Sentinel for SIEM capabilities, but it's expensive at scale.

GCP Cloud Logging and Cloud Audit Logs

Admin Activity logs are always enabled and free. Data Access logs must be enabled per service:

# Enable data access logs for Cloud Storage
gcloud projects get-iam-policy <project-id> --format=json > policy.json
# Edit policy.json to add data access logging
gcloud projects set-iam-policy <project-id> policy.json

Query logs:

gcloud logging read \
  'resource.type="gce_instance" AND protoPayload.methodName="v1.compute.instances.delete"' \
  --limit 10 \
  --format json

GCP's advantage: Security Command Center

Built-in security dashboard that identifies misconfigurations, vulnerabilities, and threats:

# Get findings
gcloud scc findings list <organization-id> \
  --filter="category=PUBLICLY_ACCESSIBLE_RESOURCE"

Azure has Defender for Cloud with similar capabilities, but Security Command Center feels more integrated into the GCP experience.

DevOps-Specific Pain Points

CI/CD Authentication

Azure: Use managed identities for Azure-hosted build agents. For GitHub Actions:

- uses: azure/login@v1
  with:
    client-id: }
    tenant-id: }
    subscription-id: }

Requires setting up federated credentials on the managed identity.

GCP: Workload Identity Federation for external CI/CD:

- uses: google-github-actions/auth@v1
  with:
    workload_identity_provider: 'projects/123/locations/global/workloadIdentityPools/github/providers/github'
    service_account: 'deploy@project.iam.gserviceaccount.com'

Both eliminate long-lived credentials in CI/CD. GCP's setup is slightly more straightforward.

Infrastructure as Code Security

Azure with Terraform:

# Enforce encryption and disable public access
resource "azurerm_storage_account" "prod" {
  name                     = "prodstorageacct"
  resource_group_name      = azurerm_resource_group.prod.name
  location                 = azurerm_resource_group.prod.location
  account_tier             = "Standard"
  account_replication_type = "GRS"
  
  min_tls_version               = "TLS1_2"
  enable_https_traffic_only     = true
  allow_nested_items_to_be_public = false
  
  network_rules {
    default_action = "Deny"
    virtual_network_subnet_ids = [azurerm_subnet.prod.id]
  }
}

GCP with Terraform:

# Lock down storage bucket
resource "google_storage_bucket" "prod" {
  name     = "prod-data-bucket"
  location = "US"
  
  uniform_bucket_level_access = true
  
  encryption {
    default_kms_key_name = google_kms_crypto_key.bucket_key.id
  }
  
  lifecycle_rule {
    condition {
      age = 90
    }
    action {
      type = "Delete"
    }
  }
}

# Prevent public access org-wide
resource "google_organization_policy" "block_public_buckets" {
  org_id     = "123456789"
  constraint = "storage.publicAccessPrevention"
  
  boolean_policy {
    enforced = true
  }
}

Both platforms support policy-as-code. GCP's organization policies can enforce standards across all projects. Azure requires Azure Policy definitions.

The Bottom Line for Enterprise DevOps

Neither Azure nor GCP is inherently more secure. Both require competent configuration.

Choose Azure if:

  • You're already invested in Microsoft ecosystem (AD, Office 365)
  • You need built-in PIM for compliance
  • Your team prefers role-based access over granular permissions
  • Hybrid cloud is a hard requirement

Choose GCP if:

  • You want more granular IAM permissions out of the box
  • Workload Identity Federation for external services matters
  • You value hierarchical policies for organization-wide enforcement
  • You're running heavy Kubernetes workloads (GKE is excellent)

Reality check:

  • Most breaches come from misconfiguration, not platform choice
  • Both platforms default to insecure settings (public access, overly broad permissions)
  • Your security posture depends on your team's expertise, not the cloud provider

What actually matters:

  1. Enable audit logging everywhere - Cloud Logging, Activity Log, both
  2. Enforce least privilege from day one - Managed identities, service accounts with minimal permissions
  3. Lock down network egress - Don't just focus on ingress
  4. Encrypt with customer-managed keys - For anything remotely sensitive
  5. Eliminate long-lived credentials - Use OIDC federation for CI/CD
  6. Codify security in IaC - Manual console clicks don't scale

The cloud providers give you tools. They don't give you security. That requires understanding threat models, architecting defensively, and actually testing your incident response before you need it.

And for the sake of everyone who has to clean up after breaches: stop creating service accounts with Owner/Editor roles and calling it "temporary." We all know it becomes permanent the moment it hits production.