Configurar um autorizador AWS Lambda para autenticação OIDC - AWS HealthImaging

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Configurar um autorizador AWS Lambda para autenticação OIDC

Este guia pressupõe que você já tenha configurado o provedor de identidade (IdP) de sua escolha para fornecer tokens de acesso compatíveis com os requisitos do recurso de autenticação HealthImaging do OIDC.

1. Configurar funções do IAM para acesso à DICOMWeb API

Antes de configurar o autorizador Lambda, crie funções do IAM HealthImaging para assumir ao DICOMWeb processar solicitações de API. A função autorizadora Lambda retorna uma dessas funções ARN após a verificação bem-sucedida do token, HealthImaging permitindo executar as solicitações com as permissões apropriadas.

  1. Crie políticas do IAM definindo os privilégios de DICOMWeb API desejados. Consulte a seção "Usando DICOMweb" da HealthImaging documentação para ver as permissões disponíveis.

  2. Crie funções do IAM que:

    • Anexe essas políticas

    • Inclua uma relação de confiança que permita que o diretor HealthImaging de serviço da AWS (medical-imaging.amazonaws.com) assuma essas funções.

Aqui está um exemplo de uma política que permite que funções associadas acessem a API HealthImaging DICOMWeb somente para leitura:

{ "Version": "2012-10-17", "Statement": [ { "Sid": "MedicalImagingDicomWebOperations", "Effect": "Allow", "Action": [ "medical-imaging:SearchDICOMInstances", "medical-imaging:GetImageSetMetadata", "medical-imaging:GetDICOMSeriesMetadata", "medical-imaging:SearchDICOMStudies", "medical-imaging:GetDICOMBulkdata", "medical-imaging:SearchDICOMSeries", "medical-imaging:GetDICOMInstanceMetadata", "medical-imaging:GetDICOMInstance", "medical-imaging:GetDICOMInstanceFrames" ], "Resource": "arn:aws:medical-imaging:{Region}:{Account}:datastore/{DatastoreId}" } ] }

Aqui está um exemplo da política de relacionamento de confiança que deve ser associada à (s) função (s):

{ "Version": "2012-10-17", "Statement": [ { "Sid": "OIDCRoleFederation", "Effect": "Allow", "Principal": { "Service": "medical-imaging.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }

O autorizador Lambda que você criará na próxima etapa pode avaliar as reivindicações de token e retornar o ARN da função apropriada. A AWS então HealthImaging representará essa função para executar a solicitação de DICOMWeb API com as permissões correspondentes.

Por exemplo:

  • Um token com declarações de “administrador” pode retornar um ARN para uma função com acesso total

  • Um token com declarações de “leitor” pode retornar um ARN para uma função com acesso somente de leitura

  • Um token com declarações “Department_A” pode retornar um ARN para uma função específica do nível de acesso desse departamento

Esse mecanismo permite que você mapeie o modelo de autorização do seu IdP para HealthImaging permissões específicas da AWS por meio de funções do IAM.

2. Criar e configurar a função Lambda Authorizer

Crie uma função Lambda que verificará o token JWT e retornará o ARN apropriado da função do IAM com base na avaliação das declarações do token. Essa função é invocada pelo serviço de imagens de saúde e transmitida por um evento que contém o ID do HealthImaging armazenamento de dados, a DICOMWeb operação e o token de acesso encontrados na solicitação HTTP:

{ "datastoreId": "{datastore id}", "operation": "{Healthimaging API name e.g. GetDICOMInstance}", "bearerToken": "{access token}" }

A função autorizadora do Lambda deve retornar uma resposta JSON com a seguinte estrutura:

{ "isTokenValid": {true or false}, "roleArn": "{role arn or empty string meaning to deny the request explicitly}" }

Você pode consultar o exemplo de implementação para obter mais informações.

nota

Como a DICOMWeb solicitação só é respondida depois que o token de acesso é verificado pelo autorizador lambda, é importante que a execução dessa função seja a mais rápida possível para fornecer o melhor tempo de resposta da DICOMWeb API.

Para que o HealthImaging serviço seja autorizado a invocar a função autorizadora lambda, ele deve ter uma política de recursos que permita que o HealthImaging serviço a invoque. Essa política de recursos pode ser criada no menu de permissões da guia de configuração lambda ou usando AWS CLI:

aws lambda add-permission \ --function-name YourAuthorizerFunctionName \ --statement-id HealthImagingInvoke \ --action lambda:InvokeFunction \ --principal medical-imaging.amazonaws.com

Essa política de recursos permite que o HealthImaging serviço invoque seu autorizador Lambda ao DICOMWeb autenticar solicitações de API.

nota

A política de recursos lambda pode ser atualizada posteriormente com uma condição "ArnLike" correspondente ao ARN de um HealthImaging armazenamento de dados específico.

Aqui está um exemplo da política de recursos lambda:

{ "Version": "2012-10-17", "Id": "default", "Statement": [ { "Sid": "LambaAuthorizer-HealthImagingInvokePermission", "Effect": "Allow", "Principal": { "Service": "medical-imaging.amazonaws.com" }, "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:{Region}:{Account}::function:{LambdaAuthorizerFunctionName}", "Condition": { "ArnLike": { "AWS:SourceArn": "arn:aws:medical-imaging:{Region}:{Account}:datastore/{DatastoreId}" } } } ] }

3. Crie um novo armazenamento de dados com a autenticação OIDC

Para habilitar a autenticação OIDC, você deve criar um novo armazenamento de dados usando o AWS CLI com o parâmetro "”. lambda-authorizer-arn A autenticação OIDC não pode ser habilitada nos armazenamentos de dados existentes sem entrar em contato com o Support. AWS

Veja um exemplo de como criar um novo armazenamento de dados com a autenticação OIDC ativada:

aws medical-imaging create-datastore \ --datastore-name YourDatastoreName \ --lambda-authorizer-arn YourAuthorizerFunctionArn

Você pode verificar se um armazenamento de dados específico tem o recurso de autenticação OIDC ativado usando o comando AWS CLI get-datastore e verificando se o atributo "" está presente: lambdaAuthorizerArn

aws medical-imaging get-datastore --datastore-id YourDatastoreId
{ "datastoreProperties": { "datastoreId": YourdatastoreId, "datastoreName": YourDatastoreName, "datastoreStatus": "ACTIVE", "lambdaAuthorizerArn": YourAuthorizerFunctionArn, "datastoreArn": YourDatastoreArn, "createdAt": "2025-09-30T14:16:04.015000-05:00", "updatedAt": "2025-09-30T14:16:04.015000-05:00" } }
nota

A função de execução do comando de criação do AWS CLI armazenamento de dados deve ter as permissões apropriadas para invocar a função autorizadora do Lambda. Isso atenua os ataques de escalonamento de privilégios em que usuários mal-intencionados podem executar funções não autorizadas do Lambda por meio da configuração do autorizador do armazenamento de dados.

Códigos de exceção

Em caso de falha na autenticação, HealthImaging retorna os seguintes códigos de resposta de erro HTTP e mensagens corporais:

Condição Resposta AHI
O Lambda Authorizer não existe ou é inválido 424 Configuração incorreta do autorizador
Autorizador encerrado devido a falha na execução 424 Falha no autorizador
Qualquer outro erro não mapeado do autorizador 424 Falha no autorizador
O autorizador retornou uma resposta inválida/mal formada 424 Configuração incorreta do autorizador
O autorizador foi executado por mais de 1s Tempo limite do autorizador 408
O token está expirado ou é inválido 403 Token inválido ou expirado
O AHI não pode federar a função IAM retornada devido à configuração incorreta do autorizador 424 Configuração incorreta do autorizador
O autorizador retornou uma função vazia 403 Acesso negado
A função retornada não pode ser chamada (configuração incorreta de assume-role/trust) 424 Configuração incorreta do autorizador
A taxa de solicitação excede os limites do DICOMweb gateway 4.29 Solicitações demais
Armazenamento de dados, função de retorno ou autorizador entre regiões Account/Cross 424 Autorizador: Acesso entre regiões Account/Cross

Exemplo de implementação

Este exemplo em Python demonstra uma função autorizadora lambda que verifica os tokens de acesso do Cognito a partir de eventos HealthImaging e retorna um AWS ARN de função do IAM com os privilégios apropriados. DICOMWeb

O autorizador Lambda implementa dois mecanismos de armazenamento em cache para reduzir as chamadas externas e a latência de resposta. O JWKS (JSON Web Key Set) é obtido uma vez a cada hora e armazenado na pasta temporária da função, permitindo que as invocações subsequentes da função o leiam localmente em vez de buscá-lo na rede pública. Você também notará que um objeto de dicionário token_cache é instanciado no contexto global dessa função Lambda. As variáveis globais são compartilhadas por todas as invocações que reutilizam o mesmo contexto aquecido do Lambda. Graças a isso, os tokens verificados com sucesso podem ser armazenados neste dicionário e pesquisados rapidamente durante a próxima execução dessa mesma função Lambda. O método de armazenamento em cache representa uma abordagem generalista que pode caber em tokens de acesso emitidos pela maioria dos provedores de identidade. Para uma opção de armazenamento em cache específica do AWS Cognito, consulte a seção Gerenciando grupos de usuários e a seção de armazenamento em cache da documentação do Cognito.AWS

import json import os import time import logging from jose import jwk, jwt from jose.exceptions import ExpiredSignatureError, JWTClaimsError, JWTError import requests import tempfile # Configure logging logger = logging.getLogger() log_level = os.environ.get('LOG_LEVEL', 'WARNING').upper() logger.setLevel(getattr(logging, log_level, logging.WARNING)) # Global token cache with TTL token_cache = {} # JWKS cache file path JWKS_CACHE_FILE = os.path.join(tempfile.gettempdir(), 'jwks.json') JWKS_CACHE_TTL = 3600 # 1 hour # Load environment variables once USER_POOL_ID = os.environ['USER_POOL_ID'] CLIENT_ID = os.environ['CLIENT_ID'] ROLE_ARN = os.environ.get('AHIDICOMWEB_READONLY_ROLE_ARN', '') def cleanup_expired_tokens(): """Remove expired tokens from cache""" now = int(time.time()) expired_keys = [token for token, data in token_cache.items() if now > data['cache_expiry']] for token in expired_keys: del token_cache[token] def get_cached_jwks(): """Get JWKS from cache file if valid, otherwise return None """ try: if os.path.exists(JWKS_CACHE_FILE): # Check if cache file is still valid cache_age = time.time() - os.path.getmtime(JWKS_CACHE_FILE) if cache_age < JWKS_CACHE_TTL: with open(JWKS_CACHE_FILE, 'r') as f: jwks = json.load(f) logger.debug(f'Using cached JWKS (age: {int(cache_age)}s)') return jwks else: logger.debug(f'JWKS cache expired (age: {int(cache_age)}s)') except Exception as e: logger.debug(f'Error reading JWKS cache: {e}') return None def cache_jwks(jwks): """Cache JWKS to file""" try: with open(JWKS_CACHE_FILE, 'w') as f: json.dump(jwks, f) logger.debug('JWKS cached successfully') except Exception as e: logger.debug(f'Error caching JWKS: {e}') def fetch_jwks(jwks_url): """Fetch JWKS from URL and cache it""" logger.debug('Fetching JWKS from URL') jwks = requests.get(jwks_url, timeout=10).json() # Convert to dict for faster lookups jwks['keys_by_kid'] = {key['kid']: key for key in jwks['keys']} cache_jwks(jwks) return jwks def is_token_cached(token): if token not in token_cache: return None cached = token_cache[token] now = int(time.time()) if now > cached['cache_expiry']: del token_cache[token] return None return cached def cache_token(token, payload): now = int(time.time()) token_exp = payload.get('exp') cache_expiry = min(now + 60, token_exp) # 1 minute or token expiry, whichever is sooner token_cache[token] = { 'payload': payload, 'cache_expiry': cache_expiry, 'role_arn': ROLE_ARN } def handler(event, context): cleanup_expired_tokens() # start be removing expired tokens from the cache try: # Extract token from bearerToken or authorizationToken field token = event.get('bearerToken') if not token: raise Exception('No token provided') # Check cache first cached = is_token_cached(token) if cached: logger.debug('Token found in cache, skipping verification') return { 'isTokenValid': True, 'roleArn': cached['role_arn'] } # Get Cognito configuration region = context.invoked_function_arn.split(':')[3] # Get JWKS (cached or fresh) jwks_url = f'https://cognito-idp.{region}.amazonaws.com/{USER_POOL_ID}/.well-known/jwks.json' jwks = get_cached_jwks() if not jwks: jwks = fetch_jwks(jwks_url) # Decode token header to get kid headers = jwt.get_unverified_headers(token) kid = headers['kid'] # Find the correct key key = None for jwk_key in jwks['keys']: if jwk_key['kid'] == kid: key = jwk_key break if not key: # Key not found - try refreshing JWKS in case of key rotation logger.debug('Key not found in cached JWKS, fetching fresh JWKS') jwks = fetch_jwks(jwks_url) for jwk_key in jwks['keys']: if jwk_key['kid'] == kid: key = jwk_key break if not key: raise Exception('Public key not found') # Construct the public key public_key = jwk.construct(key) # Verify and decode the token (includes expiry validation) payload = jwt.decode( token, public_key, algorithms=['RS256'], audience=CLIENT_ID, issuer=f'https://cognito-idp.{region}.amazonaws.com/{USER_POOL_ID}' ) logger.debug('Token validated successfully') logger.debug('User: %s', payload.get('username', 'unknown')) # Cache the validated token cache_token(token, payload) # Return authorization response return { 'isTokenValid': True, 'roleArn': ROLE_ARN } except ExpiredSignatureError: logger.debug('Token expired') return { 'isTokenValid': False, 'roleArn': '' } except JWTClaimsError: logger.debug('Invalid token claims') return { 'isTokenValid': False, 'roleArn': '' } except JWTError as e: logger.debug('JWT validation error: %s', e) return { 'isTokenValid': False, 'roleArn': '' } except Exception as e: logger.debug('Authorization failed: %s', e) return { 'isTokenValid': False, 'roleArn': '' }