Tired of wrestling with complex server configurations when you just want to ship code? OCI Functions delivers a flexible, serverless framework that scales effortlessly with demand, letting you deploy Python, Java, or Go applications without the infrastructure headache.
Introduction to OCI Functions
In this blog we will take our first step into OCI (Oracle Cloud Infrastructure) Functions. This is a service available on the Oracle Cloud, OCI Functions allow you to package your code so it can run autonomously within its own container. It's a serverless offering, meaning you won't be bogged down by the complexities of server configurations and maintenance. This service is really flexible and offers lots of benefits, some of which are:
-
Billed only for usage
-
Scalable to flex with your demand
-
Secure to keep your data and applications safe
-
Uses an open source framework enabling your applications and functions to be free from vendor lock in
OCI Functions are based on the https://fnproject.io/. This open source framework guarantees your the flexibility to host your functions as you see fit.
OCI Functions supports many languages including:
-
Python
-
Java
-
Node
-
Go
-
Ruby
-
C#
In this series I will make use Python as my language of choice but everything we do is achievable in other languages.
OCI Function Applications
Before we get into the juicy bits we need to create an Application. The Application is used to group functions under a meaningful name and hosts some useful configuration information. We can create an Application by navigating to Developer Services and then Functions. We can find Applications listed under the heading of Functions.
Use the button to create an application and give it a name.
The application is going to expect to be attached to a Virtual Cloud Network (VCN) and some subnets. If you do not have a VCN, you can create one using the wizard in the VCN section of OCI. For your exploration of the service it is recommended, for simplicity, to create a VCN with internet connectivity and attach the functions to the private subnets that are created. Attaching them to the private subnet will help protect them from being exposed to the internet. If you want the function to be accessible from the internet then you either need to attach it to the public subnets or use another solution that can handle incoming requests from the public internet.
Methods of Creating an OCI Function
Once we have created the new Application we have five ways to interact with functions.
-
Create a pre-built function
Create a function using the CLI
-
Using Cloud Shell
-
Using a local development environment
-
Create a function using the Code Editor
-
Create a function using Terraform
-
Control functions using the Rest API
We will explore the different methods and look at the pros and cons of each approach. For now we will keep it simple and focus on creating a pre-built function and how we can use it. Make sure you subscribe to our blog or follow us on social media to keep informed of new posts!
Creating a Pre-Built Function
Under the menu items Developer Services > Functions navigate to Pre-Built Functions. Here we find the functions that Oracle has developed for us. We can select the function called Object Storage File Zip. Then we can click on Create Function.
OCI is going to help guide us through the provisioning of this pre-built function in our OCI environment.
As you can see by default Oracle is taking the initiative to create the required IAM policies that enable the function to work.
There are some properties that are configurable for now we will keep the default values. The properties are:
-
Memory - the amount of memory available to the function at runtime
-
Timeout - the maximum allowed time for the function can run for
-
Provisioned Concurrency - enables quicker executions of the function at an increased cost
-
PBF_LOG_LEVEL - the amount of detail to include in the functions logs
-
Tagging - label resources to help manage and report on them
After the function is created we can review the summary of activity.
Policy
Lets take a look at the policy that Oracle has created.
As you can see, for this blog, I am working in a compartment called data-engineering-oci-functions. The dynamic group that Oracle created for us has been a set of actions on some objects.
-
Read object storage namespaces
-
Manage objects
-
Manage buckets
-
Read child compartments
If you are new to policies you can read about the https://docs.oracle.com/en-us/iaas/Content/Identity/Concepts/policysyntax.htm, this is a beautiful and easy to read language that OCI uses to define the policy definitions.
Dynamic Group
The dynamic group is a layer of abstraction that enables us to add items dynamically to the group that will inherit the permissions defined in the policy. As you can see above the policy isn’t granting actions directly to the function it is applying them to the dynamic group.
The function becomes a member of the dynamic group and will take on the permissions. This is a nice flexible approach that gives us the capability of adding more objects that need the same permissions at a later date.
Within the definition of a dynamic group we can add Matching Rules, these rules are used to dynamically add objects as members of the group.
Function
Let us take a look at the function. We can see the selections we made during the creation of the function have been applied. A notable addition at this point is the Endpoint, as the name suggests this is the information we need to execute the function.
Great, we have an endpoint to invoke the function. So how we can get it to execute?!?
Executing the Function
We have a number of methods available to execute the function, this include:
- Fn Project’s command line interface (CLI)
- OCI’s CLI
- OCI SDK
- Signed Request to the Endpoint
Fn Project CLI
The Fn Project CLI can be used to execute the function, we can take advantage of the OCI Cloud Shell which has the Fn CLI already installed and pre-configured. Alternatively, you can install it locally and configure it.
Launch the Cloud Shell from the OCI interface.
Within the shell type in the following commands:
fn invoke data-engineering-oci-functions-application data-engineering-oci-functions-object-storage-zip
Oh no! That didn’t go well, what could be the problem.
Error invoking function. status: 502 message: function failed
As might have guessed this function needs some parameters. We need to tell it the name of the bucket and the objects that we would like to zip.
The parameters required by this function are listed in its documentation which can be found here:
The easiest way to invoke the function using parameters and the Fn CLI is to create a text file containing the JSON payload. For this example, we will save the content (with your values substituted as needed) into a file called payload.json.
{
"COMPARTMENT_ID": "your-compartment-id",
"REGION": "uk-london-1",
"SOURCE_BUCKET": "source-bucket",
"SOURCE_FILES": "/",
"TARGET_BUCKET": "destination-bucket",
"ALLOW_OVERWRITE": true
}
Now we can invoke the function and pass the payload using this command:
cat payload.json | fn invoke data-engineering-oci-functions-application data-engineering-oci-functions-object-storage-zip
All being well we should see confirmation of the execution.
{"startTime":"2023-09-01T21:41:36.018Z","endTime":"2023-09-01T21:41:38.799Z","runTime":"PT2.781S","code":200,"status":"Success","data":{"additionalInformation":{"Message":"Zip File: PBF_ZIP_data-engineering-oci-functions-destination_2023-09-01T21:41:36.028Z.zip uploaded to Bucket: data-engineering-oci-functions-destination"}}}
OCI CLI
The OCI command line interface can be used to execute the function. Once again to demonstrate this we will take advantage of the Cloud Shell. The OCI CLI can also be installed locally and configured if required.
Again I am not a fan of providing lengthy payloads on the command line and OCI CLI in many cases lets us take advantage of providing the parameters from a file. For the command we are interested in using, we can get a sample JSON file to use as a template.
oci fn function invoke --generate-full-command-json-input
This gives us some output that we can translate.
{
"body": "string",
"file": "/path/to/file",
"fnIntent": "httprequest|cloudevent",
"fnInvokeType": "detached|sync",
"functionId": "string"
}
To run the exact same command that we used with the Fn CLI we can substitute some values.
{
"body": "{\"COMPARTMENT_ID\": \"ocid1.compartment.oc1..<id>\",\"REGION\": \"uk-london-1\",\"SOURCE_BUCKET\": \"data-engineering-oci-functions-source\", \"SOURCE_FILES\": \"/\", \"TARGET_BUCKET\": \"data-engineering-oci-functions-destination\", \"ALLOW_OVERWRITE\": true }",
"file": "-",
"fnIntent": "httprequest",
"fnInvokeType": "sync",
"functionId": "ocid1.fnfunc.oc1.uk-london-1.<id>"
}
At this point I’m sure we are both thinking the Fn CLI is a little bit more friendly. Of course the OCI CLI is the Swiss army knife of the Oracle Cloud and is useful beyond working with functions!
OCI SDK
There a number of SDKs that you can use to interact with OCI, here I’m going to show how we can invoke the function using the Python OCI SDK. Once again I’m running the python from the command line of the Cloud Shell to take advantage of the configuration and seamless authentication.
# import the modules we need
from oci import functions as fn
import json, oci
# Load the payload file we created when using the Fn CLI
with open('payload.json', 'r') as ifile:
payload = json.load(ifile)
# Load the OCI configuration
config = oci.config.from_file()
# Create a Functions Management client to get function details
fn_mgmt_client = fn.FunctionsManagementClient(config)
fn_details = fn_mgmt_client.get_function(function_id='ocid1.fnfunc.oc1.uk-london-1.aaaaa...').data
# Now, set the invoke endpoint based on the function's details
fn_invoke = fn.FunctionsInvokeClient(config)
fn_invoke.base_client.set_region('uk-london-1')
fn_invoke.base_client.endpoint = fn_details.invoke_endpoint
# Invoke the function
response = fn_invoke.invoke_function(
function_id='ocid1.fnfunc.oc1.uk-london-1.aaaaaaa...',
invoke_function_body=json.dumps(payload)
)
# Print the response
print(response.data.text)
This is a simple script to showcase how to invoke the function using the Python SDK. For simplicity we reused the file already created to hold the payload for the Fn CLI example. This is a great example of why holding payloads in files is so much better than typing them on the command line. They are persisted and available to reuse, for debugging and for auditing.
After we’ve got the payload we use the OCI module to get the configuration from where the script is running. So we take advantage of the Cloud Shell environment’s integration with OCI.
The next notable part of the script is interrogating the function to get some details. We use the FunctionsManagementClient to look up the function and make the endpoint available within the script.
Finally, we setup a FunctionsInvokeClient which we use to execute the function and then print the response to the terminal.
This script was executed with the simple command:
python start_function.py
Signed HTTP Request
The final method to invoke the function is using a signed HTTP request from your local machine. To perform this action we need to ensure we have setup a private key and a public key. We can do this with:
openssl genpkey -algorithm RSA -out oci_api_key.pem
chmod go-rwx oci_api_key.pem
openssl rsa -pubout -in oci_api_key.pem -out oci_api_key_public.pem
This will store two files in your working directory called:
- oci_api_key.pem - this is the private key and should be kept secure
- oci_api_key_public.pem - this is the public key that we upload to Oracle under your Profile and then API Keys
Once uploaded you should configure the OCI CLI with:
oci setup config
Now all we need to do is make a call from our machine to OCI telling it to invoke the function.
oci raw-request --http-method POST \
--target-uri <invoke-endpoint> \
--request-body file://path/to/payload.json
In the previous script we didn’t access the invoke endpoint directly we used the SDK to get it programmatically. This time we will use the Cloud Shell to access it.
fn inspect function data-engineering-oci-functions-application data-engineering-oci-functions-object-storage-zip
This gives us something like:
{
"annotations": {
"fnproject.io/fn/invokeEndpoint": "https://.../invoke",
"oracle.com/oci/compartmentId": "ocid1.compartment.oc1..aaa...",
"oracle.com/oci/imageDigest": ""
},
"app_id": "ocid1.fnapp.oc1.uk-london-1.aaaa...",
"created_at": "2023-09-01T20:58:25.872Z",
"id": "ocid1.fnfunc.oc1.uk-london-1.aaaa...",
"memory": 256,
"name": "data-engineering-oci-functions-object-storage-zip",
"timeout": 300,
"updated_at": "2023-09-01T20:58:25.872Z"
}
We want to use the value of fnproject.io/fn/invokeEndpoint as the --target-uri.
oci raw-request --http-method POST \
--target-uri https://.../invoke \
--request-body file://payload.json
This method will only work if you decided to attach your application to the public subnets.
Creating Your Own OCI Function
Now we are going to take a look at creating our own OCI Function in Python. We know from working with the Pre-Built Function we have some things to think about.
-
What does our function do?
-
What network connectivity will it need?
-
What permissions will it need within OCI?
-
Do we need some tags for reporting and cost management?
-
Where will we develop the function?
Plan
Let us set out a plan for our function.
Function Task
We’ll use our function to grab some data from the Office of National Statistics. In fact, we will collect the UK population by year. We will take the data as is and make it available in OCI as an object in a storage bucket.
Connectivity
We do not need ingress to the function only egress so our VCN needs to have internet access and we can connect our application to the private subnets. Lucky for us in Part 1 created a VCN that matches this exact connectivity requirement!
Permissions
The function will need to be able to create objects in a storage bucket.
Reporting
For now, we won’t consider using tags or how will report usage, if there is enough interest we might cover that in a later blog post.
Development Environment
I don’t know about you, but I really don’t want to set up a development environment! We are indeed blessed, Oracle has provided a Cloud Code Editor and we are going to make use of it today and the superb integration it offers with OCI.
Code Editor
When you log into OCI towards the top right we see a Developer Tools icon and from there we can select Code Editor.
This editor is really good, supports lots of languages and benefits from integration with OCI. I love the Terminal that can be embedded within the Editor combining the elegance of the Cloud Shell and the richness of a functional IDE.
Creating a Folder
I’m going to use the Terminal to create the folder that we will be working in. I’ll opt for the Terminal in most cases simply because it's easier to share in the blog for you to follow along.
mkdir oci-functions-part-2
In this blog, we will reuse the Application that was created in Part 1. It has the network connectivity we need and saves us from repeating the same information here.
Setup Fn
Again using the Terminal we can set up our Fn CLI. List the available contexts and select the one you want to use. These next few commands are available under your Application on the Getting Started page.
fn list context
fn use context uk-london-1
Now we need to update oracle.compartment-id, the easiest way to get the compartment id is to navigate using the OCI console or simply copy the command from the Getting Started page under your application.
fn update context oracle.compartment-id ocid1.compartment.oc1..aaaa....
The next step is to update Fn with the registry that will be used to store the image that is created for our function.
fn update context registry [region-key].ocir.io/[tenancy-namespace]/[repo-name-prefix]
Now you can set up an Auth Token under your user profile. This is basically an App Password so you don’t have to share your actual password with services. If you don’t have an Auth Token to use set one up and save the token for future use.
Now you can login using:
docker login -u '[tenancy-namespace]/oracleidentitycloudservice/[username]' [region-key].ocir.io
Creating the Function
Whilst in the Terminal pane we can set up the function.
fn init --runtime python get-population-data
Using the Code Editor we can now open the containing folder called oci-functions-part-2.
But what is this we see, an error in the boilerplate?! It's just a case of the fdk library not being installed. In the Terminal pane we can sort this out by running:
pip install fdk --user
You might need to reopen the editor for the change to be recognised.
Updating func.py
Here is the code that we will use for the function definition.
import io
import json
import requests
import oci
import logging
import fdk
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def handle(ctx, payload):
# Extract the URL and bucket details from the payload
try:
body = json.loads(payload)
url = body["url"]
bucket_name = body["bucket_name"]
object_name = body["object_name"]
except (ValueError, KeyError) as e:
logger.error(f"Invalid payload or missing parameters: {str(e)}")
return {"error": "Invalid payload or missing parameters"}, 400 # 400 Bad Request
# Download the CSV file
try:
response = requests.get(url)
response.raise_for_status()
content = response.content
except requests.RequestException as e:
logger.error(f"Error downloading file from URL: {str(e)}")
return {"error": str(e)}, 500 # 500 Internal Server Error
# Upload the content to OCI Object Storage
try:
signer = oci.auth.signers.get_resource_principals_signer()
object_storage = oci.object_storage.ObjectStorageClient({}, signer=signer)
namespace = object_storage.get_namespace().data
put_response = object_storage.put_object(
namespace,
bucket_name,
object_name,
io.BytesIO(content)
)
if put_response.status != 200:
logger.error("Failed to upload to OCI bucket")
return {"error": "Failed to upload to OCI bucket"}, 500 # 500 Internal Server Error
except oci.exceptions.ServiceError as e:
logger.error(f"Error uploading to OCI bucket: {str(e)}")
return {"error": str(e)}, 500 # 500 Internal Server Error
logger.info("CSV file downloaded and uploaded to OCI successfully!")
return {"success": True, "message": "CSV file downloaded and uploaded to OCI successfully!"}, 200 # 200 OK
def main(ctx, data: io.BytesIO = None):
payload = data.getvalue().decode('utf-8')
response_body, status_code = handle(ctx, payload)
return fdk.response.Response(
ctx,
response_data=json.dumps(response_body),
status_code=status_code,
headers={"Content-Type": "application/json"}
)
Our function expects a JSON payload containing the parameters.
|
Parameter |
Description |
|---|---|
|
url |
The URL used to locate the file |
|
bucket_name |
The bucket name of where we want to store the file |
|
object_name |
The name of the object to create in the bucket that stores the file data |
Updating func.yaml
We’re show boating a little bit, instead of using the expected handler function name, we used another function name called main. So we need to update the yaml file to reflect that usage.
schema_version: 20180708
name: get-population-data
version: 0.0.3
runtime: python
build_image: fnproject/python:3.9-dev
run_image: fnproject/python:3.9
entrypoint: /python/bin/fdk /function/func.py main
memory: 256
As you can see on line 7 we have changed the function name to main. You can use this technique to use functions from different files.
Updating requirements.txt
Finally, we make a change to requirements.txt, in this case, we are using some libraries that are not shipped with fn so we add them here.
fdk>=0.1.60
requests==2.31.0
oci==2.112.0
Deploying the Function
This is super simple from within the Code Editor. We open the terminal and change to the function directory. Then run the deploy command:
fn -v deploy --app data-engineering-oci-functions-application
If the function was deployed successfully we eventually get confirmation in the terminal.
Updating function get-population-data using image [region-code].ocir.io/[tenancy-namespace]/data-engineering-part-two/get-population-data:0.0.4...
Successfully created function: get-population-data with [region-code].ocir.io/[tenancy-namespace]/data-engineering-part-two/get-population-data:0.0.4
We can test the deployment with our knowledge from Part 1. To test our function we need a payload.
{
"url": "https://www.ons.gov.uk/generator?format=csv&uri=/peoplepopulationandcommunity/populationandmigration/populationestimates/timeseries/ukpop/pop",
"bucket_name": "data-engineering-oci-functions-destination",
"object_name": "ons-population-data.csv"
}
Then we can invoke the function with the payload.
cat get-population-data-payload.json | fn invoke data-engineering-oci-functions-application get-population-data
Well, at least we know the function has been deployed!
{"error": "{'target_service': 'object_storage', 'status': 404, 'code': 'BucketNotFound', 'opc-request-id': '...', 'message': \"Either the bucket named 'data-engineering-oci-functions-destination' does not exist in the namespace '...' or you are not authorized to access it\", 'operation_name': 'put_object', 'timestamp': '2023-09-10T15:33:30.153337+00:00', 'client_version': 'Oracle-PythonSDK/2.112.0', 'request_endpoint': 'PUT https://objectstorage.uk-london-1.oraclecloud.com/n/.../b/data-engineering-oci-functions-destination/o/ons-population-data.csv', 'logging_tips': 'To get more info on the failing request, refer to https://docs.oracle.com/en-us/iaas/tools/python/latest/logging.html for ways to log the request/response details.', 'troubleshooting_tips': \"See https://docs.oracle.com/iaas/Content/API/References/apierrors.htm#apierrors_404__404_bucketnotfound for more information about resolving this error. Also see https://docs.oracle.com/iaas/api/#/en/objectstorage/20160918/Object/PutObject for details on this operation's requirements. If you are unable to resolve this object_storage issue, please contact Oracle support and provide them this full error message.\"}"}
At this point, we need to give the function some permissions to operate on the bucket.
Permissions
Let's start by creating a dynamic group. Give it a name, I’ve used data-engineering-functions-get-population-data and a description. Then add a rule, I’m keeping this simple and assigning the function to the group using its OCID.
All {resource.id = 'ocid1.fnfunc.oc1.uk-london-1.aaa....'}
Now we should create a policy that gives the dynamic group permission to store objects in the bucket.
Allow dynamic-group data-engineering-functions-get-population-data to read buckets in compartment data-engineering-oci-functions
Allow dynamic-group data-engineering-functions-get-population-data to manage objects in compartment data-engineering-oci-functions
Allow dynamic-group data-engineering-functions-get-population-data to read objectstorage-namespaces in compartment data-engineering-oci-functions
Allow dynamic-group data-engineering-functions-get-population-data to read compartments in compartment data-engineering-oci-functions
Now if we test our function we get success messages!
{"success": true, "message": "CSV file downloaded and uploaded to OCI successfully!"}
Now, let's explore in more detail how we secured the function.
Securing an OCI Function
OCI is basically a public cloud provider. We need to ensure that our functions aren’t exposed on the internet or to unauthorised users. There are a number of factors that impact the security of our function.
- Security policies
- Network access
- Vulnerable libraries
OCI provides us with tools that we can use to secure the functions. However if badly configured we can expose our functions to the bad actors.
In this blog, I shall focus on configuring security policies. In the future, I will post about how we can configure the Virtual Cloud Network (VCN) to protect us from unauthorised network access and how we can use OCI Image Scanning to help protect us against vulnerable libraries.
Security Overview
There are two sides to the coin when thinking about securing functions with policies. We need to consider:
- What the function should be able to do?
- Who should be able to access the function?
When applying policies in these scenarios we should always be using the principle of least permission (PoLP). We only assign the minimum permissions that are needed.
In OCI we create Policies by making Statements using the Policy Syntax. In a nutshell, all Policy Statements are constructed to allow a subject to perform an action (verb) on a resource type in a location with some conditions. Every statement follows the same pattern:
Allow <subject> to <verb> <resource-type> in <location> where <conditions>
There is no deny statement, everything is implicitly denied and access is only granted through Policy Statements.
Subjects
The subject is the entity that will be empowered to perform the action. This may be a group, dynamic group or user. It is not recommended to use users as subjects in policy statements. It's better to use groups or dynamic groups. For the simple reason, you can always add more subjects without having to change the Policy Statements.
Groups are collections of users, groups do not have any permissions unless they are the subject of a Policy Statement.
Dynamic Groups are flexible collections of compute resources. They can be assigned using a number of approaches as described in Managing Dynamic Groups. In essence, resources can be assigned individually using IDs or collectively using resource type or a tagging namespace. All the valid attributes can be used to build a more complex expression to describe how resources of a certain type with a particular tag value belonging to a compartment can be grouped together.
Verbs
There are a limited number of actions that can be assigned to subjects. They are described on the page Verbs. At the time of writing, they are inspect, read, use and manage. It is a very clear and concise list of actions and helps to avoid confusion in the construction of policies.
Resource Types
OCI has an extensive list of resource types and the documentation does a great job of documenting them. To have a browse through the different resource types available check out Resource Types.
For this post we will focus on Functions Policy Reference, we find a similar pattern across the other services. Functions have three resource types:
- fn-app
- fn-function
- fn-invocation
When creating the function we have encountered two console objects Applications and Functions. As you may have guessed fn-app is referring to the Applications and fn-function is referring to the Functions. You may assume when you use an fn-function you would be able to invoke it. This is not the case, the documentation describes the execution of a function to only be available for fn-invocation.
We need to be conscious of this when applying PoLP as randomly assigning permissions based on our expectation leads to scenarios where a dynamic group has more power than intended. Before we know it we face unintended consequences. Always RTM, there is no F I’m a professional!
Locations
The location is a simple approach to defining the scope of our statement. This of course relies on your resources being organised into compartments in a way that is conducive to writing Policy Statements. This would require a bit of upfront planning on your part. You may shrug at this statement, how important can a logical name defining a compartment be? Well important enough for Oracle to hammer the point in Best Practice! We should not underestimate the power of compartments its a great method of securing our cloud infrastructure into logical units.
There are two location types available to our Policy Statements; compartment and tenancy. It is recommended to use compartments when creating the Policy Statements to help avoid excessive permissions being assigned.
Conditions
The conditions are important for finely controlling the permissions being applied. There are a number of elements that can be used in a condition.
All conditions start with where and will be followed by a statement. That statement can include any of the variables listed here and potentially some more described by the service page.
Our statements can include an optional operator after the where which can either be all or any. all acts as a logical AND and any acts as a logical OR. For example:
Allow group DevAdmins
to manage fn-app
in compartment Development
where any {request.region='LHR', request.region='CWL'}
This example statement allows the group DevAdmins to manage the Applications in the compartment Development but only if the request is for London or Newport. This effectively locks the DevAdmins down to two regions.
If we just wanted to lock the DevAdmins to a single region would could use a simpler statement of:
Allow group DevAdmins
to manage fn-app
in compartment Development
where request.region='LHR'
Securing our Function
Now we understand a little about how the security works we should apply it to a function. It is good practice to first create a dynamic group. We can do that in the console or we can do it through the API using the command line. It is much easier to use the console and I suggest most users go that route. For fun here we will use the OCI CLI to show off some of the commands. As always I would gently push you towards using the Cloud Shell to avoid having to install and configure the tools locally.
We need to find the ocid of the compartment we are working with.
oci iam compartment list --all
Now we need to find the application ocid to use to look up the function ocid.
oci fn application list --all compartment-id <compartment_ocid>
Now we can use the application ocid to list the functions.
oci fn function list --all application-id <application_ocid>
Now we have the function ocid we can create the dynamic group. The dynamic group always resides in the root compartment so we don’t need to specify the compartment for this statement.
oci iam dynamic-group create --name data-engineering-oci-function-security-example --description 'A dynamic group to provide permissions to functions' --matching-rule "resource.id = '<function ocid>'"
The returned statement provides feedback on whether the object was created. In the text, you will find the ocid.
Now we can create the policy using the OCI CLI. Let us begin by writing our policies in a JSON file.
[
"Allow dynamic-group data-engineering-oci-function-security-example to manage objects in compartment data-engineering-oci-functions where all {target.bucket.name='data-engineering-oci-functions-destination',target.workrequest.type='OBJECT_CREATE'}"
]
This example brings some of the concepts we have discussed to life:
- We allow our new dynamic group to manage objects in buckets
- The buckets should be in the named compartment
We apply a condition that further restricts the permission to
- A bucket with a specific name
- And only allowing the single action of OBJECT_CREATE
We have prevented the function from overwriting, deleting and reading from the objects even though it has manage. This a good example of using conditions to apply the principle of least permission.
We can use this JSON file as an input to our command to create the new policy.
oci iam policy create --compartment-id <compartment_ocid> --name data-engineering-create-objects-only --description 'This policy is an example of using conditions in a policy statement' --statements file://statements.json
Invoking our Function
To invoke the function is very similar to the above. We decide on the subjects that need to have permission to invoke and create a group or dynamic group. Then create the policy to allow them to perform the action. Here is an example of the statement:
Allow dynamic-group data-engineering-function-invokers to use fn-invocation in compartment <compartment_ocid>
Again we can use the condition with a where statement if we need to apply more fine-grained controls.
In Summary
We can add functions to dynamic groups using matching rules. The dynamic groups can then be acted upon by Policy Statements to grant permissions. There is no deny statement available in a statement everything is denied implicitly and only allowed through an explicit statement.
When writing statements we must use:
- A subject which can be a user, group or dynamic group
- A verb that describes the type of action
- A resource type that describes the collection of objects
We can further tighten the statement by using:
- A compartment that logically describes a slice of your OCI tenancy
- A condition that can be used to apply fine-grain control based on variable filters
When creating statements we should use the principle of least permission (PoLP) to help minimize the attack vector on the account.
Ready to take your OCI functions to the next level?
Share this
Share this
More resources
Learn more about Pythian by reading the following blogs and articles.

Datascape Episode 56: Oracle Database, Exadata and Cloud Update

DML Part of a Parallel INSERT Suddenly Started to Execute Serially in Oracle

Old Dog with New Tricks: Indexing Null Columns in Oracle
Ready to unlock value from your data?
With Pythian, you can accomplish your data transformation goals and more.