Skip to content

Commit 1590b98

Browse files
feat: OPA Authorizer (#777)
* add support for authorization with OPA * add docs on authorization with OPA * update helm chart * rename input.user to input.identity * add opa cm watch * remove unused test * update rego with new opa response schema * update docs * improve docs * update docs * fix docs * refactor references_config_map function * fix docs * wip: add integration test * add retries to nifi calls during test * simplify rego rules * move nifi flow copying into init container * remove oidc test and fix oidc-opa test with tls disabled * Apply suggestions from code review Co-authored-by: Malte Sander <[email protected]> * improve docs * require opa operator during integration tests * reduce flow election time * reduce verbosity, improve response handling * linter attempt 1 * remove securityContext from keycloak * remove securityContext from test container * Apply suggestions from code review Co-authored-by: Malte Sander <[email protected]> * remove 'json' annotation from code snippet * fix docs formatting * Update docs/modules/nifi/pages/usage_guide/security.adoc Co-authored-by: Malte Sander <[email protected]> * add caching default values to docs --------- Co-authored-by: Malte Sander <[email protected]>
1 parent 8b1cc2a commit 1590b98

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1683
-414
lines changed

deploy/helm/nifi-operator/crds/crds.yaml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ spec:
2929
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.
3030
properties:
3131
authentication:
32-
description: Authentication options for NiFi (required). Read more about authentication in the [security documentation](https://docs.stackable.tech/home/nightly/nifi/usage_guide/security).
32+
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).
3333
items:
3434
properties:
3535
authenticationClass:
@@ -55,6 +55,42 @@ spec:
5555
- authenticationClass
5656
type: object
5757
type: array
58+
authorization:
59+
description: Authorization options. Learn more in the [NiFi authorization usage guide](https://docs.stackable.tech/home/nightly/nifi/usage-guide/security#authorization).
60+
nullable: true
61+
properties:
62+
opa:
63+
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.
64+
nullable: true
65+
properties:
66+
cache:
67+
default:
68+
entryTimeToLive: 30s
69+
maxEntries: 10000
70+
description: Least Recently Used (LRU) cache with per-entry time-to-live (TTL) value.
71+
properties:
72+
entryTimeToLive:
73+
default: 30s
74+
description: Time to live per entry
75+
type: string
76+
maxEntries:
77+
default: 10000
78+
description: Maximum number of entries in the cache; If this threshold is reached then the least recently used item is removed.
79+
format: uint32
80+
minimum: 0.0
81+
type: integer
82+
type: object
83+
configMapName:
84+
description: The [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) for the OPA stacklet that should be used for authorization requests.
85+
type: string
86+
package:
87+
description: The name of the Rego package containing the Rego rules for the product.
88+
nullable: true
89+
type: string
90+
required:
91+
- configMapName
92+
type: object
93+
type: object
5894
createReportingTaskJob:
5995
default:
6096
enabled: true

docs/modules/nifi/pages/usage_guide/security.adoc

Lines changed: 171 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
= Security
22
:description: Secure Apache NiFi on Kubernetes with TLS, authentication, and authorization using the Stackable operator. Configure LDAP, OIDC, and sensitive data encryption.
33
:nifi-docs-authorization: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#multi-tenant-authorization
4+
:nifi-docs-access-policies: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#access-policies
5+
:nifi-docs-component-level-access-policies: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#component-level-access-policies
6+
:nifi-docs-access-policy-inheritance: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#access-policy-inheritance
47
:nifi-docs-fileusergroupprovider: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#fileusergroupprovider
58
:nifi-docs-fileaccesspolicyprovider: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#fileaccesspolicyprovider
69
:nifi-docs-sensitive-properties-key: https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#nifi_sensitive_props_key
10+
:nifi-opa-plugin: https://github.com/DavidGitter/nifi-opa-plugin/
11+
:opa-rego-docs: https://www.openpolicyagent.org/docs/latest/#rego
712

813
== TLS
914

@@ -166,9 +171,7 @@ stringData:
166171
[#authorization]
167172
== Authorization
168173

169-
NiFi supports {nifi-docs-authorization}[multiple authorization methods], the available authorization methods depend on the chosen authentication method.
170-
171-
Authorization is not fully implemented by the Stackable Operator for Apache NiFi.
174+
The Stackable Operator for Apache NiFi supports {nifi-docs-authorization}[multiple authorization methods], the available authorization methods depend on the chosen authentication method. Using Open Policy Agent for authorization is independent of the authentication method.
172175

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

196+
[#authorization-opa]
197+
=== Open Policy Agent (OPA)
198+
199+
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].
200+
201+
A NiFi cluster can be configured with OPA authorization by adding this section to the configuration:
202+
203+
[source,yaml]
204+
----
205+
spec:
206+
clusterConfig:
207+
authorization:
208+
opa:
209+
configMapName: simple-opa # <1>
210+
package: my-nifi-rules # <2>
211+
cache:
212+
entryTimeToLive: 5s # <3>
213+
maxEntries: 10 # <4>
214+
----
215+
<1> The name of your OPA Stacklet (`simple-opa` in this case)
216+
<2> The rego rule package to use for policy decisions.
217+
The package needs to contain an `allow` rule.
218+
This is optional and defaults to the name of the NiFi Stacklet.
219+
<3> TTL for items in the cache in NiFi. Optional, defaults to 30 seconds.
220+
<4> Maximum number of concurrent entries in the cache in NiFi. Optional, defaults to 10000 entries.
221+
222+
[#defining-rego-rules]
223+
=== Defining rego rules
224+
225+
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].
226+
227+
[#opa-inputs]
228+
==== OPA Inputs
229+
The payload sent by NiFi with each request to OPA, that is accessible within the rego rules, has the following structure:
230+
231+
[cols="1,2,1"]
232+
|===
233+
| Payload Field| Description| Possible Values
234+
| action.name
235+
| The action taken against the resource.
236+
|`read`, `write`
237+
| resource.id
238+
| The unique identifier of the resource that is being authorized. This might be a parent component in the case of `resourceNotFound` is set to `true`.
239+
|
240+
| resource.name
241+
| The name of the resource that is being authorized. This might be a parent component in the case of `resourceNotFound` is set to `true`.
242+
|
243+
| resource.safeDescription
244+
| The description of the resource that is being authorized.
245+
|
246+
| requestedResource.id
247+
| The unique identifier of the original resource that was requested (see <<component-level-access-policies>>).
248+
|
249+
| requestedResource.name
250+
| The name of the original resource that is being authorized on (see <<component-level-access-policies>>).
251+
|
252+
| requestedResource.safeDescription
253+
| The description of the original resource that is being authorized on (see <<component-level-access-policies>>).
254+
|
255+
| identity.name
256+
| The name of the identity/user accessing the resource.
257+
|
258+
| identity.groups
259+
| Comma-separated list of groups that the identity/user accessing the resource belongs to.
260+
|
261+
| properties.isAccessAttempt
262+
| Whether this is a direct access attempt of the resource or if it's being checked as part of another response.
263+
| `true`, `false` (String)
264+
| isAnonymous
265+
| Whether the entity accessing the resource is anonymous.
266+
| `true`, `false` (String)
267+
| resourceContext
268+
| Object containing the event attributes to make additional access decisions for provenance events.
269+
| ```{"": ""}``` if empty
270+
| userContext
271+
| Additional context for the user to make additional access decisions.
272+
| ```{"": ""}``` if empty
273+
|===
274+
275+
[#opa-result]
276+
==== OPA Result
277+
278+
The OPA authorizer plugin expects rego rules to be named `allow` and to return a result following this schema:
279+
[source]
280+
----
281+
{
282+
"allowed": <Boolean>, # <1>
283+
"resourceNotFound": <Boolean>, # <2>
284+
"dumpCache": <Boolean>, # <3>
285+
"message": <String>, # <4>
286+
}
287+
----
288+
<1> Whether the action against the resource is allowed. Optional, defaults to false.
289+
<2> Whether no rule was found for the authorization request. This should only be set to true in the default rule to e.g. forward policy decisions to parent components. If set to true the value of the "allowed" field will be ignored. Optional, defaults to false.
290+
<3> Whether the whole local cache in the OPA authorizer plugin in NiFi should be invalidated. Optional, defaults to false.
291+
<4> An optional error message that is shown to the user when access is denied.
292+
293+
[#access-policies]
294+
==== Access Policies
295+
NiFi uses {nifi-docs-access-policies}[access policies] to manage access to system-wide resources like the user interface.
296+
297+
[#component-level-access-policies]
298+
==== Component Level Access Policies and Access Policy Inheritance
299+
300+
{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.
301+
302+
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.
303+
304+
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.
305+
306+
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>").
307+
308+
[source,rego]
309+
----
310+
default allow := {
311+
"resourceNotFound": true
312+
} # <1>
313+
314+
allow := {
315+
"allowed": true
316+
} if {
317+
input.resource.name == "NiFi Flow"
318+
startswith(input.resource.id, "/process-groups")
319+
} # <2>
320+
321+
allow := {
322+
"allowed": false
323+
} if {
324+
input.resource.id == "/process-groups/a10c311e-0196-1000-2856-dc0606d3c5d7"
325+
input.identity.name == "alice"
326+
} # <3>
327+
----
328+
<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.
329+
<2> A rule that grants all users access to the root process group and thus to all components in the NiFi instance.
330+
<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.
331+
332+
[#communication-between-nifi-nodes]
333+
==== Communication between NiFi nodes
334+
To allow communication between NiFi nodes an additional rego rule is required:
335+
[source,rego]
336+
----
337+
allow := {
338+
"allowed": true
339+
} if {
340+
input.identity.name == "CN=generated certificate for pod" # <1>
341+
input.resource.id == "/proxy" # <2>
342+
}
343+
----
344+
<1> The identity of NiFi nodes authenticated with TLS certificates provided by the secrets operator.
345+
<2> Only access to the `/proxy` API is required.
346+
347+
[#caching]
348+
==== Caching
349+
350+
The OPA authorizer has a mechanism to cache results from OPA which can be configured in the NifiCluster spec (see above). To delete the whole cache add `"dumpCache": true` to the result.
351+
[source,rego]
352+
----
353+
allow := {
354+
"allowed": false
355+
"dumpCache": true
356+
} if {
357+
...
358+
}
359+
----
360+
193361
[#encrypting-sensitive-properties]
194362
== Encrypting sensitive properties on disk
195363

0 commit comments

Comments
 (0)