Autentisering till MongoDB Atlas med AWS Outbound Identity Federation

2026-03-23
This post cover image
#aws
#cloud
#serverless
#iam
#mongodb

Questo file è stato tradotto automaticamente dall'IA, potrebbero verificarsi errori

Jag har skrivit flera inlägg om autentiserings- och auktoriseringsämnen, med PEP- och PDP-mönster. PEP och PDP för säker auktorisering med Cognito, sedan utökade jag det med Amazon Verified Permissions, och senare lade jag till ABAC på toppen. Allt detta handlade om användar-till-tjänst-auktorisering. Det vill säga en människa loggar in, får en token, och backend bestämmer vad de kan göra.

Men vad är det med tjänst-till-tjänst? Maskin-till-maskin? Din Lambda måste prata med en databas eller anropa en extern API, och det finns ingen människa i loopen. Du skulle fortfarande behöva credentier.

All källkod finns på Serverless Handbook.

Det vanliga sättet att hantera M2M-tokens

Ett av de mest gemensamma tillvägen jag ser är att använda Cognito User Pools, Auth0, eller liknande med OAuth 2.0-klientkredensierflödet. Du skapar en resursserver, registrerar en appklient med ett klient-ID och en hemlighet, och din Lambda utväxlar dessa mot en åtkomsttoken. Det fungerar bra, och om du redan kör Cognito för din användarautentisering känns det naturligt att utöka det för M2M också. Vad som bör komma ihåg är att M2M-tokens från dessa tjänster snabbt kan bli dyra.

Ett annat klassiskt tillväge är bara att lagra credentier i Secrets Manager. Databaslösenord, API-nycklar, vad du än behöver. Aktivera rotation och gå vidare. Det fungerar, men det är en sak till att hantera, en sak till som kan gå sönder, och en sak till som kan läcka.

Outbound Identity Federation

Strax före re:Invent 2025 kom en mycket intressant release, AWS IAM Outbound Identity Federation.

Vi har använt identitetsfederering i AWS under lång tid. Google, Okta, Entra, eller andra utfärdar en token, AWS litar på den, och du får tillgång till AWS-resurser. Det är inbound federering. AWS är den som litar.

Outbound Identity Federation är omvänd. Nu är AWS den som utfärdar token. Din Lambda (eller EC2, eller ECS-uppgift) anropar STS för att utväxla en IAM-roll mot en signerad JWT-token som i princip säger "hej, jag är denna IAM-roll, och här är en kryptografisk bevis." Sedan skickar du den token till vilken extern eller intern tjänst du pratar med, och de kan verifiera den med standardflöden.

Det betyder, inga klienthemligheter eller lagrade lösenord. Den utfärdade token är kortlivad, knuten till IAM-rollen, och signerad av AWS. Den mottagande tjänsten behöver bara lita på utfärdaren.

Vad finns i token?

När vi anropar sts:GetWebIdentityToken, får vi tillbaka en standard JWT-token, med några anspråk som är viktiga:

iss - Utfärdaren. Detta är Token Issuer URL som vi får när vi aktiverar funktionen, den ser ut så här "https://a1810f8a-5a75-4e21-b1cd-a6b09f1836cb.tokens.sts.global.api.aws". Den är unik för vårt AWS-konto och vi kan inte ändra den.
sub - Subjektet. Detta är IAM Role ARN. Det är identiteten.
aud - Publiken. Detta är inställt av oss, och det kan vara vad som helst, "my-api, "atlas-demo", vad som helst. Båda sidorna måste bara komma överens om värdet.
exp - Utgångstid. Kortlivad är hela poängen.

Arkitekturöversikt

I denna post kommer vi att bygga två olika lösningar som använder Outbound Identity Federation, bara för att visa hur man ställer in det för en intern tjänst, värdbunden på Lambda bakom API Gateway, och en extern databas, i detta fall MongoDB Atlas.

I vårt interna användningsfallet har vi en worker Lambda-funktion som kommer att anropa STS för att få en signerad outbound JWT-token. Funktionerna anropar en API i API Gateway där vi har en Lambda Authorizer som använder de offentliga nycklarna från STS för att validera JWT.

Bild som visar arkitekturöversikten

I MongoDB-fallet har vi pretty much samma sak. Vi etablerar förtroende mellan MongoDB och STS genom att göra någon konfiguration på MongoDB-sidan. Vår worker Lambda kommer att anropa STS för att få JWT och använda det som autentisering mot MongoDB, som använder de offentliga nycklarna etc från STS för att validera användaren. För MongoDB-användningsfallet måste vi mappa allt detta till en användare, men mer om den konfigurationen senare.

Bild som visar arkitekturöversikten med MongoDB

Nu, låt oss bygga!

Aktivera Outbound Identity Federation

Innan vi kan använda Outbound Identity Federation måste vi aktivera det, som standard är det inaktiverat. Så gå över till AWS-konsolen och IAM > Account Settings.

Bild som visar IAM-konsolen med Account Settings markerat

Bläddra ner till avsnittet Outbound Identity Federation och klicka bara på Aktivera.

Bild som visar avsnittet Outbound Identity Federation med knappen Aktivera

När det är aktiverat får du din kontospecifika Token Issuer URL direkt. Kopiera det, du kommer att behöva det senare.

Bild som visar federering aktiverad med Token Issuer URL

Du kan också aktivera det via CLI om du föredrar det:

aws iam enable-outbound-web-identity-federation

Nu låt oss prova detta och anropa STS för att få en token, vi kör kommandot nedan för att få en token för vår inloggade huvudman.

aws sts get-web-identity-token --audience "my-api" --signing-algorithm RS256

Vi kommer att få en svar likt:

{
    "WebIdentityToken": "Base64 Encoded Token",
    "Expiration": "2026-03-19T08:20:06.177000+00:00"
}

Om vi avkodar WebIdentityToken, med en webbsida som jwt.io kan vi se de olika anspråken.

{
  "aud": "my-api",
  "sub": "arn:aws:iam::<account_id><iam_role>",
  "https://sts.amazonaws.com/": {
    "org_id": "<AWS_Org_Id>",
    "aws_account": "<account_id>",
    "ou_path": [
      "...."
    ],
    "original_session_exp": "2026-03-19T10:15:04Z",
    "source_region": "eu-west-1",
    "principal_id": "<iam_principal>",
    "identity_store_user_id": "...."
  },
  "iss": "https://....tokens.sts.global.api.aws",
  "exp": 1773908406,
  "iat": 1773908106,
  "jti": "..."
}

Verifiera token med API Gateway

Med det här fungerande, låt oss bygga en enkel API som en Lambda-funktion anropar, där vi har en Lambda-authorizer som validerar token.

Vi börjar med att skapa kalla funktionen, detta är ganska enkelt, vi anropar STS för att få en token och presenterar den när vi anropar API:n.

import json
import logging
import os

import boto3
import requests

logger = logging.getLogger()
logger.setLevel(logging.INFO)

sts_client = boto3.client("sts")

API_ENDPOINT = os.environ["API_ENDPOINT"]
AUDIENCE = os.environ["AUDIENCE"]

def get_token():
    response = sts_client.get_web_identity_token(
        Audience=[AUDIENCE],
        DurationSeconds=300,
        SigningAlgorithm="RS256",
    )
    return response["WebIdentityToken"]


def handler(event, context):
    try:
        token = get_token()

        response = requests.get(
            API_ENDPOINT,
            headers={"Authorization": f"Bearer {token}"},
            timeout=10,
        )

        return {
            "statusCode": response.status_code,
            "body": json.dumps(
                {
                    "api_status_code": response.status_code,
                    "api_response": response.json() if response.ok else response.text,
                    "request_id": context.aws_request_id,
                }
            ),
        }
    except Exception as e:
        return {
            "statusCode": 500,
            "body": json.dumps(
                {
                    "message": "Error calling protected API",
                    "error": str(e),
                }
            ),
        }

Om vi tittar på get_token()-funktionen, är den död enkel, bara tre parametrar och klar. Vi behöver inte hämta credentier från någonstans. Lambda säger bara "ge mig en token för denna publik" och STS signerar en med Lambdas egen identitet.

Lambda Authorizer

Lambda Authorizer fäst vid API Gateway, detta är där det börjar bli riktigt intressant. Vad vi kommer att göra är att hämta de offentliga nycklarna från STS och sedan validera token med en standard JWT-bibliotek.

Vi skapar upptäckten med utfärdarens URL och lägger till /.well-known/openid-configuration Det säger oss var vi kan hitta de offentliga nycklarna och vad värdet för utfärdaren bör vara. Sedan skapar vi en PyJWKClient som hanterar hämtning och cachelagring av nycklar.

import json
import logging
import os
import urllib.request

import jwt

logger = logging.getLogger()
logger.setLevel(logging.INFO)

AUDIENCE = os.environ["AUDIENCE"]
ISSUER_URL = os.environ["ISSUER_URL"]

DISCOVERY_URL = f"{ISSUER_URL}/.well-known/openid-configuration"

discovery_doc = json.loads(urllib.request.urlopen(DISCOVERY_URL).read())
JWKS_URI = discovery_doc["jwks_uri"]
EXPECTED_ISSUER = discovery_doc["issuer"]

jwks_client = jwt.PyJWKClient(JWKS_URI)

def handler(event, context):
    try:
        token = extract_token(event)
        if not token:
            return generate_policy("Deny", event["methodArn"])

        signing_key = jwks_client.get_signing_key_from_jwt(token)

        decoded = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256"],
            audience=OIDC_AUDIENCE,
            issuer=EXPECTED_ISSUER,
        )

        return generate_policy(
            "Allow",
            event["methodArn"],
            principal_id=decoded.get("sub", "unknown"),
            context={"sub": decoded.get("sub", ""), "iss": decoded.get("iss", "")},
        )

    except jwt.ExpiredSignatureError:
        logger.warning("Token has expired")
    except jwt.InvalidAudienceError:
        logger.warning("Invalid audience claim")
    except jwt.InvalidIssuerError:
        logger.warning("Invalid issuer claim")
    except Exception as e:
        logger.error("Token verification failed: %s", e)

    return generate_policy("Deny", event["methodArn"])

jwt.decode()-funktionen är kärnan i allt och kommer att validera de faktiska tokenerna mot de förväntade värdena, och kasta ett undantag vid problem.

Om allt passerar, tillåter vi begäran och skickar vidare sub-anspråket så att backend vet vem som ringer.

Värt att notera är att det inte finns något AWS-specifikt med denna verifieringslogik. Detta är bara en standard JWT-tokenvalidering.

Autentisera till MongoDB Atlas

Nu låt oss lägga detta i ett verkligt användningsfallet, ansluta till en MongoDB Atlas-kluster med token från IAM Outbound Federation, vilket eliminerar behovet av att lagra lösenord i Secrets Manager.

Det finns en mycket viktig sak att notera. För att detta ska fungera måste du köra en MongoDB Atlas M10+ dedikerad kluster med MongoDB 7.0.11 eller högre. Workload Identity Federation stöds inte på fria eller delade tier-kluster (M0, M2, M5).

Idén är ganska enkel och straightforward. Vår Lambda-funktion anropar STS för att få en token, vi presenterar denna token till Atlas istället för ett användarnamn och ett lösenord. Atlas kommer att validera token, med standardflöden, och mappar IAM-rollen till en databasanvändare.

Ställ in Atlas

Det första steget nu är att konfigurera saker på MongoDB-sidan, logga in på ditt konto och navigera till Federation under Identity and Access.

Bild som visar MongoDB Federation-menyn

Under Federation väljer Identity Providers, och vi kommer att börja ställa in det.

Bild som visar MongoDB Federation start

På nästa skärm väljer Workload Identity så att vi kan börja lägga till detaljer.

Bild som visar MongoDB Workload Identity start

På konfigurationsskärmen fyller i name och description. issuer URL är den från AWS IAM-konsolen, som vi såg tidigare i denna post. För audience ställ in ett värde efter val, viktigt att det är samma på både MongoDB och AWS-konfig (när du anropar STS). Publiken måste matcha. För användaranspråk behåll standard sub

Bild som visar MongoDB identity Provider Config

Spara och avsluta din konfiguration och du bör presenteras med översiktsskärmen.

Bild som visar MongoDB identity Provider Config Completed

Nästa måste vi ansluta den identitetsleverantör vi precis skapat med vår organisation. Välj Organizations i menyn och börja ansluta.

Bild som visar MongoDB identity Provider Organization connection

I popup-dialogrutan väljer den leverantör vi precis skapat och klickar på Connect.

Bild som visar MongoDB identity Provider Organization connection popup-dialog

I nästa vy bör vi se översikten över anslutningen.

Bild som visar MongoDB identity Provider Organization connection completed

Distribuera testapplikation

Det var många steg, men nu kan vi distribuera vår testapplikation och sedan göra den sista konfigurationen i Atlas innan vi testar. Vi håller testinfrastrukturen minimal. Bara en Lambda-funktion som har sts:GetWebIdentityToken behörighet:

Resources:
  DatabaseOpFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Application}-database-op
      CodeUri: src/DatabaseOp/
      Handler: database_op.handler
      Environment:
        Variables:
          MONGODB_URI: !Ref MongoDbUri
          OIDC_AUDIENCE: !Ref OidcAudience
      Policies:
        - Statement:
            - Effect: Allow
              Action:
                - sts:GetWebIdentityToken
              Resource: "*"

Outputs:
  DatabaseOpFunctionRoleArn:
    Description: IAM Role ARN for the Lambda function (use this in Atlas OIDC role mapping)
    Value: !GetAtt DatabaseOpFunctionRole.Arn

Vi lägger till roll-ARN i utdata av stapeln, eftersom vi kommer att behöva det för den sista Atlas-delen. Koden är uppdelad i Lambda-handler och en klient som kommer att hantera anslutning till MongoDB-databasen.

Pymongo-drivrutinen har en stor callback-mekanism för OIDC. Vi ger den en klass, och drivrutinen anropar den när den behöver en token. Drivrutinen träffar fetch() vid första anslutningen och igen när token är på väg att gå ut. Vi ställer in värdet till 280 sekunder, vilket är lite under den faktiska 300, så det finns en buffert.

Att ställa in authMechanism="MONGODB-OIDC" säger till drivrutinen att hoppa över användarnamn/lösenord och använda OIDC istället.

import os
import boto3
from pymongo import MongoClient
from pymongo.auth_oidc import OIDCCallback, OIDCCallbackResult

sts_client = boto3.client("sts")
cached_client = None


def get_oidc_token():
    audience = os.environ["OIDC_AUDIENCE"]

    response = sts_client.get_web_identity_token(
        Audience=[audience],
        DurationSeconds=300,
        SigningAlgorithm="RS256",
    )

    return response["WebIdentityToken"]


class AwsOidcCallback(OIDCCallback):
    def fetch(self, context):
        token = get_oidc_token()
        return OIDCCallbackResult(access_token=token, expires_in_seconds=280)


def get_mongo_client():
    global cached_client

    if cached_client is not None:
        return cached_client

    uri = os.environ["MONGODB_URI"]

    properties = {"OIDC_CALLBACK": AwsOidcCallback()}

    cached_client = MongoClient(
        uri,
        authMechanism="MONGODB-OIDC",
        authMechanismProperties=properties,
    )

    return cached_client

Sedan i vår Lambda-handler skapar vi MongoDB-klienten och interagerar med databasen, i detta enkla exempel bara genom att infoga ett dokument i en items-samling.

def handler(event, context):
    try:
        client = get_mongo_client()
        db = client["sample_db"]
        collection = db["items"]

        document = {
            "message": "Hello from Lambda with OIDC auth",
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "request_id": context.aws_request_id,
        }

        result = collection.insert_one(document)
        doc = collection.find_one({"_id": result.inserted_id})

        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": "Successfully connected to MongoDB Atlas with OIDC",
                "insertedId": str(result.inserted_id),
            }),
        }
    except Exception as e:
        logger.error("Error: %s", e)
        return {"statusCode": 500, "body": json.dumps({"error": str(e)})}

Skapa en databasanvändare i Atlas

Nu måste vi göra det sista steget i Atlas, skapa en databasanvändare och mappning till IAM Role ARN, så att vi kan få behörigheter att interagera med databasen. Gå till Database Access och lägg till en ny databasanvändare.

Bild som visar MongoDB-användarskapande

Välj Federated Auth som autentiseringsmetod och ställ in användarnamn till Lambda-funktionens IAM Role ARN, hämta detta från stapelutdata. Tilldela användaren Read and write on any database behörigheter.

Bild som visar MongoDB-användarskapande slutfört

Den roll-ARN kommer att matcha sub-anspråket i JWT. Det är så Atlas vet vilken databasanvändare som ska användas.

Tillåtlistans nätverksåtkomst

Innan vi testar allt finns det en sak till vi måste göra, vi måste tillåtlistans nätverksåtkomst för vår Lambda-funktion. Eftersom vi inte kör i ett VPC måste vi tillåta 0.0.0.0/0 i en produktionsuppställning, gör inte detta, men för detta test är det bara bra.

Navigera till IP Access List i menyn

Bild som visar MongoDB nätverksåtkomst i menyn

Sedan lägg till 0.0.0.0/0, ställ in det som tillfälligt så att det tas bort automatiskt.

Bild som visar MongoDB IP Network tillåt

Det är det!! Vi kan nu använda Outbound Identity Federation i STS för att autentisera mot MongoDB Atlas och kan läsa och skriva data, inga lösenord, inga hemligheter att hantera!!

Testning

Tid att göra ett litet test, för att göra det bara anropa den distribuerade Lambda-funktionen. Sedan gå till Data Explorer i Atlas och verifiera att data finns där.

Bild som visar MongoDB-testdata

Avslutande ord

Jag gillar verkligen detta mönster. Inga lagrade hemligheter. Token som går ut om 5 minuter. Du vet exakt vilken IAM-roll som gjorde varje anslutning. Och det fungerar med allt som talar OIDC, inte bara MongoDB. Någon extern tjänst som kan verifiera OIDC-token kan använda exakt samma get_oidc_token()-funktion. Samma kod, olika destination.

Kolla in mina andra inlägg på jimmydqv.com och följ mig på LinkedIn och X för mer serverless-innehåll.

Nu gå och bygg!