package hammerhead import ( "crypto/subtle" "errors" "net/http" "strconv" "time" "git.veillax.com/Veillax/reef/model" ) // Endpoints used by blacktip and bamboo. They authenticate with the edge/site token. func (s *Server) edgeByToken(r *http.Request) *model.Edge { t := bearer(r) if t == "" { return nil } for _, e := range s.st.Edges { if subtle.ConstantTimeCompare([]byte(e.Token), []byte(t)) == 1 { return e } } return nil } func (s *Server) siteByToken(r *http.Request) *model.Site { t := bearer(r) if t == "" { return nil } for _, e := range s.st.Sites { if !e.Local && subtle.ConstantTimeCompare([]byte(e.Token), []byte(t)) == 1 { return e } } return nil } func (s *Server) edgeConfig(w http.ResponseWriter, r *http.Request) { s.mu.Lock() e := s.edgeByToken(r) s.mu.Unlock() if e == nil { fail(w, 401, errors.New("bad edge token")) return } have, _ := strconv.ParseInt(r.URL.Query().Get("v"), 10, 64) if !s.wait(r, have) { s.mu.Lock() if e := s.edgeByToken(r); e != nil { e.LastSeen = time.Now() } s.mu.Unlock() w.WriteHeader(http.StatusNoContent) return } s.mu.Lock() defer s.mu.Unlock() if e = s.edgeByToken(r); e == nil { fail(w, 401, errors.New("bad edge token")) return } e.LastSeen = time.Now() cfg := model.EdgeConfig{ Version: s.st.Version, EdgeID: e.ID, AuthKey: e.AuthKey, Dashboard: s.st.Settings.DashboardURL, Mappings: []model.Mapping{}, Peers: []model.WGPeer{}, Local: []string{}, TunnelIP: e.TunnelIP, AcmeEmail: s.st.Settings.AcmeEmail, AcmeStages: s.st.Settings.AcmeStaging, } for _, m := range s.st.Mappings { if m.EdgeID == e.ID && m.Enabled { cfg.Mappings = append(cfg.Mappings, *m) } } if m := s.selfMapping(e); m != nil { cfg.Mappings = append(cfg.Mappings, *m) } cfg.UpdateID = e.UpdateID cfg.MailEgress = []model.MailPolicy{} for _, x := range s.st.MailEgress { if x.EdgeID == e.ID && x.Enabled { cfg.MailEgress = append(cfg.MailEgress, model.MailPolicy{ID: x.ID, SiteID: x.SiteID, SourceIP: x.SourceIP, HeloName: x.HeloName, Smarthost: x.Smarthost, RequireTLS: x.RequireTLS, RatePerMinute: x.RatePerMinute, DKIM: s.signersLocked(x.OrgID)}) } } for _, st := range s.st.Sites { switch { case st.Local: if st.EdgeID == e.ID { cfg.Local = append(cfg.Local, st.ID) } case st.PublicKey != "" && st.TunnelIP != "" && s.edgeAllowedFor(e, st.OrgID): cfg.Peers = append(cfg.Peers, model.WGPeer{SiteID: st.ID, PublicKey: st.PublicKey, IP: st.TunnelIP}) } } writeJSON(w, 200, cfg) } func (s *Server) edgeReport(w http.ResponseWriter, r *http.Request) { var in model.EdgeReport if !decode(w, r, &in) { return } s.mu.Lock() defer s.mu.Unlock() e := s.edgeByToken(r) if e == nil { fail(w, 401, errors.New("bad edge token")) return } e.LastSeen = time.Now() e.IPs = in.IPs oldVersion := e.Version answered := noteUpdateReport(&e.Version, &e.AutoUpdate, &e.UpdateID, &e.UpdateMsg, in.Version, in.AutoUpdate, in.UpdateID, in.UpdateMsg) s.recordUpdateProgress(e.ID, in.UpdateID, in.UpdateStage, in.UpdatePct, in.UpdateLog, oldVersion, e.Version) for _, x := range s.st.MailEgress { if x.EdgeID == e.ID { if chk, found := in.MailChecks[x.ID]; found { s.mailChecks[x.ID] = chk } } } for _, m := range s.st.Mappings { if m.EdgeID != e.ID { continue } if st, found := in.Status[m.ID]; found { s.status[m.ID] = st } else { delete(s.status, m.ID) } if d, found := in.Stats[m.ID]; found { t := s.st.Totals[m.ID] if t == nil { t = &Total{} s.st.Totals[m.ID] = t } t.Conns += d.Conns t.BytesIn += d.BytesIn t.BytesOut += d.BytesOut s.db.addTraffic(m.ID, d) } } for _, st := range s.st.Sites { if st.Local && st.EdgeID == e.ID { s.containers[st.ID] = in.Containers } } if e.PublicKey != in.PublicKey { e.PublicKey = in.PublicKey s.bump() // sites need the edge's new key } else if answered { s.bump() // the edge's config no longer carries the update request } else { s.save() } ok(w) } func (s *Server) edgeLogs(w http.ResponseWriter, r *http.Request) { var in []model.LogEntry if !decode(w, r, &in) { return } s.mu.Lock() e := s.edgeByToken(r) if e == nil { s.mu.Unlock() fail(w, 401, errors.New("bad edge token")) return } out := make([]model.LogEntry, 0, len(in)) for _, l := range in { if m := s.findMapping(l.Mapping); m != nil && m.EdgeID == e.ID { l.OrgID = m.OrgID } else if x := s.findMailEgress(l.Mapping); x != nil && x.EdgeID == e.ID { l.OrgID = x.OrgID } else { continue } out = append(out, l) } s.mu.Unlock() s.logs.addAccess(out) ok(w) } func (s *Server) siteConfig(w http.ResponseWriter, r *http.Request) { s.mu.Lock() st := s.siteByToken(r) s.mu.Unlock() if st == nil { fail(w, 401, errors.New("bad site token")) return } have, _ := strconv.ParseInt(r.URL.Query().Get("v"), 10, 64) if !s.wait(r, have) { s.mu.Lock() if st := s.siteByToken(r); st != nil { st.LastSeen = time.Now() } s.mu.Unlock() w.WriteHeader(http.StatusNoContent) return } s.mu.Lock() defer s.mu.Unlock() if st = s.siteByToken(r); st == nil { fail(w, 401, errors.New("bad site token")) return } st.LastSeen = time.Now() cfg := model.SiteConfig{Version: s.st.Version, SiteID: st.ID, TunnelIP: st.TunnelIP, Edges: []model.EdgeRef{}, MailEgress: []model.MailSpec{}, UpdateID: st.UpdateID} seen := map[string]bool{} addEdge := func(id string) { if seen[id] { return } seen[id] = true if e := s.findEdge(id); e != nil { cfg.Edges = append(cfg.Edges, model.EdgeRef{ID: e.ID, Name: e.Name, Endpoint: e.TunnelAddr, PublicKey: e.PublicKey, IP: e.TunnelIP}) } } for _, m := range s.st.Mappings { for _, t := range m.Targets { if t.SiteID == st.ID { addEdge(m.EdgeID) } } } for _, x := range s.st.MailEgress { if x.SiteID == st.ID && x.Enabled { addEdge(x.EdgeID) cfg.MailEgress = append(cfg.MailEgress, model.MailSpec{ID: x.ID, EdgeID: x.EdgeID, Listen: x.Listen, Username: x.Username, Password: x.Password}) } } writeJSON(w, 200, cfg) } func (s *Server) siteReport(w http.ResponseWriter, r *http.Request) { var in model.SiteReport if !decode(w, r, &in) { return } s.mu.Lock() defer s.mu.Unlock() st := s.siteByToken(r) if st == nil { fail(w, 401, errors.New("bad site token")) return } st.LastSeen = time.Now() if in.Connected == nil { in.Connected = []string{} } st.Connected = in.Connected oldVersion := st.Version answered := noteUpdateReport(&st.Version, &st.AutoUpdate, &st.UpdateID, &st.UpdateMsg, in.Version, in.AutoUpdate, in.UpdateID, in.UpdateMsg) s.recordUpdateProgress(st.ID, in.UpdateID, in.UpdateStage, in.UpdatePct, in.UpdateLog, oldVersion, st.Version) s.containers[st.ID] = in.Containers for _, x := range s.st.MailEgress { if x.SiteID != st.ID { continue } if msg, known := in.MailEgress[x.ID]; known { s.mailStatus[x.ID] = msg } else { delete(s.mailStatus, x.ID) } } if in.PublicKey != "" && st.PublicKey != in.PublicKey { st.PublicKey = in.PublicKey s.bump() // every edge this site talks to needs the new key } else if answered { s.bump() // the site's config no longer carries the update request } ok(w) }