From decdec5c435935fcb1365872bea492e40257ebc3 Mon Sep 17 00:00:00 2001 From: Jacob Weinstock Date: Thu, 8 Dec 2022 19:35:32 -0700 Subject: [PATCH] Add static IP configuration for Bottlerocket: Static IPs provide more reliable IP handling. Signed-off-by: Jacob Weinstock --- actions/writefile/v1/main.go | 127 ++++++++++++++++++++++++++++------- 1 file changed, 102 insertions(+), 25 deletions(-) diff --git a/actions/writefile/v1/main.go b/actions/writefile/v1/main.go index 339dedd..d2a5ff2 100644 --- a/actions/writefile/v1/main.go +++ b/actions/writefile/v1/main.go @@ -31,6 +31,7 @@ const ( mountAction = "/mountAction" bootConfigAction = "/usr/bin/bootconfig" hegelUserDataVersion = "2009-04-04" + bottlerocketUserData = "/user-data.toml" ) type Info struct { @@ -39,8 +40,47 @@ type Info struct { Gateway net.IP Nameservers []net.IP VLANID int + IFName string } +var bottlerocketNetTOMLTemplate = `# Version is required, it will change as we support +# additional settings +version = 2 + +[{{ .IFName }}.static4] +addresses = ["{{ ToString .IPAddr }}"] + +[[{{ .IFName }}.route]] +to = "default" +via = "{{ ToString .Gateway }}" +route-metric = 100 +` + +var bottlerocketDNSTemplate = ` + +[settings.dns] +name-servers = [{{$n := len .Nameservers}}{{range $i, $e := .Nameservers}}{{if $i}}, {{end}}"{{$e}}"{{end}}] + +` + +var netplanTemplate = `network: + version: 2 + renderer: networkd + ethernets: + id0: + match: + macaddress: {{ .HWAddr }} + addresses: + - {{ ToString .IPAddr }} + nameservers: + addresses: [{{ ToStringSlice .Nameservers ", " }}] + {{- if .Gateway }} + routes: + - to: default + via: {{ ToString .Gateway }} + {{- end }} +` + func main() { fmt.Printf("WriteFile - Write file to disk\n------------------------\n") @@ -69,23 +109,6 @@ func main() { log.Errorf("Invalid DHCP_TIMEOUT: %s, using default: %v", t, timeout) } } - netplanTemplate := `network: - version: 2 - renderer: networkd - ethernets: - id0: - match: - macaddress: {{ .HWAddr }} - addresses: - - {{ ToString .IPAddr }} - nameservers: - addresses: [{{ ToStringSlice .Nameservers ", " }}] - {{- if .Gateway }} - routes: - - to: default - via: {{ ToString .Gateway }} - {{- end }} -` if name, template, err := vlanEnabled("/proc/cmdline"); err == nil { netplanTemplate = template ifname = name @@ -117,8 +140,8 @@ func main() { validationCount++ } } - if validationCount != 1 { - log.Fatal("Only one environment vars of CONTENTS, BOOTCONFIG_CONTENTS, HEGEL_URLS can be set") + if validationCount > 1 { + log.Fatal("Only one of the following environment vars can be set: CONTENTS, BOOTCONFIG_CONTENTS, HEGEL_URLS") } fileMode := os.FileMode(modePrime) @@ -199,6 +222,38 @@ func main() { } } + // handle Bottlerocket static IP configuration + if os.Getenv("STATIC_BOTTLEROCKET") == "true" { + var err error + timeout := 2 * time.Minute + if t := os.Getenv("DHCP_TIMEOUT"); t != "" { + timeout, err = time.ParseDuration(t) + if err != nil { + log.Errorf("Invalid DHCP_TIMEOUT: %s, using default: %v", t, timeout) + } + } + ifn := determineNetIF() + d, err := dhcpRequest(ifn, timeout) + if err != nil { + log.Fatal(err, " IFName=", ifn) + } + i := translate(d) + i.IFName = os.Getenv("IFNAME") + contents, err = doTemplating(bottlerocketNetTOMLTemplate, i) + if err != nil { + log.Fatal(err) + } + // for static IP setting we must also handle the DNS config + dnsContents, err := doTemplating(bottlerocketDNSTemplate, i) + if err != nil { + log.Fatal(err) + } + f := filepath.Join(mountAction, bottlerocketUserData) + if err := appendToUserData(dnsContents, f); err != nil { + log.Fatal(err) + } + } + // If bootconfig is set, contents will be empty and will serve as output initrd file provided // to bootconfig tool fqFilePath := filepath.Join(mountAction, filePath) @@ -231,6 +286,19 @@ func main() { log.Infof("Successfully wrote file [%s] to device [%s]", filePath, blockDevice) } +func appendToUserData(contents string, filePath string) error { + f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + if _, err := f.WriteString(contents); err != nil { + return err + } + + return nil +} + func determineNetIF() string { runtime.LockOSThread() defer runtime.UnlockOSThread() @@ -404,7 +472,7 @@ func vlanEnabled(f string) (ifname, netplanTemplate string, err error) { return ifname, netplanTemplate, nil } -func dhcpAndWriteNetplan(ifname string, dhcpTimeout time.Duration, netplanTemplate string) (string, error) { +func dhcpRequest(ifname string, dhcpTimeout time.Duration) (*dhcpv4.DHCPv4, error) { // After locking a goroutine to its current OS thread with runtime.LockOSThread() // and changing its network namespace, any new subsequent goroutine won't be scheduled // on that thread while it's locked. Therefore, the new goroutine will run in a @@ -418,22 +486,31 @@ func dhcpAndWriteNetplan(ifname string, dhcpTimeout time.Duration, netplanTempla // Change to PID 1 network namespace so we can do a DHCP using the host's interface. ns1, err := netns.GetFromPid(1) if err != nil { - return "", err + return nil, err } defer ns1.Close() err = netns.Set(ns1) if err != nil { - return "", err + return nil, err } ctx, cancel := context.WithTimeout(context.Background(), dhcpTimeout) defer cancel() d, err := dhcp(ctx, ifname) + if err != nil { + return nil, err + } + + return d, nil +} + +func dhcpAndWriteNetplan(ifname string, dhcpTimeout time.Duration, netplanTemplate string) (string, error) { + d, err := dhcpRequest(ifname, dhcpTimeout) if err != nil { return "", err } - return createNetplan(netplanTemplate, translate(d)) + return doTemplating(netplanTemplate, translate(d)) } func netIPToString(ip []net.IP, sep string) string { @@ -457,8 +534,8 @@ func netToString(v interface{}) string { return fmt.Sprintf("%v", v) } -func createNetplan(tmpl string, i Info) (string, error) { - tp, err := template.New("netplan").Funcs(template.FuncMap{"ToStringSlice": netIPToString}).Funcs(template.FuncMap{"ToString": netToString}).Parse(tmpl) +func doTemplating(tmpl string, i Info) (string, error) { + tp, err := template.New("genericTemplate").Funcs(template.FuncMap{"ToStringSlice": netIPToString}).Funcs(template.FuncMap{"ToString": netToString}).Parse(tmpl) if err != nil { return "", err } -- 2.34.1