...

Source file src/github.com/letsencrypt/boulder/rocsp/config/rocsp_config.go

Documentation: github.com/letsencrypt/boulder/rocsp/config

     1  package rocsp_config
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/x509/pkix"
     6  	"encoding/asn1"
     7  	"errors"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/jmhodges/clock"
    12  	"github.com/prometheus/client_golang/prometheus"
    13  	"github.com/redis/go-redis/v9"
    14  	"golang.org/x/crypto/ocsp"
    15  
    16  	"github.com/letsencrypt/boulder/cmd"
    17  	"github.com/letsencrypt/boulder/config"
    18  	"github.com/letsencrypt/boulder/issuance"
    19  	bredis "github.com/letsencrypt/boulder/redis"
    20  	"github.com/letsencrypt/boulder/rocsp"
    21  )
    22  
    23  // RedisConfig contains the configuration needed to act as a Redis client.
    24  //
    25  // TODO(#7081): Deprecate this in favor of bredis.Config once we can support SRV
    26  // lookups in rocsp.
    27  type RedisConfig struct {
    28  	// PasswordFile is a file containing the password for the Redis user.
    29  	cmd.PasswordConfig
    30  	// TLS contains the configuration to speak TLS with Redis.
    31  	TLS cmd.TLSConfig
    32  	// Username is a Redis username.
    33  	Username string `validate:"required"`
    34  	// ShardAddrs is a map of shard names to IP address:port pairs. The go-redis
    35  	// `Ring` client will shard reads and writes across the provided Redis
    36  	// Servers based on a consistent hashing algorithm.
    37  	ShardAddrs map[string]string `validate:"min=1,dive,hostname_port"`
    38  	// Timeout is a per-request timeout applied to all Redis requests.
    39  	Timeout config.Duration `validate:"-"`
    40  
    41  	// Enables read-only commands on replicas.
    42  	ReadOnly bool
    43  	// Allows routing read-only commands to the closest primary or replica.
    44  	// It automatically enables ReadOnly.
    45  	RouteByLatency bool
    46  	// Allows routing read-only commands to a random primary or replica.
    47  	// It automatically enables ReadOnly.
    48  	RouteRandomly bool
    49  
    50  	// PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO).
    51  	PoolFIFO bool
    52  
    53  	// Maximum number of retries before giving up.
    54  	// Default is to not retry failed commands.
    55  	MaxRetries int `validate:"min=0"`
    56  	// Minimum backoff between each retry.
    57  	// Default is 8 milliseconds; -1 disables backoff.
    58  	MinRetryBackoff config.Duration `validate:"-"`
    59  	// Maximum backoff between each retry.
    60  	// Default is 512 milliseconds; -1 disables backoff.
    61  	MaxRetryBackoff config.Duration `validate:"-"`
    62  
    63  	// Dial timeout for establishing new connections.
    64  	// Default is 5 seconds.
    65  	DialTimeout config.Duration `validate:"-"`
    66  	// Timeout for socket reads. If reached, commands will fail
    67  	// with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
    68  	// Default is 3 seconds.
    69  	ReadTimeout config.Duration `validate:"-"`
    70  	// Timeout for socket writes. If reached, commands will fail
    71  	// with a timeout instead of blocking.
    72  	// Default is ReadTimeout.
    73  	WriteTimeout config.Duration `validate:"-"`
    74  
    75  	// Maximum number of socket connections.
    76  	// Default is 5 connections per every CPU as reported by runtime.NumCPU.
    77  	// If this is set to an explicit value, that's not multiplied by NumCPU.
    78  	// PoolSize applies per cluster node and not for the whole cluster.
    79  	// https://pkg.go.dev/github.com/go-redis/redis#ClusterOptions
    80  	PoolSize int `validate:"min=0"`
    81  	// Minimum number of idle connections which is useful when establishing
    82  	// new connection is slow.
    83  	MinIdleConns int `validate:"min=0"`
    84  	// Connection age at which client retires (closes) the connection.
    85  	// Default is to not close aged connections.
    86  	MaxConnAge config.Duration `validate:"-"`
    87  	// Amount of time client waits for connection if all connections
    88  	// are busy before returning an error.
    89  	// Default is ReadTimeout + 1 second.
    90  	PoolTimeout config.Duration `validate:"-"`
    91  	// Amount of time after which client closes idle connections.
    92  	// Should be less than server's timeout.
    93  	// Default is 5 minutes. -1 disables idle timeout check.
    94  	IdleTimeout config.Duration `validate:"-"`
    95  	// Frequency of idle checks made by idle connections reaper.
    96  	// Default is 1 minute. -1 disables idle connections reaper,
    97  	// but idle connections are still discarded by the client
    98  	// if IdleTimeout is set.
    99  	// Deprecated: This field has been deprecated and will be removed.
   100  	IdleCheckFrequency config.Duration `validate:"-"`
   101  }
   102  
   103  // MakeClient produces a read-write ROCSP client from a config.
   104  func MakeClient(c *RedisConfig, clk clock.Clock, stats prometheus.Registerer) (*rocsp.RWClient, error) {
   105  	password, err := c.PasswordConfig.Pass()
   106  	if err != nil {
   107  		return nil, fmt.Errorf("loading password: %w", err)
   108  	}
   109  
   110  	tlsConfig, err := c.TLS.Load(stats)
   111  	if err != nil {
   112  		return nil, fmt.Errorf("loading TLS config: %w", err)
   113  	}
   114  
   115  	rdb := redis.NewRing(&redis.RingOptions{
   116  		Addrs:     c.ShardAddrs,
   117  		Username:  c.Username,
   118  		Password:  password,
   119  		TLSConfig: tlsConfig,
   120  
   121  		MaxRetries:      c.MaxRetries,
   122  		MinRetryBackoff: c.MinRetryBackoff.Duration,
   123  		MaxRetryBackoff: c.MaxRetryBackoff.Duration,
   124  		DialTimeout:     c.DialTimeout.Duration,
   125  		ReadTimeout:     c.ReadTimeout.Duration,
   126  		WriteTimeout:    c.WriteTimeout.Duration,
   127  
   128  		PoolSize:        c.PoolSize,
   129  		MinIdleConns:    c.MinIdleConns,
   130  		ConnMaxLifetime: c.MaxConnAge.Duration,
   131  		PoolTimeout:     c.PoolTimeout.Duration,
   132  		ConnMaxIdleTime: c.IdleTimeout.Duration,
   133  	})
   134  	return rocsp.NewWritingClient(rdb, c.Timeout.Duration, clk, stats), nil
   135  }
   136  
   137  // MakeReadClient produces a read-only ROCSP client from a config.
   138  func MakeReadClient(c *RedisConfig, clk clock.Clock, stats prometheus.Registerer) (*rocsp.ROClient, error) {
   139  	if len(c.ShardAddrs) == 0 {
   140  		return nil, errors.New("redis config's 'shardAddrs' field was empty")
   141  	}
   142  
   143  	password, err := c.PasswordConfig.Pass()
   144  	if err != nil {
   145  		return nil, fmt.Errorf("loading password: %w", err)
   146  	}
   147  
   148  	tlsConfig, err := c.TLS.Load(stats)
   149  	if err != nil {
   150  		return nil, fmt.Errorf("loading TLS config: %w", err)
   151  	}
   152  
   153  	rdb := redis.NewRing(&redis.RingOptions{
   154  		Addrs:     c.ShardAddrs,
   155  		Username:  c.Username,
   156  		Password:  password,
   157  		TLSConfig: tlsConfig,
   158  
   159  		PoolFIFO: c.PoolFIFO,
   160  
   161  		MaxRetries:      c.MaxRetries,
   162  		MinRetryBackoff: c.MinRetryBackoff.Duration,
   163  		MaxRetryBackoff: c.MaxRetryBackoff.Duration,
   164  		DialTimeout:     c.DialTimeout.Duration,
   165  		ReadTimeout:     c.ReadTimeout.Duration,
   166  
   167  		PoolSize:        c.PoolSize,
   168  		MinIdleConns:    c.MinIdleConns,
   169  		ConnMaxLifetime: c.MaxConnAge.Duration,
   170  		PoolTimeout:     c.PoolTimeout.Duration,
   171  		ConnMaxIdleTime: c.IdleTimeout.Duration,
   172  	})
   173  	bredis.MustRegisterClientMetricsCollector(rdb, stats, rdb.Options().Addrs, rdb.Options().Username)
   174  	return rocsp.NewReadingClient(rdb, c.Timeout.Duration, clk, stats), nil
   175  }
   176  
   177  // A ShortIDIssuer combines an issuance.Certificate with some fields necessary
   178  // to process OCSP responses: the subject name and the shortID.
   179  type ShortIDIssuer struct {
   180  	*issuance.Certificate
   181  	subject pkix.RDNSequence
   182  	shortID byte
   183  }
   184  
   185  // LoadIssuers takes a map where the keys are filenames and the values are the
   186  // corresponding short issuer ID. It loads issuer certificates from the given
   187  // files and produces a []ShortIDIssuer.
   188  func LoadIssuers(input map[string]int) ([]ShortIDIssuer, error) {
   189  	var issuers []ShortIDIssuer
   190  	for issuerFile, shortID := range input {
   191  		if shortID > 255 || shortID < 0 {
   192  			return nil, fmt.Errorf("invalid shortID %d (must be byte)", shortID)
   193  		}
   194  		cert, err := issuance.LoadCertificate(issuerFile)
   195  		if err != nil {
   196  			return nil, fmt.Errorf("reading issuer: %w", err)
   197  		}
   198  		var subject pkix.RDNSequence
   199  		_, err = asn1.Unmarshal(cert.Certificate.RawSubject, &subject)
   200  		if err != nil {
   201  			return nil, fmt.Errorf("parsing issuer.RawSubject: %w", err)
   202  		}
   203  		shortID := byte(shortID)
   204  		for _, issuer := range issuers {
   205  			if issuer.shortID == shortID {
   206  				return nil, fmt.Errorf("duplicate shortID '%d' in (for %q and %q) in config file", shortID, issuer.subject, subject)
   207  			}
   208  			if !issuer.IsCA {
   209  				return nil, fmt.Errorf("certificate for %q is not a CA certificate", subject)
   210  			}
   211  		}
   212  		issuers = append(issuers, ShortIDIssuer{
   213  			Certificate: cert,
   214  			subject:     subject,
   215  			shortID:     shortID,
   216  		})
   217  	}
   218  	return issuers, nil
   219  }
   220  
   221  // ShortID returns the short ID of an issuer. The short ID is a single byte that
   222  // is unique for that issuer.
   223  func (si *ShortIDIssuer) ShortID() byte {
   224  	return si.shortID
   225  }
   226  
   227  // FindIssuerByID returns the issuer that matches the given IssuerID or IssuerNameID.
   228  func FindIssuerByID(longID int64, issuers []ShortIDIssuer) (*ShortIDIssuer, error) {
   229  	for _, iss := range issuers {
   230  		if iss.NameID() == issuance.IssuerNameID(longID) || iss.ID() == issuance.IssuerID(longID) {
   231  			return &iss, nil
   232  		}
   233  	}
   234  	return nil, fmt.Errorf("no issuer found for an ID in certificateStatus: %d", longID)
   235  }
   236  
   237  // FindIssuerByName returns the issuer with a Subject matching the *ocsp.Response.
   238  func FindIssuerByName(resp *ocsp.Response, issuers []ShortIDIssuer) (*ShortIDIssuer, error) {
   239  	var responder pkix.RDNSequence
   240  	_, err := asn1.Unmarshal(resp.RawResponderName, &responder)
   241  	if err != nil {
   242  		return nil, fmt.Errorf("parsing resp.RawResponderName: %w", err)
   243  	}
   244  	var responders strings.Builder
   245  	for _, issuer := range issuers {
   246  		fmt.Fprintf(&responders, "%s\n", issuer.subject)
   247  		if bytes.Equal(issuer.RawSubject, resp.RawResponderName) {
   248  			return &issuer, nil
   249  		}
   250  	}
   251  	return nil, fmt.Errorf("no issuer found matching OCSP response for %s. Available issuers:\n%s\n", responder, responders.String())
   252  }
   253  

View as plain text