Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit4d87f7d

Browse files
committed
feat: route AI provider requests to aibridged
1 parent23485a1 commit4d87f7d

File tree

2 files changed

+306
-30
lines changed

2 files changed

+306
-30
lines changed

‎enterprise/aibridgeproxyd/aibridgeproxyd.go‎

Lines changed: 109 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"errors"
99
"net"
1010
"net/http"
11+
"net/url"
1112
"strings"
1213
"time"
1314

@@ -23,17 +24,21 @@ import (
2324
// - decrypting requests using the configured CA certificate
2425
// - forwarding requests to aibridged for processing
2526
typeServerstruct {
26-
ctx context.Context
27-
logger slog.Logger
28-
proxy*goproxy.ProxyHttpServer
29-
httpServer*http.Server
30-
listener net.Listener
27+
ctx context.Context
28+
logger slog.Logger
29+
proxy*goproxy.ProxyHttpServer
30+
httpServer*http.Server
31+
listener net.Listener
32+
coderAccessURL*url.URL
3133
}
3234

3335
// Options configures the AI Bridge Proxy server.
3436
typeOptionsstruct {
3537
// ListenAddr is the address the proxy server will listen on.
3638
ListenAddrstring
39+
// CoderAccessURL is the URL of the Coder deployment where aibridged is running.
40+
// Requests to supported AI providers are forwarded here.
41+
CoderAccessURLstring
3742
// CertFile is the path to the CA certificate file used for MITM.
3843
CertFilestring
3944
// KeyFile is the path to the CA private key file used for MITM.
@@ -51,6 +56,14 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
5156
returnnil,xerrors.New("cert file and key file are required")
5257
}
5358

59+
ifopts.CoderAccessURL=="" {
60+
returnnil,xerrors.New("coder access URL is required")
61+
}
62+
coderAccessURL,err:=url.Parse(opts.CoderAccessURL)
63+
iferr!=nil {
64+
returnnil,xerrors.Errorf("invalid coder access URL %q: %w",opts.CoderAccessURL,err)
65+
}
66+
5467
// Load CA certificate for MITM
5568
iferr:=loadMitmCertificate(opts.CertFile,opts.KeyFile);err!=nil {
5669
returnnil,xerrors.Errorf("failed to load MITM certificate: %w",err)
@@ -59,18 +72,21 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
5972
proxy:=goproxy.NewProxyHttpServer()
6073

6174
srv:=&Server{
62-
ctx:ctx,
63-
logger:logger,
64-
proxy:proxy,
75+
ctx:ctx,
76+
logger:logger,
77+
proxy:proxy,
78+
coderAccessURL:coderAccessURL,
6579
}
6680

6781
// Extract Coder session token from proxy authentication to forward to aibridged.
68-
// Decrypt all HTTPS requests via MITM. Requests are forwarded to
69-
// the original destination without modification for now.
70-
// TODO(ssncferreira): Route requests to aibridged will be implemented upstack.
71-
// Related to https://github.com/coder/internal/issues/1181
7282
proxy.OnRequest().HandleConnect(goproxy.FuncHttpsHandler(srv.handleConnect))
7383

84+
// Handle decrypted requests: route to aibridged for known AI providers, or passthrough to original destination.
85+
// TODO(ssncferreira): Currently the proxy always behaves as MITM, but this should only happen for known
86+
// AI providers as all other requests should be tunneled. This will be implemented upstack.
87+
// Related to https://github.com/coder/internal/issues/1182
88+
proxy.OnRequest().DoFunc(srv.handleRequest)
89+
7490
// Create listener first so we can get the actual address.
7591
// This is useful in tests where port 0 is used to avoid conflicts.
7692
listener,err:=net.Listen("tcp",opts.ListenAddr)
@@ -218,3 +234,84 @@ func extractCoderTokenFromProxyAuth(proxyAuth string) string {
218234

219235
returncredentials[1]
220236
}
237+
238+
// canonicalHost strips the port from a host:port string and lowercases it.
239+
funccanonicalHost(hoststring)string {
240+
ifi:=strings.IndexByte(host,':');i!=-1 {
241+
host=host[:i]
242+
}
243+
returnstrings.ToLower(host)
244+
}
245+
246+
// providerFromHost maps the request host to the aibridge provider name.
247+
// - Known AI providers return their provider name, used to route to the
248+
// corresponding aibridge endpoint.
249+
// - Unknown hosts return empty string and are passed through directly.
250+
//
251+
// TODO(ssncferreira): Provider list configurable via domain allowlists will be implemented upstack.
252+
//
253+
//Related to https://github.com/coder/internal/issues/1182.
254+
funcproviderFromHost(hoststring)string {
255+
switchcanonicalHost(host) {
256+
case"api.anthropic.com":
257+
return"anthropic"
258+
case"api.openai.com":
259+
return"openai"
260+
default:
261+
return""
262+
}
263+
}
264+
265+
// handleRequest intercepts HTTP requests after MITM decryption.
266+
// - Requests to known AI providers are rewritten to aibridged, with the Coder session token
267+
// (from ctx.UserData, set during CONNECT) injected in the Authorization header.
268+
// - Unknown hosts are passed through to the original upstream.
269+
func (s*Server)handleRequest(req*http.Request,ctx*goproxy.ProxyCtx) (*http.Request,*http.Response) {
270+
// Check if this request is for a supported AI provider.
271+
provider:=providerFromHost(req.Host)
272+
ifprovider=="" {
273+
s.logger.Debug(s.ctx,"passthrough request to unknown host",
274+
slog.F("host",req.Host),
275+
slog.F("method",req.Method),
276+
slog.F("path",req.URL.Path),
277+
)
278+
returnreq,nil
279+
}
280+
281+
// Get the Coder session token stored during CONNECT.
282+
coderToken,_:=ctx.UserData.(string)
283+
284+
// Reject unauthenticated requests to AI providers.
285+
ifcoderToken=="" {
286+
s.logger.Warn(s.ctx,"rejecting unauthenticated request to AI provider",
287+
slog.F("host",req.Host),
288+
slog.F("provider",provider),
289+
)
290+
resp:=goproxy.NewResponse(req,goproxy.ContentTypeText,http.StatusProxyAuthRequired,"Proxy authentication required")
291+
// Describe to the client how to authenticate with the proxy.
292+
resp.Header.Set("Proxy-Authenticate",`Basic realm="Coder AI Bridge Proxy"`)
293+
returnreq,resp
294+
}
295+
296+
// Rewrite the request to point to aibridged.
297+
originalPath:=req.URL.Path
298+
aibridgePath:="/"+provider+originalPath
299+
300+
newURL:=*s.coderAccessURL
301+
newURL.Path="/api/v2/aibridge"+aibridgePath
302+
newURL.RawQuery=req.URL.RawQuery
303+
304+
req.URL=&newURL
305+
req.Host=newURL.Host
306+
307+
// Set Authorization header for aibridged authentication.
308+
req.Header.Set("Authorization","Bearer "+coderToken)
309+
310+
s.logger.Debug(s.ctx,"routing request to aibridged",
311+
slog.F("provider",provider),
312+
slog.F("original_path",originalPath),
313+
slog.F("aibridged_url",newURL.String()),
314+
)
315+
316+
returnreq,nil
317+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp