[ARTEMIS-3168] Implement Kubernetes JaaS LoginModule
Signed-off-by: ruromero <rromerom@redhat.com>
This commit is contained in:
parent
eb0aa118b4
commit
3e50014e0d
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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"));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
3
pom.xml
3
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>
|
||||
|
|
Loading…
Reference in New Issue