Chienomi

サーバーセキュリティ構成の話

Live With Linux::server

最近、安易に建てられた危険なサーバーが増えているため、サーバーセキュリティを鑑みた基本的な設定や構成はどういうものかという話をする。

本記事では具体的な設定や構築を説明するが、環境や前提、用途などもあるため、これを真似すれば安全ということではない。 セキュリティは銀の弾丸があるわけではなく、全ての要素を合わせて考えたア上での最適を導かねばならない。それがセキュリティの難しいところでもある。

本記事はセキュリティが未熟だと自認する人にとっては参考になる内容だと思うが、どちらかというと、本記事の内容が当たり前に「すでに理解できている内容」になっていない人は、サーバーを建てるべきではない(危険な未熟の段階である)ということが重要であり、各々が自身の技量を測る指標として使ってもらえればと思う。

宣誓の儀

「サーバーを破られるということは、すなわち犯罪に加担するということである」

この言葉をしっかりと胸に刻んだ上でサーバー建立に取り掛かること。

基本的な概念

セキュリティの基本要素の話だが、サーバーに閉じたセキュリティとしては、究極は「rootシェルを取られる」である。 「任意コマンド実行バグ」が重大なバグとされる最大の理由が「シェルを取れる」からだ。

rootプロセスでシェルを取られるとその時点で「rootのシェルをとられる」事態になるため、攻撃可能な対象からrootプロセスを外す、というのは必須になってくる。

その上で権限昇格の脆弱性を発生させないようにする。

SSHの場合はシェルを含む任意コマンドが実行可能なものなので、破られたときのリスクは非常に高い。

SSHはホストに対する操作が必要なので難しいが、多くのサーバーソフトウェアの場合は(Linuxの)namespace機能などを使い、ファイルシステムやユーザーを専用にマップしてしまうことで、「シェルを取られても被害が限定されるようにする」という手法がある。 「サーバーをコンテナ上で動かす」ことがこれと同等の意味になる。

これはフェイルセーフ構造だが、実際はコンテナへの侵入は、「サーバーへの被害を拡大させない」というものであって、コンテナからoutgoingの通信ができる時点で踏み台になっているため、状況としてはかなりアウトだ。

このように、セキュリティの問題は「それによって何が可能か」という観点から考える必要がある。その上で防衛策を決めていくわけだ。

また、場合によってはオンラインセキュリティだけを考えても仕方がない。 ラップトップを盗む、あるいはショルダーハッキングなどのオフライン攻撃に弱くては根本的なところで覆ってしまう可能性もある。

ほとんどの場合、「サーバーを止める」が最も良い選択である。 動作させるものを最小限に絞るのはもちろんだが、どちらかといえば「減らす」ことよりも、利用可能な領域そのものを絞って、不可欠なものだけを万全の対策を施した上で開放していくのが良い。

場合によっては、サーバー(デーモン)ではなく、サーバー(ホスト)を止めるというのもあるし、「そもそもサーバーを建てない」ことが正しいセキュリティである場合もある。

このほか、名前解決周り、ネットワークルーティング周り、ハイジャック関係のセキュリティ問題もある。これらは当然対策するべきものではあるが、前提次第で話が根本的に変わってくることから今回は取り扱わない。

また、サーバーアプリケーション実装に関する脆弱性や、webアプリケーションの脆弱性なども扱わない。これは、種類が異なるトピックスだからである。

最小セットを考える

本当に最小のサーバーというと、sshdだけで運用することも可能である。 なので、必須なのはsshdだけと考えて設計を始める。

私のメインサーバーの場合は、このほかにPostfix master, Dovecot, Nginxが外部NICで動いている。 localhostでlistenしているものは、Systemd Resolved, Docker, Containerd, Spamassassin (spamd), lighttpd, hiawasa, Maildeliver, OpenDKIMがある。

サブサーバーのほうはwebサーバーがない構成なので、NginxやLighttpd、それに伴うコンテナなどが動いていない。つまり、オープンで動いているサーバーはsshd, Postfix master, Dovecotだけ。DovecotはPOP3が動作していないため、ポートはさらに絞られている。

ネットワーク上で許可しているポートは22, 80, 443, 587, 993となる。 選択的な設定として、明示的SMTPSで使用する465、IMAPと暗黙的IMAPsで使用する143を通さない。 SMTPはsubmissionポートを使ってのStartTLSを、IMAPは明示的TLSによるIMAPsを使うことを強いることで解決している。 使うのはどうせ自分なので問題ない。 また、自分ではIPv6を使わないことから、IMAPはIPv6を無効化している。

まぁ、一般的には公開サーバーはsshd, httpdだけで、許可ポートは22, 80, 443の3つにするのが一般的だろうか。 計算力として使うためのサーバーであれば、例えwebインターフェイスを持つ場合でもsshdが動いているなら対応可能なため、22だけを公開するほうが良いだろう。

PAMとSSHの設定

OpenSSHの初期手順

パスワードログインを無効にするためにkeyboard-interactiveをいじるような設定を推奨していたり、PasswordAuthenticationをいじるように指示していたりするが、正しくはそうではない。

SSHのログインにパスワードが含まれるかどうかはAuthenticationMethodsによって決まるものであり、(デフォルトの設定ファイルには記載のない)AuthenticationMethodsの設定は必須であると言える。

AuthenticaitonMethodsA, B C, Dとした場合、「A→BまたはC→D」の3段階で認証を突破する必要がある。 例えば

AuthenticationMethods publickey password

とすれば、公開鍵認証またはパスワード認証のどちらかによって認証されれば良いという意味になる。

まずは第一段階として、ログインするための鍵を用意したら、直ちに

AuthenticationMethods publickey

とグローバルセクションに記載してリロードすることで、公開鍵を必須にする。 可能ならば、これ以前にsshdを起動せず、コンソールからログインしてこのようにするのが望ましい。

また、即座にrootシェルが取れてしまうため、rootでのログインは禁止するべきで、またsshログインを前提とするために、パスワードは現実的に利用不可にしたい。 これを手順としてまとめると、こうなる

  1. インスタンスを作る
  2. 入力困難なパスフレーズをrootに設定し、手元にメモとして持つ
  3. コンソールからrootとしてログインする
  4. sshdを導入する
  5. AuthenticationMethods publickey を記載
  6. DenyUsers root を記載
  7. 一般ユーザーを作り、入力が非常に困難なパスフレーズを設定する
  8. 一般ユーザーでrootログインできるようにする (後述するsudo部分)
  9. 公開鍵を一般ユーザー用に登録する
  10. sshdを起動
  11. 一般ユーザーでログインできることを確認する
  12. rootのパスフレーズを現実的に入力不可能なものに設定する
  13. コンソールからログアウトする

publickey認証の安全性

さて、ここでSSHの基本的な知識に戻るが、公開鍵認証は一定の条件のもとで十分な安全性を持つため、それを満たしているのであればこれで十分である。

その条件とは

  1. SSHv2プロトコルで公開鍵認証をし、かつ十分に安全な署名アルゴリズムを使っている
  2. 秘密鍵が十分に安全に管理されている

RSAはすでに「十分に安全な署名アルゴリズム」ではない。 ECDSAは別に安全性が主眼ではないのだが、ECDSAもしくはEdDSA鍵を使うべきである。

秘密鍵の安全性は、以下のような条件を満たす場合

  • ラップトップやUSBペンドライブに入れて外に持ち出すといったことがない
  • 鍵を格納するストレージは十分に強固に暗号化されている
  • システムは十分なオンライン安全性を持っている

ショルダーハッキングされない程度のパスフレーズによって保護することで安全であると考えられる。 そうでない場合、鍵自体がパスワードクラッキングによって破られるより早くサーバーから鍵を除去できる程度の時間を稼げるだけの強度を持つパスフレーズによって暗号化されている必要がある。

OpenSSHでの多要素認証

これを補うアプローチとして、前述した多段階認証が利用できる。

UsePAM yesであるならば、keyboard-interativeメソッドはPAMを用いて認証される。使用されるサービスはsshdである。つまり、設定ファイルは/etc/pam.d/sshdにある。

PAMが使えるならpam_execが利用可能だ。 しかし、pam_execを使って隙がないように書くのはそれなりに大変なのでpam_oauthなどを使うほうが良いだろう。(もしくはpam_execで使うことを前提にしたプログラムか)

Google AuthenticatorクライアントはPAMモジュールのgoogle-authを持っているので、Google Authenticatorを使うのは容易。

ただ、前述のとおり、publickeyは適切に運用していれば十分に安全なので、二要素認証は過剰な話になりやすい。

PAM認証とシステムパスフレーズ

さて、SSHが十分な強度を持つようになったら、次にPAMパスワード認証の話だ。

PAMのパスワードはハッシュ化されて保存されるため、実際のところ「なんでも使える」1。そして、キーボードから入力できないパスワードを設定すると、現実的にログインできなくなる。

Archlinux/Manjaro LinuxのデフォルトのハッシュアルゴリズムはSHA-512である。

ハッシュアルゴリズムである以上、ハッシュ衝突が発生するので、無限に長くすればするだけ強固になるわけではないのだが、「パスワードを推測する方法が現実的に通用しない」レベルまでいけばパスワードによって突破される心配は非常に小さくなる。

さて、前述のように、ユーザーは「非常に困難」な程度、rootは「現実的に不可能」な程度のパスフレーズにする。unprintableにするという悪魔の法を用いない話をするならば、urandomを使うmkpwd.rbのようなパスワード生成器を使って十分な長さを持つパスワードを生成する。

ユーザーであれば32〜48文字、rootは128〜256文字程度にすると良いだろう。試しに128文字生成したところ

h,_ADz|m(CSc/p".7igL!z9;3EJs.rI+~u}MQF;+$$w>qa*~j^@jC(&>};TB(~I=Rjanmf522{v%Yh/ixA'y:#]{~F(YT']o/SD8}R{1|k7*M#w|;#i7_tP%Q>X-oj\"

となった。「現実的に不可能」の意味がご理解いただけるかと思う。 これは、コンソール上では見ながら打つのも相当厳しい。コピペならできるが。

また、faillockを必ず使うこと。 どれほど強固なパスワードも、試行回数が無限ならいずれ破られる。 所要時間は非常に重要だ。

SSHのポート

ある程度以上に安全である前提であれば、SSHポートはセキュリティに貢献しない。

SSHへの攻撃は大きくわけて2種類に分けられる。

  • 単に22番ポートに向かって投げてくるもの
  • ポートスキャンし、バナーも確認した上で投げてくるもの

前者に関しては、認証が通りそうにないサーバーに固執しない。 なにせポートスキャンすらせずに投げてきているわけで、「設定もせず、脆弱なパスワードを使っている愚かなホスト」をつか探しているのである。 このため、22番ポートが疎通しないような相手は、SSHにつながってもその攻撃は通用しない可能性が高いため、諦める。 どんどん次へいって虱潰しで試すことで、弱いサーバーを見つけるほうが早いと考えているからだ。

一方、ポートスキャンしてバナーも確認してくるような相手は、そのサーバーを陥落するつもりできている。 つまり、ある程度確率された攻撃手法を試してくるし、SSHのポートを発見するのはその第一歩にすぎない。 このため、SSHのポート変更はハードルほどの防止効果もない。せいぜい「立ち入り禁止」の張り紙をする程度の効果だ。

このように、セキュリティ上の意味はまったくないが、サーバーの負荷軽減にはなる。 というのも、サーバーに来るSSH攻撃の量はものすごいものなので、結構な負担になる。 なおかつ、「ものすごい量」の大部分は前者のタイプなので、ポート変更するとSSHへの接続量は大幅に減る。

以下は、私のSNSサーバーのSSHHeatmapだ。 22番ポートを開放しているため、アクセスはかなり多い。

SSH Heatmap

5ヶ月で1383アクセスあった。1日あたり9.22アクセスほど。 攻撃者の拠点(踏み台)はソウルが最も多く、次いで北京。

ちなみに、現在は「少しでもメモリを節約したい」という理由でこのサーバーのSSHポートは変更されている。

さらに、スイッチレベル/ルーターレベルで22番ポートを遮断すると、通信はサーバーに届きすらしないため、SSHの負荷に悩まされている場合はそこそこ効果がある。

ただ、これも微妙ではある。 最大の理由は、SSHへの攻撃とは桁違いにHTTP(s)への攻撃が多いことだ。

以前はHTTPSが一般化に広く使われていなかったので、攻撃はHTTPであり、SSHと比べると接続確立自体が軽いため、SSHのポート変更で恩恵を感じることができた。 しかし現在は、基本的にHTTPS接続になっているため、接続が軽いということは特になく、HTTPSへの攻撃量自体も数十倍増えているため、HTTPSへの攻撃と比べるとSSHへの攻撃による負荷は「些細なこと」に過ぎなくなっているのだ。

例えば、私のSNSサーバーのこの5ヶ月間でのHTTP(s)による攻撃的アクセスの数は321590であった。1日あたり2144件の攻撃であり、SSHの232.5倍である。

今やとにかく「webサーバーを動かさない」が最も攻撃を削減する方法になっている。 現実的には難しいとも言えるし、webサーバー部分だけを外部に置く(SaaSなどを使う)というのが良いソリューションになっているとも言える。

なお、サーバーを立てる目的がwebサーバーしかないと思っている人は、webアプリケーション開発者ならそれでも許されるが、プラットフォーム運営者としては適格ではない。

webサーバー

webサーバーは何を採用するかにもよるし、どのような構成を選択するかにもよる。

一般的なことで言えば、rootユーザーでは動かさないこと、セキュリティパッチはちゃんと当てること、hashlimitを設定することなどだろうか。

アプリケーションサーバーを使う場合は、公開listenしないように注意を払う必要がある。

まぁ実際のところ、webサーバーはサーバーに何を選ぶかが8割で、そしてwebサーバー自体が落とされることよりも、webコンテンツやアプリケーションに起因することが圧倒的に多い。

webセキュリティの話は非常に広範なトピックスを持ち、記事の一節として扱うには膨大すぎる。 Mimir Yokohamaでのwebセキュリティの標準的な受講コマ数は20〜30である。つまり、40〜60時間程度学んでようやく通り一変の知識を獲得できるということだ。

ちなみに、セキュリティを破られる理由のほとんど(おそらく99.99%以上)は、攻撃者の能力や執念によるものではなく、単に管理者が愚かであることに起因する。 それは例えば、設定をまるでせず初期状態のままにしておくとか、パスワードが8文字以下であるとか、単語の「l」や「i」を「1」に変えることでセキュリティが向上すると考えているとかだ。

そして、ウェブアプリケーションがどの程度の権能を持っているか、つまり「愚かな人が使ったがために乗っ取られる」という自体を想定して権能を小さくしているかは純粋にアプリケーションにもよる。 任意コマンド実行が可能なウェブアプリケーションというものもあるし、それは(ちゃんと設定すれば)堅固な認証を突破しなければ使えないものであるならば、脆弱性とは呼ばない。

つまり、webセキュリティという意味では

  1. 管理者が愚かで、セキュリティを放棄している
  2. アプリケーションが脆弱である
  3. webサーバーの脆弱性を攻撃される

の順なのである。 このため、当たり前のことを適切にやっていれば防げることが大半なのだが、現実にそのようにしているのはひと握りだ。

アプリケーションの脆弱性に対する評価は難しい。 相対的に見れば、webアプリケーションは脆弱なものが圧倒的に多い。 少なくとも、Nginx, Squid, Postfix, Bindといったサーバーとは比べ物にならないほど弱い傾向にある。

かといってみんな弱いかというとそんなこともなく、強いものはちゃんと強い。 さらに、メジャーなものはみんな強いかというとそんなこともなく、よく使われているアプリケーションだけど弱いということは普通にあるし、WordPressのように、アプリケーション自体に重大な脆弱性が(ないわけではないが)あるというよりも、設定によって脆弱な環境を作ってしまいやすいようなものもある。

とはいえ、メジャーなアプリケーションは、普通に自作するものよりは安全である可能性が高い。 プログラムがセキュリティ的に安全であるためには、作者にセキュリティに対する深い知識と注意深さが要求される。 私が書くウェブアプリケーションも「だいたいは安全」だが、眠いときに書いたものは不注意によって脆弱性を含んでいたりすることもある。

そして、この問題はウェブアプリケーションを作る場合に、多くのライブラリを使うなどして、コントロール/把握できていない領域を多分に含むことが多いということも関係している。

ただまぁ、この記事でwebアプリケーション開発者に道理を説くのは変な話だからサーバーの話に戻ろう。

webサーバーは少なくとも、メールサーバーのように立てた瞬間に攻撃に加担してしまうようなリスクがあるものではない。 どのようなサーバーを使うかにもよるが、一般的にはアップストリームのデフォルトで起動してもセキュリティ的には問題のないものである場合が多い。 ただ、SSLとHTTPSに関する知識など、結構幅広く知識が必要なものではある。

webサーバーに関する余談

webサーバーに何を使うか、という話をしよう。

実は他のサーバーと違い、webサーバーは非常に豊富な選択肢がある。 webアプリケーション向けのもの(例えばThin, Unicone, Lightspeed)を除いたとしてもだ。

有力なところでいうとApache, Nginx, Lighttpdあたりだろうか。 ほかにもHiawasaなど、色々ある。

この記事はセキュリティの話なので、セキュリティ観点でいうと「Apacheは難しい」である。

Apacheは人の手に負えない高機能さがあるため、「ちょっとした迂闊で脆弱性を発生させる」ことがある。 その意味でいうと、NginxやLighttpdは割と安全だ。

NginxはwebサーバーというよりもHTTPリバースプロキシである。 つまり、アプリケーションサーバーの前段に存在するのが基本なのだが、静的ファイルのホスティングという意味でも悪くはない。 ……が、あくまで「悪くはない」であって、パフォーマンスはそこそこ良いが、機能的にはいまひとつ。 とはいえ設定はしやすい部類なので、静的ファイルをホスティングする選択肢としては十分考えられる。

私は現在はNginxを使っているが、以前はDeleGateを使っていた。 DeleGateは極限まで複雑なソフトウェアだが、「書いたこと以外一切やらない」タイプなので、以外と脆弱性は発生させづらいタイプだった。

Lighttpdは非常に合理的な動作をするが、その一風変わった動作をちゃんと理解していないと思わぬ脆弱性を生むことがある。 ただ、webアプリケーションを使わない(つまり、リバースプロキシとして動作しない)webサーバーとしては、一番有力だと私は考えている。

まぁこれらを踏まえて言うと、セキュリティ観点では「Nginxが一番無難」である。

メールサーバー

メールサーバーを運営して23年になる私が断言する。 今から自分でメールサーバーを運用しようなどと考えないほうがいい。

あらゆる角度で他者に迷惑をかける機会がものすごく多く、設定のアラをついて攻撃される機会が多く、そしてそれは実際に成功してしまいやすく、新しい攻撃手法と要素がどんどん追加されていく。

メールサーバーの運営は、無限に走り続けなければいけないソシャゲみたいなものだ。 しかも、完走できなければ加害者になる。サイアクだ。

実のところ、受け取るだけであればまだなんとかなる部分もある。 Postfixの場合、main.cfで例えば次のように設定する。

myhostname = example.com
mydomain = example.com
myorigin = $mydomain
mydestination = $myhostname, localhost.$mydoamin, localhost
mynetwork_style = host
relay_domains =

これでPostfixはメールを外部にリレーしない。自身のドメインであるexample.com宛のメールだけを受け取り、また自ホスト(localhost)から接続された場合のみメールを配送する。(実際は配送しようとしてはいけないが、配送しない設定はできない)

それで他者がこのメールサーバーを使って他者にメールを送りつけることはできなくなったが、存在しないユーザー宛に送り付け、バウンスメールを使って転送させるという手法が存在する。 そこで、main.cf

luser_relay = blackhole

そして、aliasesで次のようにする。

blackhole: /dev/null

これで受信中継用メールサーバーができるが、それでもやめたほうがいい。

そして、送信に関しては再度言うが絶対にやめたほうがいい。

ファイアウォール

基本的にはホストではなく、ネットワークレベルで遮断すべきだ。

私はVultrを使っているが、スイッチレベルのパケットフィルタリングが可能。 ConoHaやさくらもポート単位で可能だ。

一方、流量制限はDDoSやSynfloodingなどを考えても必要となってくる場合がある。

ここでは例として、SSHに対する新規接続を2回/分に制限し、10回までを除外し、テーブルを15分保持するという

iptablesを使う場合の制限:

iptables -N HLIMIT
iptables -A HLIMIT -m hashlimit --hashlimit-name hlimit_ssh --state NEW --syn --dport ssh --hashlimit 2/m --hashlimit-burst 10 --hashlimit-mode srcip --hashlimit-htable-expire 900000 -j ACCEPT
iptables -A HLIMIT -j DROP

ただ、これも割と微妙な話ではある。 というのも、HTTP(s)以外への大量の接続というのはそんなにある話でもなく、HTTPに関しては単一の接続を確立して大量のリクエストを発行する攻撃なので、この方法で防ぐことができない。

もしHTTPの通信を遮断したいと考えるならば、httpdのハッシュリミットをトリガーにする動的ファイアウォールが必要だ。 httpd自体のハッシュリミットによってリクエストを制限することはできるが、httpdに到達した状態で制御されるものなので、セキュリティへの貢献度は低く、効果は小さい。

監視

素人は監視を行わないため、サーバーが乗っ取られていることに気づかず、賢いハッカーは自分がサーバーを支配していることを気づかせない。 監視をしていないサーバーは、責任を放棄していると言って過言ではないだろう。

監視系の話は非常に複雑な話である。Xに対する解A、Yに対する解B、のように個々の事柄に対する対応が、事柄の数だけ存在するからだ。 また、監視系のソフトウェア(NagiosやMuninなど)の多くは大仰で難しい。

だが、監視という概念自体は、監視対象に対する理解がある前提なら難しくはない。 監視対象は事象に対してログを出力する。このログから一定の規則で異常を検出し、対応すれば良い。

Systemd-Journaldはサービスの出力をDBに保存する。サービスの標準出力に加え、syslogサーバーとしてもサービスの出力を受け取る。 ログを自らファイルなどに保存するサービスには対応できないが、多くの場合このログDBを取り扱うjournalctlによって統一的に操作できる。

journalctlは非常に豊富なフィルタ機能を持っており、非常に便利だ。 日常的なレベルでよく使うのは、今回の起動のログを参照するjournalctl -u $UNIT -b 0や、今日のログを確認するjournalctl -u $UNIT -S todayあたりだが、例えばSSH Heatmapのためのログを抽出する方法としては

journalctl -q -u sshd -g "authentication failure| Failed password"

が使える。

さて、これを使うと監視系が簡単に組める。 単純に問題があったときに通知するコマンドsecalertがあると仮定すると、次のようなシェルスクリプトが成立する。

if journalctl -S today -g "authentication failure| Failed password"
then
  secalert sshd
fi

これだと一度警告が発生すると重複してしまうため、それを回避するならこんな感じになる。

typeset since
if [[ -e /tmp/obs_sshd_checknext ]]
then
  since=(</tmp/obs_sshd_checknext)
else
  since=today
fi

typeset log=$(journalctl -S "$since" -g "authentication failure| Failed password")

if (( ? != 0 ))
then
  secalert sshd <<< "$log"
  ruby -e 'puts ARGF.read.each_lines[-1].sub(/myhost.*/, "").succ' <<< "$log" > /tmp/obs_sshd_checknext
fi

これはいくらなんでもトリックが過ぎるので少し解説。

since=(</tmp/obs_sshd_checknext)

Zshの省略した書き方である。デフォルトではcatへのリダイレクトになる。

(( ? != 0 ))

(( ... ))は算術評価。 算術評価の中でシンボルはすべて変数名になる。 直前のコマンドの終了パラメータは変数$?であり、算術評価の中では名前だけになるため、変数名は?で「?0でないとき」になる。 なお、$?は整数型である。

ruby -e 'print ARGF.read.split("\n")[-1].strip.sub(/myhost.*/, "").succ' <<< "$log" > /tmp/obs_sshd_checknext

Rubyを使っていてわかりにくい。これをほぐすと

print ARGF                # 出力するのは標準入力から
  .read                   # 読み込んで
  .split("\n")[-1]        # 改行で分割した最後の要素
  .strip                  # 前後の空白を除去
  .sub(/myhost.*/, "")    # myhostから先を除去
  .succ                   # の次

ポイントはふたつあり、ひとつはRubyのString#succである。 これは、文字列の「次」を返すのだが、例えば8月 02 22:38:56の「次」は8月 02 22:38:57になる。この機能を使えることでかなり話が楽になる。

そして、次がSystemd timestampの仕様だ。 Systemd Timestampは8月 02 22:38:57のようなロケールの日付でも解釈できる。 そして、時間の「秒」の値は、61まで受け付ける。 そのため、当然ながら8月 02 22:38:59の次(succ)は8月 02 22:38:60なのだが、この繰り上がり処理をしなくても良いわけだ。

まぁ、こんな感じでちょちょっとシェルスクリプトを書けば簡単に監視が可能。 もちろん、シェルスクリプトでやるから難しい部分もあるので、Perl, Python, Rubyなどが書けるならばもっと簡単。

ちなみに、私は監視系による発見はDiscordに通知されるようになっている。 なおかつ、Discordは通知専用に使っており、他のサーバーには入っていない状態で、Discordに通知が入ると爆音で通知音が鳴る。 時々certbotがコケたりするけれど、そういうときは爆音で響き渡るので「ふひぁ!」となる。 LINEを頻繁に使う人なら、LINEに通知するのもいいかもしれない。

また、異常を検知したときの振る舞いだが、私の場合メールサーバーを持たないSNSサーバーは「シャットダウンする」となっている。 シャットダウンしてしまうと、ホスティングプロバイダーのアカウントをハックされているのでない限り、攻撃者はそれ以上何もできない。これにより、無理なく、腰を据えて取り組めるときに対策を取ることができる。 また、攻撃者は仮に攻撃に成功していたとしても、タイミングを失うことで攻撃の継続が難しくなる。

メールサーバーがある場合、比較的短時間のダウンでも問題になりやすいが、webサーバーは個人運営のものなど、ダウンしても大した話ではない。 SNSサーバーはActivityPub経由で配信されたりするため、他サーバーに対して迷惑をかけないわけではないが、数時間のダウンタイムが深刻な問題になるほどではない。 サーバーの異常状態などそう起きることではない2ので、そのような異常事態に可用性を優先する必要などない。

サーバーにarchlinuxを使うメリット

設定がアップストリームのデフォルトを引き継いでいるので、デフォルトの設定が適用されているという前提で考えることができ、設定ファイルを全部改めなくてもちゃんと安全な設定ができる。

適切なパッケージ管理がしやすく、設定のアップデート管理も容易。

セキュリティを考える上で、適用されている設定を把握するのは当然ながらマストだ。 それが楽になるのは大きい。

結び

セキュリティに完璧はなく、日々最新情報を追い続けなければいけないものでもある。 非常に難しく大変で、サーバーを建てるのは子育てのように、日々目を離せず、手間をかけ続ける必要があるものである[^observable]。

そのことを理解しているならば、個人で運営するサーバーでまず考えるのは、必要な労力を減らし、目を配るべきところを減らすことだ。 だって大変だから。 だからこそ、できるだけサーバーを止め、できるだけポートを塞ぎ、できるだけ攻撃者のとっかかりを減らし、ピンポイントに押さえておけば把握できる状態を作っていく。

サーバー運営に熟練してくると、自然と「自分でサーバーを建てない」という方法も検討するようになる。 実現しないケースも多いが、PaaSやSaaSを利用するなど、様々な方法で自分が管理する領域を減らす。

まぁ、サーバー運営は楽しいという面もある。 あるにはあるが、それはそこにのしかかる責務を忘れて良いという意味ではない。 「犯罪の加担をすることがないよう、その責務を果たす」という意識を当然のものとして、日々運営しているわけである。

25年ほどサーバーを運営してきての所感だが、重要なことを要約すると次のようになる

  • 責任は本当に重い。そのことは決して忘れてはいけない
  • 人生のためを思うならサーバーの運営など始めないほうが良い
  • セキュリティの最新情報を完全に追い続けるのは、ちょっと人間には無理であるように感じる
  • サーバー運営においてやるべきことは本当に多く、腕を上げるほど漏れを発見することになる (25年経った今でもだ!)
  • しかし、熟練してくれば人力でやらないようにシステムを組むようになるから、ある程度年数が経ってくると実際の労力は非常に小さくても問題ないようになってくる
  • 「安全で手間がかからない」を実現するためには、適切な割り切り(限定)が必要。特に可用性。うまく仕上がったシステムは本当に手間がかからない (が、それはそれで不安になる)
  • 「結局、外部サービス使ったほうがいいんじゃないか」と「でもやっぱり自前のサーバー便利だよなぁ」の間で無限に揺れ動くようになる
  • 信用の問題、柔軟性と自由度の問題などで、自分のサーバーを使わないと非常にストレスを感じるようになったりする。応用する場合は特にそう
  • だが基本的には、普通の人がやらないような異常なことをやろうとしているか、もしくは過剰に強いこだわりをもっているかどっちかでないと続ける意味はない

おまけ: 金銭面の話

私のサーバーが今までどれくらいコスト(ドメイン代や証明書代を除く)をかけてきたかというと……

  • 自宅サーバー時代 ―― わからない
  • ServerMan時代 ―― 500円/月 くらい
  • ConoHa時代(前半) ―― 2000円/月 くらい
  • ConoHa時代(後半) ―― 4500円/月 くらい
  • ConoHa+さくら時代 ―― 5000円/月 くらい
  • Vultr時代 ―― 26000円/月 くらい

現在でのVultrでの運営は、体制の手厚さもあるし、そもそもサーバーが増えているのもあるのでコストはだいぶ増えている。 VPS構成は低コストなので、コンパクトに運用するなら今のところだいたい月2000〜4500円くらいの水準になるのではないだろうか。

マネージドデータベースを入れたり、ロードバランサーを入れたりといった構成になってくるとこんなものでは済まなくなってくる。サーバー1台で完結しないのは結構厳しい兆候だ。

特にストレージの不足はサーバー運営で厳しいものの代表。 Vultrはストレージが高めの設定なので、その意味でも負担は増えている。 サーバー運営は「手が負えないことにならないようにする」のが結構重要。無限に増加するものを対象にした運営はやめておいたほうが良い。