DockerFileセキュリティベストプラクティス


コンテナのセキュリティは広い問題スペースであり、リスクを軽減するために収穫することができる多くの低い絞首刑の果物があります.良い出発点は、dockerfileを書くとき、何らかの規則に従うことです.
私は一般的なセキュリティ問題のリストをコンパイルしてどのようにそれらを避けるために.すべての問題についても私はOpen Policy Agent ( OPA )ルールを使用してDockerFilesを静的に解析しますconftest . これ以上の左シフトすることはできません!
あなたは.rego ルールセットthis repository . フィードバックと貢献に感謝します.

環境変数に秘密を保管しないでください
秘密の配布は毛深い問題であり、それは間違ってそれを行うのは簡単です.コンテナ化されたアプリケーションについては、ボリュームをマウントすることによってファイルシステムからそれらを表面化することができます.
使用ENV DockerFileが通常アプリケーションで配布されるので、秘密を保存することは悪い習慣です.したがって、コードでハードコーディング秘密からの違いがありません.
検出方法
secrets_env = [
    "passwd",
    "password",
    "pass",
 # "pwd", can't use this one   
    "secret",
    "key",
    "access",
    "api_key",
    "apikey",
    "token",
    "tkn"
]

deny[msg] {    
    input[i].Cmd == "env"
    val := input[i].Value
    contains(lower(val[_]), secrets_env[_])
    msg = sprintf("Line %d: Potential secret in ENV key found: %s", [i, val])
}


信頼できるベースイメージのみを使用する
コンテナ化されたアプリケーションのためのサプライチェーン攻撃も、コンテナ自体を構築するのに用いられる層の階層から来ます.
主な犯人は明らかにベースイメージが使用されます.信頼されていないベースの画像は高いリスクであり、可能な限り避けてください.
Dockerは、Aを提供しますset of official base images ほとんどのオペレーティングシステムやアプリケーションを使用します.それらを使用することによって、私たちはいくつかの種類の共有責任をレッカー自身とレバレッジすることによって妥協のリスクを最小にします.
検出方法
deny[msg] {
    input[i].Cmd == "from"
    val := split(input[i].Value[0], "/")
    count(val) > 1
    msg = sprintf("Line %d: use a trusted base image", [i])
}

この規則はDockerHookの公式イメージに向けられている.私が名前空間の不在を見つけるだけであるので、それは非常にダムです.
信頼の定義は文脈によって異なります.

ベースイメージの'最新'タグを使用しないでください
あなたのベースイメージのバージョンをピン止めすることは、あなたが構築しているコンテナの予測可能性に関してあなたに心の若干の平和を与えます.
あなたが最新に頼るならば、あなたは黙って最高の最悪のケースであなたのアプリケーション信頼性に影響を及ぼすかもしれない更新パッケージを継承するかもしれません.
検出方法
deny[msg] {
    input[i].Cmd == "from"
    val := split(input[i].Value[0], ":")
    contains(lower(val[1]), "latest"])
    msg = sprintf("Line %d: do not use 'latest' tag for base images", [i])
}


避けてカールカール
インターネットからものを引いて、それをシェルに配管することは、それがそうでありえるのと同じくらい悪いです.残念ながら、ソフトウェアのインストールを合理化する広範囲の解決策です.
wget https://cloudberry.engineering/absolutely-trustworthy.sh | sh

リスクはサプライチェーン攻撃のために同じ枠組みであり、それは信頼を沸騰させる.もし本当にbashをカールさせなければならないなら、
  • 信頼できるソースを使用する
  • セキュア接続を使用する
  • あなたがダウンロードするものの真正性と完全性を確かめる
  • 検出方法
    deny[msg] {
        input[i].Cmd == "run"
        val := concat(" ", input[i].Value)
        matches := regex.find_n("(curl|wget)[^|^>]*[|>]", lower(val), -1)
        count(matches) > 0
        msg = sprintf("Line %d: Avoid curl bashing", [i])
    }
    
    

    システムパッケージをアップグレードしないでください
    これは少しのストレッチかもしれませんが、推論は以下の通りです:あなたがそうするならば、あなたはあなたのソフトウェア依存のバージョンをピンでとめたいですapt-get upgrade あなたが効果的にすべての最新バージョンにアップグレードされます.
    アップグレードを行う場合は、使用しているlatest ベースイメージのタグは、依存関係ツリーの予測不可能性を増幅します.
    あなたがしたいことは、ベースイメージのバージョンをピンするだけですapt/apk update .
    検出方法
    upgrade_commands = [
        "apk upgrade",
        "apt-get upgrade",
        "dist-upgrade",
    ]
    
    deny[msg] {
        input[i].Cmd == "run"
        val := concat(" ", input[i].Value)
        contains(val, upgrade_commands[_])
        msg = sprintf(“Line: %d: Do not upgrade your system packages", [i])
    }
    
    

    可能ならばアドインを使わない
    つの小さな特徴ADD コマンドは、リモートURLを指すことができ、ビルド時にコンテンツを取得できます.
    ADD https://cloudberry.engineering/absolutely-trust-me.tar.gz
    
    
    皮肉なことに、公式ドキュメントは代わりにカールバッシングを使用することを提案します.
    セキュリティの観点から、同じアドバイスが適用されます:ない.前に必要なコンテンツを取得し、それを確認してからCOPY . しかし、あなたが本当に持っているならば、安全な接続の上に信頼された源を使用してください.
    注意:動的にビルドしたシステムで、dockerfileを動的に生成します.ADD 効果的にシンクを求めている.
    検出方法
    deny[msg] {
        input[i].Cmd == "add"
        msg = sprintf("Line %d: Use COPY instead of ADD", [i])
    }
    
    

    根を出さない
    コンテナのルートは、ホストマシンと同じルートですが、Dockerデーモンの設定によって制限されます.たとえ制限があっても、俳優が容器から抜け出せば、ホストへのフルアクセスを得る方法を見つけることができるでしょう.
    もちろん、これは理想的ではありません、そして、あなたの脅威モデルはルートとして走ることによってもたらされる危険を無視することができません.
    このように常にユーザを指定するのがベストです.
    USER hopefullynotroot
    
    
    dockerfileにユーザを明示的に設定することは防御の1つの層であり、全体を解決しないことに注意してくださいrunning as root problem .
    代わりに、深さのアプローチで防御を採用し、スタック全体をさらに軽減します.厳密にはDockerデーモンを設定するか、ルートレスコンテナソリューションを使用して、ランタイム設定を制限します--privileged 可能ならば等)など.
    検出方法
    any_user {
        input[i].Cmd == "user"
     }
    
    deny[msg] {
        not any_user
        msg = "Do not run as root, use USER instead"
    }
    
    

    しないでください
    としてdo not root , あなたはいずれもsudoしない.
    ユーザーとして実行しても、ユーザーがsudoers クラブ.
    deny[msg] {
        input[i].Cmd == "run"
        val := concat(" ", input[i].Value)
        contains(lower(val), "sudo")
        msg = sprintf("Line %d: Do not use 'sudo' command", [i])
    }
    
    

    謝辞
    この作品は、触発されているとの繰り返しですprior art からMadhu Akula .
    この投稿は面白かったですか.あなたの考えを聞きたいです.hello AT cloudberry.engineeringクラウドセキュリティについて書きましたblog , を購読することができますRSS feed またはnewsletter .