From c5dae06ffcbcb60461f16bdd4fcf34384064f876 Mon Sep 17 00:00:00 2001 From: Samantha Frank Date: Wed, 9 Oct 2024 19:30:51 -0400 Subject: [PATCH] ratelimits: Add unit test coverage for TransactionBuilder methods (#7752) --- ratelimits/bucket_test.go | 174 ++++++++++++++++++ .../testdata/working_override_13371338.yml | 14 ++ 2 files changed, 188 insertions(+) create mode 100644 ratelimits/testdata/working_override_13371338.yml diff --git a/ratelimits/bucket_test.go b/ratelimits/bucket_test.go index 575577caf..747d14210 100644 --- a/ratelimits/bucket_test.go +++ b/ratelimits/bucket_test.go @@ -1,8 +1,12 @@ package ratelimits import ( + "fmt" + "net" + "sort" "testing" + "github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/test" ) @@ -14,3 +18,173 @@ func TestNewTransactionBuilder_WithBadLimitsPath(t *testing.T) { _, err = NewTransactionBuilder("testdata/defaults.yml", "testdata/does-not-exist.yml") test.AssertError(t, err, "should error") } + +func sortTransactions(txns []Transaction) []Transaction { + sort.Slice(txns, func(i, j int) bool { + return txns[i].bucketKey < txns[j].bucketKey + }) + return txns +} + +func TestNewRegistrationsPerIPAddressTransactions(t *testing.T) { + t.Parallel() + + tb, err := NewTransactionBuilder("../test/config-next/wfe2-ratelimit-defaults.yml", "") + test.AssertNotError(t, err, "creating TransactionBuilder") + + // A check-and-spend transaction for the global limit. + txn, err := tb.registrationsPerIPAddressTransaction(net.ParseIP("1.2.3.4")) + test.AssertNotError(t, err, "creating transaction") + test.AssertEquals(t, txn.bucketKey, "1:1.2.3.4") + test.Assert(t, txn.check && txn.spend, "should be check-and-spend") +} + +func TestNewRegistrationsPerIPv6AddressTransactions(t *testing.T) { + t.Parallel() + + tb, err := NewTransactionBuilder("../test/config-next/wfe2-ratelimit-defaults.yml", "") + test.AssertNotError(t, err, "creating TransactionBuilder") + + // A check-and-spend transaction for the global limit. + txn, err := tb.registrationsPerIPv6RangeTransaction(net.ParseIP("2001:db8::1")) + test.AssertNotError(t, err, "creating transaction") + test.AssertEquals(t, txn.bucketKey, "2:2001:db8::/48") + test.Assert(t, txn.check && txn.spend, "should be check-and-spend") +} + +func TestNewOrdersPerAccountTransactions(t *testing.T) { + t.Parallel() + + tb, err := NewTransactionBuilder("../test/config-next/wfe2-ratelimit-defaults.yml", "") + test.AssertNotError(t, err, "creating TransactionBuilder") + + // A check-and-spend transaction for the global limit. + txn, err := tb.ordersPerAccountTransaction(123456789) + test.AssertNotError(t, err, "creating transaction") + test.AssertEquals(t, txn.bucketKey, "3:123456789") + test.Assert(t, txn.check && txn.spend, "should be check-and-spend") +} + +func TestFailedAuthorizationsPerDomainPerAccountTransactions(t *testing.T) { + t.Parallel() + + tb, err := NewTransactionBuilder("../test/config-next/wfe2-ratelimit-defaults.yml", "testdata/working_override_13371338.yml") + test.AssertNotError(t, err, "creating TransactionBuilder") + + // A check-only transaction for the default per-account limit. + txns, err := tb.FailedAuthorizationsPerDomainPerAccountCheckOnlyTransactions(123456789, []string{"so.many.labels.here.example.com"}) + test.AssertNotError(t, err, "creating transactions") + test.AssertEquals(t, len(txns), 1) + test.AssertEquals(t, txns[0].bucketKey, "4:123456789:so.many.labels.here.example.com") + test.Assert(t, txns[0].checkOnly(), "should be check-only") + test.Assert(t, !txns[0].limit.isOverride(), "should not be an override") + + // A spend-only transaction for the default per-account limit. + txn, err := tb.FailedAuthorizationsPerDomainPerAccountSpendOnlyTransaction(123456789, "so.many.labels.here.example.com") + test.AssertNotError(t, err, "creating transaction") + test.AssertEquals(t, txn.bucketKey, "4:123456789:so.many.labels.here.example.com") + test.Assert(t, txn.spendOnly(), "should be spend-only") + test.Assert(t, !txn.limit.isOverride(), "should not be an override") + + // A check-only transaction for the per-account limit override. + txns, err = tb.FailedAuthorizationsPerDomainPerAccountCheckOnlyTransactions(13371338, []string{"so.many.labels.here.example.com"}) + test.AssertNotError(t, err, "creating transactions") + test.AssertEquals(t, len(txns), 1) + test.AssertEquals(t, txns[0].bucketKey, "4:13371338:so.many.labels.here.example.com") + test.Assert(t, txns[0].checkOnly(), "should be check-only") + test.Assert(t, txns[0].limit.isOverride(), "should be an override") + + // A spend-only transaction for the per-account limit override. + txn, err = tb.FailedAuthorizationsPerDomainPerAccountSpendOnlyTransaction(13371338, "so.many.labels.here.example.com") + test.AssertNotError(t, err, "creating transaction") + test.AssertEquals(t, txn.bucketKey, "4:13371338:so.many.labels.here.example.com") + test.Assert(t, txn.spendOnly(), "should be spend-only") + test.Assert(t, txn.limit.isOverride(), "should be an override") +} + +func TestCertificatesPerDomainTransactions(t *testing.T) { + t.Parallel() + + tb, err := NewTransactionBuilder("../test/config-next/wfe2-ratelimit-defaults.yml", "") + test.AssertNotError(t, err, "creating TransactionBuilder") + + // One check-only transaction for the global limit. + txns, err := tb.certificatesPerDomainCheckOnlyTransactions(123456789, []string{"so.many.labels.here.example.com"}) + test.AssertNotError(t, err, "creating transactions") + test.AssertEquals(t, len(txns), 1) + test.AssertEquals(t, txns[0].bucketKey, "5:example.com") + test.Assert(t, txns[0].checkOnly(), "should be check-only") + + // One spend-only transaction for the global limit. + txns, err = tb.CertificatesPerDomainSpendOnlyTransactions(123456789, []string{"so.many.labels.here.example.com"}) + test.AssertNotError(t, err, "creating transactions") + test.AssertEquals(t, len(txns), 1) + test.AssertEquals(t, txns[0].bucketKey, "5:example.com") + test.Assert(t, txns[0].spendOnly(), "should be spend-only") +} + +func TestCertificatesPerDomainPerAccountTransactions(t *testing.T) { + t.Parallel() + + tb, err := NewTransactionBuilder("../test/config-next/wfe2-ratelimit-defaults.yml", "testdata/working_override_13371338.yml") + test.AssertNotError(t, err, "creating TransactionBuilder") + + // We only expect a single check-only transaction for the per-account limit + // override. We can safely ignore the global limit when an override is + // present. + txns, err := tb.certificatesPerDomainCheckOnlyTransactions(13371338, []string{"so.many.labels.here.example.com"}) + test.AssertNotError(t, err, "creating transactions") + test.AssertEquals(t, len(txns), 1) + test.AssertEquals(t, txns[0].bucketKey, "6:13371338:example.com") + test.Assert(t, txns[0].checkOnly(), "should be check-only") + test.Assert(t, txns[0].limit.isOverride(), "should be an override") + + // Same as above, but with multiple example.com domains. + txns, err = tb.certificatesPerDomainCheckOnlyTransactions(13371338, []string{"so.many.labels.here.example.com", "z.example.com"}) + test.AssertNotError(t, err, "creating transactions") + test.AssertEquals(t, len(txns), 1) + test.AssertEquals(t, txns[0].bucketKey, "6:13371338:example.com") + test.Assert(t, txns[0].checkOnly(), "should be check-only") + test.Assert(t, txns[0].limit.isOverride(), "should be an override") + + // Same as above, but with different domains. + txns, err = tb.certificatesPerDomainCheckOnlyTransactions(13371338, []string{"so.many.labels.here.example.com", "z.example.net"}) + test.AssertNotError(t, err, "creating transactions") + txns = sortTransactions(txns) + test.AssertEquals(t, len(txns), 2) + test.AssertEquals(t, txns[0].bucketKey, "6:13371338:example.com") + test.Assert(t, txns[0].checkOnly(), "should be check-only") + test.Assert(t, txns[0].limit.isOverride(), "should be an override") + test.AssertEquals(t, txns[1].bucketKey, "6:13371338:example.net") + test.Assert(t, txns[1].checkOnly(), "should be check-only") + test.Assert(t, txns[1].limit.isOverride(), "should be an override") + + // Two spend-only transactions, one for the global limit and one for the + // per-account limit override. + txns, err = tb.CertificatesPerDomainSpendOnlyTransactions(13371338, []string{"so.many.labels.here.example.com"}) + test.AssertNotError(t, err, "creating TransactionBuilder") + test.AssertEquals(t, len(txns), 2) + txns = sortTransactions(txns) + test.AssertEquals(t, txns[0].bucketKey, "5:example.com") + test.Assert(t, txns[0].spendOnly(), "should be spend-only") + test.Assert(t, !txns[0].limit.isOverride(), "should not be an override") + + test.AssertEquals(t, txns[1].bucketKey, "6:13371338:example.com") + test.Assert(t, txns[1].spendOnly(), "should be spend-only") + test.Assert(t, txns[1].limit.isOverride(), "should be an override") +} + +func TestCertificatesPerFQDNSetTransactions(t *testing.T) { + t.Parallel() + + tb, err := NewTransactionBuilder("../test/config-next/wfe2-ratelimit-defaults.yml", "") + test.AssertNotError(t, err, "creating TransactionBuilder") + + // A single check-only transaction for the global limit. + txn, err := tb.certificatesPerFQDNSetCheckOnlyTransaction([]string{"example.com", "example.net", "example.org"}) + test.AssertNotError(t, err, "creating transaction") + namesHash := fmt.Sprintf("%x", core.HashNames([]string{"example.com", "example.net", "example.org"})) + test.AssertEquals(t, txn.bucketKey, "7:"+namesHash) + test.Assert(t, txn.checkOnly(), "should be check-only") + test.Assert(t, !txn.limit.isOverride(), "should not be an override") +} diff --git a/ratelimits/testdata/working_override_13371338.yml b/ratelimits/testdata/working_override_13371338.yml new file mode 100644 index 000000000..a260fedb5 --- /dev/null +++ b/ratelimits/testdata/working_override_13371338.yml @@ -0,0 +1,14 @@ +- CertificatesPerDomainPerAccount: + burst: 1337 + count: 1337 + period: 2160h + ids: + - id: 13371338 + comment: Used to test the TransactionBuilder +- FailedAuthorizationsPerDomainPerAccount: + burst: 1337 + count: 1337 + period: 5m + ids: + - id: 13371338 + comment: Used to test the TransactionBuilder