]> git.openstreetmap.org Git - chef.git/blob - cookbooks/networking/recipes/default.rb
Add support for using systemd-networkd directly instead of netplan
[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 if node[:networking][:engine] == "netplan"
29   package "netplan.io"
30
31   netplan = {
32     "network" => {
33       "version" => 2,
34       "renderer" => "networkd",
35       "ethernets" => {},
36       "bonds" => {},
37       "vlans" => {}
38     }
39   }
40
41   node[:networking][:interfaces].each do |name, interface|
42     if interface[:interface]
43       if interface[:role] && (role = node[:networking][:roles][interface[:role]])
44         if interface[:inet] && role[:inet]
45           node.default[:networking][:interfaces][name][:inet][:prefix] = role[:inet][:prefix]
46           node.default[:networking][:interfaces][name][:inet][:gateway] = role[:inet][:gateway]
47           node.default[:networking][:interfaces][name][:inet][:routes] = role[:inet][:routes]
48         end
49
50         if interface[:inet6] && role[:inet6]
51           node.default[:networking][:interfaces][name][:inet6][:prefix] = role[:inet6][:prefix]
52           node.default[:networking][:interfaces][name][:inet6][:gateway] = role[:inet6][:gateway]
53           node.default[:networking][:interfaces][name][:inet6][:routes] = role[:inet6][:routes]
54         end
55
56         node.default[:networking][:interfaces][name][:metric] = role[:metric]
57         node.default[:networking][:interfaces][name][:zone] = role[:zone]
58       end
59
60       interface = node[:networking][:interfaces][name]
61
62       deviceplan = if interface[:interface] =~ /^(.*)\.(\d+)$/
63                      netplan["network"]["vlans"][interface[:interface]] ||= {
64                        "id" => Regexp.last_match(2).to_i,
65                        "link" => Regexp.last_match(1),
66                        "accept-ra" => false,
67                        "addresses" => [],
68                        "routes" => []
69                      }
70                    elsif interface[:interface] =~ /^bond\d+$/
71                      netplan["network"]["bonds"][interface[:interface]] ||= {
72                        "accept-ra" => false,
73                        "addresses" => [],
74                        "routes" => []
75                      }
76                    else
77                      netplan["network"]["ethernets"][interface[:interface]] ||= {
78                        "accept-ra" => false,
79                        "addresses" => [],
80                        "routes" => []
81                      }
82                    end
83
84       if interface[:inet]
85         deviceplan["addresses"].push("#{interface[:inet][:address]}/#{interface[:inet][:prefix]}")
86       end
87
88       if interface[:inet6]
89         deviceplan["addresses"].push("#{interface[:inet6][:address]}/#{interface[:inet6][:prefix]}")
90       end
91
92       if interface[:mtu]
93         deviceplan["mtu"] = interface[:mtu]
94       end
95
96       if interface[:bond]
97         deviceplan["interfaces"] = interface[:bond][:slaves].to_a
98
99         deviceplan["parameters"] = {
100           "mode" => interface[:bond][:mode] || "active-backup",
101           "mii-monitor-interval" => interface[:bond][:miimon] || 100,
102           "down-delay" => interface[:bond][:downdelay] || 200,
103           "up-delay" => interface[:bond][:updelay] || 200
104         }
105
106         deviceplan["parameters"]["primary"] = interface[:bond][:slaves].first if deviceplan["parameters"]["mode"] == "active-backup"
107         deviceplan["parameters"]["transmit-hash-policy"] = interface[:bond][:xmithashpolicy] if interface[:bond][:xmithashpolicy]
108         deviceplan["parameters"]["lacp-rate"] = interface[:bond][:lacprate] if interface[:bond][:lacprate]
109       end
110
111       if interface[:inet]
112         if interface[:inet][:gateway] && interface[:inet][:gateway] != interface[:inet][:address]
113           deviceplan["routes"].push(
114             "to" => "0.0.0.0/0",
115             "via" => interface[:inet][:gateway],
116             "metric" => interface[:metric],
117             "on-link" => true
118           )
119         end
120
121         if interface[:inet][:routes]
122           interface[:inet][:routes].each do |to, parameters|
123             next if parameters[:via] == interface[:inet][:address]
124
125             route = {
126               "to" => to
127             }
128
129             route["type"] = parameters[:type] if parameters[:type]
130             route["via"] = parameters[:via] if parameters[:via]
131             route["metric"] = parameters[:metric] if parameters[:metric]
132
133             deviceplan["routes"].push(route)
134           end
135         end
136       end
137
138       if interface[:inet6]
139         if interface[:inet6][:gateway] && interface[:inet6][:gateway] != interface[:inet6][:address]
140           deviceplan["routes"].push(
141             "to" => "::/0",
142             "via" => interface[:inet6][:gateway],
143             "metric" => interface[:metric],
144             "on-link" => true
145           )
146
147           # This ordering relies on systemd-networkd adding routes
148           # in reverse order and will need moving before the previous
149           # route once that is fixed:
150           #
151           # https://github.com/systemd/systemd/issues/5430
152           # https://github.com/systemd/systemd/pull/10938
153           if !IPAddr.new(interface[:inet6][:address]).mask(interface[:inet6][:prefix]).include?(interface[:inet6][:gateway]) &&
154              !IPAddr.new("fe80::/64").include?(interface[:inet6][:gateway])
155             deviceplan["routes"].push(
156               "to" => interface[:inet6][:gateway],
157               "scope" => "link"
158             )
159           end
160         end
161
162         if interface[:inet6][:routes]
163           interface[:inet6][:routes].each do |to, parameters|
164             next if parameters[:via] == interface[:inet6][:address]
165
166             route = {
167               "to" => to
168             }
169
170             route["type"] = parameters[:type] if parameters[:type]
171             route["via"] = parameters[:via] if parameters[:via]
172             route["metric"] = parameters[:metric] if parameters[:metric]
173
174             deviceplan["routes"].push(route)
175           end
176         end
177       end
178     else
179       node.rm(:networking, :interfaces, name)
180     end
181   end
182
183   netplan["network"]["bonds"].each_value do |bond|
184     bond["interfaces"].each do |interface|
185       netplan["network"]["ethernets"][interface] ||= { "accept-ra" => false, "optional" => true }
186     end
187   end
188
189   netplan["network"]["vlans"].each_value do |vlan|
190     unless vlan["link"] =~ /^bond\d+$/
191       netplan["network"]["ethernets"][vlan["link"]] ||= { "accept-ra" => false }
192     end
193   end
194
195   file "/etc/netplan/00-installer-config.yaml" do
196     action :delete
197   end
198
199   file "/etc/netplan/01-netcfg.yaml" do
200     action :delete
201   end
202
203   file "/etc/netplan/50-cloud-init.yaml" do
204     action :delete
205   end
206
207   file "/etc/netplan/99-chef.yaml" do
208     owner "root"
209     group "root"
210     mode "644"
211     content YAML.dump(netplan)
212   end
213 elsif node[:networking][:engine] == "systemd-networkd"
214   file "/etc/netplan/99-chef.yaml" do
215     action :delete
216   end
217
218   package "netplan.io" do
219     action :purge
220   end
221
222   interfaces = node[:networking][:interfaces].collect do |name, interface|
223     [interface[:interface], name]
224   end.to_h
225
226   node[:networking][:interfaces].each do |name, interface|
227     if interface[:interface] =~ /^(.*)\.(\d+)$/
228       vlan_interface = Regexp.last_match(1)
229       vlan_id = Regexp.last_match(2)
230
231       parent = interfaces[vlan_interface] || "vlans_#{vlan_interface}"
232
233       node.default_unless[:networking][:interfaces][parent][:interface] = vlan_interface,
234       node.default_unless[:networking][:interfaces][parent][:vlans] = []
235
236       node.default[:networking][:interfaces][parent][:vlans] << vlan_id
237     end
238
239     next unless interface[:role] && (role = node[:networking][:roles][interface[:role]])
240
241     if interface[:inet] && role[:inet]
242       node.default[:networking][:interfaces][name][:inet][:prefix] = role[:inet][:prefix]
243       node.default[:networking][:interfaces][name][:inet][:gateway] = role[:inet][:gateway]
244       node.default[:networking][:interfaces][name][:inet][:routes] = role[:inet][:routes]
245     end
246
247     if interface[:inet6] && role[:inet6]
248       node.default[:networking][:interfaces][name][:inet6][:prefix] = role[:inet6][:prefix]
249       node.default[:networking][:interfaces][name][:inet6][:gateway] = role[:inet6][:gateway]
250       node.default[:networking][:interfaces][name][:inet6][:routes] = role[:inet6][:routes]
251     end
252
253     node.default[:networking][:interfaces][name][:metric] = role[:metric]
254     node.default[:networking][:interfaces][name][:zone] = role[:zone]
255   end
256
257   node[:networking][:interfaces].each do |_, interface|
258     file "/run/systemd/network/10-netplan-#{interface[:interface]}.netdev" do
259       action :delete
260     end
261
262     if interface[:interface] =~ /^.*\.(\d+)$/
263       template "/etc/systemd/network/10-#{interface[:interface]}.netdev" do
264         source "vlan.netdev.erb"
265         owner "root"
266         group "root"
267         mode "644"
268         variables :interface => interface, :vlan => Regexp.last_match(1)
269         notifies :run, "execute[networkctl-delete-#{interface[:interface]}]"
270         notifies :run, "notify_group[networkctl-reload]"
271       end
272
273       execute "networkctl-delete-#{interface[:interface]}" do
274         action :nothing
275         command "networkctl delete #{interface[:interface]}"
276         only_if { ::File.exist?("/sys/class/net/#{interface[:interface]}") }
277       end
278     elsif interface[:interface] =~ /^bond\d+$/
279       template "/etc/systemd/network/10-#{interface[:interface]}.netdev" do
280         source "bond.netdev.erb"
281         owner "root"
282         group "root"
283         mode "644"
284         variables :interface => interface
285         notifies :run, "execute[networkctl-delete-#{interface[:interface]}]"
286         notifies :run, "notify_group[networkctl-reload]"
287       end
288
289       execute "networkctl-delete-#{interface[:interface]}" do
290         action :nothing
291         command "networkctl delete #{interface[:interface]}"
292         only_if { ::File.exist?("/sys/class/net/#{interface[:interface]}") }
293       end
294
295       interface[:bond][:slaves].each do |slave|
296         file "/run/systemd/network/10-netplan-#{slave}.network" do
297           action :delete
298         end
299
300         template "/etc/systemd/network/10-#{slave}.network" do
301           source "slave.network.erb"
302           owner "root"
303           group "root"
304           mode "644"
305           variables :master => interface, :slave => slave
306           notifies :run, "notify_group[networkctl-reload]"
307         end
308       end
309     end
310
311     file "/run/systemd/network/10-netplan-#{interface[:interface]}.network" do
312       action :delete
313     end
314
315     template "/etc/systemd/network/10-#{interface[:interface]}.network" do
316       source "network.erb"
317       owner "root"
318       group "root"
319       mode "644"
320       variables :interface => interface
321       notifies :run, "notify_group[networkctl-reload]"
322     end
323   end
324
325   service "systemd-networkd" do
326     action [:enable, :start]
327   end
328 end
329
330 package "cloud-init" do
331   action :purge
332 end
333
334 if node[:networking][:wireguard][:enabled]
335   wireguard_id = persistent_token("networking", "wireguard")
336
337   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]}"
338
339   package "wireguard-tools" do
340     compile_time true
341     options "--no-install-recommends"
342   end
343
344   directory "/var/lib/systemd/wireguard" do
345     owner "root"
346     group "systemd-network"
347     mode "750"
348     compile_time true
349   end
350
351   file "/var/lib/systemd/wireguard/private.key" do
352     action :create_if_missing
353     owner "root"
354     group "systemd-network"
355     mode "640"
356     content %x(wg genkey)
357     compile_time true
358   end
359
360   node.default[:networking][:wireguard][:public_key] = %x(wg pubkey < /var/lib/systemd/wireguard/private.key).chomp
361
362   file "/var/lib/systemd/wireguard/preshared.key" do
363     action :create_if_missing
364     owner "root"
365     group "systemd-network"
366     mode "640"
367     content keys["wireguard"]
368   end
369
370   if node[:roles].include?("gateway")
371     search(:node, "roles:gateway") do |gateway|
372       next if gateway.name == node.name
373       next unless gateway[:networking][:wireguard] && gateway[:networking][:wireguard][:enabled]
374
375       allowed_ips = gateway.ipaddresses(:role => :internal).map(&:subnet)
376
377       node.default[:networking][:wireguard][:peers] << {
378         :public_key => gateway[:networking][:wireguard][:public_key],
379         :allowed_ips => allowed_ips,
380         :endpoint => "#{gateway.name}:51820"
381       }
382     end
383
384     search(:node, "roles:prometheus") do |server|
385       allowed_ips = server.ipaddresses(:role => :internal).map(&:subnet)
386
387       if server[:networking][:private_address]
388         allowed_ips << "#{server[:networking][:private_address]}/32"
389       end
390
391       node.default[:networking][:wireguard][:peers] << {
392         :public_key => server[:networking][:wireguard][:public_key],
393         :allowed_ips => allowed_ips,
394         :endpoint => "#{server.name}:51820"
395       }
396     end
397
398     node.default[:networking][:wireguard][:peers] << {
399       :public_key => "7Oj9ufNlgidyH/xDc+aHQKMjJPqTmD/ab13agMh6AxA=",
400       :allowed_ips => "10.0.16.1/32",
401       :endpoint => "gate.compton.nu:51820"
402     }
403
404     # Grant home
405     node.default[:networking][:wireguard][:peers] << {
406       :public_key => "RofATnvlWxP3mt87+QKRXFE5MVxtoCcTsJ+yftZYEE4=",
407       :allowed_ips => "10.89.122.1/32",
408       :endpoint => "gate.firefishy.com:51820"
409     }
410
411     # Grant roaming
412     node.default[:networking][:wireguard][:peers] << {
413       :public_key => "YbUkREE9TAmomqgL/4Fh2e5u2Hh7drN/2o5qg3ndRxg=",
414       :allowed_ips => "10.89.123.1/32",
415       :endpoint => "roaming.firefishy.com:51820"
416     }
417   elsif node[:roles].include?("shenron")
418     search(:node, "roles:gateway") do |gateway|
419       allowed_ips = gateway.ipaddresses(:role => :internal).map(&:subnet)
420
421       node.default[:networking][:wireguard][:peers] << {
422         :public_key => gateway[:networking][:wireguard][:public_key],
423         :allowed_ips => allowed_ips,
424         :endpoint => "#{gateway.name}:51820"
425       }
426     end
427   end
428
429   file "/etc/systemd/network/wireguard.netdev" do
430     action :delete
431   end
432
433   template "/etc/systemd/network/10-wg0.netdev" do
434     source "wireguard.netdev.erb"
435     owner "root"
436     group "systemd-network"
437     mode "640"
438     notifies :run, "execute[networkctl-delete-wg0]"
439     notifies :run, "notify_group[networkctl-reload]"
440   end
441
442   file "/etc/systemd/network/wireguard.network" do
443     action :delete
444   end
445
446   template "/etc/systemd/network/10-wg0.network" do
447     source "wireguard.network.erb"
448     owner "root"
449     group "root"
450     mode "644"
451     notifies :run, "execute[networkctl-reload]"
452   end
453
454   execute "networkctl-delete-wg0" do
455     action :nothing
456     command "networkctl delete wg0"
457     only_if { ::File.exist?("/sys/class/net/wg0") }
458   end
459 end
460
461 notify_group "networkctl-reload"
462
463 execute "networkctl-reload" do
464   action :nothing
465   command "networkctl reload"
466   subscribes :run, "notify_group[networkctl-reload]"
467   not_if { kitchen? && node[:networking][:engine] == "netplan" }
468 end
469
470 ohai "reload-hostname" do
471   action :nothing
472   plugin "hostname"
473 end
474
475 execute "hostnamectl-set-hostname" do
476   command "hostnamectl set-hostname #{node[:networking][:hostname]}"
477   notifies :reload, "ohai[reload-hostname]"
478   not_if { kitchen? || node[:hostnamectl][:static_hostname] == node[:networking][:hostname] }
479 end
480
481 template "/etc/hosts" do
482   source "hosts.erb"
483   owner "root"
484   group "root"
485   mode "644"
486   not_if { kitchen? }
487 end
488
489 service "systemd-resolved" do
490   action [:enable, :start]
491 end
492
493 directory "/etc/systemd/resolved.conf.d" do
494   owner "root"
495   group "root"
496   mode "755"
497 end
498
499 template "/etc/systemd/resolved.conf.d/99-chef.conf" do
500   source "resolved.conf.erb"
501   owner "root"
502   group "root"
503   mode "644"
504   notifies :restart, "service[systemd-resolved]", :immediately
505 end
506
507 if node[:filesystem][:by_mountpoint][:"/etc/resolv.conf"]
508   execute "umount-resolve-conf" do
509     command "umount -c /etc/resolv.conf"
510   end
511 end
512
513 link "/etc/resolv.conf" do
514   to "../run/systemd/resolve/stub-resolv.conf"
515 end
516
517 hosts = { :inet => [], :inet6 => [] }
518
519 search(:node, "networking:interfaces").collect do |n|
520   next if n[:fqdn] == node[:fqdn]
521
522   n.interfaces.each do |interface|
523     next unless interface[:role] == "external"
524
525     hosts[:inet] << interface[:inet][:address] if interface[:inet]
526     hosts[:inet6] << interface[:inet6][:address] if interface[:inet6]
527   end
528 end
529
530 package "nftables"
531
532 interfaces = []
533
534 node.interfaces(:role => :external).each do |interface|
535   interfaces << interface[:interface]
536 end
537
538 template "/etc/nftables.conf" do
539   source "nftables.conf.erb"
540   owner "root"
541   group "root"
542   mode "755"
543   variables :interfaces => interfaces, :hosts => hosts
544   notifies :reload, "service[nftables]"
545 end
546
547 directory "/var/lib/nftables" do
548   owner "root"
549   group "root"
550   mode "755"
551 end
552
553 template "/usr/local/bin/nftables" do
554   source "nftables.erb"
555   owner "root"
556   group "root"
557   mode "755"
558 end
559
560 systemd_service "nftables-stop" do
561   action :delete
562   service "nftables"
563   dropin "stop"
564 end
565
566 systemd_service "nftables-chef" do
567   service "nftables"
568   dropin "chef"
569   exec_start "/usr/local/bin/nftables start"
570   exec_reload "/usr/local/bin/nftables reload"
571   exec_stop "/usr/local/bin/nftables stop"
572 end
573
574 if node[:networking][:firewall][:enabled]
575   service "nftables" do
576     action [:enable, :start]
577   end
578 else
579   service "nftables" do
580     action [:disable, :stop]
581   end
582 end
583
584 if node[:networking][:wireguard][:enabled]
585   firewall_rule "accept-wireguard" do
586     action :accept
587     context :incoming
588     protocol :udp
589     source :osm unless node[:roles].include?("gateway")
590     dest_ports "51820"
591     source_ports "51820"
592   end
593 end
594
595 firewall_rule "accept-http" do
596   action :accept
597   context :incoming
598   protocol :tcp
599   dest_ports %w[http https]
600   rate_limit node[:networking][:firewall][:http_rate_limit]
601   connection_limit node[:networking][:firewall][:http_connection_limit]
602 end