This is an automated email from the ASF dual-hosted git repository.

gtully pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/main by this push:
     new d2abc56f56 ARTEMIS-4280 - map roles from review group info, optional 
roles properties file
d2abc56f56 is described below

commit d2abc56f56e9573eb5481ea23f7ed3fcff888c82
Author: Gary Tully <gary.tu...@gmail.com>
AuthorDate: Mon May 15 16:29:30 2023 +0100

    ARTEMIS-4280 - map roles from review group info, optional roles properties 
file
---
 .../core/security/jaas/KubernetesLoginModule.java  | 22 +++++---
 .../security/jaas/KubernetesLoginModuleTest.java   | 63 ++++++++++++++++++++++
 docs/user-manual/en/security.md                    | 14 ++---
 3 files changed, 87 insertions(+), 12 deletions(-)

diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModule.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModule.java
index 5a50952e86..cc0551ad2e 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModule.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModule.java
@@ -46,6 +46,7 @@ public class KubernetesLoginModule extends PropertiesLoader 
implements AuditLogi
    private CallbackHandler handler;
    private Subject subject;
    private TokenReview tokenReview = new TokenReview();
+   private boolean ignoreTokenReviewRoles = false;
    private Map<String, Set<String>> roles;
    private final Set<Principal> principals = new HashSet<>();
    private final KubernetesClient client;
@@ -68,10 +69,17 @@ public class KubernetesLoginModule extends PropertiesLoader 
implements AuditLogi
       if (debug) {
          logger.debug("Initialized debug");
       }
-      roles = load(K8S_ROLE_FILE_PROP_NAME, "k8s-roles.properties", 
options).invertedPropertiesValuesMap();
-      if (debug) {
-         logger.debug("loaded roles: {}", roles);
+      // role mapping file is optional
+      if (options.containsKey(K8S_ROLE_FILE_PROP_NAME)) {
+         roles = load(K8S_ROLE_FILE_PROP_NAME, null, 
options).invertedPropertiesValuesMap();
+         if (debug) {
+            logger.debug("loaded roles: {}", roles);
+         }
+      } else {
+         roles = Map.of();
       }
+
+      ignoreTokenReviewRoles = booleanOption("ignoreTokenReviewRoles", 
options);
    }
 
    @Override
@@ -109,9 +117,11 @@ public class KubernetesLoginModule extends 
PropertiesLoader implements AuditLogi
          UserPrincipal userPrincipal = new 
ServiceAccountPrincipal(tokenReview.getUsername());
          principals.add(userPrincipal);
          authenticatedUsers.add(userPrincipal);
-      }
-      // populate roles for UserPrincipal from other login modules too
-      for (UserPrincipal userPrincipal : authenticatedUsers) {
+         if (!ignoreTokenReviewRoles) {
+            for (String role : tokenReview.getUser().getGroups()) {
+               principals.add(new RolePrincipal(role));
+            }
+         }
          Set<String> matchedRoles = roles.get(userPrincipal.getName());
          if (matchedRoles != null) {
             for (String entry : matchedRoles) {
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModuleTest.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModuleTest.java
index fed189dca1..ad4490f35a 100644
--- 
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModuleTest.java
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModuleTest.java
@@ -59,6 +59,13 @@ public class KubernetesLoginModuleTest {
          + "  \"username\": \"" + USERNAME + "\""
          + "}}}";
 
+   public static final String AUTH_JSON_WITH_GROUPS = "{\"status\": {"
+      + "\"authenticated\": true, "
+      + "\"user\": {"
+      + "  \"username\": \"" + USERNAME + "\","
+      + "  \"groups\": [\"developers\", \"qa\"]"
+      + "}}}";
+
    public static final String UNAUTH_JSON = "{\"status\": {"
          + "\"authenticated\": false "
          + "}}";
@@ -138,6 +145,62 @@ public class KubernetesLoginModuleTest {
       verify(client, times(1)).getTokenReview(TOKEN);
    }
 
+   @Test
+   public void testRolesFromReview() throws LoginException {
+      CallbackHandler handler = new TokenCallbackHandler(TOKEN);
+      Subject subject = new Subject();
+      loginModule.initialize(subject, handler, Collections.emptyMap(), 
Map.of());
+
+      TokenReview tr = TokenReview.fromJsonString(AUTH_JSON_WITH_GROUPS);
+      when(client.getTokenReview(TOKEN)).thenReturn(tr);
+
+      assertTrue(loginModule.login());
+      assertTrue(loginModule.commit());
+
+      assertThat(subject.getPrincipals(UserPrincipal.class), hasSize(1));
+      subject.getPrincipals(ServiceAccountPrincipal.class).forEach(p -> {
+         assertThat(p.getName(), is(USERNAME));
+         assertThat(p.getSaName(), is("kermit"));
+         assertThat(p.getNamespace(), is("some-ns"));
+      });
+      Set<RolePrincipal> roles = subject.getPrincipals(RolePrincipal.class);
+      assertThat(roles, hasSize(2));
+      assertThat(roles, containsInAnyOrder(new RolePrincipal("developers"), 
new RolePrincipal("qa")));
+
+      assertTrue(loginModule.logout());
+      assertFalse(loginModule.commit());
+      assertThat(subject.getPrincipals(), empty());
+      verify(client, times(1)).getTokenReview(TOKEN);
+   }
+
+   @Test
+   public void testIgnoreRolesFromReview() throws LoginException {
+      CallbackHandler handler = new TokenCallbackHandler(TOKEN);
+      Subject subject = new Subject();
+      loginModule.initialize(subject, handler, Collections.emptyMap(), 
Map.of("ignoreTokenReviewRoles", "true"));
+
+      TokenReview tr = TokenReview.fromJsonString(AUTH_JSON_WITH_GROUPS);
+      when(client.getTokenReview(TOKEN)).thenReturn(tr);
+
+      assertTrue(loginModule.login());
+      assertTrue(loginModule.commit());
+
+      assertThat(subject.getPrincipals(UserPrincipal.class), hasSize(1));
+      subject.getPrincipals(ServiceAccountPrincipal.class).forEach(p -> {
+         assertThat(p.getName(), is(USERNAME));
+         assertThat(p.getSaName(), is("kermit"));
+         assertThat(p.getNamespace(), is("some-ns"));
+      });
+      Set<RolePrincipal> roles = subject.getPrincipals(RolePrincipal.class);
+      assertThat(roles, hasSize(0));
+
+      assertTrue(loginModule.logout());
+      assertFalse(loginModule.commit());
+      assertThat(subject.getPrincipals(), empty());
+      verify(client, times(1)).getTokenReview(TOKEN);
+   }
+
+
    private Map<String, ?> getDefaultOptions() {
       String baseDirValue = new 
File(KubernetesLoginModuleTest.class.getClassLoader().getResource("k8s-roles.properties").getPath()).getParentFile().getAbsolutePath();
       return Map.of(K8S_ROLE_FILE_PROP_NAME, "k8s-roles.properties", 
"baseDir",baseDirValue);
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index df66ea4a25..1e8da2b76e 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -1088,26 +1088,28 @@ the directory containing the file, `login.config`, to 
your CLASSPATH.
 The Kubernetes login module enables you to perform authentication and 
authorization
 by validating the `Bearer` token against the Kubernetes API. The 
authentication is done
 by submitting a `TokenReview` request that the Kubernetes cluster validates. 
The response will
-tell whether the user is authenticated and the associated username. It is 
implemented by 
`org.apache.activemq.artemis.spi.core.security.jaas.KubernetesLoginModule`.
+tell whether the user is authenticated and the associated username and roles. 
It is implemented by 
`org.apache.activemq.artemis.spi.core.security.jaas.KubernetesLoginModule`.
 
-- `org.apache.activemq.jaas.kubernetes.role` - the path to the file which
-  contains user and role mapping
+- `ignoreTokenReviewRoles` - when true, do not map roles from the TokenReview 
user groups. default false
 
-- `reload` - boolean flag; whether or not to reload the properties files when a
+- `org.apache.activemq.jaas.kubernetes.role` - the optional path to the file 
which
+  contains role mapping, useful when ignoreTokenReviewRoles=true
+
+- `reload` - boolean flag; whether or not to reload the properties file when a
   modification occurs; default is `false`
 
 - `debug` - boolean flag; if `true`, enable debugging; this is used only for
   testing or debugging; normally, it should be set to `false`, or omitted;
   default is `false`
 
-The login module must be allowed to query such Rest API. For that, it will use 
the available
+The login module must be allowed to query the required Rest API. For that, it 
will use the available
 token under `/var/run/secrets/kubernetes.io/serviceaccount/token`. Besides, in 
order to trust the
 connection the client will use the `ca.crt` file existing in the same folder. 
These two files will
 be mounted in the container. The service account running the 
KubernetesLoginModule must
 be allowed to `create::TokenReview`. The `system:auth-delegator` role is 
typically use for
 that purpose.
 
-The `k8s-roles.properties` file consists of a list of properties of the form, 
`Role=UserList`, where `UserList` is a comma-separated list of users. For 
example, to define the roles admins, users, and guests, you could create a file 
like the following:
+The optional roles properties file consists of a list of properties of the 
form, `Role=UserList`, where `UserList` is a comma-separated list of users. For 
example, to define the roles admins, users, and guests, you could create a file 
like the following:
 
 ```properties
 admins=system:serviceaccounts:example-ns:admin-sa

Reply via email to