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

BUG: UI is broken for user with access to all topic metadata but selective access to data #2027

Open
erikgb opened this issue Dec 21, 2024 · 5 comments
Labels
wait for reply Need more information from reporter

Comments

@erikgb
Copy link
Contributor

erikgb commented Dec 21, 2024

We are running multi-tenant Kafka clusters where we must ensure that tenants cannot read data from other tenants' topics. However, all users should have access to topic metadata in the cluster. Ideally, we want AKHQ to grant access to data in topics based on the ACLs configured for user/groups in the cluster, see #2025.

Right now, no users have permission to see any topic data through AKHQ. But some of our tenants already have established topic naming standards, so we are trying to set up a POC of access to topic data by applying static patterns in the AKHQ configuration. But this seems broken, and we have also tried using the dev image. It's probably related to #1910 - as we are seeing the same error message.

Extract of our configuration:

default-group: reader-no-data-access
roles:
  reader:
    - resources: ["TOPIC", "TOPIC_DATA", "CONSUMER_GROUP", "CONNECT_CLUSTER", "CONNECTOR", "SCHEMA", "NODE", "ACL", "KSQLDB"]
      actions: ["READ"]
    - resources: ["TOPIC", "NODE"]
      actions: ["READ_CONFIG"]
  reader-no-data-access:
    - resources: ["TOPIC", "CONSUMER_GROUP", "CONNECT_CLUSTER", "CONNECTOR", "SCHEMA", "NODE", "ACL", "KSQLDB"]
      actions: ["READ"]
    - resources: ["TOPIC", "NODE"]
      actions: ["READ_CONFIG"]
groups:
  reader:
    - role: reader
  fifty-reader:
    - role: reader
      patterns: [".*app_fifty_.*"]
  reader-no-data-access:
    - role: reader-no-data-access

This is what we observe:

  • UI works for an arbitrary user with no extra permissions or relevant groups. User is granted the reader-no-data-access role to all topics. ✅
  • UI works for a user belonging to the reader group. User is granted the reader role with access to all topics - including data in topics. ✅
  • UI is broken for a user belonging to the fifty-reader group with the error message "Unauthorized: missing permission on resource TOPIC_DATA and action READ" 💣

The expected behavior should (obviously) be that the user has access to metadata for all topics and access to data in the topics matching the configured pattern. Are we doing something wrong? With some pointers to where in the code this bug might be hiding, we would be happy to contribute a fix for it. 😸 Thanks a lot for providing this wonderful tool! ❤️

@AlexisSouquiere
Copy link
Collaborator

Apologies for the late answer. I tried with your roles/groups and I'm not able to reproduce the issue. I forced the default-group: fifty-reader in my case to test this particular group. Can you try the same ?

If this works, revert back to the configuration issue and then inspect in the browser the /api/me call to check in the response if the fifty-reader group is applied correctly.

@AlexisSouquiere AlexisSouquiere added the wait for reply Need more information from reporter label Jan 22, 2025
@erikgb
Copy link
Contributor Author

erikgb commented Jan 22, 2025

@AlexisSouquiere, thanks for responding! This issue has very high priority for us, and is easy for us to reproduce. One of my teammates has already done some digging into this and thinks this might be a missing feature in the frontend. He has started preparing a PR, at least he has some ideas, and we are prepared to contribute this improvement. Hopefully in cooperation with you as maintainers! 😺 I will ask him to write a summary on this issue now!

@erikgb
Copy link
Contributor Author

erikgb commented Jan 22, 2025

I tried with your roles/groups and I'm not able to reproduce the issue. I forced the default-group: fifty-reader in my case to test this particular group. Can you try the same ?

For reproducing, please ensure you have some topics matching the read-data role pattern, and some that don't match.

I am not at frontend expert, but from the error message it seems like the backend implements the security model correctly, while the frontend appears to ignore the patterns feature. Resulting in the backend rejecting some requests from the frontend.

@mikaelol
Copy link

mikaelol commented Jan 23, 2025

As @erikgb mention, it appears that patterns feature is not implemented in the frontend, similarly with clusters:

When calling the /api/me API endpoint, I get a structure with multiple different roles:

Click to see an example API call to /api/me for user mikaelol
{
  "logged": true,
  "username": "mikaelol",
  "roles": [
    {
      "resources": [
        "TOPIC",
        "TOPIC_DATA",
        "CONSUMER_GROUP",
        "CONNECT_CLUSTER",
        "CONNECTOR",
        "SCHEMA",
        "NODE",
        "ACL",
        "KSQLDB"
      ],
      "actions": [
        "READ"
      ],
      "patterns": [
        "example_topic_1",
        "example_topic_2",
        "example_topic_3"
      ],
      "clusters": [
        ".*"
      ]
    },
    {
      "resources": [
        "TOPIC",
        "CONSUMER_GROUP",
        "CONNECT_CLUSTER",
        "CONNECTOR",
        "SCHEMA",
        "NODE",
        "ACL",
        "KSQLDB"
      ],
      "actions": [
        "READ"
      ],
      "patterns": [
        ".*"
      ],
      "clusters": [
        ".*"
      ]
    },
    {
      "resources": [
        "TOPIC",
        "NODE"
      ],
      "actions": [
        "READ_CONFIG"
      ],
      "patterns": [
        ".*"
      ],
      "clusters": [
        ".*"
      ]
    }
  ]
}

What seems like the problem here is that this master data from the backend is flattened by this function before being saved to session storage:

export function organizeRoles(roles) {
let newRoles = {};
if (!roles) {
return JSON.stringify(newRoles);
}
roles.forEach(role => {
role.resources.forEach(resource => {
role.actions.forEach(action => {
if (newRoles[resource] === undefined) {
newRoles[resource] = [];
}
newRoles[resource].push(action);
});
});
});
return JSON.stringify(newRoles);
}

So that the end result looks like:

{
  "TOPIC": [
    "READ",
    "READ_CONFIG",
    "READ",
    "READ_CONFIG",
    "READ",
    "READ_CONFIG"
  ],
  "CONSUMER_GROUP": [
    "READ",
    "READ",
    "READ"
  ],
  "CONNECT_CLUSTER": [
    "READ",
    "READ",
    "READ"
  ],
  "CONNECTOR": [
    "READ",
    "READ",
    "READ"
  ],
  "SCHEMA": [
    "READ",
    "READ",
    "READ"
  ],
  "NODE": [
    "READ",
    "READ_CONFIG",
    "READ",
    "READ_CONFIG",
    "READ",
    "READ_CONFIG"
  ],
  "ACL": [
    "READ",
    "READ",
    "READ"
  ],
  "KSQLDB": [
    "READ",
    "READ",
    "READ"
  ],
  "TOPIC_DATA": [
    "READ"
  ]
}

Notice how patterns and clusters are stripped, and the role TOPIC_DATA is now present at the root level with the READ action, effectively giving the union of permissions of all roles and stripping any pattern and cluster constraints. This flattened structure is now saved to session storage and used as master data for the frontend.

The frontend does not care about the patterns but just checks if role.TOPIC_DATA == "READ". Since this is true (because it had a pattern of a few topics that had this role/action), the frontend queries the backend for all topics. This makes all API calls that include other topics fail, e.g.,

  • The fetch for the last record when looking at the topic list (unless you enable skip-last-record): /api/cluster/topic/last-record?topics=topic_x,topic_y,...
  • Clicking the magnifying glass in the topic list to check out data for a topic you don't have access to (the magnifying glass appears for all topics, regardless of whether you actually have TOPIC_DATA == "READ" or not): /api/cluster/topic/topic_x/data?sort=NEWEST&partition=All

So the solution is to not flatten the result from /api/me but to keep the pattern (and cluster) information throughout all the components of the frontend.

@AlexisSouquiere
Copy link
Collaborator

The pattern / cluster information is not needed on the frontend for filtering the data because it's the backend responsibility to give to the frontend the topic the user has access to.

So calling /topic won't give all the topics but only a filtered list of topic.

We are using this feature on a daily basis so I don't know right now what is the issue you are facing but we need to figure out

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wait for reply Need more information from reporter
Projects
Status: Backlog
Development

No branches or pull requests

3 participants