Accesso ad altre risorse di Oracle Cloud Infrastructure dall'esecuzione di funzioni

Scopri come accedere ad altre risorse di Oracle Cloud Infrastructure dall'esecuzione di funzioni distribuite in OCI Functions.

Quando una funzione distribuita in OCI Functions è in esecuzione, può accedere ad altre risorse Oracle Cloud Infrastructure. Ad esempio:

  • È possibile che una funzione ottenga un elenco di VCN dal servizio di networking.
  • Potrebbe essere necessario che una funzione legga i dati da un bucket di storage degli oggetti, esegua alcune operazioni sui dati, quindi scriva di nuovo i dati modificati nel bucket di storage degli oggetti.

Per consentire a una funzione di accedere a un'altra risorsa Oracle Cloud Infrastructure, è necessario includere la funzione in un gruppo dinamico e quindi creare un criterio per concedere al gruppo dinamico l'accesso a tale risorsa. Per ulteriori informazioni sui gruppi dinamici, incluse le autorizzazioni necessarie per crearli, vedere Gestione dei gruppi dinamici.

Dopo aver impostato il criterio e il gruppo dinamico, è possibile includere una chiamata a un 'provider di principal risorsa' nel codice funzione. Il provider di principal risorse utilizza un token di sessione (RPST, Resource Provider Session Token) che consente alla funzione di autenticarsi con altri servizi Oracle Cloud Infrastructure. Il token è valido solo per le risorse a cui è stato concesso l'accesso al gruppo dinamico.

Si noti inoltre che il token viene memorizzato nella cache per 15 minuti. Pertanto, se si modifica il criterio o il gruppo dinamico, sarà necessario attendere 15 minuti per vedere l'effetto delle modifiche.

Si consiglia di utilizzare il provider di principal delle risorse incluso nell'SDK di Oracle Cloud Infrastructure. Tuttavia, potresti scrivere una funzione in un linguaggio non supportato dall'SDK di Oracle Cloud Infrastructure. In alternativa, potresti semplicemente non voler utilizzare l'SDK Oracle Cloud Infrastructure. In entrambi i casi, puoi scrivere il tuo provider di principal delle risorse personalizzato per consentire a una funzione di autenticarsi con altri servizi Oracle Cloud Infrastructure, utilizzando file e variabili di ambiente nel contenitore in cui viene eseguita la funzione.

Utilizzo di Console

Per abilitare una funzione in esecuzione per accedere ad altre risorse di Oracle Cloud Infrastructure:

  1. Eseguire il login alla console e creare un nuovo gruppo dinamico:

    1. Aprire il menu di navigazione e selezionare Identità e sicurezza. In Identità, selezionare Domini. In Dominio di Identity, selezionare Gruppi dinamici.
    2. Seguire le istruzioni in Per creare un gruppo dinamico e assegnare al gruppo dinamico un nome (ad esempio, acme-func-dyn-grp).
    3. Quando si specifica una regola per il gruppo dinamico, tenere presenti gli esempi riportati di seguito.

      • Se si desidera che tutte le funzioni di un compartimento possano accedere a una risorsa, immettere una regola simile alla seguente che aggiunga tutte le funzioni del compartimento con l'OCID compartimento specificato al gruppo dinamico:

        ALL {resource.type = 'fnfunc', resource.compartment.id = 'ocid1.compartment.oc1..aaaaaaaa23______smwa'}
      • Se si desidera che una funzione specifica sia in grado di accedere a una risorsa, immettere una regola simile alla seguente che aggiunge la funzione con l'OCID specificato al gruppo dinamico:

        resource.id = 'ocid1.fnfunc.oc1.iad.aaaaaaaaacq______dnya'
      • Se si desidera che tutte le funzioni con una tag definita specifica possano accedere a una risorsa, immettere una regola simile alla seguente che aggiunga tutte le funzioni con la tag definita al gruppo dinamico:

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

        Tenere presente che le tag in formato libero non sono supportate. Per ulteriori informazioni sull'applicazione di tag, vedere Tag delle risorse.

    4. Selezionare Crea gruppo dinamico.

    Dopo aver creato un gruppo dinamico che include la funzione, è ora possibile creare un criterio per concedere al gruppo dinamico l'accesso alla risorsa Oracle Cloud Infrastructure richiesta.

  2. Creare un nuovo criterio:

    1. Aprire il menu di navigazione e selezionare Identità e sicurezza. In Identità, selezionare Criteri.
    2. Seguire le istruzioni in Per creare un criterio e assegnare un nome al criterio, ad esempio acme-func-dyn-grp-policy.
    3. Quando si specifica un'istruzione criterio, tenere presenti gli esempi riportati di seguito.

      • Se si desidera che le funzioni in acme-func-dyn-grp siano in grado di ottenere una lista di tutte le VCN nella tenancy, immettere una regola simile alla seguente:

        allow dynamic-group acme-func-dyn-grp to inspect vcns in tenancy
      • Se desideri che le funzioni in acme-func-dyn-grp siano in grado di leggere e scrivere in un determinato bucket di storage degli oggetti, immettere una regola simile alla seguente:

        allow dynamic-group acme-func-dyn-grp to manage objects in compartment acme-storage-compartment where all {target.bucket.name='acme-functions-bucket'}
      • Se si desidera che le funzioni in acme-func-dyn-grp siano in grado di leggere e scrivere in tutte le risorse di un compartimento, immettere una regola simile alla seguente:

        allow dynamic-group acme-func-dyn-grp to manage all-resources in compartment acme-storage-compartment
    4. Selezionare Crea per creare il nuovo criterio.
  3. Includi un provider di principal risorsa nel codice funzione per consentire alla funzione di eseguire l'autenticazione con altri servizi Oracle Cloud Infrastructure. Vedere:

    Per un esempio di funzione Java, vedere Funzione che restituisce la lista di istanze nel compartimento chiamante nel repository di esempio delle funzioni OCI in GitHub.

Esempio: aggiunta del provider di principal delle risorse Oracle a una funzione Python per ottenere una lista di VCN dal servizio di networking

Dopo aver aggiunto una funzione a un gruppo dinamico e creato un criterio che consente al gruppo dinamico di elencare le VCN nella tenancy, è possibile includere codice simile al seguente esempio per ottenere una lista di VCN dal servizio di networking. In questo esempio viene utilizzato il provider di principal delle risorse Oracle per estrarre le credenziali dal token 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, }

Esempio: aggiunta di un provider principale di risorse personalizzato a una funzione

Si consiglia di utilizzare il provider di principal delle risorse incluso nell'SDK di Oracle Cloud Infrastructure. Tuttavia, potresti scrivere una funzione in un linguaggio non supportato dall'SDK di Oracle Cloud Infrastructure. In alternativa, potresti semplicemente non voler utilizzare l'SDK Oracle Cloud Infrastructure. In entrambi i casi, puoi scrivere il tuo provider di principal delle risorse personalizzato per consentire a una funzione di autenticarsi con altri servizi Oracle Cloud Infrastructure, utilizzando file e variabili di ambiente nel contenitore in cui viene eseguita la funzione.

Il contenitore in cui viene eseguita una funzione include una struttura di directory contenente le credenziali compatibili con Oracle Cloud Infrastructure, in particolare:

  • Token di sessione del principal risorsa (RPST) in un file denominato RPST. Il token RPST viene formattato come token JWT e include richieste che identificano la tenancy host e il compartimento della funzione.
  • Chiave privata da utilizzare per effettuare richieste ai servizi Oracle Cloud Infrastructure per conto della funzione, in un file denominato private.pem.

Le seguenti variabili di ambiente vengono impostate all'interno del contenitore in cui viene eseguita la funzione:

  • OCI_RESOURCE_PRINCIPAL_VERSION, contenente il valore 2.2.
  • OCI_RESOURCE_PRINCIPAL_RPST, che contiene il percorso assoluto del file rpst (incluso il nome del file).
  • OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM, che contiene il percorso assoluto del file private.pem (incluso il nome del file).
  • OCI_RESOURCE_PRINCIPAL_REGION, che contiene l'identificativo dell'area in cui viene distribuita la funzione (ad esempio, us-phoenix-1).

Per consentire a una funzione di accedere a un altro servizio Oracle Cloud Infrastructure, aggiungere codice alla funzione in modo che possa autenticarsi con l'altra risorsa:

  1. Aggiungere il codice che carica il token RPST dal percorso nella variabile di ambiente OCI_RESOURCE_PRINCIPAL_RPST.
  2. Aggiungere il codice che carica la chiave privata dal percorso nella variabile di ambiente OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM.

  3. Aggiungere il codice che utilizza il token RPST e la chiave privata per creare una firma di richiesta Oracle Cloud Infrastructure (vedere Richiedi firme).

  4. Aggiungi il codice che costruisce la richiesta all'altra risorsa Oracle Cloud Infrastructure.

    Se necessario, è possibile identificare:

    • Gli endpoint di altri servizi Oracle Cloud Infrastructure nella stessa area (locale) della funzione, utilizzando l'identificativo dell'area nella variabile di ambiente OCI_RESOURCE_PRINCIPAL_REGION.
    • Tenancy host e compartimento della funzione, utilizzando le richieste res_tenant e res_compartment nel token RPST.

Ad esempio, la funzione Python di esempio riportata di seguito include un provider di principal delle risorse personalizzato che estrae le credenziali dal token RPST. Quindi invia una richiesta GET all'operazione getTenancy dell'interfaccia API IAM per restituire l'OCID della tenancy della funzione.

#!/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())