ARTEMIS-4280 - map roles from review group info, optional roles properties file
This commit is contained in:
parent
e959e3cfe8
commit
d2abc56f56
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue