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

AWS secrets manager store #105

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<module>vertx-config-zookeeper</module>
<module>vertx-config-consul</module>
<module>vertx-config-vault</module>
<module>vertx-config-aws-secrets-manager</module>
</modules>

<scm>
Expand Down
32 changes: 32 additions & 0 deletions vertx-config-aws-secrets-manager/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>vertx-config-parent</artifactId>
<groupId>io.vertx</groupId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>vertx-config-aws-secrets-manager</artifactId>

<properties>
<doc.skip>false</doc.skip>
</properties>

<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-config</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-secretsmanager</artifactId>
<version>1.11.697</version>
</dependency>
</dependencies>

</project>
46 changes: 46 additions & 0 deletions vertx-config-aws-secrets-manager/src/main/asciidoc/aws-store.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
=== AWS Secrets Manager Configuration Store

The AWS Secrets Manager Configuration Store extends the Vert.x Configuration Retriever and provides the way to retrieve configuration from AWS Secrets Manager.
It uses the AWS SDK as a client.

==== Using the AWS Secrets Manager Configuration Store

To use the AWS Secrets Manager Configuration Store, add the following dependency to the _dependencies_ section of your build descriptor:

* Maven (in your `pom.xml`):

[source,xml,subs="+attributes"]
----
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-config-aws-secrets-manager</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-config</artifactId>
<version>${maven.version}</version>
</dependency>
----

* Gradle (in your `build.gradle` file):

[source,groovy,subs="+attributes"]
----
compile 'io.vertx:vertx-config:${maven.version}'
compile 'io.vertx:vertx-config-aws-secrets-manager:${maven.version}'
----

==== Configuring the store

Once added to your classpath or dependencies, you need to configure the {@link io.vertx.config.ConfigRetriever} to use this store:

[source,$lang]
----
{@link examples.ConfigAWSExamples#example1(io.vertx.core.Vertx)}
----

The store configuration is used to configure the AWS SDK client and the _secretName_ containing the configuration.
Notice that the format of the configuration can be PlainText JSON only.

You must also have the _AWS_ACCESS_KEY_ID_ & _AWS_SECRET_ACCESS_KEY_ environment variables set so AWS SDK can authenticate.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package examples;

import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;

public class ConfigAWSExamples {

/**
* Environment variables that are required to be set to use AWS
* AWS_ACCESS_KEY_ID
* AWS_SECRET_ACCESS_KEY
* @param vertx
*/
public void example1(Vertx vertx) {
ConfigStoreOptions store = new ConfigStoreOptions()
.setType("aws-secrets-manager")
.setConfig(new JsonObject()
.put("region", "us-west-2")
.put("secretName", "test/example")
);

ConfigRetriever retriever = ConfigRetriever.create(vertx,
new ConfigRetrieverOptions().addStore(store));
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2014 Red Hat, Inc. and others
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/

@Source
package examples;

import io.vertx.docgen.Source;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.vertx.config.aws;

/**
* An exception that is thrown when an AWS Secret is not found
*/
public class AWSMissingSecretException extends Exception {
public AWSMissingSecretException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.vertx.config.aws;

/**
* An exception that is thrown when an AWS Secret is not of a usable type
*/
public class AWSSecretTypeException extends Exception {
public AWSSecretTypeException(String message) { super(message); }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.vertx.config.aws;


import com.amazonaws.services.secretsmanager.AWSSecretsManager;
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder;
import com.amazonaws.services.secretsmanager.model.*;
import io.vertx.config.spi.ConfigStore;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.JsonObject;

import java.util.Objects;

class AWSSecretsManagerStore implements ConfigStore {
private final Vertx vertx;
private final String region;
private final String secretName;

public AWSSecretsManagerStore(Vertx vertx, JsonObject configuration) {
this.vertx = Objects.requireNonNull(vertx);
this.region = configuration.getString("region");
this.secretName = configuration.getString("secretName");
}

@Override
public void get(Handler<AsyncResult<Buffer>> completionHandler) {

try {
// Check Environment variables exist, but we don't need to use them outselves, AWS SDK will use automatically.
System.getenv("AWS_ACCESS_KEY_ID");
System.getenv("AWS_SECRET_ACCESS_KEY");

vertx.executeBlocking(
future -> {
AWSSecretsManager client = AWSSecretsManagerClientBuilder.standard()
.withRegion(region)
.build();

GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest()
.withSecretId(secretName);

try {
GetSecretValueResult getSecretValueResult = client.getSecretValue(getSecretValueRequest);
if (getSecretValueResult != null) {
String secret = getSecretValueResult.getSecretString();

if (secret != null) {
future.complete(new JsonObject(secret));
} else {
throw new AWSSecretTypeException("Secret is not a string");
}
} else {
throw new AWSMissingSecretException(secretName + " not found");
}
} catch (DecodeException | AWSMissingSecretException | DecryptionFailureException | InternalServiceErrorException | InvalidParameterException | InvalidRequestException | ResourceNotFoundException | AWSSecretTypeException e) {
future.fail(e);
}
},
v -> {
if (v.failed()) {
completionHandler.handle(Future.failedFuture(v.cause()));
} else {
JsonObject configFromAWSSecretsManager = (JsonObject) v.result();
completionHandler.handle(Future.succeededFuture(configFromAWSSecretsManager.toBuffer()));
}
}
);

} catch (NullPointerException e) {
completionHandler.handle(Future.failedFuture("No AWS environment variable credentials present"));
}

}

@Override
public void close(Handler<Void> completionHandler) {
completionHandler.handle(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2014 Red Hat, Inc. and others
*
* Red Hat licenses this file to you under the Apache License, version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/

package io.vertx.config.aws;


import io.vertx.config.spi.ConfigStore;
import io.vertx.config.spi.ConfigStoreFactory;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;

/**
* Factory to create {@link AWSSecretsManagerStore} instances.
*
* @author <a href="http://indiealexh.com">Alexander Haslam</a>
*/
public class AWSSecretsManagerStoreFactory implements ConfigStoreFactory {
@Override
public String name() {
return "aws-secrets-manager";
}

@Override
public ConfigStore create(Vertx vertx, JsonObject configuration) {
return new AWSSecretsManagerStore(vertx, configuration);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# Copyright (c) 2014 Red Hat, Inc. and others
#
# Red Hat licenses this file to you under the Apache License, version 2.0
# (the "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
#

io.vertx.config.aws.AWSSecretsManagerStoreFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package io.vertx.config.aws;


import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.assertj.core.api.Assertions;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.IOException;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

/**
* Factory to create {@link AWSSecretsManagerStore} instances.
*
* @author <a href="http://indiealexh.com">Alexander Haslam</a>
*/
@RunWith(VertxUnitRunner.class)
public class AWSSecretsManagerStoreTest {

private ConfigRetriever retriever;
private Vertx vertx;


@Before
public void setUp(TestContext tc) throws IOException {
vertx = Vertx.vertx();
vertx.exceptionHandler(tc.exceptionHandler());
}

@After
public void tearDown(TestContext tc) {
retriever.close();
vertx.close(tc.asyncAssertSuccess());
}

@Test
public void getExampleFromAWSSecretsManager(TestContext tc) throws Exception {
Async async = tc.async();
retriever = ConfigRetriever.create(vertx,
new ConfigRetrieverOptions().addStore(
new ConfigStoreOptions()
.setType("aws-secrets-manager")
.setConfig(new JsonObject()
.put("region", "us-west-2")
.put("secretName", "test/example")
)));
retriever.getConfig(ar -> {
assertThat(ar.succeeded()).isTrue();
JsonObject json = ar.result();
Assertions.assertThat(json).isNotNull();
Assertions.assertThat(json.getString("key")).isEqualTo("value");

Assertions.assertThat(json.getBoolean("true")).isTrue();
Assertions.assertThat(json.getBoolean("false")).isFalse();

Assertions.assertThat(json.getString("missing")).isNull();

Assertions.assertThat(json.getInteger("int")).isEqualTo(5);
Assertions.assertThat(json.getDouble("float")).isEqualTo(25.3);

Assertions.assertThat(json.getJsonArray("array").size()).isEqualTo(3);
Assertions.assertThat(json.getJsonArray("array").contains(1)).isTrue();
Assertions.assertThat(json.getJsonArray("array").contains(2)).isTrue();
Assertions.assertThat(json.getJsonArray("array").contains(3)).isTrue();

Assertions.assertThat(json.getJsonObject("sub").getString("foo")).isEqualTo("bar");
async.complete();
});
}

}