From 8b141d3db71fa338912e4d6bfdeba4957bc69b60 Mon Sep 17 00:00:00 2001 From: Daniel Speichert Date: Tue, 9 Mar 2021 18:28:49 -0500 Subject: [PATCH] Manifest documentation --- README.md | 76 ++++++++++++++++++++++++++++++++++-- dhcpd/handler.go | 5 +++ examples/ubuntu-1804.yml | 2 +- examples/ubuntu-2004-ram.yml | 2 +- examples/ubuntu-2004.yml | 2 +- manifest/ipnet.go | 1 - manifest/schema.go | 16 +++++--- 7 files changed, 91 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d9c1579..01212f9 100644 --- a/README.md +++ b/README.md @@ -47,17 +47,85 @@ downloading (typically) kernel and initrd over HTTP instead of TFTP. ## Manifests -A manifest represents a machine to be provisioned/served. The behavior of built-in -DHCP, TFTP and HTTP server is specific to a manifest, meaning that it varies based -on source MAC/IP. Each host may see different content at `/something` path. +A manifest represents a machine to be provisioned/served. The behavior of built-in DHCP, TFTP and HTTP server is +specific to a manifest, meaning that it varies based on source MAC/IP. Each host may see different content +at `/something` path. -Note that this is not a security feature and you should not host any sensitive content. MAC and IPs can be easily +Note that this is not a security feature, and you should not host any sensitive content. MAC and IPs can be easily spoofed. In fact, netbootd includes a convenience feature to spoof source IP for troubleshooting purposes. Append `?spoof=` to HTTP request to see the response for a particular host. There is no TFTP counterpart of this feature. Example manifests are included in the `examples/` directory. +### Anatomy of a manifest + +```yaml +--- +# ID can be anything unique, URL-safe, used to identify it for HTTP API +id: ubuntu-1804 + +### DHCP options - used for DHCP responses from netbootd +# IP address with subnet (CIDR) to give out +ipv4: 192.168.17.101/24 +# Hostname (without domain part) (Option 12) +hostname: ubuntu-machine-1804 +# Domain part (used for hostname) (Option 15) +domain: test.local +# Lease duration is used as Option 51 +# Note that netbootd is a static-assignment server, which does not prevent IP conflicts. +leaseDuration: 1h +# The MAC addresses which map to this manifest +# List multiple for machine with multiple NICs, if not sure which one boots first +mac: + - 00:15:5d:bd:be:15 + - aa:bb:cc:dd:ee:fc +# Domain name servers (DNS) in the order of preference (Option 6) +dns: + - 1.2.3.4 + - 3.4.5.6 +# Routers in the order of preference (Option 3), more than one is rare +router: + - 192.168.17.1 +# NTP servers in the order of preference (Option 42), IP address required +ntp: + - 192.168.17.1 +# Whether a bundled iPXE bootloader should be served first (before bootFilename). +# When iPXE is loaded, it does DHCP again and netbootd detects its client string +# to break the boot loop and serve bootFilename instead. +ipxe: true +# The name of NBP file name, server over TFTP from "next server", +# which netbootd automatically points to be itself. +# This should map to a "mount" below. +bootFilename: install.ipxe + +# Mounts define virtual per-host (per-manifest) paths that are acessible +# over both TFTP and HTTP but only from the IP address of in this manifest. +# Each mount can be either a proxy mount (HTTP/HTTPS proxy) or a content mount (static). +mounts: + - path: /netboot + # When true, all paths starting with this prefix use this mount. + pathIsPrefix: true + # When proxy is defined, these requests are proxied to a HTTP/HTTPS address. + proxy: http://archive.ubuntu.com/ubuntu/dists/bionic-updates/main/installer-amd64/current/images/hwe-netboot/ubuntu-installer/amd64/ + # When true, the proxy path defined above gets a suffix to the Path prefix appended to it. + proxyAppendSuffix: true + + - path: /install.ipxe + # The templating context provides access to: .LocalIP, .RemoteIP, .HttpBaseUrl and .Manifest. + # Sprig functions are available: masterminds.github.io/sprig + content: | + #!ipxe + # See https://ipxe.org/scripting for iPXE commands/scripting documentation + + set base {{ .HttpBaseUrl }}/netboot + + {{ $hostnameParts := splitList "." .Manifest.Hostname }} + kernel ${base}/linux gfxpayload=800x600x16,800x600 initrd=initrd.gz auto=true url={{ .HttpBaseUrl.String }}/preseed.txt netcfg/get_ipaddress={{ .Manifest.IPv4.IP }} netcfg/get_netmask={{ .Manifest.IPv4.Netmask }} netcfg/get_gateway={{ first .Manifest.Router }} netcfg/get_nameservers="{{ .Manifest.DNS | join " " }}" netcfg/disable_autoconfig=true hostname={{ first $hostnameParts }} domain={{ rest $hostnameParts | join "." }} DEBCONF_DEBUG=developer + initrd ${base}/initrd.gz + boot +``` + ## HTTP API In this preview/development version, this HTTP API does not support authentication. diff --git a/dhcpd/handler.go b/dhcpd/handler.go index c128f37..397e11f 100644 --- a/dhcpd/handler.go +++ b/dhcpd/handler.go @@ -118,6 +118,11 @@ func (server *Server) HandleMsg4(buf []byte, oob *ipv4.ControlMessage, peer net. resp.Options.Update(dhcpv4.OptRouter(manifest.Router...)) } + // NTP + if req.IsOptionRequested(dhcpv4.OptionNTPServers) { + resp.Options.Update(dhcpv4.OptNTPServers(manifest.NTP...)) + } + // NBP if req.IsOptionRequested(dhcpv4.OptionTFTPServerName) && !manifest.Suspended { resp.Options.Update(dhcpv4.OptTFTPServerName(localIp.String())) diff --git a/examples/ubuntu-1804.yml b/examples/ubuntu-1804.yml index 07b6047..f9865b0 100644 --- a/examples/ubuntu-1804.yml +++ b/examples/ubuntu-1804.yml @@ -27,7 +27,7 @@ router: # in the "order of preference" ntp: - - pool.ntp.org + - 192.168.17.1 ipxe: true bootFilename: install.ipxe diff --git a/examples/ubuntu-2004-ram.yml b/examples/ubuntu-2004-ram.yml index a3b00a7..429ab27 100644 --- a/examples/ubuntu-2004-ram.yml +++ b/examples/ubuntu-2004-ram.yml @@ -24,7 +24,7 @@ router: # in the "order of preference" ntp: - - pool.ntp.org + - 192.168.17.1 ipxe: true bootFilename: install.ipxe diff --git a/examples/ubuntu-2004.yml b/examples/ubuntu-2004.yml index 02156bd..c01da2c 100644 --- a/examples/ubuntu-2004.yml +++ b/examples/ubuntu-2004.yml @@ -23,7 +23,7 @@ router: # in the "order of preference" ntp: - - pool.ntp.org + - 192.168.17.1 ipxe: true bootFilename: install.ipxe diff --git a/manifest/ipnet.go b/manifest/ipnet.go index 7798639..d575a18 100644 --- a/manifest/ipnet.go +++ b/manifest/ipnet.go @@ -4,7 +4,6 @@ import ( "net" ) -// An IPNet represents an IP network. type IPWithNet struct { IP net.IP Net net.IPNet diff --git a/manifest/schema.go b/manifest/schema.go index d88ab54..62b1e3a 100644 --- a/manifest/schema.go +++ b/manifest/schema.go @@ -9,6 +9,7 @@ import ( "time" ) +// Manifest represents user-supplied per-host manifest information. // go-yaml accepts completely lowercase version of keys but is not case-insensitive // https://github.com/go-yaml/yaml/issues/123 // some fields are forcefully mapped to camelCase instead of CamelCase and camelcase @@ -21,13 +22,14 @@ type Manifest struct { MAC []HardwareAddr DNS []net.IP Router []net.IP - NTP []string + NTP []net.IP Ipxe bool BootFilename string `yaml:"bootFilename"` Mounts []Mount Suspended bool } +// Mount represents a path exposed via TFTP and HTTP. type Mount struct { // Path at which to select this mount. Path string @@ -88,12 +90,16 @@ func (m Mount) ProxyDirector() (func(req *http.Request), error) { return director, nil } -// Content template is evaluated with ContentContext +// ContentContext is the template context available for static Content embedded in Manifests. type ContentContext struct { - LocalIP net.IP - RemoteIP net.IP + // Address of netbootd server + LocalIP net.IP + // Address of client + RemoteIP net.IP + // Base URL to the HTTP service (IP and port) - not API HttpBaseUrl *url.URL - Manifest *Manifest + // Copy of Manifest + Manifest *Manifest } // Return best matching Mount, respecting exact and prefix-based mount paths.