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

Research configuration persistence methods #86

Closed
3 tasks done
Tracked by #349
AlexRuiz7 opened this issue Oct 2, 2024 · 4 comments
Closed
3 tasks done
Tracked by #349

Research configuration persistence methods #86

AlexRuiz7 opened this issue Oct 2, 2024 · 4 comments
Assignees
Labels
level/task Task issue type/research Research issue

Comments

@AlexRuiz7
Copy link
Member

AlexRuiz7 commented Oct 2, 2024

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.

  • Main configuration file (opensearch.yml).
  • Plugin dedicated configuration file.
  • Indexer's key store (/usr/share/wazuh-indexer/bin/opensearch-keystore & /etc/wazuh-indexer/opensearch.keystore).

The goal of this issue is to:

  • Research if the configuration methods above are suitable for the command manager user case. The command manager needs to access information of the Wazuh Server in order to access its Management API (IP, port, user and password (if required)).
@AlexRuiz7 AlexRuiz7 added level/task Task issue type/research Research issue labels Oct 2, 2024
@wazuhci wazuhci moved this to Backlog in Release 5.0.0 Oct 3, 2024
@mcasas993 mcasas993 self-assigned this Oct 4, 2024
@wazuhci wazuhci moved this from Backlog to In progress in Release 5.0.0 Oct 4, 2024
@mcasas993
Copy link
Member

mcasas993 commented Oct 4, 2024

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:

  • Creating an absolute path is done by calling the Paths.get() factory method with the absolute file as parameter. Example in Unix System:
    Path path = Paths.get("/home/mcasas/myfile.txt");
  • Obtaining the absolute path from a relative path using the toAbsolutePath() method:
Path path = Paths.get("/home/mcasas/myfile.txt");
Path absPath = p.toAbsolutePath();

Relative paths

  • Creating a relative path is done by calling the Paths.get() factory method with the absolute file as parameter. Example in Unix System:
    Path path = Paths.get("mcasas/myfile.txt");
  • We can refer also to the current directory using "."
    Path currentDir = Paths.get(".");

@AlexRuiz7
Copy link
Member Author

AlexRuiz7 commented Oct 7, 2024

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
Copy link
Member

mcasas993 commented Oct 7, 2024

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;
    }

@wazuhci wazuhci moved this from In progress to Pending review in Release 5.0.0 Oct 7, 2024
@AlexRuiz7
Copy link
Member Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
level/task Task issue type/research Research issue
Projects
Status: Done
Development

No branches or pull requests

2 participants