cookbooks/fb_apt/libraries/default.rb (106 lines of code) (raw):
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
#
# Copyright (c) 2016-present, Facebook, Inc.
# All rights reserved.
#
# 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.
#
module FB
# APT utility functions
class Apt
# Internal helper function to generate /etc/apt.conf entries
def self._gen_apt_conf_entry(k, v, i = 0)
indent = ' ' * i
case v
when Hash
s = "\n#{indent}#{k} {"
v.each do |kk, vv|
s += self._gen_apt_conf_entry(kk, vv, i + 2)
end
s += "\n#{indent}};"
return s
when Array
s = ''
v.each do |vv|
s += self._gen_apt_conf_entry(k, vv, i)
end
return s
when TrueClass
return "\n#{indent}#{k} \"true\";"
when FalseClass
return "\n#{indent}#{k} \"false\";"
else
return "\n#{indent}#{k} \"#{v}\";"
end
end
# Grab all keyrings owned by a package. We do not include
def self._get_owned_keyring_files(node)
s = dpkg('-S /etc/apt/trusted.gpg.d/*')
# owned keys are on stdout, unowned keys are on stderr
owned_keys = Set.new
packages = []
s.stdout.each_line do |line|
package, file = line.strip.split(': ')
# dpkg reports on all files that WOULD match the path, even
# if they don't exist. Skip ones that have been removed
next unless ::File.exist?(file)
owned_keys.add(file)
packages << package
end
Chef::Log.debug("fb_apt[keys]: Owned keys: #{owned_keys}")
packages.each do |pkg|
cmd = dpkg("-V #{pkg}")
modified_files =
Set.new(cmd.stdout.lines.map { |line| line.split.last })
# keys that in both sets are modified keys
modified_keys = owned_keys & modified_files
Chef::Log.debug(
"fb_apt[keys]: Modified keys from #{pkg}: #{modified_keys}",
)
unless modified_keys.empty?
if node['fb_apt']['allow_modified_pkg_keyrings']
Chef::Log.warn(
'fb_apt[keys]: The following keys have been modified but we ' +
'are still trusting it, due to ' +
'node["fb_apt"]["allow_modified_pkg_keyrings"]: ' +
modified_keys.to_a.join(', '),
)
else
fail 'fb_apt[keys]: The following keyrings would be trusted, but ' +
"has been modified since package (#{pkg}) was installed: " +
modified_keys.to_a.join(', ')
end
end
end
owned_keys.to_a
end
def self._run(cmd, arg)
Mixlib::ShellOut.new("LANG=C #{cmd} #{arg}").run_command
end
def self.dpkg(arg)
_run('dpkg', arg)
end
def self.aptkey(arg)
_run('apt-key', arg)
end
def self._extract_keyids(rings)
rings.map do |keyring|
cmd = aptkey("--keyring #{keyring} finger --keyid-format long")
cmd.error!
ids = cmd.stdout.lines.map do |line|
next unless line.start_with?('pub ')
line.split[1].split('/')[1]
end.compact
Chef::Log.debug(
"fb_apt[keys]: Keyids from #{keyring}: #{ids.join(', ')}",
)
ids
end.flatten
end
# Here ye here ye, read this before touching keys!
#
# On modern debian and ubuntu, all keys are stored in files in
# `/etc/apt/trusted.gpg.d/`, and **never** on `/etc/apt/trusted.gpg`,
# this we can know what the Distro keys are by reading all keys in
# all keyring files owned by packages. So what's what we populate
# the default list with.
#
# However, for Ubuntu <= 16.04 they are on the `/etc/apt/trusted.gpg` list,
# so we hard-code those, the distros are old enough they won't change.
def self.get_official_keyids(node)
if node.ubuntu? && node['platform_version'].to_i <= 16
return %w{
40976EAF437D05B5
46181433FBB75451
3B4FE6ACC0B21F32
D94AA3F0EFE21092
0BFB847F3F272F5B
}
end
keyids = _extract_keyids(_get_owned_keyring_files(node))
Chef::Log.debug("fb_apt[keys]: Official keyids: #{keyids}")
keyids
end
def self.get_installed_keyids(node)
rings = _get_owned_keyring_files(node)
rings << '/etc/apt/trusted.gpg'
_extract_keyids(rings)
end
end
end