Skip to content

feat: Add resource-info-fetcher - #863

Open
sbernauer wants to merge 42 commits into
mainfrom
spike/rif
Open

feat: Add resource-info-fetcher#863
sbernauer wants to merge 42 commits into
mainfrom
spike/rif

Conversation

@sbernauer

@sbernauer sbernauer commented Jul 21, 2026

Copy link
Copy Markdown
Member

Description

Part of #848

Yes, the diff looks big, but most of it are lockfiles and kuttl tests

CRD change

Basically the same as for the user-info-fetcher. The only difference is the env field, which is DataHub specific.

Look at extra/crds.yaml for the concrete CRD change

  clusterConfig:
    resourceInfo: # <1>
      backend:
        dataHub:
          hostname: datahub-gms.my-namespace.svc.cluster.local
          port: 8080
          rootPath: / # optional, defaults to /
          tls:
            verification:
              server:
                caCert:
                  secretClass: tls # <2>
          credentialsSecretName: resource-info-fetcher-credentials # <3>
          env: PROD # <4>
      cache: # optional, enabled by default
        maxEntries: 42 # optional, defaults to no limit
        entryTimeToLive: 60s # optional, defaults to 60s
---
apiVersion: v1
kind: Secret
metadata:
  name: resource-info-fetcher-credentials
stringData:
  token: <datahub-personal-access-token> # <3>

API of RIF

The rego rules and HTTP API of RIF: https://github.com/stackabletech/opa-operator/blob/spike/rif/docs/modules/opa/pages/usage-guide/resource-info-fetcher.adoc

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.
  • Helm chart can be installed and deployed operator works
  • Integration tests passed (for non trivial changes)
  • Changes need to be "offline" compatible
  • Links to generated (nightly) docs added
  • Release note snippet added

Reviewer

  • Code contains useful comments
  • Code contains useful logging statements
  • (Integration-)Test cases added
  • Documentation added or updated. Follows the 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
  • Links to generated (nightly) docs added
  • Release note snippet added
  • Add type/deprecation label & add to the deprecation schedule
  • Add type/experimental label & add to the experimental features tracker

Release notes

Added
  • Add an initial version of resource-info-fetcher, which is similar to user-info-fetcher, but allows to fetch additional metadata about resource information from a data catalog.
    For now only DataHub is supported.
    Also, a rego-rule library has been added to make it easier to call resource-info-fetcher from within OPA.
    The API (especially the response) might change in the future once more data catalogs are supported

Nightly docs

As soon as this PR is merged the docs will show up at https://docs.stackable.tech/home/nightly/opa/usage-guide/resource-info-fetcher/

@sbernauer
sbernauer marked this pull request as ready for review July 30, 2026 13:49
@lfrancke

Copy link
Copy Markdown
Member

Because we had it yesterday with OpenLineage: I believe it could make sense to add a "path" or "endpoint" field. Just in case DataHub is deployed behind some proxy. ://

@sbernauer

Copy link
Copy Markdown
Member Author

I believe it could make sense to add a "path" or "endpoint" field

Good point! For consistency reasons with UIF and AuthenticationClass I went with rootPath in 215d4b9. Feel free to have a look

@sbernauer sbernauer self-assigned this Jul 31, 2026
@sbernauer
sbernauer requested a review from Maleware July 31, 2026 08:53
@sbernauer sbernauer moved this to Development: Waiting for Review in Stackable Engineering Jul 31, 2026

@Maleware Maleware left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just two typos, thanks you! Test passes locally, thus LTGTM otherwise.

}

/// Generates the trivial `From<Params> for ResourceInfoRequest` conversions, so each HTTP handler
/// can turn its deserialized query parameters into a [`ResourceInfoRequest`] via `.into()`. Adding a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
/// can turn its deserialized query parameters into a [`ResourceInfoRequest`] via `.into()`. Adding a
/// can turn its deserialized query parameters into a [`ResourceInfoRequest`] via `.from()`. Adding a

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

https://doc.rust-lang.org/std/convert/trait.From.html
From automatically implements Into, so the caller can pick.
In

P: DeserializeOwned + Into<ResourceInfoRequest>,
we actually use Into<ResourceInfoRequest>. In general I feel in this situation a into feels more natural than a from

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah okay allright. I was looking at the function beneath which reads from thought that might want to match.

Comment thread rust/resource-info-fetcher/src/main.rs Outdated
Co-authored-by: Maximilian Wittich <56642549+Maleware@users.noreply.github.com>
@sbernauer
sbernauer requested a review from Maleware August 5, 2026 05:56
@sbernauer

Copy link
Copy Markdown
Member Author

Please vote on the CRD change as well as the rego rule API on this comment. Feel free to also look at the JSON API, but I'd say that's an implementation detail we are allowed to change in the future

@maltesander
maltesander self-requested a review August 5, 2026 06:26

@maltesander maltesander left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

First round, did not check docs etc. properly yet and not testing yet.

Comment thread rust/resource-info-fetcher/src/backend/data_hub/mod.rs Outdated
Comment thread rust/resource-info-fetcher/src/main.rs Outdated
Comment on lines +174 to +175
// Trim trailing whitespace/newlines so the value is safe to use in an HTTP header.
let token = tokio::fs::read_to_string(&token_path)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is never refreshed? Will go stale and produce 401 until restart?

@sbernauer sbernauer Aug 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, but ResolvedKeycloakBackend::resolve, ResolvedEntraBackend::resolve, ResolvedOpenLdapBackend::resolve and maybe others do the exact same thing.
According to https://docs.datahub.com/docs/authentication/personal-access-tokens you can create a PAT without expiration.
I see that is not 100% ideal, but also IMHO the status quo basically everywhere else in the platform as well

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No i dont think so. They read the client credentials, not the token?

let authn = send_json_request::<OAuthResponse>(
(same for entra).
This is not ideal either, we should just cache it and refresh on 401.

Comment thread rust/resource-info-fetcher/src/backend/data_hub/graphql.rs Outdated
Comment thread docs/modules/opa/pages/usage-guide/resource-info-fetcher.adoc
///
/// [`urn_for_request`]: crate::backend::data_hub::resource_to_urn_mapping::urn_for_request
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ResourceInfoRequest {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The instance is always required. Datahub (defaults) leave that out and a trino table will become e.g. urn:li:dataset:(urn:li:dataPlatform:trino,tpch.sf1.customer,PROD) and the tableResourceInfo always builds {instance}.tpch.sf1.customer which will be empty and therefore fail a correct rego?

Can we make instance optional and skip?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I was thinking of this as well (I actually started without the instance).
I think we should very strongly recommend users to set the instance when ingesting data into DataHub.
Also we likely want to support other backends in the future.
If they don't have such a strange concept of "there is only one Trino" they will require us to send a instance for every request.
So I fear we would have a problem with future backends if we make it optional.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmm i dont think we should strongly recommend how people use a non stackable product?

I think the scope of this PR is datahub, so i would try to represent everything datahub can do with reasonable abstraction? Removing the optional part can then be done once another backend lands?

I feel this is a too generalized and therefore cutting into datahub funtionality?

@sbernauer sbernauer moved this from Development: Waiting for Review to Development: In Review in Stackable Engineering Aug 10, 2026
@sbernauer
sbernauer requested a review from maltesander August 10, 2026 12:56

@maltesander maltesander left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Claude reproduced a bigger security issue (policies can pass if backend is gone).

Silent fail-open when the backend is unavailable

Backend state allow if tag==public deny if tag==pii
healthy deny deny
PAT rejected (401) deny allow
GMS scaled to 0 deny allow

All three containers report ready=true throughout. There is no readiness probe on the sidecar.

  • Repro: kubectl -n $NS scale deploy datahub-datahub-gms --replicas=0, wait 60s for the cache to drain, query any policy that excludes a tag.
  • Fix: add a readiness probe in controller/build/resource/daemonset/resource_info_fetcher.rs

Tags and domains are not inherited from the parent container

Tagging schema tpch.sf1 with pii leaves child table nation reporting tags:[public], domain:null. A deny if pii policy allows it. DataHub exposes the parent in the same query (container { tags domain }), so this is one field away.

  • Repro: addTag/setDomain on the schema container URN, wait out the 60 s TTL, query the table.
  • Fix: add container { tags {...Tags} domain {...Domain} } to the fragment at graphql.rs:30, or state the non-inheritance explicitly in the docs.

Self::ConstructHttpClient { .. } => StatusCode::SERVICE_UNAVAILABLE,
Self::BuildDataHubEndpoint { .. } => StatusCode::BAD_REQUEST,
Self::ExecuteGraphQlQuery { .. } => StatusCode::INTERNAL_SERVER_ERROR,
Self::GraphQlErrors { .. } => StatusCode::INTERNAL_SERVER_ERROR,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this is user-controlled, e.g. a bad identifier is rather a client error?

Suggested change
Self::GraphQlErrors { .. } => StatusCode::INTERNAL_SERVER_ERROR,
Self::GraphQlErrors { .. } => StatusCode::BAD_REQUEST,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development: In Review

Development

Successfully merging this pull request may close these issues.

4 participants