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

feat: route AI provider requests to aibridged#21343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
ssncferreira wants to merge1 commit intossncferreira/feat-aiproxy-proxy-authorization
base:ssncferreira/feat-aiproxy-proxy-authorization
Choose a base branch
Loading
fromssncferreira/feat-aiproxy-routing
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat: route AI provider requests to aibridged
  • Loading branch information
@ssncferreira
ssncferreira committedDec 19, 2025
commit9b2593b93201e71b32a8dd930455af879d0fb4ae
121 changes: 109 additions & 12 deletionsenterprise/aibridgeproxyd/aibridgeproxyd.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import (
"errors"
"net"
"net/http"
"net/url"
"strings"
"time"

Expand All@@ -23,17 +24,21 @@ import (
// - decrypting requests using the configured CA certificate
// - forwarding requests to aibridged for processing
type Server struct {
ctx context.Context
logger slog.Logger
proxy *goproxy.ProxyHttpServer
httpServer *http.Server
listener net.Listener
ctx context.Context
logger slog.Logger
proxy *goproxy.ProxyHttpServer
httpServer *http.Server
listener net.Listener
coderAccessURL *url.URL
}

// Options configures the AI Bridge Proxy server.
type Options struct {
// ListenAddr is the address the proxy server will listen on.
ListenAddr string
// CoderAccessURL is the URL of the Coder deployment where aibridged is running.
// Requests to supported AI providers are forwarded here.
CoderAccessURL string
// CertFile is the path to the CA certificate file used for MITM.
CertFile string
// KeyFile is the path to the CA private key file used for MITM.
Expand All@@ -51,6 +56,14 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
return nil, xerrors.New("cert file and key file are required")
}

if opts.CoderAccessURL == "" {
return nil, xerrors.New("coder access URL is required")
}
coderAccessURL, err := url.Parse(opts.CoderAccessURL)
if err != nil {
return nil, xerrors.Errorf("invalid coder access URL %q: %w", opts.CoderAccessURL, err)
}

// Load CA certificate for MITM
if err := loadMitmCertificate(opts.CertFile, opts.KeyFile); err != nil {
return nil, xerrors.Errorf("failed to load MITM certificate: %w", err)
Expand All@@ -59,18 +72,21 @@ func New(ctx context.Context, logger slog.Logger, opts Options) (*Server, error)
proxy := goproxy.NewProxyHttpServer()

srv := &Server{
ctx: ctx,
logger: logger,
proxy: proxy,
ctx: ctx,
logger: logger,
proxy: proxy,
coderAccessURL: coderAccessURL,
}

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

// Handle decrypted requests: route to aibridged for known AI providers, or passthrough to original destination.
// TODO(ssncferreira): Currently the proxy always behaves as MITM, but this should only happen for known
// AI providers as all other requests should be tunneled. This will be implemented upstack.
// Related to https://github.com/coder/internal/issues/1182
proxy.OnRequest().DoFunc(srv.handleRequest)

// Create listener first so we can get the actual address.
// This is useful in tests where port 0 is used to avoid conflicts.
listener, err := net.Listen("tcp", opts.ListenAddr)
Expand DownExpand Up@@ -218,3 +234,84 @@ func extractCoderTokenFromProxyAuth(proxyAuth string) string {

return credentials[1]
}

// canonicalHost strips the port from a host:port string and lowercases it.
func canonicalHost(host string) string {
if i := strings.IndexByte(host, ':'); i != -1 {
host = host[:i]
}
return strings.ToLower(host)
}

// providerFromHost maps the request host to the aibridge provider name.
// - Known AI providers return their provider name, used to route to the
// corresponding aibridge endpoint.
// - Unknown hosts return empty string and are passed through directly.
//
// TODO(ssncferreira): Provider list configurable via domain allowlists will be implemented upstack.
//
//Related to https://github.com/coder/internal/issues/1182.
func providerFromHost(host string) string {
switch canonicalHost(host) {
case "api.anthropic.com":
return "anthropic"
case "api.openai.com":
return "openai"
default:
return ""
}
}

// handleRequest intercepts HTTP requests after MITM decryption.
// - Requests to known AI providers are rewritten to aibridged, with the Coder session token
// (from ctx.UserData, set during CONNECT) injected in the Authorization header.
// - Unknown hosts are passed through to the original upstream.
func (s *Server) handleRequest(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
// Check if this request is for a supported AI provider.
provider := providerFromHost(req.Host)
if provider == "" {
s.logger.Debug(s.ctx, "passthrough request to unknown host",
slog.F("host", req.Host),
slog.F("method", req.Method),
slog.F("path", req.URL.Path),
)
return req, nil
}

// Get the Coder session token stored during CONNECT.
coderToken, _ := ctx.UserData.(string)

// Reject unauthenticated requests to AI providers.
if coderToken == "" {
s.logger.Warn(s.ctx, "rejecting unauthenticated request to AI provider",
slog.F("host", req.Host),
slog.F("provider", provider),
)
resp := goproxy.NewResponse(req, goproxy.ContentTypeText, http.StatusProxyAuthRequired, "Proxy authentication required")
// Describe to the client how to authenticate with the proxy.
resp.Header.Set("Proxy-Authenticate", `Basic realm="Coder AI Bridge Proxy"`)
return req, resp
}

// Rewrite the request to point to aibridged.
originalPath := req.URL.Path
aibridgePath := "/" + provider + originalPath

newURL := *s.coderAccessURL
newURL.Path = "/api/v2/aibridge" + aibridgePath
newURL.RawQuery = req.URL.RawQuery

req.URL = &newURL
req.Host = newURL.Host

// Set Authorization header for aibridged authentication.
req.Header.Set("Authorization", "Bearer "+coderToken)

s.logger.Debug(s.ctx, "routing request to aibridged",
slog.F("provider", provider),
slog.F("original_path", originalPath),
slog.F("aibridged_url", newURL.String()),
)

return req, nil
}
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp