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 f308363133de..ca1eb8f1fd02 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 @@ -57,6 +57,7 @@ public final class StringToSignProducer { public static final String X_AMAZ_DATE = "x-amz-date"; + private static final String X_AMZ_ACL = "x-amz-acl"; private static final Logger LOG = LoggerFactory.getLogger(StringToSignProducer.class); private static final Charset UTF_8 = StandardCharsets.UTF_8; @@ -370,8 +371,8 @@ private static void validateCanonicalHeaders( if (!(canonicalHeaders.contains(header + ":"))) { // According to AWS Signature V4 documentation using Authorization Header // https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html - // The x-amz-content-sha256 header is not required for CanonicalHeaders - if (X_AMZ_CONTENT_SHA256.equals(header)) { + // The x-amz-content-sha256 and x-amz-acl header is not required for CanonicalHeaders + if (X_AMZ_CONTENT_SHA256.equals(header) || X_AMZ_ACL.equals(header)) { continue; } LOG.error("The SignedHeaders list must include all " 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 cbce030ef69f..e8646833f930 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 @@ -269,4 +269,45 @@ public void testValidateCanonicalHeaders( assertEquals(expectedResult, actualResult); } + + @Test + public void testUnsignedPayloadBypassesXAmzHeaderCheck() throws Exception { + // We add an x-amz-acl header that is NOT in SignedHeaders + String authHeader = "AWS4-HMAC-SHA256 Credential=ozone/" + + DATE_FORMATTER.format(LocalDate.now()) + + "/us-east-1/s3/aws4_request, " + + "SignedHeaders=host, " + + "Signature=db81b057718d7c1b3b" + + "8dffa29933099551c51d787b3b13b9e0f9ebed45982bf2"; + + MultivaluedMap headerMap = new MultivaluedHashMap<>(); + headerMap.putSingle("Authorization", authHeader); + headerMap.putSingle("host", "0.0.0.0:9878"); + // Extra unsigned header + headerMap.putSingle("x-amz-acl", "public-read"); + + ContainerRequestContext context = setupContext( + new URI("https://0.0.0.0:9878/"), + "PUT", + headerMap, + new MultivaluedHashMap<>()); + + // Simulate query parameter authentication where signPayload is false + SignatureInfo signatureInfo = new SignatureInfo.Builder(SignatureInfo.Version.V4) + .setDate(LocalDate.now().format(DATE_FORMATTER)) + .setDateTime(DATETIME) + .setAwsAccessId("ozone") + .setSignature("dummy") + .setSignedHeaders("host") + .setCredentialScope("ozone/" + DATE_FORMATTER.format(LocalDate.now()) + "/us-east-1/s3/aws4_request") + .setAlgorithm(SignatureProcessor.AWS4_SIGNING_ALGORITHM) + // This makes unsignedPayload = true in createSignatureBase + .setSignPayload(false) + .build(); + signatureInfo.setUnfilteredURI("/"); + + // This should NOT throw an exception bypasses the strict + // x-amz-* header validation. + StringToSignProducer.createSignatureBase(signatureInfo, context); + } }