2018-09-11

env-injectorコードリーディング

env-injectorのコードリーディングをしました。

env-injectorとはAWS System ManagerのパラメータストアやAWS Secrets Managerからクレデンシャルを取得し、環境変数としてセットした上で任意のコマンドを実行するgolang製のCLIツールになります。

たとえば

export ENV_INJECTOR_SECRET_NAME=hoge/fuga
env-injector env

とコマンドを叩くと、AWS Secrets Managerのシークレット名がhoge/fugaのシークレットを取得して、キーを環境変数名として値をセットしてからenvコマンドで環境変数を表示することになります。

コードリーディング

main.goから見ていきます。

func main() {
	envinjector.InjectEnviron()

	args := os.Args
	if len(args) <= 1 {
		log.Fatal("missing command")
	}

	path, err := exec.LookPath(args[1])
	if err != nil {
		log.Fatal(err)
	}
	err = syscall.Exec(path, args[1:], os.Environ())
	if err != nil {
		log.Fatal(err)
	}
}

envinjector.InjectEnviron()で環境変数をセットし、syscall.Execで引数のコマンドをexecしています。

InjectEnvironは以下のような実装になっています。ケースが多いのでSecrets Managerのケースを追っていきます(ENV_INJECTOR_SECRET_NAMEがセットされている場合です)

package envinjector

import "os"

// InjectEnviron injects environment variables from AWS Parameter Store and/or SecretsManager
func InjectEnviron() {
	if path := os.Getenv("ENV_INJECTOR_META_CONFIG"); path != "" {
		injectEnvironViaMetaConfig(path)
	} else {
		trace("no meta config path specified, skipping injection via meta config")
	}

	if name := os.Getenv("ENV_INJECTOR_SECRET_NAME"); name != "" {
		injectEnvironSecretManager(name, noop)
	} else {
		trace("no secret name specified, skipping injection by SecretsManager")
	}

	if path := os.Getenv("ENV_INJECTOR_PATH"); path != "" {
		injectEnvironByPath(path, noop)
	} else {
		trace("no parameter path specified, skipping injection by path")
	}

	if prefix := os.Getenv("ENV_INJECTOR_PREFIX"); prefix != "" {
		injectEnvironByPrefix(prefix)
	} else {
		trace("no parameter prefix specified, skipping injection by prefix")
	}
}

injectEnvironSecretManagerは以下のような実装になっています。getService().secretsManagerはaws-sdk-goのSecrets Managerのクライアントが入ります。

func injectEnvironSecretManager(name string, decorator envKeyDecorator) {
	tracef("secret name: %s", name)

	svc := getService().secretsManager
	ret, err := svc.GetSecretValue(&secretsmanager.GetSecretValueInput{
		SecretId: aws.String(name),
	})
	if err != nil {
		logger.Fatalf("secretsmanager:GetSecretValue failed. (name: %s)\n %v", name, err)
	}
	secrets := make(map[string]string)
	err = json.Unmarshal([]byte(aws.StringValue(ret.SecretString)), &secrets)
	if err != nil {
		logger.Fatalf("secretsmanager:GetSecretValue returns invalid json. (name: %s)\n %v", name, err)
	}
	for key, val := range secrets {
		key = decorator.decorate(key)
		if os.Getenv(key) == "" {
			os.Setenv(key, val)
			tracef("env injected: %s", key)
		}
	}
}

GetSecretValueでシークレットを取得して、実データであるSecretStringのJSONをUnmarshalした後、os.Setenvで環境変数をセットしています。

このエントリーをはてなブックマークに追加