v0.28.0 option schema in struct tags, schema replacing plugin factory metadata, tui prep
This commit is contained in:
1 parent
a1648ce17b
commit
e8e5f05e47
43 files changed
+1345
-1211
No files matched your search
+21
-113
@@ -7,6 +7,7 @@ package authz
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
@@ -42,21 +43,6 @@ const (
|
||||
BindingForce = "force"
|
||||
)
|
||||
|
||||
// Role selects the validation and behavior appropriate to the call site
|
||||
type Role int
|
||||
|
||||
const (
|
||||
// RoleListener authenticates peers of a plugin with no node concept: the
|
||||
// tcp and http sinks
|
||||
RoleListener Role = iota
|
||||
// RoleChainListener also binds the node label a peer declares: the
|
||||
// tcp_chain and http_chain sources
|
||||
RoleChainListener
|
||||
// RoleDialer proves itself to, or pins, the server: the tcp_chain and
|
||||
// http_chain sinks
|
||||
RoleDialer
|
||||
)
|
||||
|
||||
// Transport tells HTTP plugins, which carry bearer tokens, from TCP ones
|
||||
type Transport int
|
||||
|
||||
@@ -72,7 +58,7 @@ var ErrRefused = errors.New("auth: refused")
|
||||
|
||||
// Policy is the compiled form of config.AuthOptions
|
||||
type Policy struct {
|
||||
role Role
|
||||
side config.Side
|
||||
transport Transport
|
||||
method string
|
||||
identity string // mtls: identity field; scram: certificate-to-user binding, "" = none
|
||||
@@ -113,38 +99,25 @@ func (id Identity) Apply(meta map[string]any) {
|
||||
// tlsCfg is what tlsx built from the sibling `tls` block: a policy the
|
||||
// transport cannot enforce is rejected here rather than silently accepted.
|
||||
// acl is netacl's from the `acl` block, nil for a dialer.
|
||||
func New(o *config.AuthOptions, tlsCfg *tls.Config, acl *netacl.Policy, role Role, transport Transport) (*Policy, error) {
|
||||
if o == nil {
|
||||
return nil, nil
|
||||
func New(o *config.AuthOptions, tlsCfg *tls.Config, acl *netacl.Policy, side config.Side, transport Transport) (*Policy, error) {
|
||||
state := config.TLSState{On: tlsCfg != nil}
|
||||
if tlsCfg != nil {
|
||||
state.ClientAuth = tlsCfg.ClientAuth == tls.RequireAndVerifyClientCert
|
||||
state.Unverified = tlsCfg.InsecureSkipVerify && tlsCfg.VerifyPeerCertificate == nil // a pin verifies there
|
||||
}
|
||||
switch o.Type {
|
||||
case "", MethodNone:
|
||||
// Tuning keys are ignored with the block, but one that names peers or
|
||||
// credentials means auth was intended and the type was forgotten
|
||||
if key := intentKey(o); key != "" {
|
||||
return nil, fmt.Errorf("auth: %s is set but auth.type is %q", key, MethodNone)
|
||||
}
|
||||
return nil, nil
|
||||
case MethodMTLS, MethodSCRAM:
|
||||
default:
|
||||
return nil, fmt.Errorf("auth: type %q (valid: %q, %q, %q)", o.Type, MethodNone, MethodMTLS, MethodSCRAM)
|
||||
}
|
||||
if tlsCfg == nil && (o.Type != MethodSCRAM || len(o.TrustedProxies) == 0) {
|
||||
return nil, fmt.Errorf("auth: type %q requires tls.enabled", o.Type)
|
||||
}
|
||||
if role == RoleDialer && tlsCfg != nil && tlsCfg.InsecureSkipVerify && tlsCfg.VerifyPeerCertificate == nil {
|
||||
// An unverified server makes identities claims and exposes credentials;
|
||||
// tls.pin_sha256 verifies it in VerifyPeerCertificate instead
|
||||
return nil, fmt.Errorf("auth: type %q cannot be used with tls.insecure_skip_verify", o.Type)
|
||||
}
|
||||
|
||||
binding, err := nodeBinding(o.NodeBinding, role)
|
||||
if err != nil {
|
||||
if err := o.Check(side, transport == HTTP, state); err != nil || o == nil || o.Type == "" || o.Type == MethodNone {
|
||||
return nil, err
|
||||
}
|
||||
p := &Policy{role: role, transport: transport, method: o.Type, binding: binding, acl: acl}
|
||||
binding := BindingNone
|
||||
if side == config.ChainListener {
|
||||
// force is the only binding under which a misconfigured or hostile edge
|
||||
// cannot mislabel its entries
|
||||
binding = cmp.Or(o.NodeBinding, BindingForce)
|
||||
}
|
||||
p := &Policy{side: side, transport: transport, method: o.Type, binding: binding, acl: acl}
|
||||
var err error
|
||||
if o.Type == MethodMTLS {
|
||||
err = p.compileMTLS(o, tlsCfg)
|
||||
err = p.compileMTLS(o)
|
||||
} else {
|
||||
err = p.compileSCRAM(o, tlsCfg)
|
||||
}
|
||||
@@ -154,57 +127,8 @@ func New(o *config.AuthOptions, tlsCfg *tls.Config, acl *netacl.Policy, role Rol
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// intentKey names the first key that only makes sense with authentication on
|
||||
func intentKey(o *config.AuthOptions) string {
|
||||
switch {
|
||||
case len(o.Allow) > 0:
|
||||
return "allow"
|
||||
case len(o.AllowPatterns) > 0:
|
||||
return "allow_patterns"
|
||||
}
|
||||
return scramKey(o)
|
||||
}
|
||||
|
||||
func nodeBinding(binding string, role Role) (string, error) {
|
||||
if role != RoleChainListener {
|
||||
if binding != "" && binding != BindingNone {
|
||||
return "", fmt.Errorf("auth: node_binding %q applies only to chain sources", binding)
|
||||
}
|
||||
return BindingNone, nil
|
||||
}
|
||||
switch binding {
|
||||
case "":
|
||||
// The only setting under which a misconfigured or hostile edge cannot
|
||||
// mislabel its entries
|
||||
return BindingForce, nil
|
||||
case BindingNone, BindingAssert, BindingForce:
|
||||
return binding, nil
|
||||
}
|
||||
return "", fmt.Errorf("auth: node_binding %q (valid: %q, %q, %q)", binding, BindingNone, BindingAssert, BindingForce)
|
||||
}
|
||||
|
||||
func identityMode(mode string) (string, error) {
|
||||
switch mode {
|
||||
case "":
|
||||
return tlsx.IdentityCN, nil
|
||||
case tlsx.IdentityCN, tlsx.IdentitySANDNS, tlsx.IdentitySANURI, tlsx.IdentitySANEmail:
|
||||
return mode, nil
|
||||
}
|
||||
return "", fmt.Errorf("auth: identity %q (valid: %q, %q, %q, %q)",
|
||||
mode, tlsx.IdentityCN, tlsx.IdentitySANDNS, tlsx.IdentitySANURI, tlsx.IdentitySANEmail)
|
||||
}
|
||||
|
||||
func (p *Policy) compileMTLS(o *config.AuthOptions, tlsCfg *tls.Config) error {
|
||||
if key := scramKey(o); key != "" {
|
||||
return fmt.Errorf("auth: %s applies only to type %q", key, MethodSCRAM)
|
||||
}
|
||||
if p.role != RoleDialer && tlsCfg.ClientAuth != tls.RequireAndVerifyClientCert {
|
||||
return fmt.Errorf("auth: type %q requires tls.client_auth", MethodMTLS)
|
||||
}
|
||||
var err error
|
||||
if p.identity, err = identityMode(o.Identity); err != nil {
|
||||
return err
|
||||
}
|
||||
func (p *Policy) compileMTLS(o *config.AuthOptions) error {
|
||||
p.identity = cmp.Or(o.Identity, tlsx.IdentityCN)
|
||||
p.allow = make(map[string]struct{}, len(o.Allow))
|
||||
for _, a := range o.Allow {
|
||||
if a = strings.TrimSpace(a); a != "" {
|
||||
@@ -221,22 +145,6 @@ func (p *Policy) compileMTLS(o *config.AuthOptions, tlsCfg *tls.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func scramKey(o *config.AuthOptions) string {
|
||||
switch {
|
||||
case o.CredentialsFile != "":
|
||||
return "credentials_file"
|
||||
case o.TokenLifetimeMS != 0:
|
||||
return "token_lifetime_ms"
|
||||
case o.Username != "":
|
||||
return "username"
|
||||
case o.PasswordFile != "":
|
||||
return "password_file"
|
||||
case len(o.TrustedProxies) > 0:
|
||||
return "trusted_proxies"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Authorize checks the mtls certificate identity of a completed handshake. It
|
||||
// refuses under scram, so a listener that skipped its exchange fails closed.
|
||||
// A nil policy authorizes everything and yields the zero Identity.
|
||||
@@ -566,7 +474,7 @@ func (p *Policy) LogStartup(l *log.Logger, component, id string, trustNode bool)
|
||||
l.Warn("msg", w, "component", component, "instance_id", id)
|
||||
}
|
||||
}
|
||||
if p.role == RoleDialer {
|
||||
if p.side == config.Dialer {
|
||||
return
|
||||
}
|
||||
if p.BehindProxy() && p.listener.proxy.exposedHop() {
|
||||
@@ -687,7 +595,7 @@ func (p *Policy) Stats() map[string]any {
|
||||
d["auth_identity"] = p.identity
|
||||
d["auth_unrestricted"] = p.Unrestricted()
|
||||
}
|
||||
if p.role == RoleChainListener {
|
||||
if p.side == config.ChainListener {
|
||||
d["node_binding"] = p.binding
|
||||
}
|
||||
return d
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestPeerIdentityModes(t *testing.T) {
|
||||
|
||||
func TestNewDisabled(t *testing.T) {
|
||||
for _, o := range []*config.AuthOptions{nil, {}, {Type: MethodNone}} {
|
||||
p, err := New(o, nil, nil, RoleListener, TCP)
|
||||
p, err := New(o, nil, nil, config.Listener, TCP)
|
||||
if err != nil {
|
||||
t.Fatalf("New(%+v) error: %v", o, err)
|
||||
}
|
||||
@@ -122,41 +122,41 @@ func TestNewValidation(t *testing.T) {
|
||||
name string
|
||||
auth *config.AuthOptions
|
||||
tls *tls.Config
|
||||
role Role
|
||||
role config.Side
|
||||
transport Transport
|
||||
want string
|
||||
}{
|
||||
{"unknown type", &config.AuthOptions{Type: "kerberos"}, mtlsListenerTLS(), RoleListener, TCP, `type "kerberos"`},
|
||||
{"allow list without a type", &config.AuthOptions{Allow: []string{"edge-01"}}, mtlsListenerTLS(), RoleListener, TCP, "allow is set"},
|
||||
{"credentials without a type", &config.AuthOptions{CredentialsFile: f.creds}, mtlsListenerTLS(), RoleListener, TCP, "credentials_file is set"},
|
||||
{"no tls", &config.AuthOptions{Type: MethodMTLS}, nil, RoleListener, TCP, "requires tls.enabled"},
|
||||
{"no client_auth", &config.AuthOptions{Type: MethodMTLS}, &tls.Config{}, RoleListener, TCP, "requires tls.client_auth"},
|
||||
{"unknown identity", &config.AuthOptions{Type: MethodMTLS, Identity: "serial"}, mtlsListenerTLS(), RoleListener, TCP, `identity "serial"`},
|
||||
{"bad pattern", &config.AuthOptions{Type: MethodMTLS, AllowPatterns: []string{"^edge-("}}, mtlsListenerTLS(), RoleListener, TCP, "allow_patterns[0]"},
|
||||
{"unknown binding", &config.AuthOptions{Type: MethodMTLS, NodeBinding: "maybe"}, mtlsListenerTLS(), RoleChainListener, TCP, `node_binding "maybe"`},
|
||||
{"binding on plain listener", &config.AuthOptions{Type: MethodMTLS, NodeBinding: BindingForce}, mtlsListenerTLS(), RoleListener, TCP, "only to chain sources"},
|
||||
{"binding on dialer", &config.AuthOptions{Type: MethodMTLS, NodeBinding: BindingForce}, &tls.Config{}, RoleDialer, TCP, "only to chain sources"},
|
||||
{"dialer skips verify", &config.AuthOptions{Type: MethodMTLS}, &tls.Config{InsecureSkipVerify: true}, RoleDialer, TCP, "insecure_skip_verify"},
|
||||
{"mtls with a password", &config.AuthOptions{Type: MethodMTLS, PasswordFile: pw}, &tls.Config{}, RoleDialer, TCP, "password_file applies only"},
|
||||
{"scram without tls", listener(config.AuthOptions{}), nil, RoleListener, TCP, "requires tls.enabled"},
|
||||
{"scram without credentials", scram(config.AuthOptions{}), f.serverTLS, RoleListener, TCP, "requires credentials_file"},
|
||||
{"scram with an allow list", listener(config.AuthOptions{Allow: []string{"edge-01"}}), f.serverTLS, RoleListener, TCP, "credentials file is the allow list"},
|
||||
{"scram listener with a password", listener(config.AuthOptions{Username: "edge-01"}), f.serverTLS, RoleListener, TCP, "only to dialers"},
|
||||
{"scram certificate binding without client_auth", listener(config.AuthOptions{Identity: "cn"}), f.serverTLS, RoleListener, TCP, "requires tls.client_auth"},
|
||||
{"token lifetime on tcp", listener(config.AuthOptions{TokenLifetimeMS: 60000}), f.serverTLS, RoleListener, TCP, "only to HTTP listeners"},
|
||||
{"token lifetime too short", listener(config.AuthOptions{TokenLifetimeMS: 900}), f.serverTLS, RoleListener, HTTP, "outside"},
|
||||
{"token lifetime too long", listener(config.AuthOptions{TokenLifetimeMS: 1e13}), f.serverTLS, RoleListener, HTTP, "outside"},
|
||||
{"scram listener without a certificate", listener(config.AuthOptions{}), &tls.Config{}, RoleListener, TCP, "listener certificate"},
|
||||
{"scram dialer without a password", scram(config.AuthOptions{Username: "edge-01"}), &tls.Config{}, RoleDialer, TCP, "requires username and password_file"},
|
||||
{"scram dialer with credentials", dialer(config.AuthOptions{CredentialsFile: f.creds}), &tls.Config{}, RoleDialer, TCP, "only to listeners"},
|
||||
{"scram dialer skips verify", dialer(config.AuthOptions{}), &tls.Config{InsecureSkipVerify: true}, RoleDialer, TCP, "insecure_skip_verify"},
|
||||
{"trusted proxies without a type", &config.AuthOptions{TrustedProxies: proxies}, nil, RoleListener, HTTP, "trusted_proxies is set"},
|
||||
{"trusted proxies under mtls", &config.AuthOptions{Type: MethodMTLS, TrustedProxies: proxies}, mtlsListenerTLS(), RoleListener, HTTP, "trusted_proxies applies only"},
|
||||
{"trusted proxies on a chain source", listener(config.AuthOptions{TrustedProxies: proxies}), nil, RoleChainListener, HTTP, "only to the http sink"},
|
||||
{"trusted proxies on tcp", listener(config.AuthOptions{TrustedProxies: proxies}), nil, RoleListener, TCP, "only to the http sink"},
|
||||
{"trusted proxies with certificate binding", listener(config.AuthOptions{TrustedProxies: proxies, Identity: "cn"}), f.serverTLS, RoleListener, HTTP, "TLS-terminating proxy"},
|
||||
{"malformed trusted proxy", listener(config.AuthOptions{TrustedProxies: []string{"localhost"}}), nil, RoleListener, HTTP, "neither an address"},
|
||||
{"trusted proxies on a dialer", dialer(config.AuthOptions{TrustedProxies: proxies}), &tls.Config{}, RoleDialer, HTTP, "only to listeners"},
|
||||
{"unknown type", &config.AuthOptions{Type: "kerberos"}, mtlsListenerTLS(), config.Listener, TCP, `got "kerberos"`},
|
||||
{"allow list without a type", &config.AuthOptions{Allow: []string{"edge-01"}}, mtlsListenerTLS(), config.Listener, TCP, "allow is set"},
|
||||
{"credentials without a type", &config.AuthOptions{CredentialsFile: f.creds}, mtlsListenerTLS(), config.Listener, TCP, "credentials_file is set"},
|
||||
{"no tls", &config.AuthOptions{Type: MethodMTLS}, nil, config.Listener, TCP, "requires tls.enabled"},
|
||||
{"no client_auth", &config.AuthOptions{Type: MethodMTLS}, &tls.Config{}, config.Listener, TCP, "requires tls.client_auth"},
|
||||
{"unknown identity", &config.AuthOptions{Type: MethodMTLS, Identity: "serial"}, mtlsListenerTLS(), config.Listener, TCP, `got "serial"`},
|
||||
{"bad pattern", &config.AuthOptions{Type: MethodMTLS, AllowPatterns: []string{"^edge-("}}, mtlsListenerTLS(), config.Listener, TCP, "allow_patterns[0]"},
|
||||
{"unknown binding", &config.AuthOptions{Type: MethodMTLS, NodeBinding: "maybe"}, mtlsListenerTLS(), config.ChainListener, TCP, `got "maybe"`},
|
||||
{"binding on plain listener", &config.AuthOptions{Type: MethodMTLS, NodeBinding: BindingForce}, mtlsListenerTLS(), config.Listener, TCP, "only to chain sources"},
|
||||
{"binding on dialer", &config.AuthOptions{Type: MethodMTLS, NodeBinding: BindingForce}, &tls.Config{}, config.Dialer, TCP, "only to chain sources"},
|
||||
{"dialer skips verify", &config.AuthOptions{Type: MethodMTLS}, &tls.Config{InsecureSkipVerify: true}, config.Dialer, TCP, "insecure_skip_verify"},
|
||||
{"mtls with a password", &config.AuthOptions{Type: MethodMTLS, PasswordFile: pw}, &tls.Config{}, config.Dialer, TCP, "password_file applies only"},
|
||||
{"scram without tls", listener(config.AuthOptions{}), nil, config.Listener, TCP, "requires tls.enabled"},
|
||||
{"scram without credentials", scram(config.AuthOptions{}), f.serverTLS, config.Listener, TCP, "requires credentials_file"},
|
||||
{"scram with an allow list", listener(config.AuthOptions{Allow: []string{"edge-01"}}), f.serverTLS, config.Listener, TCP, "credentials file is the allow list"},
|
||||
{"scram listener with a password", listener(config.AuthOptions{Username: "edge-01"}), f.serverTLS, config.Listener, TCP, "only to dialers"},
|
||||
{"scram certificate binding without client_auth", listener(config.AuthOptions{Identity: "cn"}), f.serverTLS, config.Listener, TCP, "requires tls.client_auth"},
|
||||
{"token lifetime on tcp", listener(config.AuthOptions{TokenLifetimeMS: 60000}), f.serverTLS, config.Listener, TCP, "only to HTTP listeners"},
|
||||
{"token lifetime too short", listener(config.AuthOptions{TokenLifetimeMS: 900}), f.serverTLS, config.Listener, HTTP, "must be from 10000 to 86400000"},
|
||||
{"token lifetime too long", listener(config.AuthOptions{TokenLifetimeMS: 1e13}), f.serverTLS, config.Listener, HTTP, "must be from 10000 to 86400000"},
|
||||
{"scram listener without a certificate", listener(config.AuthOptions{}), &tls.Config{}, config.Listener, TCP, "listener certificate"},
|
||||
{"scram dialer without a password", scram(config.AuthOptions{Username: "edge-01"}), &tls.Config{}, config.Dialer, TCP, "requires username and password_file"},
|
||||
{"scram dialer with credentials", dialer(config.AuthOptions{CredentialsFile: f.creds}), &tls.Config{}, config.Dialer, TCP, "only to listeners"},
|
||||
{"scram dialer skips verify", dialer(config.AuthOptions{}), &tls.Config{InsecureSkipVerify: true}, config.Dialer, TCP, "insecure_skip_verify"},
|
||||
{"trusted proxies without a type", &config.AuthOptions{TrustedProxies: proxies}, nil, config.Listener, HTTP, "trusted_proxies is set"},
|
||||
{"trusted proxies under mtls", &config.AuthOptions{Type: MethodMTLS, TrustedProxies: proxies}, mtlsListenerTLS(), config.Listener, HTTP, "trusted_proxies applies only"},
|
||||
{"trusted proxies on a chain source", listener(config.AuthOptions{TrustedProxies: proxies}), nil, config.ChainListener, HTTP, "only to the http sink"},
|
||||
{"trusted proxies on tcp", listener(config.AuthOptions{TrustedProxies: proxies}), nil, config.Listener, TCP, "only to the http sink"},
|
||||
{"trusted proxies with certificate binding", listener(config.AuthOptions{TrustedProxies: proxies, Identity: "cn"}), f.serverTLS, config.Listener, HTTP, "TLS-terminating proxy"},
|
||||
{"malformed trusted proxy", listener(config.AuthOptions{TrustedProxies: []string{"localhost"}}), nil, config.Listener, HTTP, "neither an address"},
|
||||
{"trusted proxies on a dialer", dialer(config.AuthOptions{TrustedProxies: proxies}), &tls.Config{}, config.Dialer, HTTP, "only to listeners"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -171,16 +171,16 @@ func TestNewValidation(t *testing.T) {
|
||||
pinned := &tls.Config{InsecureSkipVerify: true, VerifyPeerCertificate: func([][]byte, [][]*x509.Certificate) error { return nil }}
|
||||
for _, o := range []*config.AuthOptions{{Type: MethodMTLS}, dialer(config.AuthOptions{})} {
|
||||
for _, c := range []*tls.Config{{}, pinned} {
|
||||
if _, err := New(o, c, nil, RoleDialer, TCP); err != nil {
|
||||
if _, err := New(o, c, nil, config.Dialer, TCP); err != nil {
|
||||
t.Fatalf("dialer policy %s rejected: %v", o.Type, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := New(listener(config.AuthOptions{TokenLifetimeMS: 60000}), f.serverTLS, nil, RoleListener, HTTP); err != nil {
|
||||
if _, err := New(listener(config.AuthOptions{TokenLifetimeMS: 60000}), f.serverTLS, nil, config.Listener, HTTP); err != nil {
|
||||
t.Fatalf("scram listener rejected: %v", err)
|
||||
}
|
||||
// Behind proxies TLS ends at the proxy, so the hop may be plaintext
|
||||
if _, err := New(listener(config.AuthOptions{TrustedProxies: proxies}), nil, nil, RoleListener, HTTP); err != nil {
|
||||
if _, err := New(listener(config.AuthOptions{TrustedProxies: proxies}), nil, nil, config.Listener, HTTP); err != nil {
|
||||
t.Fatalf("proxy-mode listener rejected: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,7 @@ func TestAuthorizeMatching(t *testing.T) {
|
||||
Type: MethodMTLS,
|
||||
Allow: []string{"edge-01", " edge-02 "},
|
||||
AllowPatterns: []string{`^relay-\d{2}$`},
|
||||
}, mtlsListenerTLS(), nil, RoleListener, TCP)
|
||||
}, mtlsListenerTLS(), nil, config.Listener, TCP)
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
@@ -232,7 +232,7 @@ func TestAuthorizeMatching(t *testing.T) {
|
||||
// Empty allow and allow_patterns admits any CA-vouched identity, but still
|
||||
// records it and still refuses a certificate with no usable identity field
|
||||
func TestAuthorizeUnrestricted(t *testing.T) {
|
||||
p, err := New(&config.AuthOptions{Type: MethodMTLS}, mtlsListenerTLS(), nil, RoleListener, TCP)
|
||||
p, err := New(&config.AuthOptions{Type: MethodMTLS}, mtlsListenerTLS(), nil, config.Listener, TCP)
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
@@ -256,7 +256,7 @@ func TestAuthorizeUnrestricted(t *testing.T) {
|
||||
|
||||
func TestResolveNodeBindings(t *testing.T) {
|
||||
newChain := func(binding string) *Policy {
|
||||
p, err := New(&config.AuthOptions{Type: MethodMTLS, NodeBinding: binding}, mtlsListenerTLS(), nil, RoleChainListener, TCP)
|
||||
p, err := New(&config.AuthOptions{Type: MethodMTLS, NodeBinding: binding}, mtlsListenerTLS(), nil, config.ChainListener, TCP)
|
||||
if err != nil {
|
||||
t.Fatalf("New(%q): %v", binding, err)
|
||||
}
|
||||
@@ -332,7 +332,7 @@ func TestIdentityApply(t *testing.T) {
|
||||
|
||||
func TestVerifyConnectionPinsServer(t *testing.T) {
|
||||
p, err := New(&config.AuthOptions{Type: MethodMTLS, Allow: []string{"relay.internal"}},
|
||||
&tls.Config{}, nil, RoleDialer, TCP)
|
||||
&tls.Config{}, nil, config.Dialer, TCP)
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lixenwraith/logwisp/internal/config"
|
||||
"github.com/lixenwraith/logwisp/internal/netacl"
|
||||
|
||||
"github.com/lixenwraith/auth"
|
||||
@@ -34,7 +35,7 @@ type proxyMode struct {
|
||||
func parseProxies(entries []string) (*proxyMode, error) {
|
||||
m := &proxyMode{}
|
||||
for _, e := range entries {
|
||||
prefix, err := netacl.Parse(e, "tcp")
|
||||
prefix, err := config.ParsePrefix(e, "tcp")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("auth: trusted_proxies entry %q: %w", e, err)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func (f forwarding) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
// proxyListener is a plaintext http sink policy behind a proxy on loopback
|
||||
func (f *fixture) proxyListener(t *testing.T) (*Policy, *httptest.Server) {
|
||||
t.Helper()
|
||||
l := f.listener(t, config.AuthOptions{TrustedProxies: []string{"127.0.0.1"}}, nil, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{TrustedProxies: []string{"127.0.0.1"}}, nil, config.Listener, HTTP)
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST "+chain.AuthPath, func(w http.ResponseWriter, r *http.Request) { l.ServeAuth(w, r) })
|
||||
mux.HandleFunc("GET /protected", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -49,7 +49,7 @@ func (f *fixture) proxyListener(t *testing.T) (*Policy, *httptest.Server) {
|
||||
// when every hop is inside a proxy range.
|
||||
func TestProxyModeTrustsOnlyItsProxies(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{TrustedProxies: []string{"127.0.0.1", "10.0.0.0/8"}}, nil, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{TrustedProxies: []string{"127.0.0.1", "10.0.0.0/8"}}, nil, config.Listener, HTTP)
|
||||
for _, tc := range []struct{ name, peer, xff, proto, want string }{
|
||||
{"untrusted peer", "192.0.2.1:5000", "203.0.113.7", "https", ""},
|
||||
{"no proto", "127.0.0.1:5000", "203.0.113.7", "", ""},
|
||||
@@ -91,7 +91,7 @@ func TestForwardedClientIsRefusedByTheRules(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l, err := New(&config.AuthOptions{Type: MethodSCRAM, CredentialsFile: f.creds, TrustedProxies: []string{"127.0.0.1"}},
|
||||
nil, acl, RoleListener, HTTP)
|
||||
nil, acl, config.Listener, HTTP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -195,7 +195,7 @@ func TestBrowserSessionBehindProxy(t *testing.T) {
|
||||
t.Fatalf("logins = %d", l.allowed.Load())
|
||||
}
|
||||
|
||||
direct := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, HTTP)
|
||||
direct := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, HTTP)
|
||||
ds, _ := f.httpListener(t, direct, f.serverTLS)
|
||||
client = f.httpClient(f.dialer(t, "edge-01", "edge-01-secret"))
|
||||
proof := &auth.ClientFinalRequest{FullNonce: strings.Repeat("n", 32), ClientProof: "AAAA"}
|
||||
@@ -210,7 +210,7 @@ func TestBrowserSessionBehindProxy(t *testing.T) {
|
||||
// only unbound proofs (lw auth token --unbound) log in.
|
||||
func TestOnlyUnboundLoginsBehindProxy(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{TrustedProxies: []string{"127.0.0.1"}}, f.serverTLS, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{TrustedProxies: []string{"127.0.0.1"}}, f.serverTLS, config.Listener, HTTP)
|
||||
srv, _ := f.httpListener(t, l, f.serverTLS)
|
||||
for _, unbound := range []bool{false, true} {
|
||||
d := f.dialer(t, "edge-01", "edge-01-secret")
|
||||
|
||||
+4
-42
@@ -34,13 +34,8 @@ const (
|
||||
ExchangeTimeout = 10 * time.Second
|
||||
// DefaultTokenLifetime is how long a bearer token from /auth stays valid
|
||||
DefaultTokenLifetime = 15 * time.Minute
|
||||
// Token lifetime bounds: exp has whole seconds, a sub-second expires_in
|
||||
// reads as unknown, and 10 s leaves room for renewal ahead of expiry
|
||||
MinTokenLifetime = 10 * time.Second
|
||||
MaxTokenLifetime = 24 * time.Hour
|
||||
|
||||
maxAuthLine = 4096 // pre-auth lines and /auth bodies come from unauthenticated peers
|
||||
streamBuffer = 64 * 1024
|
||||
maxAuthLine = 4096 // pre-auth lines and /auth bodies come from unauthenticated peers
|
||||
streamBuffer = 64 * 1024
|
||||
|
||||
limitBurst = 10 // failed or abandoned exchanges per address before throttling
|
||||
limitRate = 1 // per second
|
||||
@@ -200,36 +195,11 @@ type scramListener struct {
|
||||
}
|
||||
|
||||
func (p *Policy) compileSCRAM(o *config.AuthOptions, tlsCfg *tls.Config) error {
|
||||
if len(o.Allow) > 0 || len(o.AllowPatterns) > 0 {
|
||||
return fmt.Errorf("auth: allow and allow_patterns apply only to type %q; the credentials file is the allow list", MethodMTLS)
|
||||
}
|
||||
if p.role == RoleDialer {
|
||||
if p.side == config.Dialer {
|
||||
return p.compileSCRAMDialer(o)
|
||||
}
|
||||
switch {
|
||||
case o.Username != "" || o.PasswordFile != "":
|
||||
return errors.New("auth: username and password_file apply only to dialers")
|
||||
case o.CredentialsFile == "":
|
||||
return fmt.Errorf("auth: type %q requires credentials_file", MethodSCRAM)
|
||||
case o.TokenLifetimeMS != 0 && (o.TokenLifetimeMS < MinTokenLifetime.Milliseconds() || o.TokenLifetimeMS > MaxTokenLifetime.Milliseconds()):
|
||||
return fmt.Errorf("auth: token_lifetime_ms %d is outside %d (%s) to %d (%s)", o.TokenLifetimeMS,
|
||||
MinTokenLifetime.Milliseconds(), MinTokenLifetime, MaxTokenLifetime.Milliseconds(), MaxTokenLifetime)
|
||||
case o.TokenLifetimeMS > 0 && p.transport != HTTP:
|
||||
return errors.New("auth: token_lifetime_ms applies only to HTTP listeners")
|
||||
case len(o.TrustedProxies) > 0 && (p.role != RoleListener || p.transport != HTTP):
|
||||
return errors.New("auth: trusted_proxies applies only to the http sink")
|
||||
case len(o.TrustedProxies) > 0 && o.Identity != "":
|
||||
return errors.New("auth: identity binds a client certificate, which a TLS-terminating proxy does not pass on; drop it or trusted_proxies")
|
||||
}
|
||||
if o.Identity != "" {
|
||||
// Binds the certificate to the user: a peer needs its own of both
|
||||
if tlsCfg.ClientAuth != tls.RequireAndVerifyClientCert {
|
||||
return fmt.Errorf("auth: identity under type %q binds the client certificate to the user and requires tls.client_auth", MethodSCRAM)
|
||||
}
|
||||
var err error
|
||||
if p.identity, err = identityMode(o.Identity); err != nil {
|
||||
return err
|
||||
}
|
||||
p.identity = o.Identity // binds the certificate to the user
|
||||
}
|
||||
l := &scramListener{limit: netacl.NewTable(limitBurst, limitRate, limitPending)}
|
||||
var err error
|
||||
@@ -573,14 +543,6 @@ type scramDialer struct {
|
||||
}
|
||||
|
||||
func (p *Policy) compileSCRAMDialer(o *config.AuthOptions) error {
|
||||
switch {
|
||||
case o.CredentialsFile != "", o.TokenLifetimeMS != 0, len(o.TrustedProxies) > 0:
|
||||
return errors.New("auth: credentials_file, token_lifetime_ms and trusted_proxies apply only to listeners")
|
||||
case o.Identity != "":
|
||||
return fmt.Errorf("auth: identity on a dialer pins the server and applies only to type %q", MethodMTLS)
|
||||
case o.Username == "" || o.PasswordFile == "":
|
||||
return fmt.Errorf("auth: type %q on a dialer requires username and password_file", MethodSCRAM)
|
||||
}
|
||||
password, err := ReadPassword(o.PasswordFile)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -99,7 +99,7 @@ func (f *fixture) pool(t *testing.T) *x509.CertPool {
|
||||
return pool
|
||||
}
|
||||
|
||||
func (f *fixture) listener(t *testing.T, o config.AuthOptions, tlsCfg *tls.Config, role Role, transport Transport) *Policy {
|
||||
func (f *fixture) listener(t *testing.T, o config.AuthOptions, tlsCfg *tls.Config, role config.Side, transport Transport) *Policy {
|
||||
t.Helper()
|
||||
o.Type, o.CredentialsFile = MethodSCRAM, f.creds
|
||||
p, err := New(&o, tlsCfg, nil, role, transport)
|
||||
@@ -116,7 +116,7 @@ func (f *fixture) listener(t *testing.T, o config.AuthOptions, tlsCfg *tls.Confi
|
||||
func (f *fixture) dialer(t *testing.T, user, password string) *Policy {
|
||||
t.Helper()
|
||||
pw := f.write(t, user+"-"+strings.ReplaceAll(password, "/", "_")+".pass", password+"\n")
|
||||
p, err := New(&config.AuthOptions{Type: MethodSCRAM, Username: user, PasswordFile: pw}, f.clientTLS, nil, RoleDialer, TCP)
|
||||
p, err := New(&config.AuthOptions{Type: MethodSCRAM, Username: user, PasswordFile: pw}, f.clientTLS, nil, config.Dialer, TCP)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -167,7 +167,7 @@ func (f *fixture) connect(t *testing.T, listener *Policy, serverTLS *tls.Config,
|
||||
// as the final message reach the dialer through the reader Greet returns.
|
||||
func TestTCPExchangeAdmitsAndKeepsTheStream(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleChainListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.ChainListener, TCP)
|
||||
res, r, err := f.connect(t, l, f.serverTLS, f.dialer(t, "edge-01", "edge-01-secret"), func(a *Admission) {
|
||||
a.final = append(a.final, "first entry\n"...) // as TCP would coalesce them
|
||||
if err := a.Accept(); err != nil {
|
||||
@@ -190,7 +190,7 @@ func TestTCPExchangeAdmitsAndKeepsTheStream(t *testing.T) {
|
||||
// probe cannot tell which accounts exist.
|
||||
func TestTCPExchangeRefusesWrongPasswordAndUnknownUserAlike(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, TCP)
|
||||
var seen []string
|
||||
for _, d := range []*Policy{f.dialer(t, "edge-01", "not-the-secret"), f.dialer(t, "edge-99", "edge-01-secret")} {
|
||||
res, _, err := f.connect(t, l, f.serverTLS, d, nil)
|
||||
@@ -211,7 +211,7 @@ func TestTCPExchangeRefusesAnotherCertificate(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
other := f.serverTLS.Clone()
|
||||
other.Certificates = []tls.Certificate{f.serverLeaf(t, "relay-b")}
|
||||
l := f.listener(t, config.AuthOptions{}, other, RoleListener, TCP) // binds to relay-b
|
||||
l := f.listener(t, config.AuthOptions{}, other, config.Listener, TCP) // binds to relay-b
|
||||
res, _, err := f.connect(t, l, f.serverTLS, f.dialer(t, "edge-01", "edge-01-secret"), nil)
|
||||
if !errors.Is(err, ErrRefused) || !errors.Is(res.err, ErrRefused) {
|
||||
t.Fatalf("dialer %v, listener %v; want both refused", err, res.err)
|
||||
@@ -225,7 +225,7 @@ func TestTCPExchangeRefusesAnotherCertificate(t *testing.T) {
|
||||
// without scram answers credentials instead of leaving the dialer waiting.
|
||||
func TestHelloAndPolicyMustAgree(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleChainListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.ChainListener, TCP)
|
||||
if res, _, _ := f.connect(t, l, f.serverTLS, nil, nil); !errors.Is(res.err, ErrRefused) {
|
||||
t.Fatalf("scram listener admitted a plain hello: %v", res.err)
|
||||
}
|
||||
@@ -264,7 +264,7 @@ func TestGreetEndsWithContext(t *testing.T) {
|
||||
// and the limiter at once instead of holding a slot until it times out.
|
||||
func TestAbandonedExchangeReleasesItsSlot(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, TCP)
|
||||
sc, cc := net.Pipe()
|
||||
server, client := tls.Server(sc, f.serverTLS), tls.Client(cc, f.clientTLS)
|
||||
t.Cleanup(func() { sc.Close(); cc.Close() })
|
||||
@@ -307,7 +307,7 @@ func TestAbandonedExchangeReleasesItsSlot(t *testing.T) {
|
||||
// a reason and never as a final message it could take for admission.
|
||||
func TestRejectAfterExchangeSendsReason(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleChainListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.ChainListener, TCP)
|
||||
_, _, err := f.connect(t, l, f.serverTLS, f.dialer(t, "edge-01", "edge-01-secret"), func(a *Admission) {
|
||||
a.Reject("node label rejected")
|
||||
})
|
||||
@@ -323,7 +323,7 @@ func TestCertificateBindsToUser(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
mtls := f.serverTLS.Clone()
|
||||
mtls.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
l := f.listener(t, config.AuthOptions{Identity: "cn"}, mtls, RoleListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{Identity: "cn"}, mtls, config.Listener, TCP)
|
||||
if res, _, err := f.connect(t, l, mtls, f.dialer(t, "edge-01", "edge-01-secret"), nil); err != nil || res.err != nil {
|
||||
t.Fatalf("own certificate and password: dialer %v, listener %v", err, res.err)
|
||||
}
|
||||
@@ -331,7 +331,7 @@ func TestCertificateBindsToUser(t *testing.T) {
|
||||
t.Fatalf("edge-01's certificate admitted user edge-02: %v", res.err)
|
||||
}
|
||||
|
||||
h := f.listener(t, config.AuthOptions{Identity: "cn"}, mtls, RoleListener, HTTP)
|
||||
h := f.listener(t, config.AuthOptions{Identity: "cn"}, mtls, config.Listener, HTTP)
|
||||
srv, _ := f.httpListener(t, h, mtls)
|
||||
d := f.dialer(t, "edge-01", "edge-01-secret")
|
||||
token, err := d.Token(t.Context(), f.httpClient(d), srv.URL)
|
||||
@@ -354,7 +354,7 @@ func TestUnknownUserSaltSurvivesRestart(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
var salts []string
|
||||
for range 2 {
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, TCP)
|
||||
c, err := l.listener.server.Load().ProcessClientFirstMessage("nobody", strings.Repeat("n", 32))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -370,7 +370,7 @@ func TestUnknownUserSaltSurvivesRestart(t *testing.T) {
|
||||
// refused before it is parsed.
|
||||
func TestPreAuthInputIsCapped(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, TCP)
|
||||
sc, cc := net.Pipe()
|
||||
server, client := tls.Server(sc, f.serverTLS), tls.Client(cc, f.clientTLS)
|
||||
t.Cleanup(func() { sc.Close(); cc.Close() })
|
||||
@@ -389,7 +389,7 @@ func TestPreAuthInputIsCapped(t *testing.T) {
|
||||
t.Fatalf("oversized hello: %v", err)
|
||||
}
|
||||
|
||||
h := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, HTTP)
|
||||
h := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, HTTP)
|
||||
srv, _ := f.httpListener(t, h, f.serverTLS)
|
||||
hc := &http.Client{Transport: &http.Transport{TLSClientConfig: f.clientTLS}}
|
||||
resp, err := hc.Post(srv.URL+chain.AuthPath, "application/json", bytes.NewReader(bytes.Repeat([]byte(" "), 2*maxAuthLine)))
|
||||
@@ -406,7 +406,7 @@ func TestPreAuthInputIsCapped(t *testing.T) {
|
||||
// proof, so a hostile server cannot obtain a cheaply guessable one.
|
||||
func TestDialerRefusesACheapChallenge(t *testing.T) {
|
||||
f := newFixture(t) // verifiers at t=1, m=64 KiB
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, TCP)
|
||||
d := f.dialer(t, "edge-01", "edge-01-secret")
|
||||
minArgonTime, minArgonMemory = auth.DefaultArgonTime, auth.DefaultArgonMemory
|
||||
t.Cleanup(func() { minArgonTime, minArgonMemory = 1, 64 })
|
||||
@@ -422,7 +422,7 @@ func TestDialerRefusesACheapChallenge(t *testing.T) {
|
||||
// answers "too many attempts" without running the exchange.
|
||||
func TestOnlyFailedAttemptsAreThrottled(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, TCP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, TCP)
|
||||
right := f.dialer(t, "edge-01", "edge-01-secret")
|
||||
for i := range limitBurst + 1 {
|
||||
if _, _, err := f.connect(t, l, f.serverTLS, right, nil); err != nil {
|
||||
@@ -481,7 +481,7 @@ func get(t *testing.T, client *http.Client, url string, prepare func(*http.Reque
|
||||
// header; a refused token can be dropped and replaced by a fresh login.
|
||||
func TestHTTPTokenFlow(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, HTTP)
|
||||
srv, _ := f.httpListener(t, l, f.serverTLS)
|
||||
d := f.dialer(t, "edge-01", "edge-01-secret")
|
||||
client := f.httpClient(d)
|
||||
@@ -494,7 +494,7 @@ func TestHTTPTokenFlow(t *testing.T) {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("with a token: %d", resp.StatusCode)
|
||||
}
|
||||
other := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, HTTP)
|
||||
other := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, HTTP)
|
||||
foreignSrv, _ := f.httpListener(t, other, f.serverTLS)
|
||||
foreign, err := f.dialer(t, "edge-01", "edge-01-secret").Token(t.Context(), f.httpClient(d), foreignSrv.URL)
|
||||
if err != nil {
|
||||
@@ -520,7 +520,7 @@ func TestHTTPTokenFlow(t *testing.T) {
|
||||
// no token stored, whatever token it offers.
|
||||
func TestForgedFinalYieldsNoToken(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, HTTP)
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST "+chain.AuthPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
body := new(bytes.Buffer)
|
||||
@@ -550,7 +550,7 @@ func TestForgedFinalYieldsNoToken(t *testing.T) {
|
||||
// (through a proxy) included.
|
||||
func TestPinnedCertificateAcrossConnections(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, HTTP)
|
||||
a, _ := f.httpListener(t, l, f.serverTLS)
|
||||
otherTLS := f.serverTLS.Clone()
|
||||
otherTLS.Certificates = []tls.Certificate{f.serverLeaf(t, "relay-b")}
|
||||
@@ -589,7 +589,7 @@ func TestPinnedCertificateAcrossConnections(t *testing.T) {
|
||||
// its pending slot: otherwise four of them would lock the address out.
|
||||
func TestRefusedHellosFreeTheirSlot(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, HTTP)
|
||||
srv, _ := f.httpListener(t, l, f.serverTLS)
|
||||
client := &http.Client{Transport: &http.Transport{TLSClientConfig: f.clientTLS}}
|
||||
hello := func(user string) int {
|
||||
@@ -660,7 +660,7 @@ func TestCredentialsFileValidation(t *testing.T) {
|
||||
// exchange admits nobody rather than everybody.
|
||||
func TestScramListenerAuthorizeFailsClosed(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{}, f.serverTLS, config.Listener, HTTP)
|
||||
if _, err := l.Authorize(&tls.ConnectionState{}); !errors.Is(err, ErrRefused) {
|
||||
t.Fatalf("Authorize under scram = %v, want a refusal", err)
|
||||
}
|
||||
@@ -670,7 +670,7 @@ func TestScramListenerAuthorizeFailsClosed(t *testing.T) {
|
||||
// refused request each lifetime.
|
||||
func TestTokenIsRenewedBeforeExpiry(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{TokenLifetimeMS: MinTokenLifetime.Milliseconds()}, f.serverTLS, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{TokenLifetimeMS: 10000}, f.serverTLS, config.Listener, HTTP)
|
||||
srv, _ := f.httpListener(t, l, f.serverTLS)
|
||||
d := f.dialer(t, "edge-01", "edge-01-secret")
|
||||
client := f.httpClient(d)
|
||||
@@ -686,7 +686,7 @@ func TestTokenIsRenewedBeforeExpiry(t *testing.T) {
|
||||
t.Fatalf("logins within the lifetime = %d, want 1", n)
|
||||
}
|
||||
ahead := time.Until(time.Unix(0, d.dialer.renewAt.Load()))
|
||||
if ahead <= 0 || ahead > MinTokenLifetime-5*time.Second {
|
||||
if ahead <= 0 || ahead > 5*time.Second {
|
||||
t.Fatalf("renewal scheduled %v ahead, want within lifetime - 5 s", ahead)
|
||||
}
|
||||
d.dialer.renewAt.Store(time.Now().UnixNano()) // the renewal point arrives
|
||||
@@ -701,7 +701,7 @@ func TestTokenIsRenewedBeforeExpiry(t *testing.T) {
|
||||
// any other refusal, or the token's expiry, ends it.
|
||||
func TestPutOffRenewalKeepsTheValidToken(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
l := f.listener(t, config.AuthOptions{TokenLifetimeMS: MinTokenLifetime.Milliseconds()}, f.serverTLS, RoleListener, HTTP)
|
||||
l := f.listener(t, config.AuthOptions{TokenLifetimeMS: 10000}, f.serverTLS, config.Listener, HTTP)
|
||||
srv, _ := f.httpListener(t, l, f.serverTLS)
|
||||
// An impostor with another certificate of the same CA
|
||||
var answer, stolen atomic.Int64
|
||||
|
||||
Reference in new issue
Block a user