SORACOM NapterでリモートデバイスにFTP接続してみた

この記事はSORACOM Advent Calendar 2019 ふたつめ の12月7日分です。

2019/7/31 SORACOM Drinkup #2にて発表したLTをもとに記事にしました。

# 今時FTPかよ

って思いますよね。僕もそう思います。もう2019年ですよ？

一昔前まではFTPはインターネットでも普通に使われていたと思います。ホームページを公開しようと思ったら、レンタルサーバーを借りて、FTPでファイルをアップロードして、みたいな動きをしてた気がする。PerlのCGIとか上げてた気がする。

ところがまあご存じの通りFTPはセキュリティ的に余りにもあれなことが分かってきてしまいました。

ざっくりこんな感じです。
<img width="874" alt="スクリーンショット 2019-12-07 18.38.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/73face99-700e-d2ef-be9f-2659c36de356.png">

特に認証情報が平文（PASS XXXXという感じで送られる）というところが、基本的には信頼できないネットワークであるインターネットでは致命的すぎ、今日ではインターネット上でアクセス出来るFTPサーバは絶滅しているはずです。たぶん。（公開性の高いFTPサーバーにアノニマスで接続してダウンロードする用途はありますが、それもHTTPになってる感じです）

それもそのはず、FTPの成り立ちを調べるとRFCはRFC 114(1971)。IP（インターネットプロトコル）がRFC 791(1981)なので、現在のTCP/IPをベースとしたインターネットが成立する前からあるプロトコルなんですね。

現在ではインターネット上のサーバーとのファイル転送には使われていない（はず）なのですが、ローカルなネットワークではまだまだ使われています。特に産業用機器のファイル転送手段はFTPだったりすることがままあるのではないかと。

そういうレガシーな機器たちもIoTに参加させたいわけです。既存のものが使えると対応の幅がぐっと広がります。でもそのためにメーカーに追加開発してもらうのはまあ望めないし、外付けのゲートウェイをつけてプロトコル変換するというのもハードウェア費用やプロトコル変換部の開発コストが見合わなかったりで厳しかったりします。

で思わずグローバルIPを割り振って使ってたら、攻撃を受けました、残念です。みたいなことになります。

こういう、レガシーな機器を安全に接続する方法がないものか。。ってところで良いものが出ました。**SORACOM Napter**です。

# SORACOM Napterとは

ソラコム株式会社が提供している**オンデマンド リモートアクセス**のサービスです。SORACOM Airという携帯回線を使っているデバイスに対して、簡単にリモートアクセスすることが出来るようになります。

https://soracom.jp/services/napter/

このサービスの素晴らしい点を上げると以下のような点です

- SORACOM Airの回線は普段はグローバルIPを持っておらずインターネットからのアクセスができないため安全
- 必要な時だけ使えて、必要ない時は費用がかからない
- ポート転送設定もコンソールで簡単にできる
- Web APIでシステム連携も簡単にできる
- 転送元のIPアドレスを指定することができるので、開けている間も安全(デフォルトでは開ける操作を実行したIPアドレスになるので勝手に安全になっている) 
- デバイス側にエージェントが要らない←これはとても便利
- インターネットを通るSORACOM NapterまではTLSで保護し、サービス内でプロトコル変換して、安全なSORACOM Airネットワーク内では平文で通信する。そのため**デバイスがTLSに対応していなくてもセキュリティが確保**できる。←これ凄すぎないですか？

とりあえずこのへんにしておきます。簡単、便利、安全ですね。

さっきの絵がどう改善されるかをまとめたのがこちら

<img width="869" alt="スクリーンショット 2019-12-07 22.07.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/401aedec-155d-70fa-c86e-b8cbdb236f40.png">

さっきの問題が全て解消した上で、IP制限までしてくれてるのが分かりますね。

よーし、じゃあこれでFTPやっちゃうぞ！ってあれ？繋がらない？

<img width="872" alt="スクリーンショット 2019-12-07 22.15.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/9c63a4c2-43fb-c2b5-a274-94c92a452a0c.png">

# Napter + FTPで接続できない理由

はい、FTPを知っている人にはこの流れは読めていたかも知れませんね。正直僕もダメだろうなと思ってましたがソラコムが謎の技術でFTP対応してくれている可能性に賭けましたが、やっぱダメでしたね。

接続できない理由は大きく分けて２つあります。

1. FTPでは制御用とデータ用でコネクションが分かれていて、制御用は同じポート番号でアクセス出来るが、データ用はファイルやファイルリストの取得ごとにポート番号が変わる。単にポートを２つ開くだけじゃだめ
2. データ用ポートを指定されるコマンド：Entering Passive Mode (h1,h2,h3,h4,p1,p2).で指定されるのは回線のプライベートIPでクライアントからは直接アクセス出来ない

(ちなみにPORTモードに対応するつもりは最初からないです。不可能だし必要も無いので)

うーむ、やはり思った以上に相性悪いな。というかFTPと相性いいものなんて存在しない気がする。

とはいえここで終わると面白くないので、実現する方法を考えました。

# napter-ftp-proxy

以下のような感じで、ローカルホストもしくはLAN内のサーバでプロキシすればいけるんじゃないか？

1. FTPサーバとFTPクライアントの直接接続は諦め、間にプロキシを挟む
2. プロキシは「227 Entering Passive Mode」のたびにそのポートのNapterを開く
3. プロキシは「226 Closing data connection.」のたびにそのポートのNapterを閉じる
4. プロキシは通信を暗号化・復号して、NapterのIPアドレス、ポートに対して仲介する。さらに「227 Entering Passive Mode」の場合のみ、接続元IPアドレスを細工する

言葉では伝わりにくいかも知れないので、図解します。
緑色の線はTLSにより暗号化されています。
デバイスのFTPサーバとしては、とりあえずラズパイにUSBモデムを挿して、vsftpdをインストールして稼働させておきます。

<img width="869" alt="スクリーンショット 2019-12-07 22.31.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/e7dcdea5-a127-2a93-42e2-95b4a2687044.png">
<img width="871" alt="スクリーンショット 2019-12-07 22.32.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/563e244e-7590-24d2-8cd8-8f19440b92b8.png">
<img width="871" alt="スクリーンショット 2019-12-07 22.33.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/ea7ada0f-d809-2c32-6d76-397c3701a902.png">
<img width="867" alt="スクリーンショット 2019-12-07 22.35.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/995085be-2806-6924-6e84-d1c68de9a1d9.png">
<img width="873" alt="スクリーンショット 2019-12-07 22.36.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/6e49d52a-4305-1202-b8e4-a43232d64d64.png">
<img width="871" alt="スクリーンショット 2019-12-07 22.37.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/5e6808be-dbda-082f-5fed-30ce5e9a91b6.png">

こんな感じでファイルやファイルリスト転送のたびに作られるデータコネクションの生成、破棄コマンドが来るたびにそれをキャッチして、Web APIでNapterの生成、破棄＆コマンドを細工して送信すれば、FTPクライアントから見ればLAN内のFTPサーバと接続しているようにしか見えないけど、実はNapter経由でデバイスのFTPサーバに接続している、という状態にできます。

この動きをするプロキシサーバをGo言語で開発してみました。

https://github.com/1stship/napter-ftp-proxy

Go言語なのは、言語の実行環境やライブラリを入れてもらうことなくシングルバイナリで動作するようにしたかったからです。特にWindowsとMac、Linuxのどれでも使いたい、というような用途ではGo言語でバイナリ作るのが一番楽ですね。

上のGitHubにはリリース用のファイルもあるので、例えばMacだったら

https://github.com/1stship/napter-ftp-proxy/releases/download/0.1.1/napter-ftp-proxy_0.1.1_darwin_amd64.zip

からダウンロードして、解凍して、

`./napter-ftp-proxy --target IMSI`
として接続先のIMSIを入力して、ソラコムアカウントのメールアドレスとパスワードを入力したら準備完了です。

あとはローカルホストの21番にお手持ちのFTPクライアントでアクセスするだけです。

これで無事接続、ファイル取得できました！しかもインターネット上の通信は暗号化されている！これはすごいのでは？

# それでいけるのはルータまでだろデバイス接続はどうした

って言われますよね。まあこの場合はルータではなくUSBモデム挿したラズパイなのですが、それでもデバイスそのものに届いてない事には変わりない。

ここが結構やっかいなところで、これはFTPに限らないのですが、Napterはあくまでも回線接続している機器までの接続なんですよね。当然と言えば当然。

なので、ルータのLANの中にいるデバイスに転送するには、DNATの設定をする必要があります。ルータごとに設定の仕方が違うのですが、ポートフォワーディング（ポート転送、ポート開放などの表記もあり）の設定を見つけて、制御コネクションのポート番号21番とデータコネクションの????? - ?????(対象のデバイスのFTPサーバの仕様によって変わるパッシブモードのポートレンジ。40000-40030など)を該当デバイスのIPアドレスに転送する設定をルータに設定します。

<img width="871" alt="スクリーンショット 2019-12-07 23.27.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/637e834d-31fb-3903-93da-191064f8a81b.png">

これでデバイスにも到達できました。しかしここがどうしても手動になってしまうのをなんとかしたいですね。ルーターの共通の設定方法とかないものだろうか。もしくはソラコムが謎の技術でNapterをルーターの下のデバイスまで届かせてくれないだろうか。

# 最後に

SORACOM NapterはSSHやHTTP(TLS変換)での利用が多いと思いますが、レガシーかつ安全ではないプロトコルしか持たない産業機器をセキュアにIoTに参加させる際の救世主になる可能性を感じます。

今回は分かりやすいところでFTPに対応してみましたが、産業機器には各々のコマンド・プロトコル体系で平文のコマンドを使って制御されるやつらがいっぱいいます。そういうのに心当たりある人は、SORACOM Napterを試してみてください。そのままだとうまくいかなくても、WebAPIでNapterの制御ができるので、ちょっとしたプログラム書けばなんとかなる可能性も高いですよ。

あとはルータの下のデバイスに届かせる方法が課題ですね。ラズパイ（などの自分が制御できるLinuxマシン）の下にデバイスがいる場合なら動的にSSHでiptablesコマンド送りつけるとかでなんとかできるんですけど需要あるんですかね。