This commit is contained in:
Denis Mishin 2023-01-05 16:35:58 -05:00 committed by GitHub
parent 78fc4853db
commit 488bcd6f72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 447 additions and 67 deletions

View file

@ -0,0 +1,91 @@
// Package config implements derived certs in the Pomerium Configuration
package config
import (
"bytes"
"crypto/tls"
"fmt"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/pkg/derivecert"
)
type builder struct {
psk []byte
ca *derivecert.CA
caCertPEM []byte
domain string
certs []tls.Certificate
}
// NewBuilder returns a new derived certs config builder with caching
func NewBuilder() func(*config.Config) error {
return new(builder).Build
}
func (x *builder) Build(cfg *config.Config) error {
if cfg.Options.DeriveInternalDomainCert == nil {
return nil
}
psk, err := cfg.Options.GetSharedKey()
if err != nil {
return fmt.Errorf("shared key: %w", err)
}
if err = x.buildCA(psk); err != nil {
return err
}
if err = x.buildCert(*cfg.Options.DeriveInternalDomainCert); err != nil {
return err
}
cfg.DerivedCAPEM = x.caCertPEM
cfg.DerivedCertificates = x.certs
return nil
}
func (x *builder) buildCA(psk []byte) error {
if bytes.Equal(x.psk, psk) {
return nil
}
ca, err := derivecert.NewCA(psk)
if err != nil {
return fmt.Errorf("building certificate authority from shared key: %w", err)
}
pem, err := ca.PEM()
if err != nil {
return fmt.Errorf("encode derived CA to PEM: %w", err)
}
x.psk = psk
x.ca = ca
x.caCertPEM = pem.Cert
return nil
}
func (x *builder) buildCert(domain string) error {
if x.domain == domain {
return nil
}
certPEM, err := x.ca.NewServerCert([]string{domain})
if err != nil {
return fmt.Errorf("generate cert: %w", err)
}
cert, err := certPEM.TLS()
if err != nil {
return fmt.Errorf("parse TLS cert: %w", err)
}
x.domain = domain
x.certs = []tls.Certificate{cert}
return nil
}

View file

@ -0,0 +1,57 @@
package config_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/pkg/cryptutil"
dcfg "github.com/pomerium/pomerium/pkg/derivecert/config"
)
func TestBuild(t *testing.T) {
build := dcfg.NewBuilder()
key := cryptutil.NewBase64Key()
cfgA := config.Config{Options: &config.Options{SharedKey: key}}
t.Run("no domain requested", func(t *testing.T) {
require.NoError(t, build(&cfgA))
assert.Empty(t, cfgA.DerivedCAPEM)
assert.Empty(t, cfgA.DerivedCertificates)
})
cfgA.Options.DeriveInternalDomainCert = proto.String("example.com")
t.Run("generate server cert", func(t *testing.T) {
require.NoError(t, build(&cfgA))
assert.NotEmpty(t, cfgA.DerivedCAPEM)
assert.Len(t, cfgA.DerivedCertificates, 1)
})
cfgB := config.Config{Options: &config.Options{
SharedKey: key,
DeriveInternalDomainCert: proto.String("example.com"),
}}
t.Run("caching", func(t *testing.T) {
require.NoError(t, build(&cfgB))
assert.Equal(t, cfgA.DerivedCAPEM, cfgB.DerivedCAPEM)
assert.Equal(t, cfgA.DerivedCertificates[0].Certificate, cfgB.DerivedCertificates[0].Certificate)
})
t.Run("no domain requested after run", func(t *testing.T) {
cfg := config.Config{Options: &config.Options{SharedKey: key}}
require.NoError(t, build(&cfg))
assert.Empty(t, cfg.DerivedCAPEM)
assert.Empty(t, cfg.DerivedCertificates)
})
cfgB.Options.DeriveInternalDomainCert = proto.String("example2.com")
t.Run("ca caching", func(t *testing.T) {
require.NoError(t, build(&cfgB))
assert.Equal(t, cfgA.DerivedCAPEM, cfgB.DerivedCAPEM)
assert.NotEqual(t, cfgA.DerivedCertificates[0].Certificate, cfgB.DerivedCertificates[0].Certificate)
})
}