71fe16f6812150c40a12430d6c14b413413ae2a2
[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 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.SslContextFactoryProvider;
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.netconf.transport.tls.FixedSslHandlerFactory;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.password.grouping.password.type.CleartextPasswordBuilder;
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.SshClientParametersBuilder;
27 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;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.ClientIdentity;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.ClientIdentityBuilder;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.client.identity.PasswordBuilder;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.connection.parameters.Protocol.Name;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.credentials.Credentials;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.credentials.credentials.KeyAuth;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.credentials.credentials.LoginPw;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.device.rev240120.credentials.credentials.LoginPwUnencrypted;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev231121.NetconfNode;
37 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
38 import org.osgi.service.component.annotations.Activate;
39 import org.osgi.service.component.annotations.Component;
40 import org.osgi.service.component.annotations.Reference;
41
42 /**
43  * Default implementation of NetconfClientConfigurationBuildFactory.
44  */
45 @Component
46 @Singleton
47 public final class NetconfClientConfigurationBuilderFactoryImpl implements NetconfClientConfigurationBuilderFactory {
48     private final SslContextFactoryProvider sslContextFactoryProvider;
49     private final AAAEncryptionService encryptionService;
50     private final CredentialProvider credentialProvider;
51
52     @Inject
53     @Activate
54     public NetconfClientConfigurationBuilderFactoryImpl(
55             @Reference final AAAEncryptionService encryptionService,
56             @Reference final CredentialProvider credentialProvider,
57             @Reference final SslContextFactoryProvider sslHandlerContextProvider) {
58         this.encryptionService = requireNonNull(encryptionService);
59         this.credentialProvider = requireNonNull(credentialProvider);
60         sslContextFactoryProvider = requireNonNull(sslHandlerContextProvider);
61     }
62
63     @Override
64     public NetconfClientConfigurationBuilder createClientConfigurationBuilder(final NodeId nodeId,
65             final NetconfNode node) {
66         final var builder = NetconfClientConfigurationBuilder.create();
67         final var protocol = node.getProtocol();
68         if (node.requireTcpOnly()) {
69             builder.withProtocol(NetconfClientProtocol.TCP);
70         } else if (protocol == null || protocol.getName() == Name.SSH) {
71             builder.withProtocol(NetconfClientProtocol.SSH);
72             setSshParametersFromCredentials(builder, node.getCredentials());
73         } else if (protocol.getName() == Name.TLS) {
74             final var handlerFactory = sslContextFactoryProvider.getSslContextFactory(protocol.getSpecification());
75             final var sslContext = handlerFactory.createSslContext();
76             builder.withProtocol(NetconfClientProtocol.TLS)
77                 .withSslHandlerFactory(new FixedSslHandlerFactory(sslContext));
78         } else {
79             throw new IllegalArgumentException("Unsupported protocol type: " + protocol.getName());
80         }
81
82         final var helloCapabilities = node.getOdlHelloMessageCapabilities();
83         if (helloCapabilities != null) {
84             builder.withOdlHelloCapabilities(List.copyOf(helloCapabilities.requireCapability()));
85         }
86
87         return builder
88             .withName(nodeId.getValue())
89             .withTcpParameters(new TcpClientParametersBuilder()
90                 .setRemoteAddress(node.requireHost())
91                 .setRemotePort(node.requirePort()).build())
92             .withConnectionTimeoutMillis(node.requireConnectionTimeoutMillis().toJava());
93     }
94
95     private void setSshParametersFromCredentials(final NetconfClientConfigurationBuilder confBuilder,
96             final Credentials credentials) {
97         final var sshParamsBuilder = new SshClientParametersBuilder();
98         if (credentials instanceof LoginPwUnencrypted unencrypted) {
99             final var loginPassword = unencrypted.getLoginPasswordUnencrypted();
100             sshParamsBuilder.setClientIdentity(loginPasswordIdentity(
101                 loginPassword.getUsername(), loginPassword.getPassword()));
102         } else if (credentials instanceof LoginPw loginPw) {
103             final var loginPassword = loginPw.getLoginPassword();
104             final var username = loginPassword.getUsername();
105
106             final byte[] plainBytes;
107             try {
108                 plainBytes = encryptionService.decrypt(loginPassword.getPassword());
109             } catch (GeneralSecurityException e) {
110                 throw new IllegalStateException("Failed to decrypt password", e);
111             }
112
113             sshParamsBuilder.setClientIdentity(loginPasswordIdentity(username,
114                 new String(plainBytes, StandardCharsets.UTF_8)));
115         } else if (credentials instanceof KeyAuth keyAuth) {
116             final var keyBased = keyAuth.getKeyBased();
117             sshParamsBuilder.setClientIdentity(new ClientIdentityBuilder().setUsername(keyBased.getUsername()).build());
118             confBuilder.withSshConfigurator(factoryMgr -> {
119                 final var keyId = keyBased.getKeyId();
120                 final var keyPair = credentialProvider.credentialForId(keyId);
121                 if (keyPair == null) {
122                     throw new IllegalArgumentException("No keypair found with keyId=" + keyId);
123                 }
124                 factoryMgr.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keyPair));
125                 final var factory = new UserAuthPublicKeyFactory();
126                 factory.setSignatureFactories(factoryMgr.getSignatureFactories());
127                 factoryMgr.setUserAuthFactories(List.of(factory));
128             });
129         } else {
130             throw new IllegalArgumentException("Unsupported credential type: " + credentials.getClass());
131         }
132         confBuilder.withSshParameters(sshParamsBuilder.build());
133     }
134
135     private static ClientIdentity loginPasswordIdentity(final String username, final String password) {
136         return new ClientIdentityBuilder()
137             .setUsername(requireNonNull(username, "username is undefined"))
138             .setPassword(new PasswordBuilder()
139                 .setPasswordType(new CleartextPasswordBuilder()
140                     .setCleartextPassword(requireNonNull(password, "password is undefined"))
141                     .build())
142                 .build())
143             .build();
144     }
145 }