Skip to content

feat: OPA Authorizer #777

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion deploy/helm/nifi-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ spec:
description: Settings that affect all roles and role groups. The settings in the `clusterConfig` are cluster wide settings that do not need to be configurable at role or role group level.
properties:
authentication:
description: Authentication options for NiFi (required). Read more about authentication in the [security documentation](https://docs.stackable.tech/home/nightly/nifi/usage_guide/security).
description: Authentication options for NiFi (required). Read more about authentication in the [security documentation](https://docs.stackable.tech/home/nightly/nifi/usage_guide/security#authentication).
items:
properties:
authenticationClass:
Expand All @@ -55,6 +55,42 @@ spec:
- authenticationClass
type: object
type: array
authorization:
description: Authorization options. Learn more in the [NiFi authorization usage guide](https://docs.stackable.tech/home/nightly/nifi/usage-guide/security#authorization).
nullable: true
properties:
opa:
description: Configure the OPA stacklet [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) and the name of the Rego package containing your authorization rules. Consult the [OPA authorization documentation](https://docs.stackable.tech/home/nightly/concepts/opa) to learn how to deploy Rego authorization rules with OPA.
nullable: true
properties:
cache:
default:
entryTimeToLive: 30s
maxEntries: 10000
description: Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value.
properties:
entryTimeToLive:
default: 30s
description: Time to live per entry
type: string
maxEntries:
default: 10000
description: Maximum number of entries in the cache; If this threshold is reached then the least recently used item is removed.
format: uint32
minimum: 0.0
type: integer
type: object
configMapName:
description: The [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) for the OPA stacklet that should be used for authorization requests.
type: string
package:
description: The name of the Rego package containing the Rego rules for the product.
nullable: true
type: string
required:
- configMapName
type: object
type: object
createReportingTaskJob:
default:
enabled: true
Expand Down
172 changes: 171 additions & 1 deletion docs/modules/nifi/pages/usage_guide/security.adoc
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
= Security
:description: Secure Apache NiFi on Kubernetes with TLS, authentication, and authorization using the Stackable operator. Configure LDAP, OIDC, and sensitive data encryption.
:nifi-docs-authorization: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#multi-tenant-authorization
:nifi-docs-access-policies: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#access-policies
:nifi-docs-component-level-access-policies: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#component-level-access-policies
:nifi-docs-access-policy-inheritance: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#access-policy-inheritance
:nifi-docs-fileusergroupprovider: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#fileusergroupprovider
:nifi-docs-fileaccesspolicyprovider: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#fileaccesspolicyprovider
:nifi-docs-sensitive-properties-key: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#nifi_sensitive_props_key
:nifi-opa-plugin: https://github.com/DavidGitter/nifi-opa-plugin/
:opa-rego-docs: https://www.openpolicyagent.org/docs/latest/#rego

== TLS

Expand Down Expand Up @@ -168,7 +173,7 @@ stringData:

NiFi supports {nifi-docs-authorization}[multiple authorization methods], the available authorization methods depend on the chosen authentication method.

Authorization is not fully implemented by the Stackable Operator for Apache NiFi.
The Stackable Operator for Apache NiFi supports a default authorization methods for each authentication method and authorization with Open Policy Agent.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand that sentence at all. Would you mind reformulating or explaining it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea is that there are different default authorization methods depending on the authentication method and OPA authorization which works with every authentication method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel then this can be merged with the sentence above?

The Stackable Operator for Apache NiFi supports {nifi-docs-authorization}[multiple authorization methods], the available authorization methods depend on the chosen authentication method. Using the Open Policy Agent for authorization is independent of the authentication method.


[#authorization-single-user]
=== Single user
Expand All @@ -190,6 +195,171 @@ With this authorization method, all authenticated users have administrator capab
An admin user with an auto-generated password is created that can access the NiFi API.
The password for this user is stored in a Kubernetes Secret called `<nifi-name>-oidc-admin-password`.

[#authorization-opa]
=== Open Policy Agent (OPA)

NiFi can be configured to delegate authorization decisions to an Open Policy Agent (OPA) instance. More information on the setup and configuration of OPA can be found in the xref:opa:index.adoc[OPA Operator documentation].

A NiFi cluster can be configured with OPA authorization by adding this section to the configuration:

[source,yaml]
----
spec:
clusterConfig:
authorization:
opa:
configMapName: simple-opa # <1>
package: my-nifi-rules # <2>
cache:
entryTimeToLive: 5s # <3>
maxEntries: 10 # <4>
----
<1> The name of your OPA Stacklet (`simple-opa` in this case)
<2> The rego rule package to use for policy decisions.
The package needs to contain an `allow` rule.
This is optional and defaults to the name of the NiFi Stacklet.
<3> TTL for items in the cache in NiFi.
<4> Maximum number of concurrent entries in the cache in NiFi

[#defining-rego-rules]
=== Defining rego rules

For a general explanation of how rules are written, please refer to the {opa-rego-docs}[OPA documentation]. Authorization with OPA is done using a {nifi-opa-plugin}[custom authorizer provided by a plugin for NiFi].

[#opa-inputs]
==== OPA Inputs
The payload sent by NiFi with each request to OPA, that is accessible within the rego rules, has the following structure:

[cols="1,2,1"]
|===
| Payload Field| Description| Possible Values
| action.name
| The action taken against the resource.
|`read`, `write`
| resource.id
| The unique identifier of the resource that is being authorized.
|
| resource.name
| The name of the resource that is being authorized.
|
| resource.safeDescription
| The description of the resource that is being authorized.
|
| requestedResource.id
| The unique identifier of the original resource that was requested (see <<component-level-access-policies>>).
|
| requestedResource.name
| The name of the original resource that is being authorized on (see <<component-level-access-policies>>).
|
| requestedResource.safeDescription
| The description of the resource that is being authorized on (see <<component-level-access-policies>>).
|
| identity.name
| The name of the identity/user accessing the resource.
|
| identity.groups
| Comma-separated list of groups that the identity/user accessing the resource belongs to.
|
| properties.isAccessAttempt
| Whether this is a direct access attempt of the resource or if it's being checked as part of another response.
| `true`, `false` (String)
| isAnonymous
| Whether the entity accessing the resource is anonymous.
| `true`, `false` (String)
| resourceContext
| Object containing the event attributes to make additional access decisions for provenance events.
| ```{"": ""}``` if empty
| userContext
| Additional context for the user to make additional access decisions.
| ```{"": ""}``` if empty
|===

[#opa-result]
==== OPA Result

The OPA authorizer plugin expects relo rules to be named `allow` and to return a result following this schema:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The OPA authorizer plugin expects relo rules to be named `allow` and to return a result following this schema:
The OPA authorizer plugin expects rego rules to be named `allow` and to return a result following this schema:

[source,json]
----
{
"allowed": <Boolean>, # <1>
"resourceNotFound": <Boolean>, # <2>
"dumpCache": <Boolean>, # <3>
"message": <String>, # <4>
}
----
<1> Whether the action against the resource is allowed. Optional, defaults to false.
<2> Whether no rule was found for the authorization request. This should only be set to true in the default rule. If set to true the value of the "allowed" field will be ignored. Optional, defaults to false.
<3> Whether the whole local cache in the OPA authorizer plugin in NiFi should be invalidated. Optional, defaults to false.
<4> An optional error message that is shown to the user when access is denied.

[#access-policies]
==== Access Policies
NiFi uses {nifi-docs-access-policies}[access policies] to manage access to system-wide resources like the user interface.

[#component-level-access-policies]
==== Component Level Access Policies and Access Policy Inheritance

{nifi-docs-component-level-access-policies}[Component Level Access Policies] allow managing granular access to components like process-groups and processors. Components can {nifi-docs-access-policy-inheritance}[inherite access policies] defined for parent components, e.g. a process group is the parent component for a processor component.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{nifi-docs-component-level-access-policies}[Component Level Access Policies] allow managing granular access to components like process-groups and processors. Components can {nifi-docs-access-policy-inheritance}[inherite access policies] defined for parent components, e.g. a process group is the parent component for a processor component.
{nifi-docs-component-level-access-policies}[Component Level Access Policies] allow managing granular access to components like process-groups and processors. Components can {nifi-docs-access-policy-inheritance}[inherite access policies] defined for parent components, e.g. a process group is the parent component for a contained processor component.


The payload field `requestedResource` contains the id, name and description of the original resource that was requested. In cases with inherited policies, this will be an ancestor resource of the current resource. For the initial request, and cases without inheritance, the requested resource will be the same as the current resource.

When an authorizer returns "resourceNotFound" as result instead of an authorization decision, NiFi will send an authorization request for the parent component. Access policy inheritance can be recursive up to the root component. If "resourceNotFound" is returned for an authorization request and the component doesn't have a parent component, NiFi will deny access to the component.

To manage access for all process groups in the NiFi instance a rule has to be defined for the root process group which is identified by the resource name "NiFi Flow" and a resource id generated at random ("/process-groups/<uuid>").

[source,rego]
----
default allow := {
"resourceNotFound": true
} <1>

allow := {
"allowed": true
} if {
input.resource.name == "NiFi Flow"
startswith(input.resource.id, "/process-groups")
} <2>

allow := {
"allowed": false
} if {
input.resource.id == "/process-groups/a10c311e-0196-1000-2856-dc0606d3c5d7"
input.identity.name == "alice"
} <3>
----
<1> Default rule returns should `"resourceNotFound": true`. If this is not set NiFi's access policy inheritance won't work. Any values for the `allowed` field in the response will be ignored.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<1> Default rule returns should `"resourceNotFound": true`. If this is not set NiFi's access policy inheritance won't work. Any values for the `allowed` field in the response will be ignored.
<1> The default rule should return `"resourceNotFound": true`. If this is not set, NiFi's access policy inheritance will not work. Any values for the `allowed` field in the response will be ignored.

<2> A rule that grants all users access to the root process group and thus to all components in the NiFi instance.
<3> A rule that denies access to a specific process group for the user "alice". For this process group the default rego rule will not be applied and NiFi's component inhertiance will not be used. All child components of this process group will also be authorized based on this rule unless a more granular rule overrides it.

[#communication-between-nifi-nodes]
==== Communication between NiFi nodes
To allow communication between NiFi nodes an additional rego rule is required:
[source,rego]
----
allow := {
"allowed": true
} if {
input.identity.name == "CN=generated certificate for pod" # <1>
input.resource.id == "/proxy" # <2>
}
----
<1> The identity of NiFi nodes authenticated with TLS certificates provided by the secrets operator.
<2> Only access to the `/proxy` API is required.

[#caching]
==== Caching

The OPA authorizer has a mechanism to cache results from OPA which can be configured in the NifCluster spec (see above). To delete the whole cache add `"dumpCache": true` to the result.
[source,rego]
----
allow := {
"allowed": false
"dumpCache": true
} if {
...
}
----

[#encrypting-sensitive-properties]
== Encrypting sensitive properties on disk

Expand Down
Loading