Accès à d'autres ressources Oracle Cloud Infrastructure à partir de fonctions en cours d'exécution

Découvrez comment accéder à d'autres ressources Oracle Cloud Infrastructure à partir de fonctions en cours d'exécution déployées vers OCI Functions.

Lorsqu'une fonction déployée vers OCI Functions est en cours d'exécution, elle peut accéder à d'autres ressources Oracle Cloud Infrastructure. Par exemple :

  • Vous pouvez avoir besoin d'une fonction qui obtient la liste des réseaux cloud virtuels auprès du service Networking.
  • Vous pouvez avoir besoin d'une fonction qui lit les données d'un bucket Object Storage, effectue des opérations sur ces données, puis réécrit les données modifiées dans le bucket Object Storage.

Pour permettre à une fonction d'accéder à une autre ressource Oracle Cloud Infrastructure, vous devez inclure la fonction dans un groupe dynamique, puis créer une stratégie pour accorder au groupe l'accès à cette ressource. Pour plus d'informations sur les groupes dynamiques, y compris les droits d'accès nécessaires à leur création, reportez-vous à Gestion des groupes dynamiques.

Une fois la stratégie configurée et le groupe dynamique créé, vous pouvez inclure un appel à un "fournisseur de principal de ressource" dans le code de la fonction. Le fournisseur de principal de ressource utilise un jeton de session de fournisseur de ressources (RPST) qui permet à la fonction de s'authentifier auprès d'autres services Oracle Cloud Infrastructure. Le jeton est valide uniquement pour les ressources auxquelles le groupe dynamique a accès.

Le jeton est également mis en cache pendant 15 minutes. Par conséquent, si vous modifiez la stratégie ou le groupe dynamique, vous devrez attendre 15 minutes avant de voir vos modifications prendre effet.

Nous vous recommandons d'utiliser le fournisseur de principal de ressource inclus dans le kit SDK d'Oracle Cloud Infrastructure. Toutefois, vous pouvez écrire une fonction dans un langage non pris en charge par le kit SDK d'Oracle Cloud Infrastructure. Il est possible également que vous ne souhaitiez tout simplement pas utiliser le kit SDK d'Oracle Cloud Infrastructure. Dans les deux cas, vous pouvez écrire votre fournisseur de principal de ressource personnalisé pour permettre à une fonction de s'authentifier auprès d'autres services Oracle Cloud Infrastructure, à l'aide de fichiers et de variables d'environnement dans le conteneur où s'exécute la fonction.

Utilisation de la console

Pour permettre à une fonction en cours d'exécution d'accéder à d'autres ressources Oracle Cloud Infrastructure, procédez comme suit :

  1. Connectez-vous à la console et créez un groupe dynamique :

    1. Ouvrez le menu de navigation et sélectionnez Identité et sécurité. Sous Identité, sélectionnez Domaines. Sous Domaine d'identité, sélectionnez Groupes dynamiques.
    2. Suivez les instructions dans Procédure de création d'un groupe dynamique et donnez un nom au groupe dynamique (par exemple, acme-func-dyn-grp).
    3. Lorsque vous indiquez une règle pour le groupe dynamique, tenez compte des exemples suivants :

      • Si vous souhaitez que toutes les fonctions d'un compartiment puissent accéder à une ressource, entrez une règle semblable à la suivante pour ajouter au groupe dynamique toutes les fonctions du compartiment avec l'OCID indiqué :

        ALL {resource.type = 'fnfunc', resource.compartment.id = 'ocid1.compartment.oc1..aaaaaaaa23______smwa'}
      • Si vous souhaitez qu'une fonction spécifique puisse accéder à une ressource, entrez une règle semblable à la suivante pour ajouter au groupe dynamique la fonction avec l'OCID indiqué :

        resource.id = 'ocid1.fnfunc.oc1.iad.aaaaaaaaacq______dnya'
      • Si vous souhaitez que toutes les fonctions ayant une balise définie puissent accéder à une ressource, entrez une règle semblable à la suivante pour ajouter au groupe dynamique toutes les fonctions avec la balise définie :

        ALL {resource.type = 'fnfunc', tag.department.operations.value = '45'}

        Les balises à format libre ne sont pas prises en charge. Pour plus d'informations sur le balisage, reportez-vous à Balises de ressource.

    4. Sélectionnez Créer un groupe dynamique.

    Une fois que vous avez créé un groupe dynamique contenant la fonction, vous pouvez créer une stratégie afin de donner au groupe dynamique l'accès à la ressource Oracle Cloud Infrastructure requise.

  2. Créez une stratégie :

    1. Ouvrez le menu de navigation et sélectionnez Identité et sécurité. Sous Identité, sélectionnez Stratégies.
    2. Suivez les instructions dans Procédure de création d'une stratégie et attribuez un nom à cette stratégie (par exemple, acme-func-dyn-grp-policy).
    3. Lorsque vous indiquez une instruction de stratégie, tenez compte des exemples suivants :

      • Pour que les fonctions dans acme-func-dyn-grp puissent obtenir la liste de tous les réseaux cloud virtuels de la location, entrez une règle semblable à la suivante :

        allow dynamic-group acme-func-dyn-grp to inspect vcns in tenancy
      • Pour que les fonctions dans acme-func-dyn-grp puissent lire et écrire dans un bucket Object Storage donné, entrez une règle semblable à la suivante :

        allow dynamic-group acme-func-dyn-grp to manage objects in compartment acme-storage-compartment where all {target.bucket.name='acme-functions-bucket'}
      • Pour que les fonctions dans acme-func-dyn-grp puissent lire et écrire sur toutes les ressources d'un compartiment, entrez une règle semblable à la suivante :

        allow dynamic-group acme-func-dyn-grp to manage all-resources in compartment acme-storage-compartment
    4. Sélectionnez Créer pour créer la stratégie.
  3. Incluez un fournisseur de principal de ressource dans le code de la fonction pour permettre à la fonction de s'authentifier auprès d'autres services Oracle Cloud Infrastructure. Reportez-vous aux exemples suivants :

    Pour obtenir un exemple de fonction Java, reportez-vous à Fonction qui renvoie la liste des instances dans le compartiment appelant dans le référentiel d'exemples OCI Functions sur GitHub.

Exemple : ajout du fournisseur de principal de ressource Oracle à une fonction Python pour obtenir la liste des réseaux cloud virtuels auprès du service Networking

Après avoir ajouté une fonction à un groupe dynamique et créé une stratégie qui autorise le groupe dynamique à répertorier les réseaux cloud virtuels de la location, vous pouvez inclure une portion de code semblable à l'exemple suivant pour obtenir la liste des réseaux cloud virtuels auprès du service Networking. Dans cet exemple, le fournisseur de principal de ressource Oracle est utilisé pour extraire les informations d'identification à partir du jeton RPST.

import io
import json

from fdk import response
import oci

def handler(ctx, data: io.BytesIO=None):
    signer = oci.auth.signers.get_resource_principals_signer()
    resp = do(signer)
    return response.Response(ctx,
        response_data=json.dumps(resp),
        headers={"Content-Type": "application/json"} )

def do(signer):
    # List VCNs --------------------------------------------------------
    client = oci.core.VirtualNetworkClient({}, signer=signer)
    try:
        vcns = client.list_vcns(signer.compartment_id)
        vcns = [[v.id, v.display_name] for v in vcns.data]
    except Exception as e:
        vcns = str(e)
    return {"vcns": vcns, }

Exemple : ajout d'un fournisseur de principal de ressource personnalisé à une fonction

Nous vous recommandons d'utiliser le fournisseur de principal de ressource inclus dans le kit SDK d'Oracle Cloud Infrastructure. Toutefois, vous pouvez écrire une fonction dans un langage non pris en charge par le kit SDK d'Oracle Cloud Infrastructure. Il est possible également que vous ne souhaitiez tout simplement pas utiliser le kit SDK d'Oracle Cloud Infrastructure. Dans les deux cas, vous pouvez écrire votre fournisseur de principal de ressource personnalisé pour permettre à une fonction de s'authentifier auprès d'autres services Oracle Cloud Infrastructure, à l'aide de fichiers et de variables d'environnement dans le conteneur où s'exécute la fonction.

Le conteneur dans lequel une fonction est exécutée inclut une arborescence de répertoires contenant des informations d'identification compatibles avec Oracle Cloud Infrastructure, notamment les suivantes :

  • Jeton de session de principal de ressource (RPST) dans un fichier nommé rpst. Le jeton RPST est formaté en tant que jeton JWT et inclut les déclarations qui identifient la location d'hôte et le compartiment de la fonction.
  • Clé privée à utiliser pour envoyer des demandes aux services Oracle Cloud Infrastructure au nom de la fonction, dans un fichier nommé private.pem.

Les variables d'environnement suivantes sont définies dans le conteneur où s'exécute la fonction :

  • OCI_RESOURCE_PRINCIPAL_VERSION, contenant la valeur 2.2.
  • OCI_RESOURCE_PRINCIPAL_RPST, contenant le chemin absolu du fichier rpst (y compris le nom de fichier).
  • OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM, contenant le chemin absolu du fichier private.pem (y compris le nom de fichier).
  • OCI_RESOURCE_PRINCIPAL_REGION, contenant l'identificateur de la région dans laquelle la fonction est déployée (par exemple, us-phoenix-1).

Pour autoriser une fonction à accéder à un autre service Oracle Cloud Infrastructure, ajoutez-lui du code afin qu'elle puisse s'authentifier auprès de l'autre ressource :

  1. Ajoutez le code qui charge le jeton RPST à partir du chemin dans la variable d'environnement OCI_RESOURCE_PRINCIPAL_RPST.
  2. Ajoutez le code qui charge la clé privée à partir du chemin dans la variable d'environnement OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM.

  3. Ajoutez le code qui utilise le jeton RPST et la clé privée pour créer une signature de demande Oracle Cloud Infrastructure (reportez-vous à Signatures des demandes).

  4. Ajoutez le code qui construit la demande vers l'autre ressource Oracle Cloud Infrastructure.

    Si nécessaire, vous pouvez identifier les éléments suivants :

    • Les adresses des autres services Oracle Cloud Infrastructure dans la même région (locale) que la fonction, à l'aide de l'identificateur de région dans la variable d'environnement OCI_RESOURCE_PRINCIPAL_REGION.
    • La location d'hôte et le compartiment de la fonction, à l'aide des déclarations res_tenant et res_compartment dans le jeton RPST.

Par exemple, la fonction Python d'exemple ci-dessous inclut un fournisseur de principal de ressource personnalisé qui extrait les informations d'identification à partir du jeton RPST. Il soumet ensuite une demande GET à l'opération getTenancy de l'API IAM pour renvoyer l'OCID de la location de la fonction.

#!/usr/bin/env python3

import base64
import email.utils
import hashlib
import httpsig_cffi.sign
import json
import logging
import os.path
import re
import requests.auth
import urllib.parse


LOG = logging.getLogger(__name__)


# The following class is derived from the Python section in https://docs.cloud.oracle.com/iaas/Content/API/Concepts/signingrequests.htm

class SignedRequestAuth(requests.auth.AuthBase):
    """A requests auth instance that can be reused across requests"""
    generic_headers = [
        "date",
        "(request-target)",
        "host"
    ]
    body_headers = [
        "content-length",
        "content-type",
        "x-content-sha256",
    ]
    required_headers = {
        "get": generic_headers,
        "head": generic_headers,
        "delete": generic_headers,
        "put": generic_headers + body_headers,
        "post": generic_headers + body_headers,
    }

    def __init__(self, key_id, private_key):
        # Build a httpsig_cffi.requests_auth.HTTPSignatureAuth for each
        # HTTP method's required headers
        self.signers = {}
        for method, headers in self.required_headers.items():
            signer = httpsig_cffi.sign.HeaderSigner(
                key_id=key_id, secret=private_key,
                algorithm="rsa-sha256", headers=headers[:])
            use_host = "host" in headers
            self.signers[method] = (signer, use_host)

    def inject_missing_headers(self, request, sign_body):
        # Inject date, content-type, and host if missing
        request.headers.setdefault(
            "date", email.utils.formatdate(usegmt=True))
        request.headers.setdefault("content-type", "application/json")
        request.headers.setdefault(
            "host", urllib.parse.urlparse(request.url).netloc)

        # Requests with a body need to send content-type,
        # content-length, and x-content-sha256
        if sign_body:
            body = request.body or ""
            if "x-content-sha256" not in request.headers:
                m = hashlib.sha256(body.encode("utf-8"))
                base64digest = base64.b64encode(m.digest())
                base64string = base64digest.decode("utf-8")
                request.headers["x-content-sha256"] = base64string
            request.headers.setdefault("content-length", len(body))

    def __call__(self, request):
        verb = request.method.lower()
        # nothing to sign for options
        if verb == "options":
            return request
        signer, use_host = self.signers.get(verb, (None, None))
        if signer is None:
            raise ValueError(
                "Don't know how to sign request verb {}".format(verb))

        # Inject body headers for put/post requests, date for all requests
        sign_body = verb in ["put", "post"]
        self.inject_missing_headers(request, sign_body=sign_body)

        if use_host:
            host = urllib.parse.urlparse(request.url).netloc
        else:
            host = None

        signed_headers = signer.sign(
            request.headers, host=host,
            method=request.method, path=request.path_url)
        request.headers.update(signed_headers)
        return request


def rp_auther():
    if os.environ['OCI_RESOURCE_PRINCIPAL_VERSION'] != "2.2":
        raise EnvironmentError('{} must be set to the value "2.2"'.format('OCI_RESOURCE_PRINCIPAL_VERSION'))
    rpst = os.environ['OCI_RESOURCE_PRINCIPAL_RPST']
    if os.path.isabs(rpst):
        with open(rpst) as f:
            rpst = f.read()
    private_key = os.environ['OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM']
    if os.path.isabs(private_key):
        with open(private_key) as f:
            private_key = f.read()
    return get_claims(rpst), SignedRequestAuth('ST${}'.format(rpst), private_key)


def get_claims(rpst):
    """Parse an RPST as a JWT; return a dictionary of claims

    The claims that are important are: sub, res_compartment, and res_tenant.
    These carry the resource OCID together with its location.
    """
    s = rpst.split('.')[1]
    s += "=" * ((4 - len(s) % 4) % 4)  # Pad to a multiple of 4 characters
    return json.loads(base64.b64decode(s).decode('utf-8'))


# Use RP credentials to make a request
region = os.environ['OCI_RESOURCE_PRINCIPAL_REGION']
claims, rp_auth = rp_auther()

response = requests.get("https://identity.{}.oraclecloud.com/20160918/tenancies/{}".format(region, claims['res_tenant']), auth=rp_auth)
print(response.json())