Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a112fad71 |
@@ -162,20 +162,6 @@ public cloud metadata addresses: currently only `168.63.129.16`, Azure's
|
|||||||
WireServer, which serves an Azure VM its credentials. Because it is a
|
WireServer, which serves an Azure VM its credentials. Because it is a
|
||||||
public address, listing it in `ALLOWED_EGRESS_CIDRS` reopens it.
|
public address, listing it in `ALLOWED_EGRESS_CIDRS` reopens it.
|
||||||
|
|
||||||
That is all the default blocklist covers: the IPv4 private and reserved
|
|
||||||
ranges; of IPv6, only loopback (`::1`), unique local addresses
|
|
||||||
(`fc00::/7`) and link-local addresses (`fe80::/10`); and public
|
|
||||||
addresses that hand credentials to whatever can reach them, without the
|
|
||||||
caller presenting any. A cloud provider's other services on public
|
|
||||||
addresses are not refused. IBM Cloud, for example, serves its DNS
|
|
||||||
resolvers, package mirrors, time servers and object storage on
|
|
||||||
`161.26.0.0/16`, and the private endpoints of its own cloud services on
|
|
||||||
`166.8.0.0/14`. Neither range hands out credentials that way: the token
|
|
||||||
service among those endpoints issues a token only in exchange for
|
|
||||||
something the caller presents, such as an API key. Reaching these
|
|
||||||
services can be a legitimate delivery, and every cloud has some, so a
|
|
||||||
partial list would promise coverage it does not give.
|
|
||||||
|
|
||||||
That default is also inconvenient for the thing webhooker is mostly
|
That default is also inconvenient for the thing webhooker is mostly
|
||||||
for: taking a public webhook and forwarding it to something on your own
|
for: taking a public webhook and forwarding it to something on your own
|
||||||
network. A container on the same Docker network, a box on `10.x`, a
|
network. A container on the same Docker network, a box on `10.x`, a
|
||||||
|
|||||||
@@ -79,3 +79,9 @@ func (d *Database) ExportSetBannerOut(w io.Writer) {
|
|||||||
func DummyPasswordHashForTest() string {
|
func DummyPasswordHashForTest() string {
|
||||||
return dummyPasswordHash()
|
return dummyPasswordHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HashPasswordWithDefaultsForTest hashes with the shipped Argon2id
|
||||||
|
// parameters, which TestMain has lowered for HashPassword itself.
|
||||||
|
func HashPasswordWithDefaultsForTest(password string) (string, error) {
|
||||||
|
return hashPasswordWith(password, DefaultPasswordConfig())
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package database_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sneak.berlin/go/webhooker/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMain lowers the password hashing cost before any test runs. See
|
||||||
|
// database.LowerPasswordHashCostForTest.
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
database.LowerPasswordHashCostForTest()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
@@ -63,10 +63,24 @@ func DefaultPasswordConfig() *PasswordConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hashConfig is what HashPassword hashes with: the defaults above.
|
||||||
|
// Only a test binary changes it, through LowerPasswordHashCostForTest,
|
||||||
|
// before any test runs.
|
||||||
|
//
|
||||||
|
//nolint:gochecknoglobals // see above
|
||||||
|
var hashConfig = DefaultPasswordConfig()
|
||||||
|
|
||||||
// HashPassword generates an Argon2id hash of the password
|
// HashPassword generates an Argon2id hash of the password
|
||||||
func HashPassword(password string) (string, error) {
|
func HashPassword(password string) (string, error) {
|
||||||
config := DefaultPasswordConfig()
|
return hashPasswordWith(password, hashConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashPasswordWith generates an Argon2id hash of the password with
|
||||||
|
// the given parameters.
|
||||||
|
func hashPasswordWith(
|
||||||
|
password string,
|
||||||
|
config *PasswordConfig,
|
||||||
|
) (string, error) {
|
||||||
// Generate a salt
|
// Generate a salt
|
||||||
salt := make([]byte, config.SaltLen)
|
salt := make([]byte, config.SaltLen)
|
||||||
|
|
||||||
|
|||||||
@@ -192,6 +192,36 @@ func TestHashPasswordUniqueness(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestHashPassword_ShippedParameters hashes and verifies at the
|
||||||
|
// shipped Argon2id parameters. Every other test hashes at the lowered
|
||||||
|
// memory cost TestMain sets, so this is the one that keeps production
|
||||||
|
// hashing covered. One hash and one verification: each costs 64 MB.
|
||||||
|
func TestHashPassword_ShippedParameters(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
password := "correct horse battery staple"
|
||||||
|
|
||||||
|
hash, err := database.HashPasswordWithDefaultsForTest(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
|
// TestVerifyDummyPassword_DoesRealWork covers the anti-enumeration
|
||||||
// path. Login charges an unknown username a verification against a
|
// path. Login charges an unknown username a verification against a
|
||||||
// dummy hash so that a nonexistent account is not answered in
|
// dummy hash so that a nonexistent account is not answered in
|
||||||
|
|||||||
@@ -45,3 +45,20 @@ func NewTestWebhookDBManagerWithLogger(
|
|||||||
log: log,
|
log: log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testArgon2Memory is the Argon2id memory cost, in KiB, that test
|
||||||
|
// binaries hash with: 1 MB instead of the shipped 64 MB.
|
||||||
|
const testArgon2Memory = 1024
|
||||||
|
|
||||||
|
// LowerPasswordHashCostForTest makes HashPassword use a 1 MB Argon2id
|
||||||
|
// memory cost instead of the shipped 64 MB, for the rest of the
|
||||||
|
// process. Call it from TestMain, before any test runs.
|
||||||
|
//
|
||||||
|
// Every test that starts a database hashes the bootstrap admin
|
||||||
|
// password, and a package runs dozens of those tests in parallel.
|
||||||
|
// Under the race detector each 64 MB hash holds about 150 MB resident.
|
||||||
|
// VerifyPassword reads the cost from the hash it checks, so
|
||||||
|
// verification follows. Production code never calls this.
|
||||||
|
func LowerPasswordHashCostForTest() {
|
||||||
|
hashConfig.Memory = testArgon2Memory
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,12 +43,6 @@ var (
|
|||||||
// permit specific blocks out of this set with
|
// permit specific blocks out of this set with
|
||||||
// ALLOWED_EGRESS_CIDRS; see Guard.
|
// ALLOWED_EGRESS_CIDRS; see Guard.
|
||||||
//
|
//
|
||||||
// A public address belongs here only if it hands credentials to
|
|
||||||
// whatever can reach it, without the caller presenting any; a
|
|
||||||
// provider's other services on public addresses, such as its DNS
|
|
||||||
// resolvers or package mirrors, stay out, since reaching them can
|
|
||||||
// be legitimate and no list of them could be complete.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // package-level network list is appropriate here
|
//nolint:gochecknoglobals // package-level network list is appropriate here
|
||||||
var blockedNetworks []*net.IPNet
|
var blockedNetworks []*net.IPNet
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package gormlog_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sneak.berlin/go/webhooker/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMain lowers the password hashing cost before any test runs. See
|
||||||
|
// database.LowerPasswordHashCostForTest.
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
database.LowerPasswordHashCostForTest()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package handlers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sneak.berlin/go/webhooker/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMain lowers the password hashing cost before any test runs. See
|
||||||
|
// database.LowerPasswordHashCostForTest.
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
database.LowerPasswordHashCostForTest()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package resetpw_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sneak.berlin/go/webhooker/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMain lowers the password hashing cost before any test runs. See
|
||||||
|
// database.LowerPasswordHashCostForTest.
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
database.LowerPasswordHashCostForTest()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
@@ -140,7 +140,7 @@ func (n *noopEvictor) EvictWebhook(string) {}
|
|||||||
// and the database, exactly as internal/handlers builds them.
|
// and the database, exactly as internal/handlers builds them.
|
||||||
//
|
//
|
||||||
// One application per test function, not per case: every start that
|
// One application per test function, not per case: every start that
|
||||||
// finds no account seeds one at 64 MB of Argon2id, and this package's
|
// finds no account seeds one with an Argon2id hash, and this package's
|
||||||
// budget is not the place to spend that repeatedly.
|
// budget is not the place to spend that repeatedly.
|
||||||
func newServerApp(
|
func newServerApp(
|
||||||
t *testing.T, dir string,
|
t *testing.T, dir string,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package server_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sneak.berlin/go/webhooker/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestMain lowers the password hashing cost before any test runs. See
|
||||||
|
// database.LowerPasswordHashCostForTest.
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
database.LowerPasswordHashCostForTest()
|
||||||
|
m.Run()
|
||||||
|
}
|
||||||
+6
-1
@@ -22,13 +22,18 @@
|
|||||||
# The one figure above 90s is GOMAXPROCS 1, a synthetic core floor rather than
|
# 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
|
# 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.
|
# 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
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
go test -v -race -timeout 90s ./...
|
go test -v -race -p 4 -parallel 8 -timeout 90s ./...
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user