Allow reading incident rows with NULL columns (#6961)

Fixes https://github.com/letsencrypt/boulder/issues/6960
This commit is contained in:
Aaron Gable 2023-06-30 08:29:16 -07:00 committed by GitHub
parent 08017e436e
commit bd29cc430f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 24 deletions

View File

@ -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

View File

@ -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))
}

View File

@ -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() {

View File

@ -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 {

View File

@ -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')",

View File

@ -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
}