From 79ddaed45aa4f7b6a697d8f8e5b3c8c9bcd92588 Mon Sep 17 00:00:00 2001 From: Michele Cereda Date: Sat, 21 Sep 2024 14:59:47 +0200 Subject: [PATCH] chore(ansible): save outcomes of working with certificates, improve readability --- knowledge base/https.md | 68 +++++++++ knowledge base/openssl.md | 6 +- snippets/ansible/commands.sh | 6 + snippets/ansible/tasks.yml | 259 +++++++++++++++++++++++++---------- snippets/webserver.sh | 3 +- 5 files changed, 269 insertions(+), 73 deletions(-) create mode 100644 knowledge base/https.md diff --git a/knowledge base/https.md b/knowledge base/https.md new file mode 100644 index 0000000..048381f --- /dev/null +++ b/knowledge base/https.md @@ -0,0 +1,68 @@ +# HTTPS + +TODO + +Intro + + + +1. [TL;DR](#tldr) +1. [Further readings](#further-readings) + 1. [Sources](#sources) + +## TL;DR + + + + + + + +## Further readings + +- [Website] +- [Main repository] + +### Sources + +- [How Does HTTPS Work? RSA Encryption Explained] + + + + + + + +[main repository]: https://github.com/project/ +[website]: https://website/ + + +[how does https work? rsa encryption explained]: https://tiptopsecurity.com/how-does-https-work-rsa-encryption-explained/ diff --git a/knowledge base/openssl.md b/knowledge base/openssl.md index 7ad466f..373bdcc 100644 --- a/knowledge base/openssl.md +++ b/knowledge base/openssl.md @@ -1,7 +1,5 @@ # OpenSSL -## Table of contents - 1. [TL;DR](#tldr) 1. [Create a self signed certificate](#create-a-self-signed-certificate) 1. [Display the contents of a SSL certificate](#display-the-contents-of-a-ssl-certificate) @@ -47,6 +45,7 @@ openssl req -text -noout -verify -in 'request.csr' # Check existing keys and verify their consistency. openssl rsa -check -in 'file.key' +openssl rsa -check -in 'file.key' -noout # Check certificates or keys and return information about them. openssl x509 -text -noout -in 'certificate.crt' @@ -123,7 +122,8 @@ To avoid answering the questions (for automation), add ```sh $ openssl req -x509 -out 'cert.pem' \ - -newkey 'rsa:4096' -keyout 'key.pem' -days '365' -nodes -subj "/C=NL/ST=Nederlands/L=Amsterdam/O=Mek Net/OU=Org/CN=mek.info" + -newkey 'rsa:4096' -keyout 'key.pem' -days '365' -nodes \ + -subj "/C=NL/ST=Nederlands/L=Amsterdam/O=Mek Net/OU=Org/CN=mek.info" Generating a 4096 bit RSA private key ..............................................................................................................................................................................................................................++ ...........................................................................................................................................................................++ diff --git a/snippets/ansible/commands.sh b/snippets/ansible/commands.sh index 71a9994..7f6bb81 100644 --- a/snippets/ansible/commands.sh +++ b/snippets/ansible/commands.sh @@ -60,3 +60,9 @@ ansible-vault view 'ssh.key.pub' --vault-password-file 'password_file.txt' ansible-vault edit 'ssh.key.pub' ANSIBLE_VAULT_PASSWORD_FILE='password_file.txt' ansible-vault decrypt --output '.ssh/id_rsa' 'ssh.key' diff 'some_role/files/ssh.key.plain' <(ansible-vault view --vault-password-file 'password_file.txt' 'some_role/files/ssh.key.enc') + +# List available 'lookup' plugins. +ansible-doc -t 'lookup' -l + +# Show plugin-specific docs and examples. +ansible-doc -t 'lookup' 'fileglob' diff --git a/snippets/ansible/tasks.yml b/snippets/ansible/tasks.yml index 7024b65..81a7ea2 100644 --- a/snippets/ansible/tasks.yml +++ b/snippets/ansible/tasks.yml @@ -1,5 +1,22 @@ --- +- name: Debug tasks + block: + - ansible.builtin.debug: + msg: I always display! + - ansible.builtin.debug: + msg: I only display with 'ansible-playbook -vvv' or with more 'v's + verbosity: 3 + - debugger: on_failed + ansible.builtin.fail: + msg: Manual, enforced failure + # print all variables at this point => p task_vars + # continue => c + # abort and quit => q + +- name: Flush handlers + ansible.builtin.meta: flush_handlers + - name: Retry tasks ansible.builtin.command: /usr/bin/false retries: 3 @@ -7,12 +24,73 @@ register: command_result until: command_result is not failed +- name: Run tasks locally + delegate_to: 127.0.0.1 # 'localhost' works too + ansible.builtin.command: hostname + +- name: Assertions + ansible.builtin.assert: + that: + - "'package' in ['container', 'package']" + - "'https://www.google.com/' is ansible.builtin.url" + - "'domain.example.com' is community.general.fqdn_valid(min_labels=2)" + fail_msg: What to say if any of the above conditions fail + success_msg: What to say if all of the above conditions succeed + +- name: Test types + ansible.builtin.assert: + that: + # strings are classified as 'string', 'iterable' and 'sequence', but not 'mapping' + - "'aa' is string" + - "'aa' is iterable" + - "'aa' is sequence" + # numbers are classified as 'numbers', with 'integer' and 'float' being subclasses + - 42 is number and 5 is integer + - 21.34 is number and 12.1 is float + # lists are classified as 'iterable' and 'sequence', but not as 'string' nor 'mapping' + - "['list'] is iterable" + - "['list'] is sequence" + # dictionaries are classified as 'iterable', 'sequence' and 'mapping', but not as 'string' + - "{'a': 'dict'} is iterable" + - "{'a': 'dict'} is sequence" + - "{'a': 'dict'} is mapping" + # native booleans + - true is boolean + - True is boolean + - false is boolean + - False is boolean + +- name: Type conversion + ansible.builtin.assert: + that: + - "'string' | int is integer" + - "'string' | float is float" + - 12 | float is float + - 21.02 | int is integer + - 43 | string is string + - 74.93 | string is string + - 4 | bool is boolean + +- name: Elvis operator + # (condition) | bool | ternary(value_for_true_condition, value_for_false_condition, optional_value_for_null_condition) + ansible.builtin.set_fact: + acme_directory: >- + {{ + this_is_a_test_run + | default(true) + | bool + | ternary( + 'https://acme-staging-v02.api.letsencrypt.org/directory', + 'https://acme-v02.api.letsencrypt.org/directory' + ) + }} + - name: Create directories recursively ansible.builtin.file: path: /tmp/path/to/final/dir state: directory -- name: Write files from tasks +- name: Define files content in tasks ansible.builtin.copy: dest: "{{ ansible_user_dir }}/.tmux.conf" mode: u=rw,go=r @@ -20,12 +98,23 @@ … - name: Show input data type - set_fact: + ansible.builtin.set_fact: should_be_string: "{{ 'this' | type_debug }}" -- name: Run locally - delegate_to: 127.0.0.1 # 'localhost' works too - command: hostname +- name: Pretty print information + ansible.builtin.debug: + msg: >- + {{ + dict([ + [ 'install_method', install_method ], + [ 'install_method in supported_install_methods', install_method in supported_install_methods ], + ]) + }} + +- name: Use filters + tags: filter + ansible.builtin.set_fact: + path_list_of_all_txt_files_in_dir: "{{ lookup('ansible.builtin.fileglob', '/my/path/*.txt') }}" - name: Import tasks block: @@ -49,24 +138,6 @@ ansible.builtin.import_tasks: file: "{{ filename }}" -- name: Assertions - ansible.builtin.assert: - that: - - install_method in supported_install_methods - - external_url is ansible.builtin.url - fail_msg: What to say if any of the above conditions fail - success_msg: What to say if all of the above conditions succeed - -- name: Pretty print information - ansible.builtin.debug: - msg: >- - {{ - dict([ - [ 'install_method', install_method ], - [ 'install_method in supported_install_methods', install_method in supported_install_methods ], - ]) - }} - - name: Generate passwords block: - name: Randomly @@ -130,17 +201,27 @@ - name: Manipulate strings ansible.builtin.set_fact: string_with_first_letter_to_uppercase: "{{ 'all_lowercase' | capitalize }}" + string_with_something_replaced: "{{ 'dots.to.dashes' | replace('.','-') }}" + split_string: "{{ 'testMe@example.com' | split('@') | first }}" + string_with_pattern_replaced: >- + {{ '*.domain.com...' | regex_replace('*' | regex_escape, 'star') | regex_replace('\.+$', '') }} - name: Manipulate lists block: - name: Add elements to lists - set_fact: + vars: + programming_languages: + - C + - Python + ansible.builtin.set_fact: programming_languages: "{{ programming_languages + ['Ruby'] }}" - name: Remove elements from lists - set_fact: + vars: + dbs_list: ['primary', 'sales'] + ansible.builtin.set_fact: list_without_items: "{{ dbs_list | difference(['template0','template1','postgres','rdsadmin']) }}" - name: Get a random element - set_fact: + ansible.builtin.set_fact: random_item: "{{ ['a','b','c'] | random }}" - name: Sort dict elements in list by attribute tags: order_by @@ -150,7 +231,7 @@ create_time: '2024-06-25T00:52:55.127000+00:00' - name: test create_time: '2024-05-17T01:53:12.103220+00:00' - set_fact: + ansible.builtin.set_fact: snapshot_latest: "{{ snapshots | sort(attribute='create_time') | last }}" - name: Give back the first not null value (coalesce-like) vars: @@ -159,21 +240,21 @@ - null - something - something else - set_fact: + ansible.builtin.set_fact: first_non_null_value: "{{ list_with_null_values | select | first }}" - name: Get values for a specific attribute in a list of dictionaries - set_fact: + ansible.builtin.set_fact: vpc_security_group_ids: >- {{ instance_information.vpc_security_groups | map(attribute='vpc_security_group_id') }} volume_ids: "{{ instances_information.instances[0].block_device_mappings | map(attribute='ebs.volume_id') }}" - name: Return only elements with specific attributes matching a filter - set_fact: - available_rds_snapshots: snapshots_list | selectattr("status", "equalto", "available") - mounts_with_path: ansible_facts.mounts | selectattr('mount', 'in', path) + ansible.builtin.set_fact: + available_rds_snapshots: "{{ snapshots_list | selectattr('status', 'equalto', 'available') }}" + mounts_with_path: "{{ ansible_facts.mounts | selectattr('mount', 'in', path) }}" - name: Return all elements *but* the ones with specific attributes matching a filter - set_fact: - available_rds_snapshots: snapshots_list | rejectattr("status", "equalto", "creating") - mounts_without_path: ansible_facts.mounts | rejectattr('mount', 'in', path) + ansible.builtin.set_fact: + available_rds_snapshots: "{{ snapshots_list | rejectattr('status', 'equalto', 'creating') }}" + mounts_without_path: "{{ ansible_facts.mounts | rejectattr('mount', 'in', path) }}" - name: Remove lines about RDS protected users and permissions from a dump file # remove empty lines # remove comments @@ -198,15 +279,19 @@ }} - name: Manipulate dictionaries + vars: + organization: + address: 123 common lane + id: 123abc block: - name: Add keys to dictionaries - set_fact: + ansible.builtin.set_fact: organization: "{{ organization | combine({ 'name': 'ExampleOrg' }) }}" - name: Sort keys in dictionaries - set_fact: + ansible.builtin.set_fact: organization: "{{ organization | dictsort }}" - name: Pretty print dictionaries - set_fact: + ansible.builtin.set_fact: organization: "{{ organization | to_nice_json }}" - name: Merge dictionaries vars: @@ -218,7 +303,7 @@ z: - 4 - test - set_fact: + ansible.builtin.set_fact: merged_dict: "{{ dict1 | ansible.builtin.combine(dict_2, {'z':'new_value','w':[44]}) }}" recursively_merged_dict: >- {{ {'rest':'test'} | ansible.builtin.combine({'z':'new_value','w':[44]}, dict_1, dict_2, recursive=true) }} @@ -234,7 +319,7 @@ }) }} with_items: "{{ db_extensions_query.results }}" - - name: FIXME + - name: Register the list of extensions per DB as 'db:extensions[]' pairs vars: db_extensions: sales: @@ -280,16 +365,16 @@ {%- endfor -%} {{ ns.devices_list }} -- name: "Use the users' home directory for something" +- name: Use the users' home directory for something block: - name: Executing commands from specified users block: - - name: "Get users' homedir back" + - name: Get users' homedir back become: true become_user: "{{ item }}" - become_flags: "-iH" + become_flags: -iH check_mode: false - command: >- + ansible.builtin.command: >- echo "{{ item }}: $HOME" changed_when: false with_items: @@ -313,13 +398,13 @@ path: "{{ item.value }}/placeholder" state: touch with_dict: "{{ users_homedir }}" - - name: "From the system's entries" + - name: From the system's entries block: - - name: "Get raw information from the system's entries" + - name: Get raw information from the system's entries ansible.builtin.getent: database: passwd key: "{{ item }}" - split: ":" + split: ':' with_items: - root - ec2-user @@ -363,21 +448,13 @@ && aws s3 cp "$FILENAME" 's3://backups/prometheus/' && rm "$FILENAME" -- name: Debug tasks - debugger: on_failed - ansible.builtin.fail: - msg: Manual, enforced failure - # print all variables at this point => p task_vars - # continue => c - # abort and quit => q - - name: Error handling in blocks block: - name: This executes normally ansible.builtin.debug: msg: I execute normally - name: This errors out - ansible.builtin.command: "/bin/false" + ansible.builtin.command: /bin/false - name: This never executes ansible.builtin.debug: msg: I never execute due to the above task failing @@ -390,12 +467,45 @@ ansible.builtin.debug: msg: I always execute +- name: Integrate Ansible Vault + tags: ansible_vault + block: + - name: Use encrypted values + ansible.builtin.set_fact: + var_from_encrypted_value: + # password: '1q2w3e4r', plaintext value: 'very secret string' + !vault | + $ANSIBLE_VAULT;1.1;AES256 + 34646464653830386631363430386432666530356364313532313336373665613038633464376335 + 3539363530613130623638313063363165386230646566640a313438386133366137383939336637 + 33333365393337326239336264623462373064383663363234353635316538356461353061646563 + 3037306464363439340a663430313739393439363936613862316361353330363638323065383063 + 39613935613035343637336537643266313737666635313730353034373736353736 + - name: Use encrypted files + # The 'unvault' filter requires files to exist beforehand, but it is fine for them to be plaintext. \_(-_-)_/ + tags: tls_certificate + ansible.builtin.copy: + dest: /etc/haproxy/certificate.pem + content: | + {{ lookup('ansible.builtin.unvault', 'path/to/cert/key.pem') | string | trim }} + {{ lookup('ansible.builtin.unvault', 'path/to/cert/full_chain.pem') | string | trim }} + - name: Save data to encrypted files + # Of fu*king course the 'vault' filter would use the 'filter_default' vault ID by default to encrypt content. + # Set that parameter to '' to *not* specify a vault ID. + vars: + ansible_vault_password: >- + {{ lookup('ansible.builtin.file', [playbook_dir, 'ansible_vault_password_file.txt'] | path_join) }} + ansible.builtin.copy: + dest: path/to/file + decrypt: false # necessary if the file does not exist beforehand + content: "{{ 'some string' | ansible.builtin.vault(ansible_vault_password, vault_id='') }}" + - name: AWS tags: aws block: - name: Get current IP ranges # too many to be put into security group rules - set_fact: + ansible.builtin.set_fact: ip_ranges: >- lookup('url', 'https://ip-ranges.amazonaws.com/ip-ranges.json', split_lines=False) | from_json @@ -411,7 +521,7 @@ secret_key: 123456789abcdefghijklmnopqrstuvwxyzABCDE # optional if defined as environment variable profile: someProfile # optional if defined as environment variable role_arn: "arn:aws:iam::123456789012:role/someRole" - role_session_name: "someRoleSession" + role_session_name: someRoleSession register: assumed_role - name: Use the assumed role to take action amazon.aws.ec2_tag: @@ -428,7 +538,7 @@ amazon.aws.ec2_instance_info: filters: "tag:Application": K8S - "instance-state-name": ["running"] + instance-state-name: ["running"] - name: Clone EC2 instances vars: source_instance_id: i-0123456789abcdef0 @@ -460,44 +570,44 @@ block: - name: Create the snapshot amazon.aws.rds_instance_snapshot: - db_instance_identifier: "db-identifier" - db_snapshot_identifier: "db-identifier-snapshot" + db_instance_identifier: identifier-for-db-instance + db_snapshot_identifier: identifier-for-db-snapshot register: snapshot_creation - - name: Wait for the snapshot to be in the 'available state' + - name: Wait for the snapshot to be in the 'available' state when: snapshot_creation.snapshot_create_time is defined amazon.aws.rds_snapshot_info: db_snapshot_identifier: "{{ snapshot_creation.db_snapshot_identifier }}" register: snapshot_check retries: 3 delay: 120 - until: snapshot_check.snapshots | selectattr("status", "equalto", "available") | length > 0 - - name: "Dump roles' privileges" + until: snapshot_check.snapshots | selectattr('status', 'equalto', 'available') | length > 0 + - name: Dump roles' privileges block: - name: Dump to file environment: - PGPASSWORD: "someRandomString" + PGPASSWORD: someRandomString vars: out_file: /tmp/instance-id_roles.sql ansible.builtin.command: >- pg_dumpall - --host 'instance-id.c4v563ptr321.eu-west-1.rds.amazonaws.com' --port '5432' + --host 'instance-id.0123456789ab.eu-west-1.rds.amazonaws.com' --port '5432' --user 'postgres' --database 'postgres' --no-password --roles-only --no-role-passwords --file '{{ out_file }}' changed_when: false - name: Dump to variable for later use through 'dump_execution.stdout_lines' environment: - PGPASSWORD: "someRandomString" + PGPASSWORD: someRandomString ansible.builtin.command: >- pg_dumpall - -h 'instance-id.c4v563ptr321.eu-west-1.rds.amazonaws.com' -p '5432' + -h 'instance-id.0123456789ab.eu-west-1.rds.amazonaws.com' -p '5432' -U 'postgres' -l 'postgres' -w -r --no-role-passwords changed_when: false register: dump_execution - name: Wait for pending changes to be applied amazon.aws.rds_instance_info: - db_instance_identifier: "{{ db_instance_identifier }}" + db_instance_identifier: identifier-for-db-instance register: instance_check retries: 12 delay: 15 @@ -543,3 +653,14 @@ awx.awx.export: all: true register: awx_export_output + +- name: Let's Encrypt + # The 'acme_certificate' module takes in file paths for the certificate's files; those need either to *not* exist + # beforehand, or their content to be in specific formats. + block: + - name: Revoke test certificates with account key + community.crypto.acme_certificate_revoke: + acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory + acme_version: 2 + account_key_src: path/to/acme_account.key.pem + certificate: path/to/certificate.crt.pem diff --git a/snippets/webserver.sh b/snippets/webserver.sh index 0e299ce..405757b 100644 --- a/snippets/webserver.sh +++ b/snippets/webserver.sh @@ -9,6 +9,7 @@ python -m 'http.server' python -m 'http.server' '8080' --bind 'localhost' --directory '/files/to/serve' --protocol 'HTTP/1.1' --cgi # Quick 'n' dirty web server -# pip install --user 'twisted' 'pyopenssl' +# https://twisted.org/ +# pip install --user 'twisted[tls]' twistd -no web twistd -no web --path '/files/to/serve' --https '8443' --certificate 'server.pem' --privkey 'server.pem'