踏み台経由で接続するためのTeraTermマクロ
WindowsからAWSなんかで踏み台サーバのSSHポートフォワードで中のホストに接続する際、TeraTermの[設定]-[SSH転送]に相手先ホストを設定しているのだけれど面倒くさい。
~/.ssh/config のProxyCommandはインスタンスの再生成などで相手先がコロコロ変わるケースでは使いにくい。
ということで、踏み台は固定だけれど相手は色々でも相手の秘密鍵は共通という妙にニッチなケースのためにTeraTermマクロ書いた。詳細は当ファイル冒頭参照。適当に.ttlという拡張子でTeraTermマクロと紐付けて実行するよろし。
複数台並行接続にも対応したいところ。
; ======================================================= ; connect to internal hosts via bastion (TeraTerm Macro) ; ======================================================= ; 手順1:同じフォルダに踏み台の秘密鍵を置いて当マクロの変数BASTION_USERとBASTION_KEYを書き換える。 ; 手順2:同じフォルダに「bastion-template.ini」という名前でTERATERM.INIのコピーを置く。 ; 手順3:「bastion-template.ini」の[TTSSH]のDefaultForwardingの値を「__BASTION_REPLACE__」にする。 ; 手順4:当マクロを実行する(接続先ホスト、接続先ポート、ローカルポートを入力する)。 ; 上記により適当なツールでlocalhostのローカルポートに接続するとSSH転送される。 ;; マクロのディレクトリ getdir MACRO_DIR ;; 踏み台情報(個人ごとに変更) BASTION_HOST = 'bastion.dev.mycompany.info' BASTION_PORT = '22' BASTION_USER = 'tarou_yamada' sprintf2 BASTION_KEY '%s\%s' MACRO_DIR 'tarou.yamada' ;; 接続先情報 inputbox '接続先ホスト' '接続先情報' TARGET_HOST = inputstr inputbox '接続先ポート' '接続先情報' TARGET_PORT = inputstr inputbox 'ローカルポート' '接続先情報' TARGET_LOCAL_PORT = inputstr ;; 置換対象)DefaultForwarding=__BASTION_REPLACE__ ;; 例)L2222:aaa:23;L12222:xxx:22 TRAGET_STR = 'L' strconcat TRAGET_STR TARGET_LOCAL_PORT strconcat TRAGET_STR ':' strconcat TRAGET_STR TARGET_HOST strconcat TRAGET_STR ':' strconcat TRAGET_STR TARGET_PORT ;; 専用INIファイル INI_TEMPLATE_FILE = 'bastion-template.ini' sprintf2 INI_FILE '%s\%s' MACRO_DIR 'bastion.ini' ;; 設定ファイル書き出し fileopen FH0 INI_TEMPLATE_FILE 0 fileopen FH1 INI_FILE 0 while 1 filereadln FH0 LINE if result = 1 then break endif strreplace LINE 1 '__BASTION_REPLACE__' TRAGET_STR filewriteln FH1 LINE endwhile fileclose FH1 fileclose FH0 ;; 踏み台接続 COMMAND = BASTION_HOST strconcat COMMAND ':' strconcat COMMAND BASTION_PORT strconcat COMMAND ' /ssh 2 /auth=publickey /user=' strconcat COMMAND BASTION_USER strconcat COMMAND ' /keyfile=' strconcat COMMAND BASTION_KEY strconcat COMMAND ' /F=' strconcat COMMAND INI_FILE connect COMMAND
CircleCIとterraformとGitHubのプライベートリポジトリで3環境を構成する
表題の件、結局以下のようにしてみた。当初は単一のアクセスキー/シークレットキーを使ってクロスアカウントのAssumeRoleで切り分けようかと思ったものの、AssumeRoleの切り替えに一時セッショントークンの取得などが必要で余計にややこしくなることから、システムユーザについてはAWSアカウント別に相当するユーザを作ってしまうことにした(人間のユーザはAssumeRoleで運用している)。
prodは本番用AWSアカウント、stgとdevは開発用AWSアカウント。各環境は別VPC。それぞれPowerUserポリシーのterraformerというIAMユーザを定義済み。
通常CircleCIでAWSと連携する場合は「AWS Permissions」にアクセスキーとシークレットキーを書くが、今回はそこには何も書かずに「Environment Variables」に「DEV_AWS_ACCESS_KEY_ID」「DEV_AWS_SECRET_ACCESS_KEY」のような感じでdev/stg/prodの分のアクセスキーを記述。
S3バケットとして対応するAWSアカウントに「company01-dev-terraform」「company01-stg-terraform」「company01-prod-terraform」というのを用意してそこで各環境のtfstateを管理(初回はバケットだけ作っておけば実際に何も入ってない状態でもremote pullとかは正しく動作する)。
git-flowとはちょっと違う運用で、developにpushすると開発環境のplan、release/developmentにpush(developをマージ)すると開発環境へのapply、release/stagingにpush(developをマージ)するとステージング環境へのapply、masterにpush(developをマージ)すると本番環境のplan、release/productionにpushする(masterをマージ)と本番環境へのapply。
GitHub上はOrganizationでプライベートリポジトリを作って運用しているが、当該リポジトリの[Settings]-[Branches]-[Protected branches]から、「master」「release/production」へのpush権限を別途作成したチーム「Administrators」に絞っている。
ちなみにアクセスキーを環境変数で与えている割に「provider "aws"」に「profile」を設定しているが、これは同じリソースを使って人間が手動で流すときに自身の「~/.aws/config」の設定で切り替えられるようにしたもの。CircleCI自体では使っていない。
- ci/
- terraform-build.sh
- terraform-install.sh
- terraform-validate.sh
- env/
- dev/
- main.tf
- prod/
- main.tf
- stg/
- main.tf
- dev/
- module/
- vpc/
- main.tf
- outputs.tf
- variables.tf
- web_cluster/
- main.tf
- outputs.tf
- variables.tf
- ...
- vpc/
- .gitignore
- circle.yml
- README.md
ci/terraform-build.sh
#!/bin/bash set -xe TF_ENV_DIR=$1 TF_BUCKET=$2 ROOT_DIR=$(git rev-parse --show-toplevel) cd "${ROOT_DIR}/${TF_ENV_DIR}" terraform get -update terraform remote config -backend=s3 -backend-config="bucket=${TF_BUCKET}" -backend-config="key=common-infra/terraform.tfstate" -backend-config="region=ap-northeast-1" terraform apply terraform remote push
ci/terraform-install.sh
#!/bin/bash set -xe cd ~/bin TF_VERSION=0.6.16 if [ ! -e ~/bin/terraform ]; then wget https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip unzip terraform_${TF_VERSION}_linux_amd64.zip rm terraform_${TF_VERSION}_linux_amd64.zip fi
ci/terraform-validate.sh
#!/bin/bash set -xe TF_ENV_DIR=$1 TF_BUCKET=$2 ROOT_DIR=$(git rev-parse --show-toplevel) cd "${ROOT_DIR}/${TF_ENV_DIR}" terraform get -update terraform remote config -backend=s3 -backend-config="bucket=${TF_BUCKET}" -backend-config="key=common-infra/terraform.tfstate" -backend-config="region=ap-northeast-1" terraform plan
env/dev/main.tf
provider "aws" { profile = "closed-aws-account" region = "ap-northeast-1" } module "vpc_dev" { source = "../../module/vpc" env = "dev" service = "common" cidr = "10.x.x.x/x" public_subnets = "10.x.x.x/x,10.x.x.x/x" private_subnets = "10.x.x.x/x,10.x.x.x/x" azs = "ap-northeast-1a,ap-northeast-1c" } module "service01_dev" { source = "../../module/web_cluster" env = "dev" service = "service01" ... } module "service02_dev" { source = "../../module/web_cluster" env = "dev" service = "service02" ... }
env/prod/main.tf
provider "aws" { profile = "open-aws-account" region = "ap-northeast-1" } module "vpc_prod" { source = "../../module/vpc" env = "prod" service = "common" cidr = "10.x.x.x/x" public_subnets = "10.x.x.x/x,10.x.x.x/x" private_subnets = "10.x.x.x/x,10.x.x.x/x" azs = "ap-northeast-1a,ap-northeast-1c" } module "service01_prod" { source = "../../module/web_cluster" env = "prod" service = "service01" ... } module "service02_prod" { source = "../../module/web_cluster" env = "prod" service = "service02" ... }
env/stg/main.tf
provider "aws" { profile = "closed-aws-account" region = "ap-northeast-1" } module "vpc_stg" { source = "../../module/vpc" env = "stg" service = "common" cidr = "10.x.x.x/x" public_subnets = "10.x.x.x/x,10.x.x.x/x" private_subnets = "10.x.x.x/x,10.x.x.x/x" azs = "ap-northeast-1a,ap-northeast-1c" } module "service01_stg" { source = "../../module/web_cluster" env = "stg" service = "service01" ... } module "service02_stg" { source = "../../module/web_cluster" env = "stg" service = "service02" ... }
.gitignore
.terraform *.tfstate *.tfstate.backup
circle.yml
general: branches: only: - release/development - release/staging - release/production - develop - master machine: environment: PATH: "${HOME}/bin:${PATH}" dependencies: cache_directories: - "~/bin" pre: - | mkdir -p ~/bin bash ./ci/terraform-install.sh test: override: - | if [ "${CIRCLE_BRANCH}" = "release/development" ] || [ "${CIRCLE_BRANCH}" = "develop" ]; then AWS_ACCESS_KEY_ID=${DEV_AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${DEV_AWS_SECRET_ACCESS_KEY} AWS_DEFAULT_REGION=ap-northeast-1 bash ./ci/terraform-validate.sh env/dev company01-dev-terraform elif [ "${CIRCLE_BRANCH}" = "release/staging" ]; then AWS_ACCESS_KEY_ID=${STG_AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${STG_AWS_SECRET_ACCESS_KEY} AWS_DEFAULT_REGION=ap-northeast-1 bash ./ci/terraform-validate.sh env/stg company01-stg-terraform elif [ "${CIRCLE_BRANCH}" = "release/production" ] || [ "${CIRCLE_BRANCH}" = "master" ]; then AWS_ACCESS_KEY_ID=${PROD_AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${PROD_AWS_SECRET_ACCESS_KEY} AWS_DEFAULT_REGION=ap-northeast-1 bash ./ci/terraform-validate.sh env/prod company01-prod-terraform fi deployment: development: branch: release/development commands: - AWS_ACCESS_KEY_ID=${DEV_AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${DEV_AWS_SECRET_ACCESS_KEY} AWS_DEFAULT_REGION=ap-northeast-1 bash ./ci/terraform-build.sh env/dev company01-dev-terraform staging: branch: release/staging commands: - AWS_ACCESS_KEY_ID=${STG_AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${STG_AWS_SECRET_ACCESS_KEY} AWS_DEFAULT_REGION=ap-northeast-1 bash ./ci/terraform-build.sh env/stg company01-stg-terraform production: branch: release/production commands: - AWS_ACCESS_KEY_ID=${PROD_AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${PROD_AWS_SECRET_ACCESS_KEY} AWS_DEFAULT_REGION=ap-northeast-1 bash ./ci/terraform-build.sh env/prod company01-prod-terraform
AWSアカウント間のIAMロールを使用したアクセスの委任をしているときのクレデンシャル設定
AWS CLI上のお話
本番環境と開発環境でAWSアカウントを分けることがある。
で、以下のような感じでsts:AssumeRoleを使って開発環境のIAMユーザのまま本番環境のIAMロールで振る舞えるようにすることがある(あまりサクサク切り替えられるとAWSアカウントを分けた意味がないので実際には色々と調整)。
チュートリアル: AWS アカウント間の IAM ロールを使用したアクセスの委任
http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html
このときに手元のPCからAWS CLIのコマンドを開発環境用と本番環境用で呼び分けたいと思ったのでそのときのメモ(defaultはまた別のアカウント用なので気にしない)。
まあ実際にはAWS CLIコマンドではなくterraformの適用先の切り替えをしたくてやっているのですが(resourceのproviderが変数を受け付けてくれないのでmoduleと併用だといろいろ厳しい)。
~/.aws/credentials
[default] aws_access_key_id=xxx aws_secret_access_key=xxx [development] aws_access_key_id=yyy aws_secret_access_key=yyy
~/.aws/config
[default] region=ap-northeast-1 [profile development] region=ap-northeast-1 [profile production] source_profile=development role_arn=arn:aws:iam::xxx:role/role-xxx region=ap-northeast-1
これで以下のように切り替えて使うことができる。めでたし。
aws ec2 describe-instances --profile development aws ec2 describe-instances --profile production
terraform上のお話
なお、現時点のterraform(0.6.16)ではproviderを複数指定できるものの、resourceのproviderには変数が使えないため、provider汎用のmoduleを作って呼び元で指定しわけるということができない。
同じようなresourceでproviderをハードコードしたのを複数作らないとダメかなぁ。tjたんも「しかたないのでコピペしてる」って言ってるし。
https://github.com/hashicorp/terraform/issues/1819
追記01:
結局、ハードコードしてもproviderのaliasがmoduleに伝わらないことが分かったのでダミーのproviderをトップに置きつつ(「No valid credential sources found for AWS Provider.」って怒られるので)、実際にはaws_workaround.tfなるものを各moduleの中に入れた。うーん。
aws_workaround.tf
# TODO: # 本当はprovider汎用のmoduleにproviderのaliasを渡してハンドリングしたいが # terraform 0.6.16ではresourceのproviderに変数が使えず動的に変えられない。 # https://github.com/hashicorp/terraform/issues/1819 # かつ、そもそもmoduleにaliasしたproviderが伝達されない。 # https://github.com/hashicorp/terraform/issues/4789 # 仕方ないのでproviderを個々のmoduleで定義しているがいつ使えなくなるのか分からないので注意。 variable "profile" { } variable "region" { } provider "aws" { profile = "${var.profile}" region = "${var.region}" }
追記02:
トップのが効いてダメそうだ。うーん。
とりあえず横断的にやるのは諦めて、
env/development/main.tf
env/production/main.tf
module/xxx/main.tf
module/yyy/main.tf
的にやって各envの中(provider定義こみ)でコマンド発行する方向にした。
ただ、今度はterraform remote configでS3にアクセスする際にassume roleが効かないというのが……。
https://github.com/hashicorp/terraform/issues/7014
以下の感じだとそもそもaws-sdk-go自体が対応していないのかな。
https://github.com/aws/aws-sdk-go/issues/472