Skip to content

Commit 20cac51

Browse files
feat: make host mode directory paths configurable (#7225)
This adds configuration fields to EnvoyGatewayHostInfrastructureProvider to allow users to specify custom paths for configuration, data, state, and runtime directories, following XDG Base Directory Specification conventions while maintaining backward compatibility. The implementation introduces four configurable directory paths: - configHome: certificates and configuration files (default: ~/.config/envoy-gateway) - dataHome: Envoy binaries, sharable across configs (default: ~/.local/share/envoy-gateway) - stateHome: logs and persistent state (default: ~/.local/state/envoy-gateway) - runtimeDir: ephemeral runtime files (default: /tmp/envoy-gateway-${UID}) Certificates are stored under configHome to ensure isolation between different configurations when running multiple EnvoyGateway instances in parallel, preventing certificate conflicts. These paths are propagated to func-e which creates subdirectories as needed: - dataHome/envoy-versions/ for Envoy binaries - stateHome/envoy-runs/{runID}/ for per-run logs - runtimeDir/{runID}/ for per-run runtime files The changes include path resolution helpers, updated runners to use configurable paths, comprehensive test coverage, and updated documentation. Signed-off-by: Adrian Cole <[email protected]>
1 parent a2ce000 commit 20cac51

File tree

17 files changed

+651
-111
lines changed

17 files changed

+651
-111
lines changed

api/v1alpha1/envoygateway_types.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,25 @@ type EnvoyGatewayInfrastructureProvider struct {
430430

431431
// EnvoyGatewayHostInfrastructureProvider defines configuration for the Host Infrastructure provider.
432432
type EnvoyGatewayHostInfrastructureProvider struct {
433-
// TODO: Add config as use cases are better understood.
433+
// ConfigHome is the directory for configuration files.
434+
// Defaults to ~/.config/envoy-gateway
435+
// +optional
436+
ConfigHome *string `json:"configHome,omitempty"`
437+
438+
// DataHome is the directory for persistent data (Envoy binaries).
439+
// Defaults to ~/.local/share/envoy-gateway
440+
// +optional
441+
DataHome *string `json:"dataHome,omitempty"`
442+
443+
// StateHome is the directory for persistent state (logs).
444+
// Defaults to ~/.local/state/envoy-gateway
445+
// +optional
446+
StateHome *string `json:"stateHome,omitempty"`
447+
448+
// RuntimeDir is the directory for ephemeral runtime files.
449+
// Defaults to /tmp/envoy-gateway-${UID}
450+
// +optional
451+
RuntimeDir *string `json:"runtimeDir,omitempty"`
434452
}
435453

436454
// RateLimit defines the configuration associated with the Rate Limit Service

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/standalone/envoy-gateway.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ provider:
1111
paths: ["/tmp/envoy-gateway-test"]
1212
infrastructure:
1313
type: Host
14+
# Optional: Configure XDG-compliant directory paths under host:
15+
# If not specified, uses XDG Base Directory defaults:
16+
# - configHome: ~/.config/envoy-gateway
17+
# - dataHome: ~/.local/share/envoy-gateway
18+
# - stateHome: ~/.local/state/envoy-gateway
19+
# - runtimeDir: /tmp/envoy-gateway-${UID}
20+
# Example custom configuration:
21+
# host:
22+
# dataHome: /custom/data/path
1423
host: {}
1524
logging:
1625
level:

internal/cmd/certgen.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"fmt"
1313
"io"
1414
"path"
15+
"path/filepath"
1516

1617
"github.com/spf13/cobra"
1718
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
@@ -20,9 +21,11 @@ import (
2021
"sigs.k8s.io/controller-runtime/pkg/client"
2122
clicfg "sigs.k8s.io/controller-runtime/pkg/client/config"
2223

24+
egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
2325
"github.com/envoyproxy/gateway/internal/crypto"
2426
"github.com/envoyproxy/gateway/internal/envoygateway"
2527
"github.com/envoyproxy/gateway/internal/envoygateway/config"
28+
"github.com/envoyproxy/gateway/internal/infrastructure/host"
2629
"github.com/envoyproxy/gateway/internal/provider/kubernetes"
2730
"github.com/envoyproxy/gateway/internal/utils/file"
2831
)
@@ -32,26 +35,29 @@ var overwriteControlPlaneCerts bool
3235

3336
var disableTopologyInjector bool
3437

35-
// TODO: make this path configurable or use server config directly.
3638
const (
37-
defaultLocalCertPath = "/tmp/envoy-gateway/certs"
3839
topologyWebhookNamePrefix = "envoy-gateway-topology-injector"
3940
)
4041

4142
// GetCertGenCommand returns the certGen cobra command to be executed.
4243
func GetCertGenCommand() *cobra.Command {
43-
var local bool
44+
var (
45+
local bool
46+
dataHome string
47+
)
4448

4549
cmd := &cobra.Command{
4650
Use: "certgen",
4751
Short: "Generate Control Plane Certificates",
4852
RunE: func(cmd *cobra.Command, args []string) error {
49-
return certGen(cmd.Context(), cmd.OutOrStdout(), local)
53+
return certGen(cmd.Context(), cmd.OutOrStdout(), local, dataHome)
5054
},
5155
}
5256

5357
cmd.PersistentFlags().BoolVarP(&local, "local", "l", false,
5458
"Generate all the certificates locally.")
59+
cmd.PersistentFlags().StringVar(&dataHome, "data-home", "",
60+
"Directory for certificates (defaults to ~/.local/share/envoy-gateway)")
5561
cmd.PersistentFlags().BoolVarP(&overwriteControlPlaneCerts, "overwrite", "o", false,
5662
"Updates the secrets containing the control plane certs.")
5763
cmd.PersistentFlags().BoolVar(&disableTopologyInjector, "disable-topology-injector", false,
@@ -60,7 +66,7 @@ func GetCertGenCommand() *cobra.Command {
6066
}
6167

6268
// certGen generates control plane certificates.
63-
func certGen(ctx context.Context, logOut io.Writer, local bool) error {
69+
func certGen(ctx context.Context, logOut io.Writer, local bool, dataHome string) error {
6470
cfg, err := config.New(logOut, io.Discard)
6571
if err != nil {
6672
return err
@@ -86,8 +92,21 @@ func certGen(ctx context.Context, logOut io.Writer, local bool) error {
8692
return fmt.Errorf("failed to patch webhook: %w", err)
8793
}
8894
} else {
89-
log.Info("generated certificates", "path", defaultLocalCertPath)
90-
if err = outputCertsForLocal(defaultLocalCertPath, certs); err != nil {
95+
// Use provided dataHome or default
96+
hostCfg := &egv1a1.EnvoyGatewayHostInfrastructureProvider{}
97+
if dataHome != "" {
98+
hostCfg.DataHome = &dataHome
99+
}
100+
101+
paths, err := host.GetPaths(hostCfg)
102+
if err != nil {
103+
return fmt.Errorf("failed to determine paths: %w", err)
104+
}
105+
106+
certPath := filepath.Join(paths.DataHome, "certs")
107+
log.Info("generated certificates", "path", certPath)
108+
109+
if err = outputCertsForLocal(certPath, certs); err != nil {
91110
return fmt.Errorf("failed to output certificates locally: %w", err)
92111
}
93112
}

internal/gatewayapi/runner/runner.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"fmt"
1313
"os"
1414
"path"
15+
"path/filepath"
1516

1617
"github.com/docker/docker/pkg/fileutils"
1718
"github.com/telepresenceio/watchable"
@@ -29,6 +30,7 @@ import (
2930
extension "github.com/envoyproxy/gateway/internal/extension/types"
3031
"github.com/envoyproxy/gateway/internal/gatewayapi"
3132
"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
33+
"github.com/envoyproxy/gateway/internal/infrastructure/host"
3234
"github.com/envoyproxy/gateway/internal/message"
3335
"github.com/envoyproxy/gateway/internal/utils"
3436
"github.com/envoyproxy/gateway/internal/wasm"
@@ -40,15 +42,8 @@ const (
4042
serveTLSKeyFilepath = "/certs/tls.key"
4143
serveTLSCaFilepath = "/certs/ca.crt"
4244

43-
// TODO: Make these path configurable.
44-
// Default certificates path for envoy-gateway with Host infrastructure provider.
45-
localTLSCertFilepath = "/tmp/envoy-gateway/certs/envoy-gateway/tls.crt"
46-
localTLSKeyFilepath = "/tmp/envoy-gateway/certs/envoy-gateway/tls.key"
47-
localTLSCaFilepath = "/tmp/envoy-gateway/certs/envoy-gateway/ca.crt"
48-
4945
hmacSecretName = "envoy-oidc-hmac" // nolint: gosec
5046
hmacSecretKey = "hmac-secret"
51-
hmacSecretPath = "/tmp/envoy-gateway/certs/envoy-oidc-hmac/hmac-secret" // nolint: gosec
5247
)
5348

5449
type Config struct {
@@ -375,12 +370,31 @@ func (r *Runner) loadTLSConfig(ctx context.Context) (tlsConfig *tls.Config, salt
375370
}
376371

377372
case r.EnvoyGateway.Provider.IsRunningOnHost():
378-
salt, err = os.ReadFile(hmacSecretPath)
373+
// Get config
374+
var hostCfg *egv1a1.EnvoyGatewayHostInfrastructureProvider
375+
if p := r.EnvoyGateway.Provider; p != nil && p.Custom != nil &&
376+
p.Custom.Infrastructure != nil && p.Custom.Infrastructure.Host != nil {
377+
hostCfg = p.Custom.Infrastructure.Host
378+
}
379+
380+
paths, err := host.GetPaths(hostCfg)
381+
if err != nil {
382+
return nil, nil, fmt.Errorf("failed to determine paths: %w", err)
383+
}
384+
385+
// Read HMAC secret
386+
hmacPath := filepath.Join(paths.CertDir("envoy-oidc-hmac"), "hmac-secret")
387+
salt, err = os.ReadFile(hmacPath)
379388
if err != nil {
380389
return nil, nil, fmt.Errorf("failed to get hmac secret: %w", err)
381390
}
382391

383-
tlsConfig, err = crypto.LoadTLSConfig(localTLSCertFilepath, localTLSKeyFilepath, localTLSCaFilepath)
392+
certDir := paths.CertDir("envoy-gateway")
393+
certPath := filepath.Join(certDir, "tls.crt")
394+
keyPath := filepath.Join(certDir, "tls.key")
395+
caPath := filepath.Join(certDir, "ca.crt")
396+
397+
tlsConfig, err = crypto.LoadTLSConfig(certPath, keyPath, caPath)
384398
if err != nil {
385399
return nil, nil, fmt.Errorf("failed to create tls config: %w", err)
386400
}

internal/gatewayapi/runner/runner_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ package runner
77

88
import (
99
"context"
10+
"crypto/tls"
1011
"os"
12+
"path/filepath"
1113
"reflect"
1214
"testing"
1315
"time"
@@ -18,6 +20,7 @@ import (
1820
gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
1921

2022
egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
23+
"github.com/envoyproxy/gateway/internal/crypto"
2124
"github.com/envoyproxy/gateway/internal/envoygateway/config"
2225
"github.com/envoyproxy/gateway/internal/extension/registry"
2326
"github.com/envoyproxy/gateway/internal/ir"
@@ -235,3 +238,66 @@ func TestDeleteAllKeys(t *testing.T) {
235238
require.Empty(t, r.keyCache.SecurityPolicyStatus)
236239
require.Empty(t, r.keyCache.EnvoyExtensionPolicyStatus)
237240
}
241+
242+
func TestLoadTLSConfig_HostMode(t *testing.T) {
243+
// Create temporary directory structure for certs using t.TempDir()
244+
configHome := t.TempDir()
245+
certsDir := filepath.Join(configHome, "certs", "envoy-gateway")
246+
hmacDir := filepath.Join(configHome, "certs", "envoy-oidc-hmac")
247+
require.NoError(t, os.MkdirAll(certsDir, 0o750))
248+
require.NoError(t, os.MkdirAll(hmacDir, 0o750))
249+
250+
// Create test certificates using internal/crypto package
251+
cfg, err := config.New(os.Stdout, os.Stderr)
252+
require.NoError(t, err)
253+
254+
// Generate certificates with default provider (crypto.GenerateCerts only supports Kubernetes)
255+
certs, err := crypto.GenerateCerts(cfg)
256+
require.NoError(t, err)
257+
258+
// Write certificates to temp directory
259+
caFile := filepath.Join(certsDir, "ca.crt")
260+
certFile := filepath.Join(certsDir, "tls.crt")
261+
keyFile := filepath.Join(certsDir, "tls.key")
262+
hmacFile := filepath.Join(hmacDir, "hmac-secret")
263+
264+
require.NoError(t, os.WriteFile(caFile, certs.CACertificate, 0o600))
265+
require.NoError(t, os.WriteFile(certFile, certs.EnvoyGatewayCertificate, 0o600))
266+
require.NoError(t, os.WriteFile(keyFile, certs.EnvoyGatewayPrivateKey, 0o600))
267+
require.NoError(t, os.WriteFile(hmacFile, certs.OIDCHMACSecret, 0o600))
268+
269+
// Configure host mode with custom configHome (certs are stored in configHome)
270+
// MUST be set BEFORE creating Runner since Config{Server: *cfg} makes a copy
271+
cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{
272+
Type: egv1a1.ProviderTypeCustom,
273+
Custom: &egv1a1.EnvoyGatewayCustomProvider{
274+
Infrastructure: &egv1a1.EnvoyGatewayInfrastructureProvider{
275+
Type: egv1a1.InfrastructureProviderTypeHost,
276+
Host: &egv1a1.EnvoyGatewayHostInfrastructureProvider{
277+
ConfigHome: &configHome,
278+
},
279+
},
280+
},
281+
}
282+
283+
r := &Runner{
284+
Config: Config{
285+
Server: *cfg,
286+
},
287+
}
288+
289+
// Test loadTLSConfig with host mode
290+
tlsConfig, salt, err := r.loadTLSConfig(context.Background())
291+
require.NoError(t, err)
292+
require.NotNil(t, tlsConfig)
293+
require.NotNil(t, salt)
294+
295+
// Verify HMAC secret was loaded
296+
require.Equal(t, certs.OIDCHMACSecret, salt)
297+
298+
// Verify TLS config properties
299+
// crypto.LoadTLSConfig uses GetConfigForClient callback to load certs on demand
300+
require.NotNil(t, tlsConfig.GetConfigForClient)
301+
require.Equal(t, tls.RequireAndVerifyClientCert, tlsConfig.ClientAuth)
302+
require.Equal(t, uint16(tls.VersionTLS13), tlsConfig.MinVersion)
303+
}

internal/globalratelimit/runner/runner.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"fmt"
1212
"math"
1313
"net"
14+
"path/filepath"
1415
"strconv"
1516

1617
discoveryv3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
@@ -25,6 +26,7 @@ import (
2526
egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
2627
"github.com/envoyproxy/gateway/internal/crypto"
2728
"github.com/envoyproxy/gateway/internal/envoygateway/config"
29+
"github.com/envoyproxy/gateway/internal/infrastructure/host"
2830
"github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/ratelimit"
2931
"github.com/envoyproxy/gateway/internal/ir"
3032
"github.com/envoyproxy/gateway/internal/message"
@@ -43,12 +45,6 @@ const (
4345
rateLimitTLSKeyFilepath = "/certs/tls.key"
4446
// rateLimitTLSCACertFilepath is the ratelimit ca cert file.
4547
rateLimitTLSCACertFilepath = "/certs/ca.crt"
46-
47-
// TODO: Make these path configurable.
48-
// Default certificates path for envoy-gateway with Host infrastructure provider.
49-
localTLSCertFilepath = "/tmp/envoy-gateway/certs/envoy-gateway/tls.crt"
50-
localTLSKeyFilepath = "/tmp/envoy-gateway/certs/envoy-gateway/tls.key"
51-
localTLSCaFilepath = "/tmp/envoy-gateway/certs/envoy-gateway/ca.crt"
5248
)
5349

5450
type Config struct {
@@ -216,21 +212,37 @@ func (r *Runner) addNewSnapshot(ctx context.Context, resource types.XdsResources
216212
}
217213

218214
func (r *Runner) loadTLSConfig() (tlsConfig *tls.Config, err error) {
215+
var certPath, keyPath, caPath string
216+
219217
switch {
220218
case r.EnvoyGateway.Provider.IsRunningOnKubernetes():
221-
tlsConfig, err = crypto.LoadTLSConfig(rateLimitTLSCertFilepath, rateLimitTLSKeyFilepath, rateLimitTLSCACertFilepath)
222-
if err != nil {
223-
return nil, fmt.Errorf("failed to create tls config: %w", err)
219+
certPath = rateLimitTLSCertFilepath
220+
keyPath = rateLimitTLSKeyFilepath
221+
caPath = rateLimitTLSCACertFilepath
222+
case r.EnvoyGateway.Provider.IsRunningOnHost():
223+
// Get configuration from provider
224+
var hostCfg *egv1a1.EnvoyGatewayHostInfrastructureProvider
225+
if p := r.EnvoyGateway.Provider; p != nil && p.Custom != nil &&
226+
p.Custom.Infrastructure != nil && p.Custom.Infrastructure.Host != nil {
227+
hostCfg = p.Custom.Infrastructure.Host
224228
}
225229

226-
case r.EnvoyGateway.Provider.IsRunningOnHost():
227-
tlsConfig, err = crypto.LoadTLSConfig(localTLSCertFilepath, localTLSKeyFilepath, localTLSCaFilepath)
230+
paths, err := host.GetPaths(hostCfg)
228231
if err != nil {
229-
return nil, fmt.Errorf("failed to create tls config: %w", err)
232+
return nil, fmt.Errorf("failed to determine paths: %w", err)
230233
}
231234

235+
certDir := paths.CertDir("envoy-gateway")
236+
certPath = filepath.Join(certDir, "tls.crt")
237+
keyPath = filepath.Join(certDir, "tls.key")
238+
caPath = filepath.Join(certDir, "ca.crt")
232239
default:
233240
return nil, fmt.Errorf("no valid tls certificates")
234241
}
242+
243+
tlsConfig, err = crypto.LoadTLSConfig(certPath, keyPath, caPath)
244+
if err != nil {
245+
return nil, fmt.Errorf("failed to create tls config: %w", err)
246+
}
235247
return
236248
}

0 commit comments

Comments
 (0)