diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index d144cf2d..aec9f6c6 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 1 persist-credentials: false diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 101bfa4e..798b6248 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -27,7 +27,7 @@ jobs: actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 1 persist-credentials: false diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 3f761edf..26b61780 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index c3678004..d2f71b98 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -26,7 +26,7 @@ jobs: matrix: vpn_type: ['wireguard', 'ipsec', 'both'] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false @@ -210,7 +210,7 @@ jobs: runs-on: ubuntu-22.04 timeout-minutes: 10 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 64066458..8dcca4e1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,7 +11,7 @@ jobs: name: Ansible linting runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -42,7 +42,7 @@ jobs: name: YAML linting runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false @@ -56,7 +56,7 @@ jobs: name: Python linting runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -75,7 +75,7 @@ jobs: name: Shell script linting runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false @@ -89,7 +89,7 @@ jobs: name: PowerShell script linting runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c572bed3..d2614749 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -37,7 +37,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -59,7 +59,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -87,7 +87,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -113,7 +113,7 @@ jobs: matrix: provider: [local, ec2, digitalocean, gce] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 diff --git a/.github/workflows/smart-tests.yml b/.github/workflows/smart-tests.yml index 779b01ea..e5cf3170 100644 --- a/.github/workflows/smart-tests.yml +++ b/.github/workflows/smart-tests.yml @@ -23,7 +23,7 @@ jobs: run_lint: ${{ steps.filter.outputs.lint }} run_integration: ${{ steps.filter.outputs.integration }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false @@ -77,7 +77,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -98,7 +98,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -142,7 +142,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -171,7 +171,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 @@ -223,7 +223,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 diff --git a/.github/workflows/test-effectiveness.yml b/.github/workflows/test-effectiveness.yml index b1e30585..051e713e 100644 --- a/.github/workflows/test-effectiveness.yml +++ b/.github/workflows/test-effectiveness.yml @@ -17,7 +17,7 @@ jobs: name: Analyze Test Effectiveness runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: true diff --git a/.gitignore b/.gitignore index f414352a..4438ad8f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ inventory_users .ansible/ __pycache__/ *.pyc +algo.egg-info/ diff --git a/input.yml b/input.yml index 64b48056..5945f54c 100644 --- a/input.yml +++ b/input.yml @@ -42,7 +42,7 @@ - name: Set facts based on the input set_fact: - algo_provider: "{{ provider | default(providers_map[_algo_provider.user_input|default(omit)|int - 1]['alias']) }}" + algo_provider: "{{ provider | default(providers_map[_algo_provider.user_input | default(omit) | int - 1]['alias']) }}" - name: VPN server name prompt pause: @@ -110,10 +110,10 @@ set_fact: algo_server_name: >- {% if server_name is defined %}{% set _server = server_name %} - {%- elif _algo_server_name.user_input is defined and _algo_server_name.user_input|length > 0 -%} + {%- elif _algo_server_name.user_input is defined and _algo_server_name.user_input | length > 0 -%} {%- set _server = _algo_server_name.user_input -%} {%- else %}{% set _server = defaults['server_name'] %}{% endif -%} - {{ _server | regex_replace('(?!\.)(\W|_)', '-') }} + {{ _server | regex_replace('(?!\.)(\W | _)', '-') }} algo_ondemand_cellular: >- {% if ondemand_cellular is defined %}{{ ondemand_cellular | bool }} {%- elif _ondemand_cellular.user_input is defined %}{{ booleans_map[_ondemand_cellular.user_input] | default(defaults['ondemand_cellular']) }} @@ -124,7 +124,7 @@ {%- else %}false{% endif %} algo_ondemand_wifi_exclude: >- {% if ondemand_wifi_exclude is defined %}{{ ondemand_wifi_exclude | b64encode }} - {%- elif _ondemand_wifi_exclude.user_input is defined and _ondemand_wifi_exclude.user_input|length > 0 -%} + {%- elif _ondemand_wifi_exclude.user_input is defined and _ondemand_wifi_exclude.user_input | length > 0 -%} {{ _ondemand_wifi_exclude.user_input | b64encode }} {%- else %}{{ '_null' | b64encode }}{% endif %} algo_dns_adblocking: >- diff --git a/library/lightsail_region_facts.py b/library/lightsail_region_facts.py index a74571c2..c61a4006 100644 --- a/library/lightsail_region_facts.py +++ b/library/lightsail_region_facts.py @@ -82,7 +82,7 @@ def main(): module.fail_json(msg='Python module "botocore" is missing, please install it') try: - region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True) + region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module) client = None try: diff --git a/playbooks/cloud-post.yml b/playbooks/cloud-post.yml index 20c417b8..5a118211 100644 --- a/playbooks/cloud-post.yml +++ b/playbooks/cloud-post.yml @@ -8,8 +8,8 @@ name: "{% if cloud_instance_ip == 'localhost' %}localhost{% else %}{{ cloud_instance_ip }}{% endif %}" groups: vpn-host ansible_connection: "{% if cloud_instance_ip == 'localhost' %}local{% else %}ssh{% endif %}" - ansible_ssh_user: "{{ ansible_ssh_user|default('root') }}" - ansible_ssh_port: "{{ ansible_ssh_port|default(22) }}" + ansible_ssh_user: "{{ ansible_ssh_user | default('root') }}" + ansible_ssh_port: "{{ ansible_ssh_port | default(22) }}" ansible_python_interpreter: /usr/bin/python3 algo_provider: "{{ algo_provider }}" algo_server_name: "{{ algo_server_name }}" @@ -21,7 +21,7 @@ algo_store_pki: "{{ algo_store_pki }}" IP_subject_alt_name: "{{ IP_subject_alt_name }}" alternative_ingress_ip: "{{ alternative_ingress_ip | default(omit) }}" - cloudinit: "{{ cloudinit|default(false) }}" + cloudinit: "{{ cloudinit | default(false) }}" - name: Additional variables for the server add_host: @@ -31,7 +31,7 @@ - name: Wait until SSH becomes ready... wait_for: - port: "{{ ansible_ssh_port|default(22) }}" + port: "{{ ansible_ssh_port | default(22) }}" host: "{{ cloud_instance_ip }}" search_regex: OpenSSH delay: 10 diff --git a/pyproject.toml b/pyproject.toml index e5ea7161..8da972d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ description = "Set up a personal IPSEC VPN in the cloud" version = "2.0.0-beta" requires-python = ">=3.11" dependencies = [ - "ansible==11.8.0", + "ansible==11.9.0", "jinja2>=3.1.6", "netaddr==1.3.0", "pyyaml>=6.0.2", diff --git a/roles/cloud-azure/tasks/prompts.yml b/roles/cloud-azure/tasks/prompts.yml index 38f0b375..85babf03 100644 --- a/roles/cloud-azure/tasks/prompts.yml +++ b/roles/cloud-azure/tasks/prompts.yml @@ -1,9 +1,9 @@ --- - set_fact: - secret: "{{ azure_secret | default(lookup('env','AZURE_SECRET'), true) }}" - tenant: "{{ azure_tenant | default(lookup('env','AZURE_TENANT'), true) }}" - client_id: "{{ azure_client_id | default(lookup('env','AZURE_CLIENT_ID'), true) }}" - subscription_id: "{{ azure_subscription_id | default(lookup('env','AZURE_SUBSCRIPTION_ID'), true) }}" + secret: "{{ azure_secret | default(lookup('env', 'AZURE_SECRET'), true) }}" + tenant: "{{ azure_tenant | default(lookup('env', 'AZURE_TENANT'), true) }}" + client_id: "{{ azure_client_id | default(lookup('env', 'AZURE_CLIENT_ID'), true) }}" + subscription_id: "{{ azure_subscription_id | default(lookup('env', 'AZURE_SUBSCRIPTION_ID'), true) }}" no_log: true - block: diff --git a/roles/cloud-cloudstack/tasks/prompts.yml b/roles/cloud-cloudstack/tasks/prompts.yml index d09d4634..ebeb1c5b 100644 --- a/roles/cloud-cloudstack/tasks/prompts.yml +++ b/roles/cloud-cloudstack/tasks/prompts.yml @@ -7,7 +7,7 @@ register: _cs_key when: - cs_key is undefined - - lookup('env','CLOUDSTACK_KEY')|length <= 0 + - lookup('env', 'CLOUDSTACK_KEY')|length <= 0 no_log: true - pause: @@ -17,7 +17,7 @@ register: _cs_secret when: - cs_secret is undefined - - lookup('env','CLOUDSTACK_SECRET')|length <= 0 + - lookup('env', 'CLOUDSTACK_SECRET')|length <= 0 no_log: true - pause: @@ -30,8 +30,8 @@ - lookup('env', 'CLOUDSTACK_ENDPOINT') | length <= 0 - set_fact: - algo_cs_key: "{{ cs_key | default(_cs_key.user_input|default(None)) | default(lookup('env', 'CLOUDSTACK_KEY'), true) }}" - algo_cs_token: "{{ cs_secret | default(_cs_secret.user_input|default(None)) | default(lookup('env', 'CLOUDSTACK_SECRET'), true) }}" + algo_cs_key: "{{ cs_key | default(_cs_key.user_input | default(None)) | default(lookup('env', 'CLOUDSTACK_KEY'), true) }}" + algo_cs_token: "{{ cs_secret | default(_cs_secret.user_input | default(None)) | default(lookup('env', 'CLOUDSTACK_SECRET'), true) }}" algo_cs_url: >- {{ cs_url | default(_cs_url.user_input|default(None)) | default(lookup('env', 'CLOUDSTACK_ENDPOINT'), true) | diff --git a/roles/cloud-digitalocean/tasks/prompts.yml b/roles/cloud-digitalocean/tasks/prompts.yml index 67a9401f..f8f8e28d 100644 --- a/roles/cloud-digitalocean/tasks/prompts.yml +++ b/roles/cloud-digitalocean/tasks/prompts.yml @@ -6,12 +6,12 @@ register: _do_token when: - do_token is undefined - - lookup('env','DO_API_TOKEN')|length <= 0 + - lookup('env', 'DO_API_TOKEN')|length <= 0 no_log: true - name: Set the token as a fact set_fact: - algo_do_token: "{{ do_token | default(_do_token.user_input|default(None)) | default(lookup('env','DO_API_TOKEN'), true) }}" + algo_do_token: "{{ do_token | default(_do_token.user_input | default(None)) | default(lookup('env', 'DO_API_TOKEN'), true) }}" no_log: true - name: Get regions diff --git a/roles/cloud-ec2/tasks/prompts.yml b/roles/cloud-ec2/tasks/prompts.yml index fecc5846..765fc62b 100644 --- a/roles/cloud-ec2/tasks/prompts.yml +++ b/roles/cloud-ec2/tasks/prompts.yml @@ -22,7 +22,7 @@ no_log: true when: - aws_access_key is undefined - - lookup('env','AWS_ACCESS_KEY_ID')|length <= 0 + - lookup('env', 'AWS_ACCESS_KEY_ID')|length <= 0 # Prompt for credentials if still not available - pause: @@ -33,7 +33,7 @@ register: _aws_access_key when: - aws_access_key is undefined - - lookup('env','AWS_ACCESS_KEY_ID')|length <= 0 + - lookup('env', 'AWS_ACCESS_KEY_ID')|length <= 0 - _file_access_key is undefined or _file_access_key|length <= 0 - pause: @@ -43,7 +43,7 @@ register: _aws_secret_key when: - aws_secret_key is undefined - - lookup('env','AWS_SECRET_ACCESS_KEY')|length <= 0 + - lookup('env', 'AWS_SECRET_ACCESS_KEY')|length <= 0 - _file_secret_key is undefined or _file_secret_key|length <= 0 # Set final credentials with proper precedence diff --git a/roles/cloud-gce/tasks/main.yml b/roles/cloud-gce/tasks/main.yml index 69aa56ad..9e743dcf 100644 --- a/roles/cloud-gce/tasks/main.yml +++ b/roles/cloud-gce/tasks/main.yml @@ -29,7 +29,7 @@ ports: - "500" - "4500" - - "{{ wireguard_port|string }}" + - "{{ wireguard_port | string }}" - ip_protocol: tcp ports: - "{{ ssh_port }}" @@ -70,7 +70,7 @@ - network: "{{ gcp_compute_network }}" access_configs: - name: "{{ algo_server_name }}" - nat_ip: "{{ gcp_compute_address|default(None) }}" + nat_ip: "{{ gcp_compute_address | default(None) }}" type: ONE_TO_ONE_NAT tags: items: diff --git a/roles/cloud-gce/tasks/prompts.yml b/roles/cloud-gce/tasks/prompts.yml index 1b19db98..a8a32fe9 100644 --- a/roles/cloud-gce/tasks/prompts.yml +++ b/roles/cloud-gce/tasks/prompts.yml @@ -6,13 +6,13 @@ register: _gce_credentials_file when: - gce_credentials_file is undefined - - lookup('env','GCE_CREDENTIALS_FILE_PATH')|length <= 0 + - lookup('env', 'GCE_CREDENTIALS_FILE_PATH')|length <= 0 no_log: true - set_fact: credentials_file_path: >- {{ gce_credentials_file | default(_gce_credentials_file.user_input|default(None)) | - default(lookup('env','GCE_CREDENTIALS_FILE_PATH'), true) }} + default(lookup('env', 'GCE_CREDENTIALS_FILE_PATH'), true) }} ssh_public_key_lookup: "{{ lookup('file', '{{ SSH_keys.public }}') }}" no_log: true @@ -21,8 +21,8 @@ no_log: true - set_fact: - service_account_email: "{{ credentials_file_lookup.client_email | default(lookup('env','GCE_EMAIL')) }}" - project_id: "{{ credentials_file_lookup.project_id | default(lookup('env','GCE_PROJECT')) }}" + service_account_email: "{{ credentials_file_lookup.client_email | default(lookup('env', 'GCE_EMAIL')) }}" + project_id: "{{ credentials_file_lookup.project_id | default(lookup('env', 'GCE_PROJECT')) }}" no_log: true - block: diff --git a/roles/cloud-hetzner/tasks/prompts.yml b/roles/cloud-hetzner/tasks/prompts.yml index d12b3387..9b2f3faa 100644 --- a/roles/cloud-hetzner/tasks/prompts.yml +++ b/roles/cloud-hetzner/tasks/prompts.yml @@ -6,12 +6,12 @@ register: _hcloud_token when: - hcloud_token is undefined - - lookup('env','HCLOUD_TOKEN')|length <= 0 + - lookup('env', 'HCLOUD_TOKEN')|length <= 0 no_log: true - name: Set the token as a fact set_fact: - algo_hcloud_token: "{{ hcloud_token | default(_hcloud_token.user_input|default(None)) | default(lookup('env','HCLOUD_TOKEN'), true) }}" + algo_hcloud_token: "{{ hcloud_token | default(_hcloud_token.user_input | default(None)) | default(lookup('env', 'HCLOUD_TOKEN'), true) }}" no_log: true - name: Get regions diff --git a/roles/cloud-lightsail/tasks/prompts.yml b/roles/cloud-lightsail/tasks/prompts.yml index d49cc5de..782153d9 100644 --- a/roles/cloud-lightsail/tasks/prompts.yml +++ b/roles/cloud-lightsail/tasks/prompts.yml @@ -7,7 +7,7 @@ register: _aws_access_key when: - aws_access_key is undefined - - lookup('env','AWS_ACCESS_KEY_ID')|length <= 0 + - lookup('env', 'AWS_ACCESS_KEY_ID')|length <= 0 no_log: true - pause: @@ -17,12 +17,12 @@ register: _aws_secret_key when: - aws_secret_key is undefined - - lookup('env','AWS_SECRET_ACCESS_KEY')|length <= 0 + - lookup('env', 'AWS_SECRET_ACCESS_KEY')|length <= 0 no_log: true - set_fact: - access_key: "{{ aws_access_key | default(_aws_access_key.user_input|default(None)) | default(lookup('env','AWS_ACCESS_KEY_ID'), true) }}" - secret_key: "{{ aws_secret_key | default(_aws_secret_key.user_input|default(None)) | default(lookup('env','AWS_SECRET_ACCESS_KEY'), true) }}" + access_key: "{{ aws_access_key | default(_aws_access_key.user_input | default(None)) | default(lookup('env', 'AWS_ACCESS_KEY_ID'), true) }}" + secret_key: "{{ aws_secret_key | default(_aws_secret_key.user_input | default(None)) | default(lookup('env', 'AWS_SECRET_ACCESS_KEY'), true) }}" no_log: true - block: @@ -50,7 +50,7 @@ What region should the server be located in? (https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/) {% for r in lightsail_regions %} - {{ (loop.index|string + '.').ljust(3) }} {{ r['name'].ljust(20) }} {{ r['displayName'] }} + {{ (loop.index | string + '.').ljust(3) }} {{ r['name'].ljust(20) }} {{ r['displayName'] }} {% endfor %} Enter the number of your desired region diff --git a/roles/cloud-linode/tasks/prompts.yml b/roles/cloud-linode/tasks/prompts.yml index 90f7f2bc..18fde320 100644 --- a/roles/cloud-linode/tasks/prompts.yml +++ b/roles/cloud-linode/tasks/prompts.yml @@ -6,12 +6,12 @@ register: _linode_token when: - linode_token is undefined - - lookup('env','LINODE_API_TOKEN')|length <= 0 + - lookup('env', 'LINODE_API_TOKEN')|length <= 0 no_log: true - name: Set the token as a fact set_fact: - algo_linode_token: "{{ linode_token | default(_linode_token.user_input|default(None)) | default(lookup('env','LINODE_API_TOKEN'), true) }}" + algo_linode_token: "{{ linode_token | default(_linode_token.user_input | default(None)) | default(lookup('env', 'LINODE_API_TOKEN'), true) }}" no_log: true - name: Get regions diff --git a/roles/cloud-openstack/tasks/main.yml b/roles/cloud-openstack/tasks/main.yml index 63af0b58..055c017c 100644 --- a/roles/cloud-openstack/tasks/main.yml +++ b/roles/cloud-openstack/tasks/main.yml @@ -10,14 +10,14 @@ - name: Security group created openstack.cloud.security_group: - state: "{{ state|default('present') }}" + state: "{{ state | default('present') }}" name: "{{ algo_server_name }}-security_group" description: AlgoVPN security group register: os_security_group - name: Security rules created openstack.cloud.security_group_rule: - state: "{{ state|default('present') }}" + state: "{{ state | default('present') }}" security_group: "{{ os_security_group.id }}" protocol: "{{ item.proto }}" port_range_min: "{{ item.port_min }}" @@ -67,7 +67,7 @@ - name: Server created openstack.cloud.server: - state: "{{ state|default('present') }}" + state: "{{ state | default('present') }}" name: "{{ algo_server_name }}" image: "{{ image_id }}" flavor: "{{ flavor_id }}" diff --git a/roles/cloud-scaleway/tasks/main.yml b/roles/cloud-scaleway/tasks/main.yml index 05c1d536..1239a949 100644 --- a/roles/cloud-scaleway/tasks/main.yml +++ b/roles/cloud-scaleway/tasks/main.yml @@ -37,7 +37,7 @@ wait: true tags: - Environment:Algo - - AUTHORIZED_KEY={{ lookup('file', SSH_keys.public)|regex_replace(' ', '_') }} + - AUTHORIZED_KEY={{ lookup('file', SSH_keys.public) | regex_replace(' ', '_') }} register: scaleway_compute - name: Patch the cloud-init @@ -64,7 +64,7 @@ wait: true tags: - Environment:Algo - - AUTHORIZED_KEY={{ lookup('file', SSH_keys.public)|regex_replace(' ', '_') }} + - AUTHORIZED_KEY={{ lookup('file', SSH_keys.public) | regex_replace(' ', '_') }} register: algo_instance until: algo_instance.msg.public_ip retries: 3 diff --git a/roles/cloud-scaleway/tasks/prompts.yml b/roles/cloud-scaleway/tasks/prompts.yml index d0f35fe7..15a65272 100644 --- a/roles/cloud-scaleway/tasks/prompts.yml +++ b/roles/cloud-scaleway/tasks/prompts.yml @@ -6,7 +6,7 @@ register: _scaleway_token when: - scaleway_token is undefined - - lookup('env','SCW_TOKEN')|length <= 0 + - lookup('env', 'SCW_TOKEN')|length <= 0 no_log: true - pause: @@ -23,7 +23,7 @@ - name: Set scaleway facts set_fact: - algo_scaleway_token: "{{ scaleway_token | default(_scaleway_token.user_input) | default(lookup('env','SCW_TOKEN'), true) }}" + algo_scaleway_token: "{{ scaleway_token | default(_scaleway_token.user_input) | default(lookup('env', 'SCW_TOKEN'), true) }}" algo_region: >- {% if region is defined %}{{ region }} {%- elif _algo_region.user_input %}{{ scaleway_regions[_algo_region.user_input | int -1 ]['alias'] }} diff --git a/roles/cloud-vultr/tasks/prompts.yml b/roles/cloud-vultr/tasks/prompts.yml index d58ff4b2..fe2c3b22 100644 --- a/roles/cloud-vultr/tasks/prompts.yml +++ b/roles/cloud-vultr/tasks/prompts.yml @@ -6,12 +6,12 @@ register: _vultr_config when: - vultr_config is undefined - - lookup('env','VULTR_API_CONFIG')|length <= 0 + - lookup('env', 'VULTR_API_CONFIG')|length <= 0 no_log: true - name: Set the token as a fact set_fact: - algo_vultr_config: "{{ vultr_config | default(_vultr_config.user_input) | default(lookup('env','VULTR_API_CONFIG'), true) }}" + algo_vultr_config: "{{ vultr_config | default(_vultr_config.user_input) | default(lookup('env', 'VULTR_API_CONFIG'), true) }}" no_log: true - name: Set the Vultr API Key as a fact diff --git a/roles/common/defaults/main.yml b/roles/common/defaults/main.yml index c383d37b..26156dff 100644 --- a/roles/common/defaults/main.yml +++ b/roles/common/defaults/main.yml @@ -6,4 +6,4 @@ snat_aipv4: false ipv6_default: "{{ ansible_default_ipv6.address + '/' + ansible_default_ipv6.prefix }}" ipv6_subnet_size: "{{ ipv6_default | ansible.utils.ipaddr('size') }}" ipv6_egress_ip: >- - {{ (ipv6_default | ansible.utils.next_nth_usable(15 | random(seed=algo_server_name + ansible_fqdn))) + '/124' if ipv6_subnet_size|int > 1 else ipv6_default }} + {{ (ipv6_default | ansible.utils.next_nth_usable(15 | random(seed=algo_server_name + ansible_fqdn))) + '/124' if ipv6_subnet_size | int > 1 else ipv6_default }} diff --git a/roles/common/tasks/facts.yml b/roles/common/tasks/facts.yml index 68162645..6d3d1cc5 100644 --- a/roles/common/tasks/facts.yml +++ b/roles/common/tasks/facts.yml @@ -1,13 +1,13 @@ --- - name: Define facts set_fact: - p12_export_password: "{{ p12_password|default(lookup('password', '/dev/null length=9 chars=ascii_letters,digits,_,@')) }}" + p12_export_password: "{{ p12_password | default(lookup('password', '/dev/null length=9 chars=ascii_letters,digits,_,@')) }}" tags: update-users no_log: true - name: Set facts set_fact: - CA_password: "{{ ca_password|default(lookup('password', '/dev/null length=16 chars=ascii_letters,digits,_,@')) }}" + CA_password: "{{ ca_password | default(lookup('password', '/dev/null length=16 chars=ascii_letters,digits,_,@')) }}" IP_subject_alt_name: "{{ IP_subject_alt_name }}" no_log: true @@ -18,5 +18,5 @@ - name: Check size of MTU set_fact: - reduce_mtu: "{{ 1500 - ansible_default_ipv4['mtu']|int if reduce_mtu|int == 0 and ansible_default_ipv4['mtu']|int < 1500 else reduce_mtu|int }}" + reduce_mtu: "{{ 1500 - ansible_default_ipv4['mtu'] | int if reduce_mtu | int == 0 and ansible_default_ipv4['mtu'] | int < 1500 else reduce_mtu | int }}" tags: always diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index 573a9f1f..f1e784bf 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -19,7 +19,7 @@ sysctl: name="{{ item.item }}" value="{{ item.value }}" when: item.item with_items: - - "{{ sysctl|default([]) }}" + - "{{ sysctl | default([]) }}" tags: - always diff --git a/roles/common/tasks/ubuntu.yml b/roles/common/tasks/ubuntu.yml index 43250782..b5a919ac 100644 --- a/roles/common/tasks/ubuntu.yml +++ b/roles/common/tasks/ubuntu.yml @@ -140,7 +140,7 @@ - name: Install tools (legacy method) apt: - name: "{{ tools|default([]) }}" + name: "{{ tools | default([]) }}" state: present update_cache: true when: @@ -161,5 +161,31 @@ include_tasks: aip/main.yml when: alternative_ingress_ip +- name: Ubuntu 22.04+ | Use iptables-legacy for compatibility + block: + - name: Install iptables packages + apt: + name: + - iptables + - iptables-persistent + state: present + update_cache: true + + - name: Configure iptables-legacy as default + alternatives: + name: "{{ item }}" + path: "/usr/sbin/{{ item }}-legacy" + with_items: + - iptables + - ip6tables + - iptables-save + - iptables-restore + - ip6tables-save + - ip6tables-restore + when: + - ansible_distribution == "Ubuntu" + - ansible_distribution_version is version('22.04', '>=') + tags: iptables + - include_tasks: iptables.yml tags: iptables diff --git a/roles/common/templates/50unattended-upgrades.j2 b/roles/common/templates/50unattended-upgrades.j2 index 87c4b07d..4e8ab299 100644 --- a/roles/common/templates/50unattended-upgrades.j2 +++ b/roles/common/templates/50unattended-upgrades.j2 @@ -64,7 +64,7 @@ Unattended-Upgrade::Remove-Unused-Dependencies "true"; // Automatically reboot *WITHOUT CONFIRMATION* // if the file /var/run/reboot-required is found after the upgrade -Unattended-Upgrade::Automatic-Reboot "{{ unattended_reboot.enabled|lower }}"; +Unattended-Upgrade::Automatic-Reboot "{{ unattended_reboot.enabled | lower }}"; // If automatic reboot is enabled and needed, reboot at the specific // time instead of immediately diff --git a/roles/common/templates/rules.v4.j2 b/roles/common/templates/rules.v4.j2 index c127bdce..bf3cd047 100644 --- a/roles/common/templates/rules.v4.j2 +++ b/roles/common/templates/rules.v4.j2 @@ -1,5 +1,5 @@ {% set subnets = ([strongswan_network] if ipsec_enabled else []) + ([wireguard_network_ipv4] if wireguard_enabled else []) %} -{% set ports = (['500', '4500'] if ipsec_enabled else []) + ([wireguard_port] if wireguard_enabled else []) + ([wireguard_port_actual] if wireguard_enabled and wireguard_port|int == wireguard_port_avoid|int else []) %} +{% set ports = (['500', '4500'] if ipsec_enabled else []) + ([wireguard_port] if wireguard_enabled else []) + ([wireguard_port_actual] if wireguard_enabled and wireguard_port | int == wireguard_port_avoid | int else []) %} #### The mangle table # This table allows us to modify packet headers @@ -13,8 +13,8 @@ :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] -{% if reduce_mtu|int > 0 and ipsec_enabled %} --A FORWARD -s {{ strongswan_network }} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss {{ 1360 - reduce_mtu|int }} +{% if reduce_mtu | int > 0 and ipsec_enabled %} +-A FORWARD -s {{ strongswan_network }} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss {{ 1360 - reduce_mtu | int }} {% endif %} COMMIT @@ -29,14 +29,14 @@ COMMIT :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] -{% if wireguard_enabled and wireguard_port|int == wireguard_port_avoid|int %} +{% if wireguard_enabled and wireguard_port | int == wireguard_port_avoid | int %} # Handle the special case of allowing access to WireGuard over an already used # port like 53 --A PREROUTING -s {{ subnets|join(',') }} -p udp --dport {{ wireguard_port_avoid }} -j RETURN +-A PREROUTING -s {{ subnets | join(',') }} -p udp --dport {{ wireguard_port_avoid }} -j RETURN -A PREROUTING --in-interface {{ ansible_default_ipv4['interface'] }} -p udp --dport {{ wireguard_port_avoid }} -j REDIRECT --to-port {{ wireguard_port_actual }} {% endif %} # Allow traffic from the VPN network to the outside world, and replies --A POSTROUTING -s {{ subnets|join(',') }} -m policy --pol none --dir out {{ '-j SNAT --to ' + snat_aipv4 if snat_aipv4 else '-j MASQUERADE' }} +-A POSTROUTING -s {{ subnets | join(',') }} -m policy --pol none --dir out {{ '-j SNAT --to ' + snat_aipv4 if snat_aipv4 else '-j MASQUERADE' }} COMMIT @@ -63,8 +63,8 @@ COMMIT -A INPUT -p ah -j ACCEPT # rate limit ICMP traffic per source -A INPUT -p icmp --icmp-type echo-request -m hashlimit --hashlimit-upto 5/s --hashlimit-mode srcip --hashlimit-srcmask 32 --hashlimit-name icmp-echo-drop -j ACCEPT -# Accept IPSEC/WireGuard traffic to ports {{ subnets|join(',') }} --A INPUT -p udp -m multiport --dports {{ ports|join(',') }} -j ACCEPT +# Accept IPSEC/WireGuard traffic to ports {{ subnets | join(',') }} +-A INPUT -p udp -m multiport --dports {{ ports | join(',') }} -j ACCEPT # Allow new traffic to port {{ ansible_ssh_port }} (SSH) -A INPUT -p tcp --dport {{ ansible_ssh_port }} -m conntrack --ctstate NEW -j ACCEPT @@ -82,12 +82,12 @@ COMMIT -A INPUT -d {{ local_service_ip }} -p udp --dport 53 -j ACCEPT # Drop traffic between VPN clients --A FORWARD -s {{ subnets|join(',') }} -d {{ subnets|join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} +-A FORWARD -s {{ subnets | join(',') }} -d {{ subnets | join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} # Drop traffic to VPN clients from SSH tunnels --A OUTPUT -d {{ subnets|join(',') }} -m owner --gid-owner 15000 -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} +-A OUTPUT -d {{ subnets | join(',') }} -m owner --gid-owner 15000 -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} # Drop traffic to the link-local network --A FORWARD -s {{ subnets|join(',') }} -d 169.254.0.0/16 -j DROP +-A FORWARD -s {{ subnets | join(',') }} -d 169.254.0.0/16 -j DROP # Drop traffic to the link-local network from SSH tunnels -A OUTPUT -d 169.254.0.0/16 -m owner --gid-owner 15000 -j DROP diff --git a/roles/common/templates/rules.v6.j2 b/roles/common/templates/rules.v6.j2 index b2f6f20c..9515b685 100644 --- a/roles/common/templates/rules.v6.j2 +++ b/roles/common/templates/rules.v6.j2 @@ -1,5 +1,5 @@ {% set subnets = ([strongswan_network_ipv6] if ipsec_enabled else []) + ([wireguard_network_ipv6] if wireguard_enabled else []) %} -{% set ports = (['500', '4500'] if ipsec_enabled else []) + ([wireguard_port] if wireguard_enabled else []) + ([wireguard_port_actual] if wireguard_enabled and wireguard_port|int == wireguard_port_avoid|int else []) %} +{% set ports = (['500', '4500'] if ipsec_enabled else []) + ([wireguard_port] if wireguard_enabled else []) + ([wireguard_port_actual] if wireguard_enabled and wireguard_port | int == wireguard_port_avoid | int else []) %} #### The mangle table # This table allows us to modify packet headers @@ -13,8 +13,8 @@ :OUTPUT ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] -{% if reduce_mtu|int > 0 and ipsec_enabled %} --A FORWARD -s {{ strongswan_network_ipv6 }} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss {{ 1340 - reduce_mtu|int }} +{% if reduce_mtu | int > 0 and ipsec_enabled %} +-A FORWARD -s {{ strongswan_network_ipv6 }} -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss {{ 1340 - reduce_mtu | int }} {% endif %} COMMIT @@ -28,14 +28,14 @@ COMMIT :PREROUTING ACCEPT [0:0] :POSTROUTING ACCEPT [0:0] -{% if wireguard_enabled and wireguard_port|int == wireguard_port_avoid|int %} +{% if wireguard_enabled and wireguard_port | int == wireguard_port_avoid | int %} # Handle the special case of allowing access to WireGuard over an already used # port like 53 --A PREROUTING -s {{ subnets|join(',') }} -p udp --dport {{ wireguard_port_avoid }} -j RETURN +-A PREROUTING -s {{ subnets | join(',') }} -p udp --dport {{ wireguard_port_avoid }} -j RETURN -A PREROUTING --in-interface {{ ansible_default_ipv6['interface'] }} -p udp --dport {{ wireguard_port_avoid }} -j REDIRECT --to-port {{ wireguard_port_actual }} {% endif %} # Allow traffic from the VPN network to the outside world, and replies --A POSTROUTING -s {{ subnets|join(',') }} -m policy --pol none --dir out {{ '-j SNAT --to ' + ipv6_egress_ip | ansible.utils.ipaddr('address') if alternative_ingress_ip else '-j MASQUERADE' }} +-A POSTROUTING -s {{ subnets | join(',') }} -m policy --pol none --dir out {{ '-j SNAT --to ' + ipv6_egress_ip | ansible.utils.ipaddr('address') if alternative_ingress_ip else '-j MASQUERADE' }} COMMIT @@ -69,8 +69,8 @@ COMMIT -A INPUT -m ah -j ACCEPT # rate limit ICMP traffic per source -A INPUT -p icmpv6 --icmpv6-type echo-request -m hashlimit --hashlimit-upto 5/s --hashlimit-mode srcip --hashlimit-srcmask 32 --hashlimit-name icmp-echo-drop -j ACCEPT -# Accept IPSEC/WireGuard traffic to ports {{ subnets|join(',') }} --A INPUT -p udp -m multiport --dports {{ ports|join(',') }} -j ACCEPT +# Accept IPSEC/WireGuard traffic to ports {{ subnets | join(',') }} +-A INPUT -p udp -m multiport --dports {{ ports | join(',') }} -j ACCEPT # Allow new traffic to port {{ ansible_ssh_port }} (SSH) -A INPUT -p tcp --dport {{ ansible_ssh_port }} -m conntrack --ctstate NEW -j ACCEPT @@ -92,9 +92,9 @@ COMMIT -A INPUT -d {{ local_service_ipv6 }}/128 -p udp --dport 53 -j ACCEPT # Drop traffic between VPN clients --A FORWARD -s {{ subnets|join(',') }} -d {{ subnets|join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} +-A FORWARD -s {{ subnets | join(',') }} -d {{ subnets | join(',') }} -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} # Drop traffic to VPN clients from SSH tunnels --A OUTPUT -d {{ subnets|join(',') }} -m owner --gid-owner 15000 -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} +-A OUTPUT -d {{ subnets | join(',') }} -m owner --gid-owner 15000 -j {{ "DROP" if BetweenClients_DROP else "ACCEPT" }} -A FORWARD -j ICMPV6-CHECK -A FORWARD -p tcp --dport 445 -j {{ "DROP" if block_smb else "ACCEPT" }} diff --git a/roles/dns/tasks/dns_adblocking.yml b/roles/dns/tasks/dns_adblocking.yml index 5ef90249..ec2271a5 100644 --- a/roles/dns/tasks/dns_adblocking.yml +++ b/roles/dns/tasks/dns_adblocking.yml @@ -4,7 +4,7 @@ src: adblock.sh.j2 dest: /usr/local/sbin/adblock.sh owner: root - group: "{{ root_group|default('root') }}" + group: "{{ root_group | default('root') }}" mode: 0755 - name: Adblock script added to cron diff --git a/roles/dns/tasks/main.yml b/roles/dns/tasks/main.yml index 57ac5ac7..f845a382 100644 --- a/roles/dns/tasks/main.yml +++ b/roles/dns/tasks/main.yml @@ -7,14 +7,14 @@ - name: dnscrypt-proxy ip-blacklist configured template: src: ip-blacklist.txt.j2 - dest: "{{ config_prefix|default('/') }}etc/dnscrypt-proxy/ip-blacklist.txt" + dest: "{{ config_prefix | default('/') }}etc/dnscrypt-proxy/ip-blacklist.txt" notify: - restart dnscrypt-proxy - name: dnscrypt-proxy configured template: src: dnscrypt-proxy.toml.j2 - dest: "{{ config_prefix|default('/') }}etc/dnscrypt-proxy/dnscrypt-proxy.toml" + dest: "{{ config_prefix | default('/') }}etc/dnscrypt-proxy/dnscrypt-proxy.toml" notify: - restart dnscrypt-proxy diff --git a/roles/dns/templates/adblock.sh.j2 b/roles/dns/templates/adblock.sh.j2 index cd0be35c..c43efbb6 100644 --- a/roles/dns/templates/adblock.sh.j2 +++ b/roles/dns/templates/adblock.sh.j2 @@ -5,7 +5,7 @@ TEMP="$(mktemp)" TEMP_SORTED="$(mktemp)" WHITELIST="/etc/dnscrypt-proxy/white.list" BLACKLIST="/etc/dnscrypt-proxy/black.list" -BLOCKHOSTS="{{ config_prefix|default('/') }}etc/dnscrypt-proxy/blacklist.txt" +BLOCKHOSTS="{{ config_prefix | default('/') }}etc/dnscrypt-proxy/blacklist.txt" BLOCKLIST_URLS="{% for url in adblock_lists %}{{ url }} {% endfor %}" #Delete the old block.hosts to make room for the updates diff --git a/roles/ssh_tunneling/handlers/main.yml b/roles/ssh_tunneling/handlers/main.yml index eae0ef1c..84a7eef7 100644 --- a/roles/ssh_tunneling/handlers/main.yml +++ b/roles/ssh_tunneling/handlers/main.yml @@ -1,3 +1,3 @@ --- - name: restart ssh - service: name="{{ ssh_service_name|default('ssh') }}" state=restarted + service: name="{{ ssh_service_name | default('ssh') }}" state=restarted diff --git a/roles/ssh_tunneling/tasks/main.yml b/roles/ssh_tunneling/tasks/main.yml index f9680f3a..bb6bf26d 100644 --- a/roles/ssh_tunneling/tasks/main.yml +++ b/roles/ssh_tunneling/tasks/main.yml @@ -25,7 +25,7 @@ state: directory mode: 0755 owner: root - group: "{{ root_group|default('root') }}" + group: "{{ root_group | default('root') }}" - block: - name: Ensure that the SSH users exist @@ -67,7 +67,7 @@ passphrase: "{{ p12_export_password }}" cipher: auto force: false - no_log: "{{ algo_no_log|bool }}" + no_log: "{{ algo_no_log | bool }}" when: not item.stat.exists with_items: "{{ privatekey.results }}" register: openssl_privatekey @@ -79,7 +79,7 @@ privatekey_passphrase: "{{ p12_export_password }}" format: OpenSSH force: true - no_log: "{{ algo_no_log|bool }}" + no_log: "{{ algo_no_log | bool }}" when: item.changed with_items: "{{ openssl_privatekey.results }}" diff --git a/roles/strongswan/defaults/main.yml b/roles/strongswan/defaults/main.yml index 0ffb344f..0816b612 100644 --- a/roles/strongswan/defaults/main.yml +++ b/roles/strongswan/defaults/main.yml @@ -17,7 +17,7 @@ openssl_constraint_random_id: "{{ IP_subject_alt_name | to_uuid }}.algo" # Subject Alternative Name (SAN) configuration - CRITICAL for client compatibility # Modern clients (especially macOS/iOS) REQUIRE SAN extension in server certificates # Without SAN, IKEv2 connections will fail with certificate validation errors -subjectAltName_type: "{{ 'DNS' if IP_subject_alt_name|regex_search('[a-z]') else 'IP' }}" +subjectAltName_type: "{{ 'DNS' if IP_subject_alt_name | regex_search('[a-z]') else 'IP' }}" subjectAltName: >- {{ subjectAltName_type }}:{{ IP_subject_alt_name }} {%- if ipv6_support -%},IP:{{ ansible_default_ipv6['address'] }}{%- endif -%} diff --git a/roles/strongswan/tasks/client_configs.yml b/roles/strongswan/tasks/client_configs.yml index 814f25e2..08e51429 100644 --- a/roles/strongswan/tasks/client_configs.yml +++ b/roles/strongswan/tasks/client_configs.yml @@ -23,7 +23,7 @@ with_together: - "{{ users }}" - "{{ PayloadContent.results }}" - no_log: "{{ algo_no_log|bool }}" + no_log: "{{ algo_no_log | bool }}" - name: Build the client ipsec config file template: diff --git a/roles/strongswan/tasks/distribute_keys.yml b/roles/strongswan/tasks/distribute_keys.yml index 55d1da12..b9a22d96 100644 --- a/roles/strongswan/tasks/distribute_keys.yml +++ b/roles/strongswan/tasks/distribute_keys.yml @@ -2,7 +2,7 @@ - name: Copy the keys to the strongswan directory copy: src: "{{ ipsec_pki_path }}/{{ item.src }}" - dest: "{{ config_prefix|default('/') }}etc/ipsec.d/{{ item.dest }}" + dest: "{{ config_prefix | default('/') }}etc/ipsec.d/{{ item.dest }}" owner: "{{ item.owner }}" group: "{{ item.group }}" mode: "{{ item.mode }}" @@ -10,17 +10,17 @@ - src: cacert.pem dest: cacerts/ca.crt owner: strongswan - group: "{{ root_group|default('root') }}" + group: "{{ root_group | default('root') }}" mode: "0600" - src: certs/{{ IP_subject_alt_name }}.crt dest: certs/{{ IP_subject_alt_name }}.crt owner: strongswan - group: "{{ root_group|default('root') }}" + group: "{{ root_group | default('root') }}" mode: "0600" - src: private/{{ IP_subject_alt_name }}.key dest: private/{{ IP_subject_alt_name }}.key owner: strongswan - group: "{{ root_group|default('root') }}" + group: "{{ root_group | default('root') }}" mode: "0600" notify: - restart strongswan diff --git a/roles/strongswan/tasks/ipsec_configuration.yml b/roles/strongswan/tasks/ipsec_configuration.yml index 7ba44c37..18b9634d 100644 --- a/roles/strongswan/tasks/ipsec_configuration.yml +++ b/roles/strongswan/tasks/ipsec_configuration.yml @@ -2,7 +2,7 @@ - name: Setup the config files from our templates template: src: "{{ item.src }}" - dest: "{{ config_prefix|default('/') }}etc/{{ item.dest }}" + dest: "{{ config_prefix | default('/') }}etc/{{ item.dest }}" owner: "{{ item.owner }}" group: "{{ item.group }}" mode: "{{ item.mode }}" @@ -10,22 +10,22 @@ - src: strongswan.conf.j2 dest: strongswan.conf owner: root - group: "{{ root_group|default('root') }}" + group: "{{ root_group | default('root') }}" mode: "0644" - src: ipsec.conf.j2 dest: ipsec.conf owner: root - group: "{{ root_group|default('root') }}" + group: "{{ root_group | default('root') }}" mode: "0644" - src: ipsec.secrets.j2 dest: ipsec.secrets owner: strongswan - group: "{{ root_group|default('root') }}" + group: "{{ root_group | default('root') }}" mode: "0600" - src: charon.conf.j2 dest: strongswan.d/charon.conf owner: root - group: "{{ root_group|default('root') }}" + group: "{{ root_group | default('root') }}" mode: "0644" notify: - restart strongswan @@ -33,7 +33,7 @@ - name: Get loaded plugins shell: | set -o pipefail - find {{ config_prefix|default('/') }}etc/strongswan.d/charon/ -type f -name '*.conf' -exec basename {} \; | + find {{ config_prefix | default('/') }}etc/strongswan.d/charon/ -type f -name '*.conf' -exec basename {} \; | cut -f1 -d. changed_when: false args: @@ -42,7 +42,7 @@ - name: Disable unneeded plugins lineinfile: - dest: "{{ config_prefix|default('/') }}etc/strongswan.d/charon/{{ item }}.conf" + dest: "{{ config_prefix | default('/') }}etc/strongswan.d/charon/{{ item }}.conf" regexp: .*load.* line: load = no state: present @@ -52,7 +52,7 @@ with_items: "{{ strongswan_plugins.stdout_lines }}" - name: Ensure that required plugins are enabled - lineinfile: dest="{{ config_prefix|default('/') }}etc/strongswan.d/charon/{{ item }}.conf" regexp='.*load.*' line='load = yes' state=present + lineinfile: dest="{{ config_prefix | default('/') }}etc/strongswan.d/charon/{{ item }}.conf" regexp='.*load.*' line='load = yes' state=present notify: - restart strongswan when: item in strongswan_enabled_plugins or item in strongswan_additional_plugins diff --git a/roles/strongswan/tasks/openssl.yml b/roles/strongswan/tasks/openssl.yml index 170c6e61..8c5c0267 100644 --- a/roles/strongswan/tasks/openssl.yml +++ b/roles/strongswan/tasks/openssl.yml @@ -276,6 +276,6 @@ - name: Copy the CRL to the vpn server copy: src: "{{ ipsec_pki_path }}/crl.pem" - dest: "{{ config_prefix|default('/') }}etc/ipsec.d/crls/algo.root.pem" + dest: "{{ config_prefix | default('/') }}etc/ipsec.d/crls/algo.root.pem" notify: - rereadcrls diff --git a/roles/strongswan/tasks/ubuntu.yml b/roles/strongswan/tasks/ubuntu.yml index f98fcf4c..b17b3a0b 100644 --- a/roles/strongswan/tasks/ubuntu.yml +++ b/roles/strongswan/tasks/ubuntu.yml @@ -2,6 +2,13 @@ - name: Set OS specific facts set_fact: strongswan_additional_plugins: [] + +- name: Ubuntu | Ensure af_key kernel module is loaded + modprobe: + name: af_key + state: present + persistent: present + - name: Ubuntu | Install strongSwan (individual) apt: name: strongswan diff --git a/roles/strongswan/templates/mobileconfig.j2 b/roles/strongswan/templates/mobileconfig.j2 index bf6f6709..abdada99 100644 --- a/roles/strongswan/templates/mobileconfig.j2 +++ b/roles/strongswan/templates/mobileconfig.j2 @@ -13,8 +13,8 @@ OnDemandRules {% if algo_ondemand_wifi or algo_ondemand_cellular %} - {% if algo_ondemand_wifi_exclude|b64decode != '_null' %} - {% set WIFI_EXCLUDE_LIST = (algo_ondemand_wifi_exclude|b64decode|string).split(',') %} + {% if algo_ondemand_wifi_exclude | b64decode != '_null' %} + {% set WIFI_EXCLUDE_LIST = (algo_ondemand_wifi_exclude | b64decode | string).split(',') %} Action Disconnect @@ -23,7 +23,7 @@ SSIDMatch {% for network_name in WIFI_EXCLUDE_LIST %} - {{ network_name|e }} + {{ network_name | e }} {% endfor %} diff --git a/roles/wireguard/defaults/main.yml b/roles/wireguard/defaults/main.yml index a073d018..1d135a36 100644 --- a/roles/wireguard/defaults/main.yml +++ b/roles/wireguard/defaults/main.yml @@ -7,15 +7,15 @@ wireguard_port_avoid: 53 wireguard_port_actual: 51820 keys_clean_all: false wireguard_dns_servers: >- - {% if algo_dns_adblocking|default(false)|bool or dns_encryption|default(false)|bool %} + {% if algo_dns_adblocking | default(false) | bool or dns_encryption | default(false) | bool %} {{ local_service_ip }}{{ ', ' + local_service_ipv6 if ipv6_support else '' }} {% else %} {% for host in dns_servers.ipv4 %}{{ host }}{% if not loop.last %},{% endif %}{% endfor %} {%- if ipv6_support %},{% for host in dns_servers.ipv6 %}{{ host }}{% if not loop.last %},{% endif %}{% endfor %}{% endif %} {% endif %} wireguard_client_ip: >- - {{ wireguard_network_ipv4 | ansible.utils.ipmath(index|int+2) }} - {{ ',' + wireguard_network_ipv6 | ansible.utils.ipmath(index|int+2) if ipv6_support else '' }} + {{ wireguard_network_ipv4 | ansible.utils.ipmath(index | int+2) }} + {{ ',' + wireguard_network_ipv6 | ansible.utils.ipmath(index | int+2) if ipv6_support else '' }} wireguard_server_ip: >- {{ wireguard_network_ipv4 | ansible.utils.ipaddr('1') }} {{ ',' + wireguard_network_ipv6 | ansible.utils.ipaddr('1') if ipv6_support else '' }} diff --git a/roles/wireguard/tasks/main.yml b/roles/wireguard/tasks/main.yml index b63fe1c0..307dc357 100644 --- a/roles/wireguard/tasks/main.yml +++ b/roles/wireguard/tasks/main.yml @@ -79,7 +79,7 @@ - name: WireGuard configured template: src: server.conf.j2 - dest: "{{ config_prefix|default('/') }}etc/wireguard/{{ wireguard_interface }}.conf" + dest: "{{ config_prefix | default('/') }}etc/wireguard/{{ wireguard_interface }}.conf" mode: "0600" notify: restart wireguard tags: update-users diff --git a/roles/wireguard/templates/client.conf.j2 b/roles/wireguard/templates/client.conf.j2 index a6e48a57..8e6c8d5b 100644 --- a/roles/wireguard/templates/client.conf.j2 +++ b/roles/wireguard/templates/client.conf.j2 @@ -2,7 +2,7 @@ PrivateKey = {{ lookup('file', wireguard_pki_path + '/private/' + item.1) }} Address = {{ wireguard_client_ip }} DNS = {{ wireguard_dns_servers }} -{% if reduce_mtu|int > 0 %}MTU = {{ 1420 - reduce_mtu|int }} +{% if reduce_mtu | int > 0 %}MTU = {{ 1420 - reduce_mtu | int }} {% endif %} [Peer] @@ -10,4 +10,4 @@ PublicKey = {{ lookup('file', wireguard_pki_path + '/public/' + IP_subject_alt_n PresharedKey = {{ lookup('file', wireguard_pki_path + '/preshared/' + item.1) }} AllowedIPs = 0.0.0.0/0,::/0 Endpoint = {% if ':' in IP_subject_alt_name %}[{{ IP_subject_alt_name }}]:{{ wireguard_port }}{% else %}{{ IP_subject_alt_name }}:{{ wireguard_port }}{% endif %} -{{ 'PersistentKeepalive = ' + wireguard_PersistentKeepalive|string if wireguard_PersistentKeepalive > 0 else '' }} +{{ 'PersistentKeepalive = ' + wireguard_PersistentKeepalive | string if wireguard_PersistentKeepalive > 0 else '' }} diff --git a/roles/wireguard/templates/server.conf.j2 b/roles/wireguard/templates/server.conf.j2 index 4f00e81d..6d0aa718 100644 --- a/roles/wireguard/templates/server.conf.j2 +++ b/roles/wireguard/templates/server.conf.j2 @@ -1,6 +1,6 @@ [Interface] Address = {{ wireguard_server_ip }} -ListenPort = {{ wireguard_port_actual if wireguard_port|int == wireguard_port_avoid|int else wireguard_port }} +ListenPort = {{ wireguard_port_actual if wireguard_port | int == wireguard_port_avoid | int else wireguard_port }} PrivateKey = {{ lookup('file', wireguard_pki_path + '/private/' + IP_subject_alt_name) }} SaveConfig = false @@ -12,6 +12,6 @@ SaveConfig = false # {{ u }} PublicKey = {{ lookup('file', wireguard_pki_path + '/public/' + u) }} PresharedKey = {{ lookup('file', wireguard_pki_path + '/preshared/' + u) }} -AllowedIPs = {{ wireguard_network_ipv4 | ansible.utils.ipmath(index|int+1) | ansible.utils.ipv4('address') }}/32{{ ',' + wireguard_network_ipv6 | ansible.utils.ipmath(index|int+1) | ansible.utils.ipv6('address') + '/128' if ipv6_support else '' }} +AllowedIPs = {{ wireguard_network_ipv4 | ansible.utils.ipmath(index | int+1) | ansible.utils.ipv4('address') }}/32{{ ',' + wireguard_network_ipv6 | ansible.utils.ipmath(index | int+1) | ansible.utils.ipv6('address') + '/128' if ipv6_support else '' }} {% endif %} {% endfor %} diff --git a/roles/wireguard/templates/vpn-dict.j2 b/roles/wireguard/templates/vpn-dict.j2 index 6444df96..4cbec89b 100644 --- a/roles/wireguard/templates/vpn-dict.j2 +++ b/roles/wireguard/templates/vpn-dict.j2 @@ -32,8 +32,8 @@ OnDemandRules {% if algo_ondemand_wifi or algo_ondemand_cellular %} - {% if algo_ondemand_wifi_exclude|b64decode != '_null' %} - {% set WIFI_EXCLUDE_LIST = (algo_ondemand_wifi_exclude|b64decode|string).split(',') %} + {% if algo_ondemand_wifi_exclude | b64decode != '_null' %} + {% set WIFI_EXCLUDE_LIST = (algo_ondemand_wifi_exclude | b64decode | string).split(',') %} Action Disconnect @@ -42,7 +42,7 @@ SSIDMatch {% for network_name in WIFI_EXCLUDE_LIST %} - {{ network_name|e }} + {{ network_name | e }} {% endfor %} diff --git a/server.yml b/server.yml index 663ae2f5..1045f749 100644 --- a/server.yml +++ b/server.yml @@ -189,7 +189,7 @@ content: | server: {{ 'localhost' if inventory_hostname == 'localhost' else inventory_hostname }} server_user: {{ ansible_ssh_user }} - ansible_ssh_port: "{{ ansible_ssh_port|default(22) }}" + ansible_ssh_port: "{{ ansible_ssh_port | default(22) }}" {% if algo_provider != "local" %} ansible_ssh_private_key_file: {{ SSH_keys.private }} {% endif %} @@ -204,7 +204,7 @@ IP_subject_alt_name: {{ IP_subject_alt_name }} ipsec_enabled: {{ ipsec_enabled }} wireguard_enabled: {{ wireguard_enabled }} - {% if tests|default(false)|bool %} + {% if tests | default(false) | bool %} ca_password: '{{ CA_password }}' p12_password: '{{ p12_export_password }}' {% endif %} diff --git a/tests/unit/test_lightsail_boto3_fix.py b/tests/unit/test_lightsail_boto3_fix.py new file mode 100644 index 00000000..d8dbbd4d --- /dev/null +++ b/tests/unit/test_lightsail_boto3_fix.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python +""" +Test for AWS Lightsail boto3 parameter fix. +Verifies that get_aws_connection_info() works without the deprecated boto3 parameter. +Addresses issue #14822. +""" + +import importlib.util +import os +import sys +import unittest +from unittest.mock import MagicMock, patch + +# Add the library directory to the path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../library')) + + +class TestLightsailBoto3Fix(unittest.TestCase): + """Test that lightsail_region_facts.py works without boto3 parameter.""" + + def setUp(self): + """Set up test fixtures.""" + # Mock the ansible module_utils since we're testing outside of Ansible + self.mock_modules = { + 'ansible.module_utils.basic': MagicMock(), + 'ansible.module_utils.ec2': MagicMock(), + 'ansible.module_utils.aws.core': MagicMock(), + } + + # Apply mocks + self.patches = [] + for module_name, mock_module in self.mock_modules.items(): + patcher = patch.dict('sys.modules', {module_name: mock_module}) + patcher.start() + self.patches.append(patcher) + + def tearDown(self): + """Clean up patches.""" + for patcher in self.patches: + patcher.stop() + + def test_lightsail_region_facts_imports(self): + """Test that lightsail_region_facts can be imported.""" + try: + # Import the module + spec = importlib.util.spec_from_file_location( + "lightsail_region_facts", + os.path.join(os.path.dirname(__file__), '../../library/lightsail_region_facts.py') + ) + module = importlib.util.module_from_spec(spec) + + # This should not raise an error + spec.loader.exec_module(module) + + # Verify the module loaded + self.assertIsNotNone(module) + self.assertTrue(hasattr(module, 'main')) + + except Exception as e: + self.fail(f"Failed to import lightsail_region_facts: {e}") + + def test_get_aws_connection_info_called_without_boto3(self): + """Test that get_aws_connection_info is called without boto3 parameter.""" + # Mock get_aws_connection_info to track calls + mock_get_aws_connection_info = MagicMock( + return_value=('us-west-2', None, {}) + ) + + with patch('ansible.module_utils.ec2.get_aws_connection_info', mock_get_aws_connection_info): + # Import the module + spec = importlib.util.spec_from_file_location( + "lightsail_region_facts", + os.path.join(os.path.dirname(__file__), '../../library/lightsail_region_facts.py') + ) + module = importlib.util.module_from_spec(spec) + + # Mock AnsibleModule + mock_ansible_module = MagicMock() + mock_ansible_module.params = {} + mock_ansible_module.check_mode = False + + with patch('ansible.module_utils.basic.AnsibleModule', return_value=mock_ansible_module): + # Execute the module + try: + spec.loader.exec_module(module) + module.main() + except SystemExit: + # Module calls exit_json or fail_json which raises SystemExit + pass + except Exception: + # We expect some exceptions since we're mocking, but we want to check the call + pass + + # Verify get_aws_connection_info was called + if mock_get_aws_connection_info.called: + # Get the call arguments + call_args = mock_get_aws_connection_info.call_args + + # Ensure boto3=True is NOT in the arguments + if call_args: + # Check positional arguments + if call_args[0]: # args + self.assertTrue(len(call_args[0]) <= 1, + "get_aws_connection_info should be called with at most 1 positional arg (module)") + + # Check keyword arguments + if call_args[1]: # kwargs + self.assertNotIn('boto3', call_args[1], + "get_aws_connection_info should not be called with boto3 parameter") + + def test_no_boto3_parameter_in_source(self): + """Verify that boto3 parameter is not present in the source code.""" + lightsail_path = os.path.join(os.path.dirname(__file__), '../../library/lightsail_region_facts.py') + + with open(lightsail_path) as f: + content = f.read() + + # Check that boto3=True is not in the file + self.assertNotIn('boto3=True', content, + "boto3=True parameter should not be present in lightsail_region_facts.py") + + # Check that boto3 parameter is not used with get_aws_connection_info + self.assertNotIn('get_aws_connection_info(module, boto3', content, + "get_aws_connection_info should not be called with boto3 parameter") + + def test_regression_issue_14822(self): + """ + Regression test for issue #14822. + Ensures that the deprecated boto3 parameter is not used. + """ + # This test documents the specific issue that was fixed + # The boto3 parameter was deprecated and removed in amazon.aws collection + # that comes with Ansible 11.x + + lightsail_path = os.path.join(os.path.dirname(__file__), '../../library/lightsail_region_facts.py') + + with open(lightsail_path) as f: + lines = f.readlines() + + # Find the line that calls get_aws_connection_info + for line_num, line in enumerate(lines, 1): + if 'get_aws_connection_info' in line and 'region' in line: + # This should be around line 85 + # Verify it doesn't have boto3=True + self.assertNotIn('boto3', line, + f"Line {line_num} should not contain boto3 parameter") + + # Verify the correct format + self.assertIn('get_aws_connection_info(module)', line, + f"Line {line_num} should call get_aws_connection_info(module) without boto3") + break + else: + self.fail("Could not find get_aws_connection_info call in lightsail_region_facts.py") + + +if __name__ == '__main__': + unittest.main() diff --git a/users.yml b/users.yml index 02aa55a3..d6c1ea92 100644 --- a/users.yml +++ b/users.yml @@ -76,10 +76,10 @@ add_host: name: "{{ algo_server }}" groups: vpn-host - ansible_ssh_user: "{{ server_user|default('root') }}" + ansible_ssh_user: "{{ server_user | default('root') }}" ansible_connection: "{% if algo_server == 'localhost' %}local{% else %}ssh{% endif %}" ansible_python_interpreter: /usr/bin/python3 - CA_password: "{{ CA_password|default(omit) }}" + CA_password: "{{ CA_password | default(omit) }}" rescue: - include_tasks: playbooks/rescue.yml diff --git a/uv.lock b/uv.lock index 91091f08..60d0bf36 100644 --- a/uv.lock +++ b/uv.lock @@ -68,7 +68,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "ansible", specifier = "==11.8.0" }, + { name = "ansible", specifier = "==11.9.0" }, { name = "azure-identity", marker = "extra == 'azure'", specifier = ">=1.15.0" }, { name = "azure-mgmt-compute", marker = "extra == 'azure'", specifier = ">=30.0.0" }, { name = "azure-mgmt-network", marker = "extra == 'azure'", specifier = ">=25.0.0" }, @@ -99,19 +99,19 @@ dev = [ [[package]] name = "ansible" -version = "11.8.0" +version = "11.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ansible-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/74/b86d14d2c458edf27ddb56d42bf6d07335a0ccfc713f040fb0cbffd30017/ansible-11.8.0.tar.gz", hash = "sha256:28ea032c77f344bb8ea4d7d39f9a5d4e935e6c8b60836c8c8a28b9cf6c9adb1a", size = 44286995, upload-time = "2025-07-16T15:13:22.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/b3/01564da36375f35907c2ec6626d68f4d59e39e566c4b3f874f01d0d2ca19/ansible-11.9.0.tar.gz", hash = "sha256:528ca5a408f11cf1fea00daea7570e68d40e167be38b90c119a7cb45729e4921", size = 44589149, upload-time = "2025-08-12T16:03:31.912Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/47/23fe9f6d9cd533ce4d54f4925cb0b7fdcfd3500b226421aad6166d9aa11c/ansible-11.8.0-py3-none-any.whl", hash = "sha256:a2cd44c0d2c03972f5d676d1b024d09dd3d3edbd418fb0426f4dd356fca9e5b1", size = 56046023, upload-time = "2025-07-16T15:13:17.557Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/093dfe1f7f9f1058c0efa10b685f6049b676aa2c0ecd6f99c8664cafad6a/ansible-11.9.0-py3-none-any.whl", hash = "sha256:79b087ef38105b93e0e092e7013a0f840e154a6a8ce9b5fddd1b47593adc542a", size = 56340779, upload-time = "2025-08-12T16:03:26.18Z" }, ] [[package]] name = "ansible-core" -version = "2.18.7" +version = "2.18.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, @@ -120,9 +120,9 @@ dependencies = [ { name = "pyyaml" }, { name = "resolvelib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/33/cd25e1af669941fbb5c3d7ac4494cf4a288cb11f53225648d552f8bd8e54/ansible_core-2.18.7.tar.gz", hash = "sha256:1a129bf9fcd5dca2b17e83ce77147ee2fbc3c51a4958970152897cc5b6d0aae7", size = 3090256, upload-time = "2025-07-15T17:49:24.074Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/78/8b8680eaf7b1990a8d4c26f25cdf2b2eabaae764a3d8e5299b1d61c7c385/ansible_core-2.18.8.tar.gz", hash = "sha256:b0766215a96a47ce39933d27e1e996ca2beb54cf1b3907c742d35c913b1f78cd", size = 3088636, upload-time = "2025-08-11T19:03:12.495Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/a7/568e51c20f49c9e76a555a876ed641ecc59df29e93868f24cdf8c3289f6a/ansible_core-2.18.7-py3-none-any.whl", hash = "sha256:ac42ecb480fb98890d338072f7298cd462fb2117da6700d989c7ae688962ba69", size = 2209456, upload-time = "2025-07-15T17:49:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/a8/59/aa2c1918224b054c9a0d93812c0b446fa8ddba155dc96c855189fae9c82b/ansible_core-2.18.8-py3-none-any.whl", hash = "sha256:b60bc23b2f11fd0559a79d10ac152b52f58a18ca1b7be0a620dfe87f34ced689", size = 2218673, upload-time = "2025-08-11T19:03:04.75Z" }, ] [[package]]