-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Reintroduce the ability to configure S3 repository credentials in cluster state #88652
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,31 +14,54 @@ | |
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.elasticsearch.client.internal.node.NodeClient; | ||
import org.elasticsearch.cluster.metadata.RepositoryMetadata; | ||
import org.elasticsearch.cluster.service.ClusterService; | ||
import org.elasticsearch.common.settings.MockSecureSettings; | ||
import org.elasticsearch.common.settings.Settings; | ||
import org.elasticsearch.common.settings.SettingsFilter; | ||
import org.elasticsearch.common.util.BigArrays; | ||
import org.elasticsearch.core.SuppressForbidden; | ||
import org.elasticsearch.env.Environment; | ||
import org.elasticsearch.indices.recovery.RecoverySettings; | ||
import org.elasticsearch.plugins.Plugin; | ||
import org.elasticsearch.plugins.PluginsService; | ||
import org.elasticsearch.repositories.RepositoriesService; | ||
import org.elasticsearch.rest.AbstractRestChannel; | ||
import org.elasticsearch.rest.RestRequest; | ||
import org.elasticsearch.rest.RestResponse; | ||
import org.elasticsearch.rest.action.admin.cluster.RestGetRepositoriesAction; | ||
import org.elasticsearch.test.ESSingleNodeTestCase; | ||
import org.elasticsearch.test.rest.FakeRestRequest; | ||
import org.elasticsearch.xcontent.NamedXContentRegistry; | ||
|
||
import java.security.AccessController; | ||
import java.security.PrivilegedAction; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.concurrent.CountDownLatch; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
|
||
import static org.elasticsearch.repositories.s3.S3ClientSettings.ACCESS_KEY_SETTING; | ||
import static org.elasticsearch.repositories.s3.S3ClientSettings.SECRET_KEY_SETTING; | ||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; | ||
import static org.hamcrest.Matchers.containsString; | ||
import static org.hamcrest.Matchers.instanceOf; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.hamcrest.Matchers.not; | ||
import static org.hamcrest.Matchers.notNullValue; | ||
|
||
@SuppressForbidden(reason = "test requires to set a System property to allow insecure settings when running in IDE") | ||
public class RepositoryCredentialsTests extends ESSingleNodeTestCase { | ||
|
||
static { | ||
AccessController.doPrivileged((PrivilegedAction<Void>) () -> { | ||
// required for client settings overwriting when running in IDE | ||
System.setProperty("es.allow_insecure_settings", "true"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this needed when it is set via gradle? This doesn't seem right since it is setting the property forever, leaking into any test suites that run after it. Setting via gradle is the way to go (along with the isolated task for this test configuration). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair point, I just copied it from 7.x as it's done there. Maybe do a follow-up removing the hack from both and backport that follow-up to 7.x as well. |
||
return null; | ||
}); | ||
} | ||
|
||
@Override | ||
protected Collection<Class<? extends Plugin>> getPlugins() { | ||
return List.of(ProxyS3RepositoryPlugin.class); | ||
|
@@ -60,11 +83,51 @@ protected Settings nodeSettings() { | |
return Settings.builder().setSecureSettings(secureSettings).put(super.nodeSettings()).build(); | ||
} | ||
|
||
public void testRepositoryCredentialsOverrideSecureCredentials() { | ||
final String repositoryName = "repo-creds-override"; | ||
final Settings.Builder repositorySettings = Settings.builder() | ||
// repository settings for credentials override node secure settings | ||
.put(S3Repository.ACCESS_KEY_SETTING.getKey(), "insecure_aws_key") | ||
.put(S3Repository.SECRET_KEY_SETTING.getKey(), "insecure_aws_secret"); | ||
|
||
final String clientName = randomFrom("default", "other", null); | ||
if (clientName != null) { | ||
repositorySettings.put(S3Repository.CLIENT_NAME.getKey(), clientName); | ||
} | ||
createRepository(repositoryName, repositorySettings.build()); | ||
|
||
final RepositoriesService repositories = getInstanceFromNode(RepositoriesService.class); | ||
assertThat(repositories.repository(repositoryName), notNullValue()); | ||
assertThat(repositories.repository(repositoryName), instanceOf(S3Repository.class)); | ||
|
||
final S3Repository repository = (S3Repository) repositories.repository(repositoryName); | ||
final AmazonS3 client = repository.createBlobStore().clientReference().client(); | ||
assertThat(client, instanceOf(ProxyS3RepositoryPlugin.ClientAndCredentials.class)); | ||
|
||
final AWSCredentials credentials = ((ProxyS3RepositoryPlugin.ClientAndCredentials) client).credentials.getCredentials(); | ||
assertThat(credentials.getAWSAccessKeyId(), is("insecure_aws_key")); | ||
assertThat(credentials.getAWSSecretKey(), is("insecure_aws_secret")); | ||
|
||
assertCriticalWarnings( | ||
"[secret_key] setting was deprecated in Elasticsearch and will be removed in a future release.", | ||
"Using s3 access/secret key from repository settings. Instead store these in named clients and" | ||
+ " the elasticsearch keystore for secure settings.", | ||
"[access_key] setting was deprecated in Elasticsearch and will be removed in a future release." | ||
); | ||
} | ||
|
||
public void testReinitSecureCredentials() { | ||
final String clientName = randomFrom("default", "other"); | ||
|
||
final Settings.Builder repositorySettings = Settings.builder(); | ||
repositorySettings.put(S3Repository.CLIENT_NAME.getKey(), clientName); | ||
final boolean hasInsecureSettings = randomBoolean(); | ||
if (hasInsecureSettings) { | ||
// repository settings for credentials override node secure settings | ||
repositorySettings.put(S3Repository.ACCESS_KEY_SETTING.getKey(), "insecure_aws_key"); | ||
repositorySettings.put(S3Repository.SECRET_KEY_SETTING.getKey(), "insecure_aws_secret"); | ||
} else { | ||
repositorySettings.put(S3Repository.CLIENT_NAME.getKey(), clientName); | ||
} | ||
|
||
final String repositoryName = "repo-reinit-creds"; | ||
createRepository(repositoryName, repositorySettings.build()); | ||
|
@@ -79,7 +142,10 @@ public void testReinitSecureCredentials() { | |
assertThat(client, instanceOf(ProxyS3RepositoryPlugin.ClientAndCredentials.class)); | ||
|
||
final AWSCredentials credentials = ((ProxyS3RepositoryPlugin.ClientAndCredentials) client).credentials.getCredentials(); | ||
if ("other".equals(clientName)) { | ||
if (hasInsecureSettings) { | ||
assertThat(credentials.getAWSAccessKeyId(), is("insecure_aws_key")); | ||
assertThat(credentials.getAWSSecretKey(), is("insecure_aws_secret")); | ||
} else if ("other".equals(clientName)) { | ||
assertThat(credentials.getAWSAccessKeyId(), is("secure_other_key")); | ||
assertThat(credentials.getAWSSecretKey(), is("secure_other_secret")); | ||
} else { | ||
|
@@ -98,7 +164,10 @@ public void testReinitSecureCredentials() { | |
plugin.reload(newSettings); | ||
|
||
// check the not-yet-closed client reference still has the same credentials | ||
if ("other".equals(clientName)) { | ||
if (hasInsecureSettings) { | ||
assertThat(credentials.getAWSAccessKeyId(), is("insecure_aws_key")); | ||
assertThat(credentials.getAWSSecretKey(), is("insecure_aws_secret")); | ||
} else if ("other".equals(clientName)) { | ||
assertThat(credentials.getAWSAccessKeyId(), is("secure_other_key")); | ||
assertThat(credentials.getAWSSecretKey(), is("secure_other_secret")); | ||
} else { | ||
|
@@ -113,11 +182,66 @@ public void testReinitSecureCredentials() { | |
assertThat(client, instanceOf(ProxyS3RepositoryPlugin.ClientAndCredentials.class)); | ||
|
||
final AWSCredentials newCredentials = ((ProxyS3RepositoryPlugin.ClientAndCredentials) client).credentials.getCredentials(); | ||
assertThat(newCredentials.getAWSAccessKeyId(), is("new_secret_aws_key")); | ||
assertThat(newCredentials.getAWSSecretKey(), is("new_secret_aws_secret")); | ||
if (hasInsecureSettings) { | ||
assertThat(newCredentials.getAWSAccessKeyId(), is("insecure_aws_key")); | ||
assertThat(newCredentials.getAWSSecretKey(), is("insecure_aws_secret")); | ||
} else { | ||
assertThat(newCredentials.getAWSAccessKeyId(), is("new_secret_aws_key")); | ||
assertThat(newCredentials.getAWSSecretKey(), is("new_secret_aws_secret")); | ||
} | ||
} | ||
|
||
if (hasInsecureSettings) { | ||
assertCriticalWarnings( | ||
"[secret_key] setting was deprecated in Elasticsearch and will be removed in a future release.", | ||
"Using s3 access/secret key from repository settings. Instead store these in named clients and" | ||
+ " the elasticsearch keystore for secure settings.", | ||
"[access_key] setting was deprecated in Elasticsearch and will be removed in a future release." | ||
); | ||
} | ||
} | ||
|
||
public void testInsecureRepositoryCredentials() throws Exception { | ||
final String repositoryName = "repo-insecure-creds"; | ||
createRepository( | ||
repositoryName, | ||
Settings.builder() | ||
.put(S3Repository.ACCESS_KEY_SETTING.getKey(), "insecure_aws_key") | ||
.put(S3Repository.SECRET_KEY_SETTING.getKey(), "insecure_aws_secret") | ||
.build() | ||
); | ||
|
||
final RestRequest fakeRestRequest = new FakeRestRequest(); | ||
fakeRestRequest.params().put("repository", repositoryName); | ||
final RestGetRepositoriesAction action = new RestGetRepositoriesAction(getInstanceFromNode(SettingsFilter.class)); | ||
|
||
final CountDownLatch latch = new CountDownLatch(1); | ||
final AtomicReference<AssertionError> error = new AtomicReference<>(); | ||
action.handleRequest(fakeRestRequest, new AbstractRestChannel(fakeRestRequest, true) { | ||
@Override | ||
public void sendResponse(RestResponse response) { | ||
try { | ||
String responseAsString = response.content().utf8ToString(); | ||
assertThat(responseAsString, containsString(repositoryName)); | ||
assertThat(responseAsString, not(containsString("insecure_"))); | ||
} catch (final AssertionError ex) { | ||
error.set(ex); | ||
} | ||
latch.countDown(); | ||
} | ||
}, getInstanceFromNode(NodeClient.class)); | ||
|
||
latch.await(); | ||
if (error.get() != null) { | ||
throw error.get(); | ||
} | ||
|
||
assertWarnings( | ||
"Using s3 access/secret key from repository settings. Instead store these in named clients and" | ||
+ " the elasticsearch keystore for secure settings." | ||
); | ||
} | ||
|
||
private void createRepository(final String name, final Settings repositorySettings) { | ||
assertAcked( | ||
client().admin() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding this back both as a cluster setting (fallback) and a repository specific setting, could we only add back the latter? Then we wouldn't need to register the setting, nor have the hack to allow non-dotted names for this edge case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't really have repository settings registered anywhere do we? As with my other comment, I had to add this back for the setting filtering on the REST layer to work so we don't serialize this back to the user (maybe there's a better workaround for this?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"entity" settings as I have been calling them recently are not registered anywhere. The settings registered here in Plugin.getSettings are for node, cluster and index settings. So if the intention is to only have this settable directly in the repositories rest api, the there is no need to register it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get that we're abusing setting registering here, we definitely don't need these as cluster/node or index setting but we need the setting filtering during x-content serialization for these, which seems is only available by registering the setting as a filtered setting as done here (and in 7.x for the same reason).
Can I get the filtering in any other way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, I understand now. I don’t know of another way at the moment.