package edge // The forwarded-for proof (internal/edgeproof): the edge signs the // X-Forwarded-For chain it sends upstream, so an app on this box can tell a // client address the edge saw from one a local process typed. The console's // per-IP login limits are the reason (discussion 214, item 5): every request // the edge proxies to it arrives from loopback, and so does every request a // tenant process on the same box makes to 127.0.0.1:8090 directly. import ( "context" "net/http" "net/http/httputil" "strings" "time" "github.com/carlosframework/platform/internal/edgeproof" ) type forwardKeyCtx struct{} // withForwardKey puts the current signing key on the request, read once per // request from the home map's cached pair, so the proxies' Rewrite funcs — // which have no Edge to ask — can sign what they send. No key (no store, // nothing minted yet) leaves the request as it is, and nothing is signed. func (e *Edge) withForwardKey(r *http.Request) *http.Request { key := signingKey(e.homes.Load().meshKeys()) if len(key) == 0 { return r } return r.WithContext(context.WithValue(r.Context(), forwardKeyCtx{}, key)) } // signForwarded replaces whatever proof header the inbound request carried // with the edge's own signature over the outbound chain. The delete is // unconditional: a client-sent proof is at best a replay, and with no key to // sign, the honest outbound request carries none. func signForwarded(pr *httputil.ProxyRequest) { pr.Out.Header.Del(edgeproof.Header) key, _ := pr.In.Context().Value(forwardKeyCtx{}).([]byte) if len(key) == 0 { return } xff := strings.Join(pr.Out.Header.Values("X-Forwarded-For"), ", ") if xff == "" { return } // pr.In.Host, because every Rewrite here sends it on as pr.Out.Host // (and runs setForwarded before it does): it is the Host the upstream // will see and verify against. pr.Out.Header.Set(edgeproof.Header, edgeproof.Sign(key, pr.In.Host, xff, time.Now())) }