Linuxでルートレスコンテナを特権ポートにバインドする方法
Podmanによって導入された主な革新の一つは、ルートレスコンテナを実行できることでした。セキュリティの観点から、これは大きな改善でした。なぜなら、ルートとして実行される潜在的に侵害されたコンテナは、ホストシステムにとってセキュリティ上の脅威となるからです。同様の動作を実現するために、最近のDockerのバージョンでは、ユーザーコンテキストでdockerデーモンを実行することをサポートしています。特権のないコンテナを実行することは、より安全ですが、特権ホストポートにバインドできないという欠点もあります。
このチュートリアルでは、ルートレスのDocker/PodmanコンテナがLinux上の特権ホストポートにバインドできるようにする方法を学びます。
このチュートリアルでは、あなたは学びます:
- 特権ポートを特権でないポートにリダイレクトする方法:ファイアウォールルールを作成するか、redirを使用する
- 特権ポートにバインドできる無特権コンテナを許可する方法:CAP_NET_BIND_SERVICE の権限を設定する
- 特権ポートの範囲を変更する方法
紹介
docker run -p 80:80 httpd:latesterror while calling PortManager.AddPort(): cannot expose privileged port 80ファイアウォールルールを使用して特権ポートを特権でないポートにリダイレクトする
最初の解決策は、使用したい特権ポートからトラフィックを、選択した非特権ポートにリダイレクトし、その後、コンテナが公開するポートにマッピングすることです。
あるポートを別のポートにリダイレクトするには、ファイアウォールルールを作成することができます。実行する必要がある正確なコマンドは、使用しているファイアウォールマネージャによって異なります。パケット操作機能を提供するLinuxカーネルコンポーネントは「netfilter」です。現代のLinuxディストリビューションでは、ユーザースペースからこのコンポーネントと「対話」するために「nftables」フレームワークを使用します。しかし、nftablesルールを直接作成するのは面倒な作業であるため、ほとんどの場合、より高レベルのファイアウォール設定マネージャとやり取りします。そのようなユーティリティの一例がfirewalldです。
firewalldを使用してルールを作成する
firewalldの紹介記事を読んだことがあれば、firewall-cmdユーティリティを通じて管理されていることはすでにご存知でしょう。このユーティリティには、ポートリダイレクションルールを指定するネイティブな方法がありません。それを行うためには、「ダイレクト」ルールに「フォールバック」し、ファイアウォールとより低いレベルで対話する必要があります。「ダイレクト」ルールを設定するために、--directオプションを付けてユーティリティを起動します。ローカルホストのポート80からポート8080にトラフィックをリダイレクトしたいと仮定すると、次のように実行します:
sudo firewall-cmd --direct --add-rule ipv4 nat OUTPUT 0 -p tcp --dport=80 -o lo -j REDIRECT --to-port=8080気づくべき二つのことがあります。一つは、上記のコマンドが「デフォルト」のfirewalldゾーンにルールを追加していることです。ゾーンはfirewalldに関連する抽象概念であり、詳しく知りたい場合は私たちのfirewalldチュートリアルを見てください。二つ目は、このルールは永続的ではなく、再起動やfirewalldサービスの再起動では保持されないことです。これを永続的にするためには、--permanentオプションを使用し、次のコマンドを実行してファイアウォールを再起動する必要があります:
sudo firewall-cmd --reloadiptablesまたはnftユーティリティを使用してルールを作成すること
上記のようにfirewalldダイレクトルールを指定するために使われる構文は、iptablesで使用されるものに似ています。私のようにそれを好む場合、iptables/ip6tablesユーティリティを直接使用してルールを設定することが可能です。現代のLinuxディストリビューションでは、nftablesがiptablesフレームワークに取って代わったため、これらのユーティリティは現在iptables-nftへのリンクに過ぎず、旧構文を引き続き使用することができます:
sudo iptables -t nat -A OUTPUT -o lo -p tcp --dport=80 -j REDIRECT --to-port=8080最後に、nftablesのネイティブ構文を使用して同じリダイレクションを設定するために、nftユーティリティを使用して、次のコマンドを実行できます。
sudo nft 'add rule ip nat OUTPUT oifname "lo" tcp dport 80 counter redirect to :8080'低レベルのiptablesやnftユーティリティで設定されたルールも、持続性はありません。各起動時にそれらを再適用するためには、現在のルールセットをダンプし、自動的に再読み込みするツールを使用する必要があります。具体的な戦略は、使用しているディストリビューションによって異なり、ここではそれを探求しません。
redirを使用してポートをリダイレクトする
ファイアウォールルールを作成する代替手段として、redirのような専用ツールを使用することが考えられます。これはオープンソースであり、主要なLinuxディストリビューションのすべてのデフォルトリポジトリで利用可能です。Fedoraでは、次のコマンドでインストールできます:
sudo dnf install redirDebianおよびDebianベースのディストリビューションでは、代わりに次のように使用できます:
sudo apt install redirRedirは、ローカルポートに接続されてくるTCP接続を特定の<address>:portの組み合わせにリダイレクトすることができます。<address>の部分は任意です:省略した場合、0.0.0.0(メタ)アドレスが使用されます(これは、マシン上のすべてのIPv4アドレスを表します)。ポート80をポート8080にリダイレクトするために、redirを使用して実行するには:
sudo redir :80 127.0.0.1:8080redirで設定されたリダイレクションは持続的ではありません。これらを各システムブート時に適用するためには、-nおよび-sオプションを使用してコマンドを起動するサービスを作成する必要があります。前者はアプリケーションをフォアグラウンドで実行するために使用し、後者はログをsyslogにリダイレクトするために使用します。現在、ほとんどすべてのLinuxディストリビューションは、initシステムおよびサービスマネージャとしてSystemdを使用しています。以下は、ブート時にredirを起動するsystemdサービスユニットの非常に最小限の例です。
[Unit]
Description=Redirect tcp port 80 to 8080 with redir
[Service]
ExecStart=/bin/redir -sn :80 127.0.0.1:8080
[Install]
WantedBy=multi-user.target
ユニットを /etc/systemd/system/redir.service として保存できます。サービスを起動時に開始および有効にするために、次のコマンドを実行します:
sudo systemctl enable --now redir.serviceコンテナに未特権ポートをマッピングする
ポートトラフィックをリダイレクトしたら、ようやくコンテナを実行し、特権のないポートにバインドすることができます:
docker run --name test_httpd -p 8080:80 httpd:latestCAP_NET_BIND_SERVICE機能を設定する
ポートトラフィックをリダイレクトするためのDocker専用の代替手段は、/usr/bin/rootlesskitバイナリにCAP_NET_BIND_SERVICE権限を設定することです。権限はLinux上のプロセスに割り当てることができる特別な権限です。この場合、私たちが興味を持っているのはCAP_NET_BIND_SERVICEであり、これは特権ポートにソケットをバインドすることを許可します。CAP_NET_BIND_SERVICE権限をrootlesskitバイナリに割り当てるために、以下のコマンドを実行します:
sudo setcap cap_net_bind_service=ep /usr/bin/rootlesskitその機能を削除するには、代わりに:
sudo setcap -r /usr/bin/rootlesskit特権のないポートの範囲を変更する
ここで検討する最後の戦略は、特権ポートの範囲を変更することです。システムが特権と見なすポートを確認するには、/proc/sys/net/ipv4/ip_unprivileged_port_startファイルを読むだけで済みます。このファイルには、システム内の最初の特権でないポートが含まれています(デフォルトは1024です)。前の例に従って、システムがポート80を特権でないポートと見なすようにするためには、ファイルに適切な値を書き込む必要があります:
echo 80 | sudo tee /proc/sys/net/ipv4/ip_unprivileged_port_startこの変更を永続的にするために、/etc/sysctl.d/ ディレクトリにある設定ファイルに次の行を追加する必要があります:
echo net.ipv4.ip_unprivileged_port_start = 80 | sudo tee /etc/sysctl.d/90-unprivileged_port_start.confこの戦略を採用することによって、システムはすべてのポート >= 80 を特権のないものとして扱うことに注意してください!
結論
このチュートリアルでは、特権のないコンテナが特権ポートにバインドできるようにするためのいくつかの方法を見てきました。特権ポートから非特権ポートへのトラフィックのリダイレクト方法、CAP_NET_BIND_SERVICE機能の設定方法、最後に特権ポートの範囲を変更する方法を見ました。
