github.com/letsencrypt/boulder@v0.20251208.0/cmd/admin/admin.go (about) 1 package main 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "github.com/jmhodges/clock" 9 10 "github.com/letsencrypt/boulder/cmd" 11 "github.com/letsencrypt/boulder/db" 12 "github.com/letsencrypt/boulder/features" 13 bgrpc "github.com/letsencrypt/boulder/grpc" 14 blog "github.com/letsencrypt/boulder/log" 15 rapb "github.com/letsencrypt/boulder/ra/proto" 16 "github.com/letsencrypt/boulder/sa" 17 sapb "github.com/letsencrypt/boulder/sa/proto" 18 ) 19 20 // admin holds all of the external connections necessary to perform admin 21 // actions on a boulder deployment. 22 type admin struct { 23 rac rapb.RegistrationAuthorityClient 24 sac sapb.StorageAuthorityClient 25 saroc sapb.StorageAuthorityReadOnlyClient 26 // TODO: Remove this and only use sac and saroc to interact with the db. 27 // We cannot have true dry-run safety as long as we have a direct dbMap. 28 dbMap *db.WrappedMap 29 30 // TODO: Remove this when the dbMap is removed and the dryRunSAC and dryRunRAC 31 // handle all dry-run safety. 32 dryRun bool 33 34 clk clock.Clock 35 log blog.Logger 36 } 37 38 // newAdmin constructs a new admin object on the heap and returns a pointer to 39 // it. 40 func newAdmin(configFile string, dryRun bool) (*admin, error) { 41 // Unlike most boulder service constructors, this does all of its own config 42 // parsing and dependency setup. If this is broken out into its own package 43 // (outside the //cmd/ directory) those pieces of setup should stay behind 44 // in //cmd/admin/main.go, to match other boulder services. 45 var c Config 46 err := cmd.ReadConfigFile(configFile, &c) 47 if err != nil { 48 return nil, fmt.Errorf("parsing config file: %w", err) 49 } 50 51 scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, "") 52 defer oTelShutdown(context.Background()) 53 logger.Info(cmd.VersionString()) 54 55 clk := clock.New() 56 features.Set(c.Admin.Features) 57 58 tlsConfig, err := c.Admin.TLS.Load(scope) 59 if err != nil { 60 return nil, fmt.Errorf("loading TLS config: %w", err) 61 } 62 63 var rac rapb.RegistrationAuthorityClient = dryRunRAC{log: logger} 64 if !dryRun { 65 raConn, err := bgrpc.ClientSetup(c.Admin.RAService, tlsConfig, scope, clk) 66 if err != nil { 67 return nil, fmt.Errorf("creating RA gRPC client: %w", err) 68 } 69 rac = rapb.NewRegistrationAuthorityClient(raConn) 70 } 71 72 saConn, err := bgrpc.ClientSetup(c.Admin.SAService, tlsConfig, scope, clk) 73 if err != nil { 74 return nil, fmt.Errorf("creating SA gRPC client: %w", err) 75 } 76 saroc := sapb.NewStorageAuthorityReadOnlyClient(saConn) 77 78 var sac sapb.StorageAuthorityClient = dryRunSAC{log: logger} 79 if !dryRun { 80 sac = sapb.NewStorageAuthorityClient(saConn) 81 } 82 83 dbMap, err := sa.InitWrappedDb(c.Admin.DB, nil, logger) 84 if err != nil { 85 return nil, fmt.Errorf("creating database connection: %w", err) 86 } 87 88 return &admin{ 89 rac: rac, 90 sac: sac, 91 saroc: saroc, 92 dbMap: dbMap, 93 dryRun: dryRun, 94 clk: clk, 95 log: logger, 96 }, nil 97 } 98 99 // findActiveInputMethodFlag returns a single key from setInputs with a value of `true`, 100 // if exactly one exists. Otherwise it returns an error. 101 func findActiveInputMethodFlag(setInputs map[string]bool) (string, error) { 102 var activeFlags []string 103 for flag, isSet := range setInputs { 104 if isSet { 105 activeFlags = append(activeFlags, flag) 106 } 107 } 108 109 if len(activeFlags) == 0 { 110 return "", errors.New("at least one input method flag must be specified") 111 } else if len(activeFlags) > 1 { 112 return "", fmt.Errorf("more than one input method flag specified: %v", activeFlags) 113 } 114 115 return activeFlags[0], nil 116 }