package main import ( "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io" "log" "net" "net/http" "os/exec" "github.com/julienschmidt/httprouter" "git.nemunai.re/srs/adlin/libadlin" ) func init() { router.GET("/api/collector_info", apiHandler(func(ps httprouter.Params, body []byte) (interface{}, error) { return "\"" + base64.StdEncoding.EncodeToString(adlin.GetCollectorPublic()) + "\"", nil })) router.GET("/api/wg.conf", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { w.Header().Set("Content-Type", "text/plain") err := GenWGConfig(w) if err != nil { w.Write([]byte(err.Error())) } }) router.GET("/api/wg/", apiAuthHandler(showWgTunnel)) router.GET("/api/wginfo", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getTunnelInfo(student.Id, 0), nil })) router.POST("/api/wg/", apiAuthHandler(genWgToken)) router.GET("/api/wg/:token", getWgTunnelInfo) router.POST("/api/wg/:token", getWgTunnelInfo) router.PUT("/api/wg/:token", apiAuthHandler(updateWgTunnel)) router.DELETE("/api/wg/:token", apiAuthHandler(deleteWgTunnel)) } func showWgTunnel(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { // Get tunnels assigned to the student return student.GetTunnelTokens() } func genWgToken(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { // Generate a token to access related wg info return student.NewTunnelToken(0) } type TunnelInfo struct { Status string `json:"status"` SrvPubKey []byte `json:"srv_pubkey"` SrvPort uint16 `json:"srv_port"` CltIPv6 net.IP `json:"clt_ipv6"` CltRange uint8 `json:"clt_range"` SrvGW6 string `json:"srv_gw6"` } func getTunnelInfo(student int64, idoverride int) TunnelInfo { srv_pubkey, _ := base64.StdEncoding.DecodeString("uSpqyYovvP4OG6wDxZ0Qkq45MfyK58PMUuPaLesY8FI=") return TunnelInfo{ Status: "OK", SrvPubKey: srv_pubkey, SrvPort: 42912, CltIPv6: adlin.StudentIP(student, idoverride), CltRange: adlin.StdNetmask, SrvGW6: "2a01:e0a:518:833::1", } } type PubTunnel struct { PubKey []byte } func getWgTunnelInfo(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { if addr := r.Header.Get("X-Forwarded-For"); addr != "" { r.RemoteAddr = addr } log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent()) // Read the body if r.ContentLength < 0 || r.ContentLength > 6553600 { http.Error(w, "{errmsg:\"Request too large or request size unknown\"}", http.StatusRequestEntityTooLarge) return } // Access wg infos tokenhex := []byte(ps.ByName("token")) tokendec := make([]byte, hex.DecodedLen(len(tokenhex))) n, err := hex.Decode(tokendec, tokenhex) if err != nil { http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest) return } token, err := adlin.GetTunnelToken(tokendec[:n]) if err != nil { http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest) return } var pt PubTunnel var version int if r.Method == "POST" { if err := json.NewDecoder(r.Body).Decode(&pt); err != nil { http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest) return } version = 2 } else if pubkey := r.Header.Get("X-WG-PubKey"); pubkey != "" { pt.PubKey, _ = base64.StdEncoding.DecodeString(pubkey) version = 3 } else { http.Error(w, fmt.Sprintf("{errmsg:\"No public key given\"}"), http.StatusBadRequest) return } token.PubKey = pt.PubKey token.Version = version _, err = token.Update() if err != nil { http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest) return } // 0 is considered default for suffix, apply default now if token.SuffixIP <= 0 { token.SuffixIP = 1 } syncWgConf() tinfo := getTunnelInfo(token.IdStudent, token.OverrideID) var student *adlin.Student student, err = adlin.GetStudent(int(token.IdStudent)) if err != nil { http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest) return } w.Header().Set("Content-Type", "text/plain") w.Write([]byte(fmt.Sprintf(`[Peer] PublicKey = %s Endpoint = %s:%d AllowedIPs = ::/0 PersistentKeepalive = 5 # MyIPv6=%s%x/%d # MyNetwork=%s/%d # GWIPv6=%s # MyLogin=%s `, base64.StdEncoding.EncodeToString(tinfo.SrvPubKey), "82.64.151.41", tinfo.SrvPort, tinfo.CltIPv6, token.SuffixIP, 64, tinfo.CltIPv6, tinfo.CltRange, tinfo.SrvGW6, student.Login))) if version > 2 { w.Write([]byte(fmt.Sprintf(`# KeySign=%s`, base64.StdEncoding.EncodeToString(token.GenKeySign())))) } } func updateWgTunnel(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { token, err := adlin.GetTunnelToken(adlin.TokenFromText(ps.ByName("token"))) if err != nil { return nil, err } if token.IdStudent != student.Id { return nil, fmt.Errorf("Unauthorized") } var newToken adlin.TunnelToken if err := json.Unmarshal(body, &newToken); err != nil { return nil, err } token.TokenText = newToken.TokenText token.PubKey = newToken.PubKey token.SuffixIP = newToken.SuffixIP if _, err = token.Update(); err != nil { return nil, err } syncWgConf() return true, err } func deleteWgTunnel(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { token, err := adlin.GetTunnelToken(adlin.TokenFromText(ps.ByName("token"))) if err != nil { return nil, err } if token.IdStudent != student.Id { return nil, fmt.Errorf("Unauthorized") } if _, err = token.Delete(); err != nil { return nil, err } syncWgConf() return true, err } func GenWGConfig(w io.Writer) error { ts, err := adlin.GetStudentsTunnels() if err != nil { return err } for _, t := range ts { if t.PubKey == nil { continue } w.Write([]byte(fmt.Sprintf(`[Peer] #IdStudent = %d PublicKey = %s AllowedIPs = %s/%d `, t.IdStudent, base64.StdEncoding.EncodeToString(t.PubKey), adlin.StudentIP(t.IdStudent, t.OverrideID), 80))) } return nil } func syncWgConf() (err error) { _, err = exec.Command("sh", "/root/wg-sync.sh").Output() return }