← Projects

Multi-Cloud Secure Document Platform, AWS and Azure

What it is

A build that answers one question: how does a workload in one cloud authenticate to storage in another without an access key sitting in a config file?

The answer is OIDC workload identity federation. Everything else in the project exists to make that answer defensible - a storage layer worth protecting, two API tiers with genuinely different threat models, and negative tests that prove the boundaries rather than assuming them.

Multi-cloud secure document platform: Azure identity and API tiers federating into an encrypted AWS document store A Terraform control plane deploys into an Azure subscription and an AWS account from a single configuration. On the Azure side, Entra ID issues tokens, a user-assigned managed identity holds Key Vault access, and two API Management tiers sit in a virtual network: an external tier that validates JWTs and an internal VNet-injected tier that proxies Key Vault using its own managed identity. On the AWS side, an OIDC provider trusts the Entra issuer and an IAM role accepts AssumeRoleWithWebIdentity when the audience and subject claims match, granting read access scoped to one S3 prefix and decrypt on one customer-managed KMS key. Four negative tests confirm that a wrong identity, an expired token, an out-of-scope prefix and a missing KMS grant are all denied. Multi-cloud secure document platform Azure identity and API tiers federating into an encrypted AWS document store - no stored access keys on either side Terraform control plane One configuration, two providers, one state file azurerm 4.x · aws 5.x · azuread 3.x Remote state in an Azure Storage blob, leased on write so two applies cannot collide. Azure resource groups split by lifecycle so disposable workloads tear down without touching identity. Operator workstation - outside both clouds Azure subscription - australiaeast Entra ID App roles, signed JWTs mcdp-documents-api Tenant State backend Versioned, lease locked container: tfstate rg-mcdp-tfstate-aue Key Vault RBAC authorisation, not legacy access policies kv-mcdp-dev-nh01 User-assigned managed identity Key Vault Secrets User · subject of the AWS trust id-mcdp-reader rg-mcdp-identity-aue Virtual network 10.1.0.0/16 · workload and APIM subnets vnet-mcdp-dev-aue · snet-apim-internal Network security group Inbound 3443 control plane, 6390 load balancer nsg-apim-internal-aue rg-mcdp-platform-aue API Management - external tier validate-jwt: issuer, audience, Api.Access role apim-mcdp-ext-dev-aue · mock backend API Management - internal tier VNet-injected · managed identity to Key Vault apim-mcdp-int-dev-aue rg-mcdp-workload-aue AWS account - ap-southeast-2 OIDC identity provider Trusts the Entra v1.0 issuer for this tenant sts.windows.net/<tenant>/ Federated IAM role sts:AssumeRoleWithWebIdentity, no stored keys mcdp-databricks-federated-role Federation trust S3 document store Versioned · all four public access blocks on mcdp-documents-nh01/inbound/ Customer-managed KMS key SSE-KMS default · bucket keys · annual rotation alias/mcdp-documents Bucket policy - three explicit denies Unencrypted upload · wrong key · plain HTTP Deny wins over any IAM allow Document store Negative tests - all four denied Different managed identity assumes the role AccessDenied at STS Expired temporary credentials reused ExpiredToken Read attempted outside the inbound prefix AccessDenied at S3 kms:Decrypt removed, s3:GetObject kept AccessDenied - KMS is a separate gate Verification 1 2 3 4 5 6 internal tier reads a secret via its own managed identity Trust policy - what AWS checks Principal.Federated the OIDC provider ARN Issuer enforced structurally Condition on :aud api://<api-app-id> Condition on :sub managed identity principal There is no :iss condition key - the issuer is pinned by which provider is named. Federated role - what it may do s3:GetObject inbound/* only s3:ListBucket s3:prefix condition kms:Decrypt one key ARN No write, no delete read path only ListBucket is scoped by condition, not by ARN - the bucket ARN has no prefix. Legend Identity Data Security Edge and API Outside both clouds Token or credential path Synchronous Asynchronous or out-of-band Identity is an added category, in the same muted family as the rest. Flow 1 The workload asks the local instance metadata endpoint for a token. No credential is sent with the request. 2 Entra ID returns a signed JWT whose subject claim is the managed identity's principal ID. 3 The token is presented to AWS STS as AssumeRoleWithWebIdentity against the registered OIDC provider. 4 The trust policy checks the audience and subject claims, then STS returns short-lived credentials. 5 Those credentials read objects under the inbound prefix only; listing is scoped by an s3:prefix condition. 6 S3 asks KMS to unwrap the data key - a second, independent grant.
Cross-cloud OIDC workload identity federation, step by step A vertical path showing how an Azure workload reads an encrypted object in AWS without any stored access key. The workload asks the local metadata endpoint for a token, Entra ID issues a signed JWT, AWS STS validates the audience and subject claims against the role's trust policy, and returns short-lived credentials. Those credentials read only the inbound prefix, and S3 must separately ask KMS to unwrap the data key. Four negative tests confirm a wrong identity, an expired token, an out-of-scope prefix and a missing KMS grant are all denied. Cross-cloud federation No AWS access key exists on the Azure side Azure subscription Azure workload User-assigned managed identity id-mcdp-reader 1 Entra ID Signed JWT, subject is the identity sts.windows.net issuer 2 AWS account AWS STS Signature checked offline via JWKS AssumeRoleWithWebIdentity 3 Federated IAM role Short-lived credentials, nothing stored mcdp-databricks-federated-role 4 S3 document store Read scoped to one prefix only mcdp-documents-nh01/inbound/ 5 Customer-managed KMS key A second, independent grant alias/mcdp-documents Trust policy conditions Principal OIDC provider ARN Issuer pinned structurally :aud equals api://<api-app-id> :sub equals identity principal ID There is no :iss condition key in IAM. Negative tests - all denied A different managed identity AccessDenied at STS Credentials past expiry ExpiredToken A prefix outside inbound/ AccessDenied at S3 kms:Decrypt removed AccessDenied, cause is KMS Legend Identity Data Security Token or credential path Synchronous Flow 1 Token requested with no credential sent 2 Signed JWT presented to AWS STS 3 Audience and subject claims must match 4 Read inbound/ only 5 KMS unwraps the key
Two independent authorisation gates guard every encrypted object read A read request from the federated role is checked twice, in parallel-reading side-by-side gates: the S3 bucket policy decides whether the role may touch that prefix, and the KMS key policy decides whether it may decrypt. Both must allow for the object to be returned. Removing either one denies the read, and when the KMS grant is the missing piece the error still surfaces as an S3 AccessDenied. Two independent gates A correct bucket policy is not enough on its own Federated role Reading an SSE-KMS encrypted object s3:GetObject 1 Both must allow Bucket policy Prefix scoped inbound/* only KMS key policy Decrypt grant one key ARN 2 3 Object returned Data key unwrapped, bytes decrypted bucket key enabled Remove either gate Key disabled, bucket policy untouched KMS.DisabledException kms:Decrypt removed from the role AccessDenied, cause is KMS Bucket policy - three explicit denies Not SSE-KMS PutObject denied Not this key ARN PutObject denied Plain HTTP all actions denied Explicit deny beats any IAM allow. Legend Identity Data Security Credential path Failure path Flow 1 A read arrives with temporary credentials 2 S3 checks prefix 3 KMS checks decrypt
One Terraform configuration, two clouds, and a token exchange that leaves no access key on either side. Every read has to pass the bucket policy and the KMS key policy. Four negative tests check that the boundaries hold.

What I built

Stack

Terraform · AWS (S3, KMS, IAM, STS) · Azure (Entra ID, RBAC, Key Vault, API Management, VNet) · OIDC

github.com/nalinka1/multi-cloud-secure-document-platform