├── .github └── PULL_REQUEST_TEMPLATE.md ├── LICENSE ├── NOTICE ├── README.md ├── images └── secure-ingress-architecture.png ├── lambda ├── fle_decrypt_data.py ├── fle_decrypt_data.zip ├── fle_decrypt_data.zip.sha1.txt └── package_notes.md └── templates └── fle-sample.yaml /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Field Level Encryption Sample 2 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enhance the Security of Sensitive Customer Data by Using Amazon CloudFront Field-Level Encryption 2 | 3 | ***The complete post for this solution is [available on the AWS Security Blog](https://aws.amazon.com/blogs/security/how-to-enhance-the-security-of-sensitive-customer-data-by-using-amazon-cloudfront-field-level-encryption/).*** 4 | 5 | ### How CloudFront field-level encryption works 6 | 7 | Many web applications collect and store data from users as those users interact with the applications. For example, a travel booking website may ask for your passport number and less sensitive data such as your food preferences. This data is transmitted to web servers and also might travel among a number of services to perform tasks. However, this also means that your sensitive information may need to be accessed by only a small subset of these services (most other services do not need to access your data). 8 | User data is often stored in a database for retrieval at a later time. One approach to protecting stored sensitive data is to cautiously configure and code each service to ensure that sensitive data is protected. For example, you can develop safeguards in logging functionality to ensure sensitive data is masked or removed. This can add complexity to your code base and limit performance. 9 | 10 | Field-level encryption addresses this problem by ensuring sensitive data is encrypted at CloudFront edge locations. Sensitive data fields in HTTPS form POSTs are automatically encrypted with a user-provided public RSA key. After the data is encrypted, other systems in your architecture see only ciphertext. Even if this ciphertext unintentionally becomes externally available, the data is cryptographically protected and only designated systems with access to the private RSA key can decrypt the sensitive data. 11 | It is critical to secure private RSA key material to prevent unauthorized access to the protected data. Management of cryptographic key material is a larger topic that is out of scope for this blog post, but should be carefully considered when implementing encryption in your applications. For example, in this blog post we store private key material as a secure string in the Amazon EC2 Systems Manager Parameter Store. The Parameter Store provides a centralized location for managing your configuration data such as plaintext data (such as database strings) or secrets (such as passwords) that are encrypted using AWS Key Management Service (AWS KMS). You may have an existing key management system in place that you can use, or you can use AWS CloudHSM. CloudHSM is a cloud-based hardware security module (HSM) that enables you to easily generate and use your own encryption keys in the AWS Cloud. 12 | 13 | To illustrate field-level encryption, let's look at a simple form submission where Name and Phone values are sent to a web server using an HTTP POST. A typical form POST would contain data such as the following. 14 | 15 | ~~~ 16 | POST / HTTP/1.1 17 | Host: example.com 18 | Content-Type: application/x-www-form-urlencoded 19 | Content-Length:60 20 | 21 | Name=Jane+Doe&Phone=404-555-0150 22 | 23 | 24 | Instead of taking this typical approach, field-level encryption converts this data similar to the following. 25 | POST / HTTP/1.1 26 | Host: example.com 27 | Content-Type: application/x-www-form-urlencoded 28 | Content-Length: 1713 29 | 30 | Name=Jane+Doe&Phone=AYABeHxZ0ZqWyysqxrB5pEBSYw4AAA... 31 | ~~~ 32 | 33 | To further demonstrate field-level encryption in action, this blog post includes a sample serverless application that you can deploy by using a CloudFormation template, which creates an application environment using CloudFront, Amazon API Gateway and Lambda. The sample application is only intended to demonstrate field-level encryption functionality and is not intended for production use. The following diagram depicts the architecture and data flow of this sample application. 34 | 35 | ### Architecture Overview 36 | 37 | ![architecture diagram](images/secure-ingress-architecture.png) 38 | 39 | Here is how the sample solution works: 40 | 41 | 1. An application user submits an HTML form page with sensitive data, generating an HTTPS POST to CloudFront. 42 | 2. Field-level encryption intercepts the form POST and encrypts sensitive data with the public RSA key and replaces fields in the form post with encrypted ciphertext. The form POST ciphertext is then sent to origin servers. 43 | 3. The serverless application accepts the form post data containing cipher text where sensitive data would normally reside. If a malicious user were able to compromise your application and gain access to your data, such as the contents of a form, this data is encrypted. 44 | 4. AWS Lambda stores data in a DynamoDB table, leaving sensitive data to remain safely encrypted at rest. 45 | 5. An administrator the AWS Console and a Lambda function to view the sensitive data. 46 | 6. During the session, the administrator retrieves cipher text from the DynamoDB table. 47 | 7. An administrator decrypts sensitive data using private key material stored in EC2 Systems Manager Parameter Store. 48 | 8. Finally, decrypted sensitive data is transmitted over SSL/TLS via the AWS console to the administrator for review. 49 | -------------------------------------------------------------------------------- /images/secure-ingress-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/field-level-encryption-sample/2cd906fe9ba040cc2fe5b1b7aaf5e7a9b5a20964/images/secure-ingress-architecture.png -------------------------------------------------------------------------------- /lambda/fle_decrypt_data.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You 4 | # may not use this file except in compliance with the License. A copy of 5 | # the License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is 10 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | # ANY KIND, either express or implied. See the License for the specific 12 | # language governing permissions and limitations under the License. 13 | 14 | import os 15 | import json 16 | import boto3 17 | 18 | import aws_encryption_sdk 19 | from aws_encryption_sdk.internal.crypto import WrappingKey 20 | from aws_encryption_sdk.key_providers.raw import RawMasterKeyProvider 21 | from aws_encryption_sdk.identifiers import WrappingAlgorithm, EncryptionKeyType 22 | 23 | from Crypto.PublicKey import RSA 24 | import base64 25 | 26 | PrivateKeyPath = os.environ['PRIVATEKEYSSMPATH'] 27 | DBTableName = os.environ['TABLENAME'] 28 | provider_id = os.environ['PROVIDERID'] 29 | PublicKeyName = os.environ['PUBLICKEYNAME'] 30 | 31 | def decrypt_data(event, context): 32 | class SIFPrivateMasterKeyProvider(RawMasterKeyProvider): 33 | provider_id = provider_id 34 | 35 | def __new__(cls, *args, **kwargs): 36 | obj = super(SIFPrivateMasterKeyProvider, cls).__new__(cls) 37 | return obj 38 | 39 | def __init__(self, private_key_id, private_key_text): 40 | RawMasterKeyProvider.__init__(self) 41 | 42 | private_key = RSA.importKey(private_key_text) 43 | self._key = private_key.exportKey() 44 | 45 | RawMasterKeyProvider.add_master_key(self, private_key_id) 46 | 47 | def _get_raw_key(self, key_id): 48 | return WrappingKey( 49 | wrapping_algorithm=WrappingAlgorithm.RSA_OAEP_SHA256_MGF1, 50 | wrapping_key=self._key, 51 | wrapping_key_type=EncryptionKeyType.PRIVATE 52 | ) 53 | 54 | def DecryptField(private_key, field_data): 55 | # add padding if needed base64 decoding 56 | field_data = field_data + '=' * (-len(field_data) % 4) 57 | # base64-decode to get binary ciphertext 58 | ciphertext = base64.b64decode(field_data) 59 | # decrypt ciphertext into plaintext 60 | plaintext, header = aws_encryption_sdk.decrypt( 61 | source=ciphertext, 62 | key_provider=sif_private_master_key_provider 63 | ) 64 | return plaintext 65 | 66 | # retrieve private key from Parameter Store into local memory 67 | ssmclient = boto3.client('ssm') 68 | ssmresponse = ssmclient.get_parameter( 69 | Name=PrivateKeyPath, 70 | WithDecryption=True 71 | ) 72 | private_key_text = ssmresponse['Parameter']['Value'] 73 | sif_private_master_key_provider = SIFPrivateMasterKeyProvider(PublicKeyName, private_key_text) 74 | 75 | # connect to DynamoDB table 76 | ddbclient = boto3.client('dynamodb') 77 | DBResponse = ddbclient.scan(TableName = DBTableName) 78 | 79 | d = [] 80 | for i in DBResponse['Items']: 81 | # Phone fields are encrypted, each field will require decryption 82 | PhoneDecrypted = DecryptField( private_key_text, i['Phone']['S'] ) 83 | d.append( ['Name: ' + i['Name']['S'], 'Email: ' + i['Email']['S'], 84 | 'Phone Encrypted: ' + i['Phone']['S'][:30] + '...', 'Phone Decrypted: ' + PhoneDecrypted ] ) 85 | 86 | # remove private key from local memory 87 | private_key_text = None 88 | 89 | # return result 90 | return d 91 | 92 | def main(): 93 | decrypt_data ("test", "test") 94 | 95 | if __name__ == "__main__": 96 | main() 97 | -------------------------------------------------------------------------------- /lambda/fle_decrypt_data.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/field-level-encryption-sample/2cd906fe9ba040cc2fe5b1b7aaf5e7a9b5a20964/lambda/fle_decrypt_data.zip -------------------------------------------------------------------------------- /lambda/fle_decrypt_data.zip.sha1.txt: -------------------------------------------------------------------------------- 1 | 000497a7c5cb1a96fd96e2b476b206868ee95311 fle_decrypt_data.zip 2 | -------------------------------------------------------------------------------- /lambda/package_notes.md: -------------------------------------------------------------------------------- 1 | 2 | ### Building Lambda Decrypt Package 3 | 4 | ***Use these steps as a general guideline if you need to build your own Python decryption function package for Lambda.*** 5 | 6 | #### Launch documented Amazon Linux AMI: 7 | http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html 8 | 9 | #### ssh and install environment: 10 | 11 | ~~~~ 12 | mkdir fle 13 | sudo yum -y install gcc 14 | virtualenv fle 15 | source fle/bin/activate 16 | pip install --upgrade pip 17 | pip install cryptography aws_encryption_sdk pycrypto 18 | 19 | cd fle/lib/python2.7/site-packages/; zip -r ~/fle_decrypt_data.zip * .* 20 | 21 | cd fle/lib64/python2.7/site-packages/; zip -r ~/fle_decrypt_data.zip * .* 22 | ~~~~ 23 | 24 | #### Lastly add the function code to the zip from here: 25 | 26 | https://github.com/aws-samples/field-level-encryption-sample/blob/master/lambda/fle_decrypt_data.py 27 | 28 | #### More info: 29 | http://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html 30 | -------------------------------------------------------------------------------- /templates/fle-sample.yaml: -------------------------------------------------------------------------------- 1 | Description: > 2 | Field-level encryption sample 3 | You will be billed for the AWS resources used if you create a stack from this template. 4 | **NOTICE** Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | Licensed under the Apache License, Version 2.0 (the "License"). 6 | You may not use this file except in compliance with the License. 7 | A copy of the License is located at http://www.apache.org/licenses/LICENSE-2.0 or in the "license" file accompanying this file. 8 | This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 9 | either express or implied. See the License for the specific language governing permissions and limitations under the License. 10 | 11 | Parameters: 12 | 13 | ProviderID: 14 | Description: Provider ID used in Field-level encryption configuration in CloudFront (letters and numbers only, no special characters) 15 | Type: String 16 | Default: FLEDemo 17 | AllowedPattern: ^[0-9a-zA-Z]*$ 18 | ConstraintDescription: Letters and numbers only, no special characters 19 | PublicKeyName: 20 | Description: Name assigned to public key in Field-level encryption configuration in CloudFront (letters and numbers only, no special characters) 21 | Type: String 22 | Default: DemoPublicKey 23 | AllowedPattern: ^[0-9a-zA-Z]*$ 24 | ConstraintDescription: Letters and numbers only, no special characters 25 | PrivateKeySSMPath: 26 | Description: Parameter Store path where encrypted private key is stored 27 | Type: String 28 | Default: /cloudfront/field-encryption-sample/private-key 29 | AllowedPattern: ^/[0-9a-zA-Z]+([0-9a-zA-Z/-]*[0-9a-zA-Z])*$ 30 | ConstraintDescription: Must start with / and contain only letters, numbers, hyphens (-) and slashes (/) 31 | ArtifactsBucket: 32 | Description: S3 bucket with artifact files (Lambda functions, templates, html files, etc.) 33 | Type: String 34 | Default: awsiammedia 35 | AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$ 36 | ConstraintDescription: ArtifactsBucket S3 bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-). 37 | ArtifactsPrefix: 38 | Description: Path in the S3 bucket containing artifact files 39 | Type: String 40 | Default: public/sample/CloudFrontFieldLevelEncryption/ 41 | AllowedPattern: ^[0-9a-zA-Z-/]*$ 42 | ConstraintDescription: ArtifactsPrefix key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/). 43 | 44 | Metadata: 45 | AWS::CloudFormation::Interface: 46 | ParameterGroups: 47 | - Label: 48 | default: Field-level encryption (FLE) configuration 49 | Parameters: 50 | - ProviderID 51 | - PublicKeyName 52 | - PrivateKeySSMPath 53 | - Label: 54 | default: Artifacts configuration 55 | Parameters: 56 | - ArtifactsBucket 57 | - ArtifactsPrefix 58 | Resources: 59 | FieldEncryptionKey: 60 | Type: "AWS::KMS::Key" 61 | Properties: 62 | Description: "field-encryption-sample" 63 | KeyPolicy: 64 | Version: "2012-10-17" 65 | Id: "field-encryption-sample" 66 | Statement: 67 | - 68 | Sid: 'Enable IAM User Permissions' 69 | Effect: Allow 70 | Principal: 71 | AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root' 72 | Action: 73 | - kms:* 74 | Resource: '*' 75 | 76 | FLEApi: 77 | Type: AWS::ApiGateway::RestApi 78 | Properties: 79 | Name: FLEApi 80 | Body: 81 | swagger: 2.0 82 | info: 83 | version: "2017-11-23T15:06:37Z" 84 | title: "forms" 85 | securityDefinitions: 86 | api_key: 87 | type: apiKey 88 | name: x-api-key 89 | in: header 90 | paths: 91 | /: 92 | get: 93 | produces: 94 | - "application/json" 95 | security: 96 | - api_key: [] 97 | responses: 98 | "200": 99 | description: "200 response" 100 | schema: 101 | $ref: "#/definitions/Empty" 102 | x-amazon-apigateway-integration: 103 | responses: 104 | default: 105 | statusCode: "200" 106 | uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GETFunction.Arn}/invocations" 107 | passthroughBehavior: "when_no_match" 108 | httpMethod: "POST" 109 | contentHandling: "CONVERT_TO_TEXT" 110 | type: "aws_proxy" 111 | post: 112 | produces: 113 | - "application/json" 114 | security: 115 | - api_key: [] 116 | responses: 117 | "200": 118 | description: "200 response" 119 | schema: 120 | $ref: "#/definitions/Empty" 121 | headers: 122 | Access-Control-Allow-Origin: 123 | type: "string" 124 | x-amazon-apigateway-integration: 125 | responses: 126 | default: 127 | statusCode: "200" 128 | responseParameters: 129 | method.response.header.Access-Control-Allow-Origin: "'*'" 130 | uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${POSTFunction.Arn}/invocations" 131 | passthroughBehavior: "when_no_match" 132 | httpMethod: "POST" 133 | contentHandling: "CONVERT_TO_TEXT" 134 | type: "aws_proxy" 135 | options: 136 | consumes: 137 | - "application/json" 138 | produces: 139 | - "application/json" 140 | responses: 141 | "200": 142 | description: "200 response" 143 | schema: 144 | $ref: "#/definitions/Empty" 145 | headers: 146 | Access-Control-Allow-Origin: 147 | type: "string" 148 | Access-Control-Allow-Methods: 149 | type: "string" 150 | Access-Control-Allow-Headers: 151 | type: "string" 152 | x-amazon-apigateway-integration: 153 | responses: 154 | default: 155 | statusCode: "200" 156 | responseParameters: 157 | method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'" 158 | method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'" 159 | method.response.header.Access-Control-Allow-Origin: "'*'" 160 | passthroughBehavior: "when_no_match" 161 | requestTemplates: 162 | application/json: "{\"statusCode\": 200}" 163 | type: "mock" 164 | definitions: 165 | Empty: 166 | type: "object" 167 | title: "Empty Schema" 168 | 169 | FLEApiKey: 170 | Type: "AWS::ApiGateway::ApiKey" 171 | DependsOn: 172 | - "FLEApiDeployment" 173 | - "FLEApi" 174 | Properties: 175 | Name: "FLEApiKey" 176 | Description: "FLE CloudFormation API Key V1" 177 | Enabled: "true" 178 | StageKeys: 179 | - RestApiId: !Ref "FLEApi" 180 | StageName: "prod" 181 | FLEusagePlan: 182 | Type: AWS::ApiGateway::UsagePlan 183 | DependsOn: 184 | - "FLEApiDeployment" 185 | - "FLEApi" 186 | Properties: 187 | ApiStages: 188 | - ApiId: !Ref 'FLEApi' 189 | Stage: "prod" 190 | Description: FLE Demo Usage Plan 191 | FLEusagePlanKey: 192 | Type: "AWS::ApiGateway::UsagePlanKey" 193 | Properties : 194 | KeyId: !Ref 'FLEApiKey' 195 | KeyType: API_KEY 196 | UsagePlanId: !Ref 'FLEusagePlan' 197 | 198 | GetAPIKey: 199 | Type: "Custom::GetApiKey" 200 | Version: "1.0" 201 | Properties: 202 | ServiceToken: !GetAtt GetAPIKeyFunction.Arn 203 | Region: !Ref "AWS::Region" 204 | GetAPIKeyFunction: 205 | Type: AWS::Lambda::Function 206 | DependsOn: 207 | - "FLEApiKey" 208 | Properties: 209 | Description: Return API Gateway Key 210 | Handler: index.handler 211 | Runtime: python3.6 212 | Environment: 213 | Variables: 214 | FLEAPIKEY: !Ref FLEApiKey 215 | Role: !GetAtt GetAPIKeyExecutionRole.Arn 216 | Timeout: 60 217 | Code: 218 | ZipFile: | 219 | import cfnresponse 220 | import os 221 | import boto3 222 | from botocore.exceptions import ClientError 223 | import json 224 | import logging 225 | logger = logging.getLogger() 226 | logger.setLevel(logging.INFO) 227 | client = boto3.client('apigateway') 228 | FLEApiKey = os.environ['FLEAPIKEY'] 229 | def handler(event, context): 230 | responseData = {} 231 | try: 232 | logger.info('Received event: {}'.format(json.dumps(event))) 233 | # Assume failure unless we obtain an api key 234 | responseStatus = cfnresponse.FAILED 235 | if event['RequestType'] == 'Delete': 236 | responseStatus = cfnresponse.SUCCESS 237 | elif event['RequestType'] == 'Create' or event['RequestType'] == 'Update': 238 | # Try to obtain FLE api key 239 | response = client.get_api_key(apiKey=FLEApiKey, includeValue=True) 240 | responseData = {'APIKeyValue': response['value']} 241 | responseStatus = cfnresponse.SUCCESS 242 | except ClientError as e: 243 | logger.error('Error: {}'.format(e)) 244 | responseStatus = cfnresponse.FAILED 245 | # Log the response status 246 | logger.info('Returning response status of: {}'.format(responseStatus)) 247 | # Send result to stack 248 | cfnresponse.send(event, context, responseStatus, responseData) 249 | GetAPIKeyExecutionRole: 250 | Type: AWS::IAM::Role 251 | Properties: 252 | AssumeRolePolicyDocument: 253 | Version: 2012-10-17 254 | Statement: 255 | - Effect: Allow 256 | Principal: 257 | Service: lambda.amazonaws.com 258 | Action: sts:AssumeRole 259 | Policies: 260 | - PolicyName: AllowGetFLEApiKey 261 | PolicyDocument: 262 | Version: '2012-10-17' 263 | Statement: 264 | - Effect: Allow 265 | Action: 266 | - logs:CreateLogGroup 267 | - logs:CreateLogStream 268 | - logs:PutLogEvents 269 | - logs:DescribeLogStreams 270 | Resource: arn:aws:logs:*:*:* 271 | - Effect: Allow 272 | Action: 273 | - apigateway:GET 274 | Resource: !Sub 'arn:aws:apigateway:${AWS::Region}::/apikeys/${FLEApiKey}' 275 | 276 | FLEApiDeployment: 277 | Type: AWS::ApiGateway::Deployment 278 | Properties: 279 | Description: Prod deployment for FLE API 280 | RestApiId: !Ref FLEApi 281 | StageName: "prod" 282 | 283 | FLEApiPOSTFunctionPermissions: 284 | Type: AWS::Lambda::Permission 285 | Properties: 286 | Action: lambda:InvokeFunction 287 | FunctionName: !Ref POSTFunction 288 | Principal: apigateway.amazonaws.com 289 | SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${FLEApi}/*/POST/' 290 | 291 | FLEApiGETFunctionPermissions: 292 | Type: AWS::Lambda::Permission 293 | Properties: 294 | Action: lambda:InvokeFunction 295 | FunctionName: !Ref GETFunction 296 | Principal: apigateway.amazonaws.com 297 | SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${FLEApi}/*/GET/' 298 | 299 | ContactsDynamoDBTable: 300 | Type: AWS::DynamoDB::Table 301 | Properties: 302 | AttributeDefinitions: 303 | - 304 | AttributeName: "Email" 305 | AttributeType: "S" 306 | KeySchema: 307 | - 308 | AttributeName: "Email" 309 | KeyType: "HASH" 310 | ProvisionedThroughput: 311 | ReadCapacityUnits: "5" 312 | WriteCapacityUnits: "5" 313 | 314 | GETFunctionExecutionRole: 315 | Type: AWS::IAM::Role 316 | Properties: 317 | AssumeRolePolicyDocument: 318 | Version: '2012-10-17' 319 | Statement: 320 | - Effect: Allow 321 | Principal: 322 | Service: 323 | - lambda.amazonaws.com 324 | Action: 325 | - sts:AssumeRole 326 | Path: "/" 327 | Policies: 328 | - PolicyName: Allow-Logging-Into-CWL 329 | PolicyDocument: 330 | Version: '2012-10-17' 331 | Statement: 332 | - Effect: Allow 333 | Action: 334 | - logs:CreateLogGroup 335 | - logs:CreateLogStream 336 | - logs:PutLogEvents 337 | - logs:DescribeLogStreams 338 | Resource: arn:aws:logs:*:*:* 339 | GETFunction: 340 | Type: AWS::Lambda::Function 341 | Properties: 342 | Handler: index.lambda_handler 343 | Role: !GetAtt GETFunctionExecutionRole.Arn 344 | Runtime: python3.6 345 | Timeout: 10 346 | MemorySize: 128 347 | Code: 348 | ZipFile: | 349 | import json 350 | def html_template(): 351 | template=""" 352 | 353 | 354 | Amazon CloudFront Field-Level Encryption - Demo 368 |
369 |

Field-Level Encryption Demo

370 |

This application demonstrates Amazon CloudFront Field-Level Encryption feature

371 |
372 |
373 |

 

 

 

374 |
375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 |
Fill out the form below
Full Name:
Email Address:
Phone Number:
 
385 |
386 |
387 | 388 | 389 | """ 390 | return template 391 | def respond(err, res=None): 392 | return { 393 | 'statusCode': '400' if err else '200', 394 | 'body': err.message if err else res, 395 | 'headers': { 396 | 'Content-Type': 'text/html', 397 | 'Cache-Control': 'max-age=15, public' 398 | }, 399 | } 400 | def lambda_handler(event, context): 401 | print("Received event: " + json.dumps(event, indent=2)) 402 | RESPONSE = html_template() 403 | return respond(None, RESPONSE) 404 | 405 | POSTFunctionExecutionRole: 406 | Type: AWS::IAM::Role 407 | Properties: 408 | AssumeRolePolicyDocument: 409 | Version: '2012-10-17' 410 | Statement: 411 | - Effect: Allow 412 | Principal: 413 | Service: 414 | - lambda.amazonaws.com 415 | Action: 416 | - sts:AssumeRole 417 | Path: "/" 418 | Policies: 419 | - PolicyName: Allow-Logging-Into-CWL 420 | PolicyDocument: 421 | Version: '2012-10-17' 422 | Statement: 423 | - Effect: Allow 424 | Action: 425 | - logs:CreateLogGroup 426 | - logs:CreateLogStream 427 | - logs:PutLogEvents 428 | - logs:DescribeLogStreams 429 | Resource: arn:aws:logs:*:*:* 430 | - PolicyName: Allow-Put-Into-DynamoDB 431 | PolicyDocument: 432 | Version: '2012-10-17' 433 | Statement: 434 | - Effect: Allow 435 | Action: 436 | - dynamodb:PutItem 437 | Resource: !Sub '${ContactsDynamoDBTable.Arn}' 438 | POSTFunction: 439 | Type: AWS::Lambda::Function 440 | Properties: 441 | Handler: index.lambda_handler 442 | Role: !GetAtt POSTFunctionExecutionRole.Arn 443 | Runtime: python3.6 444 | Environment: 445 | Variables: 446 | TABLENAME: !Ref ContactsDynamoDBTable 447 | Timeout: 10 448 | MemorySize: 128 449 | Code: 450 | ZipFile: | 451 | import json 452 | import urllib 453 | import boto3 454 | import os 455 | def html_template(): 456 | template=""" 457 | 458 | 459 | Amazon CloudFront Field-Level Encryption - Demo

Contact Info

472 | 473 | 474 | 475 | 476 |
InputValue
name:##NAME##
email:##EMAIL##
phone (encrypted):##PHONE##
477 | """ 478 | return template 479 | def respond(err, res=None): 480 | return { 481 | 'statusCode': '400' if err else '200', 482 | 'body': err.message if err else res, 483 | 'headers': { 484 | 'Content-Type': 'text/html', 485 | }, 486 | } 487 | def lambda_handler(event, context): 488 | print("Received event: " + json.dumps(event, indent=2)) 489 | body = urllib.parse.unquote(event['body']) 490 | RowList = body.split('&') 491 | for Row in RowList: 492 | NameValuePair = Row.split('=') 493 | FieldName = NameValuePair[0] 494 | FieldValue = NameValuePair[1] 495 | if FieldName == 'name': 496 | Name = FieldValue 497 | elif FieldName == 'email': 498 | Email = FieldValue 499 | elif FieldName == 'phone': 500 | Phone = FieldValue 501 | if (len(Phone)<200): 502 | RESPONSE="ERROR: please check Field-level encryption configuration in CloudFront" 503 | else: 504 | DBTableName = os.environ['TABLENAME'] 505 | ddbclient = boto3.client('dynamodb') 506 | DBResponse = ddbclient.put_item( 507 | TableName = DBTableName, 508 | Item = { 509 | 'Email': {'S': Email}, 510 | 'Phone': {'S': Phone}, 511 | 'Name': {'S': Name } 512 | } 513 | ) 514 | RESPONSE = html_template() 515 | RESPONSE = RESPONSE.replace('##NAME##', Name) 516 | RESPONSE = RESPONSE.replace('##EMAIL##', Email) 517 | RESPONSE = RESPONSE.replace('##PHONE##', Phone[:30] + '...' ) 518 | return respond(None, RESPONSE) 519 | 520 | DecryptFunctionExecutionRole: 521 | Type: AWS::IAM::Role 522 | Properties: 523 | AssumeRolePolicyDocument: 524 | Version: '2012-10-17' 525 | Statement: 526 | - Effect: Allow 527 | Principal: 528 | Service: 529 | - lambda.amazonaws.com 530 | Action: 531 | - sts:AssumeRole 532 | Path: "/" 533 | Policies: 534 | - PolicyName: Allow-Logging-Into-CWL 535 | PolicyDocument: 536 | Version: '2012-10-17' 537 | Statement: 538 | - Effect: Allow 539 | Action: 540 | - logs:CreateLogGroup 541 | - logs:CreateLogStream 542 | - logs:PutLogEvents 543 | - logs:DescribeLogStreams 544 | Resource: arn:aws:logs:*:*:* 545 | - PolicyName: Allow-Get-ParamStore 546 | PolicyDocument: 547 | Version: '2012-10-17' 548 | Statement: 549 | - Effect: Allow 550 | Action: 551 | - ssm:GetParameter 552 | Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${PrivateKeySSMPath}' 553 | - PolicyName: Allow-Get-DynamoDB 554 | PolicyDocument: 555 | Version: '2012-10-17' 556 | Statement: 557 | - Effect: Allow 558 | Action: 559 | - dynamodb:Scan 560 | Resource: !Sub '${ContactsDynamoDBTable.Arn}' 561 | - PolicyName: Allow-KMS-Decrypt 562 | PolicyDocument: 563 | Version: '2012-10-17' 564 | Statement: 565 | - Effect: Allow 566 | Action: 567 | - kms:Decrypt 568 | Resource: !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/${FieldEncryptionKey}' 569 | DecryptFunction: 570 | Type: AWS::Lambda::Function 571 | Properties: 572 | Handler: fle_decrypt_data.decrypt_data 573 | Role: !GetAtt DecryptFunctionExecutionRole.Arn 574 | Runtime: python2.7 575 | Environment: 576 | Variables: 577 | TABLENAME: !Ref ContactsDynamoDBTable 578 | PRIVATEKEYSSMPATH: !Ref PrivateKeySSMPath 579 | PROVIDERID: !Ref ProviderID 580 | PUBLICKEYNAME: !Ref PublicKeyName 581 | Timeout: 10 582 | MemorySize: 1024 583 | Code: 584 | S3Bucket: !Ref ArtifactsBucket 585 | S3Key: !Sub ${ArtifactsPrefix}fle_decrypt_data.zip 586 | 587 | CloudFrontDistribution: 588 | Type: 'AWS::CloudFront::Distribution' 589 | Properties: 590 | DistributionConfig: 591 | Comment: !Sub ${AWS::StackName} Field-Level Encryption Sample 592 | Origins: 593 | - DomainName: !Sub ${FLEApi}.execute-api.${AWS::Region}.amazonaws.com 594 | Id: CFCustomOrigin 595 | CustomOriginConfig: 596 | HTTPSPort: '443' 597 | OriginProtocolPolicy: https-only 598 | OriginCustomHeaders: 599 | - HeaderName: "x-api-key" 600 | HeaderValue: !GetAtt GetAPIKey.APIKeyValue 601 | Enabled: "false" 602 | DefaultCacheBehavior: 603 | TargetOriginId: CFCustomOrigin 604 | ViewerProtocolPolicy: redirect-to-https 605 | AllowedMethods: ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] 606 | ForwardedValues: 607 | QueryString: 'false' 608 | Cookies: 609 | Forward: none 610 | PriceClass: PriceClass_200 611 | ViewerCertificate: 612 | CloudFrontDefaultCertificate: "true" 613 | 614 | Outputs: 615 | ApplicationURL: 616 | Value: !Sub https://${CloudFrontDistribution.DomainName}/prod/ 617 | Description: URL for the application 618 | KMSKeyID: 619 | Value: !Ref FieldEncryptionKey 620 | Description: KMS Key for encryption in parameter store 621 | CloudFrontDistribution: 622 | Value: !Sub https://console.aws.amazon.com/cloudfront/home?region=${AWS::Region}#distribution-settings:${CloudFrontDistribution} 623 | Description: Edit CloudFront distribution settings 624 | DecryptFunction: 625 | Value: !Sub https://console.aws.amazon.com/lambda/home?region=${AWS::Region}#/functions/${DecryptFunction} 626 | Description: Test decryption with Lambda 627 | --------------------------------------------------------------------------------