vsftpdでFTPS接続する

## 環境
Amazon Linux 2023

## 予備知識

### 今回はExplicitモードのFTPを行う
* TLSにはExplicit(明示的）モードと、Implicit(暗黙的）モードとがある。
* Explicit モードは平文のFTPのポート(TCP/21)で接続、平文でコネクションを張った後、FTPの **AUTH** メソッドでTLSへ移行する方法。（[こちらを参考](https://qiita.com/aqmr-kino/items/ad80258eacd69a78064c#%E7%94%A8%E6%84%8F%E3%81%99%E3%82%8B%E3%82%82%E3%81%AE)）

#### AUTHメソッドとは
> The AUTH command is used to initialize a secure connection to the connected FTP server. Included with this command is a case-insensitive parameter defining the security mechanism that the client is requesting to use to secure the session. Example security mechanisms used by popular FTP clients are SSL and TLS.
> 「AUTHコマンドはFTPサーバーへの安全な接続を初期化するために使用されます。このコマンドは、セキュリティのメカニズムを定義するパラメータとしては融通がきかないケースもあり、それでもクライアント側は（このコマンドを）使用してセッションを安全に行うことを求めています。
例えば、よくFTPクライアントに使用されているメカニズムにはSSLとTLSなどがあります。」

### 中間CAとは

 __中間CA__ （intermediate certificate authority）とは、デジタル証明書を発行する認証局(CA：Certificate Authority)のうち、上位の認証局の発行した証明書により自らの正当性を証明する機関。ルートCA(ルート認証局)以外の認証局。

 https://e-words.jp/w/%E4%B8%AD%E9%96%93CA.html

### 自己署名証明書とは
 __自己署名証明書__ （self-signed certificate）とは、デジタル証明書の発行形態の一つで、公開鍵の所有者が対になる自身の秘密鍵で署名した証明書。ルート認証局の発行するルート証明書以外の自己署名証明書は信頼できないとみなされる。

https://e-words.jp/w/%E8%87%AA%E5%B7%B1%E7%BD%B2%E5%90%8D%E8%A8%BC%E6%98%8E%E6%9B%B8.html
 
```vsftpd.conf
ssl_enable=YES
ssl_sslv2=NO
ssl_sslv3=NO
ssl_tlsv1=YES
require_ssl_reuse=NO
```

## vsftpd.confの設定箇所
### ssl_enable=YES
TLSを有効化する。

### ssl_sslv2=NO、ssl_sslv3=NO、ssl_tlsv1=YES
説明は下記リンク先に譲る。ただし、リンク先でssl_tlsv1=NO、ssl_tlsv1_1=NO、ssl_tlsv1_2=YESと指定しているのは、ssl_tlsv1=YESに変更しています。

※TLSv1.1を明示的に指定したい場合は、**ssl_tlsv11=YES**と、TLSv1.2を明示的に指定したい場合は、**ssl_tlsv11=NO**と**ssl_tlsv12=YES**を指定するのが正しいです。

http://vsftpd.beasts.org/vsftpd_conf.html

https://qiita.com/aqmr-kino/items/ad80258eacd69a78064c

### require_ssl_reuse=NO
デフォルトでは、コントロールコネクションと同じセッションをデータコネクションで再利用している場合のみ、SSLによるデータコネクション（FTPS）が動く（v2.1.0からの仕様）。セッションを再利用しているかどうかを無視して、再利用していなくてもとりあえず接続したい場合、このrequire_ssl_reuse=NOにする必要がある。

#### 某サイトFAQより
> Help! SSL data connections are not working.
A) As of v2.1.0, vsftpd only accepts data connections that are reused sessions
of the control connection. This is a security measure. Unfortunately, not all
FTP clients reuse sessions (e.g. curl). You can disable this requirement by
changing require_ssl_reuse to NO

「助けて！SSLデータコネクションがちゃんと動かないんだけど。
回答）v2.1.0から、vsftpdはデータコネクションとして、コントロールコネクションを再利用したセッションしか受け入れなくなったんだよ。これはセキュリティ対策ね。残念だけど、すべてのクライアントがセッションを再利用してるわけじゃない（たとえば curlとか）。ただし require_ssl_reuseをNOに変えればこの要件は無視していいよ。」

https://security.appspot.com/vsftpd/FAQ.txt

## usermod -d でユーザーのホームディレクトリを変更
``` .bash
usermod -d /var/www/html ftp-user
```

#### 某サイトより
> set the user (in this case our silicondales user – replace this with your actual username!) home directory. In this example, I’m saying the html is the user’s root

「ユーザーのホームディレクトリをセット（この場合シリコンでーるずというユーザーになってるけどー、ちゃんと実際のユーザー名に変えろよ！）する。この例だと、htmlがユーザーのルートってことね。」

**2025/5/15 追記:**
- 上記の`usermod -d`のコマンドライン (追加する前)ではホームディレクトリが`/var/www/html`となっていますが、最終的には`/var/www`をホームディレクトリにしています。(`vsftpd.conf`の`chroot_local_user `あたりの変数で設定しているファイルにユーザーごとのホームディレクトリのパスを書いて変更できます)
- これは、WinSCPの仕様でホームディレクトリに書き込み権限があると、その下の階層にアップロードできないためです。なので一つ上の`/var/www`をホームディレクトリにして読み取り権限だけを与えます。実際にアップロードする先である`/var/www/html`や/`var/www/html/blog`には書き込み権限を与えます。

## usermod -a -Gでユーザーをディレクトリの所有グループに所属させる（必須ではない）

``` .bash
# 左のftp-userグループにftp-user (右)を所属させる
usermod -a -G ftp-user ftp-user

# 右のftp-userグループにftp-user (左)が所属している
ls -al /var/www/html
total 60
drwxr-sr-x.  6 ftp-user ftp-user   112 Apr 26 23:49 .

# 追記
# apacheグループにユーザーを所属させる
usermod -a -G apache ftp-user
# 所有者がapacheグループに所属するftp-userであることを確認
ls -al /var/www/
dr-xrwsr-x.  6 ftp-user apache
```
**2025/5/15 追記:**
- `/var/www/html` (Apacheのドキュメントルート)にアップロードする場合、`www`は`apache`ユーザーと`apache`グループに所有するもの (おそらくhttpdでデフォルトになっている)なので、上記で`ftp-user`が所有者になっていてはいけないみたいです。(あるいは`ftp-user`を`apache`グループに所属させる必要があります)
- `/var/www/html/blog`などのサブディレクトリにアップロードする場合、`html`だけ上記のように`ftp-user`が所有者になるようにします。(`blog`は所有ユーザーもグループも`apache`)
- また、WinSCP (6.5.1)では`ftp-user`のホームディレクトリ (今回は`/var/www/`です)に書き込み権限があるとアップロードができません(理由は不明)。なので`html`とそのサブディレクトリだけ書き込み権限を与え (`chmod 755 /var/www/html`と`chmod -R 755 /var/www/html/blog`)、`www`は読み取りと実行権限だけにします。


#### 某サイトより
> adds the user silicondales to the group apache, which will mean this user gains the same access allowed to that group. This will help you with some permissions stuff you would otherwise experience with FTP’ing things up and down.

「(`usermod -a -G apache silicondales`で）アパッチというグループにシリコンデールってユーザーを追加するぜ。つまりこのユーザーはグループと同じようにファイルへのアクセスが許可される。これで、ほんとはいろいろやんなきゃいけないパーミッションのなんやかんやを、いい感じにしてFTPでファイルをアップしたりとってきたりが楽ちんってわけ。」

https://silicondales.com/tutorials/aws-ec2/setup-ftp-sftp/

``` .bash
# FTPでファイルをアップロードするディレクトリの所有グループを確認する
ls -al /var/www/html
total 60
drwxrwsr--.  6 ftp-user ftp-user   112 Apr 26 23:49 .

# ftp-userをftp-userというグループに所属させる
sudo usermod -a -G ftp-user ftp-user

# vsftpdを再起動
$ sudo service vsftpd restart
## または
$ sudo systemctl restart vsftpd.service
```

## クライアント側 (lftp) の設定
/etc/lftp.confに下記の設定をおこなう。lftpセッションにログインして、setコマンドでいちいち打つのが面倒なので設定ファイルに指定する。

```/etc/lftp.conf
set ftp:port-ipv4 <your.server.public.ipaddress>
set ftp:port-range <min-max> # パッシブモードで使用するポート番号の範囲を設定
set ftp:ssl-auth TLS
set ftp:ssl-force true
set ftp:ssl-allow true
set ftp:ssl-protect-list true
set ftp:ssl-protect-data true
set ftp:ssl-protect-fxp true
set ftp:passive-mode true # パッシブモードを使用
set ssl:verify-certificate false # 自己署名証明書のため、検証をしない

# プロトコルの優先順位を設定 (TLS v1.2)
set ssl:priority "NORMAL:-VERS-SSL3.0:-VERS-TLS1.0:-VERS-TLS1.1:+VERS-TLS1.2:-VERS-TLS1.3"

```

``` .bash
# 設定のリロード
lftp -c cache flush
```

## Open SSLで証明書を取得する場合

### 流れ
1. Amazon Linux 2023 (EC2 インスタンス）にSSH接続する
2. CAキー（Certificate Authority key) を作成する
3. CSRを認証局に送信する
3. CAによる署名付きの証明書（サーバー証明書）とホストキー（サーバーの秘密鍵）が返ってくる
3. Open SSLでApacheに証明書をインストール

### CAキーとCSRの作成
opensslコマンドでCAキーを作成する。/etc/pki/tls/private/localhost.keyというファイルがCAキーとなる。
CAキーから、自己署名CA証明書と、中間CA証明書 (任意。自己署名証明書の場合は不要) を発行するためのCSRを発行する。

#### 参考サイト

* CAキーの作成からCSR(Certificate Signed Request)送信、自己署名CA証明書（中間証明書）の取得まで

https://docs.redhat.com/ja/documentation/red_hat_enterprise_linux/9/html/securing_networks/creating-a-private-ca-using-openssl_creating-and-managing-tls-keys-and-certificates#creating-a-private-ca-using-openssl_creating-and-managing-tls-keys-and-certificates

``` .bash
# ssl.confのSSLCertificateKeyFile（CAキーの絶対パス）で指定されているパスに移動する
cd /etc/pki/tls/private/

# CAキー (ca.key) と自己署名 CA 証明書 (ca.crt) の取得
openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-256 -out ca.key
openssl req -key ca.key -new -x509 -days 3650 -addext keyUsage=critical,keyCertSign,cRLSign -subj "/CN=myca" -out ca.crt


# ssl.confのSSLCertificateKeyFileで指定されているファイル名（localhost.key）にリネームする
sudo mv ca.key localhost.key

# CAキーのユーザー所有権をもつユーザーとグループ所有権をもつグループの変更
sudo chown ec2-user:root localhost.key
[sudo] password for xxxxx:

# CAキーのユーザー所有権を設定
sudo chmod 600 localhost.key

# CSRの作成 (中間CA証明書を発行するためのCSR)
sudo openssl req -key localhost.key -new -out server-cert.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:.
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:.
Common Name (eg, your name or your server's hostname) []:example.com
Email Address []:email_address@example.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:asdfasdf
An optional company name []:.
```

この自己署名CA証明書 (後述の中間CA証明書の作成に使用する) と、後述の中間CA証明書とサーバ証明書 (任意。自己署名証明書を使用する場合は不要) を使用して最終的にFTPSを実現する流れになります。

### CSRを中間CA (証明局）に送信してCSR証明書を発行する

``` .bash
sudo openssl x509 -req -in server-cert.csr -CA ca.crt -CA
key localhost.key
[sudo] password for xxx:
Certificate request self-signature ok
subject=C = JP, L = Sumida City, O = Default Company Ltd, CN = tabit.life, emailAddress = winpossitivestyle@icloud.com
-----BEGIN CERTIFICATE-----
MIIBj....
```

## SSL証明書（Open SSLを使用する場合）をサーバーへ設置する
以下のAWS公式の手順に従って、サーバー証明書をApacheにインストールします。

https://docs.aws.amazon.com/ja_jp/linux/al2023/ug/SSL-on-amazon-linux-2023.html

### 中間CA証明書を作成する
/etc/pki/tls/certs/localhost.crtというファイルを作成し、キー（the certificate's private key）と自己署名証明書 (a self-signed certificate)の内容をファイルに書き込む。これが中間CA証明書となる。
``` .bash
# Apache (httpd) が有効になっていることを確認
[ec2-user@ip-xxx-xx-12-350 ~]$ sudo systemctl is-enabled httpd
enabled

# mod_sslのインストール
[ec2-user@ip-xxx-xx-12-350 ~]$ sudo dnf install openssl mod_ssl
Last metadata expiration check: 10:52:30 ago on Fri Apr 18 20:16:07 2025.
Package openssl-1:3.0.8-1.amzn2023.0.10.x86_64 is already installed.
Dependencies resolved.
========================================================================================================================
 Package                 Architecture           Version                               Repository                   Size
========================================================================================================================
Installing:
 mod_ssl                 x86_64                 1:2.4.58-1.amzn2023                   amazonlinux                 111 k
Installing dependencies:
 sscg                    x86_64                 3.0.3-76.amzn2023                     amazonlinux                  45 k

Transaction Summary
========================================================================================================================
Install  2 Packages

Total download size: 156 k
Installed size: 376 k
Is this ok [y/N]: y
Downloading Packages:
(1/2): sscg-3.0.3-76.amzn2023.x86_64.rpm                                                899 kB/s |  45 kB     00:00
(2/2): mod_ssl-2.4.58-1.amzn2023.x86_64.rpm                                             1.0 MB/s | 111 kB     00:00
------------------------------------------------------------------------------------------------------------------------

# /etc/httpd/conf.d/ssl.confで定義している中間CA証明書のファイルパスを確認（101行目のSSLCertificateFileの値）
cat -n ssl.conf | grep "SSLCertificateFile"
    92  #   Point SSLCertificateFile at a PEM encoded certificate.  If
   101  SSLCertificateFile /etc/pki/tls/certs/localhost.crt
   115  #   the referenced file can be the same as SSLCertificateFile

# /etc/pki/tls/certs/localhost.crtを作成
[ec2-user@ip-xxx-xx-12-350 ~]$ sudo mkdir /etc/pki/tls/certs
[ec2-user@ip-xxx-xx-12-350 ~]$ sudo touch /etc/pki/tls/certs/localhost.crt

# ユーザー所有権をもつユーザーとグループ所有権をもつグループの変更

[ec2-user@ip-xxx-xx-12-350 ~]$ cd /etc/pki/tls/certs
[ec2-user@ip-xxx-xx-12-350 certs]$
[ec2-user@ip-xxx-xx-12-350 certs]$ sudo chown root:root localhost.crt

# 権限を設定

[ec2-user@ip-xxx-xx-12-350 certs]$ sudo chmod 600 localhost.crt
[ec2-user@ip-xxx-xx-12-350 certs]$ ls -al localhost.crt
-rw-------. 1 root root 828 Apr 19 10:28 localhost.crt


# 作成したlocalhost.crtを編集し、中間CA証明書を作成する（上段がCAキー、下段が自己署名証明書の内容）
sudo cat /path/to/your/cakey/ca.key > /etc/pki/tls/certs/localhost.crt
sudo cat /path/to/your/crt/ca.crt >> /etc/pki/tls/certs/localhost.crt
```


```/etc/pki/tls/certs/localhost.crt
# CAキー(~/ca.key)の内容
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD2KKx/8Zk94m1q
GvwnKoMh3DlK44D9dX7IDua2PlYx5+eroA+1Lqf32ZSaAO0bBIMIYTHigwbHMZoT
...
56tE7THvH7vOEf4/iUOsIrEzaMaJ0mqkmY1A70qQGQKBgBF3H1qNRNHuyMcPODFs
...
4QQvAqOa8UheYeoXLdWcHaLP
-----END PRIVATE KEY-----

# 自己署名証明書(~/ca.crt)の内容
-----BEGIN CERTIFICATE-----
MIIEazCCA1OgAwIBAgICWxQwDQYJKoZIhvcNAQELBQAwgbExCzAJBgNVBAYTAi0t
MRIwEAYDVQQIDAlTb21lU3RhdGUxETAPBgNVBAcMCFNvbWVDaXR5MRkwFwYDVQQK
...
3ZazhBxtQSukFMOnWPP2a0DMMFGYUHOd0BQE8sBJxg==
-----END CERTIFICATE-----
```

### サーバー証明書を取得するためのCSRを作成
``` .bash
# サーバーの秘密鍵（ホストキー）を保管するディレクトリにホストキーを作成する
cd /etc/pki/tls/private/
sudo openssl genrsa -out host.key

# ユーザー所有権をもつユーザーとグループ所有権をもつグループの変更 (root)
sudo chown root:root host.key
# 権限を設定（書き込みと読み取り権限）
sudo chmod 600 host.key
ls -la
total 4
drwxr-xr-x. 2 root root   22 Apr 19 09:53 .
drwxr-xr-x. 5 root root  126 Dec 14  2023 ..
-rw-------. 1 root root 1704 Apr 19 09:53 host.key

# CSRの作成 (サーバー証明書を発行するためのCSR)
sudo openssl req -new -key host.key -out csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:.
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:.
Common Name (eg, your name or your server's hostname) []:example.com
Email Address []:email_address@example.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:yourpasswd
An optional company name []:.

```

### CAによる署名付きの証明書（サーバー証明書）の取得

上記のAWSの公式手順で作成した自己署名証明書、中間CA証明書を使用して、自己署名CAを構築し、そこからサーバー証明書を取得する。

#### 作成する自己署名CA (認証局）の概要

> 構築しようとするCA環境は以下の通り
> ルートCAは自己署名
> 中間証明書はルートCAで署名
> サーバ証明書は中間CAで署名（自己署名ではないサーバ証明書を取得する）

#### 具体的な流れ(基本的に上記の概要に沿っておこなう）
1. 以下のフローについては、上記『 __SSL証明書（Open SSLを使用する場合）をサーバーへ設置する__ 』の「中間CA証明書を作成する」および「サーバー証明書を取得するためのCSRを作成」の手順ですでに作成済み。
    * ルートCAとして自己署名した証明書（自己署名証明書）を作成する。これにより、ルートCAを構築する。
    * 中間CAに証明書署名要求を送り、中間証明書を取得する。これにより、中間CAを構築する。

1. 1. のフローを実行したうえで、最終的に必要なサーバー証明書をこれから説明する手順に従って作成する。

#### 備考
下記参考サイトではルートCA (~/self_ca/RCA) と分けて中間CAのパス (~/self_ca/ICA) を作成し、そこに中間CA証明書をおいているが、今回の投稿ではAWSの公式どおりにルートCA作成の手順を踏んでおり、ルートCAで作成した自己署名証明書を個別のディレクトリには保存していない。

中間CA証明書のファイルの中身は、ルートCAの秘密鍵＋自己署名証明書を埋め込む形になっている。中間CA証明書を使用しない場合、ssl.confのデフォルトのパスに自己署名証明書を置けばいい。中間CA証明書を使用する場合、~/self_ca/ICAを作成する。Apache 2.4ではssl.confのSSLCertificateFileに指定したファイルにサーバー証明書+中間CA証明書の内容をひとまとめにする。そのためあくまで~/self_ca/ICAは仮置き場である。サーバー証明書を置くための~/self_ca/server/certsを作成し、SSLCertificateFileの値はその下においたサーバー証明書のパスを指定する。

```/etc/httpd/conf.d/ssl.conf
# SSLCertificateFileの例（ただし、正式な記述ではない）
SSLCertificateFile ~/self_ca/server/certs/server-cert.crt
```

中間CA証明書を使用する場合でも、Apache2.4では最終的に SSL証明書として使用するサーバー証明書（上の例の場合、server-cert.crt）の中身を、サーバー証明書+中間CA証明書というようにひとまとめにするので、~/self_ca/ICA以下の中間CA証明書のファイルパスは設定ファイルに指定しなくてもよくなっている。

参考サイト:

[自己CAを構築し、自己CAで署名付けした中間証明書とサーバ証明書を作成する](https://qiita.com/thetsuthetsu/items/5292aab4ba5f61ce11ca)


#### 実際にサーバー証明書を取得する手順 (2025/6/3以降 順次追記予定)

最終的なディレクトリ階層とssl.confとを示す。

```
# 自己署名に使用した秘密鍵
/etc/pki/tls/private/localhost.key

# 自己署名証明書（ルートCA証明書）
/etc/pki/tls/certs/localhost.crt
# あるいは、pem形式（秘密鍵の内容と自己署名証明書を一つにした形式)の場合、.pemでも可。
/etc/pki/tls/certs/localhost.pem

# 中間CA証明書（使用する場合）
~/self_ca/ICA/certs/intermediate-cert.crt

# サーバー証明書に署名した秘密鍵
~/self_ca/server/private/host.key

# サーバー証明書 (SSL証明書)
~/self_ca/server/certs/server-cert.crt
```

```/etc/httpd/conf.d/ssl.conf
# SSLCertificateFileの値（PEM以外の形式の場合）
~/self_ca/server/certs/server-cert.crt

```

**【2025/6/8 追記】**
以下、詳細な手順

```
# 秘密鍵の格納用ディレクトリ作成
mkdir -p self_ca/server/private
cd ~/self_ca/server/private

# 秘密鍵 (サーバー鍵) を生成
openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-256 -out > host.key

# サーバー証明書の格納用ディレクトリ作成
~/self_ca/server/certs
cd ~/self_ca/server/certs
```

### 参考サイト

[【vsftpd】FTPをSSL/TLSで暗号化する設定](https://qiita.com/aqmr-kino/items/ad80258eacd69a78064c)

[apache 2.4におけるSSL証明書の設定](https://qiita.com/bageljp@github/items/6b9876b7571852284ead)