package proxy import ( "bytes" "encoding/json" "encoding/pem" "errors" "net/http" "os" "strconv" "strings" "time" "git.maze.io/maze/styx/internal/log" ) type Admin struct { *Proxy } func NewAdmin(proxy *Proxy) *Admin { a := &Admin{ Proxy: proxy, } return a } func (a *Admin) handleRequest(ses *Session) error { var ( logger = ses.log() err error ) switch ses.request.URL.Path { case "/ca.crt": err = a.handleCACert(ses) case "/api/v1/policy": err = a.apiPolicy(ses) case "/api/v1/policy/matcher": err = a.apiPolicyMatcher(ses) case "/api/v1/stats/log": err = a.apiStatsLog(ses) case "/api/v1/stats/status": err = a.apiStatsStatus(ses) default: if strings.HasPrefix(ses.request.URL.Path, "/api") { err = errors.New("invalid endpoint") } else { err = os.ErrNotExist } } if err != nil { logger.Warn().Err(err).Msg("admin error") ses.response = ErrorResponse(ses.request, err) defer log.OnCloseError(logger.Debug(), ses.response.Body) ses.response.Close = true return a.writeResponse(ses) } return err } func (a *Admin) handleCACert(ses *Session) error { b := pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: a.authority.Certificate().Raw, }) ses.response = NewResponse(http.StatusOK, bytes.NewReader(b), ses.request) defer log.OnCloseError(log.Debug(), ses.response.Body) ses.response.Close = true ses.response.Header.Set("Content-Type", "application/x-x509-ca-cert") ses.response.ContentLength = int64(len(b)) return a.writeResponse(ses) } func (a *Admin) apiPolicy(ses *Session) error { var ( b = new(bytes.Buffer) e = json.NewEncoder(b) ) e.SetIndent("", " ") if err := e.Encode(a.config.Policy); err != nil { return err } ses.response = NewJSONResponse(http.StatusOK, b, ses.request) defer log.OnCloseError(log.Debug(), ses.response.Body) ses.response.Close = true return a.writeResponse(ses) } func (a *Admin) apiPolicyMatcher(ses *Session) error { var ( b = new(bytes.Buffer) e = json.NewEncoder(b) ) e.SetIndent("", " ") if err := e.Encode(a.config.Policy.Matchers); err != nil { return err } ses.response = NewJSONResponse(http.StatusOK, b, ses.request) defer log.OnCloseError(log.Debug(), ses.response.Body) ses.response.Close = true return a.writeResponse(ses) } func (a *Admin) apiResponse(ses *Session, v any, err error) error { if err != nil { return err } var ( b = new(bytes.Buffer) e = json.NewEncoder(b) ) e.SetIndent("", " ") if err := e.Encode(v); err != nil { return err } ses.response = NewJSONResponse(http.StatusOK, b, ses.request) defer log.OnCloseError(log.Debug(), ses.response.Body) ses.response.Close = true return a.writeResponse(ses) } func (a *Admin) apiStatsLog(ses *Session) error { var ( query = ses.request.URL.Query() offset, _ = strconv.Atoi(query.Get("offset")) limit, _ = strconv.Atoi(query.Get("limit")) ) if limit > 100 { limit = 100 } s, err := a.stats.QueryLog(offset, limit) return a.apiResponse(ses, s, err) } func (a *Admin) apiStatsStatus(ses *Session) error { s, err := a.stats.QueryStatus(time.Time{}) return a.apiResponse(ses, s, err) }