posts

自動デプロイ用の秘密鍵をGitHubに上げてしまったので、鍵を作り直した話

サイトのフォルダを整理していたら、GitHub Actionsで使っているSSHの秘密鍵が、サイトのフォルダの中に入っていました。しかも、いつもの git add . でいっしょにコミットされ、GitHubにも上がっていました。

先に結論です。

  • 秘密鍵は、サーバーに入るための合鍵そのもの。上げてしまったら「ファイルを消す」では足りない
  • やるべきことは、新しい鍵を作って、古い鍵をサーバー側で使えなくすること
  • 順番を間違えると自動デプロイが止まるので、「新しい鍵で動くのを確かめてから、古い鍵を消す」

何が起きていたか

番外編2では、git push で自動公開するために、GitHub Actions専用の鍵を作りました。

  • 公開鍵(.pub が付いている方) … サーバーに登録する
  • 秘密鍵(.pub が付いていない方) … GitHubの「Secrets」に登録する

秘密鍵は、本当はGitHubのSecretsに貼るだけで、ファイルとしてサイトのフォルダに置く必要はありません。ところが、フォルダを見直してみると、github_actions_key と github_actions_key.pub がサイトのフォルダの中にありました。.gitignore(GitHubに上げないものの一覧)にも書いていなかったので、git add . のたびに対象になり、そのままGitHubに上がっていたのです。

ssh-keygen の -f のあとに、github_actions_key のようにファイル名だけを書くと、鍵は今いるフォルダに作られます。サイトのフォルダでPowerShellを開いて作業していると、そこに鍵ができてしまいます。

なぜ危ないのか

この秘密鍵の公開鍵は、サーバーの ~/.ssh/authorized_keys(入ってよい鍵の一覧)に登録されています。つまり、この秘密鍵を持っている人は、誰でもサーバーに管理者(root)として入れる状態です。

しかも、GitHubからファイルを消しても、過去のコミット(履歴)には残ります。あとで分かったのですが、このときのリポジトリは公開(Public)になっていて、履歴は誰でも見られる状態でした(下の「後日談」に書きます)。

だから対策は「ファイルを消す」ではなく、鍵そのものを使えなくすることです。古い鍵をサーバーの一覧から外してしまえば、履歴に残っている鍵は、もうどこの扉も開けない、ただの文字列になります。

やったこと(順番が大事)

1. 新しい鍵を、サイトのフォルダの外に作る

手元のPowerShellで打ちます。パスフレーズは聞かれても何も入れずにEnterです(Actionsで自動で使うため)。

ssh-keygen -t ed25519 -f $env:USERPROFILE\.ssh\gha_deploy

$env:USERPROFILE\.ssh\ は、ユーザーのフォルダの中の .ssh フォルダです。サイトのフォルダの外なので、git add . の対象になりません。

2. 新しい公開鍵をサーバーに登録する

type $env:USERPROFILE\.ssh\gha_deploy.pub | ssh root@xxx.xxx.xxx.xxx "cat >> ~/.ssh/authorized_keys"

この時点では、古い鍵も新しい鍵も両方使える状態です。

3. GitHubのSecretsを新しい秘密鍵に差し替える

  1. 新しい秘密鍵の中身を表示する
Get-Content $env:USERPROFILE\.ssh\gha_deploy
  1. -----BEGIN から -----END ...----- までを全部コピーする
  2. GitHubのリポジトリ →「Settings」→「Secrets and variables」→「Actions」
  3. SSH_PRIVATE_KEY の編集を開き、入力欄に貼って保存する

表示した秘密鍵の中身は、Secretの入力欄以外には絶対に貼らないでください(チャットやメモにも)。

4. 新しい鍵でActionsが動くことを確かめる

何か小さな変更をして git push し、GitHubの「Actions」タブで緑のチェックが付くのを確認します。これで、新しい鍵でサーバーに入れることが確かめられます。

古い鍵を消すのは、ここで緑になってからです。先に古い鍵を消してしまうと、Secretsの差し替えに失敗していたとき、自動デプロイが止まります。

5. サーバーから古い鍵を消す

サーバーに入って、入ってよい鍵の一覧を開きます。

nano ~/.ssh/authorized_keys

1行が1つの鍵です。各行の最後には、鍵を作ったPCの名前などのコメントが付いています。手元の古い公開鍵(github_actions_key.pub)をメモ帳ではなくVSCodeで開き、中身が同じ行を探して、その行だけ消して保存します。

自分のPCから入るための鍵(第5回で作った id_ed25519 の行)は消さないように注意してください。消すと、自分もパスワードなしでは入れなくなります。

消したあとにもう一度Actionsを動かして、緑になることを確かめておくと安心です。

6. フォルダから鍵を消し、二度と上がらないようにする

サイトのフォルダにあった古い鍵のファイル2つを削除し、.gitignore に次の行を足しました。

github_actions_key*
gha_deploy*

* は「この後ろに何が付いていても」という意味で、.pub 付きも含めて対象になります。最後に、いつもどおり git add . → git commit → git push で反映しました。

履歴からも消すべき?

過去のコミットから鍵のファイルを消す方法(履歴の書き換え)もありますが、今回はやっていません。

  • 古い鍵は、手順5でサーバーから外したので、もう何にも使えない
  • 履歴の書き換えは手順が多く、失敗するとリポジトリを壊すおそれがある

鍵を無効にすることが本当の対策で、履歴の掃除はおまけ、という考え方です。逆に言うと、鍵を無効にしないまま履歴だけきれいにしても、安全にはなりません。

後日談:リポジトリが「公開」になっていた

鍵を作り直してから数日後、サイト全体のセキュリティを点検していて、もう1つ大事なことに気づきました。このブログのGitHubリポジトリが、Public(誰でも見られる)になっていたのです。

番外編2では「Private」を選ぶように書いていたのに、自分のリポジトリは公開のままでした。つまり、履歴に残っていた古い秘密鍵は、本当に誰でも取り出せる状態だったことになります。

ここで効いたのが、手順5で古い鍵をサーバーの一覧から外していたことです。念のため、本当に外れているかを「指紋(フィンガープリント)」で確かめました。指紋は、鍵の中身を短く要約した文字列で、鍵ごとに違います。

古い鍵の指紋は、GitHubの履歴に残っていた公開鍵(.pub)から出せます。サイトのフォルダでPowerShellを開いて打ちます。

git log --all --oneline -- github_actions_key.pub
git show (上で出たコミットの番号):github_actions_key.pub > old_key.pub
ssh-keygen -lf old_key.pub

次に、サーバーに登録されている鍵の指紋を出します(サーバーで打ちます)。

ssh-keygen -lf ~/.ssh/authorized_keys | awk '{print NR": "$2}'

古い鍵の指紋は、サーバーの一覧のどれとも一致しませんでした。漏れた鍵では、もうログインできません。確かめ終わったら、old_key.pub は消しておきます。

そのうえで、リポジトリを非公開に切り替えました。GitHubでリポジトリを開き、「Settings」→ 一番下の「Danger Zone」→「Change repository visibility」→「Make private」です。非公開にしても、GitHub Actionsでの自動公開はそのまま動きます。

この点検の続きで、止めたはずのパスワードログインが有効のままだったことや、使っていない鍵が残っていたことも見つかりました。その話は VPSのSSHを点検したら、パスワードログインが有効のままで、使っていない鍵も残っていた にまとめています。

自分のリポジトリをチェックする方法

GitHubに上がっているファイルの一覧から、鍵らしい名前を探せます。サイトのフォルダでPowerShellを開いて打ちます。

git ls-files | Select-String "key|pem|id_"

何も出てこなければ大丈夫です。何か出てきたら、それが秘密鍵ではないか確認してください。

学んだこと

起きたこと・気をつけたこと 理由 今のルール
秘密鍵をGitHubに上げていた サイトのフォルダの中で鍵を作り、.gitignore にも入れていなかった 鍵は必ず $env:USERPROFILE\.ssh\ に作る
ファイルを消せば大丈夫、ではなかった 履歴には残るし、鍵自体はまだ使える 漏れた鍵は作り直して、サーバー側で無効にする
鍵を入れ替える順番 古い鍵を先に消すと、自動デプロイが止まる 新しい鍵で動くのを確かめてから、古い鍵を消す

「鍵を漏らした」と気づいたときは焦りましたが、やることを順番に並べると、思ったより落ち着いて対応できました。技術ブログを書いていると、スクショやコマンドの結果を人に見せる場面が多いので、見せてはいけない情報を一度整理しておくのがおすすめです。→ 第2回 機材編 の「記事やスクショに載せてはいけない情報」

あわせて読みたい