wazuh / wazuh-indexer-plugins

GNU Affero General Public License v3.0
1 stars 3 forks source link

Research configuration persistence methods #86

Closed AlexRuiz7 closed 1 month ago

AlexRuiz7 commented 2 months ago

Description

As part of the development of the Command Manager plugin, we need to investigate which of the methods to persist configuration options for the plugin suits our needs best.

The goal of this issue is to:

mcasas993 commented 1 month ago

Plugin dedicated configuration file

We found that OpenSearch's Observability Plugin uses a dedicated configuration file, observability.yml. We're going to research how they use this configuration file so we can use the same approach.

We found that they use this method from the Setting class that loads the configuration file

We searched other uses of the Setting class and found this use in the Security Plugin which is implemented in Java.

Main configuration file (opensearch.yml)

The AuditConfigMigrater class in Security Plugin reads the opensearch.yml, then creates new configuration files and finally edits the opensearch.yml. So, we understand that this is a complete example to use opensearch.yml to persist configuration options for the plugin.

Analyses of the Example in AuditConfigMigrater of Security Plugin

final CommandLine line = parser.parse(options, args);
[...]
final String source = line.getOptionValue("s", opensearchPath);
[...]
// create settings builder
System.out.println("Using source opensearch.yml file from path " + source);
final Settings.Builder settingsBuilder = Settings.builder().loadFromPath(Paths.get(source));

We investigated the java.nio.file.Paths class to understand if that uses absolute or relative path. We can use relatives and absolutes paths:

Absolute Path:

Relative paths

AlexRuiz7 commented 1 month ago

As a conclusion, any configuration file can be loaded on runtime using the Settings class builder. The PluginSettings class from the Observability plugin is a great example of how to define, load and validate plugin specific settings.

Settings.builder().loadFromPath(defaultSettingYmlFile)

This method can be used for reading any configuration in the file system.

While taking a look at the Settings class, I noticed the KeyStoreWrapper class, which presumably can be used to load stuff from the keystore.

We should take a look at it.

mcasas993 commented 1 month ago

KeyStoreWrapper class

This class allow us to load an opensearch.keystore file, read the keys and also write new keys.

The algorithm used to derive the cipher key from a password is "PBKDF2WithHmacSHA512". The cipher used to encrypt the keystore data is "AES".

Example in TransportNodesReloadSecureSettingsAction class

 try (KeyStoreWrapper keystore = KeyStoreWrapper.load(environment.configDir())) {
            // reread keystore from config file
            if (keystore == null) {
                return new NodesReloadSecureSettingsResponse.NodeResponse(
                    clusterService.localNode(),
                    new IllegalStateException("Keystore is missing")
                );
            }
            // decrypt the keystore using the password from the request
            keystore.decrypt(secureSettingsPassword.getChars());
            // add the keystore to the original node settings object
            final Settings settingsWithKeystore = Settings.builder().put(environment.settings(), false).setSecureSettings(keystore).build();
            final List<Exception> exceptions = new ArrayList<>();
            // broadcast the new settings object (with the open embedded keystore) to all reloadable plugins
            pluginsService.filterPlugins(ReloadablePlugin.class).stream().forEach(p -> {
                try {
                    p.reload(settingsWithKeystore);
                } catch (final Exception e) {
                    logger.warn(
                        (Supplier<?>) () -> new ParameterizedMessage("Reload failed for plugin [{}]", p.getClass().getSimpleName()),
                        e
                    );
                    exceptions.add(e);
                }
            });

Example in Bootstrap class

static SecureSettings loadSecureSettings(Environment initialEnv) throws BootstrapException {
        final KeyStoreWrapper keystore;
        try {
            keystore = KeyStoreWrapper.load(initialEnv.configDir());
        } catch (IOException e) {
            throw new BootstrapException(e);
        }

        SecureString password;
        try {
            if (keystore != null && keystore.hasPassword()) {
                password = readPassphrase(System.in, KeyStoreAwareCommand.MAX_PASSPHRASE_LENGTH);
            } else {
                password = new SecureString(new char[0]);
            }
        } catch (IOException e) {
            throw new BootstrapException(e);
        }

        try {
            if (keystore == null) {
                final KeyStoreWrapper keyStoreWrapper = KeyStoreWrapper.create();
                keyStoreWrapper.save(initialEnv.configDir(), new char[0]);
                return keyStoreWrapper;
            } else {
                keystore.decrypt(password.getChars());
                KeyStoreWrapper.upgrade(keystore, initialEnv.configDir(), password.getChars());
            }
        } catch (Exception e) {
            throw new BootstrapException(e);
        } finally {
            password.close();
        }
        return keystore;
    }
AlexRuiz7 commented 1 month ago

More uses of the KeyStoreWrapper class here.

We have all the information we need for the next step, which is to implement one of these configuration persistence methods in our command-manager plugin. Given our use case, which is to store the URL and credentials of the Management API on the Wazuh Server, I think the key store method fits better, as that's sensitive information.