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 3e50014e0d [ARTEMIS-3168] Implement Kubernetes JaaS LoginModule
3e50014e0d is described below

commit 3e50014e0de9387fedfed942d51ae0264385f417
Author: ruromero <rrome...@redhat.com>
AuthorDate: Mon Nov 21 15:23:22 2022 +0100

    [ARTEMIS-3168] Implement Kubernetes JaaS LoginModule
    
    Signed-off-by: ruromero <rrome...@redhat.com>
---
 artemis-server-osgi/pom.xml                        |   1 +
 artemis-server/pom.xml                             |   9 +-
 .../core/security/jaas/KubernetesLoginModule.java  | 166 ++++++++++++++++++++
 .../spi/core/security/jaas/PropertiesLoader.java   |   2 +-
 .../security/jaas/ServiceAccountPrincipal.java     |  46 ++++++
 .../jaas/kubernetes/client/KubernetesClient.java   |  25 +++
 .../kubernetes/client/KubernetesClientImpl.java    | 173 +++++++++++++++++++++
 .../jaas/kubernetes/model/TokenReview.java         | 136 ++++++++++++++++
 .../security/jaas/KubernetesLoginModuleTest.java   | 144 +++++++++++++++++
 .../jaas/kubernetes/TokenCallbackHandler.java      |  48 ++++++
 .../client/KubernetesClientImplTest.java           | 140 +++++++++++++++++
 .../model/ServiceAccountPrincipalTest.java         |  50 ++++++
 .../jaas/kubernetes/model/TokenReviewTest.java     | 102 ++++++++++++
 artemis-server/src/test/resources/client_token     |  17 ++
 .../src/test/resources/k8s-roles.properties        |  20 +++
 docs/user-manual/en/security.md                    |  31 ++++
 pom.xml                                            |   3 +-
 17 files changed, 1110 insertions(+), 3 deletions(-)

diff --git a/artemis-server-osgi/pom.xml b/artemis-server-osgi/pom.xml
index d86b0c255b..27c867ed90 100644
--- a/artemis-server-osgi/pom.xml
+++ b/artemis-server-osgi/pom.xml
@@ -135,6 +135,7 @@
                      org.glassfish.json*;resolution:=optional,
                      org.postgresql*;resolution:=optional,
                      io.netty.buffer;io.netty.*;version="[4.1,5)",
+                     java.net.http*;resolution:=optional,
                      *
                   </Import-Package>
                   
<_exportcontents>org.apache.activemq.artemis.*;-noimport:=true</_exportcontents>
diff --git a/artemis-server/pom.xml b/artemis-server/pom.xml
index 22914df525..bb8c64b1b9 100644
--- a/artemis-server/pom.xml
+++ b/artemis-server/pom.xml
@@ -14,7 +14,8 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
    <modelVersion>4.0.0</modelVersion>
 
    <parent>
@@ -263,6 +264,12 @@
          <artifactId>jakarta.json-api</artifactId>
          <scope>test</scope>
       </dependency>
+      <dependency>
+         <groupId>org.mock-server</groupId>
+         <artifactId>mockserver-netty</artifactId>
+         <version>${mockserver.version}</version>
+         <scope>test</scope>
+      </dependency>
    </dependencies>
 
    <profiles>
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
new file mode 100644
index 0000000000..5a50952e86
--- /dev/null
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModule.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+
+import org.apache.activemq.artemis.logs.AuditLogger;
+import 
org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.client.KubernetesClient;
+import 
org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.client.KubernetesClientImpl;
+import 
org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.model.TokenReview;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class KubernetesLoginModule extends PropertiesLoader implements 
AuditLoginModule {
+
+   private static final Logger logger = 
LoggerFactory.getLogger(KubernetesLoginModule.class);
+
+   public static final String K8S_ROLE_FILE_PROP_NAME = 
"org.apache.activemq.jaas.kubernetes.role";
+
+   private CallbackHandler handler;
+   private Subject subject;
+   private TokenReview tokenReview = new TokenReview();
+   private Map<String, Set<String>> roles;
+   private final Set<Principal> principals = new HashSet<>();
+   private final KubernetesClient client;
+
+   public KubernetesLoginModule(KubernetesClient client) {
+      this.client = client;
+   }
+
+   public KubernetesLoginModule() {
+      this(new KubernetesClientImpl());
+   }
+
+   @Override
+   public void initialize(Subject subject, CallbackHandler callbackHandler, 
Map<String, ?> sharedState,
+         Map<String, ?> options) {
+      this.handler = callbackHandler;
+      this.subject = subject;
+
+      debug = booleanOption("debug", options);
+      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);
+      }
+   }
+
+   @Override
+   public boolean login() throws LoginException {
+      Callback[] callbacks = new Callback[1];
+      callbacks[0] = new PasswordCallback("Password", false);
+
+      try {
+         handler.handle(callbacks);
+      } catch (IOException | UnsupportedCallbackException e) {
+         throw (LoginException) new LoginException().initCause(e);
+      }
+
+      char[] token = ((PasswordCallback) callbacks[0]).getPassword();
+
+      if (token.length == 0) {
+         throw new FailedLoginException("Bearer token is empty");
+      }
+
+      tokenReview = client.getTokenReview(new String(token));
+
+      if (debug) {
+         logger.debug("login {}", tokenReview);
+      }
+      return tokenReview.isAuthenticated();
+   }
+
+   @Override
+   public boolean commit() throws LoginException {
+      boolean result = false;
+      result = tokenReview.isAuthenticated();
+
+      Set<UserPrincipal> authenticatedUsers = 
subject.getPrincipals(UserPrincipal.class);
+      if (result) {
+         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) {
+         Set<String> matchedRoles = roles.get(userPrincipal.getName());
+         if (matchedRoles != null) {
+            for (String entry : matchedRoles) {
+               principals.add(new RolePrincipal(entry));
+            }
+         }
+      }
+
+      subject.getPrincipals().addAll(principals);
+
+      clear();
+
+      if (debug) {
+         logger.debug("commit, result: {}, principals: {}", result, 
principals);
+      }
+      return result;
+   }
+
+   @Override
+   public boolean abort() throws LoginException {
+      registerFailureForAudit(tokenReview.getUsername());
+      clear();
+
+      if (debug) {
+         logger.debug("abort");
+      }
+      return true;
+   }
+
+   @Override
+   public void registerFailureForAudit(String name) {
+      Subject subject = new Subject();
+      subject.getPrincipals().add(new ServiceAccountPrincipal(name));
+      AuditLogger.setCurrentCaller(subject);
+   }
+
+   @Override
+   public boolean logout() throws LoginException {
+      subject.getPrincipals().removeAll(principals);
+      principals.clear();
+      clear();
+      if (debug) {
+         logger.debug("logout");
+      }
+      return true;
+   }
+
+   private void clear() {
+      tokenReview = new TokenReview();
+   }
+
+}
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoader.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoader.java
index 80adf23ca4..6adf4dca41 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoader.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoader.java
@@ -54,7 +54,7 @@ public class PropertiesLoader {
       return result.obtained();
    }
 
-   private static boolean booleanOption(String name, Map options) {
+   protected static boolean booleanOption(String name, Map options) {
       return Boolean.parseBoolean((String) options.get(name));
    }
 
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ServiceAccountPrincipal.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ServiceAccountPrincipal.java
new file mode 100644
index 0000000000..757dc45caa
--- /dev/null
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ServiceAccountPrincipal.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ServiceAccountPrincipal extends UserPrincipal {
+
+   private static final Pattern SA_NAME_PATTERN = 
Pattern.compile("system:serviceaccounts:([\\w-]+):([\\w-]+)");
+
+   private String saName;
+   private String namespace;
+
+   public ServiceAccountPrincipal(String name) {
+      super(name);
+      Matcher matcher = SA_NAME_PATTERN.matcher(name);
+      if (matcher.find()) {
+         namespace = matcher.group(1);
+         saName = matcher.group(2);
+      }
+   }
+
+   public String getSaName() {
+      return saName;
+   }
+
+   public String getNamespace() {
+      return namespace;
+   }
+
+}
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/client/KubernetesClient.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/client/KubernetesClient.java
new file mode 100644
index 0000000000..fd0b8877df
--- /dev/null
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/client/KubernetesClient.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.client;
+
+import 
org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.model.TokenReview;
+
+public interface KubernetesClient {
+
+   TokenReview getTokenReview(String token);
+
+}
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/client/KubernetesClientImpl.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/client/KubernetesClientImpl.java
new file mode 100644
index 0000000000..6172bcd15a
--- /dev/null
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/client/KubernetesClientImpl.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.client;
+
+import static java.net.HttpURLConnection.HTTP_CREATED;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Scanner;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
+import 
org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.model.TokenReview;
+import org.apache.activemq.artemis.utils.JsonLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class KubernetesClientImpl implements KubernetesClient {
+
+   private static final Logger logger = 
LoggerFactory.getLogger(KubernetesClientImpl.class);
+
+   private static final String KUBERNETES_HOST = "KUBERNETES_SERVICE_HOST";
+   private static final String KUBERNETES_PORT = "KUBERNETES_SERVICE_PORT";
+   private static final String KUBERNETES_TOKEN_PATH = "KUBERNETES_TOKEN_PATH";
+   private static final String KUBERNETES_CA_PATH = "KUBERNETES_CA_PATH";
+
+   private static final String KUBERNETES_TOKENREVIEW_URI_PATTERN = 
"https://%s:%s/apis/authentication.k8s.io/v1/tokenreviews";;
+
+   private static final String DEFAULT_KUBERNETES_TOKEN_PATH = 
"/var/run/secrets/kubernetes.io/serviceaccount/token";
+   private static final String DEFAULT_KUBERNETES_CA_PATH = 
"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
+
+   private URI apiUri;
+   private String tokenPath;
+   private String caPath;
+
+   public KubernetesClientImpl() {
+      this.tokenPath = getParam(KUBERNETES_TOKEN_PATH, 
DEFAULT_KUBERNETES_TOKEN_PATH);
+      this.caPath = getParam(KUBERNETES_CA_PATH, DEFAULT_KUBERNETES_CA_PATH);
+      String host = getParam(KUBERNETES_HOST);
+      String port = getParam(KUBERNETES_PORT);
+      this.apiUri = 
URI.create(String.format(KUBERNETES_TOKENREVIEW_URI_PATTERN, host, port));
+   }
+
+   private String getParam(String name, String defaultValue) {
+      String value = System.getenv(name);
+      if (value == null) {
+         value = System.getProperty(name, defaultValue);
+      }
+      if (value == null) {
+         return defaultValue;
+      }
+      return value;
+   }
+
+   private String getParam(String name) {
+      return getParam(name, null);
+   }
+
+   @Override
+   public TokenReview getTokenReview(String token) {
+      TokenReview tokenReview = new TokenReview();
+      String authToken = null;
+      try {
+         logger.debug("Loading client authentication token from {}", 
tokenPath);
+         authToken = readFile(tokenPath);
+         logger.debug("Loaded client authentication token from {}", tokenPath);
+      } catch (IOException e) {
+         logger.error("Cannot retrieve Service Account Authentication Token 
from " + tokenPath, e);
+         return tokenReview;
+      }
+      String jsonRequest = buildJsonRequest(token);
+
+      SSLContext ctx;
+      try {
+         ctx = buildSSLContext();
+      } catch (Exception e) {
+         logger.error("Unable to build a valid SSLContext", e);
+         return tokenReview;
+      }
+      HttpClient client = HttpClient.newBuilder().sslContext(ctx).build();
+
+      HttpRequest request = HttpRequest.newBuilder(apiUri)
+            .header("Authorization", "Bearer " + authToken)
+            .header("Accept", "application/json; charset=utf-8")
+            .POST(HttpRequest.BodyPublishers.ofString(jsonRequest)).build();
+      logger.debug("Submit TokenReview request to Kubernetes API");
+
+      try {
+         HttpResponse<String> response = client.send(request, 
BodyHandlers.ofString());
+         if (response.statusCode() == HTTP_CREATED) {
+            logger.debug("Received valid TokenReview response");
+            return TokenReview.fromJsonString(response.body());
+         }
+         logger.error("Unable to retrieve a valid TokenReview. Received 
StatusCode: {}. Body: {}",
+               response.statusCode(), response.body());
+      } catch (IOException | InterruptedException e) {
+         logger.error("Unable to request ReviewToken", e);
+      }
+      return tokenReview;
+   }
+
+   private String readFile(String path) throws IOException {
+      try (Scanner scanner = new Scanner(Path.of(path))) {
+         StringBuilder buffer = new StringBuilder();
+         while (scanner.hasNextLine()) {
+            String line = scanner.nextLine();
+            if (!line.isBlank() && !line.startsWith("#")) {
+               buffer.append(line);
+            }
+         }
+         return buffer.toString();
+      }
+   }
+
+   private String buildJsonRequest(String clientToken) {
+      return JsonLoader.createObjectBuilder()
+            .add("apiVersion", "authentication.k8s.io/v1")
+            .add("kind", "TokenReview")
+            .add("spec", JsonLoader.createObjectBuilder()
+                  .add("token", clientToken)
+                  .build())
+            .build().toString();
+   }
+
+   private SSLContext buildSSLContext() throws Exception {
+      SSLContext ctx = SSLContext.getInstance("SSL");
+      File certFile = new File(caPath);
+      if (!certFile.exists()) {
+         logger.debug("Kubernetes CA certificate not found at: {}. Truststore 
not configured", caPath);
+         return ctx;
+      }
+      try (InputStream fis = new FileInputStream(certFile)) {
+         KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+         CertificateFactory certFactory = 
CertificateFactory.getInstance("X.509");
+         X509Certificate certificate = (X509Certificate) 
certFactory.generateCertificate(fis);
+         trustStore.load(null, null);
+         trustStore.setCertificateEntry(certFile.getName(), certificate);
+         TrustManagerFactory tmFactory = TrustManagerFactory
+               .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+         tmFactory.init(trustStore);
+
+         ctx.init(null, tmFactory.getTrustManagers(), new SecureRandom());
+      }
+      return ctx;
+   }
+}
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/model/TokenReview.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/model/TokenReview.java
new file mode 100644
index 0000000000..2b3d6bed7a
--- /dev/null
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/model/TokenReview.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.model;
+
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.activemq.artemis.json.JsonArray;
+import org.apache.activemq.artemis.json.JsonObject;
+import org.apache.activemq.artemis.json.JsonString;
+import org.apache.activemq.artemis.utils.JsonLoader;
+
+public class TokenReview {
+
+   private boolean authenticated;
+   private User user;
+   private List<String> audiences;
+
+   public boolean isAuthenticated() {
+      return authenticated;
+   }
+
+   public User getUser() {
+      return user;
+   }
+
+   public String getUsername() {
+      if (user == null) {
+         return null;
+      }
+      return user.getUsername();
+   }
+
+   public List<String> getAudiences() {
+      return audiences;
+   }
+
+   public static TokenReview fromJsonString(String obj) {
+      JsonObject json = JsonLoader.readObject(new StringReader(obj));
+      JsonObject status = json.getJsonObject("status");
+      return TokenReview.fromJson(status);
+   }
+
+   private static TokenReview fromJson(JsonObject obj) {
+      TokenReview t = new TokenReview();
+      if (obj == null) {
+         return t;
+      }
+      t.authenticated = obj.getBoolean("authenticated", false);
+      t.user = User.fromJson(obj.getJsonObject("user"));
+      t.audiences = listFromJson(obj.getJsonArray("audiences"));
+      return t;
+   }
+
+   private static List<String> listFromJson(JsonArray items) {
+      if (items == null) {
+         return Collections.emptyList();
+      }
+      return 
Collections.unmodifiableList(items.getValuesAs(JsonString::getString));
+   }
+
+   public static class User {
+
+      private String username;
+      private String uid;
+      private List<String> groups;
+      private Extra extra;
+
+      public Extra getExtra() {
+         return extra;
+      }
+
+      public List<String> getGroups() {
+         return groups;
+      }
+
+      public String getUid() {
+         return uid;
+      }
+
+      public String getUsername() {
+         return username;
+      }
+
+      public static User fromJson(JsonObject obj) {
+         if (obj == null) {
+            return null;
+         }
+         User u = new User();
+         u.username = obj.getString("username", null);
+         u.uid = obj.getString("uid", null);
+         u.groups = listFromJson(obj.getJsonArray("groups"));
+         u.extra = Extra.fromJson(obj.getJsonObject("extra"));
+         return u;
+      }
+
+   }
+
+   public static class Extra {
+      private List<String> podNames;
+      private List<String> podUids;
+
+      public List<String> getPodNames() {
+         return podNames;
+      }
+
+      public List<String> getPodUids() {
+         return podUids;
+      }
+
+      public static Extra fromJson(JsonObject obj) {
+         if (obj == null) {
+            return null;
+         }
+         Extra e = new Extra();
+         e.podNames = 
listFromJson(obj.getJsonArray("authentication.kubernetes.io/pod-name"));
+         e.podUids = 
listFromJson(obj.getJsonArray("authentication.kubernetes.io/pod-uid"));
+         return e;
+      }
+   }
+}
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
new file mode 100644
index 0000000000..be1aede8ea
--- /dev/null
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/KubernetesLoginModuleTest.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas;
+
+import static 
org.apache.activemq.artemis.spi.core.security.jaas.KubernetesLoginModule.K8S_ROLE_FILE_PROP_NAME;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+
+import 
org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.TokenCallbackHandler;
+import 
org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.client.KubernetesClient;
+import 
org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.model.TokenReview;
+import org.junit.Test;
+
+public class KubernetesLoginModuleTest {
+
+   private final KubernetesClient client = mock(KubernetesClient.class);
+   private final KubernetesLoginModule loginModule = new 
KubernetesLoginModule(client);
+   private static final String TOKEN = "the_token";
+
+   public static final String USERNAME = 
"system:serviceaccounts:some-ns:kermit";
+   public static final String AUTH_JSON = "{\"status\": {"
+         + "\"authenticated\": true, "
+         + "\"user\": {"
+         + "  \"username\": \"" + USERNAME + "\""
+         + "}}}";
+
+   public static final String UNAUTH_JSON = "{\"status\": {"
+         + "\"authenticated\": false "
+         + "}}";
+
+   @Test
+   public void testBasicLogin() throws LoginException {
+      CallbackHandler handler = new TokenCallbackHandler(TOKEN);
+      Subject subject = new Subject();
+      loginModule.initialize(subject, handler, Collections.emptyMap(), 
getDefaultOptions());
+
+      TokenReview tr = TokenReview.fromJsonString(AUTH_JSON);
+      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("muppet"), new 
RolePrincipal("admin")));
+
+      assertTrue(loginModule.logout());
+      assertFalse(loginModule.commit());
+      assertThat(subject.getPrincipals(), empty());
+      verify(client, times(1)).getTokenReview(TOKEN);
+   }
+
+   @Test
+   public void testFailedLogin() throws LoginException {
+      CallbackHandler handler = new TokenCallbackHandler(TOKEN);
+      Subject subject = new Subject();
+      loginModule.initialize(subject, handler, Collections.emptyMap(), 
getDefaultOptions());
+
+      TokenReview tr = TokenReview.fromJsonString(UNAUTH_JSON);
+      when(client.getTokenReview(TOKEN)).thenReturn(tr);
+
+      assertFalse(loginModule.login());
+      assertFalse(loginModule.commit());
+      assertThat(subject.getPrincipals(), empty());
+      verify(client, times(1)).getTokenReview(TOKEN);
+   }
+
+   @Test
+   public void testNullToken() throws LoginException {
+      CallbackHandler handler = new TokenCallbackHandler(null);
+      Subject subject = new Subject();
+      loginModule.initialize(subject, handler, Collections.emptyMap(), 
getDefaultOptions());
+
+      try {
+         assertFalse(loginModule.login());
+         fail("Exception expected");
+      } catch (LoginException e) {
+         assertNotNull(e);
+      }
+
+      assertFalse(loginModule.commit());
+      assertThat(subject.getPrincipals(), empty());
+      verifyNoInteractions(client);
+   }
+
+   @Test
+   public void testUnableToVerifyToken() throws LoginException {
+      CallbackHandler handler = new TokenCallbackHandler(TOKEN);
+      Subject subject = new Subject();
+      loginModule.initialize(subject, handler, Collections.emptyMap(), 
getDefaultOptions());
+
+      when(client.getTokenReview(TOKEN)).thenReturn(new TokenReview());
+
+      assertFalse(loginModule.login());
+      assertFalse(loginModule.commit());
+      assertThat(subject.getPrincipals(), empty());
+      verify(client, times(1)).getTokenReview(TOKEN);
+   }
+
+   private Map<String, ?> getDefaultOptions() {
+      return Map.of(K8S_ROLE_FILE_PROP_NAME,
+            "k8s-roles.properties");
+   }
+}
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/TokenCallbackHandler.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/TokenCallbackHandler.java
new file mode 100644
index 0000000000..a13e89c7f0
--- /dev/null
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/TokenCallbackHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas.kubernetes;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+public class TokenCallbackHandler implements CallbackHandler {
+
+   private final char[] password;
+
+   public TokenCallbackHandler(String password) {
+      if (password != null) {
+         this.password = password.toCharArray();
+      } else {
+         this.password = new char[0];
+      }
+   }
+
+   @Override
+   public void handle(Callback[] callbacks) throws IOException, 
UnsupportedCallbackException {
+      for (Callback c : callbacks) {
+         if (c instanceof PasswordCallback) {
+            ((PasswordCallback) c).setPassword(password);
+         } else {
+            throw new UnsupportedCallbackException(c);
+         }
+      }
+   }
+}
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/client/KubernetesClientImplTest.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/client/KubernetesClientImplTest.java
new file mode 100644
index 0000000000..f746fa6a20
--- /dev/null
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/client/KubernetesClientImplTest.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.client;
+
+import static java.net.HttpURLConnection.HTTP_CREATED;
+import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
+import static 
org.apache.activemq.artemis.spi.core.security.jaas.KubernetesLoginModuleTest.AUTH_JSON;
+import static 
org.apache.activemq.artemis.spi.core.security.jaas.KubernetesLoginModuleTest.UNAUTH_JSON;
+import static 
org.apache.activemq.artemis.spi.core.security.jaas.KubernetesLoginModuleTest.USERNAME;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+import static org.mockserver.model.JsonBody.json;
+
+import java.net.URL;
+
+import 
org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.model.TokenReview;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockserver.configuration.ConfigurationProperties;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.matchers.MatchType;
+import org.mockserver.socket.PortFactory;
+import org.mockserver.verify.VerificationTimes;
+
+public class KubernetesClientImplTest {
+
+   private static final String API_PATH = 
"/apis/authentication.k8s.io/v1/tokenreviews";
+   private static ClientAndServer mockServer;
+   private static final String host = "localhost";
+   private static String port;
+
+   private static final String BOB_REQUEST = "{\"apiVersion\": 
\"authentication.k8s.io/v1\"," +
+         "\"kind\": \"TokenReview\", \"spec\": {\"token\": \"bob_token\"}}";
+
+   private static final String KERMIT_REQUEST = "{\"apiVersion\": 
\"authentication.k8s.io/v1\"," +
+         "\"kind\": \"TokenReview\", \"spec\": {\"token\": \"kermit_token\"}}";
+
+   @BeforeClass
+   public static void startServer() {
+      
ConfigurationProperties.dynamicallyCreateCertificateAuthorityCertificate(true);
+      
ConfigurationProperties.directoryToSaveDynamicSSLCertificate("target/test-classes");
+      ConfigurationProperties.proactivelyInitialiseTLS(true);
+
+      mockServer = 
ClientAndServer.startClientAndServer(PortFactory.findFreePort());
+      port = Integer.toString(mockServer.getPort());
+
+      assertNotNull(mockServer);
+      assertTrue(mockServer.isRunning());
+      System.setProperty("KUBERNETES_SERVICE_HOST", host);
+      System.setProperty("KUBERNETES_SERVICE_PORT", port);
+      System.setProperty("KUBERNETES_TOKEN_PATH",
+            
KubernetesClientImplTest.class.getClassLoader().getResource("client_token").getPath());
+      URL caPath = KubernetesClientImplTest.class.getClassLoader()
+            .getResource("CertificateAuthorityCertificate.pem");
+      if (caPath != null) {
+         System.setProperty("KUBERNETES_CA_PATH", caPath.getPath());
+      }
+
+      mockServer.when(
+            request()
+                  .withMethod("POST")
+                  .withPath(API_PATH)
+                  .withBody(json(BOB_REQUEST, MatchType.STRICT)))
+            .respond(
+                  response()
+                        .withStatusCode(HTTP_CREATED)
+                        .withBody(UNAUTH_JSON));
+
+      mockServer.when(
+            request()
+                  .withMethod("POST")
+                  .withPath(API_PATH)
+                  .withBody(json(KERMIT_REQUEST, MatchType.STRICT)))
+            .respond(
+                  response()
+                        .withStatusCode(HTTP_CREATED)
+                        .withBody(AUTH_JSON));
+
+      mockServer.when(
+            request()
+                  .withMethod("POST")
+                  .withPath(API_PATH))
+            .respond(
+                  response()
+                        .withStatusCode(HTTP_INTERNAL_ERROR));
+
+   }
+
+   @AfterClass
+   public static void stopServer() {
+      mockServer.stop();
+   }
+
+   @Test
+   public void testGetTokenReview() {
+
+      KubernetesClient client = new KubernetesClientImpl();
+
+      TokenReview tr = client.getTokenReview("bob_token");
+      assertNotNull(tr);
+      assertFalse(tr.isAuthenticated());
+      assertNull(tr.getUser());
+      assertNull(tr.getUsername());
+
+      tr = client.getTokenReview("kermit_token");
+      assertNotNull(tr);
+      assertNotNull(tr.getUser());
+      assertThat(tr.getUsername(), is(USERNAME));
+      assertThat(tr.getUser().getUsername(), is(USERNAME));
+
+      tr = client.getTokenReview("other");
+      assertNotNull(tr);
+      assertFalse(tr.isAuthenticated());
+
+      mockServer.verify(request().withPath(API_PATH), 
VerificationTimes.exactly(3));
+
+   }
+
+}
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/model/ServiceAccountPrincipalTest.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/model/ServiceAccountPrincipalTest.java
new file mode 100644
index 0000000000..5f48c3282b
--- /dev/null
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/model/ServiceAccountPrincipalTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.model;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertNull;
+
+import 
org.apache.activemq.artemis.spi.core.security.jaas.ServiceAccountPrincipal;
+import org.junit.Test;
+
+public class ServiceAccountPrincipalTest {
+
+   @Test
+   public void testFullName() {
+      String name = "system:serviceaccounts:some-ns:some-sa";
+
+      ServiceAccountPrincipal principal = new ServiceAccountPrincipal(name);
+
+      assertThat(principal.getNamespace(), is("some-ns"));
+      assertThat(principal.getSaName(), is("some-sa"));
+      assertThat(principal.getName(), is(name));
+   }
+
+   @Test
+   public void testSimpleName() {
+      String name = "foo";
+
+      ServiceAccountPrincipal principal = new ServiceAccountPrincipal(name);
+
+      assertThat(principal.getName(), is("foo"));
+      assertNull(principal.getSaName());
+      assertNull(principal.getNamespace());
+   }
+
+}
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/model/TokenReviewTest.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/model/TokenReviewTest.java
new file mode 100644
index 0000000000..e3086bc38a
--- /dev/null
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/spi/core/security/jaas/kubernetes/model/TokenReviewTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.activemq.artemis.spi.core.security.jaas.kubernetes.model;
+
+import static 
org.apache.activemq.artemis.spi.core.security.jaas.KubernetesLoginModuleTest.USERNAME;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+public class TokenReviewTest {
+
+   @Test
+   public void testEmpty() {
+      String json = "{}";
+      TokenReview tr = TokenReview.fromJsonString(json);
+
+      assertFalse(tr.isAuthenticated());
+      assertNull(tr.getUser());
+      assertNull(tr.getUsername());
+   }
+
+   @Test
+   public void testSimple() {
+      String json = "{\"status\": {\"authenticated\": true, \"user\": 
{\"username\": \"" + USERNAME + "\"}}}";
+
+      TokenReview tr = TokenReview.fromJsonString(json);
+
+      assertNotNull(tr);
+      assertTrue(tr.isAuthenticated());
+      assertThat(tr.getUsername(), is(USERNAME));
+      assertNotNull(tr.getUser());
+      assertThat(tr.getUser().getUsername(), is(USERNAME));
+      assertThat(tr.getAudiences(), Matchers.empty());
+      assertNull(tr.getUser().getExtra());
+   }
+
+   @Test
+   public void testCompleteObject() {
+      String json = "{\"status\": {"
+            + "\"authenticated\": true, "
+            + "\"user\": {"
+            + "  \"username\": \"" + USERNAME + "\","
+            + "  \"uid\": \"kermit-uid\","
+            + "  \"groups\": ["
+            + "    \"group-1\","
+            + "    \"group-2\""
+            + "  ],"
+            + "  \"extra\": {"
+            + "    \"authentication.kubernetes.io/pod-name\": ["
+            + "      \"pod-1\","
+            + "      \"pod-2\""
+            + "    ],"
+            + "    \"authentication.kubernetes.io/pod-uid\": ["
+            + "      \"pod-uid-1\","
+            + "      \"pod-uid-2\""
+            + "    ]"
+            + "  }"
+            + "},"
+            + "\"audiences\": ["
+            + "  \"audience-1\","
+            + "  \"audience-2\""
+            + "]}}";
+
+      TokenReview tr = TokenReview.fromJsonString(json);
+
+      assertNotNull(tr);
+      assertTrue(tr.isAuthenticated());
+      assertThat(tr.getUsername(), is(USERNAME));
+      assertNotNull(tr.getUser());
+      assertThat(tr.getUser().getUsername(), is(USERNAME));
+      assertThat(tr.getAudiences(), containsInAnyOrder("audience-1", 
"audience-2"));
+      assertThat(tr.getUser().getGroups(), containsInAnyOrder("group-1", 
"group-2"));
+      assertThat(tr.getUser().getUid(), is("kermit-uid"));
+
+      assertNotNull(tr.getUser().getExtra());
+      assertThat(tr.getUser().getExtra().getPodNames(), 
containsInAnyOrder("pod-1", "pod-2"));
+      assertThat(tr.getUser().getExtra().getPodUids(), 
containsInAnyOrder("pod-uid-1", "pod-uid-2"));
+
+   }
+
+}
diff --git a/artemis-server/src/test/resources/client_token 
b/artemis-server/src/test/resources/client_token
new file mode 100644
index 0000000000..a2909ae96c
--- /dev/null
+++ b/artemis-server/src/test/resources/client_token
@@ -0,0 +1,17 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+test_token
\ No newline at end of file
diff --git a/artemis-server/src/test/resources/k8s-roles.properties 
b/artemis-server/src/test/resources/k8s-roles.properties
new file mode 100644
index 0000000000..fe7330316f
--- /dev/null
+++ b/artemis-server/src/test/resources/k8s-roles.properties
@@ -0,0 +1,20 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+
+admin=system:serviceaccounts:some-ns:kermit
+user=system:serviceaccounts:some-ns:gonzo,serviceaccounts:some-ns:joe
+muppet=system:serviceaccounts:some-ns:kermit,system:serviceaccounts:some-ns:gonzo
\ No newline at end of file
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index 7d49adbf0c..2cdb4692e5 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -1056,6 +1056,37 @@ 
org.apache.activemq.artemis.spi.core.security.jaas.Krb5LoginModule required
 The simplest way to make the login configuration available to JAAS is to add
 the directory containing the file, `login.config`, to your CLASSPATH.
 
+#### KubernetesLoginModule
+
+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`.
+
+- `org.apache.activemq.jaas.kubernetes.role` - the path to the file which
+  contains user and role mapping
+
+- `reload` - boolean flag; whether or not to reload the properties files 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
+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:
+
+```properties
+admins=system:serviceaccounts:example-ns:admin-sa
+users=system:serviceaccounts:other-ns:test-sa
+```
+
 ### SCRAM-SHA SASL Mechanism
 
 SCRAM (Salted Challenge Response Authentication Mechanism) is an 
authentication mechanism that can establish mutual
diff --git a/pom.xml b/pom.xml
index 51e76bfbf8..5f6269bcdf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -169,6 +169,7 @@
       <groovy.version>4.0.5</groovy.version>
       <vertx.version>4.3.3</vertx.version>
       <hadoop.minikdc.version>3.3.1</hadoop.minikdc.version>
+      <mockserver.version>5.13.2</mockserver.version>
 
       <owasp.version>6.1.0</owasp.version>
       <spring.version>5.3.20</spring.version>
@@ -232,7 +233,7 @@
 
       <directory-version>2.0.0.AM25</directory-version>
       <directory-jdbm2-version>2.0.0-M1</directory-jdbm2-version>
-      <bcprov-jdk15on-version>1.69</bcprov-jdk15on-version>
+      <bcprov-jdk15on-version>1.70</bcprov-jdk15on-version>
 
       
<netty-transport-native-epoll-classifier>linux-x86_64</netty-transport-native-epoll-classifier>
       
<netty-transport-native-kqueue-classifier>osx-x86_64</netty-transport-native-kqueue-classifier>

Reply via email to