Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[incubator-kie-issues-994] kafka auth feature with message header records #3037

Merged
merged 4 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,10 @@ 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";

// 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 +50,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(KeyStoreConstants.PROP_PVT_KS_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,83 @@

package org.kie.server.common;

import java.net.URI;
import java.nio.file.Paths;

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_ALIAS = "selfsigned";

@BeforeClass
public static void init() throws Exception {
// generate self signed certificate
String[] cmd = { "keytool", "-genkey",
"-keyalg", "RSA",
"-alias", KEYSTORE_ALIAS,
"-keystore", KEYSTORE_PATH,
"-storepass", KEYSTORE_PWD,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to add

                "-keypass", KEYSTORE_PWD,

otherwise the test was hanging for me infinitely.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah after java 8 the keypass is mandatory. my bad for testing only in java 8

"-validity", "360",
"-keysize", "1024",
"-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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps better to add a timeout:

p.waitFor(10L,  TimeUnit.SECONDS);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

@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_ALIAS);

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);
}

}

@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
Loading