Skip to content

Commit

Permalink
Added integration setup and test
Browse files Browse the repository at this point in the history
  • Loading branch information
yuriytkach committed Jun 28, 2023
1 parent d97f793 commit 8e906e1
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.quarkus.vault;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.vault.test.VaultTestLifecycleManager;

@QuarkusTestResource(VaultTestLifecycleManager.class)
public class VaultAwsIamITCase {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addAsResource("application-vault-aws-iam.properties", "application.properties"));

@ConfigProperty(name = "quarkus.vault.authentication.aws-iam.role")
String role;

@ConfigProperty(name = "quarkus.vault.authentication.aws-iam.aws-access-key")
String key;

@Test
public void testRole() {
assertEquals("myawsiamrole", role);
}

@Test
public void testAuthMountPath() {
System.out.println("key: " + key);
assertNotNull(key);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
quarkus.vault.url=https://localhost:8200

# vault-test.client-token-wrapping-token provided by VaultTestLifecycleManager
quarkus.vault.authentication.aws-iam.role=myawsiamrole
quarkus.vault.authentication.aws-iam.region=us-east-1
quarkus.vault.authentication.aws-iam.sts-url=http://mylocalstack:4566
quarkus.vault.authentication.aws-iam.vault-server-id=vault.example.com
quarkus.vault.authentication.aws-iam.aws-access-key=${vault-test.aws-user.access-key}
quarkus.vault.authentication.aws-iam.aws-secret-key=${vault-test.aws-user.secret-key}

#quarkus.vault.tls.skip-verify=true
quarkus.vault.tls.ca-cert=src/test/resources/vault-tls.crt

#quarkus.vault.log-confidentiality-level=low
#quarkus.vault.renew-grace-period=10

quarkus.log.category."io.quarkus.vault".level=DEBUG

#quarkus.log.level=DEBUG
#quarkus.log.console.level=DEBUG
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
import io.quarkus.vault.runtime.client.VaultInternalBase;
import io.quarkus.vault.runtime.client.dto.auth.VaultAwsIamAuth;
import io.quarkus.vault.runtime.client.dto.auth.VaultAwsIamAuthBody;
import io.quarkus.vault.runtime.config.VaultAwsIamAuthenticationConfig;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.signer.Aws4Signer;
Expand Down Expand Up @@ -97,8 +99,14 @@ private SdkHttpFullRequest signRequest(
}

private AwsCredentials getAwsCredentials() {
try (DefaultCredentialsProvider defaultCredentialsProvider = DefaultCredentialsProvider.create()) {
return defaultCredentialsProvider.resolveCredentials();
final VaultAwsIamAuthenticationConfig awsIam = vaultConfigHolder.getVaultBootstrapConfig().authentication.awsIam;

if (awsIam.awsAccessKey.isPresent() && awsIam.awsSecretKey.isPresent()) {
return AwsBasicCredentials.create(awsIam.awsAccessKey.get(), awsIam.awsSecretKey.get());
} else {
try (DefaultCredentialsProvider defaultCredentialsProvider = DefaultCredentialsProvider.create()) {
return defaultCredentialsProvider.resolveCredentials();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public class VaultAuthenticationConfig {
@ConfigItem
public VaultKubernetesAuthenticationConfig kubernetes;

/**
* AWS IAM authentication method
* <p>
* See https://developer.hashicorp.com/vault/docs/auth/aws
*/
@ConfigItem
public VaultAwsIamAuthenticationConfig awsIam;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,33 @@ public class VaultAwsIamAuthenticationConfig {
@ConfigItem
public String role;

/**
* The AWS region to use for AWS IAM authentication.
*/
@ConfigItem
public String region;

/**
* The URL of the AWS STS endpoint to use for AWS IAM authentication.
*/
@ConfigItem(defaultValue = "https://sts.amazonaws.com")
public String stsUrl;

/**
* The Vault server ID to use for AWS IAM authentication.
*/
@ConfigItem
public Optional<String> vaultServerId;

/**
* The AWS access key ID to use for AWS IAM authentication.
*/
@ConfigItem
public Optional<String> awsAccessKey;

/**
* The AWS secret access key to use for AWS IAM authentication.
*/
@ConfigItem
public Optional<String> awsSecretKey;
}
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ public String toString() {
+ ", awsIamRole=" + authentication.awsIam.role
+ ", awsIamSts=" + authentication.awsIam.stsUrl
+ ", awsIamRegion=" + authentication.awsIam.region
+ ", awsIamVaultServerId" + logConfidentialityLevel.maskWithTolerance(authentication.awsIam.vaultServerId.orElse(""), LOW) + '\''
// + ", awsIamVaultServerId" + logConfidentialityLevel.maskWithTolerance(authentication.awsIam.vaultServerId.orElse(""), LOW) + '\''
+
", clientToken=" + logConfidentialityLevel.maskWithTolerance(authentication.clientToken.orElse(""), LOW) +
", clientTokenWrappingToken="
Expand Down
14 changes: 14 additions & 0 deletions test-framework/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>localstack</artifactId>
<exclusions>
<exclusion>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>rabbitmq</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,15 @@
import org.testcontainers.containers.Network;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.containers.RabbitMQContainer;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.containers.output.OutputFrame;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;

import io.quarkus.vault.VaultException;
import io.quarkus.vault.VaultKVSecretEngine;
import io.quarkus.vault.runtime.VaultConfigHolder;
Expand Down Expand Up @@ -77,6 +84,7 @@ public class VaultTestExtension {
public static final String VAULT_AUTH_USERPASS_USER = "bob";
public static final String VAULT_AUTH_USERPASS_PASSWORD = "sinclair";
public static final String VAULT_AUTH_APPROLE = "myapprole";
public static final String VAULT_AUTH_AWS_IAM_ROLE = "myawsiamrole";
public static final String SECRET_PATH_V1 = "secret-v1";
public static final String SECRET_PATH_V2 = "secret";
public static final String LIST_PATH = "hello";
Expand All @@ -89,6 +97,7 @@ public class VaultTestExtension {
static final String VAULT_POLICY = "mypolicy";
static final String POSTGRESQL_HOST = "mypostgresdb";
static final String RABBITMQ_HOST = "myrabbitmq";
static final String LOCALSTACK_HOST = "mylocalstack";
static final String VAULT_URL = (useTls() ? "https" : "http") + "://localhost:" + VAULT_PORT;
public static final String SECRET_KEY = "secret";
public static final String ENCRYPTION_KEY_NAME = "my-encryption-key";
Expand All @@ -107,11 +116,13 @@ public class VaultTestExtension {
public static final String HOST_POSTGRES_TMP_CMD = "target/postgres_cmd";
public static final String OUT_FILE = "/out";
public static final String WRAPPING_TEST_PATH = "wrapping-test";
private static final String VAULT_AWS_SERVER_ID = "vault.example.com";

private static String CRUD_PATH = "crud";

public GenericContainer vaultContainer;
public PostgreSQLContainer postgresContainer;
public LocalStackContainer localStackContainer;
public RabbitMQContainer rabbitMQContainer;
public String rootToken = null;
public String appRoleSecretId = null;
Expand All @@ -126,6 +137,9 @@ public class VaultTestExtension {

private String db_default_ttl = "1m";
private String db_max_ttl = "10m";
public CreateAccessKeyResponse userAwsAccessKey;
private CreateAccessKeyResponse vaultAwsAccessKey;
private CreateUserResponse vaultAwsUser;

public static void testDataSource(DataSource ds) throws SQLException {
try (Connection c = ds.getConnection()) {
Expand Down Expand Up @@ -224,6 +238,15 @@ public void start() throws InterruptedException, IOException {
.withNetwork(network)
.withNetworkAliases(RABBITMQ_HOST);

Consumer<OutputFrame> localstackConsumer = outputFrame -> System.out.
print("AWS >> " + outputFrame.getUtf8String());

localStackContainer = new LocalStackContainer()
.withServices(LocalStackContainer.Service.STS, LocalStackContainer.Service.IAM)
.withLogConsumer(localstackConsumer)
.withNetwork(network)
.withNetworkAliases(LOCALSTACK_HOST);

String configFile = useTls() ? "vault-config-tls.json" : "vault-config.json";

String vaultImage = getVaultImage();
Expand Down Expand Up @@ -251,6 +274,9 @@ public void start() throws InterruptedException, IOException {

rabbitMQContainer.start();

localStackContainer.start();
initLocalStack();

Consumer<OutputFrame> consumer = outputFrame -> System.out.print("VAULT >> " + outputFrame.getUtf8String());
vaultContainer.setLogConsumers(Arrays.asList(consumer));
vaultContainer.start();
Expand All @@ -263,6 +289,56 @@ private String getVaultImage() {
return "vault:" + VaultVersions.VAULT_TEST_VERSION;
}

private void initLocalStack() throws IOException, InterruptedException {
String awsLocalstackUrl = localStackContainer.getEndpointOverride(LocalStackContainer.Service.STS).toString();
System.out.println("AWS STS URL: " + awsLocalstackUrl);

final ObjectMapper objectMapper = JsonMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();

final String outCreateUser = execLocalStack("awslocal", "iam", "create-user", "--user-name", "admin-user");
vaultAwsUser = objectMapper.readValue(
outCreateUser,
CreateUserResponse.class
);

final String outUserAccessKey = execLocalStack("awslocal", "iam", "create-access-key", "--user-name", "admin-user");
userAwsAccessKey = objectMapper.readValue(
outUserAccessKey,
CreateAccessKeyResponse.class
);

execLocalStack("awslocal", "iam", "create-user", "--user-name", "vault-user");

final String outVaultAccessKey = execLocalStack("awslocal", "iam", "create-access-key", "--user-name", "vault-user");
vaultAwsAccessKey = objectMapper.readValue(
outUserAccessKey,
CreateAccessKeyResponse.class
);
}

static class CreateAccessKeyResponse {
public AwsAccessKey accessKey;
}

static class AwsAccessKey {
public String accessKeyId;
public String secretAccessKey;
}

static class CreateUserResponse {
public AwsUser user;
}

static class AwsUser {
public String userName;
public String userId;
public String arn;
}

private void initVault() throws InterruptedException, IOException {

waitForContainerToStart();
Expand Down Expand Up @@ -306,6 +382,24 @@ private void initVault() throws InterruptedException, IOException {
log.info(
format("generated role_id=%s secret_id=%s for approle=%s", appRoleRoleId, appRoleSecretId, VAULT_AUTH_APPROLE));

// aws iam auth
execVault("vault auth enable aws");
execVault(format("vault write auth/aws/config/client secret_key=%s access_key=%s",
vaultAwsAccessKey.accessKey.secretAccessKey, vaultAwsAccessKey.accessKey.accessKeyId));

execVault(format("vault write auth/aws/config/client iam_server_id_header_value=%s "
+ "iam_endpoint=%s sts_endpoint=%s",
VAULT_AWS_SERVER_ID,
"http://mylocalstack:4566",
"http://mylocalstack:4566"
));

execVault(format("vault write auth/aws/role/%s auth_type=iam "
+ "bound_iam_principal_arn=%s policies=%s",
VAULT_AUTH_AWS_IAM_ROLE,
vaultAwsUser.user.arn.replaceAll("000000000000", "123456789012"),
VAULT_POLICY));

// policy
String policyContent = readResourceContent("vault.policy");
vaultInternalSystemBackend.createUpdatePolicy(vaultClient, rootToken, VAULT_POLICY, new VaultPolicyBody(policyContent))
Expand Down Expand Up @@ -494,6 +588,10 @@ private String execVault(String command) throws IOException, InterruptedExceptio
return exec(vaultContainer, command, cmd, HOST_VAULT_TMP_CMD + OUT_FILE);
}

private String execLocalStack(final String... command) throws IOException, InterruptedException {
return exec(localStackContainer, command).getStdout();
}

private String exec(GenericContainer container, String command, String[] cmd, String outFile)
throws IOException, InterruptedException {
exec(container, cmd);
Expand All @@ -502,7 +600,7 @@ private String exec(GenericContainer container, String command, String[] cmd, St
return out;
}

private static Container.ExecResult exec(GenericContainer container, String[] cmd)
private static Container.ExecResult exec(Container container, String[] cmd)
throws IOException, InterruptedException {

Container.ExecResult execResult = container.execInContainer(cmd);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public Map<String, String> start() {
sysprops.put("vault-test.password-kv-v2-wrapping-token", vaultTestExtension.passwordKvv2WrappingToken);
sysprops.put("vault-test.another-password-kv-v2-wrapping-token", vaultTestExtension.anotherPasswordKvv2WrappingToken);

sysprops.put("vault-test.aws-user.access-key", vaultTestExtension.userAwsAccessKey.accessKey.accessKeyId);
sysprops.put("vault-test.aws-user.secret-key", vaultTestExtension.userAwsAccessKey.accessKey.secretAccessKey);

log.info("using system properties " + sysprops);

return sysprops;
Expand Down

0 comments on commit 8e906e1

Please sign in to comment.