func (ds *DataSource) Users() ([]*User, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() response, err := ds.keysAPI.Get(ctx, fmt.Sprintf("/%s/users", ds.etcdDir), nil) if err != nil { return nil, err } var users []*User errCount := 0 for i := range response.Node.Nodes { u, e := userFromNodeValue(response.Node.Nodes[i].Value) if e != nil { errCount += 1 logging.Debug(debugTag, "Error while userFromNodeValue: %s", e) } else { users = append(users, u) } } if errCount > 0 { return nil, fmt.Errorf("Errors happened while trying to unmarshal %d user(s)", errCount) } return users, nil }
func (b *HTTPBooter) fileHandler(w http.ResponseWriter, r *http.Request) { splitPath := strings.SplitN(r.URL.Path, "/", 4) version := splitPath[2] id := splitPath[3] logging.Debug("HTTPBOOTER", "Got request for %s", r.URL.Path) var ( f io.ReadCloser err error ) f, err = b.coreOS(version, id) if err != nil { logging.Log("HTTPBOOTER", "Couldn't get byte stream for %q from %s: %s", r.URL, r.RemoteAddr, err) http.Error(w, "Couldn't get byte stream", http.StatusInternalServerError) return } defer f.Close() w.Header().Set("Content-Type", "application/octet-stream") written, err := io.Copy(w, f) if err != nil { logging.Log("HTTPBOOTER", "Error serving %s to %s: %s", id, r.RemoteAddr, err) return } logging.Log("HTTPBOOTER", "Sent %s to %s (%d bytes)", id, r.RemoteAddr, written) }
func (ds *DataSource) Groups() ([]*Group, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() response, err := ds.keysAPI.Get(ctx, fmt.Sprintf("/%s/groups", ds.etcdDir), nil) if err != nil { return nil, err } var groups []*Group errCount := 0 for i := range response.Node.Nodes { g, e := groupFromNodeValue(response.Node.Nodes[i].Value) if e != nil { errCount += 1 logging.Debug(debugTag, "Error while groupFromNodeValue: %s", e) } else { groups = append(groups, g) } } if errCount > 0 { return nil, fmt.Errorf("Errors happened while trying to unmarshal %d group(s)", errCount) } return groups, nil }
func ServeDHCP(settings *DHCPSetting, leasePool *LeasePool) error { handler, err := newDHCPHandler(settings, leasePool) if err != nil { logging.Debug("DHCP", "Error in connecting etcd - %s", err.Error()) return err } logging.Log("DHCP", "Listening on :67 - with server IP %s", settings.ServerIP.String()) if settings.IFName != "" { err = dhcp4.ListenAndServeIf(settings.IFName, handler) } else { err = dhcp4.ListenAndServe(handler) } if err != nil { logging.Debug("DHCP", "Error in server - %s", err.Error()) } return err }
func logln(level int, s string) { if level == 2 { log.Fatalf(s) } else if level == 1 { logging.Log(debugTag, s) } else { logging.Debug(debugTag, s) } }
// Get the contents of a blob mentioned in a previously issued // BootSpec. Additionally returns a pretty name for the blob for // logging purposes. func (b *HTTPBooter) coreOS(version string, id string) (io.ReadCloser, error) { imagePath := filepath.Join(b.runtimeConfig.WorkspacePath, "images") switch id { case "kernel": path := filepath.Join(imagePath, version, "coreos_production_pxe.vmlinuz") logging.Debug("HTTPBOOTER", "path=<%q>", path) return os.Open(path) case "initrd": return os.Open(filepath.Join(imagePath, version, "coreos_production_pxe_image.cpio.gz")) } return nil, fmt.Errorf("id=<%q> wasn't expected", id) }
func ServeDHCP(settings *DHCPSetting, datasource datasource.DataSource) error { handler, err := newDHCPHandler(settings, datasource) if err != nil { logging.Debug("DHCP", "Error in connecting etcd - %s", err.Error()) return err } logging.Log("DHCP", "Listening on %s:67 (interface: %s)", settings.ServerIP.String(), settings.IFName) if settings.IFName != "" { err = dhcp4.ListenAndServeIf(settings.IFName, handler) } else { err = dhcp4.ListenAndServe(handler) } if err != nil { logging.Debug("DHCP", "Error in server - %s", err.Error()) } rand.Seed(time.Now().UTC().UnixNano()) return err }
func ServeTFTP(listenAddr net.UDPAddr) error { pxelinuxDir := FS(false) pxelinux, err := pxelinuxDir.Open("/pxelinux/lpxelinux.0") if err != nil { return err } tftp.Log = func(msg string, args ...interface{}) { logging.Log("TFTP", msg, args...) } tftp.Debug = func(msg string, args ...interface{}) { logging.Debug("TFTP", msg, args...) } handler := func(string, net.Addr) (io.ReadCloser, error) { return pxelinux, nil } return tftp.ListenAndServe("udp4", listenAddr.String(), handler) }
func (ds *DataSource) StoreUser(u *User) error { userJSON, err := json.Marshal(u) if err != nil { return err } logging.Debug(debugTag, "Setting %s", userJSON) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() _, err = ds.keysAPI.Set(ctx, fmt.Sprintf("/%s/users/%s", ds.etcdDir, u.Email), string(userJSON[:]), nil) if err != nil { return err } return nil }
func (ds *DataSource) StoreGroup(g *Group) error { groupJSON, err := json.Marshal(g) if err != nil { return err } logging.Debug(debugTag, "Setting %s", groupJSON) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() _, err = ds.keysAPI.Set(ctx, fmt.Sprintf("/%s/groups/%s", ds.etcdDir, g.Email), string(groupJSON[:]), nil) if err != nil { return err } return nil }
func ServePXE(listenAddr net.UDPAddr, serverIP net.IP, httpAddr net.TCPAddr) error { conn, err := net.ListenPacket("udp4", listenAddr.String()) if err != nil { return err } defer conn.Close() l := ipv4.NewPacketConn(conn) if err = l.SetControlMessage(ipv4.FlagInterface, true); err != nil { return err } logging.Log("PXE", "Listening on %s", listenAddr.String()) buf := make([]byte, 1024) for { n, msg, addr, err := l.ReadFrom(buf) if err != nil { logging.Log("PXE", "Error reading from socket: %s", err) continue } req, err := ParsePXE(buf[:n]) if err != nil { logging.Debug("PXE", "ParsePXE: %s", err) continue } req.ServerIP = serverIP req.HTTPServer = fmt.Sprintf("http://%s/", httpAddr.String()) logging.Log("PXE", "Chainloading %s (%s) to pxelinux (via %s)", req.MAC, req.ClientIP, req.ServerIP) if _, err := l.WriteTo(ReplyPXE(req), &ipv4.ControlMessage{ IfIndex: msg.IfIndex, }, addr); err != nil { logging.Log("PXE", "Responding to %s: %s", req.MAC, err) continue } } }
func (u *User) AcceptsPassword(plainPassword string, salt []byte) bool { encodedInputPassword, err := u.encodePassword(plainPassword, salt) if err != nil { logging.Debug(debugTag, "Error while encodePassword: %s", err) return false } userPassword, err := base64.StdEncoding.DecodeString(u.Password) if err != nil { return false } if userPassword == nil || encodedInputPassword == nil { return false } if len(userPassword) != len(encodedInputPassword) { return false } for i := range userPassword { if userPassword[i] != encodedInputPassword[i] { return false } } return true }
func (b *HTTPBooter) pxelinuxConfig(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") macStr := filepath.Base(r.URL.Path) errStr := fmt.Sprintf("%s requested a pxelinux config from URL %q, which does not include a MAC address", r.RemoteAddr, r.URL) if !strings.HasPrefix(macStr, "01-") { logging.Debug("HTTPBOOTER", errStr) http.Error(w, "Missing MAC address in request", http.StatusBadRequest) return } mac, err := net.ParseMAC(macStr[3:]) if err != nil { logging.Debug("HTTPBOOTER", errStr) http.Error(w, "Malformed MAC address in request", http.StatusBadRequest) return } if _, _, err := net.SplitHostPort(r.Host); err != nil { r.Host = fmt.Sprintf("%s:%d", r.Host, b.listenAddr.Port) } // TODO: Ask dataSource about the mac // We have a machine sitting in pxelinux, but the Booter says // we shouldn't be netbooting. So, give it a config that tells // pxelinux to shut down PXE booting and continue with the // next local boot method. coreOSVersion, _ := b.runtimeConfig.GetCoreOSVersion() KernelURL := "http://" + r.Host + "/f/" + coreOSVersion + "/kernel" InitrdURL := "http://" + r.Host + "/f/" + coreOSVersion + "/initrd" host, _, err := net.SplitHostPort(r.Host) if err != nil { logging.Log("HTTPBOOTER", "error in parsing host and port") http.Error(w, "error in parsing host and port", 500) return } // generate bootparams config params, err := b.bootParamsRepo.GenerateConfig(&cloudconfig.ConfigContext{ MacAddr: strings.Replace(mac.String(), ":", "", -1), IP: "", }) if err != nil { logging.Log("HTTPBOOTER", "error in bootparam template - %s", err.Error()) http.Error(w, "error in bootparam template", 500) return } params = strings.Replace(params, "\n", " ", -1) // FIXME: 8001 is hardcoded Cmdline := fmt.Sprintf( "cloud-config-url=http://%s:8001/cloud/%s "+ "coreos.config.url=http://%s:8001/ignition/%s %s", host, strings.Replace(mac.String(), ":", "", -1), host, strings.Replace(mac.String(), ":", "", -1), params) bootMessage := strings.Replace(bootMessageTemplate, "$MAC", macStr, -1) cfg := fmt.Sprintf(` SAY %s DEFAULT linux LABEL linux LINUX %s APPEND initrd=%s %s `, strings.Replace(bootMessage, "\n", "\nSAY ", -1), KernelURL, InitrdURL, Cmdline) w.Write([]byte(cfg)) logging.Log("HTTPBOOTER", "Sent pxelinux config to %s (%s)", mac, r.RemoteAddr) }
func (h *DHCPHandler) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dhcp4.Options) (d dhcp4.Packet) { var macAddress string = strings.Join(strings.Split(p.CHAddr().String(), ":"), "") switch msgType { case dhcp4.Discover: ip, err := h.leasePool.Assign(p.CHAddr().String()) if err != nil { logging.Debug("DHCP", "err in lease pool - %s", err.Error()) return nil // pool is full } replyOptions := h.dhcpOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]) packet := dhcp4.ReplyPacket(p, dhcp4.Offer, h.settings.ServerIP, ip, h.settings.LeaseDuration, replyOptions) // this is a pxe request guidVal, is_pxe := options[97] if is_pxe { logging.Log("DHCP", "dhcp discover with PXE - CHADDR %s - IP %s - our ip %s", p.CHAddr().String(), ip.String(), h.settings.ServerIP.String()) guid := guidVal[1:] packet.AddOption(60, []byte("PXEClient")) packet.AddOption(97, guid) packet.AddOption(43, h.fillPXE()) } else { logging.Log("DHCP", "dhcp discover - CHADDR %s - IP %s", p.CHAddr().String(), ip.String()) } return packet case dhcp4.Request: if server, ok := options[dhcp4.OptionServerIdentifier]; ok && !net.IP(server).Equal(h.settings.ServerIP) { return nil // this message is not ours } requestedIP := net.IP(options[dhcp4.OptionRequestedIPAddress]) if requestedIP == nil { requestedIP = net.IP(p.CIAddr()) } if len(requestedIP) != 4 || requestedIP.Equal(net.IPv4zero) { logging.Debug("DHCP", "dhcp request - CHADDR %s - bad request", p.CHAddr().String()) return nil } _, err := h.leasePool.Request(p.CHAddr().String(), requestedIP) if err != nil { logging.Debug("DHCP", "dhcp request - CHADDR %s - Requested IP %s - NO MATCH", p.CHAddr().String(), requestedIP.String()) return dhcp4.ReplyPacket(p, dhcp4.NAK, h.settings.ServerIP, nil, 0, nil) } replyOptions := h.dhcpOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]) packet := dhcp4.ReplyPacket(p, dhcp4.ACK, h.settings.ServerIP, requestedIP, h.settings.LeaseDuration, replyOptions) // this is a pxe request guidVal, is_pxe := options[97] if is_pxe { logging.Log("DHCP", "dhcp request with PXE - CHADDR %s - Requested IP %s - our ip %s - ACCEPTED", p.CHAddr().String(), requestedIP.String(), h.settings.ServerIP.String()) guid := guidVal[1:] packet.AddOption(60, []byte("PXEClient")) packet.AddOption(97, guid) packet.AddOption(43, h.fillPXE()) } else { logging.Log("DHCP", "dhcp request - CHADDR %s - Requested IP %s - ACCEPTED", p.CHAddr().String(), requestedIP.String()) } packet.AddOption(12, []byte("node"+macAddress)) // host name option return packet case dhcp4.Release, dhcp4.Decline: return nil } return nil }
func main() { var err error flag.Parse() fmt.Printf("Blacksmith (%s)\n", version) fmt.Printf(" Commit: %s\n", commit) fmt.Printf(" Build Time: %s\n", buildTime) if *versionFlag { os.Exit(0) } // etcd config if etcdFlag == nil || clusterNameFlag == nil { fmt.Fprint(os.Stderr, "\nPlease specify the etcd endpoints\n") os.Exit(1) } // finding interface by interface name var dhcpIF *net.Interface if *listenIFFlag != "" { dhcpIF, err = net.InterfaceByName(*listenIFFlag) if err != nil { fmt.Fprintf(os.Stderr, "\nError while trying to get the interface (%s)\n", *listenIFFlag) os.Exit(1) } } else { fmt.Fprint(os.Stderr, "\nPlease specify an interface\n") os.Exit(1) } serverIP, err := interfaceIP(dhcpIF) if err != nil { fmt.Fprintf(os.Stderr, "\nError while trying to get the ip from the interface (%s)\n", dhcpIF) os.Exit(1) } webAddr := net.TCPAddr{IP: serverIP, Port: 8000} if *httpListenFlag != httpListenFlagDefaultTCPAddress { splitAddress := strings.Split(*httpListenFlag, ":") if len(splitAddress) > 2 { fmt.Printf("Incorrect tcp address provided: %s", httpListenFlag) os.Exit(1) } if len(splitAddress) == 1 { splitAddress = append(splitAddress, "8000") } webAddr.IP = net.ParseIP(splitAddress[0]) port, err := strconv.ParseInt(splitAddress[1], 10, 64) if err != nil { fmt.Printf("Incorrect tcp address provided: %s", httpListenFlag) os.Exit(1) } webAddr.Port = int(port) } // component ports // web api is exposed to requests from `webIP', 0.0.0.0 by default // other services are exposed just through the given interface var httpBooterAddr = net.TCPAddr{IP: serverIP, Port: 70} var tftpAddr = net.UDPAddr{IP: serverIP, Port: 69} var pxeAddr = net.UDPAddr{IP: serverIP, Port: 4011} // 67 -> dhcp // dhcp setting leaseStart := net.ParseIP(*leaseStartFlag) leaseRange := *leaseRangeFlag leaseSubnet := net.ParseIP(*leaseSubnetFlag) leaseRouter := net.ParseIP(*leaseRouterFlag) dnsIPStrings := strings.Split(*dnsAddressesFlag, ",") if len(dnsIPStrings) == 0 { fmt.Fprint(os.Stderr, "\nPlease specify an DNS server\n") os.Exit(1) } for _, ipString := range dnsIPStrings { ip := net.ParseIP(ipString) if ip == nil { fmt.Fprintf(os.Stderr, "\nInvalid dns ip: %s\n", ipString) os.Exit(1) } } if leaseStart == nil { fmt.Fprint(os.Stderr, "\nPlease specify the lease start ip\n") os.Exit(1) } if leaseRange <= 1 { fmt.Fprint(os.Stderr, "\nLease range should be greater that 1\n") os.Exit(1) } if leaseSubnet == nil { fmt.Fprint(os.Stderr, "\nPlease specify the lease subnet\n") os.Exit(1) } if leaseRouter == nil { fmt.Fprint(os.Stderr, "\nNo network router is defined.\n") } fmt.Printf("Interface IP: %s\n", serverIP.String()) fmt.Printf("Interface Name: %s\n", dhcpIF.Name) // datasources etcdClient, err := etcd.New(etcd.Config{ Endpoints: strings.Split(*etcdFlag, ","), HeaderTimeoutPerRequest: 5 * time.Second, }) if err != nil { fmt.Fprintf(os.Stderr, "\nCouldn't create etcd connection: %s\n", err) os.Exit(1) } kapi := etcd.NewKeysAPI(etcdClient) v := datasource.BlacksmithVersion{ Version: version, Commit: commit, BuildTime: buildTime, } etcdDataSource, err := datasource.NewEtcdDataSource(kapi, etcdClient, leaseStart, leaseRange, *clusterNameFlag, *workspacePathFlag, serverIP, dnsIPStrings, v) if err != nil { fmt.Fprintf(os.Stderr, "\nCouldn't create runtime configuration: %s\n", err) os.Exit(1) } go func() { logging.RecordLogs(log.New(os.Stderr, "", log.LstdFlags), *debugFlag) }() // serving api go func() { err := web.ServeWeb(etcdDataSource, webAddr) log.Fatalf("\nError while serving api: %s\n", err) }() c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) go func() { for _ = range c { gracefulShutdown(etcdDataSource) } }() // waiting til we're officially the master instance for !etcdDataSource.IsMaster() { logging.Debug(debugTag, "Not master, waiting to be promoted...") time.Sleep(datasource.StandbyMasterUpdateTime) } logging.Debug(debugTag, "Now we're the master instance. Starting the services...") // serving http booter go func() { err := pxe.ServeHTTPBooter(httpBooterAddr, etcdDataSource, webAddr.Port) log.Fatalf("\nError while serving http booter: %s\n", err) }() // serving tftp go func() { err := pxe.ServeTFTP(tftpAddr) log.Fatalf("\nError while serving tftp: %s\n", err) }() // pxe protocol go func() { err := pxe.ServePXE(pxeAddr, serverIP, httpBooterAddr) log.Fatalf("\nError while serving pxe: %s\n", err) }() // serving dhcp go func() { err := dhcp.ServeDHCP(&dhcp.DHCPSetting{ IFName: dhcpIF.Name, ServerIP: serverIP, RouterAddr: leaseRouter, SubnetMask: leaseSubnet, }, etcdDataSource) log.Fatalf("\nError while serving dhcp: %s\n", err) }() for etcdDataSource.IsMaster() { time.Sleep(datasource.ActiveMasterUpdateTime) } logging.Debug(debugTag, "Now we're NOT the master. Terminating. Hoping to be restarted by the service manager.") gracefulShutdown(etcdDataSource) }
func (b *HTTPBooter) pxelinuxConfig(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") macStr := filepath.Base(r.URL.Path) errStr := fmt.Sprintf("%s requested a pxelinux config from URL %q, which does not include a correct MAC address", r.RemoteAddr, r.URL) if !strings.HasPrefix(macStr, "01-") { logging.Debug("HTTPBOOTER", errStr) http.Error(w, "Missing MAC address in request", http.StatusBadRequest) return } mac, err := net.ParseMAC(macStr[3:]) if err != nil { logging.Debug("HTTPBOOTER", errStr) http.Error(w, "Malformed MAC address in request", http.StatusBadRequest) return } machine, exist := b.datasource.GetMachine(mac) if !exist { logging.Debug("HTTPBOOTER", "Machine not found. mac=%s", mac) http.Error(w, "Machine not found", http.StatusNotFound) return } if _, _, err := net.SplitHostPort(r.Host); err != nil { r.Host = fmt.Sprintf("%s:%d", r.Host, b.listenAddr.Port) } coreOSVersion, _ := b.datasource.CoreOSVersion() KernelURL := "http://" + r.Host + "/f/" + coreOSVersion + "/kernel" InitrdURL := "http://" + r.Host + "/f/" + coreOSVersion + "/initrd" host, _, err := net.SplitHostPort(r.Host) if err != nil { logging.Log("HTTPBOOTER", "error in parsing host and port") http.Error(w, "error in parsing host and port", 500) return } params, err := templating.ExecuteTemplateFolder( path.Join(b.datasource.WorkspacePath(), "config", "bootparams"), machine, r.Host) if err != nil { logging.Log("HTTPBOOTER", `Error while executing the template: %q`, err) http.Error(w, fmt.Sprintf(`Error while executing the template: %q`, err), http.StatusInternalServerError) return } if err != nil { logging.Log("HTTPBOOTER", "error in bootparam template - %s", err.Error()) http.Error(w, "error in bootparam template", 500) return } params = strings.Replace(params, "\n", " ", -1) Cmdline := fmt.Sprintf( "cloud-config-url=http://%s:%d/t/cc/%s "+ "coreos.config.url=http://%s:%d/t/ig/%s %s", host, b.webPort, mac.String(), host, b.webPort, mac.String(), params) bootMessage := strings.Replace(b.bootMessageTemplate, "$MAC", macStr, -1) cfg := fmt.Sprintf(` SAY %s DEFAULT linux LABEL linux LINUX %s APPEND initrd=%s %s `, strings.Replace(bootMessage, "\n", "\nSAY ", -1), KernelURL, InitrdURL, Cmdline) w.Write([]byte(cfg)) logging.Log("HTTPBOOTER", "Sent pxelinux config to %s (%s)", mac, r.RemoteAddr) }