Pragmatic Chef: Verifying Remote Files

The first in a short series of Chef related blog posts

Imagine this everyday occurrence:

  • You are writing a Chef cookbook
  • You need to download the source code of a program as a tarball.
  • Being the good programmer you are, you use for Chef’s remote_file resource.
  • You grab the checksum of the file from the website and add it to the resource.

Everything is good right? Unfortunately not.

The Chef remote_file resource does have a checksum property, but it is not meant as a security measure. Instead it is implemented as a bandwidth saving property. If the checksum does not match, it will download the new version but no further validation will be done to match it with the checksum.

Here is an example of using a standard remote_file callback pattern to download and install from source.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
remote_file "Download Redis Source" do
  path "#{download_dir}/#{tarball}"
  source download_url
  backup false
  checksum sha256_checksum
  notifies :run, "execute[Unpack Redis Tarball]", :immediately
end

execute "Unpack Redis Tarball" do
  command "tar -xvzf #{tarball}"
  action :nothing
  cwd download_dir
  notifies :run, "execute[Make Redis]", :immediately
end

We are using the checksum field, but it is only checking for already downloaded versions. Here is a better pattern that forces a correct checksum on the downloaded file before you unpack it any further.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
remote_file "Download Redis Source" do
  path "#{download_dir}/#{tarball}"
  source download_url
  backup false
  checksum sha256_checksum
  notifies :create, "ruby_block[Validate Tarball Checksum]", :immediately
  not_if { redis_exists? && node['redisio']['safe_install'] }
end

ruby_block "Validate Tarball Checksum" do
  action :nothing
  block do
    require 'digest'
    checksum = Digest::SHA1.file("#{download_dir}/#{tarball}").hexdigest
    if checksum != node['redisio']['sha1_checksum']
      raise "Downloaded Tarball Checksum #{checksum} does not match known checksum #{node['redisio']['sha1_checksum']}"
    end
  end
  notifies :run, "execute[Unpack Redis Tarball]", :immediately
end

execute "Unpack Redis Tarball" do
  command "tar -xvzf #{tarball}"
  action :nothing
  cwd download_dir
  notifies :run, "execute[Make Redis]", :immediately
end

In this case Redis provides SHA1 checksums while remote_file uses SHA256 so we must add an additional node attribute that stores the SHA1 checksum. These examples were taken from my fork of the Redisio cookbook.

Andrew Gross is a Developer at Yipit, you can find him as @awgross on Twitter

Comments