From f625c985aff69eea156dacbcb510683cb2f1aaca Mon Sep 17 00:00:00 2001 From: Eric Herot Date: Fri, 21 Aug 2015 15:06:24 -0700 Subject: [PATCH 1/2] Chef-client provisioner: Add encrypted data bag secret support (Fixes #1945) --- provisioner/chef-client/provisioner.go | 101 +++++++++++++------- provisioner/chef-client/provisioner_test.go | 43 +++++++++ 2 files changed, 111 insertions(+), 33 deletions(-) diff --git a/provisioner/chef-client/provisioner.go b/provisioner/chef-client/provisioner.go index 62b3732de..c9edd1953 100644 --- a/provisioner/chef-client/provisioner.go +++ b/provisioner/chef-client/provisioner.go @@ -22,23 +22,24 @@ import ( type Config struct { common.PackerConfig `mapstructure:",squash"` - ChefEnvironment string `mapstructure:"chef_environment"` - SslVerifyMode string `mapstructure:"ssl_verify_mode"` - ConfigTemplate string `mapstructure:"config_template"` - ExecuteCommand string `mapstructure:"execute_command"` - InstallCommand string `mapstructure:"install_command"` - Json map[string]interface{} - NodeName string `mapstructure:"node_name"` - PreventSudo bool `mapstructure:"prevent_sudo"` - RunList []string `mapstructure:"run_list"` - ServerUrl string `mapstructure:"server_url"` - SkipCleanClient bool `mapstructure:"skip_clean_client"` - SkipCleanNode bool `mapstructure:"skip_clean_node"` - SkipInstall bool `mapstructure:"skip_install"` - StagingDir string `mapstructure:"staging_directory"` - ClientKey string `mapstructure:"client_key"` - ValidationKeyPath string `mapstructure:"validation_key_path"` - ValidationClientName string `mapstructure:"validation_client_name"` + ChefEnvironment string `mapstructure:"chef_environment"` + EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"` + SslVerifyMode string `mapstructure:"ssl_verify_mode"` + ConfigTemplate string `mapstructure:"config_template"` + ExecuteCommand string `mapstructure:"execute_command"` + InstallCommand string `mapstructure:"install_command"` + Json map[string]interface{} + NodeName string `mapstructure:"node_name"` + PreventSudo bool `mapstructure:"prevent_sudo"` + RunList []string `mapstructure:"run_list"` + ServerUrl string `mapstructure:"server_url"` + SkipCleanClient bool `mapstructure:"skip_clean_client"` + SkipCleanNode bool `mapstructure:"skip_clean_node"` + SkipInstall bool `mapstructure:"skip_install"` + StagingDir string `mapstructure:"staging_directory"` + ClientKey string `mapstructure:"client_key"` + ValidationKeyPath string `mapstructure:"validation_key_path"` + ValidationClientName string `mapstructure:"validation_client_name"` ctx interpolate.Context } @@ -48,13 +49,15 @@ type Provisioner struct { } type ConfigTemplate struct { - NodeName string - ServerUrl string - ClientKey string - ValidationKeyPath string - ValidationClientName string - ChefEnvironment string - SslVerifyMode string + NodeName string + ServerUrl string + ClientKey string + ValidationKeyPath string + ValidationClientName string + EncryptedDataBagSecretPath string + ChefEnvironment string + SslVerifyMode string + HasEncryptedDataBagSecretPath bool } type ExecuteTemplate struct { @@ -118,6 +121,15 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { errs, fmt.Errorf("server_url must be set")) } + if p.config.EncryptedDataBagSecretPath != "" { + pFileInfo, err := os.Stat(p.config.EncryptedDataBagSecretPath) + + if err != nil || pFileInfo.IsDir() { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Bad encrypted data bag secret '%s': %s", p.config.EncryptedDataBagSecretPath, err)) + } + } + jsonValid := true for k, v := range p.config.Json { p.config.Json[k], err = p.deepJsonFix(k, v) @@ -175,8 +187,16 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { } } + encryptedDataBagSecretPath := "" + if p.config.EncryptedDataBagSecretPath != "" { + encryptedDataBagSecretPath = fmt.Sprintf("%s/encrypted_data_bag_secret", p.config.StagingDir) + if err := p.uploadFile(ui, comm, encryptedDataBagSecretPath, p.config.EncryptedDataBagSecretPath); err != nil { + return fmt.Errorf("Error uploading encrypted data bag secret: %s", err) + } + } + configPath, err := p.createConfig( - ui, comm, nodeName, serverUrl, p.config.ClientKey, remoteValidationKeyPath, p.config.ValidationClientName, p.config.ChefEnvironment, p.config.SslVerifyMode) + ui, comm, nodeName, serverUrl, p.config.ClientKey, remoteValidationKeyPath, p.config.ValidationClientName, encryptedDataBagSecretPath, p.config.ChefEnvironment, p.config.SslVerifyMode) if err != nil { return fmt.Errorf("Error creating Chef config file: %s", err) } @@ -236,7 +256,17 @@ func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, ds return comm.UploadDir(dst, src, nil) } -func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, nodeName string, serverUrl string, clientKey string, remoteKeyPath string, validationClientName string, chefEnvironment string, sslVerifyMode string) (string, error) { +func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst string, src string) error { + f, err := os.Open(src) + if err != nil { + return err + } + defer f.Close() + + return comm.Upload(dst, f, nil) +} + +func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, nodeName string, serverUrl string, clientKey string, remoteKeyPath string, validationClientName string, encryptedDataBagSecretPath string, chefEnvironment string, sslVerifyMode string) (string, error) { ui.Message("Creating configuration file 'client.rb'") // Read the template @@ -258,13 +288,15 @@ func (p *Provisioner) createConfig(ui packer.Ui, comm packer.Communicator, nodeN ctx := p.config.ctx ctx.Data = &ConfigTemplate{ - NodeName: nodeName, - ServerUrl: serverUrl, - ClientKey: clientKey, - ValidationKeyPath: remoteKeyPath, - ValidationClientName: validationClientName, - ChefEnvironment: chefEnvironment, - SslVerifyMode: sslVerifyMode, + NodeName: nodeName, + ServerUrl: serverUrl, + ClientKey: clientKey, + ValidationKeyPath: remoteKeyPath, + ValidationClientName: validationClientName, + ChefEnvironment: chefEnvironment, + SslVerifyMode: sslVerifyMode, + EncryptedDataBagSecretPath: encryptedDataBagSecretPath, + HasEncryptedDataBagSecretPath: encryptedDataBagSecretPath != "", } configString, err := interpolate.Render(tpl, &ctx) if err != nil { @@ -587,6 +619,9 @@ log_level :info log_location STDOUT chef_server_url "{{.ServerUrl}}" client_key "{{.ClientKey}}" +{{if .HasEncryptedDataBagSecretPath}} +encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}" +{{end}} {{if ne .ValidationClientName ""}} validation_client_name "{{.ValidationClientName}}" {{else}} diff --git a/provisioner/chef-client/provisioner_test.go b/provisioner/chef-client/provisioner_test.go index 934403040..10477aa39 100644 --- a/provisioner/chef-client/provisioner_test.go +++ b/provisioner/chef-client/provisioner_test.go @@ -138,6 +138,49 @@ func TestProvisionerPrepare_serverUrl(t *testing.T) { } } +func TestProvisionerPrepare_encryptedDataBagSecretPath(t *testing.T) { + var err error + var p Provisioner + + // Test no config template + config := testConfig() + delete(config, "encrypted_data_bag_secret_path") + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Test with a file + tf, err := ioutil.TempFile("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(tf.Name()) + + config = testConfig() + config["encrypted_data_bag_secret_path"] = tf.Name() + p = Provisioner{} + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Test with a directory + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + config = testConfig() + config["encrypted_data_bag_secret_path"] = td + p = Provisioner{} + err = p.Prepare(config) + if err == nil { + t.Fatal("should have err") + } +} + func TestProvisioner_createDir(t *testing.T) { p1 := &Provisioner{config: Config{PreventSudo: true}} p2 := &Provisioner{config: Config{PreventSudo: false}} From 5091f0a221dfb35eb49675c28cbdd7dbcca8bc8a Mon Sep 17 00:00:00 2001 From: Eric Herot Date: Tue, 29 Sep 2015 14:52:49 -0400 Subject: [PATCH 2/2] Add documentation about the encrypted data bag secret flag to the Chef Client provisioners section --- website/source/docs/provisioners/chef-client.html.markdown | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/source/docs/provisioners/chef-client.html.markdown b/website/source/docs/provisioners/chef-client.html.markdown index e655a4622..19d28ca5c 100644 --- a/website/source/docs/provisioners/chef-client.html.markdown +++ b/website/source/docs/provisioners/chef-client.html.markdown @@ -50,6 +50,10 @@ configuration is actually required. should use a custom configuration template. See the dedicated "Chef Configuration" section below for more details. +- `encrypted_data_bag_secret_path` (string) - The path to the file containing + the secret for encrypted data bags. By default, this is empty, so no secret + will be available. + - `execute_command` (string) - The command used to execute Chef. This has various [configuration template variables](/docs/templates/configuration-templates.html) available. See @@ -136,6 +140,7 @@ This template is a [configuration template](/docs/templates/configuration-templates.html) and has a set of variables available to use: +- `EncryptedDataBagSecretPath` - The path to the encrypted data bag secret - `NodeName` - The node name set in the configuration. - `ServerUrl` - The URL of the Chef Server set in the configuration. - `ValidationKeyPath` - Path to the validation key, if it is set.