diff --git a/sa/model.go b/sa/model.go index b61f1bb44..c979dcb2e 100644 --- a/sa/model.go +++ b/sa/model.go @@ -850,10 +850,10 @@ func incidentModelToPB(i incidentModel) sapb.Incident { // incidentSerialModel represents a row in an 'incident_*' table. type incidentSerialModel struct { - Serial string `db:"serial"` - RegistrationID int64 `db:"registrationID"` - OrderID int64 `db:"orderID"` - LastNoticeSent time.Time `db:"lastNoticeSent"` + Serial string `db:"serial"` + RegistrationID *int64 `db:"registrationID"` + OrderID *int64 `db:"orderID"` + LastNoticeSent *time.Time `db:"lastNoticeSent"` } // crlEntryModel has just the certificate status fields necessary to construct diff --git a/sa/model_test.go b/sa/model_test.go index 5b56cc43f..edac2c902 100644 --- a/sa/model_test.go +++ b/sa/model_test.go @@ -16,6 +16,7 @@ import ( "github.com/letsencrypt/boulder/db" "github.com/letsencrypt/boulder/grpc" "github.com/letsencrypt/boulder/probs" + "github.com/letsencrypt/boulder/test/vars" "github.com/letsencrypt/boulder/core" corepb "github.com/letsencrypt/boulder/core/proto" @@ -346,3 +347,52 @@ func makeKey() rsa.PrivateKey { q := bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc=") return rsa.PrivateKey{PublicKey: rsa.PublicKey{N: n, E: e}, D: d, Primes: []*big.Int{p, q}} } + +func TestIncidentSerialModel(t *testing.T) { + testIncidentsDbMap, err := DBMapForTest(vars.DBConnIncidentsFullPerms) + test.AssertNotError(t, err, "Couldn't create test dbMap") + defer test.ResetIncidentsTestDatabase(t) + + // Inserting and retrieving a row with only the serial populated should work. + _, err = testIncidentsDbMap.Exec( + "INSERT INTO incident_foo (serial) VALUES (?)", + "1337", + ) + test.AssertNotError(t, err, "inserting row with only serial") + + var res1 incidentSerialModel + err = testIncidentsDbMap.SelectOne( + &res1, + "SELECT * FROM incident_foo WHERE serial = ?", + "1337", + ) + test.AssertNotError(t, err, "selecting row with only serial") + + test.AssertEquals(t, res1.Serial, "1337") + test.AssertBoxedNil(t, res1.RegistrationID, "registrationID should be NULL") + test.AssertBoxedNil(t, res1.OrderID, "orderID should be NULL") + test.AssertBoxedNil(t, res1.LastNoticeSent, "lastNoticeSent should be NULL") + + // Inserting and retrieving a row with all columns populated should work. + _, err = testIncidentsDbMap.Exec( + "INSERT INTO incident_foo (serial, registrationID, orderID, lastNoticeSent) VALUES (?, ?, ?, ?)", + "1338", + 1, + 2, + time.Date(2023, 06, 29, 16, 9, 00, 00, time.UTC), + ) + test.AssertNotError(t, err, "inserting row with only serial") + + var res2 incidentSerialModel + err = testIncidentsDbMap.SelectOne( + &res2, + "SELECT * FROM incident_foo WHERE serial = ?", + "1338", + ) + test.AssertNotError(t, err, "selecting row with only serial") + + test.AssertEquals(t, res2.Serial, "1338") + test.AssertEquals(t, *res2.RegistrationID, int64(1)) + test.AssertEquals(t, *res2.OrderID, int64(2)) + test.AssertEquals(t, *res2.LastNoticeSent, time.Date(2023, 06, 29, 16, 9, 00, 00, time.UTC)) +} diff --git a/sa/proto/sa.pb.go b/sa/proto/sa.pb.go index d0acb71f2..74a6c4d84 100644 --- a/sa/proto/sa.pb.go +++ b/sa/proto/sa.pb.go @@ -2351,9 +2351,9 @@ type IncidentSerial struct { unknownFields protoimpl.UnknownFields Serial string `protobuf:"bytes,1,opt,name=serial,proto3" json:"serial,omitempty"` - RegistrationID int64 `protobuf:"varint,2,opt,name=registrationID,proto3" json:"registrationID,omitempty"` - OrderID int64 `protobuf:"varint,3,opt,name=orderID,proto3" json:"orderID,omitempty"` - LastNoticeSent int64 `protobuf:"varint,4,opt,name=lastNoticeSent,proto3" json:"lastNoticeSent,omitempty"` // Unix timestamp (nanoseconds) + RegistrationID int64 `protobuf:"varint,2,opt,name=registrationID,proto3" json:"registrationID,omitempty"` // May be 0 (NULL) + OrderID int64 `protobuf:"varint,3,opt,name=orderID,proto3" json:"orderID,omitempty"` // May be 0 (NULL) + LastNoticeSent int64 `protobuf:"varint,4,opt,name=lastNoticeSent,proto3" json:"lastNoticeSent,omitempty"` // Unix timestamp (nanoseconds), may be 0 (NULL) } func (x *IncidentSerial) Reset() { diff --git a/sa/proto/sa.proto b/sa/proto/sa.proto index 86efac491..f4778c602 100644 --- a/sa/proto/sa.proto +++ b/sa/proto/sa.proto @@ -334,9 +334,9 @@ message SerialsForIncidentRequest { message IncidentSerial { string serial = 1; - int64 registrationID = 2; - int64 orderID = 3; - int64 lastNoticeSent = 4; // Unix timestamp (nanoseconds) + int64 registrationID = 2; // May be 0 (NULL) + int64 orderID = 3; // May be 0 (NULL) + int64 lastNoticeSent = 4; // Unix timestamp (nanoseconds), may be 0 (NULL) } message GetRevokedCertsRequest { diff --git a/sa/sa_test.go b/sa/sa_test.go index 893df9044..3dd44da4c 100644 --- a/sa/sa_test.go +++ b/sa/sa_test.go @@ -2926,6 +2926,8 @@ func TestIncidentsForSerial(t *testing.T) { test.AssertNotError(t, err, "Couldn't create test dbMap") defer test.ResetIncidentsTestDatabase(t) + weekAgo := sa.clk.Now().Add(-time.Hour * 24 * 7) + // Add a disabled incident. err = testSADbMap.Insert(&incidentModel{ SerialTable: "incident_foo", @@ -2950,11 +2952,12 @@ func TestIncidentsForSerial(t *testing.T) { test.AssertNotError(t, err, "Failed to insert enabled incident") // Add a row to the incident table with serial '1338'. + one := int64(1) affectedCertA := incidentSerialModel{ Serial: "1338", - RegistrationID: 1, - OrderID: 1, - LastNoticeSent: sa.clk.Now().Add(time.Hour * 24 * 7), + RegistrationID: &one, + OrderID: &one, + LastNoticeSent: &weekAgo, } _, err = testIncidentsDbMap.Exec( fmt.Sprintf("INSERT INTO incident_bar (%s) VALUES ('%s', %d, %d, '%s')", @@ -2973,11 +2976,12 @@ func TestIncidentsForSerial(t *testing.T) { test.AssertEquals(t, len(result.Incidents), 0) // Add a row to the incident table with serial '1337'. + two := int64(2) affectedCertB := incidentSerialModel{ Serial: "1337", - RegistrationID: 2, - OrderID: 2, - LastNoticeSent: sa.clk.Now().Add(time.Hour * 24 * 7), + RegistrationID: &two, + OrderID: &two, + LastNoticeSent: &weekAgo, } _, err = testIncidentsDbMap.Exec( fmt.Sprintf("INSERT INTO incident_bar (%s) VALUES ('%s', %d, %d, '%s')", diff --git a/sa/saro.go b/sa/saro.go index 78de81436..5a5054e57 100644 --- a/sa/saro.go +++ b/sa/saro.go @@ -1205,7 +1205,9 @@ func (ssa *SQLStorageAuthority) IncidentsForSerial(ctx context.Context, req *sap // SerialsForIncident queries the provided incident table and returns the // resulting rows as a stream of `*sapb.IncidentSerial`s. An `io.EOF` error // signals that there are no more serials to send. If the incident table in -// question contains zero rows, only an `io.EOF` error is returned. +// question contains zero rows, only an `io.EOF` error is returned. The +// IncidentSerial messages returned may have the zero-value for their OrderID, +// RegistrationID, and LastNoticeSent fields, if those are NULL in the database. func (ssa *SQLStorageAuthorityRO) SerialsForIncident(req *sapb.SerialsForIncidentRequest, stream sapb.StorageAuthorityReadOnly_SerialsForIncidentServer) error { if req.IncidentTable == "" { return errIncompleteRequest @@ -1235,13 +1237,20 @@ func (ssa *SQLStorageAuthorityRO) SerialsForIncident(req *sapb.SerialsForInciden return err } - err = stream.Send( - &sapb.IncidentSerial{ - Serial: ism.Serial, - RegistrationID: ism.RegistrationID, - OrderID: ism.OrderID, - LastNoticeSent: ism.LastNoticeSent.UnixNano(), - }) + ispb := &sapb.IncidentSerial{ + Serial: ism.Serial, + } + if ism.RegistrationID != nil { + ispb.RegistrationID = *ism.RegistrationID + } + if ism.OrderID != nil { + ispb.OrderID = *ism.OrderID + } + if ism.LastNoticeSent != nil { + ispb.LastNoticeSent = ism.LastNoticeSent.UnixNano() + } + + err = stream.Send(ispb) if err != nil { return err }