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

Update renewed Jira refresh token back to the secrets store #5324

Merged
merged 19 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
aa7ca59
Introduced PluginConfigVariable interaface to provide ability for the…
san81 Jan 13, 2025
0c34119
renewed access token and refresh tokens are now updated back in the s…
san81 Jan 13, 2025
cc82281
better naming
san81 Jan 13, 2025
2f640df
Merge branch 'opensearch-project:main' into secrets-variable-interface
san81 Jan 13, 2025
64bf7bc
fixing the test cases based on the new PluginConfigVariable attribute…
san81 Jan 13, 2025
4df4fba
improving the coverage
san81 Jan 13, 2025
a384d85
Keeping the existing values in the secret. Just updating an existing key
san81 Jan 14, 2025
2f26378
Merge branch 'opensearch-project:main' into secrets-variable-interface
san81 Jan 15, 2025
ca1ec88
Allowing secrets manager update without a key and also some additiona…
san81 Jan 15, 2025
2f98ebc
isUpdatable boolean is introduced and its corresponding tests
san81 Jan 15, 2025
f8802e5
additional coverage
san81 Jan 16, 2025
eb2e431
implementing newly added method
san81 Jan 16, 2025
76a136a
switching PluginConfigVariable from refreshToken to accessToken
san81 Jan 16, 2025
188264e
Only the master node is responsible for Token refresh
san81 Jan 16, 2025
009eafe
Added addition parameter in the API to accept the secrets version to …
san81 Jan 16, 2025
78de3aa
better naming
san81 Jan 16, 2025
7b15c95
removing setting a versionId for idempotency
san81 Jan 16, 2025
1d14234
removed constructor argument to PluginConfigVariable
san81 Jan 16, 2025
8b946af
Merge branch 'opensearch-project:main' into secrets-variable-interface
san81 Jan 17, 2025
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 @@ -4,4 +4,6 @@ public interface PluginConfigValueTranslator {
Object translate(final String value);

String getPrefix();

PluginConfigVariable translateToPluginConfigVariable(final String value);
san81 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright OpenSearch Contributors
* 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.dataprepper.model.plugin;

/**
* Interface for a Extension Plugin configuration variable.
* It gives access to the details of a defined extension variable.
*
* @since 1.2
san81 marked this conversation as resolved.
Show resolved Hide resolved
*/
public interface PluginConfigVariable {
san81 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Returns the value of this variable.
san81 marked this conversation as resolved.
Show resolved Hide resolved
*/
Object getValue();

/**
* If this variable is updatable, this method helps to set a new value for this variable
san81 marked this conversation as resolved.
Show resolved Hide resolved
*/
void setValue(Object someValue);
san81 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.opensearch.dataprepper.model.event.EventKey;
import org.opensearch.dataprepper.model.event.EventKeyFactory;
import org.opensearch.dataprepper.model.plugin.PluginConfigVariable;
import org.opensearch.dataprepper.model.types.ByteCount;
import org.opensearch.dataprepper.pipeline.parser.ByteCountDeserializer;
import org.opensearch.dataprepper.pipeline.parser.DataPrepperDurationDeserializer;
Expand All @@ -28,7 +29,7 @@
public class ObjectMapperConfiguration {
static final Set<Class> TRANSLATE_VALUE_SUPPORTED_JAVA_TYPES = Set.of(
String.class, Number.class, Long.class, Short.class, Integer.class, Double.class, Float.class,
Boolean.class, Character.class);
Boolean.class, Character.class, PluginConfigVariable.class);

@Bean(name = "extensionPluginConfigObjectMapper")
ObjectMapper extensionPluginConfigObjectMapper() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.opensearch.dataprepper.model.plugin.PluginConfigValueTranslator;
import org.opensearch.dataprepper.model.plugin.PluginConfigVariable;

import javax.inject.Inject;
import javax.inject.Named;
Expand All @@ -29,8 +30,7 @@ public class VariableExpander {

@Inject
public VariableExpander(
@Named("extensionPluginConfigObjectMapper")
final ObjectMapper objectMapper,
@Named("extensionPluginConfigObjectMapper") final ObjectMapper objectMapper,
final Set<PluginConfigValueTranslator> pluginConfigValueTranslators) {
this.objectMapper = objectMapper;
patternPluginConfigValueTranslatorMap = pluginConfigValueTranslators.stream().collect(Collectors.toMap(
Expand All @@ -48,8 +48,13 @@ public <T> T translate(final JsonParser jsonParser, final Class<T> destinationTy
.filter(entry -> entry.getKey().matches())
.map(entry -> {
final String valueReferenceKey = entry.getKey().group(VALUE_REFERENCE_KEY);
return objectMapper.convertValue(
entry.getValue().translate(valueReferenceKey), destinationType);
if (destinationType.equals(PluginConfigVariable.class)) {
san81 marked this conversation as resolved.
Show resolved Hide resolved
return (T) entry.getValue().translateToPluginConfigVariable(valueReferenceKey);
} else {
return objectMapper.convertValue(
entry.getValue().translate(valueReferenceKey), destinationType);
}

})
.findFirst()
.orElseGet(() -> objectMapper.convertValue(rawValue, destinationType));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright OpenSearch Contributors
* 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.dataprepper.plugins.aws;

import org.opensearch.dataprepper.model.plugin.PluginConfigVariable;

/**
* AWS Plugin configuration variable implementation.
*/
public class AwsPluginConfigVariable implements PluginConfigVariable {

private final SecretsSupplier secretsSupplier;
private final String secretId;
private final String secretKey;
private Object secretValue;

public AwsPluginConfigVariable(final SecretsSupplier secretsSupplier,
final String secretId, final String secretKey, Object secretValue) {
this.secretsSupplier = secretsSupplier;
this.secretId = secretId;
this.secretKey = secretKey;
this.secretValue = secretValue;
}

@Override
public Object getValue() {
return secretValue;
}

@Override
public void setValue(Object newValue) {
this.secretsSupplier.updateValue(secretId, secretKey, newValue);
this.secretValue = newValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import software.amazon.awssdk.services.secretsmanager.model.PutSecretValueRequest;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
Expand Down Expand Up @@ -78,6 +79,14 @@ public GetSecretValueRequest createGetSecretValueRequest() {
.build();
}

public PutSecretValueRequest putSecretValueRequest(String keyToUpdate, Object newValue) {
String updatedSecretString = String.format("{\"%s\": \"%s\"}", keyToUpdate, newValue);
san81 marked this conversation as resolved.
Show resolved Hide resolved
return PutSecretValueRequest.builder()
.secretId(awsSecretId)
.secretString(updatedSecretString)
.build();
}

private AwsCredentialsProvider authenticateAwsConfiguration() {

final AwsCredentialsProvider awsCredentialsProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.opensearch.dataprepper.plugins.aws;

import org.opensearch.dataprepper.model.plugin.PluginConfigValueTranslator;
import org.opensearch.dataprepper.model.plugin.PluginConfigVariable;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -43,4 +44,19 @@ public Object translate(final String value) {
public String getPrefix() {
return AWS_SECRETS_PREFIX;
}

@Override
public PluginConfigVariable translateToPluginConfigVariable(String value) {
final Matcher matcher = SECRETS_REF_PATTERN.matcher(value);
if (!matcher.matches()) {
throw new IllegalArgumentException(String.format(
"Unable to parse %s or %s according to pattern %s",
SECRET_CONFIGURATION_ID_GROUP, SECRET_KEY_GROUP, SECRETS_REF_PATTERN.pattern()));
}
final String secretId = matcher.group(SECRET_CONFIGURATION_ID_GROUP);
final String secretKey = matcher.group(SECRET_KEY_GROUP);
final Object secretValue = secretKey != null ? secretsSupplier.retrieveValue(secretId, secretKey) :
secretsSupplier.retrieveValue(secretId);
return new AwsPluginConfigVariable(secretsSupplier, secretId, secretKey, secretValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
import software.amazon.awssdk.services.secretsmanager.model.PutSecretValueRequest;
import software.amazon.awssdk.services.secretsmanager.model.PutSecretValueResponse;

import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

public class AwsSecretsSupplier implements SecretsSupplier {
private static final Logger LOG = LoggerFactory.getLogger(AwsSecretsSupplier.class);
static final TypeReference<Map<String, String>> MAP_TYPE_REFERENCE = new TypeReference<>() {
};

private static final Logger LOG = LoggerFactory.getLogger(AwsSecretsSupplier.class);
private final SecretValueDecoder secretValueDecoder;
private final ObjectMapper objectMapper;
private final Map<String, AwsSecretManagerConfiguration> awsSecretManagerConfigurationMap;
Expand Down Expand Up @@ -94,6 +95,7 @@ public Object retrieveValue(String secretId) {
}
}


@Override
public void refresh(String secretConfigId) {
LOG.info("Retrieving latest secrets in aws:secrets:{}.", secretConfigId);
Expand Down Expand Up @@ -126,4 +128,23 @@ private Object retrieveSecretsFromSecretManager(final AwsSecretManagerConfigurat
return secretValueDecoder.decode(getSecretValueResponse);
}
}

@Override
public String updateValue(String secretId, String keyToUpdate, Object newValue) {
AwsSecretManagerConfiguration awsSecretManagerConfiguration = awsSecretManagerConfigurationMap.get(secretId);
PutSecretValueRequest putSecretValueRequest =
awsSecretManagerConfiguration.putSecretValueRequest(keyToUpdate, newValue);
SecretsManagerClient secretsManagerClient = secretsManagerClientMap.get(secretId);

try {
final PutSecretValueResponse putSecretValueResponse = secretsManagerClient.putSecretValue(putSecretValueRequest);
LOG.info("Updated key: {} in the secret {}. New version of the store is {}",
keyToUpdate, secretId, putSecretValueResponse.versionId());
return putSecretValueResponse.versionId();
} catch (Exception e) {
throw new RuntimeException(
san81 marked this conversation as resolved.
Show resolved Hide resolved
String.format("Unable to update secret: %s to put a new value for the key: %s",
awsSecretManagerConfiguration.getAwsSecretId(), keyToUpdate), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,15 @@ public interface SecretsSupplier {
Object retrieveValue(String secretId);

void refresh(String secretId);

/**
* Update the value of a secret key in the secret store and responds
* with the version id of the secret after the update.
*
* @param secretId The id of the secret to be updated
* @param keyToUpdate The key of the secret to be updated
* @param newValueToSet The value of the secret to be updated
* @return The version id of the secret after the update
*/
String updateValue(String secretId, String keyToUpdate, Object newValueToSet);
san81 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import software.amazon.awssdk.services.secretsmanager.model.PutSecretValueRequest;
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;

import java.io.IOException;
Expand Down Expand Up @@ -59,9 +60,15 @@ class AwsSecretManagerConfigurationTest {
@Mock
private GetSecretValueRequest.Builder getSecretValueRequestBuilder;

@Mock
private PutSecretValueRequest.Builder putSecretValueRequestBuilder;

@Mock
private GetSecretValueRequest getSecretValueRequest;

@Mock
private PutSecretValueRequest putSecretValueRequest;

@Mock
private SecretsManagerClientBuilder secretsManagerClientBuilder;

Expand Down Expand Up @@ -131,6 +138,24 @@ void testCreateGetSecretValueRequest() throws IOException {
verify(getSecretValueRequestBuilder).secretId("test-secret");
}

@Test
void testCreatePutSecretValueRequest() throws IOException {
san81 marked this conversation as resolved.
Show resolved Hide resolved
when(putSecretValueRequestBuilder.secretId(anyString())).thenReturn(putSecretValueRequestBuilder);
when(putSecretValueRequestBuilder.secretString(anyString())).thenReturn(putSecretValueRequestBuilder);
when(putSecretValueRequestBuilder.build()).thenReturn(putSecretValueRequest);
final InputStream inputStream = AwsSecretPluginConfigTest.class.getResourceAsStream(
"/test-aws-secret-manager-configuration-default.yaml");
final AwsSecretManagerConfiguration awsSecretManagerConfiguration = objectMapper.readValue(
inputStream, AwsSecretManagerConfiguration.class);
try (final MockedStatic<PutSecretValueRequest> putSecretValueRequestMockedStatic =
mockStatic(PutSecretValueRequest.class)) {
putSecretValueRequestMockedStatic.when(PutSecretValueRequest::builder).thenReturn(
putSecretValueRequestBuilder);
assertThat(awsSecretManagerConfiguration.putSecretValueRequest("keyToUpdate", "newValue"), is(putSecretValueRequest));
}
verify(putSecretValueRequestBuilder).secretId("test-secret");
}

@Test
void testCreateSecretManagerClientWithDefaultCredential() throws IOException {
final InputStream inputStream = AwsSecretPluginConfigTest.class.getResourceAsStream(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.opensearch.dataprepper.model.plugin.PluginConfigVariable;

import java.util.UUID;

Expand Down Expand Up @@ -65,4 +66,33 @@ void testTranslateSecretIdWithoutKeyMatch() {
when(secretsSupplier.retrieveValue(eq(testSecretName))).thenReturn(testSecretValue);
assertThat(objectUnderTest.translate(testSecretName), equalTo(testSecretValue));
}

@Test
void testTranslateToPluginConfigVariableWithoutKeyMatch() {
final String testSecretName = "valid@secret-manager_name";
final String testSecretValue = UUID.randomUUID().toString();
when(secretsSupplier.retrieveValue(eq(testSecretName))).thenReturn(testSecretValue);
PluginConfigVariable pluginConfigVariable = objectUnderTest.translateToPluginConfigVariable(testSecretName);
assertThat(pluginConfigVariable.getValue(), equalTo(testSecretValue));
}

@Test
void testTranslateToPluginConfigVariableWithKeyMatch() {
final String testSecretName = "valid@secret-manager_name";
final String testSecretKey = UUID.randomUUID().toString();
final String testSecretValue = UUID.randomUUID().toString();
final String input = String.format("%s:%s", testSecretName, testSecretKey);
when(secretsSupplier.retrieveValue(eq(testSecretName), eq(testSecretKey))).thenReturn(testSecretValue);
PluginConfigVariable pluginConfigVariable = objectUnderTest.translateToPluginConfigVariable(input);
assertThat(pluginConfigVariable.getValue(), equalTo(testSecretValue));
}

@ParameterizedTest
@ValueSource(strings = {
"",
"invalid secret id with space:secret_key"
})
void testTranslateToPluginConfigVariableInputNoMatch(final String input) {
assertThrows(IllegalArgumentException.class, () -> objectUnderTest.translateToPluginConfigVariable(input));
}
}
Loading
Loading