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

Deploy userinfofetcher regorules #580

Merged
merged 77 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
455bdc4
Add group fetcher container
nightkr Apr 12, 2023
63ee375
Spike group fetcher functionality
nightkr Apr 12, 2023
bca2d62
Rename group fetcher to user info fetcher
nightkr Apr 12, 2023
9c1098c
Generalize enrichment endpoints to have room for arbitrary user info
nightkr Apr 12, 2023
8b28fd4
Expose user roles
nightkr Apr 12, 2023
a5bbe88
Make UIF configurable
nightkr Oct 2, 2023
08823b7
Make UIF credentials configurable too
nightkr Oct 2, 2023
412e542
OPA 0.45 -> 0.51
nightkr Oct 2, 2023
22507d0
Broke out UIF into its own crate
nightkr Oct 2, 2023
cffa188
UIF error handling
nightkr Oct 3, 2023
c74b6d9
Ingest custom attributes
nightkr Oct 3, 2023
1ef78e8
Shut down on SIGTERM
nightkr Oct 19, 2023
67e411f
Split keycloak backend out into separate module
nightkr Oct 20, 2023
eb134b7
Fix UIF startup when using none backend
nightkr Oct 20, 2023
fb6da54
Rename GroupMembershipRequest to UserInfoRequest
nightkr Oct 20, 2023
f298e0b
Cache fetched UserInfo
nightkr Oct 23, 2023
b1644e4
Merge branch 'main' into spike/user-info-fetcher
nightkr Oct 23, 2023
3d160ca
Switch UIF to also use workspace dependencies
nightkr Oct 23, 2023
a4f495d
Configurable UIF cache ttl
nightkr Oct 23, 2023
57b45c7
UIF crate metadata
nightkr Oct 23, 2023
20e585c
Move userInfo rule into helm chart
nightkr Oct 23, 2023
05df3cb
Turn UIF custom attributes into a multidict
nightkr Oct 24, 2023
2c511e4
UIF smoke test
nightkr Oct 25, 2023
23a55c6
Remove rules from Helm chart until we have a better way to deploy them
nightkr Oct 25, 2023
8c45dc1
Lint-b-gone
nightkr Oct 25, 2023
0cdbfd3
Update CRD
nightkr Oct 25, 2023
fc7de76
UIF readme
nightkr Oct 25, 2023
0a684d9
SNAFU error for UIF config
nightkr Oct 25, 2023
3aceb68
Revert Cmd wrapper enum
nightkr Oct 25, 2023
65934ee
docs
nightkr Oct 25, 2023
3c60721
Prototype of new bundle builder
nightkr Oct 25, 2023
1d48068
Include cm ns in bundle path
nightkr Oct 25, 2023
d6f939c
Merge commit spike/bundle-builder-v2-/uif-common-ancestor into spike/…
nightkr May 20, 2024
dcdf82e
Merge branch 'spike/bundle-builder-v2-/uif-merged' into spike/bundle-…
nightkr May 20, 2024
fae4196
Update bundle builder to Axum 0.7
nightkr May 20, 2024
8908df5
Disable unused futures 0.1.x compat layer
nightkr May 20, 2024
a1618d5
Deploy bundle builder v2
nightkr May 20, 2024
b2a2c12
Fix bundle builder log collection
nightkr May 23, 2024
0da032e
Avoid cloning bundle for status requests
nightkr May 23, 2024
881950f
Handle and report errors
nightkr May 23, 2024
4d7a156
Respect watch namespace
nightkr May 23, 2024
20fc10c
Fix bundle builder Cargo metadata
nightkr May 23, 2024
7ab1a67
Move dependencies into the workspace level
nightkr May 23, 2024
21f77ed
Loosen dependency bounds for consistency
nightkr May 27, 2024
12459bd
Formatting
nightkr May 27, 2024
17173f9
Log bundled files
nightkr May 27, 2024
f8ec7fd
Log bundle invalidations
nightkr May 27, 2024
2484b77
Ship userinfo rego module in bundle builder
nightkr May 29, 2024
1c74f89
Fix userinfo/v1 syntax errors
nightkr May 29, 2024
22ba466
Change userinfo documentation to refer to regolib
nightkr May 30, 2024
7174021
Enable userinfofetcher docs in nav
nightkr May 30, 2024
dda07d2
Migrate tests to use regolib
nightkr May 30, 2024
9150db1
Changelog
nightkr May 30, 2024
5c99df0
Upgrade to operator-rs main and Kube-rs 0.92
nightkr Jun 26, 2024
a02b6a2
Switch bundle builder from multilog to tracing-appender
nightkr Jun 26, 2024
d801cf2
Log bundle builder output as JSON
nightkr Jun 26, 2024
ee4bfff
Merge branch 'main' into spike/bundle-builder-v2
nightkr Jun 27, 2024
a5fef33
Update operator-rs
nightkr Jun 27, 2024
3cfc0c3
Regenerate Nix definitions
nightkr Jun 27, 2024
7cd4db0
Update docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc
nightkr Jun 27, 2024
a529195
Merge branch 'spike/bundle-builder-v2' into spike/userinfofetcher-reg…
nightkr Jun 27, 2024
2182d4d
Changelog
nightkr Jun 27, 2024
232fd64
Merge branch 'spike/bundle-builder-v2' into spike/userinfofetcher-reg…
nightkr Jun 27, 2024
bfbc941
Bump operator-rs
nightkr Jul 25, 2024
6477db0
Merge branch 'main' into spike/bundle-builder-v2
nightkr Jul 29, 2024
bcadcc6
Update operator-rs
nightkr Jul 29, 2024
6bfeb3a
Use released build of operator-rs
nightkr Jul 29, 2024
3b81266
Remove dead bundle builder log bytes constant
nightkr Jul 29, 2024
f0b715d
Merge branch 'main' into spike/bundle-builder-v2
nightkr Jul 31, 2024
412e713
Move bundle builder bugfix to unreleased
nightkr Jul 31, 2024
edbe052
Add comment about bundle builder log rotation
nightkr Jul 31, 2024
a13c44b
Merge branch 'spike/bundle-builder-v2' into spike/userinfofetcher-reg…
nightkr Jul 31, 2024
0945763
Merge branch 'main' into spike/bundle-builder-v2
nightkr Jul 31, 2024
dfd64cb
Merge commit '0945763aa6a3edef7f3a20eab66fe191b27385b7' into spike/us…
nightkr Jul 31, 2024
606be47
Consistency
nightkr Jul 31, 2024
b8a9b0a
Consistently mention the /admin group
nightkr Aug 1, 2024
548422b
Update rust/regorule-library/src/userinfo/v1.rego
nightkr Aug 6, 2024
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- Added regorule library for accessing user-info-fetcher ([#580]).

### Changed

- Rewrite of the OPA bundle builder ([#578]).
Expand All @@ -13,6 +17,7 @@ All notable changes to this project will be documented in this file.
- Bundle builder should no longer keep serving deleted rules until it is restarted ([#578]).

[#578]: https://github.com/stackabletech/opa-operator/pull/578
[#580]: https://github.com/stackabletech/opa-operator/pull/580

## [24.7.0] - 2024-07-24

Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

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

28 changes: 28 additions & 0 deletions Cargo.nix

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

87 changes: 16 additions & 71 deletions docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,91 +61,36 @@

NOTE: The OAuth2 Client in Keycloak must be given the `view-users` _Service Account Role_ for the realm that the users are in.

// TODO: Document how to use it in OPA regorules, e.g. to authorize based on group membership
== Example rego rule

[NOTE]
.User-facing API & API stability
====
Since the 24.07 SDP release we provide an example rego rule in our documentation using an HTTP request.
However, our plan is that the user-facing API of the SPD is *not* the HTTP API of user-info-fetcher, but instead regorules that will automatically be shipped to the OPA server.
This enables us to make underlying (for example breaking) changes to the HTTP API while keeping the rego rules API stable.

The documentation will be updated to use the deployed rego rules once available.
====

[NOTE]
.About unencrypted HTTP
====
The User info fetcher serves endpoints over clear-text HTTP.

It is intended to only be accessed from the OPA Server via _localhost_ and to not be exposed outside of the Pod.
====

[source,rego]
----
package test # <1>

# Define a function to lookup by username
userInfoByUsername(username) := http.send({
"method": "POST",
"url": "http://127.0.0.1:9476/user",
"body": {"username": username}, <2>
"headers": {"Content-Type": "application/json"},
"raise_error": true
}).body

# Define a function to lookup by a stable identifier
userInfoById(id) := http.send({
"method": "POST",
"url": "http://127.0.0.1:9476/user",
"body": {"id": id}, <3>
"headers": {"Content-Type": "application/json"},
"raise_error": true
}).body

currentUserInfoByUsername := userInfoByUsername(input.username)
currentUserInfoById := userInfoById(input.id)
----

<1> The package name is important in the OPA URL used by the product.
<2> Lookup by username
<3> Lookup by id

For more information on the request and response payloads, see <<_user_info_fetcher_api>>

== User info fetcher API

HTTP Post Requests must be sent to the `/user` endpoint with the following header set: `Content-Type: application/json`.
User information can be retrieved from regorules using the functions `userInfoByUsername(username)` and `userInfoById(id)` in `data.stackable.opa.userinfo.v1`.

You can either lookup the user info by stable identifier:
An example of the returned structure:

[source,json]
----
{
"id": "af07f12c-a2db-40a7-93e0-874537bdf3f5",
"username": "alice",
"groups": [
"/admin"
],
"customAttributes": {}
}
----

or by the username:
For example, the following rule will allow access for users in the `/admin` group:
sbernauer marked this conversation as resolved.
Show resolved Hide resolved

[source,json]
----
{
"username": "alice",
}
[source,rego]
----
package test

If the user is found, the following response structure will be returned:
import rego.v1

[source,json]
----
{
"id": "af07f12c-a2db-40a7-93e0-874537bdf3f5",
"username": "alice",
"groups": [
"/superset-admin"
],
"customAttributes": {}
default allow := false

allow if {
user := data.stackable.opa.userinfo.v1.userInfoById(input.userId)
"/admin" in user.groups

Check notice on line 94 in docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc

View workflow job for this annotation

GitHub Actions / LanguageTool

[LanguageTool] docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc#L94

If a new sentence starts here, add a space and start with an uppercase letter. (LC_AFTER_PERIOD[1]) Suggestions: ` Groups`, ` groups` Rule: https://community.languagetool.org/rule/show/LC_AFTER_PERIOD?lang=en-US&subId=1 Category: CASING
Raw output
docs/modules/opa/pages/usage-guide/user-info-fetcher.adoc:94:21: If a new sentence starts here, add a space and start with an uppercase letter. (LC_AFTER_PERIOD[1])
 Suggestions: ` Groups`, ` groups`
 Rule: https://community.languagetool.org/rule/show/LC_AFTER_PERIOD?lang=en-US&subId=1
 Category: CASING
}
----
2 changes: 2 additions & 0 deletions rust/bundle-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
stackable-opa-regorule-library = { path = "../regorule-library" }

axum.workspace = true
clap.workspace = true
flate2.workspace = true
Expand Down
32 changes: 21 additions & 11 deletions rust/bundle-builder/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,19 @@ enum BundleError {
#[snafu(display("ConfigMap is missing required metadata"))]
ConfigMapMetadataMissing,

#[snafu(display("file {file_name:?} in {config_map} is too large ({file_size} bytes)"))]
#[snafu(display("file {file_path:?} is too large ({file_size} bytes)"))]
FileSizeOverflow {
source: TryFromIntError,
config_map: ObjectRef<ConfigMap>,
file_name: String,
file_path: String,
file_size: usize,
},

#[snafu(display("failed to add static file {file_path:?} to tarball"))]
AddStaticRuleToTarball {
source: std::io::Error,
file_path: String,
},

#[snafu(display("failed to add file {file_name:?} from {config_map} to tarball"))]
AddFileToTarball {
source: std::io::Error,
Expand All @@ -211,20 +216,15 @@ impl BundleError {

async fn build_bundle(store: Store<ConfigMap>) -> Result<Vec<u8>, BundleError> {
use bundle_error::*;
fn file_header(
config_map: &ObjectRef<ConfigMap>,
file_name: &str,
data: &[u8],
) -> Result<tar::Header, BundleError> {
fn file_header(file_path: &str, data: &[u8]) -> Result<tar::Header, BundleError> {
let mut header = tar::Header::new_gnu();
header.set_mode(0o644);
let file_size = data.len();
header.set_size(
file_size
.try_into()
.with_context(|_| FileSizeOverflowSnafu {
config_map: config_map.clone(),
file_name,
file_path,
file_size,
})?,
);
Expand All @@ -237,6 +237,16 @@ async fn build_bundle(store: Store<ConfigMap>) -> Result<Vec<u8>, BundleError> {
let mut tar = tar::Builder::new(GzEncoder::new(Vec::new(), flate2::Compression::default()));
let mut resource_versions = BTreeMap::<String, String>::new();
let mut bundle_file_paths = BTreeSet::<String>::new();

for (file_path, data) in stackable_opa_regorule_library::REGORULES {
let mut header = file_header(file_path, data.as_bytes())?;
tar.append_data(&mut header, file_path, data.as_bytes())
.context(AddStaticRuleToTarballSnafu {
file_path: *file_path,
})?;
bundle_file_paths.insert(file_path.to_string());
}

for cm in store.state() {
let ObjectMeta {
name: Some(cm_ns),
Expand All @@ -249,8 +259,8 @@ async fn build_bundle(store: Store<ConfigMap>) -> Result<Vec<u8>, BundleError> {
};
let cm_ref = ObjectRef::from_obj(&*cm);
for (file_name, data) in cm.data.iter().flatten() {
let mut header = file_header(&cm_ref, file_name, data.as_bytes())?;
let file_path = format!("configmap/{cm_ns}/{cm_name}/{file_name}");
let mut header = file_header(&file_path, data.as_bytes())?;
tar.append_data(&mut header, &file_path, data.as_bytes())
.with_context(|_| AddFileToTarballSnafu {
config_map: cm_ref.clone(),
Expand Down
13 changes: 13 additions & 0 deletions rust/regorule-library/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "stackable-opa-regorule-library"
description = "Contains Stackable's library of common regorules"
version.workspace = true
authors.workspace = true
license.workspace = true
edition.workspace = true
repository.workspace = true
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
16 changes: 16 additions & 0 deletions rust/regorule-library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Stackable library of shared regorules

This contains regorules that are shipped by the Stackable Data Platform (SDP) as libraries to help simplify writing authorization rules.

## What this is not

This library should *not* contain rules that only concern one SDP product. Those are the responsibility of their individual operators.
nightkr marked this conversation as resolved.
Show resolved Hide resolved

## Versioning

All regorules exposed by this library should be versioned, according to Kubernetes conventions.

This version covers *breaking changes to the interface*, not the implementation. If a proposed change breaks existing clients,
add a new version. Otherwise, change the latest version inline.

Ideally, old versions should be implemented on top of newer versions, rather than carry independent implementations.
NickLarsenNZ marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 4 additions & 0 deletions rust/regorule-library/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub const REGORULES: &[(&str, &str)] = &[(
"stackable/opa/userinfo/v1.rego",
include_str!("userinfo/v1.rego"),
)];
19 changes: 19 additions & 0 deletions rust/regorule-library/src/userinfo/v1.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package stackable.opa.userinfo.v1

# Lookup by (human-readable) username
userInfoByUsername(username) := http.send({
nightkr marked this conversation as resolved.
Show resolved Hide resolved
"method": "POST",
"url": "http://127.0.0.1:9476/user",
"body": {"username": username},
"headers": {"Content-Type": "application/json"},
"raise_error": true
sbernauer marked this conversation as resolved.
Show resolved Hide resolved
}).body

# Lookup by stable user identifier
userInfoById(id) := http.send({
"method": "POST",
"url": "http://127.0.0.1:9476/user",
"body": {"id": id},
"headers": {"Content-Type": "application/json"},
"raise_error": true
}).body
16 changes: 0 additions & 16 deletions tests/templates/kuttl/aas-user-info/10-install-opa.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,6 @@ commands:
- script: |
kubectl apply -n $NAMESPACE -f - <<EOF
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test
labels:
opa.stackable.tech/bundle: "true"
data:
test.rego: |
package test

userInfoByUsername(username) := http.send({"method": "POST", "url": "http://127.0.0.1:9476/user", "body": {"username": username}, "headers": {"Content-Type": "application/json"}, "raise_error": true}).body
userInfoById(id) := http.send({"method": "POST", "url": "http://127.0.0.1:9476/user", "body": {"id": id}, "headers": {"Content-Type": "application/json"}, "raise_error": true}).body

currentUserInfoByUsername := userInfoByUsername(input.username)
currentUserInfoById := userInfoById(input.id)
---
apiVersion: opa.stackable.tech/v1alpha1
kind: OpaCluster
metadata:
Expand Down
2 changes: 1 addition & 1 deletion tests/templates/kuttl/aas-user-info/30-assert.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ kind: TestAssert
metadata:
name: test-regorule
commands:
- script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-server-default:8081/v1/data/test'
- script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-server-default:8081/v1/data/stackable/opa/userinfo/v1'
16 changes: 0 additions & 16 deletions tests/templates/kuttl/keycloak-user-info/10-install-opa.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,6 @@ commands:
- script: |
kubectl apply -n $NAMESPACE -f - <<EOF
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test
labels:
opa.stackable.tech/bundle: "true"
data:
test.rego: |
package test

userInfoByUsername(username) := http.send({"method": "POST", "url": "http://127.0.0.1:9476/user", "body": {"username": username}, "headers": {"Content-Type": "application/json"}, "raise_error": true}).body
userInfoById(id) := http.send({"method": "POST", "url": "http://127.0.0.1:9476/user", "body": {"id": id}, "headers": {"Content-Type": "application/json"}, "raise_error": true}).body

currentUserInfoByUsername := userInfoByUsername(input.username)
currentUserInfoById := userInfoById(input.id)
---
apiVersion: opa.stackable.tech/v1alpha1
kind: OpaCluster
metadata:
Expand Down
2 changes: 1 addition & 1 deletion tests/templates/kuttl/keycloak-user-info/30-assert.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ kind: TestAssert
metadata:
name: test-regorule
commands:
- script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-server-default:8081/v1/data/test'
- script: kubectl exec -n $NAMESPACE test-regorule-0 -- python /tmp/test-regorule.py -u 'http://test-opa-server-default:8081/v1/data/stackable/opa/userinfo/v1'
Loading