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

Crypto Metadata support in repo registration #9802

Merged
merged 2 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}
}