Search code examples
pythonamazon-web-servicesamazon-s3amazon-cloudfront

Access Denied on Cloudfront signed URL for a private S3 bucket


I am getting Access Denied 403 when trying to access a signed Cloudfront URL to an S3 bucket. The bucket is private and has blocked all public access, and I also have an OAC defined that grants permissions to CF to the S3 bucket.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<bucket_name>/*",
            "Condition": {
                "StringEquals": {
                    "aws:SourceArn": "arn:aws:cloudfront::<id>:distribution/<id>"
                }
            }
        }
    ] }

I'm signing these URLs using the Python SDK

def get_rsa_signer(cls, message):
   kms_client = boto3.client("kms")
   response = kms_client.sign(
   KeyId=secrets_manager.get_env("CLOUDFRONT_KMS_PRIVATE_KEY_ID"),
   Message=message,
   MessageType="RAW",
   SigningAlgorithm="RSASSA_PKCS1_V1_5_SHA_256",
        )
  return response["Signature"]
 
cloudfront_signer = CloudFrontSigner( secrets_manager.get_env("CLOUDFRONT_PUBLIC_KEY_ID"),MediaStorage.get_rsa_signer)
url = cloudfront_signer.generate_presigned_url(f"{settings.AWS_CLOUDFRONT_DOMAIN}/{file_key}",
date_less_than=timezone.now() + timedelta(days=7),
    )

These are the settings on the OAC

resource "aws_cloudfront_origin_access_control" "cloudfront_backend_bucket" {
 name                    = "${var.environment}-cloudfront_backend_bucket"
 description             = "OAC for S3 bucket access"
 signing_behavior        = "always"
 signing_protocol        = "sigv4"
 origin_access_control_origin_type = "s3"
}

The public key ID on the URL is correct but I have no way of verifying whether the signature is correct, though I think the error in this case should be different.


Solution

  • It seems that the only problem is that I wasn't URL encoding the url that was to be signed. The solution was:

    url = cloudfront_signer.generate_presigned_url(f"{settings.AWS_CLOUDFRONT_DOMAIN}/{urllib.parse.quote(file_key)}",
    date_less_than=timezone.now() + timedelta(days=7),
        )