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 <[email protected]>
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