From 4cc9b3ae01a45676067ba22922c071b5995528f9 Mon Sep 17 00:00:00 2001 From: Tomas Cere Date: Thu, 19 Oct 2017 12:21:34 +0200 Subject: [PATCH] BUG-9261: introduce netconf keystore service Change-Id: I97e1e65029c339bd28557787841d485392cf9c44 Signed-off-by: Tomas Cere --- .../blueprint/netconf-topology.xml | 8 + .../util/NetconfSalKeystoreService.java | 143 ++++++++++++++++++ .../src/main/yang/netconf-keystore.yang | 60 ++++++++ 3 files changed, 211 insertions(+) create mode 100644 netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/util/NetconfSalKeystoreService.java create mode 100644 netconf/sal-netconf-connector/src/main/yang/netconf-keystore.yang diff --git a/netconf/netconf-topology-config/src/main/resources/org/opendaylight/blueprint/netconf-topology.xml b/netconf/netconf-topology-config/src/main/resources/org/opendaylight/blueprint/netconf-topology.xml index 72ca8da1af..477a90b247 100755 --- a/netconf/netconf-topology-config/src/main/resources/org/opendaylight/blueprint/netconf-topology.xml +++ b/netconf/netconf-topology-config/src/main/resources/org/opendaylight/blueprint/netconf-topology.xml @@ -73,4 +73,12 @@ + + + + + + + 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 index 0000000000..dde54749c7 --- /dev/null +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/util/NetconfSalKeystoreService.java @@ -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 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 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> removeKeystoreEntry(final RemoveKeystoreEntryInput input) { + LOG.debug("Removing keypairs: {}", input); + + final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); + final List ids = input.getKeyId(); + + for (final String id : ids) { + writeTransaction.delete(LogicalDatastoreType.OPERATIONAL, + keystoreIid.child(KeyCredential.class, new KeyCredentialKey(id))); + } + + final SettableFuture> rpcResult = SettableFuture.create(); + + final CheckedFuture submit = writeTransaction.submit(); + Futures.addCallback(submit, new FutureCallback() { + @Override + public void onSuccess(@Nullable final Void result) { + LOG.debug("remove-key-pair success. Input: {}"); + final RpcResult success = RpcResultBuilder.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> addKeystoreEntry(final AddKeystoreEntryInput input) { + LOG.debug("Adding keypairs: {}", input); + + final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction(); + final List 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 = SettableFuture.create(); + + final CheckedFuture submit = writeTransaction.submit(); + Futures.addCallback(submit, new FutureCallback() { + @Override + public void onSuccess(@Nullable final Void result) { + LOG.debug("add-key-pair success. Input: {}"); + final RpcResult success = RpcResultBuilder.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 index 0000000000..d3da9f4306 --- /dev/null +++ b/netconf/sal-netconf-connector/src/main/yang/netconf-keystore.yang @@ -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 -- 2.36.6