Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NMS-16943: Fix sync issue with scvcli #7566

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions container/features/src/main/resources/features-core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ org.jolokia.authMode=jaas
<feature>commons-codec</feature>
<feature version="${guavaOsgiVersion}" prerequisite="true">guava</feature>
<feature>scv-api</feature>
<bundle>mvn:org.opennms.core/org.opennms.core.lib/${project.version}</bundle>
<bundle>mvn:org.opennms.features.scv/org.opennms.features.scv.jceks-impl/${project.version}</bundle>
</feature>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,15 @@ public class FileUpdateWatcher {
private final AtomicBoolean closed = new AtomicBoolean(false);

public FileUpdateWatcher(String fileName, FileUpdateCallback fileUpdateCb) throws IOException {
this(fileName, fileUpdateCb, false);
}

public FileUpdateWatcher(String fileName, FileUpdateCallback fileUpdateCb, boolean skipFileChecking) throws IOException {
File file = new File(fileName);
if (!file.exists() || file.isDirectory()) {
throw new IllegalArgumentException(String.format("file %s doesn't exist", fileName));
if (!skipFileChecking) {
if (!file.exists() || file.isDirectory()) {
throw new IllegalArgumentException(String.format("file %s doesn't exist", fileName));
}
}
this.fileName = fileName;
Path path = Paths.get(file.getParent());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ public interface SecureCredentialsVault {
void setCredentials(String alias, Credentials credentials);

void deleteCredentials(String alias);

}
5 changes: 5 additions & 0 deletions features/scv/jceks-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.opennms.core</groupId>
<artifactId>org.opennms.core.lib</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@
import java.util.HashMap;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.opennms.core.fileutils.FileUpdateCallback;
import org.opennms.core.fileutils.FileUpdateWatcher;
import org.opennms.features.scv.api.Credentials;
import org.opennms.features.scv.api.SecureCredentialsVault;
import org.slf4j.Logger;
Expand All @@ -63,7 +66,7 @@
*
* @author jwhite
*/
public class JCEKSSecureCredentialsVault implements SecureCredentialsVault {
public class JCEKSSecureCredentialsVault implements SecureCredentialsVault, FileUpdateCallback {

public static final Logger LOG = LoggerFactory.getLogger(JCEKSSecureCredentialsVault.class);

Expand All @@ -74,20 +77,27 @@ public class JCEKSSecureCredentialsVault implements SecureCredentialsVault {
private final int m_iterationCount;
private final int m_keyLength;
private final HashMap<String, Credentials> m_credentialsCache = new HashMap<>();
private FileUpdateWatcher m_fileUpdateWatcher;
private final AtomicBoolean m_fileUpdated = new AtomicBoolean(false);
private long m_lastModified = System.currentTimeMillis();

public static final String KEYSTORE_KEY_PROPERTY = "org.opennms.features.scv.jceks.key";

public static final String DEFAULT_KEYSTORE_KEY = "QqSezYvBtk2gzrdpggMHvt5fJGWCdkRw";

public JCEKSSecureCredentialsVault(String keystoreFile, String password) {
this(keystoreFile, password, new byte[]{0x0, 0xd, 0xd, 0xb, 0xa, 0x1, 0x1});
public JCEKSSecureCredentialsVault(String keystoreFile, String password, boolean useWatcher) {
this(keystoreFile, password, useWatcher, new byte[]{0x0, 0xd, 0xd, 0xb, 0xa, 0x1, 0x1});
}

public JCEKSSecureCredentialsVault(String keystoreFile, String password, byte[] salt) {
this(keystoreFile, password, salt, 16, 4096);
public JCEKSSecureCredentialsVault(String keystoreFile, String password) {
this(keystoreFile, password, false, new byte[]{0x0, 0xd, 0xd, 0xb, 0xa, 0x1, 0x1});
}

public JCEKSSecureCredentialsVault(String keystoreFile, String password, byte[] salt, int iterationCount, int keyLength) {
public JCEKSSecureCredentialsVault(String keystoreFile, String password, boolean useWatcher, byte[] salt) {
this(keystoreFile, password, useWatcher, salt, 16, 4096);
}

public JCEKSSecureCredentialsVault(String keystoreFile, String password, boolean useWatcher, byte[] salt, int iterationCount, int keyLength) {
m_password = Objects.requireNonNull(password).toCharArray();
m_salt = Objects.requireNonNull(salt);
m_iterationCount = iterationCount;
Expand All @@ -104,16 +114,35 @@ public JCEKSSecureCredentialsVault(String keystoreFile, String password, byte[]
m_keystore.load(is, m_password);
}
}
// Enable watcher to load changes to keystore file that happens outside OpenNMS
if (useWatcher) {
createFileUpdateWatcher();
}

} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
throw Throwables.propagate(e);
}
}

private void createFileUpdateWatcher() {
if (m_fileUpdateWatcher == null) {
try {
m_fileUpdateWatcher = new FileUpdateWatcher(m_keystoreFile.getAbsolutePath(), this, true);
} catch (IOException e) {
LOG.warn("Failed to create file update watcher", e);
}
}
}

private void loadCredentials() {
synchronized (m_credentialsCache) {
if (!m_credentialsCache.isEmpty()) {
if (!m_credentialsCache.isEmpty() && !m_fileUpdated.get()) {
return;
}
if (m_fileUpdated.get()) {
m_fileUpdated.set(false);
m_credentialsCache.clear();
}
try {
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection(m_password);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");
Expand Down Expand Up @@ -152,8 +181,8 @@ public void setCredentials(String alias, Credentials credentials) {

KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection(m_password);
m_keystore.setEntry(alias, new KeyStore.SecretKeyEntry(generatedSecret), keyStorePP);
writeKeystoreToDisk();
synchronized (m_credentialsCache) {
writeKeystoreToDisk();
m_credentialsCache.put(alias, credentials);
}
} catch (KeyStoreException | InvalidKeySpecException | NoSuchAlgorithmException | IOException e) {
Expand All @@ -167,18 +196,25 @@ public void deleteCredentials(final String alias) {
synchronized (m_credentialsCache) {
m_keystore.deleteEntry(alias);
m_credentialsCache.remove(alias);
writeKeystoreToDisk();
}

writeKeystoreToDisk();

} catch (final KeyStoreException e) {
throw Throwables.propagate(e);
}
}

public void destroy() {
if (m_fileUpdateWatcher != null) {
m_fileUpdateWatcher.destroy();
}
}

private void writeKeystoreToDisk() {
try (OutputStream os = new FileOutputStream(m_keystoreFile)) {
m_keystore.store(os, m_password);
m_lastModified = m_keystoreFile.lastModified();
} catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
throw Throwables.propagate(e);
}
Expand Down Expand Up @@ -231,4 +267,22 @@ private static String getKeystorePassword() {
public static JCEKSSecureCredentialsVault defaultScv() {
return new JCEKSSecureCredentialsVault(getKeystoreFilename(), getKeystorePassword());
}

@Override
public void reload() {
synchronized (m_credentialsCache) {
// If the keystore file got updated by us, no need to reload
if (m_keystoreFile.lastModified() == m_lastModified) {
return;
}
// Reload the keystore file when file gets updated.
try (InputStream is = new FileInputStream(m_keystoreFile)) {
m_keystore.load(is, m_password);
} catch (NoSuchAlgorithmException | CertificateException | IOException e) {
LOG.error("Exception while loading keystore file {}", m_keystoreFile, e);
}
m_fileUpdated.set(true);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://xmlns.opennms.org/xsd/spring/onms-osgi http://xmlns.opennms.org/xsd/spring/onms-osgi.xsd">

<bean name="jceksSecureCredentialsVault" class="org.opennms.features.scv.jceks.JCEKSSecureCredentialsVault">
<bean name="jceksSecureCredentialsVault" class="org.opennms.features.scv.jceks.JCEKSSecureCredentialsVault" destroy-method="destroy">
<constructor-arg value="#{systemProperties['opennms.home']}/etc/scv.jce"/>
<constructor-arg value="#{systemProperties['org.opennms.features.scv.jceks.key'] ?: 'QqSezYvBtk2gzrdpggMHvt5fJGWCdkRw'}"/>
<constructor-arg value="#{systemProperties['org.opennms.features.scv.jceks.enable.watcher'] ?: true}" type="boolean"/>
</bean>

<onmsgi:service interface="org.opennms.features.scv.api.SecureCredentialsVault" ref="jceksSecureCredentialsVault"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
<cm:property-placeholder id="scvProperties" persistent-id="org.opennms.features.scv" placeholder-prefix="[[" placeholder-suffix="]]" update-strategy="reload">
<cm:default-properties>
<cm:property name="key" value="QqSezYvBtk2gzrdpggMHvt5fJGWCdkRw"/>
<cm:property name="enable.watcher" value="true"/>
</cm:default-properties>
</cm:property-placeholder>

<bean id="jceksSecureCredentialsVault" class="org.opennms.features.scv.jceks.JCEKSSecureCredentialsVault">
<bean id="jceksSecureCredentialsVault" class="org.opennms.features.scv.jceks.JCEKSSecureCredentialsVault" destroy-method="destroy">
<argument value="$[karaf.etc]$[file.separator]scv.jce"/>
<argument value="[[key]]"/>
<argument value="[[enable.watcher]]" type="boolean"/>
</bean>

<service ref="jceksSecureCredentialsVault" interface="org.opennms.features.scv.api.SecureCredentialsVault">
Expand Down
Loading