Skip to main content

CloudFront Distributions Field-Level Encryption Enabled

Overview

This check verifies that Amazon CloudFront distributions have Field-Level Encryption (FLE) enabled. Field-level encryption adds an extra layer of security beyond HTTPS by encrypting specific sensitive data fields (like credit card numbers or personal information) at the edge location, close to the user. This encrypted data stays encrypted as it moves through your application stack and can only be decrypted by applications that have the private key.

Risk

Without field-level encryption, sensitive data in your web forms may be exposed in several places:

  • Origin server logs: Sensitive fields like passwords or payment data could appear in plaintext in application logs
  • Middleware exposure: Data passing through load balancers, proxies, or caching layers remains readable
  • Insider threats: Anyone with access to origin servers can view sensitive user inputs
  • Data breaches: If your origin is compromised, all user-submitted data could be stolen in cleartext

Field-level encryption ensures that even if attackers gain access to your backend systems, they cannot read the encrypted sensitive fields without the private key.

Remediation Steps

Prerequisites

  • Access to the AWS Console with permissions to manage CloudFront distributions
  • An RSA key pair (2048-bit minimum) for encryption/decryption
  • Your application must be able to decrypt data using the AWS Encryption SDK
Required IAM permissions

To configure field-level encryption, you need these permissions:

  • cloudfront:CreatePublicKey
  • cloudfront:CreateFieldLevelEncryptionProfile
  • cloudfront:CreateFieldLevelEncryptionConfig
  • cloudfront:GetDistribution
  • cloudfront:GetDistributionConfig
  • cloudfront:UpdateDistribution
Generate an RSA key pair

If you don't have an RSA key pair, generate one using OpenSSL:

# Generate a 2048-bit RSA private key
openssl genrsa -out private_key.pem 2048

# Extract the public key
openssl rsa -pubout -in private_key.pem -out public_key.pem

Important: Store the private key securely (e.g., in AWS Secrets Manager or a hardware security module). You'll need it to decrypt data at your origin.

AWS Console Method

Field-level encryption setup requires four steps: adding a public key, creating an encryption profile, creating a configuration, and attaching it to your distribution.

Step 1: Add Your Public Key

  1. Open the CloudFront console
  2. In the left navigation, click Public keys
  3. Click Create public key
  4. Enter a Name (e.g., my-fle-public-key)
  5. Paste your public key contents (including the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- lines)
  6. Optionally add a Comment with an expiration date for key rotation tracking
  7. Click Create public key

Step 2: Create a Field-Level Encryption Profile

  1. In the left navigation, click Field-level encryption
  2. Under the Profiles tab, click Create profile
  3. Enter a Profile name (e.g., sensitive-data-profile)
  4. Select your Public key from the dropdown
  5. Enter a Provider name (e.g., MY_PROVIDER) - you'll use this in your decryption code
  6. Under Field patterns, add the field names you want to encrypt:
    • Click Add field pattern
    • Enter the field name exactly as it appears in your form (e.g., creditCardNumber, ssn)
    • You can use wildcards (e.g., card* to match cardNumber, cardCVV)
  7. Click Create profile

Step 3: Create a Field-Level Encryption Configuration

  1. Still on the Field-level encryption page, click the Configurations tab
  2. Click Create configuration
  3. Add a content type mapping:
    • Content type: application/x-www-form-urlencoded (this is fixed)
    • Default profile ID: Select the profile you created
  4. Click Create configuration
  5. Note the Configuration ID - you'll need it for the next step

Step 4: Attach Configuration to Your Distribution

  1. Go to Distributions and click on your distribution ID
  2. Click the Behaviors tab
  3. Select the behavior that handles your sensitive form submissions and click Edit
  4. Verify these settings (required for FLE):
    • Viewer protocol policy: Must be Redirect HTTP to HTTPS or HTTPS only
    • Allowed HTTP methods: Must include POST (select GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE)
  5. Under Field-level encryption, select your configuration from the dropdown
  6. Click Save changes
  7. Wait for the distribution status to change from "Deploying" to "Deployed"
AWS CLI (optional)

Setting up field-level encryption via CLI requires multiple steps. Here's the complete process:

Step 1: Create a public key

First, prepare a JSON file with your public key:

# Read your public key and escape for JSON
PUBLIC_KEY=$(cat public_key.pem)

# Create the request JSON
cat > create-public-key.json << 'EOF'
{
"PublicKeyConfig": {
"CallerReference": "my-fle-key-ref-001",
"Name": "my-fle-public-key",
"EncodedKey": "<paste-your-public-key-here>",
"Comment": "Public key for field-level encryption"
}
}
EOF

Edit create-public-key.json and replace <paste-your-public-key-here> with your actual public key content.

aws cloudfront create-public-key \
--public-key-config file://create-public-key.json \
--region us-east-1

Save the returned Id value as PUBLIC_KEY_ID.

Step 2: Create a field-level encryption profile

cat > create-fle-profile.json << 'EOF'
{
"FieldLevelEncryptionProfileConfig": {
"Name": "sensitive-data-profile",
"CallerReference": "my-fle-profile-ref-001",
"EncryptionEntities": {
"Quantity": 1,
"Items": [
{
"PublicKeyId": "<PUBLIC_KEY_ID>",
"ProviderId": "MY_PROVIDER",
"FieldPatterns": {
"Quantity": 2,
"Items": ["creditCardNumber", "ssn"]
}
}
]
},
"Comment": "Profile for encrypting sensitive form fields"
}
}
EOF

Replace <PUBLIC_KEY_ID> with the ID from step 1.

aws cloudfront create-field-level-encryption-profile \
--field-level-encryption-profile-config file://create-fle-profile.json \
--region us-east-1

Save the returned Id value as PROFILE_ID.

Step 3: Create a field-level encryption configuration

cat > create-fle-config.json << 'EOF'
{
"FieldLevelEncryptionConfig": {
"CallerReference": "my-fle-config-ref-001",
"Comment": "FLE configuration for sensitive data",
"ContentTypeProfileConfig": {
"ForwardWhenContentTypeIsUnknown": false,
"ContentTypeProfiles": {
"Quantity": 1,
"Items": [
{
"Format": "URLEncoded",
"ProfileId": "<PROFILE_ID>",
"ContentType": "application/x-www-form-urlencoded"
}
]
}
},
"QueryArgProfileConfig": {
"ForwardWhenQueryArgProfileIsUnknown": false,
"QueryArgProfiles": {
"Quantity": 0
}
}
}
}
EOF

Replace <PROFILE_ID> with the ID from step 2.

aws cloudfront create-field-level-encryption-config \
--field-level-encryption-config file://create-fle-config.json \
--region us-east-1

Save the returned Id value as FLE_CONFIG_ID.

Step 4: Update your distribution to use the configuration

# Get current distribution config
aws cloudfront get-distribution-config \
--id <DISTRIBUTION_ID> \
--region us-east-1 \
> dist-config.json

# Extract the ETag
ETAG=$(cat dist-config.json | jq -r '.ETag')

# Modify the config to add field-level encryption
# Extract just the DistributionConfig and add the FLE ID
cat dist-config.json | jq '.DistributionConfig | .DefaultCacheBehavior.FieldLevelEncryptionId = "<FLE_CONFIG_ID>"' > updated-config.json

# Update the distribution
aws cloudfront update-distribution \
--id <DISTRIBUTION_ID> \
--if-match "$ETAG" \
--distribution-config file://updated-config.json \
--region us-east-1

Replace <DISTRIBUTION_ID> with your CloudFront distribution ID and <FLE_CONFIG_ID> with the configuration ID from step 3.

CloudFormation (optional)
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFront distribution with field-level encryption enabled

Parameters:
OriginDomainName:
Type: String
Description: The domain name of your origin server

PublicKeyEncodedValue:
Type: String
Description: The PEM-encoded RSA public key (including BEGIN/END lines)
NoEcho: true

SensitiveFieldPatterns:
Type: CommaDelimitedList
Default: "creditCardNumber,ssn"
Description: Comma-separated list of field names to encrypt

Resources:
FLEPublicKey:
Type: AWS::CloudFront::PublicKey
Properties:
PublicKeyConfig:
CallerReference: !Sub "${AWS::StackName}-public-key-ref"
Name: !Sub "${AWS::StackName}-fle-public-key"
EncodedKey: !Ref PublicKeyEncodedValue
Comment: "Public key for field-level encryption"

FLEProfile:
Type: AWS::CloudFront::FieldLevelEncryptionProfile
Properties:
FieldLevelEncryptionProfileConfig:
Name: !Sub "${AWS::StackName}-fle-profile"
CallerReference: !Sub "${AWS::StackName}-profile-ref"
Comment: "Profile for encrypting sensitive form fields"
EncryptionEntities:
Items:
- PublicKeyId: !Ref FLEPublicKey
ProviderId: "MY_PROVIDER"
FieldPatterns:
Items: !Ref SensitiveFieldPatterns

FLEConfig:
Type: AWS::CloudFront::FieldLevelEncryptionConfig
Properties:
FieldLevelEncryptionConfig:
Comment: "FLE configuration for sensitive data"
ContentTypeProfileConfig:
ForwardWhenContentTypeIsUnknown: false
ContentTypeProfiles:
Items:
- ContentType: "application/x-www-form-urlencoded"
Format: "URLEncoded"
ProfileId: !Ref FLEProfile
QueryArgProfileConfig:
ForwardWhenQueryArgProfileIsUnknown: false

CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Comment: "Distribution with field-level encryption"
DefaultRootObject: index.html

Origins:
- Id: PrimaryOrigin
DomainName: !Ref OriginDomainName
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: https-only
OriginSSLProtocols:
- TLSv1.2

DefaultCacheBehavior:
TargetOriginId: PrimaryOrigin
ViewerProtocolPolicy: redirect-to-https
AllowedMethods:
- GET
- HEAD
- OPTIONS
- PUT
- POST
- PATCH
- DELETE
CachedMethods:
- GET
- HEAD
FieldLevelEncryptionId: !Ref FLEConfig
ForwardedValues:
QueryString: true
Cookies:
Forward: all
Compress: true

ViewerCertificate:
CloudFrontDefaultCertificate: true

Outputs:
DistributionId:
Description: CloudFront Distribution ID
Value: !Ref CloudFrontDistribution

DistributionDomainName:
Description: CloudFront Distribution Domain Name
Value: !GetAtt CloudFrontDistribution.DomainName

FLEConfigId:
Description: Field-Level Encryption Configuration ID
Value: !Ref FLEConfig

Deploy with:

aws cloudformation deploy \
--template-file cloudfront-fle.yaml \
--stack-name cloudfront-fle-distribution \
--parameter-overrides \
OriginDomainName=my-origin.example.com \
PublicKeyEncodedValue="$(cat public_key.pem)" \
SensitiveFieldPatterns="creditCardNumber,ssn,dateOfBirth" \
--region us-east-1
Terraform (optional)
# variables.tf
variable "origin_domain_name" {
description = "The domain name of your origin server"
type = string
}

variable "public_key_pem" {
description = "Path to the PEM-encoded public key file"
type = string
}

variable "sensitive_field_patterns" {
description = "List of field names/patterns to encrypt"
type = list(string)
default = ["creditCardNumber", "ssn"]
}

variable "provider_id" {
description = "Provider ID for decryption (used in your origin application)"
type = string
default = "MY_PROVIDER"
}

# main.tf
resource "aws_cloudfront_public_key" "fle_key" {
name = "fle-public-key"
comment = "Public key for field-level encryption"
encoded_key = file(var.public_key_pem)
}

resource "aws_cloudfront_field_level_encryption_profile" "fle_profile" {
name = "sensitive-data-profile"
comment = "Profile for encrypting sensitive form fields"

encryption_entities {
items {
public_key_id = aws_cloudfront_public_key.fle_key.id
provider_id = var.provider_id

field_patterns {
items = var.sensitive_field_patterns
}
}
}
}

resource "aws_cloudfront_field_level_encryption_config" "fle_config" {
comment = "FLE configuration for sensitive data"

content_type_profile_config {
forward_when_content_type_is_unknown = false

content_type_profiles {
items {
content_type = "application/x-www-form-urlencoded"
format = "URLEncoded"
profile_id = aws_cloudfront_field_level_encryption_profile.fle_profile.id
}
}
}

query_arg_profile_config {
forward_when_query_arg_profile_is_unknown = false
}
}

resource "aws_cloudfront_distribution" "main" {
enabled = true
comment = "Distribution with field-level encryption"
default_root_object = "index.html"

origin {
domain_name = var.origin_domain_name
origin_id = "primary-origin"

custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}

default_cache_behavior {
target_origin_id = "primary-origin"

# Required settings for field-level encryption
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]

# Attach the field-level encryption configuration
field_level_encryption_id = aws_cloudfront_field_level_encryption_config.fle_config.id

forwarded_values {
query_string = true
cookies {
forward = "all"
}
}

compress = true
}

restrictions {
geo_restriction {
restriction_type = "none"
}
}

viewer_certificate {
cloudfront_default_certificate = true
}

tags = {
Environment = "production"
}
}

# outputs.tf
output "distribution_id" {
description = "CloudFront Distribution ID"
value = aws_cloudfront_distribution.main.id
}

output "distribution_domain_name" {
description = "CloudFront Distribution Domain Name"
value = aws_cloudfront_distribution.main.domain_name
}

output "fle_config_id" {
description = "Field-Level Encryption Configuration ID"
value = aws_cloudfront_field_level_encryption_config.fle_config.id
}

Deploy with:

terraform init
terraform apply \
-var="origin_domain_name=my-origin.example.com" \
-var="public_key_pem=./public_key.pem" \
-var='sensitive_field_patterns=["creditCardNumber", "ssn", "dateOfBirth"]'

Verification

After enabling field-level encryption:

  1. Go to the CloudFront console
  2. Click on your distribution
  3. Go to the Behaviors tab
  4. Click on your behavior and verify that Field-level encryption shows your configuration

To test the encryption is working:

  1. Submit a form through your CloudFront distribution with a field that should be encrypted
  2. Check your origin server logs - the encrypted field should appear as a long base64-encoded string, not the original value
CLI verification

Check if FLE is configured on your distribution:

aws cloudfront get-distribution \
--id <DISTRIBUTION_ID> \
--query 'Distribution.DistributionConfig.DefaultCacheBehavior.FieldLevelEncryptionId' \
--output text \
--region us-east-1

Expected output: Your FLE configuration ID (not empty or None)

List all FLE configurations in your account:

aws cloudfront list-field-level-encryption-configs \
--region us-east-1

Re-run the Prowler check:

prowler aws --checks cloudfront_distributions_field_level_encryption_enabled

Additional Resources

Notes

  • Application changes required: Your origin application must be updated to decrypt the encrypted fields using the AWS Encryption SDK and your private key. This is not just a CloudFront configuration change.

  • Supported content types: Field-level encryption only works with application/x-www-form-urlencoded content type. JSON payloads are not supported.

  • Field limits: You can encrypt up to 10 fields per request.

  • HTTPS required: Field-level encryption requires HTTPS between viewers and CloudFront. Ensure your viewer protocol policy is set to redirect-to-https or https-only.

  • HTTP methods: The distribution must allow POST/PUT methods for forms to be submitted. Field-level encryption applies to request body data.

  • Key rotation: Plan for key rotation by including expiration dates in your public key comments. When rotating, create a new key/profile before removing the old one.

  • Private key security: Store your private key in a secure location like AWS Secrets Manager or a hardware security module (HSM). Never commit it to source control.

  • Not a replacement for encryption at rest: Field-level encryption protects data in transit and processing. You should still encrypt sensitive data at rest in your databases.

  • Deployment time: Changes to CloudFront distributions take 5-15 minutes to propagate globally.

  • Cost considerations: Field-level encryption does not have additional charges beyond standard CloudFront pricing, but the encryption/decryption processing may slightly increase latency.