Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dc7636ace |
@@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/fx"
|
||||
@@ -80,14 +79,3 @@ func (d *Database) ExportSetBannerOut(w io.Writer) {
|
||||
func DummyPasswordHashForTest() string {
|
||||
return dummyPasswordHash()
|
||||
}
|
||||
|
||||
// HashAtShippedCostForTest makes HashPassword hash at the shipped
|
||||
// memory cost until t ends. t must not run in parallel with other
|
||||
// tests, which would hash at that cost alongside it.
|
||||
func HashAtShippedCostForTest(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
hashAtShippedCostInTest = true
|
||||
|
||||
t.Cleanup(func() { hashAtShippedCostInTest = false })
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
@@ -64,30 +63,10 @@ func DefaultPasswordConfig() *PasswordConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// testArgon2Memory is the Argon2id memory cost, in KiB, that a test
|
||||
// binary hashes with: 1 MB instead of the shipped 64 MB. Every test
|
||||
// that starts a database hashes the bootstrap admin password, dozens
|
||||
// of them run in parallel, and under the race detector each 64 MB hash
|
||||
// holds about 150 MB. VerifyPassword reads the cost from the hash it
|
||||
// checks, so verification follows.
|
||||
const testArgon2Memory = 1024
|
||||
|
||||
// hashAtShippedCostInTest makes a test binary hash at the shipped
|
||||
// memory cost. Only TestHashPassword_ShippedParameters sets it.
|
||||
//
|
||||
//nolint:gochecknoglobals // set by one test, see above
|
||||
var hashAtShippedCostInTest bool
|
||||
|
||||
// HashPassword generates an Argon2id hash of the password. A binary
|
||||
// built by go test hashes at testArgon2Memory; one built by go build
|
||||
// always hashes at the defaults.
|
||||
// HashPassword generates an Argon2id hash of the password
|
||||
func HashPassword(password string) (string, error) {
|
||||
config := DefaultPasswordConfig()
|
||||
|
||||
if testing.Testing() && !hashAtShippedCostInTest {
|
||||
config.Memory = testArgon2Memory
|
||||
}
|
||||
|
||||
// Generate a salt
|
||||
salt := make([]byte, config.SaltLen)
|
||||
|
||||
|
||||
@@ -192,39 +192,6 @@ func TestHashPasswordUniqueness(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestHashPassword_ShippedParameters hashes and verifies through
|
||||
// HashPassword at the shipped Argon2id parameters. Every other test
|
||||
// hashes at the lower memory cost a test binary uses, so this is the
|
||||
// one that keeps production hashing covered. One hash and one
|
||||
// verification: each costs 64 MB.
|
||||
//
|
||||
//nolint:paralleltest // changes the hashing cost for the whole binary
|
||||
func TestHashPassword_ShippedParameters(t *testing.T) {
|
||||
database.HashAtShippedCostForTest(t)
|
||||
|
||||
password := "correct horse battery staple"
|
||||
|
||||
hash, err := database.HashPassword(password)
|
||||
if err != nil {
|
||||
t.Fatalf("hashing with the shipped parameters: %v", err)
|
||||
}
|
||||
|
||||
const shipped = "$argon2id$v=19$m=65536,t=1,p=4$"
|
||||
|
||||
if !strings.HasPrefix(hash, shipped) {
|
||||
t.Errorf("hash = %q, want prefix %q", hash, shipped)
|
||||
}
|
||||
|
||||
valid, err := database.VerifyPassword(password, hash)
|
||||
if err != nil {
|
||||
t.Fatalf("VerifyPassword() error = %v", err)
|
||||
}
|
||||
|
||||
if !valid {
|
||||
t.Error("VerifyPassword() returned false for correct password")
|
||||
}
|
||||
}
|
||||
|
||||
// TestVerifyDummyPassword_DoesRealWork covers the anti-enumeration
|
||||
// path. Login charges an unknown username a verification against a
|
||||
// dummy hash so that a nonexistent account is not answered in
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -533,67 +534,53 @@ func TestAlwaysBlockedNetworks_PinnedSet(t *testing.T) {
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
// TestDefaultBlocklist_PinnedSet pins each list of the default
|
||||
// blocklist on its own, the private and reserved ranges in
|
||||
// blockedNetworks and the public addresses in
|
||||
// blockedPublicNetworks, so moving an entry from one list to the
|
||||
// other fails it. For the first address of each entry it then
|
||||
// checks that the default guard refuses it, and that listing the
|
||||
// entry in ALLOWED_EGRESS_CIDRS opens it unless the unconditional
|
||||
// set holds that address.
|
||||
// TestDefaultBlocklist_PinnedSet pins the default blocklist, its
|
||||
// private and reserved ranges and its public addresses together,
|
||||
// and how ALLOWED_EGRESS_CIDRS opens each entry: listing an entry
|
||||
// opens it unless the unconditional set also holds it.
|
||||
func TestDefaultBlocklist_PinnedSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// public marks an entry of blockedPublicNetworks; every other
|
||||
// entry belongs in blockedNetworks.
|
||||
tests := []struct {
|
||||
cidr string
|
||||
public bool
|
||||
reopenable bool
|
||||
}{
|
||||
{cidr: "127.0.0.0/8", reopenable: true},
|
||||
{cidr: "10.0.0.0/8", reopenable: true},
|
||||
{cidr: "172.16.0.0/12", reopenable: true},
|
||||
{cidr: "192.168.0.0/16", reopenable: true},
|
||||
{cidr: linkLocalIPv4, reopenable: false},
|
||||
{cidr: "0.0.0.0/8", reopenable: true},
|
||||
{cidr: "100.64.0.0/10", reopenable: true},
|
||||
{cidr: "192.0.0.0/24", reopenable: true},
|
||||
{cidr: "192.0.2.0/24", reopenable: true},
|
||||
{cidr: "198.18.0.0/15", reopenable: true},
|
||||
{cidr: "198.51.100.0/24", reopenable: true},
|
||||
{cidr: "203.0.113.0/24", reopenable: true},
|
||||
{cidr: "224.0.0.0/4", reopenable: true},
|
||||
{cidr: "240.0.0.0/4", reopenable: true},
|
||||
{cidr: "::1/128", reopenable: true},
|
||||
{cidr: "fc00::/7", reopenable: true},
|
||||
{cidr: "fe80::/10", reopenable: false},
|
||||
{cidr: "168.63.129.16/32", public: true, reopenable: true},
|
||||
{"127.0.0.0/8", true},
|
||||
{"10.0.0.0/8", true},
|
||||
{"172.16.0.0/12", true},
|
||||
{"192.168.0.0/16", true},
|
||||
{linkLocalIPv4, false},
|
||||
{"0.0.0.0/8", true},
|
||||
{"100.64.0.0/10", true},
|
||||
{"192.0.0.0/24", true},
|
||||
{"192.0.2.0/24", true},
|
||||
{"198.18.0.0/15", true},
|
||||
{"198.51.100.0/24", true},
|
||||
{"203.0.113.0/24", true},
|
||||
{"224.0.0.0/4", true},
|
||||
{"240.0.0.0/4", true},
|
||||
{"::1/128", true},
|
||||
{"fc00::/7", true},
|
||||
{"fe80::/10", false},
|
||||
{"168.63.129.16/32", true},
|
||||
}
|
||||
|
||||
wantPrivate := make([]string, 0, len(tests))
|
||||
wantPublic := make([]string, 0, len(tests))
|
||||
|
||||
want := make([]string, 0, len(tests))
|
||||
for _, tt := range tests {
|
||||
if tt.public {
|
||||
wantPublic = append(wantPublic, tt.cidr)
|
||||
} else {
|
||||
wantPrivate = append(wantPrivate, tt.cidr)
|
||||
}
|
||||
want = append(want, tt.cidr)
|
||||
}
|
||||
|
||||
gotPrivate := make([]string, 0, len(tests))
|
||||
for _, n := range delivery.ExportBlockedNetworks() {
|
||||
gotPrivate = append(gotPrivate, n.String())
|
||||
nets := slices.Concat(
|
||||
delivery.ExportBlockedNetworks(),
|
||||
delivery.ExportBlockedPublicNetworks(),
|
||||
)
|
||||
|
||||
got := make([]string, 0, len(nets))
|
||||
for _, n := range nets {
|
||||
got = append(got, n.String())
|
||||
}
|
||||
|
||||
gotPublic := make([]string, 0, len(tests))
|
||||
for _, n := range delivery.ExportBlockedPublicNetworks() {
|
||||
gotPublic = append(gotPublic, n.String())
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, wantPrivate, gotPrivate, "blockedNetworks")
|
||||
assert.ElementsMatch(t, wantPublic, gotPublic, "blockedPublicNetworks")
|
||||
assert.ElementsMatch(t, want, got)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.cidr, func(t *testing.T) {
|
||||
|
||||
@@ -140,7 +140,7 @@ func (n *noopEvictor) EvictWebhook(string) {}
|
||||
// and the database, exactly as internal/handlers builds them.
|
||||
//
|
||||
// One application per test function, not per case: every start that
|
||||
// finds no account seeds one with an Argon2id hash, and this package's
|
||||
// finds no account seeds one at 64 MB of Argon2id, and this package's
|
||||
// budget is not the place to spend that repeatedly.
|
||||
func newServerApp(
|
||||
t *testing.T, dir string,
|
||||
|
||||
+1
-6
@@ -22,11 +22,6 @@
|
||||
# The one figure above 90s is GOMAXPROCS 1, a synthetic core floor rather than
|
||||
# a condition CI runs under. If a CPU-limited runner ever puts a real run near
|
||||
# 67s, that is the datum to revisit the org figure with.
|
||||
#
|
||||
# -p 4 -parallel 8 keep the run under 2 GB of memory: at most four test
|
||||
# binaries build or run at once, each with at most eight parallel tests. Under
|
||||
# -race every test binary and every link costs a few hundred MB, so the
|
||||
# defaults (one per core) add up to several GB on a many-core host.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
@@ -34,7 +29,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
"$ROOT/script/assets"
|
||||
go test -v -race -p 4 -parallel 8 -timeout 90s ./...
|
||||
go test -v -race -timeout 90s ./...
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user