Note :

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

Présentation

Comme les clients d'Oracle Integration adoptent des stratégies multinuages, ils ont souvent besoin de connecter des applications et des processus d'affaires de différents fournisseurs de services en nuage. Par exemple, une société peut avoir une application s'exécutant sur Microsoft Azure qui doit 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 le traitement de plusieurs jetons, qui peuvent être complexes et poser des risques de sécurité.

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

Architecture

Visualisons le flux de la solution :

Image 1

Le processus commence par l'obtention par l'utilisateur ou l'application d'affaires d'un jeton OAuth à partir de l'ID Microsoft Entra. Une fois acquis, ce jeton est utilisé pour appeler le point d'extrémité exposé au moyen de la passerelle d'API OCI. La passerelle d'API OCI, configurée pour utiliser un service des fonctions pour OCI d'autorisation personnalisée (anciennement Oracle Functions), appelle d'abord cette fonction d'autorisation pour valider le jeton. Une fois la validation réussie, il appelle ensuite le point d'extrémité dorsal réel, qui est le flux Oracle Integration.

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

Pourquoi utiliser les types d'autorisation d'assertion Données d'identification par mot de passe du responsable de la ressource (ROPC) et Jeton Web JSON (JWT) respectivement pour obtenir un jeton d'accès à partir de Microsoft Entra ID et d'OCI IAM?

L'utilisation conjointe des droits d'assertion ROPC et JWT fournit une approche simplifiée et sécurisée pour gérer l'authentification et l'échange de jetons dans un environnement multinuage.

Public cible

Objectifs

Préalables

Tâche 1 : Enregistrer une application avec Microsoft Entra ID

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

  1. Enregistrer une application. Pour plus d'informations, voir Enregistrer une application sur la plate-forme d'identités Microsoft.

  2. Notez la valeur Application (client) ID dans la section Aperçu.

    Image 2

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

    Image 3

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

  1. Effectuez les tâches préalables à partir d'ici : Préalables pour l'assertion d'utilisateur JWT.

  2. Une fois l'application Oracle Integration validée pour les portées requises, des paires de clés auto-signées sont générées et une application confidentielle est configurée. Notez la valeur portée, private_key.pem, ID client et Clé secrète client.

    Note : Lors de l'importation de la clé privée en tant que partenaire approuvé dans l'application confidentielle, utilisez la même alias que celle utilisée lors de la création des paires de clés auto-signées et notez 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 du service de chambre forte OCI.

    Image 6

Tâche 3 : Créer les clés secrètes dans la chambre forte OCI

Utilisez l'option Génération manuelle de clé secrète pour la chambre forte OCI pour stocker les clés secrètes collectées à partir des tâches 1 et 2. Pour plus d'informations, voir Création d'une clé secrète dans une chambre forte.

Image 4

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

Image 5

Tâche 4 : Créer et configurer le fichier func.py

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

  1. Pour commencer, créez une application. Dans le service des fonctions OCI, 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, voir Création d'applications.

  2. Une fois l'application créée, ajoutez-la à l'application. Nous allons extraire les éléments suivants 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 Microsoft Entra, des domaines d'identité OCI, de l'OCID des clés secrètes collectées dans la tâche 3, de l'alias, de l'étendue collectée dans la tâche 2 et d'un point d'extrémité de graphique https://graph.microsoft.com/v1.0/me sur lequel le jeton microsoft Entra ID sera validé.

    Image 7

  3. Pour créer la fonction, allez à Démarrage et cliquez sur Lancer OCI Cloud Shell pour ouvrir l'interpréteur de commandes en nuage de style Linux interactif dans votre navigateur. Une fois OCI Cloud Shell 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 Fn Project, entrez la commande suivante pour une fonction Python fn init --runtime python MyCustomAuthorizer et cliquez sur Entrée.

    Image 8

  5. La plate-forme standard de la fonction est créée. Elle peut maintenant être modifiée en conséquence pour inclure la logique d'autorisation personnalisée. Accédez au répertoire des fonctions et modifiez le fichier func.py. Copiez et collez l'extrait 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 le traitement des E/S, des données JSON, de la journalisation, des opérations de date et d'heure et de l'encodage base64.
      • jwt : Bibliothèque pour l'encodage et le décodage de jetons Web JSON (JWT).
      • requests : Bibliothèque permettant d'effectuer des demandes HTTP.
      • HTTPBasicAuth : Classe pour le traitement 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é pour l'interaction avec la chambre forte OCI.
    • Variable globale

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

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

      • getAuthContext(token, client_apps): Alimente et retourne le contexte d'authentification pour la passerelle d'API OCI. 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 dorsal du service IAM pour OCI et définit l'heure 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 à l'aide de introspection_endpoint fourni. Fait une demande GET au point d'extrémité d'introspection avec le jeton. Retourne la réponse du point d'extrémité d'introspection ou de validation. Comme l'ID Microsoft Entra n'a pas de point d'extrémité d'API d'introspection OAuth, nous appelons le point d'extrémité configuré à l'aide du jeton reçu en tant qu'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 les données utiles et les en-têtes pour la demande de jeton. Effectue une demande POST au point d'extrémité du jeton pour obtenir le jeton dorsal et retourne le jeton dorsal à 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. Retourne une réponse 200 si le jeton est valide, sinon retourne une réponse 401. Enregistre et retourne une réponse 500 en cas d'erreur.

Tâche 5 : Créer et configurer le fichier ociVault.py

Créez un fichier ociVault.py dans le même dossier et collez l'extrait de code suivant. Cette fonction d'utilitaire lit les clés secrètes du service de chambre forte OCI.

# 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

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

Image 10

Tâche 5 : Tester la fonction

Pour tester la fonction, nous devons déployer la fonction, puis l'appeler en transmettant le jeton Microsoft Entra ID en tant qu'entrée.

  1. Naviguez jusqu'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 Fn Project va créer la fonction et la déployer sur l'application du service des fonctions pour OCI.

    Image 11

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

  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 une valeur payload.json, qui sera transmise en tant qu'entrée pour tester le service des fonctions pour OCI. Conservez le fichier JSON dans le même répertoire de fonctions.

    Image 13

  4. Une fois les données utiles enregistrées, vous pouvez exécuter la commande suivante pour imiter l'exécution de la fonction, car elle sera appelée au moyen de la passerelle d'API OCI, cat payload.json | fn invoke <AppName> <function name>, comme illustré dans l'image suivante.

    Image 14

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

Tâche 6 : Configurer la passerelle d'API OCI

La passerelle d'API OCI est une plate-forme de gestion d'API en nuage 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 des services dorsaux. Nous tirerons parti de la passerelle d'API pour médier l'autorisation d'Oracle Integration à l'aide d'un fournisseur d'identités externe comme Microsoft Entra ID.

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

  1. Naviguez jusqu'à Services de développement, Gestion d'API et Passerelles. Entrez les informations suivantes et cliquez sur Créer une passerelle.

    Image 15

    Image 16

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

    • Nom : Entrez un nom.
    • Préfixe de chemin : Définissez le chemin.
    • Compartiment : Sélectionnez le compartiment approprié pour le déploiement d'API.

    Image 17

  3. Ajoutez les détails de la politique d'authentification. C'est là que vous configurez le service des fonctions pour OCI, qui doit être appelé en tant qu'approbateur personnalisé. Sélectionnez la fonction créée dans la tâche 4.

    Image 18

  4. Dans la page Routes, configurez le routage d'API vers le service dorsal. Dans ce tutoriel, nous allons définir le routage vers le point d'extrémité Oracle Integration.

    Image 19

  5. Cliquez sur Afficher les politiques de demande de routage. C'est là que l'utilisateur effectuerait le remplacement des jetons d'authentification à partir de la réponse du service des fonctions pour OCI vers l'en-tête d'authentification de la demande.

    Image 20

    Cette étape consiste à définir le jeton d'authentification pour le service dorsal en fonction du fournisseur d'identités dorsal. Dans notre scénario, nous définissons le jeton de porteur pour le service IAM pour OCI tel qu'il a été reçu du service des fonctions pour OCI de l'autorisation personnalisée. Ici, nous configurons l'en-tête d'autorisation à remplacer par la valeur ${request.auth[back_end_token]}. Notez que 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 avec succès une fois l'autorisation personnalisée pour le service des fonctions pour OCI terminée.

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

    Image 21

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

    Image 22

Tâche 7 : Tester l'API

Tout d'abord, obtenez un jeton d'accès à partir de Microsoft Entra ID à 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, car nous l'utiliserons lors de l'appel de l'API à partir de la passerelle d'API.

    Image 12

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

    Image 23

  3. Cliquez sur Envoyer pour appeler la demande d'API, il exécutera Oracle Integration et devrait fournir une sortie réussie.

    Image 24

Étapes suivantes

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

Confirmation

Autres ressources d'apprentissage

Explorez d'autres laboratoires sur la page docs.oracle.com/learn ou accédez à plus de contenu d'apprentissage gratuit sur le canal YouTube d'Oracle Learning. De plus, visitez education.oracle.com/learning-explorer pour devenir un explorateur Oracle Learning.

Pour obtenir de la documentation sur le produit, visitez Oracle Help Center.