commit 6224cd01c6a55afb5f7739ee70fc87a8958fc9f5 Author: sebastian Date: Fri Oct 10 11:07:34 2025 +0000 initial upload diff --git a/README.md b/README.md new file mode 100644 index 0000000..701b42f --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Ansible files and playbooks + +This is Maruntiel's Ansible repository + +INFRASTRUCTURE SETUP +==================== + +Examples: + + # Ping all hosts to verify connectivity: + ansible all -m ping + + # Show all facts about some hosts: + ansible mysql -m setup + + # Run a command on all asterisk servers: + ansible asterisk -m shell -a "uname" + + # Install/upgrade a package on MySQL servers: + ansible mysql -m apt -a "name=innotop state=latest" + + # Provision the whole infrastructure: + ansible-playbook site.yml [--diff] [--tags=] + + # Provision the whole infrastructure in dry run mode and see what would change: + ansible-playbook site.yml --check --diff + + # Update the hosts file on all servers: + ansible-playbook tools/update_hosts.yml + + +Files: + + ansible.cfg Ansible config file + inventory Hosts inventory file defining all hosts and groups + + site.yml Main playbook: provision all hosts and services + playbook/*.yml Playbooks for provisioning services (included by site.yml) + tools/*.yml Playbooks for operations + others \ No newline at end of file diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 0000000..a80f69a --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,19 @@ +[defaults] +inventory = ./inventory +roles_path = roles +timeout = 10 +private_key_file = ~/.ssh/id_rsa +interpreter_python = auto_silent +ansible_managed = ANSIBLE deployed. DO NOT EDIT!!! + +[inventory] +enable_plugins = host_list, script, auto, yaml, ini, toml + +[ssh_connection] +ssh_args = -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey,keyboard-interactive -o ControlMaster=auto -o ControlPersist=60s + +[privilege_escalation] +become = True +become_method = sudo +become_user = root +become_ask_pass = True diff --git a/bash-prompt.yml b/bash-prompt.yml new file mode 100644 index 0000000..0558c78 --- /dev/null +++ b/bash-prompt.yml @@ -0,0 +1,11 @@ +--- + +- hosts: all + become: true + tasks: + + - name: change bash promp and color + copy: src={{ item.src }} dest={{ item.dest }} + with_items: + - {src: 'bashrc', dest: '/root/.bashrc'} + - {src: 'bashrc', dest: '/home/sebastian/.bashrc'} diff --git a/basic-tools.yml b/basic-tools.yml new file mode 100644 index 0000000..24abfe0 --- /dev/null +++ b/basic-tools.yml @@ -0,0 +1,22 @@ +--- + +- hosts: all + become: true + tasks: + + - name: update repo index + apt: + update_cache: yes + + - name: install usefull and basic system tools + apt: + name: + - vim-nox + - mc + - nmap + - net-tools + - dnsutils + - tmux + - tcpdump + - iptraf-ng + - screen diff --git a/consul.yml b/consul.yml new file mode 100644 index 0000000..2c8a043 --- /dev/null +++ b/consul.yml @@ -0,0 +1,64 @@ +--- + +- hosts: servers + become: true + tasks: + + - name: install required UNZIP + package: + name: unzip + + - name: add the CONSUL group + group: + name: consul + state: present + gid: 199 + + - name: add the CONSUL user + user: + name: consul + comment: CONSUL user + state: present + uid: 199 + + - name: install CONSUL from HashiCorp + unarchive: + src: https://releases.hashicorp.com/consul/1.8.5/consul_1.8.5_linux_amd64.zip + dest: /usr/local/bin + remote_src: yes + mode: 0755 + owner: consul + group: consul + + - name: create CONSUL required data folders + file: + path: /opt/consul + state: directory + mode: '0755' + recurse: yes + owner: consul + group: consul + + - name: create CONSUL required config folders + file: + path: /etc/consul.d + state: directory + mode: '0755' + recurse: yes + owner: consul + group: consul + + - name: copy CONSUL systemd script + copy: src={{ item.src }} dest={{ item.dest }} + with_items: + - {src: 'consul/configs/consul.service', dest: '/etc/systemd/system'} + - {src: 'consul/configs/consul.hcl', dest: '/etc/consul.d'} + - {src: 'consul/configs/service-ssh.hcl', dest: '/etc/consul.d'} + + - name: enable CONSUL systemd script + service: + name: consul + enabled: yes + daemon_reload: yes + state: started + diff --git a/facts b/facts new file mode 100644 index 0000000..b8c6fc2 --- /dev/null +++ b/facts @@ -0,0 +1,940 @@ +admin.srv | SUCCESS => { + "ansible_facts": { + "ansible_all_ipv4_addresses": [ + "10.11.11.200" + ], + "ansible_all_ipv6_addresses": [ + "fe80::215:5dff:fe0b:6a02" + ], + "ansible_apparmor": { + "status": "enabled" + }, + "ansible_architecture": "x86_64", + "ansible_bios_date": "11/01/2019", + "ansible_bios_version": "Hyper-V UEFI Release v4.0", + "ansible_cmdline": { + "BOOT_IMAGE": "/vmlinuz-5.4.0-53-generic", + "ro": true, + "root": "/dev/mapper/ubuntu--vg-ubuntu--lv" + }, + "ansible_date_time": { + "date": "2020-11-26", + "day": "26", + "epoch": "1606413037", + "hour": "17", + "iso8601": "2020-11-26T17:50:37Z", + "iso8601_basic": "20201126T175037815822", + "iso8601_basic_short": "20201126T175037", + "iso8601_micro": "2020-11-26T17:50:37.815922Z", + "minute": "50", + "month": "11", + "second": "37", + "time": "17:50:37", + "tz": "UTC", + "tz_offset": "+0000", + "weekday": "Thursday", + "weekday_number": "4", + "weeknumber": "47", + "year": "2020" + }, + "ansible_default_ipv4": { + "address": "10.11.11.200", + "alias": "eth0", + "broadcast": "10.11.11.255", + "gateway": "10.11.11.1", + "interface": "eth0", + "macaddress": "00:15:5d:0b:6a:02", + "mtu": 1500, + "netmask": "255.255.255.0", + "network": "10.11.11.0", + "type": "ether" + }, + "ansible_default_ipv6": {}, + "ansible_device_links": { + "ids": { + "dm-0": [ + "dm-name-ubuntu--vg-ubuntu--lv", + "dm-uuid-LVM-TWUHOGOKoNuLMn4gNb51IdtDrSue1Rvw1Gv9YnBSffsXBbWX84dmduc9M2oMYmsB" + ], + "sda": [ + "scsi-14d53465420202020fb6c2ab55f82e74f8c8f7a8b61e2d533", + "scsi-360022480fb6c2ab55f827a8b61e2d533", + "wwn-0x60022480fb6c2ab55f827a8b61e2d533" + ], + "sda1": [ + "scsi-14d53465420202020fb6c2ab55f82e74f8c8f7a8b61e2d533-part1", + "scsi-360022480fb6c2ab55f827a8b61e2d533-part1", + "wwn-0x60022480fb6c2ab55f827a8b61e2d533-part1" + ], + "sda2": [ + "scsi-14d53465420202020fb6c2ab55f82e74f8c8f7a8b61e2d533-part2", + "scsi-360022480fb6c2ab55f827a8b61e2d533-part2", + "wwn-0x60022480fb6c2ab55f827a8b61e2d533-part2" + ], + "sda3": [ + "lvm-pv-uuid-yDm3er-tLzM-3fJR-VE3j-mCEz-0QJv-FzswgU", + "scsi-14d53465420202020fb6c2ab55f82e74f8c8f7a8b61e2d533-part3", + "scsi-360022480fb6c2ab55f827a8b61e2d533-part3", + "wwn-0x60022480fb6c2ab55f827a8b61e2d533-part3" + ], + "sr0": [ + "scsi-14d534654202020207305e3437703544694957d7ced624a7d" + ] + }, + "labels": {}, + "masters": { + "sda3": [ + "dm-0" + ] + }, + "uuids": { + "dm-0": [ + "78d8f127-d14d-4a2b-89da-4cc16b1c4c31" + ], + "sda1": [ + "C2A5-06BF" + ], + "sda2": [ + "ac252c97-f517-4be8-b499-9fcc8f8d5c68" + ] + } + }, + "ansible_devices": { + "dm-0": { + "holders": [], + "host": "", + "links": { + "ids": [ + "dm-name-ubuntu--vg-ubuntu--lv", + "dm-uuid-LVM-TWUHOGOKoNuLMn4gNb51IdtDrSue1Rvw1Gv9YnBSffsXBbWX84dmduc9M2oMYmsB" + ], + "labels": [], + "masters": [], + "uuids": [ + "78d8f127-d14d-4a2b-89da-4cc16b1c4c31" + ] + }, + "model": null, + "partitions": {}, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "", + "sectors": "38789120", + "sectorsize": "512", + "size": "18.50 GB", + "support_discard": "2097152", + "vendor": null, + "virtual": 1 + }, + "loop0": { + "holders": [], + "host": "", + "links": { + "ids": [], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": null, + "partitions": {}, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "mq-deadline", + "sectors": "112552", + "sectorsize": "512", + "size": "54.96 MB", + "support_discard": "4096", + "vendor": null, + "virtual": 1 + }, + "loop1": { + "holders": [], + "host": "", + "links": { + "ids": [], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": null, + "partitions": {}, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "mq-deadline", + "sectors": "113384", + "sectorsize": "512", + "size": "55.36 MB", + "support_discard": "4096", + "vendor": null, + "virtual": 1 + }, + "loop2": { + "holders": [], + "host": "", + "links": { + "ids": [], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": null, + "partitions": {}, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "mq-deadline", + "sectors": "145968", + "sectorsize": "512", + "size": "71.27 MB", + "support_discard": "4096", + "vendor": null, + "virtual": 1 + }, + "loop3": { + "holders": [], + "host": "", + "links": { + "ids": [], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": null, + "partitions": {}, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "mq-deadline", + "sectors": "61200", + "sectorsize": "512", + "size": "29.88 MB", + "support_discard": "4096", + "vendor": null, + "virtual": 1 + }, + "loop4": { + "holders": [], + "host": "", + "links": { + "ids": [], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": null, + "partitions": {}, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "mq-deadline", + "sectors": "138752", + "sectorsize": "512", + "size": "67.75 MB", + "support_discard": "4096", + "vendor": null, + "virtual": 1 + }, + "loop5": { + "holders": [], + "host": "", + "links": { + "ids": [], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": null, + "partitions": {}, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "mq-deadline", + "sectors": "63360", + "sectorsize": "512", + "size": "30.94 MB", + "support_discard": "4096", + "vendor": null, + "virtual": 1 + }, + "loop6": { + "holders": [], + "host": "", + "links": { + "ids": [], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": null, + "partitions": {}, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "mq-deadline", + "sectors": "0", + "sectorsize": "512", + "size": "0.00 Bytes", + "support_discard": "4096", + "vendor": null, + "virtual": 1 + }, + "loop7": { + "holders": [], + "host": "", + "links": { + "ids": [], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": null, + "partitions": {}, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "mq-deadline", + "sectors": "0", + "sectorsize": "512", + "size": "0.00 Bytes", + "support_discard": "0", + "vendor": null, + "virtual": 1 + }, + "sda": { + "holders": [], + "host": "", + "links": { + "ids": [ + "scsi-14d53465420202020fb6c2ab55f82e74f8c8f7a8b61e2d533", + "scsi-360022480fb6c2ab55f827a8b61e2d533", + "wwn-0x60022480fb6c2ab55f827a8b61e2d533" + ], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": "Virtual Disk", + "partitions": { + "sda1": { + "holders": [], + "links": { + "ids": [ + "scsi-14d53465420202020fb6c2ab55f82e74f8c8f7a8b61e2d533-part1", + "scsi-360022480fb6c2ab55f827a8b61e2d533-part1", + "wwn-0x60022480fb6c2ab55f827a8b61e2d533-part1" + ], + "labels": [], + "masters": [], + "uuids": [ + "C2A5-06BF" + ] + }, + "sectors": "1048576", + "sectorsize": 512, + "size": "512.00 MB", + "start": "2048", + "uuid": "C2A5-06BF" + }, + "sda2": { + "holders": [], + "links": { + "ids": [ + "scsi-14d53465420202020fb6c2ab55f82e74f8c8f7a8b61e2d533-part2", + "scsi-360022480fb6c2ab55f827a8b61e2d533-part2", + "wwn-0x60022480fb6c2ab55f827a8b61e2d533-part2" + ], + "labels": [], + "masters": [], + "uuids": [ + "ac252c97-f517-4be8-b499-9fcc8f8d5c68" + ] + }, + "sectors": "2097152", + "sectorsize": 512, + "size": "1.00 GB", + "start": "1050624", + "uuid": "ac252c97-f517-4be8-b499-9fcc8f8d5c68" + }, + "sda3": { + "holders": [ + "ubuntu--vg-ubuntu--lv" + ], + "links": { + "ids": [ + "lvm-pv-uuid-yDm3er-tLzM-3fJR-VE3j-mCEz-0QJv-FzswgU", + "scsi-14d53465420202020fb6c2ab55f82e74f8c8f7a8b61e2d533-part3", + "scsi-360022480fb6c2ab55f827a8b61e2d533-part3", + "wwn-0x60022480fb6c2ab55f827a8b61e2d533-part3" + ], + "labels": [], + "masters": [ + "dm-0" + ], + "uuids": [] + }, + "sectors": "38793216", + "sectorsize": 512, + "size": "18.50 GB", + "start": "3147776", + "uuid": null + } + }, + "removable": "0", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "none", + "sectors": "41943040", + "sectorsize": "512", + "size": "20.00 GB", + "support_discard": "2097152", + "vendor": "Msft", + "virtual": 1, + "wwn": "0x60022480fb6c2ab55f827a8b61e2d533" + }, + "sr0": { + "holders": [], + "host": "", + "links": { + "ids": [ + "scsi-14d534654202020207305e3437703544694957d7ced624a7d" + ], + "labels": [], + "masters": [], + "uuids": [] + }, + "model": "Virtual DVD-ROM", + "partitions": {}, + "removable": "1", + "rotational": "1", + "sas_address": null, + "sas_device_handle": null, + "scheduler_mode": "none", + "sectors": "2097151", + "sectorsize": "512", + "size": "1024.00 MB", + "support_discard": "0", + "vendor": "Msft", + "virtual": 1 + } + }, + "ansible_distribution": "Ubuntu", + "ansible_distribution_file_parsed": true, + "ansible_distribution_file_path": "/etc/os-release", + "ansible_distribution_file_variety": "Debian", + "ansible_distribution_major_version": "20", + "ansible_distribution_release": "focal", + "ansible_distribution_version": "20.04", + "ansible_dns": { + "nameservers": [ + "127.0.0.53" + ], + "options": { + "edns0": true, + "trust-ad": true + }, + "search": [ + "maruntiel.com" + ] + }, + "ansible_domain": "srv", + "ansible_effective_group_id": 0, + "ansible_effective_user_id": 0, + "ansible_env": { + "HOME": "/root", + "LANG": "C.UTF-8", + "LOGNAME": "root", + "MAIL": "/var/mail/root", + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin", + "PWD": "/home/sebastian", + "SHELL": "/bin/bash", + "SUDO_COMMAND": "/bin/sh -c echo BECOME-SUCCESS-vprmhxvksdowhqkfdpqvsmyyibyglvwk ; /usr/bin/python3 /home/sebastian/.ansible/tmp/ansible-tmp-1606413037.0662265-70241438907100/AnsiballZ_setup.py", + "SUDO_GID": "1000", + "SUDO_UID": "1000", + "SUDO_USER": "sebastian", + "TERM": "xterm", + "USER": "root" + }, + "ansible_eth0": { + "active": true, + "device": "eth0", + "features": { + "esp_hw_offload": "off [fixed]", + "esp_tx_csum_hw_offload": "off [fixed]", + "fcoe_mtu": "off [fixed]", + "generic_receive_offload": "on", + "generic_segmentation_offload": "on", + "highdma": "on [fixed]", + "hw_tc_offload": "off [fixed]", + "l2_fwd_offload": "off [fixed]", + "large_receive_offload": "on", + "loopback": "off [fixed]", + "netns_local": "off [fixed]", + "ntuple_filters": "off [fixed]", + "receive_hashing": "off [fixed]", + "rx_all": "off [fixed]", + "rx_checksumming": "on", + "rx_fcs": "off [fixed]", + "rx_gro_hw": "off [fixed]", + "rx_udp_tunnel_port_offload": "off [fixed]", + "rx_vlan_filter": "off [fixed]", + "rx_vlan_offload": "on [fixed]", + "rx_vlan_stag_filter": "off [fixed]", + "rx_vlan_stag_hw_parse": "off [fixed]", + "scatter_gather": "on", + "tcp_segmentation_offload": "on", + "tls_hw_record": "off [fixed]", + "tls_hw_rx_offload": "off [fixed]", + "tls_hw_tx_offload": "off [fixed]", + "tx_checksum_fcoe_crc": "off [fixed]", + "tx_checksum_ip_generic": "off [fixed]", + "tx_checksum_ipv4": "on", + "tx_checksum_ipv6": "on", + "tx_checksum_sctp": "off [fixed]", + "tx_checksumming": "on", + "tx_esp_segmentation": "off [fixed]", + "tx_fcoe_segmentation": "off [fixed]", + "tx_gre_csum_segmentation": "off [fixed]", + "tx_gre_segmentation": "off [fixed]", + "tx_gso_partial": "off [fixed]", + "tx_gso_robust": "off [fixed]", + "tx_ipxip4_segmentation": "off [fixed]", + "tx_ipxip6_segmentation": "off [fixed]", + "tx_lockless": "off [fixed]", + "tx_nocache_copy": "off", + "tx_scatter_gather": "on", + "tx_scatter_gather_fraglist": "off [fixed]", + "tx_sctp_segmentation": "off [fixed]", + "tx_tcp6_segmentation": "on", + "tx_tcp_ecn_segmentation": "off [fixed]", + "tx_tcp_mangleid_segmentation": "off", + "tx_tcp_segmentation": "on", + "tx_udp_segmentation": "off [fixed]", + "tx_udp_tnl_csum_segmentation": "off [fixed]", + "tx_udp_tnl_segmentation": "off [fixed]", + "tx_vlan_offload": "on [fixed]", + "tx_vlan_stag_hw_insert": "off [fixed]", + "vlan_challenged": "off [fixed]" + }, + "hw_timestamp_filters": [], + "ipv4": { + "address": "10.11.11.200", + "broadcast": "10.11.11.255", + "netmask": "255.255.255.0", + "network": "10.11.11.0" + }, + "ipv6": [ + { + "address": "fe80::215:5dff:fe0b:6a02", + "prefix": "64", + "scope": "link" + } + ], + "macaddress": "00:15:5d:0b:6a:02", + "module": "hv_netvsc", + "mtu": 1500, + "pciid": "afef4346-a050-4719-accd-7555c8675429", + "promisc": false, + "speed": 195, + "timestamping": [ + "tx_software", + "rx_software", + "software" + ], + "type": "ether" + }, + "ansible_fibre_channel_wwn": [], + "ansible_fips": false, + "ansible_form_factor": "Desktop", + "ansible_fqdn": "admin.srv", + "ansible_hostname": "admin", + "ansible_hostnqn": "", + "ansible_interfaces": [ + "lo", + "eth0" + ], + "ansible_is_chroot": false, + "ansible_iscsi_iqn": "iqn.1993-08.org.debian:01:af5bf2af245", + "ansible_kernel": "5.4.0-53-generic", + "ansible_kernel_version": "#59-Ubuntu SMP Wed Oct 21 09:38:44 UTC 2020", + "ansible_lo": { + "active": true, + "device": "lo", + "features": { + "esp_hw_offload": "off [fixed]", + "esp_tx_csum_hw_offload": "off [fixed]", + "fcoe_mtu": "off [fixed]", + "generic_receive_offload": "on", + "generic_segmentation_offload": "on", + "highdma": "on [fixed]", + "hw_tc_offload": "off [fixed]", + "l2_fwd_offload": "off [fixed]", + "large_receive_offload": "off [fixed]", + "loopback": "on [fixed]", + "netns_local": "on [fixed]", + "ntuple_filters": "off [fixed]", + "receive_hashing": "off [fixed]", + "rx_all": "off [fixed]", + "rx_checksumming": "on [fixed]", + "rx_fcs": "off [fixed]", + "rx_gro_hw": "off [fixed]", + "rx_udp_tunnel_port_offload": "off [fixed]", + "rx_vlan_filter": "off [fixed]", + "rx_vlan_offload": "off [fixed]", + "rx_vlan_stag_filter": "off [fixed]", + "rx_vlan_stag_hw_parse": "off [fixed]", + "scatter_gather": "on", + "tcp_segmentation_offload": "on", + "tls_hw_record": "off [fixed]", + "tls_hw_rx_offload": "off [fixed]", + "tls_hw_tx_offload": "off [fixed]", + "tx_checksum_fcoe_crc": "off [fixed]", + "tx_checksum_ip_generic": "on [fixed]", + "tx_checksum_ipv4": "off [fixed]", + "tx_checksum_ipv6": "off [fixed]", + "tx_checksum_sctp": "on [fixed]", + "tx_checksumming": "on", + "tx_esp_segmentation": "off [fixed]", + "tx_fcoe_segmentation": "off [fixed]", + "tx_gre_csum_segmentation": "off [fixed]", + "tx_gre_segmentation": "off [fixed]", + "tx_gso_partial": "off [fixed]", + "tx_gso_robust": "off [fixed]", + "tx_ipxip4_segmentation": "off [fixed]", + "tx_ipxip6_segmentation": "off [fixed]", + "tx_lockless": "on [fixed]", + "tx_nocache_copy": "off [fixed]", + "tx_scatter_gather": "on [fixed]", + "tx_scatter_gather_fraglist": "on [fixed]", + "tx_sctp_segmentation": "on", + "tx_tcp6_segmentation": "on", + "tx_tcp_ecn_segmentation": "on", + "tx_tcp_mangleid_segmentation": "on", + "tx_tcp_segmentation": "on", + "tx_udp_segmentation": "off [fixed]", + "tx_udp_tnl_csum_segmentation": "off [fixed]", + "tx_udp_tnl_segmentation": "off [fixed]", + "tx_vlan_offload": "off [fixed]", + "tx_vlan_stag_hw_insert": "off [fixed]", + "vlan_challenged": "on [fixed]" + }, + "hw_timestamp_filters": [], + "ipv4": { + "address": "127.0.0.1", + "broadcast": "host", + "netmask": "255.0.0.0", + "network": "127.0.0.0" + }, + "ipv6": [ + { + "address": "::1", + "prefix": "128", + "scope": "host" + } + ], + "mtu": 65536, + "promisc": false, + "timestamping": [ + "tx_software", + "rx_software", + "software" + ], + "type": "loopback" + }, + "ansible_local": {}, + "ansible_lsb": { + "codename": "focal", + "description": "Ubuntu 20.04.1 LTS", + "id": "Ubuntu", + "major_release": "20", + "release": "20.04" + }, + "ansible_lvm": { + "lvs": { + "ubuntu-lv": { + "size_g": "18.50", + "vg": "ubuntu-vg" + } + }, + "pvs": { + "/dev/sda3": { + "free_g": "0", + "size_g": "18.50", + "vg": "ubuntu-vg" + } + }, + "vgs": { + "ubuntu-vg": { + "free_g": "0", + "num_lvs": "1", + "num_pvs": "1", + "size_g": "18.50" + } + } + }, + "ansible_machine": "x86_64", + "ansible_machine_id": "dd0100d596a7407c9f7b39315324d71f", + "ansible_memfree_mb": 296, + "ansible_memory_mb": { + "nocache": { + "free": 1234, + "used": 2701 + }, + "real": { + "free": 296, + "total": 3935, + "used": 3639 + }, + "swap": { + "cached": 0, + "free": 3934, + "total": 3934, + "used": 0 + } + }, + "ansible_memtotal_mb": 3935, + "ansible_mounts": [ + { + "block_available": 2287820, + "block_size": 4096, + "block_total": 4739756, + "block_used": 2451936, + "device": "/dev/mapper/ubuntu--vg-ubuntu--lv", + "fstype": "ext4", + "inode_available": 1103535, + "inode_total": 1212416, + "inode_used": 108881, + "mount": "/", + "options": "rw,relatime", + "size_available": 9370910720, + "size_total": 19414040576, + "uuid": "78d8f127-d14d-4a2b-89da-4cc16b1c4c31" + }, + { + "block_available": 205917, + "block_size": 4096, + "block_total": 249830, + "block_used": 43913, + "device": "/dev/sda2", + "fstype": "ext4", + "inode_available": 65232, + "inode_total": 65536, + "inode_used": 304, + "mount": "/boot", + "options": "rw,relatime", + "size_available": 843436032, + "size_total": 1023303680, + "uuid": "ac252c97-f517-4be8-b499-9fcc8f8d5c68" + }, + { + "block_available": 128816, + "block_size": 4096, + "block_total": 130812, + "block_used": 1996, + "device": "/dev/sda1", + "fstype": "vfat", + "inode_available": 0, + "inode_total": 0, + "inode_used": 0, + "mount": "/boot/efi", + "options": "rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro", + "size_available": 527630336, + "size_total": 535805952, + "uuid": "C2A5-06BF" + }, + { + "block_available": 0, + "block_size": 131072, + "block_total": 443, + "block_used": 443, + "device": "/dev/loop1", + "fstype": "squashfs", + "inode_available": 0, + "inode_total": 10779, + "inode_used": 10779, + "mount": "/snap/core18/1932", + "options": "ro,nodev,relatime", + "size_available": 0, + "size_total": 58064896, + "uuid": "N/A" + }, + { + "block_available": 0, + "block_size": 131072, + "block_total": 440, + "block_used": 440, + "device": "/dev/loop0", + "fstype": "squashfs", + "inode_available": 0, + "inode_total": 10756, + "inode_used": 10756, + "mount": "/snap/core18/1880", + "options": "ro,nodev,relatime", + "size_available": 0, + "size_total": 57671680, + "uuid": "N/A" + }, + { + "block_available": 0, + "block_size": 131072, + "block_total": 571, + "block_used": 571, + "device": "/dev/loop2", + "fstype": "squashfs", + "inode_available": 0, + "inode_total": 1495, + "inode_used": 1495, + "mount": "/snap/lxd/16099", + "options": "ro,nodev,relatime", + "size_available": 0, + "size_total": 74842112, + "uuid": "N/A" + }, + { + "block_available": 0, + "block_size": 131072, + "block_total": 240, + "block_used": 240, + "device": "/dev/loop3", + "fstype": "squashfs", + "inode_available": 0, + "inode_total": 463, + "inode_used": 463, + "mount": "/snap/snapd/8542", + "options": "ro,nodev,relatime", + "size_available": 0, + "size_total": 31457280, + "uuid": "N/A" + }, + { + "block_available": 0, + "block_size": 131072, + "block_total": 542, + "block_used": 542, + "device": "/dev/loop4", + "fstype": "squashfs", + "inode_available": 0, + "inode_total": 1551, + "inode_used": 1551, + "mount": "/snap/lxd/18150", + "options": "ro,nodev,relatime", + "size_available": 0, + "size_total": 71041024, + "uuid": "N/A" + }, + { + "block_available": 0, + "block_size": 131072, + "block_total": 248, + "block_used": 248, + "device": "/dev/loop5", + "fstype": "squashfs", + "inode_available": 0, + "inode_total": 472, + "inode_used": 472, + "mount": "/snap/snapd/9721", + "options": "ro,nodev,relatime", + "size_available": 0, + "size_total": 32505856, + "uuid": "N/A" + } + ], + "ansible_nodename": "admin", + "ansible_os_family": "Debian", + "ansible_pkg_mgr": "apt", + "ansible_proc_cmdline": { + "BOOT_IMAGE": "/vmlinuz-5.4.0-53-generic", + "ro": true, + "root": "/dev/mapper/ubuntu--vg-ubuntu--lv" + }, + "ansible_processor": [ + "0", + "GenuineIntel", + "Intel(R) Xeon(R) CPU X5670 @ 2.93GHz", + "1", + "GenuineIntel", + "Intel(R) Xeon(R) CPU X5670 @ 2.93GHz", + "2", + "GenuineIntel", + "Intel(R) Xeon(R) CPU X5670 @ 2.93GHz", + "3", + "GenuineIntel", + "Intel(R) Xeon(R) CPU X5670 @ 2.93GHz" + ], + "ansible_processor_cores": 2, + "ansible_processor_count": 1, + "ansible_processor_threads_per_core": 2, + "ansible_processor_vcpus": 4, + "ansible_product_name": "Virtual Machine", + "ansible_product_serial": "4364-0105-9520-7945-2132-8495-89", + "ansible_product_uuid": "b9545019-0c6e-4533-b8e4-ef00df451640", + "ansible_product_version": "Hyper-V UEFI Release v4.0", + "ansible_python": { + "executable": "/usr/bin/python3", + "has_sslcontext": true, + "type": "cpython", + "version": { + "major": 3, + "micro": 5, + "minor": 8, + "releaselevel": "final", + "serial": 0 + }, + "version_info": [ + 3, + 8, + 5, + "final", + 0 + ] + }, + "ansible_python_version": "3.8.5", + "ansible_real_group_id": 0, + "ansible_real_user_id": 0, + "ansible_selinux": { + "status": "disabled" + }, + "ansible_selinux_python_present": true, + "ansible_service_mgr": "systemd", + "ansible_ssh_host_key_dsa_public": "AAAAB3NzaC1kc3MAAACBANYatGk5k7IcCJqX9dHtia70J09844SQPS8f0dPJuNJeKwBIiRHlUTHPLcFNbAcs/GCZjbQnbOWKn65H2XKZg4wWMNsrt6+iSGo2Am7F6ez+VQghIz7bQ49H7/f+mYOpsmFa6V764ZIVQJFn32rZc0zAuePUyNOw5kRKiJA0LDEzAAAAFQC9W1l577X6vlo5VtqBtc15vjPrPQAAAIBM3GuywQOtT8C86A6j55/QrY/9p8VyGJi2q5zDH70hOateJ9rYGYNMO5u1yV9cQ+Tmt4Dx9gFaDSYdrl/hPJioKLfL24vRcig3dBZ/CX2DBRKn3QB5tWZxwR6LhrMlG+ySzRyI9QOUEctoHeNwfvxhat8DGKJrYN3RvDYD/B2DCAAAAIEAonGmX0umHV1Af9E0y2Wfb8pzxshvD/36Kiw9Ra3Ice+9iDbPnliceZ6IOGbPEa51NOBvlEwz6tYmqCvkAySw1Evp8WYeovawjBNoIX1oVFcZ7Mjp5fN+2dxZpXzrNzwmgzstj1XIm3PvCggNjkjrGjp1SL3Q8hPZNjjRNOnp358=", + "ansible_ssh_host_key_ecdsa_public": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJrI8yWXqcscSUbtPzPsvwvLo381hSKnZwQe8K4nevf/7jFENYniewhC0vweZfw1wIkKsChvodSD/mErmlbRDlo=", + "ansible_ssh_host_key_ed25519_public": "AAAAC3NzaC1lZDI1NTE5AAAAICSpL5B07ScCMKtsBZ2WW3ZE45kUoy+1Zmq0ye74Cwyl", + "ansible_ssh_host_key_rsa_public": "AAAAB3NzaC1yc2EAAAADAQABAAABgQCxc/4SV8IE4wEG8aPExj7U0la2dV2bQYhKZAWtI1swYDACOLyVRUitY3vRahzBO5VX6x/S95lS1ibD4nmPEcXzjNp62za8jOE4q8SP3C6xqgokQRBjr8oWxsIpO+0EgXaqLBZ7lzovbTtKoyUvlctb9g2qEbKOzQoEyVQoZyWksrXQ4Rn21W1Y9J4+BD5XBLc9XGIDcxuTg44/tl21e5P0sj6NX1QnQT1lJPnqPl98QPaXOy9xp7/d0JTbW0fVuf4iuen6oK9z2UaH2qbnxkBMg/HnJD5Er0c2bCsJmPBqobp3rlajbvnU9Wc4jCIHVqMGfzf6ayaXzSw33uKgIpd9vh2RJFsTXkgdmk3X7jFZZppprrsukexOx4ldKO1FO9XklzuS85UFyxQtofW7wKavkAipq4TMpHRl1jvHwH/I3+FqIbfaCD4jqqmcsdcLq1+9UkgiOEBQOUQ8BYat6bnsvEX3Izv0MM07mfzhbsWhfDntjLSfy6vWVAaWqV8gMZs=", + "ansible_swapfree_mb": 3934, + "ansible_swaptotal_mb": 3934, + "ansible_system": "Linux", + "ansible_system_capabilities": [], + "ansible_system_capabilities_enforced": "False", + "ansible_system_vendor": "Microsoft Corporation", + "ansible_uptime_seconds": 250721, + "ansible_user_dir": "/root", + "ansible_user_gecos": "root", + "ansible_user_gid": 0, + "ansible_user_id": "root", + "ansible_user_shell": "/bin/bash", + "ansible_user_uid": 0, + "ansible_userspace_architecture": "x86_64", + "ansible_userspace_bits": "64", + "ansible_virtualization_role": "guest", + "ansible_virtualization_type": "VirtualPC", + "gather_subset": [ + "all" + ], + "module_setup": true + }, + "changed": false, + "deprecations": [], + "warnings": [] +} diff --git a/files/bashrc b/files/bashrc new file mode 100644 index 0000000..57c1e3f --- /dev/null +++ b/files/bashrc @@ -0,0 +1,102 @@ +# ~/.bashrc: executed by bash(1) for non-login shells. +# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) +# for examples + +# If not running interactively, don't do anything +[ -z "$PS1" ] && return + +# don't put duplicate lines in the history. See bash(1) for more options +# ... or force ignoredups and ignorespace +HISTCONTROL=ignoredups:ignorespace + +# append to the history file, don't overwrite it +shopt -s histappend + +# for setting history length see HISTSIZE and HISTFILESIZE in bash(1) +HISTSIZE=1000 +HISTFILESIZE=2000 + +# check the window size after each command and, if necessary, +# update the values of LINES and COLUMNS. +shopt -s checkwinsize + +# make less more friendly for non-text input files, see lesspipe(1) +[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" + +# set variable identifying the chroot you work in (used in the prompt below) +if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then + debian_chroot=$(cat /etc/debian_chroot) +fi + +# set a fancy prompt (non-color, unless we know we "want" color) +case "$TERM" in + xterm-color) color_prompt=yes;; +esac + +# uncomment for a colored prompt, if the terminal has the capability; turned +# off by default to not distract the user: the focus in a terminal window +# should be on the output of commands, not on the prompt +#force_color_prompt=yes + +if [ -n "$force_color_prompt" ]; then + if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then + # We have color support; assume it's compliant with Ecma-48 + # (ISO/IEC-6429). (Lack of such support is extremely rare, and such + # a case would tend to support setf rather than setaf.) + color_prompt=yes + else + color_prompt= + fi +fi + +if [ "$color_prompt" = yes ]; then + PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' +else + PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' +fi +unset color_prompt force_color_prompt + +# If this is an xterm set the title to user@host:dir +case "$TERM" in +xterm*|rxvt*) + PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" + ;; +*) + ;; +esac + +# enable color support of ls and also add handy aliases +if [ -x /usr/bin/dircolors ]; then + test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" + alias ls='ls --color=auto' + #alias dir='dir --color=auto' + #alias vdir='vdir --color=auto' + + alias grep='grep --color=auto' + alias fgrep='fgrep --color=auto' + alias egrep='egrep --color=auto' +fi + +# some more ls aliases +alias ll='ls -alF' +alias la='ls -A' +alias l='ls -CF' + +# Alias definitions. +# You may want to put all your additions into a separate file like +# ~/.bash_aliases, instead of adding them here directly. +# See /usr/share/doc/bash-doc/examples in the bash-doc package. + +if [ -f ~/.bash_aliases ]; then + . ~/.bash_aliases +fi + +# enable programmable completion features (you don't need to enable +# this, if it's already enabled in /etc/bash.bashrc and /etc/profile +# sources /etc/bash.bashrc). +#if [ -f /etc/bash_completion ] && ! shopt -oq posix; then +# . /etc/bash_completion +#fi +alias enable_ipv6='sed -i "/net.ipv6.conf.all.disable_ipv6.*/d" /etc/sysctl.conf && sysctl -q -p && echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6 && sed -i "s/#//" /etc/netplan/01-netcfg.yaml && netplan generate && netplan apply' + +export PS1="[\[$(tput sgr0)\]\[\033[38;5;10m\]\t\[$(tput sgr0)\] \[$(tput sgr0)\]\[\033[38;5;9m\]\u\[$(tput sgr0)\]@\[$(tput sgr0)\]\[\033[38;5;9m\]\h\[$(tput sgr0)\]:\[$(tput sgr0)\]\[\033[38;5;10m\]\W\[$(tput sgr0)\]]\[$(tput sgr0)\] " diff --git a/files/certbot-auto b/files/certbot-auto new file mode 100644 index 0000000..6261570 --- /dev/null +++ b/files/certbot-auto @@ -0,0 +1,2024 @@ +#!/bin/sh +# +# Download and run the latest release version of the Certbot client. +# +# NOTE: THIS SCRIPT IS AUTO-GENERATED AND SELF-UPDATING +# +# IF YOU WANT TO EDIT IT LOCALLY, *ALWAYS* RUN YOUR COPY WITH THE +# "--no-self-upgrade" FLAG +# +# IF YOU WANT TO SEND PULL REQUESTS, THE REAL SOURCE FOR THIS FILE IS +# letsencrypt-auto-source/letsencrypt-auto.template AND +# letsencrypt-auto-source/pieces/bootstrappers/* + +set -e # Work even if somebody does "sh thisscript.sh". + +# Note: you can set XDG_DATA_HOME or VENV_PATH before running this script, +# if you want to change where the virtual environment will be installed + +# HOME might not be defined when being run through something like systemd +if [ -z "$HOME" ]; then + HOME=~root +fi +if [ -z "$XDG_DATA_HOME" ]; then + XDG_DATA_HOME=~/.local/share +fi +if [ -z "$VENV_PATH" ]; then + # We export these values so they are preserved properly if this script is + # rerun with sudo/su where $HOME/$XDG_DATA_HOME may have a different value. + export OLD_VENV_PATH="$XDG_DATA_HOME/letsencrypt" + export VENV_PATH="/opt/eff.org/certbot/venv" +fi +VENV_BIN="$VENV_PATH/bin" +BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" +LE_AUTO_VERSION="1.8.0" +BASENAME=$(basename $0) +USAGE="Usage: $BASENAME [OPTIONS] +A self-updating wrapper script for the Certbot ACME client. When run, updates +to both this script and certbot will be downloaded and installed. After +ensuring you have the latest versions installed, certbot will be invoked with +all arguments you have provided. + +Help for certbot itself cannot be provided until it is installed. + + --debug attempt experimental installation + -h, --help print this help + -n, --non-interactive, --noninteractive run without asking for user input + --no-bootstrap do not install OS dependencies + --no-permissions-check do not warn about file system permissions + --no-self-upgrade do not download updates + --os-packages-only install OS dependencies and exit + --install-only install certbot, upgrade if needed, and exit + -v, --verbose provide more output + -q, --quiet provide only update/error output; + implies --non-interactive + +All arguments are accepted and forwarded to the Certbot client when run." +export CERTBOT_AUTO="$0" + +for arg in "$@" ; do + case "$arg" in + --debug) + DEBUG=1;; + --os-packages-only) + OS_PACKAGES_ONLY=1;; + --install-only) + INSTALL_ONLY=1;; + --no-self-upgrade) + # Do not upgrade this script (also prevents client upgrades, because each + # copy of the script pins a hash of the python client) + NO_SELF_UPGRADE=1;; + --no-permissions-check) + NO_PERMISSIONS_CHECK=1;; + --no-bootstrap) + NO_BOOTSTRAP=1;; + --help) + HELP=1;; + --noninteractive|--non-interactive) + NONINTERACTIVE=1;; + --quiet) + QUIET=1;; + renew) + ASSUME_YES=1;; + --verbose) + VERBOSE=1;; + -[!-]*) + OPTIND=1 + while getopts ":hnvq" short_arg $arg; do + case "$short_arg" in + h) + HELP=1;; + n) + NONINTERACTIVE=1;; + q) + QUIET=1;; + v) + VERBOSE=1;; + esac + done;; + esac +done + +if [ $BASENAME = "letsencrypt-auto" ]; then + # letsencrypt-auto does not respect --help or --yes for backwards compatibility + NONINTERACTIVE=1 + HELP=0 +fi + +# Set ASSUME_YES to 1 if QUIET or NONINTERACTIVE +if [ "$QUIET" = 1 -o "$NONINTERACTIVE" = 1 ]; then + ASSUME_YES=1 +fi + +say() { + if [ "$QUIET" != 1 ]; then + echo "$@" + fi +} + +error() { + echo "$@" +} + +# Support for busybox and others where there is no "command", +# but "which" instead +if command -v command > /dev/null 2>&1 ; then + export EXISTS="command -v" +elif which which > /dev/null 2>&1 ; then + export EXISTS="which" +else + error "Cannot find command nor which... please install one!" + exit 1 +fi + +# Certbot itself needs root access for almost all modes of operation. +# certbot-auto needs root access to bootstrap OS dependencies and install +# Certbot at a protected path so it can be safely run as root. To accomplish +# this, this script will attempt to run itself as root if it doesn't have the +# necessary privileges by using `sudo` or falling back to `su` if it is not +# available. The mechanism used to obtain root access can be set explicitly by +# setting the environment variable LE_AUTO_SUDO to 'sudo', 'su', 'su_sudo', +# 'SuSudo', or '' as used below. + +# Because the parameters in `su -c` has to be a string, +# we need to properly escape it. +SuSudo() { + args="" + # This `while` loop iterates over all parameters given to this function. + # For each parameter, all `'` will be replace by `'"'"'`, and the escaped string + # will be wrapped in a pair of `'`, then appended to `$args` string + # For example, `echo "It's only 1\$\!"` will be escaped to: + # 'echo' 'It'"'"'s only 1$!' + # │ │└┼┘│ + # │ │ │ └── `'s only 1$!'` the literal string + # │ │ └── `\"'\"` is a single quote (as a string) + # │ └── `'It'`, to be concatenated with the strings following it + # └── `echo` wrapped in a pair of `'`, it's totally fine for the shell command itself + while [ $# -ne 0 ]; do + args="$args'$(printf "%s" "$1" | sed -e "s/'/'\"'\"'/g")' " + shift + done + su root -c "$args" +} + +# Sets the environment variable SUDO to be the name of the program or function +# to call to get root access. If this script already has root privleges, SUDO +# is set to an empty string. The value in SUDO should be run with the command +# to called with root privileges as arguments. +SetRootAuthMechanism() { + SUDO="" + if [ -n "${LE_AUTO_SUDO+x}" ]; then + case "$LE_AUTO_SUDO" in + SuSudo|su_sudo|su) + SUDO=SuSudo + ;; + sudo) + SUDO="sudo -E" + ;; + '') + # If we're not running with root, don't check that this script can only + # be modified by system users and groups. + NO_PERMISSIONS_CHECK=1 + ;; + *) + error "Error: unknown root authorization mechanism '$LE_AUTO_SUDO'." + exit 1 + esac + say "Using preset root authorization mechanism '$LE_AUTO_SUDO'." + else + if test "`id -u`" -ne "0" ; then + if $EXISTS sudo 1>/dev/null 2>&1; then + SUDO="sudo -E" + else + say \"sudo\" is not available, will use \"su\" for installation steps... + SUDO=SuSudo + fi + fi + fi +} + +if [ "$1" = "--cb-auto-has-root" ]; then + shift 1 +else + SetRootAuthMechanism + if [ -n "$SUDO" ]; then + say "Requesting to rerun $0 with root privileges..." + $SUDO "$0" --cb-auto-has-root "$@" + exit 0 + fi +fi + +# Runs this script again with the given arguments. --cb-auto-has-root is added +# to the command line arguments to ensure we don't try to acquire root a +# second time. After the script is rerun, we exit the current script. +RerunWithArgs() { + "$0" --cb-auto-has-root "$@" + exit 0 +} + +BootstrapMessage() { + # Arguments: Platform name + say "Bootstrapping dependencies for $1... (you can skip this with --no-bootstrap)" +} + +ExperimentalBootstrap() { + # Arguments: Platform name, bootstrap function name + if [ "$DEBUG" = 1 ]; then + if [ "$2" != "" ]; then + BootstrapMessage $1 + $2 + fi + else + error "FATAL: $1 support is very experimental at present..." + error "if you would like to work on improving it, please ensure you have backups" + error "and then run this script again with the --debug flag!" + error "Alternatively, you can install OS dependencies yourself and run this script" + error "again with --no-bootstrap." + exit 1 + fi +} + +DeprecationBootstrap() { + # Arguments: Platform name, bootstrap function name + if [ "$DEBUG" = 1 ]; then + if [ "$2" != "" ]; then + BootstrapMessage $1 + $2 + fi + else + error "WARNING: certbot-auto support for this $1 is DEPRECATED!" + error "Please visit certbot.eff.org to learn how to download a version of" + error "Certbot that is packaged for your system. While an existing version" + error "of certbot-auto may work currently, we have stopped supporting updating" + error "system packages for your system. Please switch to a packaged version" + error "as soon as possible." + exit 1 + fi +} + +MIN_PYTHON_2_VERSION="2.7" +MIN_PYVER2=$(echo "$MIN_PYTHON_2_VERSION" | sed 's/\.//') +MIN_PYTHON_3_VERSION="3.6" +MIN_PYVER3=$(echo "$MIN_PYTHON_3_VERSION" | sed 's/\.//') +# Sets LE_PYTHON to Python version string and PYVER to the first two +# digits of the python version. +# MIN_PYVER and MIN_PYTHON_VERSION are also set by this function, and their +# values depend on if we try to use Python 3 or Python 2. +DeterminePythonVersion() { + # Arguments: "NOCRASH" if we shouldn't crash if we don't find a good python + # + # If no Python is found, PYVER is set to 0. + if [ "$USE_PYTHON_3" = 1 ]; then + MIN_PYVER=$MIN_PYVER3 + MIN_PYTHON_VERSION=$MIN_PYTHON_3_VERSION + for LE_PYTHON in "$LE_PYTHON" python3; do + # Break (while keeping the LE_PYTHON value) if found. + $EXISTS "$LE_PYTHON" > /dev/null && break + done + else + MIN_PYVER=$MIN_PYVER2 + MIN_PYTHON_VERSION=$MIN_PYTHON_2_VERSION + for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do + # Break (while keeping the LE_PYTHON value) if found. + $EXISTS "$LE_PYTHON" > /dev/null && break + done + fi + if [ "$?" != "0" ]; then + if [ "$1" != "NOCRASH" ]; then + error "Cannot find any Pythons; please install one!" + exit 1 + else + PYVER=0 + return 0 + fi + fi + + PYVER=$("$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') + if [ "$PYVER" -lt "$MIN_PYVER" ]; then + if [ "$1" != "NOCRASH" ]; then + error "You have an ancient version of Python entombed in your operating system..." + error "This isn't going to work; you'll need at least version $MIN_PYTHON_VERSION." + exit 1 + fi + fi +} + +# If new packages are installed by BootstrapDebCommon below, this version +# number must be increased. +BOOTSTRAP_DEB_COMMON_VERSION=1 + +BootstrapDebCommon() { + # Current version tested with: + # + # - Ubuntu + # - 14.04 (x64) + # - 15.04 (x64) + # - Debian + # - 7.9 "wheezy" (x64) + # - sid (2015-10-21) (x64) + + # Past versions tested with: + # + # - Debian 8.0 "jessie" (x64) + # - Raspbian 7.8 (armhf) + + # Believed not to work: + # + # - Debian 6.0.10 "squeeze" (x64) + + if [ "$QUIET" = 1 ]; then + QUIET_FLAG='-qq' + fi + + apt-get $QUIET_FLAG update || error apt-get update hit problems but continuing anyway... + + # virtualenv binary can be found in different packages depending on + # distro version (#346) + + virtualenv= + # virtual env is known to apt and is installable + if apt-cache show virtualenv > /dev/null 2>&1 ; then + if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then + virtualenv="virtualenv" + fi + fi + + if apt-cache show python-virtualenv > /dev/null 2>&1; then + virtualenv="$virtualenv python-virtualenv" + fi + + augeas_pkg="libaugeas0 augeas-lenses" + + if [ "$ASSUME_YES" = 1 ]; then + YES_FLAG="-y" + fi + + apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \ + python \ + python-dev \ + $virtualenv \ + gcc \ + $augeas_pkg \ + libssl-dev \ + openssl \ + libffi-dev \ + ca-certificates \ + + + if ! $EXISTS virtualenv > /dev/null ; then + error Failed to install a working \"virtualenv\" command, exiting + exit 1 + fi +} + +# If new packages are installed by BootstrapRpmCommonBase below, version +# numbers in rpm_common.sh and rpm_python3.sh must be increased. + +# Sets TOOL to the name of the package manager +# Sets appropriate values for YES_FLAG and QUIET_FLAG based on $ASSUME_YES and $QUIET_FLAG. +# Note: this function is called both while selecting the bootstrap scripts and +# during the actual bootstrap. Some things like prompting to user can be done in the latter +# case, but not in the former one. +InitializeRPMCommonBase() { + if type dnf 2>/dev/null + then + TOOL=dnf + elif type yum 2>/dev/null + then + TOOL=yum + + else + error "Neither yum nor dnf found. Aborting bootstrap!" + exit 1 + fi + + if [ "$ASSUME_YES" = 1 ]; then + YES_FLAG="-y" + fi + if [ "$QUIET" = 1 ]; then + QUIET_FLAG='--quiet' + fi +} + +BootstrapRpmCommonBase() { + # Arguments: whitespace-delimited python packages to install + + InitializeRPMCommonBase # This call is superfluous in practice + + pkgs=" + gcc + augeas-libs + openssl + openssl-devel + libffi-devel + redhat-rpm-config + ca-certificates + " + + # Add the python packages + pkgs="$pkgs + $1 + " + + if $TOOL list installed "httpd" >/dev/null 2>&1; then + pkgs="$pkgs + mod_ssl + " + fi + + if ! $TOOL install $YES_FLAG $QUIET_FLAG $pkgs; then + error "Could not install OS dependencies. Aborting bootstrap!" + exit 1 + fi +} + +# If new packages are installed by BootstrapRpmCommon below, this version +# number must be increased. +BOOTSTRAP_RPM_COMMON_VERSION=1 + +BootstrapRpmCommon() { + # Tested with: + # - Fedora 20, 21, 22, 23 (x64) + # - Centos 7 (x64: on DigitalOcean droplet) + # - CentOS 7 Minimal install in a Hyper-V VM + # - CentOS 6 + + InitializeRPMCommonBase + + # Most RPM distros use the "python" or "python-" naming convention. Let's try that first. + if $TOOL list python >/dev/null 2>&1; then + python_pkgs="$python + python-devel + python-virtualenv + python-tools + python-pip + " + # Fedora 26 starts to use the prefix python2 for python2 based packages. + # this elseif is theoretically for any Fedora over version 26: + elif $TOOL list python2 >/dev/null 2>&1; then + python_pkgs="$python2 + python2-libs + python2-setuptools + python2-devel + python2-virtualenv + python2-tools + python2-pip + " + # Some distros and older versions of current distros use a "python27" + # instead of the "python" or "python-" naming convention. + else + python_pkgs="$python27 + python27-devel + python27-virtualenv + python27-tools + python27-pip + " + fi + + BootstrapRpmCommonBase "$python_pkgs" +} + +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION=1 + +# Checks if rh-python36 can be installed. +Python36SclIsAvailable() { + InitializeRPMCommonBase >/dev/null 2>&1; + + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + return 0 + fi + if "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + return 0 + fi + return 1 +} + +# Try to enable rh-python36 from SCL if it is necessary and possible. +EnablePython36SCL() { + if "$EXISTS" python3.6 > /dev/null 2> /dev/null; then + return 0 + fi + if [ ! -f /opt/rh/rh-python36/enable ]; then + return 0 + fi + set +e + if ! . /opt/rh/rh-python36/enable; then + error 'Unable to enable rh-python36!' + exit 1 + fi + set -e +} + +# This bootstrap concerns old RedHat-based distributions that do not ship by default +# with Python 2.7, but only Python 2.6. We bootstrap them by enabling SCL and installing +# Python 3.6. Some of these distributions are: CentOS/RHEL/OL/SL 6. +BootstrapRpmPython3Legacy() { + # Tested with: + # - CentOS 6 + + InitializeRPMCommonBase + + if ! "${TOOL}" list rh-python36 >/dev/null 2>&1; then + echo "To use Certbot on this operating system, packages from the SCL repository need to be installed." + if ! "${TOOL}" list centos-release-scl >/dev/null 2>&1; then + error "Enable the SCL repository and try running Certbot again." + exit 1 + fi + if [ "${ASSUME_YES}" = 1 ]; then + /bin/echo -n "Enabling the SCL repository in 3 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -ne "\e[0K\rEnabling the SCL repository in 2 seconds... (Press Ctrl-C to cancel)" + sleep 1s + /bin/echo -e "\e[0K\rEnabling the SCL repository in 1 second... (Press Ctrl-C to cancel)" + sleep 1s + fi + if ! "${TOOL}" install "${YES_FLAG}" "${QUIET_FLAG}" centos-release-scl; then + error "Could not enable SCL. Aborting bootstrap!" + exit 1 + fi + fi + + # CentOS 6 must use rh-python36 from SCL + if "${TOOL}" list rh-python36 >/dev/null 2>&1; then + python_pkgs="rh-python36-python + rh-python36-python-virtualenv + rh-python36-python-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "${python_pkgs}" + + # Enable SCL rh-python36 after bootstrapping. + EnablePython36SCL +} + +# If new packages are installed by BootstrapRpmPython3 below, this version +# number must be increased. +BOOTSTRAP_RPM_PYTHON3_VERSION=1 + +BootstrapRpmPython3() { + # Tested with: + # - Fedora 29 + + InitializeRPMCommonBase + + # Fedora 29 must use python3-virtualenv + if $TOOL list python3-virtualenv >/dev/null 2>&1; then + python_pkgs="python3 + python3-virtualenv + python3-devel + " + else + error "No supported Python package available to install. Aborting bootstrap!" + exit 1 + fi + + BootstrapRpmCommonBase "$python_pkgs" +} + +# If new packages are installed by BootstrapSuseCommon below, this version +# number must be increased. +BOOTSTRAP_SUSE_COMMON_VERSION=1 + +BootstrapSuseCommon() { + # SLE12 don't have python-virtualenv + + if [ "$ASSUME_YES" = 1 ]; then + zypper_flags="-nq" + install_flags="-l" + fi + + if [ "$QUIET" = 1 ]; then + QUIET_FLAG='-qq' + fi + + if zypper search -x python-virtualenv >/dev/null 2>&1; then + OPENSUSE_VIRTUALENV_PACKAGES="python-virtualenv" + else + # Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv + # is a source package, and python2-virtualenv must be used instead. + # Also currently python2-setuptools is not a dependency of python2-virtualenv, + # while it should be. Installing it explicitly until upstream fix. + OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools" + fi + + zypper $QUIET_FLAG $zypper_flags in $install_flags \ + python \ + python-devel \ + $OPENSUSE_VIRTUALENV_PACKAGES \ + gcc \ + augeas-lenses \ + libopenssl-devel \ + libffi-devel \ + ca-certificates +} + +# If new packages are installed by BootstrapArchCommon below, this version +# number must be increased. +BOOTSTRAP_ARCH_COMMON_VERSION=1 + +BootstrapArchCommon() { + # Tested with: + # - ArchLinux (x86_64) + # + # "python-virtualenv" is Python3, but "python2-virtualenv" provides + # only "virtualenv2" binary, not "virtualenv". + + deps=" + python2 + python-virtualenv + gcc + augeas + openssl + libffi + ca-certificates + pkg-config + " + + # pacman -T exits with 127 if there are missing dependencies + missing=$(pacman -T $deps) || true + + if [ "$ASSUME_YES" = 1 ]; then + noconfirm="--noconfirm" + fi + + if [ "$missing" ]; then + if [ "$QUIET" = 1 ]; then + pacman -S --needed $missing $noconfirm > /dev/null + else + pacman -S --needed $missing $noconfirm + fi + fi +} + +# If new packages are installed by BootstrapGentooCommon below, this version +# number must be increased. +BOOTSTRAP_GENTOO_COMMON_VERSION=1 + +BootstrapGentooCommon() { + PACKAGES=" + dev-lang/python:2.7 + dev-python/virtualenv + app-admin/augeas + dev-libs/openssl + dev-libs/libffi + app-misc/ca-certificates + virtual/pkgconfig" + + ASK_OPTION="--ask" + if [ "$ASSUME_YES" = 1 ]; then + ASK_OPTION="" + fi + + case "$PACKAGE_MANAGER" in + (paludis) + cave resolve --preserve-world --keep-targets if-possible $PACKAGES -x + ;; + (pkgcore) + pmerge --noreplace --oneshot $ASK_OPTION $PACKAGES + ;; + (portage|*) + emerge --noreplace --oneshot $ASK_OPTION $PACKAGES + ;; + esac +} + +# If new packages are installed by BootstrapFreeBsd below, this version number +# must be increased. +BOOTSTRAP_FREEBSD_VERSION=1 + +BootstrapFreeBsd() { + if [ "$QUIET" = 1 ]; then + QUIET_FLAG="--quiet" + fi + + pkg install -Ay $QUIET_FLAG \ + python \ + py27-virtualenv \ + augeas \ + libffi +} + +# If new packages are installed by BootstrapMac below, this version number must +# be increased. +BOOTSTRAP_MAC_VERSION=1 + +BootstrapMac() { + if hash brew 2>/dev/null; then + say "Using Homebrew to install dependencies..." + pkgman=brew + pkgcmd="brew install" + elif hash port 2>/dev/null; then + say "Using MacPorts to install dependencies..." + pkgman=port + pkgcmd="port install" + else + say "No Homebrew/MacPorts; installing Homebrew..." + ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + pkgman=brew + pkgcmd="brew install" + fi + + $pkgcmd augeas + if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \ + -o "$(which python)" = "/usr/bin/python" ]; then + # We want to avoid using the system Python because it requires root to use pip. + # python.org, MacPorts or HomeBrew Python installations should all be OK. + say "Installing python..." + $pkgcmd python + fi + + # Workaround for _dlopen not finding augeas on macOS + if [ "$pkgman" = "port" ] && ! [ -e "/usr/local/lib/libaugeas.dylib" ] && [ -e "/opt/local/lib/libaugeas.dylib" ]; then + say "Applying augeas workaround" + mkdir -p /usr/local/lib/ + ln -s /opt/local/lib/libaugeas.dylib /usr/local/lib/ + fi + + if ! hash pip 2>/dev/null; then + say "pip not installed" + say "Installing pip..." + curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python + fi + + if ! hash virtualenv 2>/dev/null; then + say "virtualenv not installed." + say "Installing with pip..." + pip install virtualenv + fi +} + +# If new packages are installed by BootstrapSmartOS below, this version number +# must be increased. +BOOTSTRAP_SMARTOS_VERSION=1 + +BootstrapSmartOS() { + pkgin update + pkgin -y install 'gcc49' 'py27-augeas' 'py27-virtualenv' +} + +# If new packages are installed by BootstrapMageiaCommon below, this version +# number must be increased. +BOOTSTRAP_MAGEIA_COMMON_VERSION=1 + +BootstrapMageiaCommon() { + if [ "$QUIET" = 1 ]; then + QUIET_FLAG='--quiet' + fi + + if ! urpmi --force $QUIET_FLAG \ + python \ + libpython-devel \ + python-virtualenv + then + error "Could not install Python dependencies. Aborting bootstrap!" + exit 1 + fi + + if ! urpmi --force $QUIET_FLAG \ + git \ + gcc \ + python-augeas \ + libopenssl-devel \ + libffi-devel \ + rootcerts + then + error "Could not install additional dependencies. Aborting bootstrap!" + exit 1 + fi +} + + +# Set Bootstrap to the function that installs OS dependencies on this system +# and BOOTSTRAP_VERSION to the unique identifier for the current version of +# that function. If Bootstrap is set to a function that doesn't install any +# packages BOOTSTRAP_VERSION is not set. +if [ -f /etc/debian_version ]; then + Bootstrap() { + BootstrapMessage "Debian-based OSes" + BootstrapDebCommon + } + BOOTSTRAP_VERSION="BootstrapDebCommon $BOOTSTRAP_DEB_COMMON_VERSION" +elif [ -f /etc/mageia-release ]; then + # Mageia has both /etc/mageia-release and /etc/redhat-release + Bootstrap() { + ExperimentalBootstrap "Mageia" BootstrapMageiaCommon + } + BOOTSTRAP_VERSION="BootstrapMageiaCommon $BOOTSTRAP_MAGEIA_COMMON_VERSION" +elif [ -f /etc/redhat-release ]; then + # Run DeterminePythonVersion to decide on the basis of available Python versions + # whether to use 2.x or 3.x on RedHat-like systems. + # Then, revert LE_PYTHON to its previous state. + prev_le_python="$LE_PYTHON" + unset LE_PYTHON + DeterminePythonVersion "NOCRASH" + + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` + + if [ "$PYVER" -eq 26 -a $(uname -m) != 'x86_64' ]; then + # 32 bits CentOS 6 and affiliates are not supported anymore by certbot-auto. + DEPRECATED_OS=1 + fi + + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on + # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an + # error, RPM_DIST_VERSION is set to "unknown". + RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown") + + # If RPM_DIST_VERSION is an empty string or it contains any nonnumeric + # characters, the value is unexpected so we set RPM_DIST_VERSION to 0. + if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then + RPM_DIST_VERSION=0 + fi + + # Handle legacy RPM distributions + if [ "$PYVER" -eq 26 ]; then + # Check if an automated bootstrap can be achieved on this system. + if ! Python36SclIsAvailable; then + INTERACTIVE_BOOTSTRAP=1 + fi + + Bootstrap() { + BootstrapMessage "Legacy RedHat-based OSes that will use Python3" + BootstrapRpmPython3Legacy + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3Legacy $BOOTSTRAP_RPM_PYTHON3_LEGACY_VERSION" + + # Try now to enable SCL rh-python36 for systems already bootstrapped + # NB: EnablePython36SCL has been defined along with BootstrapRpmPython3Legacy in certbot-auto + EnablePython36SCL + else + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. + if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then + Bootstrap() { + BootstrapMessage "RedHat-based OSes that will use Python3" + BootstrapRpmPython3 + } + USE_PYTHON_3=1 + BOOTSTRAP_VERSION="BootstrapRpmPython3 $BOOTSTRAP_RPM_PYTHON3_VERSION" + else + Bootstrap() { + BootstrapMessage "RedHat-based OSes" + BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" + fi + fi + + LE_PYTHON="$prev_le_python" +elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then + Bootstrap() { + BootstrapMessage "openSUSE-based OSes" + BootstrapSuseCommon + } + BOOTSTRAP_VERSION="BootstrapSuseCommon $BOOTSTRAP_SUSE_COMMON_VERSION" +elif [ -f /etc/arch-release ]; then + Bootstrap() { + if [ "$DEBUG" = 1 ]; then + BootstrapMessage "Archlinux" + BootstrapArchCommon + else + error "Please use pacman to install letsencrypt packages:" + error "# pacman -S certbot certbot-apache" + error + error "If you would like to use the virtualenv way, please run the script again with the" + error "--debug flag." + exit 1 + fi + } + BOOTSTRAP_VERSION="BootstrapArchCommon $BOOTSTRAP_ARCH_COMMON_VERSION" +elif [ -f /etc/manjaro-release ]; then + Bootstrap() { + ExperimentalBootstrap "Manjaro Linux" BootstrapArchCommon + } + BOOTSTRAP_VERSION="BootstrapArchCommon $BOOTSTRAP_ARCH_COMMON_VERSION" +elif [ -f /etc/gentoo-release ]; then + DEPRECATED_OS=1 +elif uname | grep -iq FreeBSD ; then + DEPRECATED_OS=1 +elif uname | grep -iq Darwin ; then + DEPRECATED_OS=1 +elif [ -f /etc/issue ] && grep -iq "Amazon Linux" /etc/issue ; then + Bootstrap() { + ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon + } + BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" +elif [ -f /etc/product ] && grep -q "Joyent Instance" /etc/product ; then + Bootstrap() { + ExperimentalBootstrap "Joyent SmartOS Zone" BootstrapSmartOS + } + BOOTSTRAP_VERSION="BootstrapSmartOS $BOOTSTRAP_SMARTOS_VERSION" +else + Bootstrap() { + error "Sorry, I don't know how to bootstrap Certbot on your operating system!" + error + error "You will need to install OS dependencies, configure virtualenv, and run pip install manually." + error "Please see https://certbot.eff.org/docs/contributing.html#prerequisites" + error "for more info." + exit 1 + } +fi + +# We handle this case after determining the normal bootstrap version to allow +# variables like USE_PYTHON_3 to be properly set. As described above, if the +# Bootstrap function doesn't install any packages, BOOTSTRAP_VERSION should not +# be set so we unset it here. +if [ "$NO_BOOTSTRAP" = 1 ]; then + Bootstrap() { + : + } + unset BOOTSTRAP_VERSION +fi + +if [ "$DEPRECATED_OS" = 1 ]; then + Bootstrap() { + error "Skipping bootstrap because certbot-auto is deprecated on this system." + } + unset BOOTSTRAP_VERSION +fi + +# Sets PREV_BOOTSTRAP_VERSION to the identifier for the bootstrap script used +# to install OS dependencies on this system. PREV_BOOTSTRAP_VERSION isn't set +# if it is unknown how OS dependencies were installed on this system. +SetPrevBootstrapVersion() { + if [ -f $BOOTSTRAP_VERSION_PATH ]; then + PREV_BOOTSTRAP_VERSION=$(cat "$BOOTSTRAP_VERSION_PATH") + # The list below only contains bootstrap version strings that existed before + # we started writing them to disk. + # + # DO NOT MODIFY THIS LIST UNLESS YOU KNOW WHAT YOU'RE DOING! + elif grep -Fqx "$BOOTSTRAP_VERSION" << "UNLIKELY_EOF" +BootstrapDebCommon 1 +BootstrapMageiaCommon 1 +BootstrapRpmCommon 1 +BootstrapSuseCommon 1 +BootstrapArchCommon 1 +BootstrapGentooCommon 1 +BootstrapFreeBsd 1 +BootstrapMac 1 +BootstrapSmartOS 1 +UNLIKELY_EOF + then + # If there's no bootstrap version saved to disk, but the currently selected + # bootstrap script is from before we started saving the version number, + # return the currently selected version to prevent us from rebootstrapping + # unnecessarily. + PREV_BOOTSTRAP_VERSION="$BOOTSTRAP_VERSION" + fi +} + +TempDir() { + mktemp -d 2>/dev/null || mktemp -d -t 'le' # Linux || macOS +} + +# Returns 0 if a letsencrypt installation exists at $OLD_VENV_PATH, otherwise, +# returns a non-zero number. +OldVenvExists() { + [ -n "$OLD_VENV_PATH" -a -f "$OLD_VENV_PATH/bin/letsencrypt" ] +} + +# Given python path, version 1 and version 2, check if version 1 is outdated compared to version 2. +# An unofficial version provided as version 1 (eg. 0.28.0.dev0) will be treated +# specifically by printing "UNOFFICIAL". Otherwise, print "OUTDATED" if version 1 +# is outdated, and "UP_TO_DATE" if not. +# This function relies only on installed python environment (2.x or 3.x) by certbot-auto. +CompareVersions() { + "$1" - "$2" "$3" << "UNLIKELY_EOF" +import sys +from distutils.version import StrictVersion + +try: + current = StrictVersion(sys.argv[1]) +except ValueError: + sys.stdout.write('UNOFFICIAL') + sys.exit() + +try: + remote = StrictVersion(sys.argv[2]) +except ValueError: + sys.stdout.write('UP_TO_DATE') + sys.exit() + +if current < remote: + sys.stdout.write('OUTDATED') +else: + sys.stdout.write('UP_TO_DATE') +UNLIKELY_EOF +} + +# Create a new virtual environment for Certbot. It will overwrite any existing one. +# Parameters: LE_PYTHON, VENV_PATH, PYVER, VERBOSE +CreateVenv() { + "$1" - "$2" "$3" "$4" << "UNLIKELY_EOF" +#!/usr/bin/env python +import os +import shutil +import subprocess +import sys + + +def create_venv(venv_path, pyver, verbose): + if os.path.exists(venv_path): + shutil.rmtree(venv_path) + + stdout = sys.stdout if verbose == '1' else open(os.devnull, 'w') + + if int(pyver) <= 27: + # Use virtualenv binary + environ = os.environ.copy() + environ['VIRTUALENV_NO_DOWNLOAD'] = '1' + command = ['virtualenv', '--no-site-packages', '--python', sys.executable, venv_path] + subprocess.check_call(command, stdout=stdout, env=environ) + else: + # Use embedded venv module in Python 3 + command = [sys.executable, '-m', 'venv', venv_path] + subprocess.check_call(command, stdout=stdout) + + +if __name__ == '__main__': + create_venv(*sys.argv[1:]) + +UNLIKELY_EOF +} + +# Check that the given PATH_TO_CHECK has secured permissions. +# Parameters: LE_PYTHON, PATH_TO_CHECK +CheckPathPermissions() { + "$1" - "$2" << "UNLIKELY_EOF" +"""Verifies certbot-auto cannot be modified by unprivileged users. + +This script takes the path to certbot-auto as its only command line +argument. It then checks that the file can only be modified by uid/gid +< 1000 and if other users can modify the file, it prints a warning with +a suggestion on how to solve the problem. + +Permissions on symlinks in the absolute path of certbot-auto are ignored +and only the canonical path to certbot-auto is checked. There could be +permissions problems due to the symlinks that are unreported by this +script, however, issues like this were not caused by our documentation +and are ignored for the sake of simplicity. + +All warnings are printed to stdout rather than stderr so all stderr +output from this script can be suppressed to avoid printing messages if +this script fails for some reason. + +""" +from __future__ import print_function + +import os +import stat +import sys + + +FORUM_POST_URL = 'https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/' + + +def has_safe_permissions(path): + """Returns True if the given path has secure permissions. + + The permissions are considered safe if the file is only writable by + uid/gid < 1000. + + The reason we allow more IDs than 0 is because on some systems such + as Debian, system users/groups other than uid/gid 0 are used for the + path we recommend in our instructions which is /usr/local/bin. 1000 + was chosen because on Debian 0-999 is reserved for system IDs[1] and + on RHEL either 0-499 or 0-999 is reserved depending on the + version[2][3]. Due to these differences across different OSes, this + detection isn't perfect so we only determine permissions are + insecure when we can be reasonably confident there is a problem + regardless of the underlying OS. + + [1] https://www.debian.org/doc/debian-policy/ch-opersys.html#uid-and-gid-classes + [2] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/ch-managing_users_and_groups + [3] https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/system_administrators_guide/ch-managing_users_and_groups + + :param str path: filesystem path to check + :returns: True if the path has secure permissions, otherwise, False + :rtype: bool + + """ + # os.stat follows symlinks before obtaining information about a file. + stat_result = os.stat(path) + if stat_result.st_mode & stat.S_IWOTH: + return False + if stat_result.st_mode & stat.S_IWGRP and stat_result.st_gid >= 1000: + return False + if stat_result.st_mode & stat.S_IWUSR and stat_result.st_uid >= 1000: + return False + return True + + +def main(certbot_auto_path): + current_path = os.path.realpath(certbot_auto_path) + last_path = None + permissions_ok = True + # This loop makes use of the fact that os.path.dirname('/') == '/'. + while current_path != last_path and permissions_ok: + permissions_ok = has_safe_permissions(current_path) + last_path = current_path + current_path = os.path.dirname(current_path) + + if not permissions_ok: + print('{0} has insecure permissions!'.format(certbot_auto_path)) + print('To learn how to fix them, visit {0}'.format(FORUM_POST_URL)) + + +if __name__ == '__main__': + main(sys.argv[1]) + +UNLIKELY_EOF +} + +if [ "$1" = "--le-auto-phase2" ]; then + # Phase 2: Create venv, install LE, and run. + + shift 1 # the --le-auto-phase2 arg + + if [ "$DEPRECATED_OS" = 1 ]; then + # Phase 2 damage control mode for deprecated OSes. + # In this situation, we bypass any bootstrap or certbot venv setup. + error "Your system is not supported by certbot-auto anymore." + + if [ ! -d "$VENV_PATH" ] && OldVenvExists; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + + if [ -f "$VENV_BIN/letsencrypt" -a "$INSTALL_ONLY" != 1 ]; then + error "Certbot will no longer receive updates." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + "$VENV_BIN/letsencrypt" "$@" + exit 0 + else + error "Certbot cannot be installed." + error "Please visit https://certbot.eff.org/ to check for other alternatives." + exit 1 + fi + fi + + SetPrevBootstrapVersion + + if [ -z "$PHASE_1_VERSION" -a "$USE_PYTHON_3" = 1 ]; then + unset LE_PYTHON + fi + + INSTALLED_VERSION="none" + if [ -d "$VENV_PATH" ] || OldVenvExists; then + # If the selected Bootstrap function isn't a noop and it differs from the + # previously used version + if [ -n "$BOOTSTRAP_VERSION" -a "$BOOTSTRAP_VERSION" != "$PREV_BOOTSTRAP_VERSION" ]; then + # Check if we can rebootstrap without manual user intervention: this requires that + # certbot-auto is in non-interactive mode AND selected bootstrap does not claim to + # require a manual user intervention. + if [ "$NONINTERACTIVE" = 1 -a "$INTERACTIVE_BOOTSTRAP" != 1 ]; then + CAN_REBOOTSTRAP=1 + fi + # Check if rebootstrap can be done non-interactively and current shell is non-interactive + # (true if stdin and stdout are not attached to a terminal). + if [ \( "$CAN_REBOOTSTRAP" = 1 \) -o \( \( -t 0 \) -a \( -t 1 \) \) ]; then + if [ -d "$VENV_PATH" ]; then + rm -rf "$VENV_PATH" + fi + # In the case the old venv was just a symlink to the new one, + # OldVenvExists is now false because we deleted the venv at VENV_PATH. + if OldVenvExists; then + rm -rf "$OLD_VENV_PATH" + ln -s "$VENV_PATH" "$OLD_VENV_PATH" + fi + RerunWithArgs "$@" + # Otherwise bootstrap needs to be done manually by the user. + else + # If it is because bootstrapping is interactive, --non-interactive will be of no use. + if [ "$INTERACTIVE_BOOTSTRAP" = 1 ]; then + error "Skipping upgrade because new OS dependencies may need to be installed." + error "This requires manual user intervention: please run this script again manually." + # If this is because of the environment (eg. non interactive shell without + # --non-interactive flag set), help the user in that direction. + else + error "Skipping upgrade because new OS dependencies may need to be installed." + error + error "To upgrade to a newer version, please run this script again manually so you can" + error "approve changes or with --non-interactive on the command line to automatically" + error "install any required packages." + fi + # Set INSTALLED_VERSION to be the same so we don't update the venv + INSTALLED_VERSION="$LE_AUTO_VERSION" + # Continue to use OLD_VENV_PATH if the new venv doesn't exist + if [ ! -d "$VENV_PATH" ]; then + VENV_BIN="$OLD_VENV_PATH/bin" + fi + fi + elif [ -f "$VENV_BIN/letsencrypt" ]; then + # --version output ran through grep due to python-cryptography DeprecationWarnings + # grep for both certbot and letsencrypt until certbot and shim packages have been released + INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2) + if [ -z "$INSTALLED_VERSION" ]; then + error "Error: couldn't get currently installed version for $VENV_BIN/letsencrypt: " 1>&2 + "$VENV_BIN/letsencrypt" --version + exit 1 + fi + fi + fi + + if [ "$LE_AUTO_VERSION" != "$INSTALLED_VERSION" ]; then + say "Creating virtual environment..." + DeterminePythonVersion + CreateVenv "$LE_PYTHON" "$VENV_PATH" "$PYVER" "$VERBOSE" + + if [ -n "$BOOTSTRAP_VERSION" ]; then + echo "$BOOTSTRAP_VERSION" > "$BOOTSTRAP_VERSION_PATH" + elif [ -n "$PREV_BOOTSTRAP_VERSION" ]; then + echo "$PREV_BOOTSTRAP_VERSION" > "$BOOTSTRAP_VERSION_PATH" + fi + + say "Installing Python packages..." + TEMP_DIR=$(TempDir) + trap 'rm -rf "$TEMP_DIR"' EXIT + # There is no $ interpolation due to quotes on starting heredoc delimiter. + # ------------------------------------------------------------------------- + cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt" +# This is the flattened list of packages certbot-auto installs. +# To generate this, do (with docker and package hashin installed): +# ``` +# letsencrypt-auto-source/rebuild_dependencies.py \ +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 +# ``` +ConfigArgParse==1.2.3 \ + --hash=sha256:edd17be986d5c1ba2e307150b8e5f5107aba125f3574dddd02c85d5cdcfd37dc +certifi==2020.4.5.1 \ + --hash=sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304 \ + --hash=sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519 +cffi==1.14.0 \ + --hash=sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff \ + --hash=sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b \ + --hash=sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac \ + --hash=sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0 \ + --hash=sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384 \ + --hash=sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26 \ + --hash=sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6 \ + --hash=sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b \ + --hash=sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e \ + --hash=sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd \ + --hash=sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2 \ + --hash=sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66 \ + --hash=sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc \ + --hash=sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8 \ + --hash=sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55 \ + --hash=sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4 \ + --hash=sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5 \ + --hash=sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d \ + --hash=sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78 \ + --hash=sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa \ + --hash=sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793 \ + --hash=sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f \ + --hash=sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a \ + --hash=sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f \ + --hash=sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30 \ + --hash=sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f \ + --hash=sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3 \ + --hash=sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +configobj==5.0.6 \ + --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 +cryptography==2.8 \ + --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ + --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ + --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ + --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ + --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ + --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ + --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ + --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ + --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ + --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ + --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ + --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ + --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ + --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ + --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ + --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ + --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ + --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ + --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ + --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ + --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 +distro==1.5.0 \ + --hash=sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92 \ + --hash=sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799 +enum34==1.1.10; python_version < '3.4' \ + --hash=sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53 \ + --hash=sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328 \ + --hash=sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248 +funcsigs==1.0.2 \ + --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ + --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 +idna==2.9 \ + --hash=sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb \ + --hash=sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa +ipaddress==1.0.23 \ + --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ + --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 +josepy==1.3.0 \ + --hash=sha256:c341ffa403399b18e9eae9012f804843045764d1390f9cb4648980a7569b1619 \ + --hash=sha256:e54882c64be12a2a76533f73d33cba9e331950fda9e2731e843490b774e7a01c +mock==1.3.0 \ + --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ + --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb +parsedatetime==2.5 \ + --hash=sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1 \ + --hash=sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667 +pbr==5.4.5 \ + --hash=sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c \ + --hash=sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8 +pyOpenSSL==19.1.0 \ + --hash=sha256:621880965a720b8ece2f1b2f54ea2071966ab00e2970ad2ce11d596102063504 \ + --hash=sha256:9a24494b2602aaf402be5c9e30a0b82d4a5c67528fe8fb475e3f3bc00dd69507 +pyRFC3339==1.1 \ + --hash=sha256:67196cb83b470709c580bb4738b83165e67c6cc60e1f2e4f286cfcb402a926f4 \ + --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +python-augeas==0.5.0 \ + --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 +pytz==2020.1 \ + --hash=sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed \ + --hash=sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048 +requests==2.23.0 \ + --hash=sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee \ + --hash=sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6 +requests-toolbelt==0.9.1 \ + --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ + --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 +zope.component==4.6.1 \ + --hash=sha256:bfbe55d4a93e70a78b10edc3aad4de31bb8860919b7cbd8d66f717f7d7b279ac \ + --hash=sha256:d9c7c27673d787faff8a83797ce34d6ebcae26a370e25bddb465ac2182766aca +zope.deferredimport==4.3.1 \ + --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ + --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a +zope.deprecation==4.4.0 \ + --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ + --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 +zope.event==4.4 \ + --hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \ + --hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7 +zope.hookable==5.0.1 \ + --hash=sha256:0194b9b9e7f614abba60c90b231908861036578297515d3d6508eb10190f266d \ + --hash=sha256:0c2977473918bdefc6fa8dfb311f154e7f13c6133957fe649704deca79b92093 \ + --hash=sha256:17b8bdb3b77e03a152ca0d5ca185a7ae0156f5e5a2dbddf538676633a1f7380f \ + --hash=sha256:29d07681a78042cdd15b268ae9decffed9ace68a53eebeb61d65ae931d158841 \ + --hash=sha256:36fb1b35d1150267cb0543a1ddd950c0bc2c75ed0e6e92e3aaa6ac2e29416cb7 \ + --hash=sha256:3aed60c2bb5e812bbf9295c70f25b17ac37c233f30447a96c67913ba5073642f \ + --hash=sha256:3cac1565cc768911e72ca9ec4ddf5c5109e1fef0104f19f06649cf1874943b60 \ + --hash=sha256:3d4bc0cc4a37c3cd3081063142eeb2125511db3c13f6dc932d899c512690378e \ + --hash=sha256:3f73096f27b8c28be53ffb6604f7b570fbbb82f273c6febe5f58119009b59898 \ + --hash=sha256:522d1153d93f2d48aa0bd9fb778d8d4500be2e4dcf86c3150768f0e3adbbc4ef \ + --hash=sha256:523d2928fb7377bbdbc9af9c0b14ad73e6eaf226349f105733bdae27efd15b5a \ + --hash=sha256:5848309d4fc5c02150a45e8f8d2227e5bfda386a508bbd3160fed7c633c5a2fa \ + --hash=sha256:6781f86e6d54a110980a76e761eb54590630fd2af2a17d7edf02a079d2646c1d \ + --hash=sha256:6fd27921ebf3aaa945fa25d790f1f2046204f24dba4946f82f5f0a442577c3e9 \ + --hash=sha256:70d581862863f6bf9e175e85c9d70c2d7155f53fb04dcdb2f73cf288ca559a53 \ + --hash=sha256:81867c23b0dc66c8366f351d00923f2bc5902820a24c2534dfd7bf01a5879963 \ + --hash=sha256:81db29edadcbb740cd2716c95a297893a546ed89db1bfe9110168732d7f0afdd \ + --hash=sha256:86bd12624068cea60860a0759af5e2c3adc89c12aef6f71cf12f577e28deefe3 \ + --hash=sha256:9c184d8f9f7a76e1ced99855ccf390ffdd0ec3765e5cbf7b9cada600accc0a1e \ + --hash=sha256:acc789e8c29c13555e43fe4bf9fcd15a65512c9645e97bbaa5602e3201252b02 \ + --hash=sha256:afaa740206b7660d4cc3b8f120426c85761f51379af7a5b05451f624ad12b0af \ + --hash=sha256:b5f5fa323f878bb16eae68ea1ba7f6c0419d4695d0248bed4b18f51d7ce5ab85 \ + --hash=sha256:bd89e0e2c67bf4ac3aca2a19702b1a37269fb1923827f68324ac2e7afd6e3406 \ + --hash=sha256:c212de743283ec0735db24ec6ad913758df3af1b7217550ff270038062afd6ae \ + --hash=sha256:ca553f524293a0bdea05e7f44c3e685e4b7b022cb37d87bc4a3efa0f86587a8d \ + --hash=sha256:cab67065a3db92f636128d3157cc5424a145f82d96fb47159c539132833a6d36 \ + --hash=sha256:d3b3b3eedfdbf6b02898216e85aa6baf50207f4378a2a6803d6d47650cd37031 \ + --hash=sha256:d9f4a5a72f40256b686d31c5c0b1fde503172307beb12c1568296e76118e402c \ + --hash=sha256:df5067d87aaa111ed5d050e1ee853ba284969497f91806efd42425f5348f1c06 \ + --hash=sha256:e2587644812c6138f05b8a41594a8337c6790e3baf9a01915e52438c13fc6bef \ + --hash=sha256:e27fd877662db94f897f3fd532ef211ca4901eb1a70ba456f15c0866a985464a \ + --hash=sha256:e427ebbdd223c72e06ba94c004bb04e996c84dec8a0fa84e837556ae145c439e \ + --hash=sha256:e583ad4309c203ef75a09d43434cf9c2b4fa247997ecb0dcad769982c39411c7 \ + --hash=sha256:e760b2bc8ece9200804f0c2b64d10147ecaf18455a2a90827fbec4c9d84f3ad5 \ + --hash=sha256:ea9a9cc8bcc70e18023f30fa2f53d11ae069572a162791224e60cd65df55fb69 \ + --hash=sha256:ecb3f17dce4803c1099bd21742cd126b59817a4e76a6544d31d2cca6e30dbffd \ + --hash=sha256:ed794e3b3de42486d30444fb60b5561e724ee8a2d1b17b0c2e0f81e3ddaf7a87 \ + --hash=sha256:ee885d347279e38226d0a437b6a932f207f691c502ee565aba27a7022f1285df \ + --hash=sha256:fd5e7bc5f24f7e3d490698f7b854659a9851da2187414617cd5ed360af7efd63 \ + --hash=sha256:fe45f6870f7588ac7b2763ff1ce98cce59369717afe70cc353ec5218bc854bcc +zope.interface==5.1.0 \ + --hash=sha256:0103cba5ed09f27d2e3de7e48bb320338592e2fabc5ce1432cf33808eb2dfd8b \ + --hash=sha256:14415d6979356629f1c386c8c4249b4d0082f2ea7f75871ebad2e29584bd16c5 \ + --hash=sha256:1ae4693ccee94c6e0c88a4568fb3b34af8871c60f5ba30cf9f94977ed0e53ddd \ + --hash=sha256:1b87ed2dc05cb835138f6a6e3595593fea3564d712cb2eb2de963a41fd35758c \ + --hash=sha256:269b27f60bcf45438e8683269f8ecd1235fa13e5411de93dae3b9ee4fe7f7bc7 \ + --hash=sha256:27d287e61639d692563d9dab76bafe071fbeb26818dd6a32a0022f3f7ca884b5 \ + --hash=sha256:39106649c3082972106f930766ae23d1464a73b7d30b3698c986f74bf1256a34 \ + --hash=sha256:40e4c42bd27ed3c11b2c983fecfb03356fae1209de10686d03c02c8696a1d90e \ + --hash=sha256:461d4339b3b8f3335d7e2c90ce335eb275488c587b61aca4b305196dde2ff086 \ + --hash=sha256:4f98f70328bc788c86a6a1a8a14b0ea979f81ae6015dd6c72978f1feff70ecda \ + --hash=sha256:558a20a0845d1a5dc6ff87cd0f63d7dac982d7c3be05d2ffb6322a87c17fa286 \ + --hash=sha256:562dccd37acec149458c1791da459f130c6cf8902c94c93b8d47c6337b9fb826 \ + --hash=sha256:5e86c66a6dea8ab6152e83b0facc856dc4d435fe0f872f01d66ce0a2131b7f1d \ + --hash=sha256:60a207efcd8c11d6bbeb7862e33418fba4e4ad79846d88d160d7231fcb42a5ee \ + --hash=sha256:645a7092b77fdbc3f68d3cc98f9d3e71510e419f54019d6e282328c0dd140dcd \ + --hash=sha256:6874367586c020705a44eecdad5d6b587c64b892e34305bb6ed87c9bbe22a5e9 \ + --hash=sha256:74bf0a4f9091131de09286f9a605db449840e313753949fe07c8d0fe7659ad1e \ + --hash=sha256:7b726194f938791a6691c7592c8b9e805fc6d1b9632a833b9c0640828cd49cbc \ + --hash=sha256:8149ded7f90154fdc1a40e0c8975df58041a6f693b8f7edcd9348484e9dc17fe \ + --hash=sha256:8cccf7057c7d19064a9e27660f5aec4e5c4001ffcf653a47531bde19b5aa2a8a \ + --hash=sha256:911714b08b63d155f9c948da2b5534b223a1a4fc50bb67139ab68b277c938578 \ + --hash=sha256:a5f8f85986197d1dd6444763c4a15c991bfed86d835a1f6f7d476f7198d5f56a \ + --hash=sha256:a744132d0abaa854d1aad50ba9bc64e79c6f835b3e92521db4235a1991176813 \ + --hash=sha256:af2c14efc0bb0e91af63d00080ccc067866fb8cbbaca2b0438ab4105f5e0f08d \ + --hash=sha256:b054eb0a8aa712c8e9030065a59b5e6a5cf0746ecdb5f087cca5ec7685690c19 \ + --hash=sha256:b0becb75418f8a130e9d465e718316cd17c7a8acce6fe8fe07adc72762bee425 \ + --hash=sha256:b1d2ed1cbda2ae107283befd9284e650d840f8f7568cb9060b5466d25dc48975 \ + --hash=sha256:ba4261c8ad00b49d48bbb3b5af388bb7576edfc0ca50a49c11dcb77caa1d897e \ + --hash=sha256:d1fe9d7d09bb07228650903d6a9dc48ea649e3b8c69b1d263419cc722b3938e8 \ + --hash=sha256:d7804f6a71fc2dda888ef2de266727ec2f3915373d5a785ed4ddc603bbc91e08 \ + --hash=sha256:da2844fba024dd58eaa712561da47dcd1e7ad544a257482392472eae1c86d5e5 \ + --hash=sha256:dcefc97d1daf8d55199420e9162ab584ed0893a109f45e438b9794ced44c9fd0 \ + --hash=sha256:dd98c436a1fc56f48c70882cc243df89ad036210d871c7427dc164b31500dc11 \ + --hash=sha256:e74671e43ed4569fbd7989e5eecc7d06dc134b571872ab1d5a88f4a123814e9f \ + --hash=sha256:eb9b92f456ff3ec746cd4935b73c1117538d6124b8617bc0fe6fda0b3816e345 \ + --hash=sha256:ebb4e637a1fb861c34e48a00d03cffa9234f42bef923aec44e5625ffb9a8e8f9 \ + --hash=sha256:ef739fe89e7f43fb6494a43b1878a36273e5924869ba1d866f752c5812ae8d58 \ + --hash=sha256:f40db0e02a8157d2b90857c24d89b6310f9b6c3642369852cdc3b5ac49b92afc \ + --hash=sha256:f68bf937f113b88c866d090fea0bc52a098695173fc613b055a17ff0cf9683b6 \ + --hash=sha256:fb55c182a3f7b84c1a2d6de5fa7b1a05d4660d866b91dbf8d74549c57a1499e8 +zope.proxy==4.3.5 \ + --hash=sha256:00573dfa755d0703ab84bb23cb6ecf97bb683c34b340d4df76651f97b0bab068 \ + --hash=sha256:092049280f2848d2ba1b57b71fe04881762a220a97b65288bcb0968bb199ec30 \ + --hash=sha256:0cbd27b4d3718b5ec74fc65ffa53c78d34c65c6fd9411b8352d2a4f855220cf1 \ + --hash=sha256:17fc7e16d0c81f833a138818a30f366696653d521febc8e892858041c4d88785 \ + --hash=sha256:19577dfeb70e8a67249ba92c8ad20589a1a2d86a8d693647fa8385408a4c17b0 \ + --hash=sha256:207aa914576b1181597a1516e1b90599dc690c095343ae281b0772e44945e6a4 \ + --hash=sha256:219a7db5ed53e523eb4a4769f13105118b6d5b04ed169a283c9775af221e231f \ + --hash=sha256:2b50ea79849e46b5f4f2b0247a3687505d32d161eeb16a75f6f7e6cd81936e43 \ + --hash=sha256:5903d38362b6c716e66bbe470f190579c530a5baf03dbc8500e5c2357aa569a5 \ + --hash=sha256:5c24903675e271bd688c6e9e7df5775ac6b168feb87dbe0e4bcc90805f21b28f \ + --hash=sha256:5ef6bc5ed98139e084f4e91100f2b098a0cd3493d4e76f9d6b3f7b95d7ad0f06 \ + --hash=sha256:61b55ae3c23a126a788b33ffb18f37d6668e79a05e756588d9e4d4be7246ab1c \ + --hash=sha256:63ddb992931a5e616c87d3d89f5a58db086e617548005c7f9059fac68c03a5cc \ + --hash=sha256:6943da9c09870490dcfd50c4909c0cc19f434fa6948f61282dc9cb07bcf08160 \ + --hash=sha256:6ad40f85c1207803d581d5d75e9ea25327cd524925699a83dfc03bf8e4ba72b7 \ + --hash=sha256:6b44433a79bdd7af0e3337bd7bbcf53dd1f9b0fa66bf21bcb756060ce32a96c1 \ + --hash=sha256:6bbaa245015d933a4172395baad7874373f162955d73612f0b66b6c2c33b6366 \ + --hash=sha256:7007227f4ea85b40a2f5e5a244479f6a6dfcf906db9b55e812a814a8f0e2c28d \ + --hash=sha256:74884a0aec1f1609190ec8b34b5d58fb3b5353cf22b96161e13e0e835f13518f \ + --hash=sha256:7d25fe5571ddb16369054f54cdd883f23de9941476d97f2b92eb6d7d83afe22d \ + --hash=sha256:7e162bdc5e3baad26b2262240be7d2bab36991d85a6a556e48b9dfb402370261 \ + --hash=sha256:814d62678dc3a30f4aa081982d830b7c342cf230ffc9d030b020cb154eeebf9e \ + --hash=sha256:8878a34c5313ee52e20aa50b03138af8d472bae465710fb954d133a9bfd3c38d \ + --hash=sha256:a66a0d94e5b081d5d695e66d6667e91e74d79e273eee95c1747717ba9cb70792 \ + --hash=sha256:a69f5cbf4addcfdf03dda564a671040127a6b7c34cf9fe4973582e68441b63fa \ + --hash=sha256:b00f9f0c334d07709d3f73a7cb8ae63c6ca1a90c790a63b5e7effa666ef96021 \ + --hash=sha256:b6ed71e4a7b4690447b626f499d978aa13197a0e592950e5d7020308f6054698 \ + --hash=sha256:bdf5041e5851526e885af579d2f455348dba68d74f14a32781933569a327fddf \ + --hash=sha256:be034360dd34e62608419f86e799c97d389c10a0e677a25f236a971b2f40dac9 \ + --hash=sha256:cc8f590a5eed30b314ae6b0232d925519ade433f663de79cc3783e4b10d662ba \ + --hash=sha256:cd7a318a15fe6cc4584bf3c4426f092ed08c0fd012cf2a9173114234fe193e11 \ + --hash=sha256:cf19b5f63a59c20306e034e691402b02055c8f4e38bf6792c23cad489162a642 \ + --hash=sha256:cfc781ce442ec407c841e9aa51d0e1024f72b6ec34caa8fdb6ef9576d549acf2 \ + --hash=sha256:dea9f6f8633571e18bc20cad83603072e697103a567f4b0738d52dd0211b4527 \ + --hash=sha256:e4a86a1d5eb2cce83c5972b3930c7c1eac81ab3508464345e2b8e54f119d5505 \ + --hash=sha256:e7106374d4a74ed9ff00c46cc00f0a9f06a0775f8868e423f85d4464d2333679 \ + --hash=sha256:e98a8a585b5668aa9e34d10f7785abf9545fe72663b4bfc16c99a115185ae6a5 \ + --hash=sha256:f64840e68483316eb58d82c376ad3585ca995e69e33b230436de0cdddf7363f9 \ + --hash=sha256:f8f4b0a9e6683e43889852130595c8854d8ae237f2324a053cdd884de936aa9b \ + --hash=sha256:fc45a53219ed30a7f670a6d8c98527af0020e6fd4ee4c0a8fb59f147f06d816c + +# Contains the requirements for the letsencrypt package. +# +# Since the letsencrypt package depends on certbot and using pip with hashes +# requires that all installed packages have hashes listed, this allows +# dependency-requirements.txt to be used without requiring a hash for a +# (potentially unreleased) Certbot package. + +letsencrypt==0.7.0 \ + --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ + --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 + +certbot==1.8.0 \ + --hash=sha256:4bde86c53e30dc5bc0e78a0862045b053971703af727ac20c6a7da06596c7549 \ + --hash=sha256:4837c516af6543ccd10d70f1498a2113bbdf9ef9a05d3a18b1558b291a2953e4 +acme==1.8.0 \ + --hash=sha256:465033830a75f98042236f50f751f6e316735473ccb4edec0c718263f6c9ba8b \ + --hash=sha256:ad8d067d14258d73ad2643439d9365913362308c04e66cc3010e39c868c5002d +certbot-apache==1.8.0 \ + --hash=sha256:8c9d981803e1156725fcfcf228afcb754b245c9d506e5b9f4fca948d6ae89aef \ + --hash=sha256:a93c3a7ad929fe0ba5e0868e29ee2d0fe10aea2d4c638a902c4613a5c12c59b6 +certbot-nginx==1.8.0 \ + --hash=sha256:e98e883b5ea7b29dd2e6a8ff286c7550a2d7af2fc859f47067303e510ad4fb52 \ + --hash=sha256:fdb96c74fe42d90bbaf11a00314444ac5544ba87292a1b8b1d707f7561a3eacc + +UNLIKELY_EOF + # ------------------------------------------------------------------------- + cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py" +#!/usr/bin/env python +"""A small script that can act as a trust root for installing pip >=8 +Embed this in your project, and your VCS checkout is all you have to trust. In +a post-peep era, this lets you claw your way to a hash-checking version of pip, +with which you can install the rest of your dependencies safely. All it assumes +is Python 2.6 or better and *some* version of pip already installed. If +anything goes wrong, it will exit with a non-zero status code. +""" +# This is here so embedded copies are MIT-compliant: +# Copyright (c) 2016 Erik Rose +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +from __future__ import print_function +from distutils.version import StrictVersion +from hashlib import sha256 +from os import environ +from os.path import join +from shutil import rmtree +try: + from subprocess import check_output +except ImportError: + from subprocess import CalledProcessError, PIPE, Popen + + def check_output(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be ' + 'overridden.') + process = Popen(stdout=PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise CalledProcessError(retcode, cmd) + return output +import sys +from tempfile import mkdtemp +try: + from urllib2 import build_opener, HTTPHandler, HTTPSHandler +except ImportError: + from urllib.request import build_opener, HTTPHandler, HTTPSHandler +try: + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse # 3.4 + + +__version__ = 1, 5, 1 +PIP_VERSION = '9.0.1' +DEFAULT_INDEX_BASE = 'https://pypi.python.org' + + +# wheel has a conditional dependency on argparse: +maybe_argparse = ( + [('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/' + 'argparse-1.4.0.tar.gz', + '62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')] + if sys.version_info < (2, 7, 0) else []) + + +PACKAGES = maybe_argparse + [ + # Pip has no dependencies, as it vendors everything: + ('11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/' + 'pip-{0}.tar.gz'.format(PIP_VERSION), + '09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'), + # This version of setuptools has only optional dependencies: + ('37/1b/b25507861991beeade31473868463dad0e58b1978c209de27384ae541b0b/' + 'setuptools-40.6.3.zip', + '3b474dad69c49f0d2d86696b68105f3a6f195f7ab655af12ef9a9c326d2b08f8'), + ('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/' + 'wheel-0.29.0.tar.gz', + '1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648') +] + + +class HashError(Exception): + def __str__(self): + url, path, actual, expected = self.args + return ('{url} did not match the expected hash {expected}. Instead, ' + 'it was {actual}. The file (left at {path}) may have been ' + 'tampered with.'.format(**locals())) + + +def hashed_download(url, temp, digest): + """Download ``url`` to ``temp``, make sure it has the SHA-256 ``digest``, + and return its path.""" + # Based on pip 1.4.1's URLOpener but with cert verification removed. Python + # >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert + # authenticity has only privacy (not arbitrary code execution) + # implications, since we're checking hashes. + def opener(using_https=True): + opener = build_opener(HTTPSHandler()) + if using_https: + # Strip out HTTPHandler to prevent MITM spoof: + for handler in opener.handlers: + if isinstance(handler, HTTPHandler): + opener.handlers.remove(handler) + return opener + + def read_chunks(response, chunk_size): + while True: + chunk = response.read(chunk_size) + if not chunk: + break + yield chunk + + parsed_url = urlparse(url) + response = opener(using_https=parsed_url.scheme == 'https').open(url) + path = join(temp, parsed_url.path.split('/')[-1]) + actual_hash = sha256() + with open(path, 'wb') as file: + for chunk in read_chunks(response, 4096): + file.write(chunk) + actual_hash.update(chunk) + + actual_digest = actual_hash.hexdigest() + if actual_digest != digest: + raise HashError(url, path, actual_digest, digest) + return path + + +def get_index_base(): + """Return the URL to the dir containing the "packages" folder. + Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the + end if it's there; that is likely to give us the right dir. + """ + env_var = environ.get('PIP_INDEX_URL', '').rstrip('/') + if env_var: + SIMPLE = '/simple' + if env_var.endswith(SIMPLE): + return env_var[:-len(SIMPLE)] + else: + return env_var + else: + return DEFAULT_INDEX_BASE + + +def main(): + python = sys.executable or 'python' + pip_version = StrictVersion(check_output([python, '-m', 'pip', '--version']) + .decode('utf-8').split()[1]) + has_pip_cache = pip_version >= StrictVersion('6.0') + index_base = get_index_base() + temp = mkdtemp(prefix='pipstrap-') + try: + downloads = [hashed_download(index_base + '/packages/' + path, + temp, + digest) + for path, digest in PACKAGES] + # Calling pip as a module is the preferred way to avoid problems about pip self-upgrade. + command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U'] + # Disable cache since it is not used and it otherwise sometimes throws permission warnings: + command.extend(['--no-cache-dir'] if has_pip_cache else []) + command.extend(downloads) + check_output(command) + except HashError as exc: + print(exc) + except Exception: + rmtree(temp) + raise + else: + rmtree(temp) + return 0 + return 1 + + +if __name__ == '__main__': + sys.exit(main()) + +UNLIKELY_EOF + # ------------------------------------------------------------------------- + # Set PATH so pipstrap upgrades the right (v)env: + PATH="$VENV_BIN:$PATH" "$VENV_BIN/python" "$TEMP_DIR/pipstrap.py" + set +e + if [ "$VERBOSE" = 1 ]; then + "$VENV_BIN/pip" install --disable-pip-version-check --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" + else + PIP_OUT=`"$VENV_BIN/pip" install --disable-pip-version-check --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1` + fi + PIP_STATUS=$? + set -e + if [ "$PIP_STATUS" != 0 ]; then + # Report error. (Otherwise, be quiet.) + error "Had a problem while installing Python packages." + if [ "$VERBOSE" != 1 ]; then + error + error "pip prints the following errors: " + error "=====================================================" + error "$PIP_OUT" + error "=====================================================" + error + error "Certbot has problem setting up the virtual environment." + + if `echo $PIP_OUT | grep -q Killed` || `echo $PIP_OUT | grep -q "allocate memory"` ; then + error + error "Based on your pip output, the problem can likely be fixed by " + error "increasing the available memory." + else + error + error "We were not be able to guess the right solution from your pip " + error "output." + fi + + error + error "Consult https://certbot.eff.org/docs/install.html#problems-with-python-virtual-environment" + error "for possible solutions." + error "You may also find some support resources at https://certbot.eff.org/support/ ." + fi + rm -rf "$VENV_PATH" + exit 1 + fi + + if [ -d "$OLD_VENV_PATH" -a ! -L "$OLD_VENV_PATH" ]; then + rm -rf "$OLD_VENV_PATH" + ln -s "$VENV_PATH" "$OLD_VENV_PATH" + fi + + say "Installation succeeded." + fi + + # If you're modifying any of the code after this point in this current `if` block, you + # may need to update the "$DEPRECATED_OS" = 1 case at the beginning of phase 2 as well. + + if [ "$INSTALL_ONLY" = 1 ]; then + say "Certbot is installed." + exit 0 + fi + + "$VENV_BIN/letsencrypt" "$@" + +else + # Phase 1: Upgrade certbot-auto if necessary, then self-invoke. + # + # Each phase checks the version of only the thing it is responsible for + # upgrading. Phase 1 checks the version of the latest release of + # certbot-auto (which is always the same as that of the certbot + # package). Phase 2 checks the version of the locally installed certbot. + export PHASE_1_VERSION="$LE_AUTO_VERSION" + + if [ ! -f "$VENV_BIN/letsencrypt" ]; then + if ! OldVenvExists; then + if [ "$HELP" = 1 ]; then + echo "$USAGE" + exit 0 + fi + # If it looks like we've never bootstrapped before, bootstrap: + Bootstrap + fi + fi + if [ "$OS_PACKAGES_ONLY" = 1 ]; then + say "OS packages installed." + exit 0 + fi + + DeterminePythonVersion "NOCRASH" + # Don't warn about file permissions if the user disabled the check or we + # can't find an up-to-date Python. + if [ "$PYVER" -ge "$MIN_PYVER" -a "$NO_PERMISSIONS_CHECK" != 1 ]; then + # If the script fails for some reason, don't break certbot-auto. + set +e + # Suppress unexpected error output. + CHECK_PERM_OUT=$(CheckPathPermissions "$LE_PYTHON" "$0" 2>/dev/null) + CHECK_PERM_STATUS="$?" + set -e + # Only print output if the script ran successfully and it actually produced + # output. The latter check resolves + # https://github.com/certbot/certbot/issues/7012. + if [ "$CHECK_PERM_STATUS" = 0 -a -n "$CHECK_PERM_OUT" ]; then + error "$CHECK_PERM_OUT" + fi + fi + + if [ "$NO_SELF_UPGRADE" != 1 ]; then + TEMP_DIR=$(TempDir) + trap 'rm -rf "$TEMP_DIR"' EXIT + # --------------------------------------------------------------------------- + cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py" +"""Do downloading and JSON parsing without additional dependencies. :: + + # Print latest released version of LE to stdout: + python fetch.py --latest-version + + # Download letsencrypt-auto script from git tag v1.2.3 into the folder I'm + # in, and make sure its signature verifies: + python fetch.py --le-auto-script v1.2.3 + +On failure, return non-zero. + +""" + +from __future__ import print_function, unicode_literals + +from distutils.version import LooseVersion +from json import loads +from os import devnull, environ +from os.path import dirname, join +import re +import ssl +from subprocess import check_call, CalledProcessError +from sys import argv, exit +try: + from urllib2 import build_opener, HTTPHandler, HTTPSHandler + from urllib2 import HTTPError, URLError +except ImportError: + from urllib.request import build_opener, HTTPHandler, HTTPSHandler + from urllib.error import HTTPError, URLError + +PUBLIC_KEY = environ.get('LE_AUTO_PUBLIC_KEY', """-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6MR8W/galdxnpGqBsYbq +OzQb2eyW15YFjDDEMI0ZOzt8f504obNs920lDnpPD2/KqgsfjOgw2K7xWDJIj/18 +xUvWPk3LDkrnokNiRkA3KOx3W6fHycKL+zID7zy+xZYBuh2fLyQtWV1VGQ45iNRp +9+Zo7rH86cdfgkdnWTlNSHyTLW9NbXvyv/E12bppPcEvgCTAQXgnDVJ0/sqmeiij +n9tTFh03aM+R2V/21h8aTraAS24qiPCz6gkmYGC8yr6mglcnNoYbsLNYZ69zF1XH +cXPduCPdPdfLlzVlKK1/U7hkA28eG3BIAMh6uJYBRJTpiGgaGdPd7YekUB8S6cy+ +CQIDAQAB +-----END PUBLIC KEY----- +""") + +class ExpectedError(Exception): + """A novice-readable exception that also carries the original exception for + debugging""" + + +class HttpsGetter(object): + def __init__(self): + """Build an HTTPS opener.""" + # Based on pip 1.4.1's URLOpener + # This verifies certs on only Python >=2.7.9, and when NO_CERT_VERIFY isn't set. + if environ.get('NO_CERT_VERIFY') == '1' and hasattr(ssl, 'SSLContext'): + self._opener = build_opener(HTTPSHandler(context=cert_none_context())) + else: + self._opener = build_opener(HTTPSHandler()) + # Strip out HTTPHandler to prevent MITM spoof: + for handler in self._opener.handlers: + if isinstance(handler, HTTPHandler): + self._opener.handlers.remove(handler) + + def get(self, url): + """Return the document contents pointed to by an HTTPS URL. + + If something goes wrong (404, timeout, etc.), raise ExpectedError. + + """ + try: + # socket module docs say default timeout is None: that is, no + # timeout + return self._opener.open(url, timeout=30).read() + except (HTTPError, IOError) as exc: + raise ExpectedError("Couldn't download %s." % url, exc) + + +def write(contents, dir, filename): + """Write something to a file in a certain directory.""" + with open(join(dir, filename), 'wb') as file: + file.write(contents) + + +def latest_stable_version(get): + """Return the latest stable release of letsencrypt.""" + metadata = loads(get( + environ.get('LE_AUTO_JSON_URL', + 'https://pypi.python.org/pypi/certbot/json')).decode('UTF-8')) + # metadata['info']['version'] actually returns the latest of any kind of + # release release, contrary to https://wiki.python.org/moin/PyPIJSON. + # The regex is a sufficient regex for picking out prereleases for most + # packages, LE included. + return str(max(LooseVersion(r) for r + in metadata['releases'].keys() + if re.match('^[0-9.]+$', r))) + + +def verified_new_le_auto(get, tag, temp_dir): + """Return the path to a verified, up-to-date letsencrypt-auto script. + + If the download's signature does not verify or something else goes wrong + with the verification process, raise ExpectedError. + + """ + le_auto_dir = environ.get( + 'LE_AUTO_DIR_TEMPLATE', + 'https://raw.githubusercontent.com/certbot/certbot/%s/' + 'letsencrypt-auto-source/') % tag + write(get(le_auto_dir + 'letsencrypt-auto'), temp_dir, 'letsencrypt-auto') + write(get(le_auto_dir + 'letsencrypt-auto.sig'), temp_dir, 'letsencrypt-auto.sig') + write(PUBLIC_KEY.encode('UTF-8'), temp_dir, 'public_key.pem') + try: + with open(devnull, 'w') as dev_null: + check_call(['openssl', 'dgst', '-sha256', '-verify', + join(temp_dir, 'public_key.pem'), + '-signature', + join(temp_dir, 'letsencrypt-auto.sig'), + join(temp_dir, 'letsencrypt-auto')], + stdout=dev_null, + stderr=dev_null) + except CalledProcessError as exc: + raise ExpectedError("Couldn't verify signature of downloaded " + "certbot-auto.", exc) + + +def cert_none_context(): + """Create a SSLContext object to not check hostname.""" + # PROTOCOL_TLS isn't available before 2.7.13 but this code is for 2.7.9+, so use this. + context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + context.verify_mode = ssl.CERT_NONE + return context + + +def main(): + get = HttpsGetter().get + flag = argv[1] + try: + if flag == '--latest-version': + print(latest_stable_version(get)) + elif flag == '--le-auto-script': + tag = argv[2] + verified_new_le_auto(get, tag, dirname(argv[0])) + except ExpectedError as exc: + print(exc.args[0], exc.args[1]) + return 1 + else: + return 0 + + +if __name__ == '__main__': + exit(main()) + +UNLIKELY_EOF + # --------------------------------------------------------------------------- + if [ "$PYVER" -lt "$MIN_PYVER" ]; then + error "WARNING: couldn't find Python $MIN_PYTHON_VERSION+ to check for updates." + elif ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then + error "WARNING: unable to check for updates." + fi + + # If for any reason REMOTE_VERSION is not set, let's assume certbot-auto is up-to-date, + # and do not go into the self-upgrading process. + if [ -n "$REMOTE_VERSION" ]; then + LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"` + + if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then + say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION" + elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then + say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." + + # Now we drop into Python so we don't have to install even more + # dependencies (curl, etc.), for better flow control, and for the option of + # future Windows compatibility. + "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" + + # Install new copy of certbot-auto. + # TODO: Deal with quotes in pathnames. + say "Replacing certbot-auto..." + # Clone permissions with cp. chmod and chown don't have a --reference + # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: + cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" + cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" + # Using mv rather than cp leaves the old file descriptor pointing to the + # original copy so the shell can continue to read it unmolested. mv across + # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the + # cp is unlikely to fail if the rm doesn't. + mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" + fi # A newer version is available. + fi + fi # Self-upgrading is allowed. + + RerunWithArgs --le-auto-phase2 "$@" +fi diff --git a/files/consul/configs/consul.hcl b/files/consul/configs/consul.hcl new file mode 100644 index 0000000..45cdf8c --- /dev/null +++ b/files/consul/configs/consul.hcl @@ -0,0 +1,7 @@ +datacenter = "MSI-DC" +data_dir = "/opt/consul" +encrypt = "eRhnp22+c0bkV0wPolk6Mw==" +retry_join = ["consul-admin"] +performance { + raft_multiplier = 1 +} diff --git a/files/consul/configs/consul.service b/files/consul/configs/consul.service new file mode 100644 index 0000000..b75b4be --- /dev/null +++ b/files/consul/configs/consul.service @@ -0,0 +1,23 @@ +[Unit] +Description=Consul Service Discovery Agent +Documentation=https://www.consul.io/ +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=consul +Group=consul +ExecStart=/usr/local/bin/consul agent -server -ui \ + -data-dir=/opt/consul \ + -node=consul-%H \ + -config-dir=/etc/consul.d + +ExecReload=/bin/kill -HUP $MAINPID +KillSignal=SIGINT +TimeoutStopSec=5 +Restart=on-failure +SyslogIdentifier=consul + +[Install] +WantedBy=multi-user.target diff --git a/files/consul/configs/server.hcl b/files/consul/configs/server.hcl new file mode 100644 index 0000000..00d7216 --- /dev/null +++ b/files/consul/configs/server.hcl @@ -0,0 +1,4 @@ +server = true +bootstrap_expect = 2 +bind_addr = "10.11.10.101" +ui = true diff --git a/files/consul/configs/service-apache.hcl b/files/consul/configs/service-apache.hcl new file mode 100644 index 0000000..c9690bc --- /dev/null +++ b/files/consul/configs/service-apache.hcl @@ -0,0 +1,10 @@ +service { + name = "apache" + port = 443 + tags = [ "srv1", "pedimedic", "webmail", "git" ] + check { + http = "https://srv1.maruntiel.com" + interval = "5s" + tlsSkipVerify = true + } +} diff --git a/files/consul/configs/service-mysql.hcl b/files/consul/configs/service-mysql.hcl new file mode 100644 index 0000000..fb71b41 --- /dev/null +++ b/files/consul/configs/service-mysql.hcl @@ -0,0 +1,9 @@ +service { + name = "mariadb" + port = 3306 + tags = [ "db" ] + check { + tcp = "localhost:3306" + interval = "5s" + } +} diff --git a/files/consul/configs/service-ssh.hcl b/files/consul/configs/service-ssh.hcl new file mode 100644 index 0000000..edd4e29 --- /dev/null +++ b/files/consul/configs/service-ssh.hcl @@ -0,0 +1,8 @@ +service { + name = "SSHD" + port = 22 + check { + tcp = "localhost:22" + interval = "5s" + } +} diff --git a/files/consul/consul-tag b/files/consul/consul-tag new file mode 100644 index 0000000..8d82074 --- /dev/null +++ b/files/consul/consul-tag @@ -0,0 +1,70 @@ +#!/usr/bin/python3 + +import os +import sys +import requests + +CONSUL_API = 'http://localhost:8500' + + +def get_service(sess, service_id): + r = sess.get(CONSUL_API + '/v1/agent/services', timeout=2) + r.raise_for_status() + services = r.json() + + for svc in services.values(): + if svc['ID'] == service_id: + return svc + + return None + + +def change_service_tags(service, tags_to_add, tags_to_remove): + with requests.Session() as sess: + sess.headers = {'X-Consul-Token': os.getenv('CONSUL_HTTP_TOKEN')} + + svc = get_service(sess, service) + if svc: + new_tags = (set(svc.get('Tags', [])) | tags_to_add) - tags_to_remove + new_svc = { + 'ID': svc['ID'], + 'Name': svc['Service'], + 'Address': svc.get('Address', ''), + 'Port': svc.get('Port', 0), + 'Meta': svc.get('Meta', {}), + 'Tags': sorted(list(new_tags)), + 'EnableTagOverride': svc.get('EnableTagOverride', False), + } + for k, v in new_svc.items(): + print('{} = {}'.format(k, v)) + r = sess.put(CONSUL_API + '/v1/agent/service/register', json=new_svc, timeout=2) + r.raise_for_status() + + +def main(argv): + if len(argv) < 3: + print("Usage: consul-tag service +tag -tag...") + return 1 + + service = argv[1] + tags_to_add = set() + tags_to_remove = set() + for tag in argv[2:]: + if tag.startswith('-'): + tags_to_remove.add(tag[1:]) + elif tag.startswith('+'): + tags_to_add.add(tag[1:]) + else: + tags_to_add.add(tag) + + try: + change_service_tags(service, tags_to_add, tags_to_remove) + except Exception as exc: + print("Error: {}".format(exc)) + return 2 + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/files/consul/consul.1.7.4 b/files/consul/consul.1.7.4 new file mode 100644 index 0000000..230b742 Binary files /dev/null and b/files/consul/consul.1.7.4 differ diff --git a/files/gitea-1.12.5 b/files/gitea-1.12.5 new file mode 100644 index 0000000..79b4a98 Binary files /dev/null and b/files/gitea-1.12.5 differ diff --git a/files/unison-2.48.3 b/files/unison-2.48.3 new file mode 100644 index 0000000..2599224 Binary files /dev/null and b/files/unison-2.48.3 differ diff --git a/files/unison-fsmonitor b/files/unison-fsmonitor new file mode 100644 index 0000000..1c4baab --- /dev/null +++ b/files/unison-fsmonitor @@ -0,0 +1,916 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Syndicator/unison-fsmonitor at master · TentativeConvert/Syndicator · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + Permalink + + + + + +
+ + +
+ + Branch: + master + + + + +
+ + + +
+
+
+ +
+ + Find file + + + Copy path + +
+
+ + +
+ + Find file + + + Copy path + +
+
+ + + + +
+ Fetching contributors… +
+ +
+ + Cannot retrieve contributors at this time +
+
+ + + + + +
+ +
+
+ + 834 KB +
+ +
+ + + + +
+ + + + +
+ +
+
+
+ + + + + +
+
+ View raw +
+
+ +
+ + + +
+ + +
+ + +
+
+ + + +
+
+ +
+
+ + +
+ + + + + + +
+ + + You can’t perform that action at this time. +
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/group_vars/all/ansible.yml b/group_vars/all/ansible.yml new file mode 100644 index 0000000..f1a3ed4 --- /dev/null +++ b/group_vars/all/ansible.yml @@ -0,0 +1 @@ +ansible_python_interpreter: /usr/bin/python3 diff --git a/group_vars/all/consul.yml b/group_vars/all/consul.yml new file mode 100644 index 0000000..aca7963 --- /dev/null +++ b/group_vars/all/consul.yml @@ -0,0 +1,22 @@ +--- + +consul_server: "{{ ansible_hostname in consul_servers }}" + +consul_acl_datacenter: msiserv +consul_acl_master_token: "229369d9-6345-6c57-72b3-166f3c2a74a5" +consul_acl_agent_token: "ad92623d-fcab-85c2-55ae-3fbd36da6f83" +consul_acl_token: "168d2a19-0a8d-b197-03dc-0e2b0c324421" +consul_acl_replication_token: "377fdfae-02ac-7a43-f9d4-c5a9b1c2bdeb" + +# Bootstrap only: +#consul_bootstrap_expect: 2 +#consul_encrypt_key: "eUQzZHtGbDlNmMuBr1UM2Q==" + +consul_servers: + - eu.srv + - us.srv + - admin.srv + +consul_services: yes + +consul_dns_forwarders: "{{ network_fallback_resolvers }}" diff --git a/group_vars/all/datacenter.yml b/group_vars/all/datacenter.yml new file mode 100644 index 0000000..759d5f3 --- /dev/null +++ b/group_vars/all/datacenter.yml @@ -0,0 +1,10 @@ +datacenter_global_networks: + - 192.168.255.0/24 + - 10.11.0.0/16 + +datacenter_id: + - msiserv + +datacenter_public_networks: + - 62.171.160.169/32 + - 207.244.234.58/32 diff --git a/group_vars/all/firewall.yml b/group_vars/all/firewall.yml new file mode 100644 index 0000000..2cdf706 --- /dev/null +++ b/group_vars/all/firewall.yml @@ -0,0 +1,30 @@ +--- + +firewall_ssh_acl: + - 0.0.0.0/0 # allow SSH from everywhere + + +firewall_influx_acl: + - 10.11.0.0/16 # allow influx from ip(s) + - 192.168.255.0/24 # allow influx from ip(s) + + +firewall_mariadb_acl: + - 10.11.0.0/16 # allow mariadb from ip(s) + - 192.168.255.0/24 # allow mariadb from ip(s) + +firewall_ssh_acl_extra: "{{ datacenter_global_networks + datacenter_public_networks }}" + + +firewall_influx_acl_extra: "{{ datacenter_global_networks + datacenter_public_networks }}" + + +firewall_mariadb_acl_extra: "{{ datacenter_global_networks + datacenter_public_networks }}" + + +firewall_monitoring_ips: + - 10.11.11.200 + - 10.11.12.150 + +# TODO: Needs an inventory of all external services. +firewall_output_default_drop: no diff --git a/group_vars/all/network.yml b/group_vars/all/network.yml new file mode 100644 index 0000000..473bc51 --- /dev/null +++ b/group_vars/all/network.yml @@ -0,0 +1,8 @@ +--- + +network_default_gateway: "{{ ansible_default_ipv4.gateway }}" + +network_nameservers: + - 1.1.1.1 + +network_bind_listen: "{{ network_private_ip }}" diff --git a/group_vars/all/postfix.yml b/group_vars/all/postfix.yml new file mode 100644 index 0000000..039984b --- /dev/null +++ b/group_vars/all/postfix.yml @@ -0,0 +1,5 @@ +postfix_mynetworks: "{{ datacenter_global_networks + datacenter_public_networks + datacenter_public_ipv6_networks if postfix_relay else [] }}" + +postfix_dkim_domains: + maruntiel.net: + selector: 201903 diff --git a/group_vars/eu/datacenter.yml b/group_vars/eu/datacenter.yml new file mode 100644 index 0000000..4d7456a --- /dev/null +++ b/group_vars/eu/datacenter.yml @@ -0,0 +1,11 @@ +--- + +datacenter_id: msiserv +datacenter_name: EU-Germany +datacenter_full_name: Contabo +datacenter_local_networks: + - 192.168.255.0/24 + - 10.11.201.0/24 +datacenter_public_networks: + - 62.171.160.169/32 + diff --git a/group_vars/eu/network.yml b/group_vars/eu/network.yml new file mode 100644 index 0000000..aea718f --- /dev/null +++ b/group_vars/eu/network.yml @@ -0,0 +1,11 @@ +--- +network_default_gateway: 62.171.160.1 +network_nameservers: + - 213.136.95.10 + - 213.136.95.11 +network_fallback_resolvers: + - 10.11.201.101 +network_private_ip: + - 10.11.201.101 + - 10.11.202.101 + - 10.11.11.200 diff --git a/group_vars/ro/datacenter.yml b/group_vars/ro/datacenter.yml new file mode 100644 index 0000000..5ef8cac --- /dev/null +++ b/group_vars/ro/datacenter.yml @@ -0,0 +1,9 @@ +--- + +datacenter_id: msiserv +datacenter_name: EU-Romania +datacenter_full_name: Maruntiel +datacenter_local_networks: + - 10.11.11.0/24 + - 10.11.12.0/24 + diff --git a/group_vars/ro/network.yml b/group_vars/ro/network.yml new file mode 100644 index 0000000..838a68f --- /dev/null +++ b/group_vars/ro/network.yml @@ -0,0 +1,10 @@ +--- +network_default_gateway: 10.11.12.1 +network_nameservers: + - 1.1.1.2 + - 8.8.4.4 +network_fallback_resolvers: + - 10.11.201.101 +network_private_ip: + - 10.11.11.200 + - 10.11.12.150 diff --git a/group_vars/us/datacenter.yml b/group_vars/us/datacenter.yml new file mode 100644 index 0000000..0ca1cdb --- /dev/null +++ b/group_vars/us/datacenter.yml @@ -0,0 +1,10 @@ +--- + +datacenter_id: msiserv +datacenter_name: US-New_York +datacenter_full_name: Contabo +datacenter_local_networks: + - 192.168.255.0/24 + - 10.11.202.0/24 +datacenter_public_networks: + - 207.244.234.58/32 diff --git a/group_vars/us/network.yml b/group_vars/us/network.yml new file mode 100644 index 0000000..e6f2386 --- /dev/null +++ b/group_vars/us/network.yml @@ -0,0 +1,7 @@ +--- +network_default_gateway: 207.244.224.1 +network_nameservers: + - 209.126.15.51 + - 209.126.15.52 +network_fallback_resolvers: + - 10.11.202.101 diff --git a/host_vars/admin.srv/ansible.yml b/host_vars/admin.srv/ansible.yml new file mode 100644 index 0000000..f1a3ed4 --- /dev/null +++ b/host_vars/admin.srv/ansible.yml @@ -0,0 +1 @@ +ansible_python_interpreter: /usr/bin/python3 diff --git a/host_vars/admin.srv/datacenter.yml b/host_vars/admin.srv/datacenter.yml new file mode 100644 index 0000000..5ef8cac --- /dev/null +++ b/host_vars/admin.srv/datacenter.yml @@ -0,0 +1,9 @@ +--- + +datacenter_id: msiserv +datacenter_name: EU-Romania +datacenter_full_name: Maruntiel +datacenter_local_networks: + - 10.11.11.0/24 + - 10.11.12.0/24 + diff --git a/host_vars/admin.srv/network.yml b/host_vars/admin.srv/network.yml new file mode 100644 index 0000000..d1554f5 --- /dev/null +++ b/host_vars/admin.srv/network.yml @@ -0,0 +1 @@ +network_private_ip: 10.11.11.200 diff --git a/host_vars/eu.srv/ansible.yml b/host_vars/eu.srv/ansible.yml new file mode 100644 index 0000000..f1a3ed4 --- /dev/null +++ b/host_vars/eu.srv/ansible.yml @@ -0,0 +1 @@ +ansible_python_interpreter: /usr/bin/python3 diff --git a/host_vars/eu.srv/datacenter.yml b/host_vars/eu.srv/datacenter.yml new file mode 100644 index 0000000..4d7456a --- /dev/null +++ b/host_vars/eu.srv/datacenter.yml @@ -0,0 +1,11 @@ +--- + +datacenter_id: msiserv +datacenter_name: EU-Germany +datacenter_full_name: Contabo +datacenter_local_networks: + - 192.168.255.0/24 + - 10.11.201.0/24 +datacenter_public_networks: + - 62.171.160.169/32 + diff --git a/host_vars/eu.srv/network.yml b/host_vars/eu.srv/network.yml new file mode 100644 index 0000000..eacb14c --- /dev/null +++ b/host_vars/eu.srv/network.yml @@ -0,0 +1,2 @@ +network_public_ip: 62.171.160.169 +network_private_ip: 10.11.201.101 diff --git a/host_vars/rpi4.srv/ansible.yml b/host_vars/rpi4.srv/ansible.yml new file mode 100644 index 0000000..f1a3ed4 --- /dev/null +++ b/host_vars/rpi4.srv/ansible.yml @@ -0,0 +1 @@ +ansible_python_interpreter: /usr/bin/python3 diff --git a/host_vars/rpi4.srv/datacenter.yml b/host_vars/rpi4.srv/datacenter.yml new file mode 100644 index 0000000..5ef8cac --- /dev/null +++ b/host_vars/rpi4.srv/datacenter.yml @@ -0,0 +1,9 @@ +--- + +datacenter_id: msiserv +datacenter_name: EU-Romania +datacenter_full_name: Maruntiel +datacenter_local_networks: + - 10.11.11.0/24 + - 10.11.12.0/24 + diff --git a/host_vars/rpi4.srv/network.yml b/host_vars/rpi4.srv/network.yml new file mode 100644 index 0000000..11c25c1 --- /dev/null +++ b/host_vars/rpi4.srv/network.yml @@ -0,0 +1 @@ +network_private_ip: 10.11.12.150 diff --git a/host_vars/us.srv/ansible.yml b/host_vars/us.srv/ansible.yml new file mode 100644 index 0000000..f1a3ed4 --- /dev/null +++ b/host_vars/us.srv/ansible.yml @@ -0,0 +1 @@ +ansible_python_interpreter: /usr/bin/python3 diff --git a/host_vars/us.srv/datacenter.yml b/host_vars/us.srv/datacenter.yml new file mode 100644 index 0000000..0ca1cdb --- /dev/null +++ b/host_vars/us.srv/datacenter.yml @@ -0,0 +1,10 @@ +--- + +datacenter_id: msiserv +datacenter_name: US-New_York +datacenter_full_name: Contabo +datacenter_local_networks: + - 192.168.255.0/24 + - 10.11.202.0/24 +datacenter_public_networks: + - 207.244.234.58/32 diff --git a/host_vars/us.srv/network.yml b/host_vars/us.srv/network.yml new file mode 100644 index 0000000..8d20476 --- /dev/null +++ b/host_vars/us.srv/network.yml @@ -0,0 +1,2 @@ +network_public_ip: 207.244.234.58 +network_private_ip: 10.11.202.101 diff --git a/inventory b/inventory new file mode 100644 index 0000000..547bd01 --- /dev/null +++ b/inventory @@ -0,0 +1,31 @@ +[eu] +eu.srv + +[us] +us.srv + +[ro] +admin.srv + +[consul:children] +ro +eu +us + +[consul] + +[mysql] +eu.srv +us.srv + +[zookeeper] +eu.srv +us.srv + +[apache_php] +eu.srv +us.srv + +[postfix] +eu.srv +us.srv \ No newline at end of file diff --git a/playbooks/apache.yml b/playbooks/apache.yml new file mode 100644 index 0000000..e893a2e --- /dev/null +++ b/playbooks/apache.yml @@ -0,0 +1,6 @@ +--- +# Configure Apache. + +- hosts: apache_php + roles: + - apache_php diff --git a/playbooks/basic-tools.yml b/playbooks/basic-tools.yml new file mode 100644 index 0000000..24abfe0 --- /dev/null +++ b/playbooks/basic-tools.yml @@ -0,0 +1,22 @@ +--- + +- hosts: all + become: true + tasks: + + - name: update repo index + apt: + update_cache: yes + + - name: install usefull and basic system tools + apt: + name: + - vim-nox + - mc + - nmap + - net-tools + - dnsutils + - tmux + - tcpdump + - iptraf-ng + - screen diff --git a/playbooks/consul.yml b/playbooks/consul.yml new file mode 100644 index 0000000..4f3794d --- /dev/null +++ b/playbooks/consul.yml @@ -0,0 +1,6 @@ +--- +- hosts: consul + serial: 2 + gather_facts: true + roles: + - consul diff --git a/playbooks/firewall.yml b/playbooks/firewall.yml new file mode 100644 index 0000000..964798e --- /dev/null +++ b/playbooks/firewall.yml @@ -0,0 +1,4 @@ +--- +- hosts: all + roles: + - firewall diff --git a/playbooks/network.yml b/playbooks/network.yml new file mode 100644 index 0000000..69dabf5 --- /dev/null +++ b/playbooks/network.yml @@ -0,0 +1,4 @@ +--- +- hosts: all + roles: + - role: network diff --git a/playbooks/ntp.yml b/playbooks/ntp.yml new file mode 100644 index 0000000..d1c5090 --- /dev/null +++ b/playbooks/ntp.yml @@ -0,0 +1,7 @@ +--- +# Configure the base settings for all hosts. + +- hosts: all + roles: + + - role: ntp diff --git a/playbooks/postfix.yml b/playbooks/postfix.yml new file mode 100644 index 0000000..b523278 --- /dev/null +++ b/playbooks/postfix.yml @@ -0,0 +1,4 @@ +--- +- hosts: all + roles: + - postfix diff --git a/roles/apache/defaults/main.yml b/roles/apache/defaults/main.yml new file mode 100644 index 0000000..f1d13c1 --- /dev/null +++ b/roles/apache/defaults/main.yml @@ -0,0 +1,60 @@ +--- + +apache_consul_service: "{{ consul_services|default(False) }}" + +apache_mpm_prefork: true + +apache_timeout: 30 + +apache_monitoring_ips: "{{ (nagios_nrpe_monitoring_ips|default([]) + firewall_monitoring_ips|default([])) | join(' ') }}" + +apache_mod_ssl_protocols: all -SSLv2 -SSLv3 -TLSv1 +apache_mod_ssl_ciphers: + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES256-GCM-SHA384 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-CHACHA20-POLY1305 + - ECDHE-ECDSA-CHACHA20-POLY1305 + - ECDHE-RSA-AES128-SHA256 + - ECDHE-ECDSA-AES128-SHA256 + - ECDHE-RSA-AES256-SHA384 + - ECDHE-ECDSA-AES256-SHA384 + - ECDHE-RSA-AES128-SHA + - ECDHE-ECDSA-AES128-SHA + - ECDHE-RSA-AES256-SHA + - ECDHE-ECDSA-AES256-SHA + - DHE-RSA-AES128-GCM-SHA256 + - DHE-RSA-AES256-GCM-SHA384 + - DHE-RSA-AES128-SHA256 + - DHE-RSA-AES256-SHA256 + - DHE-RSA-AES128-SHA + - DHE-RSA-AES256-SHA +# - AES128-GCM-SHA256 +# - AES256-GCM-SHA384 +# - AES128-SHA256 +# - AES256-SHA256 +# - AES128-SHA +# - AES256-SHA + +apache_http2_enabled: on + +apache_firewall: yes +apache_firewall_public: yes +apache_firewall_public_isolated: no +apache_firewall_acl: [] +apache_firewall_drop_dst: [] + +apache_security_headers: false + +apache_mod_evasive: off +apache_mod_evasive_settings: + DOSHashTableSize: 3097 + DOSPageCount: 20 + DOSSiteCount: 100 + DOSPageInterval: 2 + DOSSiteInterval: 1 + DOSBlockingPeriod: 10 + +apache_mod_security: "{{ apache_firewall_public }}" +apache_mod_security_enabled: false diff --git a/roles/apache/handlers/main.yml b/roles/apache/handlers/main.yml new file mode 100644 index 0000000..67797ec --- /dev/null +++ b/roles/apache/handlers/main.yml @@ -0,0 +1,11 @@ +--- + +- name: Restart Apache + service: name=apache2 state=restarted + +- name: Reload Apache + service: name=apache2 state=reloaded + +- name: Reload Apache systemd + systemd: daemon_reload=yes + diff --git a/roles/apache/meta/main.yml b/roles/apache/meta/main.yml new file mode 100644 index 0000000..285c3c9 --- /dev/null +++ b/roles/apache/meta/main.yml @@ -0,0 +1,8 @@ +--- + +dependencies: + - role: firewall + when: apache_firewall + + - role: consul + when: apache_consul_service diff --git a/roles/apache/tasks/main.yml b/roles/apache/tasks/main.yml new file mode 100644 index 0000000..4b8af92 --- /dev/null +++ b/roles/apache/tasks/main.yml @@ -0,0 +1,164 @@ +--- + +- name: Install Apache packages + apt: + pkg: + - apache2 + - socat + state: present + tags: packages + +- name: Ensure the ssl-cert group exists + group: + name: ssl-cert + system: yes + tags: packages + +- name: Ensure apache is a member of ssl-cert + user: + name: www-data + groups: ssl-cert + append: yes + tags: packages + +- name: Install Apache config + template: + dest: /etc/apache2/apache2.conf + src: etc_apache2_apache2.conf.j2 + mode: 0644 + owner: root + group: root + notify: Reload Apache + tags: configs + +- name: Install Apache module configs + template: + dest: "/etc/apache2/mods-available/{{ item }}" + src: "etc_apache2_mods-available_{{ item }}.j2" + mode: 0644 + owner: root + group: root + with_items: + - deflate.conf + - http2.conf + - ssl.conf + - status.conf + notify: Reload Apache + tags: + - configs + - apache-configs + +- name: Enable Apache modules + apache2_module: + name: "{{ item }}" + state: present + force: yes + with_items: + - deflate + - env + - expires + - headers + - http2 + - reqtimeout + - rewrite + - setenvif + - ssl + - status + - unique_id + notify: Restart Apache + tags: configs + +- name: Install Apache other configs + template: + dest: "/etc/apache2/conf-available/{{ item }}" + src: "etc_apache2_conf-available_{{ item }}.j2" + with_items: + - logging.conf + - security.conf + notify: Reload Apache + tags: [configs, logging] + +- name: Enable Apache other configs + command: "a2enconf {{ item }}" + args: + creates: "/etc/apache2/conf-enabled/{{ item }}" + with_items: + - logging.conf + - security.conf + notify: Reload Apache + tags: configs + +- name: Enable the SSL default vhost + command: a2ensite default-ssl + args: + creates: /etc/apache2/sites-enabled/default-ssl.conf + notify: Reload Apache + tags: configs + +- name: Install Apache logrotate snippet + template: + dest: /etc/logrotate.d/apache2 + src: etc_logrotate.d_apache2.j2 + mode: 0644 + owner: root + group: root + tags: [configs, logrotate] + +- name: Install apache2.service override dir + file: + dest: /etc/systemd/system/apache2.service.d + state: directory + mode: 0755 + owner: root + group: root + tags: [configs, systemd] + +- name: Install apache2.service override + template: + dest: /etc/systemd/system/apache2.service.d/local.conf + src: etc_systemd_system_apache2.service.d_local.conf.j2 + mode: 0644 + owner: root + group: root + notify: Reload Apache systemd + tags: [configs, systemd] + +- name: Ensure Apache is running + systemd: + name: apache2 + state: started + enabled: yes + tags: configs + +- include: mod_evasive.yml + when: apache_mod_evasive + tags: mod_evasive + +- include: mod_security.yml + when: apache_mod_security + tags: mod_security + +- name: Install the Apache firewall config + template: + dest: "/etc/firewall/{{ item }}" + src: "etc_firewall_{{ item | replace('/', '_') }}.j2" + mode: 0600 + owner: root + group: root + when: firewall_enabled and apache_firewall + notify: Restart firewall + with_items: + - rules-v4.d/40_apache.sh + - rules-v6.d/40_apache.sh + tags: + - configs + - firewall + +- name: Register the apache service in Consul + template: + dest: /etc/consul.d/service-apache.hcl + src: etc_consul.d_service-apache.hcl.j2 + when: apache_consul_service + notify: Reload consul + tags: configs + diff --git a/roles/apache/tasks/mod_evasive.yml b/roles/apache/tasks/mod_evasive.yml new file mode 100644 index 0000000..6e63cb4 --- /dev/null +++ b/roles/apache/tasks/mod_evasive.yml @@ -0,0 +1,27 @@ +--- + +- name: Install Apache mod_evasive + apt: + pkg: + - libapache2-mod-evasive + state: present + notify: Restart Apache + tags: packages + +- name: Install Apache mod_evasive config + template: + dest: /etc/apache2/mods-available/evasive.conf + src: etc_apache2_mods-available_evasive.conf.j2 + mode: 0644 + owner: root + group: root + notify: Reload Apache + tags: configs + +- name: Enable Apache mod_evasive + apache2_module: + name: evasive + state: present + force: yes + notify: Restart Apache + tags: configs diff --git a/roles/apache/tasks/mod_security.yml b/roles/apache/tasks/mod_security.yml new file mode 100644 index 0000000..7867fc7 --- /dev/null +++ b/roles/apache/tasks/mod_security.yml @@ -0,0 +1,38 @@ +--- + +- name: Install Apache mod_security + apt: + pkg: + - libapache2-mod-security2=2.9.* + - modsecurity-crs=3.* + state: present + notify: Restart Apache + tags: packages + +- name: Install Apache mod_security config + template: + dest: /etc/modsecurity/modsecurity.conf + src: etc_modsecurity_modsecurity.conf.j2 + mode: 0644 + owner: root + group: root + notify: Reload Apache + tags: configs + +- name: Install Apache mod_security ruleset config + template: + dest: /etc/modsecurity/crs/crs-setup.conf + src: etc_modsecurity_crs_crs-setup.conf.j2 + mode: 0644 + owner: root + group: root + notify: Reload Apache + tags: configs + +- name: Enable Apache mod_security + apache2_module: + name: security2 + state: present + force: yes + notify: Restart Apache + tags: configs diff --git a/roles/apache/templates/etc_apache2_apache2.conf.j2 b/roles/apache/templates/etc_apache2_apache2.conf.j2 new file mode 100644 index 0000000..6772ee9 --- /dev/null +++ b/roles/apache/templates/etc_apache2_apache2.conf.j2 @@ -0,0 +1,233 @@ +# {{ ansible_managed }} + +# This is the main Apache server configuration file. It contains the +# configuration directives that give the server its instructions. +# See http://httpd.apache.org/docs/2.4/ for detailed information about +# the directives and /usr/share/doc/apache2/README.Debian about Debian specific +# hints. +# +# +# Summary of how the Apache 2 configuration works in Debian: +# The Apache 2 web server configuration in Debian is quite different to +# upstream's suggested way to configure the web server. This is because Debian's +# default Apache2 installation attempts to make adding and removing modules, +# virtual hosts, and extra configuration directives as flexible as possible, in +# order to make automating the changes and administering the server as easy as +# possible. + +# It is split into several files forming the configuration hierarchy outlined +# below, all located in the /etc/apache2/ directory: +# +# /etc/apache2/ +# |-- apache2.conf +# | `-- ports.conf +# |-- mods-enabled +# | |-- *.load +# | `-- *.conf +# |-- conf-enabled +# | `-- *.conf +# `-- sites-enabled +# `-- *.conf +# +# +# * apache2.conf is the main configuration file (this file). It puts the pieces +# together by including all remaining configuration files when starting up the +# web server. +# +# * ports.conf is always included from the main configuration file. It is +# supposed to determine listening ports for incoming connections which can be +# customized anytime. +# +# * Configuration files in the mods-enabled/, conf-enabled/ and sites-enabled/ +# directories contain particular configuration snippets which manage modules, +# global configuration fragments, or virtual host configurations, +# respectively. +# +# They are activated by symlinking available configuration files from their +# respective *-available/ counterparts. These should be managed by using our +# helpers a2enmod/a2dismod, a2ensite/a2dissite and a2enconf/a2disconf. See +# their respective man pages for detailed information. +# +# * The binary is called apache2. Due to the use of environment variables, in +# the default configuration, apache2 needs to be started/stopped with +# /etc/init.d/apache2 or apache2ctl. Calling /usr/bin/apache2 directly will not +# work with the default configuration. + + +# Global configuration +# + +# +# ServerRoot: The top of the directory tree under which the server's +# configuration, error, and log files are kept. +# +# NOTE! If you intend to place this on an NFS (or otherwise network) +# mounted filesystem then please read the Mutex documentation (available +# at ); +# you will save yourself a lot of trouble. +# +# Do NOT add a slash at the end of the directory path. +# +#ServerRoot "/etc/apache2" + +# +# The accept serialization lock file MUST BE STORED ON A LOCAL DISK. +# +#Mutex file:${APACHE_LOCK_DIR} default + +# +# The directory where shm and other runtime files will be stored. +# + +DefaultRuntimeDir ${APACHE_RUN_DIR} + +# +# PidFile: The file in which the server should record its process +# identification number when it starts. +# This needs to be set in /etc/apache2/envvars +# +PidFile ${APACHE_PID_FILE} + +# +# Timeout: The number of seconds before receives and sends time out. +# +Timeout {{ apache_timeout }} + +# +# KeepAlive: Whether or not to allow persistent connections (more than +# one request per connection). Set to "Off" to deactivate. +# +KeepAlive On + +# +# MaxKeepAliveRequests: The maximum number of requests to allow +# during a persistent connection. Set to 0 to allow an unlimited amount. +# We recommend you leave this number high, for maximum performance. +# +MaxKeepAliveRequests 100 + +# +# KeepAliveTimeout: Number of seconds to wait for the next request from the +# same client on the same connection. +# +KeepAliveTimeout 5 + + +# These need to be set in /etc/apache2/envvars +User ${APACHE_RUN_USER} +Group ${APACHE_RUN_GROUP} + +# +# HostnameLookups: Log the names of clients or just their IP addresses +# e.g., www.apache.org (on) or 204.62.129.132 (off). +# The default is off because it'd be overall better for the net if people +# had to knowingly turn this feature on, since enabling it means that +# each client request will result in AT LEAST one lookup request to the +# nameserver. +# +HostnameLookups Off + +# ErrorLog: The location of the error log file. +# If you do not specify an ErrorLog directive within a +# container, error messages relating to that virtual host will be +# logged here. If you *do* define an error logfile for a +# container, that host's errors will be logged there and not here. +# +ErrorLog ${APACHE_LOG_DIR}/error.log + +# +# LogLevel: Control the severity of messages logged to the error_log. +# Available values: trace8, ..., trace1, debug, info, notice, warn, +# error, crit, alert, emerg. +# It is also possible to configure the log level for particular modules, e.g. +# "LogLevel info ssl:warn" +# +LogLevel warn + +# Include module configuration: +IncludeOptional mods-enabled/*.load +IncludeOptional mods-enabled/*.conf + +# Include list of ports to listen on +Include ports.conf + + +# Sets the default security model of the Apache2 HTTPD server. It does +# not allow access to the root filesystem outside of /usr/share and /var/www. +# The former is used by web applications packaged in Debian, +# the latter may be used for local directories served by the web server. If +# your system is serving content from a sub-directory in /srv you must allow +# access here, or in any related virtual host. + + Options FollowSymLinks + AllowOverride None + Require all denied + + + + AllowOverride None + Require all granted + + + + Options FollowSymLinks + AllowOverride None + Require all granted + + + + Options FollowSymLinks + AllowOverride None + Require all granted + + + + Options FollowSymLinks + AllowOverride None + Require all granted + + + +# AccessFileName: The name of the file to look for in each directory +# for additional configuration directives. See also the AllowOverride +# directive. +# +AccessFileName .htaccess + +# +# The following lines prevent dot files from being +# viewed by Web clients. +# + + Require all denied + + + +# +# The following directives define some format nicknames for use with +# a CustomLog directive. +# +# These deviate from the Common Log Format definitions in that they use %O +# (the actual bytes sent including headers) instead of %b (the size of the +# requested file), because the latter makes it impossible to detect partial +# requests. +# +# Note that the use of %{X-Forwarded-For}i instead of %h is not recommended. +# Use mod_remoteip instead. +# +LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined +LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%h %l %u %t \"%r\" %>s %O" common +LogFormat "%{Referer}i -> %U" referer +LogFormat "%{User-agent}i" agent + +# Include of directories ignores editors' and dpkg's backup files, +# see README.Debian for details. + +# Include generic snippets of statements +IncludeOptional conf-enabled/*.conf + +# Include the virtual host configurations: +IncludeOptional sites-enabled/*.conf + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/roles/apache/templates/etc_apache2_conf-available_logging.conf.j2 b/roles/apache/templates/etc_apache2_conf-available_logging.conf.j2 new file mode 100644 index 0000000..a4f7148 --- /dev/null +++ b/roles/apache/templates/etc_apache2_conf-available_logging.conf.j2 @@ -0,0 +1,7 @@ +# {{ ansible_managed }} + +# BufferedLogs On + +LogFormat "%v:%p %R %m %>s %H conn=%X %D %O %I %k" metrics + +GlobalLog ${APACHE_LOG_DIR}/metrics.log metrics diff --git a/roles/apache/templates/etc_apache2_conf-available_security.conf.j2 b/roles/apache/templates/etc_apache2_conf-available_security.conf.j2 new file mode 100644 index 0000000..f7316d2 --- /dev/null +++ b/roles/apache/templates/etc_apache2_conf-available_security.conf.j2 @@ -0,0 +1,88 @@ +# {{ ansible_managed }} + +# +# Disable access to the entire file system except for the directories that +# are explicitly allowed later. +# +# This currently breaks the configurations that come with some web application +# Debian packages. +# + + AllowOverride None + Require all denied + + + +# Changing the following options will not really affect the security of the +# server, but might make attacks slightly more difficult in some cases. + +# +# ServerTokens +# This directive configures what you return as the Server HTTP response +# Header. The default is 'Full' which sends information about the OS-Type +# and compiled in modules. +# Set to one of: Full | OS | Minimal | Minor | Major | Prod +# where Full conveys the most information, and Prod the least. +ServerTokens Prod +#ServerTokens OS +#ServerTokens Full + +# +# Optionally add a line containing the server version and virtual host +# name to server-generated pages (internal error documents, FTP directory +# listings, mod_status and mod_info output etc., but not CGI generated +# documents or custom error documents). +# Set to "EMail" to also include a mailto: link to the ServerAdmin. +# Set to one of: On | Off | EMail +ServerSignature Off +#ServerSignature On + +# +# Allow TRACE method +# +# Set to "extended" to also reflect the request body (only for testing and +# diagnostic purposes). +# +# Set to one of: On | Off | extended +TraceEnable Off +#TraceEnable On + +# +# Forbid access to version control directories +# +# If you use version control systems in your document root, you should +# probably deny access to their directories. For example, for subversion: +# + + Require all denied + + +# +# Setting this header will prevent MSIE from interpreting files as something +# else than declared by the content type in the HTTP headers. +# Requires mod_headers to be enabled. +# +#Header set X-Content-Type-Options: "nosniff" + +# +# Setting this header will prevent other sites from embedding pages from this +# site as frames. This defends against clickjacking attacks. +# Requires mod_headers to be enabled. +# +#Header set X-Frame-Options: "sameorigin" + +{% if apache_security_headers %} +# +# Security headers for PCI-DSS. +# +Header always set X-Content-Type-Options: "nosniff" +Header always set X-Frame-Options: "sameorigin" +Header always set X-XSS-Protection "1; mode=block" +{% endif %} + +# +# Accept host names with _underscores_ +# +HTTPProtocolOptions unsafe + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/roles/apache/templates/etc_apache2_mods-available_deflate.conf.j2 b/roles/apache/templates/etc_apache2_mods-available_deflate.conf.j2 new file mode 100644 index 0000000..ca9ec2e --- /dev/null +++ b/roles/apache/templates/etc_apache2_mods-available_deflate.conf.j2 @@ -0,0 +1,22 @@ +# {{ ansible_managed }} + + + + # these are known to be safe with MSIE 6 + AddOutputFilterByType DEFLATE text/html text/plain text/xml image/svg+xml + + # everything else may cause problems with MSIE 6 + AddOutputFilterByType DEFLATE text/css + AddOutputFilterByType DEFLATE application/x-javascript application/javascript application/ecmascript + AddOutputFilterByType DEFLATE application/rss+xml + AddOutputFilterByType DEFLATE application/xml + + AddOutputFilterByType DEFLATE application/json + AddOutputFilterByType DEFLATE application/x-php-serialized-rpc + AddOutputFilterByType DEFLATE image/x-icon text/javascript + + DeflateFilterNote Ratio ratio + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/roles/apache/templates/etc_apache2_mods-available_evasive.conf.j2 b/roles/apache/templates/etc_apache2_mods-available_evasive.conf.j2 new file mode 100644 index 0000000..c82fdd3 --- /dev/null +++ b/roles/apache/templates/etc_apache2_mods-available_evasive.conf.j2 @@ -0,0 +1,30 @@ +# {{ ansible_managed }} + + +{% for key, value in apache_mod_evasive_settings | dictsort %} + {{ key }} {{ value }} +{% endfor %} + + #DOSEmailNotify you@yourdomain.com + #DOSSystemCommand "su - someuser -c '/sbin/... %s ...'" + #DOSLogDir "/var/log/mod_evasive" + + DOSWhitelist 10.*.*.* + DOSWhitelist 192.168.*.* + + DOSWhitelist 63.254.74.* + DOSWhitelist 8.28.239.* + +{% for ip in firewall_monitoring_ips|default([]) if ip|ipv4('public') %} + DOSWhitelist {{ ip }} +{% endfor %} + +{% for ip in firewall_whitelist_office_ip|default([]) %} + DOSWhitelist {{ ip | regex_replace('[0-9]+/[0-9]+', '*') }} +{% endfor %} + +{% for ip in apache_mod_evasive_whitelist|default([]) %} + DOSWhitelist {{ ip | regex_replace('[0-9]+/[0-9]+', '*') }} +{% endfor %} + + diff --git a/roles/apache/templates/etc_apache2_mods-available_http2.conf.j2 b/roles/apache/templates/etc_apache2_mods-available_http2.conf.j2 new file mode 100644 index 0000000..dfe0b3f --- /dev/null +++ b/roles/apache/templates/etc_apache2_mods-available_http2.conf.j2 @@ -0,0 +1,17 @@ +# {{ ansible_managed }} + + +{% if apache_http2_enabled %} + Protocols h2 h2c http/1.1 +{% else %} + Protocols http/1.1 # http/2 disabled +{% endif %} + + H2Push on + H2PushPriority * after + H2PushPriority text/css before + H2PushPriority image/jpeg after 32 + H2PushPriority image/png after 32 + H2PushPriority application/javascript interleaved + + diff --git a/roles/apache/templates/etc_apache2_mods-available_ssl.conf.j2 b/roles/apache/templates/etc_apache2_mods-available_ssl.conf.j2 new file mode 100644 index 0000000..3269c5c --- /dev/null +++ b/roles/apache/templates/etc_apache2_mods-available_ssl.conf.j2 @@ -0,0 +1,91 @@ +# {{ ansible_managed }} + + + + # Pseudo Random Number Generator (PRNG): + # Configure one or more sources to seed the PRNG of the SSL library. + # The seed data should be of good random quality. + # WARNING! On some platforms /dev/random blocks if not enough entropy + # is available. This means you then cannot use the /dev/random device + # because it would lead to very long connection times (as long as + # it requires to make more entropy available). But usually those + # platforms additionally provide a /dev/urandom device which doesn't + # block. So, if available, use this one instead. Read the mod_ssl User + # Manual for more details. + # + SSLRandomSeed startup builtin + SSLRandomSeed startup file:/dev/urandom 512 + SSLRandomSeed connect builtin + SSLRandomSeed connect file:/dev/urandom 512 + + ## + ## SSL Global Context + ## + ## All SSL configuration in this context applies both to + ## the main server and all SSL-enabled virtual hosts. + ## + + # + # Some MIME-types for downloading Certificates and CRLs + # + AddType application/x-x509-ca-cert .crt + AddType application/x-pkcs7-crl .crl + + # Pass Phrase Dialog: + # Configure the pass phrase gathering process. + # The filtering dialog program (`builtin' is a internal + # terminal dialog) has to provide the pass phrase on stdout. + SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase + + # Inter-Process Session Cache: + # Configure the SSL Session Cache: First the mechanism + # to use and second the expiring timeout (in seconds). + # (The mechanism dbm has known memory leaks and should not be used). + #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache + SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) + SSLSessionCacheTimeout 300 + + # Semaphore: + # Configure the path to the mutual exclusion semaphore the + # SSL engine uses internally for inter-process synchronization. + # (Disabled by default, the global Mutex directive consolidates by default + # this) + #Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache + + # SSL Cipher Suite: + # List the ciphers that the client is permitted to negotiate. See the + # ciphers(1) man page from the openssl package for list of all available + # options. + # Enable only secure ciphers: + SSLCipherSuite "{{ apache_mod_ssl_ciphers | join(':') }}" + #SSLOpenSSLConfCmd DHParameters /etc/apache2/ssl/dhparams.pem + + + # SSL server cipher order preference: + # Use server priorities for cipher algorithm choice. + # Clients may prefer lower grade encryption. You should enable this + # option if you want to enforce stronger encryption, and can afford + # the CPU cost, and did not override SSLCipherSuite in a way that puts + # insecure ciphers first. + # Default: Off + SSLHonorCipherOrder on + + # The protocols to enable. + # Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2 + # SSL v2 is no longer supported + SSLProtocol {{ apache_mod_ssl_protocols }} + + # Allow insecure renegotiation with clients which do not yet support the + # secure renegotiation protocol. Default: Off + #SSLInsecureRenegotiation on + + # Whether to forbid non-SNI clients to access name based virtual hosts. + # Default: Off + #SSLStrictSNIVHostCheck On + + SSLStaplingCache shmcb:${APACHE_RUN_DIR}/ssl_stcache(512000) + SSLUseStapling on + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/roles/apache/templates/etc_apache2_mods-available_status.conf.j2 b/roles/apache/templates/etc_apache2_mods-available_status.conf.j2 new file mode 100644 index 0000000..be7b38d --- /dev/null +++ b/roles/apache/templates/etc_apache2_mods-available_status.conf.j2 @@ -0,0 +1,29 @@ +# {{ ansible_managed }} + + + # Allow server status reports generated by mod_status, + # with the URL of http://servername/server-status + + + SetHandler server-status + Require ip 127.0.0.1 ::1 {{ apache_monitoring_ips }} + + + # Keep track of extended status information for each request + ExtendedStatus On + + # Determine if mod_status displays the first 63 characters of a request or + # the last 63, assuming the request itself is greater than 63 chars. + # Default: Off + #SeeRequestTail On + + + + # Show Proxy LoadBalancer status in mod_status + ProxyStatus On + + + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/roles/apache/templates/etc_consul.d_service-apache.hcl.j2 b/roles/apache/templates/etc_consul.d_service-apache.hcl.j2 new file mode 100644 index 0000000..c26e5e8 --- /dev/null +++ b/roles/apache/templates/etc_consul.d_service-apache.hcl.j2 @@ -0,0 +1,11 @@ +# {{ ansible_managed }} + +service { + name = "apache" + port = 443 + check { + http = "https://localhost/server-status?auto" + interval = "30s" + tlsSkipVerify = true + } +} diff --git a/roles/apache/templates/etc_firewall_rules-v4.d_40_apache.sh.j2 b/roles/apache/templates/etc_firewall_rules-v4.d_40_apache.sh.j2 new file mode 100644 index 0000000..27b1def --- /dev/null +++ b/roles/apache/templates/etc_firewall_rules-v4.d_40_apache.sh.j2 @@ -0,0 +1,26 @@ +# {{ ansible_managed }} + +{% if apache_firewall_public %} +iptables -N apache-in +{% if apache_firewall_public_isolated %} +{% for ip in apache_firewall_acl %} +iptables -A apache-in -s {{ ip }} -j ACCEPT +{% endfor %} +{% for ip in datacenter_global_networks + datacenter_public_networks %} +iptables -A apache-in -s {{ ip }} -j RETURN +{% endfor %} +{% for ip in apache_firewall_drop_dst %} +iptables -A apache-in -d {{ ip }} -j RETURN +{% endfor %} +{% endif %} +iptables -A apache-in -j ACCEPT + +iptables -A INPUT -p tcp --dport 80 -m comment --comment "apache-http" -j apache-in +iptables -A INPUT -p tcp --dport 443 -m comment --comment "apache-https" -j apache-in +{% else %} +iptables -A internal-in -p tcp --dport 80 -m comment --comment "apache-http" -j ACCEPT +iptables -A internal-in -p tcp --dport 443 -m comment --comment "apache-https" -j ACCEPT +{% endif %} + +iptables -A monitoring-in -p tcp --dport 80 -m comment --comment "apache-http" -j ACCEPT +iptables -A monitoring-in -p tcp --dport 443 -m comment --comment "apache-https" -j ACCEPT diff --git a/roles/apache/templates/etc_firewall_rules-v6.d_40_apache.sh.j2 b/roles/apache/templates/etc_firewall_rules-v6.d_40_apache.sh.j2 new file mode 100644 index 0000000..754a2ff --- /dev/null +++ b/roles/apache/templates/etc_firewall_rules-v6.d_40_apache.sh.j2 @@ -0,0 +1,19 @@ +# {{ ansible_managed }} + +{% if apache_firewall_public %} +ip6tables -N apache-in +{% if apache_firewall_public_isolated %} +ip6tables -A apache-in -s fe80::/10 -j RETURN +ip6tables -A apache-in -s fc00::/7 -j RETURN +{% for ip in datacenter_public_ipv6_networks %} +ip6tables -A apache-in -s {{ ip }} -j RETURN +{% endfor %} +{% endif %} +ip6tables -A apache-in -j ACCEPT + +ip6tables -A INPUT -p tcp --dport 80 -m comment --comment "apache-http" -j apache-in +ip6tables -A INPUT -p tcp --dport 443 -m comment --comment "apache-https" -j apache-in +{% else %} +ip6tables -A internal-in -p tcp --dport 80 -m comment --comment "apache-http" -j ACCEPT +ip6tables -A internal-in -p tcp --dport 443 -m comment --comment "apache-https" -j ACCEPT +{% endif %} diff --git a/roles/apache/templates/etc_logrotate.d_apache2.j2 b/roles/apache/templates/etc_logrotate.d_apache2.j2 new file mode 100644 index 0000000..0d35898 --- /dev/null +++ b/roles/apache/templates/etc_logrotate.d_apache2.j2 @@ -0,0 +1,23 @@ +/var/log/apache2/*.log { + dateext + dateformat .%Y%m%d + dateyesterday + daily + missingok + rotate 14 + compress + delaycompress + notifempty + create 640 root adm + sharedscripts + postrotate + if /etc/init.d/apache2 status > /dev/null ; then \ + /etc/init.d/apache2 reload > /dev/null; \ + fi; + endscript + prerotate + if [ -d /etc/logrotate.d/httpd-prerotate ]; then \ + run-parts /etc/logrotate.d/httpd-prerotate; \ + fi; \ + endscript +} diff --git a/roles/apache/templates/etc_modsecurity_crs_crs-setup.conf.j2 b/roles/apache/templates/etc_modsecurity_crs_crs-setup.conf.j2 new file mode 100644 index 0000000..06d6024 --- /dev/null +++ b/roles/apache/templates/etc_modsecurity_crs_crs-setup.conf.j2 @@ -0,0 +1,853 @@ +# {{ ansible_managed }} + +# ------------------------------------------------------------------------ +# OWASP ModSecurity Core Rule Set ver.3.1.0 +# Copyright (c) 2006-2018 Trustwave and contributors. All rights reserved. +# +# The OWASP ModSecurity Core Rule Set is distributed under +# Apache Software License (ASL) version 2 +# Please see the enclosed LICENSE file for full details. +# ------------------------------------------------------------------------ + + +# +# -- [[ Introduction ]] -------------------------------------------------------- +# +# The OWASP ModSecurity Core Rule Set (CRS) is a set of generic attack +# detection rules that provide a base level of protection for any web +# application. They are written for the open source, cross-platform +# ModSecurity Web Application Firewall. +# +# See also: +# https://coreruleset.org/ +# https://github.com/SpiderLabs/owasp-modsecurity-crs +# https://www.owasp.org/index.php/Category:OWASP_ModSecurity_Core_Rule_Set_Project +# + + +# +# -- [[ System Requirements ]] ------------------------------------------------- +# +# CRS requires ModSecurity version 2.8.0 or above. +# We recommend to always use the newest ModSecurity version. +# +# The configuration directives/settings in this file are used to control +# the OWASP ModSecurity CRS. These settings do **NOT** configure the main +# ModSecurity settings (modsecurity.conf) such as SecRuleEngine, +# SecRequestBodyAccess, SecAuditEngine, SecDebugLog, and XML processing. +# +# The CRS assumes that modsecurity.conf has been loaded. It is bundled with +# ModSecurity. If you don't have it, you can get it from: +# 2.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v2/master/modsecurity.conf-recommended +# 3.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended +# +# The order of file inclusion in your webserver configuration should always be: +# 1. modsecurity.conf +# 2. crs-setup.conf (this file) +# 3. rules/*.conf (the CRS rule files) +# +# Please refer to the INSTALL file for detailed installation instructions. +# + + +# +# -- [[ Mode of Operation: Anomaly Scoring vs. Self-Contained ]] --------------- +# +# The CRS can run in two modes: +# +# -- [[ Anomaly Scoring Mode (default) ]] -- +# In CRS3, anomaly mode is the default and recommended mode, since it gives the +# most accurate log information and offers the most flexibility in setting your +# blocking policies. It is also called "collaborative detection mode". +# In this mode, each matching rule increases an 'anomaly score'. +# At the conclusion of the inbound rules, and again at the conclusion of the +# outbound rules, the anomaly score is checked, and the blocking evaluation +# rules apply a disruptive action, by default returning an error 403. +# +# -- [[ Self-Contained Mode ]] -- +# In this mode, rules apply an action instantly. This was the CRS2 default. +# It can lower resource usage, at the cost of less flexibility in blocking policy +# and less informative audit logs (only the first detected threat is logged). +# Rules inherit the disruptive action that you specify (i.e. deny, drop, etc). +# The first rule that matches will execute this action. In most cases this will +# cause evaluation to stop after the first rule has matched, similar to how many +# IDSs function. +# +# -- [[ Alert Logging Control ]] -- +# In the mode configuration, you must also adjust the desired logging options. +# There are three common options for dealing with logging. By default CRS enables +# logging to the webserver error log (or Event viewer) plus detailed logging to +# the ModSecurity audit log (configured under SecAuditLog in modsecurity.conf). +# +# - To log to both error log and ModSecurity audit log file, use: "log,auditlog" +# - To log *only* to the ModSecurity audit log file, use: "nolog,auditlog" +# - To log *only* to the error log file, use: "log,noauditlog" +# +# Examples for the various modes follow. +# You must leave one of the following options enabled. +# Note that you must specify the same line for phase:1 and phase:2. +# + +# Default: Anomaly Scoring mode, log to error log, log to ModSecurity audit log +# - By default, offending requests are blocked with an error 403 response. +# - To change the disruptive action, see RESPONSE-999-EXCEPTIONS.conf.example +# and review section 'Changing the Disruptive Action for Anomaly Mode'. +# - In Apache, you can use ErrorDocument to show a friendly error page or +# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html +# +SecDefaultAction "phase:1,log,auditlog,pass" +SecDefaultAction "phase:2,log,auditlog,pass" + +# Example: Anomaly Scoring mode, log only to ModSecurity audit log +# - By default, offending requests are blocked with an error 403 response. +# - To change the disruptive action, see RESPONSE-999-EXCEPTIONS.conf.example +# and review section 'Changing the Disruptive Action for Anomaly Mode'. +# - In Apache, you can use ErrorDocument to show a friendly error page or +# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html +# +# SecDefaultAction "phase:1,nolog,auditlog,pass" +# SecDefaultAction "phase:2,nolog,auditlog,pass" + +# Example: Self-contained mode, return error 403 on blocking +# - In this configuration the default disruptive action becomes 'deny'. After a +# rule triggers, it will stop processing the request and return an error 403. +# - You can also use a different error status, such as 404, 406, et cetera. +# - In Apache, you can use ErrorDocument to show a friendly error page or +# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html +# +# SecDefaultAction "phase:1,log,auditlog,deny,status:403" +# SecDefaultAction "phase:2,log,auditlog,deny,status:403" + +# Example: Self-contained mode, redirect back to homepage on blocking +# - In this configuration the 'tag' action includes the Host header data in the +# log. This helps to identify which virtual host triggered the rule (if any). +# - Note that this might cause redirect loops in some situations; for example +# if a Cookie or User-Agent header is blocked, it will also be blocked when +# the client subsequently tries to access the homepage. You can also redirect +# to another custom URL. +# SecDefaultAction "phase:1,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'" +# SecDefaultAction "phase:2,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'" + + +# +# -- [[ Paranoia Level Initialization ]] --------------------------------------- +# +# The Paranoia Level (PL) setting allows you to choose the desired level +# of rule checks that will add to your anomaly scores. +# +# With each paranoia level increase, the CRS enables additional rules +# giving you a higher level of security. However, higher paranoia levels +# also increase the possibility of blocking some legitimate traffic due to +# false alarms (also named false positives or FPs). If you use higher +# paranoia levels, it is likely that you will need to add some exclusion +# rules for certain requests and applications receiving complex input. +# +# - A paranoia level of 1 is default. In this level, most core rules +# are enabled. PL1 is advised for beginners, installations +# covering many different sites and applications, and for setups +# with standard security requirements. +# At PL1 you should face FPs rarely. If you encounter FPs, please +# open an issue on the CRS GitHub site and don't forget to attach your +# complete Audit Log record for the request with the issue. +# - Paranoia level 2 includes many extra rules, for instance enabling +# many regexp-based SQL and XSS injection protections, and adding +# extra keywords checked for code injections. PL2 is advised +# for moderate to experienced users desiring more complete coverage +# and for installations with elevated security requirements. +# PL2 comes with some FPs which you need to handle. +# - Paranoia level 3 enables more rules and keyword lists, and tweaks +# limits on special characters used. PL3 is aimed at users experienced +# at the handling of FPs and at installations with a high security +# requirement. +# - Paranoia level 4 further restricts special characters. +# The highest level is advised for experienced users protecting +# installations with very high security requirements. Running PL4 will +# likely produce a very high number of FPs which have to be +# treated before the site can go productive. +# +# Rules in paranoia level 2 or higher will log their PL to the audit log; +# example: [tag "paranoia-level/2"]. This allows you to deduct from the +# audit log how the WAF behavior is affected by paranoia level. +# +# It is important to also look into the variable +# tx.enforce_bodyproc_urlencoded (Enforce Body Processor URLENCODED) +# defined below. Enabling it closes a possible bypass of CRS. +# +# Uncomment this rule to change the default: +# +#SecAction \ +# "id:900000,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.paranoia_level=1" + + +# It is possible to execute rules from a higher paranoia level but not include +# them in the anomaly scoring. This allows you to take a well-tuned system on +# paranoia level 1 and add rules from paranoia level 2 without having to fear +# the new rules would lead to false positives that raise your score above the +# threshold. +# This optional feature is enabled by uncommenting the following rule and +# setting the tx.executing_paranoia_level. +# Technically, rules up to the level defined in tx.executing_paranoia_level +# will be executed, but only the rules up to tx.paranoia_level affect the +# anomaly scores. +# By default, tx.executing_paranoia_level is set to tx.paranoia_level. +# tx.executing_paranoia_level must not be lower than tx.paranoia_level. +# +# Please notice that setting tx.executing_paranoia_level to a higher paranoia +# level results in a performance impact that is equally high as setting +# tx.paranoia_level to said level. +# +#SecAction \ +# "id:900001,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.executing_paranoia_level=1" + + +# +# -- [[ Enforce Body Processor URLENCODED ]] ----------------------------------- +# +# ModSecurity selects the body processor based on the Content-Type request +# header. But clients are not always setting the Content-Type header for their +# request body payloads. This will leave ModSecurity with limited vision into +# the payload. The variable tx.enforce_bodyproc_urlencoded lets you force the +# URLENCODED body processor in these situations. This is off by default, as it +# implies a change of the behaviour of ModSecurity beyond CRS (the body +# processor applies to all rules, not only CRS) and because it may lead to +# false positives already on paranoia level 1. However, enabling this variable +# closes a possible bypass of CRS so it should be considered. +# +# Uncomment this rule to change the default: +# +#SecAction \ +# "id:900010,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.enforce_bodyproc_urlencoded=1" + + +# +# -- [[ Anomaly Mode Severity Levels ]] ---------------------------------------- +# +# Each rule in the CRS has an associated severity level. +# These are the default scoring points for each severity level. +# These settings will be used to increment the anomaly score if a rule matches. +# You may adjust these points to your liking, but this is usually not needed. +# +# - CRITICAL severity: Anomaly Score of 5. +# Mostly generated by the application attack rules (93x and 94x files). +# - ERROR severity: Anomaly Score of 4. +# Generated mostly from outbound leakage rules (95x files). +# - WARNING severity: Anomaly Score of 3. +# Generated mostly by malicious client rules (91x files). +# - NOTICE severity: Anomaly Score of 2. +# Generated mostly by the protocol rules (92x files). +# +# In anomaly mode, these scores are cumulative. +# So it's possible for a request to hit multiple rules. +# +# (Note: In this file, we use 'phase:1' to set CRS configuration variables. +# In general, 'phase:request' is used. However, we want to make absolutely sure +# that all configuration variables are set before the CRS rules are processed.) +# +#SecAction \ +# "id:900100,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.critical_anomaly_score=5,\ +# setvar:tx.error_anomaly_score=4,\ +# setvar:tx.warning_anomaly_score=3,\ +# setvar:tx.notice_anomaly_score=2" + + +# +# -- [[ Anomaly Mode Blocking Threshold Levels ]] ------------------------------ +# +# Here, you can specify at which cumulative anomaly score an inbound request, +# or outbound response, gets blocked. +# +# Most detected inbound threats will give a critical score of 5. +# Smaller violations, like violations of protocol/standards, carry lower scores. +# +# [ At default value ] +# If you keep the blocking thresholds at the defaults, the CRS will work +# similarly to previous CRS versions: a single critical rule match will cause +# the request to be blocked and logged. +# +# [ Using higher values ] +# If you want to make the CRS less sensitive, you can increase the blocking +# thresholds, for instance to 7 (which would require multiple rule matches +# before blocking) or 10 (which would require at least two critical alerts - or +# a combination of many lesser alerts), or even higher. However, increasing the +# thresholds might cause some attacks to bypass the CRS rules or your policies. +# +# [ New deployment strategy: Starting high and decreasing ] +# It is a common practice to start a fresh CRS installation with elevated +# anomaly scoring thresholds (>100) and then lower the limits as your +# confidence in the setup grows. You may also look into the Sampling +# Percentage section below for a different strategy to ease into a new +# CRS installation. +# +# [ Anomaly Threshold / Paranoia Level Quadrant ] +# +# High Anomaly Limit | High Anomaly Limit +# Low Paranoia Level | High Paranoia Level +# -> Fresh Site | -> Experimental Site +# ------------------------------------------------------ +# Low Anomaly Limit | Low Anomaly Limit +# Low Paranoia Level | High Paranoia Level +# -> Standard Site | -> High Security Site +# +# Uncomment this rule to change the defaults: +# +#SecAction \ +# "id:900110,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.inbound_anomaly_score_threshold=5,\ +# setvar:tx.outbound_anomaly_score_threshold=4" + +# +# -- [[ Application Specific Rule Exclusions ]] ---------------------------------------- +# +# Some well-known applications may undertake actions that appear to be +# malicious. This includes actions such as allowing HTML or Javascript within +# parameters. In such cases the CRS aims to prevent false positives by allowing +# administrators to enable prebuilt, application specific exclusions on an +# application by application basis. +# These application specific exclusions are distinct from the rules that would +# be placed in the REQUEST-900-EXCLUSION-RULES-BEFORE-CRS configuration file as +# they are prebuilt for specific applications. The 'REQUEST-900' file is +# designed for users to add their own custom exclusions. Note, using these +# application specific exclusions may loosen restrictions of the CRS, +# especially if used with an application they weren't designed for. As a result +# they should be applied with care. +# To use this functionality you must specify a supported application. To do so +# uncomment rule 900130. In addition to uncommenting the rule you will need to +# specify which application(s) you'd like to enable exclusions for. Only a +# (very) limited set of applications are currently supported, please use the +# filenames prefixed with 'REQUEST-903' to guide you in your selection. +# Such filenames use the following convention: +# REQUEST-903.9XXX-{APPNAME}-EXCLUSIONS-RULES.conf +# +# It is recommended if you run multiple web applications on your site to limit +# the effects of the exclusion to only the path where the excluded webapp +# resides using a rule similar to the following example: +# SecRule REQUEST_URI "@beginsWith /wordpress/" setvar:tx.crs_exclusions_wordpress=1 + +# +# Modify and uncomment this rule to select which application: +# +#SecAction \ +# "id:900130,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.crs_exclusions_drupal=1,\ +# setvar:tx.crs_exclusions_wordpress=1,\ +# setvar:tx.crs_exclusions_nextcloud=1,\ +# setvar:tx.crs_exclusions_dokuwiki=1,\ +# setvar:tx.crs_exclusions_cpanel=1" + +# +# -- [[ HTTP Policy Settings ]] ------------------------------------------------ +# +# This section defines your policies for the HTTP protocol, such as: +# - allowed HTTP versions, HTTP methods, allowed request Content-Types +# - forbidden file extensions (e.g. .bak, .sql) and request headers (e.g. Proxy) +# +# These variables are used in the following rule files: +# - REQUEST-911-METHOD-ENFORCEMENT.conf +# - REQUEST-912-DOS-PROTECTION.conf +# - REQUEST-920-PROTOCOL-ENFORCEMENT.conf + +# HTTP methods that a client is allowed to use. +# Default: GET HEAD POST OPTIONS +# Example: for RESTful APIs, add the following methods: PUT PATCH DELETE +# Example: for WebDAV, add the following methods: CHECKOUT COPY DELETE LOCK +# MERGE MKACTIVITY MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK +# Uncomment this rule to change the default. +#SecAction \ +# "id:900200,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:'tx.allowed_methods=GET HEAD POST OPTIONS'" + +# Content-Types that a client is allowed to send in a request. +# Default: application/x-www-form-urlencoded|multipart/form-data|text/xml|\ +# application/xml|application/soap+xml|application/x-amf|application/json|\ +# application/octet-stream|text/plain +# Uncomment this rule to change the default. +SecAction \ + "id:900220,\ + phase:1,\ + nolog,\ + pass,\ + t:none,\ + setvar:'tx.allowed_request_content_type=application/x-php-serialized-rpc|application/x-www-form-urlencoded|multipart/form-data|text/xml|application/xml|application/soap+xml|application/x-amf|application/json|application/octet-stream|text/plain'" + +# Content-Types charsets that a client is allowed to send in a request. +# Default: utf-8|iso-8859-1|iso-8859-15|windows-1252 +# Uncomment this rule to change the default. +# Use "|" to separate multiple charsets like in the rule defining +# tx.allowed_request_content_type. +#SecAction \ +# "id:900270,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:'tx.allowed_request_content_type_charset=utf-8|iso-8859-1|iso-8859-15|windows-1252'" + +# Allowed HTTP versions. +# Default: HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 +# Example for legacy clients: HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 +# Note that some web server versions use 'HTTP/2', some 'HTTP/2.0', so +# we include both version strings by default. +# Uncomment this rule to change the default. +#SecAction \ +# "id:900230,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:'tx.allowed_http_versions=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0'" + +# Forbidden file extensions. +# Guards against unintended exposure of development/configuration files. +# Default: .asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .resources/ .resx/ .sql/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/ +# Example: .bak/ .config/ .conf/ .db/ .ini/ .log/ .old/ .pass/ .pdb/ .sql/ +# Uncomment this rule to change the default. +#SecAction \ +# "id:900240,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .axd/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .resources/ .resx/ .sql/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/'" + +# Forbidden request headers. +# Header names should be lowercase, enclosed by /slashes/ as delimiters. +# Blocking Proxy header prevents 'httpoxy' vulnerability: https://httpoxy.org +# Default: /proxy/ /lock-token/ /content-range/ /translate/ /if/ +# Uncomment this rule to change the default. +#SecAction \ +# "id:900250,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:'tx.restricted_headers=/proxy/ /lock-token/ /content-range/ /translate/ /if/'" + +# File extensions considered static files. +# Extensions include the dot, lowercase, enclosed by /slashes/ as delimiters. +# Used in DoS protection rule. See section "Anti-Automation / DoS Protection". +# Default: /.jpg/ /.jpeg/ /.png/ /.gif/ /.js/ /.css/ /.ico/ /.svg/ /.webp/ +# Uncomment this rule to change the default. +#SecAction \ +# "id:900260,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:'tx.static_extensions=/.jpg/ /.jpeg/ /.png/ /.gif/ /.js/ /.css/ /.ico/ /.svg/ /.webp/'" + + +# +# -- [[ HTTP Argument/Upload Limits ]] ----------------------------------------- +# +# Here you can define optional limits on HTTP get/post parameters and uploads. +# This can help to prevent application specific DoS attacks. +# +# These values are checked in REQUEST-920-PROTOCOL-ENFORCEMENT.conf. +# Beware of blocking legitimate traffic when enabling these limits. +# + +# Block request if number of arguments is too high +# Default: unlimited +# Example: 255 +# Uncomment this rule to set a limit. +#SecAction \ +# "id:900300,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.max_num_args=255" + +# Block request if the length of any argument name is too high +# Default: unlimited +# Example: 100 +# Uncomment this rule to set a limit. +#SecAction \ +# "id:900310,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.arg_name_length=100" + +# Block request if the length of any argument value is too high +# Default: unlimited +# Example: 400 +# Uncomment this rule to set a limit. +#SecAction \ +# "id:900320,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.arg_length=400" + +# Block request if the total length of all combined arguments is too high +# Default: unlimited +# Example: 64000 +# Uncomment this rule to set a limit. +#SecAction \ +# "id:900330,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.total_arg_length=64000" + +# Block request if the file size of any individual uploaded file is too high +# Default: unlimited +# Example: 1048576 +# Uncomment this rule to set a limit. +#SecAction \ +# "id:900340,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.max_file_size=1048576" + +# Block request if the total size of all combined uploaded files is too high +# Default: unlimited +# Example: 1048576 +# Uncomment this rule to set a limit. +#SecAction \ +# "id:900350,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.combined_file_sizes=1048576" + + +# +# -- [[ Easing In / Sampling Percentage ]] ------------------------------------- +# +# Adding the Core Rule Set to an existing productive site can lead to false +# positives, unexpected performance issues and other undesired side effects. +# +# It can be beneficial to test the water first by enabling the CRS for a +# limited number of requests only and then, when you have solved the issues (if +# any) and you have confidence in the setup, to raise the ratio of requests +# being sent into the ruleset. +# +# Adjust the percentage of requests that are funnelled into the Core Rules by +# setting TX.sampling_percentage below. The default is 100, meaning that every +# request gets checked by the CRS. The selection of requests, which are going +# to be checked, is based on a pseudo random number generated by ModSecurity. +# +# If a request is allowed to pass without being checked by the CRS, there is no +# entry in the audit log (for performance reasons), but an error log entry is +# written. If you want to disable the error log entry, then issue the +# following directive somewhere after the inclusion of the CRS +# (E.g., RESPONSE-999-EXCEPTIONS.conf). +# +# SecRuleUpdateActionById 901150 "nolog" +# +# ATTENTION: If this TX.sampling_percentage is below 100, then some of the +# requests will bypass the Core Rules completely and you lose the ability to +# protect your service with ModSecurity. +# +# Uncomment this rule to enable this feature: +# +#SecAction "id:900400,\ +# phase:1,\ +# pass,\ +# nolog,\ +# setvar:tx.sampling_percentage=100" + + +# +# -- [[ Project Honey Pot HTTP Blacklist ]] ------------------------------------ +# +# Optionally, you can check the client IP address against the Project Honey Pot +# HTTPBL (dnsbl.httpbl.org). In order to use this, you need to register to get a +# free API key. Set it here with SecHttpBlKey. +# +# Project Honeypot returns multiple different malicious IP types. +# You may specify which you want to block by enabling or disabling them below. +# +# Ref: https://www.projecthoneypot.org/httpbl.php +# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#wiki-SecHttpBlKey +# +# Uncomment these rules to use this feature: +# +#SecHttpBlKey XXXXXXXXXXXXXXXXX +#SecAction "id:900500,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.block_search_ip=1,\ +# setvar:tx.block_suspicious_ip=1,\ +# setvar:tx.block_harvester_ip=1,\ +# setvar:tx.block_spammer_ip=1" + + +# +# -- [[ GeoIP Database ]] ------------------------------------------------------ +# +# There are some rulesets that inspect geolocation data of the client IP address +# (geoLookup). The CRS uses geoLookup to implement optional country blocking. +# +# To use geolocation, we make use of the MaxMind GeoIP database. +# This database is not included with the CRS and must be downloaded. +# You should also update the database regularly, for instance every month. +# The CRS contains a tool to download it to util/geo-location/GeoIP.dat: +# util/upgrade.py --geoip +# +# This product includes GeoLite data created by MaxMind, available from: +# http://www.maxmind.com. +# +# Ref: http://blog.spiderlabs.com/2010/10/detecting-malice-with-modsecurity-geolocation-data.html +# Ref: http://blog.spiderlabs.com/2010/11/detecting-malice-with-modsecurity-ip-forensics.html +# +# Uncomment this rule to use this feature: +# +SecGeoLookupDB /usr/share/GeoIP/GeoIPCity.dat + + +# +# -=[ Block Countries ]=- +# +# Rules in the IP Reputation file can check the client against a list of high +# risk country codes. These countries have to be defined in the variable +# tx.high_risk_country_codes via their ISO 3166 two-letter country code: +# https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements +# +# If you are sure that you are not getting any legitimate requests from a given +# country, then you can disable all access from that country via this variable. +# The rule performing the test has the rule id 910100. +# +# This rule requires SecGeoLookupDB to be enabled and the GeoIP database to be +# downloaded (see the section "GeoIP Database" above.) +# +# By default, the list is empty. A list used by some sites was the following: +# setvar:'tx.high_risk_country_codes=UA ID YU LT EG RO BG TR RU PK MY CN'" +# +# Uncomment this rule to use this feature: +# +#SecAction \ +# "id:900600,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:'tx.high_risk_country_codes='" + + +# +# -- [[ Anti-Automation / DoS Protection ]] ------------------------------------ +# +# Optional DoS protection against clients making requests too quickly. +# +# When a client is making more than 100 requests (excluding static files) within +# 60 seconds, this is considered a 'burst'. After two bursts, the client is +# blocked for 600 seconds. +# +# Requests to static files are not counted towards DoS; they are listed in the +# 'tx.static_extensions' setting, which you can change in this file (see +# section "HTTP Policy Settings"). +# +# For a detailed description, see rule file REQUEST-912-DOS-PROTECTION.conf. +# +# Uncomment this rule to use this feature: +# +#SecAction \ +# "id:900700,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:'tx.dos_burst_time_slice=60',\ +# setvar:'tx.dos_counter_threshold=100',\ +# setvar:'tx.dos_block_timeout=600'" + + +# +# -- [[ Check UTF-8 encoding ]] ------------------------------------------------ +# +# The CRS can optionally check request contents for invalid UTF-8 encoding. +# We only want to apply this check if UTF-8 encoding is actually used by the +# site; otherwise it will result in false positives. +# +# Uncomment this rule to use this feature: +# +#SecAction \ +# "id:900950,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.crs_validate_utf8_encoding=1" + + +# +# -- [[ Blocking Based on IP Reputation ]] ------------------------------------ +# +# Blocking based on reputation is permanent in the CRS. Unlike other rules, +# which look at the indvidual request, the blocking of IPs is based on +# a persistent record in the IP collection, which remains active for a +# certain amount of time. +# +# There are two ways an individual client can become flagged for blocking: +# - External information (RBL, GeoIP, etc.) +# - Internal information (Core Rules) +# +# The record in the IP collection carries a flag, which tags requests from +# individual clients with a flag named IP.reput_block_flag. +# But the flag alone is not enough to have a client blocked. There is also +# a global switch named tx.do_reput_block. This is off by default. If you set +# it to 1 (=On), requests from clients with the IP.reput_block_flag will +# be blocked for a certain duration. +# +# Variables +# ip.reput_block_flag Blocking flag for the IP collection record +# ip.reput_block_reason Reason (= rule message) that caused to blocking flag +# tx.do_reput_block Switch deciding if we really block based on flag +# tx.reput_block_duration Setting to define the duration of a block +# +# It may be important to know, that all the other core rules are skipped for +# requests, when it is clear that they carry the blocking flag in question. +# +# Uncomment this rule to use this feature: +# +#SecAction \ +# "id:900960,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.do_reput_block=1" +# +# Uncomment this rule to change the blocking time: +# Default: 300 (5 minutes) +# +#SecAction \ +# "id:900970,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# setvar:tx.reput_block_duration=300" + + +# +# -- [[ Collection timeout ]] -------------------------------------------------- +# +# Set the SecCollectionTimeout directive from the ModSecurity default (1 hour) +# to a lower setting which is appropriate to most sites. +# This increases performance by cleaning out stale collection (block) entries. +# +# This value should be greater than or equal to: +# tx.reput_block_duration (see section "Blocking Based on IP Reputation") and +# tx.dos_block_timeout (see section "Anti-Automation / DoS Protection"). +# +# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#wiki-SecCollectionTimeout + +# Please keep this directive uncommented. +# Default: 600 (10 minutes) +SecCollectionTimeout 600 + + +# +# -- [[ Debug Mode ]] ---------------------------------------------------------- +# +# To enable rule development and debugging, CRS has an optional debug mode +# that does not block a request, but instead sends detection information +# back to the HTTP client. +# +# This functionality is currently only supported with the Apache web server. +# The Apache mod_headers module is required. +# +# In debug mode, the webserver inserts "X-WAF-Events" / "X-WAF-Score" +# response headers whenever a debug client makes a request. Example: +# +# # curl -v 'http://192.168.1.100/?foo=../etc/passwd' +# X-WAF-Events: TX:930110-OWASP_CRS/WEB_ATTACK/DIR_TRAVERSAL-REQUEST_URI, +# TX:930120-OWASP_CRS/WEB_ATTACK/FILE_INJECTION-ARGS:foo, +# TX:932160-OWASP_CRS/WEB_ATTACK/RCE-ARGS:foo +# X-WAF-Score: Total=15; sqli=0; xss=0; rfi=0; lfi=10; rce=5; php=0; http=0; ses=0 +# +# To enable debug mode, include the RESPONSE-981-DEBUG.conf file. +# This file resides in a separate folder, as it is not compatible with +# nginx and IIS. +# +# You must specify the source IP address/network where you will be running the +# tests from. The source IP will BYPASS all CRS blocking, and will be sent the +# response headers as specified above. Be careful to only list your private +# IP addresses/networks here. +# +# Tip: for regression testing of CRS or your own ModSecurity rules, you may +# be interested in using the OWASP CRS regression testing suite instead. +# View the file util/regression-tests/README for more information. +# +# Uncomment these rules, filling in your CRS path and the source IP address, +# to enable debug mode: +# +#Include /usr/share/modsecurity-crs/util/debug/RESPONSE-981-DEBUG.conf +#SecRule REMOTE_ADDR "@ipMatch 192.168.1.100" \ +# "id:900980,\ +# phase:1,\ +# nolog,\ +# pass,\ +# t:none,\ +# ctl:ruleEngine=DetectionOnly,\ +# setvar:tx.crs_debug_mode=1" + + +# +# -- [[ End of setup ]] -------------------------------------------------------- +# +# The CRS checks the tx.crs_setup_version variable to ensure that the setup +# has been loaded. If you are not planning to use this setup template, +# you must manually set the tx.crs_setup_version variable before including +# the CRS rules/* files. +# +# The variable is a numerical representation of the CRS version number. +# E.g., v3.0.0 is represented as 300. +# +SecAction \ + "id:900990,\ + phase:1,\ + nolog,\ + pass,\ + t:none,\ + setvar:tx.crs_setup_version=310" + + +# -- [[ Customization ]] ------------------------------------------------------- + +# triggers on user.profile for google login urls +SecRuleRemoveById 930120 diff --git a/roles/apache/templates/etc_modsecurity_modsecurity.conf.j2 b/roles/apache/templates/etc_modsecurity_modsecurity.conf.j2 new file mode 100644 index 0000000..03a02f4 --- /dev/null +++ b/roles/apache/templates/etc_modsecurity_modsecurity.conf.j2 @@ -0,0 +1,230 @@ +# {{ ansible_managed }} + +# -- Rule engine initialization ---------------------------------------------- + +# Enable ModSecurity, attaching it to every transaction. Use detection +# only to start with, because that minimises the chances of post-installation +# disruption. +# +SecRuleEngine {{ 'On' if apache_mod_security_enabled else 'DetectionOnly' }} + + +# -- Request body handling --------------------------------------------------- + +# Allow ModSecurity to access request bodies. If you don't, ModSecurity +# won't be able to see any POST parameters, which opens a large security +# hole for attackers to exploit. +# +SecRequestBodyAccess On + + +# Enable XML request body parser. +# Initiate XML Processor in case of xml content-type +# +SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\+|/)|text/)xml" \ + "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" + +# Enable JSON request body parser. +# Initiate JSON Processor in case of JSON content-type; change accordingly +# if your application does not use 'application/json' +# +SecRule REQUEST_HEADERS:Content-Type "application/json" \ + "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" + +# Maximum request body size we will accept for buffering. If you support +# file uploads then the value given on the first line has to be as large +# as the largest file you are willing to accept. The second value refers +# to the size of data, with files excluded. You want to keep that value as +# low as practical. +# +SecRequestBodyLimit 13107200 +SecRequestBodyNoFilesLimit 131072 + +# Store up to 128 KB of request body data in memory. When the multipart +# parser reachers this limit, it will start using your hard disk for +# storage. That is slow, but unavoidable. +# +SecRequestBodyInMemoryLimit 131072 + +# What do do if the request body size is above our configured limit. +# Keep in mind that this setting will automatically be set to ProcessPartial +# when SecRuleEngine is set to DetectionOnly mode in order to minimize +# disruptions when initially deploying ModSecurity. +# +SecRequestBodyLimitAction Reject + +# Verify that we've correctly processed the request body. +# As a rule of thumb, when failing to process a request body +# you should reject the request (when deployed in blocking mode) +# or log a high-severity alert (when deployed in detection-only mode). +# +SecRule REQBODY_ERROR "!@eq 0" \ +"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" + +# By default be strict with what we accept in the multipart/form-data +# request body. If the rule below proves to be too strict for your +# environment consider changing it to detection-only. You are encouraged +# _not_ to remove it altogether. +# +SecRule MULTIPART_STRICT_ERROR "!@eq 0" \ +"id:'200003',phase:2,t:none,log,deny,status:400, \ +msg:'Multipart request body failed strict validation: \ +PE %{REQBODY_PROCESSOR_ERROR}, \ +BQ %{MULTIPART_BOUNDARY_QUOTED}, \ +BW %{MULTIPART_BOUNDARY_WHITESPACE}, \ +DB %{MULTIPART_DATA_BEFORE}, \ +DA %{MULTIPART_DATA_AFTER}, \ +HF %{MULTIPART_HEADER_FOLDING}, \ +LF %{MULTIPART_LF_LINE}, \ +SM %{MULTIPART_MISSING_SEMICOLON}, \ +IQ %{MULTIPART_INVALID_QUOTING}, \ +IP %{MULTIPART_INVALID_PART}, \ +IH %{MULTIPART_INVALID_HEADER_FOLDING}, \ +FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'" + +# Did we see anything that might be a boundary? +# +SecRule MULTIPART_UNMATCHED_BOUNDARY "!@eq 0" \ +"id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" + +# PCRE Tuning +# We want to avoid a potential RegEx DoS condition +# +SecPcreMatchLimit 500000 +SecPcreMatchLimitRecursion 500000 + +# Some internal errors will set flags in TX and we will need to look for these. +# All of these are prefixed with "MSC_". The following flags currently exist: +# +# MSC_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded. +# +SecRule TX:/^MSC_/ "!@streq 0" \ + "id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'" + + +# -- Response body handling -------------------------------------------------- + +# Allow ModSecurity to access response bodies. +# You should have this directive enabled in order to identify errors +# and data leakage issues. +# +# Do keep in mind that enabling this directive does increases both +# memory consumption and response latency. +# +SecResponseBodyAccess On + +# Which response MIME types do you want to inspect? You should adjust the +# configuration below to catch documents but avoid static files +# (e.g., images and archives). +# +SecResponseBodyMimeType text/plain text/html text/xml + +# Buffer response bodies of up to 512 KB in length. +SecResponseBodyLimit 524288 + +# What happens when we encounter a response body larger than the configured +# limit? By default, we process what we have and let the rest through. +# That's somewhat less secure, but does not break any legitimate pages. +# +SecResponseBodyLimitAction ProcessPartial + + +# -- Filesystem configuration ------------------------------------------------ + +# The location where ModSecurity stores temporary files (for example, when +# it needs to handle a file upload that is larger than the configured limit). +# +# This default setting is chosen due to all systems have /tmp available however, +# this is less than ideal. It is recommended that you specify a location that's private. +# +SecTmpDir /tmp/ + +# The location where ModSecurity will keep its persistent data. This default setting +# is chosen due to all systems have /tmp available however, it +# too should be updated to a place that other users can't access. +# +SecDataDir /tmp/ + + +# -- File uploads handling configuration ------------------------------------- + +# The location where ModSecurity stores intercepted uploaded files. This +# location must be private to ModSecurity. You don't want other users on +# the server to access the files, do you? +# +#SecUploadDir /opt/modsecurity/var/upload/ + +# By default, only keep the files that were determined to be unusual +# in some way (by an external inspection script). For this to work you +# will also need at least one file inspection rule. +# +#SecUploadKeepFiles RelevantOnly + +# Uploaded files are by default created with permissions that do not allow +# any other user to access them. You may need to relax that if you want to +# interface ModSecurity to an external program (e.g., an anti-virus). +# +#SecUploadFileMode 0600 + + +# -- Debug log configuration ------------------------------------------------- + +# The default debug log configuration is to duplicate the error, warning +# and notice messages from the error log. +# +#SecDebugLog /opt/modsecurity/var/log/debug.log +#SecDebugLogLevel 3 + + +# -- Audit log configuration ------------------------------------------------- + +# Log the transactions that are marked by a rule, as well as those that +# trigger a server error (determined by a 5xx or 4xx, excluding 404, +# level response status codes). +# +SecAuditEngine RelevantOnly +SecAuditLogRelevantStatus "^(?:5|4(?!04))" + +# Log everything we know about a transaction. +SecAuditLogParts ABDEFHIJZ + +# Use a single file for logging. This is much easier to look at, but +# assumes that you will use the audit log only ocassionally. +# +SecAuditLogType Serial +SecAuditLogFormat JSON +SecAuditLog /var/log/apache2/modsec_audit.log +#SecAuditLog "|/usr/bin/socat -u - tcp:127.0.0.1:5172" + +# Specify the path for concurrent audit logging. +#SecAuditLogStorageDir /opt/modsecurity/var/audit/ + + +# -- Miscellaneous ----------------------------------------------------------- + +# Use the most commonly used application/x-www-form-urlencoded parameter +# separator. There's probably only one application somewhere that uses +# something else so don't expect to change this value. +# +SecArgumentSeparator & + +# Settle on version 0 (zero) cookies, as that is what most applications +# use. Using an incorrect cookie version may open your installation to +# evasion attacks (against the rules that examine named cookies). +# +SecCookieFormat 0 + +# Specify your Unicode Code Point. +# This mapping is used by the t:urlDecodeUni transformation function +# to properly map encoded data to your language. Properly setting +# these directives helps to reduce false positives and negatives. +# +SecUnicodeMapFile unicode.mapping 20127 + +# Improve the quality of ModSecurity by sharing information about your +# current ModSecurity version and dependencies versions. +# The following information will be shared: ModSecurity version, +# Web Server version, APR version, PCRE version, Lua version, Libxml2 +# version, Anonymous unique id for host. +SecStatusEngine On + diff --git a/roles/apache/templates/etc_systemd_system_apache2.service.d_local.conf.j2 b/roles/apache/templates/etc_systemd_system_apache2.service.d_local.conf.j2 new file mode 100644 index 0000000..a170c7f --- /dev/null +++ b/roles/apache/templates/etc_systemd_system_apache2.service.d_local.conf.j2 @@ -0,0 +1,4 @@ +# {{ ansible_managed }} + +[Service] +PrivateTmp=false diff --git a/roles/apache_php/defaults/main.yml b/roles/apache_php/defaults/main.yml new file mode 100644 index 0000000..8d4b4c0 --- /dev/null +++ b/roles/apache_php/defaults/main.yml @@ -0,0 +1,21 @@ +--- + +apache_phpfpm_php: "{{ 'php7.4' if ansible_distribution_release == 'focal' else 'php7.4' }}" + +apache_phpfpm_etc_dir: "{{ '/etc/php/7.4/fpm' if ansible_distribution_release == 'focal' else '/etc/php/7.4/fpm' }}" + +apache_phpfpm_max_workers: 30 +apache_phpfpm_timeout: 120 + +apache_phpfpm_php_settings: + short_open_tag: on + display_errors: off + +apache_phpfpm_php_admin_settings: + log_errors: on + error_log: /var/log/php-fpm.$pool.log + memory_limit: 512M + open_basedir: /srv/www:/var/www:/opt:/usr/share:/var/lib/{{ apache_phpfpm_php }}:/var/lib/php:/dev:/tmp:/var/log/kc:/var/spool/asterisk + +apache_phpfpm_xcache_size: 128M + diff --git a/roles/apache_php/handlers/main.yml b/roles/apache_php/handlers/main.yml new file mode 100644 index 0000000..60510e5 --- /dev/null +++ b/roles/apache_php/handlers/main.yml @@ -0,0 +1,5 @@ +--- + +- name: Reload PHP-FPM + service: name={{ apache_phpfpm_php }}-fpm state=reloaded + diff --git a/roles/apache_php/meta/main.yml b/roles/apache_php/meta/main.yml new file mode 100644 index 0000000..75d6199 --- /dev/null +++ b/roles/apache_php/meta/main.yml @@ -0,0 +1,4 @@ +--- + +dependencies: + - apache diff --git a/roles/apache_php/tasks/main.yml b/roles/apache_php/tasks/main.yml new file mode 100644 index 0000000..c4ed948 --- /dev/null +++ b/roles/apache_php/tasks/main.yml @@ -0,0 +1,65 @@ +--- + +- name: Install PHP packages + apt: + pkg: + - "{{ apache_phpfpm_php }}-fpm" + - php-apcu + # check_php-fpm nagios plugin dependencies: + - libany-moose-perl + - libjson-perl + - libjson-xs-perl + state: present + tags: packages + +- name: Disable Apache modules + apache2_module: name="{{ item }}" state=absent force=yes + with_items: + - "{{ apache_phpfpm_php }}" + notify: Restart Apache + tags: configs + +- name: Enable Apache modules + apache2_module: name="{{ item }}" state=present force=yes + with_items: + - proxy_fcgi + notify: Restart Apache + tags: configs + +- name: Ensure mod-php is not installed + apt: + pkg: + - libapache2-mod-{{ apache_phpfpm_php }} + - "{{ apache_phpfpm_php }}-cgi" + state: absent + purge: yes + notify: Restart Apache + tags: packages + +- name: Install Apache other configs + template: src="etc_apache2_conf-available_php-fpm.conf.j2" dest="/etc/apache2/conf-available/{{ apache_phpfpm_php }}-fpm.conf" + notify: Reload Apache + tags: configs + +- name: Install PHP-FPM pool config + template: src=etc_php_fpm_pool.d_www.conf.j2 dest={{ apache_phpfpm_etc_dir }}/pool.d/www.conf + notify: Reload PHP-FPM + tags: configs + +- name: Install the FGCI client script + template: src=usr_local_bin_fcgi-client dest=/usr/local/bin/fcgi-client mode=0755 + +- name: Enable PHP-FPM + file: dest=/etc/apache2/conf-enabled/{{ apache_phpfpm_php }}-fpm.conf src=../conf-available/{{ apache_phpfpm_php }}-fpm.conf state=link + notify: Reload Apache + tags: configs + +- name: Ensure PHP-FPM is running + service: name={{ apache_phpfpm_php }}-fpm state=started enabled=yes + tags: configs + +- name: Register the php-fpm service in Consul + template: dest=/etc/consul.d/service-php-fpm.hcl src=etc_consul.d_service-php-fpm.hcl.j2 + when: apache_consul_service + notify: Reload consul + tags: configs diff --git a/roles/apache_php/templates/etc_apache2_conf-available_php-fpm.conf.j2 b/roles/apache_php/templates/etc_apache2_conf-available_php-fpm.conf.j2 new file mode 100644 index 0000000..a5e9b0e --- /dev/null +++ b/roles/apache_php/templates/etc_apache2_conf-available_php-fpm.conf.j2 @@ -0,0 +1,12 @@ +# {{ ansible_managed }} + + + ProxySet max={{ apache_phpfpm_max_workers // 2 - 1 }} + ProxySet timeout={{ apache_phpfpm_timeout }} + ProxySet retry=0 + + + + SetEnvIf ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1 + SetHandler "proxy:fcgi://{{ apache_phpfpm_php }}-fpm" + diff --git a/roles/apache_php/templates/etc_consul.d_service-php-fpm.hcl.j2 b/roles/apache_php/templates/etc_consul.d_service-php-fpm.hcl.j2 new file mode 100644 index 0000000..099b631 --- /dev/null +++ b/roles/apache_php/templates/etc_consul.d_service-php-fpm.hcl.j2 @@ -0,0 +1,6 @@ +# {{ ansible_managed }} + +service { + name = "php-fpm" + port = 443 +} diff --git a/roles/apache_php/templates/etc_default_prometheus-phpfpm-exporter.j2 b/roles/apache_php/templates/etc_default_prometheus-phpfpm-exporter.j2 new file mode 100644 index 0000000..1d424b8 --- /dev/null +++ b/roles/apache_php/templates/etc_default_prometheus-phpfpm-exporter.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +ARGS= \ + --phpfpm.socket-directories=/run/php \ + --phpfpm.status-path=/_fpm/status diff --git a/roles/apache_php/templates/etc_nagios_nrpe.d_check_php-fpm.cfg.j2 b/roles/apache_php/templates/etc_nagios_nrpe.d_check_php-fpm.cfg.j2 new file mode 100644 index 0000000..ed4f917 --- /dev/null +++ b/roles/apache_php/templates/etc_nagios_nrpe.d_check_php-fpm.cfg.j2 @@ -0,0 +1,2 @@ +# {{ ansible_managed }} +command[check_php-fpm]={{ nagios_nrpe_tools_dir }}/plugins/check_php-fpm -s /run/php/{{ apache_phpfpm_php }}-fpm.sock -w active_workers:{{ (apache_phpfpm_max_workers * 80 / 100)|int }} -c active_workers:{{ (apache_phpfpm_max_workers * 90 / 100)|int }} diff --git a/roles/apache_php/templates/etc_php_fpm_pool.d_www.conf.j2 b/roles/apache_php/templates/etc_php_fpm_pool.d_www.conf.j2 new file mode 100644 index 0000000..d58ddb3 --- /dev/null +++ b/roles/apache_php/templates/etc_php_fpm_pool.d_www.conf.j2 @@ -0,0 +1,85 @@ +; {{ ansible_managed }} + +[www] + +;prefix = /path/to/pools/$pool + +user = www-data +group = www-data + +listen = /run/php/{{ apache_phpfpm_php }}-fpm.sock +listen.owner = www-data +listen.group = www-data +listen.mode = 0660 +;listen.allowed_clients = 127.0.0.1 + +; process.priority = -19 + +pm = dynamic +pm.max_children = {{ apache_phpfpm_max_workers }} +pm.start_servers = 3 +pm.min_spare_servers = 2 +pm.max_spare_servers = 7 +;pm.process_idle_timeout = 10s +pm.max_requests = {{ apache_phpfpm_max_requests | default(50000) }} + +pm.status_path = /_fpm/status +ping.path = /_fpm/ping +ping.response = pong + +;access.log = /var/log/{{ apache_phpfpm_php }}-fpm.$pool.access.log +;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%" +;slowlog = /var/log/{{ apache_phpfpm_php }}-fpm.$pool.slow.log +;request_slowlog_timeout = 10s + +;request_terminate_timeout = 0 + +;rlimit_files = 1024 +;rlimit_core = 0 + +;chroot = +chdir = / + +;catch_workers_output = yes +;clear_env = no + +;security.limit_extensions = .php .php3 .php4 .php5 .php7 + +;env[HOSTNAME] = $HOSTNAME +;env[PATH] = /usr/local/bin:/usr/bin:/bin +;env[TMP] = /tmp +;env[TMPDIR] = /tmp +;env[TEMP] = /tmp + +; Additional php.ini defines, specific to this pool of workers. These settings +; overwrite the values previously defined in the php.ini. The directives are the +; same as the PHP SAPI: +; php_value/php_flag - you can set classic ini defines which can +; be overwritten from PHP call 'ini_set'. +; php_admin_value/php_admin_flag - these directives won't be overwritten by +; PHP call 'ini_set' +; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no. + +; Defining 'extension' will load the corresponding shared extension from +; extension_dir. Defining 'disable_functions' or 'disable_classes' will not +; overwrite previously defined php.ini values, but will append the new value +; instead. + +; Note: path INI options can be relative and will be expanded with the prefix +; (pool, global or /usr) + +{% for key, value in apache_phpfpm_php_admin_settings|dictsort %} +{% if value in (True,False) %} +php_admin_flag[{{ key }}] = {{ 'on' if value else 'off' }} +{% else %} +php_admin_value[{{ key }}] = {{ value }} +{% endif %} +{% endfor %} + +{% for key, value in apache_phpfpm_php_settings|dictsort %} +{% if value in (True,False) %} +php_flag[{{ key }}] = {{ 'on' if value else 'off' }} +{% else %} +php_value[{{ key }}] = {{ value }} +{% endif %} +{% endfor %} diff --git a/roles/apache_php/templates/etc_php_mods-available_xcache.ini.j2 b/roles/apache_php/templates/etc_php_mods-available_xcache.ini.j2 new file mode 100644 index 0000000..778543a --- /dev/null +++ b/roles/apache_php/templates/etc_php_mods-available_xcache.ini.j2 @@ -0,0 +1,88 @@ +; {{ ansible_managed }} +; configuration for php Xcache module + +[xcache-common] +;; non-Windows example: +extension = xcache.so +;; Windows example: +; extension = php_xcache.dll + +[xcache.admin] +xcache.admin.enable_auth = On +; Configure this to use admin pages +; xcache.admin.user = "mOo" +; xcache.admin.pass = md5($your_password) +; xcache.admin.pass = "" +xcache.admin.user = "admin" +xcache.admin.pass = "726be9b7e6dea1ed28c70800d68be36c" + +[xcache] +; ini only settings, all the values here is default unless explained + +; select low level shm implemenation +xcache.shm_scheme = "mmap" +; to disable: xcache.size=0 +; to enable : xcache.size=64M etc (any size > 0) and your system mmap allows +xcache.size = {{ apache_phpfpm_xcache_size }} +; set to cpu count (cat /proc/cpuinfo |grep -c processor) +xcache.count = 2 +; just a hash hints, you can always store count(items) > slots +xcache.slots = 8K +; ttl of the cache item, 0=forever +xcache.ttl = 0 +; interval of gc scanning expired items, 0=no scan, other values is in seconds +xcache.gc_interval = 0 + +; same as aboves but for variable cache +xcache.var_size = 64M +xcache.var_count = 1 +xcache.var_slots = 8K +; default value for $ttl parameter of xcache_*() functions +xcache.var_ttl = 0 +; hard limit ttl that cannot be exceed by xcache_*() functions. 0=unlimited +xcache.var_maxttl = 0 +xcache.var_gc_interval = 300 + +; mode:0, const string specified by xcache.var_namespace +; mode:1, $_SERVER[xcache.var_namespace] +; mode:2, uid or gid (specified by xcache.var_namespace) +xcache.var_namespace_mode = 0 +xcache.var_namespace = "" + +; N/A for /dev/zero +xcache.readonly_protection = Off +; for *nix, xcache.mmap_path is a file path, not directory. (auto create/overwrite) +; Use something like "/tmp/xcache" instead of "/dev/*" if you want to turn on ReadonlyProtection +; different process group of php won't share the same /tmp/xcache +; for win32, xcache.mmap_path=anonymous map name, not file path +xcache.mmap_path = "/dev/zero" + + +; Useful when XCache crash. leave it blank(disabled) or "/tmp/phpcore/" (writable by php) +xcache.coredump_directory = "" +; Windows only. leave it as 0 (default) until you're told by XCache dev +xcache.coredump_type = 0 + +; disable cache after crash +xcache.disable_on_crash = Off + +; enable experimental documented features for each release if available +xcache.experimental = Off + +; per request settings. can ini_set, .htaccess etc +xcache.cacher = On +xcache.stat = On +xcache.optimizer = Off + +[xcache.coverager] +; enabling this feature will impact performance +; enabled only if xcache.coverager == On && xcache.coveragedump_directory == "non-empty-value" + +; per request settings. can ini_set, .htaccess etc +; enable coverage data collecting and xcache_coverager_start/stop/get/clean() functions +xcache.coverager = Off +xcache.coverager_autostart = On + +; set in php ini file only +; make sure it's readable (open_basedir is checked) by coverage viewer script +xcache.coveragedump_directory = "" diff --git a/roles/apache_php/templates/usr_local_bin_fcgi-client b/roles/apache_php/templates/usr_local_bin_fcgi-client new file mode 100644 index 0000000..2f691f1 --- /dev/null +++ b/roles/apache_php/templates/usr_local_bin_fcgi-client @@ -0,0 +1,46 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Pod::Usage; +use Getopt::Long; +use IO::Socket; +use IO::Socket::UNIX; +use lib '/usr/local/lib/nagios/plugins'; +use FCGI::Client; + +GetOptions( + 'h|help' => \my $help, +) or pod2usage(); +pod2usage() if $help; +pod2usage() if @ARGV < 2; +my ($fcgi_file, $uri, $query_string) = @ARGV; + +my $sock = IO::Socket::UNIX->new( + Type => SOCK_STREAM(), + Peer => $fcgi_file +) or die $!; + +my $client = FCGI::Client::Connection->new( sock => $sock ); +my ( $stdout, $stderr ) = $client->request( + +{ + REQUEST_METHOD => 'GET', + REQUEST_URI => $uri, + SCRIPT_FILENAME => "/a/b/c$uri", + SCRIPT_NAME => $uri, + QUERY_STRING => $query_string || '', + }, + '' +); +print STDERR $stderr if $stderr; +print $stdout; + +__END__ + +=head1 NAME + +fcgi-client - + +=head1 SYNOPSIS + + $ fcgi-client foo.fcgi URI [foo=bar&hoge=fuga] diff --git a/roles/consul/defaults/main.yml b/roles/consul/defaults/main.yml new file mode 100644 index 0000000..e405aad --- /dev/null +++ b/roles/consul/defaults/main.yml @@ -0,0 +1,19 @@ +--- + +consul_version: 1.8.5 +consul_url: https://releases.hashicorp.com/consul/{{ consul_version }}/consul_{{ consul_version }}_{{ ansible_system|lower }}_{{ ansible_userspace_architecture|replace('x86_64', 'amd64') }}.zip + +consul_data_dir: /opt/consul +consul_config_dir: /etc/consul.d +consul_server: true +consul_bootstrap_expect: 2 +consul_wan_peers: [] +consul_encrypt_key: eRhnp22+c0bkV0wPolk6Mw== + +consul_expose_apis: no +consul_client_addr: "{{ '0.0.0.0' if consul_expose_apis else '127.0.0.1' }}" + +consul_stub_mode: no +consul_dns_forwarders: [] + +consul_firewall: yes diff --git a/roles/consul/handlers/main.yml b/roles/consul/handlers/main.yml new file mode 100644 index 0000000..19c2b71 --- /dev/null +++ b/roles/consul/handlers/main.yml @@ -0,0 +1,7 @@ +--- + +- name: Restart consul + service: name=consul state=restarted + +- name: Reload consul + service: name=consul state=reloaded diff --git a/roles/consul/meta/main.yml b/roles/consul/meta/main.yml new file mode 100644 index 0000000..2d4e3af --- /dev/null +++ b/roles/consul/meta/main.yml @@ -0,0 +1,6 @@ +--- + +dependencies: + - role: firewall + when: consul_firewall + - network diff --git a/roles/consul/tasks/main.yml b/roles/consul/tasks/main.yml new file mode 100644 index 0000000..72af513 --- /dev/null +++ b/roles/consul/tasks/main.yml @@ -0,0 +1,122 @@ +--- + + +- name: Ensure the consul user exists + user: + name: consul + home: '{{ consul_data_dir }}' + system: yes + groups: ssl-cert + append: yes + shell: /bin/false + createhome: no + state: present + tags: packages + +- name: Ensure the consul config dir exists + file: + dest: /etc/consul.d + owner: root + group: consul + mode: 0750 + state: directory + tags: packages + +- name: Ensure the consul data dir exists + file: + dest: /opt/consul + owner: consul + group: consul + mode: 0750 + state: directory + tags: packages + +- name: Remove old consul config + file: + dest: /etc/consul.d/00-base_config.json + state: absent + tags: configs + +- name: Install consul config + template: + dest: /etc/consul.d/00-base_config.hcl + src: etc_consul.d_00-base_config.hcl.j2 + #validate: 'consul validate %s' + mode: 0640 + owner: root + group: consul + notify: Restart consul + tags: + - configs + - consul.conf + +- name: Install consul service config + template: + dest: /etc/default/consul + src: etc_default_consul.j2 + when: not consul_stub_mode + notify: Restart consul + tags: configs + +- name: Install consul service + template: + dest: /etc/systemd/system/consul.service + src: etc_systemd_system_consul.service.j2 + when: not consul_stub_mode + notify: Restart consul + tags: configs + +- name: Enable the consul service + systemd: + name: consul + state: "{{ 'started' if not consul_stub_mode else 'stopped' }}" + enabled: "{{ not consul_stub_mode }}" + daemon_reload: yes + when: not consul_stub_mode + tags: configs + +- name: Remove the master token if present + lineinfile: + dest: /root/.bashrc + regexp: '^export CONSUL_HTTP_TOKEN=.*' + state: absent + when: consul_acl_master_token is defined and consul_acl_master_token and not consul_stub_mode + tags: configs + +- name: Install packages needed by consul-tag + apt: + pkg: + - python3 + - python3-requests + state: present + when: not consul_stub_mode + tags: consul-tag + +- name: Install consul-tag + template: + dest: /usr/local/bin/consul-tag + src: usr_local_bin_consul-tag.j2 + mode: 0755 + owner: root + group: root + when: not consul_stub_mode + tags: consul-tag + +- name: Remove old firewall config + file: dest=/etc/firewall/rules-v4.d/28_consul.sh state=absent + when: consul_firewall and not consul_stub_mode + notify: Restart firewall + tags: + - configs + - firewall + +- name: Install the consul firewall config + template: + dest: /etc/firewall/rules-v4.d/78_consul.sh + src: etc_firewall_rules-v4.d_78_consul.sh.j2 + mode: 0600 + when: consul_firewall + notify: Restart firewall + tags: + - configs + - firewall diff --git a/roles/consul/templates/etc_consul.d_00-base_config.hcl.j2 b/roles/consul/templates/etc_consul.d_00-base_config.hcl.j2 new file mode 100644 index 0000000..bff5f38 --- /dev/null +++ b/roles/consul/templates/etc_consul.d_00-base_config.hcl.j2 @@ -0,0 +1,83 @@ +# {{ ansible_managed }} + +# Logging +enable_syslog = true +log_level = "INFO" +disable_update_check = true + +# Basics +data_dir = "{{ consul_data_dir }}" +datacenter = "{{ datacenter_id }}" +server = {{ 'false' if consul_server else 'true' }} +ui = true + +# Network +{% if consul_bootstrap_expect > 0 %} +encrypt = "{{ consul_encrypt_key }}" +{% endif %} +client_addr = "{{ consul_client_addr }}" +bind_addr = "{{ network_private_ip }}" +advertise_addr = "{{ network_private_ip }}" +retry_join = [ +{% for peer in consul_servers if peer != ansible_hostname and hostvars[peer].datacenter_id == datacenter_id %} + "{{ hostvars[peer].network_private_ip }}"{{ ',' if not loop.last else '' }} +{% endfor %} +] +{% if consul_server %} +{% if consul_bootstrap_expect > 0 %} +bootstrap_expect = {{ consul_bootstrap_expect }} +{% endif %} +rejoin_after_leave = true +retry_join_wan = [ +{% for peer in consul_servers if hostvars[peer].datacenter_id != datacenter_id %} + "{{ hostvars[peer].network_private_ip }}"{{ ',' if not loop.last else '' }} +{% endfor %} +] +{% endif %} + +# TLS +#ports { +# https = 8501 +#} +#key_file = "/etc/letsencrypt/live/{{ ansible_hostname }}.maruntiel.net/privkey1.pem" +#cert_file = "/etc/letsencrypt/live/{{ ansible_hostname }}.maruntiel.net/fullchain1.pem" +#ca_file = "/etc/letsencrypt/live/{{ ansible_hostname }}.maruntiel.net/chain1.pem" +#verify_incoming = true +#verify_outgoing = true +#tls_min_version = "tls12" + +# Features +enable_script_checks = true +disable_remote_exec = true + +# ACLs +#{% if consul_acl_datacenter is defined and consul_acl_datacenter %} +#acl_datacenter = "{{ consul_acl_datacenter }}" +#acl_default_policy = "deny" +#acl_down_policy = "extend-cache" +#acl_agent_token = "{{ consul_acl_agent_token }}" +#acl_token = "{{ consul_acl_token }}" +#{% if datacenter_id != consul_acl_datacenter %} +#acl_replication_token = "{{ consul_acl_replication_token | default(consul_acl_master_token) }}" +#{% endif %} +#{% endif %} + +# DNS +dns_config { + node_ttl = "60s" + service_ttl { + "*" = "15s" + } +} + +# Metadata +node_meta { + architecture = "{{ ansible_userspace_architecture }}" + product_name = "{{ ansible_system_vendor|replace(' Inc.', '') }} {{ ansible_product_name }}" + virtualization_role = "{{ ansible_virtualization_role }}" +} + +# Consul Stats +telemetry { + disable_hostname = true +} diff --git a/roles/consul/templates/etc_default_consul.j2 b/roles/consul/templates/etc_default_consul.j2 new file mode 100644 index 0000000..a563586 --- /dev/null +++ b/roles/consul/templates/etc_default_consul.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% if consul_ui_beta|default(False) %} +ui_config=enable +{% endif %} diff --git a/roles/consul/templates/etc_firewall_rules-v4.d_78_consul.sh.j2 b/roles/consul/templates/etc_firewall_rules-v4.d_78_consul.sh.j2 new file mode 100644 index 0000000..932af01 --- /dev/null +++ b/roles/consul/templates/etc_firewall_rules-v4.d_78_consul.sh.j2 @@ -0,0 +1,25 @@ +# {{ ansible_managed }} + +{% if not consul_stub_mode %} +{% if consul_server %} +iptables -A internal-in -p tcp --dport 8300:8302 -m comment --comment "consul" -j ACCEPT +iptables -A internal-in -p udp --dport 8300:8302 -m comment --comment "consul" -j ACCEPT +{% else %} +{% for ip in datacenter_local_networks %} +iptables -A internal-in -s {{ ip }} -p tcp --dport 8300:8302 -m comment --comment "consul" -j ACCEPT +iptables -A internal-in -s {{ ip }} -p udp --dport 8300:8302 -m comment --comment "consul" -j ACCEPT +{% endfor %} +{% endif %} + +{% if consul_expose_apis %} +iptables -A internal-in -p tcp --dport 8500:8501 -m comment --comment "consul-http" -j ACCEPT +iptables -A internal-in -p tcp --dport 8600 -m comment --comment "consul-dns" -j ACCEPT +iptables -A internal-in -p udp --dport 8600 -m comment --comment "consul-dns" -j ACCEPT +{% endif %} + +iptables -A internal-out -p tcp --dport 8300:8302 -m comment --comment "consul" -j ACCEPT +iptables -A internal-out -p udp --dport 8300:8302 -m comment --comment "consul" -j ACCEPT +iptables -A internal-out -p tcp --dport 8500:8501 -m comment --comment "consul-http" -j ACCEPT +iptables -A internal-out -p tcp --dport 8600 -m comment --comment "consul-dns" -j ACCEPT +iptables -A internal-out -p udp --dport 8600 -m comment --comment "consul-dns" -j ACCEPT +{% endif %} diff --git a/roles/consul/templates/etc_systemd_system_consul.service.j2 b/roles/consul/templates/etc_systemd_system_consul.service.j2 new file mode 100644 index 0000000..541795d --- /dev/null +++ b/roles/consul/templates/etc_systemd_system_consul.service.j2 @@ -0,0 +1,20 @@ +# {{ ansible_managed }} + +[Unit] +Description=Consul Agent +Requires=network-online.target +After=network-online.target +RequiresMountsFor={{ consul_data_dir }} + +[Service] +EnvironmentFile=-/etc/default/consul +ExecStart=/usr/local/bin/consul agent $CONSUL_FLAGS -config-dir={{ consul_config_dir }} -config-dir={{ consul_data_dir }} +ExecReload=/bin/kill -HUP $MAINPID +KillSignal=SIGINT +StandardOutput=null +User=consul +Group=consul +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/roles/consul/templates/usr_local_bin_consul-tag.j2 b/roles/consul/templates/usr_local_bin_consul-tag.j2 new file mode 100644 index 0000000..07c4620 --- /dev/null +++ b/roles/consul/templates/usr_local_bin_consul-tag.j2 @@ -0,0 +1,71 @@ +#!/usr/bin/python3 +# {{ ansible_managed }} + +import os +import sys +import requests + +CONSUL_API = 'http://localhost:8500' + + +def get_service(sess, service_id): + r = sess.get(CONSUL_API + '/v1/agent/services', timeout=2) + r.raise_for_status() + services = r.json() + + for svc in services.values(): + if svc['ID'] == service_id: + return svc + + return None + + +def change_service_tags(service, tags_to_add, tags_to_remove): + with requests.Session() as sess: + sess.headers = {'X-Consul-Token': os.getenv('CONSUL_HTTP_TOKEN')} + + svc = get_service(sess, service) + if svc: + new_tags = (set(svc.get('Tags', [])) | tags_to_add) - tags_to_remove + new_svc = { + 'ID': svc['ID'], + 'Name': svc['Service'], + 'Address': svc.get('Address', ''), + 'Port': svc.get('Port', 0), + 'Meta': svc.get('Meta', {}), + 'Tags': sorted(list(new_tags)), + 'EnableTagOverride': svc.get('EnableTagOverride', False), + } + for k, v in new_svc.items(): + print('{} = {}'.format(k, v)) + r = sess.put(CONSUL_API + '/v1/agent/service/register', json=new_svc, timeout=2) + r.raise_for_status() + + +def main(argv): + if len(argv) < 3: + print("Usage: consul-tag service +tag -tag...") + return 1 + + service = argv[1] + tags_to_add = set() + tags_to_remove = set() + for tag in argv[2:]: + if tag.startswith('-'): + tags_to_remove.add(tag[1:]) + elif tag.startswith('+'): + tags_to_add.add(tag[1:]) + else: + tags_to_add.add(tag) + + try: + change_service_tags(service, tags_to_add, tags_to_remove) + except Exception as exc: + print("Error: {}".format(exc)) + return 2 + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/roles/firewall/defaults/main.yml b/roles/firewall/defaults/main.yml new file mode 100644 index 0000000..f22bf04 --- /dev/null +++ b/roles/firewall/defaults/main.yml @@ -0,0 +1,28 @@ +--- + +firewall_enabled: yes +firewall_standard_rules: yes +firewall_log_prefix: "FW:" +firewall_whitelist_ip: [] +firewall_whitelist_ipv6: [] +firewall_late_whitelist_ip: [] +firewall_late_whitelist_ipv6: [] + +firewall_input_default_drop: true +firewall_output_default_drop: true +firewall_output_whitelist_domains: [] +firewall_output_whitelist_ipv4: [] +firewall_output_whitelist_ipv6: [] +firewall_output_learning: false + +firewall_whitelist_office_ip: [] +firewall_whitelist_office_ports: [] + +firewall_ssh_acl: [] +firewall_ssh_acl_extra: [] +firewall_influx_acl: [] +firewall_influx_acl_extra: [] +firewall_allow_internal_dns: true + +firewall_custom_ipv4_rules: "" +firewall_custom_ipv6_rules: "" diff --git a/roles/firewall/handlers/main.yml b/roles/firewall/handlers/main.yml new file mode 100644 index 0000000..e88e652 --- /dev/null +++ b/roles/firewall/handlers/main.yml @@ -0,0 +1,4 @@ +- name: Restart firewall + service: + name: firewall + state: restarted diff --git a/roles/firewall/tasks/main.yml b/roles/firewall/tasks/main.yml new file mode 100644 index 0000000..f0ae691 --- /dev/null +++ b/roles/firewall/tasks/main.yml @@ -0,0 +1,122 @@ +--- + +- name: Ensure iptables packages are installed + apt: + pkg: + - iptables + - ipset + - conntrack + - ipv6calc # Required by update-firewall-outbound + state: present + when: firewall_run is not defined + tags: packages + +- name: Install the firewall init.d script + template: + dest: /etc/init.d/firewall + src: etc_init.d_firewall.j2 + mode: 0755 + owner: root + group: root + when: firewall_run is not defined and firewall_enabled + tags: + - configs + - firewall + +- name: Enable the firewall init.d script + service: + name: firewall + enabled: yes + when: firewall_run is not defined and firewall_enabled + tags: + - configs + - firewall + +- name: Ensure the rules directories exist + file: + path: "/etc/firewall/{{ item }}" + state: directory + owner: root + group: root + mode: 0700 + with_items: + - rules-v4.d + - rules-v6.d + when: firewall_run is not defined + tags: + - configs + - firewall + +- name: Install the firewall configs + template: dest=/etc/firewall/{{ item }} src={{ item }}.j2 mode=0600 + with_items: + - rules-v4.d/10_conntrack.sh + - rules-v4.d/15_local.sh + - rules-v4.d/17_monitoring.sh + - rules-v4.d/18_internal.sh + - rules-v4.d/20_whitelist.sh + - rules-v4.d/22_ssh.sh + - rules-v4.d/24_influxdb.sh + - rules-v4.d/33_mariadb.sh + - rules-v4.d/85_whitelist.sh + - rules-v4.d/90_allow_outbound.sh + - rules-v4.d/90_drop_all.sh + - rules-v4.d/95_fail2ban.sh + + - rules-v6.d/10_conntrack.sh + - rules-v6.d/15_local.sh + - rules-v6.d/18_internal.sh + - rules-v6.d/20_whitelist.sh + - rules-v4.d/24_influxdb.sh + - rules-v4.d/33_mariadb.sh + - rules-v4.d/85_whitelist.sh + - rules-v6.d/90_allow_outbound.sh + - rules-v6.d/90_drop_all.sh + when: firewall_run is not defined and firewall_enabled and firewall_standard_rules + notify: Restart firewall + tags: + - configs + - firewall + +- name: Install the extra firewall configs + template: dest=/etc/firewall/{{ item }} src={{ item }}.j2 mode=0600 + with_items: + - rules-v4.d/50_custom.sh + - rules-v6.d/50_custom.sh + when: firewall_run is not defined and firewall_enabled and (firewall_custom_ipv4_rules or firewall_custom_ipv6_rules) + notify: Restart firewall + tags: + - configs + - firewall + +- name: Install the firewall outbound ACLs + template: dest=/etc/firewall/outbound_whitelist.acl src=etc_firewall_outbound_whitelist.acl.j2 mode=0600 + when: firewall_run is not defined and firewall_enabled and firewall_output_whitelist_domains + notify: Restart firewall + tags: + - configs + - firewall + - whitelists + +- name: Remove obsolete configs + file: dest=/etc/firewall/{{ item }} state=absent + with_items: + - rules-v4.d/19_monitoring.sh + when: firewall_run is not defined and firewall_enabled + notify: Restart firewall + tags: + - configs + - firewall + +- name: Install the firewall outbound update script + template: dest=/usr/sbin/update-firewall-outbound src=usr_sbin_update-firewall-outbound.j2 mode=0700 + when: firewall_run is not defined and firewall_enabled and firewall_output_whitelist_domains + notify: Restart firewall + tags: + - firewall + - scripts + - whitelists + +- set_fact: + firewall_run: true + when: firewall_run is not defined diff --git a/roles/firewall/templates/etc_firewall_outbound_whitelist.acl.j2 b/roles/firewall/templates/etc_firewall_outbound_whitelist.acl.j2 new file mode 100644 index 0000000..2dcfcbf --- /dev/null +++ b/roles/firewall/templates/etc_firewall_outbound_whitelist.acl.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% for name in firewall_output_whitelist_domains %} +{{ name }} +{% endfor %} diff --git a/roles/firewall/templates/etc_init.d_firewall.j2 b/roles/firewall/templates/etc_init.d_firewall.j2 new file mode 100644 index 0000000..c762f35 --- /dev/null +++ b/roles/firewall/templates/etc_init.d_firewall.j2 @@ -0,0 +1,133 @@ +#!/bin/sh + +# {{ ansible_managed }} + +### BEGIN INIT INFO +# Provides: firewall +# Required-Start: $network +# Required-Stop: $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Set up iptables rules +# Description: Loads current iptables rules from/to /etc/firewall +### END INIT INFO + +. /lib/lsb/init-functions + +PATH="/sbin:$PATH" + +rc=0 + +flush_ipv4() +{ + for chain in INPUT FORWARD OUTPUT; do + iptables -P $chain ACCEPT + done + for table in $(iptables-save | awk '/^\*/ { print substr($1,2) }'); do + iptables -t $table -F + iptables -t $table -X + iptables -t $table -Z + done +} + +flush_ipv6() +{ + for chain in INPUT FORWARD OUTPUT; do + ip6tables -P $chain ACCEPT + done + for table in $(ip6tables-save | awk '/^\*/ { print substr($1,2) }'); do + ip6tables -t $table -F + ip6tables -t $table -X + ip6tables -t $table -Z + done +} + +load_rules() +{ + log_action_begin_msg "Loading iptables rules" + + # load IPv4 rules + if [ ! -d /etc/firewall/rules-v4.d ]; then + log_action_cont_msg " skipping IPv4 (no rules to load)" + else + log_action_cont_msg " IPv4" + + flush_ipv4 + for frag in /etc/firewall/rules-v4.d/*.sh; do + if [ -r "$frag" ]; then + . "$frag" + if [ $? -ne 0 ]; then + rc=1 + fi + fi + done + fi + + # load IPv6 rules + if [ ! -d /etc/firewall/rules-v6.d ]; then + log_action_cont_msg " skipping IPv6 (no rules to load)" + else + log_action_cont_msg " IPv6" + + flush_ipv6 + for frag in /etc/firewall/rules-v6.d/*.sh; do + if [ -r "$frag" ]; then + . "$frag" + if [ $? -ne 0 ]; then + rc=1 + fi + fi + done + fi + + log_action_end_msg $rc +} + +flush_rules() +{ + log_action_begin_msg "Flushing rules" + + if [ ! -f /proc/net/ip_tables_names ]; then + log_action_cont_msg " skipping IPv4" + else + log_action_cont_msg " IPv4" + flush_ipv4 + fi + + if [ ! -f /proc/net/ip6_tables_names ]; then + log_action_cont_msg " skipping IPv6" + else + log_action_cont_msg " IPv6" + flush_ipv6 + fi + + log_action_end_msg 0 +} + +case "$1" in +start|restart|reload|force-reload) + load_rules + ;; +stop) + echo "Automatic flushing disabled, use \"flush\" instead of \"stop\"" + ;; +flush) + flush_rules + ;; +debug) + iptables() { echo "iptables $@"; } + ip6tables() { echo "ip6tables $@"; } + ipset() { echo "ipset $@"; } + log_action_begin_msg() { :; } + log_action_cont_msg() { :; } + log_action_end_msg() { :; } + + load_rules + ;; +*) + echo "Usage: $0 {start|restart|reload|force-reload|save|flush}" >&2 + exit 1 + ;; +esac + +exit $rc diff --git a/roles/firewall/templates/rules-v4.d/10_conntrack.sh.j2 b/roles/firewall/templates/rules-v4.d/10_conntrack.sh.j2 new file mode 100644 index 0000000..041db39 --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/10_conntrack.sh.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +# Allow established connections +iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT +iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT diff --git a/roles/firewall/templates/rules-v4.d/15_local.sh.j2 b/roles/firewall/templates/rules-v4.d/15_local.sh.j2 new file mode 100644 index 0000000..d9e3fe1 --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/15_local.sh.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +# Allow all traffic from localhost +iptables -A INPUT -i lo -j ACCEPT +iptables -A OUTPUT -o lo -j ACCEPT diff --git a/roles/firewall/templates/rules-v4.d/17_monitoring.sh.j2 b/roles/firewall/templates/rules-v4.d/17_monitoring.sh.j2 new file mode 100644 index 0000000..c80886e --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/17_monitoring.sh.j2 @@ -0,0 +1,7 @@ +# {{ ansible_managed }} + +iptables -N monitoring-in + +{% for srcip in firewall_monitoring_ips|default([]) %} +iptables -A INPUT -s {{ srcip }} -j monitoring-in +{% endfor %} diff --git a/roles/firewall/templates/rules-v4.d/18_internal.sh.j2 b/roles/firewall/templates/rules-v4.d/18_internal.sh.j2 new file mode 100644 index 0000000..6bffbf5 --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/18_internal.sh.j2 @@ -0,0 +1,24 @@ +# {{ ansible_managed }} + +{% if datacenter_global_networks is defined %} +iptables -N internal-in +{% if firewall_allow_internal_dns %} +iptables -A internal-in -p tcp --dport 53 -m comment --comment "common-dns" -j ACCEPT +iptables -A internal-in -p udp --dport 53 -m comment --comment "common-dns" -j ACCEPT +{% endif %} + +{% for srcip in datacenter_global_networks + datacenter_public_networks %} +iptables -A INPUT -s {{ srcip }} -j internal-in +{% endfor %} + +iptables -N internal-out +iptables -A internal-out -p tcp -m multiport --dports 53,80,443,2181,3306:3310,8086,10231 -m comment --comment "common-services" -j ACCEPT +iptables -A internal-out -p udp --dport 53 -m comment --comment "common-dns" -j ACCEPT +iptables -A internal-out -p tcp --dport 10514 -m owner --uid-owner 0 -m comment --comment "syslog" -j ACCEPT +iptables -A internal-out -p icmp -j ACCEPT + +{% for dstip in datacenter_global_networks + datacenter_public_networks %} +iptables -A OUTPUT -d {{ dstip }} -j internal-out +{% endfor %} + +{% endif %} diff --git a/roles/firewall/templates/rules-v4.d/20_whitelist.sh.j2 b/roles/firewall/templates/rules-v4.d/20_whitelist.sh.j2 new file mode 100644 index 0000000..57ba906 --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/20_whitelist.sh.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +{% if firewall_whitelist_ip %} +# Whitelist IPs +{% for ip in firewall_whitelist_ip %} +iptables -A INPUT -s {{ ip }} -m comment --comment "whitelist" -j ACCEPT +{% endfor %} +{% endif %} diff --git a/roles/firewall/templates/rules-v4.d/22_ssh.sh.j2 b/roles/firewall/templates/rules-v4.d/22_ssh.sh.j2 new file mode 100644 index 0000000..5ef9f1c --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/22_ssh.sh.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +# Allow SSH only from IPs: +iptables -N ssh-in +{% for ip in firewall_ssh_acl|default([]) + firewall_ssh_acl_extra|default([]) %} +iptables -A ssh-in -s {{ ip }} -j ACCEPT +{% endfor %} +iptables -A INPUT -p tcp --dport 22 -m comment --comment "ssh" -j ssh-in diff --git a/roles/firewall/templates/rules-v4.d/24_influxdb.sh.j2 b/roles/firewall/templates/rules-v4.d/24_influxdb.sh.j2 new file mode 100644 index 0000000..2827dfc --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/24_influxdb.sh.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +# Allow InfluxDB Replication only from IPs: +iptables -N influx-in +{% for ip in firewall_influx_acl|default([]) + firewall_influx_acl_extra|default([]) %} +iptables -A influx-in -s {{ ip }} -j ACCEPT +{% endfor %} +iptables -A INPUT -p tcp --dport 8086 -m comment --comment "influx" -j influx-in diff --git a/roles/firewall/templates/rules-v4.d/33_mariadb.sh.j2 b/roles/firewall/templates/rules-v4.d/33_mariadb.sh.j2 new file mode 100644 index 0000000..840cbc8 --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/33_mariadb.sh.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +# Allow MariaDB Replication only from IPs: +iptables -N mariadb-in +{% for ip in firewall_mariadb_acl|default([]) + firewall_mariadb_acl_extra|default([]) %} +iptables -A mariadb-in -s {{ ip }} -j ACCEPT +{% endfor %} +iptables -A INPUT -p tcp --dport 3306 -m comment --comment "mariadb" -j mariadb-in diff --git a/roles/firewall/templates/rules-v4.d/50_custom.sh.j2 b/roles/firewall/templates/rules-v4.d/50_custom.sh.j2 new file mode 100644 index 0000000..efb4f5c --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/50_custom.sh.j2 @@ -0,0 +1,3 @@ +# {{ ansible_managed }} + +{{ firewall_custom_ipv4_rules }} diff --git a/roles/firewall/templates/rules-v4.d/85_whitelist.sh.j2 b/roles/firewall/templates/rules-v4.d/85_whitelist.sh.j2 new file mode 100644 index 0000000..5c41287 --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/85_whitelist.sh.j2 @@ -0,0 +1,15 @@ +# {{ ansible_managed }} + +{% if firewall_late_whitelist_ip %} +# Whitelist IPs +{% for ip in firewall_late_whitelist_ip %} +iptables -A INPUT -s {{ ip }} -m comment --comment "whitelist" -j ACCEPT +{% endfor %} +{% endif %} + +{% if firewall_whitelist_office_ip and firewall_whitelist_office_ports %} +# Offices TODO remove +{% for ip in firewall_whitelist_office_ip %} +iptables -A INPUT -s {{ ip }} -p tcp -m multiport --dports "{{ firewall_whitelist_office_ports | join(',') }}" -m comment --comment "office-whitelist" -j ACCEPT +{% endfor %} +{% endif %} diff --git a/roles/firewall/templates/rules-v4.d/90_allow_outbound.sh.j2 b/roles/firewall/templates/rules-v4.d/90_allow_outbound.sh.j2 new file mode 100644 index 0000000..34f5b97 --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/90_allow_outbound.sh.j2 @@ -0,0 +1,69 @@ +# {{ ansible_managed }} + +{% if firewall_output_default_drop or firewall_output_whitelist_ipv4 %} +{% for ip in network_nameservers + ['8.8.8.8'] if ip|ipv4 %} +{% if loop.first %} +# Allow DNS +{% endif %} +iptables -A OUTPUT -d {{ ip }} -p tcp --dport 53 -m comment --comment "dns" -j ACCEPT +iptables -A OUTPUT -d {{ ip }} -p udp --dport 53 -m comment --comment "dns" -j ACCEPT +{% endfor %} + +if getent group postfix >/dev/null 2>&1; then + # Permit outbound SMTP for Postfix only (TODO: move to postfix role) + iptables -A OUTPUT -p tcp --dport 25 -m owner --gid-owner postfix -m comment --comment "smtp" -j ACCEPT +fi + +{% if not (firewall_output_learning or firewall_output_whitelist_ipv4) %} +# Permit outbound HTTP for user _apt +iptables -A OUTPUT -p tcp -m multiport --dports 80,443 -m owner --uid-owner _apt -m comment --comment "apt" -j ACCEPT + +# Permit outbound SSH for normal users +iptables -A OUTPUT -p tcp --dport 22 -m owner --uid-owner 1000-65500 -m comment --comment "ssh" -j ACCEPT + +# Allow all outbound traffic for the root user +iptables -A OUTPUT -m owner --uid-owner 0 -j ACCEPT +{% endif %} + +{% for ip in datacenter_global_networks|default([]) + datacenter_all_networks|default([]) %} +iptables -A OUTPUT -d {{ ip }} -m comment --comment "keepcalling" -j ACCEPT +{% endfor %} + +{% if firewall_output_whitelist_domains %} +# Outbound ACL for whitelist +if [ -r /etc/firewall/outbound_whitelist_ipv4.acl ]; then + + ipset -exist create outbound-whitelist hash:net counters comment + ipset flush outbound-whitelist + grep -v '^#' /etc/firewall/outbound_whitelist_ipv4.acl | while read ip name; do + ipset -exist add outbound-whitelist "$ip" comment "$name" + done < /etc/firewall/outbound_whitelist_ipv4.acl + +# iptables -A OUTPUT -m set --match-set outbound-whitelist,dst -j ACCEPT +fi +{% endif %} + +{% if firewall_output_whitelist_ipv4 %} +# Outbound ACL for whitelist +iptables -N outbound-whitelist +{% for item in firewall_output_whitelist_ipv4 %} +iptables -A outbound-whitelist -d {{ item.ip }} -m comment --comment "{{ item.name }}" -j ACCEPT +{% endfor %} +iptables -A OUTPUT -j outbound-whitelist +{% endif %} + +{% if firewall_output_learning %} + +ipset -exist create outbound hash:ip counters + +iptables -A OUTPUT -p tcp --syn -m set --match-set outbound dst -j ACCEPT +iptables -A OUTPUT -p udp -m set --match-set outbound dst -j ACCEPT + +iptables -A OUTPUT -p tcp --syn -j SET --add-set outbound dst +iptables -A OUTPUT -p udp -j SET --add-set outbound dst + +iptables -A internal-out -m limit --limit 10/min --limit-burst 2 -j LOG --log-prefix "{{ firewall_log_prefix }} internal-out DROP: " --log-level 5 --log-uid + +{% endif %} + +{% endif %} diff --git a/roles/firewall/templates/rules-v4.d/90_drop_all.sh.j2 b/roles/firewall/templates/rules-v4.d/90_drop_all.sh.j2 new file mode 100644 index 0000000..ef2ae5f --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/90_drop_all.sh.j2 @@ -0,0 +1,28 @@ +# {{ ansible_managed }} + +{% if firewall_input_default_drop %} +# Allow Safe ICMP +iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT +iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT +iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT +#iptables -A INPUT -p icmp --icmp-type redirect -j ACCEPT +iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT + +# Drop everything else +iptables -A INPUT -m pkttype --pkt-type broadcast -j DROP +iptables -A INPUT -m limit --limit 10/min --limit-burst 2 -j LOG --log-prefix "{{ firewall_log_prefix }} INPUT DROP: " --log-level 5 +iptables -A INPUT -j DROP +{% endif %} + +{% if firewall_output_default_drop %} +# Allow Safe ICMP +iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT +iptables -A OUTPUT -p icmp --icmp-type destination-unreachable -j ACCEPT +#iptables -A OUTPUT -p icmp --icmp-type redirect -j ACCEPT +iptables -A OUTPUT -p icmp --icmp-type time-exceeded -j ACCEPT + +# Drop everything else +iptables -A OUTPUT -p tcp --syn -m limit --limit 10/min --limit-burst 2 -j LOG --log-prefix "{{ firewall_log_prefix }} OUTPUT DROP: " --log-level 5 --log-uid +iptables -A OUTPUT ! -p tcp -m limit --limit 10/min --limit-burst 2 -j LOG --log-prefix "{{ firewall_log_prefix }} OUTPUT DROP: " --log-level 5 --log-uid +iptables -A OUTPUT -j REJECT +{% endif %} diff --git a/roles/firewall/templates/rules-v4.d/95_fail2ban.sh.j2 b/roles/firewall/templates/rules-v4.d/95_fail2ban.sh.j2 new file mode 100644 index 0000000..7e042a6 --- /dev/null +++ b/roles/firewall/templates/rules-v4.d/95_fail2ban.sh.j2 @@ -0,0 +1,6 @@ +# {{ ansible_managed }} + +# Restart fail2ban +if systemctl -q is-active fail2ban.service; then + systemctl try-restart fail2ban.service +fi diff --git a/roles/firewall/templates/rules-v6.d/10_conntrack.sh.j2 b/roles/firewall/templates/rules-v6.d/10_conntrack.sh.j2 new file mode 100644 index 0000000..3317f90 --- /dev/null +++ b/roles/firewall/templates/rules-v6.d/10_conntrack.sh.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +# Allow established connections +ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT +ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT diff --git a/roles/firewall/templates/rules-v6.d/15_local.sh.j2 b/roles/firewall/templates/rules-v6.d/15_local.sh.j2 new file mode 100644 index 0000000..561963c --- /dev/null +++ b/roles/firewall/templates/rules-v6.d/15_local.sh.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +# Allow all traffic from localhost +ip6tables -A INPUT -i lo -j ACCEPT +ip6tables -A OUTPUT -o lo -j ACCEPT diff --git a/roles/firewall/templates/rules-v6.d/18_internal.sh.j2 b/roles/firewall/templates/rules-v6.d/18_internal.sh.j2 new file mode 100644 index 0000000..01f7491 --- /dev/null +++ b/roles/firewall/templates/rules-v6.d/18_internal.sh.j2 @@ -0,0 +1,21 @@ +# {{ ansible_managed }} + +{% if datacenter_global_networks is defined %} +ip6tables -N internal-in +ip6tables -A INPUT -s fe80::/10 -j internal-in +ip6tables -A INPUT -s fc00::/7 -j internal-in +{% for net in datacenter_public_ipv6_networks|default([]) %} +ip6tables -A INPUT -s {{ net }} -j internal-in +{% endfor %} + +ip6tables -N internal-out +ip6tables -A internal-out -p tcp -m multiport --dports 53,80,443,3306:3310 -m comment --comment "common-services" -j ACCEPT +ip6tables -A internal-out -p udp -m multiport --dports 53,123 -m comment --comment "common-services" -j ACCEPT +ip6tables -A internal-out -p icmpv6 -j ACCEPT + +ip6tables -A OUTPUT -d fe80::/10 -j internal-out +ip6tables -A OUTPUT -d fc00::/7 -j internal-out +{% for net in datacenter_public_ipv6_networks|default([]) %} +ip6tables -A OUTPUT -d {{ net }} -j internal-out +{% endfor %} +{% endif %} diff --git a/roles/firewall/templates/rules-v6.d/20_whitelist.sh.j2 b/roles/firewall/templates/rules-v6.d/20_whitelist.sh.j2 new file mode 100644 index 0000000..9423cdf --- /dev/null +++ b/roles/firewall/templates/rules-v6.d/20_whitelist.sh.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +{% if firewall_whitelist_ipv6 %} +# Whitelist IPs +{% for ip in firewall_whitelist_ipv6 %} +ip6tables -A INPUT -s {{ ip }} -m comment --comment "whitelist" -j ACCEPT +{% endfor %} +{% endif %} diff --git a/roles/firewall/templates/rules-v6.d/50_custom.sh.j2 b/roles/firewall/templates/rules-v6.d/50_custom.sh.j2 new file mode 100644 index 0000000..83db0c6 --- /dev/null +++ b/roles/firewall/templates/rules-v6.d/50_custom.sh.j2 @@ -0,0 +1,3 @@ +# {{ ansible_managed }} + +{{ firewall_custom_ipv6_rules }} diff --git a/roles/firewall/templates/rules-v6.d/85_whitelist.sh.j2 b/roles/firewall/templates/rules-v6.d/85_whitelist.sh.j2 new file mode 100644 index 0000000..6606cc1 --- /dev/null +++ b/roles/firewall/templates/rules-v6.d/85_whitelist.sh.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +{% if firewall_late_whitelist_ipv6 %} +# Whitelist IPs +{% for ip in firewall_late_whitelist_ipv6 %} +ip6tables -A INPUT -s {{ ip }} -m comment --comment "whitelist" -j ACCEPT +{% endfor %} +{% endif %} diff --git a/roles/firewall/templates/rules-v6.d/90_allow_outbound.sh.j2 b/roles/firewall/templates/rules-v6.d/90_allow_outbound.sh.j2 new file mode 100644 index 0000000..e1a0cdc --- /dev/null +++ b/roles/firewall/templates/rules-v6.d/90_allow_outbound.sh.j2 @@ -0,0 +1,69 @@ +# {{ ansible_managed }} + +{% if firewall_output_default_drop or firewall_output_whitelist_ipv6 %} +{% for ip in network_nameservers if ip|ipv6 %} +{% if loop.first %} +# Allow DNS +{% endif %} +ip6tables -A OUTPUT -d {{ ip }} -p tcp --dport 53 -m comment --comment "dns" -j ACCEPT +ip6tables -A OUTPUT -d {{ ip }} -p udp --dport 53 -m comment --comment "dns" -j ACCEPT +{% endfor %} + +if getent group postfix >/dev/null 2>&1; then + # Permit outbound SMTP for Postfix only (TODO: move to postfix role) + ip6tables -A OUTPUT -p tcp --dport 25 -m owner --gid-owner postfix -m comment --comment "smtp" -j ACCEPT +fi + +{% if not firewall_output_learning %} +# Permit outbound HTTP for user _apt +ip6tables -A OUTPUT -p tcp -m multiport --dports 80,443 -m owner --uid-owner _apt -m comment --comment "apt" -j ACCEPT + +# Permit outbound SSH for normal users +ip6tables -A OUTPUT -p tcp --dport 22 -m owner --uid-owner 1000-65500 -m comment --comment "ssh" -j ACCEPT + +# Allow all outbound traffic for the root user +ip6tables -A OUTPUT -m owner --uid-owner 0 -j ACCEPT +{% endif %} + +{% for ip in datacenter_all_ipv6_networks|default([]) %} +ip6tables -A OUTPUT -d {{ ip }} -m comment --comment "keepcalling" -j ACCEPT +{% endfor %} + +{% if firewall_output_whitelist_domains %} +# Outbound ACL for whitelist +if [ -r /etc/firewall/outbound_whitelist_ipv6.acl ]; then + + ipset -exist create outbound-whitelist hash:net family inet6 counters comment + ipset flush outbound-whitelist + grep -v '^#' /etc/firewall/outbound_whitelist_ipv6.acl | while read ip name; do + ipset -exist add outbound-whitelist "$ip" comment "$name" + done < /etc/firewall/outbound_whitelist_ipv6.acl + +# ip6tables -A OUTPUT -m set --match-set outbound-whitelist,dst -j ACCEPT +fi +{% endif %} + +{% if firewall_output_whitelist_ipv6 %} +# Outbound ACL for whitelist +ip6tables -N outbound-whitelist +{% for item in firewall_output_whitelist_ipv6 %} +ip6tables -A OUTPUT -d {{ item.ip }} -m comment --comment "{{ item.name }}" -j ACCEPT +{% endfor %} +ip6tables -A OUTPUT -j outbound-whitelist +{% endif %} + +{% if firewall_output_learning %} + +ipset -exist create outbound-ipv6 hash:ip family inet6 netmask 64 counters + +ip6tables -A OUTPUT -p tcp --syn -m set --match-set outbound-ipv6 dst -j ACCEPT +ip6tables -A OUTPUT -p udp -m set --match-set outbound dst -j ACCEPT + +ip6tables -A OUTPUT -p tcp --syn -j SET --add-set outbound-ipv6 dst +ip6tables -A OUTPUT -p udp -j SET --add-set outbound-ipv6 dst + +ip6tables -A internal-out -m limit --limit 10/min --limit-burst 2 -j LOG --log-prefix "{{ firewall_log_prefix }} internal-out DROP: " --log-level 5 --log-uid + +{% endif %} + +{% endif %} diff --git a/roles/firewall/templates/rules-v6.d/90_drop_all.sh.j2 b/roles/firewall/templates/rules-v6.d/90_drop_all.sh.j2 new file mode 100644 index 0000000..e02a20d --- /dev/null +++ b/roles/firewall/templates/rules-v6.d/90_drop_all.sh.j2 @@ -0,0 +1,21 @@ +# {{ ansible_managed }} + +{% if firewall_input_default_drop %} +# Allow ICMP +ip6tables -A INPUT -p icmpv6 -j ACCEPT + +# Drop everything else +ip6tables -A INPUT -m pkttype --pkt-type broadcast -j DROP +ip6tables -A INPUT -m limit --limit 10/min --limit-burst 2 -j LOG --log-prefix "{{ firewall_log_prefix }} INPUT DROP: " --log-level 5 +ip6tables -A INPUT -j DROP +{% endif %} + +{% if firewall_output_default_drop %} +# Allow ICMP +ip6tables -A OUTPUT -p icmpv6 ! --icmpv6-type echo-request -j ACCEPT + +# Drop everything else +ip6tables -A OUTPUT -p tcp --syn -m limit --limit 10/min --limit-burst 2 -j LOG --log-prefix "{{ firewall_log_prefix }} OUTPUT DROP: " --log-level 5 --log-uid +ip6tables -A OUTPUT ! -p tcp -m limit --limit 10/min --limit-burst 2 -j LOG --log-prefix "{{ firewall_log_prefix }} OUTPUT DROP: " --log-level 5 --log-uid +ip6tables -A OUTPUT -j REJECT +{% endif %} diff --git a/roles/firewall/templates/usr_sbin_update-firewall-outbound.j2 b/roles/firewall/templates/usr_sbin_update-firewall-outbound.j2 new file mode 100644 index 0000000..1520237 --- /dev/null +++ b/roles/firewall/templates/usr_sbin_update-firewall-outbound.j2 @@ -0,0 +1,75 @@ +#!/bin/bash + +# Update /etc/firewall/outbound_whitelist_ipv*.acl from /etc/firewall/outbound_whitelist.acl + +resolve_hosts() { + local type="$1" out="$2" + local tmp=$(mktemp "$out.XXXXXX") + + ( + echo "# AUTO-GENERATED FROM outbound_whitelist.acl" + while read domain; do + case $domain in + "#"* | "") ;; + *) + (host -t "$type" "$domain" 2>&1 || true) | sort -n | while read line; do + case $line in + *"not found"*) + echo "$line" >&2 + ;; + *"has address"*) + ip="${line##* }" + case $ip in + 13.108.*) ip="13.108.0.0/14" ;; + *) ip="${ip%.*}.0/24" ;; + esac + echo "$ip $domain" + ;; + *"has IPv6 address"*) + ip="${line##* }" + case $ip in + 2607:f8b0:*) ip="2607:f8b0::/32" ;; + *) ip=$(ipv6calc --addr_to_uncompressed "$ip" | cut -d: -f1-4)::/64 ;; + esac + echo "$ip $domain" + ;; + esac + done + ;; + esac + done < /etc/firewall/outbound_whitelist.acl | sort -n -u + ) > "$tmp" + + if [ $? -ne 0 ]; then + echo "Error writing to $out" >&2 + rm -f "$tmp" + elif cmp -s "$tmp" "$out"; then + rm -f "$tmp" + else + echo "--- Differences in $(basename $out): ---" + echo + diff -u "$out" "$tmp" | grep -v '^\(+++\|---\)' | grep '^[+-]' + echo + mv -f "$tmp" "$out" + fi +} + +load_ipset() { + local name="$1" family="$2" file="$3" + local tmp="$name-$$" + + ipset -exist create "$name" hash:net family "$family" counters comment + + ipset create "$tmp" hash:net family "$family" counters comment + grep -v '^#' "$file" | while read ip name; do + ipset -exist add "$tmp" "$ip" comment "$name" + done + ipset swap "$name" "$tmp" + ipset destroy "$tmp" +} + +resolve_hosts A /etc/firewall/outbound_whitelist_ipv4.acl +resolve_hosts AAAA /etc/firewall/outbound_whitelist_ipv6.acl + +load_ipset outbound-whitelist inet /etc/firewall/outbound_whitelist_ipv4.acl +load_ipset outbound-whitelist-ipv6 inet6 /etc/firewall/outbound_whitelist_ipv6.acl diff --git a/roles/network/templates/etc_resolv.conf.j2 b/roles/network/templates/etc_resolv.conf.j2 new file mode 100644 index 0000000..e10be96 --- /dev/null +++ b/roles/network/templates/etc_resolv.conf.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} +{# use the local bind #} +nameserver 127.0.0.1 +{% if network_fallback_resolvers|d('') %} +{% for ip in network_fallback_resolvers if ip != network_private_ip %} +nameserver {{ ip }} +{% endfor %} +{% endif %} diff --git a/roles/ntp/defaults/main.yml b/roles/ntp/defaults/main.yml new file mode 100644 index 0000000..a9e2e89 --- /dev/null +++ b/roles/ntp/defaults/main.yml @@ -0,0 +1,9 @@ +--- + +ntp_servers: + - time1.google.com + - time2.google.com + - time3.google.com + - time4.google.com + +ntp_firewall: no diff --git a/roles/ntp/handlers/main.yml b/roles/ntp/handlers/main.yml new file mode 100644 index 0000000..2cb290d --- /dev/null +++ b/roles/ntp/handlers/main.yml @@ -0,0 +1,3 @@ +--- +- name: Restart NTP + service: name=ntp state=restarted diff --git a/roles/ntp/meta/main.yml b/roles/ntp/meta/main.yml new file mode 100644 index 0000000..3e8fb8e --- /dev/null +++ b/roles/ntp/meta/main.yml @@ -0,0 +1,5 @@ +--- + +dependencies: + - role: firewall + when: ntp_firewall diff --git a/roles/ntp/tasks/main.yml b/roles/ntp/tasks/main.yml new file mode 100644 index 0000000..1af9842 --- /dev/null +++ b/roles/ntp/tasks/main.yml @@ -0,0 +1,24 @@ +--- +- name: Install NTP + apt: pkg=ntp state=present + +- name: Configure NTP + template: src=ntp.conf.j2 dest=/etc/ntp.conf + notify: Restart NTP + +- name: Configure NTP keys + template: src=ntp.keys.j2 dest=/etc/ntp.keys owner=ntp group=ntp mode=0400 + notify: Restart NTP + +- name: Ensure NTP is running + service: name=ntp state=started enabled=yes + +- name: Configure firewall rules for NTP + template: + dest: /etc/firewall/rules-v4.d/21_ntp.sh + src: etc_firewall_rules-v4.d_21_ntp.sh.j2 + owner: root + group: root + mode: 0600 + when: ntp_firewall + notify: Restart firewall diff --git a/roles/ntp/templates/etc_firewall_rules-v4.d_21_ntp.sh.j2 b/roles/ntp/templates/etc_firewall_rules-v4.d_21_ntp.sh.j2 new file mode 100644 index 0000000..911421a --- /dev/null +++ b/roles/ntp/templates/etc_firewall_rules-v4.d_21_ntp.sh.j2 @@ -0,0 +1,6 @@ +# {{ ansible_managed }} + +{% for ip in ntp_servers | default([]) %} +iptables -A INPUT -s {{ ip }} -p udp --dport 123 -m comment --comment "ntp" -j ACCEPT +iptables -A OUTPUT -d {{ ip }} -p udp --dport 123 -m comment --comment "ntp" -j ACCEPT +{% endfor %} diff --git a/roles/ntp/templates/ntp.conf.j2 b/roles/ntp/templates/ntp.conf.j2 new file mode 100644 index 0000000..cf793d1 --- /dev/null +++ b/roles/ntp/templates/ntp.conf.j2 @@ -0,0 +1,34 @@ +# {{ ansible_managed }} +# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help + +driftfile /var/lib/ntp/ntp.drift +keys /etc/ntp.keys + +# Enable this if you want statistics to be logged. +#statsdir /var/log/ntpstats/ + +statistics loopstats peerstats clockstats +filegen loopstats file loopstats type day enable +filegen peerstats file peerstats type day enable +filegen clockstats file clockstats type day enable + +{% if ntp_broadcast_key is defined %} +broadcastclient +trustedkey 22 +{% else %} +# You do need to talk to an NTP server or two (or three). +{% for server in ntp_servers %} +server {{ server }} iburst +{% endfor %} +{% endif %} + +# By default, exchange time with everybody, but don't allow configuration. +restrict -4 default kod notrap nomodify nopeer noquery limited +restrict -6 default kod notrap nomodify nopeer noquery limited + +# Local users may interrogate the ntp server more closely. +restrict 127.0.0.1 +restrict ::1 + +# Needed for adding pool entries +restrict source notrap nomodify noquery diff --git a/roles/ntp/templates/ntp.keys.j2 b/roles/ntp/templates/ntp.keys.j2 new file mode 100644 index 0000000..382513d --- /dev/null +++ b/roles/ntp/templates/ntp.keys.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% if ntp_broadcast_key is defined %} +22 M {{ ntp_broadcast_key }} +{% endif %} diff --git a/roles/postfix/defaults/main.yml b/roles/postfix/defaults/main.yml new file mode 100644 index 0000000..1e6f7e9 --- /dev/null +++ b/roles/postfix/defaults/main.yml @@ -0,0 +1,165 @@ +postfix_mynetworks: [] + +postfix_mydestination_local: + - "{{ ansible_hostname }}.maruntiel.net" + - "localhost" + - "localhost.{{ ansible_domain }}" + +postfix_mydestination_extra: [] + +# main.cf settings +postfix_settings: + + compatibility_level: 2 + + myhostname: "{{ ansible_hostname }}.maruntiel.net" + myorigin: /etc/mailname + + mydestination: "{{ postfix_mydestination_local + postfix_mydestination_extra }}" + mynetworks: "10.11.0.0/16 62.171.160.169/32 207.244.234.58/32 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 {{ postfix_mynetworks | join(' ') }}" + + relayhost: "" + + alias_maps: hash:/etc/aliases + alias_database: hash:/etc/aliases + biff: no + mailbox_command: + home_mailbox: Maildir/ + mailbox_size_limit: 0 + recipient_delimiter: + + append_dot_mydomain: no + readme_directory: no + dovecot_destination_recipient_limit: 1 + + tls_random_source: dev:/dev/urandom + + default_transport: smtp + relay_transport: smtp + relay_domains: "" + + inet_protocols: ipv4 + inet_interfaces: all + + virtual_mailbox_domains: + - /etc/postfix/virtual_domains + virtual_mailbox_base: + - /var/mail/vhosts + virtual_mailbox_maps: + - hash:/etc/postfix/vmailbox + virtual_alias_maps: + - hash:/etc/postfix/virtual_alias + virtual_minimum_uid: + - 100 + virtual_uid_maps: + - static:5000 + virtual_gid_maps: + - static:5000 + virtual_transport: + - virtual + virtual_alias_domains: + - maruntiel.net + - maruntiel.com + - stillmob.ro + - pedimedic.ro + + + # SMTP SETTINGS + smtp_use_tls: yes + smtp_tls_security_level: may + smtp_tls_note_starttls_offer: yes + smtp_tls_session_cache_database: "btree:${data_directory}/smtp_scache" + + # SMTPD SETTINGS + smtpd_use_tls: yes + smtpd_tls_auth_only: no + smtpd_tls_security_level: may + smtpd_tls_loglevel: 1 + smtpd_tls_received_header: yes + smtpd_tls_session_cache_timeout: 3600s + smtpd_tls_session_cache_database: "btree:${data_directory}/smtpd_scache" + smtpd_tls_cert_file: "/etc/letsencrypt/live/maruntiel.net/fullchain.pem" + smtpd_tls_key_file: "/etc/letsencrypt/live/maruntiel.net/privkey.pem" + smtpd_banner: "$myhostname ESMTP $mail_name" + smtpd_client_restrictions: + - permit_mynetworks + - permit_sasl_authenticated + - reject_invalid_hostname + - reject_unknown_client + - reject_rbl_client sbl-xbl.spamhaus.org + smtpd_sender_restrictions: + - permit_mynetworks + - reject_unknown_address + - reject_unknown_sender_domain + - reject_non_fqdn_sender + smtpd_recipient_limit: 250 + smtpd_recipient_restrictions: + - reject_invalid_hostname + - reject_non_fqdn_sender + - reject_non_fqdn_recipient + - reject_unlisted_sender + - permit_mynetworks + - permit_sasl_authenticated + - reject_unauth_pipelining + - reject_unauth_destination + - check_policy_service unix:private/policyd-spf + - reject_non_fqdn_hostname + - reject_unknown_sender_domain + - reject_rbl_client bl.spamcop.net + - reject_rbl_client zen.spamhaus.org + - permit + + smtpd_relay_restrictions: + - reject_invalid_hostname + - reject_non_fqdn_sender + - reject_non_fqdn_recipient + - reject_unlisted_sender + - permit_mynetworks + - permit_sasl_authenticated + - reject_unauth_pipelining + - reject_unauth_destination + - check_policy_service unix:private/policyd-spf + - reject_non_fqdn_hostname + - reject_unknown_sender_domain + - reject_rbl_client bl.spamcop.net + - reject_rbl_client zen.spamhaus.org + - permit + + smtpd_client_connection_rate_limit: 10 + smtpd_client_message_rate_limit: 10 + + # SASL + smtpd_sasl_auth_enable: yes + smtpd_sasl_type: dovecot + smtpd_sasl_path: private/auth + broken_sasl_auth_clients: yes + smtpd_sasl_local_domain: \$mydomain + smtpd_sasl_security_options: noanonymous + + # Other + header_checks: + - regexp:/etc/postfix/header_checks + + # DKIM + milter_default_action: accept + milter_protocol: 6 + smtpd_milters: local:opendkim/opendkim.sock + non_smtpd_milters: $smtpd_milters + + # SPF + policyd-spf_time_limit: 3600 + +postfix_opendkim: "{{ postfix_dkim_domains|count > 0 }}" +postfix_relay: no +postfix_smtpd_public: yes +postfix_firewall: "{{ firewall_enabled|default(true) }}" + +postfix_dkim_domains: [] + +virtual_mailbox_domains: /etc/postfix/virtual_domains +virtual_mailbox_base: /var/mail/vhosts +virtual_mailbox_maps: hash:/etc/postfix/vmailbox +virtual_alias_maps: hash:/etc/postfix/virtual_alias +virtual_minimum_uid: 100 +virtual_uid_maps: static:5000 +virtual_gid_maps: static:5000 +virtual_transport: virtual diff --git a/roles/postfix/handlers/main.yml b/roles/postfix/handlers/main.yml new file mode 100644 index 0000000..f5e4640 --- /dev/null +++ b/roles/postfix/handlers/main.yml @@ -0,0 +1,12 @@ +- name: Restart postfix + service: + name: "postfix@-" + state: restarted + +- name: Rebuild postfix map files + shell: postmap /etc/postfix/*.map + +- name: Restart opendkim + service: + name: opendkim + state: restarted diff --git a/roles/postfix/meta/main.yml b/roles/postfix/meta/main.yml new file mode 100644 index 0000000..66049f1 --- /dev/null +++ b/roles/postfix/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + + - role: firewall + when: postfix_firewall diff --git a/roles/postfix/tasks/main.yml b/roles/postfix/tasks/main.yml new file mode 100644 index 0000000..b05412e --- /dev/null +++ b/roles/postfix/tasks/main.yml @@ -0,0 +1,151 @@ +--- + +- name: Install postfix + apt: + pkg: + - postfix + - postfix-pcre + state: present + tags: packages + +- name: Install postfix configs + template: + dest: "/etc/postfix/{{ item }}" + src: "etc_postfix_{{ item }}.j2" + mode: 0644 + owner: root + group: root + with_items: + - main.cf + - master.cf + - header_checks + notify: Restart postfix + tags: configs + +- name: Install postfix maps + template: + dest: "/etc/postfix/{{ item }}" + src: "etc_postfix_{{ item }}.j2" + mode: 0640 + owner: root + group: postfix + with_items: + - sasl_passwd.map + - transport.map +# - virtual.map + notify: Rebuild postfix map files + tags: configs + +- name: Install empty postfix maps + copy: + dest: "/etc/postfix/{{ item }}" + content: "" + force: no + mode: 0644 + owner: root + group: root + with_items: + - virtual.map + notify: Rebuild postfix map files + tags: configs + +- name: Install postfix-policyd-spf + apt: + pkg: + - postfix-policyd-spf-python + state: present + tags: packages + +- name: Ensure postfix is running + service: + name: postfix + state: started + enabled: yes + tags: configs + +- name: Install the postfix firewall config + template: + dest: /etc/firewall/rules-v4.d/40_postfix.sh + src: etc_firewall_rules-v4.d_40_postfix.sh.j2 + mode: 0644 + owner: root + group: root + when: postfix_firewall + notify: Restart firewall + tags: + - configs + - firewall + +# ===================================================================== + +- name: Install opendkim + apt: + pkg: + - opendkim + - opendkim-tools + state: present + when: postfix_opendkim + tags: packages + +- name: Ensure postfix is a member of opendkim + user: + name: postfix + groups: opendkim + append: yes + when: postfix_opendkim + notify: Restart postfix + tags: configs + +- name: Ensure /etc/opendkim dir exists + file: + path: /etc/opendkim + state: directory + mode: 0755 + owner: root + group: root + when: postfix_opendkim + tags: configs + +- name: Ensure /etc/opendkim/keys dir exists + file: + path: /etc/opendkim/keys + state: directory + mode: 0750 + owner: root + group: opendkim + when: postfix_opendkim + tags: configs + +- name: Install opendkim configs + template: + dest: "/{{ item }}" + src: "{{ item | replace('/', '_') }}.j2" + mode: 0644 + owner: root + group: root + with_items: + - etc/opendkim.conf + - etc/opendkim/key.table + - etc/opendkim/signing.table + - etc/opendkim/trusted.hosts + when: postfix_opendkim + notify: Restart opendkim + tags: configs + +- name: Ensure /var/spool/postfix/opendkim dir exists + file: + path: /var/spool/postfix/opendkim + state: directory + mode: 0755 + owner: opendkim + group: postfix + when: postfix_opendkim + tags: configs + +- name: Ensure opendkim is running + service: + name: postfix + state: started + enabled: yes + when: postfix_opendkim + tags: configs diff --git a/roles/postfix/templates/etc_firewall_rules-v4.d_40_postfix.sh.j2 b/roles/postfix/templates/etc_firewall_rules-v4.d_40_postfix.sh.j2 new file mode 100644 index 0000000..f08ffd1 --- /dev/null +++ b/roles/postfix/templates/etc_firewall_rules-v4.d_40_postfix.sh.j2 @@ -0,0 +1,11 @@ +# {{ ansible_managed }} + +{% if postfix_smtpd_public %} +iptables -A INPUT -p tcp --dport 25 -m comment --comment "postfix-smtp" -j ACCEPT +{% elif postfix_relay %} +iptables -A internal-in -p tcp --dport 25 -m comment --comment "postfix-smtp" -j ACCEPT +{% endif %} + +{% if firewall_output_default_drop %} +iptables -A OUTPUT -p tcp --dport 25 -m owner --gid-owner postfix -m comment --comment "smtp" -j ACCEPT +{% endif %} diff --git a/roles/postfix/templates/etc_opendkim.conf.j2 b/roles/postfix/templates/etc_opendkim.conf.j2 new file mode 100644 index 0000000..287f159 --- /dev/null +++ b/roles/postfix/templates/etc_opendkim.conf.j2 @@ -0,0 +1,38 @@ +# {{ ansible_managed }} + +# This is a basic configuration that can easily be adapted to suit a standard +# installation. For more advanced options, see opendkim.conf(5) and/or +# /usr/share/doc/opendkim/examples/opendkim.conf.sample. + +Syslog yes +LogWhy yes +PidFile /var/run/opendkim/opendkim.pid +Socket local:/var/spool/postfix/opendkim/opendkim.sock +UMask 002 +UserID opendkim + +# Map domains in From addresses to keys used to sign messages +KeyTable file:/etc/opendkim/key.table +SigningTable file:/etc/opendkim/signing.table + +# Hosts to ignore when verifying signatures +ExternalIgnoreList /etc/opendkim/trusted.hosts +InternalHosts /etc/opendkim/trusted.hosts + +# Commonly-used options; the commented-out versions show the defaults. +Canonicalization relaxed/simple +Mode sv +SubDomains yes +#ADSPAction continue +AutoRestart yes +AutoRestartRate 10/1M +Background yes +DNSTimeout 5 +SignatureAlgorithm rsa-sha256 + +# Always oversign From (sign using actual From and a null From to prevent +# malicious signatures header fields (From and/or others) between the signer +# and the verifier. From is oversigned by default in the Debian package +# because it is often the identity key used by reputation systems and thus +# somewhat security sensitive. +OversignHeaders From diff --git a/roles/postfix/templates/etc_opendkim_key.table.j2 b/roles/postfix/templates/etc_opendkim_key.table.j2 new file mode 100644 index 0000000..f636d6c --- /dev/null +++ b/roles/postfix/templates/etc_opendkim_key.table.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% for domain, info in postfix_dkim_domains | dictsort %} +mail._domainkey.{{ domain }} {{ domain }}:mail:/etc/opendkim/keys/{{ domain }}/mail.private +{% endfor %} diff --git a/roles/postfix/templates/etc_opendkim_signing.table.j2 b/roles/postfix/templates/etc_opendkim_signing.table.j2 new file mode 100644 index 0000000..bf07b6c --- /dev/null +++ b/roles/postfix/templates/etc_opendkim_signing.table.j2 @@ -0,0 +1,6 @@ +# {{ ansible_managed }} + +{% for domain, info in postfix_dkim_domains | dictsort %} +{{ domain }} mail._domainkey.{{ domain }} +.{{ domain }} mail._domainkey.{{ domain }} +{% endfor %} diff --git a/roles/postfix/templates/etc_opendkim_trusted.hosts.j2 b/roles/postfix/templates/etc_opendkim_trusted.hosts.j2 new file mode 100644 index 0000000..ef32e46 --- /dev/null +++ b/roles/postfix/templates/etc_opendkim_trusted.hosts.j2 @@ -0,0 +1,4 @@ +127.0.0.0/8 +[::ffff:127.0.0.0]/104 +[::1]/128 +{{ postfix_mynetworks | join("\n") }} \ No newline at end of file diff --git a/roles/postfix/templates/etc_postfix_header_checks.j2 b/roles/postfix/templates/etc_postfix_header_checks.j2 new file mode 100644 index 0000000..ff18da7 --- /dev/null +++ b/roles/postfix/templates/etc_postfix_header_checks.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% for item in postfix_header_checks | default([]) %} +{{ item }} +{% endfor %} diff --git a/roles/postfix/templates/etc_postfix_main.cf.j2 b/roles/postfix/templates/etc_postfix_main.cf.j2 new file mode 100644 index 0000000..0b9b06f --- /dev/null +++ b/roles/postfix/templates/etc_postfix_main.cf.j2 @@ -0,0 +1,13 @@ +# {{ ansible_managed }} + +# See /usr/share/postfix/main.cf.dist for a commented, more complete version + +{% for key, value in postfix_settings | dictsort %} +{% if value is sequence and value is not string %} +{{ key }} = {{ value | flatten | join(",\n\t") }} +{% elif value is sameas true or value is sameas false %} +{{ key }} = {{ 'yes' if value else 'no' }} +{% else %} +{{ key }} = {{ value }} +{% endif %} +{% endfor %} diff --git a/roles/postfix/templates/etc_postfix_master.cf.j2 b/roles/postfix/templates/etc_postfix_master.cf.j2 new file mode 100644 index 0000000..ab7ba68 --- /dev/null +++ b/roles/postfix/templates/etc_postfix_master.cf.j2 @@ -0,0 +1,132 @@ +# {{ ansible_managed }} + +# +# Postfix master process configuration file. For details on the format +# of the file, see the master(5) manual page (command: "man 5 master" or +# on-line: http://www.postfix.org/master.5.html). +# +# Do not forget to execute "postfix reload" after editing this file. +# +# ========================================================================== +# service type private unpriv chroot wakeup maxproc command + args +# (yes) (yes) (no) (never) (100) +# ========================================================================== +smtp inet n - y - - smtpd +#smtp inet n - y - 1 postscreen +#smtpd pass - - y - - smtpd +#dnsblog unix - - y - 0 dnsblog +#tlsproxy unix - - y - 0 tlsproxy +#submission inet n - y - - smtpd +# -o syslog_name=postfix/submission +# -o smtpd_tls_security_level=encrypt +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_reject_unlisted_recipient=no +# -o smtpd_client_restrictions=$mua_client_restrictions +# -o smtpd_helo_restrictions=$mua_helo_restrictions +# -o smtpd_sender_restrictions=$mua_sender_restrictions +# -o smtpd_recipient_restrictions= +# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +smtps inet n - y - - smtpd +# -o syslog_name=postfix/smtps +# -o smtpd_tls_wrappermode=yes +# -o smtpd_sasl_auth_enable=yes +# -o smtpd_reject_unlisted_recipient=no +# -o smtpd_client_restrictions=$mua_client_restrictions +# -o smtpd_helo_restrictions=$mua_helo_restrictions +# -o smtpd_sender_restrictions=$mua_sender_restrictions +# -o smtpd_recipient_restrictions= +# -o smtpd_relay_restrictions=permit_sasl_authenticated,reject +# -o milter_macro_daemon_name=ORIGINATING +#628 inet n - y - - qmqpd +pickup unix n - y 60 1 pickup +cleanup unix n - y - 0 cleanup +qmgr unix n - n 300 1 qmgr +#qmgr unix n - n 300 1 oqmgr +tlsmgr unix - - y 1000? 1 tlsmgr +rewrite unix - - y - - trivial-rewrite +bounce unix - - y - 0 bounce +defer unix - - y - 0 bounce +trace unix - - y - 0 bounce +verify unix - - y - 1 verify +flush unix n - y 1000? 0 flush +proxymap unix - - n - - proxymap +proxywrite unix - - n - 1 proxymap +smtp unix - - y - - smtp +relay unix - - y - - smtp + -o smtp_fallback_relay= +# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 +showq unix n - y - - showq +error unix - - y - - error +retry unix - - y - - error +discard unix - - y - - discard +local unix - n n - - local +virtual unix - n n - - virtual +lmtp unix - - y - - lmtp +anvil unix - - y - 1 anvil +scache unix - - y - 1 scache +{% if ansible_lsb.major_release|int >= 10 %} +postlog unix-dgram n - n - 1 postlogd +{% endif %} +# +# ==================================================================== +# Interfaces to non-Postfix software. Be sure to examine the manual +# pages of the non-Postfix software to find out what options it wants. +# +# Many of the following services use the Postfix pipe(8) delivery +# agent. See the pipe(8) man page for information about ${recipient} +# and other message envelope options. +# ==================================================================== +# +# maildrop. See the Postfix MAILDROP_README file for details. +# Also specify in main.cf: maildrop_destination_recipient_limit=1 +# +maildrop unix - n n - - pipe + flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient} +# +# ==================================================================== +# +# Recent Cyrus versions can use the existing "lmtp" master.cf entry. +# +# Specify in cyrus.conf: +# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4 +# +# Specify in main.cf one or more of the following: +# mailbox_transport = lmtp:inet:localhost +# virtual_transport = lmtp:inet:localhost +# +# ==================================================================== +# +# Cyrus 2.1.5 (Amos Gouaux) +# Also specify in main.cf: cyrus_destination_recipient_limit=1 +# +#cyrus unix - n n - - pipe +# user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user} +# +# ==================================================================== +# Old example of delivery via Cyrus. +# +#old-cyrus unix - n n - - pipe +# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user} +# +# ==================================================================== +# +# See the Postfix UUCP_README file for configuration details. +# +uucp unix - n n - - pipe + flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) +# +# Other external delivery methods. +# +ifmail unix - n n - - pipe + flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient) +bsmtp unix - n n - - pipe + flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient +scalemail-backend unix - n n - 2 pipe + flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension} +mailman unix - n n - - pipe + flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py + ${nexthop} ${user} + +policyd-spf unix - n n - 0 spawn + user=policyd-spf argv=/usr/bin/policyd-spf diff --git a/roles/postfix/templates/etc_postfix_sasl_passwd.map.j2 b/roles/postfix/templates/etc_postfix_sasl_passwd.map.j2 new file mode 100644 index 0000000..6a6c491 --- /dev/null +++ b/roles/postfix/templates/etc_postfix_sasl_passwd.map.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% for key, value in postfix_sasl_passwd_map | default({}) | dictsort %} +{{ key }} {{ value.username }}:{{ value.password }} +{% endfor %} diff --git a/roles/postfix/templates/etc_postfix_transport.map.j2 b/roles/postfix/templates/etc_postfix_transport.map.j2 new file mode 100644 index 0000000..d0cf95a --- /dev/null +++ b/roles/postfix/templates/etc_postfix_transport.map.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% for key, value in postfix_transport_map | default({}) | dictsort %} +{{ key }} {{ value }} +{% endfor %} diff --git a/roles/postfix/templates/etc_postfix_virtual.map.j2 b/roles/postfix/templates/etc_postfix_virtual.map.j2 new file mode 100644 index 0000000..07b22f0 --- /dev/null +++ b/roles/postfix/templates/etc_postfix_virtual.map.j2 @@ -0,0 +1,5 @@ +# {{ ansible_managed }} + +{% for key, value in postfix_virtual_map | default({}) | dictsort %} +{{ key }} {{ value }} +{% endfor %} diff --git a/roles/ssh/defaults/main.yml b/roles/ssh/defaults/main.yml new file mode 100644 index 0000000..c207160 --- /dev/null +++ b/roles/ssh/defaults/main.yml @@ -0,0 +1,43 @@ +--- + +ssh_client_settings: +# Host: +# - Host: "*" +# SendEnv: LANG LC_* +# HashKnownHosts: yes + ForwardAgent: yes + HashKnownHosts: yes + + +ssh_server_settings: + Port: 22 + Protocol: 2 + HostKey: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ed25519_key + SyslogFacility: AUTH + LogLevel: INFO + PermitRootLogin: prohibit-password + PubkeyAuthentication: yes + PermitEmptyPasswords: no + AuthenticationMethods publickey,keyboard-interactive + ChallengeResponseAuthentication: yes + PasswordAuthentication: no + X11Forwarding: no + PrintMotd: no + PrintLastLog: yes + AcceptEnv: LANG LC_* + Subsystem: + - sftp /usr/lib/openssh/sftp-server + UsePAM: yes + + # Hardened cipher list + KexAlgorithms: curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256 + Ciphers: chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr + MACs: hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256,hmac-sha2-512 + HostKeyAlgorithms: ssh-rsa,ssh-ed25519 + +# Match: +# - Match: "*" +# AllowAgentForwarding: yes diff --git a/roles/ssh/handlers/main.yml b/roles/ssh/handlers/main.yml new file mode 100644 index 0000000..dbcdf2b --- /dev/null +++ b/roles/ssh/handlers/main.yml @@ -0,0 +1,4 @@ +--- + +- name: Restart SSH + service: name=ssh state=restarted diff --git a/roles/ssh/tasks/main.yml b/roles/ssh/tasks/main.yml new file mode 100644 index 0000000..b24686b --- /dev/null +++ b/roles/ssh/tasks/main.yml @@ -0,0 +1,45 @@ +--- +# Tasks to install and configure OpenSSH + +- name: Make sure the SSH server and client packages are installed + apt: + pkg: + - openssh-client + - openssh-server + state: present + tags: ssh + +- name: Configure the SSH Client + template: + src: etc_ssh_ssh_config.j2 + dest: /etc/ssh/ssh_config + owner: root + group: root + mode: 0644 + tags: ssh + +- name: Configure the SSH Server + template: + src: etc_ssh_sshd_config.j2 + dest: /etc/ssh/sshd_config + owner: root + group: root + mode: 0644 + notify: Restart SSH + tags: ssh + +#- name: Update ssh_known_hosts +# lineinfile: +# dest: /etc/ssh/ssh_known_hosts +# regexp: "^{{ hostvars[item].ansible_hostname }}," +# line: > +# {{ hostvars[item].ansible_hostname }},{{ hostvars[item].ansible_fqdn }},{{ hostvars[item].ansible_default_ipv4.address }} +# ssh-rsa {{ hostvars[item].ansible_ssh_host_key_rsa_public }} +# state: present +# create: yes +# owner: root +# group: root +# mode: 0644 +# with_items: "{{ groups.all|sort }}" +# when: item in hostvars +# tags: ssh diff --git a/roles/ssh/templates/etc_ssh_ssh_config.j2 b/roles/ssh/templates/etc_ssh_ssh_config.j2 new file mode 100644 index 0000000..0095d04 --- /dev/null +++ b/roles/ssh/templates/etc_ssh_ssh_config.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +# See the ssh_config(5) manpage for details + +{% from 'ssh_common.j2' import ssh_config with context %} +{% call ssh_config(ssh_client_settings) %}{% endcall %} + +# EOF diff --git a/roles/ssh/templates/etc_ssh_sshd_config.j2 b/roles/ssh/templates/etc_ssh_sshd_config.j2 new file mode 100644 index 0000000..faa704f --- /dev/null +++ b/roles/ssh/templates/etc_ssh_sshd_config.j2 @@ -0,0 +1,8 @@ +# {{ ansible_managed }} + +# See the sshd_config(5) manpage for details + +{% from 'ssh_common.j2' import ssh_config with context %} +{% call ssh_config(ssh_server_settings) %}{% endcall %} + +# EOF diff --git a/roles/ssh/templates/ssh_common.j2 b/roles/ssh/templates/ssh_common.j2 new file mode 100644 index 0000000..e264669 --- /dev/null +++ b/roles/ssh/templates/ssh_common.j2 @@ -0,0 +1,25 @@ +# {{ ansible_managed }} +{% macro ssh_config(settings, caller='') %} +{% set sections = ('Host', 'Match') %} + +{%- for key, value in settings|dictsort if key not in sections %} +{% if value is sequence and value is not string %} +{% for item in value %} +{{ key }} {{ item }} +{% endfor %} +{% else %} +{{ key }} {{ ['no','yes'][value|int] if value in (False,True) else value }} +{% endif %} +{% endfor %} + +{%- for section in sections if section in settings %} + +{% for item in settings[section] %} +{{ section }} {{ item[section] }} +{% for key, value in item|dictsort if key != section %} + {{ key }} {{ ['no','yes'][value|int] if value in (False,True) else value }} +{% endfor %} +{% endfor %} +{% endfor -%} + +{% endmacro %} diff --git a/tasks/consul.yml b/tasks/consul.yml new file mode 100644 index 0000000..f86de28 --- /dev/null +++ b/tasks/consul.yml @@ -0,0 +1,82 @@ +--- + +- hosts: consul + vars: + consul_config_dir: /etc/consul.d + consul_data_dir: /opt/consul + consul_install_dir: /usr/local/bin + become: true + tasks: + + - name: install required UNZIP + package: + name: unzip + + - name: add the CONSUL group + group: + name: consul + state: present + gid: 199 + + - name: add the CONSUL user + user: + name: consul + comment: CONSUL user + state: present + uid: 199 + + - name: install CONSUL from HashiCorp + unarchive: + src: https://releases.hashicorp.com/consul/1.8.5/consul_1.8.5_linux_amd64.zip + dest: /usr/local/bin + remote_src: yes + mode: 0755 + owner: consul + group: consul + + - name: create CONSUL required data folders + file: + path: /opt/consul + state: directory + mode: '0755' + recurse: yes + owner: consul + group: consul + + - name: create CONSUL required config folders + file: + path: /etc/consul.d + state: directory + mode: '0755' + recurse: yes + owner: consul + group: consul + + - name: copy CONSUL systemd script + copy: + src: "{{ item }}" + dest: /etc/consul.d + owner: consul + group: consul + with_items: + - consul/configs/consul.hcl + - consul/configs/service-ssh.hcl + + - name: send consul configuration file + template: + dest: "{{ consul_config_dir }}/config.json" + src: consul.config.j2 + + - name: ensure consul service file exists + template: + dest: /etc/systemd/system/consul.service + src: consul.service.j2 + force: yes + mode: 0644 + + - name: enable CONSUL systemd script + service: + name: consul + enabled: yes + daemon_reload: yes + state: restarted diff --git a/tasks/consul/configs/consul.hcl b/tasks/consul/configs/consul.hcl new file mode 100644 index 0000000..45cdf8c --- /dev/null +++ b/tasks/consul/configs/consul.hcl @@ -0,0 +1,7 @@ +datacenter = "MSI-DC" +data_dir = "/opt/consul" +encrypt = "eRhnp22+c0bkV0wPolk6Mw==" +retry_join = ["consul-admin"] +performance { + raft_multiplier = 1 +} diff --git a/tasks/consul/configs/consul.service b/tasks/consul/configs/consul.service new file mode 100644 index 0000000..b75b4be --- /dev/null +++ b/tasks/consul/configs/consul.service @@ -0,0 +1,23 @@ +[Unit] +Description=Consul Service Discovery Agent +Documentation=https://www.consul.io/ +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=consul +Group=consul +ExecStart=/usr/local/bin/consul agent -server -ui \ + -data-dir=/opt/consul \ + -node=consul-%H \ + -config-dir=/etc/consul.d + +ExecReload=/bin/kill -HUP $MAINPID +KillSignal=SIGINT +TimeoutStopSec=5 +Restart=on-failure +SyslogIdentifier=consul + +[Install] +WantedBy=multi-user.target diff --git a/tasks/consul/configs/server.hcl b/tasks/consul/configs/server.hcl new file mode 100644 index 0000000..00d7216 --- /dev/null +++ b/tasks/consul/configs/server.hcl @@ -0,0 +1,4 @@ +server = true +bootstrap_expect = 2 +bind_addr = "10.11.10.101" +ui = true diff --git a/tasks/consul/configs/service-apache.hcl b/tasks/consul/configs/service-apache.hcl new file mode 100644 index 0000000..c9690bc --- /dev/null +++ b/tasks/consul/configs/service-apache.hcl @@ -0,0 +1,10 @@ +service { + name = "apache" + port = 443 + tags = [ "srv1", "pedimedic", "webmail", "git" ] + check { + http = "https://srv1.maruntiel.com" + interval = "5s" + tlsSkipVerify = true + } +} diff --git a/tasks/consul/configs/service-mysql.hcl b/tasks/consul/configs/service-mysql.hcl new file mode 100644 index 0000000..fb71b41 --- /dev/null +++ b/tasks/consul/configs/service-mysql.hcl @@ -0,0 +1,9 @@ +service { + name = "mariadb" + port = 3306 + tags = [ "db" ] + check { + tcp = "localhost:3306" + interval = "5s" + } +} diff --git a/tasks/consul/configs/service-ssh.hcl b/tasks/consul/configs/service-ssh.hcl new file mode 100644 index 0000000..edd4e29 --- /dev/null +++ b/tasks/consul/configs/service-ssh.hcl @@ -0,0 +1,8 @@ +service { + name = "SSHD" + port = 22 + check { + tcp = "localhost:22" + interval = "5s" + } +} diff --git a/tasks/consul/consul-tag b/tasks/consul/consul-tag new file mode 100644 index 0000000..8d82074 --- /dev/null +++ b/tasks/consul/consul-tag @@ -0,0 +1,70 @@ +#!/usr/bin/python3 + +import os +import sys +import requests + +CONSUL_API = 'http://localhost:8500' + + +def get_service(sess, service_id): + r = sess.get(CONSUL_API + '/v1/agent/services', timeout=2) + r.raise_for_status() + services = r.json() + + for svc in services.values(): + if svc['ID'] == service_id: + return svc + + return None + + +def change_service_tags(service, tags_to_add, tags_to_remove): + with requests.Session() as sess: + sess.headers = {'X-Consul-Token': os.getenv('CONSUL_HTTP_TOKEN')} + + svc = get_service(sess, service) + if svc: + new_tags = (set(svc.get('Tags', [])) | tags_to_add) - tags_to_remove + new_svc = { + 'ID': svc['ID'], + 'Name': svc['Service'], + 'Address': svc.get('Address', ''), + 'Port': svc.get('Port', 0), + 'Meta': svc.get('Meta', {}), + 'Tags': sorted(list(new_tags)), + 'EnableTagOverride': svc.get('EnableTagOverride', False), + } + for k, v in new_svc.items(): + print('{} = {}'.format(k, v)) + r = sess.put(CONSUL_API + '/v1/agent/service/register', json=new_svc, timeout=2) + r.raise_for_status() + + +def main(argv): + if len(argv) < 3: + print("Usage: consul-tag service +tag -tag...") + return 1 + + service = argv[1] + tags_to_add = set() + tags_to_remove = set() + for tag in argv[2:]: + if tag.startswith('-'): + tags_to_remove.add(tag[1:]) + elif tag.startswith('+'): + tags_to_add.add(tag[1:]) + else: + tags_to_add.add(tag) + + try: + change_service_tags(service, tags_to_add, tags_to_remove) + except Exception as exc: + print("Error: {}".format(exc)) + return 2 + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/tasks/consul/consul.1.7.4 b/tasks/consul/consul.1.7.4 new file mode 100644 index 0000000..230b742 Binary files /dev/null and b/tasks/consul/consul.1.7.4 differ diff --git a/tasks/handlers/consul.yml b/tasks/handlers/consul.yml new file mode 100644 index 0000000..e2a7c01 --- /dev/null +++ b/tasks/handlers/consul.yml @@ -0,0 +1,8 @@ +--- +# handlers file for consul-server +- name: restart consul + systemd: + name: consul.service + daemon_reload: yes + state: restarted + become: yes diff --git a/tasks/templates/consul.config.j2 b/tasks/templates/consul.config.j2 new file mode 100644 index 0000000..0f1ea90 --- /dev/null +++ b/tasks/templates/consul.config.j2 @@ -0,0 +1,13 @@ +{ + "addresses": { + "http": "{{ ansible_facts['all_ipv4_addresses'] | last}} 127.0.0.1" + }, + "server": true, + "advertise_addr": "{{ ansible_facts['all_ipv4_addresses'] | last}}", + "client_addr": "127.0.0.1 {{ ansible_facts['all_ipv4_addresses'] | last }}", + "connect": { + "enabled": true + }, + "data_dir": "{{ consul_data_dir }}", + "bootstrap": true +} diff --git a/tasks/templates/consul.service.j2 b/tasks/templates/consul.service.j2 new file mode 100644 index 0000000..d2c1087 --- /dev/null +++ b/tasks/templates/consul.service.j2 @@ -0,0 +1,10 @@ +[Unit] +Description==Consul Service Discovery Agent + +[Service] +WorkingDirectory={{ consul_config_dir }} +User=root +ExecStart={{ consul_install_dir }}/consul agent -config-dir={{ consul_config_dir }} -node=consul-%H + +[Install] +WantedBy=multi-user.target diff --git a/tasks/vars/consul.yml b/tasks/vars/consul.yml new file mode 100644 index 0000000..3d7dd83 --- /dev/null +++ b/tasks/vars/consul.yml @@ -0,0 +1,7 @@ +--- +# vars file for consul-server +consul_version: 1.8.5 +consul_zip_file: consul_{{ consul_version }}_linux_amd64.zip +consul_install_dir: /usr/local/bin +consul_config_dir: /etc/consul. +consul_data_dir: /opt/consul