Skip to content
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

feat: Support OPA role mapping #582

Merged
merged 78 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
ba1c7ab
First skeleton of opa integration
Maleware Dec 19, 2024
d502af3
WIP implementation opa-role-mapping
Maleware Dec 23, 2024
b3c3d0f
Add SecurityManager dynamically
Maleware Dec 23, 2024
25f143a
Better OPA_IMPORT
Maleware Dec 23, 2024
a125456
Security manager own file in Docker-Images. Fixing python expression
Maleware Dec 23, 2024
1e17009
making clippy happy for now
Maleware Dec 23, 2024
e237a7a
Updating some approaches
Maleware Dec 27, 2024
8614577
Adding more rules, more sophisticated handling of stuff
Maleware Dec 27, 2024
a4dda2a
Defaults are working
Maleware Dec 30, 2024
50050bc
Better interfering of package path
Maleware Jan 2, 2025
cc35802
Happy Clippy
Maleware Jan 2, 2025
9ae3dca
update OpaSupersetSecurityManager import path
labrenbe Jan 6, 2025
40c5cd1
import new opa_authorizer module
labrenbe Jan 10, 2025
a92dac5
Removing some ToDo's. Better comments
Maleware Jan 13, 2025
a5c22ec
Adding OpaRolesCache with 10 minutes default
Maleware Jan 13, 2025
535e2ea
pre commit becomes happy
Maleware Jan 13, 2025
1a6bcbf
Merge branch 'main' into feature/opa-role-mapping
Maleware Jan 13, 2025
c215250
create opa test basics
labrenbe Jan 14, 2025
06515ab
rename test directory
labrenbe Jan 14, 2025
a53c784
fix test-definition
labrenbe Jan 14, 2025
5ea83bd
fix typo
labrenbe Jan 14, 2025
748030f
fix opa test scaffold
labrenbe Jan 15, 2025
c77f0ba
Adding rule_name to be defined by the user. defaults to empty string
Maleware Jan 15, 2025
e19a39c
Adding default to rule_name
Maleware Jan 15, 2025
5e0c32e
StackableOpaRule to string as we interfere from CRDs
Maleware Jan 15, 2025
33d3f3a
Adding ttl to crds. Default to 10.
Maleware Jan 15, 2025
bd97146
cache_ttl now Duration type. Converted to seconds in superset_config.py
Maleware Jan 15, 2025
1d27837
wip: integration tests
labrenbe Jan 15, 2025
4419843
integrate feedback
labrenbe Jan 16, 2025
6a8f8e7
create basic user role test
labrenbe Jan 16, 2025
203e390
First documentation draft
Maleware Jan 17, 2025
cd6a64c
Adding reference to opa user-info-fetcher
Maleware Jan 17, 2025
b217dd4
Merge branch 'main' into feature/opa-role-mapping
Maleware Jan 20, 2025
49da77a
Updating changelog
Maleware Jan 20, 2025
f76ba93
fix rego and first check in integration test
labrenbe Jan 20, 2025
deddd5a
fix formatting issues
labrenbe Jan 21, 2025
1093fa2
Making rust fmt happy
Maleware Jan 21, 2025
75f19d2
lint with ruff formatter
labrenbe Jan 21, 2025
f1de6ba
use TtlCache from operator-rs
labrenbe Jan 21, 2025
a09d2a5
Regenerate charts
Maleware Jan 22, 2025
05b8b0b
Merge branch 'main' into feature/opa-role-mapping
Maleware Jan 22, 2025
98a7a55
Making pre-commit happy
Maleware Jan 22, 2025
a7f1bb1
apply typos and formatting corrections
Maleware Jan 28, 2025
48c8a52
update chart
labrenbe Jan 30, 2025
8ed6194
adress feedback in PR and rename envs
labrenbe Jan 31, 2025
23f08e5
Merge remote-tracking branch 'origin/main' into feature/opa-role-mapping
labrenbe Jan 31, 2025
5b006b8
Merge remote-tracking branch 'origin/main' into feature/opa-role-mapping
labrenbe Jan 31, 2025
4b23391
fix changelog
labrenbe Jan 31, 2025
a12c05d
main merge
razvan Feb 18, 2025
161ac74
Update opa tests.
razvan Feb 18, 2025
ec8c3d5
Merge branch 'main' into feature/opa-role-mapping
razvan Feb 21, 2025
ef001e7
support custom image for opa tests
razvan Feb 21, 2025
ff0ee46
create and assign new role via API
razvan Feb 21, 2025
c0f2368
fix typos
razvan Feb 21, 2025
d02e7bb
add some comments
razvan Feb 21, 2025
472817f
opa kuttl test is green (again)
razvan Feb 22, 2025
5885684
silence most of Pyright errors and warnings
razvan Feb 23, 2025
434232f
Update rust/crd/src/lib.rs
razvan Feb 25, 2025
93819b5
Renaming fields and structs
razvan Feb 25, 2025
1438a24
format code
razvan Feb 25, 2025
5268e8e
make field required
razvan Feb 25, 2025
95c22b1
pass on opa endpoint instead of base url to the authorizer
razvan Feb 26, 2025
26e95f3
Update docs/modules/superset/pages/usage-guide/security.adoc
razvan Feb 26, 2025
52a2214
Update docs/modules/superset/pages/usage-guide/security.adoc
razvan Feb 26, 2025
fdd8c6d
Update docs/modules/superset/pages/usage-guide/security.adoc
razvan Feb 26, 2025
80136fe
Update docs/modules/superset/pages/usage-guide/security.adoc
razvan Feb 26, 2025
d69ff8b
update security.adoc
razvan Feb 26, 2025
0d078b1
move constant
razvan Feb 26, 2025
bcd774f
rename opa dimension
razvan Feb 26, 2025
1051900
revert changes to smoke test
razvan Feb 26, 2025
63ac8cc
Update tests/templates/kuttl/opa/40_superset.yaml.j2
razvan Feb 26, 2025
499d02c
remove unused image field
razvan Feb 26, 2025
f9f8c7e
add serde cache defaults
razvan Feb 26, 2025
bc9acf3
Update rust/operator-binary/src/authorization/opa.rs
razvan Feb 26, 2025
16eb8e5
Update rust/operator-binary/src/superset_controller.rs
razvan Feb 26, 2025
9b7d6c9
add missing EOF
razvan Feb 26, 2025
26f08f0
add vector aggregator config map
razvan Feb 26, 2025
416389b
add openshift ns patch
razvan Feb 26, 2025
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

- Run a `containerdebug` process in the background of each Superset container to collect debugging information ([#578]).
- Aggregate emitted Kubernetes events on the CustomResources ([#585]).
- Support OPA role mapping as optional custom security manager for Superset ([#582]).
- Support for version `4.1.1` ([#595]).

### Changed

- Default to OCI for image metadata and product image selection ([#586]).

[#578]: https://github.com/stackabletech/superset-operator/pull/578
[#582]: https://github.com/stackabletech/superset-operator/pull/582
[#585]: https://github.com/stackabletech/superset-operator/pull/585
[#586]: https://github.com/stackabletech/superset-operator/pull/586
[#595]: https://github.com/stackabletech/superset-operator/pull/595
Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions Cargo.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
snafu = "0.8"
stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.85.0" }
stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "stackable-operator-0.86.0" }
strum = { version = "0.26", features = ["derive"] }
tokio = { version = "1.40", features = ["full"] }
tracing = "0.1"
Expand Down
6 changes: 3 additions & 3 deletions crate-hashes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions deploy/helm/superset-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,46 @@ spec:
- authenticationClass
type: object
type: array
authorization:
description: |-
Authorization options for Superset.

Currently only role assignment is supported. This means that roles are assigned to users in OPA but, due to the way Superset is implemented, the database also needs to be updated to reflect these assignments. Therefore, user roles and permissions must already exist in the Superset database before they can be assigned to a user. Warning: Any user roles assigned with the Superset UI are discarded.
nullable: true
properties:
roleMappingFromOpa:
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.
properties:
cache:
default:
entryTimeToLive: 30s
maxEntries: 10000
description: Configuration for an Superset internal cache for calls to OPA
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
required:
- roleMappingFromOpa
type: object
clusterOperation:
default:
reconciliationPaused: false
Expand Down
65 changes: 65 additions & 0 deletions docs/modules/superset/pages/usage-guide/security.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,71 @@ Further information for specifying an AuthenticationClass for an OIDC provider c
Superset has a concept called `Roles` which allows you to grant user permissions based on roles.
Have a look at the {superset-security}[Superset documentation on Security^{external-link-icon}^].

[opa]
=== OPA role mapping

Stackable ships a custom security manager that makes it possible to assign roles to users via the Open Policy Agent integration.
The roles must exist in the Superset database before they can be assigned to users.
If a role is not present in the Superset database, an error will be logged by the security manager and the user login will proceed without it.
Also the role names must match exactly the output of the Rego rule named `user_roles`.
In the following example, a rego package is defined that assigns roles to the users `admin` and `guest`.

[source,yaml]
----
apiVersion: v1
kind: ConfigMap
metadata:
name: superset-opa-regorules
labels:
opa.stackable.tech/bundle: "true"
data:
roles.rego: |
package superset

default user_roles := []

user_roles := roles if {
some user in users
roles := user.roles
user.username == input.username
}
users := [
{"username": "admin", "roles": ["Admin", "Test"]}, #<1>
{"username": "guest", "roles": ["Gamma"]} #<2>
]
----

<1> Assign the roles `Admin` and `Test` to the `admin` user. The `Test` role is not a standard Superset role and must be created before the assignment.
<2> Assign the `Gamma` role to the `guest` user.

OPA rules can make use of the xref:opa:usage-guide:user-info-fetcher[user-info-fetcher] integration.

The following snippet shows how to use the OPA security manager in a Superset stacklet.

[source,yaml]
----
apiVersion: superset.stackable.tech/v1alpha1
kind: SupersetCluster
metadata:
name: superset-with-opa-role-mapping
spec:
clusterConfig:
authorization:
roleMappingFromOpa:
configMapName: superset-opa-regorules # <1>
package: superset
cache: # <2>
entryTimeToLive: 10s # <3>
maxEntries: 5 # <4>
----

<1> ConfigMap name containing rego rules
<2> Mandatory Opa caching. If not set, default settings apply.
<3> Time for cached entries per user can live. Defaults to 30s.
<4> Number of maximum entries, defaults to 1000. Cache will be disabled for maxEntries: 0.

IMPORTANT: Any role assignments done in the Superset UI are discarded and will be overridden by the OPA security manager.

=== Superset database

You can view all the available roles in the web interface of Superset and can also assign users to these roles.
Expand Down
51 changes: 51 additions & 0 deletions rust/crd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use snafu::{OptionExt, ResultExt, Snafu};
use stackable_operator::{
commons::{
affinity::StackableAffinity,
cache::UserInformationCache,
cluster_operation::ClusterOperation,
opa::OpaConfig,
product_image_selection::ProductImage,
resources::{
CpuLimitsFragment, MemoryLimitsFragment, NoRuntimeLimits, NoRuntimeLimitsFragment,
Expand Down Expand Up @@ -89,6 +91,12 @@ pub enum SupersetConfigOptions {
AuthLdapTlsCertfile,
AuthLdapTlsKeyfile,
AuthLdapTlsCacertfile,
CustomSecurityManager,
AuthOpaRequestUrl,
AuthOpaPackage,
AuthOpaRule,
AuthOpaCacheMaxEntries,
AuthOpaCacheTtlInSec,
}

impl SupersetConfigOptions {
Expand Down Expand Up @@ -119,6 +127,7 @@ impl FlaskAppConfigOptions for SupersetConfigOptions {
SupersetConfigOptions::LoggingConfigurator => PythonType::Expression,
SupersetConfigOptions::AuthType => PythonType::Expression,
SupersetConfigOptions::AuthUserRegistration => PythonType::BoolLiteral,
// Going to be an expression as we default it from env, if and only if opa is used
SupersetConfigOptions::AuthUserRegistrationRole => PythonType::StringLiteral,
SupersetConfigOptions::AuthRolesSyncAtLogin => PythonType::BoolLiteral,
SupersetConfigOptions::AuthLdapServer => PythonType::StringLiteral,
Expand All @@ -136,6 +145,13 @@ impl FlaskAppConfigOptions for SupersetConfigOptions {
SupersetConfigOptions::AuthLdapTlsCertfile => PythonType::StringLiteral,
SupersetConfigOptions::AuthLdapTlsKeyfile => PythonType::StringLiteral,
SupersetConfigOptions::AuthLdapTlsCacertfile => PythonType::StringLiteral,
// Configuration options used by CustomOpaSecurityManager
SupersetConfigOptions::CustomSecurityManager => PythonType::Expression,
SupersetConfigOptions::AuthOpaRequestUrl => PythonType::StringLiteral,
SupersetConfigOptions::AuthOpaPackage => PythonType::StringLiteral,
SupersetConfigOptions::AuthOpaRule => PythonType::StringLiteral,
SupersetConfigOptions::AuthOpaCacheMaxEntries => PythonType::IntLiteral,
SupersetConfigOptions::AuthOpaCacheTtlInSec => PythonType::IntLiteral,
}
}
}
Expand Down Expand Up @@ -179,6 +195,17 @@ pub struct SupersetClusterConfig {
#[serde(default)]
pub authentication: Vec<SupersetClientAuthenticationDetails>,

/// Authorization options for Superset.
///
/// Currently only role assignment is supported. This means that roles are assigned to users in
/// OPA but, due to the way Superset is implemented, the database also needs to be updated
/// to reflect these assignments.
/// Therefore, user roles and permissions must already exist in the Superset database before
/// they can be assigned to a user.
/// Warning: Any user roles assigned with the Superset UI are discarded.
#[serde(skip_serializing_if = "Option::is_none")]
pub authorization: Option<SupersetAuthorization>,

/// The name of the Secret object containing the admin user credentials and database connection details.
/// Read the
/// [getting started guide first steps](DOCS_BASE_URL_PLACEHOLDER/superset/getting_started/first_steps)
Expand Down Expand Up @@ -242,6 +269,22 @@ impl CurrentlySupportedListenerClasses {
}
}
}
#[derive(Clone, Deserialize, Serialize, Eq, JsonSchema, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SupersetOpaRoleMappingConfig {
#[serde(flatten)]
pub opa: OpaConfig,

/// Configuration for an Superset internal cache for calls to OPA
#[serde(default)]
pub cache: UserInformationCache,
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SupersetAuthorization {
pub role_mapping_from_opa: SupersetOpaRoleMappingConfig,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -476,6 +519,14 @@ impl SupersetCluster {
}
}

pub fn get_opa_config(&self) -> Option<&SupersetOpaRoleMappingConfig> {
self.spec
.cluster_config
.authorization
.as_ref()
.map(|a| &a.role_mapping_from_opa)
}

/// Retrieve and merge resource configs for role and role groups
pub fn merged_config(
&self,
Expand Down
1 change: 1 addition & 0 deletions rust/operator-binary/src/authorization/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod opa;
55 changes: 55 additions & 0 deletions rust/operator-binary/src/authorization/opa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::collections::BTreeMap;

use stackable_operator::{client::Client, commons::opa::OpaApiVersion, time::Duration};
use stackable_superset_crd::{SupersetCluster, SupersetOpaRoleMappingConfig};

pub const OPA_IMPORTS: &[&str] =
&["from opa_authorizer.opa_manager import OpaSupersetSecurityManager"];

pub struct SupersetOpaConfigResolved {
opa_endpoint: String,
cache_max_entries: u32,
cache_ttl: Duration,
}

impl SupersetOpaConfigResolved {
pub async fn from_opa_config(
client: &Client,
superset: &SupersetCluster,
opa_config: &SupersetOpaRoleMappingConfig,
) -> Result<Self, stackable_operator::commons::opa::Error> {
let opa_endpoint = opa_config
.opa
.full_document_url_from_config_map(client, superset, None, OpaApiVersion::V1)
.await?;

Ok(SupersetOpaConfigResolved {
opa_endpoint,
cache_max_entries: opa_config.cache.max_entries.to_owned(),
cache_ttl: opa_config.cache.entry_time_to_live.to_owned(),
})
}

// Adding necessary configurations. Imports are solved in config.rs
pub fn as_config(&self) -> BTreeMap<String, String> {
BTreeMap::from([
(
"CUSTOM_SECURITY_MANAGER".to_string(),
"OpaSupersetSecurityManager".to_string(),
),
(
"AUTH_OPA_REQUEST_URL".to_string(),
self.opa_endpoint.to_owned(),
),
(
"AUTH_OPA_CACHE_MAX_ENTRIES".to_string(),
self.cache_max_entries.to_string(),
),
(
"AUTH_OPA_CACHE_TTL_IN_SEC".to_string(),
self.cache_ttl.as_secs().to_string(),
),
("AUTH_OPA_RULE".to_string(), "user_roles".to_string()),
])
}
}
Loading