Skip to content

Commit

Permalink
Crypto Metadata support in repo registration (#9802)
Browse files Browse the repository at this point in the history
Signed-off-by: Gaurav Bafna <gbbafna@amazon.com>
  • Loading branch information
gbbafna authored Sep 6, 2023
1 parent 1b32913 commit 742267d
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
* @opensearch.internal
*/
public class CryptoSettings implements Writeable, ToXContentObject {

private String keyProviderName;
private String keyProviderType;
private Settings settings = EMPTY_SETTINGS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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<String, String> 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<String, String> validateSettingsAttributesNonNull(DiscoveryNode node, String repositoryName) {
String settingsAttributeKeyPrefix = String.format(
Locale.getDefault(),
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<String, String> 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<String, String> 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());
}
}

0 comments on commit 742267d

Please sign in to comment.