Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 20 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@
<gson-fire-version>1.8.3</gson-fire-version>
</properties>
<dependencies>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
Expand All @@ -70,7 +74,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<version>1.18.42</version>
<scope>provided</scope>
</dependency>

Expand Down Expand Up @@ -119,16 +123,28 @@
<version>${gson-fire-version}</version>
</dependency>

<!-- AWS dependencies-->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>auth</artifactId>
<version>2.34.8</version>
<optional>true</optional>
</dependency>
<!-- Testing dependencies-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
</dependencies>


<build>
<plugins>
<plugin>
Expand Down Expand Up @@ -171,7 +187,7 @@
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<version>1.18.42</version>
</path>
</annotationProcessorPaths>
</configuration>
Expand Down
159 changes: 159 additions & 0 deletions src/main/java/com/infisical/sdk/auth/AwsAuthProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package com.infisical.sdk.auth;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.infisical.sdk.models.AwsAuthParameters;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider;
import software.amazon.awssdk.http.ContentStreamProvider;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.auth.aws.signer.AwsV4FamilyHttpSigner;
import software.amazon.awssdk.http.auth.aws.signer.AwsV4HttpSigner;
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;

@Data
@Builder
public class AwsAuthProvider {
private static final ObjectMapper objectMapper = new ObjectMapper();

@NonNull @Builder.Default private final String serviceName = "sts";
@NonNull @Builder.Default private final SdkHttpMethod httpMethod = SdkHttpMethod.POST;
@NonNull @Builder.Default private final String endpointTemplate = "https://sts.%s.amazonaws.com";

@NonNull @Builder.Default
private final String contentType = "application/x-www-form-urlencoded; charset=utf-8";

@NonNull @Builder.Default
private final Map<String, List<String>> params =
Map.ofEntries(
Map.entry("Action", List.of("GetCallerIdentity")),
Map.entry("Version", List.of("2011-06-15")));

private final Instant overrideInstant;

/**
* Create AwsAuthLoginInput from given AWS credentials.
*
* @param region region of AWS identity
* @param credentials AWS credentials for creating the login input
* @param sessionToken Session token for creating the login input
* @return the AwsAuthLoginInput created from the given credentials for exchanging access token
*/
public AwsAuthParameters fromCredentials(
String region, AwsCredentials credentials, String sessionToken) {
final AwsV4HttpSigner signer = AwsV4HttpSigner.create();
final String iamRequestURL = endpointTemplate.formatted(region);
final String iamRequestBody = encodeParameters(params);
final SdkHttpFullRequest.Builder requestBuilder =
SdkHttpFullRequest.builder()
.uri(URI.create(iamRequestURL))
.method(httpMethod)
.appendHeader("Content-Type", contentType);
if (sessionToken != null) {
requestBuilder.appendHeader("X-Amz-Security-Token", sessionToken);
}
final SdkHttpFullRequest request = requestBuilder.build();
final SdkHttpRequest signedRequest =
signer
.sign(
signingRequest -> {
var req =
signingRequest
.request(request)
.identity(credentials)
.payload(
ContentStreamProvider.fromByteArray(
iamRequestBody.getBytes(StandardCharsets.UTF_8)))
.putProperty(AwsV4FamilyHttpSigner.SERVICE_SIGNING_NAME, serviceName)
.putProperty(AwsV4HttpSigner.REGION_NAME, region);
if (overrideInstant != null) {
req.putProperty(
HttpSigner.SIGNING_CLOCK, Clock.fixed(overrideInstant, ZoneOffset.UTC));
}
})
.request();
final Map<String, String> requestHeaders =
signedRequest.headers().entrySet().stream()
.map(entry -> Map.entry(entry.getKey(), entry.getValue().getFirst()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
requestHeaders.put("Content-Length", String.valueOf(iamRequestBody.length()));
final String encodedHeader;
try {
encodedHeader =
Base64.getEncoder()
.encodeToString(
objectMapper.writeValueAsString(requestHeaders).getBytes(StandardCharsets.UTF_8));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
final String encodedBody =
Base64.getEncoder().encodeToString(iamRequestBody.getBytes(StandardCharsets.UTF_8));
return AwsAuthParameters.builder()
.iamHttpRequestMethod(httpMethod.name())
.iamRequestHeaders(encodedHeader)
.iamRequestBody(encodedBody)
.build();
}

/**
* Create AwsAuthLoginInput from the instance profile in the current environment.
*
* @return the AwsAuthLoginInput created from the current instance profile for exchanging access
* token
*/
public AwsAuthParameters fromInstanceProfile() {
try (InstanceProfileCredentialsProvider provider =
InstanceProfileCredentialsProvider.create()) {
final AwsSessionCredentials credentials =
(AwsSessionCredentials) provider.resolveCredentials();
final DefaultAwsRegionProviderChain regionProvider =
DefaultAwsRegionProviderChain.builder().build();
final Region region = regionProvider.getRegion();
final String sessionToken = credentials.sessionToken();
return fromCredentials(region.id(), credentials, sessionToken);
}
}

/**
* Encode given parameters with URL encoding for the body of form posting request.
*
* @param params parameters mapping key to values to encode
* @return URL-encoded string of the parameters
*/
public static String encodeParameters(Map<String, List<String>> params) {
return params.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(item -> Map.entry(entry.getKey(), item)))
// Notice: this is not really needed for real world usage, but it makes the
// body encoded in a deterministic order, so that unit test is much easier
.sorted(Map.Entry.comparingByKey())
.map(
entry ->
String.format(
"%s=%s",
URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8),
URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8)))
.collect(Collectors.joining("&"));
}

public static AwsAuthProvider defaultProvider() {
return builder().build();
}
}
34 changes: 34 additions & 0 deletions src/main/java/com/infisical/sdk/models/AwsAuthLoginInput.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.infisical.sdk.models;

import com.infisical.sdk.util.Helper;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;

@Data
@Builder(toBuilder = true)
public class AwsAuthLoginInput {
@NonNull private final String identityId;
@NonNull private final String iamHttpRequestMethod;
@NonNull private final String iamRequestHeaders;
@NonNull private final String iamRequestBody;

public String validate() {
if (Helper.isNullOrEmpty(identityId)) {
return "Identity ID is required";
}

if (Helper.isNullOrEmpty(iamHttpRequestMethod)) {
return "IamHttpRequestMethod is required";
}

if (Helper.isNullOrEmpty(iamRequestHeaders)) {
return "IamRequestHeaders is required";
}

if (Helper.isNullOrEmpty(iamRequestBody)) {
return "IamRequestBody is required";
}
return null;
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/infisical/sdk/models/AwsAuthParameters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.infisical.sdk.models;

import lombok.Builder;
import lombok.Data;
import lombok.NonNull;

@Data
@Builder(toBuilder = true)
public class AwsAuthParameters {
@NonNull private final String iamHttpRequestMethod;
@NonNull private final String iamRequestHeaders;
@NonNull private final String iamRequestBody;

public AwsAuthLoginInput toLoginInput(String identityId) {
return AwsAuthLoginInput.builder()
.identityId(identityId)
.iamRequestHeaders(iamRequestHeaders)
.iamHttpRequestMethod(iamHttpRequestMethod)
.iamRequestBody(iamRequestBody)
.build();
}
}
54 changes: 35 additions & 19 deletions src/main/java/com/infisical/sdk/resources/AuthClient.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.infisical.sdk.resources;

import com.infisical.sdk.api.ApiClient;
import com.infisical.sdk.auth.AwsAuthProvider;
import com.infisical.sdk.models.AwsAuthLoginInput;
import com.infisical.sdk.models.LdapAuthLoginInput;
import com.infisical.sdk.models.MachineIdentityCredential;
import com.infisical.sdk.models.UniversalAuthLoginInput;
import com.infisical.sdk.util.InfisicalException;
import java.util.function.Consumer;
import com.infisical.sdk.api.ApiClient;

import com.infisical.sdk.models.UniversalAuthLoginInput;

public class AuthClient {
private final ApiClient apiClient;
Expand All @@ -18,29 +19,44 @@ public AuthClient(ApiClient apiClient, Consumer<String> onAuthenticate) {
}

public void UniversalAuthLogin(String clientId, String clientSecret) throws InfisicalException {
var params = UniversalAuthLoginInput.builder()
.clientId(clientId)
.clientSecret(clientSecret)
.build();

var url = String.format("%s%s", this.apiClient.GetBaseUrl(), "/api/v1/auth/universal-auth/login");
var credential = this.apiClient.post(url, params, MachineIdentityCredential.class);
this.onAuthenticate.accept(credential.getAccessToken());
var params =
UniversalAuthLoginInput.builder().clientId(clientId).clientSecret(clientSecret).build();

var url =
String.format("%s%s", this.apiClient.GetBaseUrl(), "/api/v1/auth/universal-auth/login");
var credential = this.apiClient.post(url, params, MachineIdentityCredential.class);
this.onAuthenticate.accept(credential.getAccessToken());
}

public void LdapAuthLogin(LdapAuthLoginInput input) throws InfisicalException {
var validationMsg = input.validate();
var validationMsg = input.validate();

if (validationMsg != null) {
throw new InfisicalException(validationMsg);
}

var url = String.format("%s%s", this.apiClient.GetBaseUrl(), "/api/v1/auth/ldap-auth/login");
var credential = this.apiClient.post(url, input, MachineIdentityCredential.class);
this.onAuthenticate.accept(credential.getAccessToken());
}

public void AwsAuthLogin(String identityId) throws InfisicalException {
AwsAuthLogin(AwsAuthProvider.defaultProvider().fromInstanceProfile().toLoginInput(identityId));
}

public void AwsAuthLogin(AwsAuthLoginInput input) throws InfisicalException {
var validationMsg = input.validate();

if (validationMsg != null) {
throw new InfisicalException(validationMsg);
}
if (validationMsg != null) {
throw new InfisicalException(validationMsg);
}

var url = String.format("%s%s", this.apiClient.GetBaseUrl(), "/api/v1/auth/ldap-auth/login");
var credential = this.apiClient.post(url, input, MachineIdentityCredential.class);
this.onAuthenticate.accept(credential.getAccessToken());
var url = String.format("%s%s", this.apiClient.GetBaseUrl(), "/api/v1/auth/aws-auth/login");
var credential = this.apiClient.post(url, input, MachineIdentityCredential.class);
this.onAuthenticate.accept(credential.getAccessToken());
}

public void SetAccessToken(String accessToken) {
this.onAuthenticate.accept(accessToken);
}
}
}
Loading