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

ReadOnly User cannot list topics #1607

Closed
BernhardBerbuir opened this issue Oct 27, 2023 · 5 comments · Fixed by #1620
Closed

ReadOnly User cannot list topics #1607

BernhardBerbuir opened this issue Oct 27, 2023 · 5 comments · Fixed by #1620

Comments

@BernhardBerbuir
Copy link

I'm using the dev version with the new roles with OIDC and an external mapper. When a user has only the following permissions:

Permissions by API "Get current user" (get /api/me)
{
  "logged": true,
  "username": "[email protected]",
  "roles": [
    {
      "resources": ["SCHEMA","NODE","ACL"],
      "actions": ["READ"],
      "patterns": [".*"],
      "clusters": [".*"]
    },
    {
      "resources": ["NODE"],
      "actions": ["READ_CONFIG"],
      "patterns": [".*"],
      "clusters": [".*"]
    },
    {
      "resources": ["TOPIC"],
      "actions": ["READ","READ_CONFIG"],
      "patterns": ["topicpraefix-.*"],
      "clusters": [".*"]
    },
    {
      "resources": ["TOPIC_DATA"],
      "actions": ["READ"],
      "patterns": ["topicpraefix-.*"],
      "clusters": [".*"]
    }
  ]
}

then the the user is either redirected to the login page or an empty topic list is displayed.
The AKHQ log contains the following stacktrace:

2023-10-27 07:37:38,197 ERROR -thread-10 o.a.c.ErrorController      null
java.lang.NullPointerException: null
at org.akhq.controllers.AbstractController.lambda$buildUserBasedResourceFilters$5(AbstractController.java:96)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source)
at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)
at java.base/java.util.stream.ReferencePipeline.collect(Unknown Source)
at org.akhq.controllers.AbstractController.buildUserBasedResourceFilters(AbstractController.java:106)
at org.akhq.controllers.TopicController.list(TopicController.java:105)
at org.akhq.controllers.$TopicController$Definition$Exec.dispatch(Unknown Source)
at io.micronaut.context.AbstractExecutableMethodsDefinition$DispatchedExecutableMethod.invoke(AbstractExecutableMethodsDefinition.java:371)
at io.micronaut.context.DefaultBeanContext$4.invoke(DefaultBeanContext.java:594)
at io.micronaut.web.router.AbstractRouteMatch.execute(AbstractRouteMatch.java:303)
at io.micronaut.web.router.RouteMatch.execute(RouteMatch.java:111)
at io.micronaut.http.context.ServerRequestContext.with(ServerRequestContext.java:103)
at io.micronaut.http.server.RouteExecutor.lambda$executeRoute$14(RouteExecutor.java:659)
at reactor.core.publisher.FluxDeferContextual.subscribe(FluxDeferContextual.java:49)
at reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:62)
at reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.run(FluxSubscribeOn.java:194)
at io.micronaut.reactive.reactor.instrument.ReactorInstrumentation.lambda$init$0(ReactorInstrumentation.java:62)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37)
at io.micronaut.scheduling.instrument.InvocationInstrumenterWrappedCallable.call(InvocationInstrumenterWrappedCallable.java:53)
at io.micrometer.core.instrument.composite.CompositeTimer.recordCallable(CompositeTimer.java:129)
at io.micrometer.core.instrument.Timer.lambda$wrap$1(Timer.java:206)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)

When I add the role

{
  "resources":["TOPIC"],
  "actions":["DELETE","READ","READ_CONFIG"],
  "patterns":["DummyPrincipal-.*"],
  "clusters":[".*"]
}

then the topics are displayed.

Apparently something is wrong with the API ("List all topics" (get /api/{cluster}/topic)) because it produces the same error if the dummy permission is not present.

@AlexisSouquiere
Copy link
Collaborator

AlexisSouquiere commented Oct 30, 2023

@BernhardBerbuir can you please copy an extract of your application.yml and especially the akhq.security part + the ouput of yourOIDC provider ?
Based on the stacktrace you put I think something is wrong either in the mapping between roles in the application.yml and roles given by the provider
When I'll have the confirmation I'll put a check to handle this potential misconfiguration

@BernhardBerbuir
Copy link
Author

@AlexisSouquiere here is my application.yml:

akhq:
  connections:
    ...
  pagination:
    page-size: 10
    threads: 2
  security:
    default-group: no-roles
    groups:
      no-roles:
        roles: []
    oidc:
      enabled: true
      providers:
        oidc:
          # Default group for all the user even unlogged user
          default-group: no-roles # group without any roles
          label: "Login with Keycloak"
          groups-field: groups
          username-field: email
    # see https://akhq.io/docs/configuration/authentifications/external.html
    rest:
      enabled: true
      url: http://localhost:8090/get-roles-and-attributes
    roles:
      # manage **all** topics and consumergroups 
      Administrator:
        - actions: [ "READ", "UPDATE_OFFSET", "DELETE_OFFSET" ]
          resources: [ "CONSUMER_GROUP" ]
        - actions: [ "DELETE", "READ", "READ_CONFIG" ]
          resources: [ "TOPIC" ]
        - actions: [ "READ", "DELETE" ]
          resources: [ "TOPIC_DATA" ]
      # manage topics and consumergroups of a specific principal
      principal_Administrator:
        - actions: [ "READ", "UPDATE_OFFSET", "DELETE_OFFSET" ]
          resources: [ "CONSUMER_GROUP" ]
        - actions: [ "DELETE", "READ", "READ_CONFIG"  ]
          resources: [ "TOPIC" ]
        - actions: [ "READ", "DELETE" ]
          resources: [ "TOPIC_DATA" ]
      # read messages from topics of a specific principal
      principal_ReadOnly:
        - actions: [ "READ", "READ_CONFIG" ]
          resources: [ "TOPIC" ]
        - actions: [ "READ" ]
          resources: [ "TOPIC_DATA" ]
      # read messages from **all** topics
      ReadOnly:
        - actions: [ "READ", "READ_CONFIG" ]
          resources: [ "TOPIC" ]
        - actions: [ "READ" ]
          resources: [ "TOPIC_DATA" ]
      # general read permissions of meta data for every user
      # (akhq-oidc-mapper always adds this role to a user)
      AuthenticatedUser:
        - actions: [ "READ" ]
          resources: [ "SCHEMA", "NODE", "ACL" ]
        - actions: [ "READ_CONFIG" ]
          resources: [ "NODE" ]
  topic-data:
    date-time-format: ISO
    size: 10
    sort: NEWEST
micronaut:
  security:
    callback-uri: "http://localhost:5000/oauth/callback/oidc"
    enabled: true
    oauth2:
      clients:
        oidc:
          client-id: kafkanextplatform
          client-secret: "${OPENID_CLIENT_SECRET}"
          openid:
            issuer: http://localhost:8080/realms/master
      enabled: true
    token:
      jwt:
        signatures:
          secret:
            generator:
              secret: "${JWT_SECRET}"
  server:
    # use a different port in order to not interfere with Keycloak
    port: 5000

REMARK:

  • I'm using an external application for generating the roles based on LDAP groups
  • there are no cluster specific permissions ("clusters": [".*"])
  • roles Administrator and ReadOnly are used with pattern "patterns": [".*"]
  • roles principal_Administrator and principal_ReadOnly are used with pattern "patterns": ["${PrincipalName}-.*"] (for each principal a separate role is used, i.e. patterns always contains only one regex )
  • a naming convention enforces that topics and consumergroups starts with the principal name

At the top of this issue I have provided the permissions of a user.
=> I would expect it doesn't matter which way (OIDC, LDAP, internal configuration, ...) the roles are assigned to the user, right?

@AlexisSouquiere
Copy link
Collaborator

I'm able to reproduce an issue you are mentioning in your first post (user redirected to the login page) with your configuration. Not the second one (no topics displayed) if I have a topic matching the pattern. For the 1st one, this is due to the topic list page needs also the CONSUMER_GROUP resource with READ permissions. If you don't have:

akhq:
  ui-options:
    topic:
      skip-consumer-groups: true

We will try to load consumer groups for the topic and because you don't have rights, you will be redirected to the login page (a warning message is missing I agree).

Can you please try to add "CONSUMER_GROUP" in your ReadOnly roles ? And if it doesn't work, check if you still have the same issue in the logs

      principal_ReadOnly:
        - actions: [ "READ", "READ_CONFIG" ]
          resources: [ "TOPIC" ]
        - actions: [ "READ" ]
          resources: [ "TOPIC_DATA", "CONSUMER_GROUP" ]
      ReadOnly:
        - actions: [ "READ", "READ_CONFIG" ]
          resources: [ "TOPIC" ]
        - actions: [ "READ" ]
          resources: [ "TOPIC_DATA", "CONSUMER_GROUP" ]

For your last question, of course it doesn't matter. The only thing we have to check is that all the solutions produces the same output.

@BernhardBerbuir
Copy link
Author

BernhardBerbuir commented Oct 30, 2023

Not the second one (no topics displayed) if I have a topic matching the pattern.

I tried to reproduce the problem, but it no longer occurred .

I have added the following snippet to both roles (I want to avoid mixing resources) :

  - actions: [ "READ" ]
    resources: [ "CONSUMER_GROUP" ]

and now everything works fine 🎉 (thanks for your advice).

There seems to be some dependencies between resources (READ/TOPIC without READ/TOPIC_DATA also has problems). However, documenting these could be very time consuming: perhaps a general note in the documentation + an error message in the log if permissions are missing would be a possibility.

@AlexisSouquiere
Copy link
Collaborator

I will put a note in the doc to explain these dependencies and see how to display a message saying that a permission is missing. Thanks for the feedback

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

Successfully merging a pull request may close this issue.

2 participants