You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by sa...@apache.org on 2026/10/02 19:34:22 UTC
(ozone) branch master updated: S3 header auth expiry in 15min and correct date validation.
This is an automated email from the ASF dual-hosted git repository.
SaketaChalamchala pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 29b98532703 S3 header auth expiry in 15min and correct date validation.
29b98532703 is described below
commit 29b9853270342eea4d065b1f14c332ad384f83e5
Author: SaketaChalamchala <sa...@gmail.com>
AuthorDate: Fri Oct 2 12:22:59 2026 -0700
S3 header auth expiry in 15min and correct date validation.
---
.../hadoop/ozone/s3/exception/S3ErrorTable.java | 9 ++
.../ozone/s3/signature/AWSSignatureProcessor.java | 3 +-
.../s3/signature/AuthorizationV4HeaderParser.java | 43 +++++--
.../s3/signature/AuthorizationV4QueryParser.java | 7 +-
.../hadoop/ozone/s3/signature/Credential.java | 20 ++++
.../s3/signature/MalformedResourceException.java | 19 ++-
.../ozone/s3/signature/StringToSignProducer.java | 50 --------
.../hadoop/ozone/s3/TestAuthorizationFilter.java | 94 +++++++++------
.../signature/TestAuthorizationV4HeaderParser.java | 133 ++++++++++++---------
.../signature/TestAuthorizationV4QueryParser.java | 55 ++++++---
.../s3/signature/TestStringToSignProducer.java | 56 +++++----
11 files changed, 290 insertions(+), 199 deletions(-)
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
index ac52cbd851e..bb03196caab 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
@@ -66,6 +66,11 @@ public enum S3ErrorTable {
"AuthorizationHeaderMalformed", "The authorization header you provided " +
"is invalid.", HTTP_BAD_REQUEST),
+ MALFORMED_CREDENTIAL_DATE(
+ "AuthorizationHeaderMalformed", "The authorization header is malformed; "
+ + "Invalid credential date. Date is not the same as X-Amz-Date.",
+ HTTP_BAD_REQUEST),
+
NO_SUCH_KEY(
"NoSuchKey", "The specified key does not exist", HTTP_NOT_FOUND),
@@ -111,6 +116,10 @@ public enum S3ErrorTable {
"AccessDenied", "User doesn't have the right to access this " +
"resource.", HTTP_FORBIDDEN),
+ REQUEST_TIME_TOO_SKEWED(
+ "RequestTimeTooSkewed", "The difference between the request time and " +
+ "the server's time is too large.", HTTP_FORBIDDEN),
+
SIGNATURE_DOES_NOT_MATCH(
"SignatureDoesNotMatch", "The request signature we calculated does not " +
"match the signature you provided. Check your key and signing method.",
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 12ed0266f41..3ec4a43074c 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
@@ -101,7 +101,8 @@ public SignatureInfo parseSignature() throws OS3Exception, IOException, NoSuchAl
throw S3ErrorTable.newError(ACCESS_DENIED, e.getResource());
} catch (MalformedResourceException e) {
AUDIT.logAuthFailure(buildAuthFailureMessage(e));
- throw S3ErrorTable.newError(MALFORMED_HEADER, e.getResource());
+ S3ErrorTable error = e.getErrorCode() != null ? e.getErrorCode() : MALFORMED_HEADER;
+ throw S3ErrorTable.newError(error, e.getResource());
}
if (signatureInfo != null) {
break;
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4HeaderParser.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4HeaderParser.java
index ed4ef4b13dd..6382eeae56a 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4HeaderParser.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4HeaderParser.java
@@ -17,14 +17,17 @@
package org.apache.hadoop.ozone.s3.signature;
-import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.SECONDS;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.REQUEST_TIME_TOO_SKEWED;
import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.AWS4_SIGNING_ALGORITHM;
import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.DATE_FORMATTER;
import com.google.common.annotations.VisibleForTesting;
import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
import java.time.format.DateTimeParseException;
import java.util.Collection;
import org.apache.commons.codec.DecoderException;
@@ -37,6 +40,9 @@
*/
public class AuthorizationV4HeaderParser implements SignatureParser {
+ /** Max allowed clock skew for SigV4 authorization-header requests (15 minutes). */
+ private static final long SIGV4_MAX_REQUEST_SKEW_SECONDS = 15 * 60;
+
private static final String CREDENTIAL = "Credential=";
private static final String SIGNEDHEADERS = "SignedHeaders=";
private static final String SIGNATURE = "Signature=";
@@ -65,6 +71,7 @@ public SignatureInfo parseSignature() throws MalformedResourceException {
if (authHeader == null || !authHeader.startsWith("AWS4")) {
return null;
}
+ validateDateHeader();
int firstSep = authHeader.indexOf(' ');
if (firstSep < 0) {
throw new MalformedResourceException(authHeader);
@@ -175,15 +182,16 @@ private Credential parseCredentials(String credential)
"AWS service is empty. credential:" + credential, authHeader);
}
- // Date should not be empty and within valid range.
+ // Date should not be empty and must match X-Amz-Date.
if (!credentialObj.getDate().isEmpty()) {
try {
- validateDateRange(credentialObj);
+ LocalDate.parse(credentialObj.getDate(), DATE_FORMATTER);
+ Credential.validateScopeDateMatchesAmzDate(credentialObj.getDate(), dateHeader,
+ authHeader);
} catch (DateTimeParseException ex) {
throw new MalformedResourceException(
"AWS date format is invalid. credential:" + credential, authHeader);
}
-
} else {
throw new MalformedResourceException(
"AWS date is empty. credential:{}" + credential, authHeader);
@@ -191,17 +199,26 @@ private Credential parseCredentials(String credential)
return credentialObj;
}
+ /**
+ * Validates {@code X-Amz-Date} clock skew for authorization-header SigV4.
+ * AWS SigV4 clients send this timestamp in UTC.
+ */
@VisibleForTesting
- public void validateDateRange(Credential credentialObj)
- throws MalformedResourceException, DateTimeParseException {
- LocalDate date = LocalDate.parse(credentialObj.getDate(), DATE_FORMATTER);
- LocalDate now = LocalDate.now();
- if (date.isBefore(now.minus(1, DAYS)) ||
- date.isAfter(now.plus(1, DAYS))) {
+ void validateDateHeader() throws MalformedResourceException {
+ if (dateHeader == null || dateHeader.isEmpty()) {
+ throw new MalformedResourceException("Missing X-Amz-Date header.", authHeader);
+ }
+ try {
+ LocalDateTime date = LocalDateTime.parse(dateHeader,
+ StringToSignProducer.TIME_FORMATTER);
+ LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
+ if (date.isBefore(now.minus(SIGV4_MAX_REQUEST_SKEW_SECONDS, SECONDS))
+ || date.isAfter(now.plus(SIGV4_MAX_REQUEST_SKEW_SECONDS, SECONDS))) {
+ throw new MalformedResourceException(REQUEST_TIME_TOO_SKEWED, dateHeader);
+ }
+ } catch (DateTimeParseException ex) {
throw new MalformedResourceException(
- "AWS date not in valid range. Date: " + date + " should not be older "
- + "than 1 day(i.e yesterday) and "
- + "greater than 1 day(i.e tomorrow).", authHeader);
+ "Invalid X-Amz-Date format: " + dateHeader, authHeader);
}
}
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
index 3dc6d3218d1..89b4b659252 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/AuthorizationV4QueryParser.java
@@ -26,6 +26,7 @@
import com.google.common.annotations.VisibleForTesting;
import java.io.UnsupportedEncodingException;
import java.time.LocalDate;
+import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.util.Map;
@@ -136,7 +137,7 @@ protected void validateDateAndExpires()
final Long expires = Long.parseLong(expiresString);
if (expires >= X_AMZ_EXPIRES_MIN && expires <= X_AMZ_EXPIRES_MAX) {
if (ZonedDateTime.parse(dateString, StringToSignProducer.TIME_FORMATTER)
- .plus(expires, SECONDS).isBefore(ZonedDateTime.now())) {
+ .plus(expires, SECONDS).isBefore(ZonedDateTime.now(ZoneOffset.UTC))) {
// An expired pre-signed URL is an authorization failure (403), not a
// malformed header (400).
throw new AccessDeniedResourceException("Pre-signed S3 url is expired. "
@@ -170,10 +171,12 @@ private void validateCredential(Credential credential)
throw new MalformedResourceException(
"AWS service is empty. credential:" + credential);
}
- // Date should not be empty and should be properly formatted.
+ // Date should not be empty, properly formatted, and match X-Amz-Date.
if (!credential.getDate().isEmpty()) {
try {
LocalDate.parse(credential.getDate(), DATE_FORMATTER);
+ Credential.validateScopeDateMatchesAmzDate(credential.getDate(),
+ queryParameters.get("X-Amz-Date"), credential.getCredential());
} catch (DateTimeParseException ex) {
throw new MalformedResourceException(
"AWS date format is invalid. credential:" + credential);
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/Credential.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/Credential.java
index fab1718d93e..ee7c9577f79 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/Credential.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/Credential.java
@@ -17,6 +17,8 @@
package org.apache.hadoop.ozone.s3.signature;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_CREDENTIAL_DATE;
+
/**
* Credential in the AWS authorization header.
* Ref: https://docs.aws.amazon.com/AmazonS3/latest/API/
@@ -105,4 +107,22 @@ public String createScope() {
getAwsRegion(), getAwsService(),
getAwsRequest());
}
+
+ /**
+ * Validates that credential scope date matches the date portion of
+ * {@code X-Amz-Date}. AWS SigV4 requires zero tolerance between these values.
+ */
+ static void validateScopeDateMatchesAmzDate(String credentialDate,
+ String amzDate, String resource) throws MalformedResourceException {
+ if (amzDate == null || amzDate.isEmpty()) {
+ throw new MalformedResourceException("Missing X-Amz-Date.", resource);
+ }
+ if (amzDate.length() < 8) {
+ throw new MalformedResourceException(
+ "Invalid X-Amz-Date format: " + amzDate, resource);
+ }
+ if (!credentialDate.equals(amzDate.substring(0, 8))) {
+ throw new MalformedResourceException(MALFORMED_CREDENTIAL_DATE, resource);
+ }
+ }
}
diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/MalformedResourceException.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/MalformedResourceException.java
index 0d2a42a7759..b300be3030d 100644
--- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/MalformedResourceException.java
+++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/signature/MalformedResourceException.java
@@ -17,22 +17,39 @@
package org.apache.hadoop.ozone.s3.signature;
+import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
+
/**
* This exception is used to communicate validation errors when parsing
* signatures.
*/
public class MalformedResourceException extends Exception {
+ private final S3ErrorTable errorCode;
private final String resource;
public MalformedResourceException(String resource) {
- this.resource = resource;
+ this(null, null, resource);
}
public MalformedResourceException(String message, String resource) {
+ this(null, message, resource);
+ }
+
+ public MalformedResourceException(S3ErrorTable errorCode, String resource) {
+ this(errorCode, null, resource);
+ }
+
+ private MalformedResourceException(S3ErrorTable errorCode, String message,
+ String resource) {
super(message);
+ this.errorCode = errorCode;
this.resource = resource;
}
+ public S3ErrorTable getErrorCode() {
+ return errorCode;
+ }
+
public String getResource() {
return resource;
}
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 69cc088e184..793669beda8 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
@@ -17,7 +17,6 @@
package org.apache.hadoop.ozone.s3.signature;
-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.exception.S3ErrorTable.newError;
import static org.apache.hadoop.ozone.s3.util.S3Consts.X_AMZ_CONTENT_SHA256;
@@ -28,10 +27,8 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -64,11 +61,6 @@ public final class StringToSignProducer {
private static final String[] URL_ENCODE_SEARCH_CHARS = new String[] {"+", "*", "%7E"};
private static final String[] URL_ENCODE_REPLACE_CHARS = new String[] {"%20", "%2A", "~"};
- /**
- * Seconds in a week, which is the max expiration time Sig-v4 accepts.
- */
- private static final long PRESIGN_URL_MAX_EXPIRATION_SECONDS =
- 60 * 60 * 24 * 7;
public static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'")
.withZone(ZoneOffset.UTC);
@@ -81,7 +73,6 @@ public static String createSignatureBase(
ContainerRequestContext context
) throws Exception {
return createSignatureBase(signatureInfo,
- context.getUriInfo().getRequestUri().getScheme(),
context.getMethod(),
LowerCaseKeyStringMap.fromHeaderMap(context.getHeaders()),
fromMultiValueToSingleValueMap(
@@ -91,7 +82,6 @@ public static String createSignatureBase(
@VisibleForTesting
public static String createSignatureBase(
SignatureInfo signatureInfo,
- String scheme,
String method,
LowerCaseKeyStringMap headers,
Map<String, String> queryParams
@@ -123,7 +113,6 @@ public static String createSignatureBase(
.append(credentialScope).append(NEWLINE);
String canonicalRequest = buildCanonicalRequest(
- scheme,
method,
uri,
signatureInfo.getSignedHeaders(),
@@ -157,7 +146,6 @@ public static String hash(String payload) throws NoSuchAlgorithmException {
@VisibleForTesting
public static String buildCanonicalRequest(
- String schema,
String method,
String uri,
String signedHeaders,
@@ -187,15 +175,6 @@ public static String buildCanonicalRequest(
}
canonicalHeaders.append(headerValue)
.append(NEWLINE);
-
- // Set for testing purpose only to skip date and host validation.
- try {
- validateSignedHeader(schema, header, headerValue);
- } catch (DateTimeParseException ex) {
- LOG.error("DateTime format invalid.", ex);
- throw newError(S3_AUTHINFO_CREATION_ERROR);
- }
-
} else {
LOG.error("Header " + header + " not present in "
+ "request but requested to be signed.");
@@ -283,35 +262,6 @@ private static String getQueryParamString(
return result.toString();
}
- @VisibleForTesting
- static void validateSignedHeader(
- String schema,
- String header,
- String headerValue
- ) throws OS3Exception, DateTimeParseException {
- switch (header) {
- case HOST:
- break;
- case X_AMAZ_DATE:
- LocalDateTime date = LocalDateTime.parse(headerValue, TIME_FORMATTER);
- LocalDateTime now = LocalDateTime.now();
- if (date.isBefore(now.minus(PRESIGN_URL_MAX_EXPIRATION_SECONDS, SECONDS))
- || date.isAfter(now.plus(PRESIGN_URL_MAX_EXPIRATION_SECONDS,
- SECONDS))) {
- LOG.error("AWS date not in valid range. Request timestamp:{} should "
- + "not be older than {} seconds.",
- headerValue, PRESIGN_URL_MAX_EXPIRATION_SECONDS);
- throw newError(S3_AUTHINFO_CREATION_ERROR);
- }
- break;
- case X_AMZ_CONTENT_SHA256:
- // Validate x-amz-content-sha256 during upload, before committing the key.
- break;
- default:
- break;
- }
- }
-
/**
* According to AWS Sig V4 documentation:
* https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html
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 cc9dd813fb9..89d7e4a16de 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
@@ -19,8 +19,9 @@
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.MALFORMED_CREDENTIAL_DATE;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.PAYLOAD_TOO_LARGE;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.REQUEST_TIME_TOO_SKEWED;
import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.S3_AUTHINFO_CREATION_ERROR;
import static org.apache.hadoop.ozone.s3.signature.AWSSignatureProcessor.DATE_FORMATTER;
import static org.apache.hadoop.ozone.s3.signature.SignatureParser.AUTHORIZATION_HEADER;
@@ -45,6 +46,7 @@
import java.security.MessageDigest;
import java.time.LocalDate;
import java.time.LocalDateTime;
+import java.time.ZoneOffset;
import java.util.stream.Stream;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
@@ -67,48 +69,76 @@ public class TestAuthorizationFilter {
private AuthorizationFilter authorizationFilter = new AuthorizationFilter();
private static final String DATETIME = StringToSignProducer.TIME_FORMATTER.
- format(LocalDateTime.now());
+ format(LocalDateTime.now(ZoneOffset.UTC));
- private static final String CURDATE = DATE_FORMATTER.format(LocalDate.now());
+ private static final String CURDATE =
+ DATE_FORMATTER.format(LocalDate.now(ZoneOffset.UTC));
+ private static final String MISMATCHED_CREDENTIAL_DATE =
+ DATE_FORMATTER.format(LocalDate.now(ZoneOffset.UTC).minusDays(1));
private static Stream<Arguments>testAuthFilterFailuresInput() {
+ String staleCredentialDate = "20190221";
+ String staleDateTime = staleCredentialDate + "T002037Z";
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, " +
- "Signature" +
- "=56ec73ba1974f8feda8365c3caef89c5d4a688d5f9baccf47" +
- "65f46a14cd745ad",
+ "AWS4-HMAC-SHA256 Credential=testuser1/" + staleCredentialDate
+ + "/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",
+ "e2bd43f11c97cde3465e0e8d1aad77af7ec7aa2ed8e213cd0e24"
+ + "1e28375860c6",
+ staleDateTime,
"",
"/",
- MALFORMED_HEADER.getErrorMessage()
+ HTTP_FORBIDDEN,
+ REQUEST_TIME_TOO_SKEWED.getErrorMessage()
),
arguments(
"GET",
- "AWS4-HMAC-SHA256 " +
- "Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request," +
- " SignedHeaders=content-type;host;x-amz-date, " +
- "Signature=" +
- "5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400" +
- "e06b5924a6f2b5d7",
+ "AWS4-HMAC-SHA256 "
+ + "Credential=AKIDEXAMPLE/" + staleCredentialDate
+ + "/us-east-1/iam/aws4_request,"
+ + " SignedHeaders=content-type;host;x-amz-date, "
+ + "Signature="
+ + "5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400"
+ + "e06b5924a6f2b5d7",
"",
"iam.amazonaws.com",
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
- "20150830T123600Z",
+ staleDateTime,
"application/x-www-form-urlencoded; charset=utf-8",
"",
- MALFORMED_HEADER.getErrorMessage()
+ HTTP_FORBIDDEN,
+ REQUEST_TIME_TOO_SKEWED.getErrorMessage()
+ ),
+ arguments(
+ "GET",
+ "AWS4-HMAC-SHA256 Credential=testuser1/" + MISMATCHED_CREDENTIAL_DATE
+ + "/us-east-1/s3/aws4_request, SignedHeaders=content-md5;host;"
+ + "x-amz-content-sha256;x-amz-date, "
+ + "Signature"
+ + "=56ec73ba1974f8feda8365c3caef89c5d4a688d5f9baccf47"
+ + "65f46a14cd745ad",
+ "Zi68x2nPDDXv5qfDC+ZWTg==",
+ "s3g:9878",
+ "e2bd43f11c97cde3465e0e8d1aad77af7ec7aa2ed8e213cd0e24"
+ + "1e28375860c6",
+ DATETIME,
+ "",
+ "/",
+ HTTP_BAD_REQUEST,
+ MALFORMED_CREDENTIAL_DATE.getErrorMessage()
),
arguments(null, null, null, null, null, null, null, null,
+ HTTP_FORBIDDEN,
S3_AUTHINFO_CREATION_ERROR.getErrorMessage()),
arguments(null, "", null, null, null, null, null, null,
+ HTTP_FORBIDDEN,
S3_AUTHINFO_CREATION_ERROR.getErrorMessage()),
// AWS V2 signature
arguments(
@@ -120,6 +150,7 @@ public class TestAuthorizationFilter {
"Wed, 22 Mar 2023 17:00:06 +0000",
"application/octet-stream",
"/",
+ HTTP_FORBIDDEN,
S3_AUTHINFO_CREATION_ERROR.getErrorMessage()
),
// Too huge payload for signature V4 of STS request
@@ -138,6 +169,7 @@ public class TestAuthorizationFilter {
DATETIME,
"application/x-www-form-urlencoded; charset=utf-8",
"/",
+ HTTP_BAD_REQUEST,
PAYLOAD_TOO_LARGE.getErrorMessage()
)
);
@@ -149,7 +181,7 @@ public class TestAuthorizationFilter {
void testAuthFilterFailures(
String method, String authHeader, String contentMd5,
String host, String amzContentSha256, String date, String contentType,
- String path, String expectedErrorMsg
+ String path, int expectedStatus, String expectedErrorMsg
) throws Exception {
try {
ContainerRequestContext context = setupContext(method, authHeader,
@@ -172,22 +204,8 @@ void testAuthFilterFailures(
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.
- assertEquals(HTTP_FORBIDDEN, ex.getResponse().getStatus());
- assertEquals(expectedErrorMsg,
- ex.getMessage());
- } else {
- // Other requests have stale timestamp and
- // should fail with Malformed Authorization Header.
- assertEquals(HTTP_BAD_REQUEST, ex.getResponse().getStatus());
- assertEquals(expectedErrorMsg,
- ex.getMessage());
-
- }
-
+ assertEquals(expectedStatus, ex.getResponse().getStatus());
+ assertEquals(expectedErrorMsg, ex.getMessage());
}
}
diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAuthorizationV4HeaderParser.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAuthorizationV4HeaderParser.java
index 8812405d10d..0c259c42377 100644
--- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAuthorizationV4HeaderParser.java
+++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAuthorizationV4HeaderParser.java
@@ -18,12 +18,17 @@
package org.apache.hadoop.ozone.s3.signature;
import static java.time.temporal.ChronoUnit.DAYS;
+import static java.time.temporal.ChronoUnit.MINUTES;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_CREDENTIAL_DATE;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.REQUEST_TIME_TOO_SKEWED;
import static org.apache.hadoop.ozone.s3.signature.SignatureProcessor.DATE_FORMATTER;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -33,14 +38,15 @@
public class TestAuthorizationV4HeaderParser {
- private static final String SAMPLE_DATE = "20210202T144559Z";
-
private String curDate;
+ private String sampleDate;
@BeforeEach
public void setup() {
- LocalDate now = LocalDate.now();
+ LocalDate now = LocalDate.now(ZoneOffset.UTC);
curDate = DATE_FORMATTER.format(now);
+ sampleDate = StringToSignProducer.TIME_FORMATTER.format(
+ LocalDateTime.now(ZoneOffset.UTC));
}
@Test
@@ -50,7 +56,7 @@ public void testV4HeaderWellFormed() throws Exception {
"SignedHeaders=host;range;x-amz-date, " +
"Signature=fe5f80f77d5fa3beca038a248ff027";
AuthorizationV4HeaderParser v4 =
- new AuthorizationV4HeaderParser(auth, SAMPLE_DATE);
+ new AuthorizationV4HeaderParser(auth, sampleDate);
final SignatureInfo signatureInfo = v4.parseSignature();
assertEquals("ozone", signatureInfo.getAwsAccessId());
assertEquals(curDate, signatureInfo.getDate());
@@ -65,7 +71,7 @@ public void testV4HeaderMissingParts() {
"Credential=ozone/" + curDate + "/us-east-1/s3/aws4_request, " +
"SignedHeaders=host;range;x-amz-date,";
AuthorizationV4HeaderParser v4 =
- new AuthorizationV4HeaderParser(auth, SAMPLE_DATE);
+ new AuthorizationV4HeaderParser(auth, sampleDate);
assertThrows(MalformedResourceException.class, () -> v4.parseSignature());
}
@@ -76,7 +82,7 @@ public void testV4HeaderInvalidCredential() {
"SignedHeaders=host;range;x-amz-date, " +
"Signature=fe5f80f77d5fa3beca038a248ff027";
AuthorizationV4HeaderParser v4 =
- new AuthorizationV4HeaderParser(auth, SAMPLE_DATE);
+ new AuthorizationV4HeaderParser(auth, sampleDate);
assertThrows(MalformedResourceException.class, () -> v4.parseSignature());
}
@@ -91,7 +97,7 @@ public void testV4HeaderWithoutSpace() throws MalformedResourceException {
+ "=fe5f80f77d5fa3beca038a248ff027";
AuthorizationV4HeaderParser v4 = new AuthorizationV4HeaderParser(auth,
- SAMPLE_DATE);
+ sampleDate);
SignatureInfo signature = v4.parseSignature();
assertEquals("AWS4-HMAC-SHA256", signature.getAlgorithm());
@@ -106,46 +112,64 @@ public void testV4HeaderWithoutSpace() throws MalformedResourceException {
@Test
public void testV4HeaderDateValidationSuccess()
throws MalformedResourceException {
- // Case 1: valid date within range.
- LocalDate now = LocalDate.now();
- String dateStr = DATE_FORMATTER.format(now);
- testRequestWithSpecificDate(dateStr);
-
- // Case 2: Valid date with in range.
- dateStr = DATE_FORMATTER.format(now.plus(1, DAYS));
- testRequestWithSpecificDate(dateStr);
-
- // Case 3: Valid date with in range.
- dateStr = DATE_FORMATTER.format(now.minus(1, DAYS));
- testRequestWithSpecificDate(dateStr);
+ testRequestWithSpecificDate(curDate);
+
+ String amzDatePlus14Min = StringToSignProducer.TIME_FORMATTER.format(
+ LocalDateTime.now(ZoneOffset.UTC).plus(14, MINUTES));
+ testRequestWithSpecificDate(curDate, amzDatePlus14Min);
+ }
+
+ @Test
+ public void testV4HeaderCredentialDateMismatch() {
+ LocalDate yesterday = LocalDate.now(ZoneOffset.UTC).minus(1, DAYS);
+ String mismatchedCredentialDate = DATE_FORMATTER.format(yesterday);
+ String auth =
+ "AWS4-HMAC-SHA256 Credential=ozone/" + mismatchedCredentialDate
+ + "/us-east-1/s3/aws4_request,"
+ + "SignedHeaders=host;x-amz-content-sha256;x-amz-date,"
+ + "Signature=fe5f80f77d5fa3beca038a248ff027";
+ AuthorizationV4HeaderParser v4 =
+ new AuthorizationV4HeaderParser(auth, sampleDate);
+ MalformedResourceException ex = assertThrows(MalformedResourceException.class,
+ () -> v4.parseSignature());
+ assertEquals(MALFORMED_CREDENTIAL_DATE, ex.getErrorCode());
+ }
+
+ @Test
+ public void testV4HeaderDateSkewValidationFailure() {
+ String staleDateTime = StringToSignProducer.TIME_FORMATTER.format(
+ LocalDateTime.now(ZoneOffset.UTC).minusMinutes(16));
+ String auth = "AWS4-HMAC-SHA256 Credential=ozone/" + curDate
+ + "/us-east-1/s3/aws4_request, "
+ + "SignedHeaders=host;x-amz-content-sha256;x-amz-date, "
+ + "Signature=fe5f80f77d5fa3beca038a248ff027";
+ AuthorizationV4HeaderParser v4 =
+ new AuthorizationV4HeaderParser(auth, staleDateTime);
+ MalformedResourceException ex = assertThrows(MalformedResourceException.class,
+ () -> v4.parseSignature());
+ assertEquals(REQUEST_TIME_TOO_SKEWED, ex.getErrorCode());
}
@Test
public void testV4HeaderDateValidationFailure() {
// Case 1: Empty date.
- LocalDate now = LocalDate.now();
String dateStr = "";
assertThrows(MalformedResourceException.class,
() -> testRequestWithSpecificDate(dateStr));
- // Case 2: Date after yesterday.
- String dateStr2 = DATE_FORMATTER.format(now.plus(2, DAYS));
+ // Case 2: Invalid date format
+ String dateStr2 = LocalDate.now(ZoneOffset.UTC).toString();
assertThrows(MalformedResourceException.class,
() -> testRequestWithSpecificDate(dateStr2));
-
- // Case 3: Date before yesterday.
- String dateStr3 = DATE_FORMATTER.format(now.minus(2, DAYS));
- assertThrows(MalformedResourceException.class,
- () -> testRequestWithSpecificDate(dateStr3));
-
- // Case 4: Invalid date format
- String dateStr4 = now.toString();
- assertThrows(MalformedResourceException.class,
- () -> testRequestWithSpecificDate(dateStr4));
}
private void testRequestWithSpecificDate(String dateStr)
throws MalformedResourceException {
+ testRequestWithSpecificDate(dateStr, sampleDate);
+ }
+
+ private void testRequestWithSpecificDate(String dateStr, String amzDate)
+ throws MalformedResourceException {
String auth =
"AWS4-HMAC-SHA256 Credential=ozone/" + dateStr + "/us-east-1/s3" +
"/aws4_request,"
@@ -153,12 +177,13 @@ private void testRequestWithSpecificDate(String dateStr)
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
AuthorizationV4HeaderParser v4 =
- new AuthorizationV4HeaderParser(auth, SAMPLE_DATE);
+ new AuthorizationV4HeaderParser(auth, amzDate);
SignatureInfo signature = v4.parseSignature();
assertEquals("AWS4-HMAC-SHA256", signature.getAlgorithm());
assertEquals("ozone", signature.getAwsAccessId());
assertEquals(dateStr, signature.getDate());
+ assertEquals(amzDate, signature.getDateTime());
assertEquals("host;x-amz-content-sha256;x-amz-date",
signature.getSignedHeaders());
assertEquals("fe5f80f77d5fa3beca038a248ff027", signature.getSignature());
@@ -173,7 +198,7 @@ public void testV4HeaderRegionValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027%";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth, sampleDate)
.parseSignature());
String auth2 =
"AWS4-HMAC-SHA256 Credential=ozone/" + curDate + "s3/aws4_request,"
@@ -181,7 +206,7 @@ public void testV4HeaderRegionValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027%";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth2, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth2, sampleDate)
.parseSignature());
}
@@ -194,7 +219,7 @@ public void testV4HeaderServiceValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth, sampleDate)
.parseSignature());
String auth2 =
@@ -204,7 +229,7 @@ public void testV4HeaderServiceValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth2, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth2, sampleDate)
.parseSignature());
}
@@ -217,7 +242,7 @@ public void testV4HeaderRequestValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth, sampleDate)
.parseSignature());
String auth2 =
@@ -227,7 +252,7 @@ public void testV4HeaderRequestValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth2, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth2, sampleDate)
.parseSignature());
String auth3 =
@@ -237,7 +262,7 @@ public void testV4HeaderRequestValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth3, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth3, sampleDate)
.parseSignature());
String auth4 =
@@ -247,7 +272,7 @@ public void testV4HeaderRequestValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth4, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth4, sampleDate)
.parseSignature());
}
@@ -260,7 +285,7 @@ public void testV4HeaderSignedHeaderValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth, sampleDate)
.parseSignature());
String auth2 =
@@ -270,7 +295,7 @@ public void testV4HeaderSignedHeaderValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth2, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth2, sampleDate)
.parseSignature());
String auth3 =
@@ -280,7 +305,7 @@ public void testV4HeaderSignedHeaderValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth3, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth3, sampleDate)
.parseSignature());
String auth4 =
@@ -290,7 +315,7 @@ public void testV4HeaderSignedHeaderValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth4, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth4, sampleDate)
.parseSignature());
}
@@ -303,7 +328,7 @@ public void testV4HeaderSignatureValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027%";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth, sampleDate)
.parseSignature());
String auth2 =
@@ -313,7 +338,7 @@ public void testV4HeaderSignatureValidationFailure() throws Exception {
+ "Signature"
+ "=";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth2, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth2, sampleDate)
.parseSignature());
String auth3 =
@@ -322,7 +347,7 @@ public void testV4HeaderSignatureValidationFailure() throws Exception {
+ "SignedHeaders=host;x-amz-content-sha256;x-amz-date,"
+ "=";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth3, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth3, sampleDate)
.parseSignature());
}
@@ -335,7 +360,7 @@ public void testV4HeaderHashAlgoValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth, sampleDate)
.parseSignature());
String auth2 =
@@ -344,7 +369,7 @@ public void testV4HeaderHashAlgoValidationFailure() throws Exception {
+ "SignedHeaders=host;x-amz-content-sha256;x-amz-date,"
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
- assertNull(new AuthorizationV4HeaderParser(auth2, SAMPLE_DATE)
+ assertNull(new AuthorizationV4HeaderParser(auth2, sampleDate)
.parseSignature());
String auth3 =
@@ -353,7 +378,7 @@ public void testV4HeaderHashAlgoValidationFailure() throws Exception {
+ "SignedHeaders=host;x-amz-content-sha256;x-amz-date,"
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
- assertNull(new AuthorizationV4HeaderParser(auth3, SAMPLE_DATE)
+ assertNull(new AuthorizationV4HeaderParser(auth3, sampleDate)
.parseSignature());
// Invalid algorithm
@@ -362,7 +387,7 @@ public void testV4HeaderHashAlgoValidationFailure() throws Exception {
"SignedHeaders=host;range;x-amz-date, " +
"Signature=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth4, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth4, sampleDate)
.parseSignature());
}
@@ -375,7 +400,7 @@ public void testV4HeaderCredentialValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth, sampleDate)
.parseSignature());
String auth2 =
@@ -385,7 +410,7 @@ public void testV4HeaderCredentialValidationFailure() throws Exception {
+ "Signature"
+ "=fe5f80f77d5fa3beca038a248ff027";
assertThrows(MalformedResourceException.class,
- () -> new AuthorizationV4HeaderParser(auth2, SAMPLE_DATE)
+ () -> new AuthorizationV4HeaderParser(auth2, sampleDate)
.parseSignature());
}
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 bc86c2a9b28..7fbe108ab6c 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
@@ -17,6 +17,7 @@
package org.apache.hadoop.ozone.s3.signature;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_CREDENTIAL_DATE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -37,6 +38,7 @@ public class TestAuthorizationV4QueryParser {
private static final String DATETIME = ZonedDateTime.now().format(
StringToSignProducer.TIME_FORMATTER);
+ private static final String CREDENTIAL_DATE = DATETIME.substring(0, 8);
@Test
public void testInvalidAlgorithm() {
@@ -44,7 +46,8 @@ public void testInvalidAlgorithm() {
// Missing algorithm
Map<String, String> parameters = new HashMap<>();
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request");
+ "AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE
+ + "%2Fus-east-1%2Fs3%2Faws4_request");
parameters.put("X-Amz-Date", DATETIME);
parameters.put("X-Amz-Expires", "10000");
parameters.put("X-Amz-SignedHeaders", "host");
@@ -71,7 +74,8 @@ public void testInvalidDateExpiredHeaders() throws Exception {
Map<String, String> parameters = new HashMap<>();
parameters.put("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request");
+ "AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE
+ + "%2Fus-east-1%2Fs3%2Faws4_request");
parameters.put("X-Amz-Expires", "10000");
parameters.put("X-Amz-SignedHeaders", "host");
parameters.put("X-Amz-Signature",
@@ -112,6 +116,8 @@ public void testInvalidDateExpiredHeaders() throws Exception {
() -> new AuthorizationV4QueryParser(parameters).parseSignature());
// Expired request -> 403 (AccessDenied), not 400.
+ parameters.put("X-Amz-Credential",
+ "AKIAIOSFODNN7EXAMPLE%2F20160801%2Fus-east-1%2Fs3%2Faws4_request");
parameters.put("X-Amz-Date", "20160801T083241Z");
parameters.put("X-Amz-Expires", "10000");
assertThrows(AccessDeniedResourceException.class,
@@ -126,7 +132,8 @@ public void testUnExpiredHeaders() throws Exception {
Map<String, String> parameters = new HashMap<>();
parameters.put("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request");
+ "AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE
+ + "%2Fus-east-1%2Fs3%2Faws4_request");
parameters.put("X-Amz-Date", DATETIME);
parameters.put("X-Amz-Expires", "10000");
parameters.put("X-Amz-SignedHeaders", "host");
@@ -151,7 +158,7 @@ public void testInvalidCredential() throws Exception {
Map<String, String> parameters = new HashMap<>();
parameters.put("X-Amz-Algorithm", "AWS4-ZAVC-HJUA123");
parameters.put("X-Amz-Credential",
- "%2F20130524%2Fus-east-1%2Fs3%2Faws4_request");
+ "%2F" + CREDENTIAL_DATE + "%2Fus-east-1%2Fs3%2Faws4_request");
parameters.put("X-Amz-Date", DATETIME);
parameters.put("X-Amz-Expires", "10000");
parameters.put("X-Amz-SignedHeaders", "host");
@@ -162,25 +169,25 @@ public void testInvalidCredential() throws Exception {
// Empty AWS region
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE%2F20130524%2F%2Fs3%2Faws4_request");
+ "AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE + "%2F%2Fs3%2Faws4_request");
assertThrows(MalformedResourceException.class,
() -> new AuthorizationV4QueryParser(parameters).parseSignature());
// Empty AWS request
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2F");
+ "AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE + "%2Fus-east-1%2Fs3%2F");
assertThrows(MalformedResourceException.class,
() -> new AuthorizationV4QueryParser(parameters).parseSignature());
// Invalid aws request
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws_request");
+ "AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE + "%2Fus-east-1%2Fs3%2Faws_request");
assertThrows(MalformedResourceException.class,
() -> new AuthorizationV4QueryParser(parameters).parseSignature());
// Empty aws service
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2F%2Faws4_request");
+ "AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE + "%2Fus-east-1%2F%2Faws4_request");
assertThrows(MalformedResourceException.class,
() -> new AuthorizationV4QueryParser(parameters).parseSignature());
@@ -199,7 +206,7 @@ public void testInvalidCredential() throws Exception {
// No URL encoding
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request");
+ "AKIAIOSFODNN7EXAMPLE/" + CREDENTIAL_DATE + "/us-east-1/s3/aws4_request");
assertThrows(MalformedResourceException.class,
() -> new AuthorizationV4QueryParser(parameters).parseSignature());
}
@@ -211,7 +218,8 @@ public void testInvalidSignedHeaders() throws Exception {
Map<String, String> parameters = new HashMap<>();
parameters.put("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request");
+ "AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE
+ + "%2Fus-east-1%2Fs3%2Faws4_request");
parameters.put("X-Amz-Date", DATETIME);
parameters.put("X-Amz-Expires", "10000");
parameters.put("X-Amz-Signature",
@@ -232,7 +240,8 @@ public void testInvalidSignature() throws Exception {
Map<String, String> parameters = new HashMap<>();
parameters.put("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
parameters.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request");
+ "AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE
+ + "%2Fus-east-1%2Fs3%2Faws4_request");
parameters.put("X-Amz-Date", DATETIME);
parameters.put("X-Amz-Expires", "10000");
parameters.put("X-Amz-SignedHeaders", "host");
@@ -247,6 +256,22 @@ public void testInvalidSignature() throws Exception {
() -> new AuthorizationV4QueryParser(parameters).parseSignature());
}
+ @Test
+ public void testCredentialDateMismatch() {
+ Map<String, String> parameters = new HashMap<>();
+ parameters.put("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
+ parameters.put("X-Amz-Credential",
+ "AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request");
+ parameters.put("X-Amz-Date", DATETIME);
+ parameters.put("X-Amz-Expires", "10000");
+ parameters.put("X-Amz-SignedHeaders", "host");
+ parameters.put("X-Amz-Signature",
+ "aeeed9bbccd4d02ee5c0109b86d86835f995330da4c265957d157751f604d404");
+ MalformedResourceException ex = assertThrows(MalformedResourceException.class,
+ () -> new AuthorizationV4QueryParser(parameters).parseSignature());
+ assertEquals(MALFORMED_CREDENTIAL_DATE, ex.getErrorCode());
+ }
+
/**
* Based on <a href="https://docs.aws.amazon.com
* /AmazonS3/latest/API/sigv4-query-string-auth.html">AWS example</a>.
@@ -258,7 +283,7 @@ public void testWithAWSExample() throws Exception {
queryParams.put("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
queryParams.put("X-Amz-Credential",
- "AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request");
+ "AKIAIOSFODNN7EXAMPLE/" + CREDENTIAL_DATE + "/us-east-1/s3/aws4_request");
queryParams.put("X-Amz-Date", DATETIME);
queryParams.put("X-Amz-Expires", "86400");
queryParams.put("X-Amz-SignedHeaders", "host");
@@ -268,7 +293,7 @@ public void testWithAWSExample() throws Exception {
String canonicalRequest = "GET\n"
+ "/test.txt\n"
+ "X-Amz-Algorithm=AWS4-HMAC-SHA256&"
- + "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2F"
+ + "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F" + CREDENTIAL_DATE + "%2F"
+ "us-east-1%2Fs3%2Faws4_request&X-Amz-Date=" + DATETIME
+ "&X-Amz-Expires=86400&X-Amz-SignedHeaders=host\n"
+ "host:localhost\n"
@@ -291,7 +316,7 @@ protected void validateDateAndExpires() {
headers.put("host", "localhost");
final String stringToSign =
- StringToSignProducer.createSignatureBase(signatureInfo, "https", "GET",
+ StringToSignProducer.createSignatureBase(signatureInfo, "GET",
headers, queryParams);
MessageDigest md = MessageDigest.getInstance("SHA-256");
@@ -299,7 +324,7 @@ protected void validateDateAndExpires() {
assertEquals("AWS4-HMAC-SHA256\n"
+ DATETIME + "\n"
- + "20130524/us-east-1/s3/aws4_request\n"
+ + CREDENTIAL_DATE + "/us-east-1/s3/aws4_request\n"
+ Hex.encode(md.digest()).toLowerCase(),
stringToSign);
}
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 bc2da6fde91..ba95cab32c6 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
@@ -17,6 +17,8 @@
package org.apache.hadoop.ozone.s3.signature;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_HEADER;
+import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.REQUEST_TIME_TOO_SKEWED;
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.UNSIGNED_PAYLOAD;
@@ -30,6 +32,7 @@
import java.security.MessageDigest;
import java.time.LocalDate;
import java.time.LocalDateTime;
+import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
@@ -53,7 +56,7 @@
public class TestStringToSignProducer {
private static final String DATETIME = StringToSignProducer.TIME_FORMATTER.
- format(LocalDateTime.now());
+ format(LocalDateTime.now(ZoneOffset.UTC));
@Test
public void test() throws Exception {
@@ -75,8 +78,10 @@ public void test() throws Exception {
+ "host;x-amz-content-sha256;x-amz-date;content-type\n"
+ "Content-SHA";
+ String credentialDate = DATETIME.substring(0, 8);
String authHeader =
- "AWS4-HMAC-SHA256 Credential=AKIAJWFJK62WUTKNFJJA/20181009/us-east-1"
+ "AWS4-HMAC-SHA256 Credential=AKIAJWFJK62WUTKNFJJA/" + credentialDate
+ + "/us-east-1"
+ "/s3/aws4_request, "
+ "SignedHeaders=host;x-amz-content-sha256;x-amz-date;"
+ "content-type, "
@@ -90,12 +95,7 @@ public void test() throws Exception {
Map<String, String> queryParameters = new HashMap<>();
final SignatureInfo signatureInfo =
- new AuthorizationV4HeaderParser(authHeader, DATETIME) {
- @Override
- public void validateDateRange(Credential credentialObj) {
- //NOOP
- }
- }.parseSignature();
+ new AuthorizationV4HeaderParser(authHeader, DATETIME).parseSignature();
signatureInfo.setPayloadHash("Content-SHA");
signatureInfo.setUnfilteredURI("/buckets");
@@ -104,7 +104,6 @@ public void validateDateRange(Credential credentialObj) {
final String signatureBase =
StringToSignProducer.createSignatureBase(
signatureInfo,
- "http",
"GET",
headers,
queryParameters);
@@ -114,7 +113,7 @@ public void validateDateRange(Credential credentialObj) {
assertEquals("AWS4-HMAC-SHA256\n"
+ DATETIME + "\n"
- + "20181009/us-east-1/s3/aws4_request\n"
+ + credentialDate + "/us-east-1/s3/aws4_request\n"
+ Hex.encode(md.digest()).toLowerCase(),
signatureBase, "String to sign is invalid");
}
@@ -130,7 +129,7 @@ public void testUrlEncodeInCanonicalRequest() {
queryParams.put("q+1*2~3", "v 4*5~6");
final String canonicalRequest = StringToSignProducer.buildCanonicalRequest(
- "https", "GET", "/bucket/a+b*c~d/foo bar", "host;x-amz-content-sha256;x-amz-date",
+ "GET", "/bucket/a+b*c~d/foo bar", "host;x-amz-content-sha256;x-amz-date",
headers, queryParams, UNSIGNED_PAYLOAD);
assertEquals(
@@ -157,7 +156,7 @@ public void testUrlEncodeInCanonicalRequest() {
@ValueSource(strings = {"[::1]:9878", "[2001:db8::1]:9878", "[::1]"})
public void testIPv6HostInStringToSign(String host) throws Exception {
String signedHeaders = "host;x-amz-content-sha256;x-amz-date";
- String credentialScope = DATE_FORMATTER.format(LocalDate.now())
+ String credentialScope = DATETIME.substring(0, 8)
+ "/us-east-1/s3/aws4_request";
String authHeader = "AWS4-HMAC-SHA256 Credential=ozone/" + credentialScope
+ ", SignedHeaders=" + signedHeaders
@@ -223,7 +222,7 @@ private ContainerRequestContext setupContext(
private static Stream<Arguments> testValidateRequestHeadersInput() {
String authHeader = "AWS4-HMAC-SHA256 Credential=ozone/"
- + DATE_FORMATTER.format(LocalDate.now())
+ + DATE_FORMATTER.format(LocalDate.now(ZoneOffset.UTC))
+ "/us-east-1/s3/aws4_request, "
+ "SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date,"
+ " Signature=db81b057718d7c1b3b8"
@@ -246,17 +245,18 @@ private static Stream<Arguments> testValidateRequestHeadersInput() {
new MultivaluedHashMap<String, String>(headersMap1);
headersMap3.remove("X-Amz-Date");
headersMap3.putSingle("X-Amz-Date", LocalDateTime.now().toString());
- // Expired X-Amz-Date
+ // Expired X-Amz-Date (more than 15 minutes in the past)
MultivaluedMap<String, String> headersMap4 =
new MultivaluedHashMap<String, String>(headersMap1);
headersMap4.remove("X-Amz-Date");
headersMap4.putSingle("X-Amz-Date", StringToSignProducer.TIME_FORMATTER.
- format(LocalDateTime.now().minusDays(8)));
+ format(LocalDateTime.now(ZoneOffset.UTC).minusMinutes(16)));
+ // X-Amz-Date too far in the future (more than 15 minutes)
MultivaluedMap<String, String> headersMap5 =
new MultivaluedHashMap<String, String>(headersMap1);
headersMap5.remove("X-Amz-Date");
headersMap5.putSingle("X-Amz-Date", StringToSignProducer.TIME_FORMATTER.
- format(LocalDateTime.now().plusDays(8)));
+ format(LocalDateTime.now(ZoneOffset.UTC).plusMinutes(16)));
// Missing X-Amz-Content-Sha256
MultivaluedMap<String, String> headersMap6 =
new MultivaluedHashMap<String, String>(headersMap1);
@@ -264,10 +264,10 @@ private static Stream<Arguments> testValidateRequestHeadersInput() {
return Stream.of(
arguments(headersMap1, "success"),
- arguments(headersMap2, S3_AUTHINFO_CREATION_ERROR.getCode()),
- arguments(headersMap3, S3_AUTHINFO_CREATION_ERROR.getCode()),
- arguments(headersMap4, S3_AUTHINFO_CREATION_ERROR.getCode()),
- arguments(headersMap5, S3_AUTHINFO_CREATION_ERROR.getCode()),
+ arguments(headersMap2, MALFORMED_HEADER.getCode()),
+ arguments(headersMap3, MALFORMED_HEADER.getCode()),
+ arguments(headersMap4, REQUEST_TIME_TOO_SKEWED.getCode()),
+ arguments(headersMap5, REQUEST_TIME_TOO_SKEWED.getCode()),
arguments(headersMap6, S3_AUTHINFO_CREATION_ERROR.getCode())
);
}
@@ -284,12 +284,18 @@ public void testValidateRequestHeaders(
"GET",
headerMap,
new MultivaluedHashMap<>());
- SignatureInfo signatureInfo = new AuthorizationV4HeaderParser(
- headerMap.getFirst("Authorization"),
- headerMap.getFirst("X-Amz-Date")).parseSignature();
- signatureInfo.setUnfilteredURI("/");
try {
+ SignatureInfo signatureInfo = new AuthorizationV4HeaderParser(
+ headerMap.getFirst("Authorization"),
+ headerMap.getFirst("X-Amz-Date")).parseSignature();
+ signatureInfo.setUnfilteredURI("/");
StringToSignProducer.createSignatureBase(signatureInfo, context);
+ } catch (MalformedResourceException e) {
+ if (REQUEST_TIME_TOO_SKEWED.equals(e.getErrorCode())) {
+ actualResult = REQUEST_TIME_TOO_SKEWED.getCode();
+ } else {
+ actualResult = MALFORMED_HEADER.getCode();
+ }
} catch (OS3Exception e) {
actualResult = e.getCode();
}
@@ -324,7 +330,7 @@ public void testValidateCanonicalHeaders(
String expectedResult) throws Exception {
String actualResult = "success";
String authHeader = "AWS4-HMAC-SHA256 Credential=ozone/"
- + DATE_FORMATTER.format(LocalDate.now())
+ + DATE_FORMATTER.format(LocalDate.now(ZoneOffset.UTC))
+ "/us-east-1/s3/aws4_request, "
+ "SignedHeaders=" + signedHeaders + ", "
+ "Signature=db81b057718d7c1b3b" +
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org