mirror of
https://github.com/bolkedebruin/rdpgw.git
synced 2026-05-17 05:40:18 +00:00
Drop baked-in TLS cert, run as 1001, refuse known placeholder secrets
The dev container image generated a TLS keypair at build time and shipped it inside the image, so every pull of the same image tag was serving the same private key. The entrypoint also reverted to USER 0 to support a dead `createusers.txt` loop and a `chmod u+s` that was a no-op (set on a binary owned by 1001). Net result was that any RCE in the gateway landed as root and the wire-trust posture relied on a shared private key. Stop generating the cert at build time: the runtime image now carries openssl and the entrypoint mints an ephemeral self-signed cert at first start when no cert is mounted at the configured path. Each container instance gets its own key. Drop USER 0 entirely; the entrypoint runs as 1001 throughout. Prune the dead createusers loop and the `chmod u+s`. Separately, the README and the dev compose files publish a small set of literal placeholder values for SessionKey, SessionEncryptionKey, and the various Token*Key fields. Operators copy-paste these into real deployments. Refuse to start when any of those literals appear in the corresponding config field.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -13,6 +14,45 @@ import (
|
||||
"github.com/knadh/koanf/v2"
|
||||
)
|
||||
|
||||
// knownDefaultSecrets is the list of placeholder session/token-key values
|
||||
// that appear in README.md and the dev compose files. Operators that
|
||||
// copy-paste the examples into a real deployment would be running with
|
||||
// widely-published keys, so the gateway refuses to start when one of these
|
||||
// is set for any of the session-/token-binding fields.
|
||||
var knownDefaultSecrets = []string{
|
||||
"thisisasessionkeyreplacethisjetzt",
|
||||
"thisisasessionkeyreplacethisjetz",
|
||||
"thisisasessionkeyreplacethisnunu!",
|
||||
"thisisasessionkeyreplacethisnunu",
|
||||
"thisisasessionencryptionkey12345",
|
||||
}
|
||||
|
||||
func checkDefaultSecrets(c *Configuration) error {
|
||||
fields := []struct {
|
||||
name string
|
||||
value string
|
||||
}{
|
||||
{"server.sessionkey", c.Server.SessionKey},
|
||||
{"server.sessionencryptionkey", c.Server.SessionEncryptionKey},
|
||||
{"security.paatokensigningkey", c.Security.PAATokenSigningKey},
|
||||
{"security.paatokenencryptionkey", c.Security.PAATokenEncryptionKey},
|
||||
{"security.usertokensigningkey", c.Security.UserTokenSigningKey},
|
||||
{"security.usertokenencryptionkey", c.Security.UserTokenEncryptionKey},
|
||||
{"security.querytokensigningkey", c.Security.QueryTokenSigningKey},
|
||||
}
|
||||
for _, f := range fields {
|
||||
if f.value == "" {
|
||||
continue
|
||||
}
|
||||
for _, def := range knownDefaultSecrets {
|
||||
if f.value == def {
|
||||
return fmt.Errorf("%s is set to a known placeholder value (%q); replace it with a unique secret before starting", f.name, def)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
TlsDisable = "disable"
|
||||
TlsAuto = "auto"
|
||||
@@ -219,6 +259,10 @@ func Load(configFile string) Configuration {
|
||||
k.UnmarshalWithConf("Client", &Conf.Client, koanfTag)
|
||||
k.UnmarshalWithConf("Kerberos", &Conf.Kerberos, koanfTag)
|
||||
|
||||
if err := checkDefaultSecrets(&Conf); err != nil {
|
||||
log.Fatalf("refusing to start: %s", err)
|
||||
}
|
||||
|
||||
if len(Conf.Security.PAATokenEncryptionKey) != 32 {
|
||||
Conf.Security.PAATokenEncryptionKey, _ = security.GenerateRandomString(32)
|
||||
log.Printf("No valid `security.paatokenencryptionkey` specified (empty or not 32 characters). Setting to random")
|
||||
|
||||
@@ -53,6 +53,85 @@ func TestAuthenticationConstants(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDefaultSecrets(t *testing.T) {
|
||||
const placeholder = "thisisasessionkeyreplacethisjetzt"
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
mutate func(*Configuration)
|
||||
wantField string
|
||||
}{
|
||||
{
|
||||
name: "session key",
|
||||
mutate: func(c *Configuration) { c.Server.SessionKey = placeholder },
|
||||
wantField: "server.sessionkey",
|
||||
},
|
||||
{
|
||||
name: "session encryption key",
|
||||
mutate: func(c *Configuration) { c.Server.SessionEncryptionKey = placeholder },
|
||||
wantField: "server.sessionencryptionkey",
|
||||
},
|
||||
{
|
||||
name: "paa signing key",
|
||||
mutate: func(c *Configuration) { c.Security.PAATokenSigningKey = placeholder },
|
||||
wantField: "security.paatokensigningkey",
|
||||
},
|
||||
{
|
||||
name: "paa encryption key",
|
||||
mutate: func(c *Configuration) { c.Security.PAATokenEncryptionKey = placeholder },
|
||||
wantField: "security.paatokenencryptionkey",
|
||||
},
|
||||
{
|
||||
name: "user signing key",
|
||||
mutate: func(c *Configuration) { c.Security.UserTokenSigningKey = placeholder },
|
||||
wantField: "security.usertokensigningkey",
|
||||
},
|
||||
{
|
||||
name: "user encryption key",
|
||||
mutate: func(c *Configuration) { c.Security.UserTokenEncryptionKey = placeholder },
|
||||
wantField: "security.usertokenencryptionkey",
|
||||
},
|
||||
{
|
||||
name: "query signing key",
|
||||
mutate: func(c *Configuration) { c.Security.QueryTokenSigningKey = placeholder },
|
||||
wantField: "security.querytokensigningkey",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := &Configuration{}
|
||||
tc.mutate(c)
|
||||
err := checkDefaultSecrets(c)
|
||||
if err == nil {
|
||||
t.Fatalf("checkDefaultSecrets accepted a placeholder value in %s", tc.wantField)
|
||||
}
|
||||
if got := err.Error(); !contains(got, tc.wantField) {
|
||||
t.Errorf("error message %q should mention the field %q", got, tc.wantField)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDefaultSecretsAllowsRandomValues(t *testing.T) {
|
||||
c := &Configuration{}
|
||||
c.Server.SessionKey = "5aa3a1568fe8421cd7e127d5ace28d2d"
|
||||
c.Server.SessionEncryptionKey = "d3ecd7e565e56e37e2f2e95b584d8c0c"
|
||||
c.Security.PAATokenSigningKey = "0123456789abcdef0123456789abcdef"
|
||||
if err := checkDefaultSecrets(c); err != nil {
|
||||
t.Errorf("checkDefaultSecrets rejected non-placeholder values: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func contains(haystack, needle string) bool {
|
||||
for i := 0; i+len(needle) <= len(haystack); i++ {
|
||||
if haystack[i:i+len(needle)] == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestHeaderConfigValidation(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user