Acessando Outros Recursos do Oracle Cloud Infrastructure nas Funções em Execução

Descubra como acessar outros recursos do Oracle Cloud Infrastructure nas funções em execução implantadas no OCI Functions.

Quando uma função que você implantou no OCI Functions estiver em execução, ela poderá acessar outros recursos do Oracle Cloud Infrastructure. Por exemplo:

  • Você pode querer que uma função obtenha uma lista de VCNs do serviço Networking.
  • Talvez você queira que uma função leia dados de um bucket do serviço Object Storage, execute algumas operações nos dados e, em seguida, grave os dados modificados de volta para o bucket do serviço Object Storage.

Para permitir que uma função acesse outro recurso do Oracle Cloud Infrastructure, inclua a função em um grupo dinâmico e, em seguida, crie uma política para conceder acesso ao grupo dinâmico a esse recurso. Para obter mais informações sobre grupos dinâmicos, incluindo as permissões necessárias para criá-los, consulte Gerenciando Grupos Dinâmicos.

Ao configurar a política e o grupo dinâmico, você poderá incluir uma chamada para um 'provedor de controlador de recursos' no seu código de função. O provedor de controlador de recursos usa um token de sessão do provedor de recursos (resource provider session token, RPST) que permite que a função se autentique com outros serviços do Oracle Cloud Infrastructure. O token só é válido para os recursos aos quais o grupo dinâmico tem acesso.

Observe também que o token é armazenado em cache por 15 minutos. Portanto, se você alterar a política ou o grupo dinâmico, terá de esperar 15 minutos para ver o efeito das suas alterações.

Recomendamos que você use o provedor de controlador de recursos incluído no Oracle Cloud Infrastructure SDK. No entanto, você pode estar gravando uma função em uma linguagem que o Oracle Cloud Infrastructure SDK não suporta. Ou você pode simplesmente não querer usar o Oracle Cloud Infrastructure SDK. Em ambos os casos, você pode criar o seu próprio provedor de controlador de recursos personalizado para permitir que uma função se autentique com outros serviços do Oracle Cloud Infrastructure, usando arquivos e variáveis de ambiente no contêiner em que a função está sendo executada.

Usando a Console

Para permitir que uma função em execução acesse outros recursos do Oracle Cloud Infrastructure:

  1. Acesse a Console e crie um novo grupo dinâmico:

    1. Abra o menu de navegação e selecione Identidade e Segurança. Em Identidade, selecione Domínios. Em Domínio de identidades, selecione Grupos dinâmicos.
    2. Siga as instruções em Para criar um grupo dinâmico e dê um nome ao grupo dinâmico (por exemplo, acme-func-dyn-grp).
    3. Ao especificar uma regra para o grupo dinâmico, considere os seguintes exemplos:

      • Se você quiser que todas as funções de um compartimento possam acessar um recurso, informe uma regra semelhante à seguinte que adicione todas as funções do compartimento com o OCID do compartimento especificado ao grupo dinâmico:

        ALL {resource.type = 'fnfunc', resource.compartment.id = 'ocid1.compartment.oc1..aaaaaaaa23______smwa'}
      • Se você quiser que uma função específica possa acessar um recurso, informe uma regra semelhante à seguinte que adicione a função com o OCID especificado ao grupo dinâmico:

        resource.id = 'ocid1.fnfunc.oc1.iad.aaaaaaaaacq______dnya'
      • Se quiser que todas as funções com uma tag definida específica possam acessar um recurso, informe uma regra semelhante à seguinte que adicione todas as funções com a tag definida ao grupo dinâmico:

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

        Observe que as tags de formato livre não são suportadas. Para obter mais informações sobre tags, consulte Tags de Recursos.

    4. Selecione Criar Grupo Dinâmico.

    Após criar um grupo dinâmico que inclui a função, você agora pode criar uma política para conceder ao grupo dinâmico acesso ao recurso do Oracle Cloud Infrastructurenecessário.

  2. Crie uma nova política:

    1. Abra o menu de navegação e selecione Identidade e Segurança. Em Identidade, selecione Políticas.
    2. Siga as instruções em Para criar uma política e dê um nome à política (por exemplo, acme-func-dyn-grp-policy).
    3. Ao especificar uma instrução de política, considere os seguintes exemplos:

      • Se você quiser que as funções em acme-func-dyn-grp possam obter uma lista de todas as VCNs na tenancy, informe uma regra semelhante a esta:

        allow dynamic-group acme-func-dyn-grp to inspect vcns in tenancy
      • Se você quiser que as funções de acme-func-dyn-grp possam ler e gravar em um bucket específico do serviço Object Storage, informe uma regra semelhante a esta:

        allow dynamic-group acme-func-dyn-grp to manage objects in compartment acme-storage-compartment where all {target.bucket.name='acme-functions-bucket'}
      • Se você quiser que as funções em acme-func-dyn-grp possam ler e gravar em todos os recursos de um compartimento, informe uma regra semelhante a esta:

        allow dynamic-group acme-func-dyn-grp to manage all-resources in compartment acme-storage-compartment
    4. Selecione Criar para criar a nova política.
  3. Inclua um provedor de controlador de recursos no código de função para permitir que a função seja autenticada com outros serviços do Oracle Cloud Infrastructure. Consulte:

    Para obter uma função Java de amostra, consulte Função que retorna a lista de instâncias no Compartimento de chamada no repositório de amostras do OCI Functions em GitHub.

Exemplo: Adicionando o Provedor de Controlador de Recursos Oracle a uma Função Python para Obter uma Lista de VCNs do Serviço Networking

Após adicionar uma função a um grupo dinâmico e criar uma política que permita que o grupo dinâmico liste as VCNs na tenancy, você poderá incluir um código semelhante ao exemplo a seguir para obter uma lista de VCNs do serviço Networking. Este exemplo usa o provedor de controlador de recursos Oracle para extrair credenciais do 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, }

Exemplo: Adicionando um Provedor de Controlador de Recursos Personalizado a uma Função

Recomendamos que você use o provedor de controlador de recursos incluído no Oracle Cloud Infrastructure SDK. No entanto, você pode estar gravando uma função em uma linguagem que o Oracle Cloud Infrastructure SDK não suporta. Ou você pode simplesmente não querer usar o Oracle Cloud Infrastructure SDK. Em ambos os casos, você pode criar o seu próprio provedor de controlador de recursos personalizado para permitir que uma função se autentique com outros serviços do Oracle Cloud Infrastructure, usando arquivos e variáveis de ambiente no contêiner em que a função está sendo executada.

O contêiner no qual uma função é executada inclui uma árvore de diretórios que contém as credenciais compatíveis com o Oracle Cloud Infrastructure, especificamente:

  • Um token de sessão de controlador de recursos (resource principal session token, RPST) em um arquivo chamado rpst. O token RPST é formatado como um token JWT e inclui reivindicações que identificam a tenancy e o compartimento do host da função.
  • Uma chave privada a ser usada para fazer solicitações aos serviços do Oracle Cloud Infrastructure em nome da função, em um arquivo chamado private.pem.

As seguintes variáveis de ambiente são definidas dentro do contêiner no qual a função é executada:

  • OCI_RESOURCE_PRINCIPAL_VERSION, contendo o valor 2.2.
  • OCI_RESOURCE_PRINCIPAL_RPST, contendo o caminho absoluto para o arquivo rpst (incluindo o nome do arquivo).
  • OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM, contendo o caminho absoluto para o arquivo private.pem (incluindo o nome do arquivo).
  • OCI_RESOURCE_PRINCIPAL_REGION, contendo o identificador de região no qual a função é implantada (por exemplo, us-phoenix-1).

Para permitir que uma função acesse outro serviço do Oracle Cloud Infrastructure, adicione o código à função para que ele possa se autenticar com o outro recurso:

  1. Adicione o código que carrega o token RPST do caminho na variável de ambiente do OCI_RESOURCE_PRINCIPAL_RPST.
  2. Adicione o código que carrega a chave privada do caminho na variável de ambiente do OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM.

  3. Adicione o código que usa o token RPST e a chave privada para criar uma assinatura de solicitação do Oracle Cloud Infrastructure (consulte Assinaturas de Solicitação).

  4. Adicionar código que cria a solicitação para o outro recurso do Oracle Cloud Infrastructure.

    Se necessário, você pode identificar:

    • Os pontos finais de outros serviços do Oracle Cloud Infrastructure na mesma região (local) da função, usando o identificador de região na variável de ambiente do OCI_RESOURCE_PRINCIPAL_REGION.
    • A tenancy e o compartimento do host da função, usando as reivindicações res_tenant e res_compartment no token RPST.

Por exemplo, o exemplo de função Python abaixo inclui um provedor de controlador de recursos personalizado que extrai credenciais do token RPST. Em seguida, ele submete uma solicitação GET para a operação getTenancy da API do serviço IAM para retornar o OCID da tenancy da função.

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