はじめに
最近、OCIのBastionに触っています。Bastion(日本語で要塞)はサーバーへのアクセスの踏み台になってくれるサービスです。自前で踏み台サーバーを作るのと比べて、プライベートなネットワークで完結し、無料枠であることが利点としてあります。
しかし、使っているとTTLの短さやセッション作成の手間が気になってきます。そこで、何とか楽に使えないか考えていきます。
Bastionの接続方法
Bastionの使い方の詳細はOCIのドキュメントにあります。
概要を記載します。
Bastionを使ったサーバーへの接続方法としては以下の種類があります。
- SSH接続
- ポート転送
- リモートデスクトップ接続
今回はSSH接続を使います。
サーバーへの接続の流れは以下の通りです。
- OCIコンソールにアクセスする。
- セッションを作成する。
- 作成されたセッションIDを確認する。
- ターミナル(サーバーに接続するためのツール)を開く。
- セッションIDを使ってサーバーに接続する。
- TTLが切れたら、再度セッションを作成する。
大変だなーと思いますので、何とか楽にできないか考えます。
出来上がり
以下のファイルを作りました。
- セッションを作成するスクリプト
- SSHのconfigファイル
今回のスクリプトを使うと以下のようになります。
- ターミナルを開く。
- サーバーに接続する。
- TTLが切れたら、再度セッションを作成する。
接続のコマンドは以下の1文で済みます。
$ ssh <ホスト名/IPアドレス>
スクリプトのフロー
作成するスクリプトのフローは以下の通りです。
以下の2点に気を付けました。
- OCIのCLIはインストールしたくないのでREST APIを使ってcurlで実現したい。
- OCIのREST APIでは作成を待機するオプションがないので待機する処理を入れる。
セッションを作成するスクリプトを作成する
セッションを作成して、そのOCIDを取得するshellファイルは以下の通りです。自分で使う用なので、荒いのは申し訳ないです。。
#!/bin/bash
#必要なパラメータは外部のファイルに書くようにしました。
ENV_FILE=$1
source $ENV_FILE
function create_session(){
rest_api="/20210331/sessions"
publicKeyContent=$(cat ${publicKey} | awk '{print substr($0, 1, length($0)-1)}')
body="{\"bastionId\": \"${bastionId}\", \"displayName\": \"${displayName}\", \"keyDetails\": {\"publicKeyContent\": \"${publicKeyContent}\n\"}, \"sessionTtlInSeconds\": \"${sessionTtlInSeconds}\", \"targetResourceDetails\": {\"sessionType\": \"${sessionType}\", \"targetResourceId\": \"${targetResourceId}\", \"targetResourceOperatingSystemUserName\": \"${targetResourceOperatingSystemUserName}\"}}"
content_sha256="$(echo "$body" | openssl dgst -binary -sha256 | openssl enc -e -base64)"
content_sha256_header="x-content-sha256: $content_sha256"
content_length=$(echo "$body" | wc -c | xargs)
content_length_header="content-length: $content_length"
headers="(request-target) date host"
headers=$headers" x-content-sha256 content-type content-length"
content_type_header="content-type: application/json"
now=`date -u "+%a, %d %h %Y %H:%M:%S GMT"`
date_header="date: $now"
host_header="host: $bastionHost"
request_target="(request-target): post $rest_api"
signing_string="$request_target\n$date_header\n$host_header"
signing_string="$signing_string\n$content_sha256_header\n$content_type_header\n$content_length_header"
signature=`printf '%b' "$signing_string" | openssl dgst -sha256 -sign $privateKeyPath | openssl enc -e -base64 | tr -d '\n'`
response=$(echo "${body}" | curl -X POST --data-binary @- -sS https://$bastionHost$rest_api -H "date: $now" -H "x-content-sha256: $content_sha256" -H "content-type: application/json" -H "content-length: $content_length" -H "Authorization: Signature version=\"1\",keyId=\"$tenancy_ocid/$user_ocid/$fingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"")
echo $response | sed -e 's/^.*"id" : "\([^"]*\)".*$/\1/'
}
function is_session_alive(){
sessionId=$1
rest_api="/20210331/sessions/${sessionId}"
now=`date -u "+%a, %d %h %Y %H:%M:%S GMT"`
date_header="date: $now"
host_header="host: $bastionHost"
request_target="(request-target): get $rest_api"
signing_string="$request_target\n$date_header\n$host_header"
headers="(request-target) date host"
signature=`printf '%b' "$signing_string" | openssl dgst -sha256 -sign $privateKeyPath | openssl enc -e -base64 | tr -d '\n'`
response=$(curl -X GET -sS https://$bastionHost$rest_api -H "date: $now" -H "Authorization: Signature version=\"1\",keyId=\"$tenancy_ocid/$user_ocid/$fingerprint\",algorithm=\"rsa-sha256\",headers=\"$headers\",signature=\"$signature\"")
lifecycleState=$(echo $response | sed -e 's/^.*"lifecycleState":"\([^"]*\)".*$/\1/')
echo $lifecycleState >&2
if [ -n "$lifecycleState" ] && [ "$lifecycleState" = 'ACTIVE' ];then
echo true
else
echo false
fi
}
sessionId=$(create_session)
until $(is_session_alive $sessionId);do
sleep 3
done
echo $sessionId
以下に参考にしたドキュメントを載せておきます。
- Bastionのリストを取得するAPIのリファレンス
https://docs.public.oneportal.content.oci.oraclecloud.com/en-us/iaas/api/#/en/bastion/20210331/Session/ListSessions - OCIのREST APIの使用例が載っているサイト
https://www.ateam-oracle.com/post/oracle-cloud-infrastructure-oci-rest-call-walkthrough-with-curl
https://itport.cloud/?p=8745
パラメータを定義したファイルには以下の情報を書きました。
#
# セッションに関する情報
#
export displayName="..." #OCIコンソールから見えるセッション名
export publicKey="..." #SSH接続に用いる公開鍵のパス
export sessionType="MANAGED_SSH" #セッションの種類
export targetResourceId="..." #SSH接続するサーバーのOCID
export targetResourceOperatingSystemUserName="..." #SSH接続する際のユーザー名
export sessionTtlInSeconds="..." #セッションのTTL
#
# Bastionに関する情報
#
export bastionId="..." #BastionのOCID
export bastionHost="bastion...." #Bastionのエンドポイント
#
# APIに関する情報
#
export compartmentId="..." #コンパートメントのOCID
export tenancy_ocid="..." #テナンシのOCID
export user_ocid="..." #ユーザーのOCID
export privateKeyPath="..." #APIを使用するための秘密鍵
export fingerprint="..." #鍵のフィンガープリント
パラメータについて補足します。
- privateKeyPath
OCIにてAPIを使用するための鍵を登録する必要があります。手順は以下のURLにあります。
https://docs.oracle.com/ja-jp/iaas/Content/API/Concepts/apisigningkey.htm#two - bastionHost
リージョンごとのエンドポイントは以下のURLに一覧があります。
https://docs.oracle.com/en-us/iaas/api/#/en/bastion/20210331/
SSHのconfigファイルを作成する
作成したスクリプトを実行するconfigファイルを作成します。
元になる設定はセッションをOCIコンソールから作成したときに見ることができます。以下の構造になっています。
ssh -i <privateKey> -o ProxyCommand="ssh -i <privateKey> -W %h:%p -p 22 <セッションのOCID>@host.<Bastionのエンドポイント>" -p 22 <ユーザー名>@<サーバーのIPアドレス>
上記のコマンドを実行するのは長くて面倒なので、configファイルにします。
以下の通りです。
StrictHostKeyChecking no #面倒なのでOFFにしています。
UserKnownHostsFile=/dev/null #トラブルの原因になったことがあるのでOFFにしています。
ServerAliveInterval 50 #Bastionだと操作しないと切られるので設定します。
Host <ホスト名>
HostName <サーバーのIPアドレス>
User <ユーザー名>
IdentityFile <privateKey>
ProxyCommand ssh -i <privateKey> -W %h:%p -p 22 $(/path/to/get-session-ocid.sh /path/to/envfile.sh)@host.<Bastionのエンドポイント>
無理やりですが、セッションのOCIDをスクリプトから取得するようにしています。
SSHコマンドの実行
以上で準備はできました。
後は、コマンド1文でサーバーにSSH接続することができます。
$ ssh <ホスト名>
苦労した点&今後の方針
curlのデバッグオプション
curlを使ってコードを書いているとミスしたときに、原因を特定するのに苦労しました。
デバッグで使用したオプションを記載します。
- v,vvvなどのverboseオプション
リクエストのパスやヘッダーを確認できます。 - traceオプション
今回はデータに改行コードがついていたりついていなかったりで詰まりました。バイナリでデータを表示されるので、どこがおかしいかわかりやすくなります。
※参考
https://qiita.com/yasuhiroki/items/a569d3371a66e365316f
さらに楽に接続したい
今回作ったものでは以下の点が気になります。
- SSH接続で毎回セッションを作成するので時間がかかる
- TTLが切れたら切断されてしまう
そのため、今後はRemoteCommandで定期的にセッションを作成し、tmux,screen,autosshなどで自動再接続みたいにしたいなと思っています。
おわりに
今回はセッションをcurlで作成しました。できれば二度とやりたくないと思うほど面倒でした。CLIを使わないと大変だということを思い知りました。
まだ課題はあるので、勉強します。