remove tls cache
rabbitprincess committed
Mar 2, 2026 at 18:46 UTC
45e0847d57489670b121b9f06de133fd40a06e08
8 files changed
+25
-203
AGENTS.md
-1
@@ -90,7 +90,6 @@ Portal uses environment variables for domain and TLS configuration:
90
| `TLS_MODE` | `no-tls`, `self`, or `keyless` |
91
| `TLS_CERT_FILE` | Self TLS certificate chain path (self mode only) |
92
| `TLS_KEY_FILE` | Self TLS private key path (self mode only) |
93
-| `TLS_CERT_CACHE_FILE` | Keyless cert cache file path (keyless mode only) |
93
94
### Domain Derivation
95
cmd/demo-app/main.go
+1
-1
@@ -40,7 +40,7 @@ var (
40
)
41
42
func main() {
43
- flag.StringVar(&flagServerURL, "server-url", "http://localhost:4017", "relay API URL (http/https)")
43
+ flag.StringVar(&flagServerURL, "server-url", "https://gosunuts.xyz", "relay API URL (http/https)")
44
flag.IntVar(&flagPort, "port", 8092, "local demo HTTP port")
45
flag.StringVar(&flagName, "name", "demo-app", "backend display name")
46
flag.StringVar(&flagDesc, "description", "Portal demo connectivity app", "lease description")
cmd/portal-tunnel/README.md
+1
-4
@@ -53,7 +53,6 @@ Options:
53
--tls-mode TLS mode: no-tls, self, or keyless [default: no-tls] [env: TLS_MODE]
54
--tls-cert-file PEM certificate chain for --tls-mode self [env: TLS_CERT_FILE]
55
--tls-key-file PEM private key for --tls-mode self [env: TLS_KEY_FILE]
56
- --tls-cert-cache-file Certificate cache file path for --tls-mode keyless [env: TLS_CERT_CACHE_FILE]
56
--description Service description metadata [env: APP_DESCRIPTION]
57
--tags Service tags metadata (comma-separated) [env: APP_TAGS]
58
--thumbnail Service thumbnail URL metadata [env: APP_THUMBNAIL]
@@ -81,7 +80,6 @@ export RELAYS=https://portal.example.com
80
export APP_HOST=localhost:3000
81
export APP_NAME=myapp
82
export TLS_MODE=keyless
84
-export TLS_CERT_CACHE_FILE=/var/lib/portal/cache/cert-cache.json
83
84
./bin/portal-tunnel
85
```
@@ -106,7 +104,6 @@ export RELAYS=https://portal.example.com
104
export APP_HOST=localhost:3000
105
export APP_NAME=myapp
106
export TLS_MODE=keyless
109
-export TLS_CERT_CACHE_FILE=/var/lib/portal/cache/cert-cache.json
107
108
./bin/portal-tunnel
109
```
@@ -139,4 +136,4 @@ Expected signer API contract (`/v1/sign`):
136
--name myapp \
137
--relay https://portal1.example.com,https://portal2.example.com \
138
--tls-mode keyless
142
-```
\ No newline at end of file
139
+```
cmd/portal-tunnel/main.go
+12
-22
@@ -16,23 +16,21 @@ import (
16
"github.com/rs/zerolog"
17
"github.com/rs/zerolog/log"
18
19
- "gosuda.org/portal/portal/acme"
19
"gosuda.org/portal/sdk"
20
)
21
22
var (
24
- flagRelayURLs string
25
- flagHost string
26
- flagName string
27
- flagTLSMode string
28
- flagTLSCertFile string
29
- flagTLSKeyFile string
30
- flagTLSCertCacheFile string
31
- flagDescription string
32
- flagTags string
33
- flagThumbnail string
34
- flagOwner string
35
- flagHide bool
23
+ flagRelayURLs string
24
+ flagHost string
25
+ flagName string
26
+ flagTLSMode string
27
+ flagTLSCertFile string
28
+ flagTLSKeyFile string
29
+ flagDescription string
30
+ flagTags string
31
+ flagThumbnail string
32
+ flagOwner string
33
+ flagHide bool
34
)
35
36
func main() {
@@ -54,7 +52,6 @@ func main() {
52
flag.StringVar(&flagTLSMode, "tls-mode", defaultTLSMode, "TLS mode: no-tls, self, or keyless [env: TLS_MODE]")
53
flag.StringVar(&flagTLSCertFile, "tls-cert-file", os.Getenv("TLS_CERT_FILE"), "PEM certificate chain for --tls-mode self [env: TLS_CERT_FILE]")
54
flag.StringVar(&flagTLSKeyFile, "tls-key-file", os.Getenv("TLS_KEY_FILE"), "PEM private key for --tls-mode self [env: TLS_KEY_FILE]")
57
- flag.StringVar(&flagTLSCertCacheFile, "tls-cert-cache-file", os.Getenv("TLS_CERT_CACHE_FILE"), "certificate cache file path for --tls-mode keyless [env: TLS_CERT_CACHE_FILE]")
55
56
flag.StringVar(&flagDescription, "description", os.Getenv("APP_DESCRIPTION"), "Service description metadata [env: APP_DESCRIPTION]")
57
flag.StringVar(&flagTags, "tags", os.Getenv("APP_TAGS"), "Service tags metadata (comma-separated) [env: APP_TAGS]")
@@ -133,21 +130,14 @@ func runServiceTunnel(ctx context.Context, relayURLs []string) error {
130
Str("key_file", keyFile).
131
Msg("TLS: Using self-managed local certificate")
132
} else if flagTLSMode == string(sdk.TLSModeKeyless) {
136
- certCacheFile := strings.TrimSpace(flagTLSCertCacheFile)
137
- if certCacheFile == "" {
138
- certCacheFile = acme.DefaultCertCachePath()
139
- }
133
certFile := strings.TrimSpace(flagTLSCertFile)
134
if certFile != "" {
135
log.Warn().
136
Str("cert_file", certFile).
137
Msg("Ignoring --tls-cert-file in keyless mode (SDK auto configuration only)")
138
}
146
- clientOpts = append(clientOpts, sdk.WithCertCacheFile(certCacheFile))
139
clientOpts = append(clientOpts, sdk.WithTLSKeylessDefaults())
148
- log.Info().
149
- Str("cert_cache_file", certCacheFile).
150
- Msg("TLS: Using keyless remote signer (SDK auto configuration)")
140
+ log.Info().Msg("TLS: Using keyless remote signer (SDK auto configuration)")
141
} else {
142
return fmt.Errorf("unsupported TLS mode: %s", flagTLSMode)
143
}
portal/acme/certcache.go
deleted
-130
@@ -1,130 +0,0 @@
1
-package acme
2
-
3
-import (
4
- "crypto/x509"
5
- "encoding/json"
6
- "encoding/pem"
7
- "errors"
8
- "fmt"
9
- "os"
10
- "path/filepath"
11
- "time"
12
-
13
- "github.com/rs/zerolog/log"
14
-)
15
-
16
-const (
17
- // DefaultCacheTTL is the default time-to-live for cached certificates.
18
- DefaultCacheTTL = 24 * time.Hour
19
-
20
- // ExpiryThreshold is how long before cert expiration to refresh cache.
21
- ExpiryThreshold = 30 * 24 * time.Hour
22
-
23
- // DefaultCacheFileName is the default filename for certificate cache.
24
- DefaultCacheFileName = "portal-cert-cache.json"
25
-)
26
-
27
-// DefaultCertCachePath returns the default path for certificate cache.
28
-// It uses the OS-specific user cache directory if available, otherwise
29
-// falls back to the current directory.
30
-func DefaultCertCachePath() string {
31
- if cacheDir, err := os.UserCacheDir(); err == nil {
32
- return filepath.Join(cacheDir, "portal", DefaultCacheFileName)
33
- }
34
- if homeDir, err := os.UserHomeDir(); err == nil {
35
- return filepath.Join(homeDir, ".cache", "portal", DefaultCacheFileName)
36
- }
37
- return DefaultCacheFileName
38
-}
39
-
40
-// CertCacheEntry represents a cached certificate chain.
41
-type CertCacheEntry struct {
42
- CertPEM []byte `json:"cert_pem"`
43
- RootCAPEM []byte `json:"root_ca_pem"`
44
- FetchedAt int64 `json:"fetched_at"`
45
-}
46
-
47
-// ParseCertificatePEM parses a PEM-encoded certificate.
48
-func ParseCertificatePEM(pemData []byte) (*x509.Certificate, error) {
49
- block, _ := pem.Decode(pemData)
50
- if block == nil {
51
- return nil, errors.New("no PEM block found")
52
- }
53
- return x509.ParseCertificate(block.Bytes)
54
-}
55
-
56
-// LoadCertCache loads a certificate cache from file.
57
-func LoadCertCache(path string) (*CertCacheEntry, error) {
58
- if path == "" {
59
- return nil, nil
60
- }
61
- data, err := os.ReadFile(path)
62
- if err != nil {
63
- if os.IsNotExist(err) {
64
- return nil, nil
65
- }
66
- return nil, fmt.Errorf("read cert cache: %w", err)
67
- }
68
- var entry CertCacheEntry
69
- if err := json.Unmarshal(data, &entry); err != nil {
70
- return nil, fmt.Errorf("parse cert cache: %w", err)
71
- }
72
- return &entry, nil
73
-}
74
-
75
-// SaveCertCache saves a certificate cache to file.
76
-func SaveCertCache(path string, entry *CertCacheEntry) error {
77
- if path == "" || entry == nil {
78
- return nil
79
- }
80
-
81
- // Ensure parent directory exists
82
- dir := filepath.Dir(path)
83
- if dir != "" && dir != "." {
84
- if err := os.MkdirAll(dir, 0700); err != nil {
85
- return fmt.Errorf("create cert cache directory: %w", err)
86
- }
87
- }
88
-
89
- toSave := *entry
90
- toSave.FetchedAt = time.Now().Unix()
91
-
92
- data, err := json.Marshal(&toSave)
93
- if err != nil {
94
- return fmt.Errorf("marshal cert cache: %w", err)
95
- }
96
- if err := writeFileAtomic(path, data, 0o600); err != nil {
97
- return fmt.Errorf("write cert cache: %w", err)
98
- }
99
- return nil
100
-}
101
-
102
-// IsCertCacheFresh checks if a cached certificate is still fresh.
103
-// It checks both the TTL (time since fetch) and the certificate expiration.
104
-func IsCertCacheFresh(entry *CertCacheEntry, ttl time.Duration) bool {
105
- if entry == nil || len(entry.CertPEM) == 0 {
106
- return false
107
- }
108
-
109
- // Check TTL (how long since we fetched)
110
- fetchedAt := time.Unix(entry.FetchedAt, 0)
111
- if time.Since(fetchedAt) >= ttl {
112
- return false
113
- }
114
-
115
- // Check cert expiration (in case ACME renewed on relay side)
116
- cert, err := ParseCertificatePEM(entry.CertPEM)
117
- if err != nil {
118
- return false
119
- }
120
- timeUntilExpiry := time.Until(cert.NotAfter)
121
- if timeUntilExpiry < ExpiryThreshold {
122
- log.Debug().
123
- Time("not_after", cert.NotAfter).
124
- Dur("time_remaining", timeUntilExpiry).
125
- Msg("[certcache] Cached certificate is close to expiration, will refresh")
126
- return false
127
- }
128
-
129
- return true
130
-}
portal/acme/renew.go
+11
@@ -2,6 +2,8 @@ package acme
2
3
import (
4
"context"
5
+ "crypto/x509"
6
+ "encoding/pem"
7
"errors"
8
"fmt"
9
"os"
@@ -251,3 +253,12 @@ func (m *AcmeManager) doRenew(cfg provisionConfig) error {
253
254
return nil
255
}
256
+
257
+// ParseCertificatePEM parses a PEM-encoded certificate.
258
+func ParseCertificatePEM(pemData []byte) (*x509.Certificate, error) {
259
+ block, _ := pem.Decode(pemData)
260
+ if block == nil {
261
+ return nil, errors.New("no PEM block found")
262
+ }
263
+ return x509.ParseCertificate(block.Bytes)
264
+}
sdk/client.go
-33
@@ -23,7 +23,6 @@ import (
23
24
"github.com/rs/zerolog/log"
25
"gosuda.org/portal/portal"
26
- "gosuda.org/portal/portal/acme"
26
)
27
28
var urlSafeNameRegex = regexp.MustCompile(`^[\p{L}\p{N}_-]+$`)
@@ -164,8 +163,6 @@ func (c *Client) Listen(name string, options ...MetadataOption) (net.Listener, e
163
keylessServerName,
164
c.config.TLSKeylessCertificatePEM,
165
c.config.TLSKeyless.RootCAPEM,
167
- c.config.CertCacheFile,
168
- acme.DefaultCacheTTL,
166
)
167
if err != nil {
168
return nil, fmt.Errorf("prepare keyless materials: %w", err)
@@ -355,8 +352,6 @@ func resolveKeylessMaterials(
352
keylessServerName string,
353
inlineCertPEM []byte,
354
inlineRootCAPEM []byte,
358
- cacheFile string,
359
- cacheTTL time.Duration,
355
) ([]byte, []byte, error) {
356
certPEM := append([]byte(nil), inlineCertPEM...)
357
rootCAPEM := append([]byte(nil), inlineRootCAPEM...)
@@ -366,22 +361,6 @@ func resolveKeylessMaterials(
361
return certPEM, rootCAPEM, nil
362
}
363
369
- // Try loading from cache first
370
- if cacheEntry, err := acme.LoadCertCache(cacheFile); err != nil {
371
- log.Debug().Err(err).Msg("[SDK] Failed to load cert cache")
372
- } else if cacheEntry != nil && acme.IsCertCacheFresh(cacheEntry, cacheTTL) {
373
- log.Debug().Str("path", cacheFile).Msg("[SDK] Using cached certificate")
374
- if len(certPEM) == 0 && len(cacheEntry.CertPEM) > 0 {
375
- certPEM = cacheEntry.CertPEM
376
- }
377
- if len(rootCAPEM) == 0 && len(cacheEntry.RootCAPEM) > 0 {
378
- rootCAPEM = cacheEntry.RootCAPEM
379
- }
380
- if len(certPEM) > 0 && len(rootCAPEM) > 0 {
381
- return certPEM, rootCAPEM, nil
382
- }
383
- }
384
-
364
// Fetch from endpoint
365
chainFromEndpoint, err := fetchEndpointCertificateChain(ctx, keylessEndpoint, keylessServerName)
366
if err != nil && len(certPEM) == 0 {
@@ -405,18 +384,6 @@ func resolveKeylessMaterials(
384
rootCAPEM = append([]byte(nil), certPEM...)
385
}
386
408
- // Save to cache
409
- if cacheFile != "" {
410
- if err := acme.SaveCertCache(cacheFile, &acme.CertCacheEntry{
411
- CertPEM: certPEM,
412
- RootCAPEM: rootCAPEM,
413
- }); err != nil {
414
- log.Debug().Err(err).Msg("[SDK] Failed to save cert cache")
415
- } else {
416
- log.Debug().Str("path", cacheFile).Msg("[SDK] Saved certificate cache")
417
- }
418
- }
419
-
387
return certPEM, rootCAPEM, nil
388
}
389
sdk/types.go
-12
@@ -59,10 +59,6 @@ type ClientConfig struct {
59
// Optional certificate chain and remote signer config used by keyless mode.
60
TLSKeylessCertificatePEM []byte
61
TLSKeyless TLSKeylessConfig
62
-
63
- // CertCacheFile is the path to cache keyless certificates.
64
- // If set, certificates are cached to avoid re-fetching on every startup.
65
- CertCacheFile string
62
}
63
64
type ClientOption func(*ClientConfig)
@@ -152,14 +148,6 @@ func WithTLSKeylessDefaults() ClientOption {
148
}
149
}
150
155
-// WithCertCacheFile sets the path for caching keyless certificates.
156
-// Cached certificates are reused until they expire (default 24 hours).
157
-func WithCertCacheFile(path string) ClientOption {
158
- return func(c *ClientConfig) {
159
- c.CertCacheFile = path
160
- }
161
-}
162
-
151
// MetadataOption configures Metadata
152
type MetadataOption func(*portal.Metadata)
153