Skip to content

Commit

Permalink
config: generate cookie secret if not set in all-in-one mode (#3742)
Browse files Browse the repository at this point in the history
* config: generate cookie secret if not set in all-in-one mode

* fix tests

* config: add warning about cookie_secret

* breakup lines
  • Loading branch information
calebdoxsey committed Nov 11, 2022
1 parent 2c9087f commit 9413123
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 17 deletions.
3 changes: 0 additions & 3 deletions authenticate/authenticate_test.go
Expand Up @@ -32,8 +32,6 @@ func TestOptions_Validate(t *testing.T) {
emptyClientID.ClientID = ""
emptyClientSecret := newTestOptions(t)
emptyClientSecret.ClientSecret = ""
emptyCookieSecret := newTestOptions(t)
emptyCookieSecret.CookieSecret = ""
invalidCookieSecret := newTestOptions(t)
invalidCookieSecret.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw^"
shortCookieLength := newTestOptions(t)
Expand All @@ -53,7 +51,6 @@ func TestOptions_Validate(t *testing.T) {
}{
{"minimum options", good, false},
{"nil options", &config.Options{}, true},
{"no cookie secret", emptyCookieSecret, true},
{"invalid cookie secret", invalidCookieSecret, true},
{"short cookie secret", shortCookieLength, true},
{"no shared secret", badSharedKey, true},
Expand Down
2 changes: 1 addition & 1 deletion config/envoyconfig/protocols.go
Expand Up @@ -100,7 +100,7 @@ func getUpstreamProtocolForPolicy(ctx context.Context, policy *config.Policy) up
upstreamProtocol := upstreamProtocolAuto
if policy.AllowWebsockets {
// #2388, force http/1 when using web sockets
log.Info(ctx).Msg("envoyconfig: forcing http/1.1 due to web socket support")
log.WarnWebSocketHTTP1_1(getClusterID(policy))
upstreamProtocol = upstreamProtocolHTTP1
}
return upstreamProtocol
Expand Down
11 changes: 10 additions & 1 deletion config/options.go
Expand Up @@ -985,7 +985,7 @@ func (o *Options) GetSharedKey() ([]byte, error) {
sharedKey = string(bs)
}
// mutual auth between services on the same host can be generated at runtime
if IsAll(o.Services) && o.SharedKey == "" && o.DataBrokerStorageType == StorageInMemoryName {
if IsAll(o.Services) && sharedKey == "" {
sharedKey = randomSharedKey
}
if sharedKey == "" {
Expand Down Expand Up @@ -1188,6 +1188,15 @@ func (o *Options) GetCookieSecret() ([]byte, error) {
}
cookieSecret = string(bs)
}

if IsAll(o.Services) && cookieSecret == "" {
log.WarnCookieSecret()
cookieSecret = randomSharedKey
}
if cookieSecret == "" {
return nil, errors.New("empty cookie secret")
}

return base64.StdEncoding.DecodeString(cookieSecret)
}

Expand Down
36 changes: 30 additions & 6 deletions config/options_test.go
Expand Up @@ -53,11 +53,6 @@ func Test_Validate(t *testing.T) {
badSignoutRedirectURL := testOptions()
badSignoutRedirectURL.SignOutRedirectURLString = "--"

missingSharedSecretWithPersistence := testOptions()
missingSharedSecretWithPersistence.SharedKey = ""
missingSharedSecretWithPersistence.DataBrokerStorageType = StorageRedisName
missingSharedSecretWithPersistence.DataBrokerStorageConnectionString = "redis://somehost:6379"

tests := []struct {
name string
testOpts *Options
Expand All @@ -71,7 +66,6 @@ func Test_Validate(t *testing.T) {
{"invalid databroker storage type", invalidStorageType, true},
{"missing databroker storage dsn", missingStorageDSN, true},
{"invalid signout redirect url", badSignoutRedirectURL, true},
{"no shared key with databroker persistence", missingSharedSecretWithPersistence, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -776,6 +770,36 @@ func TestOptions_GetSetResponseHeaders(t *testing.T) {
})
}

func TestOptions_GetSharedKey(t *testing.T) {
t.Run("default", func(t *testing.T) {
o := NewDefaultOptions()
bs, err := o.GetSharedKey()
assert.NoError(t, err)
assert.Equal(t, randomSharedKey, base64.StdEncoding.EncodeToString(bs))
})
t.Run("missing", func(t *testing.T) {
o := NewDefaultOptions()
o.Services = ServiceProxy
_, err := o.GetSharedKey()
assert.Error(t, err)
})
}

func TestOptions_GetCookieSecret(t *testing.T) {
t.Run("default", func(t *testing.T) {
o := NewDefaultOptions()
bs, err := o.GetCookieSecret()
assert.NoError(t, err)
assert.Equal(t, randomSharedKey, base64.StdEncoding.EncodeToString(bs))
})
t.Run("missing", func(t *testing.T) {
o := NewDefaultOptions()
o.Services = ServiceProxy
_, err := o.GetCookieSecret()
assert.Error(t, err)
})
}

func encodeCert(cert *tls.Certificate) []byte {
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]})
}
Expand Down
42 changes: 42 additions & 0 deletions internal/log/warnings.go
@@ -0,0 +1,42 @@
package log

import (
"context"
"sync"

"github.com/pomerium/pomerium/internal/syncutil"
)

var warnCookieSecretOnce sync.Once

// WarnCookieSecret warns about the cookie secret.
func WarnCookieSecret() {
warnCookieSecretOnce.Do(func() {
Warn(context.Background()).
Msg("using a generated COOKIE_SECRET. " +
"Set the COOKIE_SECRET to avoid users being logged out on restart. " +
"https://www.pomerium.com/docs/reference/cookie-secret")
})
}

var warnNoTLSCertificateOnce syncutil.OnceMap[string]

// WarnNoTLSCertificate warns about no TLS certificate.
func WarnNoTLSCertificate(domain string) {
warnNoTLSCertificateOnce.Do(domain, func() {
Warn(context.Background()).
Str("domain", domain).
Msg("no TLS certificate found for domain, using a self-signed certificate")
})
}

var warnWebSocketHTTP1_1Once syncutil.OnceMap[string]

// WarnWebSocketHTTP1_1 warns about falling back to http 1.1 due to web socket support.
func WarnWebSocketHTTP1_1(clusterID string) {
warnWebSocketHTTP1_1Once.Do(clusterID, func() {
Warn(context.Background()).
Str("cluster-id", clusterID).
Msg("forcing http/1.1 due to web socket support")
})
}
27 changes: 27 additions & 0 deletions internal/syncutil/syncutil.go
@@ -0,0 +1,27 @@
// Package syncutil contains methods for working with sync code.
package syncutil

import (
"sync"
)

// A OnceMap is a collection sync.Onces accessible by a key. The zero value is usable.
type OnceMap[T comparable] struct {
mu sync.Mutex
m map[T]*sync.Once
}

// Do runs f once.
func (o *OnceMap[T]) Do(key T, f func()) {
o.mu.Lock()
if o.m == nil {
o.m = make(map[T]*sync.Once)
}
oo, ok := o.m[key]
if !ok {
oo = new(sync.Once)
o.m[key] = oo
}
o.mu.Unlock()
oo.Do(f)
}
4 changes: 1 addition & 3 deletions pkg/cryptutil/tls.go
Expand Up @@ -55,9 +55,7 @@ func GetCertificateForDomain(certificates []tls.Certificate, domain string) (*tl
}
}

log.Error(context.Background()).
Str("domain", domain).
Msg("cryptutil: no TLS certificate found for domain, using self-signed certificate")
log.WarnNoTLSCertificate(domain)

// finally fall back to a generated, self-signed certificate
return GenerateSelfSignedCertificate(domain)
Expand Down
3 changes: 0 additions & 3 deletions proxy/proxy_test.go
Expand Up @@ -41,8 +41,6 @@ func TestOptions_Validate(t *testing.T) {
badAuthURL.AuthenticateURLString = "BAD_URL"
authenticateBadScheme := testOptions(t)
authenticateBadScheme.AuthenticateURLString = "authenticate.corp.beyondperimeter.com"
emptyCookieSecret := testOptions(t)
emptyCookieSecret.CookieSecret = ""
invalidCookieSecret := testOptions(t)
invalidCookieSecret.CookieSecret = "OromP1gurwGWjQPYb1nNgSxtbVB5NnLzX6z5WOKr0Yw^"
shortCookieLength := testOptions(t)
Expand All @@ -62,7 +60,6 @@ func TestOptions_Validate(t *testing.T) {
}{
{"good - minimum options", good, false},
{"nil options", &config.Options{}, true},
{"no cookie secret", emptyCookieSecret, true},
{"invalid cookie secret", invalidCookieSecret, true},
{"short cookie secret", shortCookieLength, true},
{"no shared secret", badSharedKey, true},
Expand Down

0 comments on commit 9413123

Please sign in to comment.