Find out how to access other Oracle Cloud Infrastructure resources from running functions deployed to OCI Functions.
When a function you've deployed to OCI Functions is running, it can access other Oracle Cloud Infrastructure resources. For example:
To enable a function to access another Oracle Cloud Infrastructure resource, you have to include the function in a dynamic group, and then create a policy to grant the dynamic group access to that resource. For more information about dynamic groups, including the permissions required to create them, seeManaging Dynamic Groups.
Having set up the policy and the dynamic group, you can then include a call to a 'resource principal provider' in your function code. The resource principal provider uses a resource provider session token (RPST) that enables the function to authenticate itself with other Oracle Cloud Infrastructure services. The token is only valid for the resources to which the dynamic group has been granted access.
Note also that the token is cached for 15 minutes. So if you change the policy or the dynamic group, you will have to wait for 15 minutes to see the effect of your changes.
We recommend that you use the resource principal provider included in the Oracle Cloud Infrastructure SDK. However, you might be writing a function in a language that the Oracle Cloud Infrastructure SDK does not support. Or you might simply not want to use the Oracle Cloud Infrastructure SDK. In either case, you can write your own custom resource principal provider to enable a function to authenticate itself with other Oracle Cloud Infrastructure services, using files and environment variables in the container in which the function is executing.
To enable a running function to access other Oracle Cloud Infrastructure resources:
Log in to the Console and create a new dynamic group:
acme-func-dyn-grp).When specifying a rule for the dynamic group, consider the following examples:
If you want all functions in a compartment to be able to access a resource, enter a rule similar to the following that adds all functions in the compartment with the specified compartment OCID to the dynamic group:
ALL {resource.type = 'fnfunc', resource.compartment.id = 'ocid1.compartment.oc1..aaaaaaaa23______smwa'}If you want a specific function to be able to access a resource, enter a rule similar to the following that adds the function with the specified OCID to the dynamic group:
resource.id = 'ocid1.fnfunc.oc1.iad.aaaaaaaaacq______dnya'If you want all functions with a specific defined tag to be able to access a resource, enter a rule similar to the following that adds all functions with the defined tag to the dynamic group :
ALL {resource.type = 'fnfunc', tag.department.operations.value = '45'}Note that free-form tags are not supported. For more information about tagging, seeResource Tags.
Having created a dynamic group that includes the function, you can now create a policy to give the dynamic group access to the required Oracle Cloud Infrastructure resource.
Create a new policy:
acme-func-dyn-grp-policy).When specifying a policy statement, consider the following examples:
If you want functions in theacme-func-dyn-grp to be able to get a list of all the VCNs in the tenancy, enter a rule similar to the following:
allow dynamic-group acme-func-dyn-grp to inspect vcns in tenancyIf you want functions in theacme-func-dyn-grp to be able to read and write to a particular Object Storage bucket, enter a rule similar to the following:
allow dynamic-group acme-func-dyn-grp to manage objects in compartment acme-storage-compartment where all {target.bucket.name='acme-functions-bucket'}If you want functions in theacme-func-dyn-grp to be able to read and write to all resources in a compartment, enter a rule similar to the following:
allow dynamic-group acme-func-dyn-grp to manage all-resources in compartment acme-storage-compartmentFor a sample Java function, seeFunction that returns the list of instances in the calling Compartment in theOCI Functions samples repository on GitHub.
Having added a function to a dynamic group, and created a policy that allows the dynamic group to list the VCNs in the tenancy, you could include code similar to the following example to get a list of VCNs from the Networking service. This example uses the Oracle resource principal provider to extract credentials from the RPST token.
import ioimport jsonfrom fdk import responseimport ocidef 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, }We recommend that you use the resource principal provider included in the Oracle Cloud Infrastructure SDK. However, you might be writing a function in a language that the Oracle Cloud Infrastructure SDK does not support. Or you might simply not want to use the Oracle Cloud Infrastructure SDK. In either case, you can write your own custom resource principal provider to enable a function to authenticate itself with other Oracle Cloud Infrastructure services, using files and environment variables in the container in which the function is executing.
The container in which a function executes includes a directory tree that holds Oracle Cloud Infrastructure compatible credentials, specifically:
The following environment variables are set inside the container in which the function executes:
2.2.us-phoenix-1).To enable a function to access another Oracle Cloud Infrastructure service, add code to the function so that it can authenticate itself with the other resource:
Add code that loads the private key from the path in the OCI_RESOURCE_PRINCIPAL_PRIVATE_PEM environment variable.
Add code that uses the RPST token and the private key to create an Oracle Cloud Infrastructure request signature (seeRequest Signatures).
Add code that constructs the request to the other Oracle Cloud Infrastructure resource.
If necessary, you can identify:
res_tenant andres_compartment claims in the RPST token.For example, the sample Python function below includes a custom resource principal provider that extracts credentials from the RPST token. It then submits a GET request to the IAM API's getTenancy operation to return the OCID of the function's tenancy.
#!/usr/bin/env python3import base64import email.utilsimport hashlibimport httpsig_cffi.signimport jsonimport loggingimport os.pathimport reimport requests.authimport urllib.parseLOG = logging.getLogger(__name__)# The following class is derived from the Python section in https://docs.cloud.oracle.com/iaas/Content/API/Concepts/signingrequests.htmclass 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 requestdef 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 requestregion = 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())