Using SigV2 to sign AWS S3 requests

This week I was working on a simple task to modify S3 bucket policy to deny s3:PutObject action to anyone except services in particular vpc or AWS presigned URL. It was just a matter of adding to bucket policy

{
    "Effect": "Deny",
    "Principal": "*",
    "Action": "s3:PutObject",
    "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3::: my-bucket /*"
    ],
    "Condition": {
        "StringNotLike": {
            "aws:PrincipalArn": "arn:aws:iam::123456789:role/get-presigned-url-lambda-role"
        },
        "StringNotEquals": {
            "aws:sourceVpc": "vpc-987654321"
        }
    }
}

AWS S3 Presigned URL is generated by lambda with execution role get-presigned-url-lambda-role. According to doc

Anyone with valid security credentials can create a presigned URL. However, in order to successfully access an object, the presigned URL must be created by someone who has permission to perform the operation that the presigned URL is based upon.

Therefore get-presigned-url-lambda-role requires policy

{
    "Effect": "Allow",
    "Action": [
        "s3:PutObject"
    ],
    "Resource": [
        "arn:aws:s3::: my-bucket /*",
        "arn:aws:s3::: my-bucket "
    ]
}

I wanted also to test other policies for presigned Urls and added bucket policy like

{
    "Sid": "Deny a presigned URL request if the signature is more than 3 min old",
    "Effect": "Deny",
    "Principal": "*",
    "Action": "s3:*",
    "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
    ],
    "Condition": {
        "NumericGreaterThan": {
            "s3:signatureAge": 180000
        }
    }
}

which prevents using presigned Url if it was generated more than 3 minutes ago. I wanted also to experiment checking version of signature and tried policy

{
    "Sid": "Deny a presigned URL request if the signature is version 4",
    "Effect": "Deny",
    "Principal": "*",
    "Action": "s3:*",
    "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
    ],
    "Condition": {
        "StringEquals": {
            "s3:signatureversion": "AWS4-HMAC-SHA256"
        }
    }
}

and then disaster happened. I lost access to my S3 bucket! I couldn’t view properties of it, list its content, delete or anything. It was due to deny for any action (“Action”: “s3:*”) using signature version 4 (“s3:signatureversion”: “AWS4-HMAC-SHA256”). AWS Console, AWS CLI, AWS SDK all use SigV4. The only way to resolve this issue seemed to get access to root user for AWS account and delete bucket or its policy. As I work in corporation and do not have access to root user, I had to create a ticket to different team to fix bucket and got info that I would need to wait ~7 days for completion (the corporate wolrd :)). I found useful warning on my organisation Confluence

NEVER USE  “Action”: “s3:*” in a Deny statement unless you are supremely confident in your abilities. This may result in you making an S3 bucket inaccessible

But it was too late. The idea came to my mind that maybe it would be possible to use different signature version than v4. And such signature exists – SigV2. It is deprecated and is not supported on all AWS regions but luckily it should work in my region eu-west-1. So it was just a matter of changing signing algorithm from default v4 to older v2. For AWS_CLI it should be possible by command

aws configure set default.s3.signature_version s3

But it didn’t help. I was getting 403 for all my aws s3 commands. So I tried to use Sigv2 in AWS Java SDK (version 1.11.589). I created com.amazonaws.services.s3.AmazonS3 client

ClientConfiguration clientConfiguration = new ClientConfiguration()
    .withProtocol(Protocol.HTTPS)
    .withSignerOverride("AWS3SignerType");

return AmazonS3ClientBuilder.standard()
    .withRegion(“eu-west1”)
    .withCredentials(credentialsProvider)
    .withClientConfiguration(clientConfiguration)
    .build();

by specifying AWS3SignerType in withSignerOverride builder method. But I got 403 when executing

amazonS3.deleteBucketPolicy("my-bucket");

There is a thread on Github which suggests using S3SignerType as signer but I suppose it was supported in some older version of SDK. I tried different signers from com.amazonaws.auth.SignerFactory

public final class SignerFactory {

    public static final String QUERY_STRING_SIGNER = "QueryStringSignerType";
    public static final String VERSION_THREE_SIGNER = "AWS3SignerType";
    public static final String VERSION_FOUR_SIGNER = "AWS4SignerType";
    public static final String VERSION_FOUR_UNSIGNED_PAYLOAD_SIGNER = "AWS4UnsignedPayloadSignerType";
    public static final String NO_OP_SIGNER = "NoOpSignerType";
    private static final String S3_V4_SIGNER = "AWSS3V4SignerType";

but I was still getting 403 or runtime exceptions. I was about to quit but found promising method createSigV2Signer in AmazonS3Client

private S3Signer createSigV2Signer(final Request<?> request,
                                        final String bucketName,
                                        final String key) {
    String resourcePath = "/" +
            ((bucketName != null) ? bucketName + "/" : "") +
            ((key != null) ? key : "");
    return new S3Signer(request.getHttpMethod().toString(), resourcePath);
}

Another method

com.amazonaws.services.s3.AmazonS3Client#createSigner(com.amazonaws.Request<?>, java.lang.String, java.lang.String, boolean)

calls createSigV2Signer in a nested if. Using Intellij IDEA debugger I forced returning S3Signer

And unwanted bucket policy was successfully removed. I regain access to bucket!

Leave a Comment