Migrate netconf-topology to new transport
[netconf.git] / apps / netconf-topology / src / main / java / org / opendaylight / netconf / topology / spi / NetconfClientConfigurationBuilderFactoryImpl.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.topology.spi;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Strings;
13 import java.io.IOException;
14 import java.io.StringReader;
15 import java.security.KeyPair;
16 import java.util.List;
17 import javax.inject.Inject;
18 import javax.inject.Singleton;
19 import org.opendaylight.aaa.encrypt.AAAEncryptionService;
20 import org.opendaylight.aaa.encrypt.PKIUtil;
21 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
22 import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
23 import org.opendaylight.netconf.client.mdsal.api.CredentialProvider;
24 import org.opendaylight.netconf.client.mdsal.api.SslHandlerFactoryProvider;
25 import org.opendaylight.netconf.shaded.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
26 import org.opendaylight.netconf.shaded.sshd.common.keyprovider.KeyIdentityProvider;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev230417.password.grouping.password.type.CleartextPasswordBuilder;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.SshClientParametersBuilder;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev230417.netconf.client.initiate.stack.grouping.transport.ssh.ssh.TcpClientParametersBuilder;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.ClientIdentity;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.ClientIdentityBuilder;
32 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev230417.ssh.client.grouping.client.identity.PasswordBuilder;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.connection.parameters.Protocol.Name;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.Credentials;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.KeyAuth;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.LoginPw;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev231025.credentials.credentials.LoginPwUnencrypted;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev221225.NetconfNode;
39 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Reference;
43
44 /**
45  * Default implementation of NetconfClientConfigurationBuildFactory.
46  */
47 @Component(service = NetconfClientConfigurationBuilderFactory.class, property = "type=default")
48 @Singleton
49 public final class NetconfClientConfigurationBuilderFactoryImpl implements NetconfClientConfigurationBuilderFactory {
50     private final SslHandlerFactoryProvider sslHandlerFactoryProvider;
51     private final AAAEncryptionService encryptionService;
52     private final CredentialProvider credentialProvider;
53
54     @Inject
55     @Activate
56     public NetconfClientConfigurationBuilderFactoryImpl(
57         @Reference final AAAEncryptionService encryptionService,
58         @Reference final CredentialProvider credentialProvider,
59         @Reference final SslHandlerFactoryProvider sslHandlerFactoryProvider) {
60         this.encryptionService = requireNonNull(encryptionService);
61         this.credentialProvider = requireNonNull(credentialProvider);
62         this.sslHandlerFactoryProvider = requireNonNull(sslHandlerFactoryProvider);
63     }
64
65     @Override
66     public NetconfClientConfigurationBuilder createClientConfigurationBuilder(final NodeId nodeId,
67         final NetconfNode node) {
68         final var builder = NetconfClientConfigurationBuilder.create();
69         final var protocol = node.getProtocol();
70         if (node.requireTcpOnly()) {
71             builder.withProtocol(NetconfClientProtocol.TCP);
72         } else if (protocol == null || protocol.getName() == Name.SSH) {
73             builder.withProtocol(NetconfClientProtocol.SSH);
74             setSshParametersFromCredentials(builder, node.getCredentials());
75         } else if (protocol.getName() == Name.TLS) {
76             builder.withProtocol(NetconfClientProtocol.TLS).withTransportSslHandlerFactory(channel -> {
77                 final var sslHandlerBuilder =
78                     sslHandlerFactoryProvider.getSslHandlerFactory(protocol.getSpecification());
79                 return sslHandlerBuilder.createSslHandler();
80             });
81         } else {
82             throw new IllegalArgumentException("Unsupported protocol type: " + protocol.getName());
83         }
84
85         final var helloCapabilities = node.getOdlHelloMessageCapabilities();
86         if (helloCapabilities != null) {
87             builder.withOdlHelloCapabilities(List.copyOf(helloCapabilities.requireCapability()));
88         }
89
90         return builder
91             .withName(nodeId.getValue())
92             .withTcpParameters(new TcpClientParametersBuilder()
93                 .setRemoteAddress(node.requireHost())
94                 .setRemotePort(node.requirePort()).build())
95             .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava());
96     }
97
98     private void setSshParametersFromCredentials(final NetconfClientConfigurationBuilder confBuilder,
99         final Credentials credentials) {
100         final var sshParamsBuilder = new SshClientParametersBuilder();
101         if (credentials instanceof LoginPwUnencrypted unencrypted) {
102             final var loginPassword = unencrypted.getLoginPasswordUnencrypted();
103             sshParamsBuilder.setClientIdentity(loginPasswordIdentity(
104                 loginPassword.getUsername(), loginPassword.getPassword()));
105
106         } else if (credentials instanceof LoginPw loginPw) {
107             final var loginPassword = loginPw.getLoginPassword();
108             sshParamsBuilder.setClientIdentity(loginPasswordIdentity(
109                 loginPassword.getUsername(), encryptionService.decrypt(loginPassword.getPassword())));
110
111         } else if (credentials instanceof KeyAuth keyAuth) {
112             final var keyBased = keyAuth.getKeyBased();
113             sshParamsBuilder.setClientIdentity(new ClientIdentityBuilder().setUsername(keyBased.getUsername()).build());
114             confBuilder.withSshConfigurator(factoryMgr -> {
115                 final var keyPair = getKeyPair(keyBased.getKeyId(), credentialProvider, encryptionService);
116                 factoryMgr.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keyPair));
117                 final var factory = new UserAuthPublicKeyFactory();
118                 factory.setSignatureFactories(factoryMgr.getSignatureFactories());
119                 factoryMgr.setUserAuthFactories(List.of(factory));
120             });
121         } else {
122             throw new IllegalArgumentException("Unsupported credential type: " + credentials.getClass());
123         }
124         confBuilder.withSshParameters(sshParamsBuilder.build());
125     }
126
127     private static ClientIdentity loginPasswordIdentity(final String username, final String password) {
128         requireNonNull(username, "username is undefined");
129         requireNonNull(password, "password is undefined");
130         return new ClientIdentityBuilder()
131             .setUsername(username)
132             .setPassword(new PasswordBuilder()
133                 .setPasswordType(new CleartextPasswordBuilder().setCleartextPassword(password).build())
134                 .build())
135             .build();
136     }
137
138     private static KeyPair getKeyPair(final String keyId,
139         final CredentialProvider credentialProvider, final AAAEncryptionService encryptionService) {
140
141         // public key retrieval logic taken from DatastoreBackedPublicKeyAuth
142         final var dsKeypair = credentialProvider.credentialForId(keyId);
143         if (dsKeypair == null) {
144             throw new IllegalArgumentException("No keypair found with keyId=" + keyId);
145         }
146         final var passPhrase = Strings.isNullOrEmpty(dsKeypair.getPassphrase()) ? "" : dsKeypair.getPassphrase();
147         try {
148             return new PKIUtil().decodePrivateKey(
149                 new StringReader(encryptionService.decrypt(dsKeypair.getPrivateKey()).replace("\\n", "\n")),
150                 encryptionService.decrypt(passPhrase));
151         } catch (IOException e) {
152             throw new IllegalStateException("Could not decode private key with keyId=" + keyId, e);
153         }
154     }
155 }