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

Testing/obj-storage-mock: add dummy assume-role endpoint #8288

Merged
merged 1 commit into from
Apr 4, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions testing/object-storage-mock/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dependencies {
implementation("com.fasterxml.jackson.core:jackson-annotations")
implementation("com.fasterxml.jackson.core:jackson-databind")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
implementation("com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider")
implementation("com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-xml-provider")

Expand All @@ -57,6 +58,7 @@ dependencies {
testImplementation(platform(libs.awssdk.bom))
testImplementation("software.amazon.awssdk:s3")
testImplementation("software.amazon.awssdk:url-connection-client")
testImplementation("software.amazon.awssdk:sts")

testImplementation(platform(libs.azuresdk.bom))
testImplementation("com.azure:azure-storage-file-datalake")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import jakarta.ws.rs.container.ContainerResponseFilter;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Expand All @@ -33,6 +35,10 @@
import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.immutables.value.Value;
import org.projectnessie.objectstoragemock.sts.AssumeRoleHandler;
import org.projectnessie.objectstoragemock.sts.ImmutableAssumeRoleResult;
import org.projectnessie.objectstoragemock.sts.ImmutableCredentials;
import org.projectnessie.objectstoragemock.sts.ImmutableRoleUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -62,13 +68,38 @@ public static ImmutableObjectStorageMock.Builder builder() {

public abstract Map<String, Bucket> buckets();

@Value.Default
public AssumeRoleHandler assumeRoleHandler() {
return ((action,
version,
roleArn,
roleSessionName,
policy,
durationSeconds,
externalId,
serialNumber) ->
ImmutableAssumeRoleResult.builder()
.credentials(
ImmutableCredentials.builder()
.accessKeyId("access-key-id")
.secretAccessKey("secret-access-key")
.expiration(Instant.now().plus(15, ChronoUnit.MINUTES))
.build())
.sourceIdentity("source-identity")
.assumedRoleUser(
ImmutableRoleUser.builder().arn("arn").assumedRoleId("assumedRoleId").build())
.build());
}

public interface MockServer extends AutoCloseable {
URI getS3BaseUri();

URI getGcsBaseUri();

URI getAdlsGen2BaseUri();

URI getStsEndpointURI();

default Map<String, String> icebergProperties() {
Map<String, String> props = new HashMap<>();
props.put("s3.access-key-id", "accessKey");
Expand Down Expand Up @@ -145,6 +176,11 @@ private static URI baseUri(Server server, URI initUri) {
throw new IllegalArgumentException("Server has no connectors");
}

@Override
public URI getStsEndpointURI() {
return baseUri.resolve("sts/assumeRole");
}

@Override
public URI getS3BaseUri() {
return baseUri;
Expand Down Expand Up @@ -183,6 +219,7 @@ protected void configure() {
config.register(S3Resource.class);
config.register(AdlsGen2Resource.class);
config.register(GcsResource.class);
config.register(StsResource.class);

if (LOGGER.isDebugEnabled()) {
config.register(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2024 Dremio
*
* Licensed 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 org.projectnessie.objectstoragemock;

import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.projectnessie.objectstoragemock.sts.AssumeRoleResult;
import org.projectnessie.objectstoragemock.sts.ImmutableAssumeRoleResponse;
import org.projectnessie.objectstoragemock.sts.ImmutableResponseMetadata;

@Path("/sts/")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_XML)
public class StsResource {
@Inject ObjectStorageMock mockServer;

@Path("assumeRole")
@POST
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to this doc the original STS endpoint uses GET + query parameters.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to this doc the original STS endpoint uses GET + query parameters.

The awssdk actually uses POST + form

@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
public Object assumeRole(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed we don't support SourceIdentity, is it worth adding it?

@FormParam("Action") String action,
@FormParam("Version") String version,
@FormParam("RoleArn") String roleArn,
@FormParam("RoleSessionName") String roleSessionName,
@FormParam("Policy") String policy,
@FormParam("DurationSeconds") Integer durationSeconds,
@FormParam("ExternalId") String externalId,
@FormParam("SerialNumber") String serialNumber,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems SerialNumber is only for MFA, and also requires TokenCode. Not sure this is needed for testing STS.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is testing code - we can change and adopt later at any time.

@HeaderParam("amz-sdk-invocation-id") String amzSdkInvocationId) {
AssumeRoleResult result =
mockServer
.assumeRoleHandler()
.assumeRole(
action,
version,
roleArn,
roleSessionName,
policy,
durationSeconds,
externalId,
serialNumber);
return ImmutableAssumeRoleResponse.builder()
.assumeRoleResult(result)
.responseMetadata(ImmutableResponseMetadata.builder().requestId(amzSdkInvocationId).build())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
import jakarta.annotation.Nullable;
import java.time.Instant;
import org.immutables.value.Value;
Expand Down Expand Up @@ -70,6 +72,8 @@ default String kind() {
shape = JsonFormat.Shape.STRING,
pattern = StdDateFormat.DATE_FORMAT_STR_ISO8601,
timezone = "UTC")
@JsonSerialize(using = InstantSerializer.class)
@JsonDeserialize(using = InstantDeserializer.class)
Instant softDeleteTime();

@Nullable
Expand All @@ -78,6 +82,8 @@ default String kind() {
shape = JsonFormat.Shape.STRING,
pattern = StdDateFormat.DATE_FORMAT_STR_ISO8601,
timezone = "UTC")
@JsonSerialize(using = InstantSerializer.class)
@JsonDeserialize(using = InstantDeserializer.class)
Instant hardDeleteTime();

@Nullable
Expand Down Expand Up @@ -136,6 +142,8 @@ default String kind() {
shape = JsonFormat.Shape.STRING,
pattern = StdDateFormat.DATE_FORMAT_STR_ISO8601,
timezone = "UTC")
@JsonSerialize(using = InstantSerializer.class)
@JsonDeserialize(using = InstantDeserializer.class)
Instant timeCreated();

@Nullable
Expand All @@ -144,6 +152,8 @@ default String kind() {
shape = JsonFormat.Shape.STRING,
pattern = StdDateFormat.DATE_FORMAT_STR_ISO8601,
timezone = "UTC")
@JsonSerialize(using = InstantSerializer.class)
@JsonDeserialize(using = InstantDeserializer.class)
Instant updated();

@Nullable
Expand All @@ -152,6 +162,8 @@ default String kind() {
shape = JsonFormat.Shape.STRING,
pattern = StdDateFormat.DATE_FORMAT_STR_ISO8601,
timezone = "UTC")
@JsonSerialize(using = InstantSerializer.class)
@JsonDeserialize(using = InstantDeserializer.class)
Instant timeDeleted();

@Nullable
Expand All @@ -160,6 +172,8 @@ default String kind() {
shape = JsonFormat.Shape.STRING,
pattern = StdDateFormat.DATE_FORMAT_STR_ISO8601,
timezone = "UTC")
@JsonSerialize(using = InstantSerializer.class)
@JsonDeserialize(using = InstantDeserializer.class)
Instant timeStorageClassUpdated();

@Nullable
Expand All @@ -168,6 +182,8 @@ default String kind() {
shape = JsonFormat.Shape.STRING,
pattern = StdDateFormat.DATE_FORMAT_STR_ISO8601,
timezone = "UTC")
@JsonSerialize(using = InstantSerializer.class)
@JsonDeserialize(using = InstantDeserializer.class)
Instant customTime();

@Nullable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2024 Dremio
*
* Licensed 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 org.projectnessie.objectstoragemock.sts;

public interface AssumeRoleHandler {
AssumeRoleResult assumeRole(
String action,
String version,
String roleArn,
String roleSessionName,
String policy,
Integer durationSeconds,
String externalId,
String serialNumber);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 Dremio
*
* Licensed 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 org.projectnessie.objectstoragemock.sts;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.immutables.value.Value;

@JsonRootName("AssumeRoleResponse")
@JsonSerialize(as = ImmutableAssumeRoleResponse.class)
@JsonDeserialize(as = ImmutableAssumeRoleResponse.class)
@Value.Immutable
@Value.Style(jdkOnly = true)
public interface AssumeRoleResponse {

@JsonProperty("AssumeRoleResult")
AssumeRoleResult assumeRoleResult();

@JsonProperty("ResponseMetadata")
ResponseMetadata responseMetadata();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 Dremio
*
* Licensed 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 org.projectnessie.objectstoragemock.sts;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.immutables.value.Value;

@JsonSerialize(as = ImmutableAssumeRoleResult.class)
@JsonDeserialize(as = ImmutableAssumeRoleResult.class)
@Value.Immutable
@Value.Style(jdkOnly = true)
public interface AssumeRoleResult {

@JsonProperty("SourceIdentity")
String sourceIdentity();

@JsonProperty("AssumedRoleUser")
RoleUser assumedRoleUser();

@JsonProperty("Credentials")
Credentials credentials();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2022 Dremio
*
* Licensed 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 org.projectnessie.objectstoragemock.sts;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
import jakarta.annotation.Nullable;
import java.time.Instant;
import org.immutables.value.Value;

@JsonSerialize(as = ImmutableCredentials.class)
@JsonDeserialize(as = ImmutableCredentials.class)
@Value.Immutable
@Value.Style(jdkOnly = true)
public interface Credentials {

@JsonProperty("AccessKeyId")
String accessKeyId();

@JsonProperty("SecretAccessKey")
String secretAccessKey();

@JsonProperty("SessionToken")
@Nullable
String sessionToken();

@JsonProperty("Expiration")
@JsonFormat(
shape = JsonFormat.Shape.STRING,
pattern = StdDateFormat.DATE_FORMAT_STR_ISO8601,
timezone = "UTC")
@JsonSerialize(using = InstantSerializer.class)
@JsonDeserialize(using = InstantDeserializer.class)
Instant expiration();
}