repl.info

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)



}