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.aaa.encrypt.impl;
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;
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;
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.
44 public final class AAAEncryptionServiceConfigurator implements EncryptServiceConfig {
45 private static final Logger LOG = LoggerFactory.getLogger(AAAEncryptionServiceConfigurator.class);
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();
53 private final EncryptServiceConfig delegate;
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);
61 // Update initial configuration and config datastore
62 updateEncrySrvConfig(generatedConfig.requireEncryptKey(), generatedConfig.requireEncryptSalt());
63 updateDatastore(dataBroker, generatedConfig);
65 delegate = generatedConfig;
67 delegate = blueprintConfig;
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);
76 return new AaaEncryptServiceConfigBuilder(blueprintConfig)
77 .setEncryptKey(RandomStringUtils.random(blueprintConfig.requirePasswordLength(), true, true))
78 .setEncryptSalt(Base64.getEncoder().encodeToString(salt))
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);
86 final var tx = dataBroker.newReadWriteTransaction();
87 final var existsFuture = tx.exists(LogicalDatastoreType.CONFIGURATION, iid);
91 exists = existsFuture.get();
92 } catch (InterruptedException | ExecutionException e) {
94 LOG.error("Failed to read configuration", e);
99 LOG.info("Configuration already present, skipping update");
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>() {
108 public void onFailure(final Throwable throwable) {
109 LOG.error("initDatastore: configuration data populated: {}", iid);
113 public void onSuccess(final CommitInfo result) {
114 LOG.info("initDatastore: transaction succeeded");
116 }, MoreExecutors.directExecutor());
119 private static void updateEncrySrvConfig(final String newPwd, final String newSalt) {
120 LOG.debug("Update encryption service config file");
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)));
132 LOG.warn("The encryption service config file does not exist {}", DEFAULT_CONFIG_FILE_PATH);
134 } catch (ParserConfigurationException | TransformerException | SAXException | IOException e) {
135 LOG.error("Error while updating the encryption service config file", e);
140 public Class<? extends EncryptServiceConfig> implementedInterface() {
141 throw new UnsupportedOperationException();
145 public String getEncryptKey() {
146 return delegate.getEncryptKey();
150 public Integer getPasswordLength() {
151 return delegate.getPasswordLength();
155 public String getEncryptSalt() {
156 return delegate.getEncryptSalt();
160 public String getEncryptMethod() {
161 return delegate.getEncryptMethod();
165 public String getEncryptType() {
166 return delegate.getEncryptType();
170 public Integer getEncryptIterationCount() {
171 return delegate.getEncryptIterationCount();
175 public Integer getEncryptKeyLength() {
176 return delegate.getEncryptKeyLength();
180 public String getCipherTransforms() {
181 return delegate.getCipherTransforms();