[RESOLVED] Reading secret for Encrypted Data Bag

Hi

Short version: Why can Chef decrypt an encrypted data bag item only when the secret has been read with Chef::EncryptedDataBagItem.load_secret('secret_file') and not, when IO.read('secret_file') has been used?

Long version…

According to the data bags documentation, it should be possible to do this:

data_bag_item('bag', 'item', IO.read('secret_file'))

But when I do exactly this in my recipe, Chef is unable to decrypt the data bag; in the log, there’s then this:

[2017-08-31T13:37:06+02:00] ERROR: Error decrypting data bag value: invalid hmac. Most likely the provided key is incorrect

It seems, that I have to use this instead:

data_bag_item('bag', 'item', Chef::EncryptedDataBagItem.load_secret('secret_file'))

Ie. Chef::EncryptedDataBagItem.load_secret() vs. IO.read().

You can find the pretty small source code at

There are two branches: master with data_bag_item('bag', 'item', Chef::EncryptedDataBagItem.load_secret('secret_file')) and secret_io_read with data_bag_item('bag', 'item', IO.read('secret_file')).

I’m using Chef Zero (local mode) v12.21.4 on Ubuntu 16.04. Chef is invoked like so:

sudo /usr/bin/chef-client -j /opt/addale-kitchen/nodes/system.json -c /opt/addale-kitchen/client.rb

You need to copy the secret file to /etc/chef/ew.icinga-client.secret (this is just dummy data, so I don’t care that the secret has been made public now).

In my debug recipe, I write out the secret data to files /tmp/secret_io and /tmp/secret_edbi. Doing a diff on those files reveals:

$ diff -U0 /tmp/secret_*
--- /tmp/secret_edbi	2017-08-31 13:23:47.051027793 +0200
+++ /tmp/secret_io	2017-08-31 13:23:47.059027793 +0200
@@ -21 +21 @@
-YkNpVkFSdnFCT2RESjhPZGFkc3ZZV3hqaEhsUVAyYmpTSGZNcWJwVQo=
\ No newline at end of file
+YkNpVkFSdnFCT2RESjhPZGFkc3ZZV3hqaEhsUVAyYmpTSGZNcWJwVQo=

In secret_edbi (filled with Chef::EncryptedDataBagItem…), a newline is missing at the end of the file.

The encrypted data bag item has been created like so:

$ knife data bag create ew-icinga-client system --local-mode --config-option data_bag_path=./data_bags --secret-file ./.data_bags/ew-icinga-client.secret

On the system, where “kinfe data bag create …” ran:

$ md5sum ./.data_bags/ew-icinga-client.secret
968daf308b1b26487277563b00a5f643  ./.data_bags/ew-icinga-client.secret

On the system, where chef-client ran:

$ md5sum /tmp/secret_*
c9a4357e60430cd04d93c1003dcb7441  /tmp/secret_edbi
968daf308b1b26487277563b00a5f643  /tmp/secret_io

So, the correct file/contents would’ve been /tmp/secret_io, but when I use this content, the encrypted data bag item cannot be decrypted.

Why is that so?

Thanks,
Alexander

The code for that method is here: https://github.com/chef/chef/blob/a60adadf4ffa54f3aa1d92de824393f367579452/lib/chef/encrypted_data_bag_item.rb#L147

As best I can tell, you just need to strip whitespace after you load it.

Okay, will try that.

HOWEVER, in the docs, it says:

To load the secret from a file:

data_bag_item('bag', 'item', IO.read('secret_file'))

There’s nothing about IO.read(…).strip

And, well, why do I need to use strip in the first place? I mean, when I called knife …, I gave it the full file, not just some parts of it.

The strip method is applied to the String object that is read in from the file, not from the path. Most likely some tool you are using added a newline to the content of the secret file. When you don’t use strip, the newline is considered part of the secret and goes into the computation of the key.

The docs are probably correct in the case that you don’t have a newline in your secret file, but adding .strip or changing to use the Chef::EncryptedDataBagItem.load_secret('secret_file')) method would be more resilient.

Can you file an issue for that here: https://github.com/chef/chef-web-docs/issues/new ?

Thanks

Yep, I understood that - that’s why I wrote: “IO.read(…).strip” and not “IO.read(….strip):slight_smile:

No, it didn’t - no newline added. I showed the md5sums of the file on the system where I ran knife … and on the the system, where I ran chef-client, which invoked the recipe which read from the encrypted data bag.

As you can see there, .data_bags/ew-icinga-client.secret and /tmp/secret_io are identical.

Yes, I understand. And the newline at the end of the file is already there, when knife … --secret-file ./.data_bags/ew-icinga-client.secret is called. Seems like knife does a strip when it reads from the file. Which I find pretty bad, to be honest. What would happen, if somebody would use a “secret” of 20x " " (space) or 20x “\n” (newline) — and nothing else? Would this then be identical to 1x " "?

I transferred the secret file from the system where knife ran to the target server with scp, so, no, it cannot be that the file was modified; and I also compared the md5s of source and destination. They are identical.

Can you please come up with a setup, where IO.read() would work in this case?

Will do so, but, actually, I’d think, that knife is also not working correctly.

FWIW, I now made sure, that the secret file has NO newline at the end. Before, I had created the secret file like so:

(< /dev/urandom tr -dc '[:digit:]' | head -c${1:-500};echo;) > secret_file

Now I did:

f=open("secret_file","w")
f.write(''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(N)))
f.close()

And now IO.read() works. But, well, do you really think, that this is correct? Seems like knife is also doing a strip of some sorts. Can you confirm?

Thanks,
Alexander

Further down, you say that your secret file does end with a newline. So it doesn’t matter if the file is identical. In one case the content has newlines removed after reading the file and in the other case it does not.

In practice, no one does this. What people actually do pretty often in practice is copy the secret around using tools/processes that add newlines or remove them.

Down below you show how things worked when you removed the newline yourself.

Anything that calls Chef::EncryptedDataBagItem.load_secret(‘secret_file’) will strip whitespace, and I would guess that’s what knife does. As I said before, that may not be the most pedantically correct thing, but it solves a problem a lot of people encounter and since the software exists to solve problems for people, that makes it correct in a more important way. Ultimately, the solution for your issue is to update the docs to always use the load_secret method so things are consistently normalized.

Have done so now → https://github.com/chef/chef-web-docs/issues/911