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;
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;
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,
this.privateKeyPath = netconfTopologyDeviceSetup.getPrivateKeyPath();
this.privateKeyPassphrase = netconfTopologyDeviceSetup.getPrivateKeyPassphrase();
this.encryptionService = netconfTopologyDeviceSetup.getEncryptionService();
+ keystoreAdapter = new NetconfKeystoreAdapter(netconfTopologyDeviceSetup.getDataBroker());
}
@Override
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());
}
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;
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;
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;
this.dataBroker = dataBroker;
this.mountPointService = mountPointService;
this.encryptionService = encryptionService;
+
+ this.keystoreAdapter = new NetconfKeystoreAdapter(dataBroker);
}
public void setSchemaRegistry(final SchemaSourceRegistry schemaRegistry) {
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());
}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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));
+ }
+ }
+}
leaf pair-id {
type string;
}
+
+ leaf username {
+ type string;
+ }
}
}
}