diff --git a/pkg/metadata/Dockerfile b/pkg/metadata/Dockerfile index 216ee9aca..ce6839c95 100644 --- a/pkg/metadata/Dockerfile +++ b/pkg/metadata/Dockerfile @@ -1,12 +1,12 @@ FROM linuxkit/alpine:87a0cd10449d72f374f950004467737dbf440630 AS mirror -RUN apk add --no-cache go musl-dev +RUN apk add --no-cache go musl-dev linux-headers ENV GOPATH=/go PATH=$PATH:/go/bin COPY . /go/src/metadata/ RUN go-compile.sh /go/src/metadata -RUN mkdir -p out/tmp out/var out/dev out/etc +RUN mkdir -p out/tmp out/var out/dev out/etc out/etc/ssl/certs FROM scratch ENTRYPOINT [] @@ -15,4 +15,4 @@ WORKDIR / COPY --from=mirror /go/bin/metadata /usr/bin/metadata COPY --from=mirror /out/ / CMD ["/usr/bin/metadata"] -LABEL org.mobyproject.config='{"binds": ["/dev:/dev", "/var:/var", "/etc/resolv.conf:/etc/resolv.conf"], "tmpfs": ["/tmp"], "readonly": true, "capabilities": ["CAP_SYS_ADMIN"]}' +LABEL org.mobyproject.config='{"binds": ["/dev:/dev", "/var:/var", "/sys:/sys", "/etc/resolv.conf:/etc/resolv.conf", "/etc/ssl/certs:/etc/ssl/certs"], "tmpfs": ["/tmp"], "readonly": true, "capabilities": ["CAP_SYS_ADMIN", "CAP_NET_ADMIN"]}' diff --git a/pkg/metadata/main.go b/pkg/metadata/main.go index ccf5e09a1..1e1689392 100644 --- a/pkg/metadata/main.go +++ b/pkg/metadata/main.go @@ -41,7 +41,7 @@ var netProviders []Provider var cdromProviders []Provider func main() { - providers := []string{"aws", "gcp", "vultr", "cdrom"} + providers := []string{"aws", "gcp", "vultr", "packet", "cdrom"} if len(os.Args) > 1 { providers = os.Args[1:] } @@ -51,6 +51,8 @@ func main() { netProviders = append(netProviders, NewAWS()) case "gcp": netProviders = append(netProviders, NewGCP()) + case "packet": + netProviders = append(netProviders, NewPacket()) case "vultr": netProviders = append(netProviders, NewVultr()) case "cdrom": diff --git a/pkg/metadata/provider_packet.go b/pkg/metadata/provider_packet.go new file mode 100644 index 000000000..a52a0bc1a --- /dev/null +++ b/pkg/metadata/provider_packet.go @@ -0,0 +1,184 @@ +package main + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "path" + "strconv" + "strings" + + "github.com/packethost/packngo/metadata" + "github.com/vishvananda/netlink" +) + +// ProviderPacket is the type implementing the Provider interface for Packet.net +type ProviderPacket struct { + metadata *metadata.CurrentDevice + err error +} + +// NewPacket returns a new ProviderPacket +func NewPacket() *ProviderPacket { + return &ProviderPacket{} +} + +func (p *ProviderPacket) String() string { + return "Packet" +} + +// Probe checks if we are running on Packet +func (p *ProviderPacket) Probe() bool { + // Unfortunately the host is resolveable globally, so no easy test + p.metadata, p.err = metadata.GetMetadata() + return p.err == nil +} + +// Extract gets both the Packet specific and generic userdata +func (p *ProviderPacket) Extract() ([]byte, error) { + // do not retrieve if we Probed + if p.metadata == nil && p.err == nil { + p.metadata, p.err = metadata.GetMetadata() + if p.err != nil { + return nil, p.err + } + } else if p.err != nil { + return nil, p.err + } + + if err := ioutil.WriteFile(path.Join(ConfigPath, Hostname), []byte(p.metadata.Hostname), 0644); err != nil { + return nil, fmt.Errorf("Packet: Failed to write hostname: %s", err) + } + + if err := os.MkdirAll(path.Join(ConfigPath, SSH), 0755); err != nil { + return nil, fmt.Errorf("Failed to create %s: %s", SSH, err) + } + + sshKeys := strings.Join(p.metadata.SSHKeys, "\n") + + if err := ioutil.WriteFile(path.Join(ConfigPath, SSH, "authorized_keys"), []byte(sshKeys), 0600); err != nil { + return nil, fmt.Errorf("Failed to write ssh keys: %s", err) + } + + if err := networkConfig(p.metadata.Network); err != nil { + return nil, err + } + + userData, err := metadata.GetUserData() + if err != nil { + return nil, fmt.Errorf("Packet: failed to get userdata: %s", err) + } + + if len(userData) == 0 { + return nil, nil + } + + if len(userData) > 6 && string(userData[0:6]) == "#!ipxe" { + // if you use the userdata for ipxe boot, no use as userdata + return nil, nil + } + + return userData, nil +} + +// networkConfig handles Packet network configuration, primarily bonding +func networkConfig(ni metadata.NetworkInfo) error { + // rename interfaces to match what the metadata calls them + links, err := netlink.LinkList() + if err != nil { + return fmt.Errorf("Failed to list links: %v", err) + } + for _, link := range links { + attrs := link.Attrs() + mac := attrs.HardwareAddr.String() + for _, iface := range ni.Interfaces { + if iface.MAC == mac { + // remove existing addresses from link + addresses, err := netlink.AddrList(link, netlink.FAMILY_ALL) + if err != nil { + return fmt.Errorf("Cannot list addresses on interface: %v", err) + } + for _, addr := range addresses { + if err := netlink.AddrDel(link, &addr); err != nil { + return fmt.Errorf("Cannot remove address from interface: %v", err) + } + } + // links need to be down to be bonded + if err := netlink.LinkSetDown(link); err != nil { + return fmt.Errorf("Cannot down link: %v", err) + } + // see if we need to rename interface + if iface.Name != attrs.Name { + if err := netlink.LinkSetName(link, iface.Name); err != nil { + return fmt.Errorf("Interface rename failed: %v", err) + } + } + } + } + } + + // set up bonding + la := netlink.LinkAttrs{Name: "bond0"} + bond := &netlink.GenericLink{la, "bond"} + if err := netlink.LinkAdd(bond); err != nil { + // weirdly creating a bind always seems to return EEXIST + fmt.Fprintf(os.Stderr, "Error adding bond0: %v (ignoring)", err) + } + if err := ioutil.WriteFile("/sys/class/net/bond0/bonding/mode", []byte(strconv.Itoa(int(ni.Bonding.Mode))), 0); err != nil { + return fmt.Errorf("Cannot write to /sys/class/net/bond0/bonding/mode: %v", err) + } + if err := netlink.LinkSetUp(bond); err != nil { + return fmt.Errorf("Failed to bring bond0 up: %v", err) + } + for _, iface := range ni.Interfaces { + link, err := netlink.LinkByName(iface.Name) + if err != nil { + return fmt.Errorf("Cannot find interface %s: %v", iface.Name, err) + } + if err := netlink.LinkSetMasterByIndex(link, bond.Attrs().Index); err != nil { + return fmt.Errorf("Cannot join %s to bond0: %v", iface.Name, err) + } + } + + // set up addresses and routes + for _, address := range ni.Addresses { + cidr := "/" + strconv.Itoa(address.NetworkBits) + addr, err := netlink.ParseAddr(address.Address.String() + cidr) + if err != nil { + return err + } + if err := netlink.AddrAdd(bond, addr); err != nil { + return fmt.Errorf("Failed to add address to bonded interface: %v", err) + } + var additionalNet string + switch { + case address.Public && address.Family == metadata.IPv4: + additionalNet = "0.0.0.0/0" + case address.Public && address.Family == metadata.IPv6: + additionalNet = "::/0" + case !address.Public && address.Family == metadata.IPv4: + // add a gateway for eg 10.0.0.0/8 + mask := address.Address.DefaultMask() + masked := address.Address.Mask(mask) + network := &net.IPNet{IP: masked, Mask: mask} + additionalNet = network.String() + } + if additionalNet != "" { + _, network, err := net.ParseCIDR(additionalNet) + if err != nil { + return err + } + route := netlink.Route{ + LinkIndex: bond.Attrs().Index, + Gw: address.Gateway, + Dst: network, + } + if err := netlink.RouteAdd(&route); err != nil { + return fmt.Errorf("Failed to add route: %v", err) + } + } + } + + return nil +}