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で環境変数をセットしています。