AnsibleでGitLab/GitBucket用のReverse Proxy + SSHサーバーを構築してみた

# はじめに

Web系サービスを構築していく中で、今回はGitlab, Gitbucketによるコードのリポジトリサーバーを構築しました。

通常の80,443番ポートのProxyであればnginxを利用するのですが、今回はSSHの接続も許可したいので、10022,20022番ポートを内部のGitlab, Gitbucketサーバーの22番ポートにそれぞれ接続します。

問題はGatewayにしているサーバーは内部ネットワークのdefault gW(Router)ではなかったという点です。

![20220829_QiitaAnsible_DNAT_SNAT管理.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78296/013cea7b-66dc-2b95-e034-8af1e534b3b7.png)

これはこの記事の内容と直接関係ないですがセキュリティの観点から、Gatewayのdefault gwはRouterになっていて、パッケージの更新などインターネットに接続することはできますが、逆向きの通信は内部ネットワーク(192.168.1.0/24、192.168.2.0/24)とだけを許可しています。

> 補足すると192.168.0.0/24等のサブネットの端末から、Gateway(192.168.1.10)に接続しても、戻りは192.168.2.10から出てdefault gwのRouter(192.168.1.1)を経由します。このためRouterを経由せずに通信できる192.168.1.0/24なIPを持っていないクライアントとの通信は成立しません。

> 【2022/10/16追記】 iptablesによるポートフォワードがgitlabに対してだけ不安定になったため、socatコマンドによるポート転送に変更しています。socat自体もアドレス変換にiptablesを使用しますが、もし不具合があれば利用を検討してください。

# 環境

* Ansible 2.12.8
* Ubuntu 22.04.1

# 方針

Gatewayが192.168.2.0/24ネットワークのdefault gwであれば設定は少し楽ですが、Gateway(192.168.1.10)の10022番ポートに来たSSH接続を、192.168.2.10から出して192.168.2.122の10022番ポートにフォワードしています。このフォワード処理にはDNATを利用します。

GitbucketはKubernetesで動いているのでIPアドレスはServiceオブジェクトのLoadbalancerでEXTERNAL-IPを割り当てています。ポート番号は22番など自由に設定できたのですが、サービスに使うポートと内部のポート番号が異なるのは勘違いの元なので、一致させています。

192.168.2.122からの返信をGateway(192.168.2.10)を経由させなければいけないので、SNATを利用しています。これにより、Gitbucket(192.168.2.122)はGateway(192.168.2.10)と通信しているように錯覚しています。

最終的にGitbucketからの返信はGateway(192.168.1.10)を経由して、クライアントに戻されます。

# Ansibleによる設定

設定には自前のGalaxy Roleを利用しています。

* [https://galaxy.ansible.com/YasuhiroABE/myfavorite-setting](https://galaxy.ansible.com/YasuhiroABE/myfavorite-setting)
* [https://github.com/YasuhiroABE/ansible-myfavorite-setting](https://github.com/YasuhiroABE/ansible-myfavorite-setting)

このRoleは良く使うパッケージを導入したり、cron-aptを問答無用で適用したりするので、中身を読んで参考にする程度に留めておいてください。

## playbook/settings.yamlファイルの内容

設定に使うYAMLファイルは次のようになっています。

```yaml:playbook/settings.yamlファイル
---
- hosts: all
  vars:
    mfts_hostname: "gateway"
    mfts_sshd_listen_ipaddr: 192.168.1.10
    mfts_sysctl_rules:
      - { name: net.ipv4.ip_forward, value: 1 }
    mfts_additional_packages:
      - ca-certificates
      - curl
      - gnupg2
      - iptables-persistent
      - lsb-release
      - nginx
    ## ufw and iptables firewall settings
    mfts_ufw_enable: True
    mfts_ufw_enable_logging: True
    mfts_ufw_service_rules:
      - { type: "allow", port: "80", from_ip: "192.168.1.0/24", to_ip: "192.168.1.10/32" }
      - { type: "allow", port: "443", from_ip: "192.168.1.0/24", to_ip: "192.168.1.10/32" }
      - { type: "allow", port: "10022", from_ip: "192.168.1.0/24", to_ip: "192.168.1.10/32" }
    mfts_ufw_incoming_forward_rules:
      - { type: "allow", to_ip: "192.168.2.122", to_port: "10022" }
    mfts_ufw_outgoing_forward_rules:
      - { type: "allow", from_ip: "192.168.2.122", from_port: "10022" }
    mfts_iptables_dnat_portforwarding_rules:
      - { in_interface: "enp1s0", incoming_port: "10022", dest_port: "10022", dest: "192.168.2.122" }
    mfts_iptables_snat_portforwarding_rules:
      - { dest_port: "10022", dest: "192.168.2.122", src_port: "10022", src: "192.168.2.10" }
    mfts_copy_files:
      ## nginx
      - { src: "{{ inventory_dir }}/files/nginx/server.conf", dest: "/etc/nginx/conf.d/server.conf", owner: "root", group: "root", mode: "0644" }
    mfts_lineinfile_after_copyfiles:
      - { path: "/etc/default/ufw", regexp: "^IPV6=", line: "IPV6=no" }
    mfts_command_after_copyfiles:
      - { command: "netplan apply", become: "yes" }
      - { command: "/usr/sbin/iptables-save | tee /etc/iptables/rules.v4", become: "yes" }
    mfts_systemd_rules:
      - { name: "nginx.service", state: "restarted", enabled: "yes", daemon_reload: "yes" }
  roles:
    - YasuhiroABE.myfavorite-setting
```

このGatewayを構成するYAMLファイルでは、IP Masqueradeは有効にしていません。またSSHのアクセスも拒否する設定にしているので必要であれば適宜修正しなければいけません。

ufwはデフォルトでOUTPUT以外のデフォルトポリシーをDROPにするので、FORWARDについてのルールも追加しています。

# iptables以外のポートフォワードの手段 (socatコマンド)

ポートフォワードを行う方法はいろいろありますが、サービスに利用する場合には、接続があったら即座にforkし、LISTENは継続しつつ、個別の通信は子プロセスによる管理としなければいけません。

socatコマンドは、おそらく手軽に動作させることができるツールだろうと思います。

実際の利用は次のようなコマンドを、プロセス監視の仕組みの中で動作させています。

```bash:
## gateway(nginx)で動作させる
$ nohup socat tcp4-listen:10022,reuseaddr,fork TCP:192.168.2.122:10022 &
```

lsocat自体もiptablesを利用するので、不要な設定(この場合はポート10022に関連した設定)は削除しておくことをお勧めします。

# さいごに

default gwにしているrouterであれば、192.168.2.0/24にあるサーバーからの通信が全て集るので、SNATは不要です。

今回のような構成はイレギュラーだとは思いますが、192.168.2.0/24のネットワークはdnsmasqでDHCPdによりIPやrouting情報を管理している点や、Kubernetesであるため一つのサービスのためにネットワーク構成を変更したくない点や、IP Masqueradeをする汎用NATサーバーを増やしたくないなどの理由から面倒な構成にすることにしました。

これが何かの参考になれば幸いです。