Remarques :

Accéder en toute sécurité à Oracle Integration à l'aide de jetons d'accès à partir de l'ID Microsoft Entra

Introduction

Alors que les clients Oracle Integration adoptent des stratégies multicloud, ils ont souvent besoin de connecter des applications et des processus métier à différents fournisseurs cloud. Par exemple, une entreprise peut avoir une application exécutée sur Microsoft Azure qui a besoin d'accéder aux données des applications Oracle Cloud Infrastructure. Normalement, vous obtenez un jeton à partir d'Oracle Cloud Infrastructure Identity and Access Management (OCI IAM) pour extraire ces données. Cependant, l'utilisation de plusieurs fournisseurs de cloud implique de traiter plusieurs jetons, ce qui peut être complexe et poser des risques de sécurité.

Imaginez à quel point cela serait pratique si vous pouviez utiliser un jeton OAuth pour l'intégration avec des applications de différents fournisseurs cloud. Ce tutoriel traite de l'utilisation d'un fournisseur OAuth tiers pour appeler un flux Oracle Integration.

Architecture

Visualisons le flux de solution :

Image  1

Le processus commence par l'obtention par l'utilisateur ou l'application métier d'un jeton OAuth à partir de l'ID Microsoft Entra. Une fois acquis, ce jeton est utilisé pour appeler l'adresse exposée via OCI API Gateway. La passerelle d'API OCI, configurée pour utiliser un autorisateur personnalisé OCI Functions (anciennement appelé Oracle Functions), appelle d'abord cette fonction d'autorisation pour valider le jeton. Une fois la validation effectuée, il appelle l'adresse back-end réelle, qui est le flux Oracle Integration.

Maintenant, plongeons dans les détails de la mise en œuvre de ce processus. Pour plus de simplicité, nous allons le diviser en trois étapes :

Pourquoi utiliser les types d'octroi d'assertion Informations d'identification de mot de passe de propriétaire de ressource (ROPC) et Jeton Web JSON (JWT) respectivement pour obtenir un jeton d'accès à partir de l'ID Entra Microsoft et d'OCI IAM ?

L'utilisation conjointe des autorisations d'assertion ROPC et JWT offre une approche rationalisée et sécurisée pour gérer l'authentification et l'échange de jetons dans un environnement multicloud.

Public

Objectifs

Prérequis

Tâche 1 : enregistrer une application avec l'ID Microsoft Entra

Pour utiliser les fonctionnalités IAM de Microsoft Entra ID, y compris l'accès aux ressources protégées (API de graphes), vous devez inscrire une application.

  1. Inscrire une application. Pour plus d'informations, reportez-vous à Inscription d'une application auprès de la plate-forme d'identité Microsoft.

  2. Notez la valeur Application (client) ID de la section Présentation.

    Image  2

  3. Accédez à Gérer, Certificats et clés secrètes et ajoutez une clé secrète client. Notez la valeur de clé secrète telle qu'elle sera utilisée dans une tâche ultérieure.

    Image  3

Tâche 2 : étapes prérequises pour l'assertion utilisateur JWT dans le domaine d'identité OCI

  1. Effectuez les tâches prérequises à partir de cette page : Prérequis pour l'assertion utilisateur JWT.

  2. Une fois que l'application Oracle Integration a été validée pour les portées requises, des paires de clés autosignées sont générées et une application confidentielle est configurée. Notez la valeur de scope, private_key.pem, Client ID et Client Secret.

    Remarque : lors de l'import de la clé privée en tant que partenaire sécurisé dans l'application confidentielle, utilisez la même valeur alias que celle utilisée lors de la création des paires de clés auto-signées et notez la valeur alias pour les tâches ultérieures.

  3. Créez un groupe dynamique pour autoriser le type de ressource function à partir d'un compartiment spécifique à lire les clés secrètes à partir du service OCI Vault.

    Image  6

Tâche 3 : création des clés secrètes dans OCI Vault

Utilisez l'option de génération manuelle de clé secrète OCI Vault pour stocker les clés secrètes collectées à partir des tâches 1 et 2. Pour plus d'informations, reportez-vous à Création d'une clé secrète dans un coffre.

Image  4

Une fois les clés secrètes créées, copiez la valeur d'OCID à partir de la section Informations sur la clé secrète et stockez-la pour les tâches ultérieures.

Image  5

Tâche 4 : création et configuration du fichier func.py

Nous utiliserons OCI Functions en tant qu'autorisateur personnalisé pour valider le jeton d'accès ID Entra Microsoft et générer le jeton d'accès OCI IAM en tant que back_end_token.

  1. Pour commencer, créez une application. Dans OCI Functions, une application est un regroupement logique de fonctions. Les propriétés que vous spécifiez pour une application déterminent l'allocation et la configuration des ressources pour toutes les fonctions de cette application. Pour plus d'informations, reportez-vous à Création d'applications.

  2. Une fois l'application créée, ajoutez la configuration à l'application. Nous allons récupérer les éléments suivants à partir de notre code de fonction, le rendant plus portable et configurable sans modifier le code. Entrez une clé et une valeur, puis cliquez sur +.

    Ajoutez les ID client à partir de l'ID Entra Microsoft, des domaines d'identité OCI, l'OCID des clés secrètes collectées dans la tâche 3, l'alias, la portée collectée à partir de la tâche 2 et une adresse de graphique https://graph.microsoft.com/v1.0/me sur laquelle le jeton d'ID Entra microsoft sera validé.

    Image  7

  3. To create the function, go to Getting Started and click Launch OCI Cloud Shell to open the interactive Linux style cloud shell in your browser. Une fois qu'OCI Cloud Shell est chargé, vous pouvez créer, développer et déployer la fonction Oracle d'autorisation personnalisée directement à partir d'OCI Cloud Shell.

  4. Pour créer la fonction à l'aide de l'interface de ligne de commande du projet Fn, entrez la commande suivante pour une fonction Python fn init --runtime python MyCustomAuthorizer et cliquez sur Entrée.

    Image  8

  5. La feuille de calcul de la fonction est créée. Elle peut désormais être modifiée en conséquence pour inclure la logique d'autorisation personnalisée. Accédez au répertoire de la fonction et modifiez le fichier func.py. Copiez et collez le fragment de code suivant.

    Image  9

    import io
    import json
    import logging
    import jwt
    import datetime
    from datetime import timedelta
    import time
    import base64
    
    from fdk import response
    import requests
    from requests.auth import HTTPBasicAuth
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.backends import default_backend
    import ociVault
    
    oauth_apps = {}
    
    def initContext(context):
        # This method takes elements from the Application Context and from OCI Vault to create the OAuth App Clients object.
        if (len(oauth_apps) < 2):
            try:
                logging.getLogger().info("initContext: Initializing context")
    
                oauth_apps['idcs'] = {'introspection_endpoint': context['idcs_token_endpoint'],
                                    'client_id': context['idcs_app_client_id'],
                                    'scope':context['idcs_oauth_scope'],
                                    'alias':context['alias'],
                                    'client_secret': ociVault.getSecret(context['idcs_client_secret_ocid'])}
                oauth_apps['AD'] = {'token_endpoint': context['ad_endpoint'],
                                    'client_id': context['ad_app_client_id'],
                                    'client_secret': ociVault.getSecret(context['ad_client_secret_ocid'])}
    
            except Exception as ex:
                logging.getLogger().error("initContext: Failed to get config or secrets" + str(ex))
                raise
    
    
    def getAuthContext(token, client_apps):
        # This method populates the Auth Context that will be returned to the gateway.
        auth_context = {}
        access_token = token[len('Bearer '):]
        jwtToken = json.loads(json.dumps(jwt.decode(access_token, options={"verify_signature": False})))
        # Calling MSFT to validate the token
        try:
        logging.getLogger().info("getAuthContext: Calling Token Introspection function") 
        respIntrospectToken = introspectToken(access_token, client_apps['AD']['token_endpoint'], client_apps['AD']['client_id'], client_apps['AD']['client_secret'])
        except Exception as ex:
                logging.getLogger().error("getAuthContext: Failed to introspect token" + str(ex))
                raise
    
        # If AD confirmed the token valid and active, we can proceed to populate the auth context
        if (respIntrospectToken.status_code == 200):
            auth_context['active'] = True
            auth_context['principal'] = jwtToken['upn']
            auth_context['scope'] = 'https://graph.microsoft.com/.default'
            # Retrieving the back-end Token
            backend_token = getBackEndAuthToken(client_apps['idcs']['introspection_endpoint'], client_apps['idcs']['client_id'], client_apps['idcs']['client_secret'],client_apps['idcs']['scope'],client_apps['idcs']['alias'],auth_context['principal'])
    
            # The maximum TTL for this auth is the lesser of the API Client Auth (Entra ID) and the Gateway Client Auth (OCI IAM)
            if (datetime.datetime.fromtimestamp(jwtToken['exp']) < (datetime.datetime.utcnow() + timedelta(seconds=backend_token['expires_in']))):
                auth_context['expiresAt'] = (datetime.datetime.fromtimestamp(jwtToken['exp'])).replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
            else:
                auth_context['expiresAt'] = (datetime.datetime.utcnow() + timedelta(seconds=backend_token['expires_in'])).replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
            # Storing the back_end_token in the context of the auth decision so we can map it to Authorization header using the request/response transformation policy
            auth_context['context'] = {'back_end_token': ('Bearer ' + str(backend_token['access_token']))}
    
        else:
            # API Client token is not active, so we will go ahead and respond with the wwwAuthenticate header
            auth_context['active'] = False
            auth_context['wwwAuthenticate'] = 'Bearer realm=\"identity.oraclecloud.com\"'
    
        return(auth_context)
    
    def introspectToken(access_token, introspection_endpoint, client_id, client_secret):
        # This method simply invokes the introspection api as configured in the configuration screen.  
        # The real validation happens in the getAuthContext function.  
        #payload = {'token': access_token}
        headers = {'Accept': 'application/json',
                'Authorization':'Bearer '+access_token}
        try:
            logging.getLogger().info("introspectToken: Introspecting Token") 
            resp = requests.get(introspection_endpoint,
                                headers=headers)
            print(resp)
    
        except Exception as ex:
            logging.getLogger().error("introspectToken: Failed to introspect token" + str(ex))
            raise
    
        return resp
    
    def getBackEndAuthToken(token_endpoint, client_id, client_secret, scope, alias, principal):
        # This method gets the token from the back-end system (ORDS in this case)
        try:
            logging.getLogger().info("getBackEndAuthToken: Getting Backend Token") 
            print("Sub is " + principal)
    
            with open("private_key.pem", "rb") as key_file:
                private_key = serialization.load_pem_private_key(
                    key_file.read(),
                    password=None,
                    backend=default_backend()
                )
    
            headers = {
                "alg": "RS256",
                "typ": "JWT",
                "kid": "abc"
            }
    
            claims = {
                "sub": principal,
                "aud": "https://identity.oraclecloud.com/",
                "iss": client_id,
                "iat": int(time.time()),
                "exp": int(time.time()) + 3600,  # 1 hour expiration
                "jti": "8c7df446-bfae-40be-be09-0ab55c655436"  # random number
            }
    
            logging.getLogger().info("Claims : ")
            logging.getLogger().info(claims) 
    
            jwt_assertion = jwt.encode(
                payload=claims,
                key=private_key,
                algorithm="RS256",
                headers=headers
            )
            logging.getLogger().info("Assertion is :") 
            logging.getLogger().info(jwt_assertion) 
    
            encoded = client_id + ":" + client_secret
            baseencoded = base64.urlsafe_b64encode(encoded.encode('UTF-8')).decode('ascii')
            payload = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                    'scope':scope, 'assertion':jwt_assertion}
            headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'Authorization': 'Basic %s' % baseencoded, 'Accept': '*/*'}
            backend_token = json.loads(requests.post(token_endpoint,
                                                    data=payload,
                                                    headers=headers).text)
            logging.getLogger().info("Backend token in generated :") 
            logging.getLogger().info(backend_token) 
    
        except Exception as ex:
            logging.getLogger().error("getBackEndAuthToken: Failed to get ORDS token" + str(ex))
            raise
    
        return backend_token
    
    
    
    def handler(ctx, data: io.BytesIO = None):
        initContext(dict(ctx.Config()))
        logging.getLogger().info(oauth_apps)
    
        auth_context = {}
        try:
            logging.getLogger().info("handler: Started Function Execution") 
            gateway_auth = json.loads(data.getvalue())
            auth_context = getAuthContext(gateway_auth['token'], oauth_apps)
            if (auth_context['active']):
                logging.getLogger().info('Authorizer returning 200...')
                return response.Response(
                    ctx,
                    response_data=json.dumps(auth_context),
                    status_code = 200,
                    headers={"Content-Type": "application/json"}
                    )
            else:
                logging.getLogger().info('Authorizer returning 401...')
                return response.Response(
                    ctx,
                    response_data=json.dumps(str(auth_context)),
                    status_code = 401,
                    headers={"Content-Type": "application/json"}
                    )
    
        except (Exception, ValueError) as ex:
            logging.getLogger().info('error parsing json payload: ' + str(ex))
    
            return response.Response(
                ctx,
                response_data=json.dumps(str(auth_context)),
                status_code = 500,
                headers={"Content-Type": "application/json"}
                )
    
    
    • Importations

      • io, json, logging, datetime, time, base64 : bibliothèques Python standard pour la gestion des E/S, des données JSON, de la journalisation, des opérations de date et d'heure et du codage base64.
      • jwt : bibliothèque de codage et de décodage des jetons Web JSON (JWT).
      • requests : bibliothèque permettant d'effectuer des demandes HTTP.
      • HTTPBasicAuth : classe de gestion de l'authentification de base HTTP.
      • serialization, default_backend : à partir de la bibliothèque de cryptographie, utilisée pour gérer les opérations cryptographiques.
      • ociVault : module personnalisé permettant d'interagir avec OCI Vault.
    • Variable globale

      • Dictionnaire oauth_apps: pour stocker la configuration de l'application.
    • Functions

      • initContext(context): Cette fonction a pour but d'initialiser la configuration de l'application à l'aide des données de contexte et des clés secrètes d'OCI Vault. Il reçoit un objet de dictionnaire de contexte qui est appelé en tant que première chose dans la méthode de gestionnaire principale et extrait les clés secrètes d'OCI Vault à l'aide de la fonction getSecret() expliquée dans la tâche 5.

      • getAuthContext(token, client_apps): remplit et renvoie le contexte d'authentification pour OCI API Gateway. Extrait et décode le jeton d'accès. Appelle la fonction introspectToken() pour valider le jeton avec l'ID Entra. Si le jeton est valide, définit le contexte d'authentification, appelle la fonction getBackEndAuthToken() pour extraire le jeton back-end à partir d'OCI IAM et définit le délai d'expiration. Si le jeton n'est pas valide, définit l'en-tête wwwAuthenticate pour indiquer une erreur d'authentification.

      • introspectToken(access_token, introspection_endpoint, client_id, client_secret): valide le jeton avec la valeur introspection_endpoint fournie. Fait une demande GET à l'adresse d'introspection avec le jeton. Renvoie la réponse de l'adresse d'introspection ou de validation. Etant donné que l'ID Entra Microsoft ne dispose pas de l'adresse d'API d'introspection OAuth, nous appelons l'adresse configurée à l'aide du jeton reçu en entrée.

      • getBackEndAuthToken(token_endpoint, client_id, client_secret, scope, alias, principal): Charge la clé privée à partir d'un fichier PEM. Crée des revendications JWT et les encode dans une assertion JWT. Prépare la charge utile et les en-têtes pour la demande de jeton. Fait une demande POST à l'adresse de jeton pour obtenir le jeton back-end et renvoie le jeton back-end à la fonction getAuthContext().

      • handler(ctx, data: io.BytesIO = None): Fonction principale qui gère l'exécution de la fonction. Initialise le contexte OAuth à l'aide de la fonction initContext() et appelle la fonction getAuthContext() pour obtenir le contexte d'authentification. Renvoie une réponse 200 si le jeton est valide, sinon renvoie une réponse 401. Consigne et renvoie une réponse 500 en cas d'erreur.

Tâche 5 : création et configuration du fichier ociVault.py

Créez un fichier ociVault.py dans le même dossier et collez le fragment de code suivant. Cette fonction utilitaire lit les clés secrètes à partir du service OCI Vault.

# Utility Function to get secrets from OCI Vault
import logging
import oci
import base64

def getSecret(ocid):
    signer = oci.auth.signers.get_resource_principals_signer()
    try:
        client = oci.secrets.SecretsClient({}, signer=signer)
        secret_content = client.get_secret_bundle(ocid).data.secret_bundle_content.content.encode('utf-8')
        decrypted_secret_content = base64.b64decode(secret_content).decode('utf-8')
    except Exception as ex:
        logging.getLogger().error("getSecret: Failed to get Secret" + ex)
        print("Error [getSecret]: failed to retrieve", ex, flush=True)
        raise
    return decrypted_secret_content

Remarque : conservez le fichier private_key.pem de la tâche 2 dans le même dossier.

Image  10

Tâche 5 : tester la fonction

Afin de tester la fonction, nous devons déployer la fonction, puis l'appeler en transmettant le jeton Microsoft Entra ID comme entrée.

  1. Accédez au dossier de fonctions et exécutez la commande suivante fn -v deploy --app MyCustomAuthorizer pour le déployer. La commande d'interface de ligne de commande du projet Fn créera la fonction et la déploiera sur l'application OCI Functions.

    Image  11

    Remarque : incluez fdk>=0.1.74, requests, oci, pyjwt, serialization dans le fichier requirements.txt avant de déployer l'application Functions.

  2. Générez un jeton d'accès à partir de l'ID Microsoft Entra à l'aide du flux ROPC OAuth 2.0 à l'aide du client Postman.

    Image  12

  3. Notez le jeton d'accès pour générer un élément payload.json, qui sera transmis en tant qu'entrée pour tester les fonctions OCI. Conservez le fichier JSON dans le même répertoire de fonctions.

    Image  13

  4. Une fois la charge utile enregistrée, vous pouvez exécuter la commande suivante pour imiter l'exécution de la fonction telle qu'elle sera appelée via OCI API Gateway, cat payload.json | fn invoke <AppName> <function name>, comme indiqué dans l'image suivante.

    Image  14

    Si le jeton d'ID Microsoft Entra est valide, vous verrez une réponse comme indiqué dans l'image suivante, où vous verrez la valeur de jeton OCI IAM dans la valeur back_end_token de la structure de contexte.

Tâche 6 : configuration d'OCI API Gateway

OCI API Gateway est une plate-forme de gestion d'API cloud native entièrement gérée et évolutive qui offre une suite de services allant du déploiement rapide d'API à la gestion du cycle de vie et à l'intégration de services back-end. Nous tirerons parti de la passerelle d'API pour négocier l'autorisation pour Oracle Integration à l'aide d'un fournisseur d'identités externe tel que Microsoft Entra ID.

Commencez par créer une passerelle d'API, puis créez un déploiement sur la passerelle d'API.

  1. Accédez à Services de développeur, à Gestion des API et à Passerelles. Entrez les informations suivantes et cliquez sur Créer une passerelle.

    Image  15

    Image  16

  2. Sur la page Détails de la passerelle, cliquez sur Créer un déploiement et entrez les informations requises suivantes pour le déploiement d'API.

    • Nom : entrez un nom.
    • Préfixe de chemin : définissez le chemin.
    • Compartiment : sélectionnez le compartiment approprié pour votre déploiement d'API.

    Image  17

  3. Ajoutez les détails de la stratégie d'authentification. C'est là que vous configurez OCI Functions, qui doit être appelé en tant qu'autorisateur personnalisé. Sélectionnez la fonction créée dans la tâche 4.

    Image  18

  4. Sur la page Routages, configurez le routage d'API vers le service back-end. Dans ce tutoriel, nous allons définir le routage vers l'adresse Oracle Integration.

    Image  19

  5. Cliquez sur Afficher les stratégies de demande de routage. C'est là que l'utilisateur effectuera le swap de jetons d'authentification à partir de la réponse OCI Functions vers l'en-tête d'authentification de la demande.

    Image  20

    Cette étape consiste à définir le jeton d'authentification pour le service back-end en fonction du fournisseur d'identités back-end. Dans notre scénario, nous définissons le jeton de support pour OCI IAM tel que reçu de l'autorisateur personnalisé OCI Functions. Ici, nous configurons l'en-tête d'autorisation à remplacer par la valeur ${request.auth[back_end_token]}. back_end_token fait partie du contexte dans la structure de réponse de la fonction Oracle. Assurez-vous que cette expression est évaluée une fois que l'autorisation personnalisée OCI Functions est terminée.

  6. Après avoir vérifié la configuration, cliquez sur Enregistrer les modifications pour enregistrer le déploiement et attendre que l'état du déploiement passe à Actif.

    Image  21

    Après avoir activé le déploiement d'API, copiez l'adresse (URL de base) à partir de la section Informations sur le déploiement. Cette URL sert d'adresse pour le déploiement, où le processus métier ou l'application appellera les adresses Oracle Integration à l'aide du jeton de porteur d'ID Entra Microsoft. Nous allons utiliser l'URL de base dans la tâche suivante.

    Image  22

Tâche 7 : tester l'API

Commencez par obtenir un jeton d'accès à partir de l'ID Microsoft Entra à l'aide du client Postman. Nous utiliserons le flux ROPC pour nous assurer que le jeton d'accès inclut les informations d'identité nécessaires.

  1. Copiez le jeton d'accès tel que nous l'utiliserons lors de l'appel de l'API à partir de la passerelle d'API.

    Image  12

  2. Créez une demande REST combinant l'URL d'adresse de base copiée dans la tâche 6 à partir de la passerelle d'API et de l'adresse Oracle Integration, comme indiqué dans l'image suivante. Utilisez le jeton de porteur dans l'en-tête de demande.

    Image  23

  3. Cliquez sur Envoyer pour appeler la demande d'API. Elle exécutera Oracle Integration et produira une sortie.

    Image  24

Etapes suivantes

Nous avons appelé l'API sur Oracle API Gateway à l'aide d'un jeton OAuth provenant de l'ID Microsoft Entra, recevant une réponse du flux de déclencheur REST Oracle Integration. Cette intégration est cruciale pour les clients qui connectent des services numériques à différents fournisseurs de cloud.

Accusés de réception

Ressources de formation supplémentaires

Explorez d'autres ateliers sur docs.oracle.com/learn ou accédez à d'autres contenus de formation gratuits sur le canal Oracle Learning YouTube. De plus, visitez le site education.oracle.com/learning-explorer pour devenir un explorateur Oracle Learning.

Pour obtenir la documentation produit, consultez le site Oracle Help Center.