0

After some chef zero runs (which save the node state as local .json files) I was dismayed to find this in the node files: ... "ssmtp": { "auth_username": "secret user name", "auth_password": "even-more-secret password" } The same runs on Chef Server would save the node data on the server. That's is a problem of course, and I'll have to replace credentials, modify the recipes, etc. I am still investigating what caused this in the first place but my question is:

How I can create an rspec/chefspec test for a recipe to verify that a particular node attribute is NOT saved persistently in the node's .json file or on Chef Server?

I'd like to add this to my specs to make sure it never happens again.

Epilogue

The big lesson here is that anything finding its way into any node attribute winds up being saved in the node object representation.

Tom Wilson
  • 797
  • 9
  • 26
  • Per Chef node docs: "A normal attribute is a setting that persists in the node object. A normal attribute has a higher attribute precedence than a default attribute." so perhaps my question is "how can I test if a node attribute is a **normal** attribute? – Tom Wilson May 06 '15 at 16:35
  • 2
    You could also try to suppress the saving of this node attribute to the chef server. See: https://supermarket.chef.io/cookbooks/blacklist_node_attrs – Mark O'Connor May 06 '15 at 20:19

2 Answers2

2

Chef Server will save all attributes, even if they are normal or default or another precedence level. You can get around this by using node.run_state which can be used to "stash transient data during a chef-client run."

You can see this at work in the elkstack community cookbook, most notably in the _lumberjack_secrets.rb recipe.

node.run_state being set:

if <CONDITION ABBREVIATED>
  node.run_state['lumberjack_decoded_key'] = Base64.decode64(lumberjack_secrets['key'])
  ....
end

node.run_state being used:

lumberjack_keypair = node.run_state['lumberjack_decoded_key'] && node.run_state['lumberjack_decoded_certificate']

As for the test, I haven't "tested" this myself, but you would be testing to make sure the attribute is not set, like this:

it 'should not have secret attributes set' do
  node = chef_run.node
  expect (node['my_secret']).to be_nil
end
Matt Barlow
  • 165
  • 4
  • Thanks for the explanation. (Fog lifted) – Tom Wilson May 06 '15 at 21:55
  • So at a high level it is impossible to use node attributes to set up ssmtp (using the standard cookbook) without the credentials winding up in the stored node object. I'm accepting this: "Chef Server will save all attributes, even if they are normal or default or another precedence level." as the actual answer. – Tom Wilson May 06 '15 at 22:02
0

The key is in the comment from the Chef docs, "A normal attribute is a setting that persists in the node object. A normal attribute has a higher attribute precedence than a default attribute." That breaks the problem down into testing if an attribute is a "normal" attribute.

Digging into the Chef::Node and Chef::Node::Attribute class code, I found you can call node.attributes.normal to get just the the normal attributes. So this concise test correctly fails, indicating that my secret info is going to be stored persistently in the node object!

it 'does not have a normal attribute node[ssmtp][auth_password]' do expect(subject.node.attributes.normal['ssmtp'].keys).to_not include 'auth_password' end

resulting in this decent error message:

Failure/Error: expect(subject.node.attributes.normal['ssmtp'].keys).to_not include 'auth_password' expected ["auth_username", "auth_password"] not to include "auth_password"

You could also test to that if a specific value is not set, but I think testing that the key does not exist is more to the point.

Somehow I always get new insights from simply formulating a question to post here. So it goes.

Tom Wilson
  • 797
  • 9
  • 26
  • Rats. @MattBarlow is right. My "solution" only moved the problem to another part of the node object representation. Now it's with the default attributes, not the normal attributes. D'oh – Tom Wilson May 06 '15 at 21:49
  • 1
    Worth noting `node.normal['attribute']` will give the same behavior. (access by `normal` method of `node`instead of accessing hash by method, you should never mix method and string access `node['attributes']['normal']['ssmtp'].keys` would have the effect with a clearest meaning of what is what. – Tensibai May 07 '15 at 07:51