Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/github/gh-aw-mcpg/internal/difc"
"github.com/github/gh-aw-mcpg/internal/envutil"
"github.com/github/gh-aw-mcpg/internal/guard"
"github.com/github/gh-aw-mcpg/internal/httputil"
"github.com/github/gh-aw-mcpg/internal/logger"
"github.com/github/gh-aw-mcpg/internal/server"
"github.com/github/gh-aw-mcpg/internal/tracing"
Expand Down Expand Up @@ -450,7 +451,7 @@ func run(cmd *cobra.Command, args []string) error {
tlsEnabled := hasCert && hasKey
var tlsCfg *tls.Config
if tlsEnabled {
tlsCfg, err = server.LoadGatewayTLS(tlsCertPath, tlsKeyPath, tlsCAPath)
tlsCfg, err = httputil.LoadGatewayTLS(tlsCertPath, tlsKeyPath, tlsCAPath)
if err != nil {
_ = listener.Close()
return fmt.Errorf("failed to configure TLS: %w", err)
Expand Down
56 changes: 53 additions & 3 deletions internal/httputil/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//
// TLS helpers are split across two packages:
//
// - internal/httputil (this package): protocol-level helpers that apply to
// all TLS listeners and clients (MinTLSVersion, NewServerTLSConfig,
// NewClientTLSConfig, ConfigureTLSTrustEnvironment).
// - internal/httputil (this file): protocol-level helpers and file-loading
// helpers that apply to all TLS listeners and clients:
// MinTLSVersion, NewServerTLSConfig, NewClientTLSConfig,
// ConfigureTLSTrustEnvironment, LoadGatewayTLS.
//
// - internal/proxy: certificate *generation* (GenerateSelfSignedTLS) lives
// there because it is only needed when the proxy runs in self-signed mode.
Expand All @@ -13,6 +14,7 @@ package httputil

import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"strings"
Expand Down Expand Up @@ -80,3 +82,51 @@ func ConfigureTLSTrustEnvironment(caCertPath string) error {
logTLS.Printf("TLS trust environment configured successfully: %d env vars set", len(tlsTrustEnvKeys))
return nil
}

// LoadGatewayTLS loads a TLS configuration for the gateway HTTP server from PEM
// certificate and key files. When caPath is non-empty the returned config
// requires client certificates signed by that CA (mutual TLS / mTLS).
//
// Pass an empty caPath to use one-way TLS (server-only authentication).
//
// Example — one-way TLS (server cert only):
//
// tlsCfg, err := LoadGatewayTLS("/path/server.crt", "/path/server.key", "")
//
// Example — mutual TLS (client certs required):
//
// tlsCfg, err := LoadGatewayTLS("/path/server.crt", "/path/server.key", "/path/ca.crt")
func LoadGatewayTLS(certPath, keyPath, caPath string) (*tls.Config, error) {
logTLS.Printf("loading gateway TLS: cert=%s, key=%s, ca=%s", certPath, keyPath, caPath)

serverCert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, fmt.Errorf("failed to load server TLS certificate/key: %w", err)
}
logTLS.Printf("server TLS key pair loaded: certChainLen=%d", len(serverCert.Certificate))

cfg := NewServerTLSConfig(serverCert)

if caPath != "" {
caPEM, err := os.ReadFile(caPath)
if err != nil {
return nil, fmt.Errorf("failed to read CA certificate: %w", err)
}

caPool := x509.NewCertPool()
if !caPool.AppendCertsFromPEM(caPEM) {
return nil, fmt.Errorf("failed to parse CA certificate from %s", caPath)
}
logTLS.Printf("CA certificate pool built: ca=%s", caPath)

// Require and verify client certificates signed by the provided CA.
cfg.ClientCAs = caPool
cfg.ClientAuth = tls.RequireAndVerifyClientCert
logTLS.Printf("mTLS enabled: client certificates required, CA=%s", caPath)
} else {
logTLS.Print("one-way TLS configured: client certificates not required")
}

logTLS.Printf("gateway TLS configuration ready: minVersion=%s, mtls=%v", tls.VersionName(cfg.MinVersion), caPath != "")
return cfg, nil
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package server
package httputil_test

import (
"crypto/ecdsa"
Expand All @@ -20,6 +20,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/github/gh-aw-mcpg/internal/httputil"
"github.com/github/gh-aw-mcpg/internal/proxy"
)

Expand Down Expand Up @@ -214,7 +215,7 @@ func TestLoadGatewayTLS_ServerOnly(t *testing.T) {
tlsCfg, err := proxy.GenerateSelfSignedTLS(dir)
require.NoError(t, err)

cfg, err := LoadGatewayTLS(tlsCfg.CertPath, tlsCfg.KeyPath, "")
cfg, err := httputil.LoadGatewayTLS(tlsCfg.CertPath, tlsCfg.KeyPath, "")
require.NoError(t, err)
require.NotNil(t, cfg)

Expand All @@ -229,7 +230,7 @@ func TestLoadGatewayTLS_MutualTLS(t *testing.T) {
tlsCfg, err := proxy.GenerateSelfSignedTLS(dir)
require.NoError(t, err)

cfg, err := LoadGatewayTLS(tlsCfg.CertPath, tlsCfg.KeyPath, tlsCfg.CACertPath)
cfg, err := httputil.LoadGatewayTLS(tlsCfg.CertPath, tlsCfg.KeyPath, tlsCfg.CACertPath)
require.NoError(t, err)
require.NotNil(t, cfg)

Expand All @@ -242,7 +243,7 @@ func TestLoadGatewayTLS_ServerServesMTLS(t *testing.T) {
certs, err := generateMTLSCerts(t, dir)
require.NoError(t, err)

cfg, err := LoadGatewayTLS(certs.serverCertPath, certs.serverKeyPath, certs.caCertPath)
cfg, err := httputil.LoadGatewayTLS(certs.serverCertPath, certs.serverKeyPath, certs.caCertPath)
require.NoError(t, err)

srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
Expand All @@ -267,7 +268,7 @@ func TestLoadGatewayTLS_ServerServesMTLS(t *testing.T) {
}

func TestLoadGatewayTLS_InvalidCertPath(t *testing.T) {
_, err := LoadGatewayTLS("/nonexistent/cert.pem", "/nonexistent/key.pem", "")
_, err := httputil.LoadGatewayTLS("/nonexistent/cert.pem", "/nonexistent/key.pem", "")
require.Error(t, err)
assert.ErrorContains(t, err, "failed to load server TLS certificate/key")
}
Expand All @@ -277,7 +278,7 @@ func TestLoadGatewayTLS_InvalidCAPath(t *testing.T) {
tlsCfg, err := proxy.GenerateSelfSignedTLS(dir)
require.NoError(t, err)

_, err = LoadGatewayTLS(tlsCfg.CertPath, tlsCfg.KeyPath, "/nonexistent/ca.pem")
_, err = httputil.LoadGatewayTLS(tlsCfg.CertPath, tlsCfg.KeyPath, "/nonexistent/ca.pem")
require.Error(t, err)
assert.ErrorContains(t, err, "failed to read CA certificate")
}
Expand All @@ -291,7 +292,7 @@ func TestLoadGatewayTLS_MalformedCA(t *testing.T) {
badCA := dir + "/bad-ca.pem"
require.NoError(t, os.WriteFile(badCA, []byte("NOT A VALID PEM"), 0644))

_, err = LoadGatewayTLS(tlsCfg.CertPath, tlsCfg.KeyPath, badCA)
_, err = httputil.LoadGatewayTLS(tlsCfg.CertPath, tlsCfg.KeyPath, badCA)
require.Error(t, err)
assert.ErrorContains(t, err, "failed to parse CA certificate")
}
1 change: 0 additions & 1 deletion internal/logger/logger_namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func TestLoggerNamespacesMatchFileConventions(t *testing.T) {
"internal/guard/write_sink.go": {"guard:write-sink"},
"internal/launcher/connection_pool.go": {"launcher:pool"},
"internal/launcher/health_monitor.go": {"launcher:health"},
"internal/server/gateway_tls.go": {"server:tls"},
"internal/server/http_helpers.go": {"server:helpers"},
"internal/server/http_server.go": {"server:http_server", "server:transport"},
"internal/server/middleware_auth.go": {"server:auth"},
Expand Down
6 changes: 3 additions & 3 deletions internal/proxy/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
// - internal/proxy (this file): certificate *generation* (GenerateSelfSignedTLS).
// This is the only place self-signed certs are created.
//
// - internal/httputil: protocol-level helpers that apply to all TLS listeners
// and clients (MinTLSVersion, NewServerTLSConfig, NewClientTLSConfig,
// ConfigureTLSTrustEnvironment).
// - internal/httputil: protocol-level helpers and file-loading helpers that
// apply to all TLS listeners and clients (MinTLSVersion, NewServerTLSConfig,
// NewClientTLSConfig, ConfigureTLSTrustEnvironment, LoadGatewayTLS).
package proxy

import (
Expand Down
61 changes: 0 additions & 61 deletions internal/server/gateway_tls.go

This file was deleted.

Loading