Macでターミナルは何を利用されていますか?
私は Rust製で高速、AI搭載でコマンド補完やAgent Modeが便利なターミナル「Warp」を使っています!
履歴検索やブロック実行、共有もしやすく、日常のCLI体験が大きく改善されました。
最近は 日本語化 にも対応し、益々使いやすくなりました!
一方で、SSHの設定はどうしていますか?
~/.ssh/config に全案件を書き連ねると巨大化しがちで、差分追跡や再利用性に課題が出ます。
これを解消するために私は conf.d/*.confを分割運用し、ベース設定と個別案件設定を合成する構成にしていました。
ところが、この方式はWarpの補完と相性問題が出ます。
本記事では、その問題点と、実運用のためどのように変更を行ったかを書こうと思います。
そもそもなぜSSH設定を分割管理していたのか?
一番の理由は、単純に config ファイルが肥大化するのを避けたかったからです!
従来の課題
多くの開発者が直面する(かもしれない)SSH設定の問題
- 1. 巨大化するconfigファイル: 案件が増えるたびに
~/.ssh/configが肥大化 - 2. 差分追跡の困難: 一つのファイルに全案件が混在し、変更履歴が追いにくい
- 3. 再利用性の低さ: 案件ごとの設定を他環境に移植しにくい
- 4. セキュリティリスク: チーム共有時に不要な案件情報まで露出する可能性
分割管理のメリット
# 従来の構成
~/.ssh/config # 全案件が混在(1000行超えも珍しくない)
# 分割後の構成
~/.ssh/
├── config # グローバル設定値や共通方針を書き Include conf.d/*
├── conf.d/ # 個別設定を .conf で分割
│ ├── c-limber.conf
│ ├── ec-rent.conf
│ ├── liwoy.conf
│ └── playza.conf
├── secret_keys/ # 鍵とか
│ ├── c-limber/
│ ├── ec-rent/
│ ├── liwoy/
│ └── playza/
この構成により得られた恩恵は...
-
案件ごとの独立性: 各
.confファイルが独立して管理可能 - 再利用性向上: 必要な設定のみを他環境にコピー可能
- バージョン管理: gitで個別ファイルの変更履歴を追跡可能
ターミナルWarp × Include conf.d/* の問題
問題の詳細と技術的背景
Include conf.d/* で分割した設定を使うと、WarpのSSH補完が期待どおりにホストを列挙してくれません。
これは既知の問題で、Warp Issue #4072 で報告されています。
結構前に私もぼやいていました。
I'm having trouble with Warp's ssh suggest feature not recognizing the Include conf.d/* directive in the SSH config file. Is anyone else experiencing this issue?https://t.co/9l3hbw7AnA
— Takashi Kakizoe (@TakashiKakizoe) July 2, 2024
他のターミナルアプリ(iTerm2等)では正常に動作するかもしれませんが、
Warpでは Include ディレクティブを認識しません。
ターミナルでSSH補完が動作しない というのは、開発者にとって大きなストレスです!
実際にWarpの利用を開始してから最近までは、補完が効かないまま使っていました。
(shellコマンドを独自に書いたりしていましたが、面倒でした...)
解決への道筋
Warp側の Include ディレクティブ未対応という制約を回避するため、
Warp側での対応は待たず、巨大なconfigを生成してしまえば良いのでは?ついでに設定関連も見直せば良いのでは?
ということで、分割ファイルをビルドして単一の ~/.ssh/config を生成する方式に切り替えることにしました!
改善前の構成 ツリー
~/.ssh/
├─ config # グローバル設定値や共通方針を記載 & Include conf.d/*
├─ conf.d/ # 個別設定を .conf で分割
└─ secret_keys/ # 鍵とか
改善後の構成 ツリー
~/.ssh/
├─ bin/
│ ├─ ssh-proxy-auto # SSM経由接続などの補助
│ └─ ssm-target # AWSタグからInstanceId解決
├─ conf.d/ # 個別設定を .conf で分割
├─ public_keys/ # 1Password用公開鍵とか
├─ secret_keys/ # 従来の鍵とか
├─ config # 最終成果物へのシンボリックリンク
├─ config_base # グローバル設定値や共通方針を記載
├─ generated_config # ベース+分割を合成して生成する最終成果物
├─ config -> generated_config # シンボリックリンク
└─ Makefile # config生成を担う
改善の方針
1. Warp補完に対応
単一 config を生成し、Warp操作時に問題なく補完が効くように!
もう面倒なshell実行などとはおさらばしたい!
2. 既存設定を生かすローカル秘密鍵対応
案件別に IdentityFile ~/.ssh/secret_keys/... を行っていた箇所があるので、それはそのまま動かせるように!
xxxx.conf を書き換えて回ることはしたくなかったため、既存設定は生かす方針に!
3. 1Password対応
IdentityAgent を 1Password SSH Agent のソケットに設定
SSH鍵が1Passwordで管理しているものに対して指紋認証でアクセスできるように!
4. AWS SSM併用に対応
bin/ssh-proxy-auto と bin/ssm-target により、AWS SSM 接続をサポート
改善の詳細
ファイル設置 - Makefile
config 生成を自動化するMakefileを作成しましたしてもらいました!
CONFIG_DIR=$(HOME)/.ssh
CONF_FILES=$(wildcard $(CONFIG_DIR)/conf.d/*.conf)
GENERATED_CONFIG=$(CONFIG_DIR)/generated_config
CONFIG_LINK=$(CONFIG_DIR)/config
.PHONY: config force clean help
.DEFAULT_GOAL := help
help: ## Show this help message
@echo "Available targets:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-15s %s\n", $$1, $$2}'
config: $(GENERATED_CONFIG) ## Generate SSH config from base and conf.d files
@echo "SSH config generated successfully"
$(GENERATED_CONFIG): $(CONFIG_DIR)/config_base $(CONF_FILES)
@echo "Generating SSH config..."
awk 'FNR==1 && NR!=1{print ""} {print}' $^ > $@
chmod 600 $@
ln -sf $(GENERATED_CONFIG) $(CONFIG_LINK)
@echo "Config linked to $(CONFIG_LINK)"
force: ## Force regeneration of SSH config
$(MAKE) --always-make config
clean: ## Remove generated config and link
rm -f $(GENERATED_CONFIG) $(CONFIG_LINK)
@echo "Cleaned generated files"
このMakefileの動作:
-
依存関係の管理:
config_baseとconf.d/*.confの変更を検知 -
ファイル連結:
awkでファイル間に空行を挿入しながら連結(FNR==1 && NR!=1{print ""}) -
権限設定:
generated_configに適切な権限(600)を設定 -
シンボリックリンク:
configをgenerated_configへのリンクに変更 -
ユーザビリティ:
make helpで利用可能なターゲット一覧を表示 -
クリーンアップ:
make cleanで生成ファイルを削除可能
実際の設定例と運用方法
基本的な設定ファイル構成
グローバル設定(config_base)
### Global defaults
Host *
# 1Password SSH Agent
IdentityAgent ~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock
# その他共通設定をこの下に長々と記述
GitHub用設定(conf.d/github.conf)
###################################################################
## Github #########################################################
Host github github.com
HostName github.com
User git
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
IdentityFile ~/.ssh/public_keys/github/github.pub
IdentitiesOnly yes
各種案件用既存設定(conf.d/example-project.conf)
###################################################################
## Example Project #################################################
# 本番Webサーバー(ubuntuユーザー)
Host example-prod-web
HostName xxx.xxx.xxx.xxx
User ubuntu
IdentityFile ~/.ssh/secret_keys/example/prod-web.pem
IdentitiesOnly yes
StrictHostKeyChecking yes
CheckHostIP yes
# 本番Webサーバー(アプリケーションユーザー)
Host example-prod-app
HostName xxx.xxx.xxx.xxx
User app
IdentityFile ~/.ssh/secret_keys/example/app-key.pem
IdentitiesOnly yes
StrictHostKeyChecking yes
CheckHostIP yes
# 開発サーバー
Host example-dev
HostName xxx.xxx.xxx.xxx
User ubuntu
IdentityFile ~/.ssh/secret_keys/example/dev.pem
IdentitiesOnly yes
StrictHostKeyChecking accept-new
CheckHostIP no
# ステージングサーバー
Host example-staging
HostName xxx.xxx.xxx.xxx
User ubuntu
IdentityFile ~/.ssh/secret_keys/example/staging.pem
IdentitiesOnly yes
StrictHostKeyChecking yes
CheckHostIP yes
1Password利用時の設定(conf.d/example-1p.conf)
# 1Passwordを使う場合は、グローバル設定(config_base)で既に設定済み
# IdentityAgent ~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock
# 個別のHost設定では、公開鍵ファイルを指定するだけ
Host example-1p-server
HostName xxx.xxx.xxx.xxx
User ubuntu
IdentityFile ~/.ssh/public_keys/example/example-1p-server.pub
IdentitiesOnly yes
SSM接続用設定(conf.d/ssm.conf)
※まだちゃんと試していません
###################################################################
## AWS SSM ########################################################
# インスタンスID直指定
Host ssm/i-*
User ec2-user
ProxyCommand sh -lc 'AWS_PROFILE=prod AWS_REGION=ap-northeast-1 aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters "portNumber=%p" 2>/dev/null'
StrictHostKeyChecking yes
CheckHostIP no
# Name タグ指定
Host ssm/name:*
User ec2-user
ProxyCommand sh -lc 'AWS_PROFILE=prod AWS_REGION=ap-northeast-1 ~/.ssh/bin/ssm-target ${HOST#ssm/name:} | xargs -I{} aws ssm start-session --target {} --document-name AWS-StartSSHSession --parameters "portNumber=%p" 2>/dev/null'
StrictHostKeyChecking yes
CheckHostIP no
日常的な運用
-
設定ファイル作成:
conf.d/に新しい.confファイルを作成 -
設定生成:
make configで設定を再生成 -
接続テスト:
ssh <新しいホスト名>で接続確認
まとめ
-
Warp対応:
config_base + conf.d/*.confをビルドして単一のgenerated_configを作り、configをそれにリンクする運用で安定 - 設定管理の複雑さ回避: 巨大化するSSH設定ファイルの管理を分割・統合の仕組みで解決
- 多様な環境対応: 1PasswordのSSH Agent、AWS SSM連携、案件別鍵運用も同構成で共存可能
Warpを使いながら、SSH設定も効率的に管理したい開発者の方々に、この手法が参考になれば幸いです。
参考リンク
-
Warp Issue #4072: SSH host autocompletion for included files
- WarpのIncludeディレクティブ未対応問題の公式Issue
-
1Password SSH Agent documentation
- 1PasswordのSSH Agent設定方法
-
1Password SSH Agent の高度な設定
- Touch ID/Face IDとの連携やセキュリティ設定の詳細