カラクリスタ

個人ブログ。人です

開発環境へのサプライチェーンアタックへ備えるためのメモ

あとでやるとか、あとで試すとか、色々情報が貯まってきたのでメモ。

適宜更新するかもしれない。なお環境はNixOSです。

基本的な考え

サプライチェーンアタックの入口としてはネットワークからライブラリかデータを取ってくる部分にあるはずなので、そこを徹底的にふさぐのがいいと思ってます。

手元でやること

できる限り nix を使う

NixOSとNixをパッケージマネージャとして使っている環境限定。

Nixの場合、パッケージやアプリケーションのビルド時にサンドボックスを用いるため、悪意あるパッケージが入っていても、インストール時に実行するタイプのものは悪さがしづらい。

ただし実行時に悪さをするタイプには無力なので、そこは注意する必要がある。

また開発にdockerやpodmanを使っている場合、

を使うことによってNix流のやり方とcompose系のやり方を混在させることが出来るので便利。

fence を使って外部ネットワークを必要とするコマンドを隔離する

今のところ対策したのはNeoVimとGitのみ。なお実際には下記のような関数を使ってラッピングしたコマンドを実用している:

{
  name,
  appname ? name,
  cmd,
  settings ? null,
  extraInit ? "",

  lib,
  fence,
  writeShellScriptBin,
  writeText,
}:
let
  settingsJSON =
    if settings != null then (writeText "${name}-settings.json" (builtins.toJSON settings)) else "";
in
writeShellScriptBin name ''
  set -euo pipefail

  settingsJSON() {
    local cwd
    cwd=$PWD

    if [[ -e "${settingsJSON}" ]]; then
      echo "${settingsJSON}"
      return 0
    fi

    if [[ -e "''${cwd}/.fence/${name}.json" ]]; then
      echo "''${cwd}/.fence/${name}.json"
      return 0
    fi

    if [[ -e "$HOME/.config/fence/${name}.json" ]]; then
      echo "$HOME/.config/fence/${name}.json"
      return 0
    fi

    echo ""
    return 0
  }

  main() {
    local settings=""
    settings="$(settingsJSON)"

    local args=""
    if [[ -e "''${settings}" ]]; then
      args="--settings ''${settings}"
    fi

    exec -a ${appname} ${lib.getExe fence} $args -- ${cmd} "''${@:-}"
  }

  ${extraInit}

  main "''${@:-}"
''

設定はだいたいこんなの:

  • NixOSで動かす場合、下記のような fence.json を設定しないと動かなかった
  • fence を試し出した時点で動かなかっただけな説もある
{
  "allowPty": true,
  "command": { "acceptSharedBinaryCannotRuntimeDeny": ["chroot", "coreutils"] },
  "filesystem": {
    "allowExecute": ["/nix/store/", "/run/current-system/sw/bin/"],
    "allowRead": ["/nix/", "/run/current-system/", "/run/current-system/sw/"],
    "allowWrite": ["/tmp/"]
  }
}
  • git の場合こんな感じの制限を足している
  • 接続先が今のところ github.comgitlab.comcodeberg.org なのでそのように指定

git の場合、starship などで生の git コマンドが必要になるんだけど、その辺りについては export PATH=${pkgs.gitFull}/bin:$PATH を実行するシェルスクリプトで各コマンドを包んでおいた。

{
  "extends": "@base",
  "filesystem": {
    "allowGitConfig": true,
    "allowRead": [
      "~/.config/git/",
      "~/.ssh/id_ed25519.pub"
    ],
    "allowWrite": [".git", "~/.ssh/known_hosts", "~/.ssh/known_hosts.old"],
    "defaultDenyRead": true
  },
  "network": {
    "allowedDomains": [
      "github.com",
      "gitlab.com",
      "codeberg.org",
      "api.github.com",
      "codeload.github.com",
      "lfs.github.com",
      "objects.githubusercontent.com"
    ],
    "allowedUnixSockets": ["/run/user/*/gnupg/S.gpg-agent.ssh"]
  },
  "ssh": {
    "allowAllCommands": true,
    "allowedHosts": ["github.com", "gitlab.com", "codeberg.org"]
  }
}
  • NeoVimの場合、そもそもエディターにネットワーク要るか?という視点でネットワークを落した
  • ただしプラグイン更新の時には必要なので、その辺りは調整している
  • ~/Applications/Programs/textlint/bin/* 以下が入っているのは個人的な事情
    • textlint + nvim でMarkdownを書く際にlintしているので、その都合
    • あとwayland関係も入ってるけど、これはwl-pasteの加減
{
  "extends": "@base",
  "filesystem": {
    "allowExecute": ["~/Applications/Programs/textlint/bin/*"],
    "allowGitConfig": true,
    "allowRead": [
      "~/.config/git/",
      "~/.config/nvim/",
      "~/.local/share/nvim/",
      "~/Applications/Programs/textlint"
    ],
    "allowUnixSockets": ["/run/user/*/wayland-0", "/run/user/*/wayland-0.lock"],
    "allowWrite": [
      ".",
      "~/.cache/nvim/",
      "~/.cache/go-build",
      "~/.local/share/nvim/neo-tree.nvim.log",
      "~/.local/state/nvim/"
    ],
    "defaultDenyRead": true
  }
}
{
  "allowWrite": ["~/.config/nvim"],
  "extends": "@base",
  "filesystem": {
    "allowExecute": ["~/Applications/Programs/textlint/bin/*"],
    "allowGitConfig": true,
    "allowRead": [
      "~/.config/git/",
      "~/.config/nvim/",
      "~/.local/share/nvim/",
      "~/Applications/Programs/textlint"
    ],
    "allowUnixSockets": ["/run/user/*/wayland-0", "/run/user/*/wayland-0.lock"],
    "allowWrite": [
      ".",
      "~/.cache/nvim/",
      "~/.cache/go-build",
      "~/.local/share/nvim/neo-tree.nvim.log",
      "~/.local/state/nvim/"
    ],
    "defaultDenyRead": true
  },
  "network": {
    "allowHosts": [
      "github.com",
      "api.github.com",
      "codeload.github.com",
      "lfs.github.com",
      "objects.githubusercontent.com"
    ]
  }
}

Proton Pass CLIでローカルの .env を駆逐する

Proton Pass CLIはProton Passを有償契約している場合にのみ使える。イメージとしては1Passwordの op コマンドのようなもの。

Proton Pass CLIの場合、環境変数を読み込ませてプログラムを実行するにはこういう感じとなる:

$ pass-cli run --env-file .env.proton -- blogsync pull the.nyarla.jp
BLOGSYNC_USERNAME="pass://Deployment/HatenaBlog/username"
BLOGSYNC_PASSWORD="pass://Deployment/HatenaBlog/apikey"

Proton Pass CLIの場合、1Passwordの op とは違って .env に指定するパスが分かりやすく、上記の例だと Deployment 保存庫にある HatenaBlog のアイテムから usernameapikey のフィールドを取ってる、という感じ。

ただ分かりやすいという反面、万が一アタックされてそれが通った場合、どこに何があるかまる分かりとなるので、そこはデメリットといえばデメリット。

とは言えProton Passは暗号化なども多層に出来るので、フィッシング対策も取ろと思えばちゃんと取れる。2faとかYubikeyなどにも対応してるし。

GitHubなどのリモートでやること

Dependabot周りの設定

Dependabot Alertを有効にする

少なくとも公開リポジトリで外部から依存を取ってくる場合には下記を有効にする:

  • Dependabot alerts
  • Dependabot malware alerts
  • Dependabot security updates

これらの設定へは Settings > Advanced Security でアクセスできる。

DependabotによるDependencies updateを有効にする

これは基本。

ただし素の設定だと色々と困ることもあるので、下記のような変更を加える:

  • 依存のアップデートには必ずcooldownの期間を設ける
    • 緊急のセキュリティアップデート以外はcooldownを入れる
    • 悪意あるパッケージを仕込まれた際、割と数日で見つけられることが多い
    • ただしこれで万全ではなく、仕込まれても数リリース分で気づけない場合もある
  • 依存のグルーピングが出来る場合、下記のような分類でグループを作る
    • runtime - 本番環境用の依存ライブラリをまとめる
    • deploy - デプロイに必要な依存をまとめる
    • lint - 各種lintなどの依存ライブラリをまとめる
    • development - 開発時に必要な依存をまとめる
    • test - テストの実行に必要な依存をまとめる
  • GitHub Actionsの更新については、下記の点へ注意する
    • @{version} ではなく @{commit hash} # vA.B.C とする
      • これは悪意ある @{version} の付け替え対策
      • とは言えリリースがImmutableであれば@{version}でも問題ないかも?
    • Dependabotがdumpしてきても直ぐにはバージョンを上げない
      • 数日は様子を見る
      • ただし緊急のセキュリティアップデートは除く

依存関係のグルーピングを上記のような分類にした理由はこんな感じ:

  • 本番環境で必要なパッケージと開発時のパッケージは分けて更新する
    • 基本中の基本
  • 開発時の依存もテストとLint、その他の依存ライブラリと分ける
    • Lint関係は他のパッケージと競合しやすい(経験上)
  • デプロイ時に必要なコマンドも分けて更新する
    • デプロイコマンドを固定したい場合も考えられるので、ここは分ける

なお上記の分け方はDependabotだけに限らず、そもそも依存をインストールする時にこの分け方をすると、CIの時やデプロイの時に不要なパッケージを入れずに済むと思います。

Dependency Review Action を使う

私の環境では未実施。

公開リポジトリであれば無償で利用できるのと、非公開リポジトリでもライセンスを持ってたら導入できるらしい。

bunを使う

これに効果はあるのか?と聞かれると微妙ですが(たぶん無い)、npmよりセキュアだろう、ということでbunをパッケージマネージャとして使ってます。

Socket.devを使う

ある程度のところまでは無料で使えるので、とりあえずで入れておく。

その他

なんかあったらブコメくれ。

参考にしたページ