...

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

Documentation: github.com/letsencrypt/boulder/cmd/boulder-ca

     1  package notmain
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"os"
     7  
     8  	"github.com/prometheus/client_golang/prometheus"
     9  
    10  	"github.com/letsencrypt/boulder/ca"
    11  	capb "github.com/letsencrypt/boulder/ca/proto"
    12  	"github.com/letsencrypt/boulder/cmd"
    13  	"github.com/letsencrypt/boulder/config"
    14  	"github.com/letsencrypt/boulder/ctpolicy/loglist"
    15  	"github.com/letsencrypt/boulder/features"
    16  	"github.com/letsencrypt/boulder/goodkey"
    17  	"github.com/letsencrypt/boulder/goodkey/sagoodkey"
    18  	bgrpc "github.com/letsencrypt/boulder/grpc"
    19  	"github.com/letsencrypt/boulder/issuance"
    20  	"github.com/letsencrypt/boulder/linter"
    21  	"github.com/letsencrypt/boulder/policy"
    22  	sapb "github.com/letsencrypt/boulder/sa/proto"
    23  )
    24  
    25  type Config struct {
    26  	CA struct {
    27  		cmd.ServiceConfig
    28  
    29  		cmd.HostnamePolicyConfig
    30  
    31  		GRPCCA *cmd.GRPCServerConfig
    32  
    33  		SAService *cmd.GRPCClientConfig
    34  
    35  		// Issuance contains all information necessary to load and initialize issuers.
    36  		Issuance struct {
    37  			Profile      issuance.ProfileConfig
    38  			Issuers      []issuance.IssuerConfig `validate:"min=1,dive"`
    39  			IgnoredLints []string
    40  		}
    41  
    42  		// How long issued certificates are valid for.
    43  		Expiry config.Duration
    44  
    45  		// How far back certificates should be backdated.
    46  		Backdate config.Duration
    47  
    48  		// What digits we should prepend to serials after randomly generating them.
    49  		SerialPrefix int `validate:"required,min=1,max=255"`
    50  
    51  		// The maximum number of subjectAltNames in a single certificate
    52  		MaxNames int `validate:"required,min=1,max=100"`
    53  
    54  		// LifespanOCSP is how long OCSP responses are valid for. Per the BRs,
    55  		// Section 4.9.10, it MUST NOT be more than 10 days.
    56  		LifespanOCSP config.Duration
    57  
    58  		// LifespanCRL is how long CRLs are valid for. It should be longer than the
    59  		// `period` field of the CRL Updater. Per the BRs, Section 4.9.7, it MUST
    60  		// NOT be more than 10 days.
    61  		LifespanCRL config.Duration
    62  
    63  		// GoodKey is an embedded config stanza for the goodkey library.
    64  		GoodKey goodkey.Config
    65  
    66  		// Maximum length (in bytes) of a line accumulating OCSP audit log entries.
    67  		// Recommended to be around 4000. If this is 0, do not perform OCSP audit
    68  		// logging.
    69  		OCSPLogMaxLength int
    70  
    71  		// Maximum period (in Go duration format) to wait to accumulate a max-length
    72  		// OCSP audit log line. We will emit a log line at least once per period,
    73  		// if there is anything to be logged. Keeping this low minimizes the risk
    74  		// of losing logs during a catastrophic failure. Making it too high
    75  		// means logging more often than necessary, which is inefficient in terms
    76  		// of bytes and log system resources.
    77  		// Recommended to be around 500ms.
    78  		OCSPLogPeriod config.Duration
    79  
    80  		// Path of a YAML file containing the list of int64 RegIDs
    81  		// allowed to request ECDSA issuance
    82  		ECDSAAllowListFilename string
    83  
    84  		// CTLogListFile is the path to a JSON file on disk containing the set of
    85  		// all logs trusted by Chrome. The file must match the v3 log list schema:
    86  		// https://www.gstatic.com/ct/log_list/v3/log_list_schema.json
    87  		CTLogListFile string
    88  
    89  		// CRLDPBase is the piece of the CRL Distribution Point URI which is common
    90  		// across all issuers and shards. It must use the http:// scheme, and must
    91  		// not end with a slash. Example: "http://prod.c.lencr.org".
    92  		CRLDPBase string `validate:"required,url,startswith=http://,endsnotwith=/"`
    93  
    94  		// DisableCertService causes the CertificateAuthority gRPC service to not
    95  		// start, preventing any certificates or precertificates from being issued.
    96  		DisableCertService bool
    97  		// DisableCertService causes the OCSPGenerator gRPC service to not start,
    98  		// preventing any OCSP responses from being issued.
    99  		DisableOCSPService bool
   100  		// DisableCRLService causes the CRLGenerator gRPC service to not start,
   101  		// preventing any CRLs from being issued.
   102  		DisableCRLService bool
   103  
   104  		Features map[string]bool
   105  	}
   106  
   107  	PA cmd.PAConfig
   108  
   109  	Syslog        cmd.SyslogConfig
   110  	OpenTelemetry cmd.OpenTelemetryConfig
   111  }
   112  
   113  func loadBoulderIssuers(profileConfig issuance.ProfileConfig, issuerConfigs []issuance.IssuerConfig, ignoredLints []string) ([]*issuance.Issuer, error) {
   114  	issuers := make([]*issuance.Issuer, 0, len(issuerConfigs))
   115  	for _, issuerConfig := range issuerConfigs {
   116  		profile, err := issuance.NewProfile(profileConfig, issuerConfig)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  
   121  		cert, signer, err := issuance.LoadIssuer(issuerConfig.Location)
   122  		if err != nil {
   123  			return nil, err
   124  		}
   125  
   126  		linter, err := linter.New(cert.Certificate, signer, ignoredLints)
   127  		if err != nil {
   128  			return nil, err
   129  		}
   130  
   131  		issuer, err := issuance.NewIssuer(cert, signer, profile, linter, cmd.Clock())
   132  		if err != nil {
   133  			return nil, err
   134  		}
   135  
   136  		issuers = append(issuers, issuer)
   137  	}
   138  	return issuers, nil
   139  }
   140  
   141  func main() {
   142  	grpcAddr := flag.String("addr", "", "gRPC listen address override")
   143  	debugAddr := flag.String("debug-addr", "", "Debug server address override")
   144  	configFile := flag.String("config", "", "File path to the configuration file for this service")
   145  	flag.Parse()
   146  	if *configFile == "" {
   147  		flag.Usage()
   148  		os.Exit(1)
   149  	}
   150  
   151  	var c Config
   152  	err := cmd.ReadConfigFile(*configFile, &c)
   153  	cmd.FailOnError(err, "Reading JSON config file into config structure")
   154  
   155  	err = features.Set(c.CA.Features)
   156  	cmd.FailOnError(err, "Failed to set feature flags")
   157  
   158  	if *grpcAddr != "" {
   159  		c.CA.GRPCCA.Address = *grpcAddr
   160  	}
   161  	if *debugAddr != "" {
   162  		c.CA.DebugAddr = *debugAddr
   163  	}
   164  
   165  	if c.CA.MaxNames == 0 {
   166  		cmd.Fail("Error in CA config: MaxNames must not be 0")
   167  	}
   168  
   169  	scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.CA.DebugAddr)
   170  	defer oTelShutdown(context.Background())
   171  	logger.Info(cmd.VersionString())
   172  
   173  	// These two metrics are created and registered here so they can be shared
   174  	// between NewCertificateAuthorityImpl and NewOCSPImpl.
   175  	signatureCount := prometheus.NewCounterVec(
   176  		prometheus.CounterOpts{
   177  			Name: "signatures",
   178  			Help: "Number of signatures",
   179  		},
   180  		[]string{"purpose", "issuer"})
   181  	scope.MustRegister(signatureCount)
   182  
   183  	signErrorCount := prometheus.NewCounterVec(prometheus.CounterOpts{
   184  		Name: "signature_errors",
   185  		Help: "A counter of signature errors labelled by error type",
   186  	}, []string{"type"})
   187  	scope.MustRegister(signErrorCount)
   188  
   189  	cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")
   190  
   191  	pa, err := policy.New(c.PA.Challenges, logger)
   192  	cmd.FailOnError(err, "Couldn't create PA")
   193  
   194  	if c.CA.HostnamePolicyFile == "" {
   195  		cmd.Fail("HostnamePolicyFile was empty")
   196  	}
   197  	err = pa.LoadHostnamePolicyFile(c.CA.HostnamePolicyFile)
   198  	cmd.FailOnError(err, "Couldn't load hostname policy file")
   199  
   200  	// Do this before creating the issuers to ensure the log list is loaded before
   201  	// the linters are initialized.
   202  	if c.CA.CTLogListFile != "" {
   203  		err = loglist.InitLintList(c.CA.CTLogListFile)
   204  		cmd.FailOnError(err, "Failed to load CT Log List")
   205  	}
   206  
   207  	var boulderIssuers []*issuance.Issuer
   208  	boulderIssuers, err = loadBoulderIssuers(c.CA.Issuance.Profile, c.CA.Issuance.Issuers, c.CA.Issuance.IgnoredLints)
   209  	cmd.FailOnError(err, "Couldn't load issuers")
   210  
   211  	tlsConfig, err := c.CA.TLS.Load(scope)
   212  	cmd.FailOnError(err, "TLS config")
   213  
   214  	clk := cmd.Clock()
   215  
   216  	conn, err := bgrpc.ClientSetup(c.CA.SAService, tlsConfig, scope, clk)
   217  	cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
   218  	sa := sapb.NewStorageAuthorityClient(conn)
   219  
   220  	kp, err := sagoodkey.NewKeyPolicy(&c.CA.GoodKey, sa.KeyBlocked)
   221  	cmd.FailOnError(err, "Unable to create key policy")
   222  
   223  	var ecdsaAllowList *ca.ECDSAAllowList
   224  	var entries int
   225  	if c.CA.ECDSAAllowListFilename != "" {
   226  		// Create an allow list object.
   227  		ecdsaAllowList, entries, err = ca.NewECDSAAllowListFromFile(c.CA.ECDSAAllowListFilename)
   228  		cmd.FailOnError(err, "Unable to load ECDSA allow list from YAML file")
   229  		logger.Infof("Loaded an ECDSA allow list with %d entries", entries)
   230  	}
   231  
   232  	srv := bgrpc.NewServer(c.CA.GRPCCA, logger)
   233  
   234  	if !c.CA.DisableOCSPService {
   235  		ocspi, err := ca.NewOCSPImpl(
   236  			boulderIssuers,
   237  			c.CA.LifespanOCSP.Duration,
   238  			c.CA.OCSPLogMaxLength,
   239  			c.CA.OCSPLogPeriod.Duration,
   240  			logger,
   241  			scope,
   242  			signatureCount,
   243  			signErrorCount,
   244  			clk,
   245  		)
   246  		cmd.FailOnError(err, "Failed to create OCSP impl")
   247  		go ocspi.LogOCSPLoop()
   248  		defer ocspi.Stop()
   249  
   250  		srv = srv.Add(&capb.OCSPGenerator_ServiceDesc, ocspi)
   251  	}
   252  
   253  	if !c.CA.DisableCRLService {
   254  		crli, err := ca.NewCRLImpl(
   255  			boulderIssuers,
   256  			c.CA.LifespanCRL.Duration,
   257  			c.CA.CRLDPBase,
   258  			c.CA.OCSPLogMaxLength,
   259  			logger,
   260  		)
   261  		cmd.FailOnError(err, "Failed to create CRL impl")
   262  
   263  		srv = srv.Add(&capb.CRLGenerator_ServiceDesc, crli)
   264  	}
   265  
   266  	if !c.CA.DisableCertService {
   267  		cai, err := ca.NewCertificateAuthorityImpl(
   268  			sa,
   269  			pa,
   270  			boulderIssuers,
   271  			ecdsaAllowList,
   272  			c.CA.Expiry.Duration,
   273  			c.CA.Backdate.Duration,
   274  			c.CA.SerialPrefix,
   275  			c.CA.MaxNames,
   276  			kp,
   277  			logger,
   278  			scope,
   279  			signatureCount,
   280  			signErrorCount,
   281  			clk)
   282  		cmd.FailOnError(err, "Failed to create CA impl")
   283  
   284  		srv = srv.Add(&capb.CertificateAuthority_ServiceDesc, cai)
   285  	}
   286  
   287  	start, err := srv.Build(tlsConfig, scope, clk)
   288  	cmd.FailOnError(err, "Unable to setup CA gRPC server")
   289  
   290  	cmd.FailOnError(start(), "CA gRPC service failed")
   291  }
   292  
   293  func init() {
   294  	cmd.RegisterCommand("boulder-ca", main, &cmd.ConfigValidator{Config: &Config{}})
   295  }
   296  

View as plain text