Skip to content

IAM Security Boundaries for Cloud GIS

Serverless geospatial functions need the narrowest IAM scope possible: grant exactly the S3 prefixes, KMS keys, and queue ARNs a single pipeline stage touches — nothing more. Without explicit boundary definitions, functions inherit overly permissive execution roles that expose proprietary raster datasets, enable cross-tenant data leakage, or violate data-residency mandates under GDPR or sovereign-cloud requirements.

This page walks through a production-ready workflow for scoping, enforcing, and validating IAM boundaries for serverless GIS workloads on AWS, GCP, and Azure — covering policy authoring, organization-level enforcement, credential lifecycle monitoring, and the specific failure signatures that appear when boundaries are misconfigured.


Why IAM Boundaries Matter for Geospatial Workloads

Geospatial pipelines move unusually large, structured payloads across many services in a single invocation. A Lambda function tiling a 5 GB Cloud-Optimized GeoTIFF may read from S3, decrypt with KMS, write intermediate bands to /tmp, publish a job completion event to SQS, and call an external elevation API — all within a single 15-minute execution window. Each of those operations is a potential privilege-escalation surface if the execution role is scoped too broadly.

Three compounding factors make IAM scoping harder in geospatial contexts than in typical web workloads:

  1. Long execution windows. Cold Start Mapping for Python GDAL adds 8–25 seconds of shared-library resolution before any data access occurs. Functions that run close to their timeout ceiling hold temporary credentials for the maximum STS duration, extending the exposure window.

  2. Ephemeral storage writes. Ephemeral storage limits in AWS Lambda cap /tmp at 10 GB. Functions that write GeoTIFFs locally before uploading them to object storage need write permissions to both the ephemeral path and the destination bucket prefix — two distinct authorization surfaces on the same credential.

  3. High data classification variance. A single pipeline may process public basemap tiles, proprietary LiDAR point clouds, and PII-adjacent vector features (e.g., parcel ownership boundaries) in the same account. Boundary policies must segment these by prefix or KMS key alias, not by bucket alone.


Platform-by-Platform Boundary Mechanisms

The conceptual goal is the same across providers — cap maximum effective permissions at a layer above the individual role — but the implementation mechanisms differ significantly.

Where the permission ceiling lives on AWS, GCP and AzureThree panels comparing the boundary mechanism on each provider: AWS uses a permissions boundary policy named by the role with a Service Control Policy above it, GCP attaches IAM Conditions to the binding under organization policy constraints, and Azure uses Azure Policy deny effects at the management group. Each panel also lists the temporary-credential lifetime, the function timeout ceiling and the audit log that records a denied call.The same ceiling, three different places to hang itAWSPermissions boundary named by theroleService Control Policy is the orgceilingSTS AssumeRole session, 15 min –12 hrLambda invocation ceiling 15 minCloudTrail data events logs3:GetObjectGCPIAM Conditions ride on the rolebindingOrganization Policy constraintsabove themWorkload Identity token, 1 hrdefaultCloud Functions 2nd gen ceiling 60minCloud Audit Logs DATA_READentriesAzureAzure Policy on the managementgroupDeny effect, evaluated beforeRBACManaged identity token, 24 hrauto-refreshFunctions Consumption ceiling 10minAzure Monitor diagnostic settingsAWS caps the role itself; GCP and Azure can only cap the binding above it.
Only AWS gives a role its own cap. On GCP and Azure the ceiling sits above the role, so a per-function limit has to be written as a condition on the binding rather than as a policy the function carries.
Capability AWS GCP Azure
Boundary layer name IAM Permissions Boundary IAM Conditions (on bindings) Azure Policy + Management Group
Org-level ceiling Service Control Policy (SCP) Organization Policy Constraints Azure Policy (Deny effect)
Granularity Action + Resource + Condition Resource + Condition (CEL) Resource type + Condition
Evaluation order SCP → Boundary → Identity → Resource Org Policy → IAM Conditions Management Group → Subscription → Resource
Temporary credentials STS AssumeRole (15 min – 12 hr) Workload Identity Federation (1 hr default) Managed Identity token (24 hr, auto-refresh)
Policy linting tool IAM Access Analyzer, policy_sentry Policy Troubleshooter, gcloud iam lint Azure Policy compliance scan, checkov
Audit log CloudTrail (s3:GetObject, kms:Decrypt) Cloud Audit Logs (DATA_READ) Azure Monitor + Diagnostic Settings
Max Lambda/Function timeout 15 min 60 min (2nd gen) 10 min (Consumption)

Architecture: IAM Evaluation Flow for a GIS Pipeline

The diagram below shows how AWS evaluates an s3:GetObject call from a Lambda function tiling a GeoTIFF — starting at the organization SCP and ending at the S3 bucket policy. The same logical layers exist on GCP and Azure with provider-specific naming.

How AWS evaluates a tiling function's read of an encrypted GeoTIFFA chain of five states for a single s3:GetObject call from a Lambda tiler: the signed request enters, the Service Control Policy is checked as the organization ceiling, the permissions boundary caps the role, the identity policy and bucket policy must both allow, and only then is the read allowed. A return path shows that an implicit deny at any layer ends the chain with AccessDenied.One s3:GetObject on a GeoTIFF, four chances to be deniedSigned requests3:GetObjectSCPorganization ceilingBoundaryrole-level capIdentity + bucketexplicit allow neededAllowtile read proceedsrole credentialsnot deniedwithin capallow winsAccessDenied — an implicit deny at any layer ends the chain hereA permissions boundary grants nothing on its own; it only subtracts. The effective permission is the intersection of every layer in this chain.
The layers are conjunctive, not additive. Widening the execution role changes nothing if the SCP or the boundary above it never granted the action in the first place.

Step-by-Step Implementation

1. Inventory Geospatial Assets and Access Patterns

Map every data source, transformation step, and destination your serverless functions interact with. Categorize resources by:

  • Storage topology: S3, GCS, Azure Blob, PostGIS, DynamoDB Streams
  • Data classification: public basemaps, proprietary LiDAR, PII-adjacent vector features
  • Operation type: s3:GetObject, storage.objects.get, kms:Decrypt, sqs:SendMessage

Document exact prefixes, bucket names, and resource ARNs. Wildcard usage (*) must be avoided in production geospatial pipelines. Use resource-level scoping such as arn:aws:s3:::gis-data/processing/*/ to restrict access to specific tenant or project directories.

python
import boto3
import json

def generate_resource_inventory(function_names: list[str]) -> dict:
    """
    Pull CloudTrail events for each function to build an access pattern inventory.
    Run this against a staging environment before writing boundary policies.
    """
    ct = boto3.client("cloudtrail", region_name="eu-west-1")
    lambda_client = boto3.client("lambda", region_name="eu-west-1")

    inventory = {}
    for name in function_names:
        fn = lambda_client.get_function_configuration(FunctionName=name)
        role_arn = fn["Role"]

        # Query last 90 days of data-plane events for this role
        events = ct.lookup_events(
            LookupAttributes=[
                {"AttributeKey": "Username", "AttributeValue": role_arn.split("/")[-1]}
            ],
            MaxResults=1000,
        )
        actions = {}
        for ev in events.get("Events", []):
            detail = json.loads(ev.get("CloudTrailEvent", "{}"))
            action = f"{detail.get('eventSource','').split('.')[0]}:{detail.get('eventName','')}"
            resource = detail.get("requestParameters", {}).get("bucketName", "") or \
                       detail.get("requestParameters", {}).get("queueUrl", "")
            actions.setdefault(action, set()).add(resource)

        inventory[name] = {
            "role_arn": role_arn,
            "observed_actions": {k: list(v) for k, v in actions.items()},
        }
    return inventory

2. Define Boundary Conditions and Policy Constraints

Translate access patterns into explicit policy constraints using cloud-native condition keys. Effective boundaries restrict not only what a function can access, but how, where, and when it can access it.

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowScopedGeoTIFFRead",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:HeadObject"],
      "Resource": "arn:aws:s3:::gis-raster-store/processing/${aws:PrincipalTag/TenantId}/*",
      "Condition": {
        "StringEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms",
          "aws:SourceVpce": "vpce-0a1b2c3d4e5f6g7h8"
        }
      }
    },
    {
      "Sid": "AllowOutputWrite",
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": "arn:aws:s3:::gis-raster-store/output/${aws:PrincipalTag/TenantId}/*",
      "Condition": {
        "StringEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms"
        }
      }
    },
    {
      "Sid": "DenyEverythingElseOnRasterStore",
      "Effect": "Deny",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::gis-raster-store",
        "arn:aws:s3:::gis-raster-store/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpce": "vpce-0a1b2c3d4e5f6g7h8"
        }
      }
    }
  ]
}

Note the ${aws:PrincipalTag/TenantId} tag substitution: attribute-based access control (ABAC) lets a single boundary policy enforce per-tenant isolation without a separate policy per customer.

3. Implement Least-Privilege Execution Roles

Execution roles define the baseline permissions granted to a serverless function at invocation. The boundary policy acts as a guardrail, but the execution role itself must also be tightly scoped — the effective permission is the intersection of both.

python
import boto3

iam = boto3.client("iam")

EXECUTION_ROLE_POLICY = {
    "Version": "2012-10-17",
    "Statement": [
        # Raster read: only the input prefix, only via KMS-encrypted path
        {
            "Sid": "GeoTIFFRead",
            "Effect": "Allow",
            "Action": ["s3:GetObject", "s3:HeadObject"],
            "Resource": "arn:aws:s3:::gis-raster-store/processing/*"
        },
        # Output write: only the output prefix
        {
            "Sid": "GeoTIFFWrite",
            "Effect": "Allow",
            "Action": ["s3:PutObject"],
            "Resource": "arn:aws:s3:::gis-raster-store/output/*"
        },
        # KMS decrypt for SSE-KMS objects
        {
            "Sid": "KMSDecrypt",
            "Effect": "Allow",
            "Action": ["kms:Decrypt", "kms:GenerateDataKey"],
            "Resource": "arn:aws:kms:eu-west-1:123456789012:key/mrk-abc123"
        },
        # Dead-letter queue for failed tile jobs
        {
            "Sid": "DLQPublish",
            "Effect": "Allow",
            "Action": ["sqs:SendMessage"],
            "Resource": "arn:aws:sqs:eu-west-1:123456789012:gis-tile-dlq"
        },
        # CloudWatch Logs — scoped to this function's log group
        {
            "Sid": "Logging",
            "Effect": "Allow",
            "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
            "Resource": "arn:aws:logs:eu-west-1:123456789012:log-group:/aws/lambda/gis-tiler:*"
        }
    ]
}

def create_gis_lambda_role(role_name: str, boundary_policy_arn: str) -> str:
    trust = {
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": {"Service": "lambda.amazonaws.com"},
            "Action": "sts:AssumeRole"
        }]
    }
    role = iam.create_role(
        RoleName=role_name,
        AssumeRolePolicyDocument=str(trust).replace("'", '"'),
        PermissionsBoundary=boundary_policy_arn,  # boundary attached at creation
        Tags=[
            {"Key": "ManagedBy", "Value": "terraform"},
            {"Key": "Workload", "Value": "geospatial-tiler"}
        ]
    )
    # Attach the inline policy — intersection with boundary determines effective perms
    iam.put_role_policy(
        RoleName=role_name,
        PolicyName="GeoTIFFProcessingPolicy",
        PolicyDocument=str(EXECUTION_ROLE_POLICY).replace("'", '"')
    )
    return role["Role"]["Arn"]

For Azure deployments, least-privilege IAM policies for Azure Blob geospatial access maps Azure RBAC built-in roles to specific container-level operations and shows how to prevent lateral movement across storage accounts using Microsoft.Authorization/roleAssignments/write deny assignments.

4. Enforce Boundaries via Organization-Level Controls

Attach IAM permissions boundaries to individual roles, then layer Service Control Policies (SCPs) or Organization Policies to restrict maximum allowable permissions across all accounts in the organization. SCPs do not grant permissions; they act as a ceiling that prevents any IAM principal from exceeding defined limits.

python
import boto3

org = boto3.client("organizations")

GIS_SCP = {
    "Version": "2012-10-17",
    "Statement": [
        # Mandatory KMS encryption on all S3 writes in the GIS OU
        {
            "Sid": "RequireKMSOnS3Writes",
            "Effect": "Deny",
            "Action": ["s3:PutObject"],
            "Resource": "*",
            "Condition": {
                "StringNotEqualsIfExists": {
                    "s3:x-amz-server-side-encryption": "aws:kms"
                },
                "Null": {
                    "s3:x-amz-server-side-encryption": "true"
                }
            }
        },
        # Block public ACL changes on any bucket in this OU
        {
            "Sid": "DenyPublicACL",
            "Effect": "Deny",
            "Action": [
                "s3:PutBucketAcl",
                "s3:PutObjectAcl",
                "s3:PutBucketPublicAccessBlock"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": [
                        "public-read", "public-read-write", "authenticated-read"
                    ]
                }
            }
        },
        # Restrict cross-account role assumption to approved GIS accounts
        {
            "Sid": "RestrictCrossAccountAssume",
            "Effect": "Deny",
            "Action": "sts:AssumeRole",
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "aws:PrincipalOrgID": "o-exampleorgid11"
                }
            }
        }
    ]
}

def attach_gis_scp(ou_id: str, policy_name: str = "GISSecurityBaseline") -> str:
    policy = org.create_policy(
        Content=str(GIS_SCP).replace("'", '"'),
        Description="Security baseline for serverless GIS processing OUs",
        Name=policy_name,
        Type="SERVICE_CONTROL_POLICY"
    )
    policy_id = policy["Policy"]["PolicySummary"]["Id"]
    org.attach_policy(PolicyId=policy_id, TargetId=ou_id)
    return policy_id

On GCP, the equivalent uses Organization Policy Constraints. The constraints/gcp.resourceLocations constraint restricts where geospatial data can reside (EU region enforcement), and constraints/iam.allowedPolicyMemberDomains limits who can be granted IAM bindings on GIS datasets.

5. Validate and Monitor Credential Lifecycle

Static policy definitions are insufficient without continuous validation. Implement automated policy linting in your CI/CD pipeline, then monitor credential usage at runtime.

python
import subprocess
import boto3
import json
from datetime import datetime, timedelta, timezone

def lint_iam_policy(policy_document: dict) -> list[str]:
    """
    Run policy_sentry to detect wildcard usage, missing conditions,
    or overly broad resource scopes.
    Requires: pip install policy-sentry
    """
    with open("/tmp/policy_to_lint.json", "w") as f:
        json.dump(policy_document, f)

    result = subprocess.run(
        ["policy_sentry", "analyze-policy-file", "--input-file", "/tmp/policy_to_lint.json"],
        capture_output=True, text=True
    )
    findings = []
    if result.returncode != 0:
        findings.extend(result.stdout.splitlines())
    return findings


def check_credential_expiry_alignment(function_name: str, region: str = "eu-west-1") -> dict:
    """
    Verify that STS credential duration does not exceed the function timeout.
    Credential expiry mid-execution causes partial writes and corrupted spatial outputs.
    """
    lambda_client = boto3.client("lambda", region_name=region)
    sts = boto3.client("sts", region_name=region)

    fn_config = lambda_client.get_function_configuration(FunctionName=function_name)
    timeout_seconds = fn_config["Timeout"]

    # Simulate a credential issuance and check remaining validity
    caller = sts.get_caller_identity()
    session_expiry = sts.assume_role(
        RoleArn=fn_config["Role"],
        RoleSessionName="credential-check",
        DurationSeconds=3600  # 1 hour minimum
    )["Credentials"]["Expiration"]

    # Expiry must exceed function timeout by at least 5 minutes (safety margin)
    expiry_dt = session_expiry.replace(tzinfo=timezone.utc)
    threshold = datetime.now(timezone.utc) + timedelta(seconds=timeout_seconds + 300)

    return {
        "function": function_name,
        "timeout_seconds": timeout_seconds,
        "credential_expires_at": expiry_dt.isoformat(),
        "alignment_ok": expiry_dt > threshold,
        "warning": None if expiry_dt > threshold else
                   f"Credential expires {(threshold - expiry_dt).seconds}s before function timeout safety margin"
    }

Key metrics to track in CloudWatch Logs Insights or GCP Audit Logs:

python
# CloudWatch Logs Insights — denied calls due to boundary violations
BOUNDARY_DENIAL_QUERY = """
fields @timestamp, errorCode, errorMessage, requestParameters.bucketName, userIdentity.arn
| filter errorCode = "AccessDenied"
| filter ispresent(userIdentity.sessionContext.sessionIssuer.arn)
| stats count() as denials by userIdentity.sessionContext.sessionIssuer.arn, eventName
| sort denials desc
| limit 20
"""

# Surface these metric alarms:
# AWS/IAM/AccessDeniedEventCount — spikes indicate misconfigured boundary
# AWS/Lambda/Errors — correlate with boundary denial timestamps
# AWS/KMS/InvalidKeyUsageCount — boundary not granting kms:Decrypt scope

Measurement and Verification

After deploying boundary policies, validate using the AWS IAM Policy Simulator or equivalent:

python
import boto3

iam = boto3.client("iam")

def simulate_boundary_effectiveness(
    policy_arn: str,
    role_arn: str,
    test_actions: list[str],
    resource_arn: str
) -> dict:
    """
    Simulate the intersection of a permissions boundary + execution role.
    Expected result for all GIS data actions: 'allowed' from VPC endpoint only.
    """
    response = iam.simulate_principal_policy(
        PolicySourceArn=role_arn,
        ActionNames=test_actions,
        ResourceArns=[resource_arn],
        ContextEntries=[
            {
                "ContextKeyName": "aws:SourceVpce",
                "ContextKeyValues": ["vpce-0a1b2c3d4e5f6g7h8"],
                "ContextKeyType": "string"
            },
            {
                "ContextKeyName": "s3:x-amz-server-side-encryption",
                "ContextKeyValues": ["aws:kms"],
                "ContextKeyType": "string"
            }
        ]
    )
    return {
        ev["EvalActionName"]: ev["EvalDecision"]
        for ev in response["EvaluationResults"]
    }

# Expected output:
# {
#   "s3:GetObject": "allowed",
#   "s3:PutObject": "allowed",
#   "s3:DeleteObject": "implicitDeny",
#   "s3:ListAllMyBuckets": "implicitDeny"
# }
Effective permissions as the intersection of boundary and execution roleA grid of five actions against three columns — what the permissions boundary allows, what the execution role allows, and the effective decision returned by simulate_principal_policy. Scoped GetObject and PutObject are allowed, DeleteObject and ListAllMyBuckets are implicit denies, kms:Decrypt is denied despite both layers allowing it because the key policy omits the role, and a read from outside the VPC endpoint is an explicit deny.What the simulator returns for the tiler rolePermissions boundaryExecution roleEffective decisions3:GetObject on /processing/*AllowAllowalloweds3:PutObject on /output/*AllowSSE-KMS requiredAllowalloweds3:DeleteObject on the raster storenot grantednot grantedimplicitDenykms:Decrypt on the tenant CMKAllowAllowdeniedkey policy omits the roles3:GetObject from outside the VPCendpointexplicit Denyaws:SourceVpce mismatchAllowdeniedRun this before deployment, not after: the simulator answers with the boundary attached, which is the only view that matches what the function willactually experience at runtime.
Two rows do not follow from the intersection at all: the KMS call needs the key policy to grant it separately, and the off-endpoint read is an explicit deny that no identity policy can override.

Failure Modes and Debugging

1. AccessDenied on kms:Decrypt despite the role having KMS permissions

Symptom: ClientError: An error occurred (AccessDenied) when calling the GetObject operation: The ciphertext refers to a customer master key that does not exist, does not exist in this region, or you are not allowed to access it.

Root cause: The KMS key policy (resource-based) must also grant the Lambda role kms:Decrypt. A permissions boundary allowing kms:Decrypt is necessary but not sufficient — the KMS key policy must grant it independently.

Fix: Add the Lambda execution role ARN to the KMS key policy’s Statement with kms:Decrypt and kms:GenerateDataKey actions. Key policies are evaluated separately from IAM identity policies.


2. Partial GeoTIFF write with ExpiredTokenException mid-execution

Symptom: CloudWatch logs show ExpiredTokenException 12–14 minutes into a 15-minute function. The output raster exists in S3 but is unreadable (truncated TIFF header).

Root cause: Memory and CPU allocation for raster workloads directly influences how long GDAL holds an open file handle. If the function runs close to its maximum timeout, the STS session token may expire before the final close() flushes the TIFF file descriptor.

Fix: Set DurationSeconds=3600 in the sts:AssumeRole call and ensure the function timeout is at least 5 minutes less than the credential validity period. On Lambda, use execution role credentials (rotated automatically every ~6 hours by the Lambda service) rather than manually assumed sessions.


3. SCP blocking Terraform deployments from CI/CD pipeline

Symptom: Terraform apply fails with An error occurred (AccessDeniedException) when calling the CreateRole operation.

Root cause: The SCP RestrictCrossAccountAssume statement may be blocking the CI/CD role (running in a tools account) from creating roles in the GIS processing account.

Fix: Add the CI/CD principal to the SCP NotPrincipal condition, or attach the SCP at the GIS workload OU rather than the root. Use aws:PrincipalOrgPaths rather than blanket denial to allow infrastructure accounts while still restricting workload roles.


4. GCP IAM Conditions silently not applying to inherited bindings

Symptom: A Cloud Function reads GeoTIFFs from a GCS bucket in a region that should be blocked by a resource.name condition.

Root cause: IAM Conditions are only evaluated on bindings at or below the resource where the condition is set. Bindings inherited from a parent (project → folder → org) do not inherit conditions attached at the child level.

Fix: Attach IAM Conditions at the organization or folder level, not just on the bucket binding. Use gcloud asset search-all-iam-policies to audit where the binding with the condition actually lives.


5. Policy drift when iam:PassRole is too broad

Symptom: A developer creates a new Lambda function with a permissive execution role, bypassing the approved boundary. CloudTrail shows iam:PassRole called with * as the resource.

Root cause: Without scoping iam:PassRole to specific role ARN patterns, any principal that can create Lambda functions can attach any role in the account, bypassing the boundary enforcement entirely.

Fix: Restrict iam:PassRole in the developer policy to arn:aws:iam::*:role/gis-*-execution-role and require that the boundary policy ARN is present on any role being passed. Enforce this with an SCP condition checking for the boundary attachment.


Cost and Scaling Considerations

IAM boundary enforcement itself has no direct per-call cost — policy evaluation is internal to the cloud provider’s authorization layer. The operational cost is in the tooling that validates and monitors boundary health.

CloudTrail data events (S3 GetObject, PutObject) add approximately $0.10 per 100,000 events. At typical geospatial processing volumes (1 million tile reads per day), expect $1–$3/day per active pipeline in audit log costs. This is non-negotiable for compliance workloads; for non-regulated environments, filter data events to only log Deny outcomes to reduce spend.

IAM Access Analyzer continuous analysis costs roughly $0.20 per policy per month after the first 100 policies. For organizations with dozens of GIS function roles, run analyzer findings on a nightly schedule in CI/CD rather than continuously.

Scale-out behavior: SCPs evaluate at API call time with no additional latency observable at the application layer. IAM Conditions with complex CEL expressions on GCP add negligible overhead — Google benchmarks show sub-millisecond condition evaluation even for multi-attribute expressions. The real scaling risk is policy size limits: AWS IAM policies are capped at 6,144 characters in a single inline policy and 20 managed policies per role. For large multi-tenant GIS platforms with hundreds of tenant-prefix rules, use ABAC with tag-based conditions rather than enumerating ARN suffixes in the policy.

Inline policy size and managed policy count against the IAM limitsThree meters against IAM limits: a boundary policy that enumerates one ARN prefix per tenant reaches 5,930 of the 6,144 characters allowed in an inline policy; the same policy rewritten with an attribute-based condition on the TenantId principal tag uses 1,180 characters; and the tiler role carries 6 of the 20 managed policies a role may hold.Policy size is a scaling limit, not a style questionTenant prefixes enumerated (40 tenants)one Resource statement per tenant5,930 chSame boundary rewritten with ABACone statement, PrincipalTag/TenantId1,180 chManaged policies on the tiler roleboundary, logging, KMS, DLQ, tracing, tags6 of 20The inline-policy cap is 6,144 characters and a role may reference 20 managed policies; both are per role, and neither grows with tenantcount once the boundary is expressed with tags.
Enumerating tenants makes the policy grow with the customer list and hit the 6,144-character wall at around 40 tenants. A tag condition is the same rule at constant size.

When integrating with SQS and Pub/Sub queue routing strategies for asynchronous geospatial job dispatch, scope sqs:SendMessage permissions to the specific dead-letter queue ARN and job queue ARN for each pipeline stage — not to a wildcard queue pattern — to prevent one pipeline stage from poisoning another’s queue.


Frequently Asked Questions

What is an IAM permissions boundary in AWS for a Lambda GIS function?

A permissions boundary is a managed IAM policy attached to a role that sets the maximum permissions the role can have. Even if the role’s identity policy grants broader access, the intersection with the boundary policy determines effective permissions. For geospatial Lambda functions this means a role with s3:* can be capped so it only operates on specific GIS bucket prefixes.

How do GCP IAM Conditions differ from AWS permissions boundaries?

GCP IAM Conditions attach directly to role bindings and are evaluated at request time using Common Expression Language (CEL). There is no separate “boundary” policy layer — conditions narrow the scope of a role binding to specific resources, time windows, or request attributes. AWS permissions boundaries are standalone policies referenced by the role definition itself.

Can a Service Control Policy override an IAM role’s explicit Allow?

Yes. AWS evaluates SCPs as a ceiling before evaluating identity-based policies. An explicit Allow in an execution role is still denied if the SCP does not permit that action. This makes SCPs a hard guardrail regardless of what individual developers configure on their roles.


Guides in this topic

Back to Serverless Geospatial Architecture & Platform Limits