Skip to content

Commit 6db3cb3

Browse files
jaymzhfacebook-github-bot
authored andcommitted
new cookbook: fb_networkmanager (facebook#127)
Summary: This cookbook aims to manage NetworkManager in the most controlled way possible. It might-could-should be sent over to CPE cookbooks, I can redirect over there if preferred. While, much to my dismay, some people do run NM on servers, and this will work for that, it was built to handle this client machine case. NetworkManager treats its config files as databases and can change them, so they can't be handled simply as templates - more finesse is needed. However, write-once doesn't provide the level of management that FB-API-style cookbooks expect. This marries the two. All desired settings are managed 100%, and NetworkManager is allowed to make additive settings (such as "seen-bssids" or "password" [sic]) so that it can continue to function for users. It is immune to the format changes that NM can make - spacing and ordering changes will not cause the file to be updated any more than ignorable settings. When updating the file, care is taken to stick close to NM's style. I considered making blacklist/whitelists of fields that NM could modify, but in practice since we merge in the user's config, the user's config is effectively a blacklist, and whitelist would likely be far too fragile. It has full docs, including on it's Migration Feature which will be useful for anyone who had their own Chef to do this but wants to migrate. It also comes with pretty good test coverage. Pull Request resolved: facebook#127 Differential Revision: D69685647 fbshipit-source-id: 45632b1015565a87cc515f2b74d3dd15a6cf9894
1 parent 5c29549 commit 6db3cb3

File tree

12 files changed

+1042
-0
lines changed

12 files changed

+1042
-0
lines changed

cookbooks/fb_init_sample/metadata.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
depends 'fb_mlocate'
4848
depends 'fb_modprobe'
4949
depends 'fb_motd'
50+
depends 'fb_networkmanager'
5051
depends 'fb_nscd'
5152
depends 'fb_nsswitch'
5253
depends 'fb_postfix'

cookbooks/fb_init_sample/recipes/default.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@
8282
# until we defined a UID_MAP that works with testing, this can't
8383
# run in kitchen tests
8484
# include_recipe 'fb_users'
85+
if node.debian? || node.ubuntu?
86+
include_recipe 'fb_networkmanager'
87+
end
8588
if node.centos?
8689
# We turn this off because the override causes intermittent failures in
8790
# Travis when rsyslog is restarted
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
fb_networkmanager Cookbook
2+
============================
3+
An attribute-driven API to configure Network Manager
4+
5+
Requirements
6+
------------
7+
8+
Attributes
9+
----------
10+
* node['fb_networkmanager']['config']
11+
* node['fb_networkmanager']['enable']
12+
* node['fb_networkmanager']['system_connections']
13+
* node['fb_networkmanager']['system_connections'][$NAME]['_defaults']
14+
* node['fb_networkmanager']['system_connections'][$NAME]['_migrate_from']
15+
* node['fb_networkmanater']['extra_packages']
16+
* node['fb_networkmanater']['manage_packages']
17+
18+
Usage
19+
-----
20+
### Packages
21+
22+
By default this cookbook will install the network-manager package for your
23+
platform. If you intend to make connections that require optional packages,
24+
such as VPN connections, you can add the necessary packages to the array in
25+
`node['fb_networkmanater']['extra_packages']` so that they will be installed
26+
early enough.
27+
28+
If you'd prefer to manage packages yourself you may set
29+
`node['fb_networkmanater']['manage_packages']` to `false`.
30+
31+
### Config
32+
33+
The global config (`/etc/NetworkManager/NetworkManager.conf`), is controlled
34+
by the `config` hash. It's a two-level hash where the top-level is INI section
35+
names and the second level is key-value pairs for the options in that section.
36+
37+
For example:
38+
39+
```ruby
40+
node.default['fb_networkmanager']['config']['main']['foo'] = 'bar'
41+
```
42+
43+
would render as:
44+
45+
```text
46+
[main]
47+
foo=bar
48+
```
49+
50+
The default config is based on the Ubuntu config but should be safe for all
51+
distros.
52+
53+
### System Connections
54+
55+
Network Manager unfortunately uses the files in the `system-connections` folder
56+
as a data store about those networks. This means they can change out from under
57+
you as it addds its own information. For example, for WiFi entries, it can add
58+
BSSIDs it has seen to the file.
59+
60+
As such using the desired config to populate a template is not sufficient - this
61+
would both lose data that Network Manager wants and also cause a lot of
62+
unnecessary resource firing.
63+
64+
To work around this scenario, this cookbook loads in the existing file, merges
65+
in the desired config, and then checks to see if the resulting contents are
66+
different from just the loaded file. If the values would not change, then we do
67+
not write out the new config. If the values are different, then we write out
68+
the merged config. Since Network Manager can write out values in a different
69+
order or with different spacing, we dont' compare the actual files, but instead
70+
the parsed data. We leverage IniParse for reading and writing INI files since
71+
it is bundled with Chef.
72+
73+
All that said, the system_connections hash works a lot like the `config` hash,
74+
except there's an extra level for each connection. For example:
75+
76+
```ruby
77+
node.default['fb_networkmanager']['system-connections']['mywifi'] = {
78+
'connection' => {
79+
'type' => 'wifi',
80+
'id' => 'Cool Wifi',
81+
'uuid' => '...',
82+
},
83+
'wifi' => {
84+
'mode' => 'infrastructure',
85+
'ssid' => 'Cool Wifi',
86+
},
87+
'wifi-security' => {
88+
'auth-alg' => 'open',
89+
'key-mgmt' => 'wpa-psk',
90+
'psk' => 'SuperS3kr1t',
91+
},
92+
}
93+
```
94+
95+
Would create `/etc/NetworkManager/system-connections/fb_networkmanager_mywifi`
96+
with this content:
97+
98+
```text
99+
[connection]
100+
id=Cool Wifi
101+
uuid=...
102+
type=wifi
103+
104+
[wifi]
105+
mode=infrastructure
106+
ssid=Cool Wifi
107+
108+
[wifi-security]
109+
auth-alg=open
110+
key-mgmt=wpa-psk
111+
psk=SuperS3kr1t
112+
```
113+
114+
Note that all files we make are prefixed with `fb_networkmanager`, so that we
115+
can cleanup files we created that are no longer in the config.
116+
117+
### A note on booleans
118+
119+
It is worth noting that various plugins and parts of the config expect
120+
different kinds of booleans - some `true` and `false`, others `yes` and `no`.
121+
Normally, an FB Attribute API cookbook would take a true ruby boolean and
122+
convert it to the appropriate string for a system, but since it's not
123+
consistent across NM, we leave it to the user to specify the right one for the
124+
right value. This is true both in `config` and in `system_connections`.
125+
126+
### A note on UUIDs
127+
128+
We generally recommend coming up with a static UUID per connection you want
129+
to rollout. For example, generate a UUID (for example using `uuidgen`, or
130+
by `cat /proc/sys/kernel/random/uuid`), and then associate that with given
131+
connection, statically in your config. You must use a different UUID for each
132+
connection (obviously), but using the same UUID for the same connection across
133+
machines makes debugging easier.
134+
135+
However, if you want truly unique UUIDs, one option is to just not specify a
136+
UUID and let Network Manager fill one in. However, not all versions of NM
137+
support this, and some will just ignore that connections.
138+
139+
You can't just generate UUIDs in the recipe, as they'll change on every run. So
140+
here's one way to solve that problem: build each UUID seeded with the hostname
141+
and the connection name so they stay the same across runs:
142+
143+
```ruby
144+
node.default['fb_networkmanager']['system-connections']['mywifi'] = {
145+
'connection' => {
146+
'type' => 'wifi',
147+
'id' => 'Cool Wifi',
148+
'uuid' => UUIDTools::UUID.sha1_create(
149+
UUIDTools::UUID_DNS_NAMESPACE,
150+
"#{node['fqdn']}/Cool Wifi",
151+
),
152+
},
153+
...
154+
}
155+
```
156+
157+
#### Migrating from existing configs
158+
159+
Migrating to this cookbook could potentially pose a problem: you want all the
160+
information from the existing connection, but you don't want a duplicate
161+
connection.
162+
163+
We provide a `_migrate_from` key. When populated, we'll use that as our base
164+
config the first time, merge any data provided in the node, and then delete
165+
the old config.
166+
167+
This provides seemless transition - it will preserve the UUID, which will
168+
keep network manager from thinking any connections went away, and ensure
169+
in-use connections don't drop.
170+
171+
For example, let's say you had droped a file
172+
`/etc/NetworkManager/system-connections/OurCorpWifi` that you had dropped off
173+
with `cookbook_file`, or a script, or even that you had was built through
174+
manually setting it up in the NM GUI. You could then do:
175+
176+
```ruby
177+
node.default['fb_networkmanager']['system-connections']['our_corp_wifi'] = {
178+
'_migrate_from' => 'OurCorpWifi',
179+
'connection' => {
180+
'type' => 'wifi',
181+
'id' => 'OurCorpWifi',
182+
},
183+
'wifi' => {
184+
'mode' => 'infrastructure',
185+
'ssid' => 'OurCorpWifi',
186+
},
187+
'wifi-security' => {
188+
'auth-alg' => 'open',
189+
'key-mgmt' => 'wpa-psk',
190+
'psk' => 'SuperS3kr1t',
191+
},
192+
}
193+
```
194+
195+
Then anything not specified here will be pulled in from the existing
196+
`OurCorpWifi` file. Note that any settings that you care about should be
197+
specified in the node to ensure that on new setups, you're not missing critical
198+
configuration.
199+
200+
Note that if the original service file isn't there, Chef will just create a new
201+
connection file (though it will warn). Also note that once Chef has created one,
202+
it stop pulling in the old file (and will remove it).
203+
204+
#### Providing defaults
205+
206+
In general, the Chef config wins over the user config as described above.
207+
However it is often desirable to specify a default config in case the user does
208+
not specify anything. In that case you can use the `_defaults`. For example:
209+
210+
```ruby
211+
node.default['fb_networkmanager']['system-connections']['our_corp_wifi'] = {
212+
'_migrate_from' => 'OurCorpWifi',
213+
'_defaults' => {
214+
'connection' => {
215+
'autoconnect-priority' => '100',
216+
}
217+
},
218+
'connection' => {
219+
'type' => 'wifi',
220+
'id' => 'OurCorpWifi',
221+
},
222+
'wifi' => {
223+
'mode' => 'infrastructure',
224+
'ssid' => 'OurCorpWifi',
225+
},
226+
'wifi-security' => {
227+
'auth-alg' => 'open',
228+
'key-mgmt' => 'wpa-psk',
229+
'psk' => 'SuperS3kr1t',
230+
},
231+
}
232+
```
233+
234+
The rendered config here will set `connection.autoconnect-priority` to 100
235+
if there is no value for it found in the existing file, but will use the value
236+
in the file if it exists. The logic here is quite simple:
237+
238+
defaults < user config < chef config
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#
2+
# Cookbook:: fb_networkmanager
3+
# Recipe:: default
4+
#
5+
# Copyright (c) 2020-present, Vicarious, Inc.
6+
# Copyright (c) 2020-present, Facebook, Inc.
7+
# All rights reserved.
8+
#
9+
# Licensed under the Apache License, Version 2.0 (the "License");
10+
# you may not use this file except in compliance with the License.
11+
# You may obtain a copy of the License at
12+
#
13+
# http://www.apache.org/licenses/LICENSE-2.0
14+
#
15+
# Unless required by applicable law or agreed to in writing, software
16+
# distributed under the License is distributed on an "AS IS" BASIS,
17+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
# See the License for the specific language governing permissions and
19+
# limitations under the License.
20+
#
21+
22+
default['fb_networkmanager'] = {
23+
'enable' => false,
24+
'manage_packages' => true,
25+
'extra_packages' => [],
26+
'system_connections' => {},
27+
'config' => {
28+
'main' => {
29+
'plugins' => [
30+
'ifupdown',
31+
'keyfile',
32+
],
33+
},
34+
'ifupdown' => {
35+
'managed' => false,
36+
},
37+
'device' => {
38+
'wifi.scan-rand-mac-address' => false,
39+
},
40+
},
41+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#
2+
# Cookbook:: fb_networkmanager
3+
# Recipe:: default
4+
#
5+
# Copyright (c) 2020-present, Vicarious, Inc.
6+
# Copyright (c) 2020-present, Facebook, Inc.
7+
# All rights reserved.
8+
#
9+
# Licensed under the Apache License, Version 2.0 (the "License");
10+
# you may not use this file except in compliance with the License.
11+
# You may obtain a copy of the License at
12+
#
13+
# http://www.apache.org/licenses/LICENSE-2.0
14+
#
15+
# Unless required by applicable law or agreed to in writing, software
16+
# distributed under the License is distributed on an "AS IS" BASIS,
17+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
# See the License for the specific language governing permissions and
19+
# limitations under the License.
20+
#
21+
22+
require 'iniparse'
23+
24+
module FB
25+
class Networkmanager
26+
def self.active_connections
27+
return {} unless ::File.exist?('/usr/bin/nmcli')
28+
29+
s = Mixlib::ShellOut.new('nmcli -t conn show --active').run_command
30+
return {} if s.error?
31+
32+
cons = {}
33+
s.stdout.each_line do |line|
34+
name, uuid, type, device = line.strip.split(':')
35+
cons[name] = {
36+
'uuid' => uuid,
37+
'type' => type,
38+
'device' => device,
39+
}
40+
end
41+
cons
42+
end
43+
44+
def self.to_ini(content, array_sep)
45+
IniParse.gen do |doc|
46+
content.each_pair do |sect, opts|
47+
doc.section(sect, :option_sep => '=') do |section|
48+
opts.each_pair do |opt, val|
49+
v = [val].flatten.join(array_sep)
50+
# for multi-value
51+
if val.is_a?(Array)
52+
v << array_sep
53+
end
54+
section.option(opt, v)
55+
end
56+
end
57+
end
58+
end.to_s
59+
end
60+
end
61+
end

0 commit comments

Comments
 (0)