DockerfileのUSER設定でセキュリティを強化する方法—rootと非rootの違い
Dockerコンテナを使うとき、「このアプリはroot権限で動かしていいのかな?」と悩んだことはありませんか?
コンテナはとても便利ですが、セキュリティを考えると「rootユーザー」と「非rootユーザー」で動かす場合の違いを知っておく必要があります。今回は、具体的なサンプルコードと実行例を交えながら、それぞれの特徴を見ていきます。
rootユーザーと非rootユーザーの違い
rootユーザーの特徴
(1) 何でもできる特権ユーザー
rootユーザーはコンテナ内のすべての操作が許可されています。ファイルの編集、パッケージのインストール、システム設定の変更など、自由に実行できます。
(2) セキュリティリスクが高い
もしアプリケーションに脆弱性があった場合、攻撃者もroot権限を手にしてしまう可能性があります。たとえば、「鍵のかかっていない金庫」のように、中身を取り放題の状態になりかねません。
非rootユーザーの特徴
(1) アクセス権限が限定されている
非rootユーザーは与えられた範囲内でしか操作できません。たとえば、家の中では自分の部屋しか開けられない子供のように、他の場所には手を出せない仕組みです。
(2) 最小権限で安全性を確保
アプリケーションが乗っ取られても、アクセスできる範囲が狭いため被害を最小限に抑えられます。
サンプルコードで違いを確認
Go言語で簡単なプログラムを作り、rootユーザーと非rootユーザーの違いを試してみましょう。
サンプルコード (main.go)
package main
import (
"fmt"
"io/ioutil"
"os"
"os/user"
)
func main() {
// 現在のユーザー情報を取得
u, err := user.Current()
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Running as user: %s (UID: %s)\n", u.Username, u.Uid)
// /rootディレクトリへのアクセス
fmt.Println("Checking access to /root directory...")
files, err := ioutil.ReadDir("/root")
if err != nil {
fmt.Printf("Access denied: %v\n", err)
} else {
fmt.Println("Files in /root:")
for _, file := range files {
fmt.Println(file.Name())
}
}
// /tmpへの書き込みテスト
fmt.Println("Testing write access to /tmp...")
err = ioutil.WriteFile("/tmp/testfile.txt", []byte("test data"), 0644)
if err != nil {
fmt.Printf("Write failed: %v\n", err)
} else {
fmt.Println("File written successfully.")
}
}
Dockerfile (rootユーザー)
FROM golang:1.18-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o main .
FROM alpine:3.15
WORKDIR /app
COPY --from=builder /app/main .
CMD ["./main"]
実行結果 (rootユーザー)
Running as user: root (UID: 0)
Checking access to /root directory...
Files in /root:
important_file.txt
secret_key.pem
Testing write access to /tmp...
File written successfully.
結果のポイント:
/rootディレクトリの中身が見えてしまう。
/tmpに自由にファイルが作れる。
攻撃者にroot権限を奪われると、システム全体に影響を与えるリスクがあります。
Dockerfile (非rootユーザー)
FROM golang:1.18-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o main .
FROM alpine:3.15
# ユーザーを追加
RUN adduser -D appuser
WORKDIR /app
COPY --from=builder /app/main .
RUN chown -R appuser:appuser /app
# 非rootユーザーに切り替え
USER appuser
CMD ["./main"]
実行結果 (非rootユーザー)
Running as user: appuser (UID: 1000)
Checking access to /root directory...
Access denied: open /root: permission denied
Testing write access to /tmp...
Write failed: open /tmp/testfile.txt: permission denied
結果のポイント:
/rootディレクトリはアクセスできない。
/tmpへの書き込みも制限される。
非rootユーザーでは、制約があることで攻撃による被害が広がりにくくなっています。
まとめ
rootユーザーは自由度が高い反面、脆弱性を突かれた場合の被害も大きくなります。一方で、非rootユーザーは権限を限定することで、安全性を高めることができます。
ベストプラクティス
(1) ビルドやインストール時はrootユーザーで作業。
(2) 本番環境では非rootユーザーに切り替える。
たとえば、大工さんが家を建てるときには自由に工具を使えるけれど、完成した後は鍵をかけて他人に触らせないようにするのと似ています…?
ここら辺は、マルチステージビルドを使うと、ビルド環境と運用環境を分けることができます。これにより、ビルド中はrootユーザーで自由に作業し、本番環境では非rootユーザーに切り替える構成が簡単に作れます。