<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>logging-markers</artifactId>
+ </dependency>
<dependency>
<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-common</artifactId>
<groupId>org.opendaylight.aaa</groupId>
<artifactId>aaa-encrypt-service</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.framework</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.component</artifactId>
+ </dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.component.annotations</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
-
- <!-- FIXME: AAA-204: remove all this once we do not have Blueprint -->
- <build>
- <plugins>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- <executions>
- <execution>
- <id>attach-artifacts</id>
- <goals>
- <goal>attach-artifact</goal>
- </goals>
- <phase>package</phase>
- <configuration>
- <artifacts>
- <artifact>
- <file>${project.build.directory}/classes/initial/aaa-encrypt-service-config.xml</file>
- <type>xml</type>
- <classifier>config</classifier>
- </artifact>
- </artifacts>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <extensions>true</extensions>
- <configuration>
- <instructions>
- <!-- Mixed OSGi DS/Blueprint services, we have to override to get Blueprint bits here -->
- <Provide-Capability>
- osgi.service;objectClass:List<String>="org.opendaylight.aaa.encrypt.AAAEncryptionService";uses:="org.opendaylight.aaa.encrypt",
- osgi.service;objectClass:List<String>="org.opendaylight.aaa.encrypt.impl.EncryptServiceConfigSupplier";uses:="org.opendaylight.aaa.encrypt.impl"
- </Provide-Capability>
- </instructions>
- </configuration>
- </plugin>
- </plugins>
- </build>
</project>
+++ /dev/null
-/*
- * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.aaa.encrypt.impl;
-
-import com.google.common.base.Strings;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.MoreExecutors;
-import java.io.File;
-import java.io.IOException;
-import java.security.SecureRandom;
-import java.util.Base64;
-import java.util.concurrent.ExecutionException;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import org.apache.commons.lang3.RandomStringUtils;
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.binding.api.DataBroker;
-import org.opendaylight.mdsal.common.api.CommitInfo;
-import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
-import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfigBuilder;
-import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.EncryptServiceConfig;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.xml.sax.SAXException;
-
-/**
- * Intermediate component dealing with establishing initial configuration for {@link AAAEncryptionServiceImpl}. In
- * particular it deals with generating and persisting of encryption salt and encryption password.
- */
-public final class AAAEncryptionServiceConfigurator implements EncryptServiceConfigSupplier, EncryptServiceConfig {
- private static final Logger LOG = LoggerFactory.getLogger(AAAEncryptionServiceConfigurator.class);
-
- // Note: this is a strong binding to Blueprint, which is loading etc/opendaylight/datastore/initial/config
- private static final String DEFAULT_CONFIG_FILE_PATH = "etc" + File.separator + "opendaylight" + File.separator
- + "datastore" + File.separator + "initial" + File.separator + "config" + File.separator
- + "aaa-encrypt-service-config.xml";
- private static final SecureRandom RANDOM = new SecureRandom();
-
- private final AaaEncryptServiceConfig delegate;
-
- public AAAEncryptionServiceConfigurator(final DataBroker dataBroker,
- final AaaEncryptServiceConfig blueprintConfig) {
- if (Strings.isNullOrEmpty(blueprintConfig.getEncryptSalt())
- || Strings.isNullOrEmpty(blueprintConfig.getEncryptKey())) {
- final var generatedConfig = generateConfig(blueprintConfig);
-
- // Update initial configuration and config datastore
- updateEncrySrvConfig(generatedConfig.requireEncryptKey(), generatedConfig.requireEncryptSalt());
- updateDatastore(dataBroker, generatedConfig);
-
- delegate = generatedConfig;
- } else {
- delegate = blueprintConfig;
- }
- }
-
- private static @NonNull AaaEncryptServiceConfig generateConfig(final AaaEncryptServiceConfig blueprintConfig) {
- LOG.debug("Set the Encryption service password and encrypt salt");
- final var salt = new byte[16];
- RANDOM.nextBytes(salt);
-
- return new AaaEncryptServiceConfigBuilder(blueprintConfig)
- .setEncryptKey(RandomStringUtils.random(blueprintConfig.requirePasswordLength(), true, true))
- .setEncryptSalt(Base64.getEncoder().encodeToString(salt))
- .build();
- }
-
- // FIXME: Update configuration datastore, but only if not present?! this looks weird lifecycle-wise
- private static void updateDatastore(final DataBroker dataBroker, final @NonNull AaaEncryptServiceConfig config) {
- final var iid = InstanceIdentifier.create(AaaEncryptServiceConfig.class);
-
- final var tx = dataBroker.newReadWriteTransaction();
- final var existsFuture = tx.exists(LogicalDatastoreType.CONFIGURATION, iid);
-
- final boolean exists;
- try {
- exists = existsFuture.get();
- } catch (InterruptedException | ExecutionException e) {
- tx.cancel();
- LOG.error("Failed to read configuration", e);
- return;
- }
- if (exists) {
- tx.cancel();
- LOG.info("Configuration already present, skipping update");
- return;
- }
-
- LOG.debug("Populating configuration: {}, {}", iid, config);
- tx.put(LogicalDatastoreType.CONFIGURATION, iid, config);
- // Perform the transaction.submit asynchronously
- Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
- @Override
- public void onFailure(final Throwable throwable) {
- LOG.error("initDatastore: configuration data populated: {}", iid);
- }
-
- @Override
- public void onSuccess(final CommitInfo result) {
- LOG.info("initDatastore: transaction succeeded");
- }
- }, MoreExecutors.directExecutor());
- }
-
- private static void updateEncrySrvConfig(final String newPwd, final String newSalt) {
- LOG.debug("Update encryption service config file");
- try {
- final File configFile = new File(DEFAULT_CONFIG_FILE_PATH);
- if (configFile.exists()) {
- final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(configFile);
- final Node keyNode = doc.getElementsByTagName("encrypt-key").item(0);
- keyNode.setTextContent(newPwd);
- final Node salt = doc.getElementsByTagName("encrypt-salt").item(0);
- salt.setTextContent(newSalt);
- TransformerFactory.newInstance().newTransformer().transform(new DOMSource(doc),
- new StreamResult(new File(DEFAULT_CONFIG_FILE_PATH)));
- } else {
- LOG.warn("The encryption service config file does not exist {}", DEFAULT_CONFIG_FILE_PATH);
- }
- } catch (ParserConfigurationException | TransformerException | SAXException | IOException e) {
- LOG.error("Error while updating the encryption service config file", e);
- }
- }
-
- @Override
- public EncryptServiceConfig get() {
- return this;
- }
-
- @Override
- public Class<? extends EncryptServiceConfig> implementedInterface() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getEncryptKey() {
- return delegate.requireEncryptKey();
- }
-
- @Override
- public byte[] getEncryptSalt() {
- return Base64.getDecoder().decode(delegate.requireEncryptSalt());
- }
-
- @Override
- public String getEncryptMethod() {
- return delegate.getEncryptMethod();
- }
-
- @Override
- public String getEncryptType() {
- return delegate.getEncryptType();
- }
-
- @Override
- public Integer getEncryptIterationCount() {
- return delegate.getEncryptIterationCount();
- }
-
- @Override
- public Integer getEncryptKeyLength() {
- return delegate.getEncryptKeyLength();
- }
-
- @Override
- public String getCipherTransforms() {
- return delegate.getCipherTransforms();
- }
-}
*/
package org.opendaylight.aaa.encrypt.impl;
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
+import java.util.Map;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.opendaylight.aaa.encrypt.AAAEncryptionService;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.EncryptServiceConfig;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
-import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
* @author - Sharon Aicler (saichler@gmail.com)
*/
@Deprecated
-@Component(immediate = true)
+@Component(factory = AAAEncryptionServiceImpl.FACTORY_NAME)
public final class AAAEncryptionServiceImpl implements AAAEncryptionService {
+ static final String FACTORY_NAME = "org.opendaylight.aaa.encrypt.impl.AAAEncryptionServiceImpl";
+
private static final Logger LOG = LoggerFactory.getLogger(AAAEncryptionServiceImpl.class);
+ private static final String CONFIG_PROP = ".config";
private final SecretKey key;
private final Cipher encryptCipher;
private final Cipher decryptCipher;
- @Activate
- public AAAEncryptionServiceImpl(@Reference final EncryptServiceConfigSupplier configProvider) {
- final var encrySrvConfig = configProvider.get();
-
- final byte[] encryptionKeySalt = encrySrvConfig.requireEncryptSalt();
+ public AAAEncryptionServiceImpl(final EncryptServiceConfig configuration) {
+ final byte[] encryptionKeySalt = configuration.requireEncryptSalt();
IvParameterSpec tempIvSpec = null;
SecretKey tempKey = null;
try {
- final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encrySrvConfig.getEncryptMethod());
- final KeySpec spec = new PBEKeySpec(encrySrvConfig.requireEncryptKey().toCharArray(), encryptionKeySalt,
- encrySrvConfig.getEncryptIterationCount(), encrySrvConfig.getEncryptKeyLength());
- tempKey = new SecretKeySpec(keyFactory.generateSecret(spec).getEncoded(), encrySrvConfig.getEncryptType());
+ final SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(configuration.getEncryptMethod());
+ final KeySpec spec = new PBEKeySpec(configuration.requireEncryptKey().toCharArray(), encryptionKeySalt,
+ configuration.getEncryptIterationCount(), configuration.getEncryptKeyLength());
+ tempKey = new SecretKeySpec(keyFactory.generateSecret(spec).getEncoded(), configuration.getEncryptType());
tempIvSpec = new IvParameterSpec(encryptionKeySalt);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
LOG.error("Failed to initialize secret key", e);
final var ivSpec = tempIvSpec;
Cipher cipher = null;
try {
- cipher = Cipher.getInstance(encrySrvConfig.getCipherTransforms());
+ cipher = Cipher.getInstance(configuration.getCipherTransforms());
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException
| InvalidKeyException e) {
encryptCipher = cipher;
cipher = null;
try {
- cipher = Cipher.getInstance(encrySrvConfig.getCipherTransforms());
+ cipher = Cipher.getInstance(configuration.getCipherTransforms());
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException
| InvalidKeyException e) {
LOG.info("AAAEncryptionService activated");
}
+ @Activate
+ public AAAEncryptionServiceImpl(final Map<String, ?> properties) {
+ this((EncryptServiceConfig) verifyNotNull(properties.get(CONFIG_PROP)));
+ }
+
+ static Map<String, ?> props(final EncryptServiceConfig configuration) {
+ return Map.of(CONFIG_PROP, requireNonNull(configuration));
+ }
+
@Deactivate
void deactivate() {
LOG.info("AAAEncryptionService deactivated");
--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.encrypt.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Base64;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.EncryptServiceConfig;
+
+record EncryptServiceConfigImpl(@NonNull AaaEncryptServiceConfig delegate) implements EncryptServiceConfig {
+ EncryptServiceConfigImpl {
+ requireNonNull(delegate);
+ }
+
+ @Override
+ public Class<? extends EncryptServiceConfig> implementedInterface() {
+ // This implementation is not generated and used only internally, hence this method is never called
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getEncryptKey() {
+ return delegate.requireEncryptKey();
+ }
+
+ @Override
+ public byte[] getEncryptSalt() {
+ return Base64.getDecoder().decode(delegate.requireEncryptSalt());
+ }
+
+ @Override
+ public String getEncryptMethod() {
+ return delegate.getEncryptMethod();
+ }
+
+ @Override
+ public String getEncryptType() {
+ return delegate.getEncryptType();
+ }
+
+ @Override
+ public Integer getEncryptIterationCount() {
+ return delegate.getEncryptIterationCount();
+ }
+
+ @Override
+ public Integer getEncryptKeyLength() {
+ return delegate.getEncryptKeyLength();
+ }
+
+ @Override
+ public String getCipherTransforms() {
+ return delegate.getCipherTransforms();
+ }
+}
+++ /dev/null
-/*
- * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.aaa.encrypt.impl;
-
-import java.util.function.Supplier;
-import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.EncryptServiceConfig;
-
-/**
- * Blueprint compatibility hack. Direct injection would result in
- * <pre>
- * Caused by: java.lang.IncompatibleClassChangeError: class Proxy601f54db_5975_4863_a33b_758ee248518f cannot implement
- * sealed interface org.opendaylight.yangtools.yang.binding.DataContainer
- * </pre>
- * and hence we wrap it with a DataObject, which is not sealed.
- */
-@Deprecated
-public interface EncryptServiceConfigSupplier extends Supplier<EncryptServiceConfig> {
-
-}
--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.encrypt.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.odlparent.logging.markers.Markers;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfigBuilder;
+import org.opendaylight.yangtools.concepts.Registration;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.component.ComponentFactory;
+import org.osgi.service.component.ComponentInstance;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Intermediate component dealing with establishing initial configuration for {@link AAAEncryptionServiceImpl}. In
+ * particular it deals with generating and persisting of encryption salt and encryption password.
+ *
+ * <p>
+ * We primarily listen to the configuration being present. Whenever the salt is missing or the password does not match
+ * the required length, we generate them and persist them. This mode of operation means we potentially have a loop, i.e.
+ * our touching the datastore will trigger again {@link #onDataTreeChanged(Collection)}, which will re-evaluate the
+ * conditions and we try again.
+ */
+@Component(service = { })
+public final class OSGiEncryptionServiceConfigurator implements DataTreeChangeListener<AaaEncryptServiceConfig> {
+ private static final Logger LOG = LoggerFactory.getLogger(OSGiEncryptionServiceConfigurator.class);
+ private static final SecureRandom RANDOM = new SecureRandom();
+ private static final @NonNull AaaEncryptServiceConfig DEFAULT_CONFIG = new AaaEncryptServiceConfigBuilder()
+ // Note: mirrors defaults from YANG file
+ .setEncryptMethod("PBKDF2WithHmacSHA1")
+ .setEncryptType("AES")
+ .setEncryptIterationCount(32768)
+ .setEncryptKeyLength(128)
+ .setCipherTransforms("AES/CBC/PKCS5Padding")
+ .setPasswordLength(12)
+ .build();
+
+ private final ComponentFactory<AAAEncryptionServiceImpl> factory;
+ private final DataBroker dataBroker;
+
+ @GuardedBy("this")
+ private Registration reg;
+ @GuardedBy("this")
+ private ComponentInstance<AAAEncryptionServiceImpl> instance;
+ @GuardedBy("this")
+ private AaaEncryptServiceConfig current;
+
+ @Activate
+ public OSGiEncryptionServiceConfigurator(@Reference final DataBroker dataBroker,
+ @Reference(target = "(component.factory=" + AAAEncryptionServiceImpl.FACTORY_NAME + ")")
+ final ComponentFactory<AAAEncryptionServiceImpl> factory) {
+ this.dataBroker = requireNonNull(dataBroker);
+ this.factory = requireNonNull(factory);
+ reg = dataBroker.registerDataTreeChangeListener(
+ DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION,
+ InstanceIdentifier.create(AaaEncryptServiceConfig.class)),
+ this);
+ LOG.debug("AAA Encryption Service configurator started");
+ }
+
+ @Deactivate
+ public synchronized void deactivate() {
+ reg.close();
+ reg = null;
+ disableInstance();
+ LOG.debug("AAA Encryption Service configurator stopped");
+ }
+
+ @Override
+ public void onDataTreeChanged(final Collection<DataTreeModification<AaaEncryptServiceConfig>> changes) {
+ // Acquire the last reported configuration and check if it needs to have salt/password generated.
+ final var dsConfig = Iterables.getLast(changes).getRootNode().getDataAfter();
+ if (dsConfig == null || needKey(dsConfig) || needSalt(dsConfig)) {
+ // Generate salt/key as needed and persist it -- causing us to be re-invoked later.
+ updateDatastore(dsConfig);
+ } else {
+ // Configuration is self-consistent, proceed to activate an instance based on it
+ updateInstance(dsConfig);
+ }
+ }
+
+ @Override
+ public void onInitialData() {
+ updateDatastore(null);
+ }
+
+ @VisibleForTesting
+ static @NonNull AaaEncryptServiceConfig generateConfig(final @Nullable AaaEncryptServiceConfig datastoreConfig) {
+ // Select template and decide which parts need to be updated
+ final var template = datastoreConfig != null ? datastoreConfig : DEFAULT_CONFIG;
+ final var builder = new AaaEncryptServiceConfigBuilder(template);
+ if (needKey(template)) {
+ LOG.debug("Set the Encryption Service salt");
+ builder.setEncryptKey(RandomStringUtils.random(template.requirePasswordLength(), true, true));
+ }
+ if (needSalt(template)) {
+ LOG.debug("Set the Encryption Service salt");
+ final var salt = new byte[16];
+ RANDOM.nextBytes(salt);
+ builder.setEncryptSalt(Base64.getEncoder().encodeToString(salt));
+ }
+ return builder.build();
+ }
+
+ private void updateDatastore(final @Nullable AaaEncryptServiceConfig expected) {
+ final var target = generateConfig(expected);
+
+ // Careful update of the datastore: we are coming from DTCL thread, so inherently 'expected' may already be out
+ // of date, either by user action, or our update from another node (in a cluster). We rely on transaction's
+ // read&put atomicity to do the right thing here.
+ final var iid = InstanceIdentifier.create(AaaEncryptServiceConfig.class);
+ final var tx = dataBroker.newReadWriteTransaction();
+ final var readFuture = tx.read(LogicalDatastoreType.CONFIGURATION, iid);
+
+ final AaaEncryptServiceConfig actual;
+ try {
+ actual = readFuture.get().orElse(null);
+ } catch (InterruptedException | ExecutionException e) {
+ // Read failed: all we can do now is to disable the service and hope for an external recovery action -- like
+ // a restart of this component or a write to the datastore (which will trigger a retry).
+ tx.cancel();
+ LOG.error("Failed to read configuration, disabling service", e);
+ synchronized (this) {
+ disableInstance();
+ }
+ return;
+ }
+
+ if (!Objects.equals(actual, expected)) {
+ // Yup, there has been a race -- log that fact and bail out
+ tx.cancel();
+ LOG.debug(Markers.confidential(), "Skipping update on datastore mismatch: expected {} actual {}",
+ expected, actual);
+ return;
+ }
+
+ LOG.debug(Markers.confidential(), "Updating configuration to {}", target);
+ tx.put(LogicalDatastoreType.CONFIGURATION, iid, target);
+ Futures.addCallback(tx.commit(), new FutureCallback<CommitInfo>() {
+ @Override
+ public void onFailure(final Throwable throwable) {
+ // Async update: we should get a new onDataTreeChanged() callback
+ LOG.warn("Configuration update failed, attempting to continue", throwable);
+ }
+
+ @Override
+ public void onSuccess(final CommitInfo result) {
+ LOG.info("Configuration update succeeded");
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ @Holding("this")
+ private void disableInstance() {
+ if (instance != null) {
+ instance.dispose();
+ instance = null;
+ current = null;
+ LOG.info("Encryption Service disabled");
+ }
+ }
+
+ private synchronized void updateInstance(final AaaEncryptServiceConfig newConfig) {
+ if (reg == null) {
+ LOG.debug("Skipping instance update due to shutdown");
+ return;
+ }
+ if (newConfig.equals(current)) {
+ LOG.debug("Skipping instance update due to equal configuration");
+ return;
+ }
+
+ disableInstance();
+ instance = factory.newInstance(FrameworkUtil.asDictionary(
+ AAAEncryptionServiceImpl.props(new EncryptServiceConfigImpl(newConfig))));
+ current = newConfig;
+ LOG.info("Encryption Service enabled");
+ }
+
+ private static boolean needKey(final AaaEncryptServiceConfig config) {
+ final var key = config.getEncryptKey();
+ return key == null || key.length() != config.requirePasswordLength();
+ }
+
+ private static boolean needSalt(final AaaEncryptServiceConfig config) {
+ final var salt = config.getEncryptSalt();
+ return salt == null || salt.isEmpty();
+ }
+}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
- xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0">
- <reference id="dataBroker" interface="org.opendaylight.mdsal.binding.api.DataBroker"/>
-
- <odl:clustered-app-config id="encryptConfig" default-config-file-name="aaa-encrypt-service-config.xml"
- binding-class="org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig">
- </odl:clustered-app-config>
-
- <bean id="encryptServiceConfigurator" class="org.opendaylight.aaa.encrypt.impl.AAAEncryptionServiceConfigurator">
- <argument ref="dataBroker"/>
- <argument ref="encryptConfig"/>
- </bean>
- <service ref="encryptServiceConfigurator" interface="org.opendaylight.aaa.encrypt.impl.EncryptServiceConfigSupplier"/>
-
-</blueprint>
+++ /dev/null
-<aaa-encrypt-service-config xmlns="config:aaa:authn:encrypt:service:config">
- <encrypt-key>V1S1ED4OMeEh</encrypt-key>
- <password-length>12</password-length>
- <encrypt-salt>TdtWeHbch/7xP52/rp3Usw==</encrypt-salt>
- <encrypt-method>PBKDF2WithHmacSHA1</encrypt-method>
- <encrypt-type>AES</encrypt-type>
- <encrypt-iteration-count>32768</encrypt-iteration-count>
- <encrypt-key-length>128</encrypt-key-length>
- <cipher-transforms>AES/CBC/PKCS5Padding</cipher-transforms>
-</aaa-encrypt-service-config>
leaf encrypt-method {
description "The encryption method to use";
type string;
+ default PBKDF2WithHmacSHA1;
}
leaf encrypt-type {
description "The encryption type";
type string;
+ default AES;
}
leaf encrypt-iteration-count {
description "Number of iterations that will be used by the key";
+ // FIXME: uint32
type int32;
+ default 32768;
}
leaf encrypt-key-length {
description "Key length";
+ // FIXME: uint32
type int32;
+ default 128;
}
leaf cipher-transforms {
description "cipher transformation type ex: AES/CBC/PKCS5Padding (128)";
type string;
+ default "AES/CBC/PKCS5Padding";
}
}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.mockito.Mockito.doReturn;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-import org.opendaylight.mdsal.binding.api.DataBroker;
-import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
-import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfigBuilder;
-import org.opendaylight.yangtools.util.concurrent.FluentFutures;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
/*
* @author - Sharon Aicler (saichler@gmail.com)
*/
@Deprecated
-@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class AAAEncryptServiceImplTest {
- @Mock
- private DataBroker dataBroker;
- @Mock
- private ReadWriteTransaction tx;
-
private AAAEncryptionServiceImpl impl;
@Before
public void setup() {
- final var module = new AaaEncryptServiceConfigBuilder()
- .setCipherTransforms("AES/CBC/PKCS5Padding").setEncryptIterationCount(32768).setEncryptKey("")
- .setEncryptKeyLength(128).setEncryptMethod("PBKDF2WithHmacSHA1").setEncryptSalt("")
- .setEncryptType("AES").setPasswordLength(12).build();
-
- doReturn(tx).when(dataBroker).newReadWriteTransaction();
- doReturn(FluentFutures.immediateTrueFluentFuture()).when(tx)
- .exists(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(AaaEncryptServiceConfig.class));
-
- impl = new AAAEncryptionServiceImpl(new AAAEncryptionServiceConfigurator(dataBroker, module));
+ impl = new AAAEncryptionServiceImpl(new EncryptServiceConfigImpl(
+ OSGiEncryptionServiceConfigurator.generateConfig(new AaaEncryptServiceConfigBuilder()
+ .setCipherTransforms("AES/CBC/PKCS5Padding")
+ .setEncryptIterationCount(32768)
+ .setEncryptKey("")
+ .setEncryptKeyLength(128)
+ .setEncryptMethod("PBKDF2WithHmacSHA1")
+ .setEncryptSalt("")
+ .setEncryptType("AES")
+ .setPasswordLength(12)
+ .build())));
}
@Test
--- /dev/null
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.encrypt.impl;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+
+import java.util.Base64;
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.DataObjectModification;
+import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
+import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
+import org.opendaylight.mdsal.binding.api.DataTreeModification;
+import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.AaaEncryptServiceConfig;
+import org.opendaylight.yang.gen.v1.config.aaa.authn.encrypt.service.config.rev160915.EncryptServiceConfig;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.util.concurrent.FluentFutures;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.osgi.service.component.ComponentFactory;
+import org.osgi.service.component.ComponentInstance;
+
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class OSGiEncryptionServiceConfiguratorTest {
+ private static final @NonNull InstanceIdentifier<AaaEncryptServiceConfig> IID =
+ InstanceIdentifier.create(AaaEncryptServiceConfig.class);
+
+ @Mock
+ private DataBroker dataBroker;
+ @Mock
+ private ComponentFactory<AAAEncryptionServiceImpl> factory;
+ @Mock
+ private ComponentInstance<AAAEncryptionServiceImpl> instance;
+ @Mock
+ private ListenerRegistration<?> registration;
+ @Mock
+ private ReadWriteTransaction transaction;
+ @Mock
+ private DataTreeModification<AaaEncryptServiceConfig> treeModification;
+ @Mock
+ private DataObjectModification<AaaEncryptServiceConfig> objectModification;
+ @Captor
+ private ArgumentCaptor<DataTreeIdentifier<AaaEncryptServiceConfig>> treeIdCaptor;
+ @Captor
+ private ArgumentCaptor<DataTreeChangeListener<AaaEncryptServiceConfig>> listenerCaptor;
+ @Captor
+ private ArgumentCaptor<AaaEncryptServiceConfig> configCaptor;
+ @Captor
+ private ArgumentCaptor<Dictionary<String, ?>> propertiesCaptor;
+
+ private OSGiEncryptionServiceConfigurator configurator;
+
+ @Before
+ public void before() {
+ doReturn(registration).when(dataBroker).registerDataTreeChangeListener(treeIdCaptor.capture(),
+ listenerCaptor.capture());
+
+ configurator = new OSGiEncryptionServiceConfigurator(dataBroker, factory);
+
+ assertEquals(DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, IID), treeIdCaptor.getValue());
+ assertSame(configurator, listenerCaptor.getValue());
+ }
+
+ @Test
+ public void testImmediateDeactivate() {
+ doNothing().when(registration).close();
+ configurator.deactivate();
+ }
+
+ @Test
+ public void testEmptyDatastore() {
+ // Config datastore write is expected: capture what gets written
+ doReturn(transaction).when(dataBroker).newReadWriteTransaction();
+ doReturn(FluentFutures.immediateFluentFuture(Optional.empty())).when(transaction)
+ .read(LogicalDatastoreType.CONFIGURATION, IID);
+ doNothing().when(transaction).put(eq(LogicalDatastoreType.CONFIGURATION), eq(IID), configCaptor.capture());
+ doReturn(CommitInfo.emptyFluentFuture()).when(transaction).commit();
+
+ configurator.onInitialData();
+
+ final var config = configCaptor.getValue();
+ assertEquals("AES/CBC/PKCS5Padding", config.getCipherTransforms());
+ assertEquals(Integer.valueOf(32768), config.getEncryptIterationCount());
+ assertEquals(Integer.valueOf(128), config.getEncryptKeyLength());
+ assertEquals("PBKDF2WithHmacSHA1", config.getEncryptMethod());
+ assertEquals("AES", config.getEncryptType());
+ assertEquals(Integer.valueOf(12), config.getPasswordLength());
+
+ final var salt = Base64.getDecoder().decode(config.getEncryptSalt());
+ assertEquals(16, salt.length);
+
+ final var key = config.getEncryptKey();
+ assertNotNull(key);
+ assertEquals(12, key.length());
+
+ // Now we circle around are report that config. We expect the factory to be called
+ doReturn(config).when(objectModification).getDataAfter();
+ doReturn(objectModification).when(treeModification).getRootNode();
+ doReturn(instance).when(factory).newInstance(propertiesCaptor.capture());
+
+ configurator.onDataTreeChanged(List.of(treeModification));
+
+ final var props = propertiesCaptor.getValue();
+ assertNotNull(props);
+ assertEquals(1, props.size());
+ final var configObj = props.elements().nextElement();
+ assertNotNull(configObj);
+ assertThat(configObj, instanceOf(EncryptServiceConfig.class));
+ final var serviceConfig = (EncryptServiceConfig) configObj;
+ assertArrayEquals(salt, serviceConfig.getEncryptSalt());
+ assertEquals(key, serviceConfig.getEncryptKey());
+
+ // Now shut down configurator
+ doNothing().when(registration).close();
+ doNothing().when(instance).dispose();
+ configurator.deactivate();
+ }
+}
<artifactId>aaa-encrypt-service-impl</artifactId>
<version>${project.version}</version>
</dependency>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>aaa-encrypt-service-impl</artifactId>
- <version>${project.version}</version>
- <type>xml</type>
- <classifier>config</classifier>
- </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<groupId>${project.groupId}</groupId>
<artifactId>aaa-encrypt-service</artifactId>
</dependency>
- <dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>aaa-encrypt-service-impl</artifactId>
- <classifier>config</classifier>
- <type>xml</type>
- </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>aaa-filterchain</artifactId>
-->
<features name="odl-aaa-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0">
<feature name="odl-aaa-cert" version="${project.version}">
+ <feature version="[7,8)">odl-controller-blueprint</feature>
<feature version="[7,8)">odl-mdsal-broker</feature>
<configfile finalname="etc/opendaylight/datastore/initial/config/aaa-cert-config.xml">
mvn:org.opendaylight.aaa/aaa-cert/${project.version}/xml/config
<groupId>org.opendaylight.aaa</groupId>
<artifactId>aaa-encrypt-service-impl</artifactId>
</dependency>
- <dependency>
- <!-- finalname="etc/opendaylight/datastore/initial/config/aaa-encrypt-service-config.xml" -->
- <groupId>org.opendaylight.aaa</groupId>
- <artifactId>aaa-encrypt-service-impl</artifactId>
- <type>xml</type>
- <classifier>config</classifier>
- </dependency>
</dependencies>
</project>
-->
<features name="odl-aaa-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0">
<feature name="odl-aaa-encryption-service" version="${project.version}">
- <feature version="[7,8)">odl-controller-blueprint</feature>
<feature version="[7,8)">odl-mdsal-broker</feature>
- <configfile finalname="etc/opendaylight/datastore/initial/config/aaa-encrypt-service-config.xml">
- mvn:org.opendaylight.aaa/aaa-encrypt-service-impl/${project.version}/xml/config
- </configfile>
</feature>
</features>