handleClusters lists every configured cluster. The endpoint is intentionally unauthenticated: a client that wants to knock must already know the target cluster's public key (it's required to encrypt the KNK packet), so exposing the pubkey + the routing id derived from it leaks nothing a determined c
(w http.ResponseWriter, r *http.Request)
| 767 | // topology to be opaque to anonymous browsers, gate this handler behind the |
| 768 | // reverse proxy (e.g. nginx allow/deny) rather than adding auth here. |
| 769 | func (rs *RelayServer) handleClusters(w http.ResponseWriter, r *http.Request) { |
| 770 | if r.Method != http.MethodGet { |
| 771 | http.Error(w, "method not allowed", http.StatusMethodNotAllowed) |
| 772 | return |
| 773 | } |
| 774 | |
| 775 | out := make([]clusterInfo, 0, len(rs.clusters)) |
| 776 | for _, cr := range rs.clusters { |
| 777 | out = append(out, clusterInfo{ |
| 778 | ID: cr.id, |
| 779 | Name: cr.name, |
| 780 | PublicKey: cr.pubKeyBase64, |
| 781 | InstanceCnt: len(cr.instances), |
| 782 | LoadBalance: string(cr.scheme), |
| 783 | }) |
| 784 | } |
| 785 | // Stable output so callers that diff or hash the response (change |
| 786 | // detection, ETag generators, log diffs) don't see spurious churn |
| 787 | // from Go map iteration order. |
| 788 | sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID }) |
| 789 | |
| 790 | w.Header().Set("Content-Type", "application/json") |
| 791 | _ = json.NewEncoder(w).Encode(out) |
| 792 | } |
| 793 | |
| 794 | // corsMiddleware adds CORS headers so browser-based NHP agents can reach the |
| 795 | // relay from any origin. The relay is a public transport bridge — there is no |