OCI Functions | Part 3 - Securing OCI Functions

OCI Functions | Part 3 - Securing OCI Functions

Welcome back to Part 3 of OCI Functions. If you missed them, you can find the previous posts here:

In this post, we will 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 minimise the attack vector on the account. In the near future, I will be writing more about security within OCI, if you don’t want to miss it subscribe to our newsletter or follow us on LinkedIn.