/

皆さんはLDAPを使ったことがありますか?
LDAPとは Lightweight Directory Access Protocol の略で、ディレクトリサービスのためのプロトコルです。ディレクトリサービスというのはその名前の通りディレクトリのようなツリー状の情報を管理するサービスで、その特徴からリソースの場所・設定などの情報を一元管理するのに用いられています。
とりわけ大きな組織においてはこのような情報を管理することにおいて大きなメリットがあります。管理すべき機器やユーザの数が膨大になってきたとき、必要に応じて素早く情報にアクセスできることが要求されるためです。
多くの企業ではディレクトリサービスを用いて情報を管理しており、ディレクトリサービスを提供するためのソフトウェアとしてはActive Directoryが代表的です。優れたGUIと長年Microsoftがメンテナンスしてきたことによる実績があり、何よりWindowsマシンを管理するのに長けているからです。
一方で、昔ながらのOpenLDAPを用いる場合もあります。OpenLDAPはLDAPのオープンソース実装の一つであり、高機能なActive Directoryなどに比べて軽量・高速に動作します。先に挙げたActive DirectoryもLDAPをベースに実装されており、LDAPへの理解を深めることはディレクトリサービスを知る上で欠かせないと言えます。
今回出題したトラブルは、LDAPに関する基本的な操作・知識を問う問題でした。LDAPを運用した経験がないと時間内に解くのはなかなか難しかったのではないかと思います。

問題文

最近LDAPを導入して、GitLabの認証とサーバへのSSHをLDAP経由で行えるように設定したのだがどうもうまく動いていないようだ。 原因を突き止めて、GitLabにログインできるようにしてほしい。必要に応じてサーバの設定を変えても構わないが、なるべくセキュリティレベルが下がらないようにしてほしい。また、admin (cn=admin,dc=finals,dc=ictsc) とoperator (cn=operator,ou=users,dc=finals,dc=ictsc) のパスワードが脆弱なので、可能であれば指定したものに変更してほしい。
なお、すべてのサーバはLDAPに登録してあるSSH鍵で入れるようになっているはずだが、こちらも同様に動いてないのでLDAPサーバのみにログインできるアカウントとLDAPに登録したSSH秘密鍵を用意した。必要に応じて使ってほしい。

問題のゴール状態

GitLabにLDAPユーザのoperator (パスワード変更済み) でログインに成功する

解説

今回の問題に登場するサーバは全部で2つです。

  • LDAPサーバ (dc.finals.ictsc)
  • GitLabサーバ (gitlab.finals.ictsc)

それぞれにSSSDがインストールされており、sss_ssh_authorizedkeys コマンドを用いてLDAPに登録されている公開鍵でログインできるようになっているはずが、SSHログインできなくなっている状態がスタートです。
また、GitLabのLDAPログインも同様に動かない (SSL errorが発生する) ので、これらを直してほしいという内容です。

初期状態をまとめると以下の図のようになります。

図で×印がついている部分がLDAPで認証を行う部分であり、これらをすべて復旧するのがゴールです。

問題のゴール自体はGitLabでLDAPログインできるようになることとなっていますが、実際には認証を直してGitLabサーバへのSSHログインができるようにならないとトラブルシューティングが行えないため、まずはSSHログインを復活させるのが1つ目のゴールになります。

SSL証明書の有効化

GitLabでLDAPログインを試みると以下のようなエラーメッセージが出ます。

Could not authenticate you from Ldapmain because “Ssl connect syscall returned=5 errno=0 state=sslv2/v3 read server hello a”.

これはそもそもSSL接続が確立できていないということなので、opensslで確認してみます。

~$ openssl s_client -connect 192.168.2.10:636
CONNECTED(00000003)
140350580467344:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:177:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 289 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol  : TLSv1.2
Cipher    : 0000
Session-ID:
Session-ID-ctx:
Master-Key:
Key-Arg   : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1552100789
Timeout   : 300 (sec)
Verify return code: 0 (ok)
---

これを見ればわかるとおり、そもそも証明書が設定されていないことがわかります。
しかし /etc/ldap/slapd.conf を見ると TLSCACertificateFile=/etc/ldap/ssl/ca.crt などの設定がされているため本来は有効化されているはずですが、これらのパラメータは少なくともdebian系のslapdでは正しく動きません。よって適切なLDIFを書いて、手動で有効化してやる必要があります。

dn: cn=config
changetype: modify
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ldap/ssl/ca.crt
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ldap/ssl/dc.finals.ictsc.key
-
add: olcTLSCertficateFile
olcTLSCertficateFile: /etc/ldap/ssl/dc.finals.ictsc.crt

SSL証明書のパーミッションも正しくしておきましょう。

~$ ls -lhat /etc/ldap/ssl/
total 24K
drwxr-xr-x 2 root root 4.0K Feb 17 02:07 .
-rw-r--r-- 1 root root 2.0K Feb 17 02:07 ca.crt
-rw-r--r-- 1 root root 5.9K Feb 17 02:07 dc.finals.ictsc.crt
-rw------- 1 root root 1.7K Feb 17 02:07 dc.finals.ictsc.key
drwxr-xr-x 7 root root 4.0K Feb 17 02:07 ..


...

~$ ls -lhat /etc/ldap/ssl/
total 24K
drwxr-xr-x 2 root root     4.0K Feb 17 02:07 .
-rw-r--r-- 1 root openldap 2.0K Feb 17 02:07 ca.crt
-rw-r--r-- 1 root openldap 5.9K Feb 17 02:07 dc.finals.ictsc.crt
-rw-r----- 1 root openldap 1.7K Feb 17 02:07 dc.finals.ictsc.key
drwxr-xr-x 7 root root     4.0K Feb 17 02:07 ..

先ほどのLDIFを olcSSL.ldif という名前で保存し、以下のコマンドで適用します。
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f olcSSL.ldif

SSSDで公開鍵が取得できない

サーバの/etc/ssh/sshd_configを見ると、AuthorizedKeysCommandディレクティブにsss_ssh_authorizedkeysが設定されていることがわかります。しかし試しにこれを実行してみても公開鍵が出力される様子がありません。

~$ /usr/bin/sss_ssh_authorizedkeys operator
~$

これが原因でSSHログインができないことがわかりますが、なぜ公開鍵が取得できないのでしょうか。LDAPの設定を見直しても公開鍵はちゃんと登録されています。

sss_ssh_authorizedkeysコマンドには実はデバッグオプションが存在します(これはhelpでも出てきません)。--debug 10という引数をつけてコマンドを呼び出すと、詳しいエラーメッセージを見ることができます。

~$ ~$ sss_ssh_authorizedkeys operator --debug 10
(Wed Mar 27 20:25:46:414921 2019) [sss_ssh_authorizedkeys] [main] (0x0040): sss_ssh_format_pubkey() failed (22): Invalid argument

このメッセージから、登録されている公開鍵のフォーマットが正しくないと予想できます。もう一度LDAPに登録されている鍵をみてみましょう。

~$ ldapvi -D 'cn=admin,dc=finals,dc=ictsc' -b 'dc=finals,dc=ictsc'
...
loginShell: /bin/bash
sshPublicKey:; ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJodlzzbHLCCfldHfG7xKlA4tl6t118hAdjbzuZIYCJELLFTwctlFVOBgZHs4JkT5Cgm7eK1VXL99w7SapNzhMs= operator@dc^M\

よく注意して見ると、公開鍵の末尾に改行が含まれていることがわかります。試しに改行を消してみるとどうなるでしょうか。

~$ sss_ssh_authorizedkeys operator
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJodlzzbHLCCfldHfG7xKlA4tl6t118hAdjbzuZIYCJELLFTwctlFVOBgZHs4JkT5Cgm7eK1VXL99w7SapNzhMs= operator@dc

ちゃんと取得することができました。秘密鍵でSSHログインが可能なことも確認できます。

~$ ssh -i files/id_ecdsa operator@192.168.2.10
Last login: Sun Feb 17 03:38:42 2019 from 192.168.2.1
Could not chdir to home directory /home/operator: No such file or directory
operator@dc:/$

これで登録してある公開鍵でログインができるようになりました。

パスワードの変更

GitLabのトラブルシューティングに移る前にパスワードの変更をしておきます。

ldapviを用いてパスワードを変更しようとするとエラーが発生し変更できなかったり、phpLDAPadminにおいてはエラーすら出なかったりします。(この場合も依然として変更はできていません)

このような場合はアクセスコントロールを疑います。/etc/ldap/ldif/olcAccess.ldifが置いてあるのでこの中身をみてみましょう。

dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
# password should not be readable and only user can update
olcAccess: to attrs=userPassword
by self =sw
# uid, uidNumber, gidNumber should not be updated
olcAccess: to attrs=uid,uidNumber,gidNumber
by self read
by users read
# for ssh login
olcAccess: to attrs=sshPublicKey
by self write
by group.exact="cn=servers,ou=sgroups,dc=finals,dc=ictsc" read
# for sudo
olcAccess: to dn.children="ou=sudoers,dc=finals,dc=ictsc"
by group.exact="cn=servers,ou=sgroups,dc=finals,dc=ictsc" read
# not matched
olcAccess: to *
by self read
by users read
by anonymous auth
by * none

注目すべきはuserPasswordに対して設定してある項目です。by self =swはsearchとwriteをユーザー自身に許可する設定なので大丈夫なはずですが、by anonymous authがついていないのでbindができないようになっています。

よってolcAccess.ldifuserPasswordby anonymous authを追加してあげるとパスワードの変更が可能になります。

GitLabの証明書

SSSDのエラーを修正したことでGitLabにログインが可能になりますが、GitLabのログインページは依然としてエラーを出します。エラーメッセージを読むと証明書周りのエラーということがわかるので、/etc/gitlab/gitlab.rbの中身を確認します。

LDAPの設定部分にverify_certificates: trueとあるので、証明書がチェックに引っかかっていることが原因のようです。これを回避するためには証明書のチェックを無効にするのが一つの手ですが、セキュリティの観点からはあまり好ましい対策とは言えません。証明書 (dc.finals.ictsc) をシステムのチェーンに登録して有効な証明書とするのがベターです。

~$ sudo -s
~# cd /usr/share/ca-certificates/
~# openssl s_client -showcerts -connect dc.finals.ictsc:636 2>/dev/null | openssl x509 > rootCA.crt
~# echo "rootCA.crt" >> /etc/ca-certificates.conf
~# update-ca-certificates

GitLab上のユーザブロック解除

証明書のエラーを解決するとようやくLDAP認証が動くようになります。しかしoperatorユーザはなぜかログインできず、ブロックされている旨のメッセージが出ます。

これはすでにローカルでoperatorという名前のユーザが登録されていたことが原因ですが、解除するためにはadminアカウントが必要になります。operatorユーザでSSHログインするとsudoでrootになることができるので、GitLabのrails consoleに直接接続してパスワードを書き換えることができます。

~# gitlab-rails console production
user = User.where(id: 1).first
user.password = 'strongpassword'
user.password_confimation = 'strongpassword'
user.save!

これでadminとしてログインして、admin areaにあるユーザの設定でLDAPのoperatorユーザを有効にするとログインできるようになります。

採点基準

GitLabサーバにログインできるようになるのが一つ目のゴールなので、ここまで到達した場合は満点の50%が得られます。

  • LDAPユーザのパスワードを変更する (20%)
  • GitLabサーバにログインできるようになる (30%)
  • GitLabでLDAP認証を使えるようにする
    • LDAPをTLS通信に対応させる (30%)
    • operatorユーザのブロックを解除する (20%)

講評

今回の問題の一番のポイントはLDAPそのもではなく、Undocumentedな仕様を適切に探し出すことができるかという点にありました。OpenLDAPのslapd.confが意図した通りに動かない、SSSDが解釈できる公開鍵のフォーマットに隠された仕様があるなど、ソフトウェアは思わぬところで予想に反する挙動を示すことがあります。事前に知識を持っている場合はすぐに気づくことができますが、多くの場合はそうではありません。今回はLDAP+SSSD+GitLabという、意外と一般的だが普段は使ってなさそうな技術を意図的に選びました。

もっとも高得点を獲得したのはLDAPのSSL証明書の設定を解決したチームでした。残念ながらGitLabサーバへSSHできるところまでたどり着くチームはいませんでしたが、コンテスト時間がこれだけ短く、問題数が多いことを考えると仕方ないのかなと思います。また、得点の高さをみて手をつけなかったチームも結構いたのではないでしょうか。トラコンでは必ずしも問題を解ききる必要はないので、部分点解法を大量に稼いでいくのも一つの戦略です。

 /

問題文

同期から次の依頼がありました。

「会社の上司から勉強用にとサーバーをいただきました!
Webページを立ててみたかったのでNginxをインストールしようとしていたのですが、

sudo apt-get install nginx
をするとエラーが出てしまってインストールできないんです…
どうしたら良いんですかね…?」

問題を解決して、インストールがエラーや警告なしで成功するようにしてください。

問題のスタート状態

  • サーバーへのHTTP通信が失敗する

問題のゴール状態

  • Nginxがapt-getによってエラーや警告なしにインストールされている
  • サーバーへHTTP通信ができる

トラブルの概要

Nginxをインストールしようとしたがエラーが発生してインストールが完了しない。

解説

今回のトラブルの原因は /var/log 配下にあるファイル全てにi属性(Immutable属性)が付与されていたからでした。

今回のトラブルが起こる流れとして /var/log配下にあるdpkg.logをアップデートしようとするとコピーなどは成功するが、インストール後スクリプト(post_install)の実行時にdpkg.logファイルの権限変更(chmod)が失敗し、インストール失敗(再インストール)としてマークされます
そして、次回インストール時にpost_installスクリプトが実行されるように予約がされます。

なので、Nginx(その他のパッケージ)をインストールしようとすると再インストールがマークされたdpkgが優先的にインストールされようとしますが、同じ理由で再失敗するのでそれ以降のインストールが無視される(インストール失敗とマークされる)といった流れでした。

ですので、今回は以下のコマンドでdpkg.logのi属性をはずしたらインストールは完了します。

また、今回のゴールはNginxがapt-getによってエラーや警告なしにインストールされているも含まれています。
エラーや警告を解決していない回答が多く見られました。
今回エラーや警告が出ていた原因は/var/logのaptディレクトリにもi属性が付与されていたためです。
なので、aptディレクトリもi属性をはずしたら今回のトラブルは解決です。

回答例

50%

$ cd /var/log
$ sudo chattr -i dpkg.log

もしくは

$ sudo rm -rf /var/lib/dpkg/info/dpkg.postinst
$ sudo rm -rf /var/lib/dpkg/info/dpkg.postrm
$ sudo dpkg --configure dpkg

以上のようにNginxのインストールに成功すれば手段は問わず50%の点数を与えました。

100%

$ cd /var/log
$ sudo chattr -i *
$ sudo apt-get install nginx

2行目が sudo chattr -i aptでも問題ありません。

Nginxのインストール時にエラーや警告がでなければ100%の点数を与えました。

採点基準

  • sudo apt-get install nginxで正常にインストールされない。(0%)
  • sudo apt-get install nginxでインストールされHTTP通信はできるが、インストール時にwarningが消えていない。(50%)
  • sudo apt-get install nginx正常にインストールされHTTP通信ができ、インストール時にwarningがでない。(100%)
 /

問題文

あなたは新入社員のメンターとして研修に参加していました。
ある日、上司に急用が入り、
「今から日曜まで出張に行ってくるから、それまでに研修用のWebサーバにアクセスできることだけ確認しといて!」
と言われました。しかし、指示されたサーバーである http://192.168.9.1 にアクセスしても何も表示されません。

このサーバーはWebサーバーとしてLighttpdがインストールされており、現在の状態に戻す方法がわかっていれば自由に設定を変更することは許されています。

Webサーバが動作した上で同じ問題が再発しないように修正を行い、以下を上司に報告してください。
– トラブルの原因
– 解決のために行ったコマンド
– 行った変更を元に戻すためのコマンド

情報

  • IPアドレス: 192.168.9.1
  • ユーザー: admin
  • パスワード: hello-world
  • インストールされているWebサーバー: lighttpd

問題のスタート状態

  • サーバーへのHTTP通信が失敗する

問題のゴール状態

  • サーバーへHTTP通信が成功し、ブラウザから見るとLighttpdのプレースホルダのページが表示される
  • 来年以降同じ問題が再発しないよう設定する

トラブルの概要

systemdで起動時のターゲットにWebサーバ(lighttpd)が登録されていない

解説

この問題は、Ubuntu 18.04のinitシステム(起動時に最初に呼び出される、その他に起動に必要なアプリケーションを起動したりシステムを管理するためのアプリケーション)であるsystemdを用いた環境で「サービス」(デーモン)が起動していないというトラブルです。
使用しているサーバーはUbuntu 18.04を使用しています。このディストリビューションでは、initシステムとしてsystemdを採用しています。
initrcやinitngなどを使用しているシステムでは /etc/init.d/ 配下にあるファイルを用いて起動していますが、systemdを使用したシステムでは systemctl というコマンドを使用します。

また、OS起動時に自動起動する設定も同じコマンドで行います。

今回のトラブルでは、Webサーバーはインストール済みの状態ですので、このコマンドを使用してサービスを起動/停止することで解決可能です。

systemctl start lighttpd
systemctl stop lighttpd

また、この変更のみでは自動起動はしないため、設定を元に戻すコマンドは必要ありません。
しかし、自動起動設定を行った場合は自動起動設定を停止するコマンドも投入する必要があります。

自動起動設定コマンドと停止コマンドは以下のとおりです。

systemctl enable lighttpd
systemctl disable lighttpd

回答例

お疲れ様です。解答を送信します。

今回のトラブルは、lighttpdが起動していないことが原因でした。
lighttpdはインストール済みのため、以下の手順でWebサーバの起動をすることができます。

Webサーバの起動

systemctl start lighttpd

また、同じ問題が再発しないよう、研修終了までWebサーバを自動起動するようにします。

Webサーバの自動起動の設定

`systemctl disable lighttpd

これらの設定を元に戻す方法は以下のとおりです。

Webサーバの停止・自動起動の解除

systemctl stop lighttpd
systemctl disable lighttpd

採点基準

  • 上記手順をすべて達成して100%
  • 自動起動をしていない -10点
  • 元に戻す方法を記述していない -10点

講評

この問題は「コンテストサイトの使い方を学ぶための簡単な問題」という位置づけだったこともあり、とても簡単な問題でした。そのおかげで、全チームに満点を付与することができました。作門者もにっこり喜んでいます。
Webサーバの設定ファイルを変更したチームもありましたが、もしかしたら惰性や習慣で systemctl start lighttpd をしてしまったのかなと個人的には思っています。
私も以前に似たようなことを行い、問題解答不能になったことがありました。
実際にサーバーに対して操作を行う際には、原因の調査と解決を分けて考えることで
「なぜこのトラブルが起きているのか?」を正しく判断することができ、
また「どのようにしてこのトラブルを解決するか」が分かれば、トラブル解決の途中で新たなトラブルに遭遇した際にも
「どのようにして環境をロールバックするか」を理解することにつながると思います。
トラブルが起きている当初の環境への復旧(ロールバック)は「ICTSCの問題環境」では簡単に行えますが、実務環境では大抵できません。ここからの締めの文章が浮かびませんが、みなさん頑張ってください。

問題タイトルは、「新人(入社数ヶ月)が新人(入社数日)のメンターになり、新人研修を行っている」ようなシチュエーションを考えて付けましたが、問題文を作る能が無く、なーんか変な内容になってしまいました。

 /

問題文

通話が繋がらない!

離れたオフィス間でも円滑にミーティングをできるように社内ミーティングサービスを運用している。
セキュリティ強化として入社した新入がLinux Routerにとある変更を行ったところ、社内ミーティングサービスが動作しなくなった。

このままだと、別支店のチームがミーティング参加ができなくなる!

一刻も早く修正せよ!

情報

  • 社内ミーティングサービスURL: https://192.168.8.19/

Linux router X

IPアドレス: 192.168.8.1
ユーザー: admin
パスワード: a3nVn22U

Linux router Y

IPアドレス: 192.168.8.9
ユーザー: admin
パスワード: PGrydZ1

Application Server

IPアドレス: 192.168.8.19
ユーザー: admin
パスワード: ani8EZu

使用手元機材の情報

Client A(支店A)

2960-A: FastEthernet 0/1

Client B(支店B)

2960-A: FastEthernet 0/2

問題のスタート状態

参加者PC同士での社内ミーティングサービスを使用したビデオ通話ができない。

問題のゴール状態

参加者PC同士での社内ミーティングサービスを使用したビデオ通話ができる。

構成

注意事項

この問題で使用するブラウザは「Google Chrome」と「Firefox」のみです。
これら以外のブラウザでは問題回答ができませんので、このブラウザで動作確認をしてください。

この問題を解く際は、参加者PCを無線Wifiに接続せず使用手元機材の情報に記述されている有線LANポートのみに接続してください。
参加者は自身のPCからApplication ServerにHTTPSでアクセスすることで社内ミーティングサービスを利用することができます。
このサービスではHTTPS通信に自己署名証明書を使用をしております。そのため、ブラウザからアクセスした際に証明書の警告がされます。

トラブルの概要

社内ミーティングサービスではWebRTCが使用されており、ビデオ通話などのメディアデータの送受信は基本的にP2PでClient同士が通信するものである。
今回Client同士がP2P通信するための経路情報が互いのLinux routerに登録されていないために発生したトラブル。

解説

トラブルの概要の通り、Linux router XとLinux router Yにて、互いの経路情報を登録する。

永続化については問題文に明記していないので、永続化の有無については得点への反映はない。

解答例

Linux router X

# # 経路追加
# route add -net 192.168.8.8 netmask 255.255.255.248 gw 192.168.8.18
# # 永続化
# cat /etc/network/if-up.d/branch-b-routing
#!/bin/sh
/sbin/route add -net 192.168.8.8 netmask 255.255.255.248 gw 192.168.8.18

Linux router Y

# # 経路追加
# route add -net 192.168.8.0 netmask 255.255.255.248 gw 192.168.8.17
# # 永続化
# cat /etc/network/if-up.d/branch-a-routing
#!/bin/sh
/sbin/route add -net 192.168.8.0 netmask 255.255.255.248 gw 192.168.8.17

講評

ICTSC 2018お疲れ様でした。

今回の問題は、解答を見てしまえばとても簡単な解決方法だったかと思います。
ソースコードのほとんどは、ICTSC2018 一次予選のWebRTC問題とほとんど一緒です。

しかし、今回の問題では、テキストチャットがWebRTC通信が確立できていない際に、アプリケーションサーバを介して通信するため、戸惑ったチームも多かったかと思います。

回答率は他の問題に比べ低かったですが、WebRTCに使用されている様々な、プロトコル、技術を考慮している解答も見られ、WebRTCを多くの人に知ってもらいたかった作問者としては非常に嬉しかった結果でした。

 /

問題文

あなたの後輩が「FTPが動かない」と相談してきました。後輩の頼みを断れないあなたはこの問題を見てみることになりました。
どうやら家の外から認証は出来そうなのですが、ディレクトリ一覧を見ることが出来なさそうです。

手元PCから192.168.7.1で接続し、ディレクトリ一覧を取得できるようにしてください。

情報

server

routerがport forwardingを行っているため、routerのIPアドレスにSSHをすればserverに接続することが可能です。

IPアドレス: 192.168.7.1
ユーザー: admin
パスワード: cf1e8c14e54505f6

router

もしログインするのであれば、serverを踏み台にしてアクセスしてください。

IPアドレス: 192.168.7.254
ユーザー: admin
パスワード: f3395cd54cf857dd

トポロジー図

禁止行為

  • 以下の設定を変更してはいけない。
connect_from_port_20=YES
  • 以下のルールを削除・無効化してはいけない。
iptables -A OUTPUT -p tcp --sport 20 -j REJECT

解説

問題文に「家の外から認証は出来そうなのですが、」とあるように、家の外から接続することが出来ないようです。家の外で接続する場合、途中でアドレス変換(NAT)されたり、routerに入力されるパケット(inboundのパケット)を制限されている場合があります。そのため、ACTIVEモードよりもPASVモードで接続する方が接続しやすいでしょう。

まず、192.168.7.1にFTPで接続してみます。

$ ftp 192.168.7.1
Connected to 192.168.7.1.
220 (vsFTPd 3.0.3)
Name (192.168.7.1:root): admin
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pass
Passive mode on.
ftp> ls
227 Entering Passive Mode (192,168,7,129,169,115).
ftp: connect: Connection timed out
ftp>

すると、227 Entering Passive Modeというレスポンスが表示された後タイムアウトになります。このレスポンスの後方にある6つの数字は、PASVモードで接続する際に使用するIPアドレスとポート番号を表しています。RFC959を見てみると次のように説明されています。

DATA PORT (PORT)

The argument is a HOST-PORT specification for the data port
to be used in data connection. There are defaults for both
the user and server data ports, and under normal
circumstances this command and its reply are not needed. If
this command is used, the argument is the concatenation of a
32-bit internet host address and a 16-bit TCP port address.
This address information is broken into 8-bit fields and the
value of each field is transmitted as a decimal number (in
character string representation). The fields are separated
by commas. A port command would be:

PORT h1,h2,h3,h4,p1,p2

where h1 is the high order 8 bits of the internet host
address.

つまり、(192,168,7,129,169,115)という6つ組の数字は、「PASV接続する時は、192.168.7.129:43379に接続してください」という意味になっています。

ですが、192.168.7.129というのは、NAT内でのIPアドレスなので、NATの外から直接接続することはできません。本来であれば、192.168.7.1と表示されてほしいはず。192.168.7.1にアクセスした後に192.168.7.129にパケットが転送されてくれないと困るため、routerに対してrouterが着信した通信を転送する設定(Port forwarding)を入れる必要もありそうです。また、問題文からserverにもiptablesの設定が入っていそうなので、その設定も追記をする必要があるかもしれません。

まず始めに、serverの/etc/vsftpd.confに以下の設定をします。詳しい設定の意味は、こちらを確認すると良いです。ざっくり説明すると「vsftpdはスタンドアロンで動作し、IPv4からの接続を受け付ける。また、PASV接続の際には192.168.7.1の60001~60025番ポートを使用する」という意味になります。この設定を入れたあとは、vsftpdを再起動してください(sudo systemctl restart vsftpd)。

- LISTEN=NO
+ LISTEN=YES
- LISTEN_IPv6=YES
+ LISTEN_IPv6=NO
...
+ pasv_address=192.168.7.1
+ pasv_min_port=60001
+ pasv_max_port=60025

この次に、serverのiptablesの設定を確認してみましょう。

$ sudo iptables -L
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT tcp -- anywhere anywhere tcp dpt:ftp
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
ACCEPT tcp -- anywhere anywhere tcp dpt:domain
ACCEPT udp -- anywhere anywhere udp dpt:domain
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
REJECT tcp -- anywhere anywhere tcp spt:ftp-data /* DON'T REMOVE IT. */ reject-with icmp-port-unreachable

今回、60001番から60025番ポートをFTP用に使おうとしているので、それを許可する設定を入れる必要があります。

$ sudo iptables -A INPUT -p tcp --dport 60001:60025 -j ACCEPT

最後にrouterのPort forwardingの設定を確認してみましょう。

$ sudo iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DNAT tcp -- anywhere router tcp dpt:ftp to:192.168.7.129
DNAT tcp -- anywhere router tcp dpt:ssh to:192.168.7.129

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 192.168.7.128/25 anywhere

先程と同様に、60001番から60025番ポートを転送するような設定を入れる必要があります。

$ sudo iptables -t nat -A PREROUTING --dst 192.168.7.1 -p tcp --dport 60001:60025 -j DNAT --to 192.168.7.129

ここまで設定を入れると、次のようにPASVモードで接続できていることが分かります。

ftp 192.168.7.1
Connected to 192.168.7.1.
220 (vsFTPd 3.0.3)
Name (192.168.7.1:root): admin
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> pass
Passive mode on.
ftp> ls
227 Entering Passive Mode (192,168,7,1,234,102).
150 Here comes the directory listing.
226 Directory send OK.
ftp>

想定解法

  • serverの/etc/vsftpd.confに以下の設定を入れた。
- LISTEN=NO
+ LISTEN=YES
- LISTEN_IPv6=YES
+ LISTEN_IPv6=NO
...
+ pasv_address=192.168.7.1
+ pasv_min_port=60001
+ pasv_max_port=60025
  • serverのiptablesに以下の設定を入れた。
sudo iptables -A INPUT -p tcp --dport 60001:60025 -j ACCEPT
  • routerのiptablesに以下の設定を入れた。
sudo iptables -t nat -A PREROUTING --dst 192.168.7.1 -p tcp --dport 60001:60025 -j DNAT --to 192.168.7.129

講評

解答率はだいたい30%くらいでもうちょっと解いてくれる人多いかなと期待してました。作問者自身も中学生とかの頃はよく使っていましたが、最近はsftpで済ませてしまうことがほとんどなので、最近触れる人は意外とFTPを使ったことがないのかもしれないと考えています。

この問題は想定解法以外にもいくつかの解法があります。想定解法ではPASVモードで接続するようにしているのですが、ACTIVEモードで接続するのもこの問題のゴールである「手元PCから192.168.7.1で接続し、ディレクトリ一覧を取得できるようにしてください。」を満たしているため正解としています。中には、ip_conntrack_ftpip_nat_ftpを用いた解法もありましたが、基本的に使っても接続できるようにはならないはずです。想定解法以外の解法についてはいつかどこかで説明できたらなと思っています。