BUG-9261: Add credential handler for PublicKey auth 52/64652/3
authorTomas Cere <tcere@cisco.com>
Thu, 19 Oct 2017 13:10:51 +0000 (15:10 +0200)
committerTomas Cere <tcere@cisco.com>
Wed, 25 Oct 2017 11:38:09 +0000 (13:38 +0200)
Change-Id: I073188bede6fb592147da5891793cf84abced276
Signed-off-by: Tomas Cere <tcere@cisco.com>
netconf/netconf-topology-singleton/src/main/java/org/opendaylight/netconf/topology/singleton/impl/RemoteDeviceConnectorImpl.java
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/AbstractNetconfTopology.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/auth/DatastoreBackedPublicKeyAuth.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfKeystoreAdapter.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/yang/netconf-node-topology.yang

index 5e3960075fdcb2be20a6a5bed37789e68d876484..4bfa7982fea14d4f7d4a6c135b68713a3d4d98f7 100644 (file)
@@ -42,11 +42,13 @@ import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
+import org.opendaylight.netconf.sal.connect.netconf.auth.DatastoreBackedPublicKeyAuth;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
+import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.netconf.topology.singleton.api.RemoteDeviceConnector;
@@ -61,6 +63,7 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyBased;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordDeprecated;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
@@ -90,6 +93,7 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
     private final String privateKeyPassphrase;
     private final AAAEncryptionService encryptionService;
     private NetconfConnectorDTO deviceCommunicatorDTO;
+    private final NetconfKeystoreAdapter keystoreAdapter;
 
     public RemoteDeviceConnectorImpl(final NetconfTopologySetup netconfTopologyDeviceSetup,
                                      final RemoteDeviceId remoteDeviceId, final Timeout actorResponseWaitTime,
@@ -102,6 +106,7 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
         this.privateKeyPath = netconfTopologyDeviceSetup.getPrivateKeyPath();
         this.privateKeyPassphrase = netconfTopologyDeviceSetup.getPrivateKeyPassphrase();
         this.encryptionService = netconfTopologyDeviceSetup.getEncryptionService();
+        keystoreAdapter = new NetconfKeystoreAdapter(netconfTopologyDeviceSetup.getDataBroker());
     }
 
     @Override
@@ -315,8 +320,10 @@ public class RemoteDeviceConnectorImpl implements RemoteDeviceConnector {
             return new LoginPasswordHandler(loginPassword.getUsername(),
                     encryptionService.decrypt(loginPassword.getPassword()));
         }
-        if (credentials instanceof KeyPair) {
-            throw new UnsupportedOperationException("Not implemented yet");
+        if (credentials instanceof KeyBased) {
+            final KeyPair keyPair = ((KeyBased) credentials).getKeyPair();
+            return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getPairId(),
+                    keystoreAdapter, encryptionService);
         }
         throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
     }
index 03c2aa65cf6b0cf66d6c1bada1854678d7ad22f7..77c8d107eb1561f9ca435112d4098b8e18e94d66 100644 (file)
@@ -45,11 +45,13 @@ import org.opendaylight.netconf.sal.connect.netconf.NetconfDevice;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfDeviceBuilder;
 import org.opendaylight.netconf.sal.connect.netconf.NetconfStateSchemasResolverImpl;
 import org.opendaylight.netconf.sal.connect.netconf.SchemalessNetconfDevice;
+import org.opendaylight.netconf.sal.connect.netconf.auth.DatastoreBackedPublicKeyAuth;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCapabilities;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.listener.UserPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.sal.KeepaliveSalFacade;
+import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
 import org.opendaylight.netconf.sal.connect.netconf.schema.YangLibrarySchemaYangSourceProvider;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.netconf.topology.api.NetconfTopology;
@@ -62,6 +64,7 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability.CapabilityOrigin;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.KeyBased;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordDeprecated;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPw;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
@@ -168,6 +171,7 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
     protected final SharedSchemaRepository sharedSchemaRepository;
     protected final DataBroker dataBroker;
     protected final DOMMountPointService mountPointService;
+    private final NetconfKeystoreAdapter keystoreAdapter;
     protected SchemaSourceRegistry schemaRegistry = DEFAULT_SCHEMA_REPOSITORY;
     protected SchemaRepository schemaRepository = DEFAULT_SCHEMA_REPOSITORY;
     protected SchemaContextFactory schemaContextFactory = DEFAULT_SCHEMA_CONTEXT_FACTORY;
@@ -191,6 +195,8 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
         this.dataBroker = dataBroker;
         this.mountPointService = mountPointService;
         this.encryptionService = encryptionService;
+
+        this.keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
     }
 
     public void setSchemaRegistry(final SchemaSourceRegistry schemaRegistry) {
@@ -469,8 +475,10 @@ public abstract class AbstractNetconfTopology implements NetconfTopology {
             return new LoginPasswordHandler(loginPassword.getUsername(),
                     encryptionService.decrypt(loginPassword.getPassword()));
         }
-        if (credentials instanceof KeyPair) {
-            throw new UnsupportedOperationException("Not implemented yet");
+        if (credentials instanceof KeyBased) {
+            final KeyPair keyPair = ((KeyBased) credentials).getKeyPair();
+            return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getPairId(),
+                    keystoreAdapter, encryptionService);
         }
         throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
     }
diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/auth/DatastoreBackedPublicKeyAuth.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/auth/DatastoreBackedPublicKeyAuth.java
new file mode 100644 (file)
index 0000000..3e9115d
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.connect.netconf.auth;
+
+import com.google.common.base.Strings;
+import java.io.IOException;
+import java.io.StringReader;
+import java.security.KeyPair;
+import java.util.Optional;
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.client.future.AuthFuture;
+import org.opendaylight.aaa.encrypt.AAAEncryptionService;
+import org.opendaylight.aaa.encrypt.PKIUtil;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
+import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfKeystoreAdapter;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DatastoreBackedPublicKeyAuth extends AuthenticationHandler {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DatastoreBackedPublicKeyAuth.class);
+
+    private final String username;
+    private final String pairId;
+    private final NetconfKeystoreAdapter keystoreAdapter;
+    private final AAAEncryptionService encryptionService;
+
+    private Optional<KeyPair> keyPair;
+
+    public DatastoreBackedPublicKeyAuth(final String username, final String pairId,
+                                        final NetconfKeystoreAdapter keystoreAdapter,
+                                        final AAAEncryptionService encryptionService) {
+        this.username = username;
+        this.pairId = pairId;
+        this.keystoreAdapter = keystoreAdapter;
+        this.encryptionService = encryptionService;
+
+        // try to immediately retrieve the pair from the adapter
+        tryToSetKeyPair();
+    }
+
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    @Override
+    public AuthFuture authenticate(ClientSession session) throws IOException {
+        // if we have keypair set the identity, otherwise retry the retrieval from the adapter
+        // if successful set the identity.
+        if (keyPair.isPresent() || tryToSetKeyPair()) {
+            session.addPublicKeyIdentity(keyPair.get());
+        }
+        return session.auth();
+    }
+
+    private boolean tryToSetKeyPair() {
+        LOG.debug("Trying to retrieve keypair for: {}", pairId);
+        final Optional<KeyCredential> keypairOptional = keystoreAdapter.getKeypairFromId(pairId);
+
+        if (keypairOptional.isPresent()) {
+            final KeyCredential dsKeypair = keypairOptional.get();
+            final String passPhrase = Strings.isNullOrEmpty(dsKeypair.getPassphrase()) ? "" : dsKeypair.getPassphrase();
+
+            try {
+                this.keyPair = Optional.of(
+                        new PKIUtil().decodePrivateKey(
+                                new StringReader(encryptionService.decrypt(dsKeypair.getPrivateKey())),
+                                encryptionService.decrypt(passPhrase)));
+            } catch (IOException exception) {
+                LOG.warn("Unable to decode private key, id={}", pairId, exception);
+                return false;
+            }
+            return true;
+        }
+        LOG.debug("Unable to retrieve keypair for: {}", pairId);
+        return false;
+    }
+}
diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfKeystoreAdapter.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfKeystoreAdapter.java
new file mode 100644 (file)
index 0000000..7ab0089
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.netconf.sal.connect.netconf.sal;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NetconfKeystoreAdapter implements ClusteredDataTreeChangeListener<Keystore> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NetconfKeystoreAdapter.class);
+
+    private final InstanceIdentifier<Keystore> keystoreIid = InstanceIdentifier.create(Keystore.class);
+
+    private final DataBroker dataBroker;
+    private final Map<String, KeyCredential> pairs = Collections.synchronizedMap(new HashMap<>());
+
+    public NetconfKeystoreAdapter(final DataBroker dataBroker) {
+        this.dataBroker = dataBroker;
+
+        dataBroker.registerDataTreeChangeListener(
+                new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL, keystoreIid), this);
+    }
+
+    public Optional<KeyCredential> getKeypairFromId(final String keyId) {
+        final KeyCredential keypair = pairs.get(keyId);
+        return Optional.ofNullable(keypair);
+    }
+
+    @Override
+    public void onDataTreeChanged(@Nonnull final Collection<DataTreeModification<Keystore>> changes) {
+        LOG.debug("Keystore updated: {}", changes);
+        final Keystore dataAfter = changes.iterator().next().getRootNode().getDataAfter();
+
+        pairs.clear();
+        if (dataAfter != null) {
+            dataAfter.getKeyCredential().forEach(pair -> pairs.put(pair.getKey().getKeyId(), pair));
+        }
+    }
+}
index 43e65d7cf566d7f3394384d8acece740b363f65e..f1413e119388efc03b5488738e2f11fea46a128d 100644 (file)
@@ -56,6 +56,10 @@ module netconf-node-topology {
                     leaf pair-id {
                         type string;
                     }
+
+                    leaf username {
+                        type string;
+                    }
                 }
             }
         }