去年の暮れに
GitHub公式ブログ(Token authentication requirements for Git operations) のアナウンスで、今年2021年8月13日以降はセキュリティーの面から一切のパスワードを使った認証によるgit操作を廃止するそうです。
よって、普段GitHub上で開発作業を行っているユーザーは
Https接続の場合にはトークン認証 か、 SSH接続の場合にはキー認証 のどちらかを使ってGitHubにアクセスするということになります。
パスワードを用いた一切のGitHub上のレポジトリアクセスができなくなるので、一部のパスワードを直接送信していたgit操作のコマンドやデスクトップアプリなどに影響があるようです。
これまで日常的にhttps/ssh接続で作業してこられた方には影響はないものと思います。
とはいえ、GitHubでも2段階ファクター認証などをメールなどで催促されるなど、昨今ではセキュリティー強化の流れもあります。
今回の記事では、弊社のアカウントでSSH接続させて利用する方法を試したときのメモとして残しておこうと思います。

合同会社タコスキングダム|蛸壺の技術ブログ
Debian LinuxとAlpine DockerコンテナでもGh(Github CLI)コマンドを導入する

GitHub CLIをDebian LinuxでもLinuxbrewから一発導入してから、ghコマンドの簡単な動作確認を試してみます。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

一般OS編〜GitHubによるSSH接続



では早速SSH接続からGitHubアカウントを利用するための手順を見ていきます。

ed25519暗号のSSHキーを生成する



sshの暗号方式で、もっともメジャーなのが
rsa の4096bit設定で、他の技術サイトもよく見かける設定ですが、GitHubではもっと堅牢な ed25519 という方式が利用できるようです。
ということで今回はed25519方式で公開鍵と秘密鍵のペアを生成してみます。
生成コマンドを実行すると、
鍵の名前 > パスフレーズ > パスフレーズ(確認) の順に質問を3つ入力するのです。
何も入力せずにEnterを押してもデフォルト値の
id_ed25519.pub(公開鍵)id_ed25519(秘密鍵) が作成されます。
鍵が複数必要な場合には、鍵の名前を与えずに生成の操作を繰り返すと、直前に生成していたid_ed25519という名前の秘密鍵が上書きされてしましますので、適当な名前を付けて生成する必要があります。

            #👇-Cオプションでコメントが付けられます
$ ssh-keygen -t ed25519 -C 'tacoskingdom'
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/*******/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/*******/.ssh/id_ed25519.
Your public key has been saved in /home/*******/.ssh/id_ed25519.pub.
The key fingerprint is:
SHA256:******************************************* tacoskingdom
The key's randomart image is:
+--[ED25519 256]--+
#以下略

        

ここでの
「パスフレーズ」 とは、gitコマンドでSSH秘密キーにアクセスするたびに入力する必要があるパスワードです。
一人で使うレポジトリのために、わざわざgitコマンド一回ごとにパスワードを聞かれるのも面倒と感じる場合には、パスフレーズを空にしておくのも良いと思います。

GitHubに公開鍵を設定する



さて、生成した公開鍵と秘密鍵のペアでそのままでもssh接続することができます。
"pub"と付いている方の公開鍵(
id_ed25519.pub )がGitHubのサイトに設定する用のファイルになります。
他方、"pub"の付いていない秘密鍵の方を自分や秘密を保持させたいチームの内部で利用する流れになります。
当然GitHubにアクセスするときに、この秘密鍵をもっていないとアクセス出来ませんので、失くさないように管理・運用方法なども事前に取り決めを作っておくことも必要です。
ではこの公開鍵(
id_ed25519.pub )をGitHubに手動で設定します。
さきほど生成した公開鍵の中身をcatで確認します。

            $ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 A***********************************************************h tacoskingdom

        

なんだかズラズラーっと表示されている中程の文字が公開鍵の中身だと思われます。
これを以下の図のように、Gitgubページのアイコンから
[Settings] > [SSH and GPG keys] > [New SSH key] > Key値の設定 > [Add SSH key] の順で設定します。

合同会社タコスキングダム|蛸壺の技術ブログ


これで
[Add SSH key] のボタンを押すと公開鍵が設定されます。
ではこの公開鍵
id_ed25519.pub という鍵穴をGitHubに設定したので、手持ちの秘密鍵 id_ed25519 でロック解除できるか以下のコマンドでトライしてみます。

            $ ssh -T git@github.com
The authenticity of host 'github.com (xx.xxxx.xx.xx)' can't be established.
RSA key fingerprint is SHA256:n***********************************8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,xx.xxxx.xx.xx' (RSA) to the list of known hosts.
Hi tacoskingdom! You've successfully authenticated, but GitHub does not provide shell access.

        

ということで、どうやらGitHub側にSSH接続が成功したようです。

複数のSSHキーを管理するときの注意点



なお、複数のSSHキーがローカルに存在していた場合、システムはデフォルトでどの鍵を使えばいいのか分からないので、使う鍵を教えてあげないと利用できません。
ということで
~/.ssh/config という設定ファイルを作成し、特定のホストにどの鍵を割り当てればいいのかを記述してあげる必要があります。

            $ nano ~/.ssh/config
#👇ホストがgithub.comの場合
Host github.com
  HostName github.com
  User git
  IdentityFile "~/.ssh/id_ed25519"

        

複数のGitHubアカウントでSSHキーを割り当てて管理する場合の設定の書き方は
こちらのサイト で紹介されておりますのでご参考ください。


簡単なgit操作



後はもうここまで設定できたらいつものgit操作でレポジトリにアクセス可能です。
あとは何か新しいレポジトリのプロジェクトを作成したときに現れる以下のような手順をヒントに作業を進めていくと良いでしょう。

合同会社タコスキングダム|蛸壺の技術ブログ

合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

Dockerコンテナ編〜GitHubによるSSH接続



先程の内容までは、Gitとsshコマンドの使えるOS環境であればおそらく同じように動作するSSH接続方式のGitHub認証のやり方を解説しました。
ここからは、Dockerコンテナの内部からインタラクティブモードなどでソースコード開発を進めていく際に便利な設定を構築する手法を紹介します。
例えば開発用のDockerコンテナ内でのコーディング作業を終えたら、一旦ホストOSに戻り、その差分をコミットし、プルリクエストを送る、というように逐一Dockerコンテナ入ったり出たりするスタイルの開発方法をやっていると、たまに切り替えるのが面倒になったりします。
ホストで使ってるSSH認証キーをそのままDockerコンテナでも利用できるようにすることで、Dockerコンテナ内からでもGitHubレポジトリにアクセス出来るようなります。
ホストからDockerコンテナーへのSSH認証キーの受け渡しは以下の図のように主に2通り方法があります。

合同会社タコスキングダム|蛸壺の技術ブログ



このやり方には、複数のSSHキーを所有しているホストOSのGit環境であっても、Dockerコンテナに渡す鍵をどれか一つに限定することで、コンテナとレポジトリを一対一対応にさせることが出来ます。
これはGitHubアカウントを複数持っている場合、ホストOSはSSH鍵の保有と管理に注力して、開発時にはDockerコンテナを使い分けるだけでGitHub上のレポジトリを適切に割り当てることができるので、セキュリティを扱っている人間としては少し気持ちが楽になります。
この記事を見て頂いている方の中にはいらっしゃらないと思いますが、開発中のプロジェクト内にSSH認証キーごとファイルに含めてしまって、そのまま気づかすにGitHubレポジトリへ上げてしまった...という恐ろしい話もあります。
やってしまう人のほとんどが入社間もなくSSHやGitの経験も浅い開発者の方がほとんどかと思いますが、それはプロジェクトの開発責任者が予め教育すべきだったミスによるものです。
SSH認証キーがレポジトリ上に晒しておくのは、家の鍵を玄関のドアに挿したままにしておく位危険ですので、チームによって開発を進めていく際には必ず管理方法を見直しておきましょう。
ここでは
Alpine LinuxのDockerイメージ をベースにして利用して説明していきます。

DockerfileのCOPY(ADD)でSSH認証キーを埋め込む



まず一つ目のパターンのDockerfileにCOPY(ADD)で秘密鍵を埋め込むやり方を検討します。
Dockerfileを作成する前にまずCOPYとADDのホスト側へのアクセス権限から説明します。
結論からいうと、
COPYもしくはADDで指定されたホスト側のファイルはDockerfileから見たの相対パスで、それより上層のディレクトリにあるリソースにはアクセス出来ません。
            COPY [ホスト側Dockerfileのあるディレクトリからの相対パス] [コンテナ側の絶対パス]

        


これはセキュリティ上の理由ですので、ホスト側のファイルを自由に読み込めないのはちょっと面倒ですが、Dockerfileと同一階層か、それより下層の見える位置に秘密鍵を持ってこないとCOPY出来ません。
ということで現在のプロジェクトには以下のように.sshファイルをハードコピーして利用します。

            .
├── Dockerfile
├── docker-compose.yml
├── .gitignore
└── .ssh
  └── id_ed25519

        

この場合、秘密鍵をうっかりGitHub側へ上げてしまわないように
.gitignore の役割が非常に重要になります。

            #👇追加
/.ssh

        

とするとSSH認証キーはレポジトリに含まれなくなります。
では、必要最低限のalpineコンテナを以下のような
Dockerfile で作ります。

            FROM alpine:3.18

RUN apk update && apk upgrade && \
    apk add --no-cache bash git openssh

#👇Dockerコンテナの作業ルートディレクトリ(今回は'/usr/src/app/')へ秘密鍵をコピー
WORKDIR /usr/src/app/
COPY .ssh/ /usr/src/app/.ssh

CMD ["bash"]

        

通例として、SSHクライアント固有のSSH認証情報は、このユーザーのホームディレクトリ(
~/.ssh/ )内に置かれます。
Dockerコマンドのオプションを手で打つのは面倒ですので、以下のようにdocker-composeを利用します。

docker-compose.yml は以下のような設定のものを利用します。

            version: '3.9'

services:
  app:
    image: my-app-dev:alpine3.18
    build: .
    container_name: my-app-dev
    working_dir: "/usr/src/app"
    tty: true

        

このDockerイメージをローカルビルドし、コンテナを立ち上げ、端末からインタラクティブモードにアタッチしてみます。

            $ docker-compose build
$ docker-compose up -d
$ docker-compose exec app bash

#...インタラクティブモードでコンテナに入る

$ ls -la | grep .ssh
drwxr-xr-x    2 1000     1000          4096 Jan 26 01:48 .ssh
$ ls -la .ssh
total 12
-rw-------    1 1000     1000           399 Jan 26 01:48 id_ed25519

        

でもこれだと、ただ秘密鍵だけが作業ディレクトリに入っているだけで、プロジェクト開発に必要な他の具材がコンテナに入っていません。
そこで
docker-compose.yml を修正し、複数のVolumeを使ってssh秘密鍵だけ読み取り専用で、他リソースファイルは通常の相互読み書き可能なボリュームとして分離し、再度コンテナへマウントします。

            version: '3.9'

volumes:
  #別のダミーボリュームを準備
  ssh_key:

services:
  app:
    image: my-app-dev:alpine3.18
    build: .
    container_name: my-app-dev
    volumes:
      - ./:/usr/src/app
      #👇秘密鍵だけダミーへマウント
      - ssh_key:/usr/src/app/.ssh
    working_dir: "/usr/src/app"
    tty: true

        

これで見た目は代わりませんが、一応ホストとコンテナとのSSH認証キーとの独立性を保ちながら、他のリソースはコンテナへマウントすることが出来るようになります。
なお、Dockerコンテナを起動する際にvolumeオプションから、
.ssh フォルダを共有するやり方も考えられなくはないですが、volume相当でファイルを共有している場合、ホストとDockerクライアントの相互書き込みをしているため、ふとしたことで認証キーに別のデータが書き込まれたり消失したりする恐れがあります。
とは言え、秘密鍵への意図しない書き込みで壊れるのをそんなに気にしても仕方ない、とさほど気にする必要もないなら、端から
Dockerfile のCOPYなんぞ使う必要もなく全てボリュームマウントで済みます。
やり方としては以下のように
Dockerfiledocker-compose.yml を変更します。

            FROM alpine:3.18

RUN apk update && apk upgrade && \
    apk add --no-cache bash git openssh

CMD ["bash"]

        
            version: '3.9'

services:
  app:
    image: my-app-dev:alpine3.18
    build: .
    container_name: my-app-dev
    volumes:
      - ./:/usr/src/app
    working_dir: "/usr/src/app"
    tty: true

        

...
.ssh ファルダを特別扱いしなくて良い分、スッキリしてこちらのほうが良い気がしますが、DockerでのCOPY(ADD)とVOLUMEの違いは良く理解した上でどちらを利用するか決めるようにしましょう。
とにかく秘密鍵をコンテナ側に埋め込みましたので、この状態でまずGitHubとの接続状態を確認しましょう。
コンテナが最初に立ち上がる場合には、初回はssh-agentが立ち上がっていませんので、evalコマンドからプロセスID指定で立ち上げる必要があります。
※面倒なら起動デーモン用の設定ファイルを仕込むか、
.bashrc ファイルを作るかで対応すると良いかも知れません。

            $ eval "$(ssh-agent)"
Agent pid 16
$ ssh-add .ssh/id_ed25519
Identity added: .ssh/id_ed25519 (tacoskingdom)
$ ssh -T git@github.com
The authenticity of host 'github.com (xx.xxxx.xx.xx)' can't be established.
RSA key fingerprint is SHA256:n***********************************8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'github.com,xx.xxxx.xx.xx' (RSA) to the list of known hosts.
Hi tacoskingdom! You've successfully authenticated, but GitHub does not provide shell access.

        

...素晴らしい。
これでDockerコンテナからでもGitHubへのログイン認証が完了しました。
Dockerfileで秘密鍵をCOPYするだけですが、簡単にGitHubが操作できるようになります。
ただこのやり方での注意はビルド済みのDockerイメージにSSH認証キーが埋め込まれるため、Publicに向けてDockerHubで公開することは出来ません。
もしもDockerイメージごと開発環境として公開したい場合には、次に説明する
docker cp を使って逐一起動中のDockerコンテナへSSH認証キーを注入するやり方を推奨します。

docker cpサブコマンドでSSH認証キーをコンテナに送る



こちらも具体的にやってみましょう。
先ほどの
Dockerfile から秘密鍵をCOPYしていた部分をコメントアウトか削除したイメージを使います。

            FROM alpine:3.18

RUN apk update && apk upgrade && \
    apk add --no-cache bash git openssh

CMD ["bash"]

        

また
docker-compose.yml.ssh フォルダを空にしておくようにします。

volumes プロパティで、 [マウント元のホスト側のフォルダ]: の部分を省略したら、そこにマウントしたコンテナのフォルダの中身もゴッソリと消失する cp /dev/null 的な使い方も利用できます。

            version: '3.9'

services:
  app:
    image: my-app-dev:alpine3.18
    build: .
    container_name: my-app-dev
    volumes:
      - ./:/usr/src/app
      #exclude files in volume
      - /usr/src/app/.ssh
    working_dir: "/usr/src/app"
    tty: true

        


再びイメージをビルドして、コンテナを起動し、インタラクティブモードでコンテナに入り、
.ssh に秘密鍵がないことを確認します。

            $ docker-compose build
$ docker-compose up -d
$ docker-compose exec app bash
> ls -la ~/.ssh
#秘密鍵が無いことを確認
#Cntl+Dかexitでインタラクティブモードを抜ける

        

ここから起動しているコンテナへ秘密鍵を付与します。
DockerでホストOSから起動中のコンテナにコピーする場合、
docker cp コマンドを使います。
docker-cpの用法としては、

            $ docker cp [ホストから送りたいファイル] [送り先のコンテナ名]:[コンテナ内の送信先ディレクトリ]

        

というように利用します。
docker-compose.ymlでコンテナ名を付けている場合(
container_name: my-app-dev など)、コンテナ名を使って、

            #👇秘密鍵をコンテナへ転送する
$ docker cp ~/.ssh/id_ed25519 my-app-dev:/usr/src/app/.ssh/

        

とすることで先ほどのDockerfileでCOPYした場合と同じことになります。
もしもdocker-compose.ymlでコンテナ名を付けていない場合には、
docker-compose ps サブコマンドの -q オプションで、サービス名からコンテナIDを表示させることでも可能のようです。

            $ docker cp ~/.ssh/id_ed25519 $(docker-compose ps -q app):/usr/src/app/.ssh/

        

ではコンテナへ再度インタラクティブモードに入って秘密鍵がコピーされているか確認してみます。


            $ docker-compose exec app bash
bash-5.1# ls -la .ssh/
-rw-------    1 1000     1000           399 Feb 15  2021 id_ed25519

        

このように今度は直接ホスト側の任意のディレクトリからコンテナ側に直接鍵を注入できています。
この方法とわざわざ作業ディレクトリにキーをコピーして更新するような必要もないですので、先程よりも少しだけ楽です。
ポイントだけ掻い摘むと、DockerfileへCOPYで埋め込むよりもコンテナを立ち上げる前に一手間かかりますが、以下のコマンドでGitHubレポジトリの操作が可能な開発環境コンテナに仕上がると思います。
最終的に、

            $ docker-compose up -d && \
    docker cp ~/.ssh/id_ed25519 my-app-dev:/usr/src/app/.ssh/ && \
    docker-compose exec app bash

        

と、SSH秘密キーを分離しながら運用するのが、もっとも安全なやり方と言えます。


余談〜Expectコマンドで毎回コンテナの立ち上げでGitHubにログインするのを自動化する



先程の
docker cpサブコマンドでSSH認証キーをコンテナに送る やり方だと、コンテナを立ち上げてから、秘密鍵を転送し、コンテナ内でSSHクライアントを起動し、GitHubへ初回のSSH接続を行う...までの操作が覚えていられないくらい長いです。
特に、秘密鍵でGitHubに初回接続する際には、
ssh -T git@github.com とすると、 yes とタイプして対話的に操作することが一番面倒です。
そこで、この対話的な手続きを自動化するのが
Expectコマンド です。
Alpine Linuxではパッケージインストールして利用できるようになります。

            FROM alpine:3.18

RUN apk update && apk upgrade && \
    apk add --no-cache bash git openssh

#👇Expectを追加
RUN apk add --no-cache expect

CMD ["bash"]

        


Expectコマンドを含め、出来ればここらへんの一連の操作まとめて、以下の
container_start.sh を作成し、このスクリプトを呼び出したら全て立ち上がるように工夫します。

            #!/bin/bash

docker-compose up -d

docker cp ~/.ssh/id_ed25519 my-app-dev:/usr/src/app/.ssh/

docker-compose exec app bash -c '
    eval "$(ssh-agent)"
    ssh-add .ssh/id_ed25519
    expect -c "
        spawn ssh -T git@github.com
        expect \"Are you sure you want to continue\" { send yes }
    "
    bash
'

        


では最後にこのスクリプトでGitHubのログインまで一括で完了するか確かめてみましょう。

            $ chmod +x container_start.sh
$ ./container_start.sh
Agent pid 88
Identity added: .ssh/id_ed25519 (tacoskingdom)
Hi *******! You've successfully authenticated, but GitHub does not provide shell access.
bash-5.1#

        

ということでここまでGitHubアクセス認証済みのセキュアな開発用Dockerコンテナを作成するためのやり方を考察してみました。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

SSH秘密キーを更新する



キーが有効期限を過ぎてしまうと、

            $ git ...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@       WARNING: POSSIBLE DNS SPOOFING DETECTED!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
The RSA host key for github.com has changed,
and the key for the corresponding IP address ***.***.***.***
is unknown. This could either mean that
DNS SPOOFING is happening or the IP address for the host
and its host key have changed at the same time.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
Please contact your system administrator.
Add correct host key in /home/xxxxxx/.ssh/known_hosts to get rid of this message.
Offending RSA key in /home/xxxxxx/.ssh/known_hosts:x
  remove with:
  ssh-keygen -f "/home/xxxxxx/.ssh/known_hosts" -R "github.com"
RSA host key for github.com has changed and you have requested strict checking.
Host key verification failed.
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

        

のような警告が表示されるようになります。
一旦、GitHubのダッシュボードに移り、操作がコケてしまったプロジェクトの状態を確認してみましょう。

合同会社タコスキングダム|蛸壺の技術ブログ
[Clone] > [SSH] タブのリンクにSSHの公開キーの有効期限切れの旨が表示されています。
この場合は、新しくレポジトリ管理者が新しいSSHキーを作り直さないといけないようです。
そのまま
[add a new public key] のリンクをクリックするか、管理者権限のあるユーザーでGitHubダッシュボードに入り直し、上記で説明してきたSSH鍵を生成する手順を再度やり直して、SSH公開キーの再設定を行いましょう。

合同会社タコスキングダム|蛸壺の技術ブログ


GitHubに公開キーを設定したら、ローカルにあるSSHクライアント側にはペアの秘密キーを設定し直したら、無事いつものgitコマンドが使えるようになります。


合同会社タコスキングダム|蛸壺の技術ブログ【効果的学習法レポート】シェルスクリプトをこれから学びたい人のためのオススメ書籍&教材特集

まとめ



今回はGitHubレポジトリにed25519方式のSSH接続を試す方法をやってみました。
普段GitHubを使う分にはhttpsからのトークン認証(GitHub公式ではこちらを推奨)からの操作のほうが楽ではあるのですが、カンパニーアカウントなどのより堅牢な運用が求められる場合には、SSH接続の利用は検討に値すると思います。

参考サイト

最強であろうed25519でSSH鍵を作成してGitHubに登録するDockerでVolumeをマウントするとき一部を除外する方法docker-composeを爆速にする