| 1 | package edge |
| 2 | |
| 3 | // The forwarded-for proof (internal/edgeproof): the edge signs the |
| 4 | // X-Forwarded-For chain it sends upstream, so an app on this box can tell a |
| 5 | // client address the edge saw from one a local process typed. The console's |
| 6 | // per-IP login limits are the reason (discussion 214, item 5): every request |
| 7 | // the edge proxies to it arrives from loopback, and so does every request a |
| 8 | // tenant process on the same box makes to 127.0.0.1:8090 directly. |
| 9 | |
| 10 | import ( |
| 11 | "context" |
| 12 | "net/http" |
| 13 | "net/http/httputil" |
| 14 | "strings" |
| 15 | "time" |
| 16 | |
| 17 | "github.com/carlosframework/platform/internal/edgeproof" |
| 18 | ) |
| 19 | |
| 20 | type forwardKeyCtx struct{} |
| 21 | |
| 22 | // withForwardKey puts the current signing key on the request, read once per |
| 23 | // request from the home map's cached pair, so the proxies' Rewrite funcs — |
| 24 | // which have no Edge to ask — can sign what they send. No key (no store, |
| 25 | // nothing minted yet) leaves the request as it is, and nothing is signed. |
| 26 | func (e *Edge) withForwardKey(r *http.Request) *http.Request { |
| 27 | key := signingKey(e.homes.Load().meshKeys()) |
| 28 | if len(key) == 0 { |
| 29 | return r |
| 30 | } |
| 31 | return r.WithContext(context.WithValue(r.Context(), forwardKeyCtx{}, key)) |
| 32 | } |
| 33 | |
| 34 | // signForwarded replaces whatever proof header the inbound request carried |
| 35 | // with the edge's own signature over the outbound chain. The delete is |
| 36 | // unconditional: a client-sent proof is at best a replay, and with no key to |
| 37 | // sign, the honest outbound request carries none. |
| 38 | func signForwarded(pr *httputil.ProxyRequest) { |
| 39 | pr.Out.Header.Del(edgeproof.Header) |
| 40 | key, _ := pr.In.Context().Value(forwardKeyCtx{}).([]byte) |
| 41 | if len(key) == 0 { |
| 42 | return |
| 43 | } |
| 44 | xff := strings.Join(pr.Out.Header.Values("X-Forwarded-For"), ", ") |
| 45 | if xff == "" { |
| 46 | return |
| 47 | } |
| 48 | // pr.In.Host, because every Rewrite here sends it on as pr.Out.Host |
| 49 | // (and runs setForwarded before it does): it is the Host the upstream |
| 50 | // will see and verify against. |
| 51 | pr.Out.Header.Set(edgeproof.Header, edgeproof.Sign(key, pr.In.Host, xff, time.Now())) |
| 52 | } |
| 53 | |