You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by ta...@apache.org on 2018/03/22 14:12:05 UTC

[1/2] activemq-artemis git commit: [ARTEMIS-1758] support SASL EXTERNAL with TextCertLoginModule - rework proton handler to use saslListener

Repository: activemq-artemis
Updated Branches:
  refs/heads/master 92a73e2cb -> 2f9d37393


[ARTEMIS-1758] support SASL EXTERNAL with TextCertLoginModule
 - rework proton handler to use saslListener


Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/72ec6c8e
Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/72ec6c8e
Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/72ec6c8e

Branch: refs/heads/master
Commit: 72ec6c8e0b065acc07cc520172fd158ac2fabe3a
Parents: 92a73e2
Author: gtully <ga...@gmail.com>
Authored: Tue Mar 13 17:21:06 2018 +0000
Committer: Timothy Bish <ta...@gmail.com>
Committed: Thu Mar 22 10:09:58 2018 -0400

----------------------------------------------------------------------
 .../amqp/broker/AMQPConnectionCallback.java     |  16 ++
 .../amqp/proton/AMQPConnectionContext.java      |   1 -
 .../amqp/proton/handler/ProtonHandler.java      | 213 +++++++++--------
 .../protocol/amqp/sasl/ExternalServerSASL.java  |  62 +++++
 .../protocol/amqp/sasl/GSSAPISASLResult.java    |  51 ----
 .../protocol/amqp/sasl/GSSAPIServerSASL.java    |   4 +-
 .../protocol/amqp/sasl/PrincipalSASLResult.java |  51 ++++
 docs/user-manual/en/security.md                 |   5 +
 .../integration/amqp/JMSSaslExternalTest.java   | 235 +++++++++++++++++++
 .../integration/amqp/JMSSaslGssapiTest.java     |  11 +-
 .../src/test/resources/cert-roles.properties    |   1 +
 .../src/test/resources/cert-users.properties    |   1 +
 12 files changed, 492 insertions(+), 159 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java
index 17cae8e..05b4f4f 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java
@@ -17,6 +17,7 @@
 package org.apache.activemq.artemis.protocol.amqp.broker;
 
 import java.net.URI;
+import java.security.Principal;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -28,6 +29,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
 import org.apache.activemq.artemis.api.core.ActiveMQException;
 import org.apache.activemq.artemis.core.buffers.impl.ChannelBufferWrapper;
 import org.apache.activemq.artemis.core.client.impl.TopologyMemberImpl;
+import org.apache.activemq.artemis.core.remoting.CertificateUtil;
 import org.apache.activemq.artemis.core.remoting.CloseListener;
 import org.apache.activemq.artemis.core.remoting.FailureListener;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
@@ -42,6 +44,7 @@ import org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport;
 import org.apache.activemq.artemis.protocol.amqp.proton.handler.ExtCapability;
 import org.apache.activemq.artemis.protocol.amqp.proton.transaction.ProtonTransactionImpl;
 import org.apache.activemq.artemis.protocol.amqp.sasl.AnonymousServerSASL;
+import org.apache.activemq.artemis.protocol.amqp.sasl.ExternalServerSASL;
 import org.apache.activemq.artemis.protocol.amqp.sasl.GSSAPIServerSASL;
 import org.apache.activemq.artemis.protocol.amqp.sasl.PlainSASL;
 import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
@@ -113,7 +116,20 @@ public class AMQPConnectionCallback implements FailureListener, CloseListener {
                result = gssapiServerSASL;
                break;
 
+            case ExternalServerSASL.NAME:
+               // validate ssl cert present
+               Principal principal = CertificateUtil.getPeerPrincipalFromConnection(protonConnectionDelegate);
+               if (principal != null) {
+                  ExternalServerSASL externalServerSASL = new ExternalServerSASL();
+                  externalServerSASL.setPrincipal(principal);
+                  result = externalServerSASL;
+               } else {
+                  logger.debug("SASL EXTERNAL mechanism requires a TLS peer principal");
+               }
+               break;
+
             default:
+               logger.debug("Mo matching mechanism found for: " + mechanism);
                break;
          }
       }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
index 5d376f1..efb438d 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
@@ -332,7 +332,6 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
    @Override
    public void onAuthSuccess(final ProtonHandler protonHandler, final Connection connection) {
       connection.open();
-      flush();
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
index 58b4988..5bee66e 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
@@ -32,7 +32,6 @@ import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASL;
 import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
 import org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASL;
 import org.apache.activemq.artemis.spi.core.remoting.ReadyListener;
-import org.apache.activemq.artemis.utils.ByteUtil;
 import org.apache.qpid.proton.Proton;
 import org.apache.qpid.proton.amqp.Symbol;
 import org.apache.qpid.proton.amqp.transport.AmqpError;
@@ -42,6 +41,7 @@ import org.apache.qpid.proton.engine.Connection;
 import org.apache.qpid.proton.engine.EndpointState;
 import org.apache.qpid.proton.engine.Event;
 import org.apache.qpid.proton.engine.Sasl;
+import org.apache.qpid.proton.engine.SaslListener;
 import org.apache.qpid.proton.engine.Transport;
 import org.apache.qpid.proton.engine.impl.TransportInternal;
 import org.jboss.logging.Logger;
@@ -49,7 +49,7 @@ import org.jboss.logging.Logger;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.PooledByteBufAllocator;
 
-public class ProtonHandler extends ProtonInitializable {
+public class ProtonHandler extends ProtonInitializable implements SaslListener {
 
    private static final Logger log = Logger.getLogger(ProtonHandler.class);
 
@@ -65,8 +65,6 @@ public class ProtonHandler extends ProtonInitializable {
 
    private List<EventHandler> handlers = new ArrayList<>();
 
-   private Sasl sasl;
-
    private ServerSASL chosenMechanism;
    private ClientSASL clientSASLMechanism;
 
@@ -174,9 +172,10 @@ public class ProtonHandler extends ProtonInitializable {
    }
 
    public void createServerSASL(String[] mechanisms) {
-      this.sasl = transport.sasl();
-      this.sasl.server();
+      Sasl sasl = transport.sasl();
+      sasl.server();
       sasl.setMechanisms(mechanisms);
+      sasl.setListener(this);
    }
 
    public void flushBytes() {
@@ -281,7 +280,6 @@ public class ProtonHandler extends ProtonInitializable {
       lock.lock();
       try {
          transport.process();
-         checkSASL();
       } finally {
          lock.unlock();
       }
@@ -303,113 +301,127 @@ public class ProtonHandler extends ProtonInitializable {
       flush();
    }
 
-   protected void checkSASL() {
-      if (isServer) {
-         if (sasl != null && sasl.getRemoteMechanisms().length > 0) {
+   // server side SASL Listener
+   @Override
+   public void onSaslInit(Sasl sasl, Transport transport) {
+      log.debug("onSaslInit: " + sasl);
+      dispatchRemoteMechanismChosen(sasl.getRemoteMechanisms()[0]);
 
-            if (chosenMechanism == null) {
-               if (log.isTraceEnabled()) {
-                  log.trace("SASL chosenMechanism: " + sasl.getRemoteMechanisms()[0]);
-               }
-               dispatchRemoteMechanismChosen(sasl.getRemoteMechanisms()[0]);
+      if (chosenMechanism != null) {
+
+         processPending(sasl);
+
+      } else {
+         // no auth available, system error
+         saslComplete(sasl, Sasl.SaslOutcome.PN_SASL_SYS);
+      }
+   }
+
+   private void processPending(Sasl sasl) {
+      byte[] dataSASL = new byte[sasl.pending()];
+
+      int received = sasl.recv(dataSASL, 0, dataSASL.length);
+      if (log.isTraceEnabled()) {
+         log.trace("Working on sasl, length:" + received);
+      }
+
+      byte[] response = chosenMechanism.processSASL(received != -1 ? dataSASL : null);
+      if (response != null) {
+         sasl.send(response, 0, response.length);
+      }
+
+      saslResult = chosenMechanism.result();
+      if (saslResult != null) {
+         if (saslResult.isSuccess()) {
+            saslComplete(sasl, Sasl.SaslOutcome.PN_SASL_OK);
+         } else {
+            saslComplete(sasl, Sasl.SaslOutcome.PN_SASL_AUTH);
+         }
+      }
+   }
+
+   @Override
+   public void onSaslResponse(Sasl sasl, Transport transport) {
+      log.debug("onSaslResponse: " + sasl);
+      processPending(sasl);
+   }
+
+   // client SASL Listener
+   @Override
+   public void onSaslMechanisms(Sasl sasl, Transport transport) {
+
+      dispatchMechanismsOffered(sasl.getRemoteMechanisms());
+
+      if (clientSASLMechanism == null) {
+         log.infof("Outbound connection failed - unknown mechanism, offered mechanisms: %s",
+                   Arrays.asList(sasl.getRemoteMechanisms()));
+         dispatchAuthFailed();
+      } else {
+         sasl.setMechanisms(clientSASLMechanism.getName());
+         byte[] initialResponse = clientSASLMechanism.getInitialResponse();
+         if (initialResponse != null) {
+            sasl.send(initialResponse, 0, initialResponse.length);
+         }
+      }
+   }
+
+   @Override
+   public void onSaslChallenge(Sasl sasl, Transport transport) {
+      int challengeSize = sasl.pending();
+      byte[] challenge = new byte[challengeSize];
+      sasl.recv(challenge, 0, challengeSize);
+      byte[] response = clientSASLMechanism.getResponse(challenge);
+      sasl.send(response, 0, response.length);
+   }
+
+   @Override
+   public void onSaslOutcome(Sasl sasl, Transport transport) {
+      log.debug("onSaslOutcome: " + sasl);
+      switch (sasl.getState()) {
+         case PN_SASL_FAIL:
+            log.info("Outbound connection failed, authentication failure");
+            dispatchAuthFailed();
+            break;
+         case PN_SASL_PASS:
+            log.debug("Outbound connection succeeded");
+
+            if (sasl.pending() != 0) {
+               byte[] additionalData = new byte[sasl.pending()];
+               sasl.recv(additionalData, 0, additionalData.length);
+               clientSASLMechanism.getResponse(additionalData);
             }
-            if (chosenMechanism != null) {
 
-               byte[] dataSASL = new byte[sasl.pending()];
-               int received = sasl.recv(dataSASL, 0, dataSASL.length);
-               if (log.isTraceEnabled()) {
-                  log.trace("Working on sasl ::" + (received > 0 ? ByteUtil.bytesToHex(dataSASL, 2) : "recv:" + received));
+            saslResult = new SASLResult() {
+               @Override
+               public String getUser() {
+                  return null;
                }
 
-               byte[] response = null;
-               if (received != -1) {
-                  response = chosenMechanism.processSASL(dataSASL);
-               }
-               if (response != null) {
-                  sasl.send(response, 0, response.length);
+               @Override
+               public Subject getSubject() {
+                  return null;
                }
-               saslResult = chosenMechanism.result();
 
-               if (saslResult != null) {
-                  if (saslResult.isSuccess()) {
-                     saslComplete(Sasl.SaslOutcome.PN_SASL_OK);
-                  } else {
-                     saslComplete(Sasl.SaslOutcome.PN_SASL_AUTH);
-                  }
+               @Override
+               public boolean isSuccess() {
+                  return true;
                }
-            } else {
-               // no auth available, system error
-               saslComplete(Sasl.SaslOutcome.PN_SASL_SYS);
-            }
-         }
-      } else {
-         if (sasl != null) {
-            switch (sasl.getState()) {
-               case PN_SASL_IDLE:
-                  if (sasl.getRemoteMechanisms().length != 0) {
-                     dispatchMechanismsOffered(sasl.getRemoteMechanisms());
-
-                     if (clientSASLMechanism == null) {
-                        log.infof("Outbound connection failed - unknown mechanism, offered mechanisms: %s",
-                                  Arrays.asList(sasl.getRemoteMechanisms()));
-                        sasl = null;
-                        dispatchAuthFailed();
-                     } else {
-                        sasl.setMechanisms(clientSASLMechanism.getName());
-                        byte[] initialResponse = clientSASLMechanism.getInitialResponse();
-                        if (initialResponse != null) {
-                           sasl.send(initialResponse, 0, initialResponse.length);
-                        }
-                     }
-                  }
-                  break;
-               case PN_SASL_STEP:
-                  int challengeSize = sasl.pending();
-                  byte[] challenge = new byte[challengeSize];
-                  sasl.recv(challenge, 0, challengeSize);
-                  byte[] response = clientSASLMechanism.getResponse(challenge);
-                  sasl.send(response, 0, response.length);
-                  break;
-               case PN_SASL_FAIL:
-                  log.info("Outbound connection failed, authentication failure");
-                  sasl = null;
-                  dispatchAuthFailed();
-                  break;
-               case PN_SASL_PASS:
-                  log.debug("Outbound connection succeeded");
-                  saslResult = new SASLResult() {
-                     @Override
-                     public String getUser() {
-                        return null;
-                     }
+            };
 
-                     @Override
-                     public Subject getSubject() {
-                        return null;
-                     }
+            dispatchAuthSuccess();
+            break;
 
-                     @Override
-                     public boolean isSuccess() {
-                        return true;
-                     }
-                  };
-                  sasl = null;
-
-                  dispatchAuthSuccess();
-                  break;
-               case PN_SASL_CONF:
-                  // do nothing
-                  break;
-            }
-         }
+         default:
+            break;
       }
    }
 
-   private void saslComplete(Sasl.SaslOutcome saslOutcome) {
+   private void saslComplete(Sasl sasl, Sasl.SaslOutcome saslOutcome) {
+      log.debug("saslComplete: " + sasl);
       sasl.done(saslOutcome);
-      sasl = null;
       if (chosenMechanism != null) {
          chosenMechanism.done();
+         chosenMechanism = null;
       }
    }
 
@@ -501,7 +513,8 @@ public class ProtonHandler extends ProtonInitializable {
    }
 
    public void createClientSASL() {
-      this.sasl = transport.sasl();
-      this.sasl.client();
+      Sasl sasl = transport.sasl();
+      sasl.client();
+      sasl.setListener(this);
    }
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASL.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASL.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASL.java
new file mode 100644
index 0000000..3c1a012
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASL.java
@@ -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.protocol.amqp.sasl;
+
+import java.security.Principal;
+
+public class ExternalServerSASL implements ServerSASL {
+
+   public static final String NAME = "EXTERNAL";
+   private static final byte[] EMPTY = new byte[0];
+   private Principal principal;
+   private SASLResult result;
+
+   public ExternalServerSASL() {
+   }
+
+   @Override
+   public String getName() {
+      return NAME;
+   }
+
+   @Override
+   public byte[] processSASL(byte[] bytes) {
+      if (bytes != null) {
+         if (bytes.length == 0) {
+            result = new PrincipalSASLResult(true, principal);
+         } else {
+            // we don't accept any client identity
+            result = new PrincipalSASLResult(false, null);
+         }
+      }
+      return EMPTY;
+   }
+
+   @Override
+   public SASLResult result() {
+      return result;
+   }
+
+   @Override
+   public void done() {
+   }
+
+   public void setPrincipal(Principal principal) {
+      this.principal = principal;
+   }
+}
+

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPISASLResult.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPISASLResult.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPISASLResult.java
deleted file mode 100644
index 0b6e378..0000000
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPISASLResult.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.protocol.amqp.sasl;
-
-import javax.security.auth.Subject;
-import java.security.Principal;
-
-public class GSSAPISASLResult implements SASLResult {
-
-   private final boolean success;
-   private final Subject identity = new Subject();
-   private String user;
-
-
-   public GSSAPISASLResult(boolean success, Principal peer) {
-      this.success = success;
-      if (success) {
-         this.identity.getPrivateCredentials().add(peer);
-         this.user = peer.getName();
-      }
-   }
-
-   @Override
-   public String getUser() {
-      return user;
-   }
-
-   @Override
-   public Subject getSubject() {
-      return identity;
-   }
-
-   @Override
-   public boolean isSuccess() {
-      return success;
-   }
-}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java
index e89d548..fc601f0 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java
@@ -76,13 +76,13 @@ public class GSSAPIServerSASL implements ServerSASL {
 
          byte[] challenge = Subject.doAs(jaasId, (PrivilegedExceptionAction<byte[]>) () -> saslServer.evaluateResponse(bytes));
          if (saslServer.isComplete()) {
-            result = new GSSAPISASLResult(true, new KerberosPrincipal(saslServer.getAuthorizationID()));
+            result = new PrincipalSASLResult(true, new KerberosPrincipal(saslServer.getAuthorizationID()));
          }
          return challenge;
 
       } catch (Exception outOfHere) {
          log.info("Error on sasl input: " + outOfHere.toString(), outOfHere);
-         result = new GSSAPISASLResult(false, null);
+         result = new PrincipalSASLResult(false, null);
       }
       return null;
    }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PrincipalSASLResult.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PrincipalSASLResult.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PrincipalSASLResult.java
new file mode 100644
index 0000000..a44b0c8
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PrincipalSASLResult.java
@@ -0,0 +1,51 @@
+/*
+ * 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.protocol.amqp.sasl;
+
+import javax.security.auth.Subject;
+import java.security.Principal;
+
+public class PrincipalSASLResult implements SASLResult {
+
+   private final boolean success;
+   private final Subject identity = new Subject();
+   private String user;
+
+
+   public PrincipalSASLResult(boolean success, Principal peer) {
+      this.success = success;
+      if (success) {
+         this.identity.getPrivateCredentials().add(peer);
+         this.user = peer.getName();
+      }
+   }
+
+   @Override
+   public String getUser() {
+      return user;
+   }
+
+   @Override
+   public Subject getSubject() {
+      return identity;
+   }
+
+   @Override
+   public boolean isSuccess() {
+      return success;
+   }
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/docs/user-manual/en/security.md
----------------------------------------------------------------------
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index 8bbcb38..ac0c76f 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -741,6 +741,11 @@ SASL GSSAPI. However, for clients that don't support SASL (core client), using T
 over an *unsecure* channel.
 
 
+## SASL
+[AMQP](using-AMQP.md) supports SASL. The following mechanisms are supported; PLAIN, EXTERNAL, ANONYMOUS, GSSAPI.
+The published list can be constrained via the amqp acceptor `saslMechanisms` property. 
+Note: EXTERNAL will only be chosen if a subject is available from the TLS client certificate.
+
 ## Changing the username/password for clustering
 
 In order for cluster connections to work correctly, each node in the

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalTest.java
----------------------------------------------------------------------
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalTest.java
new file mode 100644
index 0000000..b9da886
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.tests.integration.amqp;
+
+import javax.jms.Connection;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import java.lang.management.ManagementFactory;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory;
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector;
+import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.ActiveMQServers;
+import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManagerFactory;
+import org.apache.activemq.artemis.protocol.amqp.client.AMQPClientConnectionFactory;
+import org.apache.activemq.artemis.protocol.amqp.client.ProtonClientConnectionManager;
+import org.apache.activemq.artemis.protocol.amqp.client.ProtonClientProtocolManager;
+import org.apache.activemq.artemis.protocol.amqp.proton.handler.EventHandler;
+import org.apache.activemq.artemis.protocol.amqp.proton.handler.ProtonHandler;
+import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASL;
+import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASLFactory;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
+import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.apache.activemq.artemis.tests.util.Wait;
+import org.apache.activemq.artemis.utils.RandomUtil;
+import org.apache.qpid.jms.JmsConnectionFactory;
+import org.apache.qpid.jms.sasl.ExternalMechanism;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class JMSSaslExternalTest extends ActiveMQTestBase {
+
+   static {
+      String path = System.getProperty("java.security.auth.login.config");
+      if (path == null) {
+         URL resource = JMSSaslExternalTest.class.getClassLoader().getResource("login.config");
+         if (resource != null) {
+            path = resource.getFile();
+            System.setProperty("java.security.auth.login.config", path);
+         }
+      }
+   }
+
+   private ActiveMQServer server;
+   private final boolean debug = false;
+
+   @Before
+   public void setUpDebug() throws Exception {
+
+      if (debug) {
+         for (java.util.logging.Logger logger : new java.util.logging.Logger[] {java.util.logging.Logger.getLogger("javax.security.sasl"), java.util.logging.Logger.getLogger("org.apache.qpid.proton")}) {
+            logger.setLevel(java.util.logging.Level.FINEST);
+            logger.addHandler(new java.util.logging.ConsoleHandler());
+            for (java.util.logging.Handler handler : logger.getHandlers()) {
+               handler.setLevel(java.util.logging.Level.FINEST);
+            }
+         }
+      }
+   }
+
+   @Before
+   public void startServer() throws Exception {
+      ConfigurationImpl configuration = createBasicConfig(0).setJMXManagementEnabled(false);
+      ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("CertLogin");
+      server = addServer(ActiveMQServers.newActiveMQServer(configuration.setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
+
+      Map<String, Object> params = new HashMap<>();
+      params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+      params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "keystore1.jks");
+      params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "changeit");
+      params.put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "truststore.jks");
+      params.put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "changeit");
+      params.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
+
+      Map<String, Object> extraParams = new HashMap<>();
+      extraParams.put("saslMechanisms", "EXTERNAL");
+
+      server.getConfiguration().addAcceptorConfiguration(new TransportConfiguration(NettyAcceptorFactory.class.getCanonicalName(), params, "netty", extraParams));
+
+      // role mapping via CertLogin - TextFileCertificateLoginModule
+      final String roleName = "widgets";
+      Role role = new Role(roleName, true, true, true, true, true, true, true, true, true, true);
+      Set<Role> roles = new HashSet<>();
+      roles.add(role);
+      server.getSecurityRepository().addMatch("TEST", roles);
+
+      server.start();
+   }
+
+   @After
+   public void stopServer() throws Exception {
+      server.stop();
+   }
+
+   @Test(timeout = 600000)
+   public void testConnection() throws Exception {
+
+      final String keystore = this.getClass().getClassLoader().getResource("client_not_revoked.jks").getFile();
+      final String truststore = this.getClass().getClassLoader().getResource("truststore.jks").getFile();
+
+      String connOptions = "?amqp.saslMechanisms=EXTERNAL" + "&" +
+         "transport.trustStoreLocation=" + truststore + "&" +
+         "transport.trustStorePassword=changeit" + "&" +
+         "transport.keyStoreLocation=" + keystore + "&" +
+         "transport.keyStorePassword=changeit" + "&" +
+         "transport.verifyHost=false";
+
+      JmsConnectionFactory factory = new JmsConnectionFactory(new URI("amqps://localhost:" + 61616 + connOptions));
+      Connection connection = factory.createConnection("client", null);
+      connection.start();
+
+      try {
+         Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+         javax.jms.Queue queue = session.createQueue("TEST");
+         MessageConsumer consumer = session.createConsumer(queue);
+         MessageProducer producer = session.createProducer(queue);
+
+         final String text = RandomUtil.randomString();
+         producer.send(session.createTextMessage(text));
+
+         TextMessage m = (TextMessage) consumer.receive(1000);
+         assertNotNull(m);
+         assertEquals(text, m.getText());
+
+      } finally {
+         connection.close();
+      }
+   }
+
+   @Test
+   public void testOutbound() throws Exception {
+
+      final Map<String, Object> config = new LinkedHashMap<>(); config.put(TransportConstants.HOST_PROP_NAME, "localhost");
+      config.put(TransportConstants.PORT_PROP_NAME, String.valueOf(61616));
+      config.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "client_not_revoked.jks");
+      config.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "changeit");
+      config.put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "truststore.jks");
+      config.put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "changeit");
+      config.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
+      config.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+
+
+      final AtomicBoolean connectionOpened = new AtomicBoolean();
+      final AtomicBoolean authFailed = new AtomicBoolean();
+
+      EventHandler eventHandler = new EventHandler() {
+         @Override
+         public void onRemoteOpen(org.apache.qpid.proton.engine.Connection connection) throws Exception {
+            connectionOpened.set(true);
+         }
+
+         @Override
+         public void onAuthFailed(ProtonHandler protonHandler, org.apache.qpid.proton.engine.Connection connection) {
+            authFailed.set(true);
+         }
+      };
+
+      final ClientSASLFactory clientSASLFactory = new ClientSASLFactory() {
+         @Override
+         public ClientSASL chooseMechanism(String[] availableMechanims) {
+            ExternalMechanism externalMechanism = new ExternalMechanism();
+            return new ClientSASL() {
+               @Override
+               public String getName() {
+                  return externalMechanism.getName();
+               }
+
+               @Override
+               public byte[] getInitialResponse() {
+                  return externalMechanism.getInitialResponse();
+               }
+
+               @Override
+               public byte[] getResponse(byte[] challenge) {
+                  return new byte[0];
+               }
+            };
+         }
+      };
+
+
+      ProtonClientConnectionManager lifeCycleListener = new ProtonClientConnectionManager(new AMQPClientConnectionFactory(server, "myid", Collections.singletonMap(Symbol.getSymbol("myprop"), "propvalue"), 5000), Optional.of(eventHandler), clientSASLFactory);
+      ProtonClientProtocolManager protocolManager = new ProtonClientProtocolManager(new ProtonProtocolManagerFactory(), server);
+      NettyConnector connector = new NettyConnector(config, lifeCycleListener, lifeCycleListener, server.getExecutorFactory().getExecutor(), server.getExecutorFactory().getExecutor(), server.getScheduledPool(), protocolManager);
+      connector.start();
+      connector.createConnection();
+
+      try {
+         Wait.assertEquals(1, server::getConnectionCount);
+         Wait.assertTrue(connectionOpened::get);
+         Wait.assertFalse(authFailed::get);
+
+         lifeCycleListener.stop();
+
+         Wait.assertEquals(0, server::getConnectionCount);
+      } finally {
+         lifeCycleListener.stop();
+      }
+
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java
----------------------------------------------------------------------
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java
index 5a93154..a7af43d 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java
@@ -82,11 +82,12 @@ public class JMSSaslGssapiTest extends JMSClientTestSupport {
       kdc.createPrincipal(userKeyTab, "client", "amqp/localhost");
 
       if (debug) {
-         java.util.logging.Logger logger = java.util.logging.Logger.getLogger("javax.security.sasl");
-         logger.setLevel(java.util.logging.Level.FINEST);
-         logger.addHandler(new java.util.logging.ConsoleHandler());
-         for (java.util.logging.Handler handler : logger.getHandlers()) {
-            handler.setLevel(java.util.logging.Level.FINEST);
+         for (java.util.logging.Logger logger : new java.util.logging.Logger[] {java.util.logging.Logger.getLogger("javax.security.sasl"), java.util.logging.Logger.getLogger("org.apache.qpid.proton")}) {
+            logger.setLevel(java.util.logging.Level.FINEST);
+            logger.addHandler(new java.util.logging.ConsoleHandler());
+            for (java.util.logging.Handler handler : logger.getHandlers()) {
+               handler.setLevel(java.util.logging.Level.FINEST);
+            }
          }
       }
    }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/tests/integration-tests/src/test/resources/cert-roles.properties
----------------------------------------------------------------------
diff --git a/tests/integration-tests/src/test/resources/cert-roles.properties b/tests/integration-tests/src/test/resources/cert-roles.properties
index 4f3ba3f..5f860fc 100644
--- a/tests/integration-tests/src/test/resources/cert-roles.properties
+++ b/tests/integration-tests/src/test/resources/cert-roles.properties
@@ -16,3 +16,4 @@
 #
 
 programmers=first
+widgets=second

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/tests/integration-tests/src/test/resources/cert-users.properties
----------------------------------------------------------------------
diff --git a/tests/integration-tests/src/test/resources/cert-users.properties b/tests/integration-tests/src/test/resources/cert-users.properties
index 7bd65f0..4af10d5 100644
--- a/tests/integration-tests/src/test/resources/cert-users.properties
+++ b/tests/integration-tests/src/test/resources/cert-users.properties
@@ -16,3 +16,4 @@
 #
 
 first=CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ, L=AMQ, ST=AMQ, C=AMQ
+second=O=Internet Widgits Pty Ltd, C=AU, ST=Some-State, CN=lakalkalaoioislkxn


[2/2] activemq-artemis git commit: This closes #1961

Posted by ta...@apache.org.
This closes #1961


Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/2f9d3739
Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/2f9d3739
Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/2f9d3739

Branch: refs/heads/master
Commit: 2f9d3739375ed1b2aea19d77eda8440c56101072
Parents: 92a73e2 72ec6c8
Author: Timothy Bish <ta...@gmail.com>
Authored: Thu Mar 22 10:10:50 2018 -0400
Committer: Timothy Bish <ta...@gmail.com>
Committed: Thu Mar 22 10:10:50 2018 -0400

----------------------------------------------------------------------
 .../amqp/broker/AMQPConnectionCallback.java     |  16 ++
 .../amqp/proton/AMQPConnectionContext.java      |   1 -
 .../amqp/proton/handler/ProtonHandler.java      | 213 +++++++++--------
 .../protocol/amqp/sasl/ExternalServerSASL.java  |  62 +++++
 .../protocol/amqp/sasl/GSSAPISASLResult.java    |  51 ----
 .../protocol/amqp/sasl/GSSAPIServerSASL.java    |   4 +-
 .../protocol/amqp/sasl/PrincipalSASLResult.java |  51 ++++
 docs/user-manual/en/security.md                 |   5 +
 .../integration/amqp/JMSSaslExternalTest.java   | 235 +++++++++++++++++++
 .../integration/amqp/JMSSaslGssapiTest.java     |  11 +-
 .../src/test/resources/cert-roles.properties    |   1 +
 .../src/test/resources/cert-users.properties    |   1 +
 12 files changed, 492 insertions(+), 159 deletions(-)
----------------------------------------------------------------------