github.com/letsencrypt/boulder@v0.20251208.0/sa/sa.go (about) 1 package sa 2 3 import ( 4 "context" 5 "crypto/x509" 6 "database/sql" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "google.golang.org/protobuf/proto" 11 "strings" 12 "time" 13 14 "github.com/go-jose/go-jose/v4" 15 "github.com/jmhodges/clock" 16 "github.com/prometheus/client_golang/prometheus" 17 "github.com/prometheus/client_golang/prometheus/promauto" 18 "google.golang.org/protobuf/types/known/emptypb" 19 "google.golang.org/protobuf/types/known/timestamppb" 20 21 "github.com/letsencrypt/boulder/core" 22 corepb "github.com/letsencrypt/boulder/core/proto" 23 "github.com/letsencrypt/boulder/db" 24 berrors "github.com/letsencrypt/boulder/errors" 25 "github.com/letsencrypt/boulder/features" 26 bgrpc "github.com/letsencrypt/boulder/grpc" 27 "github.com/letsencrypt/boulder/identifier" 28 blog "github.com/letsencrypt/boulder/log" 29 "github.com/letsencrypt/boulder/revocation" 30 sapb "github.com/letsencrypt/boulder/sa/proto" 31 "github.com/letsencrypt/boulder/unpause" 32 ) 33 34 var ( 35 errIncompleteRequest = errors.New("incomplete gRPC request message") 36 ) 37 38 // SQLStorageAuthority defines a Storage Authority. 39 // 40 // Note that although SQLStorageAuthority does have methods wrapping all of the 41 // read-only methods provided by the SQLStorageAuthorityRO, those wrapper 42 // implementations are in saro.go, next to the real implementations. 43 type SQLStorageAuthority struct { 44 sapb.UnsafeStorageAuthorityServer 45 46 *SQLStorageAuthorityRO 47 48 dbMap *db.WrappedMap 49 50 // rateLimitWriteErrors is a Counter for the number of times 51 // a ratelimit update transaction failed during AddCertificate request 52 // processing. We do not fail the overall AddCertificate call when ratelimit 53 // transactions fail and so use this stat to maintain visibility into the rate 54 // this occurs. 55 rateLimitWriteErrors prometheus.Counter 56 } 57 58 var _ sapb.StorageAuthorityServer = (*SQLStorageAuthority)(nil) 59 60 // NewSQLStorageAuthorityWrapping provides persistence using a SQL backend for 61 // Boulder. It takes a read-only storage authority to wrap, which is useful if 62 // you are constructing both types of implementations and want to share 63 // read-only database connections between them. 64 func NewSQLStorageAuthorityWrapping( 65 ssaro *SQLStorageAuthorityRO, 66 dbMap *db.WrappedMap, 67 stats prometheus.Registerer, 68 ) (*SQLStorageAuthority, error) { 69 rateLimitWriteErrors := promauto.With(stats).NewCounter(prometheus.CounterOpts{ 70 Name: "rate_limit_write_errors", 71 Help: "number of failed ratelimit update transactions during AddCertificate", 72 }) 73 74 ssa := &SQLStorageAuthority{ 75 SQLStorageAuthorityRO: ssaro, 76 dbMap: dbMap, 77 rateLimitWriteErrors: rateLimitWriteErrors, 78 } 79 80 return ssa, nil 81 } 82 83 // NewSQLStorageAuthority provides persistence using a SQL backend for 84 // Boulder. It constructs its own read-only storage authority to wrap. 85 func NewSQLStorageAuthority( 86 dbMap *db.WrappedMap, 87 dbReadOnlyMap *db.WrappedMap, 88 dbIncidentsMap *db.WrappedMap, 89 parallelismPerRPC int, 90 lagFactor time.Duration, 91 clk clock.Clock, 92 logger blog.Logger, 93 stats prometheus.Registerer, 94 ) (*SQLStorageAuthority, error) { 95 ssaro, err := NewSQLStorageAuthorityRO( 96 dbReadOnlyMap, dbIncidentsMap, stats, parallelismPerRPC, lagFactor, clk, logger) 97 if err != nil { 98 return nil, err 99 } 100 101 return NewSQLStorageAuthorityWrapping(ssaro, dbMap, stats) 102 } 103 104 // NewRegistration stores a new Registration 105 func (ssa *SQLStorageAuthority) NewRegistration(ctx context.Context, req *corepb.Registration) (*corepb.Registration, error) { 106 if len(req.Key) == 0 { 107 return nil, errIncompleteRequest 108 } 109 110 reg, err := registrationPbToModel(req) 111 if err != nil { 112 return nil, err 113 } 114 115 reg.CreatedAt = ssa.clk.Now() 116 117 err = ssa.dbMap.Insert(ctx, reg) 118 if err != nil { 119 if db.IsDuplicate(err) { 120 // duplicate entry error can only happen when jwk_sha256 collides, indicate 121 // to caller that the provided key is already in use 122 return nil, berrors.DuplicateError("key is already in use for a different account") 123 } 124 return nil, err 125 } 126 return registrationModelToPb(reg) 127 } 128 129 // UpdateRegistrationKey stores an updated key in a Registration. 130 func (ssa *SQLStorageAuthority) UpdateRegistrationKey(ctx context.Context, req *sapb.UpdateRegistrationKeyRequest) (*corepb.Registration, error) { 131 if core.IsAnyNilOrZero(req.RegistrationID, req.Jwk) { 132 return nil, errIncompleteRequest 133 } 134 135 // Even though we don't need to convert from JSON to an in-memory JSONWebKey 136 // for the sake of the `Key` field, we do need to do the conversion in order 137 // to compute the SHA256 key digest. 138 var jwk jose.JSONWebKey 139 err := jwk.UnmarshalJSON(req.Jwk) 140 if err != nil { 141 return nil, fmt.Errorf("parsing JWK: %w", err) 142 } 143 sha, err := core.KeyDigestB64(jwk.Key) 144 if err != nil { 145 return nil, fmt.Errorf("computing key digest: %w", err) 146 } 147 148 result, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 149 result, err := tx.ExecContext(ctx, 150 "UPDATE registrations SET jwk = ?, jwk_sha256 = ? WHERE id = ? LIMIT 1", 151 req.Jwk, 152 sha, 153 req.RegistrationID, 154 ) 155 if err != nil { 156 if db.IsDuplicate(err) { 157 // duplicate entry error can only happen when jwk_sha256 collides, indicate 158 // to caller that the provided key is already in use 159 return nil, berrors.DuplicateError("key is already in use for a different account") 160 } 161 return nil, err 162 } 163 rowsAffected, err := result.RowsAffected() 164 if err != nil || rowsAffected != 1 { 165 return nil, berrors.InternalServerError("no registration ID '%d' updated with new jwk", req.RegistrationID) 166 } 167 168 updatedRegistrationModel, err := selectRegistration(ctx, tx, "id", req.RegistrationID) 169 if err != nil { 170 if db.IsNoRows(err) { 171 return nil, berrors.NotFoundError("registration with ID '%d' not found", req.RegistrationID) 172 } 173 return nil, err 174 } 175 updatedRegistration, err := registrationModelToPb(updatedRegistrationModel) 176 if err != nil { 177 return nil, err 178 } 179 180 return updatedRegistration, nil 181 }) 182 if overallError != nil { 183 return nil, overallError 184 } 185 186 return result.(*corepb.Registration), nil 187 } 188 189 // AddSerial writes a record of a serial number generation to the DB. 190 func (ssa *SQLStorageAuthority) AddSerial(ctx context.Context, req *sapb.AddSerialRequest) (*emptypb.Empty, error) { 191 if core.IsAnyNilOrZero(req.Serial, req.RegID, req.Created, req.Expires) { 192 return nil, errIncompleteRequest 193 } 194 err := ssa.dbMap.Insert(ctx, &recordedSerialModel{ 195 Serial: req.Serial, 196 RegistrationID: req.RegID, 197 Created: req.Created.AsTime(), 198 Expires: req.Expires.AsTime(), 199 }) 200 if err != nil { 201 return nil, err 202 } 203 return &emptypb.Empty{}, nil 204 } 205 206 // AddPrecertificate writes a record of a linting certificate to the database. 207 // 208 // Note: The name "AddPrecertificate" is a historical artifact, and this is now 209 // always called with a linting certificate. See #6807. 210 // 211 // Note: this is not idempotent: it does not protect against inserting the same 212 // certificate multiple times. Calling code needs to first insert the cert's 213 // serial into the Serials table to ensure uniqueness. 214 func (ssa *SQLStorageAuthority) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest) (*emptypb.Empty, error) { 215 if core.IsAnyNilOrZero(req.Der, req.RegID, req.IssuerNameID, req.Issued) { 216 return nil, errIncompleteRequest 217 } 218 parsed, err := x509.ParseCertificate(req.Der) 219 if err != nil { 220 return nil, err 221 } 222 serialHex := core.SerialToString(parsed.SerialNumber) 223 224 preCertModel := &lintingCertModel{ 225 Serial: serialHex, 226 RegistrationID: req.RegID, 227 DER: req.Der, 228 Issued: req.Issued.AsTime(), 229 Expires: parsed.NotAfter, 230 } 231 232 _, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 233 // Select to see if precert exists 234 var row struct { 235 Count int64 236 } 237 err := tx.SelectOne(ctx, &row, "SELECT COUNT(*) as count FROM precertificates WHERE serial=?", serialHex) 238 if err != nil { 239 return nil, err 240 } 241 if row.Count > 0 { 242 return nil, berrors.DuplicateError("cannot add a duplicate cert") 243 } 244 245 err = tx.Insert(ctx, preCertModel) 246 if err != nil { 247 return nil, err 248 } 249 250 // An arbitrary, but valid date for fields revokedDate and lastExpirationNagSent. 251 // These fields in the database are NOT NULL so we can't omit them; and we don't 252 // want to pass `time.Time{}` because that results in inserts of `0000-00-00`, which 253 // is forbidden in strict mode (when NO_ZERO_DATE is on). 254 dummyDate := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 255 256 status := core.OCSPStatusGood 257 cs := &certificateStatusModel{ 258 Serial: serialHex, 259 Status: status, 260 OCSPLastUpdated: ssa.clk.Now(), 261 RevokedDate: dummyDate, 262 RevokedReason: 0, 263 LastExpirationNagSent: dummyDate, 264 NotAfter: parsed.NotAfter, 265 IsExpired: false, 266 IssuerID: req.IssuerNameID, 267 } 268 err = ssa.dbMap.Insert(ctx, cs) 269 if err != nil { 270 return nil, err 271 } 272 273 idents := identifier.FromCert(parsed) 274 275 isRenewal, err := ssa.checkFQDNSetExists( 276 ctx, 277 tx.SelectOne, 278 idents) 279 if err != nil { 280 return nil, err 281 } 282 283 err = addIssuedNames(ctx, tx, parsed, isRenewal) 284 if err != nil { 285 return nil, err 286 } 287 288 err = addKeyHash(ctx, tx, parsed) 289 if err != nil { 290 return nil, err 291 } 292 293 return nil, nil 294 }) 295 if overallError != nil { 296 return nil, overallError 297 } 298 299 return &emptypb.Empty{}, nil 300 } 301 302 // AddCertificate stores an issued certificate, returning an error if it is a 303 // duplicate or if any other failure occurs. 304 func (ssa *SQLStorageAuthority) AddCertificate(ctx context.Context, req *sapb.AddCertificateRequest) (*emptypb.Empty, error) { 305 if core.IsAnyNilOrZero(req.Der, req.RegID, req.Issued) { 306 return nil, errIncompleteRequest 307 } 308 parsedCertificate, err := x509.ParseCertificate(req.Der) 309 if err != nil { 310 return nil, err 311 } 312 digest := core.Fingerprint256(req.Der) 313 serial := core.SerialToString(parsedCertificate.SerialNumber) 314 315 cert := &core.Certificate{ 316 RegistrationID: req.RegID, 317 Serial: serial, 318 Digest: digest, 319 DER: req.Der, 320 Issued: req.Issued.AsTime(), 321 Expires: parsedCertificate.NotAfter, 322 } 323 324 _, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 325 // Select to see if cert exists 326 var row struct { 327 Count int64 328 } 329 err := tx.SelectOne(ctx, &row, "SELECT COUNT(*) as count FROM certificates WHERE serial=?", serial) 330 if err != nil { 331 return nil, err 332 } 333 if row.Count > 0 { 334 return nil, berrors.DuplicateError("cannot add a duplicate cert") 335 } 336 337 // Save the final certificate 338 err = tx.Insert(ctx, cert) 339 if err != nil { 340 return nil, err 341 } 342 343 return nil, err 344 }) 345 if overallError != nil { 346 return nil, overallError 347 } 348 349 // In a separate transaction, perform the work required to update the table 350 // used for order reuse. Since the effect of failing the write is just a 351 // missed opportunity to reuse an order, we choose to not fail the 352 // AddCertificate operation if this update transaction fails. 353 _, fqdnTransactionErr := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 354 // Update the FQDN sets now that there is a final certificate to ensure 355 // reuse is determined correctly. 356 err = addFQDNSet( 357 ctx, 358 tx, 359 identifier.FromCert(parsedCertificate), 360 core.SerialToString(parsedCertificate.SerialNumber), 361 parsedCertificate.NotBefore, 362 parsedCertificate.NotAfter, 363 ) 364 if err != nil { 365 return nil, err 366 } 367 368 return nil, nil 369 }) 370 // If the FQDN sets transaction failed, increment a stat and log a warning 371 // but don't return an error from AddCertificate. 372 if fqdnTransactionErr != nil { 373 ssa.rateLimitWriteErrors.Inc() 374 ssa.log.AuditErrf("failed AddCertificate FQDN sets insert transaction: %v", fqdnTransactionErr) 375 } 376 377 return &emptypb.Empty{}, nil 378 } 379 380 // DeactivateRegistration deactivates a currently valid registration 381 func (ssa *SQLStorageAuthority) DeactivateRegistration(ctx context.Context, req *sapb.RegistrationID) (*corepb.Registration, error) { 382 if req == nil || req.Id == 0 { 383 return nil, errIncompleteRequest 384 } 385 386 result, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 387 result, err := tx.ExecContext(ctx, 388 "UPDATE registrations SET status = ? WHERE status = ? AND id = ? LIMIT 1", 389 string(core.StatusDeactivated), 390 string(core.StatusValid), 391 req.Id, 392 ) 393 if err != nil { 394 return nil, fmt.Errorf("deactivating account %d: %w", req.Id, err) 395 } 396 rowsAffected, err := result.RowsAffected() 397 if err != nil { 398 return nil, fmt.Errorf("deactivating account %d: %w", req.Id, err) 399 } 400 if rowsAffected == 0 { 401 return nil, berrors.NotFoundError("no active account with id %d", req.Id) 402 } else if rowsAffected > 1 { 403 return nil, berrors.InternalServerError("unexpectedly deactivated multiple accounts with id %d", req.Id) 404 } 405 406 updatedRegistrationModel, err := selectRegistration(ctx, tx, "id", req.Id) 407 if err != nil { 408 if db.IsNoRows(err) { 409 return nil, berrors.NotFoundError("fetching account %d: no rows found", req.Id) 410 } 411 return nil, fmt.Errorf("fetching account %d: %w", req.Id, err) 412 } 413 414 updatedRegistration, err := registrationModelToPb(updatedRegistrationModel) 415 if err != nil { 416 return nil, err 417 } 418 419 return updatedRegistration, nil 420 }) 421 if overallError != nil { 422 return nil, overallError 423 } 424 425 res, ok := result.(*corepb.Registration) 426 if !ok { 427 return nil, fmt.Errorf("unexpected casting failure in DeactivateRegistration") 428 } 429 430 return res, nil 431 } 432 433 // DeactivateAuthorization2 deactivates a currently valid or pending authorization. 434 func (ssa *SQLStorageAuthority) DeactivateAuthorization2(ctx context.Context, req *sapb.AuthorizationID2) (*emptypb.Empty, error) { 435 if req.Id == 0 { 436 return nil, errIncompleteRequest 437 } 438 439 _, err := ssa.dbMap.ExecContext(ctx, 440 `UPDATE authz2 SET status = :deactivated WHERE id = :id and status IN (:valid,:pending)`, 441 map[string]any{ 442 "deactivated": statusUint(core.StatusDeactivated), 443 "id": req.Id, 444 "valid": statusUint(core.StatusValid), 445 "pending": statusUint(core.StatusPending), 446 }, 447 ) 448 if err != nil { 449 return nil, err 450 } 451 return &emptypb.Empty{}, nil 452 } 453 454 // NewOrderAndAuthzs adds the given authorizations to the database, adds their 455 // autogenerated IDs to the given order, and then adds the order to the db. 456 // This is done inside a single transaction to prevent situations where new 457 // authorizations are created, but then their corresponding order is never 458 // created, leading to "invisible" pending authorizations. 459 func (ssa *SQLStorageAuthority) NewOrderAndAuthzs(ctx context.Context, req *sapb.NewOrderAndAuthzsRequest) (*corepb.Order, error) { 460 if req.NewOrder == nil { 461 return nil, errIncompleteRequest 462 } 463 if len(req.NewAuthzs) == 0 && len(req.NewOrder.V2Authorizations) == 0 { 464 return nil, errIncompleteRequest 465 } 466 467 for _, authz := range req.NewAuthzs { 468 if authz.RegistrationID != req.NewOrder.RegistrationID { 469 // This is a belt-and-suspenders check. These were just created by the RA, 470 // so their RegIDs should match. But if they don't, the consequences would 471 // be very bad, so we do an extra check here. 472 return nil, errors.New("new order and authzs must all be associated with same account") 473 } 474 } 475 476 output, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 477 // First, insert all of the new authorizations and record their IDs. 478 newAuthzIDs := make([]int64, 0, len(req.NewAuthzs)) 479 for _, authz := range req.NewAuthzs { 480 am, err := newAuthzReqToModel(authz, req.NewOrder.CertificateProfileName) 481 if err != nil { 482 return nil, err 483 } 484 err = tx.Insert(ctx, am) 485 if err != nil { 486 return nil, err 487 } 488 newAuthzIDs = append(newAuthzIDs, am.ID) 489 } 490 491 // Combine the already-existing and newly-created authzs. 492 allAuthzIds := append(req.NewOrder.V2Authorizations, newAuthzIDs...) 493 494 // Second, insert the new order. 495 created := ssa.clk.Now() 496 var encodedAuthzs []byte 497 var err error 498 if features.Get().StoreAuthzsInOrders { 499 encodedAuthzs, err = proto.Marshal(&sapb.Authzs{ 500 AuthzIDs: allAuthzIds, 501 }) 502 if err != nil { 503 return nil, err 504 } 505 } 506 507 om := orderModel{ 508 RegistrationID: req.NewOrder.RegistrationID, 509 Expires: req.NewOrder.Expires.AsTime(), 510 Created: created, 511 CertificateProfileName: &req.NewOrder.CertificateProfileName, 512 Replaces: &req.NewOrder.Replaces, 513 Authzs: encodedAuthzs, 514 } 515 err = tx.Insert(ctx, &om) 516 if err != nil { 517 return nil, err 518 } 519 orderID := om.ID 520 521 // Third, insert all of the orderToAuthz relations. 522 inserter, err := db.NewMultiInserter("orderToAuthz2", []string{"orderID", "authzID"}) 523 if err != nil { 524 return nil, err 525 } 526 for _, id := range allAuthzIds { 527 err := inserter.Add([]any{orderID, id}) 528 if err != nil { 529 return nil, err 530 } 531 } 532 err = inserter.Insert(ctx, tx) 533 if err != nil { 534 return nil, err 535 } 536 537 // Fourth, insert the FQDNSet entry for the order. 538 err = addOrderFQDNSet(ctx, tx, identifier.FromProtoSlice(req.NewOrder.Identifiers), orderID, req.NewOrder.RegistrationID, req.NewOrder.Expires.AsTime()) 539 if err != nil { 540 return nil, err 541 } 542 543 if req.NewOrder.ReplacesSerial != "" { 544 // Update the replacementOrders table to indicate that this order 545 // replaces the provided certificate serial. 546 err := addReplacementOrder(ctx, tx, req.NewOrder.ReplacesSerial, orderID, req.NewOrder.Expires.AsTime()) 547 if err != nil { 548 return nil, err 549 } 550 } 551 552 // Get the partial Authorization objects for the order 553 authzValidityInfo, err := getAuthorizationStatuses(ctx, tx, allAuthzIds) 554 // If there was an error getting the authorizations, return it immediately 555 if err != nil { 556 return nil, err 557 } 558 559 // Finally, build the overall Order PB. 560 res := &corepb.Order{ 561 // ID and Created were auto-populated on the order model when it was inserted. 562 Id: orderID, 563 Created: timestamppb.New(created), 564 // These are carried over from the original request unchanged. 565 RegistrationID: req.NewOrder.RegistrationID, 566 Expires: req.NewOrder.Expires, 567 Identifiers: req.NewOrder.Identifiers, 568 // This includes both reused and newly created authz IDs. 569 V2Authorizations: allAuthzIds, 570 // A new order is never processing because it can't be finalized yet. 571 BeganProcessing: false, 572 // An empty string is allowed. When the RA retrieves the order and 573 // transmits it to the CA, the empty string will take the value of 574 // DefaultCertProfileName from the //issuance package. 575 CertificateProfileName: req.NewOrder.CertificateProfileName, 576 Replaces: req.NewOrder.Replaces, 577 } 578 579 // Calculate the order status before returning it. Since it may have reused 580 // all valid authorizations the order may be "born" in a ready status. 581 status, err := statusForOrder(res, authzValidityInfo, ssa.clk.Now()) 582 if err != nil { 583 return nil, err 584 } 585 res.Status = status 586 587 return res, nil 588 }) 589 if err != nil { 590 return nil, err 591 } 592 593 order, ok := output.(*corepb.Order) 594 if !ok { 595 return nil, fmt.Errorf("casting error in NewOrderAndAuthzs") 596 } 597 598 return order, nil 599 } 600 601 // SetOrderProcessing updates an order from pending status to processing 602 // status by updating the `beganProcessing` field of the corresponding 603 // Order table row in the DB. 604 func (ssa *SQLStorageAuthority) SetOrderProcessing(ctx context.Context, req *sapb.OrderRequest) (*emptypb.Empty, error) { 605 if req.Id == 0 { 606 return nil, errIncompleteRequest 607 } 608 _, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 609 result, err := tx.ExecContext(ctx, ` 610 UPDATE orders 611 SET beganProcessing = ? 612 WHERE id = ? 613 AND beganProcessing = ?`, 614 true, 615 req.Id, 616 false) 617 if err != nil { 618 return nil, berrors.InternalServerError("error updating order to beganProcessing status") 619 } 620 621 n, err := result.RowsAffected() 622 if err != nil || n == 0 { 623 return nil, berrors.OrderNotReadyError("Order was already processing. This may indicate your client finalized the same order multiple times, possibly due to a client bug.") 624 } 625 626 return nil, nil 627 }) 628 if overallError != nil { 629 return nil, overallError 630 } 631 return &emptypb.Empty{}, nil 632 } 633 634 // SetOrderError updates a provided Order's error field. 635 func (ssa *SQLStorageAuthority) SetOrderError(ctx context.Context, req *sapb.SetOrderErrorRequest) (*emptypb.Empty, error) { 636 if req.Id == 0 || req.Error == nil { 637 return nil, errIncompleteRequest 638 } 639 640 errJSON, err := json.Marshal(req.Error) 641 if err != nil { 642 return nil, err 643 } 644 if len(errJSON) > mediumBlobSize { 645 return nil, fmt.Errorf("error object is too large to store in the database") 646 } 647 648 _, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 649 result, err := tx.ExecContext(ctx, ` 650 UPDATE orders 651 SET error = ? 652 WHERE id = ?`, 653 errJSON, 654 req.Id) 655 if err != nil { 656 return nil, berrors.InternalServerError("error updating order error field") 657 } 658 659 n, err := result.RowsAffected() 660 if err != nil || n == 0 { 661 return nil, berrors.InternalServerError("no order updated with new error field") 662 } 663 664 return nil, nil 665 }) 666 if overallError != nil { 667 return nil, overallError 668 } 669 return &emptypb.Empty{}, nil 670 } 671 672 // FinalizeOrder finalizes a provided *corepb.Order by persisting the 673 // CertificateSerial and a valid status to the database. No fields other than 674 // CertificateSerial and the order ID on the provided order are processed (e.g. 675 // this is not a generic update RPC). 676 func (ssa *SQLStorageAuthority) FinalizeOrder(ctx context.Context, req *sapb.FinalizeOrderRequest) (*emptypb.Empty, error) { 677 if req.Id == 0 || req.CertificateSerial == "" { 678 return nil, errIncompleteRequest 679 } 680 _, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 681 result, err := tx.ExecContext(ctx, ` 682 UPDATE orders 683 SET certificateSerial = ? 684 WHERE id = ? AND 685 beganProcessing = true`, 686 req.CertificateSerial, 687 req.Id) 688 if err != nil { 689 return nil, berrors.InternalServerError("error updating order for finalization") 690 } 691 692 n, err := result.RowsAffected() 693 if err != nil || n == 0 { 694 return nil, berrors.InternalServerError("no order updated for finalization") 695 } 696 697 // Delete the orderFQDNSet row for the order now that it has been finalized. 698 // We use this table for order reuse and should not reuse a finalized order. 699 err = deleteOrderFQDNSet(ctx, tx, req.Id) 700 if err != nil { 701 return nil, err 702 } 703 704 err = setReplacementOrderFinalized(ctx, tx, req.Id) 705 if err != nil { 706 return nil, err 707 } 708 709 return nil, nil 710 }) 711 if overallError != nil { 712 return nil, overallError 713 } 714 return &emptypb.Empty{}, nil 715 } 716 717 // FinalizeAuthorization2 moves a pending authorization to either the valid or invalid status. If 718 // the authorization is being moved to invalid the validationError field must be set. If the 719 // authorization is being moved to valid the validationRecord and expires fields must be set. 720 func (ssa *SQLStorageAuthority) FinalizeAuthorization2(ctx context.Context, req *sapb.FinalizeAuthorizationRequest) (*emptypb.Empty, error) { 721 if core.IsAnyNilOrZero(req.Status, req.Attempted, req.Id, req.Expires) { 722 return nil, errIncompleteRequest 723 } 724 725 if req.Status != string(core.StatusValid) && req.Status != string(core.StatusInvalid) { 726 return nil, berrors.InternalServerError("authorization must have status valid or invalid") 727 } 728 query := `UPDATE authz2 SET 729 status = :status, 730 attempted = :attempted, 731 attemptedAt = :attemptedAt, 732 validationRecord = :validationRecord, 733 validationError = :validationError, 734 expires = :expires 735 WHERE id = :id AND status = :pending` 736 var validationRecords []core.ValidationRecord 737 for _, recordPB := range req.ValidationRecords { 738 record, err := bgrpc.PBToValidationRecord(recordPB) 739 if err != nil { 740 return nil, err 741 } 742 if req.Attempted == string(core.ChallengeTypeHTTP01) { 743 // Remove these fields because they can be rehydrated later 744 // on from the URL field. 745 record.Hostname = "" 746 record.Port = "" 747 } 748 validationRecords = append(validationRecords, record) 749 } 750 vrJSON, err := json.Marshal(validationRecords) 751 if err != nil { 752 return nil, err 753 } 754 var veJSON []byte 755 if req.ValidationError != nil { 756 validationError, err := bgrpc.PBToProblemDetails(req.ValidationError) 757 if err != nil { 758 return nil, err 759 } 760 j, err := json.Marshal(validationError) 761 if err != nil { 762 return nil, err 763 } 764 veJSON = j 765 } 766 // Check to see if the AttemptedAt time is non zero and convert to 767 // *time.Time if so. If it is zero, leave nil and don't convert. Keep the 768 // database attemptedAt field Null instead of 1970-01-01 00:00:00. 769 var attemptedTime *time.Time 770 if !core.IsAnyNilOrZero(req.AttemptedAt) { 771 val := req.AttemptedAt.AsTime() 772 attemptedTime = &val 773 } 774 params := map[string]any{ 775 "status": statusToUint[core.AcmeStatus(req.Status)], 776 "attempted": challTypeToUint[req.Attempted], 777 "attemptedAt": attemptedTime, 778 "validationRecord": vrJSON, 779 "id": req.Id, 780 "pending": statusUint(core.StatusPending), 781 "expires": req.Expires.AsTime(), 782 // if req.ValidationError is nil veJSON should also be nil 783 // which should result in a NULL field 784 "validationError": veJSON, 785 } 786 787 res, err := ssa.dbMap.ExecContext(ctx, query, params) 788 if err != nil { 789 return nil, err 790 } 791 rows, err := res.RowsAffected() 792 if err != nil { 793 return nil, err 794 } 795 if rows == 0 { 796 return nil, berrors.NotFoundError("no pending authorization with id %d", req.Id) 797 } else if rows > 1 { 798 return nil, berrors.InternalServerError("multiple rows updated for authorization id %d", req.Id) 799 } 800 return &emptypb.Empty{}, nil 801 } 802 803 // addRevokedCertificate is a helper used by both RevokeCertificate and 804 // UpdateRevokedCertificate. It inserts a new row into the revokedCertificates 805 // table based on the contents of the input request. The second argument must be 806 // a transaction object so that it is safe to conduct multiple queries with a 807 // consistent view of the database. It must only be called when the request 808 // specifies a non-zero ShardIdx. 809 func addRevokedCertificate(ctx context.Context, tx db.Executor, req *sapb.RevokeCertificateRequest, revokedDate time.Time) error { 810 if req.ShardIdx == 0 { 811 return errors.New("cannot add revoked certificate with shard index 0") 812 } 813 814 var serial struct { 815 Expires time.Time 816 } 817 err := tx.SelectOne( 818 ctx, &serial, `SELECT expires FROM serials WHERE serial = ?`, req.Serial) 819 if err != nil { 820 return fmt.Errorf("retrieving revoked certificate expiration: %w", err) 821 } 822 823 err = tx.Insert(ctx, &revokedCertModel{ 824 IssuerID: req.IssuerID, 825 Serial: req.Serial, 826 ShardIdx: req.ShardIdx, 827 RevokedDate: revokedDate, 828 RevokedReason: revocation.Reason(req.Reason), 829 // Round the notAfter up to the next hour, to reduce index size while still 830 // ensuring we correctly serve revocation info past the actual expiration. 831 NotAfterHour: serial.Expires.Add(time.Hour).Truncate(time.Hour), 832 }) 833 if err != nil { 834 return fmt.Errorf("inserting revoked certificate row: %w", err) 835 } 836 837 return nil 838 } 839 840 // RevokeCertificate stores revocation information about a certificate. It will only store this 841 // information if the certificate is not already marked as revoked. 842 // 843 // If ShardIdx is non-zero, RevokeCertificate also writes an entry for this certificate to 844 // the revokedCertificates table, with the provided shard number. 845 func (ssa *SQLStorageAuthority) RevokeCertificate(ctx context.Context, req *sapb.RevokeCertificateRequest) (*emptypb.Empty, error) { 846 if core.IsAnyNilOrZero(req.Serial, req.IssuerID, req.Date, req.ShardIdx) { 847 return nil, errIncompleteRequest 848 } 849 850 _, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 851 revokedDate := req.Date.AsTime() 852 853 res, err := tx.ExecContext(ctx, 854 `UPDATE certificateStatus SET 855 status = ?, 856 revokedReason = ?, 857 revokedDate = ?, 858 ocspLastUpdated = ? 859 WHERE serial = ? AND status != ?`, 860 string(core.OCSPStatusRevoked), 861 revocation.Reason(req.Reason), 862 revokedDate, 863 revokedDate, 864 req.Serial, 865 string(core.OCSPStatusRevoked), 866 ) 867 if err != nil { 868 return nil, err 869 } 870 rows, err := res.RowsAffected() 871 if err != nil { 872 return nil, err 873 } 874 if rows == 0 { 875 return nil, berrors.AlreadyRevokedError("no certificate with serial %s and status other than %s", req.Serial, string(core.OCSPStatusRevoked)) 876 } 877 878 err = addRevokedCertificate(ctx, tx, req, revokedDate) 879 if err != nil { 880 return nil, err 881 } 882 883 return nil, nil 884 }) 885 if overallError != nil { 886 return nil, overallError 887 } 888 889 return &emptypb.Empty{}, nil 890 } 891 892 // UpdateRevokedCertificate stores new revocation information about an 893 // already-revoked certificate. It will only store this information if the 894 // cert is already revoked, if the new revocation reason is `KeyCompromise`, 895 // and if the revokedDate is identical to the current revokedDate. 896 func (ssa *SQLStorageAuthority) UpdateRevokedCertificate(ctx context.Context, req *sapb.RevokeCertificateRequest) (*emptypb.Empty, error) { 897 if core.IsAnyNilOrZero(req.Serial, req.IssuerID, req.Date, req.Backdate, req.ShardIdx) { 898 return nil, errIncompleteRequest 899 } 900 if revocation.Reason(req.Reason) != revocation.KeyCompromise { 901 return nil, fmt.Errorf("cannot update revocation for any reason other than keyCompromise (1); got: %d", req.Reason) 902 } 903 904 _, overallError := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 905 thisUpdate := req.Date.AsTime() 906 revokedDate := req.Backdate.AsTime() 907 908 res, err := tx.ExecContext(ctx, 909 `UPDATE certificateStatus SET 910 revokedReason = ?, 911 ocspLastUpdated = ? 912 WHERE serial = ? AND status = ? AND revokedReason != ? AND revokedDate = ?`, 913 revocation.KeyCompromise, 914 thisUpdate, 915 req.Serial, 916 string(core.OCSPStatusRevoked), 917 revocation.KeyCompromise, 918 revokedDate, 919 ) 920 if err != nil { 921 return nil, err 922 } 923 rows, err := res.RowsAffected() 924 if err != nil { 925 return nil, err 926 } 927 if rows == 0 { 928 // InternalServerError because we expected this certificate status to exist, 929 // to already be revoked for a different reason, and to have a matching date. 930 return nil, berrors.InternalServerError("no certificate with serial %s and revoked reason other than keyCompromise", req.Serial) 931 } 932 933 var rcm revokedCertModel 934 // Note: this query MUST be updated to enforce the same preconditions as 935 // the "UPDATE certificateStatus SET revokedReason..." above if this 936 // query ever becomes the first or only query in this transaction. We are 937 // currently relying on the query above to exit early if the certificate 938 // does not have an appropriate status and revocation reason. 939 err = tx.SelectOne( 940 ctx, &rcm, `SELECT * FROM revokedCertificates WHERE serial = ?`, req.Serial) 941 if db.IsNoRows(err) { 942 // TODO: Remove this fallback codepath once we know that all unexpired 943 // certs marked as revoked in the certificateStatus table have 944 // corresponding rows in the revokedCertificates table. That should be 945 // 90+ days after the RA starts sending ShardIdx in its 946 // RevokeCertificateRequest messages. 947 err = addRevokedCertificate(ctx, tx, req, revokedDate) 948 if err != nil { 949 return nil, err 950 } 951 return nil, nil 952 } else if err != nil { 953 return nil, fmt.Errorf("retrieving revoked certificate row: %w", err) 954 } 955 956 if rcm.ShardIdx != req.ShardIdx { 957 return nil, berrors.InternalServerError("mismatched shard index %d != %d", req.ShardIdx, rcm.ShardIdx) 958 } 959 960 rcm.RevokedReason = revocation.KeyCompromise 961 _, err = tx.Update(ctx, &rcm) 962 if err != nil { 963 return nil, fmt.Errorf("updating revoked certificate row: %w", err) 964 } 965 966 return nil, nil 967 }) 968 if overallError != nil { 969 return nil, overallError 970 } 971 972 return &emptypb.Empty{}, nil 973 } 974 975 // AddBlockedKey adds a key hash to the blockedKeys table 976 func (ssa *SQLStorageAuthority) AddBlockedKey(ctx context.Context, req *sapb.AddBlockedKeyRequest) (*emptypb.Empty, error) { 977 if core.IsAnyNilOrZero(req.KeyHash, req.Added, req.Source) { 978 return nil, errIncompleteRequest 979 } 980 sourceInt, ok := stringToSourceInt[req.Source] 981 if !ok { 982 return nil, errors.New("unknown source") 983 } 984 cols, qs := blockedKeysColumns, "?, ?, ?, ?" 985 vals := []any{ 986 req.KeyHash, 987 req.Added.AsTime(), 988 sourceInt, 989 req.Comment, 990 } 991 if req.RevokedBy != 0 { 992 cols += ", revokedBy" 993 qs += ", ?" 994 vals = append(vals, req.RevokedBy) 995 } 996 _, err := ssa.dbMap.ExecContext(ctx, 997 fmt.Sprintf("INSERT INTO blockedKeys (%s) VALUES (%s)", cols, qs), 998 vals..., 999 ) 1000 if err != nil { 1001 if db.IsDuplicate(err) { 1002 // Ignore duplicate inserts so multiple certs with the same key can 1003 // be revoked. 1004 return &emptypb.Empty{}, nil 1005 } 1006 return nil, err 1007 } 1008 return &emptypb.Empty{}, nil 1009 } 1010 1011 // Health implements the grpc.checker interface. 1012 func (ssa *SQLStorageAuthority) Health(ctx context.Context) error { 1013 err := ssa.dbMap.SelectOne(ctx, new(int), "SELECT 1") 1014 if err != nil { 1015 return err 1016 } 1017 1018 err = ssa.SQLStorageAuthorityRO.Health(ctx) 1019 if err != nil { 1020 return err 1021 } 1022 return nil 1023 } 1024 1025 // LeaseCRLShard marks a single crlShards row as leased until the given time. 1026 // If the request names a specific shard, this function will return an error 1027 // if that shard is already leased. Otherwise, this function will return the 1028 // index of the oldest shard for the given issuer. 1029 func (ssa *SQLStorageAuthority) LeaseCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) { 1030 if core.IsAnyNilOrZero(req.Until, req.IssuerNameID) { 1031 return nil, errIncompleteRequest 1032 } 1033 if req.Until.AsTime().Before(ssa.clk.Now()) { 1034 return nil, fmt.Errorf("lease timestamp must be in the future, got %q", req.Until.AsTime()) 1035 } 1036 1037 if req.MinShardIdx == req.MaxShardIdx { 1038 return ssa.leaseSpecificCRLShard(ctx, req) 1039 } 1040 1041 return ssa.leaseOldestCRLShard(ctx, req) 1042 } 1043 1044 // leaseOldestCRLShard finds the oldest unleased crl shard for the given issuer 1045 // and then leases it. Shards within the requested range which have never been 1046 // leased or are previously-unknown indices are considered older than any other 1047 // shard. It returns an error if all shards for the issuer are already leased. 1048 func (ssa *SQLStorageAuthority) leaseOldestCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) { 1049 shardIdx, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 1050 var shards []*crlShardModel 1051 _, err := tx.Select( 1052 ctx, 1053 &shards, 1054 `SELECT id, issuerID, idx, thisUpdate, nextUpdate, leasedUntil 1055 FROM crlShards 1056 WHERE issuerID = ? 1057 AND idx BETWEEN ? AND ?`, 1058 req.IssuerNameID, req.MinShardIdx, req.MaxShardIdx, 1059 ) 1060 if err != nil { 1061 return -1, fmt.Errorf("selecting candidate shards: %w", err) 1062 } 1063 1064 // Determine which shard index we want to lease. 1065 var shardIdx int 1066 if len(shards) < (int(req.MaxShardIdx + 1 - req.MinShardIdx)) { 1067 // Some expected shards are missing (i.e. never-before-produced), so we 1068 // pick one at random. 1069 missing := make(map[int]struct{}, req.MaxShardIdx+1-req.MinShardIdx) 1070 for i := req.MinShardIdx; i <= req.MaxShardIdx; i++ { 1071 missing[int(i)] = struct{}{} 1072 } 1073 for _, shard := range shards { 1074 delete(missing, shard.Idx) 1075 } 1076 for idx := range missing { 1077 // Go map iteration is guaranteed to be in randomized key order. 1078 shardIdx = idx 1079 break 1080 } 1081 1082 _, err = tx.ExecContext(ctx, 1083 `INSERT INTO crlShards (issuerID, idx, leasedUntil) 1084 VALUES (?, ?, ?)`, 1085 req.IssuerNameID, 1086 shardIdx, 1087 req.Until.AsTime(), 1088 ) 1089 if err != nil { 1090 return -1, fmt.Errorf("inserting selected shard: %w", err) 1091 } 1092 } else { 1093 // We got all the shards we expect, so we pick the oldest unleased shard. 1094 var oldest *crlShardModel 1095 for _, shard := range shards { 1096 if shard.LeasedUntil.After(ssa.clk.Now()) { 1097 continue 1098 } 1099 if oldest == nil || 1100 (oldest.ThisUpdate != nil && shard.ThisUpdate == nil) || 1101 (oldest.ThisUpdate != nil && shard.ThisUpdate.Before(*oldest.ThisUpdate)) { 1102 oldest = shard 1103 } 1104 } 1105 if oldest == nil { 1106 return -1, fmt.Errorf("issuer %d has no unleased shards in range %d-%d", req.IssuerNameID, req.MinShardIdx, req.MaxShardIdx) 1107 } 1108 shardIdx = oldest.Idx 1109 1110 res, err := tx.ExecContext(ctx, 1111 `UPDATE crlShards 1112 SET leasedUntil = ? 1113 WHERE issuerID = ? 1114 AND idx = ? 1115 AND leasedUntil = ? 1116 LIMIT 1`, 1117 req.Until.AsTime(), 1118 req.IssuerNameID, 1119 shardIdx, 1120 oldest.LeasedUntil, 1121 ) 1122 if err != nil { 1123 return -1, fmt.Errorf("updating selected shard: %w", err) 1124 } 1125 rowsAffected, err := res.RowsAffected() 1126 if err != nil { 1127 return -1, fmt.Errorf("confirming update of selected shard: %w", err) 1128 } 1129 if rowsAffected != 1 { 1130 return -1, errors.New("failed to lease shard") 1131 } 1132 } 1133 1134 return shardIdx, err 1135 }) 1136 if err != nil { 1137 return nil, fmt.Errorf("leasing oldest shard: %w", err) 1138 } 1139 1140 return &sapb.LeaseCRLShardResponse{ 1141 IssuerNameID: req.IssuerNameID, 1142 ShardIdx: int64(shardIdx.(int)), 1143 }, nil 1144 } 1145 1146 // leaseSpecificCRLShard attempts to lease the crl shard for the given issuer 1147 // and shard index. It returns an error if the specified shard is already 1148 // leased. 1149 func (ssa *SQLStorageAuthority) leaseSpecificCRLShard(ctx context.Context, req *sapb.LeaseCRLShardRequest) (*sapb.LeaseCRLShardResponse, error) { 1150 if req.MinShardIdx != req.MaxShardIdx { 1151 return nil, fmt.Errorf("request must identify a single shard index: %d != %d", req.MinShardIdx, req.MaxShardIdx) 1152 } 1153 1154 _, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 1155 needToInsert := false 1156 var shardModel crlShardModel 1157 err := tx.SelectOne(ctx, 1158 &shardModel, 1159 `SELECT leasedUntil 1160 FROM crlShards 1161 WHERE issuerID = ? 1162 AND idx = ? 1163 LIMIT 1`, 1164 req.IssuerNameID, 1165 req.MinShardIdx, 1166 ) 1167 if db.IsNoRows(err) { 1168 needToInsert = true 1169 } else if err != nil { 1170 return nil, fmt.Errorf("selecting requested shard: %w", err) 1171 } else if shardModel.LeasedUntil.After(ssa.clk.Now()) { 1172 return nil, fmt.Errorf("shard %d for issuer %d already leased", req.MinShardIdx, req.IssuerNameID) 1173 } 1174 1175 if needToInsert { 1176 _, err = tx.ExecContext(ctx, 1177 `INSERT INTO crlShards (issuerID, idx, leasedUntil) 1178 VALUES (?, ?, ?)`, 1179 req.IssuerNameID, 1180 req.MinShardIdx, 1181 req.Until.AsTime(), 1182 ) 1183 if err != nil { 1184 return nil, fmt.Errorf("inserting selected shard: %w", err) 1185 } 1186 } else { 1187 res, err := tx.ExecContext(ctx, 1188 `UPDATE crlShards 1189 SET leasedUntil = ? 1190 WHERE issuerID = ? 1191 AND idx = ? 1192 AND leasedUntil = ? 1193 LIMIT 1`, 1194 req.Until.AsTime(), 1195 req.IssuerNameID, 1196 req.MinShardIdx, 1197 shardModel.LeasedUntil, 1198 ) 1199 if err != nil { 1200 return nil, fmt.Errorf("updating selected shard: %w", err) 1201 } 1202 rowsAffected, err := res.RowsAffected() 1203 if err != nil { 1204 return -1, fmt.Errorf("confirming update of selected shard: %w", err) 1205 } 1206 if rowsAffected != 1 { 1207 return -1, errors.New("failed to lease shard") 1208 } 1209 } 1210 1211 return nil, nil 1212 }) 1213 if err != nil { 1214 return nil, fmt.Errorf("leasing specific shard: %w", err) 1215 } 1216 1217 return &sapb.LeaseCRLShardResponse{ 1218 IssuerNameID: req.IssuerNameID, 1219 ShardIdx: req.MinShardIdx, 1220 }, nil 1221 } 1222 1223 // UpdateCRLShard updates the thisUpdate and nextUpdate timestamps of a CRL 1224 // shard. It rejects the update if it would cause the thisUpdate timestamp to 1225 // move backwards, but if thisUpdate would stay the same (for instance, multiple 1226 // CRL generations within a single second), it will succeed. 1227 // 1228 // It does *not* reject the update if the shard is no longer 1229 // leased: although this would be unexpected (because the lease timestamp should 1230 // be the same as the crl-updater's context expiration), it's not inherently a 1231 // sign of an update that should be skipped. It does reject the update if the 1232 // identified CRL shard does not exist in the database (it should exist, as 1233 // rows are created if necessary when leased). It also sets the leasedUntil time 1234 // to be equal to thisUpdate, to indicate that the shard is no longer leased. 1235 func (ssa *SQLStorageAuthority) UpdateCRLShard(ctx context.Context, req *sapb.UpdateCRLShardRequest) (*emptypb.Empty, error) { 1236 if core.IsAnyNilOrZero(req.IssuerNameID, req.ThisUpdate) { 1237 return nil, errIncompleteRequest 1238 } 1239 1240 // Only set the nextUpdate if it's actually present in the request message. 1241 var nextUpdate *time.Time 1242 if req.NextUpdate != nil { 1243 nut := req.NextUpdate.AsTime() 1244 nextUpdate = &nut 1245 } 1246 1247 _, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 1248 res, err := tx.ExecContext(ctx, 1249 `UPDATE crlShards 1250 SET thisUpdate = ?, nextUpdate = ?, leasedUntil = ? 1251 WHERE issuerID = ? 1252 AND idx = ? 1253 AND (thisUpdate is NULL OR thisUpdate <= ?) 1254 LIMIT 1`, 1255 req.ThisUpdate.AsTime(), 1256 nextUpdate, 1257 req.ThisUpdate.AsTime(), 1258 req.IssuerNameID, 1259 req.ShardIdx, 1260 req.ThisUpdate.AsTime(), 1261 ) 1262 if err != nil { 1263 return nil, err 1264 } 1265 1266 rowsAffected, err := res.RowsAffected() 1267 if err != nil { 1268 return nil, err 1269 } 1270 if rowsAffected == 0 { 1271 return nil, fmt.Errorf("unable to update shard %d for issuer %d; possibly because shard exists", req.ShardIdx, req.IssuerNameID) 1272 } 1273 if rowsAffected != 1 { 1274 return nil, errors.New("update affected unexpected number of rows") 1275 } 1276 return nil, nil 1277 }) 1278 if err != nil { 1279 return nil, err 1280 } 1281 1282 return &emptypb.Empty{}, nil 1283 } 1284 1285 // PauseIdentifiers pauses a set of identifiers for the provided account. If an 1286 // identifier is currently paused, this is a no-op. If an identifier was 1287 // previously paused and unpaused, it will be repaused unless it was unpaused 1288 // less than two weeks ago. The response will indicate how many identifiers were 1289 // paused and how many were repaused. All work is accomplished in a transaction 1290 // to limit possible race conditions. 1291 func (ssa *SQLStorageAuthority) PauseIdentifiers(ctx context.Context, req *sapb.PauseRequest) (*sapb.PauseIdentifiersResponse, error) { 1292 if core.IsAnyNilOrZero(req.RegistrationID, req.Identifiers) { 1293 return nil, errIncompleteRequest 1294 } 1295 1296 // Marshal the identifier now that we've crossed the RPC boundary. 1297 idents, err := newIdentifierModelsFromPB(req.Identifiers) 1298 if err != nil { 1299 return nil, err 1300 } 1301 1302 response := &sapb.PauseIdentifiersResponse{} 1303 _, err = db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 1304 for _, ident := range idents { 1305 pauseError := func(op string, err error) error { 1306 return fmt.Errorf("while %s identifier %s for registration ID %d: %w", 1307 op, ident.Value, req.RegistrationID, err, 1308 ) 1309 } 1310 1311 var entry pausedModel 1312 err := tx.SelectOne(ctx, &entry, ` 1313 SELECT pausedAt, unpausedAt 1314 FROM paused 1315 WHERE 1316 registrationID = ? AND 1317 identifierType = ? AND 1318 identifierValue = ?`, 1319 req.RegistrationID, 1320 ident.Type, 1321 ident.Value, 1322 ) 1323 1324 switch { 1325 case err != nil && !errors.Is(err, sql.ErrNoRows): 1326 // Error querying the database. 1327 return nil, pauseError("querying pause status for", err) 1328 1329 case err != nil && errors.Is(err, sql.ErrNoRows): 1330 // Not currently or previously paused, insert a new pause record. 1331 err = tx.Insert(ctx, &pausedModel{ 1332 RegistrationID: req.RegistrationID, 1333 PausedAt: ssa.clk.Now(), 1334 identifierModel: identifierModel{ 1335 Type: ident.Type, 1336 Value: ident.Value, 1337 }, 1338 }) 1339 if err != nil && !db.IsDuplicate(err) { 1340 return nil, pauseError("pausing", err) 1341 } 1342 1343 // Identifier successfully paused. 1344 response.Paused++ 1345 continue 1346 1347 case entry.UnpausedAt == nil || entry.PausedAt.After(*entry.UnpausedAt): 1348 // Identifier is already paused. 1349 continue 1350 1351 case entry.UnpausedAt.After(ssa.clk.Now().Add(-14 * 24 * time.Hour)): 1352 // Previously unpaused less than two weeks ago, skip this identifier. 1353 continue 1354 1355 case entry.UnpausedAt.After(entry.PausedAt): 1356 // Previously paused (and unpaused), repause the identifier. 1357 _, err := tx.ExecContext(ctx, ` 1358 UPDATE paused 1359 SET pausedAt = ?, 1360 unpausedAt = NULL 1361 WHERE 1362 registrationID = ? AND 1363 identifierType = ? AND 1364 identifierValue = ? AND 1365 unpausedAt IS NOT NULL`, 1366 ssa.clk.Now(), 1367 req.RegistrationID, 1368 ident.Type, 1369 ident.Value, 1370 ) 1371 if err != nil { 1372 return nil, pauseError("repausing", err) 1373 } 1374 1375 // Identifier successfully repaused. 1376 response.Repaused++ 1377 continue 1378 1379 default: 1380 // This indicates a database state which should never occur. 1381 return nil, fmt.Errorf("impossible database state encountered while pausing identifier %s", 1382 ident.Value, 1383 ) 1384 } 1385 } 1386 return nil, nil 1387 }) 1388 if err != nil { 1389 // Error occurred during transaction. 1390 return nil, err 1391 } 1392 return response, nil 1393 } 1394 1395 // UnpauseAccount uses up to 5 iterations of UPDATE queries each with a LIMIT of 1396 // 10,000 to unpause up to 50,000 identifiers and returns a count of identifiers 1397 // unpaused. If the returned count is 50,000 there may be more paused identifiers. 1398 func (ssa *SQLStorageAuthority) UnpauseAccount(ctx context.Context, req *sapb.RegistrationID) (*sapb.Count, error) { 1399 if core.IsAnyNilOrZero(req.Id) { 1400 return nil, errIncompleteRequest 1401 } 1402 1403 total := &sapb.Count{} 1404 1405 for range unpause.MaxBatches { 1406 result, err := ssa.dbMap.ExecContext(ctx, ` 1407 UPDATE paused 1408 SET unpausedAt = ? 1409 WHERE 1410 registrationID = ? AND 1411 unpausedAt IS NULL 1412 LIMIT ?`, 1413 ssa.clk.Now(), 1414 req.Id, 1415 unpause.BatchSize, 1416 ) 1417 if err != nil { 1418 return nil, err 1419 } 1420 1421 rowsAffected, err := result.RowsAffected() 1422 if err != nil { 1423 return nil, err 1424 } 1425 1426 total.Count += rowsAffected 1427 if rowsAffected < unpause.BatchSize { 1428 // Fewer than batchSize rows were updated, so we're done. 1429 break 1430 } 1431 } 1432 1433 return total, nil 1434 } 1435 1436 // AddRateLimitOverride adds a rate limit override to the database. If the 1437 // override already exists, it will be updated. If the override does not exist, 1438 // it will be inserted and enabled. If the override exists but has been 1439 // disabled, it will be updated but not be re-enabled. The status of the 1440 // override is returned in Enabled field of the response. To re-enable an 1441 // override, use the EnableRateLimitOverride method. 1442 func (ssa *SQLStorageAuthority) AddRateLimitOverride(ctx context.Context, req *sapb.AddRateLimitOverrideRequest) (*sapb.AddRateLimitOverrideResponse, error) { 1443 if core.IsAnyNilOrZero(req, req.Override, req.Override.LimitEnum, req.Override.BucketKey, req.Override.Count, req.Override.Burst, req.Override.Period, req.Override.Comment) { 1444 return nil, errIncompleteRequest 1445 } 1446 1447 var inserted bool 1448 var enabled bool 1449 now := ssa.clk.Now() 1450 1451 _, err := db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 1452 var alreadyEnabled bool 1453 err := tx.SelectOne(ctx, &alreadyEnabled, ` 1454 SELECT enabled 1455 FROM overrides 1456 WHERE limitEnum = ? AND 1457 bucketKey = ?`, 1458 req.Override.LimitEnum, 1459 req.Override.BucketKey, 1460 ) 1461 1462 switch { 1463 case err != nil && !db.IsNoRows(err): 1464 // Error querying the database. 1465 return nil, fmt.Errorf("querying override for rate limit %d and bucket key %s: %w", 1466 req.Override.LimitEnum, 1467 req.Override.BucketKey, 1468 err, 1469 ) 1470 1471 case db.IsNoRows(err): 1472 // Insert a new overrides row. 1473 new := overrideModelForPB(req.Override, now, true) 1474 err = tx.Insert(ctx, &new) 1475 if err != nil { 1476 return nil, fmt.Errorf("inserting override for rate limit %d and bucket key %s: %w", 1477 req.Override.LimitEnum, 1478 req.Override.BucketKey, 1479 err, 1480 ) 1481 } 1482 inserted = true 1483 enabled = true 1484 1485 default: 1486 // Update the existing overrides row. 1487 updated := overrideModelForPB(req.Override, now, alreadyEnabled) 1488 _, err = tx.Update(ctx, &updated) 1489 if err != nil { 1490 return nil, fmt.Errorf("updating override for rate limit %d and bucket key %s override: %w", 1491 req.Override.LimitEnum, 1492 req.Override.BucketKey, 1493 err, 1494 ) 1495 } 1496 inserted = false 1497 enabled = alreadyEnabled 1498 } 1499 return nil, nil 1500 }) 1501 if err != nil { 1502 // Error occurred during transaction. 1503 return nil, err 1504 } 1505 return &sapb.AddRateLimitOverrideResponse{Inserted: inserted, Enabled: enabled}, nil 1506 } 1507 1508 // setRateLimitOverride sets the enabled field of a rate limit override to the 1509 // provided value and updates the updatedAt column. If the override does not 1510 // exist, a NotFoundError is returned. If the override exists but is already in 1511 // the requested state, this is a no-op. 1512 func (ssa *SQLStorageAuthority) setRateLimitOverride(ctx context.Context, limitEnum int64, bucketKey string, enabled bool) (*emptypb.Empty, error) { 1513 overrideColumnsList, err := ssa.dbMap.ColumnsForModel(overrideModel{}) 1514 if err != nil { 1515 // This should never happen, the model is registered at init time. 1516 return nil, fmt.Errorf("getting columns for override model: %w", err) 1517 } 1518 overrideColumns := strings.Join(overrideColumnsList, ", ") 1519 _, err = db.WithTransaction(ctx, ssa.dbMap, func(tx db.Executor) (any, error) { 1520 var existing overrideModel 1521 err := tx.SelectOne(ctx, &existing, 1522 // Use SELECT FOR UPDATE to both verify the row exists and lock it 1523 // for the duration of the transaction. 1524 `SELECT `+overrideColumns+` FROM overrides 1525 WHERE limitEnum = ? AND 1526 bucketKey = ? 1527 FOR UPDATE`, 1528 limitEnum, 1529 bucketKey, 1530 ) 1531 if err != nil { 1532 if db.IsNoRows(err) { 1533 return nil, berrors.NotFoundError( 1534 "no rate limit override found for limit %d and bucket key %s", 1535 limitEnum, 1536 bucketKey, 1537 ) 1538 } 1539 return nil, fmt.Errorf("querying status of override for rate limit %d and bucket key %s: %w", 1540 limitEnum, 1541 bucketKey, 1542 err, 1543 ) 1544 } 1545 1546 if existing.Enabled == enabled { 1547 // No-op 1548 return nil, nil 1549 } 1550 1551 // Update the existing overrides row. 1552 updated := existing 1553 updated.Enabled = enabled 1554 updated.UpdatedAt = ssa.clk.Now() 1555 1556 _, err = tx.Update(ctx, &updated) 1557 if err != nil { 1558 return nil, fmt.Errorf("updating status of override for rate limit %d and bucket key %s to %t: %w", 1559 limitEnum, 1560 bucketKey, 1561 enabled, 1562 err, 1563 ) 1564 } 1565 return nil, nil 1566 }) 1567 if err != nil { 1568 return nil, err 1569 } 1570 return &emptypb.Empty{}, nil 1571 } 1572 1573 // DisableRateLimitOverride disables a rate limit override. If the override does 1574 // not exist, a NotFoundError is returned. If the override exists but is already 1575 // disabled, this is a no-op. 1576 func (ssa *SQLStorageAuthority) DisableRateLimitOverride(ctx context.Context, req *sapb.DisableRateLimitOverrideRequest) (*emptypb.Empty, error) { 1577 if core.IsAnyNilOrZero(req, req.LimitEnum, req.BucketKey) { 1578 return nil, errIncompleteRequest 1579 } 1580 return ssa.setRateLimitOverride(ctx, req.LimitEnum, req.BucketKey, false) 1581 } 1582 1583 // EnableRateLimitOverride enables a rate limit override. If the override does 1584 // not exist, a NotFoundError is returned. If the override exists but is already 1585 // enabled, this is a no-op. 1586 func (ssa *SQLStorageAuthority) EnableRateLimitOverride(ctx context.Context, req *sapb.EnableRateLimitOverrideRequest) (*emptypb.Empty, error) { 1587 if core.IsAnyNilOrZero(req, req.LimitEnum, req.BucketKey) { 1588 return nil, errIncompleteRequest 1589 } 1590 return ssa.setRateLimitOverride(ctx, req.LimitEnum, req.BucketKey, true) 1591 }