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 des fonctions en cours d'exécution déployées dans le service des fonctions pour OCI.

Lorsqu'une fonction que vous avez déployée dans le service des fonctions pour OCI est en cours d'exécution, elle peut accéder à d'autres ressources Oracle Cloud Infrastructure. Par exemple :

  • Vous pourriez vouloir obtenir une liste des réseaux en nuage virtuels du service Réseau.
  • Vous pourriez vouloir qu'une fonction lise des données à partir d'un seau de stockage d'objets, effectue des opérations sur les données, puis réécrive les données modifiées dans le seau de stockage d'objets.

Pour activer une fonction pour accéder à une autre ressource Oracle Cloud Infrastructure, vous devez inclure la fonction dans un groupe dynamique, puis créer une politique vous permettant d'accorder au groupe dynamique l'accès à cette ressource. Pour plus d'informations sur les groupes dynamiques, notamment les autorisations nécessaires pour les créer, voir Gestion des groupes dynamiques.

Une fois que vous avez défini la politique et le groupe dynamique, vous pouvez inclure un appel à un "fournisseur principal de ressources" dans le code de fonction. Le fournisseur principal de ressources utilise un jeton RPST (modèle de session du fournisseur de ressources) qui permet à la fonction de s'authentifier elle-même auprès d'autres services Oracle Cloud Infrastructure. Le jeton n'est valide que pour les ressources auxquelles le groupe dynamique a accès.

Notez également que le jeton est mis en cache pendant 15 minutes. Ainsi, si vous modifiez la politique ou le groupe dynamique, vous devez attendre 15 minutes pour voir l'effet de vos modifications.

Nous vous recommandons d'utiliser le fournisseur principal de ressources inclus dans la trousse SDK Oracle Cloud Infrastructure. Toutefois, il est possible que vous vouliez écrire une fonction dans un langage que la trousse SDK Oracle Cloud Infrastructure ne prend pas en charge. Ou que vous ne vouliez pas utiliser la trousse SDK Oracle Cloud Infrastructure. Dans les deux cas, vous pouvez écrire votre propre fournisseur principal de ressources personnalisé pour permettre à une fonction de s'authentifier elle-même auprès d'autres services Oracle Cloud Infrastructure, à l'aide de fichiers et de variables d'environnement dans le conteneur dans lequel la fonction s'exécute.

Utilisation de la console

Pour activer une fonction en cours d'exécution pour accéder à d'autres ressources Oracle Cloud Infrastructure :

  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 sous Pour créer un groupe dynamique et donnez un nom à ce groupe (par exemple, acme-func-dyn-grp).
    3. Lorsque vous spécifiez une règle pour le groupe dynamique, tenez compte des exemples suivants :

      • Si vous voulez que toutes les fonctions d'un compartiment puissent accéder à une ressource, entrez une règle similaire à la suivante, qui ajoute toutes les fonctions du compartiment avec l'OCID du compartiment spécifié au groupe dynamique :

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

        resource.id = 'ocid1.fnfunc.oc1.iad.aaaaaaaaacq______dnya'
      • Pour que toutes les fonctions ayant un marqueur défini spécifique puissent accéder à une ressource, entrez une règle similaire à la suivante, qui ajoute toutes les fonctions avec le marqueur défini au groupe dynamique :

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

        Notez que les marqueurs à structure libre ne sont pas pris en charge. Pour plus d'informations sur le marquage, voir Marqueurs de ressource.

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

    Après avoir créé un groupe dynamique qui inclut la fonction, vous pouvez créer une politique pour permettre au groupe dynamique d'accéder à la ressource Oracle Cloud Infrastructure requise.

  2. Créer une nouvelle politique :

    1. Ouvrez le menu de navigation et sélectionnez Identité et sécurité. Sous identité, sélectionnez Politiques.
    2. Suivez les instructions sous Pour créer une politique et donnez un nom à la politique (par exemple, acme-func-dyn-grp-policy).
    3. Lorsque vous spécifiez un énoncé de politique, tenez compte des exemples suivants :

      • Si vous voulez que les fonctions de acme-func-dyn-grp puissent obtenir une liste de tous les réseaux en nuage virtuels dans la location, entrez une règle similaire à celle qui suit :

        allow dynamic-group acme-func-dyn-grp to inspect vcns in tenancy
      • Pour que les fonctions de acme-func-dyn-grp puissent lire et écrire dans un compartiment de stockage d'objets particulier, entrez une règle similaire à celle qui suit :

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

        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 politique.
  3. Incluez un fournisseur principal de ressources dans le code de fonction pour permettre à la fonction de s'authentifier auprès d'autres services Oracle Cloud Infrastructure. Voir :

    Pour un exemple de fonction Java, voir Fonction qui retourne la liste des instances dans le compartiment appelant dans le référentiel d'échantillons du service des fonctions pour OCI sur GitHub.

Exemple : Ajout du fournisseur principal de ressources Oracle à une fonction Python pour obtenir une liste des réseaux en nuage virtuels à partir du service de réseau

Après avoir ajouté une fonction à un groupe dynamique et créé une politique qui permet au groupe dynamique de lister les réseaux en nuage virtuels dans la location, vous pouvez inclure du code similaire à l'exemple suivant pour obtenir une liste des réseaux en nuage virtuels à partir du service Réseau. Cet exemple utilise le fournisseur principal de ressources Oracle pour extraire les données d'identification 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 principal de ressources personnalisé à une fonction

Nous vous recommandons d'utiliser le fournisseur principal de ressources inclus dans la trousse SDK Oracle Cloud Infrastructure. Toutefois, il est possible que vous vouliez écrire une fonction dans un langage que la trousse SDK Oracle Cloud Infrastructure ne prend pas en charge. Ou que vous ne vouliez pas utiliser la trousse SDK Oracle Cloud Infrastructure. Dans les deux cas, vous pouvez écrire votre propre fournisseur principal de ressources personnalisé pour permettre à une fonction de s'authentifier elle-même auprès d'autres services Oracle Cloud Infrastructure, à l'aide de fichiers et de variables d'environnement dans le conteneur dans lequel la fonction s'exécute.

Le conteneur dans lequel une fonction exécute comprend un arbre de répertoire contenant les données d'identification compatibles avec Oracle Cloud Infrastructure, plus particulièrement :

  • Un jeton de session principal de ressource (RPST) dans un fichier nommé rpst. Le jeton RPST est formaté en tant que jeton JWT et inclut les revendications qui identifient la location et le compartiment hôte de la fonction.
  • Une clé privée utilisée pour effectuer 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 à l'intérieur du conteneur dans lequel la fonction s'exécute :

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

Pour permettre à une fonction d'accéder à un autre service Oracle Cloud Infrastructure, ajoutez un code à la fonction afin qu'elle puisse s'authentifier auprès d'une 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 (voir Signatures de demande).

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

    Si nécessaire, vous pouvez identifier :

    • Les points d'extrémité d'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 et le compartiment hôte de la fonction, à l'aide des revendications res_tenant et res_compartment du jeton RPST.

Par exemple, l'exemple de fonction Python ci-dessous inclut un fournisseur principal de ressources personnalisé qui extrait les données d'identification du jeton RPST. Il soumet ensuite une demande GET à l'opération getTenancy de l'API GIA pour retourner 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())