Skip to content

Commit

Permalink
Implemented aws iam auth (no tests yet)
Browse files Browse the repository at this point in the history
  • Loading branch information
yuriytkach committed Jun 28, 2023
1 parent d073178 commit d97f793
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.quarkus.vault.runtime.client.dto.auth;

import com.fasterxml.jackson.annotation.JsonProperty;

import io.quarkus.vault.runtime.Base64String;
import io.quarkus.vault.runtime.client.dto.VaultModel;

public class VaultAwsIamAuthBody implements VaultModel {

public String role;

@JsonProperty("iam_http_request_method")
public String requestMethod;

@JsonProperty("iam_request_url")
public Base64String requestUrl;

@JsonProperty("iam_request_body")
public Base64String requestBody;

@JsonProperty("iam_request_headers")
public Base64String requestHeaders;

public VaultAwsIamAuthBody(
final String role,
final String requestMethod,
final Base64String requestUrl,
final Base64String requestBody,
final Base64String requestHeaders
) {
this.role = role;
this.requestMethod = requestMethod;
this.requestUrl = requestUrl;
this.requestBody = requestBody;
this.requestHeaders = requestHeaders;
}
}
17 changes: 17 additions & 0 deletions runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
<artifactId>quarkus-vault</artifactId>
<name>Quarkus - Vault - Runtime</name>
<description>Store your credentials securely in HashiCorp Vault</description>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.20.94</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
Expand Down Expand Up @@ -49,6 +60,12 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
package io.quarkus.vault.runtime.client.authmethod;

import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.stream.Collectors;

import io.quarkus.vault.runtime.Base64String;
import io.quarkus.vault.runtime.StringHelper;
import io.quarkus.vault.runtime.VaultConfigHolder;
import io.quarkus.vault.runtime.client.VaultClient;
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.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.signer.Aws4Signer;
import software.amazon.awssdk.auth.signer.params.Aws4SignerParams;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.regions.Region;

@Singleton
public class VaultInternalAwsIamAuthMethod extends VaultInternalBase {

private static final String GET_CALLER_IDENTITY_REQUEST_BODY = "Action=GetCallerIdentity&Version=2011-06-15";
@Inject
private VaultConfigHolder vaultConfigHolder;

Expand All @@ -19,7 +35,89 @@ protected String opNamePrefix() {
return super.opNamePrefix() + " [AUTH (aws iam)]";
}

/**
* curl -X POST "http://127.0.0.1:8200/v1/auth/aws/login" -d '{
* "role":"dev",
* "iam_http_request_method": "POST",
* "iam_request_url": "aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8=",
* "iam_request_body": "QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ==",
* "iam_request_headers": "eyJDb250ZW50LUxlbmd0aCI6IFsiNDMiXSwgIlVzZXItQWdlbnQiOiBbImF3cy1zZGstZ28vMS40LjEyIChnbzEuNy4xOyBsaW51eDsgYW1kNjQpIl0sICJYLVZhdWx0LUFXU0lBTS1TZXJ2ZXItSWQiOiBbInZhdWx0LmV4YW1wbGUuY29tIl0sICJYLUFtei1EYXRlIjogWyIyMDE2MDkzMFQwNDMxMjFaIl0sICJDb250ZW50LVR5cGUiOiBbImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZDsgY2hhcnNldD11dGYtOCJdLCAiQXV0aG9yaXphdGlvbiI6IFsiQVdTNC1ITUFDLVNIQTI1NiBDcmVkZW50aWFsPWZvby8yMDE2MDkzMC91cy1lYXN0LTEvc3RzL2F3czRfcmVxdWVzdCwgU2lnbmVkSGVhZGVycz1jb250ZW50LWxlbmd0aDtjb250ZW50LXR5cGU7aG9zdDt4LWFtei1kYXRlO3gtdmF1bHQtc2VydmVyLCBTaWduYXR1cmU9YTY5ZmQ3NTBhMzQ0NWM0ZTU1M2UxYjNlNzlkM2RhOTBlZWY1NDA0N2YxZWI0ZWZlOGZmYmM5YzQyOGMyNjU1YiJdfQ==" }'
*
*
* Config PARAMS:
* - region
* - sts url
* - X-Vault-AWS-IAM-Server-ID
*/
public Uni<VaultAwsIamAuth> login(final VaultClient vaultClient) {
return null;
final SdkHttpFullRequest getCallerIdentityRequest;
try {
getCallerIdentityRequest = buildGetCallerIdentityRequest();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
final AwsCredentials creds = getAwsCredentials();
final SdkHttpFullRequest signedRequest = signRequest(getCallerIdentityRequest, creds);

final VaultAwsIamAuthBody vaultRequestBody = buildVaultRequestBody(signedRequest);

return vaultClient.post(opName("Login"), "auth/aws/login", null, vaultRequestBody,
VaultAwsIamAuth.class);
}

private VaultAwsIamAuthBody buildVaultRequestBody(final SdkHttpFullRequest signedRequest) {
final String headersString = "{"
+ signedRequest.headers().entrySet().stream()
.map(entry -> "\"" + entry.getKey() + "\":["
+ entry.getValue().stream().map(value -> "\"" + value + "\"").collect(Collectors.joining(","))
+ "]")
.collect(Collectors.joining(","))
+ "}";

return new VaultAwsIamAuthBody(
vaultConfigHolder.getVaultBootstrapConfig().authentication.awsIam.role,
"POST",
Base64String.from(signedRequest.getUri().toString()),
Base64String.from(GET_CALLER_IDENTITY_REQUEST_BODY),
Base64String.from(headersString)
);
}

private SdkHttpFullRequest signRequest(
final SdkHttpFullRequest getCallerIdentityRequest,
final AwsCredentials creds
) {
final Region region = Region.of(vaultConfigHolder.getVaultBootstrapConfig().authentication.awsIam.region);
Aws4SignerParams params = Aws4SignerParams.builder()
.awsCredentials(creds)
.signingName("sts")
.signingRegion(region)
.build();
return Aws4Signer.create().sign(getCallerIdentityRequest, params);
}

private AwsCredentials getAwsCredentials() {
try (DefaultCredentialsProvider defaultCredentialsProvider = DefaultCredentialsProvider.create()) {
return defaultCredentialsProvider.resolveCredentials();
}
}

private SdkHttpFullRequest buildGetCallerIdentityRequest() throws URISyntaxException {
final SdkHttpFullRequest.Builder builder = SdkHttpFullRequest.builder()
.method(SdkHttpMethod.POST)
.uri(new URI(vaultConfigHolder.getVaultBootstrapConfig().authentication.awsIam.stsUrl))
.appendHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
.appendHeader("Content-Length", String.valueOf(GET_CALLER_IDENTITY_REQUEST_BODY.length()))
.contentStreamProvider(() -> new ByteArrayInputStream(
StringHelper.stringToBytes(GET_CALLER_IDENTITY_REQUEST_BODY)
));

vaultConfigHolder.getVaultBootstrapConfig().authentication.awsIam.vaultServerId.ifPresent(
serverId -> builder.appendHeader("X-Vault-AWS-IAM-Server-ID", serverId)
);

SdkHttpFullRequest request = builder.build();
// log request
return request;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public class VaultAuthenticationConfig {
@ConfigItem
public VaultKubernetesAuthenticationConfig kubernetes;

@ConfigItem
public VaultAwsIamAuthenticationConfig awsIam;

public boolean isDirectClientToken() {
return clientToken.isPresent() || clientTokenWrappingToken.isPresent();
}
Expand All @@ -63,4 +66,8 @@ public boolean isUserpass() {
return userpass.username.isPresent() && (userpass.password.isPresent() || userpass.passwordWrappingToken.isPresent());
}

public boolean isAwsIam() {
return awsIam.stsUrl != null && awsIam.region != null && awsIam.role != null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.vault.runtime.config;

import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class VaultAwsIamAuthenticationConfig {

/**
* Aws iam authentication role that has been created in Vault to associate Vault policies, with
* aws iam service accounts. This property is required when selecting
* the aws iam authentication type.
*/
@ConfigItem
public String role;

@ConfigItem
public String region;

@ConfigItem(defaultValue = "https://sts.amazonaws.com")
public String stsUrl;

@ConfigItem
public Optional<String> vaultServerId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static io.quarkus.vault.runtime.LogConfidentialityLevel.LOW;
import static io.quarkus.vault.runtime.LogConfidentialityLevel.MEDIUM;
import static io.quarkus.vault.runtime.config.VaultAuthenticationType.APPROLE;
import static io.quarkus.vault.runtime.config.VaultAuthenticationType.AWS_IAM;
import static io.quarkus.vault.runtime.config.VaultAuthenticationType.KUBERNETES;
import static io.quarkus.vault.runtime.config.VaultAuthenticationType.USERPASS;

Expand Down Expand Up @@ -38,6 +39,7 @@ public class VaultBootstrapConfig {
public static final String DEFAULT_KUBERNETES_AUTH_MOUNT_PATH = "auth/kubernetes";
public static final String DEFAULT_APPROLE_AUTH_MOUNT_PATH = "auth/approle";


/**
* Microprofile Config ordinal.
* <p>
Expand Down Expand Up @@ -267,6 +269,8 @@ public VaultAuthenticationType getAuthenticationType() {
return USERPASS;
} else if (authentication.isAppRole()) {
return APPROLE;
} else if (authentication.isAwsIam()) {
return AWS_IAM;
} else {
return null;
}
Expand All @@ -292,7 +296,11 @@ public String toString() {
+ logConfidentialityLevel.maskWithTolerance(authentication.appRole.secretId.orElse(""), LOW) + '\'' +
", appRoleSecretIdWrappingToken='"
+ logConfidentialityLevel.maskWithTolerance(authentication.appRole.secretIdWrappingToken.orElse(""), LOW) + '\''
+
+ ", awsIamRole=" + authentication.awsIam.role
+ ", awsIamSts=" + authentication.awsIam.stsUrl
+ ", awsIamRegion=" + authentication.awsIam.region
+ ", awsIamVaultServerId" + logConfidentialityLevel.maskWithTolerance(authentication.awsIam.vaultServerId.orElse(""), LOW) + '\''
+
", clientToken=" + logConfidentialityLevel.maskWithTolerance(authentication.clientToken.orElse(""), LOW) +
", clientTokenWrappingToken="
+ logConfidentialityLevel.maskWithTolerance(authentication.clientTokenWrappingToken.orElse(""), LOW) +
Expand Down

0 comments on commit d97f793

Please sign in to comment.