Skip to content

Commit

Permalink
[incubator-kie-issues-994] kafka auth feature with message header rec…
Browse files Browse the repository at this point in the history
…ords (#3037)

* integration java-jwt for kafka and inmemory thing

* add security and integration check with RSA

* keystore helpers
  • Loading branch information
elguardian authored Mar 18, 2024
1 parent a686d1d commit 16a50c2
Show file tree
Hide file tree
Showing 16 changed files with 478 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public class KieServerConstants {
public static final String CFG_KIE_USER = "org.kie.server.user";
public static final String CFG_KIE_PASSWORD = "org.kie.server.pwd";
public static final String CFG_KIE_TOKEN = "org.kie.server.token";
public static final String CFG_KIE_ISSUER = "org.kie.server.issuer";

/**
* Security settings used to connect to KIE Server Controller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class JMSConstants {
public static final String TARGET_CAPABILITY_PROPERTY_NAME = "kie_target_capability";
public static final String USER_PROPERTY_NAME = "kie_user";
public static final String PASSWRD_PROPERTY_NAME = "kie_password";
public static final String ASSERTION_PROPERTY_NAME = "kie_token";

public static final String INTERACTION_PATTERN_PROPERTY_NAME = "kie_interaction_pattern";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
package org.kie.server.common;

import java.security.Key;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.util.HashSet;
import java.util.Set;

import org.drools.core.util.KeyStoreConstants;
import org.drools.core.util.KeyStoreHelper;
import org.kie.server.api.KieServerConstants;
import org.kie.server.api.model.KieServerConfig;
Expand All @@ -16,7 +26,11 @@ public class KeyStoreHelperUtil {
private static final String PROP_PWD_SERVER_PWD = "kie.keystore.key.server.pwd";

// the private key identifier for controller
private static final String PROP_PWD_CTRL_ALIAS = "kie.keystore.key.ctrl.alias";
public static final String PROP_PWD_JWT_ALIAS = "kie.keystore.key.jwt.alias";
public static final String PROP_PWD_JWT_PWD = "kie.keystore.key.jwt.pwd";

// the private key identifier for controller
private static final String PROP_PWD_CTRL_ALIAS = "kie.keystore.key.ctrl.alias";
// the private key identifier for controller
private static final String PROP_PWD_CTRL_PWD = "kie.keystore.key.ctrl.pwd";

Expand All @@ -37,6 +51,30 @@ public static String loadControllerPassword(final String defaultPassword) {
return loadPasswordKey(PROP_PWD_CTRL_ALIAS, PROP_PWD_CTRL_PWD, defaultPassword);
}

public static KeyPair getJwtKeyPair() {
String pwdKeyAlias = System.getProperty(PROP_PWD_JWT_ALIAS, "");
String pwdKeyPassword = System.getProperty(PROP_PWD_JWT_PWD, "");
return getJwtKeyPair(pwdKeyAlias, pwdKeyPassword);
}

public static KeyPair getJwtKeyPair(String pwdKeyAlias, String pwdKeyPassword) {
try {
KeyStoreHelper keyStoreHelper = KeyStoreHelper.get();
KeyStore keystore = keyStoreHelper.getPvtKeyStore();
Key key = (PrivateKey) keystore.getKey(pwdKeyAlias, pwdKeyPassword.toCharArray());
if (key instanceof PrivateKey) {
// Get certificate of public key
Certificate cert = keystore.getCertificate(pwdKeyAlias);
PublicKey publicKey = cert.getPublicKey();
return new KeyPair(publicKey, (PrivateKey) key);
}
return null;
} catch (RuntimeException | UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException re) {
logger.warn("Unable to load key store. Using password from configuration");
}
return null;
}

public static String loadPasswordKey(String pwdKeyAliasProperty, String pwdKeyPasswordProperty, String defaultPassword) {
String passwordKey;
KeyStoreHelper keyStoreHelper = KeyStoreHelper.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,92 @@

package org.kie.server.common;

import java.io.File;
import java.net.URI;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;

import org.drools.core.util.KeyStoreConstants;
import org.drools.core.util.KeyStoreHelper;
import org.junit.BeforeClass;
import org.junit.Test;
import org.kie.server.api.KieServerConstants;
import org.kie.server.api.model.KieServerConfig;
import org.kie.server.api.model.KieServerConfigItem;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.kie.server.common.KeyStoreHelperUtil.loadControllerPassword;

public class KeyStoreHelperUtilTest {

private static final String KEYSTORE_PATH = "target/keystore.jks";
private static final String KEYSTORE_PWD = "password";
private static final String KEYSTORE_KEY_ALIAS = "selfsigned";
private static final String KEYSTORE_KEY_PWD = "password";

@BeforeClass
public static void init() throws Exception {
File file = new File(KEYSTORE_PATH);
file.delete();

// generate self signed certificate
String[] cmd = { "keytool", "-genkey",
"-keyalg", "RSA",
"-alias", KEYSTORE_KEY_ALIAS,
"-keystore", KEYSTORE_PATH,
"-storepass", KEYSTORE_PWD,
"-validity", "360",
"-keysize", "1024",
"-keypass", KEYSTORE_KEY_PWD,
"-dname", "CN=root, OU=root, O=root, L=root, ST=root, C=root"
};

ProcessBuilder builder = new ProcessBuilder();
builder.command(cmd);
Process p = builder.start();
p.waitFor(10, TimeUnit.SECONDS);
}

@Test
public void testKeyPairReading() throws Exception {
try {
// this test if we can read our own keys properly
URI uri = Paths.get(KEYSTORE_PATH).toAbsolutePath().toUri();
System.setProperty(KeyStoreConstants.PROP_PVT_KS_URL, uri.toURL().toExternalForm());
System.setProperty(KeyStoreConstants.PROP_PVT_KS_PWD, KEYSTORE_PWD);
System.setProperty(KeyStoreHelperUtil.PROP_PWD_JWT_ALIAS, KEYSTORE_KEY_ALIAS);
System.setProperty(KeyStoreHelperUtil.PROP_PWD_JWT_PWD, KEYSTORE_KEY_PWD);

KeyStoreHelper.reInit();

assertNotNull(KeyStoreHelperUtil.getJwtKeyPair());

} finally {
System.clearProperty(KeyStoreConstants.PROP_PVT_KS_URL);
System.clearProperty(KeyStoreConstants.PROP_PVT_KS_PWD);
System.clearProperty(KeyStoreHelperUtil.PROP_PWD_JWT_ALIAS);
System.clearProperty(KeyStoreHelperUtil.PROP_PWD_JWT_PWD);
}

}

@Test
public void testDefaultPassword(){
public void testDefaultPassword() {
final String defaultPassword = "default";
final String password = loadControllerPassword(defaultPassword);
assertEquals(defaultPassword, password);
}

@Test
public void testConfigDefaultPassword(){
public void testConfigDefaultPassword() {
final KieServerConfig serverConfig = new KieServerConfig();
final String password = loadControllerPassword(serverConfig);
assertEquals("kieserver1!", password);
}

@Test
public void testConfigPassword(){
public void testConfigPassword() {
final KieServerConfig serverConfig = new KieServerConfig();
final String defaultPassword = "default";
serverConfig.addConfigItem(new KieServerConfigItem(KieServerConstants.CFG_KIE_CONTROLLER_PASSWORD, defaultPassword, null));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
import org.kie.server.services.api.KieServerExtension;
import org.kie.server.services.impl.KieServerImpl;
import org.kie.server.services.impl.KieServerLocator;
import org.kie.server.services.impl.security.adapters.JMSSecurityAdapter;
import org.kie.server.services.impl.security.adapters.BrokerSecurityAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -208,7 +208,7 @@ public void onMessage(Message message) {
logger.warn("Unable to retrieve user name and/or password, from message");
}
if (username != null && password != null) {
JMSSecurityAdapter.login(username, password);
BrokerSecurityAdapter.login(username, password);
} else {
logger.warn("Unable to login to JMSSecurityAdapter, user name and/or password missing");
}
Expand Down Expand Up @@ -320,10 +320,10 @@ public void onMessage(Message message) {
} catch (JMSRuntimeException runtimeException) {
logger.error("Error while attempting to close connection/session",runtimeException);
} finally {
JMSSecurityAdapter.logout();
BrokerSecurityAdapter.logout();
}
} else {
JMSSecurityAdapter.logout();
BrokerSecurityAdapter.logout();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>

<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@
import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.evidence.Evidence;

public class JMSSecurityAdapter implements SecurityAdapter {
private static final Logger logger = LoggerFactory.getLogger(JMSSecurityAdapter.class);
public class BrokerSecurityAdapter implements SecurityAdapter {

private static final Logger logger = LoggerFactory.getLogger(BrokerSecurityAdapter.class);

private static final ServiceLoader<SecurityAdapter> securityAdapters = ServiceLoader.load(SecurityAdapter.class);
private static List<SecurityAdapter> adapters = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.kie.server.services.impl.security.adapters;

import java.util.List;

import org.kie.server.api.security.SecurityAdapter;
import org.kie.server.services.impl.util.JwtUserDetails;

public class JwtSecurityAdaptor implements SecurityAdapter {

private static ThreadLocal<JwtUserDetails> threadLocal = new ThreadLocal<JwtUserDetails>() {
@Override
public JwtUserDetails initialValue() {
return new JwtUserDetails();
}
};

public static void login(JwtUserDetails userDetails) {
threadLocal.set(userDetails);
}

@Override
public String getUser(Object... params) {
JwtUserDetails userDetails = threadLocal.get();
if (!userDetails.isLogged()) {
return null;
}
return userDetails.getUser();
}

@Override
public List<String> getRoles(Object... params) {
return threadLocal.get().getRoles();
}

public static void logout() {
threadLocal.set(null);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* 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.kie.server.services.impl.util;

import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;

public class JwtService {

private JWTVerifier verifier;

private Algorithm algorithm;
private String issuer;

private JwtService() {
this(Algorithm.none());
}

private JwtService(Algorithm algorithm) {
this(algorithm, "jBPM");
}

private JwtService(Algorithm algorithm, String issuer) {
this.issuer = issuer;
this.algorithm = algorithm;
this.verifier = JWT.require(algorithm)
.withIssuer(issuer)
.build();
}

public String getIssuer() {
return issuer;
}

public String token(String user, String... roles) {
return JWT.create().withIssuer(this.issuer).withSubject(user).withClaim("roles", Arrays.asList(roles)).sign(algorithm);
}

public static JwtServiceBuilder newJwtServiceBuilder() {
return new JwtServiceBuilder();
}

public static class JwtServiceBuilder {
Algorithm algorithm;
String issuer;

public JwtServiceBuilder keys(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
this.algorithm = Algorithm.RSA256(publicKey, privateKey);
return this;
}

public JwtServiceBuilder issuer(String issuer) {
this.issuer = issuer;
return this;
}

public JwtService build() {
return new JwtService(algorithm != null ? algorithm : Algorithm.none(), issuer != null ? issuer : "jBPM");
}

public JwtServiceBuilder keyPair(KeyPair keyPair) {
if (keyPair != null) {
this.algorithm = Algorithm.RSA256((RSAPublicKey) keyPair.getPublic(), (RSAPrivateKey) keyPair.getPrivate());
}
return this;
}

}

public JwtUserDetails decodeUserDetails(String token) {
try {
DecodedJWT decodedJWT = verifier.verify(token);
String user = decodedJWT.getSubject();
Claim rolesClaim = decodedJWT.getClaim("roles");
List<String> roles = rolesClaim.asList(String.class);
return new JwtUserDetails(user, roles != null ? roles : new ArrayList<>());
} catch (JWTVerificationException exception) {
throw new IllegalArgumentException(exception);
}
}

}
Loading

0 comments on commit 16a50c2

Please sign in to comment.