nabeo がピーしているブログ (仮)

どーも、nabeop です

TLS で暗号化された内容を見たいとき

稀に TLS で暗号化されたパケットの中身を確認したくなる時があるけど、Change Cipher Spec のあとは暗号化されていて困る。TLS はそういうんもんなので普段は困らないけど、handshake の様子とかをつぶらに確認したくなる時とかに途方に暮れたりする。

こういう時は handshake 中の pre master secret などと呼ばれる暗号化に使っている鍵をどうにかして知る必要がある。

で、だいたいのプログラムには通信に使用した秘密を保存することができる。ググったり、help を眺めると書いてあるけど、よく忘れるのでメモっておく。

curl の場合は環境変数に pre master secret の保存先を以下のように指定する。

SSLKEYLOGFILE=./pre-master-secret.log curl -I https://nabeop.hatenablog.com/

openssls_client の場合はオプションに pre master secret の保存先を以下のように指定する。

openssl s_client -servername nabeop.hatenablog.com -connect nabeop.hatenablog.com:443 -keylogfile=pre-master-secret.log < /dev/null

あとは wireshark なり tshark でゲットした pre master secret をキャプチャしたデータにくわせると、複合された状態でデータを眺めることができる。

1ヶ月ほど開発版の emacs で生活してみた

macOSemacs を使うには日本語入力周りでパッチを当てる必要があったので、長らく emacs は安定版を使っていた。そんなある日、現実逃避でググっていたら emacs 28.0.50 で macOS でも WebKit を使えるようになったと知った。

Linux では xwidget が使えるようになっていて、lsp-ui-doc でいい感じに Doc コメントが表示されるんだろーなーと、眺めていたのだった。

開発版の emacs のビルドが 10分くらいで終わって感動したのちに、lsp-ui の設定を以下のようにしてみた。

(use-package lsp-ui
  ;; [snip]
  :custom
  (lsp-ui-doc-use-childframe (featurep 'xwidget-internal))
  (lsp-ui-doc-use-webkit (featurep 'xwidget-internal))
  ;; [snip]
  )

(featurep 'xwidget-internal) しているのは環境によっては emacs 27 を使っているので、イイ感じに吸収しようとした結果です。あとは開発版から安定版にいつでも戻れるようにしておきたかったからw

CDK を TypeScript で書いているときにドキュメントが childframe で表示されてかなりイイ感じでコードがかける。xwidget-webkit-browse-url とかして URL を読み込むとキーバインドが xwidget に完全に取られて、Ctrl-gkeyboard-quit もできない状態になるけど、lsp-ui-doc 以外で使う気になっていないのであんまり困っていない。

wl で plain text と html なマルチパートメールを読む時に WebKit で html パートのほうを表示されると困るなーと思っていたけど、今のところそういうことにはなっていない。

emacs 28.0.50 にしてから3回ほど HEAD を移動させてビルドしているけど、他でも困ったことはないから、ひとまずこのまま普通に生活できそうだなー。

gh コマンドで複数のサイトを使い分けられるようになっていた

gh コマンドとは github の公式 cli ツールです。

github.com

ある日、ボーッと gh コマンドの補完候補を眺めていたら、gh auth login というコマンドが生えていたことに気づきました。ドキュメントを読んでみると、複数のサイトの認証情報を持てるぽい。

cli.github.com

僕が使っている githubgithub.com と Github Enterprise の2種類があって、普段はこの2つのサイトを行ったり来たりしていたので、複数のサイトの認証情報を使い分けられるのは願ったり叶ったりでした。

使い方は gh auth login で各サイトの認証情報を保存すると、GH_HOSTGH_REPO という環境変数か、カレントディレクトリのレポジトリに応じて認証情報を使い分けてくれるぽい。

たとえば、自分に割り当たっている PR の状況をみるときは GH_REPO をいい感じに変えて gh pr status を実行する、といった感じです。このおかげで1年くらいマージ待ちになっていた PR を発見できました。

cdktf に入門した話

本記事ははてなエンジニア Advent Calendar 2020 の2日目の記事です。昨日は id:miki_bene さんのページのコンテンツから離れたタイミングのブラウザイベントの選び方 - ドキュメントを見たほうが早いでした

qiita.com

現職になってからクラウドプロバイダーは AWS が中心だったので、TypeScript で AWS CDK をつかっていました。最近になって Google Cloud にも興味が出てきたので、Terraform も選択肢として入ってくるようになりました。ただ、TypeScript でインフラを構成することになれていると HCL では表現力が足りないなーという課題感も感じていました。

そんなときに CDK の Terraform 版である cdktf の存在を知ったので、Terraform に入門するついでにお試しで cdktf にも入門してみました。

cdktf はこのエントリの執筆時点で v0.0.18 でまだまだ開発版という位置付けで正式版になったらいろいろと変わると思います。以下は v0.0.18 での知見です。

cdktf の始め方

HashiCorp のドキュメントに始め方があります。

learn.hashicorp.com

CDK と同じように TypeScript 向けのプロジェクトは以下のように初期化してテンプレートが生成されます。

cdktf init --template=typescript --local

cdktf init した直後のディレクトリは CDK と同じような感じです。

$ ls -a
./                 .gitignore         main.d.ts          node_modules/      tsconfig.json
../                cdktf.json         main.js            package-lock.json
.gen/              help               main.ts            package.json
$

唯一、 .gen というディレクトリだけが見慣れない感じだったので中身を確認したところ、初期状態では cdktf.jsonterraformProvidersaws が指定されている関係で Terraform の AWS プロバイダ向けと思われる ts が .gen/providers/aws 以下に配置されていました。

あと、help というファイルに cdktf での開発に必要なコマンドが書かれていて便利でした。

Google Cloud 向けに環境を整える

初期状態では AWS を想定されていますが、今回は Google Cloud を対象としたいので Terraform プロバイダを変更する必要があります。Terraform プロバイダ設定は cdktf.json で以下のように書き換えます。

{
  "language": "typescript",
  "app": "npm run --silent compile && node main.js",
  "terraformProviders": [
    "google@~> 3.0"
  ]
}

このままでは .gen 以下には AWS 向けのプロバイダが入っているので help にかいてあるとおり Google Cloud のプロバイダを導入します。

npm i -a @cdktf/provider-google
npm run get

cdktf のある生活

Google Cloud 向けのクレデンシャル情報を用意したりと Terraform / Google Cloud としてのお作法はありますが、だいたい AWS CDK と同じように開発ができます。

つまり、

  • ts ファイルで記述して
  • npm run compile して、ts ファイルから js ファイルと d.ts ファイルを生成して、
  • npm run diff で差分を確認して、
  • npm run deploy して適用する

という感じです。

npm run diffterraform plannpm run deployterraform apply に相当しています。

また、package.json の内容などを特に変更しなければ cdktf.out が Terraform としての working directory という扱いになっていそうです。

$ ls -a cdktf.out
./           ../          .terraform/  cdk.tf.json  plan
$

たとえば、cdktf.out/planterraform plan -out plan したときに生成されるファイルと同じ感じでした。つまり、cdktf.out ディレクトリに移動したら terrafom show plan で plan 時の情報を確認できたりします。cdktf.out/plan を使って差分を確認する様子は別エントリで書いています。

nabeop.hatenablog.com

Terraform プロバイダーとの対応関係

.gen/providers/google の中にプロバイダーのリソースやデータソースに対応するクラスが定義されています。

また、各クラスの実装はプロバイダーのスキーマから生成されており、以下のような対応関係になっているみたいです。

  • リソース
    • リソース名についているプロバイダーの文字列を削除して、アッパーキャメルケースに変換
  • データソース
    • データソース名に Data プレフィックスをつけて、アッパーキャメルケースに変換

つまり、

というような感じです。

snapshot テストしたい

せっかく TypeScript で IaC をしているので、テストも書いておきたいところです。AWS CDK を使っている時は snapshot テストだけは整えるようにして、バージョンアップによる不意の変更がわかるようにしています。cdktf でも同じようにしたいので調べてみたところ、cdktf のなかに Testing クラスが定義されていました

試しに jest 周りを整えて、main.test.ts を書いてみたら snapshot テストができました。

import { SandboxStack } from './main';
import { Testing } from 'cdktf';

describe ("sandbox-project", () => {
  test("snaphost test", () => {
    const app = Testing.app();
    const stack = new SandboxStack(app, 'test-stack', {
      credentials: '{}',
    });
    const results = Testing.synth(stack);
    expect(results).toMatchSnapshot();
  });
});

cdktf を触ってみた感想

まだまだ開発版なので足りていない機能はあるという前提ですが、以下のような感想です。

  • TypeScript の資産が活かせるので開発はすんなり始められそう
    • Terraform プロバイダーから半ば機械的に生成された ts ファイルをベースにしているので、HCL を書いている感はある
  • AWS CDK に慣れた身では cdktf diff の出力内容だけだと、どのような変更が行われるかわからず不安になる
    • 変更があるリソースはわかるけど、変更内容まではわからないというところが厳しかった

cdktf のレポジトリをみると活発に開発されていて、プロジェクトのロードマップもあるので正式版のリリースを気長に待とうかという気持ちです。

明日は id:dekokun さんです。

terraform で for_each でリソースを作りつつ、リソース内に Optional な引数を作りたい時

terraform の Google Cloud プロバイダーの google_compute_subnetwork リソースを定義している module で、

  • 1つのサブネットで1つ以上のセカンダリ IP レンジを持つことができる
  • Production 環境と Staging 環境など似た構成で複数のサブネットを作りたい
  • ただし、あるネットワークではセカンダリ IP レンジを持たないサブネットもある

というようなことが想定される場合はこんなふうに書いておくと良さそう。

# modules/foo/variables.tf
variable "subnets" {
  type = map(object({
    ip_cidr_range = string
  }))
}
variable "secondary_ip_ranges" {
  type = map(list(object({
    range_name    = string
    ip_cidr_range = string
  })))
  default = {}
}

サブネットそのもの情報と、サブネット内でオプショナルとなるセカンダリ IP レンジの情報を別の変数として定義しておく。ミソは map として作っているところ。また、secondary_ip_ranges は存在しない場合があるので default = {} として空の map をデフォルト値としている。

# moduels/foo/main.tf
resource "google_compute_subnetwork" "ubnetwork" {
  for_each = var.subnets

...

  ip_cidr_range = each.value.ip_cidr_range

  dynamic secondary_ip_range {
    for_each = contains(keys(var.secondary_ip_ranges), each.key) ? var.secondary_ip_ranges[each.key] : []
    content {
      range_name    = lookup(secondary_ip_range.value, "range_name", null)
      ip_cidr_range = lookup(secondary_ip_range.value, "ip_cidr_range", null)
    }
  }
}

dynamic secodary_ip_range の中で var.secondary_ip_ranges のキーと var.subnet のキーを突き合わせて存在する場合は secondary_ip_range を設定するようにしている。

実際に使う時は以下のようにする。

# main.tf
# セカンダリ IP レンジが存在するサブネット
module "hoge" {
  source = "./modules/foo"

...

  subnets = {
    "production" = {
      ip_cidr_range = "10.244.0.0/20"
    }
    "staging" = {
      ip_cidr_range = "10.244.16.0/20"
    }
  }

  secondary_ip_ranges = {
    "production" = [
      {
        range_name    = "subnet-01"
        ip_cidr_range = "10.244.192.0/20"
      }
      {
        range_name    = "subnet-02"
        ip_cidr_range = "10.244.208.0/20"
      }
    ]
    "staging" = [
      {
        range_name    = "subnet-01"
        ip_cidr_range = "10.244.224.0/20"
      }
      {
        range_name    = "subnet-02"
        ip_cidr_range = "10.244.240.0/20"
      }
    ]
  }
}

# セカンダリ IP レンジが存在しないサブネット
module "fuga" {
  source = "./modules/foo"

...

  subnets = {
    "production" = {
      ip_cidr_range = "10.245.0.0/20"
    }
    "staging" = {
      ip_cidr_range = "10.245.16.0/20"
    }
  }
} 

cdktf diff の内容を詳しく調べる

最近は Terraform を触っているんだけど、AWS-CDK に慣れた身としては HCL を直接書くのは厳しいので、cdktf を試している。cdktf とは AWS-CDK のように Terraform の HCL を Typescript などで記述できるツールです。

github.com

cdktf を使うにあたり厳しさを感じていたのが cdktf diff で変更されるリソースの内容がわかりにくいという問題があった。cdktf の使い方とかは別エントリで書こうと思っていたんだけど、terraform と cdktf を行き来するなかで現状における解決策を見つけたのでメモしておく。

まず、cdktf diff したら cdktf.out ディレクトリが作成される。このディレクトリは terraform init して terraform plan -out plan した内容が出力されている。cdktf.out の中身は以下のようになっている。

cdktf.out
├── .terraform
│   └── plugins
│       ├── registry.terraform.io
│       │   └── hashicorp
│       │       └── google
│       │           └── 3.42.0
│       │               └── darwin_amd64
│       └── selections.json
├── cdk.tf.json
└── plan

cdktf.out/cdk.tf.json は謎だけど、 cdktf.out/plan が今回の本丸。

cdktf.out ディレクトリを working ディレクトリとして、terraform show plan すると terraform plan したときの出力が得られる。

$ cd cdktf.out
$ terraform show plan

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

Plan: 0 to add, 0 to change, 0 to destroy.
$

本当は AWS-CDK 並にいい感じに cdktf diff が出力して欲しいところだけど、正式リリース前なのでこんなもんでしょという感じではある。

Terraform で Google Beta API をつかう

2行でまとめると

  • プロバイダは googlegoogle-beta の両方を定義する
  • 必要な時だけ google-beta プロバイダをつかうと宣言する

という感じ。

モノによっては Google Beta API をつかう必要がある。Terraform の Google Cloud 向けプロバイダでは API の種別ごとに googlegoogle-beta というプロバイダが用意されている。googlegoogle-beta については Google Provider Versions - Terraform by HashiCorp で説明されている。

具体的な例は以下のとおり。

2つのプロバイダを定義する。

provider "google" {
  credentials = file("account.json")
  project     = "sample"
  region      = "asia-northeast1"
}

provider "google-beta" {
  credentials = file("account.json")
  project     = "sample"
  region      = "asia-northeast1"
}

shared-vpc-host-projectというプロジェクトの定義では google プロバイダを使用するが、shared-vpc-host-project を共有 VPC として有効にするときは google-beta プロバイダを利用する。

resource "google_project" "shared_vpc_host_project" {
  name       = "shared VPC Host Project"
  project_id = "shared-vpc-host-project"
}

resource "google_compute_shared_vpc_host_project" "shared_vpc_host_project" {
 # フォルダ単位でロールをつけているので beta API をつかう必要がある
  provider = google-beta
  project  = google_project.shared_vpc_host_project.project_id
}