Skip to content

Commit a106940

Browse files
jaymzhfacebook-github-bot
authored andcommitted
fb_apt: Significant updates (facebook#250)
Summary: * Deprecate `node['fb_apt']['repos']` which was always a bad API (sorry), and replace it with `node['fb_apt']['sources']` which integrates nicely with the new `node['fb_apt']['keymap']` * Deprecate `node['fb_apt']['keys']` which was very broken on modern apt and replace it with a new `node['fb_apt']['keymap']` * Update syntax for security and update repos on modern debian and ubuntu * Remove old Ubuntu 16 cruft * Lots of cleanups and refactoring for readability Signed-off-by: Phil Dibowitz <phil@ipom.com> Pull Request resolved: facebook#250 Differential Revision: D69614396 fbshipit-source-id: c64ea119f0505ee4d8b806e05276f8954c5998f2
1 parent 1815b26 commit a106940

File tree

6 files changed

+304
-140
lines changed

6 files changed

+304
-140
lines changed

cookbooks/fb_apt/README.md

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,25 @@ Requirements
88

99
Attributes
1010
----------
11+
* node['fb_apt']['allow_modified_pkg_keyrings']
12+
* node['fb_apt']['apt_update_log_path']
1113
* node['fb_apt']['config']
1214
* node['fb_apt']['distro']
15+
* node['fb_apt']['keymap']
16+
* node['fb_apt']['keymap'][$NAME]
1317
* node['fb_apt']['keys']
1418
* node['fb_apt']['keyserver']
1519
* node['fb_apt']['mirror']
16-
* node['fb_apt']['preserve_sources_list_d']
1720
* node['fb_apt']['preferences']
21+
* node['fb_apt']['preserve_sources_list_d']
22+
* node['fb_apt']['preserve_unknown_keyrings']
1823
* node['fb_apt']['repos']
24+
* node['fb_apt']['sources']
25+
* node['fb_apt']['sources'][$NAME]
1926
* node['fb_apt']['update_delay']
2027
* node['fb_apt']['want_backports']
2128
* node['fb_apt']['want_non_free']
2229
* node['fb_apt']['want_source']
23-
* node['fb_apt']['preserve_unknown_keyrings']
24-
* node['fb_apt']['allow_modified_pkg_keyrings']
25-
* node['fb_apt']['apt_update_log_path']
2630

2731
Usage
2832
-----
@@ -34,50 +38,94 @@ to 0. The actual update is done via the `execute[apt-get update]` resource,
3438
which other cookbooks can suscribe to or notify as well.
3539

3640
### Repository sources
41+
3742
By default the cookbook will setup the base distribution repos based on the
3843
codename (as defined in `node['lsb']['codename']`) using a sensible default
3944
mirror for the package sources. The mirror can be customized with
40-
`node['fb_apt']['mirror']`; if set to `nil`, base repos will not be included
41-
at all in `/etc/apt/sources.list`. If base repos are enabled, the additional
45+
`node['fb_apt']['mirror']`; if set to `nil`, base repos will not be included at
46+
all in `/etc/apt/sources.list`. If base repos are enabled, the additional
4247
`backports` and `non-free` sources can be enabled with the
4348
`node['fb_apt']['want_backports']` and `node['fb_apt']['want_non_free']`
4449
attributes, and source code repos can be enabled with
4550
`node['fb_apt']['want_source']`; these all default to `false`.
4651

47-
Additional repository sources can be added with `node['fb_apt']['repos']`. By
48-
default `fb_apt` will clobber existing contents in `/etc/apt/sources.list.d` to
49-
ensure it has full control on the repository list; this can be disabled with
52+
Additional repository sources can be added with `node['fb_apt']['sources']`
53+
in this way:
54+
55+
```ruby
56+
node.default['fb_apt']['sources']['cool_repo'] = {
57+
'url' => 'https://cool_repo.com/',
58+
'suite' => 'stable',
59+
'components' => ['main'],
60+
'key' => 'cool_repo', # this references keymap, see below
61+
}
62+
```
63+
64+
Entries in `sources` support the following keys:
65+
66+
* `type` - The type of repo, `deb` or `deb-src` - Optional, defaults to `deb`
67+
* `url` - The URL of the repo
68+
* `suite` - The suite to pull from - usually the OS version codename
69+
* `components` - An array of components
70+
* `options` - If present, must be a hash of options to put, such as `arch`
71+
* `key` - A special-case option. This should be a string that maps to a key
72+
in `node['fb_apt']['keymap']`. The `options` hash will be updated with the
73+
`signed-by` value set to the appropriate path for the keyring generated.
74+
75+
By default `fb_apt` will clobber existing contents in `/etc/apt/sources.list.d`
76+
to ensure it has full control on the repository list; this can be disabled with
5077
`node['fb_apt']['preserve_sources_list_d']`.
5178

79+
*NOTE*: Older versions of this cookbook used `node['fb_apt']['repos']`. This
80+
is deprecated. As of this writing, sources in this list will still be added
81+
to the system, but a warning will be printed. The old syntax was significantly
82+
lacking, didn't play well with keys, and was hard to modify.
83+
5284
### Keys
53-
They `keys` hash is pre-populated with any keys from pkg-owned keyrings that
54-
exist in `/etc/apt/trusted.gpg.d/` so you don't need to worry about keeping
55-
a list of repository keys in sync.
5685

57-
You can add to this, but setting a key of your keyid and a value of either `nil`
58-
or the PEM-encoded key. If `key` is `nil` the key will be automatically fetched
59-
from the `node['fb_apt']['keyserver']` keyserver (`keys.gnupg.net` by default).
60-
Example:
86+
The `node['fb_apt']['keymap']` is designed to make it easy to work with the
87+
per-repo keys that modern Apt requires. Simple associate a PEM value with a
88+
name, and then use that name in any entries in `node['fb_apt']['sources']`
89+
signed by that key. `fb_apt` will take the PEM, generate a keyring in
90+
`/etc/apt/trusted.gpg.d/${NAME}.gpg` and populate the signed-by values in your
91+
`sources.list`.
6192

62-
```
63-
node.default['fb_apt']['keys']['94558F59'] = nil
64-
node.default['fb_apt']['keys']['F3EFDBD9'] = <<-eos
93+
For example:
94+
95+
```ruby
96+
node.default['fb_apt']['keys']['cool'] = <<-eos
6597
-----BEGIN PGP PUBLIC KEY BLOCK-----
6698
...
99+
-----END PGP PUBLIC KEY BLOCK-----
67100
eos
101+
102+
node.default['fb_apt']['sources']['cool_app'] = {
103+
...
104+
'key' => 'cool',
105+
}
68106
```
69107

70-
Automatic key fetching can be disabled by setting the keyserver to `nil`; this
71-
will produce an exception for any unspecified key.
108+
You can also make the value a http/https URL, but if you do, the file will be
109+
placed as-is in `trusted.gpg.d`, so it must be of the right format. Chef's
110+
`remote_file` resource will be used to manage the file. This is intended for
111+
repos who make full keyrings available instead of armored PEMs.
72112

73-
By default any keyring in `/etc/apt/trusted.gpg.d` that is not owned by a
74-
package will be deleted unless you set `preserve_unknown_keyrings` to false.
113+
Anything in `/etc/apt/trusted.gpg.d` that is owned by a package or by this
114+
cookbook will be kept, but any other file in there will be removed. unless you
115+
set `preserve_unknown_keyrings` to false.
75116

76117
If a keyring owned by a package is found to have been modified (based on
77118
`dpkg -V`), then the run will fail, unless `allow_modified_pkg_keyrings` is
78119
set.
79120

121+
*NOTE*: Older versions of this cookbook used `node['fb_apt']['keys']` which
122+
attempted to pull keyid's from the internet and load them via the now-deprecated
123+
`apt-key`. Use of that API will cause a warning, though this cookbook does still
124+
support it for now. However, modern `apt-key` does nothing, so your config will
125+
break if you do not migrate.
126+
80127
### Configuration
128+
81129
APT behaviour can be customized using `node['fb_apt']['config']`, which will be
82130
used to populate `/etc/apt/apt.conf`. Note that this will take precedence over
83131
anything in `/etc/apt/apt.conf.d`. Example:
@@ -89,6 +137,7 @@ node.default['fb_apt']['config']['Acquire::http'].merge!({
89137
```
90138

91139
### Preferences
140+
92141
You can fine tune which versions of packages will be selected for installation
93142
by tweaking APT preferences via `node['fb_apt']['preferences']`. Note that we
94143
clobber the contents of `/etc/apt/preferences.d` to ensure this always takes
@@ -104,12 +153,14 @@ node.default['fb_apt']['preferences'][
104153
```
105154

106155
### Distro
156+
107157
As mentioned above, `fb_apt` can assemble the basic sources for you. It uses
108158
the LSB "codename" of the current systemd to build the URLs. In the event you
109159
want to use Chef to upgrade across distros, however, you can set
110160
`node['fb_apt']['distro']` to the appropriate name and it will be used instead.
111161

112162
### Logging `apt-get update`
163+
113164
Set `node['fb_apt']['apt_update_log_path']` to log stdout and stderr of the
114165
`apt-get update` command invoked by this cookbook. This may be useful for
115166
debugging purposes. The caller must handle log rotation.

cookbooks/fb_apt/attributes/default.rb

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,24 @@
2525
end
2626

2727
default['fb_apt'] = {
28+
'allow_modified_pkg_keyrings' => false,
29+
'apt_update_log_path' => nil,
2830
'config' => {},
29-
'repos' => [],
31+
'distro' => nil,
32+
'keymap' => {},
33+
# deprecated, use keymap instead
34+
'keys' => {},
3035
'keyserver' => 'keys.gnupg.net',
3136
'mirror' => mirror,
32-
'security_mirror' => security_mirror,
3337
'preferences' => {},
3438
'preserve_sources_list_d' => false,
39+
'preserve_unknown_keyrings' => false,
40+
# deprecated, use sources instead
41+
'repos' => [],
42+
'security_mirror' => security_mirror,
43+
'sources' => {},
3544
'update_delay' => 86400,
3645
'want_backports' => false,
3746
'want_non_free' => false,
3847
'want_source' => false,
39-
'preserve_unknown_keyrings' => false,
40-
'allow_modified_pkg_keyrings' => false,
41-
'apt_update_log_path' => nil,
4248
}
43-
# fb_apt must be defined for this to work...
44-
keys = FB::Apt.get_official_keyids(node).map { |id| [id, nil] }.to_h
45-
default['fb_apt']['keys'] = keys

cookbooks/fb_apt/libraries/default.rb

Lines changed: 114 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
module FB
2020
# APT utility functions
2121
class Apt
22+
TRUSTED_D = '/etc/apt/trusted.gpg.d'.freeze
23+
PEM_D = "#{Chef::Config[:file_cache_path]}/fb_apt_pems".freeze
24+
2225
# Internal helper function to generate /etc/apt.conf entries
2326
def self._gen_apt_conf_entry(k, v, i = 0)
2427
indent = ' ' * i
@@ -116,35 +119,121 @@ def self._extract_keyids(rings)
116119
end.flatten
117120
end
118121

119-
# Here ye here ye, read this before touching keys!
120-
#
121-
# On modern debian and ubuntu, all keys are stored in files in
122-
# `/etc/apt/trusted.gpg.d/`, and **never** on `/etc/apt/trusted.gpg`,
123-
# this we can know what the Distro keys are by reading all keys in
124-
# all keyring files owned by packages. So what's what we populate
125-
# the default list with.
126-
#
127-
# However, for Ubuntu <= 16.04 they are on the `/etc/apt/trusted.gpg` list,
128-
# so we hard-code those, the distros are old enough they won't change.
129-
def self.get_official_keyids(node)
130-
if node.ubuntu? && node['platform_version'].to_i <= 16
131-
return %w{
132-
40976EAF437D05B5
133-
46181433FBB75451
134-
3B4FE6ACC0B21F32
135-
D94AA3F0EFE21092
136-
0BFB847F3F272F5B
122+
def self.get_legacy_keyids
123+
_extract_keyids(['/etc/apt/trusted.gpg'])
124+
end
125+
126+
def self.determine_base_repo_components(node)
127+
components = %w{main}
128+
if node.ubuntu?
129+
components << 'universe'
130+
end
131+
132+
if node['fb_apt']['want_non_free']
133+
if node.debian?
134+
components += %w{contrib non-free non-free-firmware}
135+
elsif node.ubuntu?
136+
components += %w{restricted multiverse}
137+
else
138+
fail "Don't know how to setup non-free for #{node['platform']}"
139+
end
140+
end
141+
142+
components
143+
end
144+
145+
def self.base_sources(node)
146+
base_repos = {}
147+
sources = {}
148+
mirror = node['fb_apt']['mirror']
149+
security_mirror = node['fb_apt']['security_mirror']
150+
# By default, we want our current distro to assemble to repo URLs.
151+
# However, for when people want to upgrade across distros, we let
152+
# them specify a distro to upgrade to.
153+
distro = node['fb_apt']['distro'] || node['lsb']['codename']
154+
155+
# only add base repos if mirror is set and codename is available
156+
if mirror && distro
157+
components = FB::Apt.determine_base_repo_components(node)
158+
159+
base_repos['base'] = {
160+
'url' => mirror,
161+
'suite' => distro,
137162
}
163+
164+
# Security updates
165+
pv = node['platform_version'].to_i
166+
if node.debian? && distro != 'sid' && pv != 0 && pv > 9
167+
# In buster/10 and before the suite was ${distro}/updates
168+
# After that it became ${distro}-security
169+
suite = pv == 10 ? "#{distro}/updates" : "#{distro}-security"
170+
base_repos['security'] = {
171+
'url' => "#{security_mirror}debian-security",
172+
'suite' => suite,
173+
}
174+
elsif node.ubuntu?
175+
base_repos['security'] = {
176+
'url' => security_mirror,
177+
'suite' => "#{distro}-security",
178+
}
179+
end
180+
181+
# Debian Sid doesn't have updates or backports
182+
unless node.debian? && distro == 'sid'
183+
# Stable updates
184+
base_repos['updates'] = {
185+
'url' => mirror,
186+
'suite' => "#{distro}-updates",
187+
}
188+
189+
if node['fb_apt']['want_backports']
190+
base_repos['backports'] = {
191+
'url' => mirror,
192+
'suite' => "#{distro}-backports",
193+
}
194+
end
195+
end
196+
197+
base_keyring = node.debian? ?
198+
'/usr/share/keyrings/debian-archive-keyring.gpg' :
199+
'/usr/share/keyrings/ubuntu-archive-keyring.gpg'
200+
base_repos.each do |name, config|
201+
config.merge!({
202+
'options' => {
203+
'signed-by' => base_keyring,
204+
},
205+
'components' => components,
206+
'type' => 'deb',
207+
})
208+
sources[name] = config
209+
if node['fb_apt']['want_source']
210+
source["#{name}_src"] = config.merge({ 'type' => 'deb-src' })
211+
end
212+
end
138213
end
139-
keyids = _extract_keyids(_get_owned_keyring_files(node))
140-
Chef::Log.debug("fb_apt[keys]: Official keyids: #{keyids}")
141-
keyids
214+
sources
215+
end
216+
217+
def self.gen_sources_line(config)
218+
type = config['type'] || 'deb'
219+
options = config['options'].dup || {}
220+
if config['key']
221+
options['signed-by'] = keyring_path_from_name(config['key'])
222+
end
223+
c_str = config['components'].join(' ')
224+
options_str = ''
225+
unless options.empty?
226+
options_str = "[#{options.map { |k, v| "#{k}=#{v}" }.join(' ')}] "
227+
end
228+
"#{type} #{options_str}#{config['url']} #{config['suite']} #{c_str}"
229+
end
230+
231+
def self.pem_path_from_name(name)
232+
"#{PEM_D}/#{name}.asc"
142233
end
143234

144-
def self.get_installed_keyids(node)
145-
rings = _get_owned_keyring_files(node)
146-
rings << '/etc/apt/trusted.gpg'
147-
_extract_keyids(rings)
235+
def self.keyring_path_from_name(name)
236+
"#{TRUSTED_D}/#{name}.gpg"
148237
end
149238
end
150239
end

0 commit comments

Comments
 (0)