From 85da85271f723aeeab712f860d45de52f84ef69f Mon Sep 17 00:00:00 2001 From: Gennady Lipenkov Date: Tue, 9 Jun 2020 11:06:12 +0300 Subject: [PATCH] Support SA Key authentication in Yandex Export post-processor --- .../yandex-export/post-processor.go | 35 +++++++++- .../yandex-export/post-processor.hcl2spec.go | 34 +++++----- .../yandex-export/post-processor_test.go | 66 +++++++++++++++++++ .../yandex-export/testdata/fake-sa-key.json | 9 +++ .../yandex-export/Config-not-required.mdx | 4 ++ 5 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 post-processor/yandex-export/post-processor_test.go create mode 100644 post-processor/yandex-export/testdata/fake-sa-key.json diff --git a/post-processor/yandex-export/post-processor.go b/post-processor/yandex-export/post-processor.go index 7987e9d78..8e75ac807 100644 --- a/post-processor/yandex-export/post-processor.go +++ b/post-processor/yandex-export/post-processor.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "github.com/yandex-cloud/go-sdk/iamkey" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/builder/yandex" "github.com/hashicorp/packer/common" @@ -46,6 +48,10 @@ type Config struct { // OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set // value by environment variable YC_TOKEN. Token string `mapstructure:"token" required:"false"` + // Path to file with Service Account key in json format. This + // is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable + // YC_SERVICE_ACCOUNT_KEY_FILE. + ServiceAccountKeyFile string `mapstructure:"service_account_key_file" required:"false"` ctx interpolate.Context } @@ -78,6 +84,31 @@ func (p *PostProcessor) Configure(raws ...interface{}) error { p.config.Token = os.Getenv("YC_TOKEN") } + if p.config.ServiceAccountKeyFile == "" { + p.config.ServiceAccountKeyFile = os.Getenv("YC_SERVICE_ACCOUNT_KEY_FILE") + } + + if p.config.Token == "" && p.config.ServiceAccountKeyFile == "" { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("a token or service account key file must be specified")) + } + + if p.config.Token != "" && p.config.ServiceAccountKeyFile != "" { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("one of token or service account key file must be specified, not both")) + } + + if p.config.Token != "" { + packer.LogSecretFilter.Set(p.config.Token) + } + + if p.config.ServiceAccountKeyFile != "" { + if _, err := iamkey.ReadFromJSONFile(p.config.ServiceAccountKeyFile); err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("fail to read service account key file: %s", err)) + } + } + if p.config.FolderID == "" { p.config.FolderID = os.Getenv("YC_FOLDER_ID") } @@ -133,6 +164,7 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact yandexConfig := ycSaneDefaults() yandexConfig.Token = p.config.Token + yandexConfig.ServiceAccountKeyFile = p.config.ServiceAccountKeyFile yandexConfig.DiskName = exporterName yandexConfig.InstanceName = exporterName yandexConfig.DiskSizeGb = p.config.DiskSizeGb @@ -144,8 +176,9 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact if p.config.ServiceAccountID != "" { yandexConfig.ServiceAccountID = p.config.ServiceAccountID } + if p.config.PlatformID != "" { - yandexConfig.ServiceAccountID = p.config.ServiceAccountID + yandexConfig.PlatformID = p.config.PlatformID } driver, err := yandex.NewDriverYC(ui, &yandexConfig) diff --git a/post-processor/yandex-export/post-processor.hcl2spec.go b/post-processor/yandex-export/post-processor.hcl2spec.go index a100724ef..a049ce8aa 100644 --- a/post-processor/yandex-export/post-processor.hcl2spec.go +++ b/post-processor/yandex-export/post-processor.hcl2spec.go @@ -9,22 +9,23 @@ import ( // FlatConfig is an auto-generated flat version of Config. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatConfig struct { - PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` - PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` - PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` - PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` - PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` - PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` - PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` - Paths []string `mapstructure:"paths" required:"true" cty:"paths" hcl:"paths"` - FolderID *string `mapstructure:"folder_id" required:"true" cty:"folder_id" hcl:"folder_id"` - ServiceAccountID *string `mapstructure:"service_account_id" required:"true" cty:"service_account_id" hcl:"service_account_id"` - DiskSizeGb *int `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"` - DiskType *string `mapstructure:"disk_type" required:"false" cty:"disk_type" hcl:"disk_type"` - PlatformID *string `mapstructure:"platform_id" required:"false" cty:"platform_id" hcl:"platform_id"` - SubnetID *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"` - Zone *string `mapstructure:"zone" required:"false" cty:"zone" hcl:"zone"` - Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"` + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + Paths []string `mapstructure:"paths" required:"true" cty:"paths" hcl:"paths"` + FolderID *string `mapstructure:"folder_id" required:"true" cty:"folder_id" hcl:"folder_id"` + ServiceAccountID *string `mapstructure:"service_account_id" required:"true" cty:"service_account_id" hcl:"service_account_id"` + DiskSizeGb *int `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"` + DiskType *string `mapstructure:"disk_type" required:"false" cty:"disk_type" hcl:"disk_type"` + PlatformID *string `mapstructure:"platform_id" required:"false" cty:"platform_id" hcl:"platform_id"` + SubnetID *string `mapstructure:"subnet_id" required:"false" cty:"subnet_id" hcl:"subnet_id"` + Zone *string `mapstructure:"zone" required:"false" cty:"zone" hcl:"zone"` + Token *string `mapstructure:"token" required:"false" cty:"token" hcl:"token"` + ServiceAccountKeyFile *string `mapstructure:"service_account_key_file" required:"false" cty:"service_account_key_file" hcl:"service_account_key_file"` } // FlatMapstructure returns a new FlatConfig. @@ -55,6 +56,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "subnet_id": &hcldec.AttrSpec{Name: "subnet_id", Type: cty.String, Required: false}, "zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false}, "token": &hcldec.AttrSpec{Name: "token", Type: cty.String, Required: false}, + "service_account_key_file": &hcldec.AttrSpec{Name: "service_account_key_file", Type: cty.String, Required: false}, } return s } diff --git a/post-processor/yandex-export/post-processor_test.go b/post-processor/yandex-export/post-processor_test.go new file mode 100644 index 000000000..82cc21a39 --- /dev/null +++ b/post-processor/yandex-export/post-processor_test.go @@ -0,0 +1,66 @@ +package yandexexport + +import ( + "testing" + + "github.com/hashicorp/packer/helper/multistep" +) + +func TestPostProcessor_Configure(t *testing.T) { + type fields struct { + config Config + runner multistep.Runner + } + type args struct { + raws []interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "no one creds", + fields: fields{ + config: Config{ + Token: "", + ServiceAccountKeyFile: "", + }, + }, + wantErr: true, + }, + { + name: "both token and sa key file", + fields: fields{ + config: Config{ + Token: "some-value", + ServiceAccountKeyFile: "path/not-exist.file", + }, + }, + wantErr: true, + }, + { + name: "use sa key file", + fields: fields{ + config: Config{ + Token: "", + ServiceAccountKeyFile: "testdata/fake-sa-key.json", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.fields.config.Paths = []string{"some-path"} // make Paths not empty + p := &PostProcessor{ + config: tt.fields.config, + runner: tt.fields.runner, + } + if err := p.Configure(tt.args.raws...); (err != nil) != tt.wantErr { + t.Errorf("Configure() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/post-processor/yandex-export/testdata/fake-sa-key.json b/post-processor/yandex-export/testdata/fake-sa-key.json new file mode 100644 index 000000000..df79c0126 --- /dev/null +++ b/post-processor/yandex-export/testdata/fake-sa-key.json @@ -0,0 +1,9 @@ +{ + "id": "ajeboa0du6edu6m43c3t", + "service_account_id": "ajeq7dsmihqple6761c5", + "created_at": "2018-11-19T13:38:09Z", + "description": "description", + "key_algorithm": "RSA_4096", + "public_key": "-----BEGIN PUBLIC KEY-----\nMIICCgKCAgEAo/s1lN5vFpFNJvS/l+yRilQHAPDeC3JqBwpLstbqJXW4kAUaKKoe\nxkIuJuPUKOUcd/JE3LXOEt/LOFb9mkCRdpjaIW7Jd5Fw0kTHIZ5rDoq7DZx0LV9b\nGJNskdccd6M6stb1GEqVuGpVcyXMCH8tMSG3c85DkcAg0cxXgyrirAzHMPiWSTpj\nJjICkxXRVj01Xq7dIDqL2LSMrZ2kLda5m+CnfscUbwnGRPPoEg20jLiEgBM2o43e\nhpWko1NStRR5fMQcQSUBbdtvbfPracjZz2/fq4fZfqlnObgq3WpYpdGynniLH3i5\nbxPM3ufYL3HY2w5aIOY6KIwMKLf3WYlug90ieviMYAvCukrCASwyqBQlt3MKCHlN\nIcebZXJDQ1VSBuEs+4qXYlhG1p+5C07zahzigNNTm6rEo47FFfClF04mv2uJN42F\nfWlEPR+V9JHBcfcBCdvyhiGzftl/vDo2NdO751ETIhyNKzxM/Ve2PR9h/qcuEatC\nLlXUA+40epNNHbSxAauxcngyrtkn7FZAEhdjyTtx46sELyb90Z56WgnbNUUGnsS/\nHBnBy5z8RyCmI5MjTC2NtplVqtAWkG+x59mU3GoCeuI8EaNtu2YPXhl1ovRkS4NB\n1G0F4c5FiJ27/E2MbNKlV5iw9ICcDforATYTeqiXbkkEKqIIiZYZWOsCAwEAAQ==\n-----END PUBLIC KEY-----\n", + "private_key": "-----BEGIN PRIVATE KEY-----\n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\n-----END PRIVATE KEY-----\n" +} \ No newline at end of file diff --git a/website/pages/partials/post-processor/yandex-export/Config-not-required.mdx b/website/pages/partials/post-processor/yandex-export/Config-not-required.mdx index 9facc490e..632c272d4 100644 --- a/website/pages/partials/post-processor/yandex-export/Config-not-required.mdx +++ b/website/pages/partials/post-processor/yandex-export/Config-not-required.mdx @@ -14,4 +14,8 @@ - `token` (string) - OAuth token to use to authenticate to Yandex.Cloud. Alternatively you may set value by environment variable YC_TOKEN. + +- `service_account_key_file` (string) - Path to file with Service Account key in json format. This + is an alternative method to authenticate to Yandex.Cloud. Alternatively you may set environment variable + YC_SERVICE_ACCOUNT_KEY_FILE. \ No newline at end of file