BUG-9261: introduce netconf keystore service 51/64651/2
authorTomas Cere <tcere@cisco.com>
Thu, 19 Oct 2017 10:21:34 +0000 (12:21 +0200)
committerTomas Cere <tcere@cisco.com>
Wed, 25 Oct 2017 11:29:32 +0000 (13:29 +0200)
Change-Id: I97e1e65029c339bd28557787841d485392cf9c44
Signed-off-by: Tomas Cere <tcere@cisco.com>
netconf/netconf-topology-config/src/main/resources/org/opendaylight/blueprint/netconf-topology.xml
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/util/NetconfSalKeystoreService.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/yang/netconf-keystore.yang [new file with mode: 0644]

index 72ca8da1afae7b194e94f0cbb5e5dd5029d5f6c9..477a90b247af56a096221cacd365acd2f2ed51e9 100755 (executable)
 
     <odl:rpc-implementation ref="netconfNodeRegisterEncryptedRPC"/>
 
+    <bean id="netconfKeystoreProvider"
+          class="org.opendaylight.netconf.sal.connect.util.NetconfSalKeystoreService">
+        <argument ref="dataBroker"/>
+        <argument ref="encryptionService"/>
+    </bean>
+
+    <odl:rpc-implementation ref="netconfKeystoreProvider"/>
+
 </blueprint>
diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/util/NetconfSalKeystoreService.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/util/NetconfSalKeystoreService.java
new file mode 100644 (file)
index 0000000..dde5474
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * 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.util;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.opendaylight.aaa.encrypt.AAAEncryptionService;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.AddKeystoreEntryInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.Keystore;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.KeystoreBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.NetconfKeystoreService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.RemoveKeystoreEntryInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredential;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredentialBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.keystore.rev171017.keystore.entry.KeyCredentialKey;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NetconfSalKeystoreService implements NetconfKeystoreService {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NetconfSalKeystoreService.class);
+
+    private final DataBroker dataBroker;
+    private final AAAEncryptionService encryptionService;
+
+    private final InstanceIdentifier<Keystore> keystoreIid = InstanceIdentifier.create(Keystore.class);
+
+    public NetconfSalKeystoreService(final DataBroker dataBroker,
+                                     final AAAEncryptionService encryptionService) {
+        LOG.info("Starting NETCONF keystore service.");
+
+        this.dataBroker = dataBroker;
+        this.encryptionService = encryptionService;
+
+        initKeystore();
+    }
+
+    private void initKeystore() {
+        final Keystore keystore = new KeystoreBuilder().build();
+
+        final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+        writeTransaction.merge(LogicalDatastoreType.OPERATIONAL, keystoreIid, keystore);
+
+        final CheckedFuture<Void, TransactionCommitFailedException> submit = writeTransaction.submit();
+
+        try {
+            submit.checkedGet();
+            LOG.debug("init keystore done");
+        } catch (TransactionCommitFailedException exception) {
+            LOG.error("Unable to initialize Netconf key-pair store.", exception);
+        }
+    }
+
+    @Override
+    public Future<RpcResult<Void>> removeKeystoreEntry(final RemoveKeystoreEntryInput input) {
+        LOG.debug("Removing keypairs: {}", input);
+
+        final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+        final List<String> ids = input.getKeyId();
+
+        for (final String id : ids) {
+            writeTransaction.delete(LogicalDatastoreType.OPERATIONAL,
+                    keystoreIid.child(KeyCredential.class, new KeyCredentialKey(id)));
+        }
+
+        final SettableFuture<RpcResult<Void>> rpcResult = SettableFuture.create();
+
+        final CheckedFuture<Void, TransactionCommitFailedException> submit = writeTransaction.submit();
+        Futures.addCallback(submit, new FutureCallback<Void>() {
+            @Override
+            public void onSuccess(@Nullable final Void result) {
+                LOG.debug("remove-key-pair success. Input: {}");
+                final RpcResult<Void> success = RpcResultBuilder.<Void>success().build();
+                rpcResult.set(success);
+            }
+
+            @Override
+            public void onFailure(final Throwable throwable) {
+                LOG.warn("remove-key-pair failed. Input: {}", input, throwable);
+                rpcResult.setException(throwable);
+            }
+        }, MoreExecutors.directExecutor());
+
+        return rpcResult;
+    }
+
+    @Override
+    public Future<RpcResult<Void>> addKeystoreEntry(final AddKeystoreEntryInput input) {
+        LOG.debug("Adding keypairs: {}", input);
+
+        final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+        final List<KeyCredential> keypairs = input.getKeyCredential().stream().map(keypair ->
+                new KeyCredentialBuilder(keypair)
+                        .setPrivateKey(encryptionService.encrypt(keypair.getPrivateKey()))
+                        .setPassphrase(encryptionService.encrypt(keypair.getPassphrase()))
+                        .build()).collect(Collectors.toList());
+
+        for (KeyCredential keypair : keypairs) {
+            writeTransaction.merge(LogicalDatastoreType.OPERATIONAL,
+                    keystoreIid.child(KeyCredential.class, keypair.getKey()), keypair);
+        }
+
+        final SettableFuture<RpcResult<Void>> rpcResult = SettableFuture.create();
+
+        final CheckedFuture<Void, TransactionCommitFailedException> submit = writeTransaction.submit();
+        Futures.addCallback(submit, new FutureCallback<Void>() {
+            @Override
+            public void onSuccess(@Nullable final Void result) {
+                LOG.debug("add-key-pair success. Input: {}");
+                final RpcResult<Void> success = RpcResultBuilder.<Void>success().build();
+                rpcResult.set(success);
+            }
+
+            @Override
+            public void onFailure(final Throwable throwable) {
+                LOG.warn("add-key-pair failed. Input: {}", input, throwable);
+                rpcResult.setException(throwable);
+            }
+        }, MoreExecutors.directExecutor());
+
+        return rpcResult;
+    }
+}
diff --git a/netconf/sal-netconf-connector/src/main/yang/netconf-keystore.yang b/netconf/sal-netconf-connector/src/main/yang/netconf-keystore.yang
new file mode 100644 (file)
index 0000000..d3da9f4
--- /dev/null
@@ -0,0 +1,60 @@
+module netconf-keystore {
+    namespace "urn:opendaylight:netconf:keystore";
+    prefix "keystore";
+
+    revision "2017-10-17" {
+        description "Initial revision of the Netconf SBP keystore.";
+    }
+
+    description "Store used for key based Credentials for Netconf SBP. Before a connector with key based authentication
+                 is created it needs to have a record for the key pair it uses. All the records here need to be
+                 encrypted as they contain sensitive data. Therefore NEVER do direct writes and only use the provided
+                 RPC's for adding/removing key entries.";
+
+    grouping keystore-entry {
+        list key-credential {
+            key key-id;
+
+            leaf key-id {
+                type string;
+            }
+
+            leaf private-key {
+                description "Base64 encoded private key that should be used for authentication with a netconf device.
+                             Do not include a public key as that is calculated from the private key.
+                             DO NOT write this directly into the datastore, use the provided rpc's as these will
+                             encrypt the key before the entry is written into the datastore.";
+                type string;
+            }
+
+            leaf passphrase {
+                description "If the provided key is encrypted by a passphrase this needs to be included. Leave empty
+                             if the key does not have a passphrase.
+                             DO NOT write write this directly into the datastore, use the provided rpc's as these will
+                             encrypt the passhprase before the entry is written into the datastore.";
+                type string;
+            }
+        }
+    }
+
+    container keystore {
+        uses keystore-entry;
+    }
+
+    rpc add-keystore-entry {
+        description "Use this rpc to add a single or multiple new keys into the keystore. The private key
+                     and passphrase will both be encrypted before they are written into the datastore.";
+        input {
+            uses keystore-entry;
+        }
+    }
+
+    rpc remove-keystore-entry {
+        description "Use this rpc to remove a single or multiple keys from the datastore.";
+        input {
+            leaf-list key-id {
+                type string;
+            }
+        }
+    }
+}
\ No newline at end of file