Skip to content

Commit

Permalink
NMS-16943: Use file watcher only on Spring/Karaf
Browse files Browse the repository at this point in the history
Also use file update time to restrict reloading of keystore
  • Loading branch information
Chandra Gorantla authored and Chandra Gorantla committed Jan 7, 2025
1 parent 0c1d6f9 commit bec32e2
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 20 deletions.
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);

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,22 +77,27 @@ public class JCEKSSecureCredentialsVault implements SecureCredentialsVault, File
private final int m_iterationCount;
private final int m_keyLength;
private final HashMap<String, Credentials> m_credentialsCache = new HashMap<>();
private FileUpdateWatcher fileUpdateWatcher;
private final AtomicBoolean m_fileUpdatedByOthers = new AtomicBoolean(false);
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, boolean useWatcher) {
this(keystoreFile, password, useWatcher, new byte[]{0x0, 0xd, 0xd, 0xb, 0xa, 0x1, 0x1});
}

public JCEKSSecureCredentialsVault(String keystoreFile, String password) {
this(keystoreFile, password, new byte[]{0x0, 0xd, 0xd, 0xb, 0xa, 0x1, 0x1});
this(keystoreFile, password, false, 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, boolean useWatcher, byte[] salt) {
this(keystoreFile, password, useWatcher, salt, 16, 4096);
}

public JCEKSSecureCredentialsVault(String keystoreFile, String password, byte[] salt, int iterationCount, int keyLength) {
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 @@ -109,16 +114,20 @@ public JCEKSSecureCredentialsVault(String keystoreFile, String password, byte[]
m_keystore.load(is, m_password);
}
}
createFileUpdateWatcher();
// 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 (fileUpdateWatcher == null) {
if (m_fileUpdateWatcher == null) {
try {
fileUpdateWatcher = new FileUpdateWatcher(m_keystoreFile.getAbsolutePath(), this, true);
m_fileUpdateWatcher = new FileUpdateWatcher(m_keystoreFile.getAbsolutePath(), this, true);
} catch (IOException e) {
LOG.warn("Failed to create file update watcher", e);
}
Expand All @@ -127,11 +136,11 @@ private void createFileUpdateWatcher() {

private void loadCredentials() {
synchronized (m_credentialsCache) {
if (!m_credentialsCache.isEmpty() && !m_fileUpdatedByOthers.get()) {
if (!m_credentialsCache.isEmpty() && !m_fileUpdated.get()) {
return;
}
if (m_fileUpdatedByOthers.get()) {
m_fileUpdatedByOthers.set(false);
if (m_fileUpdated.get()) {
m_fileUpdated.set(false);
m_credentialsCache.clear();
}
try {
Expand Down Expand Up @@ -172,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 @@ -187,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 @@ -254,16 +270,18 @@ public static JCEKSSecureCredentialsVault defaultScv() {

@Override
public void reload() {
// TODO: Need to optimize this for not reloading multiple times and
// not reload when it is invoked within the same process
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) {
throw Throwables.propagate(e);
LOG.error("Exception while loading keystore file {}", m_keystoreFile, e);
}
m_fileUpdatedByOthers.set(true);
m_fileUpdated.set(true);
}
}

Expand Down
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

0 comments on commit bec32e2

Please sign in to comment.