github.com/letsencrypt/boulder@v0.20251208.0/cmd/boulder-ca/main.go (about)

     1  package notmain
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"os"
     8  	"strconv"
     9  
    10  	"github.com/jmhodges/clock"
    11  
    12  	"github.com/letsencrypt/boulder/ca"
    13  	capb "github.com/letsencrypt/boulder/ca/proto"
    14  	"github.com/letsencrypt/boulder/cmd"
    15  	"github.com/letsencrypt/boulder/ctpolicy/loglist"
    16  	"github.com/letsencrypt/boulder/features"
    17  	"github.com/letsencrypt/boulder/goodkey"
    18  	"github.com/letsencrypt/boulder/goodkey/sagoodkey"
    19  	bgrpc "github.com/letsencrypt/boulder/grpc"
    20  	"github.com/letsencrypt/boulder/issuance"
    21  	"github.com/letsencrypt/boulder/policy"
    22  	rapb "github.com/letsencrypt/boulder/ra/proto"
    23  	sapb "github.com/letsencrypt/boulder/sa/proto"
    24  )
    25  
    26  type Config struct {
    27  	CA struct {
    28  		cmd.ServiceConfig
    29  
    30  		cmd.HostnamePolicyConfig
    31  
    32  		GRPCCA *cmd.GRPCServerConfig
    33  
    34  		SAService *cmd.GRPCClientConfig
    35  
    36  		SCTService *cmd.GRPCClientConfig
    37  
    38  		// Issuance contains all information necessary to load and initialize issuers.
    39  		Issuance struct {
    40  			// The name of the certificate profile to use if one wasn't provided
    41  			// by the RA during NewOrder and Finalize requests. Must match a
    42  			// configured certificate profile or boulder-ca will fail to start.
    43  			//
    44  			// Deprecated: set the defaultProfileName in the RA config instead.
    45  			DefaultCertificateProfileName string `validate:"omitempty,alphanum,min=1,max=32"`
    46  
    47  			// One of the profile names must match the value of ra.defaultProfileName
    48  			// or large amounts of issuance will fail.
    49  			CertProfiles map[string]issuance.ProfileConfig `validate:"required,dive,keys,alphanum,min=1,max=32,endkeys"`
    50  
    51  			// TODO(#7159): Make this required once all live configs are using it.
    52  			CRLProfile issuance.CRLProfileConfig `validate:"-"`
    53  			Issuers    []issuance.IssuerConfig   `validate:"min=1,dive"`
    54  		}
    55  
    56  		// What digits we should prepend to serials after randomly generating them.
    57  		// Deprecated: Use SerialPrefixHex instead.
    58  		SerialPrefix int `validate:"required_without=SerialPrefixHex,omitempty,min=1,max=127"`
    59  
    60  		// SerialPrefixHex is the hex string to prepend to serials after randomly
    61  		// generating them. The minimum value is "01" to ensure that at least
    62  		// one bit in the prefix byte is set. The maximum value is "7f" to
    63  		// ensure that the first bit in the prefix byte is not set. The validate
    64  		// library cannot enforce mix/max values on strings, so that is done in
    65  		// NewCertificateAuthorityImpl.
    66  		//
    67  		// TODO(#7213): Replace `required_without` with `required` when SerialPrefix is removed.
    68  		SerialPrefixHex string `validate:"required_without=SerialPrefix,omitempty,hexadecimal,len=2"`
    69  
    70  		// MaxNames is the maximum number of subjectAltNames in a single cert.
    71  		// The value supplied MUST be greater than 0 and no more than 100. These
    72  		// limits are per section 7.1 of our combined CP/CPS, under "DV-SSL
    73  		// Subscriber Certificate". The value must match the RA and WFE
    74  		// configurations.
    75  		MaxNames int `validate:"required,min=1,max=100"`
    76  
    77  		// GoodKey is an embedded config stanza for the goodkey library.
    78  		GoodKey goodkey.Config
    79  
    80  		// Maximum length (in bytes) of a line documenting the signing of a CRL.
    81  		// The name is a carryover from when this config was shared between both
    82  		// OCSP and CRL audit log emission. Recommended to be around 4000.
    83  		OCSPLogMaxLength int
    84  
    85  		// CTLogListFile is the path to a JSON file on disk containing the set of
    86  		// all logs trusted by Chrome. The file must match the v3 log list schema:
    87  		// https://www.gstatic.com/ct/log_list/v3/log_list_schema.json
    88  		CTLogListFile string
    89  
    90  		// DisableCertService causes the CertificateAuthority gRPC service to not
    91  		// start, preventing any certificates or precertificates from being issued.
    92  		DisableCertService bool
    93  
    94  		// DisableCRLService causes the CRLGenerator gRPC service to not start,
    95  		// preventing any CRLs from being issued.
    96  		DisableCRLService bool
    97  
    98  		Features features.Config
    99  	}
   100  
   101  	PA cmd.PAConfig
   102  
   103  	Syslog        cmd.SyslogConfig
   104  	OpenTelemetry cmd.OpenTelemetryConfig
   105  }
   106  
   107  func main() {
   108  	grpcAddr := flag.String("addr", "", "gRPC listen address override")
   109  	debugAddr := flag.String("debug-addr", "", "Debug server address override")
   110  	configFile := flag.String("config", "", "File path to the configuration file for this service")
   111  	flag.Parse()
   112  	if *configFile == "" {
   113  		flag.Usage()
   114  		os.Exit(1)
   115  	}
   116  
   117  	var c Config
   118  	err := cmd.ReadConfigFile(*configFile, &c)
   119  	cmd.FailOnError(err, "Reading JSON config file into config structure")
   120  
   121  	features.Set(c.CA.Features)
   122  
   123  	if *grpcAddr != "" {
   124  		c.CA.GRPCCA.Address = *grpcAddr
   125  	}
   126  	if *debugAddr != "" {
   127  		c.CA.DebugAddr = *debugAddr
   128  	}
   129  
   130  	serialPrefix := byte(c.CA.SerialPrefix)
   131  	if c.CA.SerialPrefixHex != "" {
   132  		parsedSerialPrefix, err := strconv.ParseUint(c.CA.SerialPrefixHex, 16, 8)
   133  		cmd.FailOnError(err, "Couldn't convert SerialPrefixHex to int")
   134  		serialPrefix = byte(parsedSerialPrefix)
   135  	}
   136  
   137  	if c.CA.MaxNames == 0 {
   138  		cmd.Fail("Error in CA config: MaxNames must not be 0")
   139  	}
   140  
   141  	scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.CA.DebugAddr)
   142  	defer oTelShutdown(context.Background())
   143  	logger.Info(cmd.VersionString())
   144  
   145  	metrics := ca.NewCAMetrics(scope)
   146  
   147  	cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
   148  	cmd.FailOnError(c.PA.CheckIdentifiers(), "Invalid PA configuration")
   149  
   150  	pa, err := policy.New(c.PA.Identifiers, c.PA.Challenges, logger)
   151  	cmd.FailOnError(err, "Couldn't create PA")
   152  
   153  	if c.CA.HostnamePolicyFile == "" {
   154  		cmd.Fail("HostnamePolicyFile was empty")
   155  	}
   156  	err = pa.LoadIdentPolicyFile(c.CA.HostnamePolicyFile)
   157  	cmd.FailOnError(err, "Couldn't load identifier policy file")
   158  
   159  	// Do this before creating the issuers to ensure the log list is loaded before
   160  	// the linters are initialized.
   161  	if c.CA.CTLogListFile != "" {
   162  		err = loglist.InitLintList(c.CA.CTLogListFile)
   163  		cmd.FailOnError(err, "Failed to load CT Log List")
   164  	}
   165  
   166  	profiles := make(map[string]*issuance.Profile)
   167  	for name, profileConfig := range c.CA.Issuance.CertProfiles {
   168  		profile, err := issuance.NewProfile(profileConfig)
   169  		cmd.FailOnError(err, "Loading profile")
   170  		profiles[name] = profile
   171  	}
   172  
   173  	clk := clock.New()
   174  	var crlShards int
   175  	issuers := make([]*issuance.Issuer, 0, len(c.CA.Issuance.Issuers))
   176  	for i, issuerConfig := range c.CA.Issuance.Issuers {
   177  		// Double check that all issuers have the same number of CRL shards, because
   178  		// crl-updater relies upon that invariant.
   179  		if issuerConfig.CRLShards != 0 && crlShards == 0 {
   180  			crlShards = issuerConfig.CRLShards
   181  		}
   182  		if issuerConfig.CRLShards != crlShards {
   183  			cmd.Fail(fmt.Sprintf("issuer %d has %d shards, want %d", i, issuerConfig.CRLShards, crlShards))
   184  		}
   185  		// Also check that all the profiles they list actually exist.
   186  		for _, profile := range issuerConfig.Profiles {
   187  			_, found := profiles[profile]
   188  			if !found {
   189  				cmd.Fail(fmt.Sprintf("issuer %d lists unrecognized profile %q", i, profile))
   190  			}
   191  		}
   192  
   193  		issuer, err := issuance.LoadIssuer(issuerConfig, clk)
   194  		cmd.FailOnError(err, "Loading issuer")
   195  		issuers = append(issuers, issuer)
   196  	}
   197  
   198  	if len(c.CA.Issuance.CertProfiles) == 0 {
   199  		cmd.Fail("At least one profile must be configured")
   200  	}
   201  
   202  	tlsConfig, err := c.CA.TLS.Load(scope)
   203  	cmd.FailOnError(err, "TLS config")
   204  
   205  	saConn, err := bgrpc.ClientSetup(c.CA.SAService, tlsConfig, scope, clk)
   206  	cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
   207  	sa := sapb.NewStorageAuthorityClient(saConn)
   208  
   209  	var sctService rapb.SCTProviderClient
   210  	if c.CA.SCTService != nil {
   211  		sctConn, err := bgrpc.ClientSetup(c.CA.SCTService, tlsConfig, scope, clk)
   212  		cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA for SCTs")
   213  		sctService = rapb.NewSCTProviderClient(sctConn)
   214  	}
   215  
   216  	kp, err := sagoodkey.NewPolicy(&c.CA.GoodKey, sa.KeyBlocked)
   217  	cmd.FailOnError(err, "Unable to create key policy")
   218  
   219  	srv := bgrpc.NewServer(c.CA.GRPCCA, logger)
   220  
   221  	if !c.CA.DisableCRLService {
   222  		crli, err := ca.NewCRLImpl(
   223  			issuers,
   224  			c.CA.Issuance.CRLProfile,
   225  			c.CA.OCSPLogMaxLength,
   226  			logger,
   227  			metrics,
   228  		)
   229  		cmd.FailOnError(err, "Failed to create CRL impl")
   230  
   231  		srv = srv.Add(&capb.CRLGenerator_ServiceDesc, crli)
   232  	}
   233  
   234  	if !c.CA.DisableCertService {
   235  		cai, err := ca.NewCertificateAuthorityImpl(
   236  			sa,
   237  			sctService,
   238  			pa,
   239  			issuers,
   240  			profiles,
   241  			serialPrefix,
   242  			c.CA.MaxNames,
   243  			kp,
   244  			logger,
   245  			metrics,
   246  			clk)
   247  		cmd.FailOnError(err, "Failed to create CA impl")
   248  
   249  		srv = srv.Add(&capb.CertificateAuthority_ServiceDesc, cai)
   250  	}
   251  
   252  	start, err := srv.Build(tlsConfig, scope, clk)
   253  	cmd.FailOnError(err, "Unable to setup CA gRPC server")
   254  
   255  	cmd.FailOnError(start(), "CA gRPC service failed")
   256  }
   257  
   258  func init() {
   259  	cmd.RegisterCommand("boulder-ca", main, &cmd.ConfigValidator{Config: &Config{}})
   260  }