http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/bb84f679/docs/user-manual/en/masking-passwords.md ---------------------------------------------------------------------- diff --git a/docs/user-manual/en/masking-passwords.md b/docs/user-manual/en/masking-passwords.md index 4f43524..32329ba 100644 --- a/docs/user-manual/en/masking-passwords.md +++ b/docs/user-manual/en/masking-passwords.md @@ -18,12 +18,33 @@ Apache ActiveMQ Artemis provides a default password encoder and decoder. Optiona users can use or implement their own encoder and decoder for masking the passwords. +In general, a masked password can be identified using one of two ways. The first one +is the ENC() syntax, i.e. any string value wrapped in ENC() is to be treated as +a masked password. For example + +`ENC(xyz)` + +The above indicates that the password is masked and the masked value is `xyz`. + +The ENC() syntax is the preferred way to indicating a masked password and is +universally supported in every password configuration in Artemis. + +The other way is to use a `mask-password` attribute to tell that a password +in a configuration file should be treated as 'masked'. For example: + +``` +<mask-password>true</mask-password> +<cluster-password>xyz</cluster-password> +``` +This method is now deprecated and exists only to maintain backward-compatibility. +Newer configurations may not support it. + ### Password Masking in Server Configuration File #### General Masking Configuration -The server configuration file (i.e. broker.xml )has a property that defines the -default masking behaviors over the entire file scope. +Besides supporting the ENC() syntax, the server configuration file (i.e. broker.xml) +has a property that defines the default masking behaviors over the entire file scope. `mask-password`: this boolean type property indicates if a password should be masked or not. Set it to "true" if you want your passwords @@ -38,6 +59,8 @@ will be used. ##### cluster-password +If it is specified in ENC() syntax it will be treated as masked, or + If `mask-password` is `true` the `cluster-password` will be treated as masked. ##### Passwords in connectors and acceptors @@ -55,16 +78,16 @@ and `activemq.passwordcodec` respectively. The Netty and InVM implementations will use these as needed and any other implementations will have access to these to use if they so wish. +The preferred way, however, is to use the ENC() syntax. + ##### Passwords in bridge configurations Core Bridges are configured in the server configuration file and so the masking of its `password` properties follows the same rules as that of -`cluster-password`. - -#### Examples +`cluster-password`. It supports ENC() syntax. -The following table summarizes the relations among the above-mentioned -properties +For using 'mask-password' property, the following table summarizes the +relations among the above-mentioned properties mask-password | cluster-password | acceptor/connector passwords | bridge password :------------- | :---------------- | :--------------------------- | :--------------- @@ -72,7 +95,9 @@ properties false | plain text | plain text | plain text true | masked | masked | masked -Examples +It is recommended that you use the ENC() syntax for new applications/deployments. + +#### Examples Note: In the following examples if related attributed or properties are absent, it means they are not specified in the configure file. @@ -88,6 +113,14 @@ This indicates the cluster password is a plain text value ("bbc"). example 2 ```xml +<cluster-password>ENC(xyz)</cluster-password> +``` + +This indicates the cluster password is a masked value ("xyz"). + +example 3 + +```xml <mask-password>true</mask-password> <cluster-password>80cf731af62c290</cluster-password> ``` @@ -97,21 +130,64 @@ use its built-in decoder to decode it. All other passwords in the configuration file, Connectors, Acceptors and Bridges, will also use masked passwords. +#### Passwords in bootstrap.xml + +The broker embeds a web-server for hosting some web applications such as a +management console. It is configured in bootstrap.xml as a web +component. The web server can be secured using https protocol, and it can be +configured with a keystore password and/or truststore password which by +default are specified in plain text forms. + +To mask these passwords you need to use ENC() syntax. The `mask-password` is +not supported here. + +You can also set the `passwordCodec` attribute if you want to use a password codec +other than the default one. For example + +```xml + <web bind="https://localhost:8443" path="web" + keyStorePassword="ENC(-5a2376c61c668aaf)" + trustStorePassword="ENC(3d617352d12839eb71208edf41d66b34)"> + <app url="activemq-branding" war="activemq-branding.war"/> + </web> +``` + ### Masking passwords in ActiveMQ Artemis JCA ResourceAdapter and MDB activation configurations Both ra.xml and MDB activation configuration have a `password` property -that can be masked. They are controlled by the following two optional -Resource Adapter properties in ra.xml: +that can be masked preferably using ENC() syntax. + +Alternatively it can use a optional attribute in ra.xml to indicate that a password +is masked: `UseMaskedPassword` -- If setting to "true" the passwords are masked. Default is false. +There is another property in ra.xml that can specify a codec: + `PasswordCodec` -- Class name and its parameters for the Decoder used to decode the masked password. Ignored if UseMaskedPassword 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: +Example 1 Using the ENC() syntax: + +```xml +<config-property> + <config-property-name>password</config-property-name> + <config-property-type>String</config-property-type> + <config-property-value>ENC(xyz)</config-property-value> +</config-property> +<config-property> + <config-property-name>PasswordCodec</config-property-name> + <config-property-type>java.lang.String</config-property-type> + <config-property-value>com.foo.ADecoder;key=helloworld</config-property-value> +</config-property> +``` + +Example 2 Using the "UseMaskedPassword" property: + ```xml <config-property> <config-property-name>UseMaskedPassword</config-property-name> @@ -119,6 +195,11 @@ Bridges. Example: <config-property-value>true</config-property-value> </config-property> <config-property> + <config-property-name>password</config-property-name> + <config-property-type>String</config-property-type> + <config-property-value>xyz</config-property-value> +</config-property> +<config-property> <config-property-name>PasswordCodec</config-property-name> <config-property-type>java.lang.String</config-property-type> <config-property-value>com.foo.ADecoder;key=helloworld</config-property-value> @@ -150,6 +231,43 @@ Passwords in `artemis-users.properties` are automatically detected as hashed or by looking for the syntax `ENC(<hash>)`. The `mask-password` parameter does not need to be `true` to use hashed passwords here. +### Masking password in JAAS login config file (login.config) + +Artemis supports LDAP login modules to be configured in JAAS configuration +file (default name is `login.config`). When connecting to a LDAP server usually +you need to supply a connection password in the config file. By default this +password is in plain text form. + +To mask it you need to configure the passwords in your login module +using ENC() syntax. To specify a codec using the following property: + +`passwordCodec` - the password codec class name. (the default codec +will be used if it is absent) + +For example: + +``` +LDAPLoginExternalPasswordCodec { + org.apache.activemq.artemis.spi.core.security.jaas.LDAPLoginModule required + debug=true + initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory + connectionURL="ldap://localhost:1024" + connectionUsername="uid=admin,ou=system" + connectionPassword="ENC(-170b9ef34d79ed12)" + passwordCodec="org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec;key=helloworld" + connectionProtocol=s + authentication=simple + userBase="ou=system" + userSearchMatching="(uid={0})" + userSearchSubtree=false + roleBase="ou=system" + roleName=dummyRoleName + roleSearchMatching="(uid={1})" + roleSearchSubtree=false + ; +}; +``` + ### Choosing a decoder for password masking As described in the previous sections, all password masking requires a @@ -206,8 +324,7 @@ pairs when configuring. For instance if your decoder needs say a Then configure your cluster-password like this: ```xml - <mask-password>true</mask-password> - <cluster-password>masked_password</cluster-password> + <cluster-password>ENC(masked_password)</cluster-password> ``` When Apache ActiveMQ Artemis reads the cluster-password it will initialize the
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/bb84f679/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ResourceAdapterTest.java ---------------------------------------------------------------------- diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ResourceAdapterTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ResourceAdapterTest.java index c7a1474..811cc29 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ResourceAdapterTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/ra/ResourceAdapterTest.java @@ -41,6 +41,7 @@ import org.apache.activemq.artemis.service.extensions.xa.recovery.XARecoveryConf import org.apache.activemq.artemis.tests.unit.ra.BootstrapContext; import org.apache.activemq.artemis.tests.unit.ra.MessageEndpointFactory; import org.apache.activemq.artemis.utils.DefaultSensitiveStringCodec; +import org.apache.activemq.artemis.utils.PasswordMaskingUtil; import org.junit.Test; public class ResourceAdapterTest extends ActiveMQRATestBase { @@ -565,7 +566,7 @@ public class ResourceAdapterTest extends ActiveMQRATestBase { ActiveMQRATestBase.MyBootstrapContext ctx = new ActiveMQRATestBase.MyBootstrapContext(); DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec(); - String mask = (String) codec.encode("helloworld"); + String mask = codec.encode("helloworld"); qResourceAdapter.setUseMaskedPassword(true); qResourceAdapter.setPassword(mask); @@ -595,6 +596,41 @@ public class ResourceAdapterTest extends ActiveMQRATestBase { } @Test + public void testMaskPasswordENC() throws Exception { + ActiveMQResourceAdapter qResourceAdapter = new ActiveMQResourceAdapter(); + qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY); + ActiveMQRATestBase.MyBootstrapContext ctx = new ActiveMQRATestBase.MyBootstrapContext(); + + DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec(); + String mask = codec.encode("helloworld"); + + qResourceAdapter.setPassword(PasswordMaskingUtil.wrap(mask)); + + qResourceAdapter.start(ctx); + + assertEquals("helloworld", qResourceAdapter.getPassword()); + + ActiveMQActivationSpec spec = new ActiveMQActivationSpec(); + spec.setResourceAdapter(qResourceAdapter); + spec.setUseJNDI(false); + spec.setDestinationType("javax.jms.Queue"); + spec.setDestination(MDBQUEUE); + + mask = codec.encode("mdbpassword"); + spec.setPassword(PasswordMaskingUtil.wrap(mask)); + qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY); + CountDownLatch latch = new CountDownLatch(1); + DummyMessageEndpoint endpoint = new DummyMessageEndpoint(latch); + DummyMessageEndpointFactory endpointFactory = new DummyMessageEndpointFactory(endpoint, false); + qResourceAdapter.endpointActivation(endpointFactory, spec); + + assertEquals("mdbpassword", spec.getPassword()); + + qResourceAdapter.stop(); + assertTrue(endpoint.released); + } + + @Test public void testMaskPassword2() throws Exception { ActiveMQResourceAdapter qResourceAdapter = new ActiveMQResourceAdapter(); qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY); @@ -609,7 +645,7 @@ public class ResourceAdapterTest extends ActiveMQRATestBase { prop.put("key", "anotherkey"); codec.init(prop); - String mask = (String) codec.encode("helloworld"); + String mask = codec.encode("helloworld"); qResourceAdapter.setPassword(mask); @@ -623,7 +659,7 @@ public class ResourceAdapterTest extends ActiveMQRATestBase { spec.setDestinationType("javax.jms.Queue"); spec.setDestination(MDBQUEUE); - mask = (String) codec.encode("mdbpassword"); + mask = codec.encode("mdbpassword"); spec.setPassword(mask); qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY); CountDownLatch latch = new CountDownLatch(1); @@ -638,6 +674,48 @@ public class ResourceAdapterTest extends ActiveMQRATestBase { } @Test + public void testMaskPassword2ENC() throws Exception { + ActiveMQResourceAdapter qResourceAdapter = new ActiveMQResourceAdapter(); + qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY); + ActiveMQRATestBase.MyBootstrapContext ctx = new ActiveMQRATestBase.MyBootstrapContext(); + + qResourceAdapter.setPasswordCodec(DefaultSensitiveStringCodec.class.getName() + ";key=anotherkey"); + + DefaultSensitiveStringCodec codec = new DefaultSensitiveStringCodec(); + Map<String, String> prop = new HashMap<>(); + + prop.put("key", "anotherkey"); + codec.init(prop); + + String mask = codec.encode("helloworld"); + + qResourceAdapter.setPassword(PasswordMaskingUtil.wrap(mask)); + + qResourceAdapter.start(ctx); + + assertEquals("helloworld", qResourceAdapter.getPassword()); + + ActiveMQActivationSpec spec = new ActiveMQActivationSpec(); + spec.setResourceAdapter(qResourceAdapter); + spec.setUseJNDI(false); + spec.setDestinationType("javax.jms.Queue"); + spec.setDestination(MDBQUEUE); + + mask = codec.encode("mdbpassword"); + spec.setPassword(PasswordMaskingUtil.wrap(mask)); + qResourceAdapter.setConnectorClassName(INVM_CONNECTOR_FACTORY); + CountDownLatch latch = new CountDownLatch(1); + DummyMessageEndpoint endpoint = new DummyMessageEndpoint(latch); + DummyMessageEndpointFactory endpointFactory = new DummyMessageEndpointFactory(endpoint, false); + qResourceAdapter.endpointActivation(endpointFactory, spec); + + assertEquals("mdbpassword", spec.getPassword()); + + qResourceAdapter.stop(); + assertTrue(endpoint.released); + } + + @Test public void testConnectionParameterStringParsing() throws Exception { ActiveMQResourceAdapter resourceAdapter = new ActiveMQResourceAdapter(); resourceAdapter.setConnectionParameters("enabledProtocols=TLS1\\,TLS1.2;sslEnabled=true"); http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/bb84f679/tests/timing-tests/src/test/java/org/apache/activemq/artemis/tests/timing/jms/bridge/impl/JMSBridgeImplTest.java ---------------------------------------------------------------------- diff --git a/tests/timing-tests/src/test/java/org/apache/activemq/artemis/tests/timing/jms/bridge/impl/JMSBridgeImplTest.java b/tests/timing-tests/src/test/java/org/apache/activemq/artemis/tests/timing/jms/bridge/impl/JMSBridgeImplTest.java index 9007bb4..f6a08a0 100644 --- a/tests/timing-tests/src/test/java/org/apache/activemq/artemis/tests/timing/jms/bridge/impl/JMSBridgeImplTest.java +++ b/tests/timing-tests/src/test/java/org/apache/activemq/artemis/tests/timing/jms/bridge/impl/JMSBridgeImplTest.java @@ -288,6 +288,45 @@ public class JMSBridgeImplTest extends ActiveMQTestBase { * expires even if the maxBatchSize is not reached */ @Test + public void testBridgeWithMaskPasswords() throws Exception { + + ConnectionFactoryFactory sourceCFF = JMSBridgeImplTest.newConnectionFactoryFactory(JMSBridgeImplTest.createConnectionFactory()); + ConnectionFactoryFactory targetCFF = JMSBridgeImplTest.newConnectionFactoryFactory(JMSBridgeImplTest.createConnectionFactory()); + DestinationFactory sourceDF = JMSBridgeImplTest.newDestinationFactory(ActiveMQJMSClient.createQueue(JMSBridgeImplTest.SOURCE)); + DestinationFactory targetDF = JMSBridgeImplTest.newDestinationFactory(ActiveMQJMSClient.createQueue(JMSBridgeImplTest.TARGET)); + TransactionManager tm = JMSBridgeImplTest.newTransactionManager(); + + JMSBridgeImpl bridge = new JMSBridgeImpl(); + Assert.assertNotNull(bridge); + + bridge.setSourceConnectionFactoryFactory(sourceCFF); + bridge.setSourceDestinationFactory(sourceDF); + bridge.setTargetConnectionFactoryFactory(targetCFF); + bridge.setTargetDestinationFactory(targetDF); + bridge.setFailureRetryInterval(10); + bridge.setMaxRetries(1); + bridge.setMaxBatchSize(1); + bridge.setMaxBatchTime(-1); + bridge.setTransactionManager(tm); + bridge.setQualityOfServiceMode(QualityOfServiceMode.AT_MOST_ONCE); + + bridge.setSourceUsername("sourceuser"); + bridge.setSourcePassword("ENC(5493dd76567ee5ec269d11823973462f)"); + bridge.setTargetUsername("targetuser"); + bridge.setTargetPassword("ENC(56a0db3b71043054269d11823973462f)"); + + Assert.assertFalse(bridge.isStarted()); + bridge.start(); + Assert.assertTrue(bridge.isStarted()); + + assertEquals("sourcepassword", bridge.getSourcePassword()); + assertEquals("targetpassword", bridge.getTargetPassword()); + + bridge.stop(); + Assert.assertFalse(bridge.isStarted()); + } + + @Test public void testSendMessagesWhenMaxBatchTimeExpires() throws Exception { int maxBatchSize = 2; long maxBatchTime = 500;