github.com/letsencrypt/boulder@v0.20251208.0/sa/saro.go (about) 1 package sa 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "github.com/letsencrypt/boulder/features" 8 "math" 9 "regexp" 10 "strings" 11 "time" 12 13 "github.com/go-jose/go-jose/v4" 14 "github.com/jmhodges/clock" 15 "github.com/prometheus/client_golang/prometheus" 16 "github.com/prometheus/client_golang/prometheus/promauto" 17 "google.golang.org/grpc" 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/identifier" 26 blog "github.com/letsencrypt/boulder/log" 27 sapb "github.com/letsencrypt/boulder/sa/proto" 28 ) 29 30 var ( 31 validIncidentTableRegexp = regexp.MustCompile(`^incident_[0-9a-zA-Z_]{1,100}$`) 32 ) 33 34 // SQLStorageAuthorityRO defines a read-only subset of a Storage Authority 35 type SQLStorageAuthorityRO struct { 36 sapb.UnsafeStorageAuthorityReadOnlyServer 37 38 dbReadOnlyMap *db.WrappedMap 39 dbIncidentsMap *db.WrappedMap 40 41 // For RPCs that generate multiple, parallelizable SQL queries, this is the 42 // max parallelism they will use (to avoid consuming too many MariaDB 43 // threads). 44 parallelismPerRPC int 45 46 // lagFactor is the amount of time we're willing to delay before retrying a 47 // request that may have failed due to replication lag. For example, a user 48 // might create a new account and then immediately create a new order, but 49 // validating that new-order request requires reading their account info from 50 // a read-only database replica... which may not have their brand new data 51 // yet. This value should be less than, but about the same order of magnitude 52 // as, the observed database replication lag. 53 lagFactor time.Duration 54 55 clk clock.Clock 56 log blog.Logger 57 58 // lagFactorCounter is a Prometheus counter that tracks the number of times 59 // we've retried a query inside of GetRegistration, GetOrder, and 60 // GetAuthorization2 due to replication lag. It is labeled by method name 61 // and whether data from the retry attempt was found, notfound, or some 62 // other error was encountered. 63 lagFactorCounter *prometheus.CounterVec 64 } 65 66 var _ sapb.StorageAuthorityReadOnlyServer = (*SQLStorageAuthorityRO)(nil) 67 68 // NewSQLStorageAuthorityRO provides persistence using a SQL backend for 69 // Boulder. It will modify the given borp.DbMap by adding relevant tables. 70 func NewSQLStorageAuthorityRO( 71 dbReadOnlyMap *db.WrappedMap, 72 dbIncidentsMap *db.WrappedMap, 73 stats prometheus.Registerer, 74 parallelismPerRPC int, 75 lagFactor time.Duration, 76 clk clock.Clock, 77 logger blog.Logger, 78 ) (*SQLStorageAuthorityRO, error) { 79 lagFactorCounter := promauto.With(stats).NewCounterVec(prometheus.CounterOpts{ 80 Name: "sa_lag_factor", 81 Help: "A counter of SA lagFactor checks labelled by method and pass/fail", 82 }, []string{"method", "result"}) 83 84 ssaro := &SQLStorageAuthorityRO{ 85 dbReadOnlyMap: dbReadOnlyMap, 86 dbIncidentsMap: dbIncidentsMap, 87 parallelismPerRPC: parallelismPerRPC, 88 lagFactor: lagFactor, 89 clk: clk, 90 log: logger, 91 lagFactorCounter: lagFactorCounter, 92 } 93 94 return ssaro, nil 95 } 96 97 // GetRegistration obtains a Registration by ID 98 func (ssa *SQLStorageAuthorityRO) GetRegistration(ctx context.Context, req *sapb.RegistrationID) (*corepb.Registration, error) { 99 if req == nil || req.Id == 0 { 100 return nil, errIncompleteRequest 101 } 102 103 model, err := selectRegistration(ctx, ssa.dbReadOnlyMap, "id", req.Id) 104 if db.IsNoRows(err) && ssa.lagFactor != 0 { 105 // GetRegistration is often called to validate a JWK belonging to a brand 106 // new account whose registrations table row hasn't propagated to the read 107 // replica yet. If we get a NoRows, wait a little bit and retry, once. 108 ssa.clk.Sleep(ssa.lagFactor) 109 model, err = selectRegistration(ctx, ssa.dbReadOnlyMap, "id", req.Id) 110 if err != nil { 111 if db.IsNoRows(err) { 112 ssa.lagFactorCounter.WithLabelValues("GetRegistration", "notfound").Inc() 113 } else { 114 ssa.lagFactorCounter.WithLabelValues("GetRegistration", "other").Inc() 115 } 116 } else { 117 ssa.lagFactorCounter.WithLabelValues("GetRegistration", "found").Inc() 118 } 119 } 120 if err != nil { 121 if db.IsNoRows(err) { 122 return nil, berrors.NotFoundError("registration with ID '%d' not found", req.Id) 123 } 124 return nil, err 125 } 126 127 return registrationModelToPb(model) 128 } 129 130 // GetRegistrationByKey obtains a Registration by JWK 131 func (ssa *SQLStorageAuthorityRO) GetRegistrationByKey(ctx context.Context, req *sapb.JSONWebKey) (*corepb.Registration, error) { 132 if req == nil || len(req.Jwk) == 0 { 133 return nil, errIncompleteRequest 134 } 135 136 var jwk jose.JSONWebKey 137 err := jwk.UnmarshalJSON(req.Jwk) 138 if err != nil { 139 return nil, err 140 } 141 142 sha, err := core.KeyDigestB64(jwk.Key) 143 if err != nil { 144 return nil, err 145 } 146 model, err := selectRegistration(ctx, ssa.dbReadOnlyMap, "jwk_sha256", sha) 147 if err != nil { 148 if db.IsNoRows(err) { 149 return nil, berrors.NotFoundError("no registrations with public key sha256 %q", sha) 150 } 151 return nil, err 152 } 153 154 return registrationModelToPb(model) 155 } 156 157 // GetSerialMetadata returns metadata stored alongside the serial number, 158 // such as the RegID whose certificate request created that serial, and when 159 // the certificate with that serial will expire. 160 func (ssa *SQLStorageAuthorityRO) GetSerialMetadata(ctx context.Context, req *sapb.Serial) (*sapb.SerialMetadata, error) { 161 if req == nil || req.Serial == "" { 162 return nil, errIncompleteRequest 163 } 164 165 if !core.ValidSerial(req.Serial) { 166 return nil, fmt.Errorf("invalid serial %q", req.Serial) 167 } 168 169 recordedSerial := recordedSerialModel{} 170 err := ssa.dbReadOnlyMap.SelectOne( 171 ctx, 172 &recordedSerial, 173 "SELECT * FROM serials WHERE serial = ?", 174 req.Serial, 175 ) 176 if err != nil { 177 if db.IsNoRows(err) { 178 return nil, berrors.NotFoundError("serial %q not found", req.Serial) 179 } 180 return nil, err 181 } 182 183 return &sapb.SerialMetadata{ 184 Serial: recordedSerial.Serial, 185 RegistrationID: recordedSerial.RegistrationID, 186 Created: timestamppb.New(recordedSerial.Created), 187 Expires: timestamppb.New(recordedSerial.Expires), 188 }, nil 189 } 190 191 // GetCertificate takes a serial number and returns the corresponding 192 // certificate, or error if it does not exist. 193 func (ssa *SQLStorageAuthorityRO) GetCertificate(ctx context.Context, req *sapb.Serial) (*corepb.Certificate, error) { 194 if req == nil || req.Serial == "" { 195 return nil, errIncompleteRequest 196 } 197 if !core.ValidSerial(req.Serial) { 198 return nil, fmt.Errorf("invalid certificate serial %s", req.Serial) 199 } 200 201 cert, err := SelectCertificate(ctx, ssa.dbReadOnlyMap, req.Serial) 202 if db.IsNoRows(err) { 203 return nil, berrors.NotFoundError("certificate with serial %q not found", req.Serial) 204 } 205 if err != nil { 206 return nil, err 207 } 208 return cert, nil 209 } 210 211 // GetLintPrecertificate takes a serial number and returns the corresponding 212 // linting precertificate, or error if it does not exist. The returned precert 213 // is identical to the actual submitted-to-CT-logs precertificate, except for 214 // its signature. 215 func (ssa *SQLStorageAuthorityRO) GetLintPrecertificate(ctx context.Context, req *sapb.Serial) (*corepb.Certificate, error) { 216 if req == nil || req.Serial == "" { 217 return nil, errIncompleteRequest 218 } 219 if !core.ValidSerial(req.Serial) { 220 return nil, fmt.Errorf("invalid precertificate serial %s", req.Serial) 221 } 222 223 cert, err := SelectPrecertificate(ctx, ssa.dbReadOnlyMap, req.Serial) 224 if db.IsNoRows(err) { 225 return nil, berrors.NotFoundError("precertificate with serial %q not found", req.Serial) 226 } 227 if err != nil { 228 return nil, err 229 } 230 return cert, nil 231 } 232 233 // GetCertificateStatus takes a hexadecimal string representing the full 128-bit serial 234 // number of a certificate and returns data about that certificate's current 235 // validity. 236 func (ssa *SQLStorageAuthorityRO) GetCertificateStatus(ctx context.Context, req *sapb.Serial) (*corepb.CertificateStatus, error) { 237 if req.Serial == "" { 238 return nil, errIncompleteRequest 239 } 240 if !core.ValidSerial(req.Serial) { 241 err := fmt.Errorf("invalid certificate serial %s", req.Serial) 242 return nil, err 243 } 244 245 certStatus, err := SelectCertificateStatus(ctx, ssa.dbReadOnlyMap, req.Serial) 246 if db.IsNoRows(err) { 247 return nil, berrors.NotFoundError("certificate status with serial %q not found", req.Serial) 248 } 249 if err != nil { 250 return nil, err 251 } 252 253 return certStatus, nil 254 } 255 256 // GetRevocationStatus takes a hexadecimal string representing the full serial 257 // number of a certificate and returns a minimal set of data about that cert's 258 // current validity. 259 func (ssa *SQLStorageAuthorityRO) GetRevocationStatus(ctx context.Context, req *sapb.Serial) (*sapb.RevocationStatus, error) { 260 if req.Serial == "" { 261 return nil, errIncompleteRequest 262 } 263 if !core.ValidSerial(req.Serial) { 264 return nil, fmt.Errorf("invalid certificate serial %s", req.Serial) 265 } 266 267 status, err := SelectRevocationStatus(ctx, ssa.dbReadOnlyMap, req.Serial) 268 if err != nil { 269 if db.IsNoRows(err) { 270 return nil, berrors.NotFoundError("certificate status with serial %q not found", req.Serial) 271 } 272 return nil, err 273 } 274 275 return status, nil 276 } 277 278 // FQDNSetTimestampsForWindow returns the issuance timestamps for each 279 // certificate, issued for a set of identifiers, during a given window of time, 280 // starting from the most recent issuance. 281 // 282 // If req.Limit is nonzero, it returns only the most recent `Limit` results 283 func (ssa *SQLStorageAuthorityRO) FQDNSetTimestampsForWindow(ctx context.Context, req *sapb.CountFQDNSetsRequest) (*sapb.Timestamps, error) { 284 idents := identifier.FromProtoSlice(req.Identifiers) 285 286 if core.IsAnyNilOrZero(req.Window) || len(idents) == 0 { 287 return nil, errIncompleteRequest 288 } 289 limit := req.Limit 290 if limit == 0 { 291 limit = math.MaxInt64 292 } 293 type row struct { 294 Issued time.Time 295 } 296 var rows []row 297 _, err := ssa.dbReadOnlyMap.Select( 298 ctx, 299 &rows, 300 `SELECT issued FROM fqdnSets 301 WHERE setHash = ? 302 AND issued > ? 303 ORDER BY issued DESC 304 LIMIT ?`, 305 core.HashIdentifiers(idents), 306 ssa.clk.Now().Add(-req.Window.AsDuration()), 307 limit, 308 ) 309 if err != nil { 310 return nil, err 311 } 312 313 var results []*timestamppb.Timestamp 314 for _, i := range rows { 315 results = append(results, timestamppb.New(i.Issued)) 316 } 317 return &sapb.Timestamps{Timestamps: results}, nil 318 } 319 320 // FQDNSetExists returns a bool indicating if one or more FQDN sets |names| 321 // exists in the database 322 func (ssa *SQLStorageAuthorityRO) FQDNSetExists(ctx context.Context, req *sapb.FQDNSetExistsRequest) (*sapb.Exists, error) { 323 idents := identifier.FromProtoSlice(req.Identifiers) 324 if len(idents) == 0 { 325 return nil, errIncompleteRequest 326 } 327 exists, err := ssa.checkFQDNSetExists(ctx, ssa.dbReadOnlyMap.SelectOne, idents) 328 if err != nil { 329 return nil, err 330 } 331 return &sapb.Exists{Exists: exists}, nil 332 } 333 334 // oneSelectorFunc is a func type that matches both borp.Transaction.SelectOne 335 // and borp.DbMap.SelectOne. 336 type oneSelectorFunc func(ctx context.Context, holder any, query string, args ...any) error 337 338 // checkFQDNSetExists uses the given oneSelectorFunc to check whether an fqdnSet 339 // for the given names exists. 340 func (ssa *SQLStorageAuthorityRO) checkFQDNSetExists(ctx context.Context, selector oneSelectorFunc, idents identifier.ACMEIdentifiers) (bool, error) { 341 namehash := core.HashIdentifiers(idents) 342 var exists bool 343 err := selector( 344 ctx, 345 &exists, 346 `SELECT EXISTS (SELECT id FROM fqdnSets WHERE setHash = ? LIMIT 1)`, 347 namehash, 348 ) 349 return exists, err 350 } 351 352 // GetOrder is used to retrieve an already existing order object 353 func (ssa *SQLStorageAuthorityRO) GetOrder(ctx context.Context, req *sapb.OrderRequest) (*corepb.Order, error) { 354 if req == nil || req.Id == 0 { 355 return nil, errIncompleteRequest 356 } 357 358 txn := func(tx db.Executor) (any, error) { 359 omObj, err := tx.Get(ctx, orderModel{}, req.Id) 360 if err != nil { 361 return nil, err 362 } 363 if omObj == nil { 364 return nil, berrors.NotFoundError("no order found for ID %d", req.Id) 365 } 366 367 order, err := modelToOrder(omObj.(*orderModel)) 368 if err != nil { 369 return nil, err 370 } 371 372 orderExp := order.Expires.AsTime() 373 if orderExp.Before(ssa.clk.Now()) { 374 return nil, berrors.NotFoundError("no order found for ID %d", req.Id) 375 } 376 377 // For orders created before feature flag StoreAuthzsInOrders, fetch the list of authz IDs 378 // from the orderToAuthz2 table. 379 if len(order.V2Authorizations) == 0 { 380 authzIDs, err := authzForOrder(ctx, tx, order.Id) 381 if err != nil { 382 return nil, err 383 } 384 order.V2Authorizations = authzIDs 385 } 386 387 // Get the partial Authorization objects for the order 388 authzValidityInfo, err := getAuthorizationStatuses(ctx, tx, order.V2Authorizations) 389 // If there was an error getting the authorizations, return it immediately 390 if err != nil { 391 return nil, err 392 } 393 394 var idents identifier.ACMEIdentifiers 395 for _, a := range authzValidityInfo { 396 idents = append(idents, identifier.ACMEIdentifier{Type: uintToIdentifierType[a.IdentifierType], Value: a.IdentifierValue}) 397 } 398 order.Identifiers = idents.ToProtoSlice() 399 400 // Calculate the status for the order 401 status, err := statusForOrder(order, authzValidityInfo, ssa.clk.Now()) 402 if err != nil { 403 return nil, err 404 } 405 order.Status = status 406 407 return order, nil 408 } 409 410 output, err := db.WithTransaction(ctx, ssa.dbReadOnlyMap, txn) 411 if (db.IsNoRows(err) || errors.Is(err, berrors.NotFound)) && ssa.lagFactor != 0 { 412 // GetOrder is often called shortly after a new order is created, sometimes 413 // before the order or its associated rows have propagated to the read 414 // replica yet. If we get a NoRows, wait a little bit and retry, once. 415 ssa.clk.Sleep(ssa.lagFactor) 416 output, err = db.WithTransaction(ctx, ssa.dbReadOnlyMap, txn) 417 if err != nil { 418 if db.IsNoRows(err) || errors.Is(err, berrors.NotFound) { 419 ssa.lagFactorCounter.WithLabelValues("GetOrder", "notfound").Inc() 420 } else { 421 ssa.lagFactorCounter.WithLabelValues("GetOrder", "other").Inc() 422 } 423 } else { 424 ssa.lagFactorCounter.WithLabelValues("GetOrder", "found").Inc() 425 } 426 } 427 if err != nil { 428 return nil, err 429 } 430 431 order, ok := output.(*corepb.Order) 432 if !ok { 433 return nil, fmt.Errorf("casting error in GetOrder") 434 } 435 436 return order, nil 437 } 438 439 // GetOrderForNames tries to find a **pending** or **ready** order with the 440 // exact set of names requested, associated with the given accountID. Only 441 // unexpired orders are considered. If no order meeting these requirements is 442 // found a nil corepb.Order pointer is returned. 443 func (ssa *SQLStorageAuthorityRO) GetOrderForNames(ctx context.Context, req *sapb.GetOrderForNamesRequest) (*corepb.Order, error) { 444 idents := identifier.FromProtoSlice(req.Identifiers) 445 446 if req.AcctID == 0 || len(idents) == 0 { 447 return nil, errIncompleteRequest 448 } 449 450 // Hash the names requested for lookup in the orderFqdnSets table 451 fqdnHash := core.HashIdentifiers(idents) 452 453 // Find a possibly-suitable order. We don't include the account ID or order 454 // status in this query because there's no index that includes those, so 455 // including them could require the DB to scan extra rows. 456 // Instead, we select one unexpired order that matches the fqdnSet. If 457 // that order doesn't match the account ID or status we need, just return 458 // nothing. We use `ORDER BY expires ASC` because the index on 459 // (setHash, expires) is in ASC order. DESC would be slightly nicer from a 460 // user experience perspective but would be slow when there are many entries 461 // to sort. 462 // This approach works fine because in most cases there's only one account 463 // issuing for a given name. If there are other accounts issuing for the same 464 // name, it just means order reuse happens less often. 465 var result struct { 466 OrderID int64 467 RegistrationID int64 468 } 469 var err error 470 err = ssa.dbReadOnlyMap.SelectOne(ctx, &result, ` 471 SELECT orderID, registrationID 472 FROM orderFqdnSets 473 WHERE setHash = ? 474 AND expires > ? 475 ORDER BY expires ASC 476 LIMIT 1`, 477 fqdnHash, ssa.clk.Now()) 478 479 if db.IsNoRows(err) { 480 return nil, berrors.NotFoundError("no order matching request found") 481 } else if err != nil { 482 return nil, err 483 } 484 485 if result.RegistrationID != req.AcctID { 486 return nil, berrors.NotFoundError("no order matching request found") 487 } 488 489 // Get the order 490 order, err := ssa.GetOrder(ctx, &sapb.OrderRequest{Id: result.OrderID}) 491 if err != nil { 492 return nil, err 493 } 494 // Only return a pending or ready order 495 if order.Status != string(core.StatusPending) && 496 order.Status != string(core.StatusReady) { 497 return nil, berrors.NotFoundError("no order matching request found") 498 } 499 return order, nil 500 } 501 502 func (ssa *SQLStorageAuthorityRO) getAuthorizationsByID(ctx context.Context, ids []int64) (*sapb.Authorizations, error) { 503 selector, err := db.NewMappedSelector[authzModel](ssa.dbReadOnlyMap) 504 if err != nil { 505 return nil, fmt.Errorf("initializing db map: %w", err) 506 } 507 508 clauses := fmt.Sprintf(`WHERE id IN (%s)`, db.QuestionMarks(len(ids))) 509 510 var sliceOfAny []any 511 for _, id := range ids { 512 sliceOfAny = append(sliceOfAny, id) 513 } 514 rows, err := selector.QueryContext(ctx, clauses, sliceOfAny...) 515 if err != nil { 516 return nil, fmt.Errorf("reading db: %w", err) 517 } 518 519 var ret []*corepb.Authorization 520 err = rows.ForEach(func(row *authzModel) error { 521 authz, err := modelToAuthzPB(*row) 522 if err != nil { 523 return err 524 } 525 ret = append(ret, authz) 526 return nil 527 }) 528 if err != nil { 529 return nil, fmt.Errorf("reading db: %w", err) 530 } 531 return &sapb.Authorizations{Authzs: ret}, nil 532 } 533 534 // GetAuthorization2 returns the authz2 style authorization identified by the provided ID or an error. 535 // If no authorization is found matching the ID a berrors.NotFound type error is returned. 536 func (ssa *SQLStorageAuthorityRO) GetAuthorization2(ctx context.Context, req *sapb.AuthorizationID2) (*corepb.Authorization, error) { 537 if req.Id == 0 { 538 return nil, errIncompleteRequest 539 } 540 obj, err := ssa.dbReadOnlyMap.Get(ctx, authzModel{}, req.Id) 541 if db.IsNoRows(err) && ssa.lagFactor != 0 { 542 // GetAuthorization2 is often called shortly after a new order is created, 543 // sometimes before the order's associated authz rows have propagated to the 544 // read replica yet. If we get a NoRows, wait a little bit and retry, once. 545 ssa.clk.Sleep(ssa.lagFactor) 546 obj, err = ssa.dbReadOnlyMap.Get(ctx, authzModel{}, req.Id) 547 if err != nil { 548 if db.IsNoRows(err) { 549 ssa.lagFactorCounter.WithLabelValues("GetAuthorization2", "notfound").Inc() 550 } else { 551 ssa.lagFactorCounter.WithLabelValues("GetAuthorization2", "other").Inc() 552 } 553 } else { 554 ssa.lagFactorCounter.WithLabelValues("GetAuthorization2", "found").Inc() 555 } 556 } 557 if err != nil { 558 return nil, err 559 } 560 if obj == nil { 561 return nil, berrors.NotFoundError("authorization %d not found", req.Id) 562 } 563 return modelToAuthzPB(*(obj.(*authzModel))) 564 } 565 566 // authzModelMapToPB converts a mapping of identifiers to authzModels into a 567 // protobuf authorizations map 568 func authzModelMapToPB(m map[identifier.ACMEIdentifier]authzModel) (*sapb.Authorizations, error) { 569 resp := &sapb.Authorizations{} 570 for _, v := range m { 571 authzPB, err := modelToAuthzPB(v) 572 if err != nil { 573 return nil, err 574 } 575 resp.Authzs = append(resp.Authzs, authzPB) 576 } 577 return resp, nil 578 } 579 580 // CountPendingAuthorizations2 returns the number of pending, unexpired authorizations 581 // for the given registration. 582 func (ssa *SQLStorageAuthorityRO) CountPendingAuthorizations2(ctx context.Context, req *sapb.RegistrationID) (*sapb.Count, error) { 583 if req.Id == 0 { 584 return nil, errIncompleteRequest 585 } 586 587 var count int64 588 err := ssa.dbReadOnlyMap.SelectOne(ctx, &count, 589 `SELECT COUNT(*) FROM authz2 WHERE 590 registrationID = :regID AND 591 expires > :expires AND 592 status = :status`, 593 map[string]any{ 594 "regID": req.Id, 595 "expires": ssa.clk.Now(), 596 "status": statusUint(core.StatusPending), 597 }, 598 ) 599 if err != nil { 600 return nil, err 601 } 602 return &sapb.Count{Count: count}, nil 603 } 604 605 // GetValidOrderAuthorizations2 is used to get all authorizations 606 // associated with the given Order ID. 607 // NOTE: The name is outdated. It does *not* filter out invalid or expired 608 // authorizations; that it left to the caller. It also ignores the RegID field 609 // of the input: ensuring that the returned authorizations match the same RegID 610 // as the Order is also left to the caller. This is because the caller is 611 // generally in a better position to provide insightful error messages, whereas 612 // simply omitting an authz from this method's response would leave the caller 613 // wondering why that authz was omitted. 614 func (ssa *SQLStorageAuthorityRO) GetValidOrderAuthorizations2(ctx context.Context, req *sapb.GetValidOrderAuthorizationsRequest) (*sapb.Authorizations, error) { 615 if core.IsAnyNilOrZero(req.Id) { 616 return nil, errIncompleteRequest 617 } 618 619 if features.Get().StoreAuthzsInOrders { 620 om, err := ssa.dbReadOnlyMap.Get(ctx, &orderModel{}, req.Id) 621 if err != nil { 622 return nil, err 623 } 624 // Nonexistent orders return no error, with an empty list of authorizations 625 if om == nil { 626 return &sapb.Authorizations{}, nil 627 } 628 629 order, err := modelToOrder(om.(*orderModel)) 630 if err != nil { 631 return nil, err 632 } 633 634 // If the order had a list of authz IDs (from the `Authzs` column in the DB), query 635 // them and return. Otherwise, fall through to doing a JOIN query using orderToAuthz2 636 // and authz2 tables. 637 if len(order.V2Authorizations) > 0 { 638 authzs, err := ssa.getAuthorizationsByID(ctx, order.V2Authorizations) 639 if err != nil { 640 return nil, err 641 } 642 return authzs, nil 643 } 644 } 645 646 // The authz2 and orderToAuthz2 tables both have a column named "id", so we 647 // need to be explicit about which table's "id" column we want to select. 648 qualifiedAuthzFields := strings.Split(authzFields, " ") 649 for i, field := range qualifiedAuthzFields { 650 if field == "id," { 651 qualifiedAuthzFields[i] = "authz2.id," 652 break 653 } 654 } 655 656 var ams []authzModel 657 _, err := ssa.dbReadOnlyMap.Select( 658 ctx, 659 &ams, 660 fmt.Sprintf(`SELECT %s FROM authz2 661 LEFT JOIN orderToAuthz2 ON authz2.ID = orderToAuthz2.authzID 662 WHERE orderToAuthz2.orderID = :orderID`, 663 strings.Join(qualifiedAuthzFields, " "), 664 ), 665 map[string]any{ 666 "orderID": req.Id, 667 }, 668 ) 669 if err != nil { 670 return nil, err 671 } 672 673 // TODO(#8111): Consider reducing the volume of data in this map. 674 byIdent := make(map[identifier.ACMEIdentifier]authzModel) 675 for _, am := range ams { 676 identType, ok := uintToIdentifierType[am.IdentifierType] 677 if !ok { 678 return nil, fmt.Errorf("unrecognized identifier type encoding %d on authz id %d", am.IdentifierType, am.ID) 679 } 680 ident := identifier.ACMEIdentifier{Type: identType, Value: am.IdentifierValue} 681 _, present := byIdent[ident] 682 if present { 683 return nil, fmt.Errorf("identifier %q appears twice in authzs for order %d", am.IdentifierValue, req.Id) 684 } 685 byIdent[ident] = am 686 } 687 688 return authzModelMapToPB(byIdent) 689 } 690 691 // CountInvalidAuthorizations2 counts invalid authorizations for a user expiring 692 // in a given time range. 693 func (ssa *SQLStorageAuthorityRO) CountInvalidAuthorizations2(ctx context.Context, req *sapb.CountInvalidAuthorizationsRequest) (*sapb.Count, error) { 694 ident := identifier.FromProto(req.Identifier) 695 696 if core.IsAnyNilOrZero(req.RegistrationID, ident, req.Range.Earliest, req.Range.Latest) { 697 return nil, errIncompleteRequest 698 } 699 700 idType, ok := identifierTypeToUint[ident.ToProto().Type] 701 if !ok { 702 return nil, fmt.Errorf("unsupported identifier type %q", ident.ToProto().Type) 703 } 704 705 var count int64 706 err := ssa.dbReadOnlyMap.SelectOne( 707 ctx, 708 &count, 709 `SELECT COUNT(*) FROM authz2 WHERE 710 registrationID = :regID AND 711 status = :status AND 712 expires > :expiresEarliest AND 713 expires <= :expiresLatest AND 714 identifierType = :identType AND 715 identifierValue = :identValue`, 716 map[string]any{ 717 "regID": req.RegistrationID, 718 "identType": idType, 719 "identValue": ident.Value, 720 "expiresEarliest": req.Range.Earliest.AsTime(), 721 "expiresLatest": req.Range.Latest.AsTime(), 722 "status": statusUint(core.StatusInvalid), 723 }, 724 ) 725 if err != nil { 726 return nil, err 727 } 728 return &sapb.Count{Count: count}, nil 729 } 730 731 // GetValidAuthorizations2 returns a single valid authorization owned by the 732 // given account for all given identifiers. If more than one valid authorization 733 // exists, only the one with the latest expiry will be returned. 734 func (ssa *SQLStorageAuthorityRO) GetValidAuthorizations2(ctx context.Context, req *sapb.GetValidAuthorizationsRequest) (*sapb.Authorizations, error) { 735 idents := identifier.FromProtoSlice(req.Identifiers) 736 737 if core.IsAnyNilOrZero(req, req.RegistrationID, idents, req.ValidUntil) { 738 return nil, errIncompleteRequest 739 } 740 741 // The WHERE clause returned by this function does not contain any 742 // user-controlled strings; all user-controlled input ends up in the 743 // returned placeholder args. 744 identConditions, identArgs := buildIdentifierQueryConditions(idents) 745 query := fmt.Sprintf( 746 `SELECT %s FROM authz2 747 USE INDEX (regID_identifier_status_expires_idx) 748 WHERE registrationID = ? AND 749 status = ? AND 750 expires > ? AND 751 (%s)`, 752 authzFields, 753 identConditions, 754 ) 755 756 params := []any{ 757 req.RegistrationID, 758 statusUint(core.StatusValid), 759 req.ValidUntil.AsTime(), 760 } 761 params = append(params, identArgs...) 762 763 var authzModels []authzModel 764 _, err := ssa.dbReadOnlyMap.Select( 765 ctx, 766 &authzModels, 767 query, 768 params..., 769 ) 770 if err != nil { 771 return nil, err 772 } 773 774 if len(authzModels) == 0 { 775 return &sapb.Authorizations{}, nil 776 } 777 778 // TODO(#8111): Consider reducing the volume of data in this map. 779 authzMap := make(map[identifier.ACMEIdentifier]authzModel, len(authzModels)) 780 for _, am := range authzModels { 781 if req.Profile != "" { 782 // Don't return authzs whose profile doesn't match that requested. 783 if am.CertificateProfileName == nil || *am.CertificateProfileName != req.Profile { 784 continue 785 } 786 } 787 // If there is an existing authorization in the map only replace it with one 788 // which has a later expiry. 789 identType, ok := uintToIdentifierType[am.IdentifierType] 790 if !ok { 791 return nil, fmt.Errorf("unrecognized identifier type encoding %d on authz id %d", am.IdentifierType, am.ID) 792 } 793 ident := identifier.ACMEIdentifier{Type: identType, Value: am.IdentifierValue} 794 existing, present := authzMap[ident] 795 if present && am.Expires.Before(existing.Expires) { 796 continue 797 } 798 authzMap[ident] = am 799 } 800 801 return authzModelMapToPB(authzMap) 802 } 803 804 // KeyBlocked checks if a key, indicated by a hash, is present in the blockedKeys table 805 func (ssa *SQLStorageAuthorityRO) KeyBlocked(ctx context.Context, req *sapb.SPKIHash) (*sapb.Exists, error) { 806 if req == nil || req.KeyHash == nil { 807 return nil, errIncompleteRequest 808 } 809 810 var id int64 811 err := ssa.dbReadOnlyMap.SelectOne(ctx, &id, `SELECT ID FROM blockedKeys WHERE keyHash = ?`, req.KeyHash) 812 if err != nil { 813 if db.IsNoRows(err) { 814 return &sapb.Exists{Exists: false}, nil 815 } 816 return nil, err 817 } 818 819 return &sapb.Exists{Exists: true}, nil 820 } 821 822 // IncidentsForSerial queries each active incident table and returns every 823 // incident that currently impacts `req.Serial`. 824 func (ssa *SQLStorageAuthorityRO) IncidentsForSerial(ctx context.Context, req *sapb.Serial) (*sapb.Incidents, error) { 825 if req == nil { 826 return nil, errIncompleteRequest 827 } 828 829 var activeIncidents []incidentModel 830 _, err := ssa.dbReadOnlyMap.Select(ctx, &activeIncidents, `SELECT * FROM incidents WHERE enabled = 1`) 831 if err != nil { 832 if db.IsNoRows(err) { 833 return &sapb.Incidents{}, nil 834 } 835 return nil, err 836 } 837 838 var incidentsForSerial []*sapb.Incident 839 for _, i := range activeIncidents { 840 var count int 841 err := ssa.dbIncidentsMap.SelectOne(ctx, &count, fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE serial = ?", 842 i.SerialTable), req.Serial) 843 if err != nil { 844 if db.IsNoRows(err) { 845 continue 846 } 847 return nil, err 848 } 849 if count > 0 { 850 incident := incidentModelToPB(i) 851 incidentsForSerial = append(incidentsForSerial, &incident) 852 } 853 854 } 855 if len(incidentsForSerial) == 0 { 856 return &sapb.Incidents{}, nil 857 } 858 return &sapb.Incidents{Incidents: incidentsForSerial}, nil 859 } 860 861 // SerialsForIncident queries the provided incident table and returns the 862 // resulting rows as a stream of `*sapb.IncidentSerial`s. An `io.EOF` error 863 // signals that there are no more serials to send. If the incident table in 864 // question contains zero rows, only an `io.EOF` error is returned. The 865 // IncidentSerial messages returned may have the zero-value for their OrderID, 866 // RegistrationID, and LastNoticeSent fields, if those are NULL in the database. 867 func (ssa *SQLStorageAuthorityRO) SerialsForIncident(req *sapb.SerialsForIncidentRequest, stream grpc.ServerStreamingServer[sapb.IncidentSerial]) error { 868 if req.IncidentTable == "" { 869 return errIncompleteRequest 870 } 871 872 // Check that `req.IncidentTable` is a valid incident table name. 873 if !validIncidentTableRegexp.MatchString(req.IncidentTable) { 874 return fmt.Errorf("malformed table name %q", req.IncidentTable) 875 } 876 877 selector, err := db.NewMappedSelector[incidentSerialModel](ssa.dbIncidentsMap) 878 if err != nil { 879 return fmt.Errorf("initializing db map: %w", err) 880 } 881 882 rows, err := selector.QueryFrom(stream.Context(), req.IncidentTable, "") 883 if err != nil { 884 return fmt.Errorf("starting db query: %w", err) 885 } 886 887 return rows.ForEach(func(row *incidentSerialModel) error { 888 // Scan the row into the model. Note: the fields must be passed in the 889 // same order as the columns returned by the query above. 890 ism, err := rows.Get() 891 if err != nil { 892 return err 893 } 894 895 ispb := &sapb.IncidentSerial{ 896 Serial: ism.Serial, 897 } 898 if ism.RegistrationID != nil { 899 ispb.RegistrationID = *ism.RegistrationID 900 } 901 if ism.OrderID != nil { 902 ispb.OrderID = *ism.OrderID 903 } 904 if ism.LastNoticeSent != nil { 905 ispb.LastNoticeSent = timestamppb.New(*ism.LastNoticeSent) 906 } 907 908 return stream.Send(ispb) 909 }) 910 } 911 912 // GetRevokedCertsByShard returns revoked certificates by explicit sharding. 913 // 914 // It returns all unexpired certificates from the revokedCertificates table with the given 915 // shardIdx. It limits the results those revoked before req.RevokedBefore. 916 func (ssa *SQLStorageAuthorityRO) GetRevokedCertsByShard(req *sapb.GetRevokedCertsByShardRequest, stream grpc.ServerStreamingServer[corepb.CRLEntry]) error { 917 if core.IsAnyNilOrZero(req.ShardIdx, req.IssuerNameID, req.RevokedBefore, req.ExpiresAfter) { 918 return errIncompleteRequest 919 } 920 921 atTime := req.RevokedBefore.AsTime() 922 923 clauses := ` 924 WHERE issuerID = ? 925 AND shardIdx = ? 926 AND notAfterHour >= ?` 927 params := []any{ 928 req.IssuerNameID, 929 req.ShardIdx, 930 // Round the expiry down to the nearest hour, to take advantage of our 931 // smaller index while still capturing at least as many certs as intended. 932 req.ExpiresAfter.AsTime().Truncate(time.Hour), 933 } 934 935 selector, err := db.NewMappedSelector[revokedCertModel](ssa.dbReadOnlyMap) 936 if err != nil { 937 return fmt.Errorf("initializing db map: %w", err) 938 } 939 940 rows, err := selector.QueryContext(stream.Context(), clauses, params...) 941 if err != nil { 942 return fmt.Errorf("reading db: %w", err) 943 } 944 945 return rows.ForEach(func(row *revokedCertModel) error { 946 // Double-check that the cert wasn't revoked between the time at which we're 947 // constructing this snapshot CRL and right now. If the cert was revoked 948 // at-or-after the "atTime", we'll just include it in the next generation 949 // of CRLs. 950 if row.RevokedDate.After(atTime) || row.RevokedDate.Equal(atTime) { 951 return nil 952 } 953 954 return stream.Send(&corepb.CRLEntry{ 955 Serial: row.Serial, 956 Reason: int32(row.RevokedReason), //nolint: gosec // Revocation reasons are guaranteed to be small, no risk of overflow. 957 RevokedAt: timestamppb.New(row.RevokedDate), 958 }) 959 }) 960 } 961 962 // Health implements the grpc.checker interface. 963 func (ssa *SQLStorageAuthorityRO) Health(ctx context.Context) error { 964 err := ssa.dbReadOnlyMap.SelectOne(ctx, new(int), "SELECT 1") 965 if err != nil { 966 return err 967 } 968 return nil 969 } 970 971 // ReplacementOrderExists returns whether a valid replacement order exists for 972 // the given certificate serial number. An existing but expired or otherwise 973 // invalid replacement order is not considered to exist. 974 func (ssa *SQLStorageAuthorityRO) ReplacementOrderExists(ctx context.Context, req *sapb.Serial) (*sapb.Exists, error) { 975 if req == nil || req.Serial == "" { 976 return nil, errIncompleteRequest 977 } 978 979 var replacement replacementOrderModel 980 err := ssa.dbReadOnlyMap.SelectOne( 981 ctx, 982 &replacement, 983 "SELECT * FROM replacementOrders WHERE serial = ? LIMIT 1", 984 req.Serial, 985 ) 986 if err != nil { 987 if db.IsNoRows(err) { 988 // No replacement order exists. 989 return &sapb.Exists{Exists: false}, nil 990 } 991 return nil, err 992 } 993 if replacement.Replaced { 994 // Certificate has already been replaced. 995 return &sapb.Exists{Exists: true}, nil 996 } 997 if replacement.OrderExpires.Before(ssa.clk.Now()) { 998 // The existing replacement order has expired. 999 return &sapb.Exists{Exists: false}, nil 1000 } 1001 1002 // Pull the replacement order so we can inspect its status. 1003 replacementOrder, err := ssa.GetOrder(ctx, &sapb.OrderRequest{Id: replacement.OrderID}) 1004 if err != nil { 1005 if errors.Is(err, berrors.NotFound) { 1006 // The existing replacement order has been deleted. This should 1007 // never happen. 1008 ssa.log.Errf("replacement order %d for serial %q not found", replacement.OrderID, req.Serial) 1009 return &sapb.Exists{Exists: false}, nil 1010 } 1011 } 1012 1013 switch replacementOrder.Status { 1014 case string(core.StatusPending), string(core.StatusReady), string(core.StatusProcessing), string(core.StatusValid): 1015 // An existing replacement order is either still being worked on or has 1016 // already been finalized. 1017 return &sapb.Exists{Exists: true}, nil 1018 1019 case string(core.StatusInvalid): 1020 // The existing replacement order cannot be finalized. The requester 1021 // should create a new replacement order. 1022 return &sapb.Exists{Exists: false}, nil 1023 1024 default: 1025 // Replacement order is in an unknown state. This should never happen. 1026 return nil, fmt.Errorf("unknown replacement order status: %q", replacementOrder.Status) 1027 } 1028 } 1029 1030 // GetSerialsByKey returns a stream of serials for all unexpired certificates 1031 // whose public key matches the given SPKIHash. This is useful for revoking all 1032 // certificates affected by a key compromise. 1033 func (ssa *SQLStorageAuthorityRO) GetSerialsByKey(req *sapb.SPKIHash, stream grpc.ServerStreamingServer[sapb.Serial]) error { 1034 clauses := ` 1035 WHERE keyHash = ? 1036 AND certNotAfter > ?` 1037 params := []any{ 1038 req.KeyHash, 1039 ssa.clk.Now(), 1040 } 1041 1042 selector, err := db.NewMappedSelector[keyHashModel](ssa.dbReadOnlyMap) 1043 if err != nil { 1044 return fmt.Errorf("initializing db map: %w", err) 1045 } 1046 1047 rows, err := selector.QueryContext(stream.Context(), clauses, params...) 1048 if err != nil { 1049 return fmt.Errorf("reading db: %w", err) 1050 } 1051 1052 return rows.ForEach(func(row *keyHashModel) error { 1053 return stream.Send(&sapb.Serial{Serial: row.CertSerial}) 1054 }) 1055 } 1056 1057 // GetSerialsByAccount returns a stream of all serials for all unexpired 1058 // certificates issued to the given RegID. This is useful for revoking all of 1059 // an account's certs upon their request. 1060 func (ssa *SQLStorageAuthorityRO) GetSerialsByAccount(req *sapb.RegistrationID, stream grpc.ServerStreamingServer[sapb.Serial]) error { 1061 clauses := ` 1062 WHERE registrationID = ? 1063 AND expires > ?` 1064 params := []any{ 1065 req.Id, 1066 ssa.clk.Now(), 1067 } 1068 1069 selector, err := db.NewMappedSelector[recordedSerialModel](ssa.dbReadOnlyMap) 1070 if err != nil { 1071 return fmt.Errorf("initializing db map: %w", err) 1072 } 1073 1074 rows, err := selector.QueryContext(stream.Context(), clauses, params...) 1075 if err != nil { 1076 return fmt.Errorf("reading db: %w", err) 1077 } 1078 1079 return rows.ForEach(func(row *recordedSerialModel) error { 1080 return stream.Send(&sapb.Serial{Serial: row.Serial}) 1081 }) 1082 } 1083 1084 // CheckIdentifiersPaused takes a slice of identifiers and returns a slice of 1085 // the first 15 identifier values which are currently paused for the provided 1086 // account. If no matches are found, an empty slice is returned. 1087 func (ssa *SQLStorageAuthorityRO) CheckIdentifiersPaused(ctx context.Context, req *sapb.PauseRequest) (*sapb.Identifiers, error) { 1088 if core.IsAnyNilOrZero(req.RegistrationID, req.Identifiers) { 1089 return nil, errIncompleteRequest 1090 } 1091 1092 idents, err := newIdentifierModelsFromPB(req.Identifiers) 1093 if err != nil { 1094 return nil, err 1095 } 1096 1097 if len(idents) == 0 { 1098 // No identifier values to check. 1099 return nil, nil 1100 } 1101 1102 identsByType := map[uint8][]string{} 1103 for _, id := range idents { 1104 identsByType[id.Type] = append(identsByType[id.Type], id.Value) 1105 } 1106 1107 // Build a query to retrieve up to 15 paused identifiers using OR clauses 1108 // for conditions specific to each type. This approach handles mixed 1109 // identifier types in a single query. Assuming 3 DNS identifiers and 1 IP 1110 // identifier, the resulting query would look like: 1111 // 1112 // SELECT identifierType, identifierValue 1113 // FROM paused WHERE registrationID = ? AND 1114 // unpausedAt IS NULL AND 1115 // ((identifierType = ? AND identifierValue IN (?, ?, ?)) OR 1116 // (identifierType = ? AND identifierValue IN (?))) 1117 // LIMIT 15 1118 // 1119 // Corresponding args array for placeholders: [<regID>, 0, "example.com", 1120 // "example.net", "example.org", 1, "1.2.3.4"] 1121 1122 var conditions []string 1123 args := []any{req.RegistrationID} 1124 for idType, values := range identsByType { 1125 conditions = append(conditions, 1126 fmt.Sprintf("identifierType = ? AND identifierValue IN (%s)", 1127 db.QuestionMarks(len(values)), 1128 ), 1129 ) 1130 args = append(args, idType) 1131 for _, value := range values { 1132 args = append(args, value) 1133 } 1134 } 1135 1136 query := fmt.Sprintf(` 1137 SELECT identifierType, identifierValue 1138 FROM paused 1139 WHERE registrationID = ? AND unpausedAt IS NULL AND (%s) LIMIT 15`, 1140 strings.Join(conditions, " OR ")) 1141 1142 var matches []identifierModel 1143 _, err = ssa.dbReadOnlyMap.Select(ctx, &matches, query, args...) 1144 if err != nil && !db.IsNoRows(err) { 1145 // Error querying the database. 1146 return nil, err 1147 } 1148 1149 return newPBFromIdentifierModels(matches) 1150 } 1151 1152 // GetPausedIdentifiers returns a slice of paused identifiers for the provided 1153 // account. If no paused identifiers are found, an empty slice is returned. The 1154 // results are limited to the first 15 paused identifiers. 1155 func (ssa *SQLStorageAuthorityRO) GetPausedIdentifiers(ctx context.Context, req *sapb.RegistrationID) (*sapb.Identifiers, error) { 1156 if core.IsAnyNilOrZero(req.Id) { 1157 return nil, errIncompleteRequest 1158 } 1159 1160 var matches []identifierModel 1161 _, err := ssa.dbReadOnlyMap.Select(ctx, &matches, ` 1162 SELECT identifierType, identifierValue 1163 FROM paused 1164 WHERE 1165 registrationID = ? AND 1166 unpausedAt IS NULL 1167 LIMIT 15`, 1168 req.Id, 1169 ) 1170 if err != nil && !db.IsNoRows(err) { 1171 return nil, err 1172 } 1173 1174 return newPBFromIdentifierModels(matches) 1175 } 1176 1177 // GetRateLimitOverride retrieves a rate limit override for the given bucket key 1178 // and limit. If no override is found, a NotFound error is returned. 1179 func (ssa *SQLStorageAuthorityRO) GetRateLimitOverride(ctx context.Context, req *sapb.GetRateLimitOverrideRequest) (*sapb.RateLimitOverrideResponse, error) { 1180 if core.IsAnyNilOrZero(req, req.LimitEnum, req.BucketKey) { 1181 return nil, errIncompleteRequest 1182 } 1183 1184 obj, err := ssa.dbReadOnlyMap.Get(ctx, overrideModel{}, req.LimitEnum, req.BucketKey) 1185 if db.IsNoRows(err) { 1186 return nil, berrors.NotFoundError( 1187 "no rate limit override found for limit %d and bucket key %s", 1188 req.LimitEnum, 1189 req.BucketKey, 1190 ) 1191 } 1192 if err != nil { 1193 return nil, err 1194 } 1195 row := obj.(*overrideModel) 1196 1197 return &sapb.RateLimitOverrideResponse{ 1198 Override: newPBFromOverrideModel(row), 1199 Enabled: row.Enabled, 1200 UpdatedAt: timestamppb.New(row.UpdatedAt), 1201 }, nil 1202 } 1203 1204 // GetEnabledRateLimitOverrides retrieves all enabled rate limit overrides from 1205 // the database. The results are returned as a stream. If no enabled overrides 1206 // are found, an empty stream is returned. 1207 func (ssa *SQLStorageAuthorityRO) GetEnabledRateLimitOverrides(_ *emptypb.Empty, stream sapb.StorageAuthorityReadOnly_GetEnabledRateLimitOverridesServer) error { 1208 selector, err := db.NewMappedSelector[overrideModel](ssa.dbReadOnlyMap) 1209 if err != nil { 1210 return fmt.Errorf("initializing selector: %w", err) 1211 } 1212 1213 rows, err := selector.QueryContext(stream.Context(), "WHERE enabled = true") 1214 if err != nil { 1215 return fmt.Errorf("querying enabled overrides: %w", err) 1216 } 1217 1218 return rows.ForEach(func(m *overrideModel) error { 1219 return stream.Send(&sapb.RateLimitOverrideResponse{ 1220 Override: newPBFromOverrideModel(m), 1221 Enabled: m.Enabled, 1222 UpdatedAt: timestamppb.New(m.UpdatedAt), 1223 }) 1224 }) 1225 }