This closes #835
This commit is contained in:
commit
9f7fc88363
|
@ -30,6 +30,8 @@
|
|||
<properties>
|
||||
<activemq.basedir>${project.basedir}/..</activemq.basedir>
|
||||
<winsw.version>1.18</winsw.version>
|
||||
<commons.config.version>2.1</commons.config.version>
|
||||
<commons.lang.version>3.0</commons.lang.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -67,6 +69,16 @@
|
|||
<groupId>io.airlift</groupId>
|
||||
<artifactId>airline</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-configuration2</artifactId>
|
||||
<version>${commons.config.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons.lang.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.winsw</groupId>
|
||||
<artifactId>winsw</artifactId>
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.apache.activemq.artemis.cli.commands.ActionContext;
|
|||
import org.apache.activemq.artemis.cli.commands.Create;
|
||||
import org.apache.activemq.artemis.cli.commands.HelpAction;
|
||||
import org.apache.activemq.artemis.cli.commands.Kill;
|
||||
import org.apache.activemq.artemis.cli.commands.Mask;
|
||||
import org.apache.activemq.artemis.cli.commands.Run;
|
||||
import org.apache.activemq.artemis.cli.commands.Stop;
|
||||
import org.apache.activemq.artemis.cli.commands.destination.CreateDestination;
|
||||
|
@ -42,6 +43,11 @@ import org.apache.activemq.artemis.cli.commands.tools.HelpData;
|
|||
import org.apache.activemq.artemis.cli.commands.tools.PrintData;
|
||||
import org.apache.activemq.artemis.cli.commands.tools.XmlDataExporter;
|
||||
import org.apache.activemq.artemis.cli.commands.tools.XmlDataImporter;
|
||||
import org.apache.activemq.artemis.cli.commands.user.AddUser;
|
||||
import org.apache.activemq.artemis.cli.commands.user.HelpUser;
|
||||
import org.apache.activemq.artemis.cli.commands.user.ListUser;
|
||||
import org.apache.activemq.artemis.cli.commands.user.RemoveUser;
|
||||
import org.apache.activemq.artemis.cli.commands.user.ResetUser;
|
||||
|
||||
/**
|
||||
* Artemis is the main CLI entry point for managing/running a broker.
|
||||
|
@ -120,7 +126,7 @@ public class Artemis {
|
|||
|
||||
private static Cli.CliBuilder<Action> builder(File artemisInstance) {
|
||||
String instance = artemisInstance != null ? artemisInstance.getAbsolutePath() : System.getProperty("artemis.instance");
|
||||
Cli.CliBuilder<Action> builder = Cli.<Action>builder("artemis").withDescription("ActiveMQ Artemis Command Line").withCommand(HelpAction.class).withCommand(Producer.class).withCommand(Consumer.class).withCommand(Browse.class).withDefaultCommand(HelpAction.class);
|
||||
Cli.CliBuilder<Action> builder = Cli.<Action>builder("artemis").withDescription("ActiveMQ Artemis Command Line").withCommand(HelpAction.class).withCommand(Producer.class).withCommand(Consumer.class).withCommand(Browse.class).withCommand(Mask.class).withDefaultCommand(HelpAction.class);
|
||||
|
||||
builder.withGroup("destination").withDescription("Destination tools group (create|delete) (example ./artemis destination create)").
|
||||
withDefaultCommand(HelpDestination.class).withCommands(CreateDestination.class, DeleteDestination.class);
|
||||
|
@ -128,6 +134,8 @@ public class Artemis {
|
|||
if (instance != null) {
|
||||
builder.withGroup("data").withDescription("data tools group (print|exp|imp|exp|encode|decode|compact) (example ./artemis data print)").
|
||||
withDefaultCommand(HelpData.class).withCommands(PrintData.class, XmlDataExporter.class, XmlDataImporter.class, DecodeJournal.class, EncodeJournal.class, CompactJournal.class);
|
||||
builder.withGroup("user").withDescription("default file-based user management (add|rm|list|reset) (example ./artemis user list)").
|
||||
withDefaultCommand(HelpUser.class).withCommands(ListUser.class, AddUser.class, RemoveUser.class, ResetUser.class);
|
||||
builder = builder.withCommands(Run.class, Stop.class, Kill.class);
|
||||
} else {
|
||||
builder.withGroup("data").withDescription("data tools group (print) (example ./artemis data print)").
|
||||
|
|
|
@ -38,6 +38,7 @@ import io.airlift.airline.Arguments;
|
|||
import io.airlift.airline.Command;
|
||||
import io.airlift.airline.Option;
|
||||
import org.apache.activemq.artemis.cli.CLIException;
|
||||
import org.apache.activemq.artemis.cli.commands.util.HashUtil;
|
||||
import org.apache.activemq.artemis.cli.commands.util.SyncCalculation;
|
||||
import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
|
||||
import org.apache.activemq.artemis.jlibaio.LibaioContext;
|
||||
|
@ -415,9 +416,11 @@ public class Create extends InputAbstract {
|
|||
public String getPassword() {
|
||||
|
||||
if (password == null) {
|
||||
this.password = inputPassword("--password", "Please provide the default password:", "admin");
|
||||
password = inputPassword("--password", "Please provide the default password:", "admin");
|
||||
}
|
||||
|
||||
password = HashUtil.tryHash(context, password);
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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.cli.commands;
|
||||
|
||||
import io.airlift.airline.Arguments;
|
||||
import io.airlift.airline.Command;
|
||||
import io.airlift.airline.Option;
|
||||
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
|
||||
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Command(name = "mask", description = "mask a password and print it out")
|
||||
public class Mask implements Action {
|
||||
|
||||
@Arguments(description = "The password to be masked", required = true)
|
||||
String password;
|
||||
|
||||
@Option(name = "--hash", description = "whether to use hash (one-way), default false")
|
||||
boolean hash = false;
|
||||
|
||||
@Option(name = "--key", description = "the key (Blowfish) to mask a password")
|
||||
String key;
|
||||
|
||||
private DefaultSensitiveStringCodec codec;
|
||||
|
||||
@Override
|
||||
public Object execute(ActionContext context) throws Exception {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
|
||||
if (hash) {
|
||||
params.put(DefaultSensitiveStringCodec.ALGORITHM, DefaultSensitiveStringCodec.ONE_WAY);
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
if (hash) {
|
||||
context.out.println("Option --key ignored in case of hashing");
|
||||
} else {
|
||||
params.put(DefaultSensitiveStringCodec.BLOWFISH_KEY, key);
|
||||
}
|
||||
}
|
||||
|
||||
codec = PasswordMaskingUtil.getDefaultCodec();
|
||||
codec.init(params);
|
||||
|
||||
String masked = codec.encode(password);
|
||||
context.out.println("result: " + masked);
|
||||
return masked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHomeValues(File brokerHome, File brokerInstance) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrokerInstance() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrokerHome() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void setHash(boolean hash) {
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public DefaultSensitiveStringCodec getCodec() {
|
||||
return codec;
|
||||
}
|
||||
}
|
|
@ -58,7 +58,7 @@ public class CreateDestination extends DestinationAction {
|
|||
}
|
||||
|
||||
private void createJmsTopic(final ActionContext context) throws Exception {
|
||||
performJmsManagement(brokerURL, user, password, new ManagementCallback<Message>() {
|
||||
performJmsManagement(new ManagementCallback<Message>() {
|
||||
@Override
|
||||
public void setUpInvocation(Message message) throws Exception {
|
||||
JMSManagementHelper.putOperationInvocation(message, "jms.server", "createTopic", getName(), bindings);
|
||||
|
@ -90,7 +90,7 @@ public class CreateDestination extends DestinationAction {
|
|||
}
|
||||
|
||||
private void createCoreQueue(final ActionContext context) throws Exception {
|
||||
performCoreManagement(brokerURL, user, password, new ManagementCallback<ClientMessage>() {
|
||||
performCoreManagement(new ManagementCallback<ClientMessage>() {
|
||||
@Override
|
||||
public void setUpInvocation(ClientMessage message) throws Exception {
|
||||
String address = getAddress();
|
||||
|
@ -112,7 +112,7 @@ public class CreateDestination extends DestinationAction {
|
|||
|
||||
private void createJmsQueue(final ActionContext context) throws Exception {
|
||||
|
||||
performJmsManagement(brokerURL, user, password, new ManagementCallback<Message>() {
|
||||
performJmsManagement(new ManagementCallback<Message>() {
|
||||
|
||||
@Override
|
||||
public void setUpInvocation(Message message) throws Exception {
|
||||
|
|
|
@ -49,7 +49,7 @@ public class DeleteDestination extends DestinationAction {
|
|||
}
|
||||
|
||||
private void deleteJmsTopic(final ActionContext context) throws Exception {
|
||||
performJmsManagement(brokerURL, user, password, new ManagementCallback<Message>() {
|
||||
performJmsManagement(new ManagementCallback<Message>() {
|
||||
@Override
|
||||
public void setUpInvocation(Message message) throws Exception {
|
||||
JMSManagementHelper.putOperationInvocation(message, "jms.server", "destroyTopic", getName(), removeConsumers);
|
||||
|
@ -74,7 +74,7 @@ public class DeleteDestination extends DestinationAction {
|
|||
}
|
||||
|
||||
private void deleteJmsQueue(final ActionContext context) throws Exception {
|
||||
performJmsManagement(brokerURL, user, password, new ManagementCallback<Message>() {
|
||||
performJmsManagement(new ManagementCallback<Message>() {
|
||||
@Override
|
||||
public void setUpInvocation(Message message) throws Exception {
|
||||
JMSManagementHelper.putOperationInvocation(message, "jms.server", "destroyQueue", getName(), removeConsumers);
|
||||
|
@ -99,7 +99,7 @@ public class DeleteDestination extends DestinationAction {
|
|||
}
|
||||
|
||||
private void deleteCoreQueue(final ActionContext context) throws Exception {
|
||||
performCoreManagement(brokerURL, user, password, new ManagementCallback<ClientMessage>() {
|
||||
performCoreManagement(new ManagementCallback<ClientMessage>() {
|
||||
@Override
|
||||
public void setUpInvocation(ClientMessage message) throws Exception {
|
||||
ManagementHelper.putOperationInvocation(message, "core.server", "destroyQueue", getName());
|
||||
|
|
|
@ -31,13 +31,12 @@ import org.apache.activemq.artemis.api.core.client.ServerLocator;
|
|||
import org.apache.activemq.artemis.api.core.management.ManagementHelper;
|
||||
import org.apache.activemq.artemis.api.jms.ActiveMQJMSClient;
|
||||
import org.apache.activemq.artemis.api.jms.management.JMSManagementHelper;
|
||||
import org.apache.activemq.artemis.cli.commands.InputAbstract;
|
||||
import org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl;
|
||||
import org.apache.activemq.artemis.cli.commands.messages.ConnectionAbstract;
|
||||
import org.apache.activemq.artemis.jms.client.ActiveMQConnection;
|
||||
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
|
||||
import org.apache.activemq.artemis.jms.client.ActiveMQSession;
|
||||
|
||||
public abstract class DestinationAction extends InputAbstract {
|
||||
public abstract class DestinationAction extends ConnectionAbstract {
|
||||
|
||||
public static final String JMS_QUEUE = "jms-queue";
|
||||
public static final String JMS_TOPIC = "topic";
|
||||
|
@ -46,24 +45,12 @@ public abstract class DestinationAction extends InputAbstract {
|
|||
@Option(name = "--type", description = "type of destination to be created (one of jms-queue, topic and core-queue, default jms-queue")
|
||||
String destType = JMS_QUEUE;
|
||||
|
||||
@Option(name = "--url", description = "URL towards the broker. (default: tcp://localhost:61616)")
|
||||
String brokerURL = "tcp://localhost:61616";
|
||||
|
||||
@Option(name = "--user", description = "User used to connect")
|
||||
String user;
|
||||
|
||||
@Option(name = "--password", description = "Password used to connect")
|
||||
String password;
|
||||
|
||||
@Option(name = "--name", description = "destination name")
|
||||
String name;
|
||||
|
||||
public static void performJmsManagement(String brokerURL,
|
||||
String user,
|
||||
String password,
|
||||
ManagementCallback<Message> cb) throws Exception {
|
||||
public void performJmsManagement(ManagementCallback<Message> cb) throws Exception {
|
||||
|
||||
try (ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL, user, password);
|
||||
try (ActiveMQConnectionFactory factory = createConnectionFactory();
|
||||
ActiveMQConnection connection = (ActiveMQConnection) factory.createConnection();
|
||||
ActiveMQSession session = (ActiveMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE)) {
|
||||
|
||||
|
@ -88,12 +75,10 @@ public abstract class DestinationAction extends InputAbstract {
|
|||
}
|
||||
}
|
||||
|
||||
public static void performCoreManagement(String brokerURL,
|
||||
String user,
|
||||
String password,
|
||||
ManagementCallback<ClientMessage> cb) throws Exception {
|
||||
public void performCoreManagement(ManagementCallback<ClientMessage> cb) throws Exception {
|
||||
|
||||
try (ServerLocator locator = ServerLocatorImpl.newLocator(brokerURL);
|
||||
try (ActiveMQConnectionFactory factory = createConnectionFactory();
|
||||
ServerLocator locator = factory.getServerLocator();
|
||||
ClientSessionFactory sessionFactory = locator.createSessionFactory();
|
||||
ClientSession session = sessionFactory.createSession(user, password, false, true, true, false, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE)) {
|
||||
session.start();
|
||||
|
|
|
@ -40,7 +40,7 @@ public class Browse extends DestAbstract {
|
|||
|
||||
System.out.println("Consumer:: filter = " + filter);
|
||||
|
||||
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL, user, password);
|
||||
ActiveMQConnectionFactory factory = createConnectionFactory();
|
||||
|
||||
Destination dest = ActiveMQDestination.createDestination(this.destination, ActiveMQDestination.QUEUE_TYPE);
|
||||
try (Connection connection = factory.createConnection()) {
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* 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.cli.commands.messages;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.JMSSecurityException;
|
||||
|
||||
import io.airlift.airline.Option;
|
||||
import org.apache.activemq.artemis.cli.commands.InputAbstract;
|
||||
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
|
||||
|
||||
public class ConnectionAbstract extends InputAbstract {
|
||||
@Option(name = "--url", description = "URL towards the broker. (default: tcp://localhost:61616)")
|
||||
protected String brokerURL = "tcp://localhost:61616";
|
||||
|
||||
@Option(name = "--user", description = "User used to connect")
|
||||
protected String user;
|
||||
|
||||
@Option(name = "--password", description = "Password used to connect")
|
||||
protected String password;
|
||||
|
||||
|
||||
protected ActiveMQConnectionFactory createConnectionFactory() throws Exception {
|
||||
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory(brokerURL, user, password);
|
||||
try {
|
||||
Connection connection = cf.createConnection();
|
||||
connection.close();
|
||||
return cf;
|
||||
} catch (JMSSecurityException e) {
|
||||
// if a security exception will get the user and password through an input
|
||||
context.err.println("Connection failed::" + e.getMessage());
|
||||
userPassword();
|
||||
return new ActiveMQConnectionFactory(brokerURL, user, password);
|
||||
} catch (JMSException e) {
|
||||
// if a connection exception will ask for the URL, user and password
|
||||
context.err.println("Connection failed::" + e.getMessage());
|
||||
brokerURL = input("--url", "Type in the broker URL for a retry (e.g. tcp://localhost:61616)", brokerURL);
|
||||
userPassword();
|
||||
return new ActiveMQConnectionFactory(brokerURL, user, password);
|
||||
}
|
||||
}
|
||||
|
||||
private void userPassword() {
|
||||
if (user == null) {
|
||||
user = input("--user", "Type the username for a retry", null);
|
||||
}
|
||||
if (password == null) {
|
||||
password = inputPassword("--password", "Type the password for a retry", null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -49,7 +49,7 @@ public class Consumer extends DestAbstract {
|
|||
|
||||
System.out.println("Consumer:: filter = " + filter);
|
||||
|
||||
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL, user, password);
|
||||
ActiveMQConnectionFactory factory = createConnectionFactory();
|
||||
|
||||
Destination dest = ActiveMQDestination.createDestination(this.destination, ActiveMQDestination.QUEUE_TYPE);
|
||||
try (Connection connection = factory.createConnection()) {
|
||||
|
|
|
@ -18,12 +18,8 @@
|
|||
package org.apache.activemq.artemis.cli.commands.messages;
|
||||
|
||||
import io.airlift.airline.Option;
|
||||
import org.apache.activemq.artemis.cli.commands.ActionAbstract;
|
||||
|
||||
public class DestAbstract extends ActionAbstract {
|
||||
|
||||
@Option(name = "--url", description = "URL towards the broker. (default: tcp://localhost:61616)")
|
||||
String brokerURL = "tcp://localhost:61616";
|
||||
public class DestAbstract extends ConnectionAbstract {
|
||||
|
||||
@Option(name = "--destination", description = "Destination to be used. it could be prefixed with queue:// or topic:: (Default: queue://TEST")
|
||||
String destination = "queue://TEST";
|
||||
|
@ -31,12 +27,6 @@ public class DestAbstract extends ActionAbstract {
|
|||
@Option(name = "--message-count", description = "Number of messages to act on (Default: 1000)")
|
||||
int messageCount = 1000;
|
||||
|
||||
@Option(name = "--user", description = "User used to connect")
|
||||
String user;
|
||||
|
||||
@Option(name = "--password", description = "Password used to connect")
|
||||
String password;
|
||||
|
||||
@Option(name = "--sleep", description = "Time wait between each message")
|
||||
int sleep = 0;
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ public class Producer extends DestAbstract {
|
|||
public Object execute(ActionContext context) throws Exception {
|
||||
super.execute(context);
|
||||
|
||||
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL, user, password);
|
||||
ActiveMQConnectionFactory factory = createConnectionFactory();
|
||||
|
||||
Destination dest = ActiveMQDestination.createDestination(this.destination, ActiveMQDestination.QUEUE_TYPE);
|
||||
try (Connection connection = factory.createConnection()) {
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.cli.commands.user;
|
||||
|
||||
import io.airlift.airline.Command;
|
||||
import io.airlift.airline.Option;
|
||||
import org.apache.activemq.artemis.cli.commands.ActionContext;
|
||||
import org.apache.activemq.artemis.cli.commands.util.HashUtil;
|
||||
import org.apache.activemq.artemis.util.FileBasedSecStoreConfig;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Adding a new user, example:
|
||||
* ./artemis user add --username guest --role admin --password ***
|
||||
*/
|
||||
@Command(name = "add", description = "Add a new user")
|
||||
public class AddUser extends PasswordAction {
|
||||
|
||||
@Option(name = "--plaintext", description = "using plaintext (Default false)")
|
||||
boolean plaintext = false;
|
||||
|
||||
@Override
|
||||
public Object execute(ActionContext context) throws Exception {
|
||||
super.execute(context);
|
||||
|
||||
checkInputUser();
|
||||
checkInputPassword();
|
||||
checkInputRole();
|
||||
|
||||
String hash = plaintext ? password : HashUtil.tryHash(context, password);
|
||||
add(hash, StringUtils.split(role, ","));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding a new user
|
||||
* @param hash the password
|
||||
* @param role the role
|
||||
* @throws IllegalArgumentException if user exists
|
||||
*/
|
||||
protected void add(String hash, String... role) throws Exception {
|
||||
FileBasedSecStoreConfig config = getConfiguration();
|
||||
config.addNewUser(username, hash, role);
|
||||
config.save();
|
||||
context.out.println("User added successfully.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.cli.commands.user;
|
||||
|
||||
import io.airlift.airline.Help;
|
||||
import org.apache.activemq.artemis.cli.commands.Action;
|
||||
import org.apache.activemq.artemis.cli.commands.ActionContext;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class HelpUser extends Help implements Action {
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHomeValues(File brokerHome, File brokerInstance) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrokerInstance() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBrokerHome() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(ActionContext context) throws Exception {
|
||||
List<String> commands = new ArrayList<>(1);
|
||||
commands.add("user");
|
||||
help(global, commands);
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.cli.commands.user;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.airlift.airline.Command;
|
||||
import org.apache.activemq.artemis.cli.commands.ActionContext;
|
||||
import org.apache.activemq.artemis.util.FileBasedSecStoreConfig;
|
||||
|
||||
/**
|
||||
* list existing users, example:
|
||||
* ./artemis user list --username guest
|
||||
*/
|
||||
@Command(name = "list", description = "List existing user(s)")
|
||||
public class ListUser extends UserAction {
|
||||
|
||||
@Override
|
||||
public Object execute(ActionContext context) throws Exception {
|
||||
super.execute(context);
|
||||
|
||||
list();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* list a single user or all users
|
||||
* if username is not specified
|
||||
*/
|
||||
protected void list() throws Exception {
|
||||
FileBasedSecStoreConfig config = getConfiguration();
|
||||
List<String> result = config.listUser(username);
|
||||
for (String str : result) {
|
||||
context.out.println(str);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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.cli.commands.user;
|
||||
|
||||
import io.airlift.airline.Option;
|
||||
|
||||
public class PasswordAction extends UserAction {
|
||||
|
||||
@Option(name = "--password", description = "the password (Default: input)")
|
||||
String password;
|
||||
|
||||
protected void checkInputPassword() {
|
||||
if (password == null) {
|
||||
password = inputPassword("--password", "Please provide the password:", null);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.cli.commands.user;
|
||||
|
||||
import io.airlift.airline.Command;
|
||||
import org.apache.activemq.artemis.cli.commands.ActionContext;
|
||||
import org.apache.activemq.artemis.util.FileBasedSecStoreConfig;
|
||||
|
||||
/**
|
||||
* Remove a user, example:
|
||||
* ./artemis user rm --username guest
|
||||
*/
|
||||
@Command(name = "rm", description = "Remove an existing user")
|
||||
public class RemoveUser extends UserAction {
|
||||
|
||||
@Override
|
||||
public Object execute(ActionContext context) throws Exception {
|
||||
super.execute(context);
|
||||
checkInputUser();
|
||||
remove();
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void remove() throws Exception {
|
||||
FileBasedSecStoreConfig config = getConfiguration();
|
||||
config.removeUser(username);
|
||||
config.save();
|
||||
context.out.println("User removed.");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.cli.commands.user;
|
||||
|
||||
import io.airlift.airline.Command;
|
||||
import io.airlift.airline.Option;
|
||||
import org.apache.activemq.artemis.cli.commands.ActionContext;
|
||||
import org.apache.activemq.artemis.cli.commands.util.HashUtil;
|
||||
import org.apache.activemq.artemis.util.FileBasedSecStoreConfig;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Reset a user's password or roles, example:
|
||||
* ./artemis user reset --username guest --role admin --password ***
|
||||
*/
|
||||
@Command(name = "reset", description = "Reset user's password or roles")
|
||||
public class ResetUser extends PasswordAction {
|
||||
|
||||
@Option(name = "--plaintext", description = "using plaintext (Default false)")
|
||||
boolean plaintext = false;
|
||||
|
||||
@Override
|
||||
public Object execute(ActionContext context) throws Exception {
|
||||
super.execute(context);
|
||||
|
||||
checkInputUser();
|
||||
checkInputPassword();
|
||||
|
||||
if (password != null) {
|
||||
password = plaintext ? password : HashUtil.tryHash(context, password);
|
||||
}
|
||||
|
||||
String[] roles = null;
|
||||
if (role != null) {
|
||||
roles = StringUtils.split(role, ",");
|
||||
}
|
||||
|
||||
reset(password, roles);
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void reset(String password, String[] roles) throws Exception {
|
||||
if (password == null && roles == null) {
|
||||
context.err.println("Nothing to update.");
|
||||
return;
|
||||
}
|
||||
FileBasedSecStoreConfig config = getConfiguration();
|
||||
config.updateUser(username, password, roles);
|
||||
config.save();
|
||||
context.out.println("User updated");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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.cli.commands.user;
|
||||
|
||||
import io.airlift.airline.Option;
|
||||
import org.apache.activemq.artemis.cli.commands.InputAbstract;
|
||||
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule;
|
||||
import org.apache.activemq.artemis.util.FileBasedSecStoreConfig;
|
||||
|
||||
import javax.security.auth.login.AppConfigurationEntry;
|
||||
import javax.security.auth.login.Configuration;
|
||||
import java.io.File;
|
||||
|
||||
import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.ROLE_FILE_PROP_NAME;
|
||||
import static org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule.USER_FILE_PROP_NAME;
|
||||
|
||||
public abstract class UserAction extends InputAbstract {
|
||||
|
||||
@Option(name = "--role", description = "user's role(s), comma separated")
|
||||
String role;
|
||||
|
||||
@Option(name = "--user", description = "The user name (Default: input)")
|
||||
String username = null;
|
||||
|
||||
@Option(name = "--entry", description = "The appConfigurationEntry (default: activemq)")
|
||||
String entry = "activemq";
|
||||
|
||||
protected void checkInputUser() {
|
||||
if (username == null) {
|
||||
username = input("--user", "Please provider the userName:", null);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public void checkInputRole() {
|
||||
if (role == null) {
|
||||
role = input("--role", "type a comma separated list of roles", null);
|
||||
}
|
||||
}
|
||||
|
||||
protected FileBasedSecStoreConfig getConfiguration() throws Exception {
|
||||
|
||||
Configuration securityConfig = Configuration.getConfiguration();
|
||||
AppConfigurationEntry[] entries = securityConfig.getAppConfigurationEntry(entry);
|
||||
|
||||
for (AppConfigurationEntry entry : entries) {
|
||||
if (entry.getLoginModuleName().equals(PropertiesLoginModule.class.getName())) {
|
||||
String userFileName = (String) entry.getOptions().get(USER_FILE_PROP_NAME);
|
||||
String roleFileName = (String) entry.getOptions().get(ROLE_FILE_PROP_NAME);
|
||||
|
||||
File etcDir = new File(getBrokerInstance(), "etc");
|
||||
File userFile = new File(etcDir, userFileName);
|
||||
File roleFile = new File(etcDir, roleFileName);
|
||||
|
||||
if (!userFile.exists() || !roleFile.exists()) {
|
||||
throw new IllegalArgumentException("Couldn't find user file or role file!");
|
||||
}
|
||||
|
||||
return new FileBasedSecStoreConfig(userFile, roleFile);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Failed to load security file");
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.cli.commands.util;
|
||||
|
||||
import org.apache.activemq.artemis.cli.commands.ActionContext;
|
||||
import org.apache.activemq.artemis.utils.HashProcessor;
|
||||
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
|
||||
|
||||
public class HashUtil {
|
||||
|
||||
private static final HashProcessor HASH_PROCESSOR = PasswordMaskingUtil.getHashProcessor();
|
||||
|
||||
//calculate the hash for plaintext.
|
||||
//any exception will cause plaintext returned unchanged.
|
||||
public static String tryHash(ActionContext context, String plaintext) {
|
||||
|
||||
try {
|
||||
String hash = HASH_PROCESSOR.hash(plaintext);
|
||||
return hash;
|
||||
} catch (Exception e) {
|
||||
context.err.println("Warning: Failed to calculate hash value for password using " + HASH_PROCESSOR);
|
||||
context.err.println("Reason: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
return plaintext;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* 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.util;
|
||||
|
||||
import org.apache.activemq.artemis.api.core.Pair;
|
||||
import org.apache.activemq.artemis.utils.StringUtil;
|
||||
import org.apache.commons.configuration2.PropertiesConfiguration;
|
||||
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
|
||||
import org.apache.commons.configuration2.builder.fluent.Configurations;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class FileBasedSecStoreConfig {
|
||||
|
||||
private static final String LICENSE_HEADER =
|
||||
"## ---------------------------------------------------------------------------\n" +
|
||||
"## Licensed to the Apache Software Foundation (ASF) under one or more\n" +
|
||||
"## contributor license agreements. See the NOTICE file distributed with\n" +
|
||||
"## this work for additional information regarding copyright ownership.\n" +
|
||||
"## The ASF licenses this file to You under the Apache License, Version 2.0\n" +
|
||||
"## (the \"License\"); you may not use this file except in compliance with\n" +
|
||||
"## the License. You may obtain a copy of the License at\n" +
|
||||
"##\n" +
|
||||
"## http://www.apache.org/licenses/LICENSE-2.0\n" +
|
||||
"##\n" +
|
||||
"## Unless required by applicable law or agreed to in writing, software\n" +
|
||||
"## distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
|
||||
"## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
|
||||
"## See the License for the specific language governing permissions and\n" +
|
||||
"## limitations under the License.\n" +
|
||||
"## ---------------------------------------------------------------------------\n";
|
||||
private FileBasedConfigurationBuilder<PropertiesConfiguration> userBuilder;
|
||||
private FileBasedConfigurationBuilder<PropertiesConfiguration> roleBuilder;
|
||||
private PropertiesConfiguration userConfig;
|
||||
private PropertiesConfiguration roleConfig;
|
||||
|
||||
public FileBasedSecStoreConfig(File userFile, File roleFile) throws Exception {
|
||||
Configurations configs = new Configurations();
|
||||
userBuilder = configs.propertiesBuilder(userFile);
|
||||
roleBuilder = configs.propertiesBuilder(roleFile);
|
||||
userConfig = userBuilder.getConfiguration();
|
||||
roleConfig = roleBuilder.getConfiguration();
|
||||
|
||||
String roleHeader = roleConfig.getLayout().getHeaderComment();
|
||||
String userHeader = userConfig.getLayout().getHeaderComment();
|
||||
|
||||
if (userHeader == null) {
|
||||
if (userConfig.isEmpty()) {
|
||||
//clean and reset header
|
||||
userConfig.clear();
|
||||
userConfig.setHeader(LICENSE_HEADER);
|
||||
}
|
||||
}
|
||||
|
||||
if (roleHeader == null) {
|
||||
if (roleConfig.isEmpty()) {
|
||||
//clean and reset header
|
||||
roleConfig.clear();
|
||||
roleConfig.setHeader(LICENSE_HEADER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addNewUser(String username, String hash, String... roles) throws Exception {
|
||||
if (userConfig.getString(username) != null) {
|
||||
throw new IllegalArgumentException("User already exist: " + username);
|
||||
}
|
||||
userConfig.addProperty(username, hash);
|
||||
addRoles(username, roles);
|
||||
}
|
||||
|
||||
public void save() throws Exception {
|
||||
userBuilder.save();
|
||||
roleBuilder.save();
|
||||
}
|
||||
|
||||
public void removeUser(String username) throws Exception {
|
||||
if (userConfig.getProperty(username) == null) {
|
||||
throw new IllegalArgumentException("user " + username + " doesn't exist.");
|
||||
}
|
||||
userConfig.clearProperty(username);
|
||||
removeRoles(username);
|
||||
}
|
||||
|
||||
public List<String> listUser(String username) {
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add("--- \"user\"(roles) ---\n");
|
||||
|
||||
int totalUsers = 0;
|
||||
if (username != null) {
|
||||
String roles = findRoles(username);
|
||||
result.add("\"" + username + "\"(" + roles + ")");
|
||||
totalUsers++;
|
||||
} else {
|
||||
Iterator<String> iter = userConfig.getKeys();
|
||||
while (iter.hasNext()) {
|
||||
String keyUser = iter.next();
|
||||
String roles = findRoles(keyUser);
|
||||
result.add("\"" + keyUser + "\"(" + roles + ")");
|
||||
totalUsers++;
|
||||
}
|
||||
}
|
||||
result.add("\n Total: " + totalUsers);
|
||||
return result;
|
||||
}
|
||||
|
||||
private String findRoles(String uname) {
|
||||
Iterator<String> iter = roleConfig.getKeys();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean first = true;
|
||||
while (iter.hasNext()) {
|
||||
String role = iter.next();
|
||||
List<String> names = roleConfig.getList(String.class, role);
|
||||
for (String value : names) {
|
||||
//each value may be a comma separated list
|
||||
String[] items = value.split(",");
|
||||
for (String item : items) {
|
||||
if (item.equals(uname)) {
|
||||
if (!first) {
|
||||
builder.append(",");
|
||||
}
|
||||
builder.append(role);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public void updateUser(String username, String password, String[] roles) {
|
||||
String oldPassword = (String) userConfig.getProperty(username);
|
||||
if (oldPassword == null) {
|
||||
throw new IllegalArgumentException("user " + username + " doesn't exist.");
|
||||
}
|
||||
|
||||
if (password != null) {
|
||||
userConfig.setProperty(username, password);
|
||||
}
|
||||
|
||||
if (roles != null && roles.length > 0) {
|
||||
|
||||
removeRoles(username);
|
||||
addRoles(username, roles);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRoles(String username, String[] roles) {
|
||||
for (String role : roles) {
|
||||
List<String> users = roleConfig.getList(String.class, role);
|
||||
if (users == null) {
|
||||
users = new ArrayList<>();
|
||||
}
|
||||
users.add(username);
|
||||
roleConfig.setProperty(role, StringUtil.joinStringList(users, ","));
|
||||
}
|
||||
}
|
||||
|
||||
private void removeRoles(String username) {
|
||||
|
||||
Iterator<String> iterKeys = roleConfig.getKeys();
|
||||
|
||||
List<Pair<String, List<String>>> updateMap = new ArrayList<>();
|
||||
while (iterKeys.hasNext()) {
|
||||
String theRole = iterKeys.next();
|
||||
|
||||
List<String> userList = roleConfig.getList(String.class, theRole);
|
||||
List<String> newList = new ArrayList<>();
|
||||
|
||||
boolean roleChaned = false;
|
||||
for (String value : userList) {
|
||||
//each value may be comma separated.
|
||||
List<String> update = new ArrayList<>();
|
||||
String[] items = value.split(",");
|
||||
boolean found = false;
|
||||
for (String item : items) {
|
||||
if (!item.equals(username)) {
|
||||
update.add(item);
|
||||
} else {
|
||||
found = true;
|
||||
roleChaned = true;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
if (update.size() > 0) {
|
||||
newList.add(StringUtil.joinStringList(update, ","));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (roleChaned) {
|
||||
updateMap.add(new Pair(theRole, newList));
|
||||
}
|
||||
}
|
||||
//do update
|
||||
Iterator<Pair<String, List<String>>> iterUpdate = updateMap.iterator();
|
||||
while (iterUpdate.hasNext()) {
|
||||
Pair<String, List<String>> entry = iterUpdate.next();
|
||||
roleConfig.clearProperty(entry.getA());
|
||||
if (entry.getB().size() > 0) {
|
||||
roleConfig.addProperty(entry.getA(), entry.getB());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,4 +14,5 @@
|
|||
## See the License for the specific language governing permissions and
|
||||
## limitations under the License.
|
||||
## ---------------------------------------------------------------------------
|
||||
${role}=${user}
|
||||
|
||||
${role} = ${user}
|
|
@ -14,4 +14,5 @@
|
|||
## See the License for the specific language governing permissions and
|
||||
## limitations under the License.
|
||||
## ---------------------------------------------------------------------------
|
||||
${user}=${password}
|
||||
|
||||
${user} = ${password}
|
|
@ -26,6 +26,7 @@ import javax.xml.parsers.ParserConfigurationException;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.activemq.artemis.api.core.SimpleString;
|
||||
|
@ -35,16 +36,29 @@ import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
|
|||
import org.apache.activemq.artemis.api.core.client.ServerLocator;
|
||||
import org.apache.activemq.artemis.cli.Artemis;
|
||||
import org.apache.activemq.artemis.cli.CLIException;
|
||||
import org.apache.activemq.artemis.cli.commands.ActionContext;
|
||||
import org.apache.activemq.artemis.cli.commands.Create;
|
||||
import org.apache.activemq.artemis.cli.commands.Mask;
|
||||
import org.apache.activemq.artemis.cli.commands.Run;
|
||||
import org.apache.activemq.artemis.cli.commands.tools.LockAbstract;
|
||||
import org.apache.activemq.artemis.cli.commands.user.AddUser;
|
||||
import org.apache.activemq.artemis.cli.commands.user.ListUser;
|
||||
import org.apache.activemq.artemis.cli.commands.user.RemoveUser;
|
||||
import org.apache.activemq.artemis.cli.commands.user.ResetUser;
|
||||
import org.apache.activemq.artemis.cli.commands.util.SyncCalculation;
|
||||
import org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl;
|
||||
import org.apache.activemq.artemis.jlibaio.LibaioContext;
|
||||
import org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;
|
||||
import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
|
||||
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoader;
|
||||
import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;
|
||||
import org.apache.activemq.artemis.utils.ThreadLeakCheckRule;
|
||||
import org.apache.activemq.artemis.utils.HashProcessor;
|
||||
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
|
||||
import org.apache.activemq.artemis.utils.StringUtil;
|
||||
import org.apache.commons.configuration2.PropertiesConfiguration;
|
||||
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
|
||||
import org.apache.commons.configuration2.builder.fluent.Configurations;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
@ -55,6 +69,11 @@ import org.w3c.dom.Document;
|
|||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Test to validate that the CLI doesn't throw improper exceptions when invoked.
|
||||
*/
|
||||
|
@ -134,7 +153,7 @@ public class ArtemisTest {
|
|||
System.out.println("TotalAvg = " + totalAvg);
|
||||
long nanoTime = SyncCalculation.toNanos(totalAvg, writes);
|
||||
System.out.println("nanoTime avg = " + nanoTime);
|
||||
Assert.assertEquals(0, LibaioContext.getTotalMaxIO());
|
||||
assertEquals(0, LibaioContext.getTotalMaxIO());
|
||||
|
||||
}
|
||||
|
||||
|
@ -146,47 +165,47 @@ public class ArtemisTest {
|
|||
File instance1 = new File(temporaryFolder.getRoot(), "instance1");
|
||||
Artemis.main("create", instance1.getAbsolutePath(), "--silent", "--no-fsync");
|
||||
File bootstrapFile = new File(new File(instance1, "etc"), "bootstrap.xml");
|
||||
Assert.assertTrue(bootstrapFile.exists());
|
||||
assertTrue(bootstrapFile.exists());
|
||||
Document config = parseXml(bootstrapFile);
|
||||
Element webElem = (Element) config.getElementsByTagName("web").item(0);
|
||||
|
||||
String bindAttr = webElem.getAttribute("bind");
|
||||
String bindStr = "http://localhost:" + Create.HTTP_PORT;
|
||||
|
||||
Assert.assertEquals(bindAttr, bindStr);
|
||||
assertEquals(bindAttr, bindStr);
|
||||
//no any of those
|
||||
Assert.assertFalse(webElem.hasAttribute("keyStorePath"));
|
||||
Assert.assertFalse(webElem.hasAttribute("keyStorePassword"));
|
||||
Assert.assertFalse(webElem.hasAttribute("clientAuth"));
|
||||
Assert.assertFalse(webElem.hasAttribute("trustStorePath"));
|
||||
Assert.assertFalse(webElem.hasAttribute("trustStorePassword"));
|
||||
assertFalse(webElem.hasAttribute("keyStorePath"));
|
||||
assertFalse(webElem.hasAttribute("keyStorePassword"));
|
||||
assertFalse(webElem.hasAttribute("clientAuth"));
|
||||
assertFalse(webElem.hasAttribute("trustStorePath"));
|
||||
assertFalse(webElem.hasAttribute("trustStorePassword"));
|
||||
|
||||
//instance2: https
|
||||
File instance2 = new File(temporaryFolder.getRoot(), "instance2");
|
||||
Artemis.main("create", instance2.getAbsolutePath(), "--silent", "--ssl-key", "etc/keystore", "--ssl-key-password", "password1", "--no-fsync");
|
||||
bootstrapFile = new File(new File(instance2, "etc"), "bootstrap.xml");
|
||||
Assert.assertTrue(bootstrapFile.exists());
|
||||
assertTrue(bootstrapFile.exists());
|
||||
config = parseXml(bootstrapFile);
|
||||
webElem = (Element) config.getElementsByTagName("web").item(0);
|
||||
|
||||
bindAttr = webElem.getAttribute("bind");
|
||||
bindStr = "https://localhost:" + Create.HTTP_PORT;
|
||||
Assert.assertEquals(bindAttr, bindStr);
|
||||
assertEquals(bindAttr, bindStr);
|
||||
|
||||
String keyStr = webElem.getAttribute("keyStorePath");
|
||||
Assert.assertEquals("etc/keystore", keyStr);
|
||||
assertEquals("etc/keystore", keyStr);
|
||||
String keyPass = webElem.getAttribute("keyStorePassword");
|
||||
Assert.assertEquals("password1", keyPass);
|
||||
assertEquals("password1", keyPass);
|
||||
|
||||
Assert.assertFalse(webElem.hasAttribute("clientAuth"));
|
||||
Assert.assertFalse(webElem.hasAttribute("trustStorePath"));
|
||||
Assert.assertFalse(webElem.hasAttribute("trustStorePassword"));
|
||||
assertFalse(webElem.hasAttribute("clientAuth"));
|
||||
assertFalse(webElem.hasAttribute("trustStorePath"));
|
||||
assertFalse(webElem.hasAttribute("trustStorePassword"));
|
||||
|
||||
//instance3: https with clientAuth
|
||||
File instance3 = new File(temporaryFolder.getRoot(), "instance3");
|
||||
Artemis.main("create", instance3.getAbsolutePath(), "--silent", "--ssl-key", "etc/keystore", "--ssl-key-password", "password1", "--use-client-auth", "--ssl-trust", "etc/truststore", "--ssl-trust-password", "password2", "--no-fsync");
|
||||
bootstrapFile = new File(new File(instance3, "etc"), "bootstrap.xml");
|
||||
Assert.assertTrue(bootstrapFile.exists());
|
||||
assertTrue(bootstrapFile.exists());
|
||||
|
||||
byte[] contents = Files.readAllBytes(bootstrapFile.toPath());
|
||||
String cfgText = new String(contents);
|
||||
|
@ -197,19 +216,298 @@ public class ArtemisTest {
|
|||
|
||||
bindAttr = webElem.getAttribute("bind");
|
||||
bindStr = "https://localhost:" + Create.HTTP_PORT;
|
||||
Assert.assertEquals(bindAttr, bindStr);
|
||||
assertEquals(bindAttr, bindStr);
|
||||
|
||||
keyStr = webElem.getAttribute("keyStorePath");
|
||||
Assert.assertEquals("etc/keystore", keyStr);
|
||||
assertEquals("etc/keystore", keyStr);
|
||||
keyPass = webElem.getAttribute("keyStorePassword");
|
||||
Assert.assertEquals("password1", keyPass);
|
||||
assertEquals("password1", keyPass);
|
||||
|
||||
String clientAuthAttr = webElem.getAttribute("clientAuth");
|
||||
Assert.assertEquals("true", clientAuthAttr);
|
||||
assertEquals("true", clientAuthAttr);
|
||||
String trustPathAttr = webElem.getAttribute("trustStorePath");
|
||||
Assert.assertEquals("etc/truststore", trustPathAttr);
|
||||
assertEquals("etc/truststore", trustPathAttr);
|
||||
String trustPass = webElem.getAttribute("trustStorePassword");
|
||||
Assert.assertEquals("password2", trustPass);
|
||||
assertEquals("password2", trustPass);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserCommand() throws Exception {
|
||||
Run.setEmbedded(true);
|
||||
File instance1 = new File(temporaryFolder.getRoot(), "instance_user");
|
||||
System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config");
|
||||
Artemis.main("create", instance1.getAbsolutePath(), "--silent");
|
||||
System.setProperty("artemis.instance", instance1.getAbsolutePath());
|
||||
|
||||
File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
|
||||
File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
|
||||
|
||||
ListUser listCmd = new ListUser();
|
||||
TestActionContext context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
|
||||
String result = context.getStdout();
|
||||
System.out.println("output1:\n" + result);
|
||||
|
||||
//default only one user admin with role amq
|
||||
assertTrue(result.contains("\"admin\"(amq)"));
|
||||
checkRole("admin", roleFile, "amq");
|
||||
|
||||
//add a simple user
|
||||
AddUser addCmd = new AddUser();
|
||||
addCmd.setUsername("guest");
|
||||
addCmd.setPassword("guest123");
|
||||
addCmd.setRole("admin");
|
||||
addCmd.execute(new TestActionContext());
|
||||
|
||||
//verify use list cmd
|
||||
context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
result = context.getStdout();
|
||||
System.out.println("output2:\n" + result);
|
||||
|
||||
assertTrue(result.contains("\"admin\"(amq)"));
|
||||
assertTrue(result.contains("\"guest\"(admin)"));
|
||||
|
||||
checkRole("guest", roleFile, "admin");
|
||||
assertTrue(checkPassword("guest", "guest123", userFile));
|
||||
|
||||
//add a user with 2 roles
|
||||
addCmd = new AddUser();
|
||||
addCmd.setUsername("scott");
|
||||
addCmd.setPassword("tiger");
|
||||
addCmd.setRole("admin,operator");
|
||||
addCmd.execute(ActionContext.system());
|
||||
|
||||
//verify
|
||||
context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
result = context.getStdout();
|
||||
System.out.println("output3:\n" + result);
|
||||
|
||||
assertTrue(result.contains("\"admin\"(amq)"));
|
||||
assertTrue(result.contains("\"guest\"(admin)"));
|
||||
assertTrue(result.contains("\"scott\"(admin,operator)"));
|
||||
|
||||
checkRole("scott", roleFile, "admin", "operator");
|
||||
assertTrue(checkPassword("scott", "tiger", userFile));
|
||||
|
||||
//add an existing user
|
||||
addCmd = new AddUser();
|
||||
addCmd.setUsername("scott");
|
||||
addCmd.setPassword("password");
|
||||
addCmd.setRole("visitor");
|
||||
try {
|
||||
addCmd.execute(ActionContext.system());
|
||||
fail("should throw an exception if adding a existing user");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
|
||||
//check existing users are intact
|
||||
context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
result = context.getStdout();
|
||||
System.out.println("output4:\n" + result);
|
||||
|
||||
assertTrue(result.contains("\"admin\"(amq)"));
|
||||
assertTrue(result.contains("\"guest\"(admin)"));
|
||||
assertTrue(result.contains("\"scott\"(admin,operator)"));
|
||||
|
||||
//remove a user
|
||||
RemoveUser rmCmd = new RemoveUser();
|
||||
rmCmd.setUsername("guest");
|
||||
rmCmd.execute(ActionContext.system());
|
||||
|
||||
//check
|
||||
context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
result = context.getStdout();
|
||||
System.out.println("output5:\n" + result);
|
||||
|
||||
assertTrue(result.contains("\"admin\"(amq)"));
|
||||
assertFalse(result.contains("\"guest\"(admin)"));
|
||||
assertTrue(result.contains("\"scott\"(admin,operator)") || result.contains("\"scott\"(operator,admin)"));
|
||||
assertTrue(result.contains("Total: 2"));
|
||||
|
||||
//remove another
|
||||
rmCmd = new RemoveUser();
|
||||
rmCmd.setUsername("scott");
|
||||
rmCmd.execute(ActionContext.system());
|
||||
|
||||
//check
|
||||
context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
result = context.getStdout();
|
||||
System.out.println("output6:\n" + result);
|
||||
|
||||
assertTrue(result.contains("\"admin\"(amq)"));
|
||||
assertFalse(result.contains("\"guest\"(admin)"));
|
||||
assertFalse(result.contains("\"scott\"(admin,operator)") || result.contains("\"scott\"(operator,admin)"));
|
||||
assertTrue(result.contains("Total: 1"));
|
||||
|
||||
//remove non-exist
|
||||
rmCmd = new RemoveUser();
|
||||
rmCmd.setUsername("alien");
|
||||
try {
|
||||
rmCmd.execute(ActionContext.system());
|
||||
fail("should throw exception when removing a non-existing user");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
|
||||
//check
|
||||
context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
result = context.getStdout();
|
||||
System.out.println("output7:\n" + result);
|
||||
assertTrue(result.contains("\"admin\"(amq)"));
|
||||
assertTrue(result.contains("Total: 1"));
|
||||
|
||||
//now remove last
|
||||
rmCmd = new RemoveUser();
|
||||
rmCmd.setUsername("admin");
|
||||
rmCmd.execute(ActionContext.system());
|
||||
|
||||
//check
|
||||
context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
result = context.getStdout();
|
||||
System.out.println("output8:\n" + result);
|
||||
|
||||
assertTrue(result.contains("Total: 0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserCommandReset() throws Exception {
|
||||
Run.setEmbedded(true);
|
||||
File instance1 = new File(temporaryFolder.getRoot(), "instance_user");
|
||||
System.setProperty("java.security.auth.login.config", instance1.getAbsolutePath() + "/etc/login.config");
|
||||
Artemis.main("create", instance1.getAbsolutePath(), "--silent");
|
||||
System.setProperty("artemis.instance", instance1.getAbsolutePath());
|
||||
|
||||
File userFile = new File(instance1.getAbsolutePath() + "/etc/artemis-users.properties");
|
||||
File roleFile = new File(instance1.getAbsolutePath() + "/etc/artemis-roles.properties");
|
||||
|
||||
ListUser listCmd = new ListUser();
|
||||
TestActionContext context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
|
||||
String result = context.getStdout();
|
||||
System.out.println("output1:\n" + result);
|
||||
|
||||
//default only one user admin with role amq
|
||||
assertTrue(result.contains("\"admin\"(amq)"));
|
||||
|
||||
//remove a user
|
||||
RemoveUser rmCmd = new RemoveUser();
|
||||
rmCmd.setUsername("admin");
|
||||
rmCmd.execute(ActionContext.system());
|
||||
|
||||
//check
|
||||
context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
result = context.getStdout();
|
||||
System.out.println("output8:\n" + result);
|
||||
|
||||
assertTrue(result.contains("Total: 0"));
|
||||
|
||||
//add some users
|
||||
AddUser addCmd = new AddUser();
|
||||
addCmd.setUsername("guest");
|
||||
addCmd.setPassword("guest123");
|
||||
addCmd.setRole("admin");
|
||||
addCmd.execute(new TestActionContext());
|
||||
|
||||
addCmd.setUsername("user1");
|
||||
addCmd.setPassword("password1");
|
||||
addCmd.setRole("admin,manager");
|
||||
addCmd.execute(new TestActionContext());
|
||||
assertTrue(checkPassword("user1", "password1", userFile));
|
||||
|
||||
addCmd.setUsername("user2");
|
||||
addCmd.setPassword("password2");
|
||||
addCmd.setRole("admin,manager,master");
|
||||
addCmd.execute(new TestActionContext());
|
||||
|
||||
addCmd.setUsername("user3");
|
||||
addCmd.setPassword("password3");
|
||||
addCmd.setRole("system,master");
|
||||
addCmd.execute(new TestActionContext());
|
||||
|
||||
//verify use list cmd
|
||||
context = new TestActionContext();
|
||||
listCmd.execute(context);
|
||||
result = context.getStdout();
|
||||
System.out.println("output2:\n" + result);
|
||||
|
||||
assertTrue(result.contains("Total: 4"));
|
||||
assertTrue(result.contains("\"guest\"(admin)"));
|
||||
assertTrue(result.contains("\"user1\"(admin,manager)"));
|
||||
assertTrue(result.contains("\"user2\"(admin,manager,master)"));
|
||||
assertTrue(result.contains("\"user3\"(master,system)"));
|
||||
|
||||
checkRole("user1", roleFile, "admin", "manager");
|
||||
|
||||
//reset password
|
||||
context = new TestActionContext();
|
||||
ResetUser resetCommand = new ResetUser();
|
||||
resetCommand.setUsername("user1");
|
||||
resetCommand.setPassword("newpassword1");
|
||||
resetCommand.execute(context);
|
||||
|
||||
checkRole("user1", roleFile, "admin", "manager");
|
||||
assertFalse(checkPassword("user1", "password1", userFile));
|
||||
assertTrue(checkPassword("user1", "newpassword1", userFile));
|
||||
|
||||
//reset role
|
||||
resetCommand.setUsername("user2");
|
||||
resetCommand.setRole("manager,master,operator");
|
||||
resetCommand.execute(new TestActionContext());
|
||||
|
||||
checkRole("user2", roleFile, "manager", "master", "operator");
|
||||
|
||||
//reset both
|
||||
resetCommand.setUsername("user3");
|
||||
resetCommand.setPassword("newpassword3");
|
||||
resetCommand.setRole("admin,system");
|
||||
resetCommand.execute(new ActionContext());
|
||||
|
||||
checkRole("user3", roleFile, "admin", "system");
|
||||
assertTrue(checkPassword("user3", "newpassword3", userFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaskCommand() throws Exception {
|
||||
|
||||
String password1 = "password";
|
||||
String encrypt1 = "3a34fd21b82bf2a822fa49a8d8fa115d";
|
||||
String newKey = "artemisfun";
|
||||
String encrypt2 = "-2b8e3b47950b9b481a6f3100968e42e9";
|
||||
|
||||
|
||||
TestActionContext context = new TestActionContext();
|
||||
Mask mask = new Mask();
|
||||
mask.setPassword(password1);
|
||||
|
||||
String result = (String) mask.execute(context);
|
||||
System.out.println(context.getStdout());
|
||||
assertEquals(encrypt1, result);
|
||||
|
||||
context = new TestActionContext();
|
||||
mask = new Mask();
|
||||
mask.setPassword(password1);
|
||||
mask.setHash(true);
|
||||
result = (String) mask.execute(context);
|
||||
System.out.println(context.getStdout());
|
||||
DefaultSensitiveStringCodec codec = mask.getCodec();
|
||||
codec.verify(password1.toCharArray(), result);
|
||||
|
||||
context = new TestActionContext();
|
||||
mask = new Mask();
|
||||
mask.setPassword(password1);
|
||||
mask.setKey(newKey);
|
||||
result = (String) mask.execute(context);
|
||||
System.out.println(context.getStdout());
|
||||
assertEquals(encrypt2, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -251,11 +549,11 @@ public class ArtemisTest {
|
|||
ClientSession coreSession = factory.createSession("admin", "admin", false, true, true, false, 0)) {
|
||||
for (String str : queues.split(",")) {
|
||||
ClientSession.QueueQuery queryResult = coreSession.queueQuery(SimpleString.toSimpleString("jms.queue." + str));
|
||||
Assert.assertTrue("Couldn't find queue " + str, queryResult.isExists());
|
||||
assertTrue("Couldn't find queue " + str, queryResult.isExists());
|
||||
}
|
||||
for (String str : topics.split(",")) {
|
||||
ClientSession.QueueQuery queryResult = coreSession.queueQuery(SimpleString.toSimpleString("jms.topic." + str));
|
||||
Assert.assertTrue("Couldn't find topic " + str, queryResult.isExists());
|
||||
assertTrue("Couldn't find topic " + str, queryResult.isExists());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,8 +564,8 @@ public class ArtemisTest {
|
|||
}
|
||||
Artemis.internalExecute("data", "print", "--f");
|
||||
|
||||
Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("producer", "--message-count", "100", "--verbose", "--user", "admin", "--password", "admin"));
|
||||
Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
|
||||
assertEquals(Integer.valueOf(100), Artemis.internalExecute("producer", "--message-count", "100", "--verbose", "--user", "admin", "--password", "admin"));
|
||||
assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
|
||||
|
||||
ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616");
|
||||
Connection connection = cf.createConnection("admin", "admin");
|
||||
|
@ -288,20 +586,20 @@ public class ArtemisTest {
|
|||
connection.close();
|
||||
cf.close();
|
||||
|
||||
Assert.assertEquals(Integer.valueOf(1), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
|
||||
assertEquals(Integer.valueOf(1), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
|
||||
|
||||
Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='orange'", "--user", "admin", "--password", "admin"));
|
||||
assertEquals(Integer.valueOf(100), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--filter", "fruit='orange'", "--user", "admin", "--password", "admin"));
|
||||
|
||||
Assert.assertEquals(Integer.valueOf(101), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--user", "admin", "--password", "admin"));
|
||||
assertEquals(Integer.valueOf(101), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--user", "admin", "--password", "admin"));
|
||||
|
||||
// should only receive 10 messages on browse as I'm setting messageCount=10
|
||||
Assert.assertEquals(Integer.valueOf(10), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--message-count", "10", "--user", "admin", "--password", "admin"));
|
||||
assertEquals(Integer.valueOf(10), Artemis.internalExecute("browser", "--txt-size", "50", "--verbose", "--message-count", "10", "--user", "admin", "--password", "admin"));
|
||||
|
||||
// Nothing was consumed until here as it was only browsing, check it's receiving again
|
||||
Assert.assertEquals(Integer.valueOf(1), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
|
||||
assertEquals(Integer.valueOf(1), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--filter", "fruit='banana'", "--user", "admin", "--password", "admin"));
|
||||
|
||||
// Checking it was acked before
|
||||
Assert.assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
|
||||
assertEquals(Integer.valueOf(100), Artemis.internalExecute("consumer", "--txt-size", "50", "--verbose", "--break-on-null", "--receive-timeout", "100", "--user", "admin", "--password", "admin"));
|
||||
} finally {
|
||||
stopServer();
|
||||
}
|
||||
|
@ -312,7 +610,7 @@ public class ArtemisTest {
|
|||
Artemis.main(args);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Assert.fail("Exception caught " + e.getMessage());
|
||||
fail("Exception caught " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -322,8 +620,8 @@ public class ArtemisTest {
|
|||
|
||||
private void stopServer() throws Exception {
|
||||
Artemis.internalExecute("stop");
|
||||
Assert.assertTrue(Run.latchRunning.await(5, TimeUnit.SECONDS));
|
||||
Assert.assertEquals(0, LibaioContext.getTotalMaxIO());
|
||||
assertTrue(Run.latchRunning.await(5, TimeUnit.SECONDS));
|
||||
assertEquals(0, LibaioContext.getTotalMaxIO());
|
||||
}
|
||||
|
||||
private static Document parseXml(File xmlFile) throws ParserConfigurationException, IOException, SAXException {
|
||||
|
@ -332,4 +630,27 @@ public class ArtemisTest {
|
|||
return domBuilder.parse(xmlFile);
|
||||
}
|
||||
|
||||
private void checkRole(String user, File roleFile, String... roles) throws Exception {
|
||||
Configurations configs = new Configurations();
|
||||
FileBasedConfigurationBuilder<PropertiesConfiguration> roleBuilder = configs.propertiesBuilder(roleFile);
|
||||
PropertiesConfiguration roleConfig = roleBuilder.getConfiguration();
|
||||
|
||||
for (String r : roles) {
|
||||
String storedUsers = (String) roleConfig.getProperty(r);
|
||||
|
||||
System.out.println("users in role: " + r + " ; " + storedUsers);
|
||||
List<String> userList = StringUtil.splitStringList(storedUsers, ",");
|
||||
assertTrue(userList.contains(user));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPassword(String user, String password, File userFile) throws Exception {
|
||||
Configurations configs = new Configurations();
|
||||
FileBasedConfigurationBuilder<PropertiesConfiguration> userBuilder = configs.propertiesBuilder(userFile);
|
||||
PropertiesConfiguration userConfig = userBuilder.getConfiguration();
|
||||
String storedPassword = (String) userConfig.getProperty(user);
|
||||
HashProcessor processor = PasswordMaskingUtil.getHashProcessor(storedPassword);
|
||||
return processor.compare(password.toCharArray(), storedPassword);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.cli.test;
|
||||
|
||||
import org.apache.activemq.artemis.cli.commands.ActionContext;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
public class TestActionContext extends ActionContext {
|
||||
|
||||
public ByteArrayOutputStream stdout;
|
||||
public ByteArrayOutputStream stderr;
|
||||
private int bufferSize;
|
||||
|
||||
public TestActionContext(int bufferSize) {
|
||||
this.bufferSize = bufferSize;
|
||||
this.stdout = new ByteArrayOutputStream(bufferSize);
|
||||
this.stderr = new ByteArrayOutputStream(bufferSize);
|
||||
this.in = System.in;
|
||||
this.out = new PrintStream(stdout);
|
||||
this.err = new PrintStream(stderr);
|
||||
}
|
||||
|
||||
public TestActionContext() {
|
||||
this(4096);
|
||||
}
|
||||
|
||||
public String getStdout() {
|
||||
return stdout.toString();
|
||||
}
|
||||
|
||||
public String getStderr() {
|
||||
return stderr.toString();
|
||||
}
|
||||
}
|
|
@ -134,6 +134,14 @@ public class ByteUtil {
|
|||
return buffer.array();
|
||||
}
|
||||
|
||||
public static byte[] hexToBytes(String hexStr) {
|
||||
byte[] bytes = new byte[hexStr.length() / 2];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] = (byte) Integer.parseInt(hexStr.substring(2 * i, 2 * i + 2), 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public static String readLine(ActiveMQBuffer buffer) {
|
||||
StringBuilder sb = new StringBuilder("");
|
||||
char c = buffer.readChar();
|
||||
|
|
|
@ -16,15 +16,19 @@
|
|||
*/
|
||||
package org.apache.activemq.artemis.utils;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* A DefaultSensitiveDataCodec
|
||||
|
@ -34,52 +38,39 @@ import java.util.Map;
|
|||
* file to use a masked password but doesn't give a
|
||||
* codec implementation.
|
||||
*
|
||||
* The decode() and encode() method is copied originally from
|
||||
* JBoss AS code base.
|
||||
* It supports one-way hash (digest) and two-way (encrypt-decrpt) algorithms
|
||||
* The two-way uses "Blowfish" algorithm
|
||||
* The one-way uses "PBKDF2" hash algorithm
|
||||
*/
|
||||
public class DefaultSensitiveStringCodec implements SensitiveDataCodec<String> {
|
||||
|
||||
private byte[] internalKey = "clusterpassword".getBytes();
|
||||
public static final String ALGORITHM = "algorithm";
|
||||
public static final String BLOWFISH_KEY = "key";
|
||||
public static final String ONE_WAY = "one-way";
|
||||
public static final String TWO_WAY = "two-way";
|
||||
|
||||
private CodecAlgorithm algorithm = new BlowfishAlgorithm(Collections.EMPTY_MAP);
|
||||
|
||||
@Override
|
||||
public String decode(Object secret) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
|
||||
SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
|
||||
|
||||
BigInteger n = new BigInteger((String) secret, 16);
|
||||
byte[] encoding = n.toByteArray();
|
||||
|
||||
// JBAS-3457: fix leading zeros
|
||||
if (encoding.length % 8 != 0) {
|
||||
int length = encoding.length;
|
||||
int newLength = ((length / 8) + 1) * 8;
|
||||
int pad = newLength - length; // number of leading zeros
|
||||
byte[] old = encoding;
|
||||
encoding = new byte[newLength];
|
||||
System.arraycopy(old, 0, encoding, pad, old.length);
|
||||
}
|
||||
|
||||
Cipher cipher = Cipher.getInstance("Blowfish");
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||
byte[] decode = cipher.doFinal(encoding);
|
||||
|
||||
return new String(decode);
|
||||
}
|
||||
|
||||
public Object encode(String secret) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
|
||||
SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("Blowfish");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] encoding = cipher.doFinal(secret.getBytes());
|
||||
BigInteger n = new BigInteger(encoding);
|
||||
return n.toString(16);
|
||||
public String decode(Object secret) throws Exception {
|
||||
return algorithm.decode((String) secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Map<String, String> params) {
|
||||
String key = params.get("key");
|
||||
if (key != null) {
|
||||
updateKey(key);
|
||||
public String encode(Object secret) throws Exception {
|
||||
return algorithm.encode((String) secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Map<String, String> params) throws Exception {
|
||||
String algorithm = params.get(ALGORITHM);
|
||||
if (algorithm == null || algorithm.equals(TWO_WAY)) {
|
||||
//two way
|
||||
this.algorithm = new BlowfishAlgorithm(params);
|
||||
} else if (algorithm.equals(ONE_WAY)) {
|
||||
this.algorithm = new PBKDF2Algorithm(params);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid algorithm: " + algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,12 +87,149 @@ public class DefaultSensitiveStringCodec implements SensitiveDataCodec<String> {
|
|||
System.exit(-1);
|
||||
}
|
||||
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
Properties properties = System.getProperties();
|
||||
for (final String name: properties.stringPropertyNames()) {
|
||||
params.put(name, properties.getProperty(name));
|
||||
}
|
||||
codec.init(params);
|
||||
Object encode = codec.encode(args[0]);
|
||||
|
||||
System.out.println("Encoded password (without quotes): \"" + encode + "\"");
|
||||
}
|
||||
|
||||
private void updateKey(String key) {
|
||||
this.internalKey = key.getBytes();
|
||||
public boolean verify(char[] inputValue, String storedValue) {
|
||||
return algorithm.verify(inputValue, storedValue);
|
||||
}
|
||||
|
||||
private abstract class CodecAlgorithm {
|
||||
|
||||
protected Map<String, String> params;
|
||||
|
||||
CodecAlgorithm(Map<String, String> params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public abstract String decode(String secret) throws Exception;
|
||||
public abstract String encode(String secret) throws Exception;
|
||||
|
||||
public boolean verify(char[] inputValue, String storedValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class BlowfishAlgorithm extends CodecAlgorithm {
|
||||
|
||||
private byte[] internalKey = "clusterpassword".getBytes();
|
||||
|
||||
BlowfishAlgorithm(Map<String, String> params) {
|
||||
super(params);
|
||||
String key = params.get(BLOWFISH_KEY);
|
||||
if (key != null) {
|
||||
updateKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateKey(String key) {
|
||||
this.internalKey = key.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(String secret) throws Exception {
|
||||
SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
|
||||
|
||||
BigInteger n = new BigInteger((String) secret, 16);
|
||||
byte[] encoding = n.toByteArray();
|
||||
|
||||
if (encoding.length % 8 != 0) {
|
||||
int length = encoding.length;
|
||||
int newLength = ((length / 8) + 1) * 8;
|
||||
int pad = newLength - length; // number of leading zeros
|
||||
byte[] old = encoding;
|
||||
encoding = new byte[newLength];
|
||||
System.arraycopy(old, 0, encoding, pad, old.length);
|
||||
}
|
||||
|
||||
Cipher cipher = Cipher.getInstance("Blowfish");
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||
byte[] decode = cipher.doFinal(encoding);
|
||||
|
||||
return new String(decode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String secret) throws Exception {
|
||||
SecretKeySpec key = new SecretKeySpec(internalKey, "Blowfish");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("Blowfish");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] encoding = cipher.doFinal(secret.getBytes());
|
||||
BigInteger n = new BigInteger(encoding);
|
||||
return n.toString(16);
|
||||
}
|
||||
}
|
||||
|
||||
private class PBKDF2Algorithm extends CodecAlgorithm {
|
||||
private static final String SEPERATOR = ":";
|
||||
private String sceretKeyAlgorithm = "PBKDF2WithHmacSHA1";
|
||||
private String randomScheme = "SHA1PRNG";
|
||||
private int keyLength = 64 * 8;
|
||||
private int saltLength = 32;
|
||||
private int iterations = 1024;
|
||||
private SecretKeyFactory skf;
|
||||
|
||||
PBKDF2Algorithm(Map<String, String> params) throws NoSuchAlgorithmException {
|
||||
super(params);
|
||||
skf = SecretKeyFactory.getInstance(sceretKeyAlgorithm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decode(String secret) throws Exception {
|
||||
throw new IllegalArgumentException("Algorithm doesn't support decoding");
|
||||
}
|
||||
|
||||
public byte[] getSalt() throws NoSuchAlgorithmException {
|
||||
byte[] salt = new byte[this.saltLength];
|
||||
|
||||
SecureRandom sr = SecureRandom.getInstance(this.randomScheme);
|
||||
sr.nextBytes(salt);
|
||||
return salt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(String secret) throws Exception {
|
||||
char[] chars = secret.toCharArray();
|
||||
byte[] salt = getSalt();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(iterations).append(SEPERATOR).append(ByteUtil.bytesToHex(salt)).append(SEPERATOR);
|
||||
|
||||
PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, keyLength);
|
||||
|
||||
byte[] hash = skf.generateSecret(spec).getEncoded();
|
||||
String hexValue = ByteUtil.bytesToHex(hash);
|
||||
builder.append(hexValue);
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(char[] plainChars, String storedValue) {
|
||||
String[] parts = storedValue.split(SEPERATOR);
|
||||
int originalIterations = Integer.parseInt(parts[0]);
|
||||
byte[] salt = ByteUtil.hexToBytes(parts[1]);
|
||||
byte[] originalHash = ByteUtil.hexToBytes(parts[2]);
|
||||
|
||||
PBEKeySpec spec = new PBEKeySpec(plainChars, salt, originalIterations, originalHash.length * 8);
|
||||
byte[] newHash;
|
||||
|
||||
try {
|
||||
newHash = skf.generateSecret(spec).getEncoded();
|
||||
} catch (InvalidKeySpecException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Arrays.equals(newHash, originalHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.utils;
|
||||
|
||||
|
||||
/**
|
||||
* Used to process Hash text for passwords
|
||||
*/
|
||||
public interface HashProcessor {
|
||||
|
||||
/**
|
||||
* produce hash text from plain text
|
||||
* @param plainText Plain text input
|
||||
* @return the Hash value of the input plain text
|
||||
* @throws Exception
|
||||
*/
|
||||
String hash(String plainText) throws Exception;
|
||||
|
||||
/**
|
||||
* compare the plain char array against the hash value
|
||||
* @param inputValue value of the plain text
|
||||
* @param storedHash the existing hash value
|
||||
* @return true if the char array matches the hash value,
|
||||
* otherwise false.
|
||||
*/
|
||||
boolean compare(char[] inputValue, String storedHash);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.utils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A hash processor that just does plain text comparison
|
||||
*/
|
||||
public class NoHashProcessor implements HashProcessor {
|
||||
|
||||
@Override
|
||||
public String hash(String plainText) throws Exception {
|
||||
return plainText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean compare(char[] inputValue, String storedHash) {
|
||||
return Arrays.equals(inputValue, storedHash.toCharArray());
|
||||
}
|
||||
}
|
|
@ -27,6 +27,64 @@ import org.apache.activemq.artemis.logs.ActiveMQUtilBundle;
|
|||
|
||||
public class PasswordMaskingUtil {
|
||||
|
||||
private static final String PLAINTEXT_PROCESSOR = "plaintext";
|
||||
private static final String SECURE_PROCESSOR = "secure";
|
||||
|
||||
private static final Map<String, HashProcessor> processors = new HashMap<>();
|
||||
|
||||
//stored password takes 2 forms, ENC() or plain text
|
||||
public static HashProcessor getHashProcessor(String storedPassword) throws Exception {
|
||||
|
||||
if (!isEncoded(storedPassword)) {
|
||||
return getPlaintextProcessor();
|
||||
}
|
||||
return getSecureProcessor();
|
||||
}
|
||||
|
||||
private static boolean isEncoded(String storedPassword) {
|
||||
if (storedPassword == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (storedPassword.startsWith("ENC(") && storedPassword.endsWith(")")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static HashProcessor getHashProcessor() {
|
||||
HashProcessor processor = null;
|
||||
try {
|
||||
processor = getSecureProcessor();
|
||||
} catch (Exception e) {
|
||||
processor = getPlaintextProcessor();
|
||||
}
|
||||
return processor;
|
||||
}
|
||||
|
||||
public static HashProcessor getPlaintextProcessor() {
|
||||
synchronized (processors) {
|
||||
HashProcessor plain = processors.get(PLAINTEXT_PROCESSOR);
|
||||
if (plain == null) {
|
||||
plain = new NoHashProcessor();
|
||||
processors.put(PLAINTEXT_PROCESSOR, plain);
|
||||
}
|
||||
return plain;
|
||||
}
|
||||
}
|
||||
|
||||
public static HashProcessor getSecureProcessor() throws Exception {
|
||||
synchronized (processors) {
|
||||
HashProcessor processor = processors.get(SECURE_PROCESSOR);
|
||||
if (processor == null) {
|
||||
DefaultSensitiveStringCodec codec = (DefaultSensitiveStringCodec) getCodec("org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;algorithm=one-way");
|
||||
processor = new SecureHashProcessor(codec);
|
||||
processors.put(SECURE_PROCESSOR, processor);
|
||||
}
|
||||
return processor;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Loading the codec class.
|
||||
*
|
||||
|
@ -37,7 +95,7 @@ public class PasswordMaskingUtil {
|
|||
* Where only <full qualified class name> is required. key/value pairs are optional
|
||||
*/
|
||||
public static SensitiveDataCodec<String> getCodec(String codecDesc) throws ActiveMQException {
|
||||
SensitiveDataCodec<String> codecInstance = null;
|
||||
SensitiveDataCodec<String> codecInstance;
|
||||
|
||||
// semi colons
|
||||
String[] parts = codecDesc.split(";");
|
||||
|
@ -70,13 +128,19 @@ public class PasswordMaskingUtil {
|
|||
throw ActiveMQUtilBundle.BUNDLE.invalidProperty(parts[i]);
|
||||
props.put(keyVal[0], keyVal[1]);
|
||||
}
|
||||
codecInstance.init(props);
|
||||
try {
|
||||
codecInstance.init(props);
|
||||
} catch (Exception e) {
|
||||
throw new ActiveMQException("Fail to init codec", e, ActiveMQExceptionType.SECURITY_EXCEPTION);
|
||||
}
|
||||
}
|
||||
|
||||
return codecInstance;
|
||||
}
|
||||
|
||||
public static SensitiveDataCodec<String> getDefaultCodec() {
|
||||
public static DefaultSensitiveStringCodec getDefaultCodec() {
|
||||
return new DefaultSensitiveStringCodec();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.utils;
|
||||
|
||||
/**
|
||||
* Hash function
|
||||
*/
|
||||
public class SecureHashProcessor implements HashProcessor {
|
||||
|
||||
private static final String BEGIN_HASH = "ENC(";
|
||||
private static final String END_HASH = ")";
|
||||
|
||||
private DefaultSensitiveStringCodec codec;
|
||||
|
||||
public SecureHashProcessor(DefaultSensitiveStringCodec codec) {
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String hash(String plainText) throws Exception {
|
||||
return BEGIN_HASH + codec.encode(plainText) + END_HASH;
|
||||
}
|
||||
|
||||
@Override
|
||||
//storedValue must take form of ENC(...)
|
||||
public boolean compare(char[] inputValue, String storedValue) {
|
||||
String storedHash = storedValue.substring(4, storedValue.length() - 2);
|
||||
return codec.verify(inputValue, storedHash);
|
||||
}
|
||||
}
|
|
@ -29,5 +29,7 @@ public interface SensitiveDataCodec<T> {
|
|||
|
||||
T decode(Object mask) throws Exception;
|
||||
|
||||
void init(Map<String, String> params);
|
||||
T encode(Object secret) throws Exception;
|
||||
|
||||
void init(Map<String, String> params) throws Exception;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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.utils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class DefaultSensitiveStringCodecTest {
|
||||
|
||||
@Test
|
||||
public void testDefaultAlgorithm() throws Exception {
|
||||
SensitiveDataCodec<String> codec = PasswordMaskingUtil.getDefaultCodec();
|
||||
assertTrue(codec instanceof DefaultSensitiveStringCodec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnewayAlgorithm() throws Exception {
|
||||
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put(DefaultSensitiveStringCodec.ALGORITHM, DefaultSensitiveStringCodec.ONE_WAY);
|
||||
codec.init(params);
|
||||
|
||||
String plainText = "some_password";
|
||||
String maskedText = codec.encode(plainText);
|
||||
System.out.println("encoded value: " + maskedText);
|
||||
|
||||
//one way can't decode
|
||||
try {
|
||||
codec.decode(maskedText);
|
||||
fail("one way algorithm can't decode");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
|
||||
assertTrue(codec.verify(plainText.toCharArray(), maskedText));
|
||||
|
||||
String otherPassword = "some_other_password";
|
||||
assertFalse(codec.verify(otherPassword.toCharArray(), maskedText));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwowayAlgorithm() throws Exception {
|
||||
DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec();
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put(DefaultSensitiveStringCodec.ALGORITHM, DefaultSensitiveStringCodec.TWO_WAY);
|
||||
codec.init(params);
|
||||
|
||||
String plainText = "some_password";
|
||||
String maskedText = codec.encode(plainText);
|
||||
System.out.println("encoded value: " + maskedText);
|
||||
|
||||
String decoded = codec.decode(maskedText);
|
||||
System.out.println("encoded value: " + maskedText);
|
||||
|
||||
assertEquals("decoded result not match: " + decoded, decoded, plainText);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.utils;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class HashProcessorTest {
|
||||
|
||||
private static final String USER1_PASSWORD = "password";
|
||||
private static final String USER1_HASHED_PASSWORD = "ENC(1024:973A466A489ABFDED3D4B3D181DC77F410F2FC6E87432809A46B72B294147D76:C999ECA8A85387E1FFB14E4FE5CECD17948BA80BA04318A9BE4C3E34B7FE2925F43AB6BC9DFE0D9855DA67439AEEB9850351BC4D5D3AEC6A6903C42B8EB4ED1E)";
|
||||
|
||||
private static final String USER2_PASSWORD = "manager";
|
||||
private static final String USER2_HASHED_PASSWORD = "ENC(1024:48018CDB1B5925DA2CC51DBD6F7E8C5FF156C22C03C6C69720C56F8BE76A1D48:0A0F68C2C01F46D347C6C51D641291A4608EDA50A873ED122909D9134B7A757C14176F0C033F0BD3CE35B3C373D5B652650CDE5FFBBB0F286D4495CEFEEDB166)";
|
||||
|
||||
private static final String USER3_PASSWORD = "artemis000";
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: testing password {0}")
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][] {
|
||||
{USER1_PASSWORD, USER1_HASHED_PASSWORD, true},
|
||||
{USER2_PASSWORD, USER2_HASHED_PASSWORD, true},
|
||||
{USER3_PASSWORD, USER3_PASSWORD, true},
|
||||
{USER1_PASSWORD, USER2_PASSWORD, false},
|
||||
{USER3_PASSWORD, USER2_HASHED_PASSWORD, false}
|
||||
});
|
||||
}
|
||||
|
||||
private String password;
|
||||
private String storedPassword;
|
||||
private boolean match;
|
||||
|
||||
public HashProcessorTest(String password, String storedPassword, boolean match) {
|
||||
this.password = password;
|
||||
this.storedPassword = storedPassword;
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordVerification() throws Exception {
|
||||
HashProcessor processor = PasswordMaskingUtil.getHashProcessor(storedPassword);
|
||||
boolean result = processor.compare(password.toCharArray(), storedPassword);
|
||||
assertEquals(match, result);
|
||||
}
|
||||
}
|
|
@ -89,6 +89,8 @@
|
|||
<include>commons-beanutils:commons-beanutils</include>
|
||||
<include>commons-logging:commons-logging</include>
|
||||
<include>commons-collections:commons-collections</include>
|
||||
<include>org.apache.commons:commons-configuration2</include>
|
||||
<include>org.apache.commons:commons-lang3</include>
|
||||
<include>org.fusesource.hawtbuf:hawtbuf</include>
|
||||
<include>org.jgroups:jgroups</include>
|
||||
<include>org.apache.geronimo.specs:geronimo-json_1.0_spec</include>
|
||||
|
|
|
@ -32,14 +32,16 @@ import java.util.Map;
|
|||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.activemq.artemis.utils.HashProcessor;
|
||||
import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
public class PropertiesLoginModule extends PropertiesLoader implements LoginModule {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PropertiesLoginModule.class);
|
||||
|
||||
private static final String USER_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.user";
|
||||
private static final String ROLE_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.role";
|
||||
public static final String USER_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.user";
|
||||
public static final String ROLE_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.role";
|
||||
|
||||
private Subject subject;
|
||||
private CallbackHandler callbackHandler;
|
||||
|
@ -49,6 +51,7 @@ public class PropertiesLoginModule extends PropertiesLoader implements LoginModu
|
|||
private String user;
|
||||
private final Set<Principal> principals = new HashSet<>();
|
||||
private boolean loginSucceeded;
|
||||
private HashProcessor hashProcessor;
|
||||
|
||||
@Override
|
||||
public void initialize(Subject subject,
|
||||
|
@ -90,10 +93,21 @@ public class PropertiesLoginModule extends PropertiesLoader implements LoginModu
|
|||
if (password == null) {
|
||||
throw new FailedLoginException("User does exist");
|
||||
}
|
||||
if (!password.equals(new String(tmpPassword))) {
|
||||
throw new FailedLoginException("Password does not match");
|
||||
|
||||
//password is hashed
|
||||
try {
|
||||
hashProcessor = PasswordMaskingUtil.getHashProcessor(password);
|
||||
|
||||
if (!hashProcessor.compare(tmpPassword, password)) {
|
||||
throw new FailedLoginException("Password does not match");
|
||||
}
|
||||
loginSucceeded = true;
|
||||
} catch (Exception e) {
|
||||
if (debug) {
|
||||
logger.debug("Exception getting a hash processor", e);
|
||||
}
|
||||
throw new FailedLoginException("Failed to get hash processor");
|
||||
}
|
||||
loginSucceeded = true;
|
||||
|
||||
if (debug) {
|
||||
logger.debug("login " + user);
|
||||
|
|
|
@ -396,29 +396,14 @@ will have to be in masked form.
|
|||
### Masking passwords in artemis-users.properties
|
||||
|
||||
Apache ActiveMQ Artemis's built-in security manager uses plain properties files
|
||||
where the user passwords are specified in plaintext forms by default. To
|
||||
mask those parameters the following two properties need to be set
|
||||
in the 'bootstrap.xml' file.
|
||||
where the user passwords are specified in hash forms by default.
|
||||
|
||||
`mask-password` -- If set to "true" all the passwords are masked.
|
||||
Default is false.
|
||||
Please use Artemis CLI command to add a password. For example
|
||||
|
||||
`password-codec` -- Class name and its parameters for the Decoder used
|
||||
to decode the masked password. Ignored if `mask-password` is false. The
|
||||
format of this property is a full qualified class name optionally
|
||||
followed by key/value pairs. It is the same format as that for JMS
|
||||
Bridges. Example:
|
||||
|
||||
```xml
|
||||
<mask-password>true</mask-password>
|
||||
<password-codec>org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;key=hello world</password-codec>
|
||||
```sh
|
||||
./artemis user add --username guest --password guest --role admin
|
||||
```
|
||||
|
||||
When so configured, the Apache ActiveMQ Artemis security manager will initialize a
|
||||
DefaultSensitiveStringCodec with the parameters "key"-\>"hello world",
|
||||
then use it to decode all the masked passwords in this configuration
|
||||
file.
|
||||
|
||||
### Choosing a decoder for password masking
|
||||
|
||||
As described in the previous sections, all password masking requires a
|
||||
|
|
Loading…
Reference in New Issue