mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-23 10:09:03 +02:00
fix: separate TLS inputs and preserve certificate reloads (#1653)
Co-authored-by: Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
This commit is contained in:
co-authored by
Alessandro Segala
parent
1f9cc5e58e
commit
06ccadfcd0
@@ -2,11 +2,22 @@ package bootstrap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"log/slog"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pocket-id/pocket-id/backend/internal/apperror"
|
||||
@@ -130,3 +141,98 @@ func TestRequestLoggerLogsAtConfiguredMinimumLevel(t *testing.T) {
|
||||
require.Equal(t, http.StatusNoContent, recorder.Code)
|
||||
require.Contains(t, output.String(), "level=INFO")
|
||||
}
|
||||
|
||||
func TestNewCertProviderSupportsInlineCertificateData(t *testing.T) {
|
||||
certPEM, keyPEM := newTestTLSKeyPair(t, 1)
|
||||
|
||||
provider, err := newCertProvider(certPEM, keyPEM, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, provider.certFile)
|
||||
require.Empty(t, provider.keyFile)
|
||||
require.True(t, certProviderHasSerial(provider, 1))
|
||||
|
||||
watcher, err := startCertWatcher(t.Context(), provider)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, watcher)
|
||||
}
|
||||
|
||||
func TestCertProviderReloadsAfterRepeatedAtomicReplacement(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
certFile := filepath.Join(tempDir, "cert.pem")
|
||||
keyFile := filepath.Join(tempDir, "key.pem")
|
||||
writeTestTLSKeyPair(t, certFile, keyFile, 1)
|
||||
|
||||
provider, err := newCertProvider("", "", certFile, keyFile)
|
||||
require.NoError(t, err)
|
||||
require.True(t, certProviderHasSerial(provider, 1))
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
watcher, err := startCertWatcher(ctx, provider)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
cancel()
|
||||
closeCertWatcher(watcher)
|
||||
})
|
||||
|
||||
for serial := int64(2); serial <= 3; serial++ {
|
||||
replaceTestTLSKeyPair(t, certFile, keyFile, serial)
|
||||
require.Eventually(t, func() bool {
|
||||
return certProviderHasSerial(provider, serial)
|
||||
}, 5*time.Second, 50*time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func replaceTestTLSKeyPair(t *testing.T, certFile, keyFile string, serial int64) {
|
||||
t.Helper()
|
||||
|
||||
replacementCertFile := certFile + ".new"
|
||||
replacementKeyFile := keyFile + ".new"
|
||||
writeTestTLSKeyPair(t, replacementCertFile, replacementKeyFile, serial)
|
||||
require.NoError(t, os.Rename(replacementCertFile, certFile))
|
||||
require.NoError(t, os.Rename(replacementKeyFile, keyFile))
|
||||
}
|
||||
|
||||
func writeTestTLSKeyPair(t *testing.T, certFile, keyFile string, serial int64) {
|
||||
t.Helper()
|
||||
|
||||
certPEM, keyPEM := newTestTLSKeyPair(t, serial)
|
||||
require.NoError(t, os.WriteFile(certFile, []byte(certPEM), 0600))
|
||||
require.NoError(t, os.WriteFile(keyFile, []byte(keyPEM), 0600))
|
||||
}
|
||||
|
||||
func newTestTLSKeyPair(t *testing.T, serial int64) (string, string) {
|
||||
t.Helper()
|
||||
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now()
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(serial),
|
||||
Subject: pkix.Name{CommonName: "localhost"},
|
||||
NotBefore: now.Add(-time.Minute),
|
||||
NotAfter: now.Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
DNSNames: []string{"localhost"},
|
||||
}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
keyDER, err := x509.MarshalECPrivateKey(privateKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
|
||||
return string(certPEM), string(keyPEM)
|
||||
}
|
||||
|
||||
func certProviderHasSerial(provider *tlsCertProvider, serial int64) bool {
|
||||
cert, err := provider.GetCertificate(nil)
|
||||
if err != nil || cert == nil || len(cert.Certificate) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
parsedCert, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
return err == nil && parsedCert.SerialNumber.Cmp(big.NewInt(serial)) == 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user