Split out AAAEncryptionServiceConfigurator
[aaa.git] / aaa-encrypt-service / impl / src / main / java / org / opendaylight / aaa / encrypt / impl / AAAEncryptionServiceConfigurator.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.aaa.encrypt.impl;
9
10 import com.google.common.base.Strings;
11 import com.google.common.util.concurrent.FutureCallback;
12 import com.google.common.util.concurrent.Futures;
13 import com.google.common.util.concurrent.MoreExecutors;
14 import java.io.File;
15 import java.io.IOException;
16 import java.security.SecureRandom;
17 import java.util.Base64;
18 import java.util.concurrent.ExecutionException;
19 import javax.xml.parsers.DocumentBuilderFactory;
20 import javax.xml.parsers.ParserConfigurationException;
21 import javax.xml.transform.TransformerException;
22 import javax.xml.transform.TransformerFactory;
23 import javax.xml.transform.dom.DOMSource;
24 import javax.xml.transform.stream.StreamResult;
25 import org.apache.commons.lang3.RandomStringUtils;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.opendaylight.mdsal.binding.api.DataBroker;
28 import org.opendaylight.mdsal.common.api.CommitInfo;
29 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
30 import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
31 import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfigBuilder;
32 import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.EncryptServiceConfig;
33 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.w3c.dom.Document;
37 import org.w3c.dom.Node;
38 import org.xml.sax.SAXException;
39
40 /**
41  * Intermediate component dealing with establishing initial configuration for {@link AAAEncryptionServiceImpl}. In
42  * particular it deals with generating and persisting of encryption salt and encryption password.
43  */
44 public final class AAAEncryptionServiceConfigurator implements EncryptServiceConfig {
45     private static final Logger LOG = LoggerFactory.getLogger(AAAEncryptionServiceConfigurator.class);
46
47     // Note: this is a strong binding to Blueprint, which is loading etc/opendaylight/datastore/initial/config
48     private static final String DEFAULT_CONFIG_FILE_PATH = "etc" + File.separator + "opendaylight" + File.separator
49         + "datastore" + File.separator + "initial" + File.separator + "config" + File.separator
50         + "aaa-encrypt-service-config.xml";
51     private static final SecureRandom RANDOM = new SecureRandom();
52
53     private final EncryptServiceConfig delegate;
54
55     public AAAEncryptionServiceConfigurator(final DataBroker dataBroker,
56             final AaaEncryptServiceConfig blueprintConfig) {
57         if (Strings.isNullOrEmpty(blueprintConfig.getEncryptSalt())
58             || Strings.isNullOrEmpty(blueprintConfig.getEncryptKey())) {
59             final var generatedConfig = generateConfig(blueprintConfig);
60
61             // Update initial configuration and config datastore
62             updateEncrySrvConfig(generatedConfig.requireEncryptKey(), generatedConfig.requireEncryptSalt());
63             updateDatastore(dataBroker, generatedConfig);
64
65             delegate = generatedConfig;
66         } else {
67             delegate = blueprintConfig;
68         }
69     }
70
71     private static @NonNull AaaEncryptServiceConfig generateConfig(final EncryptServiceConfig blueprintConfig) {
72         LOG.debug("Set the Encryption service password and encrypt salt");
73         final var salt = new byte[16];
74         RANDOM.nextBytes(salt);
75
76         return new AaaEncryptServiceConfigBuilder(blueprintConfig)
77             .setEncryptKey(RandomStringUtils.random(blueprintConfig.requirePasswordLength(), true, true))
78             .setEncryptSalt(Base64.getEncoder().encodeToString(salt))
79             .build();
80     }
81
82     // FIXME: Update configuration datastore, but only if not present?! this looks weird lifecycle-wise
83     private static void updateDatastore(final DataBroker dataBroker, final @NonNull AaaEncryptServiceConfig config) {
84         final var iid = InstanceIdentifier.create(AaaEncryptServiceConfig.class);
85
86         final var tx = dataBroker.newReadWriteTransaction();
87         final var existsFuture = tx.exists(LogicalDatastoreType.CONFIGURATION, iid);
88
89         final boolean exists;
90         try {
91             exists = existsFuture.get();
92         } catch (InterruptedException | ExecutionException e) {
93             tx.cancel();
94             LOG.error("Failed to read configuration", e);
95             return;
96         }
97         if (exists) {
98             tx.cancel();
99             LOG.info("Configuration already present, skipping update");
100             return;
101         }
102
103         LOG.debug("Populating configuration: {}, {}", iid, config);
104         tx.put(LogicalDatastoreType.CONFIGURATION, iid, config);
105         // Perform the transaction.submit asynchronously
106         Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
107             @Override
108             public void onFailure(final Throwable throwable) {
109                 LOG.error("initDatastore: configuration data populated: {}", iid);
110             }
111
112             @Override
113             public void onSuccess(final CommitInfo result) {
114                 LOG.info("initDatastore: transaction succeeded");
115             }
116         }, MoreExecutors.directExecutor());
117     }
118
119     private static void updateEncrySrvConfig(final String newPwd, final String newSalt) {
120         LOG.debug("Update encryption service config file");
121         try {
122             final File configFile = new File(DEFAULT_CONFIG_FILE_PATH);
123             if (configFile.exists()) {
124                 final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(configFile);
125                 final Node keyNode = doc.getElementsByTagName("encrypt-key").item(0);
126                 keyNode.setTextContent(newPwd);
127                 final Node salt = doc.getElementsByTagName("encrypt-salt").item(0);
128                 salt.setTextContent(newSalt);
129                 TransformerFactory.newInstance().newTransformer().transform(new DOMSource(doc),
130                     new StreamResult(new File(DEFAULT_CONFIG_FILE_PATH)));
131             } else {
132                 LOG.warn("The encryption service config file does not exist {}", DEFAULT_CONFIG_FILE_PATH);
133             }
134         } catch (ParserConfigurationException | TransformerException | SAXException | IOException e) {
135             LOG.error("Error while updating the encryption service config file", e);
136         }
137     }
138
139     @Override
140     public Class<? extends EncryptServiceConfig> implementedInterface() {
141         throw new UnsupportedOperationException();
142     }
143
144     @Override
145     public String getEncryptKey() {
146         return delegate.getEncryptKey();
147     }
148
149     @Override
150     public Integer getPasswordLength() {
151         return delegate.getPasswordLength();
152     }
153
154     @Override
155     public String getEncryptSalt() {
156         return delegate.getEncryptSalt();
157     }
158
159     @Override
160     public String getEncryptMethod() {
161         return delegate.getEncryptMethod();
162     }
163
164     @Override
165     public String getEncryptType() {
166         return delegate.getEncryptType();
167     }
168
169     @Override
170     public Integer getEncryptIterationCount() {
171         return delegate.getEncryptIterationCount();
172     }
173
174     @Override
175     public Integer getEncryptKeyLength() {
176         return delegate.getEncryptKeyLength();
177     }
178
179     @Override
180     public String getCipherTransforms() {
181         return delegate.getCipherTransforms();
182     }
183 }