AnsibleでFTPSサーバ(vsftpd)を立ててみた

AWS事業本部 梶原@福岡オフィスです。

FTPSサーバを立てる機会があり、ちょっと証明書やらポート設定やらでつまづいてしまったので、次回一発でたてれるようにAnsible Playbookを作成しましたので共有します。
といっても、0からプレイブックを作成したわけでなく、vsFTPdを使用し、vsFTPdの設定はAnsible Galaxyに登録されている(https://galaxy.ansible.com/weareinteractive/vsftpd)を使用して、証明書の作成とvsftpdの設定を行っています。

では、Ansibleでもサクサクいきます。

事前準備

  • ansibleのインストール(pip等でインストールを実施してください)
  • AWS Configure等を実施してAWSの認証設定を行います。
  • weareinteractive/vsftpd をAnsible Galaxyからインストール
$ ansible-galaxy install weareinteractive.vsftpd

roles(環境変数:roles_path)の配下にweareinteractive.vsftpdまた、依存しているweareinteractive.opensslがダウンロードされます

EC2をAnsibleで立てる

せっかくなのでAnsibleでAWS上にEC2を立ててみようと思います。 すでに構築してあるEC2に対して実施する場合は本項はスキップしてください 構成はシンプルにパブリックサブネットにAmazon Linux2のインスタンスを立て、EIPを割り当てます。
Ansible Playbookでは

  • 鍵作成
  • VPC
  • サブネット
  • ルートテーブル
  • インターネットゲートウェイ
  • Elastic IP
  • セキュリティグループ
  • EC2(Amazon Linux2)

を行います。 セキュリティグループはSSH用の22番ポート、またFTPSように21番ポート, 50000-50010を開けています。 プレイブックは下記になります。 パラメータとして、秘密鍵またアクセス元のIPをvarsで定義しているので適時変更してください

  • 秘密鍵:key_name
  • アクセス元のIPアドレス:ipaddress

PlayBook(EC2作成)

# ansible-playbook aws-ftps.yml
- hosts: localhost
  connection: local
  gather_facts: False
  become: False
  vars:
    region: ap-northeast-1
    ipaddress: XXX.XXX.XXX.XXX/32
    key_name: ec2-ftps
  tasks:
    - ec2_key:
        region: "{{ region  }}"
        name: "{{ key_name  }}"
      register: _key
    - copy:
        content: "{{ _key.key.private_key }}"
        dest: "~/.ssh/{{ _key.key.name }}.pem"
        mode: 0600
      when: _key.changed
    - ec2_vpc_net:
        region: "{{ region }}"
        name: vpc-ftps
        cidr_block: 10.0.0.0/16
      register: _vpc
    - ec2_vpc_igw:
        region: "{{ region }}"
        vpc_id: "{{ _vpc.vpc.id }}"
      register: _igw
    - ec2_vpc_subnet:
        region: "{{ region }}"
        vpc_id: "{{ _vpc.vpc.id }}"
        cidr: 10.0.0.0/24
      register: _subnet
    - ec2_vpc_route_table:
        region: "{{ region }}"
        vpc_id: "{{ _vpc.vpc.id }}"
        tags:
          Name: Public
        subnets:
          - "{{ _subnet.subnet.id }}"
        routes:
          - dest: 0.0.0.0/0
            gateway_id: "{{ _igw.gateway_id  }}"
      register: _public_route_table
    # Security Group
    - ec2_group: 
        region: "{{ region }}"
        vpc_id: "{{ _vpc.vpc.id }}"
        name: sg_ssh
        description: sg-ssh
        rules:
          - proto: tcp
            from_port: 22
            to_port: 22
            cidr_ip: "{{ ipaddress }}"
      register: _group_ssh
    - ec2_group: 
        region: "{{ region }}"
        vpc_id: "{{ _vpc.vpc.id }}"
        name: sg_ftps
        description: sg-ftps
        rules:
          - proto: tcp
            from_port: 21
            to_port: 21
            cidr_ip: "{{ ipaddress }}"
          - proto: tcp
            from_port: 50000
            to_port: 50010
            cidr_ip: "{{ ipaddress }}"
      register: _group_ftps
    - ec2_instance:
        name: "ftps"
        key_name: "{{ key_name }}"
        vpc_subnet_id: "{{ _subnet.subnet.id }}"
        instance_type: t3.micro
        image_id: ami-0c3fd0f5d33134a76
        security_groups:
          - "{{ _group_ssh.group_id}}"
          - "{{ _group_ftps.group_id}}"
        network:
          assign_public_ip: true
      register: _instance
    - ec2_eip:
        region: "{{ region }}"
        device_id: "{{ item }}"
      loop: "{{ _instance.instance_ids }}"
      register: _eip

    - debug:
        msg: "Allocated IP is {{ _eip.results[0].public_ip }}"

実行結果

ansible-playbook aws-ftps.yml

<<略>>

TASK [debug] ********************************************************************************************************************************************************************************************************************ok: [localhost] => {
    "msg": "Allocated IP is AAA.AAA.AAA.AAA"
}

正常にAnsible Playbookが実行されますとEIPが割り当てられているので、(ここではAAA.AAA.AAA.AAA)こちらをhostsに設定します。

ssh_config

Host ftps
    Hostname AAA.AAA.AAA.AAA
    User ec2-user

hosts

[all]
ftps

接続確認

$ ssh -F ssh_config ftps

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
No packages needed for security; 6 packages available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-10-0-0-140 ~]$ logout

vsftpd-ftps.ymlを証明書の各値を環境、設定するドメインに合わせて書き換えてください。 各値の設定内容はOpenssl、またvsFTPDの設定項目に準拠しています。

主な設定内容としては

  • openssl_self_signed: 部分の作成したい証明書の内容
  • vsftpd_users: 接続するユーザ/パスワード
  • vsftpd_config: vsftpd.confの内容そのままです。プロトコル等を変更してください
    • pasv_addressは作成したEIPを設定します

になるかと思いますので、サーバ構成にあわせて変更お願いします。

vsftpd-ftps.yml

# ansible-playbook vsftpd-ftps.yml
- hosts: all
  vars:
    # 自己証明書作成
    openssl_keys_path: /etc/pki/tls/private
    openssl_certs_path: /etc/pki/tls/private
    openssl_csrs_path: /etc/pki/tls/private
    openssl_default_key_owner: root
    openssl_default_key_group: root
    openssl_default_cert_owner: root
    openssl_default_cert_group: root
    openssl_generate_csr: yes
    openssl_self_signed:
      - name: ftp.classmethod.info
        subject:
           C: JP
           ST: Tokyo
           L: Chiyoda-ku
           O: Classmethod,Inc.
           CN: ftp.classmethod.info
           emailAddress: hogefuga@classmethod.jp
    # FTP用 User 追加
    vsftpd_users:
       - username: ftpuser
         name: FTP User
         password: "{{ 'ftpuser' | password_hash('sha256', 'mysecretsalt') }}" # パスワード変更
    # vsFTPD設定
    vsftpd_config:
      anonymous_enable: NO
      local_enable: YES
      write_enable: YES
      allow_writeable_chroot: YES
      dirmessage_enable: NO
      ascii_upload_enable: YES
      ascii_download_enable: YES
      chroot_local_user: YES
      tcp_wrappers: NO
      connect_from_port_20: NO
      xferlog_std_format: NO
      pasv_enable: YES
      pasv_addr_resolve: YES
      pasv_address: AAA.AAA.AAA.AAA # 割り当てられたElasticIpアドレスに変更
      pasv_min_port: 50000
      pasv_max_port: 50010
      use_localtime: YES
      force_dot_files: YES
      # userlist_deny: NO
      listen: YES
      listen_ipv6: NO
      # FTP(s) 設定
      ssl_enable: YES
      ssl_sslv2: NO
      ssl_sslv3: NO
      ssl_tlsv1: YES
      ssl_tlsv1_1: YES
      ssl_tlsv1_2: YES
      force_local_data_ssl: YES
      force_local_logins_ssl: YES
      # log_ftp_protocol: YES
    # ssl key
    vsftpd_key_file: "ftp.classmethod.info.key"
    # ssl cert
    vsftpd_cert_file: "ftp.classmethod.info.crt"
  roles:
    - weareinteractive.openssl
    - weareinteractive.vsftpd

Ansible Playbookの実行

各項目を変更してAnsible Playbookをながします。

ansible-playbook vsftpd-ftps.yml

PLAY [all] **********************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************************************************************************ok: [ftps]

TASK [weareinteractive.openssl : Including variables] ***************************************************************************************************************************************************************************ok: [ftps] => 

TASK [weareinteractive.openssl : Installing packages] ***************************************************************************************************************************************************************************changed: [ftps]

TASK [weareinteractive.openssl : Installing python packages] ********************************************************************************************************************************************************************changed: [ftps]

TASK [weareinteractive.openssl : Checking dir exists] ***************************************************************************************************************************************************************************ok: [ftps]

TASK [weareinteractive.openssl : Creating directory] ****************************************************************************************************************************************************************************skipping: [ftps]

TASK [weareinteractive.openssl : Checking dir exists] ***************************************************************************************************************************************************************************ok: [ftps]

TASK [weareinteractive.openssl : Creating directory] ****************************************************************************************************************************************************************************skipping: [ftps]

TASK [weareinteractive.openssl : Checking dir exists] ***************************************************************************************************************************************************************************ok: [ftps]

TASK [weareinteractive.openssl : Creating directory] ****************************************************************************************************************************************************************************skipping: [ftps]

TASK [weareinteractive.openssl : Configuring] ***********************************************************************************************************************************************************************************changed: [ftps]

TASK [weareinteractive.openssl : Copying keys] **********************************************************************************************************************************************************************************
TASK [weareinteractive.openssl : Copying certs] *********************************************************************************************************************************************************************************
TASK [weareinteractive.openssl : Creating self-signed private keys] *************************************************************************************************************************************************************changed: [ftps] => (item={'name': 'ftp.classmethod.info', 'subject': {'C': 'JP', 'ST': 'Tokyo', 'L': 'Chiyoda-ku', 'O': 'Classmethod,Inc.', 'CN': 'ftp.classmethod.info', 'emailAddress': 'hogefuga@classmethod.jp'}})

TASK [weareinteractive.openssl : Creating self-signed CSRs] *********************************************************************************************************************************************************************changed: [ftps] => (item={'name': 'ftp.classmethod.info', 'subject': {'C': 'JP', 'ST': 'Tokyo', 'L': 'Chiyoda-ku', 'O': 'Classmethod,Inc.', 'CN': 'ftp.classmethod.info', 'emailAddress': 'hogefuga@classmethod.jp'}})

TASK [weareinteractive.openssl : Generate a Self Signed OpenSSL certificate.] ***************************************************************************************************************************************************changed: [ftps] => (item={'name': 'ftp.classmethod.info', 'subject': {'C': 'JP', 'ST': 'Tokyo', 'L': 'Chiyoda-ku', 'O': 'Classmethod,Inc.', 'CN': 'ftp.classmethod.info', 'emailAddress': 'hogefuga@classmethod.jp'}})

TASK [weareinteractive.openssl : Create directory for CAcert certificates] ******************************************************************************************************************************************************skipping: [ftps]

TASK [weareinteractive.openssl : Download CAcert Class 1 PKI key] ***************************************************************************************************************************************************************skipping: [ftps]

TASK [weareinteractive.openssl : Download CAcert Class 3 PKI key] ***************************************************************************************************************************************************************skipping: [ftps]

TASK [weareinteractive.openssl : Update ca certificates to enable CAcert] *******************************************************************************************************************************************************skipping: [ftps]

TASK [weareinteractive.vsftpd : Includeing OS specific variables] ***************************************************************************************************************************************************************ok: [ftps]

TASK [weareinteractive.vsftpd : Installing packages] ****************************************************************************************************************************************************************************changed: [ftps]

TASK [weareinteractive.vsftpd : Configuring vsftp] ******************************************************************************************************************************************************************************changed: [ftps]

TASK [weareinteractive.vsftpd : Adding nologin shell] ***************************************************************************************************************************************************************************changed: [ftps]

TASK [weareinteractive.vsftpd : Setting SELinux booleans] ***********************************************************************************************************************************************************************
TASK [weareinteractive.vsftpd : Managing users] *********************************************************************************************************************************************************************************changed: [ftps] => (item={'username': 'ftpuser', 'name': 'FTP User', 'password': '$5$mysecretsalt$xxxxxxxxxxxxxxxxx'})

TASK [weareinteractive.vsftpd : Configuring service] ****************************************************************************************************************************************************************************changed: [ftps]

RUNNING HANDLER [weareinteractive.vsftpd : restart vsftpd] **********************************************************************************************************************************************************************changed: [ftps]

PLAY RECAP **********************************************************************************************************************************************************************************************************************ftps                       : ok=18   changed=12   unreachable=0    failed=0

接続確認

各FTPクライアントから接続確認を実施します。 とくに特別なクライアントからじゃないと接続できないとかいうことはないので割愛します。 注意事項としては

  • クライアントがサポートしているプロトコルの確認
  • 自己証明を使用しているためクライアント側で証明書のチェックを無効
  • passiveモードでの接続

などをご注意ください。

まとめ

SFTPについては、AWSにはマネージドサービスのAWS Transfer for SFTP(https://aws.amazon.com/jp/sftp/) があるのですが、FTPまたFTPSサーバを立てる機会もまだあるかと思います。 また 【AWS】AWSでFTPサーバー立てる時に気をつけるべき2つのこと+α に沿って気をつけても、結構設定もれしていたりして子ハマりするので、次回からはサクッと終わらせれそうです。

参考

https://galaxy.ansible.com/

weareinteractive/ansible-vsftpd https://github.com/weareinteractive/ansible-vsftpd