import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
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.NetconfNodeBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordDeprecatedBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
node.setHost(new Host(key.getIpAddress()));
node.setPort(key.getPort());
node.setTcpOnly(Boolean.FALSE);
- node.setCredentials(new LoginPasswordBuilder().setUsername("ommited").setPassword("ommited").build());
+ node.setCredentials(new LoginPasswordDeprecatedBuilder().setUsername("ommited").setPassword("ommited").build());
node.setSchemaless(Boolean.FALSE);
return node.build();
}
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
-import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
import org.opendaylight.protocol.framework.NeverReconnectStrategy;
NioEventLoopGroup nettyGroup = new NioEventLoopGroup();
NetconfClientDispatcherImpl netconfClientDispatcher = new NetconfClientDispatcherImpl(nettyGroup, nettyGroup,
hashedWheelTimer);
- LoginPassword authHandler = new LoginPassword("admin", "admin");
+ LoginPasswordHandler authHandler = new LoginPasswordHandler("admin", "admin");
TestingNetconfClient client = new TestingNetconfClient("client", netconfClientDispatcher,
getClientConfig("127.0.0.1", 1830, true, Optional.of(authHandler)));
System.console().writer().println(client.getCapabilities());
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.NetconfNodeBuilder;
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.LoginPasswordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordDeprecatedBuilder;
@Command(name = "netconf:connect-device", scope = "netconf", description = "Connect to a netconf device.")
public class NetconfConnectDeviceCommand extends AbstractAction {
final boolean isTcpOnly = connectionType.equals("true");
final boolean isSchemaless = schemaless.equals("true");
- final Credentials credentials = new LoginPasswordBuilder().setPassword(password).setUsername(username).build();
+ final Credentials credentials =
+ new LoginPasswordDeprecatedBuilder().setPassword(password).setUsername(username).build();
final NetconfNode netconfNode = new NetconfNodeBuilder()
.setHost(new Host(new IpAddress(new Ipv4Address(deviceIp))))
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability;
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.LoginPasswordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordDeprecatedBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
? updated.get(NetconfConsoleConstants.PASSWORD) : password;
final Credentials credentials =
- new LoginPasswordBuilder().setPassword(newPassword).setUsername(newUsername).build();
+ new LoginPasswordDeprecatedBuilder().setPassword(newPassword).setUsername(newUsername).build();
final NetconfNode updatedNetconfNode = new NetconfNodeBuilder()
.setHost(new Host(new IpAddress(new Ipv4Address(deviceIp))))
.setPort(new PortNumber(Integer.decode(devicePort)))
}
}
- grouping netconf-node-credentials {
+ grouping username-password {
+ leaf username {
+ type string;
+ }
+
+ leaf password {
+ type string;
+ }
+ }
+ grouping netconf-node-credentials {
choice credentials {
config true;
- case login-password {
- leaf username {
- type string;
+ case login-password-deprecated {
+ description "Deprecated way of storing credentials, unencrypted.";
+
+ status deprecated;
+ uses username-password;
+ }
+ case login-pw {
+ description "login-password credentials, encrypted.";
+
+
+ container login-password {
+ uses username-password;
}
+ }
+ case login-pw-unencrypted {
+ description "login-password credentials, not encrypted.";
+
+ container login-password-unencrypted {
+ uses username-password;
+ }
+ }
+ case key-based {
+ description "key-pair based authentication, use the id for the pair thats stored in the keystore.";
- leaf password {
+ leaf pair-id {
type string;
}
}
description "Limit of concurrent messages that can be send before reply messages are received.
If value <1 is provided, no limit will be enforced";
}
+
+ leaf actor-response-wait-time {
+ config true;
+ type uint16 {
+ range "1..max";
+ }
+ default 5;
+ description "Time that slave actor will wait for response from master.";
+ }
}
grouping netconf-node-connection-status {
}
+ rpc add-netconf-node {
+ input {
+ uses netconf-node-fields;
+ leaf node-id {
+ type string;
+ }
+ }
+ }
+
augment "/nt:network-topology/nt:topology/nt:node" {
when "../../nt:topology-types/topology-netconf";
ext:augment-identifier "netconf-node";
uses netconf-node-fields;
}
+
}
import java.io.IOException;
import org.apache.sshd.ClientSession;
import org.apache.sshd.client.future.AuthFuture;
-import org.opendaylight.aaa.encrypt.AAAEncryptionService;
/**
* Class Providing username/password authentication option to
* {@link org.opendaylight.netconf.nettyutil.handler.ssh.client.AsyncSshHandler}.
*/
-public class LoginPassword extends AuthenticationHandler {
+public class LoginPasswordHandler extends AuthenticationHandler {
protected final String username;
protected final String password;
- protected final AAAEncryptionService encryptionService;
- public LoginPassword(String username, String password) {
- this(username, password, null);
- }
-
- public LoginPassword(final String username, final String password, final AAAEncryptionService encryptionService) {
+ public LoginPasswordHandler(final String username, final String password) {
this.username = username;
this.password = password;
- this.encryptionService = encryptionService;
}
@Override
@Override
public AuthFuture authenticate(final ClientSession session) throws IOException {
- if (encryptionService != null) {
- String decryptedPassword = encryptionService.decrypt(password);
- session.addPasswordIdentity(decryptedPassword);
- } else {
- session.addPasswordIdentity(password);
- }
+ session.addPasswordIdentity(password);
return session.auth();
}
}
import java.security.KeyPair;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents Auth information for the public key based authentication for netconf.
*/
-public class PublicKeyAuth extends LoginPassword {
+public class PublicKeyAuth extends LoginPasswordHandler {
private KeyPair keyPair = null;
private static final Logger LOG = LoggerFactory.getLogger(PublicKeyAuth.class);
public PublicKeyAuth(String username, String password, String keyPath,
- String passPhrase, AAAEncryptionService encryptionService) {
- super(username, password, encryptionService);
+ String passPhrase) {
+ super(username, password);
try {
boolean isKeyPathAbsent = Strings.isNullOrEmpty(keyPath);
passPhrase = Strings.isNullOrEmpty(passPhrase) ? "" : passPhrase;
import org.apache.sshd.client.future.AuthFuture;
import org.junit.Test;
-public class LoginPasswordTest {
+public class LoginPasswordHandlerTest {
@Test
public void testLoginPassword() throws Exception {
- final LoginPassword loginPassword = new LoginPassword("user", "pwd");
- assertEquals("user", loginPassword.getUsername());
+ final LoginPasswordHandler loginPasswordHandler = new LoginPasswordHandler("user", "pwd");
+ assertEquals("user", loginPasswordHandler.getUsername());
final ClientSession session = mock(ClientSession.class);
doNothing().when(session).addPasswordIdentity("pwd");
doReturn(mock(AuthFuture.class)).when(session).auth();
- loginPassword.authenticate(session);
+ loginPasswordHandler.authenticate(session);
verify(session).addPasswordIdentity("pwd");
verify(session).auth();
(username, password, session)
-> sshProxyServerConfiguration.getAuthenticator().authenticated(username, password));
+ sshProxyServerConfiguration.getPublickeyAuthenticator().ifPresent(sshServer::setPublickeyAuthenticator);
+
sshServer.setKeyPairProvider(sshProxyServerConfiguration.getKeyPairProvider());
sshServer.setIoServiceFactoryFactory(nioServiceWithPoolFactoryFactory);
import com.google.common.base.Preconditions;
import io.netty.channel.local.LocalAddress;
import java.net.InetSocketAddress;
+import java.util.Optional;
import org.apache.sshd.common.KeyPairProvider;
+import org.apache.sshd.server.PublickeyAuthenticator;
import org.opendaylight.netconf.auth.AuthProvider;
public final class SshProxyServerConfiguration {
private final AuthProvider authenticator;
private final KeyPairProvider keyPairProvider;
private final int idleTimeout;
+ private final Optional<PublickeyAuthenticator> publickeyAuthenticator;
SshProxyServerConfiguration(final InetSocketAddress bindingAddress, final LocalAddress localAddress,
final AuthProvider authenticator, final KeyPairProvider keyPairProvider, final int idleTimeout) {
+ this(bindingAddress, localAddress, authenticator, null, keyPairProvider, idleTimeout);
+ }
+
+ SshProxyServerConfiguration(final InetSocketAddress bindingAddress, final LocalAddress localAddress,
+ final AuthProvider authenticator, final PublickeyAuthenticator publickeyAuthenticator,
+ final KeyPairProvider keyPairProvider, final int idleTimeout) {
this.bindingAddress = Preconditions.checkNotNull(bindingAddress);
this.localAddress = Preconditions.checkNotNull(localAddress);
this.authenticator = Preconditions.checkNotNull(authenticator);
// Idle timeout cannot be disabled in the sshd by using =< 0 value
Preconditions.checkArgument(idleTimeout > 0, "Idle timeout has to be > 0");
this.idleTimeout = idleTimeout;
+ this.publickeyAuthenticator = Optional.ofNullable(publickeyAuthenticator);
}
public InetSocketAddress getBindingAddress() {
return idleTimeout;
}
-
+ public Optional<PublickeyAuthenticator> getPublickeyAuthenticator() {
+ return publickeyAuthenticator;
+ }
}
import io.netty.channel.local.LocalAddress;
import java.net.InetSocketAddress;
import org.apache.sshd.common.KeyPairProvider;
+import org.apache.sshd.server.PublickeyAuthenticator;
import org.opendaylight.netconf.auth.AuthProvider;
public final class SshProxyServerConfigurationBuilder {
private AuthProvider authenticator;
private KeyPairProvider keyPairProvider;
private int idleTimeout;
+ private PublickeyAuthenticator publicKeyAuthenticator = null;
public SshProxyServerConfigurationBuilder setBindingAddress(final InetSocketAddress bindingAddress) {
this.bindingAddress = bindingAddress;
return this;
}
+ public SshProxyServerConfigurationBuilder setPublickeyAuthenticator(final PublickeyAuthenticator authenticator) {
+ this.publicKeyAuthenticator = authenticator;
+ return this;
+ }
+
public SshProxyServerConfigurationBuilder setKeyPairProvider(final KeyPairProvider keyPairProvider) {
this.keyPairProvider = keyPairProvider;
return this;
}
public SshProxyServerConfiguration createSshProxyServerConfiguration() {
- return new SshProxyServerConfiguration(bindingAddress, localAddress, authenticator,
+ return new SshProxyServerConfiguration(bindingAddress, localAddress, authenticator, publicKeyAuthenticator,
keyPairProvider, idleTimeout);
}
import org.junit.BeforeClass;
import org.junit.Test;
import org.opendaylight.netconf.netty.EchoClientHandler.State;
-import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
import org.opendaylight.netconf.nettyutil.handler.ssh.client.AsyncSshHandler;
import org.opendaylight.netconf.ssh.SshProxyServer;
import org.opendaylight.netconf.ssh.SshProxyServerConfigurationBuilder;
final ChannelInitializer<NioSocketChannel> channelInitializer = new ChannelInitializer<NioSocketChannel>() {
@Override
public void initChannel(final NioSocketChannel ch) throws Exception {
- ch.pipeline().addFirst(AsyncSshHandler.createForNetconfSubsystem(new LoginPassword("a", "a")));
+ ch.pipeline().addFirst(AsyncSshHandler.createForNetconfSubsystem(new LoginPasswordHandler("a", "a")));
ch.pipeline().addLast(echoClientHandler);
}
};
<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>
import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
-import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.PublicKeyAuth;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
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;
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.KeyAuth;
+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;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
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
betweenAttemptsTimeoutMillis, sleepFactor);
final ReconnectStrategy strategy = sf.createReconnectStrategy();
- final AuthenticationHandler authHandler;
- final Credentials credentials = node.getCredentials();
- if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf
- .node.credentials.credentials.LoginPassword) {
- authHandler = new PublicKeyAuth(
- ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf
- .node.credentials.credentials.LoginPassword) credentials).getUsername(),
- ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf
- .node.credentials.credentials.LoginPassword) credentials).getPassword(),
- this.privateKeyPath, this.privateKeyPassphrase, encryptionService);
-
- } else {
- throw new IllegalStateException(remoteDeviceId + ": Only login/password authentication is supported");
- }
+ final AuthenticationHandler authHandler = getHandlerFromCredentials(node.getCredentials());
return NetconfReconnectingClientConfigurationBuilder.create()
.withAddress(socketAddress)
.build();
}
+ private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
+ if (credentials instanceof LoginPasswordDeprecated) {
+ final LoginPasswordDeprecated loginPassword = (LoginPasswordDeprecated) credentials;
+ return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
+ }
+ if (credentials instanceof LoginPwUnencrypted) {
+ final LoginPasswordUnencrypted loginPassword =
+ ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
+ return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
+ }
+ if (credentials instanceof LoginPw) {
+ final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
+ return new LoginPasswordHandler(loginPassword.getUsername(),
+ encryptionService.decrypt(loginPassword.getPassword()));
+ }
+ if (credentials instanceof KeyAuth) {
+ final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
+ return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
+ keystoreAdapter, encryptionService);
+ }
+ throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
+ }
+
private static final class TimedReconnectStrategyFactory implements ReconnectStrategyFactory {
private final Long connectionAttempts;
private final EventExecutor executor;
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.NetconfNodeBuilder;
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.LoginPasswordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordDeprecatedBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
@Test
public void testStopRemoteDeviceConnection() {
- final Credentials credentials = new LoginPasswordBuilder().setPassword("admin").setUsername("admin").build();
+ final Credentials credentials = new LoginPasswordDeprecatedBuilder()
+ .setPassword("admin").setUsername("admin").build();
final NetconfNode netconfNode = new NetconfNodeBuilder()
.setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1"))))
.setPort(new PortNumber(9999))
final ExecutorService executorService = mock(ExecutorService.class);
doReturn(executorService).when(processingExecutor).getExecutor();
- final Credentials credentials = new LoginPasswordBuilder().setPassword("admin").setUsername("admin").build();
+ final Credentials credentials = new LoginPasswordDeprecatedBuilder()
+ .setPassword("admin").setUsername("admin").build();
final NetconfNode netconfNode = new NetconfNodeBuilder()
.setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1"))))
.setPort(new PortNumber(9999))
final ExecutorService executorService = mock(ExecutorService.class);
doReturn(executorService).when(processingExecutor).getExecutor();
- final Credentials credentials = new LoginPasswordBuilder().setPassword("admin").setUsername("admin").build();
+ final Credentials credentials = new LoginPasswordDeprecatedBuilder()
+ .setPassword("admin").setUsername("admin").build();
final NetconfNode netconfNode = new NetconfNodeBuilder()
.setHost(new Host(new IpAddress(new Ipv4Address("127.0.0.1"))))
.setPort(new PortNumber(9999))
.setDefaultRequestTimeoutMillis(2000L)
.setHost(host)
.setPort(portNumber)
- .setCredentials(new LoginPasswordBuilder()
+ .setCredentials(new LoginPasswordDeprecatedBuilder()
.setUsername("testuser")
.setPassword("testpassword").build())
.setTcpOnly(true)
import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.AuthenticationHandler;
-import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.PublicKeyAuth;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
import org.opendaylight.netconf.sal.connect.netconf.LibraryModulesSchemas;
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.KeyAuth;
+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;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.key.auth.KeyBased;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencrypted;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
import org.opendaylight.yangtools.yang.model.repo.api.SchemaContextFactory;
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) {
maxConnectionAttempts, betweenAttemptsTimeoutMillis, sleepFactor);
final ReconnectStrategy strategy = sf.createReconnectStrategy();
- final AuthenticationHandler authHandler;
- final Credentials credentials = node.getCredentials();
- if (credentials instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114
- .netconf.node.credentials.credentials.LoginPassword) {
- authHandler = new PublicKeyAuth(
- ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114
- .netconf.node.credentials.credentials.LoginPassword) credentials).getUsername(),
- ((org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114
- .netconf.node.credentials.credentials.LoginPassword) credentials).getPassword(),
- privateKeyPath, privateKeyPassphrase, encryptionService);
- } else {
- throw new IllegalStateException("Only login/password authentification is supported");
- }
+ final AuthenticationHandler authHandler = getHandlerFromCredentials(node.getCredentials());
return NetconfReconnectingClientConfigurationBuilder.create()
.withAddress(socketAddress)
.build();
}
+ private AuthenticationHandler getHandlerFromCredentials(final Credentials credentials) {
+ if (credentials instanceof LoginPasswordDeprecated) {
+ final LoginPasswordDeprecated loginPassword = (LoginPasswordDeprecated) credentials;
+ return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
+ }
+ if (credentials instanceof LoginPwUnencrypted) {
+ final LoginPasswordUnencrypted loginPassword =
+ ((LoginPwUnencrypted) credentials).getLoginPasswordUnencrypted();
+ return new LoginPasswordHandler(loginPassword.getUsername(), loginPassword.getPassword());
+ }
+ if (credentials instanceof LoginPw) {
+ final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
+ return new LoginPasswordHandler(loginPassword.getUsername(),
+ encryptionService.decrypt(loginPassword.getPassword()));
+ }
+ if (credentials instanceof KeyAuth) {
+ final KeyBased keyPair = ((KeyAuth) credentials).getKeyBased();
+ return new DatastoreBackedPublicKeyAuth(keyPair.getUsername(), keyPair.getKeyId(),
+ keystoreAdapter, encryptionService);
+ }
+ throw new IllegalStateException("Unsupported credential type: " + credentials.getClass());
+ }
+
protected abstract RemoteDeviceHandler<NetconfSessionPreferences> createSalFacade(RemoteDeviceId id);
private InetSocketAddress getSocketAddress(final Host host, final int port) {
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.NetconfNodeBuilder;
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.LoginPasswordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPasswordBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
final NodeId nodeId = new NodeId(instanceName);
final NodeKey nodeKey = new NodeKey(nodeId);
- final Credentials credentials = new LoginPasswordBuilder()
- .setUsername(username)
- .setPassword(password)
+ final Credentials credentials = new LoginPwBuilder()
+ .setLoginPassword(
+ new LoginPasswordBuilder()
+ .setUsername(username)
+ .setPassword(password)
+ .build())
.build();
final Host host = HostBuilder.getDefaultInstance(address);
final PortNumber portNumber = new PortNumber(port);
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
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.NetconfNodeBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordDeprecatedBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopologyBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
.setBetweenAttemptsTimeoutMillis(100)
.setKeepaliveDelay(1000L)
.setTcpOnly(true)
- .setCredentials(new LoginPasswordBuilder().setUsername("testuser").setPassword("testpassword").build())
+ .setCredentials(new LoginPasswordDeprecatedBuilder()
+ .setUsername("testuser").setPassword("testpassword").build())
.build();
final NodeBuilder nn = new NodeBuilder().addAugmentation(NetconfNode.class, testingNode);
--- /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 = Optional.empty();
+
+ 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.CONFIGURATION, 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));
+ }
+ }
+}
--- /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.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.CONFIGURATION, 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.CONFIGURATION,
+ 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.CONFIGURATION,
+ 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;
+ }
+}
*/
package org.opendaylight.netconf.sal.connect.util;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
-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.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import java.util.concurrent.Future;
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.node.topology.rev150114.AddNetconfNodeInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.CreateDeviceInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.DeleteDeviceInput;
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.NetconfNodeBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeTopologyService;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPassword;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordBuilder;
+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.LoginPw;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPassword;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPasswordBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
}
@Override
- public Future<RpcResult<Void>> addNetconfNode(AddNetconfNodeInput input) {
- NetconfNode node = this.encryptPassword(input);
+ public Future<RpcResult<Void>> createDevice(final CreateDeviceInput input) {
+ final NetconfNode node = this.encryptPassword(input);
final SettableFuture<RpcResult<Void>> futureResult = SettableFuture.create();
- NodeId nodeId = new NodeId(input.getNodeId());
+ final NodeId nodeId = new NodeId(input.getNodeId());
writeToConfigDS(node, nodeId, topologyId, futureResult);
return futureResult;
}
- private NetconfNode encryptPassword(AddNetconfNodeInput input) {
- NetconfNodeBuilder builder = new NetconfNodeBuilder();
+ @VisibleForTesting
+ public NetconfNode encryptPassword(final CreateDeviceInput input) {
+ final NetconfNodeBuilder builder = new NetconfNodeBuilder();
builder.fieldsFrom(input);
- boolean encrypt = input.isEncrypt();
- LoginPassword loginPassword = (LoginPassword) input.getCredentials();
- if (encrypt) {
- String encryptedPassword = encryptionService.encrypt(loginPassword.getPassword());
- LoginPassword newCreds = new LoginPasswordBuilder().setPassword(encryptedPassword)
- .setUsername(loginPassword.getUsername()).build();
- builder.setCredentials(newCreds);
+ final Credentials credentials = handleEncryption(input.getCredentials());
+ builder.setCredentials(credentials);
+
+ return builder.build();
+ }
+
+ private Credentials handleEncryption(final Credentials credentials) {
+ if (credentials instanceof LoginPw) {
+ final LoginPassword loginPassword = ((LoginPw) credentials).getLoginPassword();
+ final String encryptedPassword =
+ encryptionService.encrypt(loginPassword.getPassword());
+
+ return new LoginPwBuilder().setLoginPassword(new LoginPasswordBuilder()
+ .setPassword(encryptedPassword)
+ .setUsername(loginPassword.getUsername()).build()).build();
}
- NetconfNode node = builder.build();
- return node;
+ // nothing else needs to be encrypted
+ return credentials;
}
- private void writeToConfigDS(NetconfNode node, NodeId nodeId, String topologyId,
+ private void writeToConfigDS(final NetconfNode node, final NodeId nodeId, final String topologyId,
final SettableFuture<RpcResult<Void>> futureResult) {
- WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+ final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
final InstanceIdentifier<NetworkTopology> networkTopologyId =
InstanceIdentifier.builder(NetworkTopology.class).build();
final InstanceIdentifier<NetconfNode> niid = networkTopologyId.child(Topology.class,
new TopologyKey(new TopologyId(topologyId))).child(Node.class,
new NodeKey(nodeId)).augmentation(NetconfNode.class);
writeTransaction.merge(LogicalDatastoreType.CONFIGURATION, niid, node, true);
- final CheckedFuture<Void, TransactionCommitFailedException> future = writeTransaction.submit();
+ final ListenableFuture<Void> future = writeTransaction.submit();
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
- public void onSuccess(Void result) {
+ public void onSuccess(final Void result) {
LOG.info("add-netconf-node RPC: Added netconf node successfully.");
futureResult.set(RpcResultBuilder.<Void>success().build());
}
@Override
- public void onFailure(Throwable exception) {
+ public void onFailure(final Throwable exception) {
LOG.error("add-netconf-node RPC: Unable to add netconf node.", exception);
futureResult.setException(exception);
}
}, MoreExecutors.directExecutor());
}
+
+ @Override
+ public Future<RpcResult<Void>> deleteDevice(final DeleteDeviceInput input) {
+ final NodeId nodeId = new NodeId(input.getNodeId());
+
+ final InstanceIdentifier<NetworkTopology> networkTopologyId =
+ InstanceIdentifier.builder(NetworkTopology.class).build();
+ final InstanceIdentifier<Node> niid = networkTopologyId.child(Topology.class,
+ new TopologyKey(new TopologyId(topologyId))).child(Node.class,
+ new NodeKey(nodeId));
+
+ final WriteTransaction wtx = dataBroker.newWriteOnlyTransaction();
+ wtx.delete(LogicalDatastoreType.CONFIGURATION, niid);
+
+ final ListenableFuture<Void> future = wtx.submit();
+ final SettableFuture<RpcResult<Void>> rpcFuture = SettableFuture.create();
+
+ Futures.addCallback(future, new FutureCallback<Void>() {
+
+ @Override
+ public void onSuccess(final Void result) {
+ LOG.info("delete-device RPC: Removed netconf node successfully.");
+ rpcFuture.set(RpcResultBuilder.<Void>success().build());
+ }
+
+ @Override
+ public void onFailure(final Throwable exception) {
+ LOG.error("delete-device RPC: Unable to remove netconf node.", exception);
+ rpcFuture.setException(exception);
+ }
+ }, MoreExecutors.directExecutor());
+
+ return rpcFuture;
+ }
}
--- /dev/null
+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
}
}
- grouping netconf-node-credentials {
+ grouping username-password {
+ leaf username {
+ type string;
+ }
+
+ leaf password {
+ type string;
+ }
+ }
+ grouping netconf-node-credentials {
choice credentials {
config true;
- case login-password {
- leaf username {
- type string;
+ case login-password-deprecated {
+ description "Deprecated way of storing credentials, unencrypted.";
+
+ status deprecated;
+ uses username-password;
+ }
+ case login-pw {
+ description "login-password credentials, encrypted.";
+
+
+ container login-password {
+ uses username-password;
}
+ }
+ case login-pw-unencrypted {
+ description "login-password credentials, not encrypted.";
- leaf password {
- type string;
+ container login-password-unencrypted {
+ uses username-password;
+ }
+ }
+ case key-auth {
+ description "key-based authentication, use the id for the pair thats stored in the keystore.";
+
+ container key-based {
+ leaf key-id {
+ type string;
+ }
+
+ leaf username {
+ type string;
+ }
}
}
}
}
- rpc add-netconf-node {
+ rpc create-device {
input {
uses netconf-node-fields;
leaf node-id {
type string;
}
- leaf encrypt {
- type boolean;
- default false;
+ }
+ }
+
+ rpc delete-device {
+ input {
+ leaf node-id {
+ type string;
}
}
}
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfReconnectingClientConfigurationBuilder;
-import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
.withAddress(new InetSocketAddress("localhost", 65000))
.withReconnectStrategy(reconnectStrategy)
.withConnectStrategyFactory(() -> reconnectStrategy)
- .withAuthHandler(new LoginPassword("admin", "admin"))
+ .withAuthHandler(new LoginPasswordHandler("admin", "admin"))
.withConnectionTimeoutMillis(10000)
.withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH)
.withSessionListener(listener)
package org.opendaylight.netconf.sal.connect.netconf.util;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
-import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.AddNetconfNodeInput;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.AddNetconfNodeInputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.CreateDeviceInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.CreateDeviceInputBuilder;
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.credentials.credentials.LoginPassword;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordBuilder;
+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.LoginPw;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencrypted;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPwUnencryptedBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.LoginPasswordBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.login.pw.unencrypted.LoginPasswordUnencryptedBuilder;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
public class NetconfTopologyRPCProviderTest {
@Mock
private AAAEncryptionService encryptionService;
- NetconfTopologyRPCProvider rpcProvider ;
+ private NetconfTopologyRPCProvider rpcProvider ;
@Before
public void setUp() {
@Test
public void testEncryptPassword() throws Exception {
- NetconfNode node = invokeEncryption(true);
- assertNotEquals(TEST_PWD, ((LoginPassword)node.getCredentials()).getPassword());
+ final NetconfNode encryptedPwNode = rpcProvider.encryptPassword(getInput(true));
- node = invokeEncryption(false);
- assertEquals(TEST_PWD, ((LoginPassword)node.getCredentials()).getPassword());
- }
-
- private NetconfNode invokeEncryption(boolean encrypt) throws Exception {
- Method method = null;
+ final Credentials credentials = encryptedPwNode.getCredentials();
+ assertTrue(credentials instanceof LoginPw);
+ final LoginPw loginPw = (LoginPw) credentials;
- method = NetconfTopologyRPCProvider.class.getDeclaredMethod("encryptPassword", AddNetconfNodeInput.class);
+ assertEquals(ENC_PWD, loginPw.getLoginPassword().getPassword());
+ }
- method.setAccessible(true);
- NetconfNode node = null;
+ @Test
+ public void testNoEncryption() throws Exception {
+ final NetconfNode encryptedPwNode = rpcProvider.encryptPassword(getInput(false));
- node = (NetconfNode)method.invoke(rpcProvider, getInput(encrypt));
+ final Credentials credentials = encryptedPwNode.getCredentials();
+ assertTrue(credentials instanceof LoginPwUnencrypted);
+ final LoginPwUnencrypted loginPw = (LoginPwUnencrypted) credentials;
- return node;
+ assertEquals(TEST_PWD, loginPw.getLoginPasswordUnencrypted().getPassword());
}
- private AddNetconfNodeInput getInput(boolean encrypt) {
- AddNetconfNodeInputBuilder builder = new AddNetconfNodeInputBuilder();
- builder.setCredentials(new LoginPasswordBuilder().setPassword(TEST_PWD).setUsername("test").build());
+ private CreateDeviceInput getInput(boolean encrypt) {
+ CreateDeviceInputBuilder builder = new CreateDeviceInputBuilder();
+ final Credentials credentials;
+ if (encrypt) {
+ credentials = new LoginPwBuilder().setLoginPassword(
+ new LoginPasswordBuilder().setUsername("test").setPassword(TEST_PWD).build()).build();
+ } else {
+ credentials = new LoginPwUnencryptedBuilder().setLoginPasswordUnencrypted(
+ new LoginPasswordUnencryptedBuilder().setUsername("test").setPassword(TEST_PWD).build()).build();
+ }
+
+ builder.setCredentials(credentials);
builder.setHost(new Host(new IpAddress(new Ipv4Address("10.18.16.188"))));
builder.setPort(new PortNumber(830));
builder.setTcpOnly(false);
builder.setNodeId(NODE_ID.toString());
- builder.setEncrypt(encrypt);
return builder.build();
}
import org.opendaylight.netconf.cli.io.ConsoleIOImpl;
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
-import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
/**
return parsed.getString(key);
}
- public LoginPassword getCredentials() {
- return new LoginPassword(getUsername(), getPassword());
+ public LoginPasswordHandler getCredentials() {
+ return new LoginPasswordHandler(getUsername(), getPassword());
}
public String getPassword() {
import org.opendaylight.netconf.cli.commands.output.OutputDefinition;
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
-import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
import org.opendaylight.protocol.framework.NeverReconnectStrategy;
import org.opendaylight.protocol.framework.ReconnectStrategy;
import org.opendaylight.yangtools.yang.common.QName;
return NetconfClientConfigurationBuilder.create().withAddress(inetAddress)
.withConnectionTimeoutMillis(connectionTimeout)
.withReconnectStrategy(strategy)
- .withAuthHandler(new LoginPassword(username, passwd))
+ .withAuthHandler(new LoginPasswordHandler(username, passwd))
.withProtocol(NetconfClientConfiguration.NetconfClientProtocol.SSH);
}
.setBindingAddress(bindingAddress)
.setLocalAddress(tcpLocalAddress)
.setAuthenticator((username, password) -> true)
+ .setPublickeyAuthenticator(((username, key, session) -> {
+ LOG.info("Auth with public key: {}", key);
+ return true;
+ }))
.setKeyPairProvider(keyPairProvider)
.setIdleTimeout(Integer.MAX_VALUE)
.createSshProxyServerConfiguration();
import org.opendaylight.netconf.client.NetconfClientSession;
import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
-import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPassword;
+import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
import org.opendaylight.netconf.sal.connect.api.RemoteDevice;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
}
netconfClientConfigurationBuilder.withProtocol(params.ssh ? NetconfClientConfiguration.NetconfClientProtocol.SSH
: NetconfClientConfiguration.NetconfClientProtocol.TCP);
- netconfClientConfigurationBuilder.withAuthHandler(new LoginPassword(params.username, params.password));
+ netconfClientConfigurationBuilder.withAuthHandler(new LoginPasswordHandler(params.username, params.password));
netconfClientConfigurationBuilder.withConnectionTimeoutMillis(20000L);
netconfClientConfigurationBuilder.withReconnectStrategy(
new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000));
@Override
public Optional<Document> getResponse(XmlElement rpcElement) {
LOG.info("getResponse: {}", rpcElement.toString());
- return null;
+ return Optional.empty();
}
}