Chef Validator Bootstrap Process

Validatorless Bootstrap

Run against Chef Zero

In last post regarding chef bootstrap, here, a chef-zero server is running as chef server, and everything looks fine.

What if we run against a real chef server, let’s see what will happen.

Oooops, it’s not working ….

#=> k.client_builder.run
Creating new client for one_node
Net::HTTPServerException: 403 "Forbidden"
....

If we run equivalent knife bootstrap command, it will show you more friendly message.

The client_key or client the bootstrap command tries to act as has no create permission on the chef server, that is why we got 403 "Forbidden" exeception at first place.

#=> knife bootstrap localhost -N test_node -y --node-ssl-verify-mode none --server-url 'https://localhost/organizations/test_org'
Creating new client for test_node
ERROR: You authenticated successfully to https://localhost/organizations/test_org as bootstrap_node but you are not authorized for this action.
Response:  missing create permission

It makes perfect sense, as Chef Zero is designed to be that, since it has no validation, authentication or authorization, as:

Chef Zero is a simple, easy-install, in-memory Chef server that can be useful for Chef Client testing and chef-solo-like tasks that require a full Chef Server….. It does NO input validation, authentication or authorization (it will not throw a 400, 401 or 403).

Run against Real Chef server

We need chef client that has create permission, let’s have a try with chef admin, or root.

Let’s update the Chef::Config as below

Chef::Config.node_name = 'root'
Chef::Config.client_key = "/etc/opscode/users/root/root.pem"

So, it works !

#=> k.client_builder.run
Creating new client for one_node
Creating new node for one_node
=> #<Chef::Node:0x0000000002f01d40 ........> 

Look back the create_client code, it’s self-explanatory: REST client using the cli user's knife credentials

# 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
# @return [Chef::ServerAPI] REST client using the cli user's knife credentials
# this uses the users's credentials
def rest
  @rest ||= Chef::ServerAPI.new(chef_server_url)
end

So, what we have been discussing is known as a validatorless bootstrap, let’s move to Validator Bootstrap.

Validator Bootstrap

So, let’s begin with so-called Validation Client testorg-validator, reset key of testorg-validator and download the testorg-validator.pem

Chef::Config.validation_client_name = 'testorg-validator'
Chef::Config.validation_key = "#{Dir.home}/.chef/testorg-validator.pem"

With validation_key, Chef::Knife::Bootstrap no longer calls client_builder.run, instead it call knife_ssh.run directly, refer to below:

def run
  # ....
  if chef_vault_handler.doing_chef_vault? ||
      (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))
    client_builder.run

    chef_vault_handler.run(client_builder.client)

    bootstrap_context.client_pem = client_builder.client_path
  else
    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

  ui.info("Connecting to #{ui.color(server_name, :bold)}")

  begin
    knife_ssh.run
  rescue Net::SSH::AuthenticationFailed
    # .....
  end
end

The testorg-validator.pem is passed on by the template/script, and cast into /etc/chef/validation.pem in remote node.

#=> puts k.render_template
#.....
mkdir -p /etc/chef

cat > /etc/chef/validation.pem <<'EOP'
-----BEGIN RSA PRIVATE KEY-----
# ......
-----END RSA PRIVATE KEY-----

EOP
# ......
chmod 0600 /etc/chef/validation.pem

cat > /etc/chef/client.rb <<'EOP'
chef_server_url  "https://localhost/organizations/testorg"
validation_client_name "testorg-validator"
log_location   STDOUT
node_name "test_validator_node"
ssl_verify_mode :verify_none
EOP
# ......

At last, chef-client will be responsible for creating client and node using the validator key at /etc/chef/validation.pem, take chef-run below as example

#=> k.run
Doing old-style registration with the validation key at /root/.chef/testorg-validator.pem...
Delete your validation key in order to use your user credentials instead

Connecting to localhost
-----> Existing Chef installation detected
Starting the first Chef Client run...
Starting Chef Client, version 14.9.19
Creating a new client identity for test_validator_node using the validator key.
resolving cookbooks for run list: []
Synchronizing Cookbooks:
Installing Cookbook Gems:
Compiling Cookbooks...
[2018-12-10T04:23:51+00:00] WARN: Node test_validator_node has an empty run list.
Converging 0 resources

Running handlers:
Running handlers complete
Chef Client finished, 0/0 resources updated in 03 seconds

Simple Simulation of Validator Bootstrap

We can use a simple script to demonstrate the validator bootstrap process.

NOTE: we are assuming chef-client or chefdk has been installed on your machine.

Start a dummy chef zero server in debug mode, so you can see clearly what’s happening behind the scene.

#=> sudo /opt/chefdk/embedded/bin/chef-zero -l debug --ssl -p 443

Save and run this script

# simple_validator_bootstrap.sh

mkdir -p /etc/chef

rm -fv /etc/chef/client.pem

cat > /etc/chef/validation.pem <<'EOP'
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAwOkhMXYsEgb54muxnrHe4ItYS50xGAkRE5R2pGCLAU2sxPj5
5OlWMDUtmtkyFjIAxXE2jdxSSEmrvSrlAkn6ec+mGJGzVjd6Fkyuoep6FXSamqUi
860bT1KXA3S6+gJIBhvW/lwLaUL1erVT6VQbmOh3YgUzaaoOM4FFy+pski9adIJC
FZf/lHBJsXYEX3Da4tNSLsADU/gNpgK0rG1JdnvvoMaVp9khsc2YrBFpT4ugXoqY
7V2qKHTEmvBlQ8ldCnI4Vl/Ifmpqhtwllxn3YovIKfHNXwlYJ6v/EqYp1oqEJrjZ
72797djhUq5ENGt9EhQw1GQbuFbrqve/bnpLtQIDAQABAoIBAQCbtE+fXZNqpYjR
0TzXKxgKw03sEh9LGB5ZYG52dJod3jUB8ze4JQH0/ScnIgHEWm22748p51fekt/0
WofnVhC+evTERe2rPDHlh9U4SUpqwOf8xCc26VTurGnJV1GHc4nwrE3WljJ7rpj2
hx8IaGOyAohBbJM3yROXTNMqKLejMP/JByDCZ25UOkKhTXDLriWDmoWA9asqwk6v
U8zNKjse+bHcM6RRp96kQ31iwsv1xFtsrlm5aeOP9p6hI1VTIh/yoa5efLx/f50Z
IFIZ5kOKFP2mdsqAJ+adi8A40AAIbuHgG6xRgYVVzOlsX6TIpnZLxgb5+po1Lm+B
sBYNq6bhAoGBAO4WaLWedVXCTz5mV4AXFLOZW2aoYY7louD/sjfuE7Naj3XVVnFh
hvXNy24ig1q8+mNLk3tpt08BH4/G1fYEZ6MxM8OuTxls05liNIflLV+cyhlIOnxQ
U6Ncfa+XglyF6aBF2eln6Pwe338Is477wl9Zs1uA4BxgvUgKERmTmAApAoGBAM9s
m/yIhK+P7iWUUpNlaFG7P0Mq55bEDdxgiOgZti5RchkFzRaioWdiFYa3QlfDiaO1
COJinxjHggwTUrPIna2wGXsvLmih0rCFTyC045vDc0rtaAoXBHZIh4oIVD9BmhlE
0z67ROkTnOhr+iaMdjpf0564l7LRz7GxGKHoNrCtAoGAcBawuUCesP9H23LHIxC6
uEss0snXFDVcV11KBDbbo4axH6KOjdaCeVqnuXQaLy/lGbZM+r8sg89dkozj0m0E
dboGSsvXhXrMq9umK4xjri3cn8Z3cmtG1RQIQBCuWOzaro/0JYS8FWZbhi0Mi/ZO
7iEG5b9owzNwKWhD4Kyx1PECgYEAuFN83tJ2jwkpiU2ggAmKxa7PTiIPgYQiCSfk
IdXPdqO78A6erTHCmvunw3qRQyqp4sfa6ErZtQx+PbriMI/jx1iJnFVWOXcsot8k
bR0yctYiW4BThzvjJDXZ9MjoDPqANVpbGxER8MoUEtr5hk4mNkO37AGAFVGr7u1A
xYh1KVUCgYEAtfN5IsEZqxsqkvK9nCgLAbrwITqOQkL/LuwGy2ODwtijNT3PPZbe
D/4ShleDvvbBZRSath8tAXIm2ZfRUxt3xBJQv8WtdOexmfIO3sBPF8IT0N5ZbjUL
Y9m/UmPUvgENzLvzMrPJchTwLNg/Ep1VuQBEMIAsvqCLQqaLng1UTc8=
-----END RSA PRIVATE KEY-----

EOP
chmod 0600 /etc/chef/validation.pem




cat > /etc/chef/client.rb <<'EOP'
chef_server_url  "https://localhost:443"
validation_client_name "testorg-validator"
log_location   STDOUT
node_name "test_validator_node"
ssl_verify_mode :verify_none

EOP

cat > /etc/chef/first-boot.json <<'EOP'
{"run_list":[]}
EOP


echo "Starting the first Chef Client run..."

chef-client -j /etc/chef/first-boot.json

Sample output:

Creating a new client identity for test_validator_node using the validator key.
[2018-12-10T14:59:09+08:00] INFO: Client key /etc/chef/client.pem is not present - registering

Source code can let us have better understand what the process is like:

def register(client_name = node_name, config = Chef::Config)
  if !config[:client_key]
    events.skipping_registration(client_name, config)
    logger.trace("Client key is unspecified - skipping registration")
  elsif File.exists?(config[:client_key])
    events.skipping_registration(client_name, config)
    logger.trace("Client key #{config[:client_key]} is present - skipping registration")
  else
    events.registration_start(node_name, config)
    logger.info("Client key #{config[:client_key]} is not present - registering")
    Chef::ApiClient::Registration.new(node_name, config[:client_key]).run
    events.registration_completed
  end
  # We now have the client key, and should use it from now on.
  @rest = Chef::ServerAPI.new(config[:chef_server_url], client_name: client_name,
  # ......