All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.awssdk.services.s3.internal.endpoints.S3AccessPointEndpointResolver Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.services.s3.internal.endpoints;

import static software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointUtils.isAccelerateEnabled;
import static software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointUtils.isArnRegionEnabled;
import static software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointUtils.isDualstackEnabled;
import static software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointUtils.isFipsRegion;
import static software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointUtils.isPathStyleAccessEnabled;
import static software.amazon.awssdk.services.s3.internal.endpoints.S3EndpointUtils.removeFipsIfNeeded;

import java.net.URI;
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.arns.Arn;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.regions.PartitionMetadata;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.internal.ConfiguredS3SdkHttpRequest;
import software.amazon.awssdk.services.s3.internal.resource.S3AccessPointBuilder;
import software.amazon.awssdk.services.s3.internal.resource.S3AccessPointResource;
import software.amazon.awssdk.services.s3.internal.resource.S3ArnConverter;
import software.amazon.awssdk.services.s3.internal.resource.S3ObjectLambdaEndpointBuilder;
import software.amazon.awssdk.services.s3.internal.resource.S3ObjectLambdaResource;
import software.amazon.awssdk.services.s3.internal.resource.S3OutpostAccessPointBuilder;
import software.amazon.awssdk.services.s3.internal.resource.S3OutpostResource;
import software.amazon.awssdk.services.s3.internal.resource.S3Resource;
import software.amazon.awssdk.utils.Validate;
import software.amazon.awssdk.utils.http.SdkHttpUtils;

/**
 * Returns a new configured HTTP request with a resolved access point endpoint and signing overrides.
 */
@SdkInternalApi
public final class S3AccessPointEndpointResolver implements S3EndpointResolver {

    private static final String S3_CONFIG_ERROR_MESSAGE = "An access point ARN cannot be passed as a bucket parameter to "
                                                          + "an S3 operation if the S3 client has been configured with %s";
    private static final String S3_OUTPOSTS_NAME = "s3-outposts";
    private static final String S3_OBJECT_LAMBDA_NAME = "s3-object-lambda";

    private S3AccessPointEndpointResolver() {
    }

    public static S3AccessPointEndpointResolver create() {
        return new S3AccessPointEndpointResolver();
    }

    @Override
    public ConfiguredS3SdkHttpRequest applyEndpointConfiguration(S3EndpointResolverContext context) {

        S3Resource s3Resource = S3ArnConverter.create().convertArn(Arn.fromString(getBucketName(context)));

        S3AccessPointResource s3EndpointResource =
            Validate.isInstanceOf(S3AccessPointResource.class, s3Resource,
                                  "An ARN was passed as a bucket parameter to an S3 operation, however it does not "
                                  + "appear to be a valid S3 access point ARN.");

        PartitionMetadata clientPartitionMetadata = PartitionMetadata.of(context.region());

        validateConfiguration(context, s3EndpointResource);

        URI accessPointUri = getUriForAccessPointResource(context, clientPartitionMetadata, s3EndpointResource);
        String path = buildPath(accessPointUri, context);

        SdkHttpRequest httpRequest = context.request().toBuilder()
                                            .protocol(accessPointUri.getScheme())
                                            .host(accessPointUri.getHost())
                                            .port(accessPointUri.getPort())
                                            .encodedPath(path)
                                            .build();

        Region signingRegionModification = s3EndpointResource.region().map(Region::of).orElse(null);
        String signingServiceModification = s3EndpointResource.parentS3Resource()
                                                              .flatMap(S3AccessPointEndpointResolver::resolveSigningService)
                                                              .orElse(null);

        return ConfiguredS3SdkHttpRequest.builder()
                                         .sdkHttpRequest(httpRequest)
                                         .signingRegionModification(signingRegionModification)
                                         .signingServiceModification(signingServiceModification)
                                         .build();
    }

    private String buildPath(URI accessPointUri, S3EndpointResolverContext context) {
        String key = context.originalRequest().getValueForField("Key", String.class).orElse(null);

        StringBuilder pathBuilder = new StringBuilder();
        if (accessPointUri.getPath() != null) {
            pathBuilder.append(accessPointUri.getPath());
        }

        if (key != null) {
            if (pathBuilder.length() > 0) {
                pathBuilder.append('/');
            }
            pathBuilder.append(SdkHttpUtils.urlEncodeIgnoreSlashes(key));
        }
        return pathBuilder.length() > 0 ? pathBuilder.toString() : null;
    }

    private void validateConfiguration(S3EndpointResolverContext context, S3AccessPointResource s3Resource) {
        S3Configuration serviceConfig = context.serviceConfiguration();

        Validate.isFalse(isAccelerateEnabled(serviceConfig), S3_CONFIG_ERROR_MESSAGE, "accelerate mode enabled.");
        Validate.isFalse(isPathStyleAccessEnabled(serviceConfig), S3_CONFIG_ERROR_MESSAGE, "path style addressing enabled.");
        Validate.isTrue(s3Resource.accountId().isPresent(), "An S3 access point ARN must have an account ID");

        Region clientRegion = context.region();
        
        if (s3Resource.region().isPresent()) {
            validateRegion(s3Resource, serviceConfig, clientRegion, context.fipsEnabled());
        } else {
            validateGlobalConfiguration(serviceConfig, clientRegion);
        }
        validatePartition(s3Resource, clientRegion);
    }

    private void validatePartition(S3AccessPointResource s3Resource, Region clientRegion) {
        String clientPartition = PartitionMetadata.of(clientRegion).id();
        Validate.isFalse(illegalPartitionConfiguration(s3Resource, clientPartition),
                         "The partition field of the ARN being passed as a bucket parameter to an S3 operation "
                         + "does not match the partition the S3 client has been configured with. Provided "
                         + "partition: '%s'; client partition: '%s'.", s3Resource.partition().orElse(""),
                         clientPartition);
    }

    private boolean illegalPartitionConfiguration(S3Resource s3Resource, String clientPartition) {
        return clientPartition == null || clientPartition.isEmpty() || !s3Resource.partition().isPresent()
               || !clientPartition.equals(s3Resource.partition().get());
    }

    private void validateRegion(S3AccessPointResource s3Resource, S3Configuration serviceConfig, Region clientRegion,
                                boolean fipsEnabledOnClient) {
        String arnRegion = s3Resource.region().get();
        Validate.isFalse(isFipsRegion(arnRegion), "Invalid ARN, FIPS region is not allowed in ARN."
                                                 + " Provided arn region: '" + arnRegion + "'.");

        boolean fipsEnabled = fipsEnabledOnClient || isFipsRegion(clientRegion.id());
        Validate.isFalse(fipsEnabled && clientRegionDiffersFromArnRegion(clientRegion, arnRegion),
                        String.format("The region field of the ARN being passed as a bucket parameter to an S3 operation "
                                      + "does not match the region the client was configured with. " +
                                      "Cross region access not allowed for fips region in client or arn."
                                      + " Provided region: '%s'; client region:'%s'.", arnRegion, clientRegion));

        Validate.isFalse(!isArnRegionEnabled(serviceConfig) && clientRegionDiffersFromArnRegion(clientRegion, arnRegion),
                         "The region field of the ARN being passed as a bucket parameter to an S3 operation "
                         + "does not match the region the client was configured with. To enable this "
                         + "behavior and prevent this exception set 'useArnRegionEnabled' to true in the "
                         + "configuration when building the S3 client. Provided region: '%s'; client region:"
                         + " '%s'.", arnRegion, clientRegion);
    }


    private boolean clientRegionDiffersFromArnRegion(Region clientRegion, String arnRegion) {
        return !removeFipsIfNeeded(clientRegion.id()).equals(arnRegion);
    }

    private void validateGlobalConfiguration(S3Configuration serviceConfiguration, Region region) {
        Validate.isTrue(serviceConfiguration.multiRegionEnabled(), "An Access Point ARN without a region value was passed as "
                                                                   + "a bucket parameter but multi-region is disabled. Check "
                                                                   + "client configuration, environment variables and system "
                                                                   + "configuration for multi-region disable configurations.");

        Validate.isFalse(isDualstackEnabled(serviceConfiguration), S3_CONFIG_ERROR_MESSAGE,
                         "dualstack, if the ARN contains no region.");
        Validate.isFalse(isFipsRegion(region.toString()), S3_CONFIG_ERROR_MESSAGE,
                         "a FIPS enabled region, if the ARN contains no region.");
    }

    private String getBucketName(S3EndpointResolverContext context) {
        return context.originalRequest().getValueForField("Bucket", String.class).orElseThrow(
            () -> new IllegalArgumentException("Bucket name cannot be empty when parsing access points."));
    }

    private URI getUriForAccessPointResource(S3EndpointResolverContext context,
                                             PartitionMetadata clientPartitionMetadata,
                                             S3AccessPointResource s3EndpointResource) {

        if (isOutpostAccessPoint(s3EndpointResource)) {
            return getOutpostAccessPointUri(context, clientPartitionMetadata, s3EndpointResource);
        } else if (isObjectLambdaAccessPoint(s3EndpointResource)) {
            return getObjectLambdaAccessPointUri(context, clientPartitionMetadata, s3EndpointResource);
        }

        return S3AccessPointBuilder.create()
                                   .endpointOverride(context.endpointOverride())
                                   .accessPointName(s3EndpointResource.accessPointName())
                                   .accountId(s3EndpointResource.accountId().get())
                                   .fipsEnabled(isFipsEnabled(context))
                                   .region(s3EndpointResource.region().orElse(null))
                                   .protocol(context.request().protocol())
                                   .domain(clientPartitionMetadata.dnsSuffix())
                                   .dualstackEnabled(isDualstackEnabled(context.serviceConfiguration()))
                                   .toUri();
    }

    private boolean isOutpostAccessPoint(S3AccessPointResource s3EndpointResource) {
        return s3EndpointResource.parentS3Resource().filter(r -> r instanceof S3OutpostResource).isPresent();
    }

    private boolean isObjectLambdaAccessPoint(S3AccessPointResource s3EndpointResource) {
        return s3EndpointResource.parentS3Resource().filter(r -> r instanceof S3ObjectLambdaResource).isPresent();
    }

    private URI getOutpostAccessPointUri(S3EndpointResolverContext context,
                                         PartitionMetadata clientPartitionMetadata,
                                         S3AccessPointResource s3EndpointResource) {
        if (isDualstackEnabled(context.serviceConfiguration())) {
            throw new IllegalArgumentException("An Outpost Access Point ARN cannot be passed as a bucket parameter to an S3 "
                                               + "operation if the S3 client has been configured with dualstack");
        }

        if (isFipsEnabled(context)) {
            throw new IllegalArgumentException("An access point ARN cannot be passed as a bucket parameter to an S3"
                                               + " operation if the S3 client has been configured with a FIPS"
                                               + " enabled region.");
        }

        S3OutpostResource parentResource = (S3OutpostResource) s3EndpointResource.parentS3Resource().get();
        return S3OutpostAccessPointBuilder.create()
                                          .endpointOverride(context.endpointOverride())
                                          .accountId(s3EndpointResource.accountId().get())
                                          .outpostId(parentResource.outpostId())
                                          .region(s3EndpointResource.region().get())
                                          .accessPointName(s3EndpointResource.accessPointName())
                                          .protocol(context.request().protocol())
                                          .domain(clientPartitionMetadata.dnsSuffix())
                                          .toUri();
    }

    private URI getObjectLambdaAccessPointUri(S3EndpointResolverContext context,
                                              PartitionMetadata clientPartitionMetadata,
                                              S3AccessPointResource s3EndpointResource) {
        if (isDualstackEnabled(context.serviceConfiguration())) {
            throw new IllegalArgumentException("An Object Lambda Access Point ARN cannot be passed as a bucket parameter to "
                                               + "an S3 operation if the S3 client has been configured with dualstack.");
        }

        return S3ObjectLambdaEndpointBuilder.create()
                                            .endpointOverride(context.endpointOverride())
                                            .accountId(s3EndpointResource.accountId().get())
                                            .region(s3EndpointResource.region().get())
                                            .accessPointName(s3EndpointResource.accessPointName())
                                            .protocol(context.request().protocol())
                                            .fipsEnabled(isFipsEnabled(context))
                                            .dualstackEnabled(isDualstackEnabled(context.serviceConfiguration()))
                                            .domain(clientPartitionMetadata.dnsSuffix())
                                            .toUri();
    }

    private static Optional resolveSigningService(S3Resource resource) {
        if (resource instanceof S3OutpostResource) {
            return Optional.of(S3_OUTPOSTS_NAME);
        }

        if (resource instanceof S3ObjectLambdaResource) {
            return Optional.of(S3_OBJECT_LAMBDA_NAME);
        }

        return Optional.empty();
    }

    private boolean isFipsEnabled(S3EndpointResolverContext context) {
        return context.fipsEnabled() || isFipsRegion(context.region().toString());
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy