From 0b1ea27a514e42bc27960f269a70f8a7bb40814b Mon Sep 17 00:00:00 2001 From: saketa Date: Wed, 26 Jul 2023 14:31:14 -0700 Subject: [PATCH 1/7] HDDS-7035. Rebased. --- .../ozone/s3/VirtualHostStyleFilter.java | 6 ++ .../s3/signature/StringToSignProducer.java | 12 ++++ .../apache/hadoop/ozone/s3/util/S3Consts.java | 3 +- .../ozone/s3/TestVirtualHostStyleFilter.java | 16 +++-- .../signature/TestStringToSignProducer.java | 58 +++++++++++++++++++ 5 files changed, 90 insertions(+), 5 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java index a257155e764d..38708e5d8cd7 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java @@ -39,6 +39,8 @@ import org.slf4j.LoggerFactory; import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_DOMAIN_NAME; +import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_PARAM; +import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_VIRTUAL; /** * Filter used to convert virtual host style pattern to path style pattern. @@ -111,6 +113,10 @@ public void filter(ContainerRequestContext requestContext) throws UriBuilder requestAddrBuilder = UriBuilder.fromUri(baseURI).path(newPath); queryParams.forEach((k, v) -> requestAddrBuilder.queryParam(k, v.toArray())); + // Enhance request URI to indicate virtual-host addressing style + // for later processing. + requestAddrBuilder.queryParam( + ENDPOINT_STYLE_PARAM, ENDPOINT_STYLE_VIRTUAL); URI requestAddr = requestAddrBuilder.build(); requestContext.setRequestUri(baseURI, requestAddr); } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java index b3fec69ba1d8..f29841a31590 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java @@ -45,6 +45,9 @@ import com.google.common.annotations.VisibleForTesting; import static java.time.temporal.ChronoUnit.SECONDS; import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR; +import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_PARAM; +import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_PATH; +import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_VIRTUAL; import org.apache.kerby.util.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -170,6 +173,15 @@ public static String buildCanonicalRequest( for (String p : parts) { encParts.add(urlEncode(p)); } + if (queryParams.getOrDefault(ENDPOINT_STYLE_PARAM, ENDPOINT_STYLE_PATH) + .equals(ENDPOINT_STYLE_VIRTUAL)) { + // Remove bucket name from URI if virtual-host style addressing is used. + encParts.remove(1); + if (encParts.size() == 1) { + encParts.add(1, ""); + } + queryParams.remove(ENDPOINT_STYLE_PARAM); + } String canonicalUri = join("/", encParts); String canonicalQueryStr = getQueryParamString(queryParams); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java index 05e9503225cc..06c2c7dd4d71 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java @@ -64,7 +64,8 @@ private S3Consts() { public static final String CUSTOM_METADATA_HEADER_PREFIX = "x-amz-meta-"; + public static final String DECODED_CONTENT_LENGTH_HEADER = "x-amz-decoded-content-length"; - + } diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java index 19d9380de917..2f81c1c6a148 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java @@ -20,6 +20,7 @@ import org.apache.hadoop.fs.InvalidRequestException; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.ozone.test.GenericTestUtils; +import org.apache.hadoop.ozone.s3.util.S3Consts; import org.glassfish.jersey.internal.PropertiesDelegate; import org.glassfish.jersey.server.ContainerRequest; import org.junit.Assert; @@ -106,7 +107,8 @@ public void testVirtualHostStyle() throws Exception { ".localhost:9878", "/myfile", null, true); virtualHostStyleFilter.filter(containerRequest); URI expected = new URI("http://" + s3HttpAddr + - "/mybucket/myfile"); + "/mybucket/myfile?" + S3Consts.ENDPOINT_STYLE_PARAM + "=" + + S3Consts.ENDPOINT_STYLE_VIRTUAL); Assert.assertEquals(expected, containerRequest.getRequestUri()); } @@ -136,7 +138,9 @@ public void testVirtualHostStyleWithCreateBucketRequest() throws Exception { ContainerRequest containerRequest = createContainerRequest("mybucket" + ".localhost:9878", null, null, true); virtualHostStyleFilter.filter(containerRequest); - URI expected = new URI("http://" + s3HttpAddr + "/mybucket"); + URI expected = new URI("http://" + s3HttpAddr + "/mybucket?" + + S3Consts.ENDPOINT_STYLE_PARAM + "=" + + S3Consts.ENDPOINT_STYLE_VIRTUAL); Assert.assertEquals(expected, containerRequest.getRequestUri()); } @@ -151,7 +155,9 @@ public void testVirtualHostStyleWithQueryParams() throws Exception { ContainerRequest containerRequest = createContainerRequest("mybucket" + ".localhost:9878", null, "?prefix=bh", true); virtualHostStyleFilter.filter(containerRequest); - URI expected = new URI("http://" + s3HttpAddr + "/mybucket?prefix=bh"); + URI expected = new URI("http://" + s3HttpAddr + "/mybucket?prefix=bh&" + + S3Consts.ENDPOINT_STYLE_PARAM + "=" + + S3Consts.ENDPOINT_STYLE_VIRTUAL); assertTrue(expected.toString().contains(containerRequest.getRequestUri() .toString())); @@ -159,7 +165,9 @@ public void testVirtualHostStyleWithQueryParams() throws Exception { ".localhost:9878", null, "?prefix=bh&type=dir", true); virtualHostStyleFilter.filter(containerRequest); expected = new URI("http://" + s3HttpAddr + - "/mybucket?prefix=bh&type=dir"); + "/mybucket?prefix=bh&type=dir&" + + S3Consts.ENDPOINT_STYLE_PARAM + "=" + + S3Consts.ENDPOINT_STYLE_VIRTUAL); assertTrue(expected.toString().contains(containerRequest.getRequestUri() .toString())); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java index 9d521721791c..c454d9c5125d 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java @@ -44,6 +44,8 @@ import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR; import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.DATE_FORMATTER; +import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_PARAM; +import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_VIRTUAL; import static org.junit.jupiter.params.provider.Arguments.arguments; /** @@ -269,4 +271,60 @@ public void testValidateCanonicalHeaders( Assert.assertEquals(expectedResult, actualResult); } + + @Test + public void testVirtualStyleAddressURI() throws Exception { + String canonicalRequest = "GET\n" + + "/\n" + + "\n" + + "host:bucket1.s3g.internal:9878\nx-amz-content-sha256:Content-SHA\n" + + "x-amz-date:" + DATETIME + "\n" + + "\n" + + "host;x-amz-content-sha256;x-amz-date\n" + + "Content-SHA"; + + String authHeader = + "AWS4-HMAC-SHA256 Credential=AKIAJWFJK62WUTKNFJJA/20181009/us-east-1" + + "/s3/aws4_request, " + + "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " + + "Signature" + + + "=db81b057718d7c1b3b8dffa29933099551c51d787b3b13b9e0f9ebed45982bf2"; + + MultivaluedMap headers = new MultivaluedHashMap<>(); + headers.putSingle("Authorization", authHeader); + headers.putSingle("Content-Length", "123"); + headers.putSingle("Host", "bucket1.s3g.internal:9878"); + headers.putSingle("X-AMZ-Content-Sha256", "Content-SHA"); + headers.putSingle("X-AMZ-Date", DATETIME); + + final SignatureInfo signatureInfo = + new AuthorizationV4HeaderParser(authHeader, DATETIME) { + @Override + public void validateDateRange(Credential credentialObj) { + //NOOP + } + }.parseSignature(); + + ContainerRequestContext context = setupContext( + new URI("http://bucket1.s3g.internal:9878?" + + ENDPOINT_STYLE_PARAM + "=" + ENDPOINT_STYLE_VIRTUAL), + "GET", + headers, + new MultivaluedHashMap<>()); + + final String signatureBase = + StringToSignProducer.createSignatureBase(signatureInfo, context); + + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(canonicalRequest.getBytes(StandardCharsets.UTF_8)); + + Assert.assertEquals( + "String to sign is invalid", + "AWS4-HMAC-SHA256\n" + + DATETIME + "\n" + + "20181009/us-east-1/s3/aws4_request\n" + + Hex.encode(md.digest()).toLowerCase(), + signatureBase); + } } From be5016b43a717e3d2ac71cc307e186850b6a993c Mon Sep 17 00:00:00 2001 From: saketa Date: Tue, 29 Aug 2023 14:58:49 -0700 Subject: [PATCH 2/7] HDDS-7035. Removed custom variables. Moved String to Sign generation to AuthFilter. --- .../ozonesecure-ha/s3g-virtual-host.yaml | 47 +++++ .../ozonesecure-ha/test-s3g-virtual-host.sh | 34 ++++ .../src/main/compose/ozonesecure-ha/test.sh | 6 +- .../main/smoketest/s3/awss3virtualhost.robot | 56 ++++++ .../src/main/smoketest/s3/commonawslib.robot | 4 + .../hadoop/ozone/s3/AuthorizationFilter.java | 103 +++++++++++ .../hadoop/ozone/s3/ClientIpFilter.java | 3 +- .../hadoop/ozone/s3/HeaderPreprocessor.java | 6 +- .../hadoop/ozone/s3/OzoneClientProducer.java | 64 ------- .../ozone/s3/VirtualHostStyleFilter.java | 15 +- .../ozone/s3/endpoint/EndpointBase.java | 8 + .../s3/signature/AWSSignatureProcessor.java | 2 + .../ozone/s3/signature/SignatureInfo.java | 73 ++++++++ .../s3/signature/StringToSignProducer.java | 15 +- .../apache/hadoop/ozone/s3/util/S3Consts.java | 4 +- .../ozone/s3/TestAuthorizationFilter.java | 165 ++++++++++++++++++ .../ozone/s3/TestOzoneClientProducer.java | 151 +--------------- .../ozone/s3/TestVirtualHostStyleFilter.java | 31 ++-- .../TestAuthorizationV4QueryParser.java | 4 +- .../signature/TestStringToSignProducer.java | 62 +------ 20 files changed, 533 insertions(+), 320 deletions(-) create mode 100644 hadoop-ozone/dist/src/main/compose/ozonesecure-ha/s3g-virtual-host.yaml create mode 100755 hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-s3g-virtual-host.sh create mode 100644 hadoop-ozone/dist/src/main/smoketest/s3/awss3virtualhost.robot create mode 100644 hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java create mode 100644 hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/s3g-virtual-host.yaml b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/s3g-virtual-host.yaml new file mode 100644 index 000000000000..708231ad20e8 --- /dev/null +++ b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/s3g-virtual-host.yaml @@ -0,0 +1,47 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +version: "3.8" + +x-s3g-virtual-host-config: + &s3g-virtual-host-config + environment: + - OZONE-SITE.XML_ozone.s3g.domain.name=s3g.internal +services: + datanode1: + <<: *s3g-virtual-host-config + datanode2: + <<: *s3g-virtual-host-config + datanode3: + <<: *s3g-virtual-host-config + om1: + <<: *s3g-virtual-host-config + om2: + <<: *s3g-virtual-host-config + om3: + <<: *s3g-virtual-host-config + scm1.org: + <<: *s3g-virtual-host-config + scm2.org: + <<: *s3g-virtual-host-config + scm3.org: + <<: *s3g-virtual-host-config + s3g: + <<: *s3g-virtual-host-config + extra_hosts: + - "bucket1.s3g.internal: 172.25.0.114" + recon: + <<: *s3g-virtual-host-config diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-s3g-virtual-host.sh b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-s3g-virtual-host.sh new file mode 100755 index 000000000000..2bc3125801fe --- /dev/null +++ b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test-s3g-virtual-host.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +#suite:HA-secure + +COMPOSE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +export COMPOSE_DIR + +export SECURITY_ENABLED=true +export OM_SERVICE_ID="omservice" +export SCM=scm1.org +export COMPOSE_FILE=docker-compose.yaml:s3g-virtual-host.yaml + +# shellcheck source=/dev/null +source "$COMPOSE_DIR/../testlib.sh" + +start_docker_env + +## Run virtual host test cases +execute_robot_test s3g -N s3-virtual-host s3/awss3virtualhost.robot diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test.sh b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test.sh index 70d45b33c651..fc57e7a782ea 100755 --- a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test.sh +++ b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test.sh @@ -41,11 +41,13 @@ execute_robot_test s3g -v SCHEME:o3fs -v BUCKET_TYPE:link -N ozonefs-o3fs-link o execute_robot_test s3g basic/links.robot -exclude="" +## Exclude virtual-host.robot +exclude="--exclude virtual-host" for bucket in encrypted link; do execute_robot_test s3g -v BUCKET:${bucket} -N s3-${bucket} ${exclude} s3 # some tests are independent of the bucket type, only need to be run once - exclude="--exclude no-bucket-type" + ## Exclude virtual-host.robot + exclude="--exclude virtual-host --exclude no-bucket-type" done execute_robot_test s3g admincli diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/awss3virtualhost.robot b/hadoop-ozone/dist/src/main/smoketest/s3/awss3virtualhost.robot new file mode 100644 index 000000000000..8f77e2c3e876 --- /dev/null +++ b/hadoop-ozone/dist/src/main/smoketest/s3/awss3virtualhost.robot @@ -0,0 +1,56 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +*** Settings *** +Documentation S3 gateway test with aws cli using virtual host style address +Library OperatingSystem +Library String +Resource ../commonlib.robot +Resource ./commonawslib.robot +Test Timeout 5 minutes +Suite Setup Setup s3 tests +Default Tags virtual-host + +*** Variables *** +${ENDPOINT_URL} http://s3g.internal:9878 +${BUCKET} bucket1 +${OZONE_S3_ADDRESS_STYLE} virtual + +*** Test Cases *** + +File upload and directory list with virtual style addressing + Create bucket with name ${BUCKET} + Execute date > /tmp/testfile + ${result} = Execute AWSS3Cli cp /tmp/testfile s3://${BUCKET} --debug + Should contain ${result} url=http://bucket1.s3g.internal:9878/ + Should contain ${result} upload + ${result} = Execute AWSS3Cli cp /tmp/testfile s3://${BUCKET}/dir1/dir2/file --debug + Should contain ${result} url=http://bucket1.s3g.internal:9878/dir1/dir2/file + Should contain ${result} upload + ${result} = Execute AWSS3Cli ls s3://${BUCKET} --debug + Should contain ${result} url=http://bucket1.s3g.internal:9878/ + Should contain ${result} testfile + Should contain ${result} dir1 + Should not contain ${result} dir2 + ${result} = Execute AWSS3Cli ls s3://${BUCKET}/dir1/ --debug + Should contain ${result} url=http://bucket1.s3g.internal:9878/ + Should contain ${result} prefix=dir1 + Should not contain ${result} testfile + Should contain ${result} dir2/ + ${result} = Execute AWSS3Cli ls s3://${BUCKET}/dir1/dir2/file + Should not contain ${result} testfile + Should not contain ${result} dir1 + Should not contain ${result} dir2 + Should contain ${result} file diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot b/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot index 0a6a42f005f3..f0f617eb1b61 100644 --- a/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot +++ b/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot @@ -26,6 +26,7 @@ ${BUCKET} generated ${KEY_NAME} key1 ${OZONE_S3_TESTS_SET_UP} ${FALSE} ${OZONE_AWS_ACCESS_KEY_ID} ${EMPTY} +${OZONE_S3_ADDRESS_STYLE} path *** Keywords *** Execute AWSS3APICli @@ -85,6 +86,9 @@ Setup secure v4 headers Execute aws configure set aws_access_key_id ${accessKey} Execute aws configure set aws_secret_access_key ${secret} Execute aws configure set region us-west-1 + Return From Keyword If '${OZONE_S3_ADDRESS_STYLE}' != 'virtual' + Execute aws configure set default.s3.addressing_style virtual + Setup dummy credentials for S3 Execute aws configure set default.s3.signature_version s3v4 diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java new file mode 100644 index 000000000000..eb41aa663c89 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java @@ -0,0 +1,103 @@ +package org.apache.hadoop.ozone.s3; + +import javax.annotation.Priority; +import javax.inject.Inject; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.PreMatching; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; +import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; +import org.apache.hadoop.ozone.s3.signature.SignatureInfo; +import org.apache.hadoop.ozone.s3.signature.SignatureInfo.Version; +import org.apache.hadoop.ozone.s3.signature.SignatureProcessor; +import org.apache.hadoop.ozone.s3.signature.StringToSignProducer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.ACCESS_DENIED; +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INTERNAL_ERROR; +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR; + +/** + * Filter used to construct string to sign from unfiltered request. + * It should be executed before all other filters as the original + * could be enriched later. + */ + +@Provider +@PreMatching +@Priority(AuthorizationFilter.PRIORITY) +public class AuthorizationFilter implements ContainerRequestFilter { + public static final int PRIORITY = 50; + + private static final Logger LOG = LoggerFactory.getLogger( + AuthorizationFilter.class); + + @Inject + private SignatureProcessor signatureProcessor; + + @Inject + private SignatureInfo signatureInfo; + + @Override + public void filter(ContainerRequestContext context) throws + IOException { + + try { + signatureInfo.initialize(signatureProcessor.parseSignature()); + if (signatureInfo.getVersion() == Version.V4) { + signatureInfo.setStrToSign( + StringToSignProducer.createSignatureBase(signatureInfo, context)); + } else { + LOG.debug("Unsupported AWS signature version: {}", + signatureInfo.getVersion()); + throw S3_AUTHINFO_CREATION_ERROR; + } + + String awsAccessId = signatureInfo.getAwsAccessId(); + // ONLY validate aws access id when needed. + if (awsAccessId == null || awsAccessId.equals("")) { + LOG.debug("Malformed s3 header. awsAccessID: {}", awsAccessId); + throw ACCESS_DENIED; + } + } catch (OS3Exception ex) { + LOG.debug("Error during Client Creation: ", ex); + throw wrapOS3Exception(ex); + } catch (Exception e) { + // For any other critical errors during object creation throw Internal + // error. + LOG.debug("Error during Client Creation: ", e); + throw wrapOS3Exception( + S3ErrorTable.newError(INTERNAL_ERROR, null, e)); + } + } + + @VisibleForTesting + public void setSignatureParser(SignatureProcessor awsSignatureProcessor) { + this.signatureProcessor = awsSignatureProcessor; + } + + @VisibleForTesting + public void setSignatureInfo(SignatureInfo signatureInfo) { + this.signatureInfo = signatureInfo; + } + + @VisibleForTesting + public SignatureInfo getSignatureInfo() { + return signatureInfo; + } + + private WebApplicationException wrapOS3Exception(OS3Exception os3Exception) { + return new WebApplicationException(os3Exception.getErrorMessage(), + os3Exception, + Response.status(os3Exception.getHttpCode()) + .entity(os3Exception.toXml()).build()); + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/ClientIpFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/ClientIpFilter.java index 921b18d9b59d..858ebbcd0b9c 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/ClientIpFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/ClientIpFilter.java @@ -38,7 +38,8 @@ @Priority(ClientIpFilter.PRIORITY) public class ClientIpFilter implements ContainerRequestFilter { - public static final int PRIORITY = 200; + public static final int PRIORITY = HeaderPreprocessor.PRIORITY + + S3GatewayHttpServer.FILTER_PRIORITY_DO_AFTER; public static final String CLIENT_IP_HEADER = "client_ip"; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/HeaderPreprocessor.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/HeaderPreprocessor.java index 344fd9097162..c9deb2264967 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/HeaderPreprocessor.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/HeaderPreprocessor.java @@ -34,10 +34,12 @@ */ @Provider @PreMatching -@Priority(VirtualHostStyleFilter.PRIORITY - + S3GatewayHttpServer.FILTER_PRIORITY_DO_AFTER) +@Priority(HeaderPreprocessor.PRIORITY) public class HeaderPreprocessor implements ContainerRequestFilter { + public static final int PRIORITY = VirtualHostStyleFilter.PRIORITY + + S3GatewayHttpServer.FILTER_PRIORITY_DO_AFTER; + public static final String MULTIPART_UPLOAD_MARKER = "ozone/mpu"; public static final String CONTENT_TYPE = "Content-Type"; diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/OzoneClientProducer.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/OzoneClientProducer.java index bfab4559a350..50e1f80e9c22 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/OzoneClientProducer.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/OzoneClientProducer.java @@ -24,26 +24,13 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.core.Context; -import javax.ws.rs.core.Response; import java.io.IOException; -import com.google.common.annotations.VisibleForTesting; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.ozone.client.OzoneClient; -import org.apache.hadoop.ozone.om.protocol.S3Auth; -import org.apache.hadoop.ozone.s3.exception.OS3Exception; -import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; -import org.apache.hadoop.ozone.s3.signature.SignatureInfo; -import org.apache.hadoop.ozone.s3.signature.SignatureInfo.Version; -import org.apache.hadoop.ozone.s3.signature.SignatureProcessor; -import org.apache.hadoop.ozone.s3.signature.StringToSignProducer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.ACCESS_DENIED; -import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INTERNAL_ERROR; -import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR; - /** * This class creates the OzoneClient for the Rest endpoints. */ @@ -55,9 +42,6 @@ public class OzoneClientProducer { private OzoneClient client; - @Inject - private SignatureProcessor signatureProcessor; - @Inject private OzoneConfiguration ozoneConfiguration; @@ -76,42 +60,6 @@ public synchronized OzoneClient createClient() throws WebApplicationException, public void destroy() throws IOException { client.getObjectStore().getClientProxy().clearThreadLocalS3Auth(); } - @Produces - public S3Auth getSignature() { - try { - SignatureInfo signatureInfo = signatureProcessor.parseSignature(); - String stringToSign = ""; - if (signatureInfo.getVersion() == Version.V4) { - stringToSign = - StringToSignProducer.createSignatureBase(signatureInfo, context); - } else { - LOG.debug("Unsupported AWS signature version: {}", - signatureInfo.getVersion()); - throw S3_AUTHINFO_CREATION_ERROR; - } - - String awsAccessId = signatureInfo.getAwsAccessId(); - // ONLY validate aws access id when needed. - if (awsAccessId == null || awsAccessId.equals("")) { - LOG.debug("Malformed s3 header. awsAccessID: {}", awsAccessId); - throw ACCESS_DENIED; - } - - // Note: userPrincipal is initialized to be the same value as accessId, - // could be updated later in RpcClient#getS3Volume - return new S3Auth(stringToSign, - signatureInfo.getSignature(), - awsAccessId, awsAccessId); - } catch (OS3Exception ex) { - LOG.debug("Error during Client Creation: ", ex); - throw wrapOS3Exception(ex); - } catch (Exception e) { - // For any other critical errors during object creation throw Internal - // error. - LOG.debug("Error during Client Creation: ", e); - throw wrapOS3Exception(S3ErrorTable.newError(INTERNAL_ERROR, null, e)); - } - } private OzoneClient getClient(OzoneConfiguration config) throws IOException { @@ -133,16 +81,4 @@ private OzoneClient getClient(OzoneConfiguration config) public synchronized void setOzoneConfiguration(OzoneConfiguration config) { this.ozoneConfiguration = config; } - - @VisibleForTesting - public void setSignatureParser(SignatureProcessor awsSignatureProcessor) { - this.signatureProcessor = awsSignatureProcessor; - } - - private WebApplicationException wrapOS3Exception(OS3Exception os3Exception) { - return new WebApplicationException(os3Exception.getErrorMessage(), - os3Exception, - Response.status(os3Exception.getHttpCode()) - .entity(os3Exception.toXml()).build()); - } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java index 38708e5d8cd7..81235fb9d568 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java @@ -39,8 +39,6 @@ import org.slf4j.LoggerFactory; import static org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_DOMAIN_NAME; -import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_PARAM; -import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_VIRTUAL; /** * Filter used to convert virtual host style pattern to path style pattern. @@ -51,7 +49,8 @@ @Priority(VirtualHostStyleFilter.PRIORITY) public class VirtualHostStyleFilter implements ContainerRequestFilter { - public static final int PRIORITY = 100; + public static final int PRIORITY = AuthorizationFilter.PRIORITY + + S3GatewayHttpServer.FILTER_PRIORITY_DO_AFTER; private static final Logger LOG = LoggerFactory.getLogger( VirtualHostStyleFilter.class); @@ -105,18 +104,14 @@ public void filter(ContainerRequestContext requestContext) throws URI baseURI = requestContext.getUriInfo().getBaseUri(); String currentPath = requestContext.getUriInfo().getPath(); String newPath = bucketName; - if (currentPath != null) { - newPath += String.format("%s", currentPath); - } MultivaluedMap queryParams = requestContext.getUriInfo() .getQueryParameters(); UriBuilder requestAddrBuilder = UriBuilder.fromUri(baseURI).path(newPath); + if (currentPath != null) { + requestAddrBuilder.path(currentPath); + } queryParams.forEach((k, v) -> requestAddrBuilder.queryParam(k, v.toArray())); - // Enhance request URI to indicate virtual-host addressing style - // for later processing. - requestAddrBuilder.queryParam( - ENDPOINT_STYLE_PARAM, ENDPOINT_STYLE_VIRTUAL); URI requestAddr = requestAddrBuilder.build(); requestContext.setRequestUri(baseURI, requestAddr); } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java index 66fb7df190a9..083c530c5da7 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java @@ -57,6 +57,7 @@ import com.google.common.annotations.VisibleForTesting; import org.apache.hadoop.ozone.s3.metrics.S3GatewayMetrics; +import org.apache.hadoop.ozone.s3.signature.SignatureInfo; import org.apache.hadoop.ozone.s3.util.AuditUtils; import org.apache.hadoop.util.Time; import org.slf4j.Logger; @@ -75,6 +76,8 @@ public abstract class EndpointBase implements Auditor { @Inject private OzoneClient client; @Inject + SignatureInfo signatureInfo; + private S3Auth s3Auth; @Context private ContainerRequestContext context; @@ -114,6 +117,11 @@ protected OzoneBucket getBucket(OzoneVolume volume, String bucketName) */ @PostConstruct public void initialization() { + // Note: userPrincipal is initialized to be the same value as accessId, + // could be updated later in RpcClient#getS3Volume + s3Auth = new S3Auth(signatureInfo.getStringToSign(), + signatureInfo.getSignature(), + signatureInfo.getAwsAccessId(), signatureInfo.getAwsAccessId()); LOG.debug("S3 access id: {}", s3Auth.getAccessID()); getClient().getObjectStore() .getClientProxy() diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AWSSignatureProcessor.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AWSSignatureProcessor.java index 1ae8682186b1..43a1e6b71309 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AWSSignatureProcessor.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AWSSignatureProcessor.java @@ -95,6 +95,8 @@ public SignatureInfo parseSignature() throws OS3Exception { "", "", "", "", "", "", "", false ); } + signatureInfo.setUnfilteredURI( + context.getUriInfo().getRequestUri().getPath()); return signatureInfo; } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java index 0da8895921ba..90f1922ae974 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java @@ -17,11 +17,14 @@ */ package org.apache.hadoop.ozone.s3.signature; +import javax.enterprise.context.RequestScoped; + /** * Signature and related information. *

* Required to create stringToSign and token. */ +@RequestScoped public class SignatureInfo { private Version version; @@ -48,6 +51,13 @@ public class SignatureInfo { private boolean signPayload = true; + private String unfilteredURI; + + + private String stringToSign; + + public SignatureInfo(){} + @SuppressWarnings("checkstyle:ParameterNumber") public SignatureInfo( Version version, @@ -59,6 +69,51 @@ public SignatureInfo( String credentialScope, String algorithm, boolean signPayload + ) { + this(version, date, dateTime, awsAccessId, signature, signedHeaders, + credentialScope, algorithm, signPayload, null, null); + } + + public SignatureInfo( + Version version, + String date, + String dateTime, + String awsAccessId, + String signature, + String signedHeaders, + String credentialScope, + String algorithm, + boolean signPayload, + String uri, + String stringToSign + ) { + initialize(version, date, dateTime, awsAccessId, signature, signedHeaders, + credentialScope, algorithm, signPayload, uri, stringToSign); + } + + public void initialize( + SignatureInfo signatureInfo + ) { + initialize(signatureInfo.getVersion(), signatureInfo.getDate(), + signatureInfo.getDateTime(), signatureInfo.getAwsAccessId(), + signatureInfo.getSignature(), signatureInfo.getSignedHeaders(), + signatureInfo.getCredentialScope(), signatureInfo.getAlgorithm(), + signatureInfo.isSignPayload(), signatureInfo.getUnfilteredURI(), + signatureInfo.getStringToSign()); + } + + public void initialize( + Version version, + String date, + String dateTime, + String awsAccessId, + String signature, + String signedHeaders, + String credentialScope, + String algorithm, + boolean signPayload, + String uri, + String stringToSign ) { this.version = version; this.date = date; @@ -69,6 +124,8 @@ public SignatureInfo( this.credentialScope = credentialScope; this.algorithm = algorithm; this.signPayload = signPayload; + this.unfilteredURI = uri; + this.stringToSign = stringToSign; } public String getAwsAccessId() { @@ -107,6 +164,22 @@ public String getDateTime() { return dateTime; } + public String getUnfilteredURI() { + return unfilteredURI; + } + + public String getStringToSign() { + return stringToSign; + } + + public void setUnfilteredURI(String uri) { + this.unfilteredURI = uri; + } + + public void setStrToSign(String strToSign) { + this.stringToSign = strToSign; + } + /** * Signature version. */ diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java index f29841a31590..a8a4f6072d29 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/StringToSignProducer.java @@ -45,9 +45,6 @@ import com.google.common.annotations.VisibleForTesting; import static java.time.temporal.ChronoUnit.SECONDS; import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR; -import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_PARAM; -import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_PATH; -import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_VIRTUAL; import org.apache.kerby.util.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,7 +81,6 @@ public static String createSignatureBase( return createSignatureBase(signatureInfo, context.getUriInfo().getRequestUri().getScheme(), context.getMethod(), - context.getUriInfo().getRequestUri().getPath(), LowerCaseKeyStringMap.fromHeaderMap(context.getHeaders()), fromMultiValueToSingleValueMap( context.getUriInfo().getQueryParameters())); @@ -95,7 +91,6 @@ public static String createSignatureBase( SignatureInfo signatureInfo, String scheme, String method, - String uri, LowerCaseKeyStringMap headers, Map queryParams ) throws Exception { @@ -114,6 +109,7 @@ public static String createSignatureBase( String credentialScope = signatureInfo.getCredentialScope(); // If the absolute path is empty, use a forward slash (/) + String uri = signatureInfo.getUnfilteredURI(); uri = (uri.trim().length() > 0) ? uri : "/"; // Encode URI and preserve forward slashes strToSign.append(signatureInfo.getAlgorithm() + NEWLINE); @@ -173,15 +169,6 @@ public static String buildCanonicalRequest( for (String p : parts) { encParts.add(urlEncode(p)); } - if (queryParams.getOrDefault(ENDPOINT_STYLE_PARAM, ENDPOINT_STYLE_PATH) - .equals(ENDPOINT_STYLE_VIRTUAL)) { - // Remove bucket name from URI if virtual-host style addressing is used. - encParts.remove(1); - if (encParts.size() == 1) { - encParts.add(1, ""); - } - queryParams.remove(ENDPOINT_STYLE_PARAM); - } String canonicalUri = join("/", encParts); String canonicalQueryStr = getQueryParamString(queryParams); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java index 06c2c7dd4d71..e151c7b82c5f 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java @@ -63,9 +63,9 @@ private S3Consts() { ".com/doc/2006-03-01/"; public static final String CUSTOM_METADATA_HEADER_PREFIX = "x-amz-meta-"; - + public static final String DECODED_CONTENT_LENGTH_HEADER = "x-amz-decoded-content-length"; - + } diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java new file mode 100644 index 000000000000..8f1b8cfaf4cc --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java @@ -0,0 +1,165 @@ +package org.apache.hadoop.ozone.s3; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; +import java.net.URI; +import java.util.stream.Stream; + +import org.apache.hadoop.ozone.s3.signature.AWSSignatureProcessor; + +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_FORBIDDEN; + +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_HEADER; +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR; +import static org.apache.hadoop.ozone.s3.signature.SignatureParser.AUTHORIZATION_HEADER; +import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.CONTENT_MD5; +import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.CONTENT_TYPE; +import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.HOST_HEADER; +import static org.apache.hadoop.ozone.s3.signature.StringToSignProducer.X_AMAZ_DATE; +import static org.apache.hadoop.ozone.s3.signature.StringToSignProducer.X_AMZ_CONTENT_SHA256; +import static org.junit.Assert.fail; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import org.apache.hadoop.ozone.s3.signature.SignatureInfo; +import org.apache.hadoop.ozone.s3.signature.SignatureProcessor; +import org.junit.Assert; +import org.junit.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.Mockito; + +/** + * This class test string to sign generation. + */ +public class TestAuthorizationFilter { + + private AuthorizationFilter authorizationFilter = new AuthorizationFilter(); + + private MultivaluedMap headerMap; + private MultivaluedMap queryMap; + private MultivaluedMap pathParamsMap; + + private static StreamtestAuthFilterFailuresInput() { + return Stream.of( + arguments( + "AWS4-HMAC-SHA256 Credential=testuser1/20190221/us-west-1/s3" + + "/aws4_request, SignedHeaders=content-md5;host;" + + "x-amz-content-sha256;x-amz-date, " + + "Signature" + + "=56ec73ba1974f8feda8365c3caef89c5d4a688d5f9baccf47" + + "65f46a14cd745ad", + "Zi68x2nPDDXv5qfDC+ZWTg==", + "s3g:9878", + "e2bd43f11c97cde3465e0e8d1aad77af7ec7aa2ed8e213cd0e24" + + "1e28375860c6", + "20190221T002037Z", + "", + "/" + ), + arguments( + "AWS4-HMAC-SHA256 " + + "Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request," + + " SignedHeaders=content-type;host;x-amz-date, " + + "Signature=" + + "5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400" + + "e06b5924a6f2b5d7", + "", + "iam.amazonaws.com", + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "20150830T123600Z", + "application/x-www-form-urlencoded; charset=utf-8", + "" + ), + arguments(null, null, null, null, null, null, null), + arguments("", null, null, null, null, null, null), + // AWS V2 signature + arguments( + "AWS AKIDEXAMPLE:St7bHPOdkmsX/GITGe98rOQiUCg=", + "", + "s3g:9878", + "", + "Wed, 22 Mar 2023 17:00:06 +0000", + "application/octet-stream", + "/" + ) + ); + } + + @ParameterizedTest + @MethodSource("testAuthFilterFailuresInput") + public void testAuthFilterFailures( + String authHeader, String contentMd5, + String host, String amzContentSha256, String date, String contentType, + String path + ) { + headerMap = new MultivaluedHashMap<>(); + queryMap = new MultivaluedHashMap<>(); + pathParamsMap = new MultivaluedHashMap<>(); + try { + System.err.println("Testing: " + authHeader); + headerMap.putSingle(AUTHORIZATION_HEADER, authHeader); + headerMap.putSingle(CONTENT_MD5, contentMd5); + headerMap.putSingle(HOST_HEADER, host); + headerMap.putSingle(X_AMZ_CONTENT_SHA256, amzContentSha256); + headerMap.putSingle(X_AMAZ_DATE, date); + headerMap.putSingle(CONTENT_TYPE, contentType); + + UriInfo uriInfo = Mockito.mock(UriInfo.class); + ContainerRequestContext context = Mockito.mock(ContainerRequestContext.class); + Mockito.when(uriInfo.getQueryParameters()).thenReturn(queryMap); + Mockito.when(uriInfo.getRequestUri()).thenReturn( + new URI("http://" + host+path)); + + Mockito.when(context.getUriInfo()).thenReturn(uriInfo); + Mockito.when(context.getHeaders()).thenReturn(headerMap); + Mockito.when(context.getHeaderString(AUTHORIZATION_HEADER)) + .thenReturn(authHeader); + Mockito.when(context.getUriInfo().getQueryParameters()) + .thenReturn(queryMap); + Mockito.when(context.getUriInfo().getPathParameters()) + .thenReturn(pathParamsMap); + + AWSSignatureProcessor awsSignatureProcessor = new AWSSignatureProcessor(); + awsSignatureProcessor.setContext(context); + + SignatureInfo signatureInfo = new SignatureInfo(); + + authorizationFilter.setSignatureParser(awsSignatureProcessor); + authorizationFilter.setSignatureInfo(signatureInfo); + + authorizationFilter.filter(context); + if ("".equals(authHeader)) { + fail("Empty AuthHeader must fail"); + } + } catch (WebApplicationException ex) { + if (authHeader == null || authHeader.isEmpty() || + authHeader.startsWith("AWS ")) { + // Empty auth header and unsupported AWS signature + // should fail with Invalid Request. + Assert.assertEquals(HTTP_FORBIDDEN, ex.getResponse().getStatus()); + Assert.assertEquals(S3_AUTHINFO_CREATION_ERROR.getErrorMessage(), + ex.getMessage()); + } else { + // Other requests have stale timestamp and + // should fail with Malformed Authorization Header. + Assert.assertEquals(HTTP_BAD_REQUEST, ex.getResponse().getStatus()); + Assert.assertEquals(MALFORMED_HEADER.getErrorMessage(), + ex.getMessage()); + + } + + } catch (Exception ex) { + fail("Unexpected exception: " + ex); + } + } + + //testStrToSign generation + +} diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestOzoneClientProducer.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestOzoneClientProducer.java index 064d9836f6fe..13efecbde406 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestOzoneClientProducer.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestOzoneClientProducer.java @@ -30,118 +30,29 @@ import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.ozone.OzoneConfigKeys; import org.apache.hadoop.ozone.om.OMConfigKeys; -import org.apache.hadoop.ozone.s3.signature.AWSSignatureProcessor; -import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; -import static java.net.HttpURLConnection.HTTP_FORBIDDEN; - -import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_HEADER; -import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR; -import static org.apache.hadoop.ozone.s3.signature.SignatureParser.AUTHORIZATION_HEADER; -import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.CONTENT_MD5; -import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.CONTENT_TYPE; -import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.HOST_HEADER; -import static org.apache.hadoop.ozone.s3.signature.StringToSignProducer.X_AMAZ_DATE; -import static org.apache.hadoop.ozone.s3.signature.StringToSignProducer.X_AMZ_CONTENT_SHA256; import static org.junit.Assert.fail; import org.junit.Assert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.mockito.Mockito; /** * Test class for @{@link OzoneClientProducer}. */ -@RunWith(Parameterized.class) public class TestOzoneClientProducer { private OzoneClientProducer producer; - private MultivaluedMap headerMap; - private MultivaluedMap queryMap; - private MultivaluedMap pathParamsMap; - private String authHeader; - private String contentMd5; - private String host; - private String amzContentSha256; - private String date; - private String contentType; - private ContainerRequestContext context; - private UriInfo uriInfo; - public TestOzoneClientProducer( - String authHeader, String contentMd5, - String host, String amzContentSha256, String date, String contentType - ) + public TestOzoneClientProducer() throws Exception { - this.authHeader = authHeader; - this.contentMd5 = contentMd5; - this.host = host; - this.amzContentSha256 = amzContentSha256; - this.date = date; - this.contentType = contentType; producer = new OzoneClientProducer(); - headerMap = new MultivaluedHashMap<>(); - queryMap = new MultivaluedHashMap<>(); - pathParamsMap = new MultivaluedHashMap<>(); - uriInfo = Mockito.mock(UriInfo.class); - context = Mockito.mock(ContainerRequestContext.class); OzoneConfiguration config = new OzoneConfiguration(); config.setBoolean(OzoneConfigKeys.OZONE_SECURITY_ENABLED_KEY, true); config.set(OMConfigKeys.OZONE_OM_ADDRESS_KEY, ""); - setupContext(); producer.setOzoneConfiguration(config); } - @Parameterized.Parameters - public static Collection data() { - return Arrays.asList(new Object[][] { - { - "AWS4-HMAC-SHA256 Credential=testuser1/20190221/us-west-1/s3" + - "/aws4_request, SignedHeaders=content-md5;host;" + - "x-amz-content-sha256;x-amz-date, " + - "Signature" + - "=56ec73ba1974f8feda8365c3caef89c5d4a688d5f9baccf47" + - "65f46a14cd745ad", - "Zi68x2nPDDXv5qfDC+ZWTg==", - "s3g:9878", - "e2bd43f11c97cde3465e0e8d1aad77af7ec7aa2ed8e213cd0e24" + - "1e28375860c6", - "20190221T002037Z", - "" - }, - { - "AWS4-HMAC-SHA256 " + - "Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request," + - " SignedHeaders=content-type;host;x-amz-date, " + - "Signature=" + - "5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400" + - "e06b5924a6f2b5d7", - "", - "iam.amazonaws.com", - "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "20150830T123600Z", - "application/x-www-form-urlencoded; charset=utf-8" - }, - { - null, null, null, null, null, null - }, - { - "", null, null, null, null, null - }, - // AWS V2 signature - { - "AWS AKIDEXAMPLE:St7bHPOdkmsX/GITGe98rOQiUCg=", - "", - "s3g:9878", - "", - "Wed, 22 Mar 2023 17:00:06 +0000", - "application/octet-stream" - } - }); - } - @Test public void testGetClientFailure() { try { @@ -152,40 +63,6 @@ public void testGetClientFailure() { } } - @Test - public void testGetSignature() { - try { - System.err.println("Testing: " + authHeader); - OzoneConfiguration configuration = new OzoneConfiguration(); - configuration.set(OMConfigKeys.OZONE_OM_SERVICE_IDS_KEY, "ozone1"); - configuration.set(OMConfigKeys.OZONE_OM_ADDRESS_KEY, "ozone1addr:9399"); - producer.setOzoneConfiguration(configuration); - producer.getSignature(); - if ("".equals(authHeader)) { - fail("Empty AuthHeader must fail"); - } - } catch (WebApplicationException ex) { - if (authHeader == null || authHeader.isEmpty() || - authHeader.startsWith("AWS ")) { - // Empty auth header and unsupported AWS signature - // should fail with Invalid Request. - Assert.assertEquals(HTTP_FORBIDDEN, ex.getResponse().getStatus()); - Assert.assertEquals(S3_AUTHINFO_CREATION_ERROR.getErrorMessage(), - ex.getMessage()); - } else { - // Other requests have stale timestamp and - // should fail with Malformed Authorization Header. - Assert.assertEquals(HTTP_BAD_REQUEST, ex.getResponse().getStatus()); - Assert.assertEquals(MALFORMED_HEADER.getErrorMessage(), - ex.getMessage()); - - } - - } catch (Exception ex) { - fail("Unexpected exception: " + ex); - } - } - @Test public void testGetClientFailureWithMultipleServiceIds() { try { @@ -219,30 +96,4 @@ public void testGetClientFailureWithMultipleServiceIdsAndInternalServiceId() { } } - private void setupContext() throws Exception { - headerMap.putSingle(AUTHORIZATION_HEADER, authHeader); - headerMap.putSingle(CONTENT_MD5, contentMd5); - headerMap.putSingle(HOST_HEADER, host); - headerMap.putSingle(X_AMZ_CONTENT_SHA256, amzContentSha256); - headerMap.putSingle(X_AMAZ_DATE, date); - headerMap.putSingle(CONTENT_TYPE, contentType); - - Mockito.when(uriInfo.getQueryParameters()).thenReturn(queryMap); - Mockito.when(uriInfo.getRequestUri()).thenReturn(new URI("")); - - Mockito.when(context.getUriInfo()).thenReturn(uriInfo); - Mockito.when(context.getHeaders()).thenReturn(headerMap); - Mockito.when(context.getHeaderString(AUTHORIZATION_HEADER)) - .thenReturn(authHeader); - Mockito.when(context.getUriInfo().getQueryParameters()) - .thenReturn(queryMap); - Mockito.when(context.getUriInfo().getPathParameters()) - .thenReturn(pathParamsMap); - - AWSSignatureProcessor awsSignatureProcessor = new AWSSignatureProcessor(); - awsSignatureProcessor.setContext(context); - - producer.setSignatureParser(awsSignatureProcessor); - } - } diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java index 2f81c1c6a148..d4f4e794a025 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java @@ -20,7 +20,6 @@ import org.apache.hadoop.fs.InvalidRequestException; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.ozone.test.GenericTestUtils; -import org.apache.hadoop.ozone.s3.util.S3Consts; import org.glassfish.jersey.internal.PropertiesDelegate; import org.glassfish.jersey.server.ContainerRequest; import org.junit.Assert; @@ -106,9 +105,7 @@ public void testVirtualHostStyle() throws Exception { ContainerRequest containerRequest = createContainerRequest("mybucket" + ".localhost:9878", "/myfile", null, true); virtualHostStyleFilter.filter(containerRequest); - URI expected = new URI("http://" + s3HttpAddr + - "/mybucket/myfile?" + S3Consts.ENDPOINT_STYLE_PARAM + "=" + - S3Consts.ENDPOINT_STYLE_VIRTUAL); + URI expected = new URI("http://" + s3HttpAddr + "/mybucket/myfile"); Assert.assertEquals(expected, containerRequest.getRequestUri()); } @@ -138,26 +135,34 @@ public void testVirtualHostStyleWithCreateBucketRequest() throws Exception { ContainerRequest containerRequest = createContainerRequest("mybucket" + ".localhost:9878", null, null, true); virtualHostStyleFilter.filter(containerRequest); - URI expected = new URI("http://" + s3HttpAddr + "/mybucket?" + - S3Consts.ENDPOINT_STYLE_PARAM + "=" + - S3Consts.ENDPOINT_STYLE_VIRTUAL); + URI expected = new URI("http://" + s3HttpAddr + "/mybucket"); Assert.assertEquals(expected, containerRequest.getRequestUri()); } + @Test + public void testVirtualHostStyleWithCreateKeyRequest() throws Exception { + VirtualHostStyleFilter virtualHostStyleFilter = + new VirtualHostStyleFilter(); + virtualHostStyleFilter.setConfiguration(conf); + + ContainerRequest containerRequest = createContainerRequest("mybucket" + + ".localhost:9878", "/key1", null, true); + virtualHostStyleFilter.filter(containerRequest); + URI expected = new URI("http://" + s3HttpAddr + "/mybucket/key1"); + Assert.assertEquals(expected, containerRequest.getRequestUri()); + } + @Test public void testVirtualHostStyleWithQueryParams() throws Exception { VirtualHostStyleFilter virtualHostStyleFilter = new VirtualHostStyleFilter(); virtualHostStyleFilter.setConfiguration(conf); - + URI expected = new URI("http://" + s3HttpAddr + "/mybucket?prefix=bh"); ContainerRequest containerRequest = createContainerRequest("mybucket" + ".localhost:9878", null, "?prefix=bh", true); virtualHostStyleFilter.filter(containerRequest); - URI expected = new URI("http://" + s3HttpAddr + "/mybucket?prefix=bh&" + - S3Consts.ENDPOINT_STYLE_PARAM + "=" + - S3Consts.ENDPOINT_STYLE_VIRTUAL); assertTrue(expected.toString().contains(containerRequest.getRequestUri() .toString())); @@ -165,9 +170,7 @@ public void testVirtualHostStyleWithQueryParams() throws Exception { ".localhost:9878", null, "?prefix=bh&type=dir", true); virtualHostStyleFilter.filter(containerRequest); expected = new URI("http://" + s3HttpAddr + - "/mybucket?prefix=bh&type=dir&" + - S3Consts.ENDPOINT_STYLE_PARAM + "=" + - S3Consts.ENDPOINT_STYLE_VIRTUAL); + "/mybucket?prefix=bh&type=dir"); assertTrue(expected.toString().contains(containerRequest.getRequestUri() .toString())); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAuthorizationV4QueryParser.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAuthorizationV4QueryParser.java index 7221dfbb02ec..181e962e5ca6 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAuthorizationV4QueryParser.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAuthorizationV4QueryParser.java @@ -284,14 +284,14 @@ protected void validateDateAndExpires() { }; final SignatureInfo signatureInfo = parser.parseSignature(); + signatureInfo.setUnfilteredURI("/test.txt"); LowerCaseKeyStringMap headers = new LowerCaseKeyStringMap(); headers.put("host", "localhost"); final String stringToSign = StringToSignProducer.createSignatureBase(signatureInfo, "https", "GET", - "/test.txt", headers, - queryParams); + headers, queryParams); MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(canonicalRequest.getBytes(StandardCharsets.UTF_8)); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java index c454d9c5125d..14aa64947665 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestStringToSignProducer.java @@ -44,8 +44,6 @@ import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR; import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.DATE_FORMATTER; -import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_PARAM; -import static org.apache.hadoop.ozone.s3.util.S3Consts.ENDPOINT_STYLE_VIRTUAL; import static org.junit.jupiter.params.provider.Arguments.arguments; /** @@ -97,6 +95,7 @@ public void validateDateRange(Credential credentialObj) { //NOOP } }.parseSignature(); + signatureInfo.setUnfilteredURI("/buckets"); headers.fixContentType(); @@ -105,7 +104,6 @@ public void validateDateRange(Credential credentialObj) { signatureInfo, "http", "GET", - "/buckets", headers, queryParameters); @@ -205,6 +203,7 @@ public void testValidateRequestHeaders( SignatureInfo signatureInfo = new AuthorizationV4HeaderParser( headerMap.getFirst("Authorization"), headerMap.getFirst("X-Amz-Date")).parseSignature(); + signatureInfo.setUnfilteredURI("/"); try { StringToSignProducer.createSignatureBase(signatureInfo, context); } catch (OS3Exception e) { @@ -262,6 +261,7 @@ public void testValidateCanonicalHeaders( SignatureInfo signatureInfo = new AuthorizationV4HeaderParser( headerMap.getFirst("Authorization"), headerMap.getFirst("x-amz-date")).parseSignature(); + signatureInfo.setUnfilteredURI("/"); try { StringToSignProducer.createSignatureBase(signatureInfo, context); @@ -271,60 +271,4 @@ public void testValidateCanonicalHeaders( Assert.assertEquals(expectedResult, actualResult); } - - @Test - public void testVirtualStyleAddressURI() throws Exception { - String canonicalRequest = "GET\n" - + "/\n" - + "\n" - + "host:bucket1.s3g.internal:9878\nx-amz-content-sha256:Content-SHA\n" - + "x-amz-date:" + DATETIME + "\n" - + "\n" - + "host;x-amz-content-sha256;x-amz-date\n" - + "Content-SHA"; - - String authHeader = - "AWS4-HMAC-SHA256 Credential=AKIAJWFJK62WUTKNFJJA/20181009/us-east-1" - + "/s3/aws4_request, " - + "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " - + "Signature" - + - "=db81b057718d7c1b3b8dffa29933099551c51d787b3b13b9e0f9ebed45982bf2"; - - MultivaluedMap headers = new MultivaluedHashMap<>(); - headers.putSingle("Authorization", authHeader); - headers.putSingle("Content-Length", "123"); - headers.putSingle("Host", "bucket1.s3g.internal:9878"); - headers.putSingle("X-AMZ-Content-Sha256", "Content-SHA"); - headers.putSingle("X-AMZ-Date", DATETIME); - - final SignatureInfo signatureInfo = - new AuthorizationV4HeaderParser(authHeader, DATETIME) { - @Override - public void validateDateRange(Credential credentialObj) { - //NOOP - } - }.parseSignature(); - - ContainerRequestContext context = setupContext( - new URI("http://bucket1.s3g.internal:9878?" + - ENDPOINT_STYLE_PARAM + "=" + ENDPOINT_STYLE_VIRTUAL), - "GET", - headers, - new MultivaluedHashMap<>()); - - final String signatureBase = - StringToSignProducer.createSignatureBase(signatureInfo, context); - - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(canonicalRequest.getBytes(StandardCharsets.UTF_8)); - - Assert.assertEquals( - "String to sign is invalid", - "AWS4-HMAC-SHA256\n" - + DATETIME + "\n" - + "20181009/us-east-1/s3/aws4_request\n" - + Hex.encode(md.digest()).toLowerCase(), - signatureBase); - } } From 1a05023210b28ef5c1de3ce194c49bd8508bff47 Mon Sep 17 00:00:00 2001 From: saketa Date: Tue, 29 Aug 2023 16:09:00 -0700 Subject: [PATCH 3/7] HDDS-7035. Fixed checkstyle errors. --- .../ozone/s3/endpoint/EndpointBase.java | 2 +- .../ozone/s3/signature/SignatureInfo.java | 26 ++++--------------- .../ozone/s3/TestAuthorizationFilter.java | 9 +++---- .../ozone/s3/TestOzoneClientProducer.java | 9 ------- 4 files changed, 9 insertions(+), 37 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java index 083c530c5da7..97027abd7c81 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java @@ -76,7 +76,7 @@ public abstract class EndpointBase implements Auditor { @Inject private OzoneClient client; @Inject - SignatureInfo signatureInfo; + private SignatureInfo signatureInfo; private S3Auth s3Auth; @Context diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java index 90f1922ae974..d1db38a50aa5 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/SignatureInfo.java @@ -51,12 +51,12 @@ public class SignatureInfo { private boolean signPayload = true; - private String unfilteredURI; + private String unfilteredURI = null; - private String stringToSign; + private String stringToSign = null; - public SignatureInfo(){} + public SignatureInfo() { } @SuppressWarnings("checkstyle:ParameterNumber") public SignatureInfo( @@ -69,26 +69,9 @@ public SignatureInfo( String credentialScope, String algorithm, boolean signPayload - ) { - this(version, date, dateTime, awsAccessId, signature, signedHeaders, - credentialScope, algorithm, signPayload, null, null); - } - - public SignatureInfo( - Version version, - String date, - String dateTime, - String awsAccessId, - String signature, - String signedHeaders, - String credentialScope, - String algorithm, - boolean signPayload, - String uri, - String stringToSign ) { initialize(version, date, dateTime, awsAccessId, signature, signedHeaders, - credentialScope, algorithm, signPayload, uri, stringToSign); + credentialScope, algorithm, signPayload, null, null); } public void initialize( @@ -102,6 +85,7 @@ public void initialize( signatureInfo.getStringToSign()); } + @SuppressWarnings({"checkstyle:ParameterNumber", "checkstyle:HiddenField"}) public void initialize( Version version, String date, diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java index 8f1b8cfaf4cc..3e0b589ca991 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java @@ -25,14 +25,10 @@ import static org.junit.jupiter.params.provider.Arguments.arguments; import org.apache.hadoop.ozone.s3.signature.SignatureInfo; -import org.apache.hadoop.ozone.s3.signature.SignatureProcessor; import org.junit.Assert; -import org.junit.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.mockito.Mockito; /** @@ -112,10 +108,11 @@ public void testAuthFilterFailures( headerMap.putSingle(CONTENT_TYPE, contentType); UriInfo uriInfo = Mockito.mock(UriInfo.class); - ContainerRequestContext context = Mockito.mock(ContainerRequestContext.class); + ContainerRequestContext context = Mockito.mock( + ContainerRequestContext.class); Mockito.when(uriInfo.getQueryParameters()).thenReturn(queryMap); Mockito.when(uriInfo.getRequestUri()).thenReturn( - new URI("http://" + host+path)); + new URI("http://" + host + path)); Mockito.when(context.getUriInfo()).thenReturn(uriInfo); Mockito.when(context.getHeaders()).thenReturn(headerMap); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestOzoneClientProducer.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestOzoneClientProducer.java index 13efecbde406..ed7a8be944ac 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestOzoneClientProducer.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestOzoneClientProducer.java @@ -17,15 +17,7 @@ */ package org.apache.hadoop.ozone.s3; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.core.MultivaluedHashMap; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.UriInfo; import java.io.IOException; -import java.net.URI; -import java.util.Arrays; -import java.util.Collection; import org.apache.hadoop.hdds.conf.OzoneConfiguration; import org.apache.hadoop.ozone.OzoneConfigKeys; @@ -35,7 +27,6 @@ import org.junit.Assert; import org.junit.Test; -import org.mockito.Mockito; /** * Test class for @{@link OzoneClientProducer}. From 8a11cbca8b313ec1b68632f39eaa6337bae34050 Mon Sep 17 00:00:00 2001 From: saketa Date: Tue, 29 Aug 2023 16:49:28 -0700 Subject: [PATCH 4/7] HDDS-7035. Added licenses to new classes. --- .../hadoop/ozone/s3/AuthorizationFilter.java | 17 +++++++++++++++++ .../ozone/s3/TestAuthorizationFilter.java | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java index eb41aa663c89..515519b5194c 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java @@ -1,3 +1,20 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.hadoop.ozone.s3; import javax.annotation.Priority; diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java index 3e0b589ca991..26fbc0f09d94 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.hadoop.ozone.s3; import javax.ws.rs.WebApplicationException; From 5cae64b2f0c656ebf3791207eabf06dba498a540 Mon Sep 17 00:00:00 2001 From: saketa Date: Tue, 5 Sep 2023 19:00:02 -0700 Subject: [PATCH 5/7] HDDS-7035. Fixed acceptance test errors. --- hadoop-ozone/dist/src/main/compose/common/ec-test.sh | 3 ++- hadoop-ozone/dist/src/main/compose/ozone-ha/test.sh | 5 +++-- .../dist/src/main/compose/ozone/disabled-test-s3-haproxy.sh | 3 ++- hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test.sh | 2 +- hadoop-ozone/dist/src/main/compose/ozonesecure/test-vault.sh | 3 ++- hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot | 3 +-- .../java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java | 5 +++++ .../org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java | 5 +++++ .../main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java | 2 +- 9 files changed, 22 insertions(+), 9 deletions(-) diff --git a/hadoop-ozone/dist/src/main/compose/common/ec-test.sh b/hadoop-ozone/dist/src/main/compose/common/ec-test.sh index cfc46f058ad0..65a659563e59 100755 --- a/hadoop-ozone/dist/src/main/compose/common/ec-test.sh +++ b/hadoop-ozone/dist/src/main/compose/common/ec-test.sh @@ -17,7 +17,8 @@ start_docker_env 5 -execute_robot_test scm -v BUCKET:erasure s3 +## Exclude virtual-host tests. This is tested separately as it requires additional config. +execute_robot_test scm -v BUCKET:erasure --exclude virtual-host s3 prefix=${RANDOM} execute_robot_test scm -v PREFIX:${prefix} ec/basic.robot diff --git a/hadoop-ozone/dist/src/main/compose/ozone-ha/test.sh b/hadoop-ozone/dist/src/main/compose/ozone-ha/test.sh index 2d73133fdd94..08c852269610 100755 --- a/hadoop-ozone/dist/src/main/compose/ozone-ha/test.sh +++ b/hadoop-ozone/dist/src/main/compose/ozone-ha/test.sh @@ -35,11 +35,12 @@ execute_robot_test ${SCM} basic/links.robot execute_robot_test ${SCM} -v SCHEME:ofs -v BUCKET_TYPE:link -N ozonefs-ofs-link ozonefs/ozonefs.robot -exclude="" +## Exclude virtual-host tests. This is tested separately as it requires additional config. +exclude="--exclude virtual-host" for bucket in generated; do execute_robot_test ${SCM} -v BUCKET:${bucket} -N s3-${bucket} ${exclude} s3 # some tests are independent of the bucket type, only need to be run once - exclude="--exclude no-bucket-type" + exclude="--exclude virtual-host --exclude no-bucket-type" done execute_robot_test ${SCM} freon diff --git a/hadoop-ozone/dist/src/main/compose/ozone/disabled-test-s3-haproxy.sh b/hadoop-ozone/dist/src/main/compose/ozone/disabled-test-s3-haproxy.sh index 9612b92da476..6cf3901b9d28 100755 --- a/hadoop-ozone/dist/src/main/compose/ozone/disabled-test-s3-haproxy.sh +++ b/hadoop-ozone/dist/src/main/compose/ozone/disabled-test-s3-haproxy.sh @@ -26,4 +26,5 @@ source "$COMPOSE_DIR/../testlib.sh" start_docker_env -execute_robot_test scm s3 +## Exclude virtual-host tests. This is tested separately as it requires additional config. +execute_robot_test scm --exclude virtual-host s3 diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test.sh b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test.sh index fc57e7a782ea..f6ab69746c38 100755 --- a/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test.sh +++ b/hadoop-ozone/dist/src/main/compose/ozonesecure-ha/test.sh @@ -41,7 +41,7 @@ execute_robot_test s3g -v SCHEME:o3fs -v BUCKET_TYPE:link -N ozonefs-o3fs-link o execute_robot_test s3g basic/links.robot -## Exclude virtual-host.robot +## Exclude virtual-host tests. This is tested separately as it requires additional config. exclude="--exclude virtual-host" for bucket in encrypted link; do execute_robot_test s3g -v BUCKET:${bucket} -N s3-${bucket} ${exclude} s3 diff --git a/hadoop-ozone/dist/src/main/compose/ozonesecure/test-vault.sh b/hadoop-ozone/dist/src/main/compose/ozonesecure/test-vault.sh index f0af40792d0e..f2bc0948feb4 100755 --- a/hadoop-ozone/dist/src/main/compose/ozonesecure/test-vault.sh +++ b/hadoop-ozone/dist/src/main/compose/ozonesecure/test-vault.sh @@ -28,4 +28,5 @@ export COMPOSE_FILE=docker-compose.yaml:vault.yaml start_docker_env -execute_robot_test scm s3 +## Exclude virtual-host tests. This is tested separately as it requires additional config. +execute_robot_test scm --exclude virtual-host s3 diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot b/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot index f0f617eb1b61..98691876e12c 100644 --- a/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot +++ b/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot @@ -86,8 +86,7 @@ Setup secure v4 headers Execute aws configure set aws_access_key_id ${accessKey} Execute aws configure set aws_secret_access_key ${secret} Execute aws configure set region us-west-1 - Return From Keyword If '${OZONE_S3_ADDRESS_STYLE}' != 'virtual' - Execute aws configure set default.s3.addressing_style virtual + Execute aws configure set default.s3.addressing_style ${OZONE_S3_ADDRESS_STYLE} Setup dummy credentials for S3 diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java index 515519b5194c..16665fccd4b9 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java @@ -66,6 +66,11 @@ public class AuthorizationFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext context) throws IOException { + // Skip authentication if the uri is hitting S3Secret generation or + // revocation endpoint. + if (context.getUriInfo().getPath().startsWith("secret")) { + return; + } try { signatureInfo.initialize(signatureProcessor.parseSignature()); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java index 81235fb9d568..bcce5ddf164a 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java @@ -63,6 +63,11 @@ public class VirtualHostStyleFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { + // Skip this filter if the uri is hitting S3Secret generation or + // revocation endpoint. + if (requestContext.getUriInfo().getPath().startsWith("secret")) { + return; + } domains = conf.getTrimmedStrings(OZONE_S3G_DOMAIN_NAME); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java index e151c7b82c5f..df3d01936b18 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java @@ -63,7 +63,7 @@ private S3Consts() { ".com/doc/2006-03-01/"; public static final String CUSTOM_METADATA_HEADER_PREFIX = "x-amz-meta-"; - + public static final String DECODED_CONTENT_LENGTH_HEADER = "x-amz-decoded-content-length"; From 96cf32061ee69a295d356ca45efc091d0298242e Mon Sep 17 00:00:00 2001 From: saketa Date: Wed, 6 Sep 2023 16:35:17 -0700 Subject: [PATCH 6/7] HDDS-7035. Added more Unit tests. --- .../hadoop/ozone/s3/AuthorizationFilter.java | 2 +- .../ozone/s3/VirtualHostStyleFilter.java | 3 +- .../ozone/s3/TestAuthorizationFilter.java | 228 +++++++++++++++--- .../ozone/s3/TestVirtualHostStyleFilter.java | 23 ++ 4 files changed, 217 insertions(+), 39 deletions(-) diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java index 16665fccd4b9..d49ff17f3bfe 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/AuthorizationFilter.java @@ -68,7 +68,7 @@ public void filter(ContainerRequestContext context) throws IOException { // Skip authentication if the uri is hitting S3Secret generation or // revocation endpoint. - if (context.getUriInfo().getPath().startsWith("secret")) { + if (context.getUriInfo().getRequestUri().getPath().startsWith("/secret")) { return; } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java index bcce5ddf164a..32c1eb9eb237 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java @@ -65,7 +65,8 @@ public void filter(ContainerRequestContext requestContext) throws IOException { // Skip this filter if the uri is hitting S3Secret generation or // revocation endpoint. - if (requestContext.getUriInfo().getPath().startsWith("secret")) { + if (requestContext.getUriInfo().getRequestUri().getPath() + .startsWith("/secret")) { return; } diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java index 26fbc0f09d94..3bf3dcdd7bf6 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestAuthorizationFilter.java @@ -23,13 +23,22 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriInfo; import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.stream.Stream; import org.apache.hadoop.ozone.s3.signature.AWSSignatureProcessor; +import org.apache.hadoop.ozone.s3.signature.SignatureInfo; +import org.apache.hadoop.ozone.s3.signature.StringToSignProducer; +import org.apache.kerby.util.Hex; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static org.apache.hadoop.ozone.s3.signature.AWSSignatureProcessor.DATE_FORMATTER; import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_HEADER; import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR; import static org.apache.hadoop.ozone.s3.signature.SignatureParser.AUTHORIZATION_HEADER; @@ -41,7 +50,6 @@ import static org.junit.Assert.fail; import static org.junit.jupiter.params.provider.Arguments.arguments; -import org.apache.hadoop.ozone.s3.signature.SignatureInfo; import org.junit.Assert; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -59,9 +67,15 @@ public class TestAuthorizationFilter { private MultivaluedMap queryMap; private MultivaluedMap pathParamsMap; + private static final String DATETIME = StringToSignProducer.TIME_FORMATTER. + format(LocalDateTime.now()); + + private static final String CURDATE = DATE_FORMATTER.format(LocalDate.now()); + private static StreamtestAuthFilterFailuresInput() { return Stream.of( arguments( + "GET", "AWS4-HMAC-SHA256 Credential=testuser1/20190221/us-west-1/s3" + "/aws4_request, SignedHeaders=content-md5;host;" + "x-amz-content-sha256;x-amz-date, " + @@ -74,9 +88,11 @@ public class TestAuthorizationFilter { "1e28375860c6", "20190221T002037Z", "", - "/" + "/", + MALFORMED_HEADER.getErrorMessage() ), arguments( + "GET", "AWS4-HMAC-SHA256 " + "Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request," + " SignedHeaders=content-type;host;x-amz-date, " + @@ -88,57 +104,39 @@ public class TestAuthorizationFilter { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "20150830T123600Z", "application/x-www-form-urlencoded; charset=utf-8", - "" + "", + MALFORMED_HEADER.getErrorMessage() ), - arguments(null, null, null, null, null, null, null), - arguments("", null, null, null, null, null, null), + arguments(null, null, null, null, null, null, null, null, + S3_AUTHINFO_CREATION_ERROR.getErrorMessage()), + arguments(null, "", null, null, null, null, null, null, + S3_AUTHINFO_CREATION_ERROR.getErrorMessage()), // AWS V2 signature arguments( + "GET", "AWS AKIDEXAMPLE:St7bHPOdkmsX/GITGe98rOQiUCg=", "", "s3g:9878", "", "Wed, 22 Mar 2023 17:00:06 +0000", "application/octet-stream", - "/" + "/", + S3_AUTHINFO_CREATION_ERROR.getErrorMessage() ) ); } + @SuppressWarnings("checkstyle:ParameterNumber") @ParameterizedTest @MethodSource("testAuthFilterFailuresInput") public void testAuthFilterFailures( - String authHeader, String contentMd5, + String method, String authHeader, String contentMd5, String host, String amzContentSha256, String date, String contentType, - String path + String path, String expectedErrorMsg ) { - headerMap = new MultivaluedHashMap<>(); - queryMap = new MultivaluedHashMap<>(); - pathParamsMap = new MultivaluedHashMap<>(); try { - System.err.println("Testing: " + authHeader); - headerMap.putSingle(AUTHORIZATION_HEADER, authHeader); - headerMap.putSingle(CONTENT_MD5, contentMd5); - headerMap.putSingle(HOST_HEADER, host); - headerMap.putSingle(X_AMZ_CONTENT_SHA256, amzContentSha256); - headerMap.putSingle(X_AMAZ_DATE, date); - headerMap.putSingle(CONTENT_TYPE, contentType); - - UriInfo uriInfo = Mockito.mock(UriInfo.class); - ContainerRequestContext context = Mockito.mock( - ContainerRequestContext.class); - Mockito.when(uriInfo.getQueryParameters()).thenReturn(queryMap); - Mockito.when(uriInfo.getRequestUri()).thenReturn( - new URI("http://" + host + path)); - - Mockito.when(context.getUriInfo()).thenReturn(uriInfo); - Mockito.when(context.getHeaders()).thenReturn(headerMap); - Mockito.when(context.getHeaderString(AUTHORIZATION_HEADER)) - .thenReturn(authHeader); - Mockito.when(context.getUriInfo().getQueryParameters()) - .thenReturn(queryMap); - Mockito.when(context.getUriInfo().getPathParameters()) - .thenReturn(pathParamsMap); + ContainerRequestContext context = setupContext(method, authHeader, + contentMd5, host, amzContentSha256, date, contentType, path); AWSSignatureProcessor awsSignatureProcessor = new AWSSignatureProcessor(); awsSignatureProcessor.setContext(context); @@ -158,13 +156,13 @@ public void testAuthFilterFailures( // Empty auth header and unsupported AWS signature // should fail with Invalid Request. Assert.assertEquals(HTTP_FORBIDDEN, ex.getResponse().getStatus()); - Assert.assertEquals(S3_AUTHINFO_CREATION_ERROR.getErrorMessage(), + Assert.assertEquals(expectedErrorMsg, ex.getMessage()); } else { // Other requests have stale timestamp and // should fail with Malformed Authorization Header. Assert.assertEquals(HTTP_BAD_REQUEST, ex.getResponse().getStatus()); - Assert.assertEquals(MALFORMED_HEADER.getErrorMessage(), + Assert.assertEquals(expectedErrorMsg, ex.getMessage()); } @@ -174,6 +172,162 @@ public void testAuthFilterFailures( } } - //testStrToSign generation + private static StreamtestAuthFilterInput() { + return Stream.of( + // Path style URI + arguments( + "GET", + "AWS4-HMAC-SHA256 Credential=testuser1/" + CURDATE + + "/us-east-1/s3/aws4_request, " + + "SignedHeaders=host;x-amz-content-sha256;" + + "x-amz-date, " + + "Signature" + + "=56ec73ba1974f8feda8365c3caef89c5d4a688d5f9baccf47" + + "65f46a14cd745ad", + "Content-SHA", + "s3g:9878", + "Content-SHA", + DATETIME, + "", + "/bucket1/key1" + ), + // Virtual style URI + arguments( + "GET", + "AWS4-HMAC-SHA256 Credential=testuser1/" + CURDATE + + "/us-east-1/s3/aws4_request, " + + "SignedHeaders=host;x-amz-content-sha256;" + + "x-amz-date, " + + "Signature" + + "=56ec73ba1974f8feda8365c3caef89c5d4a688d5f9baccf47" + + "65f46a14cd745ad", + "Content-SHA", + "bucket1.s3g.internal:9878", + "Content-SHA", + DATETIME, + "", + "/key1" + ), + // S3 secret generation endpoint + arguments( + "POST", + null, + null, + "s3g:9878", + null, + null, + "", + "/secret/generate" + ), + // S3 secret generation endpoint + arguments( + "POST", + null, + null, + "s3g:9878", + null, + null, + "", + "/secret/revoke" + ) + ); + } + + @SuppressWarnings("checkstyle:ParameterNumber") + @ParameterizedTest + @MethodSource("testAuthFilterInput") + public void testAuthFilter( + String method, String authHeader, String contentMd5, + String host, String amzContentSha256, String date, String contentType, + String path + ) { + try { + ContainerRequestContext context = setupContext(method, authHeader, + contentMd5, host, amzContentSha256, date, contentType, path); + + AWSSignatureProcessor awsSignatureProcessor = new AWSSignatureProcessor(); + awsSignatureProcessor.setContext(context); + + SignatureInfo signatureInfo = new SignatureInfo(); + + authorizationFilter.setSignatureParser(awsSignatureProcessor); + authorizationFilter.setSignatureInfo(signatureInfo); + + authorizationFilter.filter(context); + + if (path.startsWith("/secret")) { + Assert.assertNull( + authorizationFilter.getSignatureInfo().getUnfilteredURI()); + + Assert.assertNull( + authorizationFilter.getSignatureInfo().getStringToSign()); + } else { + String canonicalRequest = method + "\n" + + path + "\n" + + "\n" + + "host:" + host + "\nx-amz-content-sha256:" + amzContentSha256 + + "\n" + + "x-amz-date:" + DATETIME + "\n" + + "\n" + + "host;x-amz-content-sha256;x-amz-date\n" + + amzContentSha256; + + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(canonicalRequest.getBytes(StandardCharsets.UTF_8)); + + String expectedStrToSign = "AWS4-HMAC-SHA256\n" + + DATETIME + "\n" + + CURDATE + "/us-east-1/s3/aws4_request\n" + + Hex.encode(md.digest()).toLowerCase(); + + Assert.assertEquals("Unfiltered URI is not preserved", + path, + authorizationFilter.getSignatureInfo().getUnfilteredURI()); + + Assert.assertEquals("String to sign is invalid", + expectedStrToSign, + authorizationFilter.getSignatureInfo().getStringToSign()); + } + } catch (Exception ex) { + fail("Unexpected exception: " + ex); + } + } + + @SuppressWarnings("checkstyle:ParameterNumber") + private ContainerRequestContext setupContext( + String method, String authHeader, String contentMd5, + String host, String amzContentSha256, String date, String contentType, + String path) throws URISyntaxException { + headerMap = new MultivaluedHashMap<>(); + queryMap = new MultivaluedHashMap<>(); + pathParamsMap = new MultivaluedHashMap<>(); + + System.err.println("Testing: " + authHeader); + headerMap.putSingle(AUTHORIZATION_HEADER, authHeader); + headerMap.putSingle(CONTENT_MD5, contentMd5); + headerMap.putSingle(HOST_HEADER, host); + headerMap.putSingle(X_AMZ_CONTENT_SHA256, amzContentSha256); + headerMap.putSingle(X_AMAZ_DATE, date); + headerMap.putSingle(CONTENT_TYPE, contentType); + + UriInfo uriInfo = Mockito.mock(UriInfo.class); + ContainerRequestContext context = Mockito.mock( + ContainerRequestContext.class); + Mockito.when(uriInfo.getQueryParameters()).thenReturn(queryMap); + Mockito.when(uriInfo.getRequestUri()).thenReturn( + new URI("http://" + host + path)); + + Mockito.when(context.getMethod()).thenReturn(method); + Mockito.when(context.getUriInfo()).thenReturn(uriInfo); + Mockito.when(context.getHeaders()).thenReturn(headerMap); + Mockito.when(context.getHeaderString(AUTHORIZATION_HEADER)) + .thenReturn(authHeader); + Mockito.when(context.getUriInfo().getQueryParameters()) + .thenReturn(queryMap); + Mockito.when(context.getUriInfo().getPathParameters()) + .thenReturn(pathParamsMap); + + return context; + } } diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java index d4f4e794a025..81b8e0c5037a 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java @@ -125,6 +125,29 @@ public void testPathStyle() throws Exception { } + @Test + public void testS3SecretEndpoint() throws Exception { + + VirtualHostStyleFilter virtualHostStyleFilter = + new VirtualHostStyleFilter(); + virtualHostStyleFilter.setConfiguration(conf); + + ContainerRequest containerRequest = createContainerRequest("mybucket" + + ".localhost:9878", "/secret/generate", + null, true); + virtualHostStyleFilter.filter(containerRequest); + URI expected = new URI("http://" + s3HttpAddr + "/secret/generate"); + Assert.assertEquals(expected, containerRequest.getRequestUri()); + + containerRequest = createContainerRequest("mybucket" + + ".localhost:9878", "/secret/revoke", + null, true); + virtualHostStyleFilter.filter(containerRequest); + expected = new URI("http://" + s3HttpAddr + "/secret/revoke"); + Assert.assertEquals(expected, containerRequest.getRequestUri()); + + } + @Test public void testVirtualHostStyleWithCreateBucketRequest() throws Exception { From 7b87a33a2a6e6f0b1083c963e10f0e192bdf0188 Mon Sep 17 00:00:00 2001 From: saketa Date: Thu, 22 Feb 2024 19:16:03 -0800 Subject: [PATCH 7/7] HDDS-10399. Fixed index error when shallow listing empty directories in LEGACY/OBS buckets. --- .../hadoop/ozone/client/OzoneBucket.java | 2 +- .../apache/hadoop/ozone/om/TestListKeys.java | 114 +++++++++++++++--- 2 files changed, 95 insertions(+), 21 deletions(-) diff --git a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java index ca885b3b6b06..a5ff8d1e3608 100644 --- a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java +++ b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneBucket.java @@ -1253,7 +1253,7 @@ List getNextShallowListOfKeys(String prevKey) proxy.listStatusLight(volumeName, name, delimiterKeyPrefix, false, startKey, listCacheSize, false); - if (addedKeyPrefix) { + if (addedKeyPrefix && statuses.size() > 0) { // previous round already include the startKey, so remove it statuses.remove(0); } else { diff --git a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestListKeys.java b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestListKeys.java index be972557f4a4..204c0ee66818 100644 --- a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestListKeys.java +++ b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestListKeys.java @@ -63,6 +63,8 @@ public class TestListKeys { private static OzoneConfiguration conf; private static OzoneBucket legacyOzoneBucket; + + private static OzoneBucket obsOzoneBucket; private static OzoneClient client; /** @@ -86,6 +88,10 @@ public static void init() throws Exception { legacyOzoneBucket = TestDataUtil .createVolumeAndBucket(client, BucketLayout.LEGACY); + // create a volume and a OBJECT_STORE bucket + obsOzoneBucket = TestDataUtil + .createVolumeAndBucket(client, BucketLayout.OBJECT_STORE); + initFSNameSpace(); } @@ -99,6 +105,7 @@ public static void teardownClass() { private static void initFSNameSpace() throws Exception { buildNameSpaceTree(legacyOzoneBucket); + buildNameSpaceTree(obsOzoneBucket); } /** @@ -108,9 +115,9 @@ private static void initFSNameSpace() throws Exception { * | * a1 * | - * ----------------------------------- - * | | | - * b1 b2 b3 + * -------------------------------------------------------- + * | | | | + * b1 b2 b3 b4 * ------- --------- ----------- * | | | | | | | | * c1 c2 d1 d2 d3 e1 e2 e3 @@ -125,25 +132,27 @@ private static void initFSNameSpace() throws Exception { private static void buildNameSpaceTree(OzoneBucket ozoneBucket) throws Exception { LinkedList keys = new LinkedList<>(); - keys.add("/a1/b1/c1111.tx"); - keys.add("/a1/b1/c1222.tx"); - keys.add("/a1/b1/c1333.tx"); - keys.add("/a1/b1/c1444.tx"); - keys.add("/a1/b1/c1555.tx"); - keys.add("/a1/b1/c1/c1.tx"); - keys.add("/a1/b1/c12/c2.tx"); - keys.add("/a1/b1/c12/c3.tx"); - - keys.add("/a1/b2/d1/d11.tx"); - keys.add("/a1/b2/d2/d21.tx"); - keys.add("/a1/b2/d2/d22.tx"); - keys.add("/a1/b2/d3/d31.tx"); - - keys.add("/a1/b3/e1/e11.tx"); - keys.add("/a1/b3/e2/e21.tx"); - keys.add("/a1/b3/e3/e31.tx"); + keys.add("a1/b1/c1111.tx"); + keys.add("a1/b1/c1222.tx"); + keys.add("a1/b1/c1333.tx"); + keys.add("a1/b1/c1444.tx"); + keys.add("a1/b1/c1555.tx"); + keys.add("a1/b1/c1/c1.tx"); + keys.add("a1/b1/c12/c2.tx"); + keys.add("a1/b1/c12/c3.tx"); + + keys.add("a1/b2/d1/d11.tx"); + keys.add("a1/b2/d2/d21.tx"); + keys.add("a1/b2/d2/d22.tx"); + keys.add("a1/b2/d3/d31.tx"); + + keys.add("a1/b3/e1/e11.tx"); + keys.add("a1/b3/e2/e21.tx"); + keys.add("a1/b3/e3/e31.tx"); createKeys(ozoneBucket, keys); + + ozoneBucket.createDirectory("a1/b4/"); } private static Stream shallowListDataWithTrailingSlash() { @@ -186,6 +195,58 @@ private static Stream shallowListDataWithTrailingSlash() { "a1/b1/c1333.tx", "a1/b1/c1444.tx", "a1/b1/c1555.tx" + ))), + + // Case-7: StartKey is empty, return key that is same as keyPrefix. + of("a1/b4/", "", newLinkedList(Arrays.asList( + "a1/b4/" + ))) + ); + } + + private static Stream shallowListObsDataWithTrailingSlash() { + return Stream.of( + + // Case-1: StartKey is less than prefixKey, return emptyList. + of("a1/b2/", "a1", newLinkedList(Collections.emptyList())), + + // Case-2: StartKey is empty, return all immediate node. + of("a1/b2/", "", newLinkedList(Arrays.asList( + "a1/b2/d1/", + "a1/b2/d2/", + "a1/b2/d3/" + ))), + + // Case-3: StartKey is same as prefixKey, return all immediate nodes. + of("a1/b2/", "a1/b2", newLinkedList(Arrays.asList( + "a1/b2/d1/", + "a1/b2/d2/", + "a1/b2/d3/" + ))), + + // Case-4: StartKey is greater than prefixKey + of("a1/b2/", "a1/b2/d2/d21.tx", newLinkedList(Arrays.asList( + "a1/b2/d2/", + "a1/b2/d3/" + ))), + + // Case-5: StartKey reaches last element, return emptyList + of("a1/b2/", "a1/b2/d3/d31.tx", newLinkedList( + Collections.emptyList() + )), + + // Case-6: Mix result + of("a1/b1/", "a1/b1/c12", newLinkedList(Arrays.asList( + "a1/b1/c12/", + "a1/b1/c1222.tx", + "a1/b1/c1333.tx", + "a1/b1/c1444.tx", + "a1/b1/c1555.tx" + ))), + + // Case-7: StartKey is empty, return key that is same as keyPrefix. + of("a1/b4/", "", newLinkedList(Arrays.asList( + "a1/b4/" ))) ); } @@ -252,6 +313,11 @@ private static Stream shallowListDataWithoutTrailingSlash() { of("a1/b1/c12", "", newLinkedList(Arrays.asList( "a1/b1/c12/", "a1/b1/c1222.tx" + ))), + + // Case-10: + of("a1/b4", "", newLinkedList(Arrays.asList( + "a1/b4/" ))) ); @@ -264,11 +330,19 @@ public void testShallowListKeysWithPrefixTrailingSlash(String keyPrefix, checkKeyShallowList(keyPrefix, startKey, expectedKeys, legacyOzoneBucket); } + @ParameterizedTest + @MethodSource("shallowListObsDataWithTrailingSlash") + public void testShallowListObsKeysWithPrefixTrailingSlash(String keyPrefix, + String startKey, List expectedKeys) throws Exception { + checkKeyShallowList(keyPrefix, startKey, expectedKeys, obsOzoneBucket); + } + @ParameterizedTest @MethodSource("shallowListDataWithoutTrailingSlash") public void testShallowListKeysWithoutPrefixTrailingSlash(String keyPrefix, String startKey, List expectedKeys) throws Exception { checkKeyShallowList(keyPrefix, startKey, expectedKeys, legacyOzoneBucket); + checkKeyShallowList(keyPrefix, startKey, expectedKeys, obsOzoneBucket); } private void checkKeyShallowList(String keyPrefix, String startKey,