diff --git a/roles/aws/aws_cloudfront_distribution/README.md b/roles/aws/aws_cloudfront_distribution/README.md index e9baf31f6..f45a01af6 100644 --- a/roles/aws/aws_cloudfront_distribution/README.md +++ b/roles/aws/aws_cloudfront_distribution/README.md @@ -69,7 +69,6 @@ aws_cloudfront_distribution: cache_behaviors: [] # A list of cache behaviors same as default_cache_behavior with additional path_pattern var required. enabled: true purge_existing: true # Set to false to append entries instead of replacing them. - web_acl: false # Set to true to create Web ACL for WAF. ``` diff --git a/roles/aws/aws_cloudfront_distribution/defaults/main.yml b/roles/aws/aws_cloudfront_distribution/defaults/main.yml index 29687bb52..6a015b54b 100644 --- a/roles/aws/aws_cloudfront_distribution/defaults/main.yml +++ b/roles/aws/aws_cloudfront_distribution/defaults/main.yml @@ -61,4 +61,3 @@ aws_cloudfront_distribution: cache_behaviors: [] # A list of cache behaviors same as default_cache_behavior with additional path_pattern var required. enabled: true purge_existing: true # Set to false to append entries instead of replacing them. - web_acl: false # Set to true to create Web ACL for WAF. diff --git a/roles/aws/aws_cloudfront_distribution/tasks/add_function.yml b/roles/aws/aws_cloudfront_distribution/tasks/add_function.yml new file mode 100644 index 000000000..ae8132cfa --- /dev/null +++ b/roles/aws/aws_cloudfront_distribution/tasks/add_function.yml @@ -0,0 +1,16 @@ +- name: Set empty list. + ansible.builtin.set_fact: + _lambda_funct_list: [] + +- name: Create dict. + ansible.builtin.set_fact: + _lambda_funct_list: "{{ _lambda_funct_list + [{'event_type': item.event_type, 'lambda_function_arn': aws_lambda._result[item.lambda_name].configuration.function_arn}] }}" + loop: "{{ _behavior.lambda_functions }}" + +- name: Remove lambda_functions and add lambda_function_associations. + ansible.builtin.set_fact: + _processed_behavior: "{{ (_behavior | dict2items | rejectattr('key', 'equalto', 'lambda_functions') | items2dict) | combine({'lambda_function_associations': _lambda_funct_list}) }}" + +- name: Append behaviour to list. + ansible.builtin.set_fact: + _processed_cache_behaviors: "{{ _processed_cache_behaviors + [_processed_behavior] }}" diff --git a/roles/aws/aws_cloudfront_distribution/tasks/check_function.yml b/roles/aws/aws_cloudfront_distribution/tasks/check_function.yml new file mode 100644 index 000000000..8c1e9d151 --- /dev/null +++ b/roles/aws/aws_cloudfront_distribution/tasks/check_function.yml @@ -0,0 +1,7 @@ +- name: Create CloudFront function. + ansible.builtin.include_tasks: create_cf_function.yml + when: _funct.type == 'cf' + +- name: Create Lambda function. + ansible.builtin.include_tasks: create_lambda_function.yml + when: _funct.type == 'lambda' diff --git a/roles/aws/aws_cloudfront_distribution/tasks/create_cf_function.yml b/roles/aws/aws_cloudfront_distribution/tasks/create_cf_function.yml new file mode 100644 index 000000000..829ad5f1e --- /dev/null +++ b/roles/aws/aws_cloudfront_distribution/tasks/create_cf_function.yml @@ -0,0 +1,48 @@ +- name: Check if CF function exists. + ansible.builtin.command: "aws cloudfront list-functions --query \"FunctionList.Items[?Name=='{{ _funct.name }}']\"" + register: _cf_funct_list_result + +- name: Get details of CloudFront function + ansible.builtin.command: "aws cloudfront get-function --name {{ _funct.name }} /tmp/cf_funct" + register: _cf_funct_result + when: (_cf_funct_list_result.stdout | from_json) | length > 0 + +- name: Setting command output into variable. + ansible.builtin.set_fact: + _cf_funct_result: "{{ _cf_funct_result.stdout | from_json }}" + when: (_cf_funct_list_result.stdout | from_json) | length > 0 + +- name: Create config for CloudFront function. + ansible.builtin.template: + src: "config.j2" + dest: "/tmp/cf_config" + owner: controller + group: controller + mode: 0644 + +- name: Create CF function. + ansible.builtin.command: "aws cloudfront create-function --function-config file:///tmp/cf_config --name {{ _funct.name }} --function-code fileb://{{ _ce_provision_build_dir }}/files/{{ _funct.code }}" + register: _cf_function + when: (_cf_funct_list_result.stdout | from_json) | length == 0 + +- name: Setting previous command output into variable. + ansible.builtin.set_fact: + _cf_function: "{{ _cf_function.stdout | from_json }}" + when: (_cf_funct_list_result.stdout | from_json) | length == 0 + +- name: Update CloudFront function. + ansible.builtin.command: "aws cloudfront update-function --name {{ _funct.name }} --if-match {{ _cf_funct_result.ETag }} --function-config file:///tmp/cf_config --function-code fileb://{{ _ce_provision_build_dir }}/files/{{ _funct.code }}" + register: _cf_function + when: (_cf_funct_list_result.stdout | from_json) | length > 0 + +- name: Setting previous command output into variable. + ansible.builtin.set_fact: + _cf_function: "{{ _cf_function.stdout | from_json }}" + when: (_cf_funct_list_result.stdout | from_json) | length > 0 + +- name: Update CloudFront function. + ansible.builtin.command: "aws cloudfront publish-function --name {{ _funct.name }} --if-match {{ _cf_function.ETag }}" + +- name: Register aws_lambda results. + ansible.builtin.set_fact: + _function_results: "{{ _function_results + [_cf_function] }}" diff --git a/roles/aws/aws_cloudfront_distribution/tasks/create_lambda_function.yml b/roles/aws/aws_cloudfront_distribution/tasks/create_lambda_function.yml new file mode 100644 index 000000000..4f7c06275 --- /dev/null +++ b/roles/aws/aws_cloudfront_distribution/tasks/create_lambda_function.yml @@ -0,0 +1,32 @@ +- name: Attach CloudWatch policy. + ansible.builtin.set_fact: + _policies: "{{ ['arn:aws:iam::aws:policy/AWSLambda_FullAccess'] + ['arn:aws:iam::aws:policy/CloudWatchLogsFullAccess'] }}" + +- name: Create a Lambda@Edge role. + ansible.builtin.include_role: + name: aws/aws_iam_role + vars: + aws_iam_role: + name: "lambda_edge" + aws_profile: "{{ _aws_profile }}" + managed_policies: "{{ _policies }}" + inline_policies: + - "lambda:GetFunction" + policy_document: "{{ lookup('template', 'lambda_policy.json') }}" + +- name: Create Lambda function. + ansible.builtin.include_role: + name: aws/aws_lambda + vars: + aws_lambda: + name: "{{ _funct.name }}" + region: "us-east-1" + description: "Lambda function for {{ _funct.name }}" + timeout: "5" + role: "{{ aws_iam_role._result['lambda_edge'] }}" + runtime: "{{ _funct.runtime }}" + function_file: "{{ _ce_provision_build_dir }}/files/{{ _funct.code }}" + s3_bucket: "codeenigma-{{ _aws_profile }}-general-storage-us-east-1" + s3_bucket_prefix: "lambda-functions" + tags: + Name: "{{ _funct.name }}" diff --git a/roles/aws/aws_cloudfront_distribution/tasks/main.yml b/roles/aws/aws_cloudfront_distribution/tasks/main.yml index 34bf41ddb..732b8f671 100644 --- a/roles/aws/aws_cloudfront_distribution/tasks/main.yml +++ b/roles/aws/aws_cloudfront_distribution/tasks/main.yml @@ -3,7 +3,7 @@ ansible.builtin.include_role: name: aws/aws_acl when: - - aws_cloudfront_distribution.web_acl +# - aws_cloudfront_distribution.web_acl - aws_acl.name is defined - aws_acl.scope is defined - aws_acl.scope == 'CLOUDFRONT' @@ -29,6 +29,93 @@ include_cookies: false # Set true to add cookies in logs prefix: "cf-logging/" # Prefix for S3 object names +- name: Set empty list for function results. + ansible.builtin.set_fact: + _function_results: [] + +- name: Create CloudFront function if defined. + ansible.builtin.include_tasks: check_function.yml + loop: "{{ aws_cloudfront_distribution.functions }}" + loop_control: + loop_var: _funct + when: aws_cloudfront_distribution.functions is defined + +- name: Initialize processed cache behaviors list. + ansible.builtin.set_fact: + _processed_cache_behaviors: [] + +- name: Process default cache behavior if it has lambda functions + ansible.builtin.include_tasks: add_function.yml + vars: + _behavior: "{{ aws_cloudfront_distribution.default_cache_behavior }}" + register: default_result + when: aws_cloudfront_distribution.default_cache_behavior.lambda_functions is defined + +- name: Update default cache behavior + ansible.builtin.set_fact: + _default_cache_string: "{{ _processed_behavior }}" + when: aws_cloudfront_distribution.default_cache_behavior.lambda_functions is defined + +- name: Create dict for default cache behavior + ansible.builtin.set_fact: + _default_cache: "{{ _default_cache_string | from_yaml }}" + when: aws_cloudfront_distribution.default_cache_behavior.lambda_functions is defined + +- name: Create dict for default cache behavior + ansible.builtin.set_fact: + _default_cache: "{{ aws_cloudfront_distribution.default_cache_behavior }}" + when: aws_cloudfront_distribution.default_cache_behavior.lambda_functions is not defined + +- name: Initialize processed cache behaviors list. + ansible.builtin.set_fact: + _processed_cache_behaviors: [] + +- name: Extract the cache behaviors with Lambda function. + ansible.builtin.set_fact: + _cache_behavior_with_lambda: "{{ aws_cloudfront_distribution.cache_behaviors | selectattr('lambda_functions', 'defined') | list }}" + +- name: Extract the cache behaviors with Lambda function. + ansible.builtin.set_fact: + _cache_behavior_without_lambda: "{{ aws_cloudfront_distribution.cache_behaviors | selectattr('lambda_functions', 'undefined') | list }}" + +# Process behaviors with lambda functions +- name: Process behavior with lambda functions + ansible.builtin.include_tasks: add_function.yml + loop: "{{ _cache_behavior_with_lambda | default([]) }}" + loop_control: + loop_var: _behavior + when: _cache_behavior_with_lambda | length > 0 + +# Add each behavior to the final list (processed or original) +- name: Add behavior to final list + ansible.builtin.set_fact: + _processed_cache_behaviors: "{{ _processed_cache_behaviors + _cache_behavior_without_lambda }}" + +- name: Update cache behaviors in aws_cloudfront_distribution + ansible.builtin.set_fact: + _cache_behavior_with_lambda: "{{ {'cache_behaviors': _processed_cache_behaviors} }}" + +- name: Extract the Lambda functios. + ansible.builtin.set_fact: + _lambda_functions: "{{ aws_cloudfront_distribution.functions | selectattr('type', '==', 'lambda') | list }}" + +- name: Wait for Lambda function to be Active. + ansible.builtin.command: > + aws lambda get-function + --function-name {{ item.name }} + --query 'Configuration.[State, LastUpdateStatus]' + --region us-east-1 + register: lambda_status + until: (lambda_status.stdout | from_json)[0] == "Active" + retries: 30 + delay: 10 + changed_when: false + loop: "{{ _lambda_functions }}" + +- name: Wait for 10 seconds. + ansible.builtin.wait_for: + timeout: 10 + - name: Create a CloudFront distribution. community.aws.cloudfront_distribution: profile: "{{ aws_cloudfront_distribution.aws_profile }}" @@ -38,8 +125,8 @@ aliases: "{{ aws_cloudfront_distribution.aliases }}" origins: "{{ aws_cloudfront_distribution.origins }}" web_acl_id: "{{ _created_acl.arn | default(omit) }}" - default_cache_behavior: "{{ aws_cloudfront_distribution.default_cache_behavior }}" - cache_behaviors: "{{ aws_cloudfront_distribution.cache_behaviors }}" + default_cache_behavior: "{{ _default_cache }}" + cache_behaviors: "{{ _processed_cache_behaviors }}" validate_certs: "{{ aws_cloudfront_distribution.validate_certs }}" viewer_certificate: "{{ aws_cloudfront_distribution.viewer_certificate }}" purge_origins: "{{ aws_cloudfront_distribution.purge_existing }}" diff --git a/roles/aws/aws_cloudfront_distribution/tasks/process_all_behaviors.yml b/roles/aws/aws_cloudfront_distribution/tasks/process_all_behaviors.yml new file mode 100644 index 000000000..2562efbbb --- /dev/null +++ b/roles/aws/aws_cloudfront_distribution/tasks/process_all_behaviors.yml @@ -0,0 +1,28 @@ +- name: Process default cache behavior lambda functions. + when: aws_cloudfront_distribution.default_cache_behavior.lambda_functions is defined + block: + - name: Set empty list for default behavior. + ansible.builtin.set_fact: + _lambda_funct_list: [] + + - name: Create lambda function associations for default behavior. + ansible.builtin.set_fact: + _lambda_funct_list: "{{ _lambda_funct_list + [{'event_type': item.event_type, 'lambda_function_arn': aws_lambda._result[item.lambda_name].configuration.function_arn}] }}" + loop: "{{ aws_cloudfront_distribution.default_cache_behavior.lambda_functions }}" + + - name: Update default cache behavior. + ansible.builtin.set_fact: + aws_cloudfront_distribution: "{{ aws_cloudfront_distribution | combine({'default_cache_behavior': (aws_cloudfront_distribution.default_cache_behavior | dict2items | rejectattr('key', 'equalto', 'lambda_functions') | items2dict) | combine({'lambda_function_associations': _lambda_funct_list})}) }}" + +- name: Process cache behaviors lambda functions. + ansible.builtin.set_fact: + updated_cache_behaviors: "{{ updated_cache_behaviors | default([]) + [processed_behavior] }}" + vars: + lambda_list: "{{ item.lambda_functions | map('combine', {'lambda_function_arn': aws_lambda._result[item.lambda_name].configuration.function_arn}) | map('dict2items') | map('rejectattr', 'key', 'equalto', 'lambda_name') | map('items2dict') | list if item.lambda_functions is defined else [] }}" + processed_behavior: "{{ (item | dict2items | rejectattr('key', 'equalto', 'lambda_functions') | items2dict) | combine({'lambda_function_associations': lambda_list}) if item.lambda_functions is defined else item }}" + loop: "{{ aws_cloudfront_distribution.cache_behaviors | default([]) }}" + +- name: Update cache behaviors in main structure. + ansible.builtin.set_fact: + aws_cloudfront_distribution: "{{ aws_cloudfront_distribution | combine({'cache_behaviors': updated_cache_behaviors}) }}" + when: updated_cache_behaviors is defined diff --git a/roles/aws/aws_cloudfront_distribution/templates/config.j2 b/roles/aws/aws_cloudfront_distribution/templates/config.j2 new file mode 100644 index 000000000..7ddd2a9d0 --- /dev/null +++ b/roles/aws/aws_cloudfront_distribution/templates/config.j2 @@ -0,0 +1,13 @@ +{ + "Comment": "{{ _funct.description }}", + "Runtime": "{{ _funct.runtime }}"{% if _funct.kvs %}, + "KeyValueStoreAssociations": { + "Quantity": 1, + "Items": [ + { + "KeyValueStoreARN": "{{_funct.kvs }}" + } + ] + } + {% endif %} +} diff --git a/roles/aws/aws_cloudfront_distribution/templates/lambda_policy.json b/roles/aws/aws_cloudfront_distribution/templates/lambda_policy.json new file mode 100644 index 000000000..ccb10609a --- /dev/null +++ b/roles/aws/aws_cloudfront_distribution/templates/lambda_policy.json @@ -0,0 +1,15 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com", + "edgelambda.amazonaws.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } diff --git a/roles/aws/aws_lambda/defaults/main.yml b/roles/aws/aws_lambda/defaults/main.yml index 72793abbd..004eb8eaf 100644 --- a/roles/aws/aws_lambda/defaults/main.yml +++ b/roles/aws/aws_lambda/defaults/main.yml @@ -1,6 +1,7 @@ aws_lambda: name: "lambda_function_name" description: "Description for AWS Lambda function" + region: "{{ _aws_region }}" timeout: "20" # Maximum number of seconds before function times out handler: "lambda_handler" # Name of main function s3_bucket: "codeenigma-{{ _aws_profile }}-general-storage-{{ _aws_region }}" diff --git a/roles/aws/aws_lambda/tasks/main.yml b/roles/aws/aws_lambda/tasks/main.yml index f39dd2422..e8d1fe95c 100644 --- a/roles/aws/aws_lambda/tasks/main.yml +++ b/roles/aws/aws_lambda/tasks/main.yml @@ -4,26 +4,51 @@ vars: aws_s3_bucket: profile: "{{ _aws_profile }}" - region: "{{ _aws_region }}" + region: "{{ aws_lambda.region }}" name: "{{ aws_lambda.s3_bucket }}" tags: [] state: "present" +- name: Check string type using regex + ansible.builtin.set_fact: + string_type: >- + {%- if input_string | regex_search('^https?://') -%} + url + {%- elif input_string | regex_search('\.zip$', ignorecase=True) -%} + zip + {%- else -%} + single + {%- endif -%} + vars: + input_string: "{{ aws_lambda.function_file }}" + - name: Check and clean previous Lambda function. ansible.builtin.file: path: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.py" state: absent + when: string_type == 'single' - name: Write Lambda function. ansible.builtin.copy: content: "{{ aws_lambda.function_file }}" dest: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.py" + when: string_type == 'single' - name: Create a zip archive of Lambda function. community.general.archive: path: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.py" dest: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.zip" format: zip + when: string_type == 'single' + +- name: Copy a zip archive of Lambda function. + ansible.builtin.copy: + src: "{{ aws_lambda.function_file }}" + dest: "{{ _ce_provision_build_dir }}/{{ aws_lambda.name }}.zip" + owner: controller + group: controller + mode: '0644' + when: string_type == 'zip' - name: Place Lambda function in S3 bucket. amazon.aws.s3_object: @@ -36,7 +61,7 @@ amazon.aws.lambda: name: "{{ aws_lambda.name }}" description: "{{ aws_lambda.description }}" - region: "{{ _aws_region }}" + region: "{{ aws_lambda.region }}" timeout: "{{ aws_lambda.timeout }}" s3_bucket: "{{ aws_lambda.s3_bucket }}" s3_key: "{{ aws_lambda.s3_bucket_prefix }}/{{ aws_lambda.name }}.zip"