AWXをSSL対応させてみた

# Ansible AWX をSSL対応にしてみた。

前提：Ansible AWXは導入済みとする事

# デフォルト状態だとSSL化出来ない
・鍵を登録する事ができない
・Let's Encrypt の Webhook が使えない

## 鍵を登録出来るようにする
証明書が無いと始まらないので取り敢えず自己証明書でも作る
非常に雑だけどこんな感じ？

```shell:
openssl genrsa 2024 > server.key
openssl req -new -key server.key > server.csr
openssl x509 -req -days 3650 -signkey server.key < server.csr > server.crt
```

## inventory を修正し鍵の項目を作る
/root/awx/installer/inventory を手直し

```python:/root/awx/installer/inventory
ssl_certificate=./server.crt
ssl_certificate_key=./server.key
```

## コンテナに鍵を渡す部分のために一部ソースを修正

```python:roles/local_docker/templates/docker-compose.yml.j2

  web:
    image: {{ awx_web_docker_actual_image }}
    container_name: awx_web

<snip>

    volumes:
      - "{{ docker_compose_dir }}/SECRET_KEY:/etc/tower/SECRET_KEY"
      - "{{ docker_compose_dir }}/environment.sh:/etc/tower/conf.d/environment.sh"
      - "{{ docker_compose_dir }}/credentials.py:/etc/tower/conf.d/credentials.py"
これを追加  ---->      - "{{ docker_compose_dir }}/nginx.conf:/etc/nginx/nginx.conf"

<snip>

    {% if ssl_certificate is defined %}
      - "{{ ssl_certificate +':/etc/nginx/awxweb.pem:ro' }}"
これを追加  ---->      - "{{ ssl_certificate_key +':/etc/nginx/awxweb.key:ro' }}"
    {% endif %}
```

## nginx.conf.j2 を展開しないバグがあるので以下のファイルを追加

```text:/tmp/awxcompose/nginx.conf

#user awx;

worker_processes  1;

pid        /tmp/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /dev/stdout main;
#X#    access_log  /var/log/nginx/nginx_access.log main;
    error_log   /var/log/nginx/nginx_error.log;

    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    sendfile        on;
    #tcp_nopush     on;
    #gzip  on;

    upstream uwsgi {
        server 127.0.0.1:8050;
        }

    upstream daphne {
        server 127.0.0.1:8051;
    }

        server {
#X#        listen 8052 default_server;
        listen 8052;
        server_name _;

        # Redirect all HTTP links to the matching HTTPS page
        return 301 https://$host$request_uri;
    }

    server {
        listen 8053 ssl default_server;

        ssl_certificate /etc/nginx/awxweb.pem;
        ssl_certificate_key /etc/nginx/awxweb.key;

        ssl_session_cache  builtin:1000  shared:SSL:10m;
        ssl_prefer_server_ciphers on;
        ssl_protocols TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';

        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_session_timeout 20m;

        # If you have a domain name, this is where to add it
        server_name _;
        keepalive_timeout 65;

        # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
        add_header Strict-Transport-Security max-age=15768000;
        add_header Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' cdn.pendo.io; img-src 'self' data:; report-uri /csp-violation/";
        add_header X-Content-Security-Policy "default-src 'self'; connect-src 'self' ws: wss:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' cdn.pendo.io; img-src 'self' data:; report-uri /csp-violation/";

        # Protect against click-jacking https://www.owasp.org/index.php/Testing_for_Clickjacking_(OTG-CLIENT-009)
        add_header X-Frame-Options "DENY";

        location /nginx_status {
          stub_status on;
          access_log off;
          allow 127.0.0.1;
          deny all;
        }

        location /static/ {
            alias /var/lib/awx/public/static/;
        }

        location /favicon.ico { alias /var/lib/awx/public/static/favicon.ico; }

        location /websocket {
            # Pass request to the upstream alias
            proxy_pass http://daphne;
            # Require http version 1.1 to allow for upgrade requests
            proxy_http_version 1.1;
            # We want proxy_buffering off for proxying to websockets.
            proxy_buffering off;
            # http://en.wikipedia.org/wiki/X-Forwarded-For
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            # enable this if you use HTTPS:
            proxy_set_header X-Forwarded-Proto https;
            # pass the Host: header from the client for the sake of redirects
            proxy_set_header Host $http_host;
            # We've set the Host header, so we don't need Nginx to muddle
            # about with redirects
            proxy_redirect off;
            # Depending on the request value, set the Upgrade and
            # connection headers
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Connection $connection_upgrade;
        }

        location / {
            # Add trailing / if missing
            rewrite ^(.*)$http_host(.*[^/])$ $1$http_host$2/ permanent;
            uwsgi_read_timeout 120s;
            uwsgi_pass uwsgi;
            include /etc/nginx/uwsgi_params;            proxy_set_header X-Forwarded-Port 443;
        }
    }
}

```
を書き込んでおく

## secret_key を一時変更

```python:/root/awx/installer/inventory

<snip>

secret_key=awxsecret を何か適当に書き換え「""」でも可
```

## AWX を再インストール(コンテナのリメイク）

```
ansible-playbook -i inventory install.yml
```
正常に完了したら `http://該当ホスト/` へアクセスすると自動的に `https://` に rewrite されていると思います。

## Let's Encrypt の Webhook が使えないため standalone で証明書を取得
### Let's Encrypt を取得 

```shell:
yum -y install certbot
```

### certbot-auto を /usr/local/sbin にコピー

```
cd certbot
cp certbot-auto /usr/local/sbin
```

### standalone で証明書取得の為一旦 docker を停止

```shell:
systemctl stop docker
```

### standalone で証明書取得をテストモードで実行(--dry-run)

```shell:
certbot-auto certonly --standalone --agree-tos -m encrypy@example.com -d encrypt.example.com --dry-run
```

これは、1時間に5回以上のリクエストを受け付けないという `rate-limit` があるため出来るだけ `--dry-run` で確認しておいたほうが良い。

```shell:
certbot-auto certonly --standalone --agree-tos -m encrypy@example.com -d encrypt.example.com --test-cert
```

試験的に1週間位の期間の証明書を取得することも出来る

### 動作確認が済んだら実際に取得

```shell:
certbot-auto certonly --standalone --agree-tos -m encrypy@example.com -d encrypt.example.com
```

正常に取得出来ると証明書は `/etc/letsencrypt/live/encrypt.example.com/fullchain.pem`
秘密鍵は `/etc/letsencrypt/live/encrypt.example.com/private.pem` 
に記録されている。

ここまで読んで「う〜ん、止めたくないし色々手間だな」とおもったら 
[SSLなう!](https://sslnow.ml/) を使用すれば手間はないかも知れません。
＃但し、後々手間になりますけどね

### inventory の 修正


```python:/root/awx/installer/inventory
ssl_certificate=/etc/letsencrypt/live/encrypt.example.com/fullchain.pem
ssl_certificate_key=/etc/letsencrypt/live/encrypt.example.com/privkey.pem
```
に修正
また、secret_key を変更する

```python:/root/awx/installer/inventory
secret_key=awxsecret を何か適当に書き換え「""」でも可
```

なぜ secret_key を書き換えるのかと言うと、コンテナイメージ作成の変更の有無を見ているからである。

#### コンテナの再ビルド

```shell

cd /root/installer/
ansible-playbook -i inventory install.yml
```

これでLet's Encrypt での証明書が使えるようになりました。
さぁ、[SSL Labs](https://www.ssllabs.com/ssltest/) で試験をしてみましょう。
＃数分で終わります


### さて、証明書の自動更新どうしよう・・・・・

Let's encrypt は無料ですが期間が３ヶ月しかありません。
３ヶ月ごとに上記作業を手作業でやるのはツライ・・・・・・
刺し身タンポポの仕事は嫌なので自動化します。

```shell:cert_update.sh
#!/bin/bash
INVENTORY=/root/awx/installer/inventory
#### date md5取得
MD5_DATE=`date | md5sum | awk '{print $1}'`
MD5_DATE_OLD=`cat MD5_DATE_OLD`

#### 証明書取得
/usr/local/sbin/certbot-auto renew

#### inventry の secret_key を更新
sed -i "s/^secret_key=$MD5_DATE_OLD/secret_key=$MD5_DATE/g" $INVENTORY
echo $MD5_DATE  > MD5_DATE_OLD

#### awx再インストール
cd /root/awx/installer/
ansible-playbook -i inventory install.yml
```
こんな感じのスクリプトを毎月1日に実行するように `cron` に仕込んで起くと便利かもしれません。