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 executeTemplate(rootTemplte *template.Template, templateName string, machine datasource.Machine, hostAddr string) (string, error) { template := rootTemplte.Lookup(templateName) if template == nil { return "", fmt.Errorf("template with name=%s wasn't found for root=%s", templateName, rootTemplte) } buf := new(bytes.Buffer) template.Funcs(map[string]interface{}{ "V": func(key string) string { flag, err := machine.GetFlag(key) if err != nil { // TODO excepts Not-Found logging.Log(templatesDebugTag, "Error while getting flag key=%s for machine=%s: %s", key, machine.Name(), err) return "" } return flag }, "b64": func(text string) string { return base64.StdEncoding.EncodeToString([]byte(text)) }, "b64template": func(templateName string) string { text, err := executeTemplate(rootTemplte, templateName, machine, hostAddr) if err != nil { logging.Log(templatesDebugTag, "Error while b64template for templateName=%s machine=%s: %s", templateName, machine.Name(), err) return "" } return base64.StdEncoding.EncodeToString([]byte(text)) }, }) ip, _ := machine.IP() data := struct { Mac string IP string Hostname string Domain string HostAddr string }{ machine.Mac().String(), ip.String(), machine.Name(), machine.Domain(), hostAddr, } err := template.ExecuteTemplate(buf, templateName, &data) if err != nil { return "", err } str := buf.String() str = strings.Trim(str, "\n") return str, nil }
func (c *CloudConfig) handler(w http.ResponseWriter, r *http.Request) { req := strings.Split(r.URL.Path, "/")[1:] queryMap, _ := extractQueries(r.URL.RawQuery) if len(req) != 2 { logging.Log("CLOUDCONFIG", "Received request - request not found") http.NotFound(w, r) return } var selectedRepo *Repo switch req[0] { case "cloud": selectedRepo = c.cloudRepo case "ignition": selectedRepo = c.ignitionRepo default: http.NotFound(w, r) return } ip, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { http.Error(w, "internal server error - parsing host and port", 500) logging.Log("CLOUDCONFIG", "Error - %s with mac %s - %s", req[0], req[1], err.Error()) return } configCtx := &ConfigContext{ MacAddr: req[1], IP: ip, } config, err := selectedRepo.GenerateConfig(configCtx) if err != nil { http.Error(w, "internal server error - error in generating config", 500) logging.Log("CLOUDCONFIG", "Error when generating config - %s with mac %s - %s", req[0], req[1], err.Error()) return } w.Header().Set("Content-Type", "application/x-yaml") //always validate the cloudconfig. Don't if explicitly stated. if value, exists := queryMap["validate"]; !exists || value != "false" { config += validateCloudConfig(config) } w.Write([]byte(config)) logging.Log("CLOUDCONFIG", "Received request - %s with mac %s", req[0], req[1]) }
// Assign assigns an ip to the node with the specified nic // Will use etcd machines records as LeasePool // part of DHCPDataSource interface implementation func (ds *EtcdDataSource) Assign(nic string) (net.IP, error) { ds.lockDHCPAssign() defer ds.unlockdhcpAssign() // TODO: first try to retrieve the machine, if exists (for performance) assignedIPs := make(map[string]bool) //find by Mac machines, _ := ds.Machines() for _, node := range machines { if node.Mac().String() == nic { ip, _ := node.IP() ds.store(node, ip) return ip, nil } nodeIP, _ := node.IP() assignedIPs[nodeIP.String()] = true } //find an unused ip for i := 0; i < ds.LeaseRange(); i++ { ip := dhcp4.IPAdd(ds.LeaseStart(), i) if _, exists := assignedIPs[ip.String()]; !exists { macAddress, _ := net.ParseMAC(nic) ds.CreateMachine(macAddress, ip) return ip, nil } } //use an expired ip //not implemented logging.Log(debugTag, "DHCP pool is full") return nil, nil }
func ServeHTTPBooter(listenAddr net.TCPAddr, runtimeConfig *datasource.RuntimeConfiguration, bootParamsRepo *cloudconfig.Repo) error { logging.Log("HTTPBOOTER", "Listening on %s", listenAddr.String()) mux, err := HTTPBooterMux(listenAddr, runtimeConfig, bootParamsRepo) if err != nil { return err } return http.ListenAndServe(listenAddr.String(), mux) }
func ServeHTTPBooter(listenAddr net.TCPAddr, ds datasource.DataSource, webPort int) error { logging.Log("HTTPBOOTER", "Listening on %s", listenAddr.String()) mux, err := HTTPBooterMux(listenAddr, ds, webPort) if err != nil { return err } return http.ListenAndServe(listenAddr.String(), mux) }
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) } }
func Serve(listenAddr net.TCPAddr, datasource *datasource.DataSource) error { logging.Log(debugTag, "Serving Rest API on %s", listenAddr) restApi := newRestServerAPI(datasource) handler, err := restApi.MakeHandler() if err != nil { return err } return http.ListenAndServe(listenAddr.String(), handler) }
func (ds *DataSource) ConfigByteArray(name string) []byte { base64Value := ds.ConfigString(name) value, err := base64.StdEncoding.DecodeString(base64Value) if err != nil { logging.Log(debugTag, "Error while decoding config value %s: %s", name, err) return nil } return value }
func newRestServerAPI(datasource *datasource.DataSource) *restServerAPI { rest := grest.NewApi() rest.Use(grest.DefaultDevStack...) rest.Use(&grest.CorsMiddleware{ RejectNonCorsRequests: false, OriginValidator: func(origin string, request *grest.Request) bool { // TODO Origin check return true }, AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, AllowedHeaders: []string{ "Accept", "Content-Type", "X-Custom-Header", "Origin", "Authorization"}, AccessControlAllowCredentials: true, AccessControlMaxAge: 3600, }) var bearerAuthMiddleware = &AuthBearerMiddleware{ Realm: "RestAuthentication", Authenticator: func(token string) string { parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return datasource.ConfigByteArray("TOKEN_SIGN_KEY"), nil }) if err == nil && parsedToken.Valid { return parsedToken.Claims["email"].(string) } else { return "" } }, Authorizer: func(request *grest.Request, userID string) bool { user, err := datasource.UserByEmail(userID) if err != nil { logging.Log(debugTag, "Couldn't fetch user for userID=%s", userID) return false } request.Env["REMOTE_USER_OBJECT"] = user return true }, } rest.Use(&grest.IfMiddleware{ Condition: func(request *grest.Request) bool { return request.URL.Path != "/login" }, IfTrue: bearerAuthMiddleware, }) return &restServerAPI{ rest: rest, ds: datasource, } }
func ServeCloudConfig(listenAddr net.TCPAddr, workspacePath string, datasources map[string]DataSource) error { logging.Log("CLOUDCONFIG", "Listening on %s", listenAddr.String()) cloudRepo, err := FromPath(datasources, path.Join(workspacePath, "config/cloudconfig")) if err != nil { return err } ignitionRepo, err := FromPath(datasources, path.Join(workspacePath, "config/ignition")) if err != nil { return err } cloudConfig := NewCloudConfig(cloudRepo, ignitionRepo) return http.ListenAndServe(listenAddr.String(), cloudConfig.Mux()) }
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 } } }
// IsMaster checks for being master, and makes a heartbeat func (ds *EtcdDataSource) IsMaster() bool { var err error if ds.instancesEtcdDir == invalidEtcdKey { err = ds.registerOnEtcd() if err != nil { logging.Log(debugTag, "error while registerOnEtcd: %s", err) return false } } else { err = ds.etcdHeartbeat() if err != nil { ds.instancesEtcdDir = invalidEtcdKey logging.Log(debugTag, "error while updateOnEtcd: %s", err) return false } } ctx, cancel := context.WithTimeout(context.Background(), etcdTimeout) defer cancel() masterGetOptions := etcd.GetOptions{ Recursive: true, Quorum: true, Sort: true, } resp, err := ds.keysAPI.Get(ctx, ds.prefixify(instancesEtcdDir), &masterGetOptions) if err != nil { logging.Log(debugTag, "error while getting the dir list from etcd: %s", err) return false } if len(resp.Node.Nodes) < 1 { logging.Log(debugTag, "empty list while getting the dir list from etcd") return false } if resp.Node.Nodes[0].Key == ds.instancesEtcdDir { return true } return false }
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 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 (r *restServerAPI) Login(w grest.ResponseWriter, req *grest.Request) { up := userPass{} err := req.DecodeJsonPayload(&up) if err != nil { grest.Error(w, err.Error(), http.StatusInternalServerError) return } if up.Email == "" || up.Password == "" { grest.Error(w, "user/password missing", http.StatusBadRequest) } user, err := r.ds.UserByEmail(up.Email) if err != nil { grest.Error(w, "user/password failed", http.StatusBadRequest) return } if !user.AcceptsPassword(up.Password, r.ds.ConfigByteArray("PASSWORD_SALT")) { grest.Error(w, "user/password failed", http.StatusBadRequest) return } if !user.Active { grest.Error(w, "user isn't activated", http.StatusForbidden) return } token := jwt.New(jwt.GetSigningMethod("HS512")) token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() token.Claims["email"] = user.Email token.Claims["isAdmin"] = user.Admin tokenString, err := token.SignedString(r.ds.ConfigByteArray("TOKEN_SIGN_KEY")) if err != nil { logging.Log(debugTag, "Signing failed: %s", err) grest.Error(w, "Authentication failed", http.StatusInternalServerError) return } w.WriteJson(map[string]string{ "token": tokenString, }) }
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 (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) }
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 }