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  }