diff --git a/.github/actions/ansible/Dockerfile b/.github/actions/ansible/Dockerfile new file mode 100644 index 0000000..55c3630 --- /dev/null +++ b/.github/actions/ansible/Dockerfile @@ -0,0 +1,7 @@ +FROM lexauw/ansible-alpine + +WORKDIR /github/workspace +RUN apk add rsync + +COPY entrypoint.sh /usr/local/bin/entrypoint +ENTRYPOINT [ "entrypoint" ] \ No newline at end of file diff --git a/.github/actions/ansible/action.yml b/.github/actions/ansible/action.yml new file mode 100644 index 0000000..f70ad48 --- /dev/null +++ b/.github/actions/ansible/action.yml @@ -0,0 +1,29 @@ +name: Ansible +description: Ansible +inputs: + ssh_key_b64: + description: SSH private key to be used by ansible to access remote hosts (in base64) + required: true + playbook: + description: Playbook to run + required: true + inventory: + description: Inventory + required: true + inventory_script: + description: Whether inventory is a script + required: false + default: "false" + extra_vars: + description: Extra variables + required: false + wait: + description: Seconds to wait before starting + required: false + default: "0" + vault_password: + description: Password for ansible-vault + required: false +runs: + using: docker + image: 'Dockerfile' \ No newline at end of file diff --git a/.github/actions/ansible/entrypoint.sh b/.github/actions/ansible/entrypoint.sh new file mode 100755 index 0000000..3a11887 --- /dev/null +++ b/.github/actions/ansible/entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +mkdir -p ~/.ssh && echo $INPUT_SSH_KEY_B64 | base64 -d > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa + +if [ "$INPUT_INVENTORY_SCRIPT" == "true" ] +then + chmod +x $INPUT_INVENTORY; +fi + +if [ -n "$INPUT_VAULT_PASSWORD" ]; then + echo $INPUT_VAULT_PASSWORD > /tmp/vault-passwd + export ANSIBLE_VAULT_PASSWORD_FILE=/tmp/vault-passwd +fi + +sleep $INPUT_WAIT +ANSIBLE_HOST_KEY_CHECKING=False \ + ansible-playbook \ + -i $INPUT_INVENTORY \ + $INPUT_PLAYBOOK \ + -e "$INPUT_EXTRA_VARS" \ + --key-file ~/.ssh/id_rsa diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..c8022f1 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,20 @@ +on: + push: + branches: + - master + +jobs: + deploy: + name: apply ansible scripts + runs-on: ubuntu-latest + steps: + - name: checkout repository + uses: actions/checkout@v2 + + - name: apply ansible + uses: ./.github/actions/ansible + with: + ssh_key_b64: ${{ secrets.SSH_KEY_B64 }} + playbook: ansible/playbook.yml + inventory: ansible/inventory.ini + extra_vars: reset=false diff --git a/ansible/inventory.ini b/ansible/inventory.ini new file mode 100644 index 0000000..72cf09d --- /dev/null +++ b/ansible/inventory.ini @@ -0,0 +1,5 @@ +[validators] +3.235.19.123 +34.234.208.18 +3.231.205.70 +3.216.27.122 diff --git a/ansible/playbook.yml b/ansible/playbook.yml new file mode 100644 index 0000000..c050f5a --- /dev/null +++ b/ansible/playbook.yml @@ -0,0 +1,22 @@ +--- +- hosts: validators + become: yes + remote_user: ubuntu + vars: + dex_version: master + reset: false + chain_id: uex-testnet + denom: asset + key_password: supersecret + roles: + - common + - validator + +- hosts: validators[0] + become: yes + remote_user: ubuntu + vars: + domain_name: dex.hopto.org + letsencrypt_email: helder@tendermint.com + roles: + - webapp diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000..192e446 --- /dev/null +++ b/ansible/roles/common/tasks/main.yml @@ -0,0 +1,36 @@ +--- +- name: add the dex user + user: + name: dex + shell: /bin/bash + +- name: install required tools + apt: + update_cache: yes + pkg: + - software-properties-common + - make + - jq + - build-essential + - moreutils + +- name: add golang ppa + apt_repository: + repo: ppa:longsleep/golang-backports + +- name: install golang + apt: + name: golang-go + update_cache: yes + +- name: set GOPATH + lineinfile: + dest: /etc/bash.bashrc + state: present + line: "export GOPATH=$HOME/go" + +- name: add GOPATH/bin PATH + lineinfile: + dest: /etc/bash.bashrc + state: present + line: "export PATH=$GOPATH/bin:$PATH" diff --git a/ansible/roles/validator/tasks/main.yml b/ansible/roles/validator/tasks/main.yml new file mode 100644 index 0000000..ac6203f --- /dev/null +++ b/ansible/roles/validator/tasks/main.yml @@ -0,0 +1,183 @@ +--- +- name: clone dex-demo repo + git: + repo: https://github.com/tendermint/dex-demo + dest: /usr/local/src/dex-demo + version: "{{ dex_version }}" + +- name: install dexd and dexcli + become_user: dex + make: + chdir: /usr/local/src/dex-demo + target: install + +- name: install dexd service + template: + src: templates/dexd.service.j2 + dest: /etc/systemd/system/dexd.service + owner: root + group: root + mode: 0644 + +- name: install dexcli rest server service + template: + src: templates/dexcli.service.j2 + dest: /etc/systemd/system/dexcli.service + owner: root + group: root + mode: 0644 + +- name: stop dexd service + systemd: + name: dexd + state: stopped + +- name: stop dexcli service + systemd: + name: dexcli + state: stopped + +- name: reset node and genesis + when: reset | bool == true + become_user: dex + shell: | + ~/go/bin/dexd unsafe-reset-all + rm -rf ~/.dex* + +- name: config dexcli + become_user: dex + shell: | + ~/go/bin/dexcli config chain-id {{ chain_id }} + ~/go/bin/dexcli config output json + ~/go/bin/dexcli config indent true + ~/go/bin/dexcli config trust-node true + +- name: check if genesis exist + become_user: dex + stat: + path: ~/.dexd/config/genesis.json + register: genesis + +- name: initialize node + when: genesis.stat.exists == false + become_user: dex + args: + executable: /bin/bash + shell: ~/go/bin/dexd init {{ ansible_hostname }} --chain-id {{ chain_id }} + +- name: get genesis time + run_once: true + become_user: dex + args: + chdir: ~/.dexd/config + shell: jq -r .genesis_time genesis.json + register: genesis_time + +- name: synchronize genesis time and chain id + become_user: dex + args: + chdir: ~/.dexd/config + shell: | + jq -r '.genesis_time="{{ genesis_time.stdout }}"' genesis.json | sponge genesis.json + jq -r '.chain_id="{{ chain_id }}"' genesis.json | sponge genesis.json + +- name: check if key exists + become_user: dex + stat: + path: ~/.dexcli/keys + register: keyring + +- name: create key + when: keyring.stat.exists == false + become_user: dex + no_log: true + args: + executable: /bin/bash + shell: 'echo -e "{{ key_password }}\n{{ key_password }}" | ~/go/bin/dexcli keys add {{ ansible_hostname }} 2> ~/secret' + +- name: get node id + become_user: dex + args: + executable: /bin/bash + shell: ~/go/bin/dexd tendermint show-node-id + register: node_id + +- name: set persistent peers list + become_user: dex + args: + executable: /bin/bash + chdir: ~/.dexd/config + shell: | + IFS=',' read -r -a IDS <<< "{{ groups['validators'] | reject('equalto', inventory_hostname) | map('extract', hostvars, ['node_id', 'stdout']) | join(",") }}" + IFS=',' read -r -a IPS <<< "{{ groups['validators'] | reject('equalto', inventory_hostname) | map('extract', hostvars, ['ansible_default_ipv4', 'address']) | join(",") }}" + for idx in "${!IDS[@]}" + do + peers="$peers,${IDS[index]}@${IPS[index]}:26656"; + done + sed -i "s/persistent_peers =.*/persistent_peers = \"${peers:1}\"/g" config.toml + +- name: get key address + become_user: dex + args: + executable: /bin/bash + shell: ~/go/bin/dexcli keys show {{ ansible_hostname }} -a + register: key_address + +- name: adding keys to genesis + become_user: dex + with_items: "{{ groups['validators'] | map('extract', hostvars, ['key_address', 'stdout']) | list }}" + args: + executable: /bin/bash + chdir: ~/.dexd/config + shell: ~/go/bin/dexd add-genesis-account {{ item }} 40000000000000000000000000stake,40000000000000000000000000{{ denom }} || true + +- name: (re)generate gentx + become_user: dex + args: + executable: /bin/bash + warn: no + shell: | + rm -rf ~/.dexd/config/gentx + echo '{{ key_password }}' | ~/go/bin/dexd gentx --name {{ ansible_hostname }} + +- name: grab gentx file + become_user: dex + synchronize: + src: /home/dex/.dexd/config/gentx + dest: /tmp + mode: pull + +- name: add all gentxs + become_user: dex + synchronize: + src: /tmp/gentx + dest: /home/dex/.dexd/config + mode: push + owner: no + group: no + +- name: remove temporary gentx files + become: no + local_action: command rm -rf /tmp/gentx + args: + warn: no + +- name: collect all gentxs + become_user: dex + args: + executable: /bin/bash + shell: ~/go/bin/dexd collect-gentxs + +- name: start the dexd service + systemd: + name: dexd + daemon_reload: yes + state: restarted + enabled: True + +- name: start the dexcli rest server service + systemd: + name: dexcli + daemon_reload: yes + state: restarted + enabled: True diff --git a/ansible/roles/validator/templates/dexcli.service.j2 b/ansible/roles/validator/templates/dexcli.service.j2 new file mode 100644 index 0000000..e26dba7 --- /dev/null +++ b/ansible/roles/validator/templates/dexcli.service.j2 @@ -0,0 +1,13 @@ +[Unit] +Description=dexcli +Requires=network-online.target +After=network-online.target + +[Service] +User=dex +Group=dex +ExecStart=/home/dex/go/bin/dexcli rest-server --trust-node true +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/ansible/roles/validator/templates/dexd.service.j2 b/ansible/roles/validator/templates/dexd.service.j2 new file mode 100644 index 0000000..92b83c2 --- /dev/null +++ b/ansible/roles/validator/templates/dexd.service.j2 @@ -0,0 +1,13 @@ +[Unit] +Description=dexd +Requires=network-online.target +After=network-online.target + +[Service] +User=dex +Group=dex +ExecStart=/home/dex/go/bin/dexd start --pruning=nothing +Restart=on-failure + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/ansible/roles/webapp/tasks/main.yml b/ansible/roles/webapp/tasks/main.yml new file mode 100644 index 0000000..5fbb16b --- /dev/null +++ b/ansible/roles/webapp/tasks/main.yml @@ -0,0 +1,58 @@ +--- +- name: install nginx and letsencrypt + apt: + update_cache: yes + pkg: + - nginx + - letsencrypt + +- name: create letsencrypt directory + file: + name: /var/www/letsencrypt + state: directory + +- name: remove default nginx config + file: + name: /etc/nginx/sites-enabled/default + state: absent + +- name: install system nginx config + template: + src: nginx.conf.j2 + dest: /etc/nginx/nginx.conf + +- name: install nginx site for letsencrypt requests + template: + src: nginx-http.j2 + dest: /etc/nginx/sites-enabled/http + +- name: reload nginx to activate letsencrypt site + service: + name: nginx + state: restarted + +- name: create letsencrypt certificate + shell: letsencrypt certonly -n --webroot -w /var/www/letsencrypt -m {{ letsencrypt_email }} --agree-tos -d {{ domain_name }} + args: + creates: /etc/letsencrypt/live/{{ domain_name }} + +- name: generate dhparams + shell: openssl dhparam -out /etc/nginx/dhparams.pem 2048 + args: + creates: /etc/nginx/dhparams.pem + +- name: install nginx site for specified site + template: + src: nginx-le.j2 + dest: /etc/nginx/sites-enabled/le + +- name: reload nginx to activate specified site + service: + name: nginx + state: restarted + +- name: add letsencrypt cronjob for cert renewal + cron: + name: letsencrypt_renewal + special_time: weekly + job: letsencrypt --renew certonly -n --webroot -w /var/www/letsencrypt -m {{ letsencrypt_email }} --agree-tos -d {{ domain_name }} && service nginx reload \ No newline at end of file diff --git a/ansible/roles/webapp/templates/nginx-http.j2 b/ansible/roles/webapp/templates/nginx-http.j2 new file mode 100644 index 0000000..e4e99b1 --- /dev/null +++ b/ansible/roles/webapp/templates/nginx-http.j2 @@ -0,0 +1,16 @@ +server_tokens off; + +server { + listen 80 default_server; + server_name {{ domain_name }}; + + location /.well-known/acme-challenge { + root /var/www/letsencrypt; + try_files $uri $uri/ =404; + } + + location / { + rewrite ^ https://{{ domain_name }}$request_uri? permanent; + } +} + diff --git a/ansible/roles/webapp/templates/nginx-le.j2 b/ansible/roles/webapp/templates/nginx-le.j2 new file mode 100644 index 0000000..5eccedb --- /dev/null +++ b/ansible/roles/webapp/templates/nginx-le.j2 @@ -0,0 +1,33 @@ +add_header X-Frame-Options SAMEORIGIN; +add_header X-Content-Type-Options nosniff; +add_header X-XSS-Protection "1; mode=block"; + +# HTTPS server +# +server { + listen 443 ssl default deferred; + server_name {{ domain_name }}; + + ssl on; + ssl_certificate /etc/letsencrypt/live/{{ domain_name }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ domain_name }}/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/{{ domain_name }}/fullchain.pem; + + ssl_session_cache shared:SSL:50m; + ssl_session_timeout 5m; + ssl_stapling on; + ssl_stapling_verify on; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + + ssl_dhparam /etc/nginx/dhparams.pem; + ssl_prefer_server_ciphers on; + + location / { + proxy_redirect off; + proxy_set_header X-real-ip $remote_addr; + proxy_set_header X-forward-for $proxy_add_x_forwarded_for; + proxy_pass http://localhost:1317; + } +} diff --git a/ansible/roles/webapp/templates/nginx.conf.j2 b/ansible/roles/webapp/templates/nginx.conf.j2 new file mode 100644 index 0000000..6e03214 --- /dev/null +++ b/ansible/roles/webapp/templates/nginx.conf.j2 @@ -0,0 +1,27 @@ +user www-data; +worker_processes 4; +pid /run/nginx.pid; + +events { + worker_connections 768; +} + +http { + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + gzip on; + gzip_disable "msie6"; + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +}