Compare commits
29 Commits
v0.2025070
...
main
Author | SHA1 | Date |
---|---|---|
|
473b4059c4 | |
|
440c6957f9 | |
|
80c75ab435 | |
|
85d1e3cf5e | |
|
04ae9ebcda | |
|
a3c1e62049 | |
|
cd59eed63d | |
|
5a5ae229a0 | |
|
d5bb88b975 | |
|
b9dbcdbba2 | |
|
15824538c1 | |
|
cfd5c12f6f | |
|
7e04a7176f | |
|
9b3fc4016b | |
|
6ba4207153 | |
|
99be9909a6 | |
|
79a7cb3c1b | |
|
102997da4a | |
|
21d304812e | |
|
caa980f9ee | |
|
e15368c205 | |
|
a3eb6aa043 | |
|
05e631593e | |
|
c1ce0c83d0 | |
|
5b380adb53 | |
|
bf0439bc12 | |
|
9d5512abb8 | |
|
d7e4ed18ad | |
|
4d0ae36512 |
|
@ -14,6 +14,8 @@ updates:
|
|||
schedule:
|
||||
interval: "weekly"
|
||||
day: "wednesday"
|
||||
cooldown:
|
||||
default-days: 30
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
|
|
@ -150,8 +150,8 @@ func setup(t *testing.T) *testCtx {
|
|||
|
||||
pa, err := policy.New(map[identifier.IdentifierType]bool{"dns": true}, nil, blog.NewMock())
|
||||
test.AssertNotError(t, err, "Couldn't create PA")
|
||||
err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
|
||||
test.AssertNotError(t, err, "Couldn't set hostname policy")
|
||||
err = pa.LoadIdentPolicyFile("../test/ident-policy.yaml")
|
||||
test.AssertNotError(t, err, "Couldn't set identifier policy")
|
||||
|
||||
certProfiles := make(map[string]*issuance.ProfileConfig, 0)
|
||||
certProfiles["legacy"] = &issuance.ProfileConfig{
|
||||
|
|
|
@ -46,16 +46,17 @@ type revoker interface {
|
|||
}
|
||||
|
||||
type badKeyRevoker struct {
|
||||
dbMap *db.WrappedMap
|
||||
maxRevocations int
|
||||
serialBatchSize int
|
||||
raClient revoker
|
||||
logger blog.Logger
|
||||
clk clock.Clock
|
||||
backoffIntervalBase time.Duration
|
||||
backoffIntervalMax time.Duration
|
||||
backoffFactor float64
|
||||
backoffTicker int
|
||||
dbMap *db.WrappedMap
|
||||
maxRevocations int
|
||||
serialBatchSize int
|
||||
raClient revoker
|
||||
logger blog.Logger
|
||||
clk clock.Clock
|
||||
backoffIntervalBase time.Duration
|
||||
backoffIntervalMax time.Duration
|
||||
backoffFactor float64
|
||||
backoffTicker int
|
||||
maxExpectedReplicationLag time.Duration
|
||||
}
|
||||
|
||||
// uncheckedBlockedKey represents a row in the blockedKeys table
|
||||
|
@ -76,8 +77,10 @@ func (bkr *badKeyRevoker) countUncheckedKeys(ctx context.Context) (int, error) {
|
|||
&count,
|
||||
`SELECT COUNT(*)
|
||||
FROM (SELECT 1 FROM blockedKeys
|
||||
WHERE extantCertificatesChecked = false
|
||||
WHERE extantCertificatesChecked = false AND added < ? - INTERVAL ? SECOND
|
||||
LIMIT ?) AS a`,
|
||||
bkr.clk.Now(),
|
||||
bkr.maxExpectedReplicationLag.Seconds(),
|
||||
blockedKeysGaugeLimit,
|
||||
)
|
||||
return count, err
|
||||
|
@ -90,8 +93,10 @@ func (bkr *badKeyRevoker) selectUncheckedKey(ctx context.Context) (uncheckedBloc
|
|||
&row,
|
||||
`SELECT keyHash, revokedBy
|
||||
FROM blockedKeys
|
||||
WHERE extantCertificatesChecked = false
|
||||
WHERE extantCertificatesChecked = false AND added < ? - INTERVAL ? SECOND
|
||||
LIMIT 1`,
|
||||
bkr.clk.Now(),
|
||||
bkr.maxExpectedReplicationLag.Seconds(),
|
||||
)
|
||||
return row, err
|
||||
}
|
||||
|
@ -275,6 +280,7 @@ type Config struct {
|
|||
// is higher than MaximumRevocations bad-key-revoker will error out and refuse to
|
||||
// progress until this is addressed.
|
||||
MaximumRevocations int `validate:"gte=0"`
|
||||
|
||||
// FindCertificatesBatchSize specifies the maximum number of serials to select from the
|
||||
// keyHashToSerial table at once
|
||||
FindCertificatesBatchSize int `validate:"required"`
|
||||
|
@ -289,15 +295,12 @@ type Config struct {
|
|||
// or no work to do.
|
||||
BackoffIntervalMax config.Duration `validate:"-"`
|
||||
|
||||
// Deprecated: the bad-key-revoker no longer sends emails; we use ARI.
|
||||
// TODO(#8199): Remove this config stanza entirely.
|
||||
Mailer struct {
|
||||
cmd.SMTPConfig `validate:"-"`
|
||||
SMTPTrustedRootFile string
|
||||
From string
|
||||
EmailSubject string
|
||||
EmailTemplate string
|
||||
}
|
||||
// MaxExpectedReplicationLag specifies the minimum duration
|
||||
// bad-key-revoker should wait before searching for certificates
|
||||
// matching a blockedKeys row. This should be just slightly greater than
|
||||
// the database's maximum replication lag, and always well under 24
|
||||
// hours.
|
||||
MaxExpectedReplicationLag config.Duration `validate:"-"`
|
||||
}
|
||||
|
||||
Syslog cmd.SyslogConfig
|
||||
|
@ -340,15 +343,16 @@ func main() {
|
|||
rac := rapb.NewRegistrationAuthorityClient(conn)
|
||||
|
||||
bkr := &badKeyRevoker{
|
||||
dbMap: dbMap,
|
||||
maxRevocations: config.BadKeyRevoker.MaximumRevocations,
|
||||
serialBatchSize: config.BadKeyRevoker.FindCertificatesBatchSize,
|
||||
raClient: rac,
|
||||
logger: logger,
|
||||
clk: clk,
|
||||
backoffIntervalMax: config.BadKeyRevoker.BackoffIntervalMax.Duration,
|
||||
backoffIntervalBase: config.BadKeyRevoker.Interval.Duration,
|
||||
backoffFactor: 1.3,
|
||||
dbMap: dbMap,
|
||||
maxRevocations: config.BadKeyRevoker.MaximumRevocations,
|
||||
serialBatchSize: config.BadKeyRevoker.FindCertificatesBatchSize,
|
||||
raClient: rac,
|
||||
logger: logger,
|
||||
clk: clk,
|
||||
backoffIntervalMax: config.BadKeyRevoker.BackoffIntervalMax.Duration,
|
||||
backoffIntervalBase: config.BadKeyRevoker.Interval.Duration,
|
||||
backoffFactor: 1.3,
|
||||
maxExpectedReplicationLag: config.BadKeyRevoker.MaxExpectedReplicationLag.Duration,
|
||||
}
|
||||
|
||||
// If `BackoffIntervalMax` was not set via the config, set it to 60
|
||||
|
@ -364,6 +368,14 @@ func main() {
|
|||
bkr.backoffIntervalBase = time.Second
|
||||
}
|
||||
|
||||
// If `MaxExpectedReplicationLag` was not set via the config, then set
|
||||
// `bkr.maxExpectedReplicationLag` to a default 22 seconds. This is based on
|
||||
// ProxySQL's max_replication_lag for bad-key-revoker (10s), times two, plus
|
||||
// two seconds.
|
||||
if bkr.maxExpectedReplicationLag == 0 {
|
||||
bkr.maxExpectedReplicationLag = time.Second * 22
|
||||
}
|
||||
|
||||
// Run bad-key-revoker in a loop. Backoff if no work or errors.
|
||||
for {
|
||||
noWork, err := bkr.invoke(context.Background())
|
||||
|
|
|
@ -45,6 +45,12 @@ func insertBlockedRow(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock, hash [
|
|||
test.AssertNotError(t, err, "failed to add test row")
|
||||
}
|
||||
|
||||
func fcBeforeRepLag(clk clock.Clock, bkr *badKeyRevoker) clock.FakeClock {
|
||||
fc := clock.NewFake()
|
||||
fc.Set(clk.Now().Add(-bkr.maxExpectedReplicationLag - time.Second))
|
||||
return fc
|
||||
}
|
||||
|
||||
func TestSelectUncheckedRows(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -55,12 +61,15 @@ func TestSelectUncheckedRows(t *testing.T) {
|
|||
fc := clock.NewFake()
|
||||
|
||||
bkr := &badKeyRevoker{
|
||||
dbMap: dbMap,
|
||||
logger: blog.NewMock(),
|
||||
clk: fc,
|
||||
dbMap: dbMap,
|
||||
logger: blog.NewMock(),
|
||||
clk: fc,
|
||||
maxExpectedReplicationLag: time.Second * 22,
|
||||
}
|
||||
|
||||
hashA, hashB, hashC := randHash(t), randHash(t), randHash(t)
|
||||
|
||||
// insert a blocked key that's marked as already checked
|
||||
insertBlockedRow(t, dbMap, fc, hashA, 1, true)
|
||||
count, err := bkr.countUncheckedKeys(ctx)
|
||||
test.AssertNotError(t, err, "countUncheckedKeys failed")
|
||||
|
@ -68,11 +77,14 @@ func TestSelectUncheckedRows(t *testing.T) {
|
|||
_, err = bkr.selectUncheckedKey(ctx)
|
||||
test.AssertError(t, err, "selectUncheckedKey didn't fail with no rows to process")
|
||||
test.Assert(t, db.IsNoRows(err), "returned error is not sql.ErrNoRows")
|
||||
insertBlockedRow(t, dbMap, fc, hashB, 1, false)
|
||||
|
||||
// insert a blocked key that's due to be checked
|
||||
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashB, 1, false)
|
||||
// insert a freshly blocked key, so it's not yet due to be checked
|
||||
insertBlockedRow(t, dbMap, fc, hashC, 1, false)
|
||||
count, err = bkr.countUncheckedKeys(ctx)
|
||||
test.AssertNotError(t, err, "countUncheckedKeys failed")
|
||||
test.AssertEquals(t, count, 2)
|
||||
test.AssertEquals(t, count, 1)
|
||||
row, err := bkr.selectUncheckedKey(ctx)
|
||||
test.AssertNotError(t, err, "selectUncheckKey failed")
|
||||
test.AssertByteEquals(t, row.KeyHash, hashB)
|
||||
|
@ -86,13 +98,12 @@ func insertRegistration(t *testing.T, dbMap *db.WrappedMap, fc clock.Clock) int6
|
|||
test.AssertNotError(t, err, "failed to read rand")
|
||||
res, err := dbMap.ExecContext(
|
||||
context.Background(),
|
||||
"INSERT INTO registrations (jwk, jwk_sha256, agreement, createdAt, status, LockCol) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
"INSERT INTO registrations (jwk, jwk_sha256, agreement, createdAt, status) VALUES (?, ?, ?, ?, ?)",
|
||||
[]byte{},
|
||||
fmt.Sprintf("%x", jwkHash),
|
||||
"yes",
|
||||
fc.Now(),
|
||||
string(core.StatusValid),
|
||||
0,
|
||||
)
|
||||
test.AssertNotError(t, err, "failed to insert test registrations row")
|
||||
regID, err := res.LastInsertId()
|
||||
|
@ -192,7 +203,13 @@ func TestFindUnrevokedNoRows(t *testing.T) {
|
|||
)
|
||||
test.AssertNotError(t, err, "failed to insert test keyHashToSerial row")
|
||||
|
||||
bkr := &badKeyRevoker{dbMap: dbMap, serialBatchSize: 1, maxRevocations: 10, clk: fc}
|
||||
bkr := &badKeyRevoker{
|
||||
dbMap: dbMap,
|
||||
serialBatchSize: 1,
|
||||
maxRevocations: 10,
|
||||
clk: fc,
|
||||
maxExpectedReplicationLag: time.Second * 22,
|
||||
}
|
||||
_, err = bkr.findUnrevoked(ctx, uncheckedBlockedKey{KeyHash: hashA})
|
||||
test.Assert(t, db.IsNoRows(err), "expected NoRows error")
|
||||
}
|
||||
|
@ -208,7 +225,13 @@ func TestFindUnrevoked(t *testing.T) {
|
|||
|
||||
regID := insertRegistration(t, dbMap, fc)
|
||||
|
||||
bkr := &badKeyRevoker{dbMap: dbMap, serialBatchSize: 1, maxRevocations: 10, clk: fc}
|
||||
bkr := &badKeyRevoker{
|
||||
dbMap: dbMap,
|
||||
serialBatchSize: 1,
|
||||
maxRevocations: 10,
|
||||
clk: fc,
|
||||
maxExpectedReplicationLag: time.Second * 22,
|
||||
}
|
||||
|
||||
hashA := randHash(t)
|
||||
// insert valid, unexpired
|
||||
|
@ -252,7 +275,11 @@ func TestRevokeCerts(t *testing.T) {
|
|||
|
||||
fc := clock.NewFake()
|
||||
mr := &mockRevoker{}
|
||||
bkr := &badKeyRevoker{dbMap: dbMap, raClient: mr, clk: fc}
|
||||
bkr := &badKeyRevoker{
|
||||
dbMap: dbMap,
|
||||
raClient: mr,
|
||||
clk: fc,
|
||||
}
|
||||
|
||||
err = bkr.revokeCerts([]unrevokedCertificate{
|
||||
{ID: 0, Serial: "ff"},
|
||||
|
@ -270,11 +297,20 @@ func TestCertificateAbsent(t *testing.T) {
|
|||
defer test.ResetBoulderTestDatabase(t)()
|
||||
|
||||
fc := clock.NewFake()
|
||||
bkr := &badKeyRevoker{
|
||||
dbMap: dbMap,
|
||||
maxRevocations: 1,
|
||||
serialBatchSize: 1,
|
||||
raClient: &mockRevoker{},
|
||||
logger: blog.NewMock(),
|
||||
clk: fc,
|
||||
maxExpectedReplicationLag: time.Second * 22,
|
||||
}
|
||||
|
||||
// populate DB with all the test data
|
||||
regIDA := insertRegistration(t, dbMap, fc)
|
||||
hashA := randHash(t)
|
||||
insertBlockedRow(t, dbMap, fc, hashA, regIDA, false)
|
||||
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashA, regIDA, false)
|
||||
|
||||
// Add an entry to keyHashToSerial but not to certificateStatus or certificate
|
||||
// status, and expect an error.
|
||||
|
@ -287,14 +323,6 @@ func TestCertificateAbsent(t *testing.T) {
|
|||
)
|
||||
test.AssertNotError(t, err, "failed to insert test keyHashToSerial row")
|
||||
|
||||
bkr := &badKeyRevoker{
|
||||
dbMap: dbMap,
|
||||
maxRevocations: 1,
|
||||
serialBatchSize: 1,
|
||||
raClient: &mockRevoker{},
|
||||
logger: blog.NewMock(),
|
||||
clk: fc,
|
||||
}
|
||||
_, err = bkr.invoke(ctx)
|
||||
test.AssertError(t, err, "expected error when row in keyHashToSerial didn't have a matching cert")
|
||||
}
|
||||
|
@ -310,12 +338,13 @@ func TestInvoke(t *testing.T) {
|
|||
|
||||
mr := &mockRevoker{}
|
||||
bkr := &badKeyRevoker{
|
||||
dbMap: dbMap,
|
||||
maxRevocations: 10,
|
||||
serialBatchSize: 1,
|
||||
raClient: mr,
|
||||
logger: blog.NewMock(),
|
||||
clk: fc,
|
||||
dbMap: dbMap,
|
||||
maxRevocations: 10,
|
||||
serialBatchSize: 1,
|
||||
raClient: mr,
|
||||
logger: blog.NewMock(),
|
||||
clk: fc,
|
||||
maxExpectedReplicationLag: time.Second * 22,
|
||||
}
|
||||
|
||||
// populate DB with all the test data
|
||||
|
@ -324,7 +353,7 @@ func TestInvoke(t *testing.T) {
|
|||
regIDC := insertRegistration(t, dbMap, fc)
|
||||
regIDD := insertRegistration(t, dbMap, fc)
|
||||
hashA := randHash(t)
|
||||
insertBlockedRow(t, dbMap, fc, hashA, regIDC, false)
|
||||
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashA, regIDC, false)
|
||||
insertGoodCert(t, dbMap, fc, hashA, "ff", regIDA)
|
||||
insertGoodCert(t, dbMap, fc, hashA, "ee", regIDB)
|
||||
insertGoodCert(t, dbMap, fc, hashA, "dd", regIDC)
|
||||
|
@ -345,7 +374,7 @@ func TestInvoke(t *testing.T) {
|
|||
|
||||
// add a row with no associated valid certificates
|
||||
hashB := randHash(t)
|
||||
insertBlockedRow(t, dbMap, fc, hashB, regIDC, false)
|
||||
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashB, regIDC, false)
|
||||
insertCert(t, dbMap, fc, hashB, "bb", regIDA, Expired, Revoked)
|
||||
|
||||
noWork, err = bkr.invoke(ctx)
|
||||
|
@ -376,11 +405,12 @@ func TestInvokeRevokerHasNoExtantCerts(t *testing.T) {
|
|||
|
||||
mr := &mockRevoker{}
|
||||
bkr := &badKeyRevoker{dbMap: dbMap,
|
||||
maxRevocations: 10,
|
||||
serialBatchSize: 1,
|
||||
raClient: mr,
|
||||
logger: blog.NewMock(),
|
||||
clk: fc,
|
||||
maxRevocations: 10,
|
||||
serialBatchSize: 1,
|
||||
raClient: mr,
|
||||
logger: blog.NewMock(),
|
||||
clk: fc,
|
||||
maxExpectedReplicationLag: time.Second * 22,
|
||||
}
|
||||
|
||||
// populate DB with all the test data
|
||||
|
@ -390,7 +420,7 @@ func TestInvokeRevokerHasNoExtantCerts(t *testing.T) {
|
|||
|
||||
hashA := randHash(t)
|
||||
|
||||
insertBlockedRow(t, dbMap, fc, hashA, regIDA, false)
|
||||
insertBlockedRow(t, dbMap, fcBeforeRepLag(fc, bkr), hashA, regIDA, false)
|
||||
|
||||
insertGoodCert(t, dbMap, fc, hashA, "ee", regIDB)
|
||||
insertGoodCert(t, dbMap, fc, hashA, "dd", regIDB)
|
||||
|
|
|
@ -172,8 +172,8 @@ func main() {
|
|||
if c.CA.HostnamePolicyFile == "" {
|
||||
cmd.Fail("HostnamePolicyFile was empty")
|
||||
}
|
||||
err = pa.LoadHostnamePolicyFile(c.CA.HostnamePolicyFile)
|
||||
cmd.FailOnError(err, "Couldn't load hostname policy file")
|
||||
err = pa.LoadIdentPolicyFile(c.CA.HostnamePolicyFile)
|
||||
cmd.FailOnError(err, "Couldn't load identifier policy file")
|
||||
|
||||
// Do this before creating the issuers to ensure the log list is loaded before
|
||||
// the linters are initialized.
|
||||
|
|
|
@ -175,8 +175,8 @@ func main() {
|
|||
if c.RA.HostnamePolicyFile == "" {
|
||||
cmd.Fail("HostnamePolicyFile must be provided.")
|
||||
}
|
||||
err = pa.LoadHostnamePolicyFile(c.RA.HostnamePolicyFile)
|
||||
cmd.FailOnError(err, "Couldn't load hostname policy file")
|
||||
err = pa.LoadIdentPolicyFile(c.RA.HostnamePolicyFile)
|
||||
cmd.FailOnError(err, "Couldn't load identifier policy file")
|
||||
|
||||
tlsConfig, err := c.RA.TLS.Load(scope)
|
||||
cmd.FailOnError(err, "TLS config")
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
# `ceremony`
|
||||
|
||||
```
|
||||
```sh
|
||||
ceremony --config path/to/config.yml
|
||||
```
|
||||
|
||||
`ceremony` is a tool designed for Certificate Authority specific key and certificate ceremonies. The main design principle is that unlike most ceremony tooling there is a single user input, a configuration file, which is required to complete a root, intermediate, or key ceremony. The goal is to make ceremonies as simple as possible and allow for simple verification of a single file, instead of verification of a large number of independent commands.
|
||||
|
||||
`ceremony` has these modes:
|
||||
* `root` - generates a signing key on HSM and creates a self-signed root certificate that uses the generated key, outputting a PEM public key, and a PEM certificate. After generating such a root for public trust purposes, it should be submitted to [as many root programs as is possible/practical](https://github.com/daknob/root-programs).
|
||||
* `intermediate` - creates a intermediate certificate and signs it using a signing key already on a HSM, outputting a PEM certificate
|
||||
* `cross-csr` - creates a CSR for signing by a third party, outputting a PEM CSR.
|
||||
* `cross-certificate` - issues a certificate for one root, signed by another root. This is distinct from an intermediate because there is no path length constraint and there are no EKUs.
|
||||
* `ocsp-signer` - creates a delegated OCSP signing certificate and signs it using a signing key already on a HSM, outputting a PEM certificate
|
||||
* `crl-signer` - creates a delegated CRL signing certificate and signs it using a signing key already on a HSM, outputting a PEM certificate
|
||||
* `key` - generates a signing key on HSM, outputting a PEM public key
|
||||
* `ocsp-response` - creates a OCSP response for the provided certificate and signs it using a signing key already on a HSM, outputting a base64 encoded response
|
||||
* `crl` - creates a CRL with the IDP extension and `onlyContainsCACerts = true` from the provided profile and signs it using a signing key already on a HSM, outputting a PEM CRL
|
||||
|
||||
- `root`: generates a signing key on HSM and creates a self-signed root certificate that uses the generated key, outputting a PEM public key, and a PEM certificate. After generating such a root for public trust purposes, it should be submitted to [as many root programs as is possible/practical](https://github.com/daknob/root-programs).
|
||||
- `intermediate`: creates a intermediate certificate and signs it using a signing key already on a HSM, outputting a PEM certificate
|
||||
- `cross-csr`: creates a CSR for signing by a third party, outputting a PEM CSR.
|
||||
- `cross-certificate`: issues a certificate for one root, signed by another root. This is distinct from an intermediate because there is no path length constraint and there are no EKUs.
|
||||
- `key`: generates a signing key on HSM, outputting a PEM public key
|
||||
- `ocsp-response`: creates a OCSP response for the provided certificate and signs it using a signing key already on a HSM, outputting a base64 encoded response
|
||||
- `crl`: creates a CRL with the IDP extension and `onlyContainsCACerts = true` from the provided profile and signs it using a signing key already on a HSM, outputting a PEM CRL
|
||||
|
||||
These modes are set in the `ceremony-type` field of the configuration file.
|
||||
|
||||
|
@ -29,23 +28,29 @@ This tool always generates key pairs such that the public and private key are bo
|
|||
|
||||
- `ceremony-type`: string describing the ceremony type, `root`.
|
||||
- `pkcs11`: object containing PKCS#11 related fields.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
|
||||
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
|
||||
| `store-key-in-slot` | Specifies which HSM object slot the generated signing key should be stored in. |
|
||||
| `store-key-with-label` | Specifies the HSM object label for the generated signing key. Both public and private key objects are stored with this label. |
|
||||
|
||||
- `key`: object containing key generation related fields.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `type` | Specifies the type of key to be generated, either `rsa` or `ecdsa`. If `rsa` the generated key will have an exponent of 65537 and a modulus length specified by `rsa-mod-length`. If `ecdsa` the curve is specified by `ecdsa-curve`. |
|
||||
| `ecdsa-curve` | Specifies the ECDSA curve to use when generating key, either `P-224`, `P-256`, `P-384`, or `P-521`. |
|
||||
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`.
|
||||
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`. |
|
||||
|
||||
- `outputs`: object containing paths to write outputs.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `public-key-path` | Path to store generated PEM public key. |
|
||||
| `certificate-path` | Path to store signed PEM certificate. |
|
||||
|
||||
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format).
|
||||
|
||||
Example:
|
||||
|
@ -76,25 +81,31 @@ certificate-profile:
|
|||
|
||||
This config generates a ECDSA P-384 key in the HSM with the object label `root signing key` and uses this key to sign a self-signed certificate. The public key for the key generated is written to `/home/user/root-signing-pub.pem` and the certificate is written to `/home/user/root-cert.pem`.
|
||||
|
||||
### Intermediate or Cross-Certificate ceremony
|
||||
### Intermediate ceremony
|
||||
|
||||
- `ceremony-type`: string describing the ceremony type, `intermediate` or `cross-certificate`.
|
||||
- `ceremony-type`: string describing the ceremony type, `intermediate`.
|
||||
- `pkcs11`: object containing PKCS#11 related fields.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
|
||||
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
|
||||
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
|
||||
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
|
||||
|
||||
- `inputs`: object containing paths for inputs
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `public-key-path` | Path to PEM subject public key for certificate. |
|
||||
| `issuer-certificate-path` | Path to PEM issuer certificate. |
|
||||
| `public-key-path` | Path to PEM subject public key for certificate. |
|
||||
|
||||
- `outputs`: object containing paths to write outputs.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `certificate-path` | Path to store signed PEM certificate. |
|
||||
|
||||
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format).
|
||||
|
||||
Example:
|
||||
|
@ -106,8 +117,8 @@ pkcs11:
|
|||
signing-key-slot: 0
|
||||
signing-key-label: root signing key
|
||||
inputs:
|
||||
public-key-path: /home/user/intermediate-signing-pub.pem
|
||||
issuer-certificate-path: /home/user/root-cert.pem
|
||||
public-key-path: /home/user/intermediate-signing-pub.pem
|
||||
outputs:
|
||||
certificate-path: /home/user/intermediate-cert.pem
|
||||
certificate-profile:
|
||||
|
@ -131,26 +142,95 @@ certificate-profile:
|
|||
|
||||
This config generates an intermediate certificate signed by a key in the HSM, identified by the object label `root signing key` and the object ID `ffff`. The subject key used is taken from `/home/user/intermediate-signing-pub.pem` and the issuer is `/home/user/root-cert.pem`, the resulting certificate is written to `/home/user/intermediate-cert.pem`.
|
||||
|
||||
Note: Intermediate certificates always include the extended key usages id-kp-serverAuth as required by 7.1.2.2.g of the CABF Baseline Requirements. Since we also include id-kp-clientAuth in end-entity certificates in boulder we also include it in intermediates, if this changes we may remove this inclusion.
|
||||
Note: Intermediate certificates always include the extended key usages id-kp-serverAuth as required by 7.1.2.2.g of the CABF Baseline Requirements.
|
||||
|
||||
### Cross-CSR ceremony
|
||||
### Cross-Certificate ceremony
|
||||
|
||||
- `ceremony-type`: string describing the ceremony type, `cross-csr`.
|
||||
- `ceremony-type`: string describing the ceremony type, `cross-certificate`.
|
||||
- `pkcs11`: object containing PKCS#11 related fields.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
|
||||
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
|
||||
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
|
||||
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
|
||||
|
||||
- `inputs`: object containing paths for inputs
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `issuer-certificate-path` | Path to PEM issuer certificate. |
|
||||
| `public-key-path` | Path to PEM subject public key for certificate. |
|
||||
| `certificate-to-cross-sign-path` | Path to PEM self-signed certificate that this ceremony is a cross-sign of. |
|
||||
|
||||
- `outputs`: object containing paths to write outputs.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `certificate-path` | Path to store signed PEM certificate. |
|
||||
|
||||
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format).
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
ceremony-type: cross-certificate
|
||||
pkcs11:
|
||||
module: /usr/lib/opensc-pkcs11.so
|
||||
signing-key-slot: 0
|
||||
signing-key-label: root signing key
|
||||
inputs:
|
||||
issuer-certificate-path: /home/user/root-cert.pem
|
||||
public-key-path: /home/user/root-signing-pub-2.pem
|
||||
certificate-to-cross-sign-path: /home/user/root-cert-2.pem
|
||||
outputs:
|
||||
certificate-path: /home/user/root-cert-2-cross.pem
|
||||
certificate-profile:
|
||||
signature-algorithm: ECDSAWithSHA384
|
||||
common-name: CA root 2
|
||||
organization: good guys
|
||||
country: US
|
||||
not-before: 2020-01-01 12:00:00
|
||||
not-after: 2040-01-01 12:00:00
|
||||
ocsp-url: http://good-guys.com/ocsp
|
||||
crl-url: http://good-guys.com/crl
|
||||
issuer-url: http://good-guys.com/root
|
||||
policies:
|
||||
- oid: 1.2.3
|
||||
- oid: 4.5.6
|
||||
key-usages:
|
||||
- Digital Signature
|
||||
- Cert Sign
|
||||
- CRL Sign
|
||||
```
|
||||
|
||||
This config generates a cross-sign of the already-created "CA root 2", issued from the similarly-already-created "CA root". The subject key used is taken from `/home/user/root-signing-pub-2.pem`. The EKUs and Subject Key Identifier are taken from `/home/user/root-cert-2-cross.pem`. The issuer is `/home/user/root-cert.pem`, and the Issuer and Authority Key Identifier fields are taken from that cert. The resulting certificate is written to `/home/user/root-cert-2-cross.pem`.
|
||||
|
||||
### Cross-CSR ceremony
|
||||
|
||||
- `ceremony-type`: string describing the ceremony type, `cross-csr`.
|
||||
- `pkcs11`: object containing PKCS#11 related fields.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
|
||||
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
|
||||
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
|
||||
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
|
||||
|
||||
- `inputs`: object containing paths for inputs
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `public-key-path` | Path to PEM subject public key for certificate. |
|
||||
|
||||
- `outputs`: object containing paths to write outputs.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `csr-path` | Path to store PEM CSR for cross-signing, optional. |
|
||||
|
||||
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format). Should only include Subject related fields `common-name`, `organization`, `country`.
|
||||
|
||||
Example:
|
||||
|
@ -173,119 +253,28 @@ certificate-profile:
|
|||
|
||||
This config generates a CSR signed by a key in the HSM, identified by the object label `intermediate signing key`, and writes it to `/home/user/csr.pem`.
|
||||
|
||||
### OCSP Signing Certificate ceremony
|
||||
|
||||
- `ceremony-type`: string describing the ceremony type, `ocsp-signer`.
|
||||
- `pkcs11`: object containing PKCS#11 related fields.
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
|
||||
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
|
||||
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
|
||||
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
|
||||
- `inputs`: object containing paths for inputs
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `public-key-path` | Path to PEM subject public key for certificate. |
|
||||
| `issuer-certificate-path` | Path to PEM issuer certificate. |
|
||||
- `outputs`: object containing paths to write outputs.
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `certificate-path` | Path to store signed PEM certificate. |
|
||||
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format). The key-usages, ocsp-url, and crl-url fields must not be set.
|
||||
|
||||
When generating an OCSP signing certificate the key usages field will be set to just Digital Signature and an EKU extension will be included with the id-kp-OCSPSigning usage. Additionally an id-pkix-ocsp-nocheck extension will be included in the certificate.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
ceremony-type: ocsp-signer
|
||||
pkcs11:
|
||||
module: /usr/lib/opensc-pkcs11.so
|
||||
signing-key-slot: 0
|
||||
signing-key-label: intermediate signing key
|
||||
inputs:
|
||||
public-key-path: /home/user/ocsp-signer-signing-pub.pem
|
||||
issuer-certificate-path: /home/user/intermediate-cert.pem
|
||||
outputs:
|
||||
certificate-path: /home/user/ocsp-signer-cert.pem
|
||||
certificate-profile:
|
||||
signature-algorithm: ECDSAWithSHA384
|
||||
common-name: CA OCSP signer
|
||||
organization: good guys
|
||||
country: US
|
||||
not-before: 2020-01-01 12:00:00
|
||||
not-after: 2040-01-01 12:00:00
|
||||
issuer-url: http://good-guys.com/root
|
||||
```
|
||||
|
||||
This config generates a delegated OCSP signing certificate signed by a key in the HSM, identified by the object label `intermediate signing key` and the object ID `ffff`. The subject key used is taken from `/home/user/ocsp-signer-signing-pub.pem` and the issuer is `/home/user/intermediate-cert.pem`, the resulting certificate is written to `/home/user/ocsp-signer-cert.pem`.
|
||||
|
||||
### CRL Signing Certificate ceremony
|
||||
|
||||
- `ceremony-type`: string describing the ceremony type, `crl-signer`.
|
||||
- `pkcs11`: object containing PKCS#11 related fields.
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
|
||||
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
|
||||
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
|
||||
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
|
||||
- `inputs`: object containing paths for inputs
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `public-key-path` | Path to PEM subject public key for certificate. |
|
||||
| `issuer-certificate-path` | Path to PEM issuer certificate. |
|
||||
- `outputs`: object containing paths to write outputs.
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `certificate-path` | Path to store signed PEM certificate. |
|
||||
- `certificate-profile`: object containing profile for certificate to generate. Fields are documented [below](#certificate-profile-format). The key-usages, ocsp-url, and crl-url fields must not be set.
|
||||
|
||||
When generating a CRL signing certificate the key usages field will be set to just CRL Sign.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
ceremony-type: crl-signer
|
||||
pkcs11:
|
||||
module: /usr/lib/opensc-pkcs11.so
|
||||
signing-key-slot: 0
|
||||
signing-key-label: intermediate signing key
|
||||
inputs:
|
||||
public-key-path: /home/user/crl-signer-signing-pub.pem
|
||||
issuer-certificate-path: /home/user/intermediate-cert.pem
|
||||
outputs:
|
||||
certificate-path: /home/user/crl-signer-cert.pem
|
||||
certificate-profile:
|
||||
signature-algorithm: ECDSAWithSHA384
|
||||
common-name: CA CRL signer
|
||||
organization: good guys
|
||||
country: US
|
||||
not-before: 2020-01-01 12:00:00
|
||||
not-after: 2040-01-01 12:00:00
|
||||
issuer-url: http://good-guys.com/root
|
||||
```
|
||||
|
||||
This config generates a delegated CRL signing certificate signed by a key in the HSM, identified by the object label `intermediate signing key` and the object ID `ffff`. The subject key used is taken from `/home/user/crl-signer-signing-pub.pem` and the issuer is `/home/user/intermediate-cert.pem`, the resulting certificate is written to `/home/user/crl-signer-cert.pem`.
|
||||
|
||||
### Key ceremony
|
||||
|
||||
- `ceremony-type`: string describing the ceremony type, `key`.
|
||||
- `pkcs11`: object containing PKCS#11 related fields.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
|
||||
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
|
||||
| `store-key-in-slot` | Specifies which HSM object slot the generated signing key should be stored in. |
|
||||
| `store-key-with-label` | Specifies the HSM object label for the generated signing key. Both public and private key objects are stored with this label. |
|
||||
|
||||
- `key`: object containing key generation related fields.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `type` | Specifies the type of key to be generated, either `rsa` or `ecdsa`. If `rsa` the generated key will have an exponent of 65537 and a modulus length specified by `rsa-mod-length`. If `ecdsa` the curve is specified by `ecdsa-curve`. |
|
||||
| `ecdsa-curve` | Specifies the ECDSA curve to use when generating key, either `P-224`, `P-256`, `P-384`, or `P-521`. |
|
||||
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`.
|
||||
| `rsa-mod-length` | Specifies the length of the RSA modulus, either `2048` or `4096`. |
|
||||
|
||||
- `outputs`: object containing paths to write outputs.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `public-key-path` | Path to store generated PEM public key. |
|
||||
|
@ -311,23 +300,30 @@ This config generates an ECDSA P-384 key in the HSM with the object label `inter
|
|||
|
||||
- `ceremony-type`: string describing the ceremony type, `ocsp-response`.
|
||||
- `pkcs11`: object containing PKCS#11 related fields.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
|
||||
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
|
||||
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
|
||||
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
|
||||
|
||||
- `inputs`: object containing paths for inputs
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `certificate-path` | Path to PEM certificate to create a response for. |
|
||||
| `issuer-certificate-path` | Path to PEM issuer certificate. |
|
||||
| `delegated-issuer-certificate-path` | Path to PEM delegated issuer certificate, if one is being used. |
|
||||
|
||||
- `outputs`: object containing paths to write outputs.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `response-path` | Path to store signed base64 encoded response. |
|
||||
|
||||
- `ocsp-profile`: object containing profile for the OCSP response.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `this-update` | Specifies the OCSP response thisUpdate date, in the format `2006-01-02 15:04:05`. The time will be interpreted as UTC. |
|
||||
|
@ -359,21 +355,28 @@ This config generates a OCSP response signed by a key in the HSM, identified by
|
|||
|
||||
- `ceremony-type`: string describing the ceremony type, `crl`.
|
||||
- `pkcs11`: object containing PKCS#11 related fields.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `module` | Path to the PKCS#11 module to use to communicate with a HSM. |
|
||||
| `pin` | Specifies the login PIN, should only be provided if the HSM device requires one to interact with the slot. |
|
||||
| `signing-key-slot` | Specifies which HSM object slot the signing key is in. |
|
||||
| `signing-key-label` | Specifies the HSM object label for the signing keypair's public key. |
|
||||
|
||||
- `inputs`: object containing paths for inputs
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `issuer-certificate-path` | Path to PEM issuer certificate. |
|
||||
|
||||
- `outputs`: object containing paths to write outputs.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `crl-path` | Path to store signed PEM CRL. |
|
||||
|
||||
- `crl-profile`: object containing profile for the CRL.
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `this-update` | Specifies the CRL thisUpdate date, in the format `2006-01-02 15:04:05`. The time will be interpreted as UTC. |
|
||||
|
|
|
@ -76,8 +76,6 @@ type certType int
|
|||
const (
|
||||
rootCert certType = iota
|
||||
intermediateCert
|
||||
ocspCert
|
||||
crlCert
|
||||
crossCert
|
||||
requestCert
|
||||
)
|
||||
|
@ -153,23 +151,12 @@ func (profile *certProfile) verifyProfile(ct certType) error {
|
|||
}
|
||||
|
||||
// BR 7.1.2.10.5 CA Certificate Certificate Policies
|
||||
// OID 2.23.140.1.2.1 is an anyPolicy
|
||||
// OID 2.23.140.1.2.1 is CABF BRs Domain Validated
|
||||
if len(profile.Policies) != 1 || profile.Policies[0].OID != "2.23.140.1.2.1" {
|
||||
return errors.New("policy should be exactly BRs domain-validated for subordinate CAs")
|
||||
}
|
||||
}
|
||||
|
||||
if ct == ocspCert || ct == crlCert {
|
||||
if len(profile.KeyUsages) != 0 {
|
||||
return errors.New("key-usages cannot be set for a delegated signer")
|
||||
}
|
||||
if profile.CRLURL != "" {
|
||||
return errors.New("crl-url cannot be set for a delegated signer")
|
||||
}
|
||||
if profile.OCSPURL != "" {
|
||||
return errors.New("ocsp-url cannot be set for a delegated signer")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -194,8 +181,6 @@ var stringToKeyUsage = map[string]x509.KeyUsage{
|
|||
"Cert Sign": x509.KeyUsageCertSign,
|
||||
}
|
||||
|
||||
var oidOCSPNoCheck = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 5}
|
||||
|
||||
func generateSKID(pk []byte) ([]byte, error) {
|
||||
var pkixPublicKey struct {
|
||||
Algo pkix.AlgorithmIdentifier
|
||||
|
@ -252,11 +237,6 @@ func makeTemplate(randReader io.Reader, profile *certProfile, pubKey []byte, tbc
|
|||
}
|
||||
ku |= kuBit
|
||||
}
|
||||
if ct == ocspCert {
|
||||
ku = x509.KeyUsageDigitalSignature
|
||||
} else if ct == crlCert {
|
||||
ku = x509.KeyUsageCRLSign
|
||||
}
|
||||
if ku == 0 {
|
||||
return nil, errors.New("at least one key usage must be set")
|
||||
}
|
||||
|
@ -296,14 +276,6 @@ func makeTemplate(randReader io.Reader, profile *certProfile, pubKey []byte, tbc
|
|||
// BR 7.1.2.1.2 Root CA Extensions
|
||||
// Extension Presence Critical Description
|
||||
// extKeyUsage MUST NOT N -
|
||||
case ocspCert:
|
||||
cert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageOCSPSigning}
|
||||
// ASN.1 NULL is 0x05, 0x00
|
||||
ocspNoCheckExt := pkix.Extension{Id: oidOCSPNoCheck, Value: []byte{5, 0}}
|
||||
cert.ExtraExtensions = append(cert.ExtraExtensions, ocspNoCheckExt)
|
||||
cert.IsCA = false
|
||||
case crlCert:
|
||||
cert.IsCA = false
|
||||
case requestCert, intermediateCert:
|
||||
// id-kp-serverAuth is included in intermediate certificates, as required by
|
||||
// Section 7.1.2.10.6 of the CA/BF Baseline Requirements.
|
||||
|
@ -314,6 +286,8 @@ func makeTemplate(randReader io.Reader, profile *certProfile, pubKey []byte, tbc
|
|||
case crossCert:
|
||||
cert.ExtKeyUsage = tbcs.ExtKeyUsage
|
||||
cert.MaxPathLenZero = tbcs.MaxPathLenZero
|
||||
// The SKID needs to match the previous SKID, no matter how it was computed.
|
||||
cert.SubjectKeyId = tbcs.SubjectKeyId
|
||||
}
|
||||
|
||||
for _, policyConfig := range profile.Policies {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
|
@ -174,73 +173,6 @@ func TestMakeTemplateRestrictedCrossCertificate(t *testing.T) {
|
|||
test.AssertEquals(t, cert.ExtKeyUsage[0], x509.ExtKeyUsageServerAuth)
|
||||
}
|
||||
|
||||
func TestMakeTemplateOCSP(t *testing.T) {
|
||||
s, ctx := pkcs11helpers.NewSessionWithMock()
|
||||
ctx.GenerateRandomFunc = realRand
|
||||
randReader := newRandReader(s)
|
||||
profile := &certProfile{
|
||||
SignatureAlgorithm: "SHA256WithRSA",
|
||||
CommonName: "common name",
|
||||
Organization: "organization",
|
||||
Country: "country",
|
||||
OCSPURL: "ocsp",
|
||||
CRLURL: "crl",
|
||||
IssuerURL: "issuer",
|
||||
NotAfter: "2018-05-18 11:31:00",
|
||||
NotBefore: "2018-05-18 11:31:00",
|
||||
}
|
||||
pubKey := samplePubkey()
|
||||
|
||||
cert, err := makeTemplate(randReader, profile, pubKey, nil, ocspCert)
|
||||
test.AssertNotError(t, err, "makeTemplate failed")
|
||||
|
||||
test.Assert(t, !cert.IsCA, "IsCA is set")
|
||||
// Check KU is only KeyUsageDigitalSignature
|
||||
test.AssertEquals(t, cert.KeyUsage, x509.KeyUsageDigitalSignature)
|
||||
// Check there is a single EKU with id-kp-OCSPSigning
|
||||
test.AssertEquals(t, len(cert.ExtKeyUsage), 1)
|
||||
test.AssertEquals(t, cert.ExtKeyUsage[0], x509.ExtKeyUsageOCSPSigning)
|
||||
// Check ExtraExtensions contains a single id-pkix-ocsp-nocheck
|
||||
hasExt := false
|
||||
asnNULL := []byte{5, 0}
|
||||
for _, ext := range cert.ExtraExtensions {
|
||||
if ext.Id.Equal(oidOCSPNoCheck) {
|
||||
if hasExt {
|
||||
t.Error("template contains multiple id-pkix-ocsp-nocheck extensions")
|
||||
}
|
||||
hasExt = true
|
||||
if !bytes.Equal(ext.Value, asnNULL) {
|
||||
t.Errorf("id-pkix-ocsp-nocheck has unexpected content: want %x, got %x", asnNULL, ext.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
test.Assert(t, hasExt, "template doesn't contain id-pkix-ocsp-nocheck extensions")
|
||||
}
|
||||
|
||||
func TestMakeTemplateCRL(t *testing.T) {
|
||||
s, ctx := pkcs11helpers.NewSessionWithMock()
|
||||
ctx.GenerateRandomFunc = realRand
|
||||
randReader := newRandReader(s)
|
||||
profile := &certProfile{
|
||||
SignatureAlgorithm: "SHA256WithRSA",
|
||||
CommonName: "common name",
|
||||
Organization: "organization",
|
||||
Country: "country",
|
||||
OCSPURL: "ocsp",
|
||||
CRLURL: "crl",
|
||||
IssuerURL: "issuer",
|
||||
NotAfter: "2018-05-18 11:31:00",
|
||||
NotBefore: "2018-05-18 11:31:00",
|
||||
}
|
||||
pubKey := samplePubkey()
|
||||
|
||||
cert, err := makeTemplate(randReader, profile, pubKey, nil, crlCert)
|
||||
test.AssertNotError(t, err, "makeTemplate failed")
|
||||
|
||||
test.Assert(t, !cert.IsCA, "IsCA is set")
|
||||
test.AssertEquals(t, cert.KeyUsage, x509.KeyUsageCRLSign)
|
||||
}
|
||||
|
||||
func TestVerifyProfile(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
profile certProfile
|
||||
|
@ -366,114 +298,6 @@ func TestVerifyProfile(t *testing.T) {
|
|||
},
|
||||
certType: []certType{rootCert},
|
||||
},
|
||||
{
|
||||
profile: certProfile{
|
||||
NotBefore: "a",
|
||||
NotAfter: "b",
|
||||
SignatureAlgorithm: "c",
|
||||
CommonName: "d",
|
||||
Organization: "e",
|
||||
Country: "f",
|
||||
IssuerURL: "g",
|
||||
KeyUsages: []string{"j"},
|
||||
},
|
||||
certType: []certType{ocspCert},
|
||||
expectedErr: "key-usages cannot be set for a delegated signer",
|
||||
},
|
||||
{
|
||||
profile: certProfile{
|
||||
NotBefore: "a",
|
||||
NotAfter: "b",
|
||||
SignatureAlgorithm: "c",
|
||||
CommonName: "d",
|
||||
Organization: "e",
|
||||
Country: "f",
|
||||
IssuerURL: "g",
|
||||
CRLURL: "i",
|
||||
},
|
||||
certType: []certType{ocspCert},
|
||||
expectedErr: "crl-url cannot be set for a delegated signer",
|
||||
},
|
||||
{
|
||||
profile: certProfile{
|
||||
NotBefore: "a",
|
||||
NotAfter: "b",
|
||||
SignatureAlgorithm: "c",
|
||||
CommonName: "d",
|
||||
Organization: "e",
|
||||
Country: "f",
|
||||
IssuerURL: "g",
|
||||
OCSPURL: "h",
|
||||
},
|
||||
certType: []certType{ocspCert},
|
||||
expectedErr: "ocsp-url cannot be set for a delegated signer",
|
||||
},
|
||||
{
|
||||
profile: certProfile{
|
||||
NotBefore: "a",
|
||||
NotAfter: "b",
|
||||
SignatureAlgorithm: "c",
|
||||
CommonName: "d",
|
||||
Organization: "e",
|
||||
Country: "f",
|
||||
IssuerURL: "g",
|
||||
},
|
||||
certType: []certType{ocspCert},
|
||||
},
|
||||
{
|
||||
profile: certProfile{
|
||||
NotBefore: "a",
|
||||
NotAfter: "b",
|
||||
SignatureAlgorithm: "c",
|
||||
CommonName: "d",
|
||||
Organization: "e",
|
||||
Country: "f",
|
||||
IssuerURL: "g",
|
||||
KeyUsages: []string{"j"},
|
||||
},
|
||||
certType: []certType{crlCert},
|
||||
expectedErr: "key-usages cannot be set for a delegated signer",
|
||||
},
|
||||
{
|
||||
profile: certProfile{
|
||||
NotBefore: "a",
|
||||
NotAfter: "b",
|
||||
SignatureAlgorithm: "c",
|
||||
CommonName: "d",
|
||||
Organization: "e",
|
||||
Country: "f",
|
||||
IssuerURL: "g",
|
||||
CRLURL: "i",
|
||||
},
|
||||
certType: []certType{crlCert},
|
||||
expectedErr: "crl-url cannot be set for a delegated signer",
|
||||
},
|
||||
{
|
||||
profile: certProfile{
|
||||
NotBefore: "a",
|
||||
NotAfter: "b",
|
||||
SignatureAlgorithm: "c",
|
||||
CommonName: "d",
|
||||
Organization: "e",
|
||||
Country: "f",
|
||||
IssuerURL: "g",
|
||||
OCSPURL: "h",
|
||||
},
|
||||
certType: []certType{crlCert},
|
||||
expectedErr: "ocsp-url cannot be set for a delegated signer",
|
||||
},
|
||||
{
|
||||
profile: certProfile{
|
||||
NotBefore: "a",
|
||||
NotAfter: "b",
|
||||
SignatureAlgorithm: "c",
|
||||
CommonName: "d",
|
||||
Organization: "e",
|
||||
Country: "f",
|
||||
IssuerURL: "g",
|
||||
},
|
||||
certType: []certType{crlCert},
|
||||
},
|
||||
{
|
||||
profile: certProfile{
|
||||
NotBefore: "a",
|
||||
|
|
|
@ -7,8 +7,9 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/letsencrypt/boulder/pkcs11helpers"
|
||||
"github.com/miekg/pkcs11"
|
||||
|
||||
"github.com/letsencrypt/boulder/pkcs11helpers"
|
||||
)
|
||||
|
||||
var stringToCurve = map[string]elliptic.Curve{
|
||||
|
@ -70,7 +71,7 @@ func ecPub(
|
|||
return nil, err
|
||||
}
|
||||
if pubKey.Curve != expectedCurve {
|
||||
return nil, errors.New("Returned EC parameters doesn't match expected curve")
|
||||
return nil, errors.New("returned EC parameters doesn't match expected curve")
|
||||
}
|
||||
log.Printf("\tX: %X\n", pubKey.X.Bytes())
|
||||
log.Printf("\tY: %X\n", pubKey.Y.Bytes())
|
||||
|
|
|
@ -7,8 +7,9 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/letsencrypt/boulder/pkcs11helpers"
|
||||
"github.com/miekg/pkcs11"
|
||||
|
||||
"github.com/letsencrypt/boulder/pkcs11helpers"
|
||||
)
|
||||
|
||||
type hsmRandReader struct {
|
||||
|
@ -49,7 +50,7 @@ func generateKey(session *pkcs11helpers.Session, label string, outputPath string
|
|||
{Type: pkcs11.CKA_LABEL, Value: []byte(label)},
|
||||
})
|
||||
if err != pkcs11helpers.ErrNoObject {
|
||||
return nil, fmt.Errorf("expected no preexisting objects with label %q in slot for key storage. got error: %s", label, err)
|
||||
return nil, fmt.Errorf("expected no preexisting objects with label %q in slot for key storage. got error: %w", label, err)
|
||||
}
|
||||
|
||||
var pubKey crypto.PublicKey
|
||||
|
@ -58,25 +59,25 @@ func generateKey(session *pkcs11helpers.Session, label string, outputPath string
|
|||
case "rsa":
|
||||
pubKey, keyID, err = rsaGenerate(session, label, config.RSAModLength)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate RSA key pair: %s", err)
|
||||
return nil, fmt.Errorf("failed to generate RSA key pair: %w", err)
|
||||
}
|
||||
case "ecdsa":
|
||||
pubKey, keyID, err = ecGenerate(session, label, config.ECDSACurve)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate ECDSA key pair: %s", err)
|
||||
return nil, fmt.Errorf("failed to generate ECDSA key pair: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
der, err := x509.MarshalPKIXPublicKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal public key: %s", err)
|
||||
return nil, fmt.Errorf("failed to marshal public key: %w", err)
|
||||
}
|
||||
|
||||
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})
|
||||
log.Printf("Public key PEM:\n%s\n", pemBytes)
|
||||
err = writeFile(outputPath, pemBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to write public key to %q: %s", outputPath, err)
|
||||
return nil, fmt.Errorf("failed to write public key to %q: %w", outputPath, err)
|
||||
}
|
||||
log.Printf("Public key written to %q\n", outputPath)
|
||||
|
||||
|
|
|
@ -239,7 +239,7 @@ type intermediateConfig struct {
|
|||
SkipLints []string `yaml:"skip-lints"`
|
||||
}
|
||||
|
||||
func (ic intermediateConfig) validate(ct certType) error {
|
||||
func (ic intermediateConfig) validate() error {
|
||||
err := ic.PKCS11.validate()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -260,7 +260,7 @@ func (ic intermediateConfig) validate(ct certType) error {
|
|||
}
|
||||
|
||||
// Certificate profile
|
||||
err = ic.CertProfile.verifyProfile(ct)
|
||||
err = ic.CertProfile.verifyProfile(intermediateCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -504,7 +504,7 @@ func loadCert(filename string) (*x509.Certificate, error) {
|
|||
log.Printf("Loaded certificate from %s\n", filename)
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("No data in cert PEM file %s", filename)
|
||||
return nil, fmt.Errorf("no data in cert PEM file %q", filename)
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
|
@ -599,7 +599,7 @@ func loadPubKey(filename string) (crypto.PublicKey, []byte, error) {
|
|||
log.Printf("Loaded public key from %s\n", filename)
|
||||
block, _ := pem.Decode(keyPEM)
|
||||
if block == nil {
|
||||
return nil, nil, fmt.Errorf("No data in cert PEM file %s", filename)
|
||||
return nil, nil, fmt.Errorf("no data in cert PEM file %q", filename)
|
||||
}
|
||||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
|
@ -658,17 +658,14 @@ func rootCeremony(configBytes []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func intermediateCeremony(configBytes []byte, ct certType) error {
|
||||
if ct != intermediateCert && ct != ocspCert && ct != crlCert {
|
||||
return fmt.Errorf("wrong certificate type provided")
|
||||
}
|
||||
func intermediateCeremony(configBytes []byte) error {
|
||||
var config intermediateConfig
|
||||
err := strictyaml.Unmarshal(configBytes, &config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse config: %s", err)
|
||||
}
|
||||
log.Printf("Preparing intermediate ceremony for %s\n", config.Outputs.CertificatePath)
|
||||
err = config.validate(ct)
|
||||
err = config.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate config: %s", err)
|
||||
}
|
||||
|
@ -684,7 +681,7 @@ func intermediateCeremony(configBytes []byte, ct certType) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, nil, ct)
|
||||
template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, nil, intermediateCert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create certificate profile: %s", err)
|
||||
}
|
||||
|
@ -713,10 +710,7 @@ func intermediateCeremony(configBytes []byte, ct certType) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func crossCertCeremony(configBytes []byte, ct certType) error {
|
||||
if ct != crossCert {
|
||||
return fmt.Errorf("wrong certificate type provided")
|
||||
}
|
||||
func crossCertCeremony(configBytes []byte) error {
|
||||
var config crossCertConfig
|
||||
err := strictyaml.Unmarshal(configBytes, &config)
|
||||
if err != nil {
|
||||
|
@ -743,7 +737,7 @@ func crossCertCeremony(configBytes []byte, ct certType) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, toBeCrossSigned, ct)
|
||||
template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, toBeCrossSigned, crossCert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create certificate profile: %s", err)
|
||||
}
|
||||
|
@ -773,12 +767,24 @@ func crossCertCeremony(configBytes []byte, ct certType) error {
|
|||
return fmt.Errorf("cross-signed subordinate CA's NotBefore predates the existing CA's NotBefore")
|
||||
}
|
||||
// BR 7.1.2.2.3 Cross-Certified Subordinate CA Extensions
|
||||
// We want the Extended Key Usages of our cross-signs to be identical to those
|
||||
// in the cert being cross-signed, for the sake of consistency. However, our
|
||||
// Root CA Certificates do not contain any EKUs, as required by BR 7.1.2.1.2.
|
||||
// Therefore, cross-signs of our roots count as "unrestricted" cross-signs per
|
||||
// the definition in BR 7.1.2.2.3, and are subject to the requirement that
|
||||
// the cross-sign's Issuer and Subject fields must either:
|
||||
// - have identical organizationNames; or
|
||||
// - have orgnaizationNames which are affiliates of each other.
|
||||
// Therefore, we enforce that cross-signs with empty EKUs have identical
|
||||
// Subject Organization Name fields... or allow one special case where the
|
||||
// issuer is "Internet Security Research Group" and the subject is "ISRG" to
|
||||
// allow us to migrate from the longer string to the shorter one.
|
||||
if !slices.Equal(lintCert.ExtKeyUsage, toBeCrossSigned.ExtKeyUsage) {
|
||||
return fmt.Errorf("lint cert and toBeCrossSigned cert EKUs differ")
|
||||
}
|
||||
if len(lintCert.ExtKeyUsage) == 0 {
|
||||
// "Unrestricted" case, the issuer and subject need to be the same or at least affiliates.
|
||||
if !slices.Equal(lintCert.Subject.Organization, issuer.Subject.Organization) {
|
||||
if !slices.Equal(lintCert.Subject.Organization, issuer.Subject.Organization) &&
|
||||
!(slices.Equal(issuer.Subject.Organization, []string{"Internet Security Research Group"}) && slices.Equal(lintCert.Subject.Organization, []string{"ISRG"})) {
|
||||
return fmt.Errorf("attempted unrestricted cross-sign of certificate operated by a different organization")
|
||||
}
|
||||
}
|
||||
|
@ -1044,12 +1050,12 @@ func main() {
|
|||
log.Fatalf("root ceremony failed: %s", err)
|
||||
}
|
||||
case "cross-certificate":
|
||||
err = crossCertCeremony(configBytes, crossCert)
|
||||
err = crossCertCeremony(configBytes)
|
||||
if err != nil {
|
||||
log.Fatalf("cross-certificate ceremony failed: %s", err)
|
||||
}
|
||||
case "intermediate":
|
||||
err = intermediateCeremony(configBytes, intermediateCert)
|
||||
err = intermediateCeremony(configBytes)
|
||||
if err != nil {
|
||||
log.Fatalf("intermediate ceremony failed: %s", err)
|
||||
}
|
||||
|
@ -1058,11 +1064,6 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatalf("cross-csr ceremony failed: %s", err)
|
||||
}
|
||||
case "ocsp-signer":
|
||||
err = intermediateCeremony(configBytes, ocspCert)
|
||||
if err != nil {
|
||||
log.Fatalf("ocsp signer ceremony failed: %s", err)
|
||||
}
|
||||
case "key":
|
||||
err = keyCeremony(configBytes)
|
||||
if err != nil {
|
||||
|
@ -1078,12 +1079,7 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatalf("crl ceremony failed: %s", err)
|
||||
}
|
||||
case "crl-signer":
|
||||
err = intermediateCeremony(configBytes, crlCert)
|
||||
if err != nil {
|
||||
log.Fatalf("crl signer ceremony failed: %s", err)
|
||||
}
|
||||
default:
|
||||
log.Fatalf("unknown ceremony-type, must be one of: root, cross-certificate, intermediate, cross-csr, ocsp-signer, key, ocsp-response, crl, crl-signer")
|
||||
log.Fatalf("unknown ceremony-type, must be one of: root, cross-certificate, intermediate, cross-csr, key, ocsp-response, crl")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -484,7 +484,7 @@ func TestIntermediateConfigValidate(t *testing.T) {
|
|||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.config.validate(intermediateCert)
|
||||
err := tc.config.validate()
|
||||
if err != nil && err.Error() != tc.expectedError {
|
||||
t.Fatalf("Unexpected error, wanted: %q, got: %q", tc.expectedError, err)
|
||||
} else if err == nil && tc.expectedError != "" {
|
||||
|
|
|
@ -38,8 +38,8 @@ import (
|
|||
"github.com/letsencrypt/boulder/sa"
|
||||
)
|
||||
|
||||
// For defense-in-depth in addition to using the PA & its hostnamePolicy to
|
||||
// check domain names we also perform a check against the regex's from the
|
||||
// For defense-in-depth in addition to using the PA & its identPolicy to check
|
||||
// domain names we also perform a check against the regex's from the
|
||||
// forbiddenDomains array
|
||||
var forbiddenDomainPatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`^\s*$`),
|
||||
|
@ -612,7 +612,7 @@ func main() {
|
|||
pa, err := policy.New(config.PA.Identifiers, config.PA.Challenges, logger)
|
||||
cmd.FailOnError(err, "Failed to create PA")
|
||||
|
||||
err = pa.LoadHostnamePolicyFile(config.CertChecker.HostnamePolicyFile)
|
||||
err = pa.LoadIdentPolicyFile(config.CertChecker.HostnamePolicyFile)
|
||||
cmd.FailOnError(err, "Failed to load HostnamePolicyFile")
|
||||
|
||||
if config.CertChecker.CTLogListFile != "" {
|
||||
|
|
|
@ -60,7 +60,7 @@ func init() {
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = pa.LoadHostnamePolicyFile("../../test/hostname-policy.yaml")
|
||||
err = pa.LoadIdentPolicyFile("../../test/ident-policy.yaml")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -537,7 +537,7 @@ func TestIsForbiddenDomain(t *testing.T) {
|
|||
// Note: These testcases are not an exhaustive representation of domains
|
||||
// Boulder won't issue for, but are instead testing the defense-in-depth
|
||||
// `isForbiddenDomain` function called *after* the PA has vetted the name
|
||||
// against the complex hostname policy file.
|
||||
// against the complex identifier policy file.
|
||||
testcases := []struct {
|
||||
Name string
|
||||
Expected bool
|
||||
|
|
|
@ -89,15 +89,6 @@ func (d *DBConfig) URL() (string, error) {
|
|||
return strings.TrimSpace(string(url)), err
|
||||
}
|
||||
|
||||
// SMTPConfig is deprecated.
|
||||
// TODO(#8199): Delete this when it is removed from bad-key-revoker's config.
|
||||
type SMTPConfig struct {
|
||||
PasswordConfig
|
||||
Server string `validate:"required"`
|
||||
Port string `validate:"required,numeric,min=1,max=65535"`
|
||||
Username string `validate:"required"`
|
||||
}
|
||||
|
||||
// PAConfig specifies how a policy authority should connect to its
|
||||
// database, what policies it should enforce, and what challenges
|
||||
// it should offer.
|
||||
|
|
|
@ -24,7 +24,7 @@ func init() {
|
|||
|
||||
func main() {
|
||||
inputFilename := flag.String("input", "", "File containing a list of reversed hostnames to check, newline separated. Defaults to stdin")
|
||||
policyFile := flag.String("policy", "test/hostname-policy.yaml", "File containing a hostname policy in yaml.")
|
||||
policyFile := flag.String("policy", "test/ident-policy.yaml", "File containing an identifier policy in YAML.")
|
||||
flag.Parse()
|
||||
|
||||
var input io.Reader
|
||||
|
@ -45,7 +45,7 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = pa.LoadHostnamePolicyFile(*policyFile)
|
||||
err = pa.LoadIdentPolicyFile(*policyFile)
|
||||
if err != nil {
|
||||
log.Fatalf("reading %s: %s", *policyFile, err)
|
||||
}
|
||||
|
|
|
@ -67,3 +67,8 @@ func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
d.Duration = dur
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML returns the string form of the duration, as a string.
|
||||
func (d Duration) MarshalYAML() (any, error) {
|
||||
return d.Duration.String(), nil
|
||||
}
|
||||
|
|
|
@ -25,6 +25,11 @@ func TLSALPNChallenge01(token string) Challenge {
|
|||
return newChallenge(ChallengeTypeTLSALPN01, token)
|
||||
}
|
||||
|
||||
// DNSAccountChallenge01 constructs a dns-account-01 challenge.
|
||||
func DNSAccountChallenge01(token string) Challenge {
|
||||
return newChallenge(ChallengeTypeDNSAccount01, token)
|
||||
}
|
||||
|
||||
// NewChallenge constructs a challenge of the given kind. It returns an
|
||||
// error if the challenge type is unrecognized.
|
||||
func NewChallenge(kind AcmeChallenge, token string) (Challenge, error) {
|
||||
|
@ -35,6 +40,8 @@ func NewChallenge(kind AcmeChallenge, token string) (Challenge, error) {
|
|||
return DNSChallenge01(token), nil
|
||||
case ChallengeTypeTLSALPN01:
|
||||
return TLSALPNChallenge01(token), nil
|
||||
case ChallengeTypeDNSAccount01:
|
||||
return DNSAccountChallenge01(token), nil
|
||||
default:
|
||||
return Challenge{}, fmt.Errorf("unrecognized challenge type %q", kind)
|
||||
}
|
||||
|
|
|
@ -32,12 +32,16 @@ func TestChallenges(t *testing.T) {
|
|||
dns01 := DNSChallenge01(token)
|
||||
test.AssertNotError(t, dns01.CheckPending(), "CheckConsistencyForClientOffer returned an error")
|
||||
|
||||
dnsAccount01 := DNSAccountChallenge01(token)
|
||||
test.AssertNotError(t, dnsAccount01.CheckPending(), "CheckConsistencyForClientOffer returned an error")
|
||||
|
||||
tlsalpn01 := TLSALPNChallenge01(token)
|
||||
test.AssertNotError(t, tlsalpn01.CheckPending(), "CheckConsistencyForClientOffer returned an error")
|
||||
|
||||
test.Assert(t, ChallengeTypeHTTP01.IsValid(), "Refused valid challenge")
|
||||
test.Assert(t, ChallengeTypeDNS01.IsValid(), "Refused valid challenge")
|
||||
test.Assert(t, ChallengeTypeTLSALPN01.IsValid(), "Refused valid challenge")
|
||||
test.Assert(t, ChallengeTypeDNSAccount01.IsValid(), "Refused valid challenge")
|
||||
test.Assert(t, !AcmeChallenge("nonsense-71").IsValid(), "Accepted invalid challenge")
|
||||
}
|
||||
|
||||
|
|
|
@ -53,15 +53,16 @@ type AcmeChallenge string
|
|||
|
||||
// These types are the available challenges
|
||||
const (
|
||||
ChallengeTypeHTTP01 = AcmeChallenge("http-01")
|
||||
ChallengeTypeDNS01 = AcmeChallenge("dns-01")
|
||||
ChallengeTypeTLSALPN01 = AcmeChallenge("tls-alpn-01")
|
||||
ChallengeTypeHTTP01 = AcmeChallenge("http-01")
|
||||
ChallengeTypeDNS01 = AcmeChallenge("dns-01")
|
||||
ChallengeTypeTLSALPN01 = AcmeChallenge("tls-alpn-01")
|
||||
ChallengeTypeDNSAccount01 = AcmeChallenge("dns-account-01")
|
||||
)
|
||||
|
||||
// IsValid tests whether the challenge is a known challenge
|
||||
func (c AcmeChallenge) IsValid() bool {
|
||||
switch c {
|
||||
case ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01:
|
||||
case ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01, ChallengeTypeDNSAccount01:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -228,7 +229,7 @@ func (ch Challenge) RecordsSane() bool {
|
|||
(ch.ValidationRecord[0].AddressUsed == netip.Addr{}) || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
|
||||
return false
|
||||
}
|
||||
case ChallengeTypeDNS01:
|
||||
case ChallengeTypeDNS01, ChallengeTypeDNSAccount01:
|
||||
if len(ch.ValidationRecord) > 1 {
|
||||
return false
|
||||
}
|
||||
|
@ -415,9 +416,9 @@ type CertificateStatus struct {
|
|||
LastExpirationNagSent time.Time `db:"lastExpirationNagSent"`
|
||||
|
||||
// NotAfter and IsExpired are convenience columns which allow expensive
|
||||
// queries to quickly filter out certificates that we don't need to care about
|
||||
// anymore. These are particularly useful for the expiration mailer and CRL
|
||||
// updater. See https://github.com/letsencrypt/boulder/issues/1864.
|
||||
// queries to quickly filter out certificates that we don't need to care
|
||||
// about anymore. These are particularly useful for the CRL updater. See
|
||||
// https://github.com/letsencrypt/boulder/issues/1864.
|
||||
NotAfter time.Time `db:"notAfter"`
|
||||
IsExpired bool `db:"isExpired"`
|
||||
|
||||
|
@ -429,16 +430,6 @@ type CertificateStatus struct {
|
|||
IssuerNameID int64 `db:"issuerID"`
|
||||
}
|
||||
|
||||
// FQDNSet contains the SHA256 hash of the lowercased, comma joined dNSNames
|
||||
// contained in a certificate.
|
||||
type FQDNSet struct {
|
||||
ID int64
|
||||
SetHash []byte
|
||||
Serial string
|
||||
Issued time.Time
|
||||
Expires time.Time
|
||||
}
|
||||
|
||||
// SCTDERs is a convenience type
|
||||
type SCTDERs [][]byte
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ func TestChallengeSanityCheck(t *testing.T) {
|
|||
}`), &accountKey)
|
||||
test.AssertNotError(t, err, "Error unmarshaling JWK")
|
||||
|
||||
types := []AcmeChallenge{ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01}
|
||||
types := []AcmeChallenge{ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01, ChallengeTypeDNSAccount01}
|
||||
for _, challengeType := range types {
|
||||
chall := Challenge{
|
||||
Type: challengeType,
|
||||
|
@ -152,6 +152,8 @@ func TestChallengeStringID(t *testing.T) {
|
|||
test.AssertEquals(t, ch.StringID(), "iFVMwA")
|
||||
ch.Type = ChallengeTypeHTTP01
|
||||
test.AssertEquals(t, ch.StringID(), "0Gexug")
|
||||
ch.Type = ChallengeTypeDNSAccount01
|
||||
test.AssertEquals(t, ch.StringID(), "8z2wSg")
|
||||
}
|
||||
|
||||
func TestFindChallengeByType(t *testing.T) {
|
||||
|
|
|
@ -540,7 +540,6 @@ type Registration struct {
|
|||
// Next unused field number: 10
|
||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
|
||||
Contact []string `protobuf:"bytes,3,rep,name=contact,proto3" json:"contact,omitempty"`
|
||||
Agreement string `protobuf:"bytes,5,opt,name=agreement,proto3" json:"agreement,omitempty"`
|
||||
CreatedAt *timestamppb.Timestamp `protobuf:"bytes,9,opt,name=createdAt,proto3" json:"createdAt,omitempty"`
|
||||
Status string `protobuf:"bytes,8,opt,name=status,proto3" json:"status,omitempty"`
|
||||
|
@ -592,13 +591,6 @@ func (x *Registration) GetKey() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *Registration) GetContact() []string {
|
||||
if x != nil {
|
||||
return x.Contact
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Registration) GetAgreement() string {
|
||||
if x != nil {
|
||||
return x.Agreement
|
||||
|
@ -1006,85 +998,84 @@ var file_core_proto_rawDesc = string([]byte{
|
|||
0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x49, 0x44, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08,
|
||||
0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x49, 0x44, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04,
|
||||
0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08,
|
||||
0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0xcc, 0x01, 0x0a,
|
||||
0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0xb8, 0x01, 0x0a,
|
||||
0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a,
|
||||
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a,
|
||||
0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
|
||||
0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x67, 0x72,
|
||||
0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x67,
|
||||
0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x64, 0x41, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41,
|
||||
0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a,
|
||||
0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x22, 0xc8, 0x02, 0x0a, 0x0d,
|
||||
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a,
|
||||
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a,
|
||||
0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x30, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66,
|
||||
0x69, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0a, 0x69, 0x64, 0x65,
|
||||
0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
|
||||
0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x1c, 0x0a, 0x09, 0x61, 0x67, 0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x09, 0x61, 0x67, 0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a,
|
||||
0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x78,
|
||||
0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e,
|
||||
0x67, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6c,
|
||||
0x6c, 0x65, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
|
||||
0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04,
|
||||
0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09,
|
||||
0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0x93, 0x04, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72,
|
||||
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64,
|
||||
0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74,
|
||||
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74,
|
||||
0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||
0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65,
|
||||
0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
|
||||
0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f,
|
||||
0x72, 0x65, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0b, 0x69,
|
||||
0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72,
|
||||
0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52,
|
||||
0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x76, 0x32, 0x41, 0x75, 0x74, 0x68,
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03,
|
||||
0x52, 0x10, 0x76, 0x32, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x65, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63,
|
||||
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c,
|
||||
0x12, 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x63,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
|
||||
0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x65,
|
||||
0x67, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x0f, 0x62, 0x65, 0x67, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
|
||||
0x73, 0x69, 0x6e, 0x67, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07,
|
||||
0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x22, 0x7a, 0x0a, 0x08,
|
||||
0x43, 0x52, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69,
|
||||
0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c,
|
||||
0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
|
||||
0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x6f,
|
||||
0x6b, 0x65, 0x64, 0x41, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64,
|
||||
0x41, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79,
|
||||
0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a,
|
||||
0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x06, 0x10,
|
||||
0x07, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x22, 0xc8, 0x02, 0x0a, 0x0d, 0x41, 0x75, 0x74, 0x68,
|
||||
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67,
|
||||
0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49,
|
||||
0x44, 0x12, 0x30, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18,
|
||||
0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x64, 0x65,
|
||||
0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66,
|
||||
0x69, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x65,
|
||||
0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65,
|
||||
0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x73, 0x18,
|
||||
0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61,
|
||||
0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67,
|
||||
0x65, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50,
|
||||
0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06,
|
||||
0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x02,
|
||||
0x10, 0x03, 0x22, 0x93, 0x04, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02,
|
||||
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x0e,
|
||||
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, 0x07,
|
||||
0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72,
|
||||
0x65, 0x73, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72,
|
||||
0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49,
|
||||
0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74,
|
||||
0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f,
|
||||
0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x05, 0x65, 0x72, 0x72,
|
||||
0x6f, 0x72, 0x12, 0x2a, 0x0a, 0x10, 0x76, 0x32, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03, 0x52, 0x10, 0x76, 0x32,
|
||||
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2c,
|
||||
0x0a, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72,
|
||||
0x69, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69,
|
||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x34, 0x0a, 0x07,
|
||||
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x64, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0e, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50,
|
||||
0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65,
|
||||
0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65,
|
||||
0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x65, 0x67, 0x61, 0x6e, 0x50,
|
||||
0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||
0x0f, 0x62, 0x65, 0x67, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67,
|
||||
0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, 0x08, 0x0a,
|
||||
0x10, 0x0b, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x22, 0x7a, 0x0a, 0x08, 0x43, 0x52, 0x4c, 0x45,
|
||||
0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06,
|
||||
0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x65,
|
||||
0x61, 0x73, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41,
|
||||
0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x4a, 0x04,
|
||||
0x08, 0x03, 0x10, 0x04, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62,
|
||||
0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
var (
|
||||
|
|
|
@ -83,7 +83,7 @@ message Registration {
|
|||
// Next unused field number: 10
|
||||
int64 id = 1;
|
||||
bytes key = 2;
|
||||
repeated string contact = 3;
|
||||
reserved 3; // Previously contact
|
||||
reserved 4; // Previously contactsPresent
|
||||
string agreement = 5;
|
||||
reserved 6; // Previously initialIP
|
||||
|
|
|
@ -123,7 +123,7 @@ func TestTableFromQuery(t *testing.T) {
|
|||
expectedTable string
|
||||
}{
|
||||
{
|
||||
query: "SELECT id, jwk, jwk_sha256, contact, agreement, createdAt, LockCol, status FROM registrations WHERE jwk_sha256 = ?",
|
||||
query: "SELECT id, jwk, jwk_sha256, contact, agreement, createdAt, status FROM registrations WHERE jwk_sha256 = ?",
|
||||
expectedTable: "registrations",
|
||||
},
|
||||
{
|
||||
|
@ -135,11 +135,11 @@ func TestTableFromQuery(t *testing.T) {
|
|||
expectedTable: "authz2",
|
||||
},
|
||||
{
|
||||
query: "insert into `registrations` (`id`,`jwk`,`jw k_sha256`,`contact`,`agreement`,`createdAt`,`LockCol`,`status`) values (null,?,?,?,?,?,?,?,?);",
|
||||
query: "insert into `registrations` (`id`,`jwk`,`jwk_sha256`,`contact`,`agreement`,`createdAt`,`status`) values (null,?,?,?,?,?,?,?);",
|
||||
expectedTable: "`registrations`",
|
||||
},
|
||||
{
|
||||
query: "update `registrations` set `jwk`=?, `jwk_sh a256`=?, `contact`=?, `agreement`=?, `createdAt`=?, `LockCol` =?, `status`=? where `id`=? and `LockCol`=?;",
|
||||
query: "update `registrations` set `jwk`=?, `jwk_sha256`=?, `contact`=?, `agreement`=?, `createdAt`=?, `status`=? where `id`=?;",
|
||||
expectedTable: "`registrations`",
|
||||
},
|
||||
{
|
||||
|
|
|
@ -79,7 +79,7 @@ services:
|
|||
- setup
|
||||
|
||||
bmysql:
|
||||
image: mariadb:10.6.22
|
||||
image: mariadb:10.11.13
|
||||
networks:
|
||||
bouldernet:
|
||||
aliases:
|
||||
|
|
|
@ -18,10 +18,12 @@ const (
|
|||
// tokenPath is the path to the Salesforce OAuth2 token endpoint.
|
||||
tokenPath = "/services/oauth2/token"
|
||||
|
||||
// contactsPath is the path to the Pardot v5 Prospects endpoint. This
|
||||
// endpoint will create a new Prospect if one does not already exist with
|
||||
// the same email address.
|
||||
contactsPath = "/api/v5/objects/prospects"
|
||||
// contactsPath is the path to the Pardot v5 Prospect upsert-by-email
|
||||
// endpoint. This endpoint will create a new Prospect if one does not
|
||||
// already exist with the same email address.
|
||||
//
|
||||
// https://developer.salesforce.com/docs/marketing/pardot/guide/prospect-v5.html#prospect-upsert-by-email
|
||||
contactsPath = "/api/v5/objects/prospects/do/upsertLatestByEmail"
|
||||
|
||||
// maxAttempts is the maximum number of attempts to retry a request.
|
||||
maxAttempts = 3
|
||||
|
@ -60,7 +62,7 @@ type PardotClientImpl struct {
|
|||
businessUnit string
|
||||
clientId string
|
||||
clientSecret string
|
||||
contactsURL string
|
||||
endpointURL string
|
||||
tokenURL string
|
||||
token *oAuthToken
|
||||
clk clock.Clock
|
||||
|
@ -70,7 +72,7 @@ var _ PardotClient = &PardotClientImpl{}
|
|||
|
||||
// NewPardotClientImpl creates a new PardotClientImpl.
|
||||
func NewPardotClientImpl(clk clock.Clock, businessUnit, clientId, clientSecret, oauthbaseURL, pardotBaseURL string) (*PardotClientImpl, error) {
|
||||
contactsURL, err := url.JoinPath(pardotBaseURL, contactsPath)
|
||||
endpointURL, err := url.JoinPath(pardotBaseURL, contactsPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to join contacts path: %w", err)
|
||||
}
|
||||
|
@ -83,7 +85,7 @@ func NewPardotClientImpl(clk clock.Clock, businessUnit, clientId, clientSecret,
|
|||
businessUnit: businessUnit,
|
||||
clientId: clientId,
|
||||
clientSecret: clientSecret,
|
||||
contactsURL: contactsURL,
|
||||
endpointURL: endpointURL,
|
||||
tokenURL: tokenURL,
|
||||
token: &oAuthToken{},
|
||||
clk: clk,
|
||||
|
@ -140,6 +142,19 @@ func redactEmail(body []byte, email string) string {
|
|||
return string(bytes.ReplaceAll(body, []byte(email), []byte("[REDACTED]")))
|
||||
}
|
||||
|
||||
type prospect struct {
|
||||
// Email is the email address of the prospect.
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type upsertPayload struct {
|
||||
// MatchEmail is the email address to match against existing prospects to
|
||||
// avoid adding duplicates.
|
||||
MatchEmail string `json:"matchEmail"`
|
||||
// Prospect is the prospect data to be upserted.
|
||||
Prospect prospect `json:"prospect"`
|
||||
}
|
||||
|
||||
// SendContact submits an email to the Pardot Contacts endpoint, retrying up
|
||||
// to 3 times with exponential backoff.
|
||||
func (pc *PardotClientImpl) SendContact(email string) error {
|
||||
|
@ -156,7 +171,10 @@ func (pc *PardotClientImpl) SendContact(email string) error {
|
|||
return fmt.Errorf("failed to update token: %w", err)
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(map[string]string{"email": email})
|
||||
payload, err := json.Marshal(upsertPayload{
|
||||
MatchEmail: email,
|
||||
Prospect: prospect{Email: email},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal payload: %w", err)
|
||||
}
|
||||
|
@ -165,7 +183,7 @@ func (pc *PardotClientImpl) SendContact(email string) error {
|
|||
for attempt := range maxAttempts {
|
||||
time.Sleep(core.RetryBackoff(attempt, retryBackoffMin, retryBackoffMax, retryBackoffBase))
|
||||
|
||||
req, err := http.NewRequest("POST", pc.contactsURL, bytes.NewReader(payload))
|
||||
req, err := http.NewRequest("POST", pc.endpointURL, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
finalErr = fmt.Errorf("failed to create new contact request: %w", err)
|
||||
continue
|
||||
|
|
|
@ -245,7 +245,7 @@ func CertificatesPerDomainError(retryAfter time.Duration, msg string, args ...an
|
|||
func CertificatesPerFQDNSetError(retryAfter time.Duration, msg string, args ...any) error {
|
||||
return &BoulderError{
|
||||
Type: RateLimit,
|
||||
Detail: fmt.Sprintf(msg+": see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-exact-set-of-hostnames", args...),
|
||||
Detail: fmt.Sprintf(msg+": see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-exact-set-of-identifiers", args...),
|
||||
RetryAfter: retryAfter,
|
||||
}
|
||||
}
|
||||
|
|
6
go.mod
6
go.mod
|
@ -5,9 +5,9 @@ go 1.24.0
|
|||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.5
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.17
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0
|
||||
github.com/aws/smithy-go v1.22.4
|
||||
github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941
|
||||
github.com/eggsampler/acme/v3 v3.6.2
|
||||
github.com/go-jose/go-jose/v4 v4.1.0
|
||||
github.com/go-logr/stdr v1.2.2
|
||||
github.com/go-sql-driver/mysql v1.9.1
|
||||
|
@ -25,7 +25,7 @@ require (
|
|||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/prometheus/client_model v0.6.1
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.5.3
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
github.com/redis/go-redis/v9 v9.10.0
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399
|
||||
github.com/weppos/publicsuffix-go v0.40.3-0.20250307081557-c05521c3453a
|
||||
github.com/zmap/zcrypto v0.0.0-20250129210703-03c45d0bae98
|
||||
|
|
12
go.sum
12
go.sum
|
@ -33,8 +33,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzRE
|
|||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0 h1:1GmCadhKR3J2sMVKs2bAYq9VnwYeCqfRyZzD4RASGlA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0 h1:5Y75q0RPQoAbieyOuGLhjV9P3txvYgXv2lg0UwJOfmE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
|
||||
|
@ -70,8 +70,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941 h1:CnQwymLMJ3MSfjbZQ/bpaLfuXBZuM3LUgAHJ0gO/7d8=
|
||||
github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo=
|
||||
github.com/eggsampler/acme/v3 v3.6.2 h1:gvyZbQ92wNQLDASVftGpHEdFwPSfg0+17P0lLt09Tp8=
|
||||
github.com/eggsampler/acme/v3 v3.6.2/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
|
@ -214,8 +214,8 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3 h1:1/BDligzCa40GTllkDnY3Y5DTH
|
|||
github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3/go.mod h1:3dZmcLn3Qw6FLlWASn1g4y+YO9ycEFUOM+bhBmzLVKQ=
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.5.3 h1:kuvuJL/+MZIEdvtb/kTBRiRgYaOmx1l+lYJyVdrRUOs=
|
||||
github.com/redis/go-redis/extra/redisotel/v9 v9.5.3/go.mod h1:7f/FMrf5RRRVHXgfk7CzSVzXHiWeuOQUu2bsVqWoa+g=
|
||||
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
|
||||
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
|
||||
github.com/redis/go-redis/v9 v9.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
|
||||
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
|
|
|
@ -232,10 +232,6 @@ func RegistrationToPB(reg core.Registration) (*corepb.Registration, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var contacts []string
|
||||
if reg.Contact != nil {
|
||||
contacts = *reg.Contact
|
||||
}
|
||||
var createdAt *timestamppb.Timestamp
|
||||
if reg.CreatedAt != nil {
|
||||
createdAt = timestamppb.New(reg.CreatedAt.UTC())
|
||||
|
@ -247,7 +243,6 @@ func RegistrationToPB(reg core.Registration) (*corepb.Registration, error) {
|
|||
return &corepb.Registration{
|
||||
Id: reg.ID,
|
||||
Key: keyBytes,
|
||||
Contact: contacts,
|
||||
Agreement: reg.Agreement,
|
||||
CreatedAt: createdAt,
|
||||
Status: string(reg.Status),
|
||||
|
@ -265,14 +260,9 @@ func PbToRegistration(pb *corepb.Registration) (core.Registration, error) {
|
|||
c := pb.CreatedAt.AsTime()
|
||||
createdAt = &c
|
||||
}
|
||||
var contacts *[]string
|
||||
if len(pb.Contact) != 0 {
|
||||
contacts = &pb.Contact
|
||||
}
|
||||
return core.Registration{
|
||||
ID: pb.Id,
|
||||
Key: &key,
|
||||
Contact: contacts,
|
||||
Agreement: pb.Agreement,
|
||||
CreatedAt: createdAt,
|
||||
Status: core.AcmeStatus(pb.Status),
|
||||
|
|
|
@ -167,7 +167,6 @@ func TestValidationResult(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRegistration(t *testing.T) {
|
||||
contacts := []string{"email"}
|
||||
var key jose.JSONWebKey
|
||||
err := json.Unmarshal([]byte(`
|
||||
{
|
||||
|
@ -181,7 +180,6 @@ func TestRegistration(t *testing.T) {
|
|||
inReg := core.Registration{
|
||||
ID: 1,
|
||||
Key: &key,
|
||||
Contact: &contacts,
|
||||
Agreement: "yup",
|
||||
CreatedAt: &createdAt,
|
||||
Status: core.StatusValid,
|
||||
|
@ -192,28 +190,9 @@ func TestRegistration(t *testing.T) {
|
|||
test.AssertNotError(t, err, "PbToRegistration failed")
|
||||
test.AssertDeepEquals(t, inReg, outReg)
|
||||
|
||||
inReg.Contact = nil
|
||||
pbReg, err = RegistrationToPB(inReg)
|
||||
test.AssertNotError(t, err, "registrationToPB failed")
|
||||
pbReg.Contact = []string{}
|
||||
outReg, err = PbToRegistration(pbReg)
|
||||
test.AssertNotError(t, err, "PbToRegistration failed")
|
||||
test.AssertDeepEquals(t, inReg, outReg)
|
||||
|
||||
var empty []string
|
||||
inReg.Contact = &empty
|
||||
pbReg, err = RegistrationToPB(inReg)
|
||||
test.AssertNotError(t, err, "registrationToPB failed")
|
||||
outReg, err = PbToRegistration(pbReg)
|
||||
test.AssertNotError(t, err, "PbToRegistration failed")
|
||||
if outReg.Contact != nil {
|
||||
t.Errorf("Empty contacts should be a nil slice")
|
||||
}
|
||||
|
||||
inRegNilCreatedAt := core.Registration{
|
||||
ID: 1,
|
||||
Key: &key,
|
||||
Contact: &contacts,
|
||||
Agreement: "yup",
|
||||
CreatedAt: nil,
|
||||
Status: core.StatusValid,
|
||||
|
|
|
@ -157,6 +157,8 @@ func parseReservedPrefixFile(registryData []byte, addressFamily string) ([]reser
|
|||
|
||||
// IsReservedAddr returns an error if an IP address is part of a reserved range.
|
||||
func IsReservedAddr(ip netip.Addr) error {
|
||||
// Strip zone from IPv6 addresses before checking
|
||||
ip = ip.WithZone("")
|
||||
for _, rpx := range reservedPrefixes {
|
||||
if rpx.addressBlock.Contains(ip) {
|
||||
return fmt.Errorf("IP address is in a reserved address block: %s: %s", rpx.rfc, rpx.name)
|
||||
|
|
|
@ -26,6 +26,9 @@ func TestIsReservedAddr(t *testing.T) {
|
|||
{"febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "Link-Local Unicast"}, // highest IP in a reserved /10
|
||||
{"fec0::1", ""}, // second-lowest IP just above a reserved /10
|
||||
|
||||
{"fe80::1%eth0", "Link-Local Unicast"}, // IPv6 link-local with zone
|
||||
{"::1%lo", "Loopback Address"}, // IPv6 loopback with zone
|
||||
|
||||
{"192.0.0.170", "NAT64/DNS64 Discovery"}, // first of two reserved IPs that are comma-split in IANA's CSV; also a more-specific of a larger reserved block that comes first
|
||||
{"192.0.0.171", "NAT64/DNS64 Discovery"}, // second of two reserved IPs that are comma-split in IANA's CSV; also a more-specific of a larger reserved block that comes first
|
||||
{"2001:1::1", "Port Control Protocol Anycast"}, // reserved IP that comes after a line with a line break in IANA's CSV; also a more-specific of a larger reserved block that comes first
|
||||
|
|
|
@ -118,10 +118,28 @@ func NewIP(ip netip.Addr) ACMEIdentifier {
|
|||
// RFC 8738, Sec. 3: The identifier value MUST contain the textual form
|
||||
// of the address as defined in RFC 1123, Sec. 2.1 for IPv4 and in RFC
|
||||
// 5952, Sec. 4 for IPv6.
|
||||
Value: ip.String(),
|
||||
Value: ip.WithZone("").String(),
|
||||
}
|
||||
}
|
||||
|
||||
// FromString converts a string to an ACMEIdentifier.
|
||||
func FromString(identStr string) ACMEIdentifier {
|
||||
ip, err := netip.ParseAddr(identStr)
|
||||
if err == nil {
|
||||
return NewIP(ip)
|
||||
}
|
||||
return NewDNS(identStr)
|
||||
}
|
||||
|
||||
// FromStringSlice converts a slice of strings to a slice of ACMEIdentifier.
|
||||
func FromStringSlice(identStrs []string) ACMEIdentifiers {
|
||||
var idents ACMEIdentifiers
|
||||
for _, identStr := range identStrs {
|
||||
idents = append(idents, FromString(identStr))
|
||||
}
|
||||
return idents
|
||||
}
|
||||
|
||||
// fromX509 extracts the Subject Alternative Names from a certificate or CSR's fields, and
|
||||
// returns a slice of ACMEIdentifiers.
|
||||
func fromX509(commonName string, dnsNames []string, ipAddresses []net.IP) ACMEIdentifiers {
|
||||
|
|
|
@ -10,6 +10,39 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestNewIP(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
ip netip.Addr
|
||||
want ACMEIdentifier
|
||||
}{
|
||||
{
|
||||
name: "IPv4 address",
|
||||
ip: netip.MustParseAddr("9.9.9.9"),
|
||||
want: ACMEIdentifier{Type: TypeIP, Value: "9.9.9.9"},
|
||||
},
|
||||
{
|
||||
name: "IPv6 address",
|
||||
ip: netip.MustParseAddr("fe80::cafe"),
|
||||
want: ACMEIdentifier{Type: TypeIP, Value: "fe80::cafe"},
|
||||
},
|
||||
{
|
||||
name: "IPv6 address with scope zone",
|
||||
ip: netip.MustParseAddr("fe80::cafe%lo"),
|
||||
want: ACMEIdentifier{Type: TypeIP, Value: "fe80::cafe"},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := NewIP(tc.ip)
|
||||
if got != tc.want {
|
||||
t.Errorf("NewIP(%#v) = %#v, but want %#v", tc.ip, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFromX509 tests FromCert and FromCSR, which are fromX509's public
|
||||
// wrappers.
|
||||
func TestFromX509(t *testing.T) {
|
||||
|
|
|
@ -142,10 +142,10 @@ func (p *Profile) GenerateValidity(now time.Time) (time.Time, time.Time) {
|
|||
// Don't use the full maxBackdate, to ensure that the actual backdate remains
|
||||
// acceptable throughout the rest of the issuance process.
|
||||
backdate := time.Duration(float64(p.maxBackdate.Nanoseconds()) * 0.9)
|
||||
notBefore := now.Add(-1 * backdate)
|
||||
notBefore := now.Add(-1 * backdate).Truncate(time.Second)
|
||||
// Subtract one second, because certificate validity periods are *inclusive*
|
||||
// of their final second (Baseline Requirements, Section 1.6.1).
|
||||
notAfter := notBefore.Add(p.maxValidity).Add(-1 * time.Second)
|
||||
notAfter := notBefore.Add(p.maxValidity).Add(-1 * time.Second).Truncate(time.Second)
|
||||
return notBefore, notAfter
|
||||
}
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ func TestLoadSigner(t *testing.T) {
|
|||
{"invalid key file", IssuerLoc{File: "../test/hierarchy/int-e1.crl.pem"}, "unable to parse"},
|
||||
{"ECDSA key file", IssuerLoc{File: "../test/hierarchy/int-e1.key.pem"}, ""},
|
||||
{"RSA key file", IssuerLoc{File: "../test/hierarchy/int-r3.key.pem"}, ""},
|
||||
{"invalid config file", IssuerLoc{ConfigFile: "../test/hostname-policy.yaml"}, "invalid character"},
|
||||
{"invalid config file", IssuerLoc{ConfigFile: "../test/ident-policy.yaml"}, "invalid character"},
|
||||
// Note that we don't have a test for "valid config file" because it would
|
||||
// always fail -- in CI, the softhsm hasn't been initialized, so there's no
|
||||
// key to look up; locally even if the softhsm has been initialized, the
|
||||
|
|
|
@ -77,7 +77,6 @@ func (sa *StorageAuthorityReadOnly) GetRegistration(_ context.Context, req *sapb
|
|||
Id: req.Id,
|
||||
Key: []byte(test1KeyPublicJSON),
|
||||
Agreement: agreementURL,
|
||||
Contact: []string{"mailto:person@mail.com"},
|
||||
Status: string(core.StatusValid),
|
||||
}
|
||||
|
||||
|
@ -131,14 +130,11 @@ func (sa *StorageAuthorityReadOnly) GetRegistrationByKey(_ context.Context, req
|
|||
return nil, err
|
||||
}
|
||||
|
||||
contacts := []string{"mailto:person@mail.com"}
|
||||
|
||||
if bytes.Equal(req.Jwk, []byte(test1KeyPublicJSON)) {
|
||||
return &corepb.Registration{
|
||||
Id: 1,
|
||||
Key: req.Jwk,
|
||||
Agreement: agreementURL,
|
||||
Contact: contacts,
|
||||
Status: string(core.StatusValid),
|
||||
}, nil
|
||||
}
|
||||
|
@ -172,7 +168,6 @@ func (sa *StorageAuthorityReadOnly) GetRegistrationByKey(_ context.Context, req
|
|||
Id: 2,
|
||||
Key: req.Jwk,
|
||||
Agreement: agreementURL,
|
||||
Contact: contacts,
|
||||
Status: string(core.StatusDeactivated),
|
||||
}, nil
|
||||
}
|
||||
|
|
174
policy/pa.go
174
policy/pa.go
|
@ -28,10 +28,11 @@ import (
|
|||
type AuthorityImpl struct {
|
||||
log blog.Logger
|
||||
|
||||
blocklist map[string]bool
|
||||
exactBlocklist map[string]bool
|
||||
wildcardExactBlocklist map[string]bool
|
||||
blocklistMu sync.RWMutex
|
||||
domainBlocklist map[string]bool
|
||||
fqdnBlocklist map[string]bool
|
||||
wildcardFqdnBlocklist map[string]bool
|
||||
ipPrefixBlocklist []netip.Prefix
|
||||
blocklistMu sync.RWMutex
|
||||
|
||||
enabledChallenges map[core.AcmeChallenge]bool
|
||||
enabledIdentifiers map[identifier.IdentifierType]bool
|
||||
|
@ -46,56 +47,61 @@ func New(identifierTypes map[identifier.IdentifierType]bool, challengeTypes map[
|
|||
}, nil
|
||||
}
|
||||
|
||||
// blockedNamesPolicy is a struct holding lists of blocked domain names. One for
|
||||
// exact blocks and one for blocks including all subdomains.
|
||||
type blockedNamesPolicy struct {
|
||||
// ExactBlockedNames is a list of domain names. Issuance for names exactly
|
||||
// matching an entry in the list will be forbidden. (e.g. `ExactBlockedNames`
|
||||
// containing `www.example.com` will not block `example.com` or
|
||||
// `mail.example.com`).
|
||||
// blockedIdentsPolicy is a struct holding lists of blocked identifiers.
|
||||
type blockedIdentsPolicy struct {
|
||||
// ExactBlockedNames is a list of Fully Qualified Domain Names (FQDNs).
|
||||
// Issuance for names exactly matching an entry in the list will be
|
||||
// forbidden. (e.g. `ExactBlockedNames` containing `www.example.com` will
|
||||
// not block `example.com`, `mail.example.com`, or `dev.www.example.com`).
|
||||
ExactBlockedNames []string `yaml:"ExactBlockedNames"`
|
||||
// HighRiskBlockedNames is like ExactBlockedNames except that issuance is
|
||||
// blocked for subdomains as well. (e.g. BlockedNames containing `example.com`
|
||||
// will block `www.example.com`).
|
||||
|
||||
// HighRiskBlockedNames is a list of domain names: like ExactBlockedNames
|
||||
// except that issuance is blocked for subdomains as well. (e.g.
|
||||
// BlockedNames containing `example.com` will block `www.example.com`).
|
||||
//
|
||||
// This list typically doesn't change with much regularity.
|
||||
HighRiskBlockedNames []string `yaml:"HighRiskBlockedNames"`
|
||||
|
||||
// AdminBlockedNames operates the same as BlockedNames but is changed with more
|
||||
// frequency based on administrative blocks/revocations that are added over
|
||||
// time above and beyond the high-risk domains. Managing these entries separately
|
||||
// from HighRiskBlockedNames makes it easier to vet changes accurately.
|
||||
// AdminBlockedNames operates the same as HighRiskBlockedNames but is
|
||||
// changed with more frequency based on administrative blocks/revocations
|
||||
// that are added over time above and beyond the high-risk domains. Managing
|
||||
// these entries separately from HighRiskBlockedNames makes it easier to vet
|
||||
// changes accurately.
|
||||
AdminBlockedNames []string `yaml:"AdminBlockedNames"`
|
||||
|
||||
// AdminBlockedPrefixes is a list of IP address prefixes. All IP addresses
|
||||
// contained within the prefix are blocked.
|
||||
AdminBlockedPrefixes []string `yaml:"AdminBlockedPrefixes"`
|
||||
}
|
||||
|
||||
// LoadHostnamePolicyFile will load the given policy file, returning an error if
|
||||
// it fails.
|
||||
func (pa *AuthorityImpl) LoadHostnamePolicyFile(f string) error {
|
||||
// LoadIdentPolicyFile will load the given policy file, returning an error if it
|
||||
// fails.
|
||||
func (pa *AuthorityImpl) LoadIdentPolicyFile(f string) error {
|
||||
configBytes, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash := sha256.Sum256(configBytes)
|
||||
pa.log.Infof("loading hostname policy, sha256: %s", hex.EncodeToString(hash[:]))
|
||||
var policy blockedNamesPolicy
|
||||
pa.log.Infof("loading identifier policy, sha256: %s", hex.EncodeToString(hash[:]))
|
||||
var policy blockedIdentsPolicy
|
||||
err = strictyaml.Unmarshal(configBytes, &policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(policy.HighRiskBlockedNames) == 0 {
|
||||
return fmt.Errorf("No entries in HighRiskBlockedNames.")
|
||||
return fmt.Errorf("no entries in HighRiskBlockedNames")
|
||||
}
|
||||
if len(policy.ExactBlockedNames) == 0 {
|
||||
return fmt.Errorf("No entries in ExactBlockedNames.")
|
||||
return fmt.Errorf("no entries in ExactBlockedNames")
|
||||
}
|
||||
return pa.processHostnamePolicy(policy)
|
||||
return pa.processIdentPolicy(policy)
|
||||
}
|
||||
|
||||
// processHostnamePolicy handles loading a new blockedNamesPolicy into the PA.
|
||||
// All of the policy.ExactBlockedNames will be added to the
|
||||
// wildcardExactBlocklist by processHostnamePolicy to ensure that wildcards for
|
||||
// exact blocked names entries are forbidden.
|
||||
func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error {
|
||||
// processIdentPolicy handles loading a new blockedIdentsPolicy into the PA. All
|
||||
// of the policy.ExactBlockedNames will be added to the wildcardExactBlocklist
|
||||
// by processIdentPolicy to ensure that wildcards for exact blocked names
|
||||
// entries are forbidden.
|
||||
func (pa *AuthorityImpl) processIdentPolicy(policy blockedIdentsPolicy) error {
|
||||
nameMap := make(map[string]bool)
|
||||
for _, v := range policy.HighRiskBlockedNames {
|
||||
nameMap[v] = true
|
||||
|
@ -103,6 +109,7 @@ func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error
|
|||
for _, v := range policy.AdminBlockedNames {
|
||||
nameMap[v] = true
|
||||
}
|
||||
|
||||
exactNameMap := make(map[string]bool)
|
||||
wildcardNameMap := make(map[string]bool)
|
||||
for _, v := range policy.ExactBlockedNames {
|
||||
|
@ -119,16 +126,28 @@ func (pa *AuthorityImpl) processHostnamePolicy(policy blockedNamesPolicy) error
|
|||
// at least be a "something." and a TLD like "com"
|
||||
if len(parts) < 2 {
|
||||
return fmt.Errorf(
|
||||
"Malformed ExactBlockedNames entry, only one label: %q", v)
|
||||
"malformed ExactBlockedNames entry, only one label: %q", v)
|
||||
}
|
||||
// Add the second part, the domain minus the first label, to the
|
||||
// wildcardNameMap to block issuance for `*.`+parts[1]
|
||||
wildcardNameMap[parts[1]] = true
|
||||
}
|
||||
|
||||
var prefixes []netip.Prefix
|
||||
for _, p := range policy.AdminBlockedPrefixes {
|
||||
prefix, err := netip.ParsePrefix(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"malformed AdminBlockedPrefixes entry, not a prefix: %q", p)
|
||||
}
|
||||
prefixes = append(prefixes, prefix)
|
||||
}
|
||||
|
||||
pa.blocklistMu.Lock()
|
||||
pa.blocklist = nameMap
|
||||
pa.exactBlocklist = exactNameMap
|
||||
pa.wildcardExactBlocklist = wildcardNameMap
|
||||
pa.domainBlocklist = nameMap
|
||||
pa.fqdnBlocklist = exactNameMap
|
||||
pa.wildcardFqdnBlocklist = wildcardNameMap
|
||||
pa.ipPrefixBlocklist = prefixes
|
||||
pa.blocklistMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
@ -326,6 +345,7 @@ func ValidDomain(domain string) error {
|
|||
// ValidIP checks that an IP address:
|
||||
// - isn't empty
|
||||
// - is an IPv4 or IPv6 address
|
||||
// - doesn't contain a scope zone (RFC 4007)
|
||||
// - isn't in an IANA special-purpose address registry
|
||||
//
|
||||
// It does NOT ensure that the IP address is absent from any PA blocked lists.
|
||||
|
@ -340,7 +360,7 @@ func ValidIP(ip string) error {
|
|||
// 5952, Sec. 4 for IPv6.") ParseAddr() will accept a non-compliant but
|
||||
// otherwise valid string; String() will output a compliant string.
|
||||
parsedIP, err := netip.ParseAddr(ip)
|
||||
if err != nil || parsedIP.String() != ip {
|
||||
if err != nil || parsedIP.WithZone("").String() != ip {
|
||||
return errIPInvalid
|
||||
}
|
||||
|
||||
|
@ -423,32 +443,26 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
|
|||
continue
|
||||
}
|
||||
|
||||
// Only DNS identifiers are subject to wildcard and blocklist checks.
|
||||
// Unsupported identifier types will have been caught by
|
||||
// WellFormedIdentifiers().
|
||||
//
|
||||
// TODO(#8237): We may want to implement IP address blocklists too.
|
||||
if ident.Type == identifier.TypeDNS {
|
||||
if strings.Count(ident.Value, "*") > 0 {
|
||||
// The base domain is the wildcard request with the `*.` prefix removed
|
||||
baseDomain := strings.TrimPrefix(ident.Value, "*.")
|
||||
// Wildcard DNS identifiers are checked against an additional blocklist.
|
||||
if ident.Type == identifier.TypeDNS && strings.Count(ident.Value, "*") > 0 {
|
||||
// The base domain is the wildcard request with the `*.` prefix removed
|
||||
baseDomain := strings.TrimPrefix(ident.Value, "*.")
|
||||
|
||||
// The base domain can't be in the wildcard exact blocklist
|
||||
err = pa.checkWildcardHostList(baseDomain)
|
||||
if err != nil {
|
||||
subErrors = append(subErrors, subError(ident, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// For both wildcard and non-wildcard domains, check whether any parent domain
|
||||
// name is on the regular blocklist.
|
||||
err := pa.checkHostLists(ident.Value)
|
||||
// The base domain can't be in the wildcard exact blocklist
|
||||
err = pa.checkWildcardBlocklist(baseDomain)
|
||||
if err != nil {
|
||||
subErrors = append(subErrors, subError(ident, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// For all identifier types, check whether the identifier value is
|
||||
// covered by the regular blocklists.
|
||||
err := pa.checkBlocklists(ident)
|
||||
if err != nil {
|
||||
subErrors = append(subErrors, subError(ident, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
return combineSubErrors(subErrors)
|
||||
}
|
||||
|
@ -477,6 +491,7 @@ func (pa *AuthorityImpl) WillingToIssue(idents identifier.ACMEIdentifiers) error
|
|||
//
|
||||
// For IP identifiers:
|
||||
// - MUST match the syntax of an IP address
|
||||
// - MUST NOT contain a scope zone (RFC 4007)
|
||||
// - MUST NOT be in an IANA special-purpose address registry
|
||||
//
|
||||
// If multiple identifiers are invalid, the error will contain suberrors
|
||||
|
@ -528,42 +543,57 @@ func combineSubErrors(subErrors []berrors.SubBoulderError) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// checkWildcardHostList checks the wildcardExactBlocklist for a given domain.
|
||||
// checkWildcardBlocklist checks the wildcardExactBlocklist for a given domain.
|
||||
// If the domain is not present on the list nil is returned, otherwise
|
||||
// errPolicyForbidden is returned.
|
||||
func (pa *AuthorityImpl) checkWildcardHostList(domain string) error {
|
||||
func (pa *AuthorityImpl) checkWildcardBlocklist(domain string) error {
|
||||
pa.blocklistMu.RLock()
|
||||
defer pa.blocklistMu.RUnlock()
|
||||
|
||||
if pa.wildcardExactBlocklist == nil {
|
||||
return fmt.Errorf("Hostname policy not yet loaded.")
|
||||
if pa.wildcardFqdnBlocklist == nil {
|
||||
return fmt.Errorf("identifier policy not yet loaded")
|
||||
}
|
||||
|
||||
if pa.wildcardExactBlocklist[domain] {
|
||||
if pa.wildcardFqdnBlocklist[domain] {
|
||||
return errPolicyForbidden
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pa *AuthorityImpl) checkHostLists(domain string) error {
|
||||
func (pa *AuthorityImpl) checkBlocklists(ident identifier.ACMEIdentifier) error {
|
||||
pa.blocklistMu.RLock()
|
||||
defer pa.blocklistMu.RUnlock()
|
||||
|
||||
if pa.blocklist == nil {
|
||||
return fmt.Errorf("Hostname policy not yet loaded.")
|
||||
if pa.domainBlocklist == nil {
|
||||
return fmt.Errorf("identifier policy not yet loaded")
|
||||
}
|
||||
|
||||
labels := strings.Split(domain, ".")
|
||||
for i := range labels {
|
||||
joined := strings.Join(labels[i:], ".")
|
||||
if pa.blocklist[joined] {
|
||||
switch ident.Type {
|
||||
case identifier.TypeDNS:
|
||||
labels := strings.Split(ident.Value, ".")
|
||||
for i := range labels {
|
||||
joined := strings.Join(labels[i:], ".")
|
||||
if pa.domainBlocklist[joined] {
|
||||
return errPolicyForbidden
|
||||
}
|
||||
}
|
||||
|
||||
if pa.fqdnBlocklist[ident.Value] {
|
||||
return errPolicyForbidden
|
||||
}
|
||||
}
|
||||
|
||||
if pa.exactBlocklist[domain] {
|
||||
return errPolicyForbidden
|
||||
case identifier.TypeIP:
|
||||
ip, err := netip.ParseAddr(ident.Value)
|
||||
if err != nil {
|
||||
return errIPInvalid
|
||||
}
|
||||
for _, prefix := range pa.ipPrefixBlocklist {
|
||||
if prefix.Contains(ip.WithZone("")) {
|
||||
return errPolicyForbidden
|
||||
}
|
||||
}
|
||||
default:
|
||||
return errUnsupportedIdent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -136,6 +136,8 @@ func TestWellFormedIdentifiers(t *testing.T) {
|
|||
{identifier.ACMEIdentifier{Type: "ip", Value: `1.1.168.192.in-addr.arpa`}, errIPInvalid}, // reverse DNS
|
||||
|
||||
// Unexpected IPv6 variants
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `2602:80a:6000:abad:cafe::1%lo`}, errIPInvalid}, // scope zone (RFC 4007)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `2602:80a:6000:abad:cafe::1%`}, errIPInvalid}, // empty scope zone (RFC 4007)
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:deed:ffff`}, errIPInvalid}, // extra octet
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `3fff:aaa:a:c0ff:ee:a:bad:mead`}, errIPInvalid}, // character out of range
|
||||
{identifier.ACMEIdentifier{Type: "ip", Value: `2001:db8::/32`}, errIPInvalid}, // with CIDR
|
||||
|
@ -178,6 +180,9 @@ func TestWillingToIssue(t *testing.T) {
|
|||
identifier.NewDNS(`lots.of.labels.website4.com`),
|
||||
identifier.NewDNS(`banned.in.dc.com`),
|
||||
identifier.NewDNS(`bad.brains.banned.in.dc.com`),
|
||||
identifier.NewIP(netip.MustParseAddr(`64.112.117.66`)),
|
||||
identifier.NewIP(netip.MustParseAddr(`2602:80a:6000:666::1`)),
|
||||
identifier.NewIP(netip.MustParseAddr(`2602:80a:6000:666::1%lo`)),
|
||||
}
|
||||
blocklistContents := []string{
|
||||
`website2.com`,
|
||||
|
@ -191,9 +196,13 @@ func TestWillingToIssue(t *testing.T) {
|
|||
`highvalue.website1.org`,
|
||||
`dl.website1.org`,
|
||||
}
|
||||
adminBlockedContents := []string{
|
||||
adminBlockedNamesContents := []string{
|
||||
`banned.in.dc.com`,
|
||||
}
|
||||
adminBlockedPrefixesContents := []string{
|
||||
`64.112.117.66/32`,
|
||||
`2602:80a:6000:666::/64`,
|
||||
}
|
||||
|
||||
shouldBeAccepted := identifier.ACMEIdentifiers{
|
||||
identifier.NewDNS(`lowvalue.website1.org`),
|
||||
|
@ -204,14 +213,17 @@ func TestWillingToIssue(t *testing.T) {
|
|||
identifier.NewDNS(`8675309.com`),
|
||||
identifier.NewDNS(`web5ite2.com`),
|
||||
identifier.NewDNS(`www.web-site2.com`),
|
||||
identifier.NewIP(netip.MustParseAddr(`9.9.9.9`)),
|
||||
identifier.NewDNS(`www.highvalue.website1.org`),
|
||||
identifier.NewIP(netip.MustParseAddr(`64.112.117.67`)),
|
||||
identifier.NewIP(netip.MustParseAddr(`2620:fe::fe`)),
|
||||
identifier.NewIP(netip.MustParseAddr(`2602:80a:6000:667::`)),
|
||||
}
|
||||
|
||||
policy := blockedNamesPolicy{
|
||||
policy := blockedIdentsPolicy{
|
||||
HighRiskBlockedNames: blocklistContents,
|
||||
ExactBlockedNames: exactBlocklistContents,
|
||||
AdminBlockedNames: adminBlockedContents,
|
||||
AdminBlockedNames: adminBlockedNamesContents,
|
||||
AdminBlockedPrefixes: adminBlockedPrefixesContents,
|
||||
}
|
||||
|
||||
yamlPolicyBytes, err := yaml.Marshal(policy)
|
||||
|
@ -223,7 +235,7 @@ func TestWillingToIssue(t *testing.T) {
|
|||
|
||||
pa := paImpl(t)
|
||||
|
||||
err = pa.LoadHostnamePolicyFile(yamlPolicyFile.Name())
|
||||
err = pa.LoadIdentPolicyFile(yamlPolicyFile.Name())
|
||||
test.AssertNotError(t, err, "Couldn't load rules")
|
||||
|
||||
// Invalid encoding
|
||||
|
@ -240,7 +252,7 @@ func TestWillingToIssue(t *testing.T) {
|
|||
test.AssertNotError(t, err, "WillingToIssue failed on a properly formed domain with IDN TLD")
|
||||
features.Reset()
|
||||
|
||||
// Test expected blocked domains
|
||||
// Test expected blocked identifiers
|
||||
for _, ident := range shouldBeBlocked {
|
||||
err := pa.WillingToIssue(identifier.ACMEIdentifiers{ident})
|
||||
test.AssertError(t, err, "identifier was not correctly forbidden")
|
||||
|
@ -249,7 +261,7 @@ func TestWillingToIssue(t *testing.T) {
|
|||
test.AssertContains(t, berr.Detail, errPolicyForbidden.Error())
|
||||
}
|
||||
|
||||
// Test acceptance of good names
|
||||
// Test acceptance of good identifiers
|
||||
for _, ident := range shouldBeAccepted {
|
||||
err := pa.WillingToIssue(identifier.ACMEIdentifiers{ident})
|
||||
test.AssertNotError(t, err, "identifier was incorrectly forbidden")
|
||||
|
@ -265,7 +277,7 @@ func TestWillingToIssue_Wildcards(t *testing.T) {
|
|||
}
|
||||
pa := paImpl(t)
|
||||
|
||||
bannedBytes, err := yaml.Marshal(blockedNamesPolicy{
|
||||
bannedBytes, err := yaml.Marshal(blockedIdentsPolicy{
|
||||
HighRiskBlockedNames: bannedDomains,
|
||||
ExactBlockedNames: exactBannedDomains,
|
||||
})
|
||||
|
@ -274,7 +286,7 @@ func TestWillingToIssue_Wildcards(t *testing.T) {
|
|||
defer os.Remove(f.Name())
|
||||
err = os.WriteFile(f.Name(), bannedBytes, 0640)
|
||||
test.AssertNotError(t, err, "Couldn't write serialized banned list to file")
|
||||
err = pa.LoadHostnamePolicyFile(f.Name())
|
||||
err = pa.LoadIdentPolicyFile(f.Name())
|
||||
test.AssertNotError(t, err, "Couldn't load policy contents from file")
|
||||
|
||||
testCases := []struct {
|
||||
|
@ -359,7 +371,7 @@ func TestWillingToIssue_SubErrors(t *testing.T) {
|
|||
}
|
||||
pa := paImpl(t)
|
||||
|
||||
bannedBytes, err := yaml.Marshal(blockedNamesPolicy{
|
||||
bannedBytes, err := yaml.Marshal(blockedIdentsPolicy{
|
||||
HighRiskBlockedNames: banned,
|
||||
ExactBlockedNames: banned,
|
||||
})
|
||||
|
@ -368,7 +380,7 @@ func TestWillingToIssue_SubErrors(t *testing.T) {
|
|||
defer os.Remove(f.Name())
|
||||
err = os.WriteFile(f.Name(), bannedBytes, 0640)
|
||||
test.AssertNotError(t, err, "Couldn't write serialized banned list to file")
|
||||
err = pa.LoadHostnamePolicyFile(f.Name())
|
||||
err = pa.LoadIdentPolicyFile(f.Name())
|
||||
test.AssertNotError(t, err, "Couldn't load policy contents from file")
|
||||
|
||||
// Test multiple malformed domains and one banned domain; only the malformed ones will generate errors
|
||||
|
@ -511,7 +523,7 @@ func TestMalformedExactBlocklist(t *testing.T) {
|
|||
}
|
||||
|
||||
// Create YAML for the exactBannedDomains
|
||||
bannedBytes, err := yaml.Marshal(blockedNamesPolicy{
|
||||
bannedBytes, err := yaml.Marshal(blockedIdentsPolicy{
|
||||
HighRiskBlockedNames: bannedDomains,
|
||||
ExactBlockedNames: exactBannedDomains,
|
||||
})
|
||||
|
@ -524,11 +536,11 @@ func TestMalformedExactBlocklist(t *testing.T) {
|
|||
err = os.WriteFile(f.Name(), bannedBytes, 0640)
|
||||
test.AssertNotError(t, err, "Couldn't write serialized banned list to file")
|
||||
|
||||
// Try to use the YAML tempfile as the hostname policy. It should produce an
|
||||
// Try to use the YAML tempfile as the ident policy. It should produce an
|
||||
// error since the exact blocklist contents are malformed.
|
||||
err = pa.LoadHostnamePolicyFile(f.Name())
|
||||
err = pa.LoadIdentPolicyFile(f.Name())
|
||||
test.AssertError(t, err, "Loaded invalid exact blocklist content without error")
|
||||
test.AssertEquals(t, err.Error(), "Malformed ExactBlockedNames entry, only one label: \"com\"")
|
||||
test.AssertEquals(t, err.Error(), "malformed ExactBlockedNames entry, only one label: \"com\"")
|
||||
}
|
||||
|
||||
func TestValidEmailError(t *testing.T) {
|
||||
|
@ -694,7 +706,7 @@ func TestWillingToIssue_IdentifierType(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
policy := blockedNamesPolicy{
|
||||
policy := blockedIdentsPolicy{
|
||||
HighRiskBlockedNames: []string{"zombo.gov.us"},
|
||||
ExactBlockedNames: []string{`highvalue.website1.org`},
|
||||
AdminBlockedNames: []string{`banned.in.dc.com`},
|
||||
|
@ -709,7 +721,7 @@ func TestWillingToIssue_IdentifierType(t *testing.T) {
|
|||
|
||||
pa := paImpl(t)
|
||||
|
||||
err = pa.LoadHostnamePolicyFile(yamlPolicyFile.Name())
|
||||
err = pa.LoadIdentPolicyFile(yamlPolicyFile.Name())
|
||||
test.AssertNotError(t, err, "Couldn't load rules")
|
||||
|
||||
pa.enabledIdentifiers = tc.enabled
|
||||
|
|
|
@ -157,58 +157,6 @@ func (x *GenerateOCSPRequest) GetSerial() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
type UpdateRegistrationContactRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RegistrationID int64 `protobuf:"varint,1,opt,name=registrationID,proto3" json:"registrationID,omitempty"`
|
||||
Contacts []string `protobuf:"bytes,2,rep,name=contacts,proto3" json:"contacts,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UpdateRegistrationContactRequest) Reset() {
|
||||
*x = UpdateRegistrationContactRequest{}
|
||||
mi := &file_ra_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *UpdateRegistrationContactRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UpdateRegistrationContactRequest) ProtoMessage() {}
|
||||
|
||||
func (x *UpdateRegistrationContactRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UpdateRegistrationContactRequest.ProtoReflect.Descriptor instead.
|
||||
func (*UpdateRegistrationContactRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *UpdateRegistrationContactRequest) GetRegistrationID() int64 {
|
||||
if x != nil {
|
||||
return x.RegistrationID
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *UpdateRegistrationContactRequest) GetContacts() []string {
|
||||
if x != nil {
|
||||
return x.Contacts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateRegistrationKeyRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
RegistrationID int64 `protobuf:"varint,1,opt,name=registrationID,proto3" json:"registrationID,omitempty"`
|
||||
|
@ -219,7 +167,7 @@ type UpdateRegistrationKeyRequest struct {
|
|||
|
||||
func (x *UpdateRegistrationKeyRequest) Reset() {
|
||||
*x = UpdateRegistrationKeyRequest{}
|
||||
mi := &file_ra_proto_msgTypes[4]
|
||||
mi := &file_ra_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -231,7 +179,7 @@ func (x *UpdateRegistrationKeyRequest) String() string {
|
|||
func (*UpdateRegistrationKeyRequest) ProtoMessage() {}
|
||||
|
||||
func (x *UpdateRegistrationKeyRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[4]
|
||||
mi := &file_ra_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -244,7 +192,7 @@ func (x *UpdateRegistrationKeyRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use UpdateRegistrationKeyRequest.ProtoReflect.Descriptor instead.
|
||||
func (*UpdateRegistrationKeyRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{4}
|
||||
return file_ra_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *UpdateRegistrationKeyRequest) GetRegistrationID() int64 {
|
||||
|
@ -270,7 +218,7 @@ type DeactivateRegistrationRequest struct {
|
|||
|
||||
func (x *DeactivateRegistrationRequest) Reset() {
|
||||
*x = DeactivateRegistrationRequest{}
|
||||
mi := &file_ra_proto_msgTypes[5]
|
||||
mi := &file_ra_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -282,7 +230,7 @@ func (x *DeactivateRegistrationRequest) String() string {
|
|||
func (*DeactivateRegistrationRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DeactivateRegistrationRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[5]
|
||||
mi := &file_ra_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -295,7 +243,7 @@ func (x *DeactivateRegistrationRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use DeactivateRegistrationRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DeactivateRegistrationRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{5}
|
||||
return file_ra_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *DeactivateRegistrationRequest) GetRegistrationID() int64 {
|
||||
|
@ -316,7 +264,7 @@ type UpdateAuthorizationRequest struct {
|
|||
|
||||
func (x *UpdateAuthorizationRequest) Reset() {
|
||||
*x = UpdateAuthorizationRequest{}
|
||||
mi := &file_ra_proto_msgTypes[6]
|
||||
mi := &file_ra_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -328,7 +276,7 @@ func (x *UpdateAuthorizationRequest) String() string {
|
|||
func (*UpdateAuthorizationRequest) ProtoMessage() {}
|
||||
|
||||
func (x *UpdateAuthorizationRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[6]
|
||||
mi := &file_ra_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -341,7 +289,7 @@ func (x *UpdateAuthorizationRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use UpdateAuthorizationRequest.ProtoReflect.Descriptor instead.
|
||||
func (*UpdateAuthorizationRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{6}
|
||||
return file_ra_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *UpdateAuthorizationRequest) GetAuthz() *proto.Authorization {
|
||||
|
@ -375,7 +323,7 @@ type PerformValidationRequest struct {
|
|||
|
||||
func (x *PerformValidationRequest) Reset() {
|
||||
*x = PerformValidationRequest{}
|
||||
mi := &file_ra_proto_msgTypes[7]
|
||||
mi := &file_ra_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -387,7 +335,7 @@ func (x *PerformValidationRequest) String() string {
|
|||
func (*PerformValidationRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PerformValidationRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[7]
|
||||
mi := &file_ra_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -400,7 +348,7 @@ func (x *PerformValidationRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use PerformValidationRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PerformValidationRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{7}
|
||||
return file_ra_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *PerformValidationRequest) GetAuthz() *proto.Authorization {
|
||||
|
@ -428,7 +376,7 @@ type RevokeCertByApplicantRequest struct {
|
|||
|
||||
func (x *RevokeCertByApplicantRequest) Reset() {
|
||||
*x = RevokeCertByApplicantRequest{}
|
||||
mi := &file_ra_proto_msgTypes[8]
|
||||
mi := &file_ra_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -440,7 +388,7 @@ func (x *RevokeCertByApplicantRequest) String() string {
|
|||
func (*RevokeCertByApplicantRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RevokeCertByApplicantRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[8]
|
||||
mi := &file_ra_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -453,7 +401,7 @@ func (x *RevokeCertByApplicantRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use RevokeCertByApplicantRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RevokeCertByApplicantRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{8}
|
||||
return file_ra_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *RevokeCertByApplicantRequest) GetCert() []byte {
|
||||
|
@ -486,7 +434,7 @@ type RevokeCertByKeyRequest struct {
|
|||
|
||||
func (x *RevokeCertByKeyRequest) Reset() {
|
||||
*x = RevokeCertByKeyRequest{}
|
||||
mi := &file_ra_proto_msgTypes[9]
|
||||
mi := &file_ra_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -498,7 +446,7 @@ func (x *RevokeCertByKeyRequest) String() string {
|
|||
func (*RevokeCertByKeyRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RevokeCertByKeyRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[9]
|
||||
mi := &file_ra_proto_msgTypes[8]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -511,7 +459,7 @@ func (x *RevokeCertByKeyRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use RevokeCertByKeyRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RevokeCertByKeyRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{9}
|
||||
return file_ra_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *RevokeCertByKeyRequest) GetCert() []byte {
|
||||
|
@ -548,7 +496,7 @@ type AdministrativelyRevokeCertificateRequest struct {
|
|||
|
||||
func (x *AdministrativelyRevokeCertificateRequest) Reset() {
|
||||
*x = AdministrativelyRevokeCertificateRequest{}
|
||||
mi := &file_ra_proto_msgTypes[10]
|
||||
mi := &file_ra_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -560,7 +508,7 @@ func (x *AdministrativelyRevokeCertificateRequest) String() string {
|
|||
func (*AdministrativelyRevokeCertificateRequest) ProtoMessage() {}
|
||||
|
||||
func (x *AdministrativelyRevokeCertificateRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[10]
|
||||
mi := &file_ra_proto_msgTypes[9]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -573,7 +521,7 @@ func (x *AdministrativelyRevokeCertificateRequest) ProtoReflect() protoreflect.M
|
|||
|
||||
// Deprecated: Use AdministrativelyRevokeCertificateRequest.ProtoReflect.Descriptor instead.
|
||||
func (*AdministrativelyRevokeCertificateRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{10}
|
||||
return file_ra_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
func (x *AdministrativelyRevokeCertificateRequest) GetCert() []byte {
|
||||
|
@ -641,7 +589,7 @@ type NewOrderRequest struct {
|
|||
|
||||
func (x *NewOrderRequest) Reset() {
|
||||
*x = NewOrderRequest{}
|
||||
mi := &file_ra_proto_msgTypes[11]
|
||||
mi := &file_ra_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -653,7 +601,7 @@ func (x *NewOrderRequest) String() string {
|
|||
func (*NewOrderRequest) ProtoMessage() {}
|
||||
|
||||
func (x *NewOrderRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[11]
|
||||
mi := &file_ra_proto_msgTypes[10]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -666,7 +614,7 @@ func (x *NewOrderRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use NewOrderRequest.ProtoReflect.Descriptor instead.
|
||||
func (*NewOrderRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{11}
|
||||
return file_ra_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *NewOrderRequest) GetRegistrationID() int64 {
|
||||
|
@ -713,7 +661,7 @@ type GetAuthorizationRequest struct {
|
|||
|
||||
func (x *GetAuthorizationRequest) Reset() {
|
||||
*x = GetAuthorizationRequest{}
|
||||
mi := &file_ra_proto_msgTypes[12]
|
||||
mi := &file_ra_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -725,7 +673,7 @@ func (x *GetAuthorizationRequest) String() string {
|
|||
func (*GetAuthorizationRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetAuthorizationRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[12]
|
||||
mi := &file_ra_proto_msgTypes[11]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -738,7 +686,7 @@ func (x *GetAuthorizationRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use GetAuthorizationRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetAuthorizationRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{12}
|
||||
return file_ra_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
func (x *GetAuthorizationRequest) GetId() int64 {
|
||||
|
@ -758,7 +706,7 @@ type FinalizeOrderRequest struct {
|
|||
|
||||
func (x *FinalizeOrderRequest) Reset() {
|
||||
*x = FinalizeOrderRequest{}
|
||||
mi := &file_ra_proto_msgTypes[13]
|
||||
mi := &file_ra_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -770,7 +718,7 @@ func (x *FinalizeOrderRequest) String() string {
|
|||
func (*FinalizeOrderRequest) ProtoMessage() {}
|
||||
|
||||
func (x *FinalizeOrderRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[13]
|
||||
mi := &file_ra_proto_msgTypes[12]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -783,7 +731,7 @@ func (x *FinalizeOrderRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use FinalizeOrderRequest.ProtoReflect.Descriptor instead.
|
||||
func (*FinalizeOrderRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{13}
|
||||
return file_ra_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *FinalizeOrderRequest) GetOrder() *proto.Order {
|
||||
|
@ -810,7 +758,7 @@ type UnpauseAccountRequest struct {
|
|||
|
||||
func (x *UnpauseAccountRequest) Reset() {
|
||||
*x = UnpauseAccountRequest{}
|
||||
mi := &file_ra_proto_msgTypes[14]
|
||||
mi := &file_ra_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -822,7 +770,7 @@ func (x *UnpauseAccountRequest) String() string {
|
|||
func (*UnpauseAccountRequest) ProtoMessage() {}
|
||||
|
||||
func (x *UnpauseAccountRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[14]
|
||||
mi := &file_ra_proto_msgTypes[13]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -835,7 +783,7 @@ func (x *UnpauseAccountRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use UnpauseAccountRequest.ProtoReflect.Descriptor instead.
|
||||
func (*UnpauseAccountRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{14}
|
||||
return file_ra_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
func (x *UnpauseAccountRequest) GetRegistrationID() int64 {
|
||||
|
@ -855,7 +803,7 @@ type UnpauseAccountResponse struct {
|
|||
|
||||
func (x *UnpauseAccountResponse) Reset() {
|
||||
*x = UnpauseAccountResponse{}
|
||||
mi := &file_ra_proto_msgTypes[15]
|
||||
mi := &file_ra_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -867,7 +815,7 @@ func (x *UnpauseAccountResponse) String() string {
|
|||
func (*UnpauseAccountResponse) ProtoMessage() {}
|
||||
|
||||
func (x *UnpauseAccountResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[15]
|
||||
mi := &file_ra_proto_msgTypes[14]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -880,7 +828,7 @@ func (x *UnpauseAccountResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use UnpauseAccountResponse.ProtoReflect.Descriptor instead.
|
||||
func (*UnpauseAccountResponse) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{15}
|
||||
return file_ra_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
func (x *UnpauseAccountResponse) GetCount() int64 {
|
||||
|
@ -904,7 +852,7 @@ type AddRateLimitOverrideRequest struct {
|
|||
|
||||
func (x *AddRateLimitOverrideRequest) Reset() {
|
||||
*x = AddRateLimitOverrideRequest{}
|
||||
mi := &file_ra_proto_msgTypes[16]
|
||||
mi := &file_ra_proto_msgTypes[15]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -916,7 +864,7 @@ func (x *AddRateLimitOverrideRequest) String() string {
|
|||
func (*AddRateLimitOverrideRequest) ProtoMessage() {}
|
||||
|
||||
func (x *AddRateLimitOverrideRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[16]
|
||||
mi := &file_ra_proto_msgTypes[15]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -929,7 +877,7 @@ func (x *AddRateLimitOverrideRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use AddRateLimitOverrideRequest.ProtoReflect.Descriptor instead.
|
||||
func (*AddRateLimitOverrideRequest) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{16}
|
||||
return file_ra_proto_rawDescGZIP(), []int{15}
|
||||
}
|
||||
|
||||
func (x *AddRateLimitOverrideRequest) GetLimitEnum() int64 {
|
||||
|
@ -984,7 +932,7 @@ type AddRateLimitOverrideResponse struct {
|
|||
|
||||
func (x *AddRateLimitOverrideResponse) Reset() {
|
||||
*x = AddRateLimitOverrideResponse{}
|
||||
mi := &file_ra_proto_msgTypes[17]
|
||||
mi := &file_ra_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -996,7 +944,7 @@ func (x *AddRateLimitOverrideResponse) String() string {
|
|||
func (*AddRateLimitOverrideResponse) ProtoMessage() {}
|
||||
|
||||
func (x *AddRateLimitOverrideResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_ra_proto_msgTypes[17]
|
||||
mi := &file_ra_proto_msgTypes[16]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1009,7 +957,7 @@ func (x *AddRateLimitOverrideResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use AddRateLimitOverrideResponse.ProtoReflect.Descriptor instead.
|
||||
func (*AddRateLimitOverrideResponse) Descriptor() ([]byte, []int) {
|
||||
return file_ra_proto_rawDescGZIP(), []int{17}
|
||||
return file_ra_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *AddRateLimitOverrideResponse) GetInserted() bool {
|
||||
|
@ -1044,193 +992,181 @@ var file_ra_proto_rawDesc = string([]byte{
|
|||
0x28, 0x0c, 0x52, 0x06, 0x73, 0x63, 0x74, 0x44, 0x45, 0x52, 0x22, 0x2d, 0x0a, 0x13, 0x47, 0x65,
|
||||
0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x22, 0x66, 0x0a, 0x20, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43,
|
||||
0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a,
|
||||
0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74,
|
||||
0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74,
|
||||
0x73, 0x22, 0x58, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73,
|
||||
0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73,
|
||||
0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x10, 0x0a, 0x03, 0x6a, 0x77, 0x6b,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6a, 0x77, 0x6b, 0x22, 0x47, 0x0a, 0x1d, 0x44,
|
||||
0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e,
|
||||
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x49, 0x44, 0x22, 0x9c, 0x01, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41,
|
||||
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
|
||||
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x12, 0x26,
|
||||
0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67,
|
||||
0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2b, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
|
||||
0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x18, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61,
|
||||
0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x29, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
|
||||
0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x68,
|
||||
0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64,
|
||||
0x65, 0x78, 0x22, 0x5c, 0x0a, 0x1c, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74,
|
||||
0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x22, 0x58, 0x0a, 0x1c, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b,
|
||||
0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67,
|
||||
0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49,
|
||||
0x44, 0x12, 0x10, 0x0a, 0x03, 0x6a, 0x77, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03,
|
||||
0x6a, 0x77, 0x6b, 0x22, 0x47, 0x0a, 0x1d, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74,
|
||||
0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65,
|
||||
0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x9c, 0x01, 0x0a,
|
||||
0x1a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x61,
|
||||
0x75, 0x74, 0x68, 0x7a, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x72,
|
||||
0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x05, 0x61, 0x75, 0x74, 0x68, 0x7a, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65,
|
||||
0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e,
|
||||
0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x2b,
|
||||
0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67,
|
||||
0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x18, 0x50,
|
||||
0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x05, 0x61, 0x75, 0x74, 0x68, 0x7a,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75,
|
||||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x05, 0x61, 0x75, 0x74,
|
||||
0x68, 0x7a, 0x12, 0x26, 0x0a, 0x0e, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49,
|
||||
0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x63, 0x68, 0x61, 0x6c,
|
||||
0x6c, 0x65, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x5c, 0x0a, 0x1c, 0x52, 0x65,
|
||||
0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63,
|
||||
0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65,
|
||||
0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f,
|
||||
0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65, 0x67, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x05, 0x72, 0x65, 0x67, 0x49, 0x44, 0x22, 0x32, 0x0a, 0x16, 0x52, 0x65, 0x76, 0x6f,
|
||||
0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x65,
|
||||
0x67, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x72, 0x65, 0x67, 0x49, 0x44,
|
||||
0x22, 0x32, 0x0a, 0x16, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79,
|
||||
0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65,
|
||||
0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x4a, 0x04,
|
||||
0x08, 0x02, 0x10, 0x03, 0x22, 0xe6, 0x01, 0x0a, 0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73,
|
||||
0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43,
|
||||
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
||||
0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64,
|
||||
0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x65, 0x79, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b,
|
||||
0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64,
|
||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65,
|
||||
0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x72, 0x6c, 0x53, 0x68, 0x61, 0x72, 0x64, 0x18, 0x07, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x72, 0x6c, 0x53, 0x68, 0x61, 0x72, 0x64, 0x22, 0xfb, 0x01,
|
||||
0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73,
|
||||
0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x64, 0x65,
|
||||
0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10,
|
||||
0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72,
|
||||
0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x36, 0x0a,
|
||||
0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66,
|
||||
0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63,
|
||||
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c,
|
||||
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
|
||||
0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65,
|
||||
0x73, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72,
|
||||
0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61,
|
||||
0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a,
|
||||
0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0x29, 0x0a, 0x17, 0x47,
|
||||
0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4b, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69,
|
||||
0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21,
|
||||
0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e,
|
||||
0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65,
|
||||
0x72, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03,
|
||||
0x63, 0x73, 0x72, 0x22, 0x3f, 0x0a, 0x15, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e,
|
||||
0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x49, 0x44, 0x22, 0x2e, 0x0a, 0x16, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x22, 0xd2, 0x01, 0x0a, 0x1b, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65,
|
||||
0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e, 0x75,
|
||||
0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e,
|
||||
0x75, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x70, 0x65,
|
||||
0x72, 0x69, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x75, 0x72, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x05, 0x62, 0x75, 0x72, 0x73, 0x74, 0x22, 0x54, 0x0a, 0x1c, 0x41, 0x64, 0x64,
|
||||
0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
|
||||
0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x73,
|
||||
0x65, 0x72, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x73,
|
||||
0x65, 0x72, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x32,
|
||||
0x87, 0x09, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0f, 0x4e, 0x65, 0x77,
|
||||
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63,
|
||||
0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x57, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74,
|
||||
0x61, 0x63, 0x74, 0x12, 0x24, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52,
|
||||
0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xe6, 0x01, 0x0a,
|
||||
0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c,
|
||||
0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72,
|
||||
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
|
||||
0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x6d,
|
||||
0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64,
|
||||
0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42,
|
||||
0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73,
|
||||
0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6d,
|
||||
0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
|
||||
0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x72, 0x6c,
|
||||
0x53, 0x68, 0x61, 0x72, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x72, 0x6c,
|
||||
0x53, 0x68, 0x61, 0x72, 0x64, 0x22, 0xfb, 0x01, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64,
|
||||
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67,
|
||||
0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49,
|
||||
0x44, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73,
|
||||
0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x49, 0x64,
|
||||
0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
|
||||
0x66, 0x69, 0x65, 0x72, 0x73, 0x12, 0x36, 0x0a, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69,
|
||||
0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18,
|
||||
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x70,
|
||||
0x6c, 0x61, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x69, 0x61,
|
||||
0x6c, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08,
|
||||
0x06, 0x10, 0x07, 0x22, 0x29, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
|
||||
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4b,
|
||||
0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64,
|
||||
0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x63, 0x73, 0x72, 0x22, 0x3f, 0x0a, 0x15, 0x55,
|
||||
0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65,
|
||||
0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x2e, 0x0a, 0x16,
|
||||
0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xd2, 0x01, 0x0a,
|
||||
0x1b, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65,
|
||||
0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09,
|
||||
0x6c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||
0x09, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x75,
|
||||
0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62,
|
||||
0x75, 0x63, 0x6b, 0x65, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d,
|
||||
0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65,
|
||||
0x6e, 0x74, 0x12, 0x31, 0x0a, 0x06, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x70,
|
||||
0x65, 0x72, 0x69, 0x6f, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62,
|
||||
0x75, 0x72, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x62, 0x75, 0x72, 0x73,
|
||||
0x74, 0x22, 0x54, 0x0a, 0x1c, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69,
|
||||
0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a,
|
||||
0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
|
||||
0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x32, 0xae, 0x08, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69,
|
||||
0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
|
||||
0x79, 0x12, 0x3b, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69,
|
||||
0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
|
||||
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x4f,
|
||||
0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b,
|
||||
0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12,
|
||||
0x4f, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70,
|
||||
0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72,
|
||||
0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00,
|
||||
0x12, 0x51, 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65,
|
||||
0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x72, 0x61, 0x2e,
|
||||
0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
|
||||
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e,
|
||||
0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61,
|
||||
0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50, 0x65,
|
||||
0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75,
|
||||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x48, 0x0a,
|
||||
0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
||||
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
|
||||
0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b,
|
||||
0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74,
|
||||
0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74,
|
||||
0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0f,
|
||||
0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x12,
|
||||
0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42,
|
||||
0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73,
|
||||
0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43,
|
||||
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x2e,
|
||||
0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79,
|
||||
0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x13,
|
||||
0x2e, 0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72,
|
||||
0x22, 0x00, 0x12, 0x46, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
|
||||
0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x41,
|
||||
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f,
|
||||
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0d, 0x46, 0x69,
|
||||
0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x61,
|
||||
0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64,
|
||||
0x65, 0x72, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65,
|
||||
0x4f, 0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61,
|
||||
0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e,
|
||||
0x63, 0x61, 0x2e, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x12, 0x49, 0x0a, 0x0e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65,
|
||||
0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a,
|
||||
0x2e, 0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x14,
|
||||
0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72,
|
||||
0x72, 0x69, 0x64, 0x65, 0x12, 0x1f, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74,
|
||||
0x51, 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67,
|
||||
0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x72, 0x61, 0x2e, 0x44,
|
||||
0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63,
|
||||
0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x22, 0x00, 0x12, 0x48, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c,
|
||||
0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50, 0x65, 0x72,
|
||||
0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74,
|
||||
0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x17,
|
||||
0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
|
||||
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41,
|
||||
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67,
|
||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65,
|
||||
0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x12,
|
||||
0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42,
|
||||
0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0f, 0x52,
|
||||
0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a,
|
||||
0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79,
|
||||
0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74,
|
||||
0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65,
|
||||
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x2e, 0x41,
|
||||
0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52,
|
||||
0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,
|
||||
0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x13, 0x2e,
|
||||
0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22,
|
||||
0x00, 0x12, 0x46, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75,
|
||||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
|
||||
0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0d, 0x46, 0x69, 0x6e,
|
||||
0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x61, 0x2e,
|
||||
0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65,
|
||||
0x72, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f,
|
||||
0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
|
||||
0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x63,
|
||||
0x61, 0x2e, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x12, 0x49, 0x0a, 0x0e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x12, 0x19, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e,
|
||||
0x72, 0x61, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x75, 0x73, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x14, 0x41,
|
||||
0x64, 0x64, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72,
|
||||
0x69, 0x64, 0x65, 0x12, 0x1f, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74, 0x65,
|
||||
0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x61, 0x74,
|
||||
0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x64, 0x52, 0x61,
|
||||
0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x3b, 0x0a, 0x0b, 0x53, 0x43, 0x54,
|
||||
0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x53,
|
||||
0x43, 0x54, 0x73, 0x12, 0x0e, 0x2e, 0x72, 0x61, 0x2e, 0x53, 0x43, 0x54, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x72, 0x61, 0x2e, 0x53, 0x43, 0x54, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
|
||||
0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x32, 0x3b, 0x0a, 0x0b, 0x53, 0x43, 0x54, 0x50,
|
||||
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x53, 0x43,
|
||||
0x54, 0x73, 0x12, 0x0e, 0x2e, 0x72, 0x61, 0x2e, 0x53, 0x43, 0x54, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x72, 0x61, 0x2e, 0x53, 0x43, 0x54, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
|
||||
0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f,
|
||||
0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
|
||||
var (
|
||||
|
@ -1245,76 +1181,73 @@ func file_ra_proto_rawDescGZIP() []byte {
|
|||
return file_ra_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_ra_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
|
||||
var file_ra_proto_msgTypes = make([]protoimpl.MessageInfo, 17)
|
||||
var file_ra_proto_goTypes = []any{
|
||||
(*SCTRequest)(nil), // 0: ra.SCTRequest
|
||||
(*SCTResponse)(nil), // 1: ra.SCTResponse
|
||||
(*GenerateOCSPRequest)(nil), // 2: ra.GenerateOCSPRequest
|
||||
(*UpdateRegistrationContactRequest)(nil), // 3: ra.UpdateRegistrationContactRequest
|
||||
(*UpdateRegistrationKeyRequest)(nil), // 4: ra.UpdateRegistrationKeyRequest
|
||||
(*DeactivateRegistrationRequest)(nil), // 5: ra.DeactivateRegistrationRequest
|
||||
(*UpdateAuthorizationRequest)(nil), // 6: ra.UpdateAuthorizationRequest
|
||||
(*PerformValidationRequest)(nil), // 7: ra.PerformValidationRequest
|
||||
(*RevokeCertByApplicantRequest)(nil), // 8: ra.RevokeCertByApplicantRequest
|
||||
(*RevokeCertByKeyRequest)(nil), // 9: ra.RevokeCertByKeyRequest
|
||||
(*AdministrativelyRevokeCertificateRequest)(nil), // 10: ra.AdministrativelyRevokeCertificateRequest
|
||||
(*NewOrderRequest)(nil), // 11: ra.NewOrderRequest
|
||||
(*GetAuthorizationRequest)(nil), // 12: ra.GetAuthorizationRequest
|
||||
(*FinalizeOrderRequest)(nil), // 13: ra.FinalizeOrderRequest
|
||||
(*UnpauseAccountRequest)(nil), // 14: ra.UnpauseAccountRequest
|
||||
(*UnpauseAccountResponse)(nil), // 15: ra.UnpauseAccountResponse
|
||||
(*AddRateLimitOverrideRequest)(nil), // 16: ra.AddRateLimitOverrideRequest
|
||||
(*AddRateLimitOverrideResponse)(nil), // 17: ra.AddRateLimitOverrideResponse
|
||||
(*proto.Authorization)(nil), // 18: core.Authorization
|
||||
(*proto.Challenge)(nil), // 19: core.Challenge
|
||||
(*proto.Identifier)(nil), // 20: core.Identifier
|
||||
(*proto.Order)(nil), // 21: core.Order
|
||||
(*durationpb.Duration)(nil), // 22: google.protobuf.Duration
|
||||
(*proto.Registration)(nil), // 23: core.Registration
|
||||
(*emptypb.Empty)(nil), // 24: google.protobuf.Empty
|
||||
(*proto1.OCSPResponse)(nil), // 25: ca.OCSPResponse
|
||||
(*UpdateRegistrationKeyRequest)(nil), // 3: ra.UpdateRegistrationKeyRequest
|
||||
(*DeactivateRegistrationRequest)(nil), // 4: ra.DeactivateRegistrationRequest
|
||||
(*UpdateAuthorizationRequest)(nil), // 5: ra.UpdateAuthorizationRequest
|
||||
(*PerformValidationRequest)(nil), // 6: ra.PerformValidationRequest
|
||||
(*RevokeCertByApplicantRequest)(nil), // 7: ra.RevokeCertByApplicantRequest
|
||||
(*RevokeCertByKeyRequest)(nil), // 8: ra.RevokeCertByKeyRequest
|
||||
(*AdministrativelyRevokeCertificateRequest)(nil), // 9: ra.AdministrativelyRevokeCertificateRequest
|
||||
(*NewOrderRequest)(nil), // 10: ra.NewOrderRequest
|
||||
(*GetAuthorizationRequest)(nil), // 11: ra.GetAuthorizationRequest
|
||||
(*FinalizeOrderRequest)(nil), // 12: ra.FinalizeOrderRequest
|
||||
(*UnpauseAccountRequest)(nil), // 13: ra.UnpauseAccountRequest
|
||||
(*UnpauseAccountResponse)(nil), // 14: ra.UnpauseAccountResponse
|
||||
(*AddRateLimitOverrideRequest)(nil), // 15: ra.AddRateLimitOverrideRequest
|
||||
(*AddRateLimitOverrideResponse)(nil), // 16: ra.AddRateLimitOverrideResponse
|
||||
(*proto.Authorization)(nil), // 17: core.Authorization
|
||||
(*proto.Challenge)(nil), // 18: core.Challenge
|
||||
(*proto.Identifier)(nil), // 19: core.Identifier
|
||||
(*proto.Order)(nil), // 20: core.Order
|
||||
(*durationpb.Duration)(nil), // 21: google.protobuf.Duration
|
||||
(*proto.Registration)(nil), // 22: core.Registration
|
||||
(*emptypb.Empty)(nil), // 23: google.protobuf.Empty
|
||||
(*proto1.OCSPResponse)(nil), // 24: ca.OCSPResponse
|
||||
}
|
||||
var file_ra_proto_depIdxs = []int32{
|
||||
18, // 0: ra.UpdateAuthorizationRequest.authz:type_name -> core.Authorization
|
||||
19, // 1: ra.UpdateAuthorizationRequest.response:type_name -> core.Challenge
|
||||
18, // 2: ra.PerformValidationRequest.authz:type_name -> core.Authorization
|
||||
20, // 3: ra.NewOrderRequest.identifiers:type_name -> core.Identifier
|
||||
21, // 4: ra.FinalizeOrderRequest.order:type_name -> core.Order
|
||||
22, // 5: ra.AddRateLimitOverrideRequest.period:type_name -> google.protobuf.Duration
|
||||
23, // 6: ra.RegistrationAuthority.NewRegistration:input_type -> core.Registration
|
||||
3, // 7: ra.RegistrationAuthority.UpdateRegistrationContact:input_type -> ra.UpdateRegistrationContactRequest
|
||||
4, // 8: ra.RegistrationAuthority.UpdateRegistrationKey:input_type -> ra.UpdateRegistrationKeyRequest
|
||||
5, // 9: ra.RegistrationAuthority.DeactivateRegistration:input_type -> ra.DeactivateRegistrationRequest
|
||||
7, // 10: ra.RegistrationAuthority.PerformValidation:input_type -> ra.PerformValidationRequest
|
||||
18, // 11: ra.RegistrationAuthority.DeactivateAuthorization:input_type -> core.Authorization
|
||||
8, // 12: ra.RegistrationAuthority.RevokeCertByApplicant:input_type -> ra.RevokeCertByApplicantRequest
|
||||
9, // 13: ra.RegistrationAuthority.RevokeCertByKey:input_type -> ra.RevokeCertByKeyRequest
|
||||
10, // 14: ra.RegistrationAuthority.AdministrativelyRevokeCertificate:input_type -> ra.AdministrativelyRevokeCertificateRequest
|
||||
11, // 15: ra.RegistrationAuthority.NewOrder:input_type -> ra.NewOrderRequest
|
||||
12, // 16: ra.RegistrationAuthority.GetAuthorization:input_type -> ra.GetAuthorizationRequest
|
||||
13, // 17: ra.RegistrationAuthority.FinalizeOrder:input_type -> ra.FinalizeOrderRequest
|
||||
2, // 18: ra.RegistrationAuthority.GenerateOCSP:input_type -> ra.GenerateOCSPRequest
|
||||
14, // 19: ra.RegistrationAuthority.UnpauseAccount:input_type -> ra.UnpauseAccountRequest
|
||||
16, // 20: ra.RegistrationAuthority.AddRateLimitOverride:input_type -> ra.AddRateLimitOverrideRequest
|
||||
0, // 21: ra.SCTProvider.GetSCTs:input_type -> ra.SCTRequest
|
||||
23, // 22: ra.RegistrationAuthority.NewRegistration:output_type -> core.Registration
|
||||
23, // 23: ra.RegistrationAuthority.UpdateRegistrationContact:output_type -> core.Registration
|
||||
23, // 24: ra.RegistrationAuthority.UpdateRegistrationKey:output_type -> core.Registration
|
||||
23, // 25: ra.RegistrationAuthority.DeactivateRegistration:output_type -> core.Registration
|
||||
18, // 26: ra.RegistrationAuthority.PerformValidation:output_type -> core.Authorization
|
||||
24, // 27: ra.RegistrationAuthority.DeactivateAuthorization:output_type -> google.protobuf.Empty
|
||||
24, // 28: ra.RegistrationAuthority.RevokeCertByApplicant:output_type -> google.protobuf.Empty
|
||||
24, // 29: ra.RegistrationAuthority.RevokeCertByKey:output_type -> google.protobuf.Empty
|
||||
24, // 30: ra.RegistrationAuthority.AdministrativelyRevokeCertificate:output_type -> google.protobuf.Empty
|
||||
21, // 31: ra.RegistrationAuthority.NewOrder:output_type -> core.Order
|
||||
18, // 32: ra.RegistrationAuthority.GetAuthorization:output_type -> core.Authorization
|
||||
21, // 33: ra.RegistrationAuthority.FinalizeOrder:output_type -> core.Order
|
||||
25, // 34: ra.RegistrationAuthority.GenerateOCSP:output_type -> ca.OCSPResponse
|
||||
15, // 35: ra.RegistrationAuthority.UnpauseAccount:output_type -> ra.UnpauseAccountResponse
|
||||
17, // 36: ra.RegistrationAuthority.AddRateLimitOverride:output_type -> ra.AddRateLimitOverrideResponse
|
||||
1, // 37: ra.SCTProvider.GetSCTs:output_type -> ra.SCTResponse
|
||||
22, // [22:38] is the sub-list for method output_type
|
||||
6, // [6:22] is the sub-list for method input_type
|
||||
17, // 0: ra.UpdateAuthorizationRequest.authz:type_name -> core.Authorization
|
||||
18, // 1: ra.UpdateAuthorizationRequest.response:type_name -> core.Challenge
|
||||
17, // 2: ra.PerformValidationRequest.authz:type_name -> core.Authorization
|
||||
19, // 3: ra.NewOrderRequest.identifiers:type_name -> core.Identifier
|
||||
20, // 4: ra.FinalizeOrderRequest.order:type_name -> core.Order
|
||||
21, // 5: ra.AddRateLimitOverrideRequest.period:type_name -> google.protobuf.Duration
|
||||
22, // 6: ra.RegistrationAuthority.NewRegistration:input_type -> core.Registration
|
||||
3, // 7: ra.RegistrationAuthority.UpdateRegistrationKey:input_type -> ra.UpdateRegistrationKeyRequest
|
||||
4, // 8: ra.RegistrationAuthority.DeactivateRegistration:input_type -> ra.DeactivateRegistrationRequest
|
||||
6, // 9: ra.RegistrationAuthority.PerformValidation:input_type -> ra.PerformValidationRequest
|
||||
17, // 10: ra.RegistrationAuthority.DeactivateAuthorization:input_type -> core.Authorization
|
||||
7, // 11: ra.RegistrationAuthority.RevokeCertByApplicant:input_type -> ra.RevokeCertByApplicantRequest
|
||||
8, // 12: ra.RegistrationAuthority.RevokeCertByKey:input_type -> ra.RevokeCertByKeyRequest
|
||||
9, // 13: ra.RegistrationAuthority.AdministrativelyRevokeCertificate:input_type -> ra.AdministrativelyRevokeCertificateRequest
|
||||
10, // 14: ra.RegistrationAuthority.NewOrder:input_type -> ra.NewOrderRequest
|
||||
11, // 15: ra.RegistrationAuthority.GetAuthorization:input_type -> ra.GetAuthorizationRequest
|
||||
12, // 16: ra.RegistrationAuthority.FinalizeOrder:input_type -> ra.FinalizeOrderRequest
|
||||
2, // 17: ra.RegistrationAuthority.GenerateOCSP:input_type -> ra.GenerateOCSPRequest
|
||||
13, // 18: ra.RegistrationAuthority.UnpauseAccount:input_type -> ra.UnpauseAccountRequest
|
||||
15, // 19: ra.RegistrationAuthority.AddRateLimitOverride:input_type -> ra.AddRateLimitOverrideRequest
|
||||
0, // 20: ra.SCTProvider.GetSCTs:input_type -> ra.SCTRequest
|
||||
22, // 21: ra.RegistrationAuthority.NewRegistration:output_type -> core.Registration
|
||||
22, // 22: ra.RegistrationAuthority.UpdateRegistrationKey:output_type -> core.Registration
|
||||
22, // 23: ra.RegistrationAuthority.DeactivateRegistration:output_type -> core.Registration
|
||||
17, // 24: ra.RegistrationAuthority.PerformValidation:output_type -> core.Authorization
|
||||
23, // 25: ra.RegistrationAuthority.DeactivateAuthorization:output_type -> google.protobuf.Empty
|
||||
23, // 26: ra.RegistrationAuthority.RevokeCertByApplicant:output_type -> google.protobuf.Empty
|
||||
23, // 27: ra.RegistrationAuthority.RevokeCertByKey:output_type -> google.protobuf.Empty
|
||||
23, // 28: ra.RegistrationAuthority.AdministrativelyRevokeCertificate:output_type -> google.protobuf.Empty
|
||||
20, // 29: ra.RegistrationAuthority.NewOrder:output_type -> core.Order
|
||||
17, // 30: ra.RegistrationAuthority.GetAuthorization:output_type -> core.Authorization
|
||||
20, // 31: ra.RegistrationAuthority.FinalizeOrder:output_type -> core.Order
|
||||
24, // 32: ra.RegistrationAuthority.GenerateOCSP:output_type -> ca.OCSPResponse
|
||||
14, // 33: ra.RegistrationAuthority.UnpauseAccount:output_type -> ra.UnpauseAccountResponse
|
||||
16, // 34: ra.RegistrationAuthority.AddRateLimitOverride:output_type -> ra.AddRateLimitOverrideResponse
|
||||
1, // 35: ra.SCTProvider.GetSCTs:output_type -> ra.SCTResponse
|
||||
21, // [21:36] is the sub-list for method output_type
|
||||
6, // [6:21] is the sub-list for method input_type
|
||||
6, // [6:6] is the sub-list for extension type_name
|
||||
6, // [6:6] is the sub-list for extension extendee
|
||||
0, // [0:6] is the sub-list for field type_name
|
||||
|
@ -1331,7 +1264,7 @@ func file_ra_proto_init() {
|
|||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_ra_proto_rawDesc), len(file_ra_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 18,
|
||||
NumMessages: 17,
|
||||
NumExtensions: 0,
|
||||
NumServices: 2,
|
||||
},
|
||||
|
|
|
@ -10,7 +10,6 @@ import "google/protobuf/duration.proto";
|
|||
|
||||
service RegistrationAuthority {
|
||||
rpc NewRegistration(core.Registration) returns (core.Registration) {}
|
||||
rpc UpdateRegistrationContact(UpdateRegistrationContactRequest) returns (core.Registration) {}
|
||||
rpc UpdateRegistrationKey(UpdateRegistrationKeyRequest) returns (core.Registration) {}
|
||||
rpc DeactivateRegistration(DeactivateRegistrationRequest) returns (core.Registration) {}
|
||||
rpc PerformValidation(PerformValidationRequest) returns (core.Authorization) {}
|
||||
|
@ -43,11 +42,6 @@ message GenerateOCSPRequest {
|
|||
string serial = 1;
|
||||
}
|
||||
|
||||
message UpdateRegistrationContactRequest {
|
||||
int64 registrationID = 1;
|
||||
repeated string contacts = 2;
|
||||
}
|
||||
|
||||
message UpdateRegistrationKeyRequest {
|
||||
int64 registrationID = 1;
|
||||
bytes jwk = 2;
|
||||
|
|
|
@ -23,7 +23,6 @@ const _ = grpc.SupportPackageIsVersion9
|
|||
|
||||
const (
|
||||
RegistrationAuthority_NewRegistration_FullMethodName = "/ra.RegistrationAuthority/NewRegistration"
|
||||
RegistrationAuthority_UpdateRegistrationContact_FullMethodName = "/ra.RegistrationAuthority/UpdateRegistrationContact"
|
||||
RegistrationAuthority_UpdateRegistrationKey_FullMethodName = "/ra.RegistrationAuthority/UpdateRegistrationKey"
|
||||
RegistrationAuthority_DeactivateRegistration_FullMethodName = "/ra.RegistrationAuthority/DeactivateRegistration"
|
||||
RegistrationAuthority_PerformValidation_FullMethodName = "/ra.RegistrationAuthority/PerformValidation"
|
||||
|
@ -44,7 +43,6 @@ const (
|
|||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type RegistrationAuthorityClient interface {
|
||||
NewRegistration(ctx context.Context, in *proto.Registration, opts ...grpc.CallOption) (*proto.Registration, error)
|
||||
UpdateRegistrationContact(ctx context.Context, in *UpdateRegistrationContactRequest, opts ...grpc.CallOption) (*proto.Registration, error)
|
||||
UpdateRegistrationKey(ctx context.Context, in *UpdateRegistrationKeyRequest, opts ...grpc.CallOption) (*proto.Registration, error)
|
||||
DeactivateRegistration(ctx context.Context, in *DeactivateRegistrationRequest, opts ...grpc.CallOption) (*proto.Registration, error)
|
||||
PerformValidation(ctx context.Context, in *PerformValidationRequest, opts ...grpc.CallOption) (*proto.Authorization, error)
|
||||
|
@ -79,16 +77,6 @@ func (c *registrationAuthorityClient) NewRegistration(ctx context.Context, in *p
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *registrationAuthorityClient) UpdateRegistrationContact(ctx context.Context, in *UpdateRegistrationContactRequest, opts ...grpc.CallOption) (*proto.Registration, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(proto.Registration)
|
||||
err := c.cc.Invoke(ctx, RegistrationAuthority_UpdateRegistrationContact_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *registrationAuthorityClient) UpdateRegistrationKey(ctx context.Context, in *UpdateRegistrationKeyRequest, opts ...grpc.CallOption) (*proto.Registration, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(proto.Registration)
|
||||
|
@ -224,7 +212,6 @@ func (c *registrationAuthorityClient) AddRateLimitOverride(ctx context.Context,
|
|||
// for forward compatibility.
|
||||
type RegistrationAuthorityServer interface {
|
||||
NewRegistration(context.Context, *proto.Registration) (*proto.Registration, error)
|
||||
UpdateRegistrationContact(context.Context, *UpdateRegistrationContactRequest) (*proto.Registration, error)
|
||||
UpdateRegistrationKey(context.Context, *UpdateRegistrationKeyRequest) (*proto.Registration, error)
|
||||
DeactivateRegistration(context.Context, *DeactivateRegistrationRequest) (*proto.Registration, error)
|
||||
PerformValidation(context.Context, *PerformValidationRequest) (*proto.Authorization, error)
|
||||
|
@ -252,9 +239,6 @@ type UnimplementedRegistrationAuthorityServer struct{}
|
|||
func (UnimplementedRegistrationAuthorityServer) NewRegistration(context.Context, *proto.Registration) (*proto.Registration, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method NewRegistration not implemented")
|
||||
}
|
||||
func (UnimplementedRegistrationAuthorityServer) UpdateRegistrationContact(context.Context, *UpdateRegistrationContactRequest) (*proto.Registration, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistrationContact not implemented")
|
||||
}
|
||||
func (UnimplementedRegistrationAuthorityServer) UpdateRegistrationKey(context.Context, *UpdateRegistrationKeyRequest) (*proto.Registration, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistrationKey not implemented")
|
||||
}
|
||||
|
@ -333,24 +317,6 @@ func _RegistrationAuthority_NewRegistration_Handler(srv interface{}, ctx context
|
|||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _RegistrationAuthority_UpdateRegistrationContact_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateRegistrationContactRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(RegistrationAuthorityServer).UpdateRegistrationContact(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: RegistrationAuthority_UpdateRegistrationContact_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(RegistrationAuthorityServer).UpdateRegistrationContact(ctx, req.(*UpdateRegistrationContactRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _RegistrationAuthority_UpdateRegistrationKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateRegistrationKeyRequest)
|
||||
if err := dec(in); err != nil {
|
||||
|
@ -596,10 +562,6 @@ var RegistrationAuthority_ServiceDesc = grpc.ServiceDesc{
|
|||
MethodName: "NewRegistration",
|
||||
Handler: _RegistrationAuthority_NewRegistration_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateRegistrationContact",
|
||||
Handler: _RegistrationAuthority_UpdateRegistrationContact_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateRegistrationKey",
|
||||
Handler: _RegistrationAuthority_UpdateRegistrationKey_Handler,
|
||||
|
|
106
ra/ra.go
106
ra/ra.go
|
@ -111,9 +111,6 @@ type RegistrationAuthorityImpl struct {
|
|||
// TODO(#8177): Remove once the rate of requests failing to finalize due to
|
||||
// requesting Must-Staple has diminished.
|
||||
mustStapleRequestsCounter *prometheus.CounterVec
|
||||
// TODO(#7966): Remove once the rate of registrations with contacts has been
|
||||
// determined.
|
||||
newOrUpdatedContactCounter *prometheus.CounterVec
|
||||
}
|
||||
|
||||
var _ rapb.RegistrationAuthorityServer = (*RegistrationAuthorityImpl)(nil)
|
||||
|
@ -230,45 +227,36 @@ func NewRegistrationAuthorityImpl(
|
|||
}, []string{"allowlist"})
|
||||
stats.MustRegister(mustStapleRequestsCounter)
|
||||
|
||||
// TODO(#7966): Remove once the rate of registrations with contacts has been
|
||||
// determined.
|
||||
newOrUpdatedContactCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "new_or_updated_contact",
|
||||
Help: "A counter of new or updated contacts, labeled by new=[bool]",
|
||||
}, []string{"new"})
|
||||
stats.MustRegister(newOrUpdatedContactCounter)
|
||||
|
||||
issuersByNameID := make(map[issuance.NameID]*issuance.Certificate)
|
||||
for _, issuer := range issuers {
|
||||
issuersByNameID[issuer.NameID()] = issuer
|
||||
}
|
||||
|
||||
ra := &RegistrationAuthorityImpl{
|
||||
clk: clk,
|
||||
log: logger,
|
||||
profiles: profiles,
|
||||
maxContactsPerReg: maxContactsPerReg,
|
||||
keyPolicy: keyPolicy,
|
||||
limiter: limiter,
|
||||
txnBuilder: txnBuilder,
|
||||
publisher: pubc,
|
||||
finalizeTimeout: finalizeTimeout,
|
||||
ctpolicy: ctp,
|
||||
ctpolicyResults: ctpolicyResults,
|
||||
purger: purger,
|
||||
issuersByNameID: issuersByNameID,
|
||||
namesPerCert: namesPerCert,
|
||||
newRegCounter: newRegCounter,
|
||||
recheckCAACounter: recheckCAACounter,
|
||||
newCertCounter: newCertCounter,
|
||||
revocationReasonCounter: revocationReasonCounter,
|
||||
authzAges: authzAges,
|
||||
orderAges: orderAges,
|
||||
inflightFinalizes: inflightFinalizes,
|
||||
certCSRMismatch: certCSRMismatch,
|
||||
pauseCounter: pauseCounter,
|
||||
mustStapleRequestsCounter: mustStapleRequestsCounter,
|
||||
newOrUpdatedContactCounter: newOrUpdatedContactCounter,
|
||||
clk: clk,
|
||||
log: logger,
|
||||
profiles: profiles,
|
||||
maxContactsPerReg: maxContactsPerReg,
|
||||
keyPolicy: keyPolicy,
|
||||
limiter: limiter,
|
||||
txnBuilder: txnBuilder,
|
||||
publisher: pubc,
|
||||
finalizeTimeout: finalizeTimeout,
|
||||
ctpolicy: ctp,
|
||||
ctpolicyResults: ctpolicyResults,
|
||||
purger: purger,
|
||||
issuersByNameID: issuersByNameID,
|
||||
namesPerCert: namesPerCert,
|
||||
newRegCounter: newRegCounter,
|
||||
recheckCAACounter: recheckCAACounter,
|
||||
newCertCounter: newCertCounter,
|
||||
revocationReasonCounter: revocationReasonCounter,
|
||||
authzAges: authzAges,
|
||||
orderAges: orderAges,
|
||||
inflightFinalizes: inflightFinalizes,
|
||||
certCSRMismatch: certCSRMismatch,
|
||||
pauseCounter: pauseCounter,
|
||||
mustStapleRequestsCounter: mustStapleRequestsCounter,
|
||||
}
|
||||
return ra
|
||||
}
|
||||
|
@ -527,17 +515,9 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(ctx context.Context, reques
|
|||
return nil, berrors.MalformedError("invalid public key: %s", err.Error())
|
||||
}
|
||||
|
||||
// Check that contacts conform to our expectations.
|
||||
// TODO(#8199): Remove this when no contacts are included in any requests.
|
||||
err = ra.validateContacts(request.Contact)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Don't populate ID or CreatedAt because those will be set by the SA.
|
||||
req := &corepb.Registration{
|
||||
Key: request.Key,
|
||||
Contact: request.Contact,
|
||||
Agreement: request.Agreement,
|
||||
Status: string(core.StatusValid),
|
||||
}
|
||||
|
@ -548,12 +528,6 @@ func (ra *RegistrationAuthorityImpl) NewRegistration(ctx context.Context, reques
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(#7966): Remove once the rate of registrations with contacts has been
|
||||
// determined.
|
||||
for range request.Contact {
|
||||
ra.newOrUpdatedContactCounter.With(prometheus.Labels{"new": "true"}).Inc()
|
||||
}
|
||||
|
||||
ra.newRegCounter.Inc()
|
||||
return res, nil
|
||||
}
|
||||
|
@ -1400,38 +1374,6 @@ func (ra *RegistrationAuthorityImpl) getSCTs(ctx context.Context, precertDER []b
|
|||
return scts, nil
|
||||
}
|
||||
|
||||
// UpdateRegistrationContact updates an existing Registration's contact. The
|
||||
// updated contacts field may be empty.
|
||||
//
|
||||
// Deprecated: This method has no callers. See
|
||||
// https://github.com/letsencrypt/boulder/issues/8199 for removal.
|
||||
func (ra *RegistrationAuthorityImpl) UpdateRegistrationContact(ctx context.Context, req *rapb.UpdateRegistrationContactRequest) (*corepb.Registration, error) {
|
||||
if core.IsAnyNilOrZero(req.RegistrationID) {
|
||||
return nil, errIncompleteGRPCRequest
|
||||
}
|
||||
|
||||
err := ra.validateContacts(req.Contacts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid contact: %w", err)
|
||||
}
|
||||
|
||||
update, err := ra.SA.UpdateRegistrationContact(ctx, &sapb.UpdateRegistrationContactRequest{
|
||||
RegistrationID: req.RegistrationID,
|
||||
Contacts: req.Contacts,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update registration contact: %w", err)
|
||||
}
|
||||
|
||||
// TODO(#7966): Remove once the rate of registrations with contacts has
|
||||
// been determined.
|
||||
for range req.Contacts {
|
||||
ra.newOrUpdatedContactCounter.With(prometheus.Labels{"new": "false"}).Inc()
|
||||
}
|
||||
|
||||
return update, nil
|
||||
}
|
||||
|
||||
// UpdateRegistrationKey updates an existing Registration's key.
|
||||
func (ra *RegistrationAuthorityImpl) UpdateRegistrationKey(ctx context.Context, req *rapb.UpdateRegistrationKeyRequest) (*corepb.Registration, error) {
|
||||
if core.IsAnyNilOrZero(req.RegistrationID, req.Jwk) {
|
||||
|
|
|
@ -335,8 +335,8 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, sapb.StorageAutho
|
|||
},
|
||||
blog.NewMock())
|
||||
test.AssertNotError(t, err, "Couldn't create PA")
|
||||
err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
|
||||
test.AssertNotError(t, err, "Couldn't set hostname policy")
|
||||
err = pa.LoadIdentPolicyFile("../test/ident-policy.yaml")
|
||||
test.AssertNotError(t, err, "Couldn't set identifier policy")
|
||||
|
||||
stats := metrics.NoopRegisterer
|
||||
|
||||
|
@ -473,12 +473,10 @@ func TestValidateContacts(t *testing.T) {
|
|||
func TestNewRegistration(t *testing.T) {
|
||||
_, sa, ra, _, _, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
mailto := "mailto:foo@letsencrypt.org"
|
||||
acctKeyB, err := AccountKeyB.MarshalJSON()
|
||||
test.AssertNotError(t, err, "failed to marshal account key")
|
||||
input := &corepb.Registration{
|
||||
Contact: []string{mailto},
|
||||
Key: acctKeyB,
|
||||
Key: acctKeyB,
|
||||
}
|
||||
|
||||
result, err := ra.NewRegistration(ctx, input)
|
||||
|
@ -486,7 +484,6 @@ func TestNewRegistration(t *testing.T) {
|
|||
t.Fatalf("could not create new registration: %s", err)
|
||||
}
|
||||
test.AssertByteEquals(t, result.Key, acctKeyB)
|
||||
test.Assert(t, len(result.Contact) == 0, "Wrong number of contacts")
|
||||
test.Assert(t, result.Agreement == "", "Agreement didn't default empty")
|
||||
|
||||
reg, err := sa.GetRegistration(ctx, &sapb.RegistrationID{Id: result.Id})
|
||||
|
@ -509,8 +506,7 @@ func TestNewRegistrationSAFailure(t *testing.T) {
|
|||
acctKeyB, err := AccountKeyB.MarshalJSON()
|
||||
test.AssertNotError(t, err, "failed to marshal account key")
|
||||
input := corepb.Registration{
|
||||
Contact: []string{"mailto:test@example.com"},
|
||||
Key: acctKeyB,
|
||||
Key: acctKeyB,
|
||||
}
|
||||
result, err := ra.NewRegistration(ctx, &input)
|
||||
if err == nil {
|
||||
|
@ -521,13 +517,11 @@ func TestNewRegistrationSAFailure(t *testing.T) {
|
|||
func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
|
||||
_, _, ra, _, _, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
mailto := "mailto:foo@letsencrypt.org"
|
||||
acctKeyC, err := AccountKeyC.MarshalJSON()
|
||||
test.AssertNotError(t, err, "failed to marshal account key")
|
||||
input := &corepb.Registration{
|
||||
Id: 23,
|
||||
Key: acctKeyC,
|
||||
Contact: []string{mailto},
|
||||
Agreement: "I agreed",
|
||||
}
|
||||
|
||||
|
@ -541,12 +535,10 @@ func TestNewRegistrationNoFieldOverwrite(t *testing.T) {
|
|||
func TestNewRegistrationBadKey(t *testing.T) {
|
||||
_, _, ra, _, _, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
mailto := "mailto:foo@letsencrypt.org"
|
||||
shortKey, err := ShortKey.MarshalJSON()
|
||||
test.AssertNotError(t, err, "failed to marshal account key")
|
||||
input := &corepb.Registration{
|
||||
Contact: []string{mailto},
|
||||
Key: shortKey,
|
||||
Key: shortKey,
|
||||
}
|
||||
_, err = ra.NewRegistration(ctx, input)
|
||||
test.AssertError(t, err, "Should have rejected authorization with short key")
|
||||
|
@ -2913,8 +2905,8 @@ func TestFinalizeOrderDisabledChallenge(t *testing.T) {
|
|||
},
|
||||
ra.log)
|
||||
test.AssertNotError(t, err, "creating test PA")
|
||||
err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
|
||||
test.AssertNotError(t, err, "loading test hostname policy")
|
||||
err = pa.LoadIdentPolicyFile("../test/ident-policy.yaml")
|
||||
test.AssertNotError(t, err, "loading test identifier policy")
|
||||
ra.PA = pa
|
||||
|
||||
// Now finalizing this order should fail
|
||||
|
@ -4190,34 +4182,17 @@ type NoUpdateSA struct {
|
|||
sapb.StorageAuthorityClient
|
||||
}
|
||||
|
||||
func (sa *NoUpdateSA) UpdateRegistrationContact(_ context.Context, _ *sapb.UpdateRegistrationContactRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
|
||||
return nil, fmt.Errorf("UpdateRegistrationContact() is mocked to always error")
|
||||
}
|
||||
|
||||
func (sa *NoUpdateSA) UpdateRegistrationKey(_ context.Context, _ *sapb.UpdateRegistrationKeyRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
|
||||
return nil, fmt.Errorf("UpdateRegistrationKey() is mocked to always error")
|
||||
}
|
||||
|
||||
// mockSARecordingRegistration tests UpdateRegistrationContact and UpdateRegistrationKey.
|
||||
// mockSARecordingRegistration tests UpdateRegistrationKey.
|
||||
type mockSARecordingRegistration struct {
|
||||
sapb.StorageAuthorityClient
|
||||
providedRegistrationID int64
|
||||
providedContacts []string
|
||||
providedJwk []byte
|
||||
}
|
||||
|
||||
// UpdateRegistrationContact records the registration ID and updated contacts
|
||||
// (optional) provided.
|
||||
func (sa *mockSARecordingRegistration) UpdateRegistrationContact(ctx context.Context, req *sapb.UpdateRegistrationContactRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
|
||||
sa.providedRegistrationID = req.RegistrationID
|
||||
sa.providedContacts = req.Contacts
|
||||
|
||||
return &corepb.Registration{
|
||||
Id: req.RegistrationID,
|
||||
Contact: req.Contacts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateRegistrationKey records the registration ID and updated key provided.
|
||||
func (sa *mockSARecordingRegistration) UpdateRegistrationKey(ctx context.Context, req *sapb.UpdateRegistrationKeyRequest, _ ...grpc.CallOption) (*corepb.Registration, error) {
|
||||
sa.providedRegistrationID = req.RegistrationID
|
||||
|
@ -4229,62 +4204,6 @@ func (sa *mockSARecordingRegistration) UpdateRegistrationKey(ctx context.Context
|
|||
}, nil
|
||||
}
|
||||
|
||||
// TestUpdateRegistrationContact tests that the RA's UpdateRegistrationContact
|
||||
// method correctly: requires a registration ID; validates the contact provided;
|
||||
// does not require a contact; passes the requested registration ID and contact
|
||||
// to the SA; passes the updated Registration back to the caller; and can return
|
||||
// an error.
|
||||
func TestUpdateRegistrationContact(t *testing.T) {
|
||||
_, _, ra, _, _, cleanUp := initAuthorities(t)
|
||||
defer cleanUp()
|
||||
|
||||
expectRegID := int64(1)
|
||||
expectContacts := []string{"mailto:test@contoso.com"}
|
||||
mockSA := mockSARecordingRegistration{}
|
||||
ra.SA = &mockSA
|
||||
|
||||
_, err := ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{})
|
||||
test.AssertError(t, err, "should not have been able to update registration contact without a registration ID")
|
||||
test.AssertContains(t, err.Error(), "incomplete gRPC request message")
|
||||
|
||||
_, err = ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
|
||||
RegistrationID: expectRegID,
|
||||
Contacts: []string{"tel:+44123"},
|
||||
})
|
||||
test.AssertError(t, err, "should not have been able to update registration contact to an invalid contact")
|
||||
test.AssertContains(t, err.Error(), "invalid contact")
|
||||
|
||||
res, err := ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
|
||||
RegistrationID: expectRegID,
|
||||
})
|
||||
test.AssertNotError(t, err, "should have been able to update registration with a blank contact")
|
||||
test.AssertEquals(t, res.Id, expectRegID)
|
||||
test.AssertEquals(t, mockSA.providedRegistrationID, expectRegID)
|
||||
test.AssertDeepEquals(t, res.Contact, []string(nil))
|
||||
test.AssertDeepEquals(t, mockSA.providedContacts, []string(nil))
|
||||
|
||||
res, err = ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
|
||||
RegistrationID: expectRegID,
|
||||
Contacts: expectContacts,
|
||||
})
|
||||
test.AssertNotError(t, err, "should have been able to update registration with a populated contact")
|
||||
test.AssertEquals(t, res.Id, expectRegID)
|
||||
test.AssertEquals(t, mockSA.providedRegistrationID, expectRegID)
|
||||
test.AssertDeepEquals(t, res.Contact, expectContacts)
|
||||
test.AssertDeepEquals(t, mockSA.providedContacts, expectContacts)
|
||||
|
||||
// Switch to a mock SA that will always error if UpdateRegistrationContact()
|
||||
// is called.
|
||||
ra.SA = &NoUpdateSA{}
|
||||
_, err = ra.UpdateRegistrationContact(context.Background(), &rapb.UpdateRegistrationContactRequest{
|
||||
RegistrationID: expectRegID,
|
||||
Contacts: expectContacts,
|
||||
})
|
||||
test.AssertError(t, err, "should have received an error from the SA")
|
||||
test.AssertContains(t, err.Error(), "failed to update registration contact")
|
||||
test.AssertContains(t, err.Error(), "mocked to always error")
|
||||
}
|
||||
|
||||
// TestUpdateRegistrationKey tests that the RA's UpdateRegistrationKey method
|
||||
// correctly requires a registration ID and key, passes them to the SA, and
|
||||
// passes the updated Registration back to the caller.
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
// returns a Decision struct with the result of the decision and the updated
|
||||
// TAT. The cost must be 0 or greater and <= the burst capacity of the limit.
|
||||
func maybeSpend(clk clock.Clock, txn Transaction, tat time.Time) *Decision {
|
||||
if txn.cost < 0 || txn.cost > txn.limit.burst {
|
||||
if txn.cost < 0 || txn.cost > txn.limit.Burst {
|
||||
// The condition above is the union of the conditions checked in Check
|
||||
// and Spend methods of Limiter. If this panic is reached, it means that
|
||||
// the caller has introduced a bug.
|
||||
|
@ -67,7 +67,7 @@ func maybeSpend(clk clock.Clock, txn Transaction, tat time.Time) *Decision {
|
|||
// or greater. A cost will only be refunded up to the burst capacity of the
|
||||
// limit. A partial refund is still considered successful.
|
||||
func maybeRefund(clk clock.Clock, txn Transaction, tat time.Time) *Decision {
|
||||
if txn.cost < 0 || txn.cost > txn.limit.burst {
|
||||
if txn.cost < 0 || txn.cost > txn.limit.Burst {
|
||||
// The condition above is checked in the Refund method of Limiter. If
|
||||
// this panic is reached, it means that the caller has introduced a bug.
|
||||
panic("invalid cost for maybeRefund")
|
||||
|
@ -80,7 +80,7 @@ func maybeRefund(clk clock.Clock, txn Transaction, tat time.Time) *Decision {
|
|||
// The TAT is in the past, therefore the bucket is full.
|
||||
return &Decision{
|
||||
allowed: false,
|
||||
remaining: txn.limit.burst,
|
||||
remaining: txn.limit.Burst,
|
||||
retryIn: time.Duration(0),
|
||||
resetIn: time.Duration(0),
|
||||
newTAT: tat,
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
func TestDecide(t *testing.T) {
|
||||
clk := clock.NewFake()
|
||||
limit := &limit{burst: 10, count: 1, period: config.Duration{Duration: time.Second}}
|
||||
limit := &Limit{Burst: 10, Count: 1, Period: config.Duration{Duration: time.Second}}
|
||||
limit.precompute()
|
||||
|
||||
// Begin by using 1 of our 10 requests.
|
||||
|
@ -139,7 +139,7 @@ func TestDecide(t *testing.T) {
|
|||
|
||||
func TestMaybeRefund(t *testing.T) {
|
||||
clk := clock.NewFake()
|
||||
limit := &limit{burst: 10, count: 1, period: config.Duration{Duration: time.Second}}
|
||||
limit := &Limit{Burst: 10, Count: 1, Period: config.Duration{Duration: time.Second}}
|
||||
limit.precompute()
|
||||
|
||||
// Begin by using 1 of our 10 requests.
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package ratelimits
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/letsencrypt/boulder/config"
|
||||
|
@ -38,26 +41,32 @@ type LimitConfig struct {
|
|||
|
||||
type LimitConfigs map[string]*LimitConfig
|
||||
|
||||
// limit defines the configuration for a rate limit or a rate limit override.
|
||||
// Limit defines the configuration for a rate limit or a rate limit override.
|
||||
//
|
||||
// The zero value of this struct is invalid, because some of the fields must
|
||||
// be greater than zero.
|
||||
type limit struct {
|
||||
// burst specifies maximum concurrent allowed requests at any given time. It
|
||||
// The zero value of this struct is invalid, because some of the fields must be
|
||||
// greater than zero. It and several of its fields are exported to support admin
|
||||
// tooling used during the migration from overrides.yaml to the overrides
|
||||
// database table.
|
||||
type Limit struct {
|
||||
// Burst specifies maximum concurrent allowed requests at any given time. It
|
||||
// must be greater than zero.
|
||||
burst int64
|
||||
Burst int64
|
||||
|
||||
// count is the number of requests allowed per period. It must be greater
|
||||
// Count is the number of requests allowed per period. It must be greater
|
||||
// than zero.
|
||||
count int64
|
||||
Count int64
|
||||
|
||||
// period is the duration of time in which the count (of requests) is
|
||||
// Period is the duration of time in which the count (of requests) is
|
||||
// allowed. It must be greater than zero.
|
||||
period config.Duration
|
||||
Period config.Duration
|
||||
|
||||
// name is the name of the limit. It must be one of the Name enums defined
|
||||
// Name is the name of the limit. It must be one of the Name enums defined
|
||||
// in this package.
|
||||
name Name
|
||||
Name Name
|
||||
|
||||
// Comment is an optional field that can be used to provide additional
|
||||
// context for an override. It is not used for default limits.
|
||||
Comment string
|
||||
|
||||
// emissionInterval is the interval, in nanoseconds, at which tokens are
|
||||
// added to a bucket (period / count). This is also the steady-state rate at
|
||||
|
@ -76,25 +85,25 @@ type limit struct {
|
|||
}
|
||||
|
||||
// precompute calculates the emissionInterval and burstOffset for the limit.
|
||||
func (l *limit) precompute() {
|
||||
l.emissionInterval = l.period.Nanoseconds() / l.count
|
||||
l.burstOffset = l.emissionInterval * l.burst
|
||||
func (l *Limit) precompute() {
|
||||
l.emissionInterval = l.Period.Nanoseconds() / l.Count
|
||||
l.burstOffset = l.emissionInterval * l.Burst
|
||||
}
|
||||
|
||||
func validateLimit(l *limit) error {
|
||||
if l.burst <= 0 {
|
||||
return fmt.Errorf("invalid burst '%d', must be > 0", l.burst)
|
||||
func ValidateLimit(l *Limit) error {
|
||||
if l.Burst <= 0 {
|
||||
return fmt.Errorf("invalid burst '%d', must be > 0", l.Burst)
|
||||
}
|
||||
if l.count <= 0 {
|
||||
return fmt.Errorf("invalid count '%d', must be > 0", l.count)
|
||||
if l.Count <= 0 {
|
||||
return fmt.Errorf("invalid count '%d', must be > 0", l.Count)
|
||||
}
|
||||
if l.period.Duration <= 0 {
|
||||
return fmt.Errorf("invalid period '%s', must be > 0", l.period)
|
||||
if l.Period.Duration <= 0 {
|
||||
return fmt.Errorf("invalid period '%s', must be > 0", l.Period)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type limits map[string]*limit
|
||||
type Limits map[string]*Limit
|
||||
|
||||
// loadDefaults marshals the defaults YAML file at path into a map of limits.
|
||||
func loadDefaults(path string) (LimitConfigs, error) {
|
||||
|
@ -149,9 +158,9 @@ func parseOverrideNameId(key string) (Name, string, error) {
|
|||
return Unknown, "", fmt.Errorf("empty name in override %q, must be formatted 'name:id'", key)
|
||||
}
|
||||
|
||||
name, ok := stringToName[nameStr]
|
||||
name, ok := StringToName[nameStr]
|
||||
if !ok {
|
||||
return Unknown, "", fmt.Errorf("unrecognized name %q in override limit %q, must be one of %v", nameStr, key, limitNames)
|
||||
return Unknown, "", fmt.Errorf("unrecognized name %q in override limit %q, must be one of %v", nameStr, key, LimitNames)
|
||||
}
|
||||
id := nameAndId[1]
|
||||
if id == "" {
|
||||
|
@ -160,37 +169,52 @@ func parseOverrideNameId(key string) (Name, string, error) {
|
|||
return name, id, nil
|
||||
}
|
||||
|
||||
// parseOverrideNameEnumId is like parseOverrideNameId, but it expects the
|
||||
// key to be formatted as 'name:id', where 'name' is a Name enum string and 'id'
|
||||
// is a string identifier. It returns an error if either part is missing or invalid.
|
||||
func parseOverrideNameEnumId(key string) (Name, string, error) {
|
||||
if !strings.Contains(key, ":") {
|
||||
// Avoids a potential panic in strings.SplitN below.
|
||||
return Unknown, "", fmt.Errorf("invalid override %q, must be formatted 'name:id'", key)
|
||||
}
|
||||
nameStrAndId := strings.SplitN(key, ":", 2)
|
||||
if len(nameStrAndId) != 2 {
|
||||
return Unknown, "", fmt.Errorf("invalid override %q, must be formatted 'name:id'", key)
|
||||
}
|
||||
|
||||
nameInt, err := strconv.Atoi(nameStrAndId[0])
|
||||
if err != nil {
|
||||
return Unknown, "", fmt.Errorf("invalid name %q in override limit %q, must be an integer", nameStrAndId[0], key)
|
||||
}
|
||||
name := Name(nameInt)
|
||||
if !name.isValid() {
|
||||
return Unknown, "", fmt.Errorf("invalid name %q in override limit %q, must be one of %v", nameStrAndId[0], key, LimitNames)
|
||||
|
||||
}
|
||||
id := nameStrAndId[1]
|
||||
if id == "" {
|
||||
return Unknown, "", fmt.Errorf("empty id in override %q, must be formatted 'name:id'", key)
|
||||
}
|
||||
return name, id, nil
|
||||
}
|
||||
|
||||
// parseOverrideLimits validates a YAML list of override limits. It must be
|
||||
// formatted as a list of maps, where each map has a single key representing the
|
||||
// limit name and a value that is a map containing the limit fields and an
|
||||
// additional 'ids' field that is a list of ids that this override applies to.
|
||||
func parseOverrideLimits(newOverridesYAML overridesYAML) (limits, error) {
|
||||
parsed := make(limits)
|
||||
func parseOverrideLimits(newOverridesYAML overridesYAML) (Limits, error) {
|
||||
parsed := make(Limits)
|
||||
|
||||
for _, ov := range newOverridesYAML {
|
||||
for k, v := range ov {
|
||||
name, ok := stringToName[k]
|
||||
name, ok := StringToName[k]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unrecognized name %q in override limit, must be one of %v", k, limitNames)
|
||||
}
|
||||
|
||||
lim := &limit{
|
||||
burst: v.Burst,
|
||||
count: v.Count,
|
||||
period: v.Period,
|
||||
name: name,
|
||||
isOverride: true,
|
||||
}
|
||||
lim.precompute()
|
||||
|
||||
err := validateLimit(lim)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating override limit %q: %w", k, err)
|
||||
return nil, fmt.Errorf("unrecognized name %q in override limit, must be one of %v", k, LimitNames)
|
||||
}
|
||||
|
||||
for _, entry := range v.Ids {
|
||||
id := entry.Id
|
||||
err = validateIdForName(name, id)
|
||||
err := validateIdForName(name, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"validating name %s and id %q for override limit %q: %w", name, id, k, err)
|
||||
|
@ -204,7 +228,7 @@ func parseOverrideLimits(newOverridesYAML overridesYAML) (limits, error) {
|
|||
// (IPv6) prefixes in CIDR notation.
|
||||
ip, err := netip.ParseAddr(id)
|
||||
if err == nil {
|
||||
prefix, err := coveringPrefix(ip)
|
||||
prefix, err := coveringIPPrefix(name, ip)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"computing prefix for IP address %q: %w", id, err)
|
||||
|
@ -214,16 +238,22 @@ func parseOverrideLimits(newOverridesYAML overridesYAML) (limits, error) {
|
|||
case CertificatesPerFQDNSet:
|
||||
// Compute the hash of a comma-separated list of identifier
|
||||
// values.
|
||||
var idents identifier.ACMEIdentifiers
|
||||
for _, value := range strings.Split(id, ",") {
|
||||
ip, err := netip.ParseAddr(value)
|
||||
if err == nil {
|
||||
idents = append(idents, identifier.NewIP(ip))
|
||||
} else {
|
||||
idents = append(idents, identifier.NewDNS(value))
|
||||
}
|
||||
}
|
||||
id = fmt.Sprintf("%x", core.HashIdentifiers(idents))
|
||||
id = fmt.Sprintf("%x", core.HashIdentifiers(identifier.FromStringSlice(strings.Split(id, ","))))
|
||||
}
|
||||
|
||||
lim := &Limit{
|
||||
Burst: v.Burst,
|
||||
Count: v.Count,
|
||||
Period: v.Period,
|
||||
Name: name,
|
||||
Comment: entry.Comment,
|
||||
isOverride: true,
|
||||
}
|
||||
lim.precompute()
|
||||
|
||||
err = ValidateLimit(lim)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating override limit %q: %w", k, err)
|
||||
}
|
||||
|
||||
parsed[joinWithColon(name.EnumString(), id)] = lim
|
||||
|
@ -234,23 +264,23 @@ func parseOverrideLimits(newOverridesYAML overridesYAML) (limits, error) {
|
|||
}
|
||||
|
||||
// parseDefaultLimits validates a map of default limits and rekeys it by 'Name'.
|
||||
func parseDefaultLimits(newDefaultLimits LimitConfigs) (limits, error) {
|
||||
parsed := make(limits)
|
||||
func parseDefaultLimits(newDefaultLimits LimitConfigs) (Limits, error) {
|
||||
parsed := make(Limits)
|
||||
|
||||
for k, v := range newDefaultLimits {
|
||||
name, ok := stringToName[k]
|
||||
name, ok := StringToName[k]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unrecognized name %q in default limit, must be one of %v", k, limitNames)
|
||||
return nil, fmt.Errorf("unrecognized name %q in default limit, must be one of %v", k, LimitNames)
|
||||
}
|
||||
|
||||
lim := &limit{
|
||||
burst: v.Burst,
|
||||
count: v.Count,
|
||||
period: v.Period,
|
||||
name: name,
|
||||
lim := &Limit{
|
||||
Burst: v.Burst,
|
||||
Count: v.Count,
|
||||
Period: v.Period,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
err := validateLimit(lim)
|
||||
err := ValidateLimit(lim)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing default limit %q: %w", k, err)
|
||||
}
|
||||
|
@ -263,10 +293,10 @@ func parseDefaultLimits(newDefaultLimits LimitConfigs) (limits, error) {
|
|||
|
||||
type limitRegistry struct {
|
||||
// defaults stores default limits by 'name'.
|
||||
defaults limits
|
||||
defaults Limits
|
||||
|
||||
// overrides stores override limits by 'name:id'.
|
||||
overrides limits
|
||||
overrides Limits
|
||||
}
|
||||
|
||||
func newLimitRegistryFromFiles(defaults, overrides string) (*limitRegistry, error) {
|
||||
|
@ -308,7 +338,7 @@ func newLimitRegistry(defaults LimitConfigs, overrides overridesYAML) (*limitReg
|
|||
// required, bucketKey is optional. If bucketkey is empty, the default for the
|
||||
// limit specified by name is returned. If no default limit exists for the
|
||||
// specified name, errLimitDisabled is returned.
|
||||
func (l *limitRegistry) getLimit(name Name, bucketKey string) (*limit, error) {
|
||||
func (l *limitRegistry) getLimit(name Name, bucketKey string) (*Limit, error) {
|
||||
if !name.isValid() {
|
||||
// This should never happen. Callers should only be specifying the limit
|
||||
// Name enums defined in this package.
|
||||
|
@ -327,3 +357,103 @@ func (l *limitRegistry) getLimit(name Name, bucketKey string) (*limit, error) {
|
|||
}
|
||||
return nil, errLimitDisabled
|
||||
}
|
||||
|
||||
// LoadOverridesByBucketKey loads the overrides YAML at the supplied path,
|
||||
// parses it with the existing helpers, and returns the resulting limits map
|
||||
// keyed by "<name>:<id>". This function is exported to support admin tooling
|
||||
// used during the migration from overrides.yaml to the overrides database
|
||||
// table.
|
||||
func LoadOverridesByBucketKey(path string) (Limits, error) {
|
||||
ovs, err := loadOverrides(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseOverrideLimits(ovs)
|
||||
}
|
||||
|
||||
// DumpOverrides writes the provided overrides to CSV at the supplied path. Each
|
||||
// override is written as a single row, one per ID. Rows are sorted in the
|
||||
// following order:
|
||||
// - Name (ascending)
|
||||
// - Count (descending)
|
||||
// - Burst (descending)
|
||||
// - Period (ascending)
|
||||
// - Comment (ascending)
|
||||
// - ID (ascending)
|
||||
//
|
||||
// This function supports admin tooling that routinely exports the overrides
|
||||
// table for investigation or auditing.
|
||||
func DumpOverrides(path string, overrides Limits) error {
|
||||
type row struct {
|
||||
name string
|
||||
id string
|
||||
count int64
|
||||
burst int64
|
||||
period string
|
||||
comment string
|
||||
}
|
||||
|
||||
var rows []row
|
||||
for bucketKey, limit := range overrides {
|
||||
name, id, err := parseOverrideNameEnumId(bucketKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows = append(rows, row{
|
||||
name: name.String(),
|
||||
id: id,
|
||||
count: limit.Count,
|
||||
burst: limit.Burst,
|
||||
period: limit.Period.Duration.String(),
|
||||
comment: limit.Comment,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
// Sort by limit name in ascending order.
|
||||
if rows[i].name != rows[j].name {
|
||||
return rows[i].name < rows[j].name
|
||||
}
|
||||
// Sort by count in descending order (higher counts first).
|
||||
if rows[i].count != rows[j].count {
|
||||
return rows[i].count > rows[j].count
|
||||
}
|
||||
// Sort by burst in descending order (higher bursts first).
|
||||
if rows[i].burst != rows[j].burst {
|
||||
return rows[i].burst > rows[j].burst
|
||||
}
|
||||
// Sort by period in ascending order (shorter durations first).
|
||||
if rows[i].period != rows[j].period {
|
||||
return rows[i].period < rows[j].period
|
||||
}
|
||||
// Sort by comment in ascending order.
|
||||
if rows[i].comment != rows[j].comment {
|
||||
return rows[i].comment < rows[j].comment
|
||||
}
|
||||
// Sort by ID in ascending order.
|
||||
return rows[i].id < rows[j].id
|
||||
})
|
||||
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
w := csv.NewWriter(f)
|
||||
err = w.Write([]string{"name", "id", "count", "burst", "period", "comment"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range rows {
|
||||
err := w.Write([]string{r.name, r.id, strconv.FormatInt(r.count, 10), strconv.FormatInt(r.burst, 10), r.period, r.comment})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
return w.Error()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package ratelimits
|
|||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -15,7 +17,7 @@ import (
|
|||
// parseDefaultLimits to handle a YAML file.
|
||||
//
|
||||
// TODO(#7901): Update the tests to test these functions individually.
|
||||
func loadAndParseDefaultLimits(path string) (limits, error) {
|
||||
func loadAndParseDefaultLimits(path string) (Limits, error) {
|
||||
fromFile, err := loadDefaults(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -28,7 +30,7 @@ func loadAndParseDefaultLimits(path string) (limits, error) {
|
|||
// parseOverrideLimits to handle a YAML file.
|
||||
//
|
||||
// TODO(#7901): Update the tests to test these functions individually.
|
||||
func loadAndParseOverrideLimits(path string) (limits, error) {
|
||||
func loadAndParseOverrideLimits(path string) (Limits, error) {
|
||||
fromFile, err := loadOverrides(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -69,17 +71,79 @@ func TestParseOverrideNameId(t *testing.T) {
|
|||
test.AssertError(t, err, "invalid enum")
|
||||
}
|
||||
|
||||
func TestParseOverrideNameEnumId(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantLimit Name
|
||||
wantId string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "valid IPv4 address",
|
||||
input: NewRegistrationsPerIPAddress.EnumString() + ":10.0.0.1",
|
||||
wantLimit: NewRegistrationsPerIPAddress,
|
||||
wantId: "10.0.0.1",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid IPv6 address range",
|
||||
input: NewRegistrationsPerIPv6Range.EnumString() + ":2001:0db8:0000::/48",
|
||||
wantLimit: NewRegistrationsPerIPv6Range,
|
||||
wantId: "2001:0db8:0000::/48",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "missing colon",
|
||||
input: NewRegistrationsPerIPAddress.EnumString() + "10.0.0.1",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "only a colon",
|
||||
input: NewRegistrationsPerIPAddress.EnumString() + ":",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid enum",
|
||||
input: "lol:noexist",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
limit, id, err := parseOverrideNameEnumId(tc.input)
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("expected error for input %q, but got none", tc.input)
|
||||
}
|
||||
} else {
|
||||
test.AssertNotError(t, err, tc.name)
|
||||
test.AssertEquals(t, limit, tc.wantLimit)
|
||||
test.AssertEquals(t, id, tc.wantId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLimit(t *testing.T) {
|
||||
err := validateLimit(&limit{burst: 1, count: 1, period: config.Duration{Duration: time.Second}})
|
||||
err := ValidateLimit(&Limit{Burst: 1, Count: 1, Period: config.Duration{Duration: time.Second}})
|
||||
test.AssertNotError(t, err, "valid limit")
|
||||
|
||||
// All of the following are invalid.
|
||||
for _, l := range []*limit{
|
||||
{burst: 0, count: 1, period: config.Duration{Duration: time.Second}},
|
||||
{burst: 1, count: 0, period: config.Duration{Duration: time.Second}},
|
||||
{burst: 1, count: 1, period: config.Duration{Duration: 0}},
|
||||
for _, l := range []*Limit{
|
||||
{Burst: 0, Count: 1, Period: config.Duration{Duration: time.Second}},
|
||||
{Burst: 1, Count: 0, Period: config.Duration{Duration: time.Second}},
|
||||
{Burst: 1, Count: 1, Period: config.Duration{Duration: 0}},
|
||||
} {
|
||||
err = validateLimit(l)
|
||||
err = ValidateLimit(l)
|
||||
test.AssertError(t, err, "limit should be invalid")
|
||||
}
|
||||
}
|
||||
|
@ -89,29 +153,29 @@ func TestLoadAndParseOverrideLimits(t *testing.T) {
|
|||
l, err := loadAndParseOverrideLimits("testdata/working_override.yml")
|
||||
test.AssertNotError(t, err, "valid single override limit")
|
||||
expectKey := joinWithColon(NewRegistrationsPerIPAddress.EnumString(), "64.112.117.1")
|
||||
test.AssertEquals(t, l[expectKey].burst, int64(40))
|
||||
test.AssertEquals(t, l[expectKey].count, int64(40))
|
||||
test.AssertEquals(t, l[expectKey].period.Duration, time.Second)
|
||||
test.AssertEquals(t, l[expectKey].Burst, int64(40))
|
||||
test.AssertEquals(t, l[expectKey].Count, int64(40))
|
||||
test.AssertEquals(t, l[expectKey].Period.Duration, time.Second)
|
||||
|
||||
// Load single valid override limit with a 'domainOrCIDR' Id.
|
||||
l, err = loadAndParseOverrideLimits("testdata/working_override_regid_domainorcidr.yml")
|
||||
test.AssertNotError(t, err, "valid single override limit with Id of regId:domainOrCIDR")
|
||||
expectKey = joinWithColon(CertificatesPerDomain.EnumString(), "example.com")
|
||||
test.AssertEquals(t, l[expectKey].burst, int64(40))
|
||||
test.AssertEquals(t, l[expectKey].count, int64(40))
|
||||
test.AssertEquals(t, l[expectKey].period.Duration, time.Second)
|
||||
test.AssertEquals(t, l[expectKey].Burst, int64(40))
|
||||
test.AssertEquals(t, l[expectKey].Count, int64(40))
|
||||
test.AssertEquals(t, l[expectKey].Period.Duration, time.Second)
|
||||
|
||||
// Load multiple valid override limits with 'regId' Ids.
|
||||
l, err = loadAndParseOverrideLimits("testdata/working_overrides.yml")
|
||||
test.AssertNotError(t, err, "multiple valid override limits")
|
||||
expectKey1 := joinWithColon(NewRegistrationsPerIPAddress.EnumString(), "64.112.117.1")
|
||||
test.AssertEquals(t, l[expectKey1].burst, int64(40))
|
||||
test.AssertEquals(t, l[expectKey1].count, int64(40))
|
||||
test.AssertEquals(t, l[expectKey1].period.Duration, time.Second)
|
||||
test.AssertEquals(t, l[expectKey1].Burst, int64(40))
|
||||
test.AssertEquals(t, l[expectKey1].Count, int64(40))
|
||||
test.AssertEquals(t, l[expectKey1].Period.Duration, time.Second)
|
||||
expectKey2 := joinWithColon(NewRegistrationsPerIPv6Range.EnumString(), "2602:80a:6000::/48")
|
||||
test.AssertEquals(t, l[expectKey2].burst, int64(50))
|
||||
test.AssertEquals(t, l[expectKey2].count, int64(50))
|
||||
test.AssertEquals(t, l[expectKey2].period.Duration, time.Second*2)
|
||||
test.AssertEquals(t, l[expectKey2].Burst, int64(50))
|
||||
test.AssertEquals(t, l[expectKey2].Count, int64(50))
|
||||
test.AssertEquals(t, l[expectKey2].Period.Duration, time.Second*2)
|
||||
|
||||
// Load multiple valid override limits with 'fqdnSet' Ids, as follows:
|
||||
// - CertificatesPerFQDNSet:example.com
|
||||
|
@ -128,18 +192,18 @@ func TestLoadAndParseOverrideLimits(t *testing.T) {
|
|||
|
||||
l, err = loadAndParseOverrideLimits("testdata/working_overrides_regid_fqdnset.yml")
|
||||
test.AssertNotError(t, err, "multiple valid override limits with 'fqdnSet' Ids")
|
||||
test.AssertEquals(t, l[entryKey1].burst, int64(40))
|
||||
test.AssertEquals(t, l[entryKey1].count, int64(40))
|
||||
test.AssertEquals(t, l[entryKey1].period.Duration, time.Second)
|
||||
test.AssertEquals(t, l[entryKey2].burst, int64(50))
|
||||
test.AssertEquals(t, l[entryKey2].count, int64(50))
|
||||
test.AssertEquals(t, l[entryKey2].period.Duration, time.Second*2)
|
||||
test.AssertEquals(t, l[entryKey3].burst, int64(60))
|
||||
test.AssertEquals(t, l[entryKey3].count, int64(60))
|
||||
test.AssertEquals(t, l[entryKey3].period.Duration, time.Second*3)
|
||||
test.AssertEquals(t, l[entryKey4].burst, int64(60))
|
||||
test.AssertEquals(t, l[entryKey4].count, int64(60))
|
||||
test.AssertEquals(t, l[entryKey4].period.Duration, time.Second*4)
|
||||
test.AssertEquals(t, l[entryKey1].Burst, int64(40))
|
||||
test.AssertEquals(t, l[entryKey1].Count, int64(40))
|
||||
test.AssertEquals(t, l[entryKey1].Period.Duration, time.Second)
|
||||
test.AssertEquals(t, l[entryKey2].Burst, int64(50))
|
||||
test.AssertEquals(t, l[entryKey2].Count, int64(50))
|
||||
test.AssertEquals(t, l[entryKey2].Period.Duration, time.Second*2)
|
||||
test.AssertEquals(t, l[entryKey3].Burst, int64(60))
|
||||
test.AssertEquals(t, l[entryKey3].Count, int64(60))
|
||||
test.AssertEquals(t, l[entryKey3].Period.Duration, time.Second*3)
|
||||
test.AssertEquals(t, l[entryKey4].Burst, int64(60))
|
||||
test.AssertEquals(t, l[entryKey4].Count, int64(60))
|
||||
test.AssertEquals(t, l[entryKey4].Period.Duration, time.Second*4)
|
||||
|
||||
// Path is empty string.
|
||||
_, err = loadAndParseOverrideLimits("")
|
||||
|
@ -186,19 +250,19 @@ func TestLoadAndParseDefaultLimits(t *testing.T) {
|
|||
// Load a single valid default limit.
|
||||
l, err := loadAndParseDefaultLimits("testdata/working_default.yml")
|
||||
test.AssertNotError(t, err, "valid single default limit")
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].burst, int64(20))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].count, int64(20))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].period.Duration, time.Second)
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Burst, int64(20))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Count, int64(20))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Period.Duration, time.Second)
|
||||
|
||||
// Load multiple valid default limits.
|
||||
l, err = loadAndParseDefaultLimits("testdata/working_defaults.yml")
|
||||
test.AssertNotError(t, err, "multiple valid default limits")
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].burst, int64(20))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].count, int64(20))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].period.Duration, time.Second)
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].burst, int64(30))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].count, int64(30))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].period.Duration, time.Second*2)
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Burst, int64(20))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Count, int64(20))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPAddress.EnumString()].Period.Duration, time.Second)
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].Burst, int64(30))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].Count, int64(30))
|
||||
test.AssertEquals(t, l[NewRegistrationsPerIPv6Range.EnumString()].Period.Duration, time.Second*2)
|
||||
|
||||
// Path is empty string.
|
||||
_, err = loadAndParseDefaultLimits("")
|
||||
|
@ -230,3 +294,146 @@ func TestLoadAndParseDefaultLimits(t *testing.T) {
|
|||
test.AssertError(t, err, "multiple default limits, one is bad")
|
||||
test.Assert(t, !os.IsNotExist(err), "test file should exist")
|
||||
}
|
||||
|
||||
func TestLoadAndDumpOverrides(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
input := `
|
||||
- CertificatesPerDomain:
|
||||
burst: 5000
|
||||
count: 5000
|
||||
period: 168h0m0s
|
||||
ids:
|
||||
- id: example.com
|
||||
comment: IN-10057
|
||||
- id: example.net
|
||||
comment: IN-10057
|
||||
- CertificatesPerDomain:
|
||||
burst: 300
|
||||
count: 300
|
||||
period: 168h0m0s
|
||||
ids:
|
||||
- id: example.org
|
||||
comment: IN-10057
|
||||
- CertificatesPerDomainPerAccount:
|
||||
burst: 12000
|
||||
count: 12000
|
||||
period: 168h0m0s
|
||||
ids:
|
||||
- id: "123456789"
|
||||
comment: Affluent (IN-8322)
|
||||
- CertificatesPerDomainPerAccount:
|
||||
burst: 6000
|
||||
count: 6000
|
||||
period: 168h0m0s
|
||||
ids:
|
||||
- id: "543219876"
|
||||
comment: Affluent (IN-8322)
|
||||
- id: "987654321"
|
||||
comment: Affluent (IN-8322)
|
||||
- CertificatesPerFQDNSet:
|
||||
burst: 50
|
||||
count: 50
|
||||
period: 168h0m0s
|
||||
ids:
|
||||
- id: example.co.uk,example.cn
|
||||
comment: IN-6843
|
||||
- CertificatesPerFQDNSet:
|
||||
burst: 24
|
||||
count: 24
|
||||
period: 168h0m0s
|
||||
ids:
|
||||
- id: example.org,example.com,example.net
|
||||
comment: IN-6006
|
||||
- FailedAuthorizationsPerDomainPerAccount:
|
||||
burst: 250
|
||||
count: 250
|
||||
period: 1h0m0s
|
||||
ids:
|
||||
- id: "123456789"
|
||||
comment: Digital Lake (IN-6736)
|
||||
- FailedAuthorizationsPerDomainPerAccount:
|
||||
burst: 50
|
||||
count: 50
|
||||
period: 1h0m0s
|
||||
ids:
|
||||
- id: "987654321"
|
||||
comment: Digital Lake (IN-6856)
|
||||
- FailedAuthorizationsPerDomainPerAccount:
|
||||
burst: 10
|
||||
count: 10
|
||||
period: 1h0m0s
|
||||
ids:
|
||||
- id: "543219876"
|
||||
comment: Big Mart (IN-6949)
|
||||
- NewOrdersPerAccount:
|
||||
burst: 3000
|
||||
count: 3000
|
||||
period: 3h0m0s
|
||||
ids:
|
||||
- id: "123456789"
|
||||
comment: Galaxy Hoster (IN-8180)
|
||||
- NewOrdersPerAccount:
|
||||
burst: 1000
|
||||
count: 1000
|
||||
period: 3h0m0s
|
||||
ids:
|
||||
- id: "543219876"
|
||||
comment: Big Mart (IN-8180)
|
||||
- id: "987654321"
|
||||
comment: Buy More (IN-10057)
|
||||
- NewRegistrationsPerIPAddress:
|
||||
burst: 100000
|
||||
count: 100000
|
||||
period: 3h0m0s
|
||||
ids:
|
||||
- id: 2600:1f1c:5e0:e702:ca06:d2a3:c7ce:a02e
|
||||
comment: example.org IN-2395
|
||||
- id: 55.66.77.88
|
||||
comment: example.org IN-2395
|
||||
- NewRegistrationsPerIPAddress:
|
||||
burst: 200
|
||||
count: 200
|
||||
period: 3h0m0s
|
||||
ids:
|
||||
- id: 11.22.33.44
|
||||
comment: example.net (IN-1583)`
|
||||
|
||||
expectCSV := `
|
||||
name,id,count,burst,period,comment
|
||||
CertificatesPerDomain,example.com,5000,5000,168h0m0s,IN-10057
|
||||
CertificatesPerDomain,example.net,5000,5000,168h0m0s,IN-10057
|
||||
CertificatesPerDomain,example.org,300,300,168h0m0s,IN-10057
|
||||
CertificatesPerDomainPerAccount,123456789,12000,12000,168h0m0s,Affluent (IN-8322)
|
||||
CertificatesPerDomainPerAccount,543219876,6000,6000,168h0m0s,Affluent (IN-8322)
|
||||
CertificatesPerDomainPerAccount,987654321,6000,6000,168h0m0s,Affluent (IN-8322)
|
||||
CertificatesPerFQDNSet,7c956936126b492845ddb48f4d220034509e7c0ad54ed2c1ba2650406846d9c3,50,50,168h0m0s,IN-6843
|
||||
CertificatesPerFQDNSet,394e82811f52e2da38b970afdb21c9bc9af81060939c690183c00fce37408738,24,24,168h0m0s,IN-6006
|
||||
FailedAuthorizationsPerDomainPerAccount,123456789,250,250,1h0m0s,Digital Lake (IN-6736)
|
||||
FailedAuthorizationsPerDomainPerAccount,987654321,50,50,1h0m0s,Digital Lake (IN-6856)
|
||||
FailedAuthorizationsPerDomainPerAccount,543219876,10,10,1h0m0s,Big Mart (IN-6949)
|
||||
NewOrdersPerAccount,123456789,3000,3000,3h0m0s,Galaxy Hoster (IN-8180)
|
||||
NewOrdersPerAccount,543219876,1000,1000,3h0m0s,Big Mart (IN-8180)
|
||||
NewOrdersPerAccount,987654321,1000,1000,3h0m0s,Buy More (IN-10057)
|
||||
NewRegistrationsPerIPAddress,2600:1f1c:5e0:e702:ca06:d2a3:c7ce:a02e,100000,100000,3h0m0s,example.org IN-2395
|
||||
NewRegistrationsPerIPAddress,55.66.77.88,100000,100000,3h0m0s,example.org IN-2395
|
||||
NewRegistrationsPerIPAddress,11.22.33.44,200,200,3h0m0s,example.net (IN-1583)
|
||||
`
|
||||
tempDir := t.TempDir()
|
||||
tempFile := filepath.Join(tempDir, "overrides.yaml")
|
||||
|
||||
err := os.WriteFile(tempFile, []byte(input), 0644)
|
||||
test.AssertNotError(t, err, "writing temp overrides.yaml")
|
||||
|
||||
original, err := LoadOverridesByBucketKey(tempFile)
|
||||
test.AssertNotError(t, err, "loading overrides")
|
||||
test.Assert(t, len(original) > 0, "expected at least one override loaded")
|
||||
|
||||
dumpFile := filepath.Join(tempDir, "dumped.yaml")
|
||||
err = DumpOverrides(dumpFile, original)
|
||||
test.AssertNotError(t, err, "dumping overrides")
|
||||
|
||||
dumped, err := os.ReadFile(dumpFile)
|
||||
test.AssertNotError(t, err, "reading dumped overrides file")
|
||||
test.AssertEquals(t, strings.TrimLeft(string(dumped), "\n"), strings.TrimLeft(expectCSV, "\n"))
|
||||
}
|
||||
|
|
|
@ -104,13 +104,13 @@ func (d *Decision) Result(now time.Time) error {
|
|||
|
||||
// There is no case for FailedAuthorizationsForPausingPerDomainPerAccount
|
||||
// because the RA will pause clients who exceed that ratelimit.
|
||||
switch d.transaction.limit.name {
|
||||
switch d.transaction.limit.Name {
|
||||
case NewRegistrationsPerIPAddress:
|
||||
return berrors.RegistrationsPerIPAddressError(
|
||||
retryAfter,
|
||||
"too many new registrations (%d) from this IP address in the last %s, retry after %s",
|
||||
d.transaction.limit.burst,
|
||||
d.transaction.limit.period.Duration,
|
||||
d.transaction.limit.Burst,
|
||||
d.transaction.limit.Period.Duration,
|
||||
retryAfterTs,
|
||||
)
|
||||
|
||||
|
@ -118,16 +118,16 @@ func (d *Decision) Result(now time.Time) error {
|
|||
return berrors.RegistrationsPerIPv6RangeError(
|
||||
retryAfter,
|
||||
"too many new registrations (%d) from this /48 subnet of IPv6 addresses in the last %s, retry after %s",
|
||||
d.transaction.limit.burst,
|
||||
d.transaction.limit.period.Duration,
|
||||
d.transaction.limit.Burst,
|
||||
d.transaction.limit.Period.Duration,
|
||||
retryAfterTs,
|
||||
)
|
||||
case NewOrdersPerAccount:
|
||||
return berrors.NewOrdersPerAccountError(
|
||||
retryAfter,
|
||||
"too many new orders (%d) from this account in the last %s, retry after %s",
|
||||
d.transaction.limit.burst,
|
||||
d.transaction.limit.period.Duration,
|
||||
d.transaction.limit.Burst,
|
||||
d.transaction.limit.Period.Duration,
|
||||
retryAfterTs,
|
||||
)
|
||||
|
||||
|
@ -141,9 +141,9 @@ func (d *Decision) Result(now time.Time) error {
|
|||
return berrors.FailedAuthorizationsPerDomainPerAccountError(
|
||||
retryAfter,
|
||||
"too many failed authorizations (%d) for %q in the last %s, retry after %s",
|
||||
d.transaction.limit.burst,
|
||||
d.transaction.limit.Burst,
|
||||
identValue,
|
||||
d.transaction.limit.period.Duration,
|
||||
d.transaction.limit.Period.Duration,
|
||||
retryAfterTs,
|
||||
)
|
||||
|
||||
|
@ -157,9 +157,9 @@ func (d *Decision) Result(now time.Time) error {
|
|||
return berrors.CertificatesPerDomainError(
|
||||
retryAfter,
|
||||
"too many certificates (%d) already issued for %q in the last %s, retry after %s",
|
||||
d.transaction.limit.burst,
|
||||
d.transaction.limit.Burst,
|
||||
domainOrCIDR,
|
||||
d.transaction.limit.period.Duration,
|
||||
d.transaction.limit.Period.Duration,
|
||||
retryAfterTs,
|
||||
)
|
||||
|
||||
|
@ -167,8 +167,8 @@ func (d *Decision) Result(now time.Time) error {
|
|||
return berrors.CertificatesPerFQDNSetError(
|
||||
retryAfter,
|
||||
"too many certificates (%d) already issued for this exact set of identifiers in the last %s, retry after %s",
|
||||
d.transaction.limit.burst,
|
||||
d.transaction.limit.period.Duration,
|
||||
d.transaction.limit.Burst,
|
||||
d.transaction.limit.Period.Duration,
|
||||
retryAfterTs,
|
||||
)
|
||||
|
||||
|
@ -346,7 +346,7 @@ func (l *Limiter) BatchSpend(ctx context.Context, txns []Transaction) (*Decision
|
|||
totalLatency := l.clk.Since(start)
|
||||
perTxnLatency := totalLatency / time.Duration(len(txnOutcomes))
|
||||
for txn, outcome := range txnOutcomes {
|
||||
l.spendLatency.WithLabelValues(txn.limit.name.String(), outcome).Observe(perTxnLatency.Seconds())
|
||||
l.spendLatency.WithLabelValues(txn.limit.Name.String(), outcome).Observe(perTxnLatency.Seconds())
|
||||
}
|
||||
return batchDecision, nil
|
||||
}
|
||||
|
|
|
@ -464,10 +464,10 @@ func TestRateLimitError(t *testing.T) {
|
|||
allowed: false,
|
||||
retryIn: 5 * time.Second,
|
||||
transaction: Transaction{
|
||||
limit: &limit{
|
||||
name: NewRegistrationsPerIPAddress,
|
||||
burst: 10,
|
||||
period: config.Duration{Duration: time.Hour},
|
||||
limit: &Limit{
|
||||
Name: NewRegistrationsPerIPAddress,
|
||||
Burst: 10,
|
||||
Period: config.Duration{Duration: time.Hour},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -480,10 +480,10 @@ func TestRateLimitError(t *testing.T) {
|
|||
allowed: false,
|
||||
retryIn: 10 * time.Second,
|
||||
transaction: Transaction{
|
||||
limit: &limit{
|
||||
name: NewRegistrationsPerIPv6Range,
|
||||
burst: 5,
|
||||
period: config.Duration{Duration: time.Hour},
|
||||
limit: &Limit{
|
||||
Name: NewRegistrationsPerIPv6Range,
|
||||
Burst: 5,
|
||||
Period: config.Duration{Duration: time.Hour},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -496,10 +496,10 @@ func TestRateLimitError(t *testing.T) {
|
|||
allowed: false,
|
||||
retryIn: 10 * time.Second,
|
||||
transaction: Transaction{
|
||||
limit: &limit{
|
||||
name: NewOrdersPerAccount,
|
||||
burst: 2,
|
||||
period: config.Duration{Duration: time.Hour},
|
||||
limit: &Limit{
|
||||
Name: NewOrdersPerAccount,
|
||||
Burst: 2,
|
||||
Period: config.Duration{Duration: time.Hour},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -512,10 +512,10 @@ func TestRateLimitError(t *testing.T) {
|
|||
allowed: false,
|
||||
retryIn: 15 * time.Second,
|
||||
transaction: Transaction{
|
||||
limit: &limit{
|
||||
name: FailedAuthorizationsPerDomainPerAccount,
|
||||
burst: 7,
|
||||
period: config.Duration{Duration: time.Hour},
|
||||
limit: &Limit{
|
||||
Name: FailedAuthorizationsPerDomainPerAccount,
|
||||
Burst: 7,
|
||||
Period: config.Duration{Duration: time.Hour},
|
||||
},
|
||||
bucketKey: "4:12345:example.com",
|
||||
},
|
||||
|
@ -529,10 +529,10 @@ func TestRateLimitError(t *testing.T) {
|
|||
allowed: false,
|
||||
retryIn: 20 * time.Second,
|
||||
transaction: Transaction{
|
||||
limit: &limit{
|
||||
name: CertificatesPerDomain,
|
||||
burst: 3,
|
||||
period: config.Duration{Duration: time.Hour},
|
||||
limit: &Limit{
|
||||
Name: CertificatesPerDomain,
|
||||
Burst: 3,
|
||||
Period: config.Duration{Duration: time.Hour},
|
||||
},
|
||||
bucketKey: "5:example.org",
|
||||
},
|
||||
|
@ -546,10 +546,10 @@ func TestRateLimitError(t *testing.T) {
|
|||
allowed: false,
|
||||
retryIn: 20 * time.Second,
|
||||
transaction: Transaction{
|
||||
limit: &limit{
|
||||
name: CertificatesPerDomainPerAccount,
|
||||
burst: 3,
|
||||
period: config.Duration{Duration: time.Hour},
|
||||
limit: &Limit{
|
||||
Name: CertificatesPerDomainPerAccount,
|
||||
Burst: 3,
|
||||
Period: config.Duration{Duration: time.Hour},
|
||||
},
|
||||
bucketKey: "6:12345678:example.net",
|
||||
},
|
||||
|
@ -563,8 +563,8 @@ func TestRateLimitError(t *testing.T) {
|
|||
allowed: false,
|
||||
retryIn: 30 * time.Second,
|
||||
transaction: Transaction{
|
||||
limit: &limit{
|
||||
name: 9999999,
|
||||
limit: &Limit{
|
||||
Name: 9999999,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/letsencrypt/boulder/iana"
|
||||
"github.com/letsencrypt/boulder/identifier"
|
||||
"github.com/letsencrypt/boulder/policy"
|
||||
)
|
||||
|
||||
|
@ -17,8 +18,9 @@ import (
|
|||
// IMPORTANT: If you add or remove a limit Name, you MUST update:
|
||||
// - the string representation of the Name in nameToString,
|
||||
// - the validators for that name in validateIdForName(),
|
||||
// - the transaction constructors for that name in bucket.go, and
|
||||
// - the Subscriber facing error message in ErrForDecision().
|
||||
// - the transaction constructors for that name in bucket.go
|
||||
// - the Subscriber facing error message in ErrForDecision(), and
|
||||
// - the case in BuildBucketKey() for that name.
|
||||
type Name int
|
||||
|
||||
const (
|
||||
|
@ -206,7 +208,7 @@ func validateRegIdIdentValue(id string) error {
|
|||
// validateDomainOrCIDR validates that the provided string is either a domain
|
||||
// name or an IP address. IPv6 addresses must be the lowest address in their
|
||||
// /64, i.e. their last 64 bits must be zero.
|
||||
func validateDomainOrCIDR(id string) error {
|
||||
func validateDomainOrCIDR(limit Name, id string) error {
|
||||
domainErr := policy.ValidDomain(id)
|
||||
if domainErr == nil {
|
||||
// This is a valid domain.
|
||||
|
@ -222,14 +224,13 @@ func validateDomainOrCIDR(id string) error {
|
|||
return fmt.Errorf("invalid IP address %q, must be in canonical form (%q)", id, ip.String())
|
||||
}
|
||||
|
||||
prefix, prefixErr := coveringPrefix(ip)
|
||||
prefix, prefixErr := coveringIPPrefix(limit, ip)
|
||||
if prefixErr != nil {
|
||||
return fmt.Errorf("invalid IP address %q, couldn't determine prefix: %w", id, prefixErr)
|
||||
}
|
||||
if prefix.Addr() != ip {
|
||||
return fmt.Errorf("invalid IP address %q, must be the lowest address in its prefix (%q)", id, prefix.Addr().String())
|
||||
}
|
||||
|
||||
return iana.IsReservedPrefix(prefix)
|
||||
}
|
||||
|
||||
|
@ -237,7 +238,7 @@ func validateDomainOrCIDR(id string) error {
|
|||
// 'regId:domainOrCIDR', where domainOrCIDR is either a domain name or an IP
|
||||
// address. IPv6 addresses must be the lowest address in their /64, i.e. their
|
||||
// last 64 bits must be zero.
|
||||
func validateRegIdDomainOrCIDR(id string) error {
|
||||
func validateRegIdDomainOrCIDR(limit Name, id string) error {
|
||||
regIdDomainOrCIDR := strings.Split(id, ":")
|
||||
if len(regIdDomainOrCIDR) != 2 {
|
||||
return fmt.Errorf(
|
||||
|
@ -248,7 +249,7 @@ func validateRegIdDomainOrCIDR(id string) error {
|
|||
return fmt.Errorf(
|
||||
"invalid regId, %q must be formatted 'regId:domainOrCIDR'", id)
|
||||
}
|
||||
err = validateDomainOrCIDR(regIdDomainOrCIDR[1])
|
||||
err = validateDomainOrCIDR(limit, regIdDomainOrCIDR[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid domainOrCIDR, %q must be formatted 'regId:domainOrCIDR': %w", id, err)
|
||||
}
|
||||
|
@ -301,7 +302,7 @@ func validateIdForName(name Name, id string) error {
|
|||
case CertificatesPerDomainPerAccount:
|
||||
if strings.Contains(id, ":") {
|
||||
// 'enum:regId:domainOrCIDR' for transaction
|
||||
return validateRegIdDomainOrCIDR(id)
|
||||
return validateRegIdDomainOrCIDR(name, id)
|
||||
} else {
|
||||
// 'enum:regId' for overrides
|
||||
return validateRegId(id)
|
||||
|
@ -309,7 +310,7 @@ func validateIdForName(name Name, id string) error {
|
|||
|
||||
case CertificatesPerDomain:
|
||||
// 'enum:domainOrCIDR'
|
||||
return validateDomainOrCIDR(id)
|
||||
return validateDomainOrCIDR(name, id)
|
||||
|
||||
case CertificatesPerFQDNSet:
|
||||
// 'enum:fqdnSet'
|
||||
|
@ -333,8 +334,8 @@ func validateIdForName(name Name, id string) error {
|
|||
}
|
||||
}
|
||||
|
||||
// stringToName is a map of string names to Name values.
|
||||
var stringToName = func() map[string]Name {
|
||||
// StringToName is a map of string names to Name values.
|
||||
var StringToName = func() map[string]Name {
|
||||
m := make(map[string]Name, len(nameToString))
|
||||
for k, v := range nameToString {
|
||||
m[v] = k
|
||||
|
@ -342,11 +343,94 @@ var stringToName = func() map[string]Name {
|
|||
return m
|
||||
}()
|
||||
|
||||
// limitNames is a slice of all rate limit names.
|
||||
var limitNames = func() []string {
|
||||
// LimitNames is a slice of all rate limit names.
|
||||
var LimitNames = func() []string {
|
||||
names := make([]string, 0, len(nameToString))
|
||||
for _, v := range nameToString {
|
||||
names = append(names, v)
|
||||
}
|
||||
return names
|
||||
}()
|
||||
|
||||
// BuildBucketKey builds a bucketKey for the given rate limit name from the
|
||||
// provided components. It returns an error if the name is not valid or if the
|
||||
// components are not valid for the given name.
|
||||
func BuildBucketKey(name Name, regId int64, singleIdent identifier.ACMEIdentifier, setOfIdents identifier.ACMEIdentifiers, subscriberIP netip.Addr) (string, error) {
|
||||
makeMissingErr := func(field string) error {
|
||||
return fmt.Errorf("%s is required for limit %s (enum: %s)", field, name, name.EnumString())
|
||||
}
|
||||
|
||||
switch name {
|
||||
case NewRegistrationsPerIPAddress:
|
||||
if !subscriberIP.IsValid() {
|
||||
return "", makeMissingErr("subscriberIP")
|
||||
}
|
||||
return newIPAddressBucketKey(name, subscriberIP), nil
|
||||
|
||||
case NewRegistrationsPerIPv6Range:
|
||||
if !subscriberIP.IsValid() {
|
||||
return "", makeMissingErr("subscriberIP")
|
||||
}
|
||||
prefix, err := coveringIPPrefix(name, subscriberIP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return newIPv6RangeCIDRBucketKey(name, prefix), nil
|
||||
|
||||
case NewOrdersPerAccount:
|
||||
if regId == 0 {
|
||||
return "", makeMissingErr("regId")
|
||||
}
|
||||
return newRegIdBucketKey(name, regId), nil
|
||||
|
||||
case CertificatesPerDomain:
|
||||
if singleIdent.Value == "" {
|
||||
return "", makeMissingErr("singleIdent")
|
||||
}
|
||||
coveringIdent, err := coveringIdentifier(name, singleIdent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return newDomainOrCIDRBucketKey(name, coveringIdent), nil
|
||||
|
||||
case CertificatesPerDomainPerAccount:
|
||||
if singleIdent.Value != "" {
|
||||
if regId == 0 {
|
||||
return "", makeMissingErr("regId")
|
||||
}
|
||||
// Default: use 'enum:regId:identValue' bucket key format.
|
||||
coveringIdent, err := coveringIdentifier(name, singleIdent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return NewRegIdIdentValueBucketKey(name, regId, coveringIdent), nil
|
||||
}
|
||||
if regId == 0 {
|
||||
return "", makeMissingErr("regId")
|
||||
}
|
||||
// Override: use 'enum:regId' bucket key format.
|
||||
return newRegIdBucketKey(name, regId), nil
|
||||
|
||||
case CertificatesPerFQDNSet:
|
||||
if len(setOfIdents) == 0 {
|
||||
return "", makeMissingErr("setOfIdents")
|
||||
}
|
||||
return newFQDNSetBucketKey(name, setOfIdents), nil
|
||||
|
||||
case FailedAuthorizationsPerDomainPerAccount, FailedAuthorizationsForPausingPerDomainPerAccount:
|
||||
if singleIdent.Value != "" {
|
||||
if regId == 0 {
|
||||
return "", makeMissingErr("regId")
|
||||
}
|
||||
// Default: use 'enum:regId:identValue' bucket key format.
|
||||
return NewRegIdIdentValueBucketKey(name, regId, singleIdent.Value), nil
|
||||
}
|
||||
if regId == 0 {
|
||||
return "", makeMissingErr("regId")
|
||||
}
|
||||
// Override: use 'enum:regId' bucket key format.
|
||||
return newRegIdBucketKey(name, regId), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unknown limit enum %s", name.EnumString())
|
||||
}
|
||||
|
|
|
@ -2,8 +2,11 @@ package ratelimits
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/letsencrypt/boulder/identifier"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
|
@ -293,3 +296,202 @@ func TestValidateIdForName(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildBucketKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name Name
|
||||
desc string
|
||||
regId int64
|
||||
singleIdent identifier.ACMEIdentifier
|
||||
setOfIdents identifier.ACMEIdentifiers
|
||||
subscriberIP netip.Addr
|
||||
expectErrContains string
|
||||
outputTest func(t *testing.T, key string)
|
||||
}{
|
||||
// NewRegistrationsPerIPAddress
|
||||
{
|
||||
name: NewRegistrationsPerIPAddress,
|
||||
desc: "valid subscriber IPv4 address",
|
||||
subscriberIP: netip.MustParseAddr("1.2.3.4"),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:1.2.3.4", NewRegistrationsPerIPAddress), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: NewRegistrationsPerIPAddress,
|
||||
desc: "valid subscriber IPv6 address",
|
||||
subscriberIP: netip.MustParseAddr("2001:db8::1"),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:2001:db8::1", NewRegistrationsPerIPAddress), key)
|
||||
},
|
||||
},
|
||||
// NewRegistrationsPerIPv6Range
|
||||
{
|
||||
name: NewRegistrationsPerIPv6Range,
|
||||
desc: "valid subscriber IPv6 address",
|
||||
subscriberIP: netip.MustParseAddr("2001:db8:abcd:12::1"),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:2001:db8:abcd::/48", NewRegistrationsPerIPv6Range), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: NewRegistrationsPerIPv6Range,
|
||||
desc: "subscriber IPv4 given for subscriber IPv6 range limit",
|
||||
subscriberIP: netip.MustParseAddr("1.2.3.4"),
|
||||
expectErrContains: "requires an IPv6 address",
|
||||
},
|
||||
|
||||
// NewOrdersPerAccount
|
||||
{
|
||||
name: NewOrdersPerAccount,
|
||||
desc: "valid registration ID",
|
||||
regId: 1337,
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:1337", NewOrdersPerAccount), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: NewOrdersPerAccount,
|
||||
desc: "registration ID missing",
|
||||
expectErrContains: "regId is required",
|
||||
},
|
||||
|
||||
// CertificatesPerDomain
|
||||
{
|
||||
name: CertificatesPerDomain,
|
||||
desc: "DNS identifier to eTLD+1",
|
||||
singleIdent: identifier.NewDNS("www.example.com"),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:example.com", CertificatesPerDomain), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: CertificatesPerDomain,
|
||||
desc: "valid IPv4 address used as identifier",
|
||||
singleIdent: identifier.NewIP(netip.MustParseAddr("5.6.7.8")),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:5.6.7.8/32", CertificatesPerDomain), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: CertificatesPerDomain,
|
||||
desc: "valid IPv6 address used as identifier",
|
||||
singleIdent: identifier.NewIP(netip.MustParseAddr("2001:db8::1")),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:2001:db8::/64", CertificatesPerDomain), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: CertificatesPerDomain,
|
||||
desc: "identifier missing",
|
||||
expectErrContains: "singleIdent is required",
|
||||
},
|
||||
|
||||
// CertificatesPerFQDNSet
|
||||
{
|
||||
name: CertificatesPerFQDNSet,
|
||||
desc: "multiple valid DNS identifiers",
|
||||
setOfIdents: identifier.NewDNSSlice([]string{"example.com", "example.org"}),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
if !strings.HasPrefix(key, fmt.Sprintf("%d:", CertificatesPerFQDNSet)) {
|
||||
t.Errorf("expected key to start with %d: got %s", CertificatesPerFQDNSet, key)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: CertificatesPerFQDNSet,
|
||||
desc: "multiple valid DNS and IP identifiers",
|
||||
setOfIdents: identifier.ACMEIdentifiers{identifier.NewDNS("example.net"), identifier.NewIP(netip.MustParseAddr("5.6.7.8")), identifier.NewIP(netip.MustParseAddr("2001:db8::1"))},
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
if !strings.HasPrefix(key, fmt.Sprintf("%d:", CertificatesPerFQDNSet)) {
|
||||
t.Errorf("expected key to start with %d: got %s", CertificatesPerFQDNSet, key)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: CertificatesPerFQDNSet,
|
||||
desc: "identifiers missing",
|
||||
expectErrContains: "setOfIdents is required",
|
||||
},
|
||||
|
||||
// CertificatesPerDomainPerAccount
|
||||
{
|
||||
name: CertificatesPerDomainPerAccount,
|
||||
desc: "only registration ID",
|
||||
regId: 1337,
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:1337", CertificatesPerDomainPerAccount), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: CertificatesPerDomainPerAccount,
|
||||
desc: "registration ID and single DNS identifier provided",
|
||||
regId: 1337,
|
||||
singleIdent: identifier.NewDNS("example.com"),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:1337:example.com", CertificatesPerDomainPerAccount), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: CertificatesPerDomainPerAccount,
|
||||
desc: "single DNS identifier provided without registration ID",
|
||||
singleIdent: identifier.NewDNS("example.com"),
|
||||
expectErrContains: "regId is required",
|
||||
},
|
||||
|
||||
// FailedAuthorizationsPerDomainPerAccount
|
||||
{
|
||||
name: FailedAuthorizationsPerDomainPerAccount,
|
||||
desc: "registration ID and single DNS identifier",
|
||||
regId: 1337,
|
||||
singleIdent: identifier.NewDNS("example.com"),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:1337:example.com", FailedAuthorizationsPerDomainPerAccount), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: FailedAuthorizationsPerDomainPerAccount,
|
||||
desc: "only registration ID",
|
||||
regId: 1337,
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:1337", FailedAuthorizationsPerDomainPerAccount), key)
|
||||
},
|
||||
},
|
||||
|
||||
// FailedAuthorizationsForPausingPerDomainPerAccount
|
||||
{
|
||||
name: FailedAuthorizationsForPausingPerDomainPerAccount,
|
||||
desc: "registration ID and single DNS identifier",
|
||||
regId: 1337,
|
||||
singleIdent: identifier.NewDNS("example.com"),
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:1337:example.com", FailedAuthorizationsForPausingPerDomainPerAccount), key)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: FailedAuthorizationsForPausingPerDomainPerAccount,
|
||||
desc: "only registration ID",
|
||||
regId: 1337,
|
||||
outputTest: func(t *testing.T, key string) {
|
||||
test.AssertEquals(t, fmt.Sprintf("%d:1337", FailedAuthorizationsForPausingPerDomainPerAccount), key)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%s/%s", tc.name, tc.desc), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
key, err := BuildBucketKey(tc.name, tc.regId, tc.singleIdent, tc.setOfIdents, tc.subscriberIP)
|
||||
if tc.expectErrContains != "" {
|
||||
test.AssertError(t, err, "expected error")
|
||||
test.AssertContains(t, err.Error(), tc.expectErrContains)
|
||||
return
|
||||
}
|
||||
test.AssertNotError(t, err, "unexpected error")
|
||||
tc.outputTest(t, key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
count: 40
|
||||
period: 1s
|
||||
ids:
|
||||
- id: 10.0.0.2
|
||||
- id: 55.66.77.88
|
||||
comment: Foo
|
||||
|
|
|
@ -16,32 +16,25 @@ var ErrInvalidCost = fmt.Errorf("invalid cost, must be >= 0")
|
|||
// ErrInvalidCostOverLimit indicates that the cost specified was > limit.Burst.
|
||||
var ErrInvalidCostOverLimit = fmt.Errorf("invalid cost, must be <= limit.Burst")
|
||||
|
||||
// newIPAddressBucketKey validates and returns a bucketKey for limits that use
|
||||
// newIPAddressBucketKey returns a bucketKey for limits that use
|
||||
// the 'enum:ipAddress' bucket key format.
|
||||
func newIPAddressBucketKey(name Name, ip netip.Addr) string { //nolint:unparam // Only one named rate limit uses this helper
|
||||
func newIPAddressBucketKey(name Name, ip netip.Addr) string {
|
||||
return joinWithColon(name.EnumString(), ip.String())
|
||||
}
|
||||
|
||||
// newIPv6RangeCIDRBucketKey validates and returns a bucketKey for limits that
|
||||
// newIPv6RangeCIDRBucketKey returns a bucketKey for limits that
|
||||
// use the 'enum:ipv6RangeCIDR' bucket key format.
|
||||
func newIPv6RangeCIDRBucketKey(name Name, ip netip.Addr) (string, error) {
|
||||
if ip.Is4() {
|
||||
return "", fmt.Errorf("invalid IPv6 address, %q must be an IPv6 address", ip.String())
|
||||
}
|
||||
prefix, err := ip.Prefix(48)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid IPv6 address, can't calculate prefix of %q: %s", ip.String(), err)
|
||||
}
|
||||
return joinWithColon(name.EnumString(), prefix.String()), nil
|
||||
func newIPv6RangeCIDRBucketKey(name Name, prefix netip.Prefix) string {
|
||||
return joinWithColon(name.EnumString(), prefix.String())
|
||||
}
|
||||
|
||||
// newRegIdBucketKey validates and returns a bucketKey for limits that use the
|
||||
// newRegIdBucketKey returns a bucketKey for limits that use the
|
||||
// 'enum:regId' bucket key format.
|
||||
func newRegIdBucketKey(name Name, regId int64) string {
|
||||
return joinWithColon(name.EnumString(), strconv.FormatInt(regId, 10))
|
||||
}
|
||||
|
||||
// newDomainOrCIDRBucketKey validates and returns a bucketKey for limits that use
|
||||
// newDomainOrCIDRBucketKey returns a bucketKey for limits that use
|
||||
// the 'enum:domainOrCIDR' bucket key formats.
|
||||
func newDomainOrCIDRBucketKey(name Name, domainOrCIDR string) string {
|
||||
return joinWithColon(name.EnumString(), domainOrCIDR)
|
||||
|
@ -56,7 +49,7 @@ func NewRegIdIdentValueBucketKey(name Name, regId int64, orderIdent string) stri
|
|||
|
||||
// newFQDNSetBucketKey validates and returns a bucketKey for limits that use the
|
||||
// 'enum:fqdnSet' bucket key format.
|
||||
func newFQDNSetBucketKey(name Name, orderIdents identifier.ACMEIdentifiers) string { //nolint: unparam // Only one named rate limit uses this helper
|
||||
func newFQDNSetBucketKey(name Name, orderIdents identifier.ACMEIdentifiers) string {
|
||||
return joinWithColon(name.EnumString(), fmt.Sprintf("%x", core.HashIdentifiers(orderIdents)))
|
||||
}
|
||||
|
||||
|
@ -80,7 +73,7 @@ func newFQDNSetBucketKey(name Name, orderIdents identifier.ACMEIdentifiers) stri
|
|||
// it would fail validateTransaction (for instance because cost and burst are zero).
|
||||
type Transaction struct {
|
||||
bucketKey string
|
||||
limit *limit
|
||||
limit *Limit
|
||||
cost int64
|
||||
check bool
|
||||
spend bool
|
||||
|
@ -102,7 +95,7 @@ func validateTransaction(txn Transaction) (Transaction, error) {
|
|||
if txn.cost < 0 {
|
||||
return Transaction{}, ErrInvalidCost
|
||||
}
|
||||
if txn.limit.burst == 0 {
|
||||
if txn.limit.Burst == 0 {
|
||||
// This should never happen. If the limit was loaded from a file,
|
||||
// Burst was validated then. If this is a zero-valued Transaction
|
||||
// (that is, an allow-only transaction), then validateTransaction
|
||||
|
@ -110,13 +103,13 @@ func validateTransaction(txn Transaction) (Transaction, error) {
|
|||
// valid.
|
||||
return Transaction{}, fmt.Errorf("invalid limit, burst must be > 0")
|
||||
}
|
||||
if txn.cost > txn.limit.burst {
|
||||
if txn.cost > txn.limit.Burst {
|
||||
return Transaction{}, ErrInvalidCostOverLimit
|
||||
}
|
||||
return txn, nil
|
||||
}
|
||||
|
||||
func newTransaction(limit *limit, bucketKey string, cost int64) (Transaction, error) {
|
||||
func newTransaction(limit *Limit, bucketKey string, cost int64) (Transaction, error) {
|
||||
return validateTransaction(Transaction{
|
||||
bucketKey: bucketKey,
|
||||
limit: limit,
|
||||
|
@ -126,7 +119,7 @@ func newTransaction(limit *limit, bucketKey string, cost int64) (Transaction, er
|
|||
})
|
||||
}
|
||||
|
||||
func newCheckOnlyTransaction(limit *limit, bucketKey string, cost int64) (Transaction, error) {
|
||||
func newCheckOnlyTransaction(limit *Limit, bucketKey string, cost int64) (Transaction, error) {
|
||||
return validateTransaction(Transaction{
|
||||
bucketKey: bucketKey,
|
||||
limit: limit,
|
||||
|
@ -135,7 +128,7 @@ func newCheckOnlyTransaction(limit *limit, bucketKey string, cost int64) (Transa
|
|||
})
|
||||
}
|
||||
|
||||
func newSpendOnlyTransaction(limit *limit, bucketKey string, cost int64) (Transaction, error) {
|
||||
func newSpendOnlyTransaction(limit *Limit, bucketKey string, cost int64) (Transaction, error) {
|
||||
return validateTransaction(Transaction{
|
||||
bucketKey: bucketKey,
|
||||
limit: limit,
|
||||
|
@ -197,10 +190,12 @@ func (builder *TransactionBuilder) registrationsPerIPAddressTransaction(ip netip
|
|||
// NewRegistrationsPerIPv6Range limit for the /48 IPv6 range which contains the
|
||||
// provided IPv6 address.
|
||||
func (builder *TransactionBuilder) registrationsPerIPv6RangeTransaction(ip netip.Addr) (Transaction, error) {
|
||||
bucketKey, err := newIPv6RangeCIDRBucketKey(NewRegistrationsPerIPv6Range, ip)
|
||||
prefix, err := coveringIPPrefix(NewRegistrationsPerIPv6Range, ip)
|
||||
if err != nil {
|
||||
return Transaction{}, err
|
||||
return Transaction{}, fmt.Errorf("computing covering prefix for %q: %w", ip, err)
|
||||
}
|
||||
bucketKey := newIPv6RangeCIDRBucketKey(NewRegistrationsPerIPv6Range, prefix)
|
||||
|
||||
limit, err := builder.getLimit(NewRegistrationsPerIPv6Range, bucketKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, errLimitDisabled) {
|
||||
|
|
|
@ -223,7 +223,7 @@ func TestNewTransactionBuilder(t *testing.T) {
|
|||
|
||||
newRegDefault, ok := tb.limitRegistry.defaults[NewRegistrationsPerIPAddress.EnumString()]
|
||||
test.Assert(t, ok, "NewRegistrationsPerIPAddress was not populated in registry")
|
||||
test.AssertEquals(t, newRegDefault.burst, expectedBurst)
|
||||
test.AssertEquals(t, newRegDefault.count, expectedCount)
|
||||
test.AssertEquals(t, newRegDefault.period, expectedPeriod)
|
||||
test.AssertEquals(t, newRegDefault.Burst, expectedBurst)
|
||||
test.AssertEquals(t, newRegDefault.Count, expectedCount)
|
||||
test.AssertEquals(t, newRegDefault.Period, expectedPeriod)
|
||||
}
|
||||
|
|
|
@ -16,57 +16,106 @@ func joinWithColon(args ...string) string {
|
|||
return strings.Join(args, ":")
|
||||
}
|
||||
|
||||
// coveringIdentifiers transforms a slice of ACMEIdentifiers into strings of
|
||||
// their "covering" identifiers, for the CertificatesPerDomain limit. It also
|
||||
// de-duplicates the output. For DNS identifiers, this is eTLD+1's; exact public
|
||||
// suffix matches are included. For IP address identifiers, this is the address
|
||||
// (/32) for IPv4, or the /64 prefix for IPv6, in CIDR notation.
|
||||
// coveringIdentifiers returns the set of "covering" identifiers used to enforce
|
||||
// the CertificatesPerDomain rate limit. For DNS names, this is the eTLD+1 as
|
||||
// determined by the Public Suffix List; exact public suffix matches are
|
||||
// preserved. For IP addresses, the covering prefix is /32 for IPv4 and /64 for
|
||||
// IPv6. This groups requests by registered domain or address block to match the
|
||||
// scope of the limit. The result is deduplicated and lowercased. If the
|
||||
// identifier type is unsupported, an error is returned.
|
||||
func coveringIdentifiers(idents identifier.ACMEIdentifiers) ([]string, error) {
|
||||
var covers []string
|
||||
for _, ident := range idents {
|
||||
switch ident.Type {
|
||||
case identifier.TypeDNS:
|
||||
domain, err := publicsuffix.Domain(ident.Value)
|
||||
if err != nil {
|
||||
if err.Error() == fmt.Sprintf("%s is a suffix", ident.Value) {
|
||||
// If the public suffix is the domain itself, that's fine.
|
||||
// Include the original name in the result.
|
||||
covers = append(covers, ident.Value)
|
||||
continue
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
covers = append(covers, domain)
|
||||
case identifier.TypeIP:
|
||||
ip, err := netip.ParseAddr(ident.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prefix, err := coveringPrefix(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
covers = append(covers, prefix.String())
|
||||
cover, err := coveringIdentifier(CertificatesPerDomain, ident)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
covers = append(covers, cover)
|
||||
}
|
||||
return core.UniqueLowerNames(covers), nil
|
||||
}
|
||||
|
||||
// coveringPrefix transforms a netip.Addr into its "covering" prefix, for the
|
||||
// CertificatesPerDomain limit. For IPv4, this is the IP address (/32). For
|
||||
// IPv6, this is the /64 that contains the address.
|
||||
func coveringPrefix(addr netip.Addr) (netip.Prefix, error) {
|
||||
var bits int
|
||||
if addr.Is4() {
|
||||
bits = 32
|
||||
} else {
|
||||
bits = 64
|
||||
// coveringIdentifier returns the "covering" identifier used to enforce the
|
||||
// CertificatesPerDomain, CertificatesPerDomainPerAccount, and
|
||||
// NewRegistrationsPerIPv6Range rate limits. For DNS names, this is the eTLD+1
|
||||
// as determined by the Public Suffix List; exact public suffix matches are
|
||||
// preserved. For IP addresses, the covering prefix depends on the limit:
|
||||
//
|
||||
// - CertificatesPerDomain and CertificatesPerDomainPerAccount:
|
||||
// - /32 for IPv4
|
||||
// - /64 for IPv6
|
||||
//
|
||||
// - NewRegistrationsPerIPv6Range:
|
||||
// - /48 for IPv6 only
|
||||
//
|
||||
// This groups requests by registered domain or address block to match the scope
|
||||
// of each limit. The result is deduplicated and lowercased. If the identifier
|
||||
// type or limit is unsupported, an error is returned.
|
||||
func coveringIdentifier(limit Name, ident identifier.ACMEIdentifier) (string, error) {
|
||||
switch ident.Type {
|
||||
case identifier.TypeDNS:
|
||||
domain, err := publicsuffix.Domain(ident.Value)
|
||||
if err != nil {
|
||||
if err.Error() == fmt.Sprintf("%s is a suffix", ident.Value) {
|
||||
// If the public suffix is the domain itself, that's fine.
|
||||
// Include the original name in the result.
|
||||
return ident.Value, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return domain, nil
|
||||
case identifier.TypeIP:
|
||||
ip, err := netip.ParseAddr(ident.Value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
prefix, err := coveringIPPrefix(limit, ip)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return prefix.String(), nil
|
||||
}
|
||||
prefix, err := addr.Prefix(bits)
|
||||
if err != nil {
|
||||
// This should be impossible because bits is hardcoded.
|
||||
return netip.Prefix{}, err
|
||||
}
|
||||
return prefix, nil
|
||||
return "", fmt.Errorf("unsupported identifier type: %s", ident.Type)
|
||||
}
|
||||
|
||||
// coveringIPPrefix returns the "covering" IP prefix used to enforce the
|
||||
// CertificatesPerDomain, CertificatesPerDomainPerAccount, and
|
||||
// NewRegistrationsPerIPv6Range rate limits. The prefix length depends on the
|
||||
// limit and IP version:
|
||||
//
|
||||
// - CertificatesPerDomain and CertificatesPerDomainPerAccount:
|
||||
// - /32 for IPv4
|
||||
// - /64 for IPv6
|
||||
//
|
||||
// - NewRegistrationsPerIPv6Range:
|
||||
// - /48 for IPv6 only
|
||||
//
|
||||
// This groups requests by address block to match the scope of each limit. If
|
||||
// the limit does not require a covering prefix, an error is returned.
|
||||
func coveringIPPrefix(limit Name, addr netip.Addr) (netip.Prefix, error) {
|
||||
switch limit {
|
||||
case CertificatesPerDomain, CertificatesPerDomainPerAccount:
|
||||
var bits int
|
||||
if addr.Is4() {
|
||||
bits = 32
|
||||
} else {
|
||||
bits = 64
|
||||
}
|
||||
prefix, err := addr.Prefix(bits)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, fmt.Errorf("building covering prefix for %s: %w", addr, err)
|
||||
}
|
||||
return prefix, nil
|
||||
|
||||
case NewRegistrationsPerIPv6Range:
|
||||
if !addr.Is6() {
|
||||
return netip.Prefix{}, fmt.Errorf("limit %s requires an IPv6 address, got %s", limit, addr)
|
||||
}
|
||||
prefix, err := addr.Prefix(48)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, fmt.Errorf("building covering prefix for %s: %w", addr, err)
|
||||
}
|
||||
return prefix, nil
|
||||
}
|
||||
return netip.Prefix{}, fmt.Errorf("limit %s does not require a covering prefix", limit)
|
||||
}
|
||||
|
|
|
@ -271,7 +271,7 @@ func initTables(dbMap *borp.DbMap) {
|
|||
dbMap.AddTableWithName(issuedNameModel{}, "issuedNames").SetKeys(true, "ID")
|
||||
dbMap.AddTableWithName(core.Certificate{}, "certificates").SetKeys(true, "ID")
|
||||
dbMap.AddTableWithName(certificateStatusModel{}, "certificateStatus").SetKeys(true, "ID")
|
||||
dbMap.AddTableWithName(core.FQDNSet{}, "fqdnSets").SetKeys(true, "ID")
|
||||
dbMap.AddTableWithName(fqdnSet{}, "fqdnSets").SetKeys(true, "ID")
|
||||
tableMap := dbMap.AddTableWithName(orderModel{}, "orders").SetKeys(true, "ID")
|
||||
if !features.Get().StoreARIReplacesInOrders {
|
||||
tableMap.ColMap("Replaces").SetTransient(true)
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
-- +migrate Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
ALTER TABLE `registrations` ALTER COLUMN `LockCol` SET DEFAULT 0;
|
||||
|
||||
-- +migrate Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
ALTER TABLE `registrations` ALTER COLUMN `LockCol` DROP DEFAULT;
|
|
@ -0,0 +1 @@
|
|||
../../db/boulder_sa/20250110000000_NullRegistrationsLockCol.sql
|
|
@ -0,0 +1,9 @@
|
|||
-- +migrate Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
ALTER TABLE `registrations` DROP COLUMN `LockCol`;
|
||||
|
||||
-- +migrate Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
ALTER TABLE `registrations` ADD COLUMN `LockCol` BIGINT(20) NOT NULL DEFAULT 0;
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
-- +migrate Up
|
||||
-- SQL in section 'Up' is executed when this migration is applied
|
||||
|
||||
ALTER TABLE `registrations` ALTER COLUMN `LockCol` SET DEFAULT 0;
|
||||
|
||||
-- +migrate Down
|
||||
-- SQL section 'Down' is executed when this migration is rolled back
|
||||
|
||||
ALTER TABLE `registrations` ALTER COLUMN `LockCol` DROP DEFAULT;
|
88
sa/model.go
88
sa/model.go
|
@ -12,7 +12,6 @@ import (
|
|||
"math"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -61,69 +60,7 @@ func badJSONError(msg string, jsonData []byte, err error) error {
|
|||
}
|
||||
}
|
||||
|
||||
const regFields = "id, jwk, jwk_sha256, agreement, createdAt, LockCol, status"
|
||||
|
||||
// ClearEmail removes the provided email address from one specified registration. If
|
||||
// there are multiple email addresses present, it does not modify other ones. If the email
|
||||
// address is not present, it does not modify the registration and will return a nil error.
|
||||
func ClearEmail(ctx context.Context, dbMap db.DatabaseMap, regID int64, email string) error {
|
||||
_, overallError := db.WithTransaction(ctx, dbMap, func(tx db.Executor) (interface{}, error) {
|
||||
curr, err := selectRegistration(ctx, tx, "id", regID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currPb, err := registrationModelToPb(curr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// newContacts will be a copy of all emails in currPb.Contact _except_ the one to be removed
|
||||
var newContacts []string
|
||||
for _, contact := range currPb.Contact {
|
||||
if contact != "mailto:"+email {
|
||||
newContacts = append(newContacts, contact)
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Equal(currPb.Contact, newContacts) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// We don't want to write literal JSON "null" strings into the database if the
|
||||
// list of contact addresses is empty. Replace any possibly-`nil` slice with
|
||||
// an empty JSON array. We don't need to check reg.ContactPresent, because
|
||||
// we're going to write the whole object to the database anyway.
|
||||
jsonContact := []byte("[]")
|
||||
if len(newContacts) != 0 {
|
||||
jsonContact, err = json.Marshal(newContacts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// UPDATE the row with a direct database query, in order to avoid LockCol issues.
|
||||
result, err := tx.ExecContext(ctx,
|
||||
"UPDATE registrations SET contact = ? WHERE id = ? LIMIT 1",
|
||||
jsonContact,
|
||||
regID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil || rowsAffected != 1 {
|
||||
return nil, berrors.InternalServerError("no registration updated with new contact field")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
})
|
||||
if overallError != nil {
|
||||
return overallError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
const regFields = "id, jwk, jwk_sha256, agreement, createdAt, status"
|
||||
|
||||
// selectRegistration selects all fields of one registration model
|
||||
func selectRegistration(ctx context.Context, s db.OneSelector, whereCol string, args ...interface{}) (*regModel, error) {
|
||||
|
@ -275,8 +212,7 @@ type regModel struct {
|
|||
KeySHA256 string `db:"jwk_sha256"`
|
||||
Agreement string `db:"agreement"`
|
||||
CreatedAt time.Time `db:"createdAt"`
|
||||
LockCol int64
|
||||
Status string `db:"status"`
|
||||
Status string `db:"status"`
|
||||
}
|
||||
|
||||
func registrationPbToModel(reg *corepb.Registration) (*regModel, error) {
|
||||
|
@ -480,15 +416,17 @@ func modelToOrder(om *orderModel) (*corepb.Order, error) {
|
|||
}
|
||||
|
||||
var challTypeToUint = map[string]uint8{
|
||||
"http-01": 0,
|
||||
"dns-01": 1,
|
||||
"tls-alpn-01": 2,
|
||||
"http-01": 0,
|
||||
"dns-01": 1,
|
||||
"tls-alpn-01": 2,
|
||||
"dns-account-01": 3,
|
||||
}
|
||||
|
||||
var uintToChallType = map[uint8]string{
|
||||
0: "http-01",
|
||||
1: "dns-01",
|
||||
2: "tls-alpn-01",
|
||||
3: "dns-account-01",
|
||||
}
|
||||
|
||||
var identifierTypeToUint = map[string]uint8{
|
||||
|
@ -963,6 +901,16 @@ type crlEntryModel struct {
|
|||
RevokedDate time.Time `db:"revokedDate"`
|
||||
}
|
||||
|
||||
// fqdnSet contains the SHA256 hash of the lowercased, comma joined dNSNames
|
||||
// contained in a certificate.
|
||||
type fqdnSet struct {
|
||||
ID int64
|
||||
SetHash []byte
|
||||
Serial string
|
||||
Issued time.Time
|
||||
Expires time.Time
|
||||
}
|
||||
|
||||
// orderFQDNSet contains the SHA256 hash of the lowercased, comma joined names
|
||||
// from a new-order request, along with the corresponding orderID, the
|
||||
// registration ID, and the order expiry. This is used to find
|
||||
|
@ -976,7 +924,7 @@ type orderFQDNSet struct {
|
|||
}
|
||||
|
||||
func addFQDNSet(ctx context.Context, db db.Inserter, idents identifier.ACMEIdentifiers, serial string, issued time.Time, expires time.Time) error {
|
||||
return db.Insert(ctx, &core.FQDNSet{
|
||||
return db.Insert(ctx, &fqdnSet{
|
||||
SetHash: core.HashIdentifiers(idents),
|
||||
Serial: serial,
|
||||
Issued: issued,
|
||||
|
|
1024
sa/proto/sa.pb.go
1024
sa/proto/sa.pb.go
File diff suppressed because it is too large
Load Diff
|
@ -39,7 +39,7 @@ service StorageAuthorityReadOnly {
|
|||
rpc CheckIdentifiersPaused (PauseRequest) returns (Identifiers) {}
|
||||
rpc GetPausedIdentifiers (RegistrationID) returns (Identifiers) {}
|
||||
rpc GetRateLimitOverride(GetRateLimitOverrideRequest) returns (RateLimitOverrideResponse) {}
|
||||
rpc GetEnabledRateLimitOverrides(google.protobuf.Empty) returns (stream RateLimitOverride) {}
|
||||
rpc GetEnabledRateLimitOverrides(google.protobuf.Empty) returns (stream RateLimitOverrideResponse) {}
|
||||
}
|
||||
|
||||
// StorageAuthority provides full read/write access to the database.
|
||||
|
@ -74,7 +74,7 @@ service StorageAuthority {
|
|||
rpc CheckIdentifiersPaused (PauseRequest) returns (Identifiers) {}
|
||||
rpc GetPausedIdentifiers (RegistrationID) returns (Identifiers) {}
|
||||
rpc GetRateLimitOverride(GetRateLimitOverrideRequest) returns (RateLimitOverrideResponse) {}
|
||||
rpc GetEnabledRateLimitOverrides(google.protobuf.Empty) returns (stream RateLimitOverride) {}
|
||||
rpc GetEnabledRateLimitOverrides(google.protobuf.Empty) returns (stream RateLimitOverrideResponse) {}
|
||||
|
||||
// Adders
|
||||
rpc AddBlockedKey(AddBlockedKeyRequest) returns (google.protobuf.Empty) {}
|
||||
|
@ -91,7 +91,6 @@ service StorageAuthority {
|
|||
rpc RevokeCertificate(RevokeCertificateRequest) returns (google.protobuf.Empty) {}
|
||||
rpc SetOrderError(SetOrderErrorRequest) returns (google.protobuf.Empty) {}
|
||||
rpc SetOrderProcessing(OrderRequest) returns (google.protobuf.Empty) {}
|
||||
rpc UpdateRegistrationContact(UpdateRegistrationContactRequest) returns (core.Registration) {}
|
||||
rpc UpdateRegistrationKey(UpdateRegistrationKeyRequest) returns (core.Registration) {}
|
||||
rpc UpdateRevokedCertificate(RevokeCertificateRequest) returns (google.protobuf.Empty) {}
|
||||
rpc LeaseCRLShard(LeaseCRLShardRequest) returns (LeaseCRLShardResponse) {}
|
||||
|
@ -436,11 +435,6 @@ message PauseIdentifiersResponse {
|
|||
int64 repaused = 2;
|
||||
}
|
||||
|
||||
message UpdateRegistrationContactRequest {
|
||||
int64 registrationID = 1;
|
||||
repeated string contacts = 2;
|
||||
}
|
||||
|
||||
message UpdateRegistrationKeyRequest {
|
||||
int64 registrationID = 1;
|
||||
bytes jwk = 2;
|
||||
|
|
|
@ -89,7 +89,7 @@ type StorageAuthorityReadOnlyClient interface {
|
|||
CheckIdentifiersPaused(ctx context.Context, in *PauseRequest, opts ...grpc.CallOption) (*Identifiers, error)
|
||||
GetPausedIdentifiers(ctx context.Context, in *RegistrationID, opts ...grpc.CallOption) (*Identifiers, error)
|
||||
GetRateLimitOverride(ctx context.Context, in *GetRateLimitOverrideRequest, opts ...grpc.CallOption) (*RateLimitOverrideResponse, error)
|
||||
GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverride], error)
|
||||
GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverrideResponse], error)
|
||||
}
|
||||
|
||||
type storageAuthorityReadOnlyClient struct {
|
||||
|
@ -435,13 +435,13 @@ func (c *storageAuthorityReadOnlyClient) GetRateLimitOverride(ctx context.Contex
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *storageAuthorityReadOnlyClient) GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverride], error) {
|
||||
func (c *storageAuthorityReadOnlyClient) GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverrideResponse], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StorageAuthorityReadOnly_ServiceDesc.Streams[5], StorageAuthorityReadOnly_GetEnabledRateLimitOverrides_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, RateLimitOverride]{ClientStream: stream}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, RateLimitOverrideResponse]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -452,7 +452,7 @@ func (c *storageAuthorityReadOnlyClient) GetEnabledRateLimitOverrides(ctx contex
|
|||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StorageAuthorityReadOnly_GetEnabledRateLimitOverridesClient = grpc.ServerStreamingClient[RateLimitOverride]
|
||||
type StorageAuthorityReadOnly_GetEnabledRateLimitOverridesClient = grpc.ServerStreamingClient[RateLimitOverrideResponse]
|
||||
|
||||
// StorageAuthorityReadOnlyServer is the server API for StorageAuthorityReadOnly service.
|
||||
// All implementations must embed UnimplementedStorageAuthorityReadOnlyServer
|
||||
|
@ -489,7 +489,7 @@ type StorageAuthorityReadOnlyServer interface {
|
|||
CheckIdentifiersPaused(context.Context, *PauseRequest) (*Identifiers, error)
|
||||
GetPausedIdentifiers(context.Context, *RegistrationID) (*Identifiers, error)
|
||||
GetRateLimitOverride(context.Context, *GetRateLimitOverrideRequest) (*RateLimitOverrideResponse, error)
|
||||
GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverride]) error
|
||||
GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverrideResponse]) error
|
||||
mustEmbedUnimplementedStorageAuthorityReadOnlyServer()
|
||||
}
|
||||
|
||||
|
@ -587,7 +587,7 @@ func (UnimplementedStorageAuthorityReadOnlyServer) GetPausedIdentifiers(context.
|
|||
func (UnimplementedStorageAuthorityReadOnlyServer) GetRateLimitOverride(context.Context, *GetRateLimitOverrideRequest) (*RateLimitOverrideResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetRateLimitOverride not implemented")
|
||||
}
|
||||
func (UnimplementedStorageAuthorityReadOnlyServer) GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverride]) error {
|
||||
func (UnimplementedStorageAuthorityReadOnlyServer) GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverrideResponse]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method GetEnabledRateLimitOverrides not implemented")
|
||||
}
|
||||
func (UnimplementedStorageAuthorityReadOnlyServer) mustEmbedUnimplementedStorageAuthorityReadOnlyServer() {
|
||||
|
@ -1104,11 +1104,11 @@ func _StorageAuthorityReadOnly_GetEnabledRateLimitOverrides_Handler(srv interfac
|
|||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StorageAuthorityReadOnlyServer).GetEnabledRateLimitOverrides(m, &grpc.GenericServerStream[emptypb.Empty, RateLimitOverride]{ServerStream: stream})
|
||||
return srv.(StorageAuthorityReadOnlyServer).GetEnabledRateLimitOverrides(m, &grpc.GenericServerStream[emptypb.Empty, RateLimitOverrideResponse]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StorageAuthorityReadOnly_GetEnabledRateLimitOverridesServer = grpc.ServerStreamingServer[RateLimitOverride]
|
||||
type StorageAuthorityReadOnly_GetEnabledRateLimitOverridesServer = grpc.ServerStreamingServer[RateLimitOverrideResponse]
|
||||
|
||||
// StorageAuthorityReadOnly_ServiceDesc is the grpc.ServiceDesc for StorageAuthorityReadOnly service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
|
@ -1294,7 +1294,6 @@ const (
|
|||
StorageAuthority_RevokeCertificate_FullMethodName = "/sa.StorageAuthority/RevokeCertificate"
|
||||
StorageAuthority_SetOrderError_FullMethodName = "/sa.StorageAuthority/SetOrderError"
|
||||
StorageAuthority_SetOrderProcessing_FullMethodName = "/sa.StorageAuthority/SetOrderProcessing"
|
||||
StorageAuthority_UpdateRegistrationContact_FullMethodName = "/sa.StorageAuthority/UpdateRegistrationContact"
|
||||
StorageAuthority_UpdateRegistrationKey_FullMethodName = "/sa.StorageAuthority/UpdateRegistrationKey"
|
||||
StorageAuthority_UpdateRevokedCertificate_FullMethodName = "/sa.StorageAuthority/UpdateRevokedCertificate"
|
||||
StorageAuthority_LeaseCRLShard_FullMethodName = "/sa.StorageAuthority/LeaseCRLShard"
|
||||
|
@ -1342,7 +1341,7 @@ type StorageAuthorityClient interface {
|
|||
CheckIdentifiersPaused(ctx context.Context, in *PauseRequest, opts ...grpc.CallOption) (*Identifiers, error)
|
||||
GetPausedIdentifiers(ctx context.Context, in *RegistrationID, opts ...grpc.CallOption) (*Identifiers, error)
|
||||
GetRateLimitOverride(ctx context.Context, in *GetRateLimitOverrideRequest, opts ...grpc.CallOption) (*RateLimitOverrideResponse, error)
|
||||
GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverride], error)
|
||||
GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverrideResponse], error)
|
||||
// Adders
|
||||
AddBlockedKey(ctx context.Context, in *AddBlockedKeyRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
AddCertificate(ctx context.Context, in *AddCertificateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
|
@ -1358,7 +1357,6 @@ type StorageAuthorityClient interface {
|
|||
RevokeCertificate(ctx context.Context, in *RevokeCertificateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SetOrderError(ctx context.Context, in *SetOrderErrorRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SetOrderProcessing(ctx context.Context, in *OrderRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
UpdateRegistrationContact(ctx context.Context, in *UpdateRegistrationContactRequest, opts ...grpc.CallOption) (*proto.Registration, error)
|
||||
UpdateRegistrationKey(ctx context.Context, in *UpdateRegistrationKeyRequest, opts ...grpc.CallOption) (*proto.Registration, error)
|
||||
UpdateRevokedCertificate(ctx context.Context, in *RevokeCertificateRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
LeaseCRLShard(ctx context.Context, in *LeaseCRLShardRequest, opts ...grpc.CallOption) (*LeaseCRLShardResponse, error)
|
||||
|
@ -1713,13 +1711,13 @@ func (c *storageAuthorityClient) GetRateLimitOverride(ctx context.Context, in *G
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *storageAuthorityClient) GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverride], error) {
|
||||
func (c *storageAuthorityClient) GetEnabledRateLimitOverrides(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[RateLimitOverrideResponse], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StorageAuthority_ServiceDesc.Streams[5], StorageAuthority_GetEnabledRateLimitOverrides_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, RateLimitOverride]{ClientStream: stream}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, RateLimitOverrideResponse]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1730,7 +1728,7 @@ func (c *storageAuthorityClient) GetEnabledRateLimitOverrides(ctx context.Contex
|
|||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StorageAuthority_GetEnabledRateLimitOverridesClient = grpc.ServerStreamingClient[RateLimitOverride]
|
||||
type StorageAuthority_GetEnabledRateLimitOverridesClient = grpc.ServerStreamingClient[RateLimitOverrideResponse]
|
||||
|
||||
func (c *storageAuthorityClient) AddBlockedKey(ctx context.Context, in *AddBlockedKeyRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
|
@ -1872,16 +1870,6 @@ func (c *storageAuthorityClient) SetOrderProcessing(ctx context.Context, in *Ord
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *storageAuthorityClient) UpdateRegistrationContact(ctx context.Context, in *UpdateRegistrationContactRequest, opts ...grpc.CallOption) (*proto.Registration, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(proto.Registration)
|
||||
err := c.cc.Invoke(ctx, StorageAuthority_UpdateRegistrationContact_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *storageAuthorityClient) UpdateRegistrationKey(ctx context.Context, in *UpdateRegistrationKeyRequest, opts ...grpc.CallOption) (*proto.Registration, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(proto.Registration)
|
||||
|
@ -2008,7 +1996,7 @@ type StorageAuthorityServer interface {
|
|||
CheckIdentifiersPaused(context.Context, *PauseRequest) (*Identifiers, error)
|
||||
GetPausedIdentifiers(context.Context, *RegistrationID) (*Identifiers, error)
|
||||
GetRateLimitOverride(context.Context, *GetRateLimitOverrideRequest) (*RateLimitOverrideResponse, error)
|
||||
GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverride]) error
|
||||
GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverrideResponse]) error
|
||||
// Adders
|
||||
AddBlockedKey(context.Context, *AddBlockedKeyRequest) (*emptypb.Empty, error)
|
||||
AddCertificate(context.Context, *AddCertificateRequest) (*emptypb.Empty, error)
|
||||
|
@ -2024,7 +2012,6 @@ type StorageAuthorityServer interface {
|
|||
RevokeCertificate(context.Context, *RevokeCertificateRequest) (*emptypb.Empty, error)
|
||||
SetOrderError(context.Context, *SetOrderErrorRequest) (*emptypb.Empty, error)
|
||||
SetOrderProcessing(context.Context, *OrderRequest) (*emptypb.Empty, error)
|
||||
UpdateRegistrationContact(context.Context, *UpdateRegistrationContactRequest) (*proto.Registration, error)
|
||||
UpdateRegistrationKey(context.Context, *UpdateRegistrationKeyRequest) (*proto.Registration, error)
|
||||
UpdateRevokedCertificate(context.Context, *RevokeCertificateRequest) (*emptypb.Empty, error)
|
||||
LeaseCRLShard(context.Context, *LeaseCRLShardRequest) (*LeaseCRLShardResponse, error)
|
||||
|
@ -2131,7 +2118,7 @@ func (UnimplementedStorageAuthorityServer) GetPausedIdentifiers(context.Context,
|
|||
func (UnimplementedStorageAuthorityServer) GetRateLimitOverride(context.Context, *GetRateLimitOverrideRequest) (*RateLimitOverrideResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetRateLimitOverride not implemented")
|
||||
}
|
||||
func (UnimplementedStorageAuthorityServer) GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverride]) error {
|
||||
func (UnimplementedStorageAuthorityServer) GetEnabledRateLimitOverrides(*emptypb.Empty, grpc.ServerStreamingServer[RateLimitOverrideResponse]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method GetEnabledRateLimitOverrides not implemented")
|
||||
}
|
||||
func (UnimplementedStorageAuthorityServer) AddBlockedKey(context.Context, *AddBlockedKeyRequest) (*emptypb.Empty, error) {
|
||||
|
@ -2176,9 +2163,6 @@ func (UnimplementedStorageAuthorityServer) SetOrderError(context.Context, *SetOr
|
|||
func (UnimplementedStorageAuthorityServer) SetOrderProcessing(context.Context, *OrderRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SetOrderProcessing not implemented")
|
||||
}
|
||||
func (UnimplementedStorageAuthorityServer) UpdateRegistrationContact(context.Context, *UpdateRegistrationContactRequest) (*proto.Registration, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistrationContact not implemented")
|
||||
}
|
||||
func (UnimplementedStorageAuthorityServer) UpdateRegistrationKey(context.Context, *UpdateRegistrationKeyRequest) (*proto.Registration, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistrationKey not implemented")
|
||||
}
|
||||
|
@ -2719,11 +2703,11 @@ func _StorageAuthority_GetEnabledRateLimitOverrides_Handler(srv interface{}, str
|
|||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StorageAuthorityServer).GetEnabledRateLimitOverrides(m, &grpc.GenericServerStream[emptypb.Empty, RateLimitOverride]{ServerStream: stream})
|
||||
return srv.(StorageAuthorityServer).GetEnabledRateLimitOverrides(m, &grpc.GenericServerStream[emptypb.Empty, RateLimitOverrideResponse]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StorageAuthority_GetEnabledRateLimitOverridesServer = grpc.ServerStreamingServer[RateLimitOverride]
|
||||
type StorageAuthority_GetEnabledRateLimitOverridesServer = grpc.ServerStreamingServer[RateLimitOverrideResponse]
|
||||
|
||||
func _StorageAuthority_AddBlockedKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AddBlockedKeyRequest)
|
||||
|
@ -2977,24 +2961,6 @@ func _StorageAuthority_SetOrderProcessing_Handler(srv interface{}, ctx context.C
|
|||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StorageAuthority_UpdateRegistrationContact_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateRegistrationContactRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StorageAuthorityServer).UpdateRegistrationContact(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StorageAuthority_UpdateRegistrationContact_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StorageAuthorityServer).UpdateRegistrationContact(ctx, req.(*UpdateRegistrationContactRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StorageAuthority_UpdateRegistrationKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateRegistrationKeyRequest)
|
||||
if err := dec(in); err != nil {
|
||||
|
@ -3316,10 +3282,6 @@ var StorageAuthority_ServiceDesc = grpc.ServiceDesc{
|
|||
MethodName: "SetOrderProcessing",
|
||||
Handler: _StorageAuthority_SetOrderProcessing_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateRegistrationContact",
|
||||
Handler: _StorageAuthority_UpdateRegistrationContact_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpdateRegistrationKey",
|
||||
Handler: _StorageAuthority_UpdateRegistrationKey_Handler,
|
||||
|
|
10
sa/sa.go
10
sa/sa.go
|
@ -125,14 +125,6 @@ func (ssa *SQLStorageAuthority) NewRegistration(ctx context.Context, req *corepb
|
|||
return registrationModelToPb(reg)
|
||||
}
|
||||
|
||||
// UpdateRegistrationContact makes no changes, and simply returns the account
|
||||
// as it exists in the database.
|
||||
//
|
||||
// Deprecated: See https://github.com/letsencrypt/boulder/issues/8199 for removal.
|
||||
func (ssa *SQLStorageAuthority) UpdateRegistrationContact(ctx context.Context, req *sapb.UpdateRegistrationContactRequest) (*corepb.Registration, error) {
|
||||
return ssa.GetRegistration(ctx, &sapb.RegistrationID{Id: req.RegistrationID})
|
||||
}
|
||||
|
||||
// UpdateRegistrationKey stores an updated key in a Registration.
|
||||
func (ssa *SQLStorageAuthority) UpdateRegistrationKey(ctx context.Context, req *sapb.UpdateRegistrationKeyRequest) (*corepb.Registration, error) {
|
||||
if core.IsAnyNilOrZero(req.RegistrationID, req.Jwk) {
|
||||
|
@ -407,7 +399,7 @@ func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.Ad
|
|||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
// DeactivateRegistration deactivates a currently valid registration and removes its contact field
|
||||
// DeactivateRegistration deactivates a currently valid registration
|
||||
func (ssa *SQLStorageAuthority) DeactivateRegistration(ctx context.Context, req *sapb.RegistrationID) (*corepb.Registration, error) {
|
||||
if req == nil || req.Id == 0 {
|
||||
return nil, errIncompleteRequest
|
||||
|
|
123
sa/sa_test.go
123
sa/sa_test.go
|
@ -126,7 +126,6 @@ func initSA(t testing.TB) (*SQLStorageAuthority, clock.FakeClock, func()) {
|
|||
func createWorkingRegistration(t testing.TB, sa *SQLStorageAuthority) *corepb.Registration {
|
||||
reg, err := sa.NewRegistration(context.Background(), &corepb.Registration{
|
||||
Key: []byte(theKey),
|
||||
Contact: []string{"mailto:foo@example.com"},
|
||||
CreatedAt: mustTimestamp("2003-05-10 00:00"),
|
||||
Status: string(core.StatusValid),
|
||||
})
|
||||
|
@ -190,14 +189,12 @@ func TestAddRegistration(t *testing.T) {
|
|||
|
||||
jwkJSON, _ := goodTestJWK().MarshalJSON()
|
||||
reg, err := sa.NewRegistration(ctx, &corepb.Registration{
|
||||
Key: jwkJSON,
|
||||
Contact: []string{"mailto:foo@example.com"},
|
||||
Key: jwkJSON,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create new registration: %s", err)
|
||||
}
|
||||
test.Assert(t, reg.Id != 0, "ID shouldn't be 0")
|
||||
test.AssertEquals(t, len(reg.Contact), 0)
|
||||
|
||||
// Confirm that the registration can be retrieved by ID.
|
||||
dbReg, err := sa.GetRegistration(ctx, &sapb.RegistrationID{Id: reg.Id})
|
||||
|
@ -207,7 +204,6 @@ func TestAddRegistration(t *testing.T) {
|
|||
test.AssertEquals(t, dbReg.Id, reg.Id)
|
||||
test.AssertByteEquals(t, dbReg.Key, jwkJSON)
|
||||
test.AssertDeepEquals(t, dbReg.CreatedAt.AsTime(), createdAt)
|
||||
test.AssertEquals(t, len(dbReg.Contact), 0)
|
||||
|
||||
_, err = sa.GetRegistration(ctx, &sapb.RegistrationID{Id: 0})
|
||||
test.AssertError(t, err, "Registration object for ID 0 was returned")
|
||||
|
@ -254,8 +250,7 @@ func TestSelectRegistration(t *testing.T) {
|
|||
test.AssertNotError(t, err, "couldn't parse jwk.Key")
|
||||
|
||||
reg, err := sa.NewRegistration(ctx, &corepb.Registration{
|
||||
Key: jwkJSON,
|
||||
Contact: []string{"mailto:foo@example.com"},
|
||||
Key: jwkJSON,
|
||||
})
|
||||
test.AssertNotError(t, err, fmt.Sprintf("couldn't create new registration: %s", err))
|
||||
test.Assert(t, reg.Id != 0, "ID shouldn't be 0")
|
||||
|
@ -884,7 +879,6 @@ func TestDeactivateAccount(t *testing.T) {
|
|||
test.AssertNotError(t, err, "DeactivateRegistration failed")
|
||||
test.AssertEquals(t, got.Id, reg.Id)
|
||||
test.AssertEquals(t, core.AcmeStatus(got.Status), core.StatusDeactivated)
|
||||
test.AssertEquals(t, len(got.Contact), 0)
|
||||
|
||||
// Double-check that the DeactivateRegistration method returned the right
|
||||
// thing, by fetching the same account ourselves.
|
||||
|
@ -892,7 +886,6 @@ func TestDeactivateAccount(t *testing.T) {
|
|||
test.AssertNotError(t, err, "GetRegistration failed")
|
||||
test.AssertEquals(t, got.Id, reg.Id)
|
||||
test.AssertEquals(t, core.AcmeStatus(got.Status), core.StatusDeactivated)
|
||||
test.AssertEquals(t, len(got.Contact), 0)
|
||||
|
||||
// Attempting to deactivate it a second time should fail, since it is already
|
||||
// deactivated.
|
||||
|
@ -2639,6 +2632,36 @@ func TestGetValidAuthorizations2(t *testing.T) {
|
|||
aaa = am.ID
|
||||
}
|
||||
|
||||
var dac int64
|
||||
{
|
||||
tokenStr := core.NewToken()
|
||||
token, err := base64.RawURLEncoding.DecodeString(tokenStr)
|
||||
test.AssertNotError(t, err, "computing test authorization challenge token")
|
||||
|
||||
profile := "test"
|
||||
attempted := challTypeToUint[string(core.ChallengeTypeDNSAccount01)]
|
||||
attemptedAt := fc.Now()
|
||||
vr, _ := json.Marshal([]core.ValidationRecord{})
|
||||
|
||||
am := authzModel{
|
||||
IdentifierType: identifierTypeToUint[string(identifier.TypeDNS)],
|
||||
IdentifierValue: "aaa",
|
||||
RegistrationID: 3,
|
||||
CertificateProfileName: &profile,
|
||||
Status: statusToUint[core.StatusValid],
|
||||
Expires: fc.Now().Add(24 * time.Hour),
|
||||
Challenges: 1 << challTypeToUint[string(core.ChallengeTypeDNSAccount01)],
|
||||
Attempted: &attempted,
|
||||
AttemptedAt: &attemptedAt,
|
||||
Token: token,
|
||||
ValidationError: nil,
|
||||
ValidationRecord: vr,
|
||||
}
|
||||
err = sa.dbMap.Insert(context.Background(), &am)
|
||||
test.AssertNotError(t, err, "failed to insert valid authz with dns-account-01")
|
||||
dac = am.ID
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
regID int64
|
||||
|
@ -2655,6 +2678,14 @@ func TestGetValidAuthorizations2(t *testing.T) {
|
|||
validUntil: fc.Now().Add(time.Hour),
|
||||
wantIDs: []int64{aaa},
|
||||
},
|
||||
{
|
||||
name: "happy path, dns-account-01 challenge",
|
||||
regID: 3,
|
||||
identifiers: []*corepb.Identifier{identifier.NewDNS("aaa").ToProto()},
|
||||
profile: "test",
|
||||
validUntil: fc.Now().Add(time.Hour),
|
||||
wantIDs: []int64{dac},
|
||||
},
|
||||
{
|
||||
name: "different identifier type",
|
||||
regID: 1,
|
||||
|
@ -4492,74 +4523,6 @@ func newAcctKey(t *testing.T) []byte {
|
|||
return acctKey
|
||||
}
|
||||
|
||||
func TestUpdateRegistrationContact(t *testing.T) {
|
||||
// TODO(#8199): Delete this.
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
||||
noContact, _ := json.Marshal("")
|
||||
exampleContact, _ := json.Marshal("test@example.com")
|
||||
twoExampleContacts, _ := json.Marshal([]string{"test1@example.com", "test2@example.com"})
|
||||
|
||||
_, err := sa.UpdateRegistrationContact(ctx, &sapb.UpdateRegistrationContactRequest{})
|
||||
test.AssertError(t, err, "should not have been able to update registration contact without a registration ID")
|
||||
test.AssertContains(t, err.Error(), "incomplete gRPC request message")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
oldContactsJSON []string
|
||||
newContacts []string
|
||||
}{
|
||||
{
|
||||
name: "update a valid registration from no contacts to one email address",
|
||||
oldContactsJSON: []string{string(noContact)},
|
||||
newContacts: []string{"mailto:test@example.com"},
|
||||
},
|
||||
{
|
||||
name: "update a valid registration from no contacts to two email addresses",
|
||||
oldContactsJSON: []string{string(noContact)},
|
||||
newContacts: []string{"mailto:test1@example.com", "mailto:test2@example.com"},
|
||||
},
|
||||
{
|
||||
name: "update a valid registration from one email address to no contacts",
|
||||
oldContactsJSON: []string{string(exampleContact)},
|
||||
newContacts: []string{},
|
||||
},
|
||||
{
|
||||
name: "update a valid registration from one email address to two email addresses",
|
||||
oldContactsJSON: []string{string(exampleContact)},
|
||||
newContacts: []string{"mailto:test1@example.com", "mailto:test2@example.com"},
|
||||
},
|
||||
{
|
||||
name: "update a valid registration from two email addresses to no contacts",
|
||||
oldContactsJSON: []string{string(twoExampleContacts)},
|
||||
newContacts: []string{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reg, err := sa.NewRegistration(ctx, &corepb.Registration{
|
||||
Contact: tt.oldContactsJSON,
|
||||
Key: newAcctKey(t),
|
||||
})
|
||||
test.AssertNotError(t, err, "creating new registration")
|
||||
|
||||
updatedReg, err := sa.UpdateRegistrationContact(ctx, &sapb.UpdateRegistrationContactRequest{
|
||||
RegistrationID: reg.Id,
|
||||
Contacts: tt.newContacts,
|
||||
})
|
||||
test.AssertNotError(t, err, "unexpected error for UpdateRegistrationContact()")
|
||||
test.AssertEquals(t, updatedReg.Id, reg.Id)
|
||||
test.AssertEquals(t, len(updatedReg.Contact), 0)
|
||||
|
||||
refetchedReg, err := sa.GetRegistration(ctx, &sapb.RegistrationID{Id: reg.Id})
|
||||
test.AssertNotError(t, err, "retrieving registration")
|
||||
test.AssertEquals(t, refetchedReg.Id, reg.Id)
|
||||
test.AssertEquals(t, len(refetchedReg.Contact), 0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateRegistrationKey(t *testing.T) {
|
||||
sa, _, cleanUp := initSA(t)
|
||||
defer cleanUp()
|
||||
|
@ -4624,7 +4587,7 @@ func TestUpdateRegistrationKey(t *testing.T) {
|
|||
|
||||
type mockRLOStream struct {
|
||||
grpc.ServerStream
|
||||
sent []*sapb.RateLimitOverride
|
||||
sent []*sapb.RateLimitOverrideResponse
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
|
@ -4633,7 +4596,7 @@ func newMockRLOStream() *mockRLOStream {
|
|||
}
|
||||
func (m *mockRLOStream) Context() context.Context { return m.ctx }
|
||||
func (m *mockRLOStream) RecvMsg(any) error { return io.EOF }
|
||||
func (m *mockRLOStream) Send(ov *sapb.RateLimitOverride) error {
|
||||
func (m *mockRLOStream) Send(ov *sapb.RateLimitOverrideResponse) error {
|
||||
m.sent = append(m.sent, ov)
|
||||
return nil
|
||||
}
|
||||
|
@ -4767,5 +4730,5 @@ func TestGetEnabledRateLimitOverrides(t *testing.T) {
|
|||
err = sa.GetEnabledRateLimitOverrides(&emptypb.Empty{}, stream)
|
||||
test.AssertNotError(t, err, "expected streaming enabled overrides to succeed, got error")
|
||||
test.AssertEquals(t, len(stream.sent), 1)
|
||||
test.AssertEquals(t, stream.sent[0].BucketKey, "on")
|
||||
test.AssertEquals(t, stream.sent[0].Override.BucketKey, "on")
|
||||
}
|
||||
|
|
|
@ -1304,6 +1304,10 @@ func (ssa *SQLStorageAuthorityRO) GetEnabledRateLimitOverrides(_ *emptypb.Empty,
|
|||
}
|
||||
|
||||
return rows.ForEach(func(m *overrideModel) error {
|
||||
return stream.Send(newPBFromOverrideModel(m))
|
||||
return stream.Send(&sapb.RateLimitOverrideResponse{
|
||||
Override: newPBFromOverrideModel(m),
|
||||
Enabled: m.Enabled,
|
||||
UpdatedAt: timestamppb.New(m.UpdatedAt),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,10 +5,11 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
corepb "github.com/letsencrypt/boulder/core/proto"
|
||||
sapb "github.com/letsencrypt/boulder/sa/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
// CreateWorkingRegistration inserts a new, correct Registration into
|
||||
|
@ -21,7 +22,6 @@ func CreateWorkingRegistration(t *testing.T, sa sapb.StorageAuthorityClient) *co
|
|||
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw",
|
||||
"e": "AQAB"
|
||||
}`),
|
||||
Contact: []string{"mailto:foo@example.com"},
|
||||
CreatedAt: timestamppb.New(time.Date(2003, 5, 10, 0, 0, 0, 0, time.UTC)),
|
||||
Status: string(core.StatusValid),
|
||||
})
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
"maximumRevocations": 15,
|
||||
"findCertificatesBatchSize": 10,
|
||||
"interval": "50ms",
|
||||
"backoffIntervalMax": "2s"
|
||||
"backoffIntervalMax": "2s",
|
||||
"maxExpectedReplicationLag": "100ms"
|
||||
},
|
||||
"syslog": {
|
||||
"stdoutlevel": 4,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"certFile": "test/certs/ipki/ca.boulder/cert.pem",
|
||||
"keyFile": "test/certs/ipki/ca.boulder/key.pem"
|
||||
},
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||
"hostnamePolicyFile": "test/ident-policy.yaml",
|
||||
"grpcCA": {
|
||||
"maxConnectionAge": "30s",
|
||||
"services": {
|
||||
|
@ -54,28 +54,20 @@
|
|||
"issuance": {
|
||||
"certProfiles": {
|
||||
"legacy": {
|
||||
"omitCommonName": false,
|
||||
"omitKeyEncipherment": false,
|
||||
"omitClientAuth": false,
|
||||
"omitSKID": false,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "7776000s",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
"lintConfig": "test/config-next/zlint.toml",
|
||||
"ignoredLints": [
|
||||
"w_subject_common_name_included",
|
||||
"e_dnsname_not_valid_tld",
|
||||
"w_ext_subject_key_identifier_not_recommended_subscriber"
|
||||
]
|
||||
},
|
||||
"modern": {
|
||||
"omitCommonName": true,
|
||||
"omitKeyEncipherment": true,
|
||||
"omitClientAuth": true,
|
||||
"omitSKID": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "583200s",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
"lintConfig": "test/config-next/zlint.toml",
|
||||
"ignoredLints": [
|
||||
"w_ext_subject_key_identifier_missing_sub_cert"
|
||||
]
|
||||
},
|
||||
"shortlived": {
|
||||
"omitCommonName": true,
|
||||
"omitKeyEncipherment": true,
|
||||
|
@ -86,7 +78,22 @@
|
|||
"maxValidityBackdate": "1h5m",
|
||||
"lintConfig": "test/config-next/zlint.toml",
|
||||
"ignoredLints": [
|
||||
"w_ext_subject_key_identifier_missing_sub_cert"
|
||||
"w_ext_subject_key_identifier_missing_sub_cert",
|
||||
"e_dnsname_not_valid_tld"
|
||||
]
|
||||
},
|
||||
"modern": {
|
||||
"omitCommonName": true,
|
||||
"omitKeyEncipherment": true,
|
||||
"omitClientAuth": true,
|
||||
"omitSKID": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "583200s",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
"lintConfig": "test/config-next/zlint.toml",
|
||||
"ignoredLints": [
|
||||
"w_ext_subject_key_identifier_missing_sub_cert",
|
||||
"e_dnsname_not_valid_tld"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -4,18 +4,20 @@
|
|||
"dbConnectFile": "test/secrets/cert_checker_dburl",
|
||||
"maxOpenConns": 10
|
||||
},
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||
"hostnamePolicyFile": "test/ident-policy.yaml",
|
||||
"workers": 16,
|
||||
"unexpiredOnly": true,
|
||||
"badResultsOnly": true,
|
||||
"checkPeriod": "72h",
|
||||
"acceptableValidityDurations": [
|
||||
"7776000s"
|
||||
"7776000s",
|
||||
"160h"
|
||||
],
|
||||
"lintConfig": "test/config-next/zlint.toml",
|
||||
"ignoredLints": [
|
||||
"w_subject_common_name_included",
|
||||
"w_ext_subject_key_identifier_missing_sub_cert",
|
||||
"w_subject_common_name_included",
|
||||
"e_dnsname_not_valid_tld",
|
||||
"w_ext_subject_key_identifier_not_recommended_subscriber"
|
||||
],
|
||||
"ctLogListFile": "test/ct-test-srv/log_list.json",
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
"Overrides": "test/config-next/wfe2-ratelimit-overrides.yml"
|
||||
},
|
||||
"maxContactsPerRegistration": 3,
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||
"hostnamePolicyFile": "test/ident-policy.yaml",
|
||||
"goodkey": {},
|
||||
"finalizeTimeout": "30s",
|
||||
"issuerCerts": [
|
||||
|
|
|
@ -20,20 +20,11 @@
|
|||
"noWaitForReady": true,
|
||||
"timeout": "15s"
|
||||
},
|
||||
"mailer": {
|
||||
"server": "localhost",
|
||||
"port": "9380",
|
||||
"username": "cert-manager@example.com",
|
||||
"from": "bad key revoker <bad-key-revoker@test.org>",
|
||||
"passwordFile": "test/secrets/smtp_password",
|
||||
"SMTPTrustedRootFile": "test/certs/ipki/minica.pem",
|
||||
"emailSubject": "Certificates you've issued have been revoked due to key compromise",
|
||||
"emailTemplate": "test/example-bad-key-revoker-template"
|
||||
},
|
||||
"maximumRevocations": 15,
|
||||
"findCertificatesBatchSize": 10,
|
||||
"interval": "50ms",
|
||||
"backoffIntervalMax": "2s"
|
||||
"backoffIntervalMax": "2s",
|
||||
"maxExpectedReplicationLag": "100ms"
|
||||
},
|
||||
"syslog": {
|
||||
"stdoutlevel": 4,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"certFile": "test/certs/ipki/ca.boulder/cert.pem",
|
||||
"keyFile": "test/certs/ipki/ca.boulder/key.pem"
|
||||
},
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||
"hostnamePolicyFile": "test/ident-policy.yaml",
|
||||
"grpcCA": {
|
||||
"maxConnectionAge": "30s",
|
||||
"address": ":9093",
|
||||
|
@ -55,7 +55,11 @@
|
|||
"issuance": {
|
||||
"certProfiles": {
|
||||
"legacy": {
|
||||
"allowMustStaple": true,
|
||||
"allowMustStaple": false,
|
||||
"omitCommonName": false,
|
||||
"omitKeyEncipherment": false,
|
||||
"omitClientAuth": false,
|
||||
"omitSKID": false,
|
||||
"omitOCSP": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "7776000s",
|
||||
|
@ -63,26 +67,12 @@
|
|||
"lintConfig": "test/config-next/zlint.toml",
|
||||
"ignoredLints": [
|
||||
"w_subject_common_name_included",
|
||||
"e_dnsname_not_valid_tld",
|
||||
"w_ext_subject_key_identifier_not_recommended_subscriber"
|
||||
]
|
||||
},
|
||||
"modern": {
|
||||
"allowMustStaple": true,
|
||||
"omitCommonName": true,
|
||||
"omitKeyEncipherment": true,
|
||||
"omitClientAuth": true,
|
||||
"omitSKID": true,
|
||||
"omitOCSP": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "583200s",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
"lintConfig": "test/config-next/zlint.toml",
|
||||
"ignoredLints": [
|
||||
"w_ext_subject_key_identifier_missing_sub_cert"
|
||||
]
|
||||
},
|
||||
"shortlived": {
|
||||
"allowMustStaple": true,
|
||||
"allowMustStaple": false,
|
||||
"omitCommonName": true,
|
||||
"omitKeyEncipherment": true,
|
||||
"omitClientAuth": true,
|
||||
|
@ -93,7 +83,24 @@
|
|||
"maxValidityBackdate": "1h5m",
|
||||
"lintConfig": "test/config-next/zlint.toml",
|
||||
"ignoredLints": [
|
||||
"w_ext_subject_key_identifier_missing_sub_cert"
|
||||
"w_ext_subject_key_identifier_missing_sub_cert",
|
||||
"e_dnsname_not_valid_tld"
|
||||
]
|
||||
},
|
||||
"modern": {
|
||||
"allowMustStaple": false,
|
||||
"omitCommonName": true,
|
||||
"omitKeyEncipherment": true,
|
||||
"omitClientAuth": true,
|
||||
"omitSKID": true,
|
||||
"omitOCSP": true,
|
||||
"includeCRLDistributionPoints": true,
|
||||
"maxValidityPeriod": "583200s",
|
||||
"maxValidityBackdate": "1h5m",
|
||||
"lintConfig": "test/config-next/zlint.toml",
|
||||
"ignoredLints": [
|
||||
"w_ext_subject_key_identifier_missing_sub_cert",
|
||||
"e_dnsname_not_valid_tld"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -193,7 +200,8 @@
|
|||
"tls-alpn-01": true
|
||||
},
|
||||
"identifiers": {
|
||||
"dns": true
|
||||
"dns": true,
|
||||
"ip": true
|
||||
}
|
||||
},
|
||||
"syslog": {
|
||||
|
|
|
@ -4,17 +4,19 @@
|
|||
"dbConnectFile": "test/secrets/cert_checker_dburl",
|
||||
"maxOpenConns": 10
|
||||
},
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||
"hostnamePolicyFile": "test/ident-policy.yaml",
|
||||
"workers": 16,
|
||||
"unexpiredOnly": true,
|
||||
"badResultsOnly": true,
|
||||
"checkPeriod": "72h",
|
||||
"acceptableValidityDurations": [
|
||||
"7776000s"
|
||||
"7776000s",
|
||||
"160h"
|
||||
],
|
||||
"ignoredLints": [
|
||||
"w_subject_common_name_included",
|
||||
"w_ext_subject_key_identifier_missing_sub_cert",
|
||||
"w_subject_common_name_included",
|
||||
"e_dnsname_not_valid_tld",
|
||||
"w_ext_subject_key_identifier_not_recommended_subscriber"
|
||||
],
|
||||
"ctLogListFile": "test/ct-test-srv/log_list.json"
|
||||
|
@ -26,7 +28,8 @@
|
|||
"tls-alpn-01": true
|
||||
},
|
||||
"identifiers": {
|
||||
"dns": true
|
||||
"dns": true,
|
||||
"ip": true
|
||||
}
|
||||
},
|
||||
"syslog": {
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
},
|
||||
"maxContactsPerRegistration": 3,
|
||||
"debugAddr": ":8002",
|
||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||
"hostnamePolicyFile": "test/ident-policy.yaml",
|
||||
"goodkey": {},
|
||||
"issuerCerts": [
|
||||
"test/certs/webpki/int-rsa-a.cert.pem",
|
||||
|
@ -61,7 +61,8 @@
|
|||
"orderLifetime": "7h",
|
||||
"maxNames": 10,
|
||||
"identifierTypes": [
|
||||
"dns"
|
||||
"dns",
|
||||
"ip"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -193,7 +194,8 @@
|
|||
"tls-alpn-01": true
|
||||
},
|
||||
"identifiers": {
|
||||
"dns": true
|
||||
"dns": true,
|
||||
"ip": true
|
||||
}
|
||||
},
|
||||
"syslog": {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Example YAML Boulder hostname policy
|
||||
# Example YAML Boulder identifier policy
|
||||
#
|
||||
# This is *not* a production ready policy file and not reflective of Let's
|
||||
# Encrypt's policies! It is just an example.
|
||||
|
@ -31,3 +31,13 @@ HighRiskBlockedNames:
|
|||
# they are separated into their own list.
|
||||
AdminBlockedNames:
|
||||
- "sealand"
|
||||
|
||||
# AdminBlockedPrefixes is a list of IP address prefixes. All IP addresses
|
||||
# contained within the prefix are blocked.
|
||||
#
|
||||
# To block a single IP, append "/32" for IPv4 or "/128" for IPv6.
|
||||
AdminBlockedPrefixes:
|
||||
- "64.112.117.66/32"
|
||||
- "64.112.117.68/30"
|
||||
- "2602:80a:6000:baa:ffff:ffff:ffff:ffff/128"
|
||||
- "2602:80a:6000:bad::/64"
|
|
@ -7,8 +7,9 @@ import (
|
|||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"os"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -212,22 +213,89 @@ func TestIPShortLived(t *testing.T) {
|
|||
|
||||
// Get one cert for the shortlived profile.
|
||||
res, err := authAndIssue(client, key, idents, false, "shortlived")
|
||||
if os.Getenv("BOULDER_CONFIG_DIR") == "test/config-next" {
|
||||
if err != nil {
|
||||
t.Errorf("issuing under shortlived profile: %s", err)
|
||||
}
|
||||
if res.Order.Profile != "shortlived" {
|
||||
t.Errorf("got '%s' profile, wanted 'shortlived'", res.Order.Profile)
|
||||
}
|
||||
cert := res.certs[0]
|
||||
if err != nil {
|
||||
t.Errorf("issuing under shortlived profile: %s", err)
|
||||
}
|
||||
if res.Order.Profile != "shortlived" {
|
||||
t.Errorf("got '%s' profile, wanted 'shortlived'", res.Order.Profile)
|
||||
}
|
||||
cert := res.certs[0]
|
||||
|
||||
// Check that the shortlived profile worked as expected.
|
||||
if cert.IPAddresses[0].String() != ip {
|
||||
t.Errorf("got cert with first IP SAN '%s', wanted '%s'", cert.IPAddresses[0], ip)
|
||||
}
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), "Profile \"shortlived\" does not permit ip type identifiers") {
|
||||
t.Errorf("issuing under shortlived profile failed for the wrong reason: %s", err)
|
||||
}
|
||||
// Check that the shortlived profile worked as expected.
|
||||
if cert.IPAddresses[0].String() != ip {
|
||||
t.Errorf("got cert with first IP SAN '%s', wanted '%s'", cert.IPAddresses[0], ip)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIPCNRejected verifies that we will reject IP address identifiers when
|
||||
// they occur in the Subject CommonName.
|
||||
func TestIPCNRejected(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create an account.
|
||||
client, err := makeClient("mailto:example@letsencrypt.org")
|
||||
if err != nil {
|
||||
t.Fatalf("creating acme client: %s", err)
|
||||
}
|
||||
|
||||
// Create an IP address identifier to request.
|
||||
ip := "64.112.117.122"
|
||||
ipParsed := net.ParseIP(ip)
|
||||
idents := []acme.Identifier{
|
||||
{Type: "ip", Value: ip},
|
||||
}
|
||||
|
||||
order, err := client.Client.NewOrderExtension(client.Account, idents, acme.OrderExtension{Profile: "shortlived"})
|
||||
if err != nil {
|
||||
t.Fatalf("creating order: %s", err)
|
||||
}
|
||||
|
||||
if len(order.Authorizations) != 1 {
|
||||
t.Fatalf("Got %d authorizations, expected 1", len(order.Authorizations))
|
||||
}
|
||||
auth, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
|
||||
chal, ok := auth.ChallengeMap[acme.ChallengeTypeHTTP01]
|
||||
if !ok {
|
||||
t.Fatalf("no HTTP challenge at %s", order.Authorizations[0])
|
||||
}
|
||||
|
||||
_, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization)
|
||||
if err != nil {
|
||||
t.Fatalf("adding HTTP challenge response: %s", err)
|
||||
}
|
||||
defer testSrvClient.RemoveHTTP01Response(chal.Token)
|
||||
|
||||
chal, err = client.Client.UpdateChallenge(client.Account, chal)
|
||||
if err != nil {
|
||||
t.Fatalf("updating challenge: %s", err)
|
||||
}
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("creating random cert key: %s", err)
|
||||
}
|
||||
csrTemplate := &x509.CertificateRequest{
|
||||
Subject: pkix.Name{CommonName: ip},
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
PublicKeyAlgorithm: x509.ECDSA,
|
||||
PublicKey: key.Public(),
|
||||
IPAddresses: []net.IP{ipParsed},
|
||||
}
|
||||
csrDer, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, key)
|
||||
if err != nil {
|
||||
t.Fatalf("making csr: %s", err)
|
||||
}
|
||||
csr, err := x509.ParseCertificateRequest(csrDer)
|
||||
if err != nil {
|
||||
t.Fatalf("parsing csr: %s", err)
|
||||
}
|
||||
|
||||
_, err = client.Client.FinalizeOrder(client.Account, order, csr)
|
||||
if err == nil {
|
||||
t.Errorf("Finalizing order with IP in CN: got nil error, want badCSR error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "CSR contains IP address in Common Name") {
|
||||
t.Errorf("issuing with IP in CN failed for the wrong reason: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/eggsampler/acme/v3"
|
||||
|
@ -16,12 +15,9 @@ import (
|
|||
|
||||
func TestDuplicateFQDNRateLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
idents := []acme.Identifier{{Type: "dns", Value: random_domain()}}
|
||||
|
||||
// TODO(#8235): Remove this conditional once IP address identifiers are
|
||||
// enabled in test/config.
|
||||
if os.Getenv("BOULDER_CONFIG_DIR") == "test/config-next" {
|
||||
idents = append(idents, acme.Identifier{Type: "ip", Value: "64.112.117.122"})
|
||||
idents := []acme.Identifier{
|
||||
{Type: "dns", Value: random_domain()},
|
||||
{Type: "ip", Value: "64.112.117.122"},
|
||||
}
|
||||
|
||||
// The global rate limit for a duplicate certificates is 2 per 3 hours.
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// filter filters mails based on the To: and From: fields.
|
||||
// The zero value matches all mails.
|
||||
type filter struct {
|
||||
To string
|
||||
From string
|
||||
}
|
||||
|
||||
func (f *filter) Match(m rcvdMail) bool {
|
||||
if f.To != "" && f.To != m.To {
|
||||
return false
|
||||
}
|
||||
if f.From != "" && f.From != m.From {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
/count - number of mails
|
||||
/count?to=foo@bar.com - number of mails for foo@bar.com
|
||||
/count?from=service@test.org - number of mails sent by service@test.org
|
||||
/clear - clear the mail list
|
||||
/mail/0 - first mail
|
||||
/mail/1 - second mail
|
||||
/mail/0?to=foo@bar.com - first mail for foo@bar.com
|
||||
/mail/1?to=foo@bar.com - second mail for foo@bar.com
|
||||
/mail/1?to=foo@bar.com&from=service@test.org - second mail for foo@bar.com from service@test.org
|
||||
*/
|
||||
|
||||
func (srv *mailSrv) setupHTTP(serveMux *http.ServeMux) {
|
||||
serveMux.HandleFunc("/count", srv.httpCount)
|
||||
serveMux.HandleFunc("/clear", srv.httpClear)
|
||||
serveMux.Handle("/mail/", http.StripPrefix("/mail/", http.HandlerFunc(srv.httpGetMail)))
|
||||
}
|
||||
|
||||
func (srv *mailSrv) httpClear(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "POST" {
|
||||
srv.allMailMutex.Lock()
|
||||
srv.allReceivedMail = nil
|
||||
srv.allMailMutex.Unlock()
|
||||
w.WriteHeader(200)
|
||||
} else {
|
||||
w.WriteHeader(405)
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *mailSrv) httpCount(w http.ResponseWriter, r *http.Request) {
|
||||
count := 0
|
||||
srv.iterMail(extractFilter(r), func(m rcvdMail) bool {
|
||||
count++
|
||||
return false
|
||||
})
|
||||
fmt.Fprintf(w, "%d\n", count)
|
||||
}
|
||||
|
||||
func (srv *mailSrv) httpGetMail(w http.ResponseWriter, r *http.Request) {
|
||||
mailNum, err := strconv.Atoi(strings.Trim(r.URL.Path, "/"))
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
log.Println("mail-test-srv: bad request:", r.URL.Path, "-", err)
|
||||
return
|
||||
}
|
||||
idx := 0
|
||||
found := srv.iterMail(extractFilter(r), func(m rcvdMail) bool {
|
||||
if mailNum == idx {
|
||||
printMail(w, m)
|
||||
return true
|
||||
}
|
||||
idx++
|
||||
return false
|
||||
})
|
||||
if !found {
|
||||
w.WriteHeader(404)
|
||||
}
|
||||
}
|
||||
|
||||
func extractFilter(r *http.Request) filter {
|
||||
values := r.URL.Query()
|
||||
return filter{To: values.Get("to"), From: values.Get("from")}
|
||||
}
|
||||
|
||||
func (srv *mailSrv) iterMail(f filter, cb func(rcvdMail) bool) bool {
|
||||
srv.allMailMutex.Lock()
|
||||
defer srv.allMailMutex.Unlock()
|
||||
for _, v := range srv.allReceivedMail {
|
||||
if !f.Match(v) {
|
||||
continue
|
||||
}
|
||||
if cb(v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func printMail(w io.Writer, mail rcvdMail) {
|
||||
fmt.Fprintf(w, "FROM %s\n", mail.From)
|
||||
fmt.Fprintf(w, "TO %s\n", mail.To)
|
||||
fmt.Fprintf(w, "\n%s\n", mail.Mail)
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func reqAndRecorder(t testing.TB, method, relativeUrl string, body io.Reader) (*httptest.ResponseRecorder, *http.Request) {
|
||||
endURL := fmt.Sprintf("http://localhost:9381%s", relativeUrl)
|
||||
r, err := http.NewRequest(method, endURL, body)
|
||||
if err != nil {
|
||||
t.Fatalf("could not construct request: %v", err)
|
||||
}
|
||||
return httptest.NewRecorder(), r
|
||||
}
|
||||
|
||||
func TestHTTPClear(t *testing.T) {
|
||||
srv := mailSrv{}
|
||||
w, r := reqAndRecorder(t, "POST", "/clear", nil)
|
||||
srv.allReceivedMail = []rcvdMail{{}}
|
||||
srv.httpClear(w, r)
|
||||
if w.Code != 200 {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
if len(srv.allReceivedMail) != 0 {
|
||||
t.Error("/clear failed to clear mail buffer")
|
||||
}
|
||||
|
||||
w, r = reqAndRecorder(t, "GET", "/clear", nil)
|
||||
srv.allReceivedMail = []rcvdMail{{}}
|
||||
srv.httpClear(w, r)
|
||||
if w.Code != 405 {
|
||||
t.Errorf("expected 405, got %d", w.Code)
|
||||
}
|
||||
if len(srv.allReceivedMail) != 1 {
|
||||
t.Error("GET /clear cleared the mail buffer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPCount(t *testing.T) {
|
||||
srv := mailSrv{}
|
||||
srv.allReceivedMail = []rcvdMail{
|
||||
{From: "a", To: "b"},
|
||||
{From: "a", To: "b"},
|
||||
{From: "a", To: "c"},
|
||||
{From: "c", To: "a"},
|
||||
{From: "c", To: "b"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
URL string
|
||||
Count int
|
||||
}{
|
||||
{URL: "/count", Count: 5},
|
||||
{URL: "/count?to=b", Count: 3},
|
||||
{URL: "/count?to=c", Count: 1},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, test := range tests {
|
||||
w, r := reqAndRecorder(t, "GET", test.URL, nil)
|
||||
buf.Reset()
|
||||
w.Body = &buf
|
||||
|
||||
srv.httpCount(w, r)
|
||||
if w.Code != 200 {
|
||||
t.Errorf("%s: expected 200, got %d", test.URL, w.Code)
|
||||
}
|
||||
n, err := strconv.Atoi(strings.TrimSpace(buf.String()))
|
||||
if err != nil {
|
||||
t.Errorf("%s: expected a number, got '%s'", test.URL, buf.String())
|
||||
} else if n != test.Count {
|
||||
t.Errorf("%s: expected %d, got %d", test.URL, test.Count, n)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,248 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
)
|
||||
|
||||
type mailSrv struct {
|
||||
closeFirst uint
|
||||
allReceivedMail []rcvdMail
|
||||
allMailMutex sync.Mutex
|
||||
connNumber uint
|
||||
connNumberMutex sync.RWMutex
|
||||
logger blog.Logger
|
||||
}
|
||||
|
||||
type rcvdMail struct {
|
||||
From string
|
||||
To string
|
||||
Mail string
|
||||
}
|
||||
|
||||
func expectLine(buf *bufio.Reader, expected string) error {
|
||||
line, _, err := buf.ReadLine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("readline: %v", err)
|
||||
}
|
||||
if string(line) != expected {
|
||||
return fmt.Errorf("Expected %s, got %s", expected, line)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var mailFromRegex = regexp.MustCompile(`^MAIL FROM:<(.*)>\s*BODY=8BITMIME\s*$`)
|
||||
var rcptToRegex = regexp.MustCompile(`^RCPT TO:<(.*)>\s*$`)
|
||||
var smtpErr501 = []byte("501 syntax error in parameters or arguments \r\n")
|
||||
var smtpOk250 = []byte("250 OK \r\n")
|
||||
|
||||
func (srv *mailSrv) handleConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
srv.connNumberMutex.Lock()
|
||||
srv.connNumber++
|
||||
srv.connNumberMutex.Unlock()
|
||||
srv.logger.Infof("mail-test-srv: Got connection from %s", conn.RemoteAddr())
|
||||
|
||||
readBuf := bufio.NewReader(conn)
|
||||
conn.Write([]byte("220 smtp.example.com ESMTP\r\n"))
|
||||
err := expectLine(readBuf, "EHLO localhost")
|
||||
if err != nil {
|
||||
log.Printf("mail-test-srv: %s: %v\n", conn.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
conn.Write([]byte("250-PIPELINING\r\n"))
|
||||
conn.Write([]byte("250-AUTH PLAIN LOGIN\r\n"))
|
||||
conn.Write([]byte("250 8BITMIME\r\n"))
|
||||
// This AUTH PLAIN is the output of: echo -en '\0cert-manager@example.com\0password' | base64
|
||||
// Must match the mail configs for integration tests.
|
||||
err = expectLine(readBuf, "AUTH PLAIN AGNlcnQtbWFuYWdlckBleGFtcGxlLmNvbQBwYXNzd29yZA==")
|
||||
if err != nil {
|
||||
log.Printf("mail-test-srv: %s: %v\n", conn.RemoteAddr(), err)
|
||||
return
|
||||
}
|
||||
conn.Write([]byte("235 2.7.0 Authentication successful\r\n"))
|
||||
srv.logger.Infof("mail-test-srv: Successful auth from %s", conn.RemoteAddr())
|
||||
|
||||
// necessary commands:
|
||||
// MAIL RCPT DATA QUIT
|
||||
|
||||
var fromAddr string
|
||||
var toAddr []string
|
||||
|
||||
clearState := func() {
|
||||
fromAddr = ""
|
||||
toAddr = nil
|
||||
}
|
||||
|
||||
reader := bufio.NewScanner(readBuf)
|
||||
scan:
|
||||
for reader.Scan() {
|
||||
line := reader.Text()
|
||||
cmdSplit := strings.SplitN(line, " ", 2)
|
||||
cmd := cmdSplit[0]
|
||||
switch cmd {
|
||||
case "QUIT":
|
||||
conn.Write([]byte("221 Bye \r\n"))
|
||||
break scan
|
||||
case "RSET":
|
||||
clearState()
|
||||
conn.Write(smtpOk250)
|
||||
case "NOOP":
|
||||
conn.Write(smtpOk250)
|
||||
case "MAIL":
|
||||
srv.connNumberMutex.RLock()
|
||||
if srv.connNumber <= srv.closeFirst {
|
||||
// Half of the time, close cleanly to simulate the server side closing
|
||||
// unexpectedly.
|
||||
if srv.connNumber%2 == 0 {
|
||||
log.Printf(
|
||||
"mail-test-srv: connection # %d < -closeFirst parameter %d, disconnecting client. Bye!\n",
|
||||
srv.connNumber, srv.closeFirst)
|
||||
clearState()
|
||||
conn.Close()
|
||||
} else {
|
||||
// The rest of the time, simulate a stale connection timeout by sending
|
||||
// a SMTP 421 message. This replicates the timeout/close from issue
|
||||
// 2249 - https://github.com/letsencrypt/boulder/issues/2249
|
||||
log.Printf(
|
||||
"mail-test-srv: connection # %d < -closeFirst parameter %d, disconnecting with 421. Bye!\n",
|
||||
srv.connNumber, srv.closeFirst)
|
||||
clearState()
|
||||
conn.Write([]byte("421 1.2.3 foo.bar.baz Error: timeout exceeded \r\n"))
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
srv.connNumberMutex.RUnlock()
|
||||
clearState()
|
||||
matches := mailFromRegex.FindStringSubmatch(line)
|
||||
if matches == nil {
|
||||
log.Panicf("mail-test-srv: %s: MAIL FROM parse error\n", conn.RemoteAddr())
|
||||
}
|
||||
addr, err := mail.ParseAddress(matches[1])
|
||||
if err != nil {
|
||||
log.Panicf("mail-test-srv: %s: addr parse error: %v\n", conn.RemoteAddr(), err)
|
||||
}
|
||||
fromAddr = addr.Address
|
||||
conn.Write(smtpOk250)
|
||||
case "RCPT":
|
||||
matches := rcptToRegex.FindStringSubmatch(line)
|
||||
if matches == nil {
|
||||
conn.Write(smtpErr501)
|
||||
continue
|
||||
}
|
||||
addr, err := mail.ParseAddress(matches[1])
|
||||
if err != nil {
|
||||
log.Panicf("mail-test-srv: %s: addr parse error: %v\n", conn.RemoteAddr(), err)
|
||||
}
|
||||
toAddr = append(toAddr, addr.Address)
|
||||
conn.Write(smtpOk250)
|
||||
case "DATA":
|
||||
conn.Write([]byte("354 Start mail input \r\n"))
|
||||
var msgBuf bytes.Buffer
|
||||
|
||||
for reader.Scan() {
|
||||
line := reader.Text()
|
||||
msgBuf.WriteString(line)
|
||||
msgBuf.WriteString("\r\n")
|
||||
if strings.HasSuffix(msgBuf.String(), "\r\n.\r\n") {
|
||||
break
|
||||
}
|
||||
}
|
||||
if reader.Err() != nil {
|
||||
log.Printf("mail-test-srv: read from %s: %v\n", conn.RemoteAddr(), reader.Err())
|
||||
return
|
||||
}
|
||||
|
||||
mailResult := rcvdMail{
|
||||
From: fromAddr,
|
||||
Mail: msgBuf.String(),
|
||||
}
|
||||
srv.allMailMutex.Lock()
|
||||
for _, rcpt := range toAddr {
|
||||
mailResult.To = rcpt
|
||||
srv.allReceivedMail = append(srv.allReceivedMail, mailResult)
|
||||
log.Printf("mail-test-srv: Got mail: %s -> %s\n", fromAddr, rcpt)
|
||||
}
|
||||
srv.allMailMutex.Unlock()
|
||||
conn.Write([]byte("250 Got mail \r\n"))
|
||||
clearState()
|
||||
}
|
||||
}
|
||||
if reader.Err() != nil {
|
||||
log.Printf("mail-test-srv: read from %s: %s\n", conn.RemoteAddr(), reader.Err())
|
||||
}
|
||||
}
|
||||
|
||||
func (srv *mailSrv) serveSMTP(ctx context.Context, l net.Listener) error {
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
// If the accept call returned an error because the listener has been
|
||||
// closed, then the context should have been canceled too. In that case,
|
||||
// ignore the error.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
go srv.handleConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var listenAPI = flag.String("http", "0.0.0.0:9381", "http port to listen on")
|
||||
var listenSMTP = flag.String("smtp", "0.0.0.0:9380", "smtp port to listen on")
|
||||
var certFilename = flag.String("cert", "", "certificate to serve")
|
||||
var privKeyFilename = flag.String("key", "", "private key for certificate")
|
||||
var closeFirst = flag.Uint("closeFirst", 0, "close first n connections after MAIL for reconnection tests")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(*certFilename, *privKeyFilename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
l, err := tls.Listen("tcp", *listenSMTP, &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Couldn't bind %q for SMTP: %s", *listenSMTP, err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
srv := mailSrv{
|
||||
closeFirst: *closeFirst,
|
||||
logger: cmd.NewLogger(cmd.SyslogConfig{StdoutLevel: 7}),
|
||||
}
|
||||
|
||||
srv.setupHTTP(http.DefaultServeMux)
|
||||
go func() {
|
||||
err := http.ListenAndServe(*listenAPI, http.DefaultServeMux) //nolint: gosec // No request timeout is fine for test-only code.
|
||||
if err != nil {
|
||||
log.Fatalln("Couldn't start HTTP server", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go cmd.FailOnError(srv.serveSMTP(ctx, l), "Failed to accept connection")
|
||||
|
||||
cmd.WaitForSignal()
|
||||
}
|
|
@ -85,7 +85,7 @@ func (ts *testServer) checkToken(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (ts *testServer) createContactsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func (ts *testServer) upsertContactsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ts.checkToken(w, r)
|
||||
|
||||
businessUnitId := r.Header.Get("Pardot-Business-Unit-Id")
|
||||
|
@ -100,19 +100,22 @@ func (ts *testServer) createContactsHandler(w http.ResponseWriter, r *http.Reque
|
|||
return
|
||||
}
|
||||
|
||||
type contactData struct {
|
||||
Email string `json:"email"`
|
||||
type upsertPayload struct {
|
||||
MatchEmail string `json:"matchEmail"`
|
||||
Prospect struct {
|
||||
Email string `json:"email"`
|
||||
} `json:"prospect"`
|
||||
}
|
||||
|
||||
var contact contactData
|
||||
err = json.Unmarshal(body, &contact)
|
||||
var payload upsertPayload
|
||||
err = json.Unmarshal(body, &payload)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if contact.Email == "" {
|
||||
http.Error(w, "Missing 'email' field in request body", http.StatusBadRequest)
|
||||
if payload.MatchEmail == "" || payload.Prospect.Email == "" {
|
||||
http.Error(w, "Missing 'matchEmail' or 'prospect.email' in request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -122,7 +125,7 @@ func (ts *testServer) createContactsHandler(w http.ResponseWriter, r *http.Reque
|
|||
// with a small number of contacts, so it's fine.
|
||||
ts.contacts.created = ts.contacts.created[1:]
|
||||
}
|
||||
ts.contacts.created = append(ts.contacts.created, contact.Email)
|
||||
ts.contacts.created = append(ts.contacts.created, payload.Prospect.Email)
|
||||
ts.contacts.Unlock()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
@ -198,7 +201,7 @@ func main() {
|
|||
|
||||
// Pardot API Server
|
||||
pardotMux := http.NewServeMux()
|
||||
pardotMux.HandleFunc("/api/v5/objects/prospects", ts.createContactsHandler)
|
||||
pardotMux.HandleFunc("/api/v5/objects/prospects/do/upsertLatestByEmail", ts.upsertContactsHandler)
|
||||
pardotMux.HandleFunc("/contacts", ts.queryContactsHandler)
|
||||
|
||||
pardotServer := &http.Server{
|
||||
|
|
|
@ -50,10 +50,6 @@ SERVICES = (
|
|||
8109, 9491, 'publisher.boulder',
|
||||
('./bin/boulder', 'boulder-publisher', '--config', os.path.join(config_dir, 'publisher.json'), '--addr', ':9491', '--debug-addr', ':8109'),
|
||||
None),
|
||||
Service('mail-test-srv',
|
||||
9380, None, None,
|
||||
('./bin/mail-test-srv', '--closeFirst', '5', '--cert', 'test/certs/ipki/localhost/cert.pem', '--key', 'test/certs/ipki/localhost/key.pem'),
|
||||
None),
|
||||
Service('ocsp-responder',
|
||||
8005, None, None,
|
||||
('./bin/boulder', 'ocsp-responder', '--config', os.path.join(config_dir, 'ocsp-responder.json'), '--addr', ':4002', '--debug-addr', ':8005'),
|
||||
|
@ -116,7 +112,7 @@ SERVICES = (
|
|||
Service('bad-key-revoker',
|
||||
8020, None, None,
|
||||
('./bin/boulder', 'bad-key-revoker', '--config', os.path.join(config_dir, 'bad-key-revoker.json'), '--debug-addr', ':8020'),
|
||||
('boulder-ra-1', 'boulder-ra-2', 'mail-test-srv')),
|
||||
('boulder-ra-1', 'boulder-ra-2')),
|
||||
# Note: the nonce-service instances bind to specific ports, not "all interfaces",
|
||||
# because they use their explicitly bound port in calculating the nonce
|
||||
# prefix, which is used by WFEs when deciding where to redeem nonces.
|
||||
|
|
|
@ -524,7 +524,7 @@ def test_highrisk_blocklist():
|
|||
fail with a policy error.
|
||||
"""
|
||||
|
||||
# We include "example.org" in `test/hostname-policy.yaml` in the
|
||||
# We include "example.org" in `test/ident-policy.yaml` in the
|
||||
# HighRiskBlockedNames list so issuing for "foo.example.org" should be
|
||||
# blocked.
|
||||
domain = "foo.example.org"
|
||||
|
@ -538,7 +538,7 @@ def test_wildcard_exactblacklist():
|
|||
should fail with a policy error.
|
||||
"""
|
||||
|
||||
# We include "highrisk.le-test.hoffman-andrews.com" in `test/hostname-policy.yaml`
|
||||
# We include "highrisk.le-test.hoffman-andrews.com" in `test/ident-policy.yaml`
|
||||
# Issuing for "*.le-test.hoffman-andrews.com" should be blocked
|
||||
domain = "*.le-test.hoffman-andrews.com"
|
||||
# We expect this to produce a policy problem
|
||||
|
@ -955,7 +955,7 @@ def test_new_order_policy_errs():
|
|||
"""
|
||||
client = chisel2.make_client(None)
|
||||
|
||||
# 'in-addr.arpa' is present in `test/hostname-policy.yaml`'s
|
||||
# 'in-addr.arpa' is present in `test/ident-policy.yaml`'s
|
||||
# HighRiskBlockedNames list.
|
||||
csr_pem = chisel2.make_csr(["out-addr.in-addr.arpa", "between-addr.in-addr.arpa"])
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ func branch(args []string) error {
|
|||
// components.
|
||||
parts := strings.SplitN(tag, ".", 3)
|
||||
if len(parts) != 3 {
|
||||
return fmt.Errorf("failed to parse patch version from release tag %q", tag)
|
||||
return fmt.Errorf("failed to parse release tag %q as semver", tag)
|
||||
}
|
||||
|
||||
major := parts[0]
|
||||
|
@ -101,7 +101,7 @@ func branch(args []string) error {
|
|||
minor := parts[1]
|
||||
t, err := time.Parse("20060102", minor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected minor portion of release tag to be a ")
|
||||
return fmt.Errorf("expected minor portion of release tag to be a date: %w", err)
|
||||
}
|
||||
if t.Year() < 2015 {
|
||||
return fmt.Errorf("minor portion of release tag appears to be an unrealistic date: %q", t.String())
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
@ -85,63 +86,105 @@ func tag(args []string) error {
|
|||
return fmt.Errorf("invalid flags: %w", err)
|
||||
}
|
||||
|
||||
if len(fs.Args()) > 1 {
|
||||
var branch string
|
||||
switch len(fs.Args()) {
|
||||
case 0:
|
||||
branch = "main"
|
||||
case 1:
|
||||
branch = fs.Arg(0)
|
||||
if !strings.HasPrefix(branch, "release-branch-") {
|
||||
return fmt.Errorf("branch must be 'main' or 'release-branch-...', got %q", branch)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("too many args: %#v", fs.Args())
|
||||
}
|
||||
|
||||
branch := "main"
|
||||
if len(fs.Args()) == 1 {
|
||||
branch = fs.Arg(0)
|
||||
}
|
||||
|
||||
switch {
|
||||
case branch == "main":
|
||||
break
|
||||
case strings.HasPrefix(branch, "release-branch-"):
|
||||
return fmt.Errorf("sorry, tagging hotfix release branches is not yet supported")
|
||||
default:
|
||||
return fmt.Errorf("branch must be 'main' or 'release-branch-...', got %q", branch)
|
||||
}
|
||||
|
||||
// Fetch all of the latest commits on this ref from origin, so that we can
|
||||
// ensure we're tagging the tip of the upstream branch.
|
||||
// ensure we're tagging the tip of the upstream branch, and that we have all
|
||||
// of the extant tags along this branch if its a release branch.
|
||||
_, err = git("fetch", "origin", branch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We use semver's vMajor.Minor.Patch format, where the Major version is
|
||||
// always 0 (no backwards compatibility guarantees), the Minor version is
|
||||
// the date of the release, and the Patch number is zero for normal releases
|
||||
// and only non-zero for hotfix releases.
|
||||
minor := time.Now().Format("20060102")
|
||||
version := fmt.Sprintf("v0.%s.0", minor)
|
||||
message := fmt.Sprintf("Release %s", version)
|
||||
var tag string
|
||||
switch branch {
|
||||
case "main":
|
||||
tag = fmt.Sprintf("v0.%s.0", time.Now().Format("20060102"))
|
||||
default:
|
||||
tag, err = nextTagOnBranch(branch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compute next hotfix tag: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Produce the tag, using -s to PGP sign it. This will fail if a tag with
|
||||
// that name already exists.
|
||||
_, err = git("tag", "-s", "-m", message, version, "origin/"+branch)
|
||||
message := fmt.Sprintf("Release %s", tag)
|
||||
_, err = git("tag", "-s", "-m", message, tag, "origin/"+branch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Show the result of the tagging operation, including the tag message and
|
||||
// signature, and the commit hash and message, but not the diff.
|
||||
out, err := git("show", "-s", version)
|
||||
out, err := git("show", "-s", tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
show(out)
|
||||
|
||||
if push {
|
||||
_, err = git("push", "origin", version)
|
||||
_, err = git("push", "origin", tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Println()
|
||||
fmt.Println("Please inspect the tag above, then run:")
|
||||
fmt.Printf(" git push origin %s\n", version)
|
||||
fmt.Printf(" git push origin %s\n", tag)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextTagOnBranch(branch string) (string, error) {
|
||||
baseVersion := strings.TrimPrefix(branch, "release-branch-")
|
||||
out, err := git("tag", "--list", "--no-column", baseVersion+".*")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to list extant tags on branch %q: %w", branch, err)
|
||||
}
|
||||
|
||||
maxPatch := 0
|
||||
for tag := range strings.SplitSeq(strings.TrimSpace(out), "\n") {
|
||||
parts := strings.SplitN(tag, ".", 3)
|
||||
if len(parts) != 3 {
|
||||
return "", fmt.Errorf("failed to parse release tag %q as semver", tag)
|
||||
}
|
||||
|
||||
major := parts[0]
|
||||
if major != "v0" {
|
||||
return "", fmt.Errorf("expected major portion of prior release tag %q to be 'v0'", tag)
|
||||
}
|
||||
|
||||
minor := parts[1]
|
||||
t, err := time.Parse("20060102", minor)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("expected minor portion of prior release tag %q to be a date: %w", tag, err)
|
||||
}
|
||||
if t.Year() < 2015 {
|
||||
return "", fmt.Errorf("minor portion of prior release tag %q appears to be an unrealistic date: %q", tag, t.String())
|
||||
}
|
||||
|
||||
patch := parts[2]
|
||||
patchInt, err := strconv.Atoi(patch)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("patch portion of prior release tag %q is not an integer: %w", tag, err)
|
||||
}
|
||||
|
||||
if patchInt > maxPatch {
|
||||
maxPatch = patchInt
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s.%d", baseVersion, maxPatch+1), nil
|
||||
}
|
||||
|
|
|
@ -338,6 +338,10 @@ func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (iden
|
|||
|
||||
reqIP, err := netip.ParseAddr(reqHost)
|
||||
if err == nil {
|
||||
// Reject IPv6 addresses with a scope zone (RFCs 4007 & 6874)
|
||||
if reqIP.Zone() != "" {
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: contains scope zone")
|
||||
}
|
||||
err := va.isReservedIPFunc(reqIP)
|
||||
if err != nil {
|
||||
return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: %s", err)
|
||||
|
|
|
@ -360,6 +360,14 @@ func TestExtractRequestTarget(t *testing.T) {
|
|||
ExpectedError: fmt.Errorf("Invalid host in redirect target: " +
|
||||
"IP address is in a reserved address block: [RFC9637]: Documentation"),
|
||||
},
|
||||
{
|
||||
Name: "bare IPv6, scope zone",
|
||||
Req: &http.Request{
|
||||
URL: mustURL("http://[::1%25lo]"),
|
||||
},
|
||||
ExpectedError: fmt.Errorf("Invalid host in redirect target: " +
|
||||
"contains scope zone"),
|
||||
},
|
||||
{
|
||||
Name: "valid HTTP redirect, explicit port",
|
||||
Req: &http.Request{
|
||||
|
|
|
@ -84,6 +84,10 @@ func (va *ValidationAuthorityImpl) tryGetChallengeCert(
|
|||
return nil, nil, validationRecord, fmt.Errorf("can't parse IP address %q: %s", ident.Value, err)
|
||||
}
|
||||
addrs = []netip.Addr{netIP}
|
||||
// This field shouldn't be necessary: it's redundant with the Hostname
|
||||
// field. But Challenge.RecordsSane expects it, and there's no easy way to
|
||||
// special-case IP identifiers within that function.
|
||||
validationRecord.AddressesResolved = addrs
|
||||
default:
|
||||
// This should never happen. The calling function should check the
|
||||
// identifier type.
|
||||
|
|
|
@ -368,10 +368,13 @@ func TestTLSALPN01SuccessDNS(t *testing.T) {
|
|||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
res, err := va.validateTLSALPN01(ctx, identifier.NewDNS("expected"), expectedKeyAuthorization)
|
||||
if err != nil {
|
||||
t.Errorf("Validation failed: %v", err)
|
||||
}
|
||||
if !(core.Challenge{Type: core.ChallengeTypeTLSALPN01, ValidationRecord: res}).RecordsSane() {
|
||||
t.Errorf("got validation record %#v, but want something sane", res)
|
||||
}
|
||||
test.AssertMetricWithLabelsEquals(
|
||||
t, va.metrics.tlsALPNOIDCounter, prometheus.Labels{"oid": IdPeAcmeIdentifier.String()}, 1)
|
||||
|
||||
|
@ -384,10 +387,13 @@ func TestTLSALPN01SuccessIPv4(t *testing.T) {
|
|||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
|
||||
res, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedKeyAuthorization)
|
||||
if err != nil {
|
||||
t.Errorf("Validation failed: %v", err)
|
||||
}
|
||||
if !(core.Challenge{Type: core.ChallengeTypeTLSALPN01, ValidationRecord: res}).RecordsSane() {
|
||||
t.Errorf("got validation record %#v, but want something sane", res)
|
||||
}
|
||||
test.AssertMetricWithLabelsEquals(
|
||||
t, va.metrics.tlsALPNOIDCounter, prometheus.Labels{"oid": IdPeAcmeIdentifier.String()}, 1)
|
||||
|
||||
|
@ -400,10 +406,13 @@ func TestTLSALPN01SuccessIPv6(t *testing.T) {
|
|||
|
||||
va, _ := setup(hs, "", nil, nil)
|
||||
|
||||
_, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("::1")), expectedKeyAuthorization)
|
||||
res, err := va.validateTLSALPN01(ctx, identifier.NewIP(netip.MustParseAddr("::1")), expectedKeyAuthorization)
|
||||
if err != nil {
|
||||
t.Errorf("Validation failed: %v", err)
|
||||
}
|
||||
if !(core.Challenge{Type: core.ChallengeTypeTLSALPN01, ValidationRecord: res}).RecordsSane() {
|
||||
t.Errorf("got validation record %#v, but want something sane", res)
|
||||
}
|
||||
test.AssertMetricWithLabelsEquals(
|
||||
t, va.metrics.tlsALPNOIDCounter, prometheus.Labels{"oid": IdPeAcmeIdentifier.String()}, 1)
|
||||
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
# v1.83.0 (2025-07-02)
|
||||
|
||||
* **Feature**: Added support for directory bucket creation with tags and bucket ARN retrieval in CreateBucket, ListDirectoryBuckets, and HeadBucket operations
|
||||
|
||||
# v1.82.0 (2025-06-25)
|
||||
|
||||
* **Feature**: Adds support for additional server-side encryption mode and storage class values for accessing Amazon FSx data from Amazon S3 using S3 Access Points
|
||||
|
||||
# v1.81.0 (2025-06-18)
|
||||
|
||||
* **Feature**: Added support for renaming objects within the same bucket using the new RenameObject API.
|
||||
|
|
6
vendor/github.com/aws/aws-sdk-go-v2/service/s3/api_op_CompleteMultipartUpload.go
generated
vendored
6
vendor/github.com/aws/aws-sdk-go-v2/service/s3/api_op_CompleteMultipartUpload.go
generated
vendored
|
@ -465,8 +465,10 @@ type CompleteMultipartUploadOutput struct {
|
|||
// If present, indicates the ID of the KMS key that was used for object encryption.
|
||||
SSEKMSKeyId *string
|
||||
|
||||
// The server-side encryption algorithm used when storing this object in Amazon S3
|
||||
// (for example, AES256 , aws:kms ).
|
||||
// The server-side encryption algorithm used when storing this object in Amazon S3.
|
||||
//
|
||||
// When accessing data stored in Amazon FSx file systems using S3 access points,
|
||||
// the only valid server side encryption option is aws:fsx .
|
||||
ServerSideEncryption types.ServerSideEncryption
|
||||
|
||||
// Version ID of the newly created object, in case the bucket has versioning
|
||||
|
|
|
@ -695,6 +695,13 @@ type CopyObjectInput struct {
|
|||
// the same customer managed key that you specified for the directory bucket's
|
||||
// default encryption configuration.
|
||||
//
|
||||
// - S3 access points for Amazon FSx - When accessing data stored in Amazon FSx
|
||||
// file systems using S3 access points, the only valid server side encryption
|
||||
// option is aws:fsx . All Amazon FSx file systems have encryption configured by
|
||||
// default and are encrypted at rest. Data is automatically encrypted before being
|
||||
// written to the file system, and automatically decrypted as it is read. These
|
||||
// processes are handled transparently by Amazon FSx.
|
||||
//
|
||||
// [Using Server-Side Encryption]: https://docs.aws.amazon.com/AmazonS3/latest/dev/serv-side-encryption.html
|
||||
// [Specifying server-side encryption with KMS for new object uploads]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-specifying-kms-encryption.html
|
||||
// [customer managed key]: https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#customer-cmk
|
||||
|
@ -896,7 +903,10 @@ type CopyObjectOutput struct {
|
|||
SSEKMSKeyId *string
|
||||
|
||||
// The server-side encryption algorithm used when you store this object in Amazon
|
||||
// S3 (for example, AES256 , aws:kms , aws:kms:dsse ).
|
||||
// S3 or Amazon FSx.
|
||||
//
|
||||
// When accessing data stored in Amazon FSx file systems using S3 access points,
|
||||
// the only valid server side encryption option is aws:fsx .
|
||||
ServerSideEncryption types.ServerSideEncryption
|
||||
|
||||
// Version ID of the newly created copy.
|
||||
|
|
|
@ -268,6 +268,15 @@ func (in *CreateBucketInput) bindEndpointParams(p *EndpointParameters) {
|
|||
|
||||
type CreateBucketOutput struct {
|
||||
|
||||
// The Amazon Resource Name (ARN) of the S3 bucket. ARNs uniquely identify Amazon
|
||||
// Web Services resources across all of Amazon Web Services.
|
||||
//
|
||||
// This parameter is only supported for S3 directory buckets. For more
|
||||
// information, see [Using tags with directory buckets].
|
||||
//
|
||||
// [Using tags with directory buckets]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/directory-buckets-tagging.html
|
||||
BucketArn *string
|
||||
|
||||
// A forward slash followed by the name of the bucket.
|
||||
Location *string
|
||||
|
||||
|
|
14
vendor/github.com/aws/aws-sdk-go-v2/service/s3/api_op_CreateMultipartUpload.go
generated
vendored
14
vendor/github.com/aws/aws-sdk-go-v2/service/s3/api_op_CreateMultipartUpload.go
generated
vendored
|
@ -686,7 +686,7 @@ type CreateMultipartUploadInput struct {
|
|||
SSEKMSKeyId *string
|
||||
|
||||
// The server-side encryption algorithm used when you store this object in Amazon
|
||||
// S3 (for example, AES256 , aws:kms ).
|
||||
// S3 or Amazon FSx.
|
||||
//
|
||||
// - Directory buckets - For directory buckets, there are only two supported
|
||||
// options for server-side encryption: server-side encryption with Amazon S3
|
||||
|
@ -719,6 +719,13 @@ type CreateMultipartUploadInput struct {
|
|||
// request headers must match the default encryption configuration of the directory
|
||||
// bucket.
|
||||
//
|
||||
// - S3 access points for Amazon FSx - When accessing data stored in Amazon FSx
|
||||
// file systems using S3 access points, the only valid server side encryption
|
||||
// option is aws:fsx . All Amazon FSx file systems have encryption configured by
|
||||
// default and are encrypted at rest. Data is automatically encrypted before being
|
||||
// written to the file system, and automatically decrypted as it is read. These
|
||||
// processes are handled transparently by Amazon FSx.
|
||||
//
|
||||
// [Specifying server-side encryption with KMS for new object uploads]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-specifying-kms-encryption.html
|
||||
// [Protecting data with server-side encryption]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-serv-side-encryption.html
|
||||
// [CopyObject]: https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
|
||||
|
@ -838,7 +845,10 @@ type CreateMultipartUploadOutput struct {
|
|||
SSEKMSKeyId *string
|
||||
|
||||
// The server-side encryption algorithm used when you store this object in Amazon
|
||||
// S3 (for example, AES256 , aws:kms ).
|
||||
// S3 or Amazon FSx.
|
||||
//
|
||||
// When accessing data stored in Amazon FSx file systems using S3 access points,
|
||||
// the only valid server side encryption option is aws:fsx .
|
||||
ServerSideEncryption types.ServerSideEncryption
|
||||
|
||||
// ID for the initiated multipart upload.
|
||||
|
|
|
@ -219,6 +219,13 @@ type CreateSessionInput struct {
|
|||
// Amazon S3 encrypts data with SSE-S3. For more information, see [Protecting data with server-side encryption]in the Amazon S3
|
||||
// User Guide.
|
||||
//
|
||||
// S3 access points for Amazon FSx - When accessing data stored in Amazon FSx file
|
||||
// systems using S3 access points, the only valid server side encryption option is
|
||||
// aws:fsx . All Amazon FSx file systems have encryption configured by default and
|
||||
// are encrypted at rest. Data is automatically encrypted before being written to
|
||||
// the file system, and automatically decrypted as it is read. These processes are
|
||||
// handled transparently by Amazon FSx.
|
||||
//
|
||||
// [Protecting data with server-side encryption]: https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html
|
||||
ServerSideEncryption types.ServerSideEncryption
|
||||
|
||||
|
@ -264,6 +271,9 @@ type CreateSessionOutput struct {
|
|||
|
||||
// The server-side encryption algorithm used when you store objects in the
|
||||
// directory bucket.
|
||||
//
|
||||
// When accessing data stored in Amazon FSx file systems using S3 access points,
|
||||
// the only valid server side encryption option is aws:fsx .
|
||||
ServerSideEncryption types.ServerSideEncryption
|
||||
|
||||
// Metadata pertaining to the operation's result.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue