Skip to content

Commit

Permalink
Setup extension TLS (opensearch-project#619)
Browse files Browse the repository at this point in the history
* WIP on Handler naming and SSL

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add concept of extension shortname via settings

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* WIP on extension ssl

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Get registry from runner

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Read settings from extension config file

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Update license headers

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Run spotlessApply

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove authz changes and only keep TLS

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove authz changes and only keep TLS

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove authz changes and only keep TLS

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove authz changes and only keep TLS

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove authz changes and only keep TLS

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Update cert generation documents

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add ssl.transport.enabled in ExtensionsRunner

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Merge main into branch

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add instructions for running in SSL only mode

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add all SSL settings to extension settings

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Set default enforce_hostname_verification

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Run spotlessApply

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Respond to code review feedback

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Fix typos in debug messages

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add docstrings

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Address code review feedback

Signed-off-by: Craig Perkins <cwperx@amazon.com>

---------

Signed-off-by: Craig Perkins <cwperx@amazon.com>
  • Loading branch information
cwperks committed May 1, 2023
1 parent 9b6ac0c commit b13f257
Show file tree
Hide file tree
Showing 22 changed files with 2,773 additions and 16 deletions.
137 changes: 137 additions & 0 deletions Docs/CERTIFICATE_GENERATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Certificate Generation

Use the script below to generate certificates for running an extension.
The certificate for the extension needs to be in the same chain as the
root certificate for the OpenSearch cluster defined in the `plugins.security.ssl.http.pemtrustedcas_filepath`
setting of `opensearch.yml`


## Certificate Generation Script

```
#! /bin/bash
openssl genrsa -out root-ca-key.pem 2048
openssl req -new -x509 -sha256 -key root-ca-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=ROOT" -out root-ca.pem -days 730
openssl genrsa -out extension-01-key-temp.pem 2048
openssl pkcs8 -inform PEM -outform PEM -in extension-01-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out extension-01-key.pem
openssl req -new -key extension-01-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=extension-01" -out extension-01.csr
echo 'subjectAltName=DNS:extension-01' | tee -a extension-01.ext
echo 'subjectAltName=IP:172.20.0.11' | tee -a extension-01.ext
openssl x509 -req -in extension-01.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out extension-01.pem -days 730 -extfile extension-01.ext
rm extension-01-key-temp.pem
rm extension-01.csr
rm extension-01.ext
rm root-ca.srl
```

## Certificate Generation Script for OpenSearch with single node and admin cert

```
#! /bin/bash
openssl genrsa -out admin-key-temp.pem 2048
openssl pkcs8 -inform PEM -outform PEM -in admin-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out admin-key.pem
openssl req -new -key admin-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=A" -out admin.csr
openssl x509 -req -in admin.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out admin.pem -days 730
openssl genrsa -out os-node-01-key-temp.pem 2048
openssl pkcs8 -inform PEM -outform PEM -in os-node-01-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out os-node-01-key.pem
openssl req -new -key os-node-01-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=os-node-01" -out os-node-01.csr
echo 'subjectAltName=DNS:os-node-01' | tee -a os-node-01.ext
echo 'subjectAltName=IP:172.20.0.11' | tee -a os-node-01.ext
openssl x509 -req -in os-node-01.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out os-node-01.pem -days 730 -extfile os-node-01.ext
rm admin-key-temp.pem
rm admin.csr
rm os-node-01-key-temp.pem
rm os-node-01.csr
rm os-node-01.ext
rm root-ca.srl
```

## Install Security plugin and run in SSL only mode

To test an extension running with SSL and connected to an OpenSearch node with SSL you must install
the security plugin in the OpenSearch node and run it in SSL only mode.

Follow the steps below to test an extension running with TLS and connect to an OpenSearch node with
the security plugin and SSL enabled:

1. Create a local distribution of [OpenSearch](https://github.com/opensearch-project/opensearch) and move the output to location you would like to install
OpenSearch into:

```
cd opensearch
./gradlew localDistro
mv distribution/archives/darwin-tar/build/install/opensearch-<OPENSEARCH_VERSION>-SNAPSHOT/ ~/opensearch
```

2. Assemble the [Security plugin](https://github.com/opensearch-project/security) and move the output to the same directory

```
cd security
./gradlew assemble
mv build/distributions/opensearch-security-<OPENSEARCH_VERSION>.0-SNAPSHOT.zip ~/opensearch
```

3. Install the Security plugin and associated certificates created above

3.1. Navigate to the root of the OpenSearch installation:

```
cd ~/opensearch/opensearch-<OPENSEARCH_VERSION>-SNAPSHOT/
./bin/opensearch-plugin install file:$HOME/opensearch/opensearch-security-<OPENSEARCH_VERSION>.0-SNAPSHOT.zip
```

3.2 Copy the certificates generated above into `config/` directory

The certificates needed are:

- `os-node-01.pem`
- `os-node-01-key.pem`
- `root-ca.pem`

3.3 Add settings in `opensearch.yml`

Add the following settings in `opensearch.yml`

```
opensearch.experimental.feature.extensions.enabled: true
plugins.security.ssl_only: true
plugins.security.ssl.transport.pemcert_filepath: os-node-01.pem
plugins.security.ssl.transport.pemkey_filepath: os-node-01-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: root-ca.pem
plugins.security.ssl.transport.enforce_hostname_verification: false
plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemcert_filepath: os-node-01.pem
plugins.security.ssl.http.pemkey_filepath: os-node-01-key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: root-ca.pem
network.host: 0.0.0.0
```

4. Install certificates on extension

Installation for the OpenSearch node is now complete, the rest of the installation is for the extension.

Create a `config/` folder in the extension's home directory and install the certificates generated above.

The certificates you need to add are:

- `extension-01.pem`
- `extension-01-key.pem`
- `root-ca.pem`

4.1 Add references to these certifications in extension settings file and enable SSL

Add the following settings to the extension setting files. i.e. `helloworld-settings.yml`

```
ssl.transport.enabled: true
ssl.transport.pemcert_filepath: extension-01.pem
ssl.transport.pemkey_filepath: extension-01-key.pem
ssl.transport.pemtrustedcas_filepath: root-ca.pem
ssl.transport.enforce_hostname_verification: false
path.home: <path/to/extension>
```
89 changes: 88 additions & 1 deletion src/main/java/org/opensearch/sdk/ExtensionSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,34 @@
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.yaml.snakeyaml.Yaml;

import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_CLIENT_PEMTRUSTEDCAS_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_ENABLED;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_ENABLED_CIPHERS;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_ENABLED_PROTOCOLS;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_KEYSTORE_ALIAS;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_KEYSTORE_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_KEYSTORE_TYPE;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_PEMCERT_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_PEMKEY_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_SERVER_PEMCERT_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_TRUSTSTORE_ALIAS;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_TRUSTSTORE_FILEPATH;
import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_TRUSTSTORE_TYPE;

/**
* This class encapsulates the settings for an Extension.
*/
Expand All @@ -30,6 +54,33 @@ public class ExtensionSettings {
private String opensearchAddress;
private String opensearchPort;

public static final Set<String> SECURITY_SETTINGS_KEYS = Set.of(
"path.home", // TODO Find the right place to put this setting
SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH,
SSL_TRANSPORT_CLIENT_PEMKEY_FILEPATH,
SSL_TRANSPORT_CLIENT_PEMTRUSTEDCAS_FILEPATH,
SSL_TRANSPORT_ENABLED,
SSL_TRANSPORT_ENABLED_CIPHERS,
SSL_TRANSPORT_ENABLED_PROTOCOLS,
SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION,
SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME,
SSL_TRANSPORT_EXTENDED_KEY_USAGE_ENABLED,
SSL_TRANSPORT_KEYSTORE_ALIAS,
SSL_TRANSPORT_KEYSTORE_FILEPATH,
SSL_TRANSPORT_KEYSTORE_TYPE,
SSL_TRANSPORT_PEMCERT_FILEPATH,
SSL_TRANSPORT_PEMKEY_FILEPATH,
SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH,
SSL_TRANSPORT_SERVER_PEMCERT_FILEPATH,
SSL_TRANSPORT_SERVER_PEMKEY_FILEPATH,
SSL_TRANSPORT_SERVER_PEMTRUSTEDCAS_FILEPATH,
SSL_TRANSPORT_TRUSTSTORE_ALIAS,
SSL_TRANSPORT_TRUSTSTORE_FILEPATH,
SSL_TRANSPORT_TRUSTSTORE_TYPE
);

private Map<String, String> securitySettings;

/**
* Jackson requires a no-arg constructor.
*/
Expand All @@ -54,6 +105,29 @@ public ExtensionSettings(String extensionName, String hostAddress, String hostPo
this.hostPort = hostPort;
this.opensearchAddress = opensearchAddress;
this.opensearchPort = opensearchPort;
this.securitySettings = Map.of();
}

/**
* Instantiate this class using the specified parameters.
*
* @param extensionName The extension name. Provided to OpenSearch as a response to initialization query. Must match the defined extension name in OpenSearch.
* @param hostAddress The IP Address to bind this extension to.
* @param hostPort The port to bind this extension to.
* @param opensearchAddress The IP Address on which OpenSearch is running.
* @param opensearchPort The port on which OpenSearch is running.
* @param securitySettings A generic map of any settings set in the config file that are not default setting keys
*/
public ExtensionSettings(
String extensionName,
String hostAddress,
String hostPort,
String opensearchAddress,
String opensearchPort,
Map<String, String> securitySettings
) {
this(extensionName, hostAddress, hostPort, opensearchAddress, opensearchPort);
this.securitySettings = securitySettings;
}

public String getExtensionName() {
Expand All @@ -76,6 +150,10 @@ public String getOpensearchPort() {
return opensearchPort;
}

public Map<String, String> getSecuritySettings() {
return securitySettings;
}

@Override
public String toString() {
return "ExtensionSettings{extensionName="
Expand All @@ -88,6 +166,8 @@ public String toString() {
+ opensearchAddress
+ ", opensearchPort="
+ opensearchPort
+ ", securitySettings="
+ securitySettings
+ "}";
}

Expand All @@ -109,12 +189,19 @@ public static ExtensionSettings readSettingsFromYaml(String extensionSettingsPat
if (extensionMap == null) {
throw new IOException("extension.yml is empty");
}
Map<String, String> securitySettings = new HashMap<>();
for (String settingKey : extensionMap.keySet()) {
if (SECURITY_SETTINGS_KEYS.contains(settingKey)) {
securitySettings.put(settingKey, extensionMap.get(settingKey).toString());
}
}
return new ExtensionSettings(
extensionMap.get("extensionName").toString(),
extensionMap.get("hostAddress").toString(),
extensionMap.get("hostPort").toString(),
extensionMap.get("opensearchAddress").toString(),
extensionMap.get("opensearchPort").toString()
extensionMap.get("opensearchPort").toString(),
securitySettings
);
} catch (URISyntaxException e) {
throw new IOException("Error reading from extension.yml");
Expand Down
42 changes: 29 additions & 13 deletions src/main/java/org/opensearch/sdk/ExtensionsRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

package org.opensearch.sdk;

import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.action.ActionType;
Expand All @@ -25,20 +29,20 @@
import org.opensearch.common.settings.Settings;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.discovery.InitializeExtensionRequest;
import org.opensearch.extensions.DiscoveryExtensionNode;
import org.opensearch.extensions.AddSettingsUpdateConsumerRequest;
import org.opensearch.extensions.UpdateSettingsRequest;
import org.opensearch.extensions.action.ExtensionActionRequest;
import org.opensearch.extensions.DiscoveryExtensionNode;
import org.opensearch.extensions.ExtensionRequest;
import org.opensearch.extensions.ExtensionsManager;
import org.opensearch.extensions.UpdateSettingsRequest;
import org.opensearch.extensions.action.ExtensionActionRequest;
import org.opensearch.index.IndicesModuleRequest;
import org.opensearch.sdk.action.SDKActionModule;
import org.opensearch.sdk.handlers.AcknowledgedResponseHandler;
import org.opensearch.sdk.api.ActionExtension;
import org.opensearch.sdk.handlers.ClusterSettingsResponseHandler;
import org.opensearch.sdk.handlers.ClusterStateResponseHandler;
import org.opensearch.sdk.handlers.EnvironmentSettingsResponseHandler;
import org.opensearch.sdk.handlers.ExtensionActionRequestHandler;
import org.opensearch.sdk.action.SDKActionModule;
import org.opensearch.sdk.handlers.AcknowledgedResponseHandler;
import org.opensearch.sdk.handlers.ExtensionDependencyResponseHandler;
import org.opensearch.sdk.handlers.ExtensionsIndicesModuleNameRequestHandler;
import org.opensearch.sdk.handlers.ExtensionsIndicesModuleRequestHandler;
Expand All @@ -56,11 +60,6 @@
import org.opensearch.transport.TransportService;
import org.opensearch.transport.TransportSettings;

import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -74,6 +73,8 @@
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static org.opensearch.sdk.ssl.SSLConfigConstants.SSL_TRANSPORT_ENABLED;

/**
* The primary class to run an extension.
* <p>
Expand Down Expand Up @@ -179,11 +180,20 @@ protected ExtensionsRunner(Extension extension) throws IOException {
// These must have getters from this class to be accessible via createComponents
// If they require later initialization, create a concrete wrapper class and update the internals
ExtensionSettings extensionSettings = extension.getExtensionSettings();
this.settings = Settings.builder()
Settings.Builder settingsBuilder = Settings.builder()
.put(NODE_NAME_SETTING, extensionSettings.getExtensionName())
.put(TransportSettings.BIND_HOST.getKey(), extensionSettings.getHostAddress())
.put(TransportSettings.PORT.getKey(), extensionSettings.getHostPort())
.build();
.put(TransportSettings.PORT.getKey(), extensionSettings.getHostPort());
boolean sslEnabled = extensionSettings.getSecuritySettings().containsKey(SSL_TRANSPORT_ENABLED)
&& "true".equals(extensionSettings.getSecuritySettings().get(SSL_TRANSPORT_ENABLED));
if (sslEnabled) {
for (String settingsKey : ExtensionSettings.SECURITY_SETTINGS_KEYS) {
addSettingsToBuilder(settingsBuilder, settingsKey, extensionSettings);
}
}
String sslText = sslEnabled ? "enabled" : "disabled";
logger.info("SSL is " + sslText + " for transport");
this.settings = settingsBuilder.build();

final List<ExecutorBuilder<?>> executorBuilders = extension.getExecutorBuilders(settings);

Expand Down Expand Up @@ -250,6 +260,12 @@ protected ExtensionsRunner(Extension extension) throws IOException {
}
}

private void addSettingsToBuilder(Settings.Builder settingsBuilder, String settingKey, ExtensionSettings extensionSettings) {
if (extensionSettings.getSecuritySettings().containsKey(settingKey)) {
settingsBuilder.put(settingKey, extensionSettings.getSecuritySettings().get(settingKey));
}
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private void injectComponents(Binder b) {
extension.createComponents(this).stream().forEach(p -> b.bind((Class) p.getClass()).toInstance(p));
Expand Down

0 comments on commit b13f257

Please sign in to comment.