From 18b1681a72865811331167c0cffd032f217d0f66 Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Thu, 22 Jan 2015 10:32:06 -0800 Subject: moving bootstrap templates directory --- lib/chef/knife/bootstrap.rb | 2 +- lib/chef/knife/bootstrap/README.md | 12 ---- lib/chef/knife/bootstrap/archlinux-gems.erb | 67 ------------------ lib/chef/knife/bootstrap/chef-aix.erb | 63 ----------------- lib/chef/knife/bootstrap/chef-full.erb | 79 ---------------------- lib/chef/knife/bootstrap/templates/README.md | 12 ++++ .../knife/bootstrap/templates/archlinux-gems.erb | 67 ++++++++++++++++++ lib/chef/knife/bootstrap/templates/chef-aix.erb | 63 +++++++++++++++++ lib/chef/knife/bootstrap/templates/chef-full.erb | 79 ++++++++++++++++++++++ 9 files changed, 222 insertions(+), 222 deletions(-) delete mode 100644 lib/chef/knife/bootstrap/README.md delete mode 100644 lib/chef/knife/bootstrap/archlinux-gems.erb delete mode 100644 lib/chef/knife/bootstrap/chef-aix.erb delete mode 100644 lib/chef/knife/bootstrap/chef-full.erb create mode 100644 lib/chef/knife/bootstrap/templates/README.md create mode 100644 lib/chef/knife/bootstrap/templates/archlinux-gems.erb create mode 100644 lib/chef/knife/bootstrap/templates/chef-aix.erb create mode 100644 lib/chef/knife/bootstrap/templates/chef-full.erb (limited to 'lib/chef/knife') diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index 19a329199d..dc41e67c8d 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -216,7 +216,7 @@ class Chef # Otherwise search the template directories until we find the right one bootstrap_files = [] - bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap', "#{template}.erb") + bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb") bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir bootstrap_files << File.join(ENV['HOME'], '.chef', 'bootstrap', "#{template}.erb") if ENV['HOME'] bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb")) diff --git a/lib/chef/knife/bootstrap/README.md b/lib/chef/knife/bootstrap/README.md deleted file mode 100644 index 13a0fe7ada..0000000000 --- a/lib/chef/knife/bootstrap/README.md +++ /dev/null @@ -1,12 +0,0 @@ -This directory contains bootstrap templates which can be used with the -d flag -to 'knife bootstrap' to install Chef in different ways. To simplify installation, -and reduce the matrix of common installation patterns to support, we have -standardized on the [Omnibus](https://github.com/opscode/omnibus-ruby) built installation -packages. - -The 'chef-full' template downloads a script which is used to determine the correct -Omnibus package for this system from the [Omnitruck](https://github.com/opscode/opscode-omnitruck) API. All other templates in this directory are deprecated and will be removed -in the future. - -You can still utilize custom bootstrap templates on your system if your installation -needs are unique. Additional information can be found on the [docs site](http://docs.opscode.com/knife_bootstrap.html#custom-templates). \ No newline at end of file diff --git a/lib/chef/knife/bootstrap/archlinux-gems.erb b/lib/chef/knife/bootstrap/archlinux-gems.erb deleted file mode 100644 index 581293daa3..0000000000 --- a/lib/chef/knife/bootstrap/archlinux-gems.erb +++ /dev/null @@ -1,67 +0,0 @@ -bash -c ' -<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%> - -if [ ! -f /usr/bin/chef-client ]; then - pacman -Syy - pacman -S --noconfirm ruby ntp base-devel - ntpdate -u pool.ntp.org - gem install ohai --no-user-install --no-document --verbose - gem install chef --no-user-install --no-document --verbose <%= Chef::VERSION %> -fi - -mkdir -p /etc/chef - -cat > /etc/chef/validation.pem <<'EOP' -<%= validation_key %> -EOP -chmod 0600 /etc/chef/validation.pem - -<% if encrypted_data_bag_secret -%> -cat > /etc/chef/encrypted_data_bag_secret <<'EOP' -<%= encrypted_data_bag_secret %> -EOP -chmod 0600 /etc/chef/encrypted_data_bag_secret -<% end -%> - -<% unless trusted_certs.empty? -%> -mkdir -p /etc/chef/trusted_certs -<%= trusted_certs %> -<% end -%> - -<%# Generate Ohai Hints -%> -<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> -mkdir -p /etc/chef/ohai/hints - -<% @chef_config[:knife][:hints].each do |name, hash| -%> -cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' -<%= Chef::JSONCompat.to_json(hash) %> -EOP -<% end -%> -<% end -%> - -cat > /etc/chef/client.rb <<'EOP' -log_level :info -log_location STDOUT -chef_server_url "<%= @chef_config[:chef_server_url] %>" -validation_client_name "<%= @chef_config[:validation_client_name] %>" -<% if @config[:chef_node_name] -%> -node_name "<%= @config[:chef_node_name] %>" -<% else -%> -# Using default node name (fqdn) -<% end -%> -# ArchLinux follows the Filesystem Hierarchy Standard -file_cache_path "/var/cache/chef" -file_backup_path "/var/lib/chef/backup" -pid_file "/var/run/chef/client.pid" -cache_options({ :path => "/var/cache/chef/checksums", :skip_expires => true}) -<% if knife_config[:bootstrap_proxy] %> -http_proxy "<%= knife_config[:bootstrap_proxy] %>" -https_proxy "<%= knife_config[:bootstrap_proxy] %>" -<% end -%> -EOP - -cat > /etc/chef/first-boot.json <<'EOP' -<%= Chef::JSONCompat.to_json(first_boot) %> -EOP - -<%= start_chef %>' diff --git a/lib/chef/knife/bootstrap/chef-aix.erb b/lib/chef/knife/bootstrap/chef-aix.erb deleted file mode 100644 index 013ad1decb..0000000000 --- a/lib/chef/knife/bootstrap/chef-aix.erb +++ /dev/null @@ -1,63 +0,0 @@ -ksh -c ' - -function exists { - if type $1 >/dev/null 2>&1 - then - return 0 - else - return 1 - fi -} - -if ! exists /usr/bin/chef-client; then - <% if @chef_config[:aix_package] -%> - # Read the download URL/location from knife.rb with option aix_package - rm -rf /tmp/chef_installer # ensure there no older pkg - echo "<%= @chef_config[:aix_package] %>" - perl -e '\''use LWP::Simple; getprint($ARGV[0]);'\'' <%= @chef_config[:aix_package] %> > /tmp/chef_installer - installp -aYF -d /tmp/chef_installer chef - <% else -%> - echo ":aix_package location is not set in knife.rb" - exit - <% end -%> -fi - -mkdir -p /etc/chef - -cat > /etc/chef/validation.pem <<'EOP' -<%= validation_key %> -EOP -chmod 0600 /etc/chef/validation.pem - -<% if encrypted_data_bag_secret -%> -cat > /etc/chef/encrypted_data_bag_secret <<'EOP' -<%= encrypted_data_bag_secret %> -EOP -chmod 0600 /etc/chef/encrypted_data_bag_secret -<% end -%> - -<% unless trusted_certs.empty? -%> -mkdir -p /etc/chef/trusted_certs -<%= trusted_certs %> -<% end -%> - -<%# Generate Ohai Hints -%> -<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> -mkdir -p /etc/chef/ohai/hints - -<% @chef_config[:knife][:hints].each do |name, hash| -%> -cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' -<%= Chef::JSONCompat.to_json(hash) %> -EOP -<% end -%> -<% end -%> - -cat > /etc/chef/client.rb <<'EOP' -<%= config_content %> -EOP - -cat > /etc/chef/first-boot.json <<'EOP' -<%= Chef::JSONCompat.to_json(first_boot) %> -EOP - -<%= start_chef %>' diff --git a/lib/chef/knife/bootstrap/chef-full.erb b/lib/chef/knife/bootstrap/chef-full.erb deleted file mode 100644 index f49fafa98b..0000000000 --- a/lib/chef/knife/bootstrap/chef-full.erb +++ /dev/null @@ -1,79 +0,0 @@ -bash -c ' -<%= "export https_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%> - -distro=`uname -s` - -if test "x$distro" = "xSunOS"; then - if test -d "/usr/sfw/bin"; then - PATH=/usr/sfw/bin:$PATH - export PATH - fi -fi - -exists() { - if command -v $1 &>/dev/null - then - return 0 - else - return 1 - fi -} - -<% if knife_config[:bootstrap_install_command] %> - <%= knife_config[:bootstrap_install_command] %> -<% else %> - install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.chef.io/chef/install.sh" %>" - if ! exists /usr/bin/chef-client; then - echo "Installing Chef Client..." - if exists wget; then - bash <(wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> ${install_sh} -O -) <%= latest_current_chef_version_string %> - elif exists curl; then - bash <(curl -L <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> ${install_sh}) <%= latest_current_chef_version_string %> - else - echo "Neither wget nor curl found. Please install one and try again." >&2 - exit 1 - fi - fi -<% end %> - -mkdir -p /etc/chef - -cat > /etc/chef/validation.pem <<'EOP' -<%= validation_key %> -EOP -chmod 0600 /etc/chef/validation.pem - -<% if encrypted_data_bag_secret -%> -cat > /etc/chef/encrypted_data_bag_secret <<'EOP' -<%= encrypted_data_bag_secret %> -EOP -chmod 0600 /etc/chef/encrypted_data_bag_secret -<% end -%> - -<% unless trusted_certs.empty? -%> -mkdir -p /etc/chef/trusted_certs -<%= trusted_certs %> -<% end -%> - -<%# Generate Ohai Hints -%> -<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> -mkdir -p /etc/chef/ohai/hints - -<% @chef_config[:knife][:hints].each do |name, hash| -%> -cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' -<%= Chef::JSONCompat.to_json(hash) %> -EOP -<% end -%> -<% end -%> - -cat > /etc/chef/client.rb <<'EOP' -<%= config_content %> -EOP - -cat > /etc/chef/first-boot.json <<'EOP' -<%= Chef::JSONCompat.to_json(first_boot) %> -EOP - -echo "Starting first Chef Client run..." - -<%= start_chef %>' diff --git a/lib/chef/knife/bootstrap/templates/README.md b/lib/chef/knife/bootstrap/templates/README.md new file mode 100644 index 0000000000..13a0fe7ada --- /dev/null +++ b/lib/chef/knife/bootstrap/templates/README.md @@ -0,0 +1,12 @@ +This directory contains bootstrap templates which can be used with the -d flag +to 'knife bootstrap' to install Chef in different ways. To simplify installation, +and reduce the matrix of common installation patterns to support, we have +standardized on the [Omnibus](https://github.com/opscode/omnibus-ruby) built installation +packages. + +The 'chef-full' template downloads a script which is used to determine the correct +Omnibus package for this system from the [Omnitruck](https://github.com/opscode/opscode-omnitruck) API. All other templates in this directory are deprecated and will be removed +in the future. + +You can still utilize custom bootstrap templates on your system if your installation +needs are unique. Additional information can be found on the [docs site](http://docs.opscode.com/knife_bootstrap.html#custom-templates). \ No newline at end of file diff --git a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb b/lib/chef/knife/bootstrap/templates/archlinux-gems.erb new file mode 100644 index 0000000000..581293daa3 --- /dev/null +++ b/lib/chef/knife/bootstrap/templates/archlinux-gems.erb @@ -0,0 +1,67 @@ +bash -c ' +<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%> + +if [ ! -f /usr/bin/chef-client ]; then + pacman -Syy + pacman -S --noconfirm ruby ntp base-devel + ntpdate -u pool.ntp.org + gem install ohai --no-user-install --no-document --verbose + gem install chef --no-user-install --no-document --verbose <%= Chef::VERSION %> +fi + +mkdir -p /etc/chef + +cat > /etc/chef/validation.pem <<'EOP' +<%= validation_key %> +EOP +chmod 0600 /etc/chef/validation.pem + +<% if encrypted_data_bag_secret -%> +cat > /etc/chef/encrypted_data_bag_secret <<'EOP' +<%= encrypted_data_bag_secret %> +EOP +chmod 0600 /etc/chef/encrypted_data_bag_secret +<% end -%> + +<% unless trusted_certs.empty? -%> +mkdir -p /etc/chef/trusted_certs +<%= trusted_certs %> +<% end -%> + +<%# Generate Ohai Hints -%> +<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> +mkdir -p /etc/chef/ohai/hints + +<% @chef_config[:knife][:hints].each do |name, hash| -%> +cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' +<%= Chef::JSONCompat.to_json(hash) %> +EOP +<% end -%> +<% end -%> + +cat > /etc/chef/client.rb <<'EOP' +log_level :info +log_location STDOUT +chef_server_url "<%= @chef_config[:chef_server_url] %>" +validation_client_name "<%= @chef_config[:validation_client_name] %>" +<% if @config[:chef_node_name] -%> +node_name "<%= @config[:chef_node_name] %>" +<% else -%> +# Using default node name (fqdn) +<% end -%> +# ArchLinux follows the Filesystem Hierarchy Standard +file_cache_path "/var/cache/chef" +file_backup_path "/var/lib/chef/backup" +pid_file "/var/run/chef/client.pid" +cache_options({ :path => "/var/cache/chef/checksums", :skip_expires => true}) +<% if knife_config[:bootstrap_proxy] %> +http_proxy "<%= knife_config[:bootstrap_proxy] %>" +https_proxy "<%= knife_config[:bootstrap_proxy] %>" +<% end -%> +EOP + +cat > /etc/chef/first-boot.json <<'EOP' +<%= Chef::JSONCompat.to_json(first_boot) %> +EOP + +<%= start_chef %>' diff --git a/lib/chef/knife/bootstrap/templates/chef-aix.erb b/lib/chef/knife/bootstrap/templates/chef-aix.erb new file mode 100644 index 0000000000..013ad1decb --- /dev/null +++ b/lib/chef/knife/bootstrap/templates/chef-aix.erb @@ -0,0 +1,63 @@ +ksh -c ' + +function exists { + if type $1 >/dev/null 2>&1 + then + return 0 + else + return 1 + fi +} + +if ! exists /usr/bin/chef-client; then + <% if @chef_config[:aix_package] -%> + # Read the download URL/location from knife.rb with option aix_package + rm -rf /tmp/chef_installer # ensure there no older pkg + echo "<%= @chef_config[:aix_package] %>" + perl -e '\''use LWP::Simple; getprint($ARGV[0]);'\'' <%= @chef_config[:aix_package] %> > /tmp/chef_installer + installp -aYF -d /tmp/chef_installer chef + <% else -%> + echo ":aix_package location is not set in knife.rb" + exit + <% end -%> +fi + +mkdir -p /etc/chef + +cat > /etc/chef/validation.pem <<'EOP' +<%= validation_key %> +EOP +chmod 0600 /etc/chef/validation.pem + +<% if encrypted_data_bag_secret -%> +cat > /etc/chef/encrypted_data_bag_secret <<'EOP' +<%= encrypted_data_bag_secret %> +EOP +chmod 0600 /etc/chef/encrypted_data_bag_secret +<% end -%> + +<% unless trusted_certs.empty? -%> +mkdir -p /etc/chef/trusted_certs +<%= trusted_certs %> +<% end -%> + +<%# Generate Ohai Hints -%> +<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> +mkdir -p /etc/chef/ohai/hints + +<% @chef_config[:knife][:hints].each do |name, hash| -%> +cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' +<%= Chef::JSONCompat.to_json(hash) %> +EOP +<% end -%> +<% end -%> + +cat > /etc/chef/client.rb <<'EOP' +<%= config_content %> +EOP + +cat > /etc/chef/first-boot.json <<'EOP' +<%= Chef::JSONCompat.to_json(first_boot) %> +EOP + +<%= start_chef %>' diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb new file mode 100644 index 0000000000..f49fafa98b --- /dev/null +++ b/lib/chef/knife/bootstrap/templates/chef-full.erb @@ -0,0 +1,79 @@ +bash -c ' +<%= "export https_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%> + +distro=`uname -s` + +if test "x$distro" = "xSunOS"; then + if test -d "/usr/sfw/bin"; then + PATH=/usr/sfw/bin:$PATH + export PATH + fi +fi + +exists() { + if command -v $1 &>/dev/null + then + return 0 + else + return 1 + fi +} + +<% if knife_config[:bootstrap_install_command] %> + <%= knife_config[:bootstrap_install_command] %> +<% else %> + install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.chef.io/chef/install.sh" %>" + if ! exists /usr/bin/chef-client; then + echo "Installing Chef Client..." + if exists wget; then + bash <(wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> ${install_sh} -O -) <%= latest_current_chef_version_string %> + elif exists curl; then + bash <(curl -L <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> ${install_sh}) <%= latest_current_chef_version_string %> + else + echo "Neither wget nor curl found. Please install one and try again." >&2 + exit 1 + fi + fi +<% end %> + +mkdir -p /etc/chef + +cat > /etc/chef/validation.pem <<'EOP' +<%= validation_key %> +EOP +chmod 0600 /etc/chef/validation.pem + +<% if encrypted_data_bag_secret -%> +cat > /etc/chef/encrypted_data_bag_secret <<'EOP' +<%= encrypted_data_bag_secret %> +EOP +chmod 0600 /etc/chef/encrypted_data_bag_secret +<% end -%> + +<% unless trusted_certs.empty? -%> +mkdir -p /etc/chef/trusted_certs +<%= trusted_certs %> +<% end -%> + +<%# Generate Ohai Hints -%> +<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%> +mkdir -p /etc/chef/ohai/hints + +<% @chef_config[:knife][:hints].each do |name, hash| -%> +cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP' +<%= Chef::JSONCompat.to_json(hash) %> +EOP +<% end -%> +<% end -%> + +cat > /etc/chef/client.rb <<'EOP' +<%= config_content %> +EOP + +cat > /etc/chef/first-boot.json <<'EOP' +<%= Chef::JSONCompat.to_json(first_boot) %> +EOP + +echo "Starting first Chef Client run..." + +<%= start_chef %>' -- cgit v1.2.1 From 1aa9b128d22a21a39e6d7b7a833538ae3e15929d Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Mon, 9 Feb 2015 20:31:30 -0800 Subject: validatorless bootstraps and chef-vault integration --- lib/chef/knife/bootstrap.rb | 96 +++++++++-- lib/chef/knife/bootstrap/chef_vault_handler.rb | 165 ++++++++++++++++++ lib/chef/knife/bootstrap/client_builder.rb | 190 +++++++++++++++++++++ .../knife/bootstrap/templates/archlinux-gems.erb | 9 + lib/chef/knife/bootstrap/templates/chef-aix.erb | 9 + lib/chef/knife/bootstrap/templates/chef-full.erb | 9 + lib/chef/knife/core/bootstrap_context.rb | 8 +- 7 files changed, 471 insertions(+), 15 deletions(-) create mode 100644 lib/chef/knife/bootstrap/chef_vault_handler.rb create mode 100644 lib/chef/knife/bootstrap/client_builder.rb (limited to 'lib/chef/knife') diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index dc41e67c8d..79d9db0e97 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -19,12 +19,17 @@ require 'chef/knife' require 'chef/knife/data_bag_secret_options' require 'erubis' +require 'chef/knife/bootstrap/chef_vault_handler' +require 'chef/knife/bootstrap/client_builder' class Chef class Knife class Bootstrap < Knife include DataBagSecretOptions + attr_accessor :client_builder + attr_accessor :chef_vault_handler + deps do require 'chef/knife/core/bootstrap_context' require 'chef/json_compat' @@ -194,10 +199,54 @@ class Chef :description => "Verify the SSL cert for HTTPS requests to the Chef server API.", :boolean => true + option :vault_file, + :long => '--vault-file VAULT_FILE', + :description => 'A JSON file with a list of vault(s) and item(s) to be updated' + + option :vault_list, + :long => '--vault-list VAULT_LIST', + :description => 'A JSON string with the vault(s) and item(s) to be updated' + + option :vault_item, + :long => '--vault-item VAULT_ITEM', + :description => 'A single vault and item to update as "vault:item"', + :proc => Proc.new { |i| + (vault, item) = i.split(/:/) + vault_item ||= {} + vault_item[vault] ||= [] + vault_item[vault].push(item) + } + + def initialize(argv=[]) + super + @client_builder = Chef::Knife::Bootstrap::ClientBuilder.new( + chef_config: Chef::Config, + knife_config: config, + ui: ui, + ) + @chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new( + knife_config: config, + ui: ui + ) + end + + # The default bootstrap template to use to bootstrap a server This is a public API hook + # which knife plugins use or inherit and override. + # + # @return [String] Default bootstrap template def default_bootstrap_template "chef-full" end + # The server_name is the DNS or IP we are going to connect to, it is not necessarily + # the node name, the fqdn, or the hostname of the server. This is a public API hook + # which knife plugins use or inherit and override. + # + # @return [String] The DNS or IP that bootstrap will connect to + def server_name + Array(@name_args).first + end + def bootstrap_template # The order here is important. We want to check if we have the new Chef 12 option is set first. # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at @@ -237,20 +286,45 @@ class Chef template_file end + def secret + @secret ||= encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil + end + + def bootstrap_context + @bootstrap_context ||= Knife::Core::BootstrapContext.new( + config, + config[:run_list], + Chef::Config, + secret + ) + end + def render_template template_file = find_template template = IO.read(template_file).chomp - secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil - context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config, secret) - Erubis::Eruby.new(template).evaluate(context) + Erubis::Eruby.new(template).evaluate(bootstrap_context) end def run validate_name_args! - @node_name = Array(@name_args).first $stdout.sync = true - ui.info("Connecting to #{ui.color(@node_name, :bold)}") + + # chef-vault integration must use the new client-side hawtness, otherwise to use the + # new client-side hawtness, just delete your validation key. + if chef_vault_handler.doing_chef_vault? || !File.exist?(File.expand_path(Chef::Config[:validation_key])) + client_builder.run + + chef_vault_handler.run(node_name: config[:chef_node_name]) + + bootstrap_context.client_pem = client_builder.client_path + else + ui.info("Doing old-style registration with a validation key...") + ui.info("Delete your validation key in order to use your user credentials instead") + ui.info("") + end + + ui.info("Connecting to #{ui.color(server_name, :bold)}") begin knife_ssh.run @@ -265,24 +339,19 @@ class Chef end def validate_name_args! - if Array(@name_args).first.nil? + if server_name.nil? ui.error("Must pass an FQDN or ip to bootstrap") exit 1 - elsif Array(@name_args).first == "windows" + elsif server_name == "windows" + # catches "knife bootstrap windows" when that command is not installed ui.warn("Hostname containing 'windows' specified. Please install 'knife-windows' if you are attempting to bootstrap a Windows node via WinRM.") end end - def server_name - Array(@name_args).first - end - def knife_ssh ssh = Chef::Knife::Ssh.new ssh.ui = ui ssh.name_args = [ server_name, ssh_command ] - - # command line arguments and config file values are now merged into config in Chef::Knife#merge_configs ssh.config[:ssh_user] = config[:ssh_user] ssh.config[:ssh_password] = config[:ssh_password] ssh.config[:ssh_port] = config[:ssh_port] @@ -311,7 +380,6 @@ class Chef command end - end end end diff --git a/lib/chef/knife/bootstrap/chef_vault_handler.rb b/lib/chef/knife/bootstrap/chef_vault_handler.rb new file mode 100644 index 0000000000..c421d2cb15 --- /dev/null +++ b/lib/chef/knife/bootstrap/chef_vault_handler.rb @@ -0,0 +1,165 @@ +# +# Author:: Lamont Granquist () +# Copyright:: Copyright (c) 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + class Knife + class Bootstrap < Knife + class ChefVaultHandler + + # @return [Hash] knife merged config, typically @config + attr_accessor :knife_config + + # @return [Chef::Knife::UI] ui object for output + attr_accessor :ui + + # @return [String] name of the node (technically name of the client) + attr_reader :node_name + + # @param knife_config [Hash] knife merged config, typically @config + # @param ui [Chef::Knife::UI] ui object for output + def initialize(knife_config: {}, ui: nil) + @knife_config = knife_config + @ui = ui + end + + # Updates the chef vault items for the newly created node. + # + # @param node_name [String] name of the node (technically name of the client) + # @todo: node_name should be mandatory (ruby 2.0 compat) + def run(node_name: nil) + return unless doing_chef_vault? + + sanity_check + + @node_name = node_name + + ui.info("Updating Chef Vault, waiting for client to be searchable..") while wait_for_client + + update_vault_list! + end + + # Iterate through all the vault items to update. Items may be either a String + # or an Array of Strings: + # + # { + # "vault1": "item", + # "vault2": [ "item1", "item2", "item2" ] + # } + # + def update_vault_list! + vault_json.each do |vault, items| + [ items ].flatten.each do |item| + update_vault(vault, item) + end + end + end + + # @return [Boolean] if we've got chef vault options to act on or not + def doing_chef_vault? + !!(vault_list || vault_file || vault_item) + end + + private + + # warn if the user has given mutual conflicting options + def sanity_check + if vault_item && (vault_list || vault_file) + ui.warn "--vault-item given with --vault-list or --vault-file, ignoring the latter" + end + + if vault_list && vault_file + ui.warn "--vault-list given with --vault-file, ignoring the latter" + end + end + + # @return [String] string with serialized JSON representing the chef vault items + def vault_list + knife_config[:vault_list] + end + + # @return [String] JSON text in a file representing the chef vault items + def vault_file + knife_config[:vault_file] + end + + # @return [Hash] Ruby object representing the chef vault items to create + def vault_item + knife_config[:vault_item] + end + + # Helper to return a ruby object represeting all the data bags and items + # to update via chef-vault. + # + # @return [Hash] deserialized ruby hash with all the vault items + def vault_json + @vault_json ||= + begin + if vault_item + vault_item + else + json = vault_list ? vault_list : File.read(vault_file) + Chef::JSONCompat.from_json(json) + end + end + end + + # Update an individual vault item and save it + # + # @param vault [String] name of the chef-vault encrypted data bag + # @param item [String] name of the chef-vault encrypted item + def update_vault(vault, item) + require_chef_vault! + vault_item = load_chef_vault_item(vault, item) + vault_item.clients("name:#{node_name}") + vault_item.save + end + + # Hook to stub out ChefVault + # + # @param vault [String] name of the chef-vault encrypted data bag + # @param item [String] name of the chef-vault encrypted item + # @returns [ChefVault::Item] ChefVault::Item object + def load_chef_vault_item(vault, item) + ChefVault::Item.load(vault, item) + end + + public :load_chef_vault_item # for stubbing + + # Helper used to spin waiting for the client to appear in search. + # + # @return [Boolean] true if the client is searchable + def wait_for_client + sleep 1 + !Chef::Search::Query.new.search(:client, "name:#{node_name}")[0] + end + + # Helper to very lazily require the chef-vault gem + def require_chef_vault! + @require_chef_vault ||= + begin + require 'chef-vault' + true + rescue LoadError + raise "Knife bootstrap cannot configure chef vault items when the chef-vault gem is not installed" + end + end + + end + end + end +end diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb new file mode 100644 index 0000000000..b9c1d98bec --- /dev/null +++ b/lib/chef/knife/bootstrap/client_builder.rb @@ -0,0 +1,190 @@ +# +# Author:: Lamont Granquist () +# Copyright:: Copyright (c) 2015 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/node' +require 'chef/rest' +require 'chef/api_client/registration' +require 'chef/api_client' +require 'tmpdir' + +class Chef + class Knife + class Bootstrap < Knife + class ClientBuilder + + # @return [Hash] knife merged config, typically @config + attr_accessor :knife_config + # @return [Hash] chef config object + attr_accessor :chef_config + # @return [Chef::Knife::UI] ui object for output + attr_accessor :ui + + # @param knife_config [Hash] Hash of knife config settings + # @param chef_config [Hash] Hash of chef config settings + # @param ui [Chef::Knife::UI] UI object for output + def initialize(knife_config: {}, chef_config: {}, ui: nil) + @knife_config = knife_config + @chef_config = chef_config + @ui = ui + end + + # Main entry. Prompt the user to clean up any old client or node objects. Then create + # the new client, then create the new node. + def run + sanity_check + + ui.info("Creating new client for #{node_name}") + + create_client! + + ui.info("Creating new node for #{node_name}") + + create_node! + end + + # Tempfile to use to write newly created client credentials to. + # + # This method is public so that the knife bootstrapper can read then and pass the value into + # the handler for chef vault which needs the client cert we create here. + # + # We hang onto the tmpdir as an ivar as well so that it will not get GC'd and removed + # + # @return [String] path to the generated client.pem + def client_path + @client_path ||= + begin + @tmpdir = Dir.mktmpdir + File.join(@tmpdir, "#{node_name}.pem") + end + end + + private + + # @return [String] node name from the knife_config + def node_name + knife_config[:chef_node_name] + end + + # @return [String] enviroment from the knife_config + def environment + knife_config[:environment] + end + + # @return [String] run_list from the knife_config + def run_list + knife_config[:run_list] + end + + # @return [Hash,Array] Object representation of json first-boot attributes from the knife_config + def first_boot_attributes + knife_config[:first_boot_attributes] + end + + # @return [String] chef server url from the Chef::Config + def chef_server_url + chef_config[:chef_server_url] + end + + # Accesses the run_list and coerces it into an Array, changing nils into + # the empty Array, and splitting strings representations of run_lists into + # Arrays. + # + # @return [Array] run_list coerced into an array + def normalized_run_list + case run_list + when nil + [] + when String + run_list.split(/\s*,\s*/) + when Array + run_list + end + end + + # Create the client object and save it to the Chef API + def create_client! + Chef::ApiClient::Registration.new(node_name, client_path, http_api: rest).run + end + + # Create the node object (via the lazy accessor) and save it to the Chef API + def create_node! + node.save + end + + # Create a new Chef::Node. Supports creating the node with its name, run_list, attributes + # and environment. This injects a rest object into the Chef::Node which uses the client key + # for authentication so that the client creates the node and therefore we get the acls setup + # correctly. + # + # @return [Chef::Node] new chef node to create + def node + @node ||= + begin + node = Chef::Node.new(chef_server_rest: client_rest) + node.name(node_name) + node.run_list(normalized_run_list) + node.normal_attrs = first_boot_attributes if first_boot_attributes + node.environment(environment) if environment + node + end + end + + # Check for the existence of a node and/or client already on the server. If the node + # already exists, we must delete it in order to proceed so that we can create a new node + # object with the permissions of the new client. There is a use case for creating a new + # client and wiring it up to a precreated node object, but we do currently support that. + # + # We prompt the user about what to do and will fail hard if we do not get confirmation to + # delete any prior node/client objects. + def sanity_check + if resource_exists?("nodes/#{node_name}") + ui.confirm("Node #{node_name} exists, overwrite it") + rest.delete("nodes/#{node_name}") + end + if resource_exists?("clients/#{node_name}") + ui.confirm("Client #{node_name} exists, overwrite it") + rest.delete("clients/#{node_name}") + end + end + + # Check if an relative path exists on the chef server + # + # @param relative_path [String] URI path relative to the chef organization + # @return [Boolean] if the relative path exists or returns a 404 + def resource_exists?(relative_path) + rest.get_rest(relative_path) + true + rescue Net::HTTPServerException => e + raise unless e.response.code == "404" + false + end + + # @return [Chef::REST] REST client using the client credentials + def client_rest + @client_rest ||= Chef::REST.new(chef_server_url, node_name, client_path) + end + + # @return [Chef::REST] REST client using the cli user's knife credentials + # this uses the users's credentials + def rest + @rest ||= Chef::REST.new(chef_server_url) + end + end + end + end +end diff --git a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb b/lib/chef/knife/bootstrap/templates/archlinux-gems.erb index 581293daa3..55d2c0cc12 100644 --- a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb +++ b/lib/chef/knife/bootstrap/templates/archlinux-gems.erb @@ -11,10 +11,12 @@ fi mkdir -p /etc/chef +<% if validation_key -%> cat > /etc/chef/validation.pem <<'EOP' <%= validation_key %> EOP chmod 0600 /etc/chef/validation.pem +<% end -%> <% if encrypted_data_bag_secret -%> cat > /etc/chef/encrypted_data_bag_secret <<'EOP' @@ -39,6 +41,13 @@ EOP <% end -%> <% end -%> +<% if client_pem -%> +cat > /etc/chef/client.pem <<'EOP' +<%= ::File.read(::File.expand_path(client_pem)) %> +EOP +chmod 0600 /etc/chef/client.pem +<% end -%> + cat > /etc/chef/client.rb <<'EOP' log_level :info log_location STDOUT diff --git a/lib/chef/knife/bootstrap/templates/chef-aix.erb b/lib/chef/knife/bootstrap/templates/chef-aix.erb index 013ad1decb..45fbba7b48 100644 --- a/lib/chef/knife/bootstrap/templates/chef-aix.erb +++ b/lib/chef/knife/bootstrap/templates/chef-aix.erb @@ -24,10 +24,19 @@ fi mkdir -p /etc/chef +<% if client_pem -%> +cat > /etc/chef/client.pem <<'EOP' +<%= ::File.read(::File.expand_path(client_pem)) %> +EOP +chmod 0600 /etc/chef/client.pem +<% end -%> + +<% if validation_key -%> cat > /etc/chef/validation.pem <<'EOP' <%= validation_key %> EOP chmod 0600 /etc/chef/validation.pem +<% end -%> <% if encrypted_data_bag_secret -%> cat > /etc/chef/encrypted_data_bag_secret <<'EOP' diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb index f49fafa98b..17d7a9e3b5 100644 --- a/lib/chef/knife/bootstrap/templates/chef-full.erb +++ b/lib/chef/knife/bootstrap/templates/chef-full.erb @@ -38,10 +38,19 @@ exists() { mkdir -p /etc/chef +<% if client_pem -%> +cat > /etc/chef/client.pem <<'EOP' +<%= ::File.read(::File.expand_path(client_pem)) %> +EOP +chmod 0600 /etc/chef/client.pem +<% end -%> + +<% if validation_key -%> cat > /etc/chef/validation.pem <<'EOP' <%= validation_key %> EOP chmod 0600 /etc/chef/validation.pem +<% end -%> <% if encrypted_data_bag_secret -%> cat > /etc/chef/encrypted_data_bag_secret <<'EOP' diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index 60db34c8d0..7197653489 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -30,6 +30,8 @@ class Chef # class BootstrapContext + attr_accessor :client_pem + def initialize(config, run_list, chef_config, secret = nil) @config = config @run_list = run_list @@ -42,7 +44,11 @@ class Chef end def validation_key - IO.read(File.expand_path(@chef_config[:validation_key])) + if File.exist?(File.expand_path(@chef_config[:validation_key])) + IO.read(File.expand_path(@chef_config[:validation_key])) + else + false + end end def encrypted_data_bag_secret -- cgit v1.2.1 From ee8e333ad7c23aa3b84d510c3804cf840620924a Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Wed, 11 Feb 2015 19:19:19 -0800 Subject: prepend bootstrap_ to all CLI args also rename vault_item to bootstrap_vault_json --- lib/chef/knife/bootstrap.rb | 18 ++++++------ lib/chef/knife/bootstrap/chef_vault_handler.rb | 38 +++++++++++++------------- 2 files changed, 28 insertions(+), 28 deletions(-) (limited to 'lib/chef/knife') diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index 79d9db0e97..dde1037a66 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -199,22 +199,22 @@ class Chef :description => "Verify the SSL cert for HTTPS requests to the Chef server API.", :boolean => true - option :vault_file, - :long => '--vault-file VAULT_FILE', + option :bootstrap_vault_file, + :long => '--bootstrap-vault-file VAULT_FILE', :description => 'A JSON file with a list of vault(s) and item(s) to be updated' - option :vault_list, - :long => '--vault-list VAULT_LIST', + option :bootstrap_vault_json, + :long => '--bootstrap-vault-json VAULT_JSON', :description => 'A JSON string with the vault(s) and item(s) to be updated' - option :vault_item, - :long => '--vault-item VAULT_ITEM', + option :bootstrap_vault_item, + :long => '--bootstrap-vault-item VAULT_ITEM', :description => 'A single vault and item to update as "vault:item"', :proc => Proc.new { |i| (vault, item) = i.split(/:/) - vault_item ||= {} - vault_item[vault] ||= [] - vault_item[vault].push(item) + bootstrap_vault_item ||= {} + bootstrap_vault_item[vault] ||= [] + bootstrap_vault_item[vault].push(item) } def initialize(argv=[]) diff --git a/lib/chef/knife/bootstrap/chef_vault_handler.rb b/lib/chef/knife/bootstrap/chef_vault_handler.rb index c421d2cb15..749f61e6da 100644 --- a/lib/chef/knife/bootstrap/chef_vault_handler.rb +++ b/lib/chef/knife/bootstrap/chef_vault_handler.rb @@ -50,7 +50,7 @@ class Chef ui.info("Updating Chef Vault, waiting for client to be searchable..") while wait_for_client - update_vault_list! + update_bootstrap_vault_json! end # Iterate through all the vault items to update. Items may be either a String @@ -61,7 +61,7 @@ class Chef # "vault2": [ "item1", "item2", "item2" ] # } # - def update_vault_list! + def update_bootstrap_vault_json! vault_json.each do |vault, items| [ items ].flatten.each do |item| update_vault(vault, item) @@ -71,35 +71,35 @@ class Chef # @return [Boolean] if we've got chef vault options to act on or not def doing_chef_vault? - !!(vault_list || vault_file || vault_item) + !!(bootstrap_vault_json || bootstrap_vault_file || bootstrap_vault_item) end private # warn if the user has given mutual conflicting options def sanity_check - if vault_item && (vault_list || vault_file) + if bootstrap_vault_item && (bootstrap_vault_json || bootstrap_vault_file) ui.warn "--vault-item given with --vault-list or --vault-file, ignoring the latter" end - if vault_list && vault_file + if bootstrap_vault_json && bootstrap_vault_file ui.warn "--vault-list given with --vault-file, ignoring the latter" end end # @return [String] string with serialized JSON representing the chef vault items - def vault_list - knife_config[:vault_list] + def bootstrap_vault_json + knife_config[:bootstrap_vault_json] end # @return [String] JSON text in a file representing the chef vault items - def vault_file - knife_config[:vault_file] + def bootstrap_vault_file + knife_config[:bootstrap_vault_file] end # @return [Hash] Ruby object representing the chef vault items to create - def vault_item - knife_config[:vault_item] + def bootstrap_vault_item + knife_config[:bootstrap_vault_item] end # Helper to return a ruby object represeting all the data bags and items @@ -109,10 +109,10 @@ class Chef def vault_json @vault_json ||= begin - if vault_item - vault_item + if bootstrap_vault_item + bootstrap_vault_item else - json = vault_list ? vault_list : File.read(vault_file) + json = bootstrap_vault_json ? bootstrap_vault_json : File.read(bootstrap_vault_file) Chef::JSONCompat.from_json(json) end end @@ -124,9 +124,9 @@ class Chef # @param item [String] name of the chef-vault encrypted item def update_vault(vault, item) require_chef_vault! - vault_item = load_chef_vault_item(vault, item) - vault_item.clients("name:#{node_name}") - vault_item.save + bootstrap_vault_item = load_chef_bootstrap_vault_item(vault, item) + bootstrap_vault_item.clients("name:#{node_name}") + bootstrap_vault_item.save end # Hook to stub out ChefVault @@ -134,11 +134,11 @@ class Chef # @param vault [String] name of the chef-vault encrypted data bag # @param item [String] name of the chef-vault encrypted item # @returns [ChefVault::Item] ChefVault::Item object - def load_chef_vault_item(vault, item) + def load_chef_bootstrap_vault_item(vault, item) ChefVault::Item.load(vault, item) end - public :load_chef_vault_item # for stubbing + public :load_chef_bootstrap_vault_item # for stubbing # Helper used to spin waiting for the client to appear in search. # -- cgit v1.2.1 From e150d7dc2c781e230d46f8a23f2c8e17e9de0042 Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Fri, 13 Feb 2015 14:49:05 -0800 Subject: add logging of where the validation key is i was mildly surprised that we defaulted to /etc/chef/validation.pem in knife and surprised that i had that file on my laptop... --- lib/chef/knife/bootstrap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/chef/knife') diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index dde1037a66..7fcdc0820f 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -319,7 +319,7 @@ class Chef bootstrap_context.client_pem = client_builder.client_path else - ui.info("Doing old-style registration with a validation key...") + ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...") ui.info("Delete your validation key in order to use your user credentials instead") ui.info("") end -- cgit v1.2.1 From 621bec9e651e89ccdb4b627111a4e1e62b69acda Mon Sep 17 00:00:00 2001 From: Lamont Granquist Date: Fri, 13 Feb 2015 15:14:27 -0800 Subject: fixed a bug in --bootstrap-vault-item --- lib/chef/knife/bootstrap.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib/chef/knife') diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index 7fcdc0820f..f23c15fa70 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -212,9 +212,10 @@ class Chef :description => 'A single vault and item to update as "vault:item"', :proc => Proc.new { |i| (vault, item) = i.split(/:/) - bootstrap_vault_item ||= {} - bootstrap_vault_item[vault] ||= [] - bootstrap_vault_item[vault].push(item) + Chef::Config[:knife][:bootstrap_vault_item] ||= {} + Chef::Config[:knife][:bootstrap_vault_item][vault] ||= [] + Chef::Config[:knife][:bootstrap_vault_item][vault].push(item) + Chef::Config[:knife][:bootstrap_vault_item] } def initialize(argv=[]) -- cgit v1.2.1