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 16 commits into
base: main
Choose a base branch
from
Open

feat: OPA Authorizer #777

wants to merge 16 commits into from

Conversation

labrenbe
Copy link
Member

Description

Adds support for authorization with OPA using Nifi OPA Plugin .

=== NAME  kuttl
    harness.go:408: run tests finished
    harness.go:516: cleaning up
    harness.go:573: removing temp folder: ""
--- PASS: kuttl (539.62s)
    --- PASS: kuttl/harness (0.00s)
        --- PASS: kuttl/harness/opa_nifi-1.27.0_zookeeper-latest-3.9.3_opa-latest-1.0.1_openshift-false (261.39s)
        --- PASS: kuttl/harness/opa_nifi-2.2.0_zookeeper-latest-3.9.3_opa-latest-1.0.1_openshift-false (268.44s)
        --- PASS: kuttl/harness/opa_nifi-1.28.1_zookeeper-latest-3.9.3_opa-latest-1.0.1_openshift-false (278.21s)
PASS

Should not be merged before: stackabletech/docker-images#1058

Definition of Done Checklist

  • Not all of these items are applicable to all PRs, the author should update this template to only leave the boxes in that are relevant
  • Please make sure all these things are done and tick the boxes
# Author
- [ ] Changes are OpenShift compatible
- [ ] CRD changes approved
- [ ] CRD documentation for all fields, following the [style guide](https://docs.stackable.tech/home/nightly/contributor/docs/style-guide).
- [ ] Helm chart can be installed and deployed operator works
- [ ] Integration tests passed (for non trivial changes)
- [ ] Changes need to be "offline" compatible
# Reviewer
- [ ] Code contains useful comments
- [ ] Code contains useful logging statements
- [ ] (Integration-)Test cases added
- [ ] Documentation added or updated. Follows the [style guide](https://docs.stackable.tech/home/nightly/contributor/docs/style-guide).
- [ ] Changelog updated
- [ ] Cargo.toml only contains references to git tags (not specific commits or branches)
# Acceptance
- [ ] Feature Tracker has been updated
- [ ] Proper release label has been added
- [ ] [Roadmap](https://github.com/orgs/stackabletech/projects/25/views/1) has been updated

@labrenbe labrenbe self-assigned this Apr 15, 2025
@labrenbe labrenbe marked this pull request as ready for review April 15, 2025 08:54
@labrenbe labrenbe linked an issue Apr 22, 2025 that may be closed by this pull request
@maltesander maltesander self-requested a review April 22, 2025 14:57
Copy link
Member

@maltesander maltesander left a comment

Choose a reason for hiding this comment

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

first batch of comments

@@ -168,7 +169,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.

Comment on lines 207 to 211
configMapName: simple-opa <1>
package: my-druid-rules <2>
cache:
entryTimeToLive: 5s <3>
maxEntries: 10 <4>
Copy link
Member

@maltesander maltesander Apr 22, 2025

Choose a reason for hiding this comment

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

Suggested change
configMapName: simple-opa <1>
package: my-druid-rules <2>
cache:
entryTimeToLive: 5s <3>
maxEntries: 10 <4>
configMapName: simple-opa # <1>
package: my-nifi-rules # <2>
cache:
entryTimeToLive: 5s # <3>
maxEntries: 10 # <4>

Comment on lines 228 to 252
{
"action": {
"name": <String> <1>
},
"properties": {
"isAccessAttempt": <String>, <2>
"isAnonymous": <String> <3>
},
"resource": { <4>
"id": <String>,
"name": <String>,
"safeDescription": <String>
},
"requestedResource": { <5>
"id": <String>,
"name": <String>,
"safeDescription": <String>
},
"identity": {
"name": <String>, <6>
"groups": String, <7>
},
"resourceContext": <Object>, <8>
"userContext": <Object> <9>
}
Copy link
Member

Choose a reason for hiding this comment

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

the footnotes do not render correcly

"userContext": <Object> <9>
}
----
<1> The action taken against the resource. Possible values: `"read"` or `"write"`.
Copy link
Member

Choose a reason for hiding this comment

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

I think ticking or quoting is enough?

Suggested change
<1> The action taken against the resource. Possible values: `"read"` or `"write"`.
<1> The action taken against the resource. Possible values: `read` or `write`.


=== Defining rego rules

For a general explanation of how rules are written, please refer to the {opa-rego-docs}[OPA documentation]. For more information on NiFi's access policies please refer to the {nifi-docs-access-policies}[NiFi documentation].
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
For a general explanation of how rules are written, please refer to the {opa-rego-docs}[OPA documentation]. For more information on NiFi's access policies please refer to the {nifi-docs-access-policies}[NiFi documentation].
For a general explanation of how rules are written, please refer to the {opa-rego-docs}[OPA documentation]. For more information on NiFi's access policies please refer to the {nifi-docs-access-policies}[NiFi documentation].


For a general explanation of how rules are written, please refer to the {opa-rego-docs}[OPA documentation]. For more information on NiFi's access policies please refer to the {nifi-docs-access-policies}[NiFi documentation].

Inside of your rego rules you have access to the content of the authorization request from NiFi as input for OPA:
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
Inside of your rego rules you have access to the content of the authorization request from NiFi as input for OPA:
The payload sent by NiFi with each request to OPA, that is accessible within the rego rules, has the following structure:

maxEntries: 10 <4>
----
<1> The name of your OPA Stacklet (`simple-opa` in this case)
<2> The RegoRule package to use for policy decisions.
Copy link
Member

Choose a reason for hiding this comment

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

I would go for "rego rule" instead of "RegoRule" as in other cases.

Comment on lines 256 to 258
<3> Whether the entity accessing the resource is anonymous. Possible values: `"true"` or `"false"`.
<4> The current resource being authorized on.
<5> The original resource being 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.
Copy link
Member

Choose a reason for hiding this comment

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

I think this whole section is probably best displayed as table. Something like:

payload - description - possible values
action.name - The action taken against the resource - read/write

Comment on lines 120 to 125
/// Read more about authentication in the [security documentation](DOCS_BASE_URL_PLACEHOLDER/nifi/usage_guide/security#_authentication).
// We don't add `#[serde(default)]` here, as we require authentication
pub authentication: Vec<ClientAuthenticationDetails>,

/// Authorization options.
/// Learn more in the [NiFi authorization usage guide](DOCS_BASE_URL_PLACEHOLDER/nifi/usage-guide/security#authorization).
Copy link
Member

Choose a reason for hiding this comment

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

It fixes "security#_authentication", shouldnt it be "security#_authorization" as well (or the other way round)?

Copy link
Member

@maltesander maltesander left a comment

Choose a reason for hiding this comment

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

I think we should already prepare a more extensive test environment.
Meaning having a couple of users in keycloak with different access in the regos that can be tested via API and easily extended?

authorization:
opa:
configMapName: simple-opa <1>
package: my-druid-rules <2>
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
package: my-druid-rules <2>
package: my-nifi-rules <2>

<1> The name of your OPA Stacklet (`simple-opa` in this case)
<2> The RegoRule package to use for policy decisions.
The package needs to contain an `allow` rule.
This is optional and defaults to the name of the Druid Stacklet.
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
This is optional and defaults to the name of the Druid Stacklet.
This is optional and defaults to the name of the NiFi Stacklet.

Comment on lines 183 to 190
|| match nifi.spec.cluster_config.authorization.to_owned() {
Some(trino_authorization) => match trino_authorization.opa {
Some(opa_config) => opa_config.opa.config_map_name == config_map.name_any(),
None => false,
},
None => false,
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we split this to make it more readable?

fn references_config_map(
    nifi: &DeserializeGuard<v1alpha1::NifiCluster>,
    config_map: &DeserializeGuard<ConfigMap>,
) -> bool {
    let Ok(nifi) = &nifi.0 else {
        return false;
    };

    let references_zookeeper_config_map =
        nifi.spec.cluster_config.zookeeper_config_map_name == config_map.name_any();
    let references_authorization_config_map = references_authorization_config_map(nifi, config_map);

    references_zookeeper_config_map || references_authorization_config_map
}

fn references_authorization_config_map(
    nifi: &v1alpha1::NifiCluster,
    config_map: &DeserializeGuard<ConfigMap>,
) -> bool {
    nifi.spec
        .cluster_config
        .authorization
        .as_ref()
        .and_then(|authz| authz.opa.as_ref())
        .map(|opa_config| opa_config.opa.config_map_name == config_map.name_any())
        .unwrap_or(false)
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Did this in 13e60bc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a NiFi Authorizer that interfaces with OPA
2 participants