mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
a3265bc346
* feat: Introduce computation runner, log forwarder, ingress, and egress proxy services. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Update Go environment variable parsing and build system to use new architecture and repository. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Update package sources to `sammyoina/cocos-ai` at a specific commit, add log-forwarder pre-start hook, and rename proxy binaries. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * chore: Update build system references to a specific commit and enhance logging for service connections and message processing. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * build: Update package source repositories and versions, migrate client logging to slog, and adjust ingress/egress proxy build and install steps. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * debug stuck Signed-off-by: Sammy Oina <sammyoina@gmail.com> * debug Signed-off-by: Sammy Oina <sammyoina@gmail.com> * debug Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: add HTTP/2 support to egress proxy and update build system to use specific commit hashes Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: enhance egress proxy CONNECT handling, update package sources, and add gRPC test utility Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Update build system for various services to a specific commit from a new repository, change agent gRPC port to 7001, and add a gRPC test client. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Migrate agent-internal gRPC communication to Unix sockets, set ingress proxy to port 7002, and update build hashes. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * refactor: Remove standalone ingress-proxy systemd service and update component versions. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * fix: Prevent computation re-initialization in agent and update component versions across several packages. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: update package versions and enable h2c support in ingress proxy. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: refactor ingress proxy to support HTTP/2 over Unix sockets and update component versions. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: Update build system package sources to `ultravioletrs/cocos` and reduce agent logging verbosity. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * refactor: improve error handling in proxy commands and remove unused gRPC test Signed-off-by: Sammy Oina <sammyoina@gmail.com> * test: add mock service state return value in handleRunReqChunks test Signed-off-by: Sammy Oina <sammyoina@gmail.com> * feat: add comprehensive tests for service and proxy components Signed-off-by: Sammy Oina <sammyoina@gmail.com> * fix linter Signed-off-by: Sammy Oina <sammyoina@gmail.com> * improve coverage Signed-off-by: Sammy Oina <sammyoina@gmail.com> * test: add gRPC client and ingress adapter tests, and update egress proxy tests. Signed-off-by: Sammy Oina <sammyoina@gmail.com> * improve coverage Signed-off-by: Sammy Oina <sammyoina@gmail.com> --------- Signed-off-by: Sammy Oina <sammyoina@gmail.com>
224 lines
5.8 KiB
Go
224 lines
5.8 KiB
Go
// Copyright (c) Ultraviolet
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
package ingress
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"sync"
|
|
|
|
"github.com/ultravioletrs/cocos/pkg/atls"
|
|
"github.com/ultravioletrs/cocos/pkg/server"
|
|
"golang.org/x/net/http2"
|
|
"golang.org/x/net/http2/h2c"
|
|
)
|
|
|
|
// ProxyConfig contains configuration for starting a proxy instance.
|
|
type ProxyConfig struct {
|
|
Port string
|
|
CertFile string
|
|
KeyFile string
|
|
ServerCAFile string
|
|
ClientCAFile string
|
|
AttestedTLS bool
|
|
}
|
|
|
|
// ProxyContext provides context information for logging and tracking.
|
|
type ProxyContext struct {
|
|
ID string
|
|
Name string
|
|
}
|
|
|
|
// ProxyServer manages ingress proxy instances.
|
|
type ProxyServer interface {
|
|
Start(cfg ProxyConfig, ctx ProxyContext) error
|
|
Stop() error
|
|
}
|
|
|
|
type proxyServer struct {
|
|
mu sync.RWMutex
|
|
logger *slog.Logger
|
|
backendURL *url.URL
|
|
certProvider atls.CertificateProvider
|
|
httpServer *http.Server
|
|
started bool
|
|
stopped bool
|
|
}
|
|
|
|
// NewProxyServer creates a new ingress proxy server manager.
|
|
func NewProxyServer(logger *slog.Logger, backendURL *url.URL, certProvider atls.CertificateProvider) ProxyServer {
|
|
return &proxyServer{
|
|
logger: logger,
|
|
backendURL: backendURL,
|
|
certProvider: certProvider,
|
|
}
|
|
}
|
|
|
|
// Start starts the proxy server with the given configuration.
|
|
func (p *proxyServer) Start(cfg ProxyConfig, ctx ProxyContext) error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.started {
|
|
return fmt.Errorf("proxy server already started")
|
|
}
|
|
if p.stopped {
|
|
return fmt.Errorf("proxy server already stopped")
|
|
}
|
|
|
|
if cfg.Port == "" {
|
|
cfg.Port = "7002"
|
|
}
|
|
|
|
addr := fmt.Sprintf("0.0.0.0:%s", cfg.Port)
|
|
|
|
// Configure Reverse Proxy
|
|
var rp *httputil.ReverseProxy
|
|
|
|
// Check if backend is Unix socket or TCP
|
|
if p.backendURL.Scheme == "unix" {
|
|
// For Unix socket backend, we need to manually configure the reverse proxy
|
|
// because NewSingleHostReverseProxy doesn't support unix:// scheme
|
|
targetURL := &url.URL{
|
|
Scheme: "http",
|
|
Host: "unix",
|
|
}
|
|
rp = httputil.NewSingleHostReverseProxy(targetURL)
|
|
|
|
// Override the Director to not modify the request
|
|
originalDirector := rp.Director
|
|
rp.Director = func(req *http.Request) {
|
|
originalDirector(req)
|
|
// Set the URL to point to the backend service
|
|
req.URL.Scheme = "http"
|
|
req.URL.Host = "unix"
|
|
}
|
|
|
|
// Configure Transport for Unix socket with HTTP/2
|
|
rp.Transport = &http2.Transport{
|
|
AllowHTTP: true,
|
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
|
var d net.Dialer
|
|
// Use Unix socket path from URL
|
|
return d.DialContext(ctx, "unix", p.backendURL.Path)
|
|
},
|
|
}
|
|
} else {
|
|
// TCP backend
|
|
rp = httputil.NewSingleHostReverseProxy(p.backendURL)
|
|
rp.Transport = &http2.Transport{
|
|
AllowHTTP: true,
|
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
|
var d net.Dialer
|
|
return d.DialContext(ctx, network, addr)
|
|
},
|
|
}
|
|
}
|
|
|
|
// Wrap handler with h2c for HTTP/2 cleartext support (required for gRPC without TLS)
|
|
h2cHandler := h2c.NewHandler(rp, &http2.Server{})
|
|
|
|
p.httpServer = &http.Server{
|
|
Addr: addr,
|
|
Handler: h2cHandler,
|
|
}
|
|
|
|
// Configure TLS
|
|
var tlsConfig *tls.Config
|
|
contextDesc := fmt.Sprintf("context %s", ctx.ID)
|
|
if ctx.Name != "" {
|
|
contextDesc = fmt.Sprintf("%s (%s)", ctx.Name, ctx.ID)
|
|
}
|
|
|
|
if cfg.AttestedTLS {
|
|
if p.certProvider == nil {
|
|
return fmt.Errorf("attested TLS requested but no certificate provider available")
|
|
}
|
|
tlsConfig = &tls.Config{
|
|
GetCertificate: p.certProvider.GetCertificate,
|
|
ClientAuth: tls.NoClientCert,
|
|
NextProtos: []string{"h2", "http/1.1"},
|
|
}
|
|
|
|
mtls, err := server.ConfigureCertificateAuthorities(tlsConfig, cfg.ServerCAFile, cfg.ClientCAFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to configure certificate authorities: %w", err)
|
|
}
|
|
|
|
if mtls {
|
|
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
p.logger.Info(fmt.Sprintf("ingress-proxy listening at %s with Attested mTLS for %s", addr, contextDesc))
|
|
} else {
|
|
p.logger.Info(fmt.Sprintf("ingress-proxy listening at %s with Attested TLS for %s", addr, contextDesc))
|
|
}
|
|
} else if cfg.CertFile != "" && cfg.KeyFile != "" {
|
|
// Regular TLS
|
|
tlsSetup, err := server.SetupRegularTLS(cfg.CertFile, cfg.KeyFile, cfg.ServerCAFile, cfg.ClientCAFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to setup TLS: %w", err)
|
|
}
|
|
tlsConfig = tlsSetup.Config
|
|
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
|
|
|
|
if tlsSetup.MTLS {
|
|
p.logger.Info(fmt.Sprintf("ingress-proxy listening at %s with mTLS for %s", addr, contextDesc))
|
|
} else {
|
|
p.logger.Info(fmt.Sprintf("ingress-proxy listening at %s with TLS for %s", addr, contextDesc))
|
|
}
|
|
} else {
|
|
p.logger.Info(fmt.Sprintf("ingress-proxy listening at %s without TLS for %s", addr, contextDesc))
|
|
}
|
|
|
|
p.started = true
|
|
|
|
// Start server in goroutine
|
|
go func() {
|
|
var err error
|
|
if tlsConfig != nil {
|
|
ln, listenErr := net.Listen("tcp", addr)
|
|
if listenErr != nil {
|
|
p.logger.Error(fmt.Sprintf("failed to listen: %s", listenErr))
|
|
return
|
|
}
|
|
tlsLn := tls.NewListener(ln, tlsConfig)
|
|
err = p.httpServer.Serve(tlsLn)
|
|
} else {
|
|
err = p.httpServer.ListenAndServe()
|
|
}
|
|
|
|
if err != nil && err != http.ErrServerClosed {
|
|
p.logger.Error(fmt.Sprintf("ingress-proxy server error: %s", err))
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop stops the proxy server.
|
|
func (p *proxyServer) Stop() error {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.stopped {
|
|
return nil
|
|
}
|
|
p.stopped = true
|
|
|
|
if p.httpServer != nil {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*1000000000) // 5 seconds
|
|
defer cancel()
|
|
if err := p.httpServer.Shutdown(ctx); err != nil {
|
|
return fmt.Errorf("failed to shutdown server: %w", err)
|
|
}
|
|
p.logger.Info("ingress-proxy stopped")
|
|
}
|
|
|
|
return nil
|
|
}
|