ServiceAccountのトークンで使われているJWTの処理はどこに書かれているのか

KubernetesのServiceAccountと、そのトークンで使われているJWTのSign/Verifyのコードについて追った。ServiceAccountとJWTについての説明はしません。

ServiceAccountのトークンはServiceAccountと紐づくSecretに含まれている。このSecretはkube-controller-manager内のtoken-controllerが作成している。

https://github.com/kubernetes/kubernetes/blob/v1.16.2/pkg/controller/serviceaccount/tokens_controller.go#L383-L400

トークンの署名にはkube-controller-managerの引数である --service-account-private-key-file で渡した秘密鍵を使う。

トークンはKubernetesのAPIを利用する際の認証・認可に使われる。トークンのベリファイにはkube-apiserverの引数である --service-account-key-file で渡した秘密鍵もしくは公開鍵を使う。指定していない場合は --tls-private-key-file で渡した鍵が使われる。--service-account-key-file を渡している場合、jwtTokenAuthenticatorがauthenticatorsとして使えるようになる。

https://github.com/kubernetes/kubernetes/blob/v1.16.2/pkg/kubeapiserver/authenticator/config.go#L139-L145

jwtTokenAuthenticatorはトークンのベリファイをAuthenticateTokenメソッドで行う。

最後に、Sign/ParseSignの練習コードを乗せておく。RS256で、秘密鍵と証明書を使っている。https://github.com/takaishi/hello2019/blob/master/jwt/rs256/main.go にもある。

package main

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"github.com/k0kubun/pp"
	"gopkg.in/square/go-jose.v2"
	"gopkg.in/square/go-jose.v2/jwt"
	"io/ioutil"
	"time"
)

func readPrivateKey(path string) (*rsa.PrivateKey, error) {
	privateKeyData, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	privateKeyBlock, _ := pem.Decode(privateKeyData)
	if privateKeyBlock == nil {
		panic(errors.New("invalid private key data"))
	}
	if privateKeyBlock.Type != "RSA PRIVATE KEY" {
		panic(errors.New(fmt.Sprintf("invalid private key type : %s", privateKeyBlock.Type)))
	}

	return x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
}

func readCertificate(path string) (*x509.Certificate, error) {
	certificateData, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	certificateBlock, _ := pem.Decode(certificateData)
	if certificateBlock == nil {
		panic(errors.New("invalid private key data"))
	}
	return x509.ParseCertificate(certificateBlock.Bytes)
}

func main() {
	privateKey, err := readPrivateKey("./service-account-key.pem")
	if err != nil {
		panic(err)
	}

	privateJWK := &jose.JSONWebKey{
		Algorithm: string(jose.RS256),
		Key:       privateKey,
		Use:       "sig",
	}

	signer, err := jose.NewSigner(
		jose.SigningKey{
			Key:       privateJWK,
			Algorithm: jose.RS256,
		},
		nil,
	)

	cl := jwt.Claims{
		Subject:   "subject",
		Issuer:    "issuer",
		NotBefore: jwt.NewNumericDate(time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)),
		Audience:  jwt.Audience{"leela", "fry"},
		ID:        "ryo",
	}
	raw, err := jwt.Signed(signer).Claims(cl).CompactSerialize()
	if err != nil {
		panic(err)
	}

	fmt.Println(raw)

	tok, err := jwt.ParseSigned(raw)
	if err != nil {
		panic(err)
	}

	certificate, err := readCertificate("./service-account.pem")
	if err != nil {
		panic(err)
	}

	out := jwt.Claims{}
	err = tok.Claims(certificate.PublicKey, &out)
	if err != nil {
		panic(err)
	}
	pp.Print(out)

}

kelm v0.0.5をリリースした

https://github.com/takaishi/kelm/releases/tag/v0.0.5

アクションに対してカスタム変数を渡せるようになったのと、フラグにnamespaceとkindを追加した。これにより、選択したNodeのInternalIPをjsonpathで取得して変数に格納、アクションに渡すということが実現できるようになり、ノードへ楽にSSHできるようになった。

以下、設定サンプル。

ノードを選択してSSHする

~/.kelm に以下のように設定する。

---
actions:
  nodes:
    - name: "ssh"
      variables:
        - name: address
          jsonpath: '{.status.addresses[?(@.type=="InternalIP")].address}'
      command: 'ssh {{ .address }}'

そして、以下のコマンドを実行。

$ kelm -c ./config.yaml --namespace default --kind nodes --action ssh 

デプロイメントを選択してsternする

~/.kelmに以下のように設定し、deploymentのアクションでsternを選択すればOK。

---
actions:
  deployments:
    - name: "stern"
      command: "stern -n {{ .Namespace }} {{ .Obj.metadata.name }}"

PowerColor Mini Proを購入した

普段はMacbook Proを27インチ4Kモニターに繋いで、クラムシェルモードで使っているのだけど、どうもGolandやIntellijの描画がモタついてイラっとすることが多かった。どうもGPU性能が足りてないらしく、eGPUを使えば解消する…という話らしく、@jacopenが購入してサクサクになっていたので数ヶ月遅れで買うことにした。

ひとまず設置して繋いだところだけど、常時ファンが回っているのでその音がするなあという感想。後ACアダプターでかい。有線LAN刺せたりUSBハブになるのでケーブリングも見直していこうかなっと。猫の毛がかなり吸われそうなので、定期的な掃除は必要だろうなあ。

kelmというk8sのリソースとアクションをインタラクティブに選択・実行するツールを作っている

https://github.com/takaishi/kelm

少し前に投稿したプロトタイプを綺麗にしてパッケージングしました。

これは何か?

k8sの名前空間、Kind、リソース、アクションを順番に選択することで任意の処理を実行するためのツールです。プロトタイプの動画を見ると雰囲気はわかると思う。

k8s、クラスターから始まって名前空間、Kind、リソースなど選択項目がすごく多くて、 kubectl get pods した後にPod名をコピーして kubectl describe pod XXXXX する、というような操作が多い。これを楽にしたいなあと思い、kelmを作ることにした。

体験としてはpecoのようなツールや、Emacsの拡張であるHelmをベースに設計している。特に、アクションについては利用者が任意の処理をあれこれできるようにしたいと考えており、選択したリソースを外部コマンドに渡したりできる。なので、kubectlを呼び出すだけではなくsternに渡してログを読んだり、将来的には実行結果をjsonで出力してjqで加工、クリップボードにコピーするということも視野に入れている。

最近聞いているPodCast

何聞くことが多いかなと思って整理してみた。皆さんはどういうPodCastを聞いていますか?これおすすめだよ、というものがあれば教えてください。

Fukabori.fm

タイトルの通り、毎回特定の技術について深掘りしていく番組。技術といってもネットワークのようなレイヤから組織まで、幅広くていい。

Misreading Chat

CSの論文を読んでそれについて話をする番組。自分の専門領域に近いテーマの回を聞くことが多い。

ドングリFM

雑談系の番組。技術ネタはほとんどないけど、幅広い内容でゆるく話していて楽に聞ける。10〜15分でさっと聞けるのもいい。

Remark Life

Twitterで宣伝かRTを見て知った。最近始まった番組。IT/テクノロジー/ガジェット系らしいが、まだ1回しかないのでどういう方向性になるのか謎。

EM.FM

エンジニアリングマネジメントをテーマとした番組。僕はそういうロールではないのだけど、追いかけているテーマなので聞いている。

流行りモノ通信簿

身近に流行っているものについて話す番組。最近知ったけど、結構昔からやっているみたい。会話のテンポがよいので聞いてて楽しい。