2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.topology.spi;
10 import static java.util.Objects.requireNonNull;
12 import java.nio.charset.StandardCharsets;
13 import java.security.GeneralSecurityException;
14 import java.util.List;
15 import javax.inject.Inject;
16 import javax.inject.Singleton;
17 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
18 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
19 import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
20 import org.opendaylight.netconf.client.mdsal.api.CredentialProvider;
21 import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
22 import org.opendaylight.netconf.shaded.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
23 import org.opendaylight.netconf.shaded.sshd.common.keyprovider.KeyIdentityProvider;
24 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.password.grouping.password.type.CleartextPasswordBuilder;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev231228.netconf.client.initiate.stack.grouping.transport.ssh.ssh.SshClientParametersBuilder;
26 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev231228.netconf.client.initiate.stack.grouping.transport.ssh.ssh.TcpClientParametersBuilder;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.ClientIdentity;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.ClientIdentityBuilder;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.client.identity.PasswordBuilder;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.parameters.Protocol.Name;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.credentials.Credentials;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.credentials.credentials.KeyAuth;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.credentials.credentials.LoginPw;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.credentials.credentials.LoginPwUnencrypted;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev231121.NetconfNode;
36 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
37 import org.osgi.service.component.annotations.Activate;
38 import org.osgi.service.component.annotations.Component;
39 import org.osgi.service.component.annotations.Reference;
42 * Default implementation of NetconfClientConfigurationBuildFactory.
46 public final class NetconfClientConfigurationBuilderFactoryImpl implements NetconfClientConfigurationBuilderFactory {
47 private final SslHandlerFactoryProvider sslHandlerFactoryProvider;
48 private final AAAEncryptionService encryptionService;
49 private final CredentialProvider credentialProvider;
53 public NetconfClientConfigurationBuilderFactoryImpl(
54 @Reference final AAAEncryptionService encryptionService,
55 @Reference final CredentialProvider credentialProvider,
56 @Reference final SslHandlerFactoryProvider sslHandlerFactoryProvider) {
57 this.encryptionService = requireNonNull(encryptionService);
58 this.credentialProvider = requireNonNull(credentialProvider);
59 this.sslHandlerFactoryProvider = requireNonNull(sslHandlerFactoryProvider);
63 public NetconfClientConfigurationBuilder createClientConfigurationBuilder(final NodeId nodeId,
64 final NetconfNode node) {
65 final var builder = NetconfClientConfigurationBuilder.create();
66 final var protocol = node.getProtocol();
67 if (node.requireTcpOnly()) {
68 builder.withProtocol(NetconfClientProtocol.TCP);
69 } else if (protocol == null || protocol.getName() == Name.SSH) {
70 builder.withProtocol(NetconfClientProtocol.SSH);
71 setSshParametersFromCredentials(builder, node.getCredentials());
72 } else if (protocol.getName() == Name.TLS) {
73 final var handlerFactory = sslHandlerFactoryProvider.getSslHandlerFactory(protocol.getSpecification());
74 builder.withProtocol(NetconfClientProtocol.TLS)
75 .withSslHandlerFactory(channel -> handlerFactory.createSslHandler());
77 throw new IllegalArgumentException("Unsupported protocol type: " + protocol.getName());
80 final var helloCapabilities = node.getOdlHelloMessageCapabilities();
81 if (helloCapabilities != null) {
82 builder.withOdlHelloCapabilities(List.copyOf(helloCapabilities.requireCapability()));
86 .withName(nodeId.getValue())
87 .withTcpParameters(new TcpClientParametersBuilder()
88 .setRemoteAddress(node.requireHost())
89 .setRemotePort(node.requirePort()).build())
90 .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava());
93 private void setSshParametersFromCredentials(final NetconfClientConfigurationBuilder confBuilder,
94 final Credentials credentials) {
95 final var sshParamsBuilder = new SshClientParametersBuilder();
96 if (credentials instanceof LoginPwUnencrypted unencrypted) {
97 final var loginPassword = unencrypted.getLoginPasswordUnencrypted();
98 sshParamsBuilder.setClientIdentity(loginPasswordIdentity(
99 loginPassword.getUsername(), loginPassword.getPassword()));
100 } else if (credentials instanceof LoginPw loginPw) {
101 final var loginPassword = loginPw.getLoginPassword();
102 final var username = loginPassword.getUsername();
104 final byte[] plainBytes;
106 plainBytes = encryptionService.decrypt(loginPassword.getPassword());
107 } catch (GeneralSecurityException e) {
108 throw new IllegalStateException("Failed to decrypt password", e);
111 sshParamsBuilder.setClientIdentity(loginPasswordIdentity(username,
112 new String(plainBytes, StandardCharsets.UTF_8)));
113 } else if (credentials instanceof KeyAuth keyAuth) {
114 final var keyBased = keyAuth.getKeyBased();
115 sshParamsBuilder.setClientIdentity(new ClientIdentityBuilder().setUsername(keyBased.getUsername()).build());
116 confBuilder.withSshConfigurator(factoryMgr -> {
117 final var keyId = keyBased.getKeyId();
118 final var keyPair = credentialProvider.credentialForId(keyId);
119 if (keyPair == null) {
120 throw new IllegalArgumentException("No keypair found with keyId=" + keyId);
122 factoryMgr.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keyPair));
123 final var factory = new UserAuthPublicKeyFactory();
124 factory.setSignatureFactories(factoryMgr.getSignatureFactories());
125 factoryMgr.setUserAuthFactories(List.of(factory));
128 throw new IllegalArgumentException("Unsupported credential type: " + credentials.getClass());
130 confBuilder.withSshParameters(sshParamsBuilder.build());
133 private static ClientIdentity loginPasswordIdentity(final String username, final String password) {
134 return new ClientIdentityBuilder()
135 .setUsername(requireNonNull(username, "username is undefined"))
136 .setPassword(new PasswordBuilder()
137 .setPasswordType(new CleartextPasswordBuilder()
138 .setCleartextPassword(requireNonNull(password, "password is undefined"))