feat: Support HTTP web app#310
Conversation
b7034ad to
add758a
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
add758a to
b21c9c4
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #310 +/- ##
==========================================
+ Coverage 85.71% 86.35% +0.63%
==========================================
Files 37 40 +3
Lines 2716 2828 +112
==========================================
+ Hits 2328 2442 +114
+ Misses 264 262 -2
Partials 124 124
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds support for HTTP web app resources alongside the existing Kubernetes and SSH handlers. The HTTP proxy now dispatches requests to handlers by GAT resource.type, a new webapphandler package implements a reverse proxy that propagates identity to upstream via configurable header templates, and the second TLS upgrade is skipped for WEB_APP.
Changes:
- Introduce
internal/webapphandler(reverse proxy + header template config) and a genericinternal/utils/parserfor{{twingate.var}}placeholders. - Refactor
httpproxyto a resource-type-keyed handler map and wire web app + Kubernetes through it;proxy.NewProxybuilds the map. - Extend
token.GATClaimswithWEB_APPresource type andDevice.Location.GeoIP; extendProxyConnwith raw bearerToken,GetToken(), andShouldUpgradeTLS()(skips inner TLS for web app); addWebAppConfigto gateway YAML config.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/webapphandler/web_app.go | New reverse proxy that targets http://<address> and applies configured header templates. |
| internal/webapphandler/web_app_test.go | Tests rewrite header substitution and geo handling. |
| internal/webapphandler/web_app_config.go | Parses raw header templates and restricts them to an allowlist of variables. |
| internal/webapphandler/web_app_config_test.go | Tests config parsing, unsupported variable, and invalid syntax cases. |
| internal/utils/parser/parser.go | New {{twingate.var}} single-variable template parser/evaluator. |
| internal/utils/parser/parser_test.go | Covers parser and evaluator behavior including error paths. |
| internal/token/gat_claims.go | Adds GeoIP/DeviceLocation types and ResourceTypeWebApp. |
| internal/token/gat_claims_test.go | Extends fixture with device location data. |
| internal/proxy/proxy.go | Builds handler map; unconditionally registers round-tripper metrics; wires WebApp handler. |
| internal/proxy/proxy_test.go | Updates test for new Handlers map field. |
| internal/metrics/round_tripper.go | Adds resourceType label and a resourceType arg to InstrumentRoundTripper. |
| internal/metrics/round_tripper_test.go | Updates expected labels and exercises multiple resource types. |
| internal/metrics/http_middleware.go | Adds labelResourceType constant. |
| internal/kuberneteshandler/kubernetes.go | Passes "kubernetes" to instrumented round tripper. |
| internal/httpproxy/proxy.go | Switches Config.Handler → Handlers map; introduces newResourceRouter. |
| internal/httpproxy/proxy_test.go | Tests resource dispatch and unknown-resource 404. |
| internal/connect/listener.go | Uses ShouldUpgradeTLS() instead of inline tp != TransportSSH check. |
| internal/connect/listener_test.go | Extends mock with GetToken and ShouldUpgradeTLS. |
| internal/connect/connect.go | Returns bearer Token in Info. |
| internal/connect/connect_test.go | Asserts connectInfo.Token is populated/empty appropriately. |
| internal/connect/conn.go | Stores token on ProxyConn, exposes GetToken/ShouldUpgradeTLS, logs resource type. |
| internal/connect/conn_test.go | Adds ShouldUpgradeTLS matrix test. |
| internal/config/config.go | Adds WebAppConfig and includes WebApp in the "at least one protocol" validation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
6953252 to
4c2a306
Compare
| if config.Kubernetes != nil || config.WebApp != nil { | ||
| roundTripperMetrics = metrics.RegisterRoundTripperMetrics(registry) | ||
| } |
There was a problem hiding this comment.
When we have 3+ HTTP resource types, I think we should refactor the config to register the handler + metrics more nicely and avoid this check... It's out of the scope of this PR for now
There was a problem hiding this comment.
Would it be simpler if each resource type has its own HTTP server + proxy handler instead of all them sharing the same HTTP server? In that case, I think the HTTP proxy could still stay generic and doesn't have to know about the resource type.
There was a problem hiding this comment.
I updated the code to support each resource type to own its HTTP server. e7ceee9. This should simplify the setup quite a bit
There was a problem hiding this comment.
I didn't use Go's text/template package because it doesn't support the {{namespace.key}} syntax — it requires a leading dot (e.g., {{ .twingate.key}}).
There was a problem hiding this comment.
I think this parser should be in webapphandler than here. Also it doesn't need to be under /utils/parser path. I would put it in internal/webapphandler/template.go
There was a problem hiding this comment.
My thinking is that this parser could also be shared with other protocols (e.g. MCP) that support header rewrite or other applications that need to parse a template.
We can move this under webapphandler for now, but I think we might still need to make this a shared utility package eventually 🤔
| ) | ||
|
|
||
| const ( | ||
| allowedNamespace = "twingate" |
There was a problem hiding this comment.
We can extend this to support more namespaces like internal and external in the future, but I don't think we need to do it for now 🤔
| `\.` + | ||
| `([a-zA-Z0-9_-]+)` + // key | ||
| `\s*}}` + // closing braces | ||
| `([^{}]*)$`, // suffix (no brackets allowed) |
There was a problem hiding this comment.
FYI, This won't allow header templates with multiple variables like X-Header: {{twingate.username}} {{twingate.groups}}. Not sure there's a real use case for it 🤔
We could support it with some additional changes, but I'd rather not add complexity to the parser unless we need to. We can follow-up PR if a use case comes up.
| Name: "api_server_requests_total", | ||
| Help: "Total number of requests from Gateway to API Server processed", | ||
| }, []string{"type", "method", "code"}), | ||
| }, []string{labelResourceType, "type", "method", "code"}), |
There was a problem hiding this comment.
We also need to update the grafana-dashboard.json file to support this new label, i'll follow up with a separate PR instead
4c2a306 to
a5b9ebd
Compare
…bernetes-access-gateway into feat/ct/support-http-web-app
| func (p *ProxyConn) ShouldUpgradeTLS() bool { | ||
| return p.TransportProtocol() == TransportTLS && p.GATClaims().Resource.Type != token.ResourceTypeWebApp | ||
| } |
There was a problem hiding this comment.
This is okayish for now but I think eventually this logic should be on the GAT's resource. It knows whether the inner connection should be not encrypted or TLS/SSH.
There was a problem hiding this comment.
After updating the code to HTTP proxy per resource type, I think it's simpler to have this check on the GAT claims. Also, we could remove the TransportProtocol type to simplify the check.
| if err := rewrite(r, conn, cfg.headers); err != nil { | ||
| cfg.logger.Error("failed to rewrite headers", zap.Error(err)) | ||
| } |
There was a problem hiding this comment.
I think when header rewriting fails, it's safer to fail the request.
| if config.Kubernetes != nil || config.WebApp != nil { | ||
| roundTripperMetrics = metrics.RegisterRoundTripperMetrics(registry) | ||
| } |
There was a problem hiding this comment.
Would it be simpler if each resource type has its own HTTP server + proxy handler instead of all them sharing the same HTTP server? In that case, I think the HTTP proxy could still stay generic and doesn't have to know about the resource type.
a1a51d4 to
e7ceee9
Compare
bf612ab to
558f37e
Compare
Related Tickets & Documents
Changes
webapphandlerpackage to handle HTTP web applicationprefix {{twingate.<key>}} suffixtemplateconnectpackage's channel-based routing byResourceTypeinstead ofTransportProtocoltyperesourceTypelabel to roundtripper and http middleware metricsNotes
🤖 Generated with Claude Code