]> git.openstreetmap.org Git - chef.git/blob - cookbooks/networking/recipes/default.rb
Merge interface families
[chef.git] / cookbooks / networking / recipes / default.rb
1 #
2 # Cookbook:: networking
3 # Recipe:: default
4 #
5 # Copyright:: 2010, OpenStreetMap Foundation.
6 # Copyright:: 2009, Opscode, Inc.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License");
9 # you may not use this file except in compliance with the License.
10 # You may obtain a copy of the License at
11 #
12 #     https://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS,
16 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
19 #
20 # = Requires
21 # * node[:networking][:nameservers]
22
23 require "ipaddr"
24 require "yaml"
25
26 keys = data_bag_item("networking", "keys")
27
28 package "netplan.io"
29
30 netplan = {
31   "network" => {
32     "version" => 2,
33     "renderer" => "networkd",
34     "ethernets" => {},
35     "bonds" => {},
36     "vlans" => {}
37   }
38 }
39
40 node[:networking][:interfaces].each do |name, interface|
41   if interface[:interface]
42     if interface[:role] && (role = node[:networking][:roles][interface[:role]])
43       if interface[:inet] && role[:inet]
44         node.default[:networking][:interfaces][name][:inet][:prefix] = role[:inet][:prefix]
45         node.default[:networking][:interfaces][name][:inet][:gateway] = role[:inet][:gateway]
46         node.default[:networking][:interfaces][name][:inet][:routes] = role[:inet][:routes]
47       end
48
49       if interface[:inet6] && role[:inet6]
50         node.default[:networking][:interfaces][name][:inet6][:prefix] = role[:inet6][:prefix]
51         node.default[:networking][:interfaces][name][:inet6][:gateway] = role[:inet6][:gateway]
52         node.default[:networking][:interfaces][name][:inet6][:routes] = role[:inet6][:routes]
53       end
54
55       node.default[:networking][:interfaces][name][:metric] = role[:metric]
56       node.default[:networking][:interfaces][name][:zone] = role[:zone]
57     end
58
59     interface = node[:networking][:interfaces][name]
60
61     deviceplan = if interface[:interface] =~ /^(.*)\.(\d+)$/
62                    netplan["network"]["vlans"][interface[:interface]] ||= {
63                      "id" => Regexp.last_match(2).to_i,
64                      "link" => Regexp.last_match(1),
65                      "accept-ra" => false,
66                      "addresses" => [],
67                      "routes" => []
68                    }
69                  elsif interface[:interface] =~ /^bond\d+$/
70                    netplan["network"]["bonds"][interface[:interface]] ||= {
71                      "accept-ra" => false,
72                      "addresses" => [],
73                      "routes" => []
74                    }
75                  else
76                    netplan["network"]["ethernets"][interface[:interface]] ||= {
77                      "accept-ra" => false,
78                      "addresses" => [],
79                      "routes" => []
80                    }
81                  end
82
83     if interface[:inet]
84       deviceplan["addresses"].push("#{interface[:inet][:address]}/#{interface[:inet][:prefix]}")
85     end
86
87     if interface[:inet6]
88       deviceplan["addresses"].push("#{interface[:inet6][:address]}/#{interface[:inet6][:prefix]}")
89     end
90
91     if interface[:mtu]
92       deviceplan["mtu"] = interface[:mtu]
93     end
94
95     if interface[:bond]
96       deviceplan["interfaces"] = interface[:bond][:slaves].to_a
97
98       deviceplan["parameters"] = {
99         "mode" => interface[:bond][:mode] || "active-backup",
100         "mii-monitor-interval" => interface[:bond][:miimon] || 100,
101         "down-delay" => interface[:bond][:downdelay] || 200,
102         "up-delay" => interface[:bond][:updelay] || 200
103       }
104
105       deviceplan["parameters"]["primary"] = interface[:bond][:slaves].first if deviceplan["parameters"]["mode"] == "active-backup"
106       deviceplan["parameters"]["transmit-hash-policy"] = interface[:bond][:xmithashpolicy] if interface[:bond][:xmithashpolicy]
107       deviceplan["parameters"]["lacp-rate"] = interface[:bond][:lacprate] if interface[:bond][:lacprate]
108     end
109
110     if interface[:inet]
111       if interface[:inet][:gateway] && interface[:inet][:gateway] != interface[:inet][:address]
112         deviceplan["routes"].push(
113           "to" => "0.0.0.0/0",
114           "via" => interface[:inet][:gateway],
115           "metric" => interface[:metric],
116           "on-link" => true
117         )
118       end
119
120       if interface[:inet][:routes]
121         interface[:inet][:routes].each do |to, parameters|
122           next if parameters[:via] == interface[:inet][:address]
123
124           route = {
125             "to" => to
126           }
127
128           route["type"] = parameters[:type] if parameters[:type]
129           route["via"] = parameters[:via] if parameters[:via]
130           route["metric"] = parameters[:metric] if parameters[:metric]
131
132           deviceplan["routes"].push(route)
133         end
134       end
135     end
136
137     if interface[:inet6]
138       if interface[:inet6][:gateway] && interface[:inet6][:gateway] != interface[:inet6][:address]
139         deviceplan["routes"].push(
140           "to" => "::/0",
141           "via" => interface[:inet6][:gateway],
142           "metric" => interface[:metric],
143           "on-link" => true
144         )
145
146         # This ordering relies on systemd-networkd adding routes
147         # in reverse order and will need moving before the previous
148         # route once that is fixed:
149         #
150         # https://github.com/systemd/systemd/issues/5430
151         # https://github.com/systemd/systemd/pull/10938
152         if !IPAddr.new(interface[:inet6][:address]).mask(interface[:inet6][:prefix]).include?(interface[:inet6][:gateway]) &&
153            !IPAddr.new("fe80::/64").include?(interface[:inet6][:gateway])
154           deviceplan["routes"].push(
155             "to" => interface[:inet6][:gateway],
156             "scope" => "link"
157           )
158         end
159       end
160
161       if interface[:inet6][:routes]
162         interface[:inet6][:routes].each do |to, parameters|
163           next if parameters[:via] == interface[:inet6][:address]
164
165           route = {
166             "to" => to
167           }
168
169           route["type"] = parameters[:type] if parameters[:type]
170           route["via"] = parameters[:via] if parameters[:via]
171           route["metric"] = parameters[:metric] if parameters[:metric]
172
173           deviceplan["routes"].push(route)
174         end
175       end
176     end
177   else
178     node.rm(:networking, :interfaces, name)
179   end
180 end
181
182 netplan["network"]["bonds"].each_value do |bond|
183   bond["interfaces"].each do |interface|
184     netplan["network"]["ethernets"][interface] ||= { "accept-ra" => false, "optional" => true }
185   end
186 end
187
188 netplan["network"]["vlans"].each_value do |vlan|
189   unless vlan["link"] =~ /^bond\d+$/
190     netplan["network"]["ethernets"][vlan["link"]] ||= { "accept-ra" => false }
191   end
192 end
193
194 file "/etc/netplan/00-installer-config.yaml" do
195   action :delete
196 end
197
198 file "/etc/netplan/01-netcfg.yaml" do
199   action :delete
200 end
201
202 file "/etc/netplan/50-cloud-init.yaml" do
203   action :delete
204 end
205
206 file "/etc/netplan/99-chef.yaml" do
207   owner "root"
208   group "root"
209   mode "644"
210   content YAML.dump(netplan)
211 end
212
213 package "cloud-init" do
214   action :purge
215 end
216
217 if node[:networking][:wireguard][:enabled]
218   wireguard_id = persistent_token("networking", "wireguard")
219
220   node.default[:networking][:wireguard][:address] = "fd43:e709:ea6d:1:#{wireguard_id[0, 4]}:#{wireguard_id[4, 4]}:#{wireguard_id[8, 4]}:#{wireguard_id[12, 4]}"
221
222   package "wireguard-tools" do
223     compile_time true
224     options "--no-install-recommends"
225   end
226
227   directory "/var/lib/systemd/wireguard" do
228     owner "root"
229     group "systemd-network"
230     mode "750"
231     compile_time true
232   end
233
234   file "/var/lib/systemd/wireguard/private.key" do
235     action :create_if_missing
236     owner "root"
237     group "systemd-network"
238     mode "640"
239     content %x(wg genkey)
240     compile_time true
241   end
242
243   node.default[:networking][:wireguard][:public_key] = %x(wg pubkey < /var/lib/systemd/wireguard/private.key).chomp
244
245   file "/var/lib/systemd/wireguard/preshared.key" do
246     action :create_if_missing
247     owner "root"
248     group "systemd-network"
249     mode "640"
250     content keys["wireguard"]
251   end
252
253   if node[:roles].include?("gateway")
254     search(:node, "roles:gateway") do |gateway|
255       next if gateway.name == node.name
256       next unless gateway[:networking][:wireguard] && gateway[:networking][:wireguard][:enabled]
257
258       allowed_ips = gateway.ipaddresses(:role => :internal).map(&:subnet)
259
260       node.default[:networking][:wireguard][:peers] << {
261         :public_key => gateway[:networking][:wireguard][:public_key],
262         :allowed_ips => allowed_ips,
263         :endpoint => "#{gateway.name}:51820"
264       }
265     end
266
267     search(:node, "roles:prometheus") do |server|
268       allowed_ips = server.ipaddresses(:role => :internal).map(&:subnet)
269
270       if server[:networking][:private_address]
271         allowed_ips << "#{server[:networking][:private_address]}/32"
272       end
273
274       node.default[:networking][:wireguard][:peers] << {
275         :public_key => server[:networking][:wireguard][:public_key],
276         :allowed_ips => allowed_ips,
277         :endpoint => "#{server.name}:51820"
278       }
279     end
280
281     node.default[:networking][:wireguard][:peers] << {
282       :public_key => "7Oj9ufNlgidyH/xDc+aHQKMjJPqTmD/ab13agMh6AxA=",
283       :allowed_ips => "10.0.16.1/32",
284       :endpoint => "gate.compton.nu:51820"
285     }
286
287     # Grant home
288     node.default[:networking][:wireguard][:peers] << {
289       :public_key => "RofATnvlWxP3mt87+QKRXFE5MVxtoCcTsJ+yftZYEE4=",
290       :allowed_ips => "10.89.122.1/32",
291       :endpoint => "gate.firefishy.com:51820"
292     }
293
294     # Grant roaming
295     node.default[:networking][:wireguard][:peers] << {
296       :public_key => "YbUkREE9TAmomqgL/4Fh2e5u2Hh7drN/2o5qg3ndRxg=",
297       :allowed_ips => "10.89.123.1/32",
298       :endpoint => "roaming.firefishy.com:51820"
299     }
300   elsif node[:roles].include?("shenron")
301     search(:node, "roles:gateway") do |gateway|
302       allowed_ips = gateway.ipaddresses(:role => :internal).map(&:subnet)
303
304       node.default[:networking][:wireguard][:peers] << {
305         :public_key => gateway[:networking][:wireguard][:public_key],
306         :allowed_ips => allowed_ips,
307         :endpoint => "#{gateway.name}:51820"
308       }
309     end
310   end
311
312   template "/etc/systemd/network/wireguard.netdev" do
313     source "wireguard.netdev.erb"
314     owner "root"
315     group "systemd-network"
316     mode "640"
317   end
318
319   template "/etc/systemd/network/wireguard.network" do
320     source "wireguard.network.erb"
321     owner "root"
322     group "root"
323     mode "644"
324   end
325
326   if node[:lsb][:release].to_f < 20.04
327     execute "ip-link-delete-wg0" do
328       action :nothing
329       command "ip link delete wg0"
330       subscribes :run, "template[/etc/systemd/network/wireguard.netdev]"
331       only_if { ::File.exist?("/sys/class/net/wg0") }
332     end
333
334     service "systemd-networkd" do
335       action :nothing
336       subscribes :restart, "template[/etc/systemd/network/wireguard.netdev]"
337       subscribes :restart, "template[/etc/systemd/network/wireguard.network]"
338       not_if { kitchen? }
339     end
340   else
341     execute "networkctl-delete-wg0" do
342       action :nothing
343       command "networkctl delete wg0"
344       subscribes :run, "template[/etc/systemd/network/wireguard.netdev]"
345       only_if { ::File.exist?("/sys/class/net/wg0") }
346     end
347
348     execute "networkctl-reload" do
349       action :nothing
350       command "networkctl reload"
351       subscribes :run, "template[/etc/systemd/network/wireguard.netdev]"
352       subscribes :run, "template[/etc/systemd/network/wireguard.network]"
353       not_if { kitchen? }
354     end
355   end
356 end
357
358 ohai "reload-hostname" do
359   action :nothing
360   plugin "hostname"
361 end
362
363 execute "hostnamectl-set-hostname" do
364   command "hostnamectl set-hostname #{node[:networking][:hostname]}"
365   notifies :reload, "ohai[reload-hostname]"
366   not_if { kitchen? || node[:hostnamectl][:static_hostname] == node[:networking][:hostname] }
367 end
368
369 template "/etc/hosts" do
370   source "hosts.erb"
371   owner "root"
372   group "root"
373   mode "644"
374   not_if { kitchen? }
375 end
376
377 service "systemd-resolved" do
378   action [:enable, :start]
379 end
380
381 directory "/etc/systemd/resolved.conf.d" do
382   owner "root"
383   group "root"
384   mode "755"
385 end
386
387 template "/etc/systemd/resolved.conf.d/99-chef.conf" do
388   source "resolved.conf.erb"
389   owner "root"
390   group "root"
391   mode "644"
392   notifies :restart, "service[systemd-resolved]", :immediately
393 end
394
395 if node[:filesystem][:by_mountpoint][:"/etc/resolv.conf"]
396   execute "umount-resolve-conf" do
397     command "umount -c /etc/resolv.conf"
398   end
399 end
400
401 link "/etc/resolv.conf" do
402   to "../run/systemd/resolve/stub-resolv.conf"
403 end
404
405 hosts = { :inet => [], :inet6 => [] }
406
407 search(:node, "networking:interfaces").collect do |n|
408   next if n[:fqdn] == node[:fqdn]
409
410   n.interfaces.each do |interface|
411     next unless interface[:role] == "external"
412
413     hosts[:inet] << interface[:inet][:address] if interface[:inet]
414     hosts[:inet6] << interface[:inet6][:address] if interface[:inet6]
415   end
416 end
417
418 package "nftables"
419
420 interfaces = []
421
422 node.interfaces(:role => :external).each do |interface|
423   interfaces << interface[:interface]
424 end
425
426 template "/etc/nftables.conf" do
427   source "nftables.conf.erb"
428   owner "root"
429   group "root"
430   mode "755"
431   variables :interfaces => interfaces, :hosts => hosts
432   notifies :reload, "service[nftables]"
433 end
434
435 directory "/var/lib/nftables" do
436   owner "root"
437   group "root"
438   mode "755"
439 end
440
441 template "/usr/local/bin/nftables" do
442   source "nftables.erb"
443   owner "root"
444   group "root"
445   mode "755"
446 end
447
448 systemd_service "nftables-stop" do
449   action :delete
450   service "nftables"
451   dropin "stop"
452 end
453
454 systemd_service "nftables-chef" do
455   service "nftables"
456   dropin "chef"
457   exec_start "/usr/local/bin/nftables start"
458   exec_reload "/usr/local/bin/nftables reload"
459   exec_stop "/usr/local/bin/nftables stop"
460 end
461
462 if node[:networking][:firewall][:enabled]
463   service "nftables" do
464     action [:enable, :start]
465   end
466 else
467   service "nftables" do
468     action [:disable, :stop]
469   end
470 end
471
472 if node[:networking][:wireguard][:enabled]
473   firewall_rule "accept-wireguard" do
474     action :accept
475     context :incoming
476     protocol :udp
477     source :osm unless node[:roles].include?("gateway")
478     dest_ports "51820"
479     source_ports "51820"
480   end
481 end
482
483 firewall_rule "accept-http" do
484   action :accept
485   context :incoming
486   protocol :tcp
487   dest_ports %w[http https]
488   rate_limit node[:networking][:firewall][:http_rate_limit]
489   connection_limit node[:networking][:firewall][:http_connection_limit]
490 end