diff --git a/server/src/main/java/org/opensearch/action/admin/cluster/crypto/CryptoSettings.java b/server/src/main/java/org/opensearch/action/admin/cluster/crypto/CryptoSettings.java index 0d289e3552cdf..bd783b349bed4 100644 --- a/server/src/main/java/org/opensearch/action/admin/cluster/crypto/CryptoSettings.java +++ b/server/src/main/java/org/opensearch/action/admin/cluster/crypto/CryptoSettings.java @@ -31,7 +31,6 @@ * @opensearch.internal */ public class CryptoSettings implements Writeable, ToXContentObject { - private String keyProviderName; private String keyProviderType; private Settings settings = EMPTY_SETTINGS; diff --git a/server/src/main/java/org/opensearch/cluster/metadata/CryptoMetadata.java b/server/src/main/java/org/opensearch/cluster/metadata/CryptoMetadata.java index bf55ef0837ea9..27803cb106005 100644 --- a/server/src/main/java/org/opensearch/cluster/metadata/CryptoMetadata.java +++ b/server/src/main/java/org/opensearch/cluster/metadata/CryptoMetadata.java @@ -27,6 +27,10 @@ * @opensearch.internal */ public class CryptoMetadata implements Writeable { + static final public String CRYPTO_METADATA_KEY = "crypto_metadata"; + static final public String KEY_PROVIDER_NAME_KEY = "key_provider_name"; + static final public String KEY_PROVIDER_TYPE_KEY = "key_provider_type"; + static final public String SETTINGS_KEY = "settings"; private final String keyProviderName; private final String keyProviderType; private final Settings settings; @@ -104,17 +108,17 @@ public static CryptoMetadata fromXContent(XContentParser parser) throws IOExcept while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { String currentFieldName = parser.currentName(); - if ("key_provider_name".equals(currentFieldName)) { + if (KEY_PROVIDER_NAME_KEY.equals(currentFieldName)) { if (parser.nextToken() != XContentParser.Token.VALUE_STRING) { throw new OpenSearchParseException("failed to parse crypto metadata [{}], unknown type"); } keyProviderName = parser.text(); - } else if ("key_provider_type".equals(currentFieldName)) { + } else if (KEY_PROVIDER_TYPE_KEY.equals(currentFieldName)) { if (parser.nextToken() != XContentParser.Token.VALUE_STRING) { throw new OpenSearchParseException("failed to parse crypto metadata [{}], unknown type"); } keyProviderType = parser.text(); - } else if ("settings".equals(currentFieldName)) { + } else if (SETTINGS_KEY.equals(currentFieldName)) { if (parser.nextToken() != XContentParser.Token.START_OBJECT) { throw new OpenSearchParseException("failed to parse crypto metadata [{}], unknown type"); } @@ -130,10 +134,10 @@ public static CryptoMetadata fromXContent(XContentParser parser) throws IOExcept } public void toXContent(CryptoMetadata cryptoMetadata, XContentBuilder builder, ToXContent.Params params) throws IOException { - builder.startObject("crypto_metadata"); - builder.field("key_provider_name", cryptoMetadata.keyProviderName()); - builder.field("key_provider_type", cryptoMetadata.keyProviderType()); - builder.startObject("settings"); + builder.startObject(CRYPTO_METADATA_KEY); + builder.field(KEY_PROVIDER_NAME_KEY, cryptoMetadata.keyProviderName()); + builder.field(KEY_PROVIDER_TYPE_KEY, cryptoMetadata.keyProviderType()); + builder.startObject(SETTINGS_KEY); cryptoMetadata.settings().toXContent(builder, params); builder.endObject(); builder.endObject(); diff --git a/server/src/main/java/org/opensearch/cluster/metadata/RepositoriesMetadata.java b/server/src/main/java/org/opensearch/cluster/metadata/RepositoriesMetadata.java index 3cde53c3ae74b..1ab402e1bde4e 100644 --- a/server/src/main/java/org/opensearch/cluster/metadata/RepositoriesMetadata.java +++ b/server/src/main/java/org/opensearch/cluster/metadata/RepositoriesMetadata.java @@ -232,7 +232,7 @@ public static RepositoriesMetadata fromXContent(XContentParser parser) throws IO throw new OpenSearchParseException("failed to parse repository [{}], unknown type", name); } pendingGeneration = parser.longValue(); - } else if ("crypto_metadata".equals(currentFieldName)) { + } else if (CryptoMetadata.CRYPTO_METADATA_KEY.equals(currentFieldName)) { if (parser.nextToken() != XContentParser.Token.START_OBJECT) { throw new OpenSearchParseException("failed to parse repository [{}], unknown type", name); } diff --git a/server/src/main/java/org/opensearch/node/remotestore/RemoteStoreNodeAttribute.java b/server/src/main/java/org/opensearch/node/remotestore/RemoteStoreNodeAttribute.java index b514089966484..9cb171790dbe2 100644 --- a/server/src/main/java/org/opensearch/node/remotestore/RemoteStoreNodeAttribute.java +++ b/server/src/main/java/org/opensearch/node/remotestore/RemoteStoreNodeAttribute.java @@ -8,6 +8,7 @@ package org.opensearch.node.remotestore; +import org.opensearch.cluster.metadata.CryptoMetadata; import org.opensearch.cluster.metadata.RepositoriesMetadata; import org.opensearch.cluster.metadata.RepositoryMetadata; import org.opensearch.cluster.node.DiscoveryNode; @@ -36,6 +37,11 @@ public class RemoteStoreNodeAttribute { public static final String REMOTE_STORE_SEGMENT_REPOSITORY_NAME_ATTRIBUTE_KEY = "remote_store.segment.repository"; public static final String REMOTE_STORE_TRANSLOG_REPOSITORY_NAME_ATTRIBUTE_KEY = "remote_store.translog.repository"; public static final String REMOTE_STORE_REPOSITORY_TYPE_ATTRIBUTE_KEY_FORMAT = "remote_store.repository.%s.type"; + public static final String REMOTE_STORE_REPOSITORY_CRYPTO_ATTRIBUTE_KEY_FORMAT = "remote_store.repository.%s." + + CryptoMetadata.CRYPTO_METADATA_KEY; + public static final String REMOTE_STORE_REPOSITORY_CRYPTO_SETTINGS_PREFIX = REMOTE_STORE_REPOSITORY_CRYPTO_ATTRIBUTE_KEY_FORMAT + + "." + + CryptoMetadata.SETTINGS_KEY; public static final String REMOTE_STORE_REPOSITORY_SETTINGS_ATTRIBUTE_KEY_PREFIX = "remote_store.repository.%s.settings."; private final RepositoriesMetadata repositoriesMetadata; @@ -55,6 +61,34 @@ private String validateAttributeNonNull(DiscoveryNode node, String attributeKey) return attributeValue; } + private CryptoMetadata buildCryptoMetadata(DiscoveryNode node, String repositoryName) { + String metadataKey = String.format(Locale.getDefault(), REMOTE_STORE_REPOSITORY_CRYPTO_ATTRIBUTE_KEY_FORMAT, repositoryName); + boolean isRepoEncrypted = node.getAttributes().keySet().stream().anyMatch(key -> key.startsWith(metadataKey)); + if (isRepoEncrypted == false) { + return null; + } + + String keyProviderName = validateAttributeNonNull(node, metadataKey + "." + CryptoMetadata.KEY_PROVIDER_NAME_KEY); + String keyProviderType = validateAttributeNonNull(node, metadataKey + "." + CryptoMetadata.KEY_PROVIDER_TYPE_KEY); + + String settingsAttributeKeyPrefix = String.format( + Locale.getDefault(), + REMOTE_STORE_REPOSITORY_CRYPTO_SETTINGS_PREFIX, + repositoryName + ); + + Map settingsMap = node.getAttributes() + .keySet() + .stream() + .filter(key -> key.startsWith(settingsAttributeKeyPrefix)) + .collect(Collectors.toMap(key -> key.replace(settingsAttributeKeyPrefix + ".", ""), key -> node.getAttributes().get(key))); + + Settings.Builder settings = Settings.builder(); + settingsMap.forEach(settings::put); + + return new CryptoMetadata(keyProviderName, keyProviderType, settings.build()); + } + private Map validateSettingsAttributesNonNull(DiscoveryNode node, String repositoryName) { String settingsAttributeKeyPrefix = String.format( Locale.getDefault(), @@ -86,10 +120,12 @@ private RepositoryMetadata buildRepositoryMetadata(DiscoveryNode node, String na Settings.Builder settings = Settings.builder(); settingsMap.forEach(settings::put); + CryptoMetadata cryptoMetadata = buildCryptoMetadata(node, name); + // Repository metadata built here will always be for a system repository. settings.put(BlobStoreRepository.SYSTEM_REPOSITORY_SETTING.getKey(), true); - return new RepositoryMetadata(name, type, settings.build()); + return new RepositoryMetadata(name, type, settings.build(), cryptoMetadata); } private RepositoriesMetadata buildRepositoriesMetadata(DiscoveryNode node) { diff --git a/server/src/test/java/org/opensearch/node/RemoteStoreNodeAttributeTests.java b/server/src/test/java/org/opensearch/node/RemoteStoreNodeAttributeTests.java new file mode 100644 index 0000000000000..5206064273334 --- /dev/null +++ b/server/src/test/java/org/opensearch/node/RemoteStoreNodeAttributeTests.java @@ -0,0 +1,126 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.node; + +import org.opensearch.Version; +import org.opensearch.cluster.metadata.CryptoMetadata; +import org.opensearch.cluster.metadata.RepositoryMetadata; +import org.opensearch.cluster.node.DiscoveryNode; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.transport.TransportAddress; +import org.opensearch.node.remotestore.RemoteStoreNodeAttribute; +import org.opensearch.test.OpenSearchTestCase; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Map; + +import static java.util.Collections.emptySet; + +public class RemoteStoreNodeAttributeTests extends OpenSearchTestCase { + + static private final String KEY_ARN = "arn:aws:kms:us-east-1:123456789:key/6e9aa906-2cc3-4924-8ded-f385c78d9dcf"; + static private final String REGION = "us-east-1"; + + public void testCryptoMetadata() throws UnknownHostException { + Map attr = Map.of( + "remote_store.segment.repository", + "remote-store-A", + "remote_store.translog.repository", + "remote-store-A", + "remote_store.repository.remote-store-A.type", + "s3", + "remote_store.repository.remote-store-A.settings.bucket", + "abc", + "remote_store.repository.remote-store-A.settings.base_path", + "xyz", + "remote_store.repository.remote-store-A.crypto_metadata.key_provider_name", + "store-test", + "remote_store.repository.remote-store-A.crypto_metadata.key_provider_type", + "aws-kms", + "remote_store.repository.remote-store-A.crypto_metadata.settings.region", + REGION, + "remote_store.repository.remote-store-A.crypto_metadata.settings.key_arn", + KEY_ARN + ); + DiscoveryNode node = new DiscoveryNode( + "C", + new TransportAddress(InetAddress.getByName("localhost"), 9876), + attr, + emptySet(), + Version.CURRENT + ); + + RemoteStoreNodeAttribute remoteStoreNodeAttribute = new RemoteStoreNodeAttribute(node); + assertEquals(remoteStoreNodeAttribute.getRepositoriesMetadata().repositories().size(), 1); + RepositoryMetadata repositoryMetadata = remoteStoreNodeAttribute.getRepositoriesMetadata().repositories().get(0); + Settings.Builder settings = Settings.builder(); + settings.put("region", REGION); + settings.put("key_arn", KEY_ARN); + CryptoMetadata cryptoMetadata = new CryptoMetadata("store-test", "aws-kms", settings.build()); + assertEquals(cryptoMetadata, repositoryMetadata.cryptoMetadata()); + } + + public void testInvalidCryptoMetadata() throws UnknownHostException { + Map attr = Map.of( + "remote_store.segment.repository", + "remote-store-A", + "remote_store.translog.repository", + "remote-store-A", + "remote_store.repository.remote-store-A.type", + "s3", + "remote_store.repository.remote-store-A.settings.bucket", + "abc", + "remote_store.repository.remote-store-A.settings.base_path", + "xyz", + "remote_store.repository.remote-store-A.crypto_metadata.key_provider_name", + "store-test", + "remote_store.repository.remote-store-A.crypto_metadata.settings.region", + REGION, + "remote_store.repository.remote-store-A.crypto_metadata.settings.key_arn", + KEY_ARN + ); + DiscoveryNode node = new DiscoveryNode( + "C", + new TransportAddress(InetAddress.getByName("localhost"), 9876), + attr, + emptySet(), + Version.CURRENT + ); + + assertThrows(IllegalStateException.class, () -> new RemoteStoreNodeAttribute(node)); + } + + public void testNoCryptoMetadata() throws UnknownHostException { + Map attr = Map.of( + "remote_store.segment.repository", + "remote-store-A", + "remote_store.translog.repository", + "remote-store-A", + "remote_store.repository.remote-store-A.type", + "s3", + "remote_store.repository.remote-store-A.settings.bucket", + "abc", + "remote_store.repository.remote-store-A.settings.base_path", + "xyz" + ); + DiscoveryNode node = new DiscoveryNode( + "C", + new TransportAddress(InetAddress.getByName("localhost"), 9876), + attr, + emptySet(), + Version.CURRENT + ); + + RemoteStoreNodeAttribute remoteStoreNodeAttribute = new RemoteStoreNodeAttribute(node); + assertEquals(remoteStoreNodeAttribute.getRepositoriesMetadata().repositories().size(), 1); + RepositoryMetadata repositoryMetadata = remoteStoreNodeAttribute.getRepositoriesMetadata().repositories().get(0); + assertNull(repositoryMetadata.cryptoMetadata()); + } +}