...

Source file src/github.com/letsencrypt/boulder/cmd/rocsp-tool/main.go

Documentation: github.com/letsencrypt/boulder/cmd/rocsp-tool

     1  package notmain
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/pem"
     7  	"flag"
     8  	"fmt"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/jmhodges/clock"
    13  	"github.com/prometheus/client_golang/prometheus"
    14  	"golang.org/x/crypto/ocsp"
    15  
    16  	capb "github.com/letsencrypt/boulder/ca/proto"
    17  	"github.com/letsencrypt/boulder/cmd"
    18  	"github.com/letsencrypt/boulder/db"
    19  	bgrpc "github.com/letsencrypt/boulder/grpc"
    20  	"github.com/letsencrypt/boulder/metrics"
    21  	rocsp_config "github.com/letsencrypt/boulder/rocsp/config"
    22  	"github.com/letsencrypt/boulder/sa"
    23  	"github.com/letsencrypt/boulder/test/ocsp/helper"
    24  )
    25  
    26  type Config struct {
    27  	ROCSPTool struct {
    28  		DebugAddr string `validate:"required,hostname_port"`
    29  		Redis     rocsp_config.RedisConfig
    30  
    31  		// If using load-from-db, this provides credentials to connect to the DB
    32  		// and the CA. Otherwise, it's optional.
    33  		LoadFromDB *LoadFromDBConfig
    34  	}
    35  	Syslog        cmd.SyslogConfig
    36  	OpenTelemetry cmd.OpenTelemetryConfig
    37  }
    38  
    39  // LoadFromDBConfig provides the credentials and configuration needed to load
    40  // data from the certificateStatuses table in the DB and get it signed.
    41  type LoadFromDBConfig struct {
    42  	// Credentials to connect to the DB.
    43  	DB cmd.DBConfig
    44  	// Credentials to request OCSP signatures from the CA.
    45  	GRPCTLS cmd.TLSConfig
    46  	// Timeouts and hostnames for the CA.
    47  	OCSPGeneratorService cmd.GRPCClientConfig
    48  	// How fast to process rows.
    49  	Speed ProcessingSpeed
    50  }
    51  
    52  type ProcessingSpeed struct {
    53  	// If using load-from-db, this limits how many items per second we
    54  	// scan from the DB. We might go slower than this depending on how fast
    55  	// we read rows from the DB, but we won't go faster. Defaults to 2000.
    56  	RowsPerSecond int `validate:"min=0"`
    57  	// If using load-from-db, this controls how many parallel requests to
    58  	// boulder-ca for OCSP signing we can make. Defaults to 100.
    59  	ParallelSigns int `validate:"min=0"`
    60  	// If using load-from-db, the LIMIT on our scanning queries. We have to
    61  	// apply a limit because MariaDB will cut off our response at some
    62  	// threshold of total bytes transferred (1 GB by default). Defaults to 10000.
    63  	ScanBatchSize int `validate:"min=0"`
    64  }
    65  
    66  func init() {
    67  	cmd.RegisterCommand("rocsp-tool", main, &cmd.ConfigValidator{Config: &Config{}})
    68  }
    69  
    70  func main() {
    71  	err := main2()
    72  	if err != nil {
    73  		cmd.FailOnError(err, "")
    74  	}
    75  }
    76  
    77  var startFromID *int64
    78  
    79  func main2() error {
    80  	configFile := flag.String("config", "", "File path to the configuration file for this service")
    81  	startFromID = flag.Int64("start-from-id", 0, "For load-from-db, the first ID in the certificateStatus table to scan")
    82  	flag.Usage = helpExit
    83  	flag.Parse()
    84  	if *configFile == "" || len(flag.Args()) < 1 {
    85  		helpExit()
    86  	}
    87  
    88  	var conf Config
    89  	err := cmd.ReadConfigFile(*configFile, &conf)
    90  	if err != nil {
    91  		return fmt.Errorf("reading JSON config file: %w", err)
    92  	}
    93  
    94  	_, logger, oTelShutdown := cmd.StatsAndLogging(conf.Syslog, conf.OpenTelemetry, conf.ROCSPTool.DebugAddr)
    95  	defer oTelShutdown(context.Background())
    96  	logger.Info(cmd.VersionString())
    97  
    98  	clk := cmd.Clock()
    99  	redisClient, err := rocsp_config.MakeClient(&conf.ROCSPTool.Redis, clk, metrics.NoopRegisterer)
   100  	if err != nil {
   101  		return fmt.Errorf("making client: %w", err)
   102  	}
   103  
   104  	var db *db.WrappedMap
   105  	var ocspGenerator capb.OCSPGeneratorClient
   106  	var scanBatchSize int
   107  	if conf.ROCSPTool.LoadFromDB != nil {
   108  		lfd := conf.ROCSPTool.LoadFromDB
   109  		db, err = sa.InitWrappedDb(lfd.DB, nil, logger)
   110  		if err != nil {
   111  			return fmt.Errorf("connecting to DB: %w", err)
   112  		}
   113  
   114  		ocspGenerator, err = configureOCSPGenerator(lfd.GRPCTLS,
   115  			lfd.OCSPGeneratorService, clk, metrics.NoopRegisterer)
   116  		if err != nil {
   117  			return fmt.Errorf("configuring gRPC to CA: %w", err)
   118  		}
   119  		setDefault(&lfd.Speed.RowsPerSecond, 2000)
   120  		setDefault(&lfd.Speed.ParallelSigns, 100)
   121  		setDefault(&lfd.Speed.ScanBatchSize, 10000)
   122  		scanBatchSize = lfd.Speed.ScanBatchSize
   123  	}
   124  
   125  	ctx := context.Background()
   126  	cl := client{
   127  		redis:         redisClient,
   128  		db:            db,
   129  		ocspGenerator: ocspGenerator,
   130  		clk:           clk,
   131  		scanBatchSize: scanBatchSize,
   132  		logger:        logger,
   133  	}
   134  
   135  	for _, sc := range subCommands {
   136  		if flag.Arg(0) == sc.name {
   137  			return sc.cmd(ctx, cl, conf, flag.Args()[1:])
   138  		}
   139  	}
   140  	fmt.Fprintf(os.Stderr, "unrecognized subcommand %q\n", flag.Arg(0))
   141  	helpExit()
   142  	return nil
   143  }
   144  
   145  // subCommand represents a single subcommand. `name` is the name used to invoke it, and `help` is
   146  // its help text.
   147  type subCommand struct {
   148  	name string
   149  	help string
   150  	cmd  func(context.Context, client, Config, []string) error
   151  }
   152  
   153  var (
   154  	Store = subCommand{"store", "for each filename on command line, read the file as an OCSP response and store it in Redis",
   155  		func(ctx context.Context, cl client, _ Config, args []string) error {
   156  			err := cl.storeResponsesFromFiles(ctx, flag.Args()[1:])
   157  			if err != nil {
   158  				return err
   159  			}
   160  			return nil
   161  		},
   162  	}
   163  	Get = subCommand{
   164  		"get",
   165  		"for each serial on command line, fetch that serial's response and pretty-print it",
   166  		func(ctx context.Context, cl client, _ Config, args []string) error {
   167  			for _, serial := range flag.Args()[1:] {
   168  				resp, err := cl.redis.GetResponse(ctx, serial)
   169  				if err != nil {
   170  					return err
   171  				}
   172  				parsed, err := ocsp.ParseResponse(resp, nil)
   173  				if err != nil {
   174  					fmt.Fprintf(os.Stderr, "parsing error on %x: %s", resp, err)
   175  					continue
   176  				} else {
   177  					fmt.Printf("%s\n", helper.PrettyResponse(parsed))
   178  				}
   179  			}
   180  			return nil
   181  		},
   182  	}
   183  	GetPEM = subCommand{"get-pem", "for each serial on command line, fetch that serial's response and print it PEM-encoded",
   184  		func(ctx context.Context, cl client, _ Config, args []string) error {
   185  			for _, serial := range flag.Args()[1:] {
   186  				resp, err := cl.redis.GetResponse(ctx, serial)
   187  				if err != nil {
   188  					return err
   189  				}
   190  				block := pem.Block{
   191  					Bytes: resp,
   192  					Type:  "OCSP RESPONSE",
   193  				}
   194  				err = pem.Encode(os.Stdout, &block)
   195  				if err != nil {
   196  					return err
   197  				}
   198  			}
   199  			return nil
   200  		},
   201  	}
   202  	LoadFromDB = subCommand{"load-from-db", "scan the database for all OCSP entries for unexpired certificates, and store in Redis",
   203  		func(ctx context.Context, cl client, c Config, args []string) error {
   204  			if c.ROCSPTool.LoadFromDB == nil {
   205  				return fmt.Errorf("config field LoadFromDB was missing")
   206  			}
   207  			err := cl.loadFromDB(ctx, c.ROCSPTool.LoadFromDB.Speed, *startFromID)
   208  			if err != nil {
   209  				return fmt.Errorf("loading OCSP responses from DB: %w", err)
   210  			}
   211  			return nil
   212  		},
   213  	}
   214  	ScanResponses = subCommand{"scan-responses", "scan Redis for OCSP response entries. For each entry, print the serial and base64-encoded response",
   215  		func(ctx context.Context, cl client, _ Config, args []string) error {
   216  			results := cl.redis.ScanResponses(ctx, "*")
   217  			for r := range results {
   218  				if r.Err != nil {
   219  					return r.Err
   220  				}
   221  				fmt.Printf("%s: %s\n", r.Serial, base64.StdEncoding.EncodeToString(r.Body))
   222  			}
   223  			return nil
   224  		},
   225  	}
   226  )
   227  
   228  var subCommands = []subCommand{
   229  	Store, Get, GetPEM, LoadFromDB, ScanResponses,
   230  }
   231  
   232  func helpExit() {
   233  	var names []string
   234  	var helpStrings []string
   235  	for _, s := range subCommands {
   236  		names = append(names, s.name)
   237  		helpStrings = append(helpStrings, fmt.Sprintf("  %s -- %s", s.name, s.help))
   238  	}
   239  	fmt.Fprintf(os.Stderr, "Usage: %s [%s] --config path/to/config.json\n", os.Args[0], strings.Join(names, "|"))
   240  	os.Stderr.Write([]byte(strings.Join(helpStrings, "\n")))
   241  	fmt.Fprintln(os.Stderr)
   242  	fmt.Fprintln(os.Stderr)
   243  	flag.PrintDefaults()
   244  	os.Exit(1)
   245  }
   246  
   247  func configureOCSPGenerator(tlsConf cmd.TLSConfig, grpcConf cmd.GRPCClientConfig, clk clock.Clock, scope prometheus.Registerer) (capb.OCSPGeneratorClient, error) {
   248  	tlsConfig, err := tlsConf.Load(scope)
   249  	if err != nil {
   250  		return nil, fmt.Errorf("loading TLS config: %w", err)
   251  	}
   252  
   253  	caConn, err := bgrpc.ClientSetup(&grpcConf, tlsConfig, scope, clk)
   254  	cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to CA")
   255  	return capb.NewOCSPGeneratorClient(caConn), nil
   256  }
   257  
   258  // setDefault sets the target to a default value, if it is zero.
   259  func setDefault(target *int, def int) {
   260  	if *target == 0 {
   261  		*target = def
   262  	}
   263  }
   264  

View as plain text