github.com/letsencrypt/boulder@v0.20251208.0/cmd/admin/main.go (about) 1 // Package main provides the "admin" tool, which can perform various 2 // administrative actions (such as revoking certificates) against a Boulder 3 // deployment. 4 // 5 // Run "admin -h" for a list of flags and subcommands. 6 // 7 // Note that the admin tool runs in "dry-run" mode *by default*. All commands 8 // which mutate the database (either directly or via gRPC requests) will refuse 9 // to do so, and instead print log lines representing the work they would do, 10 // unless the "-dry-run=false" flag is passed. 11 package main 12 13 import ( 14 "context" 15 "flag" 16 "fmt" 17 "os" 18 "strings" 19 20 "github.com/letsencrypt/boulder/cmd" 21 "github.com/letsencrypt/boulder/features" 22 ) 23 24 type Config struct { 25 Admin struct { 26 // DB controls the admin tool's direct connection to the database. 27 DB cmd.DBConfig 28 // TLS controls the TLS client the admin tool uses for gRPC connections. 29 TLS cmd.TLSConfig 30 31 RAService *cmd.GRPCClientConfig 32 SAService *cmd.GRPCClientConfig 33 34 Features features.Config 35 } 36 37 Syslog cmd.SyslogConfig 38 OpenTelemetry cmd.OpenTelemetryConfig 39 } 40 41 // subcommand specifies the set of methods that a struct must implement to be 42 // usable as an admin subcommand. 43 type subcommand interface { 44 // Desc should return a short (one-sentence) description of the subcommand for 45 // use in help/usage strings. 46 Desc() string 47 // Flags should register command line flags on the provided flagset. These 48 // should use the "TypeVar" methods on the provided flagset, targeting fields 49 // on the subcommand struct, so that the results of command line parsing can 50 // be used by other methods on the struct. 51 Flags(*flag.FlagSet) 52 // Run should do all of the subcommand's heavy lifting, with behavior gated on 53 // the subcommand struct's member fields which have been populated from the 54 // command line. The provided admin object can be used for access to external 55 // services like the RA, SA, and configured logger. 56 Run(context.Context, *admin) error 57 } 58 59 // main is the entry-point for the admin tool. We do not include admin in the 60 // suite of tools which are subcommands of the "boulder" binary, since it 61 // should be small and portable and standalone. 62 func main() { 63 // Do setup as similarly as possible to all other boulder services, including 64 // config parsing and stats and logging setup. However, the one downside of 65 // not being bundled with the boulder binary is that we don't get config 66 // validation for free. 67 defer cmd.AuditPanic() 68 69 // This is the registry of all subcommands that the admin tool can run. 70 subcommands := map[string]subcommand{ 71 "revoke-cert": &subcommandRevokeCert{}, 72 "block-key": &subcommandBlockKey{}, 73 "pause-identifier": &subcommandPauseIdentifier{}, 74 "unpause-account": &subcommandUnpauseAccount{}, 75 "import-limit-overrides": &subcommandImportOverrides{}, 76 "dump-limit-overrides": &subcommandDumpEnabledOverrides{}, 77 "toggle-limit-override": &subcommandToggleOverride{}, 78 "add-limit-override": &subcommandAddOverride{}, 79 } 80 81 defaultUsage := flag.Usage 82 flag.Usage = func() { 83 defaultUsage() 84 fmt.Printf("\nSubcommands:\n") 85 for name, command := range subcommands { 86 fmt.Printf(" %s\n", name) 87 fmt.Printf("\t%s\n", command.Desc()) 88 } 89 fmt.Print("\nYou can run \"admin <subcommand> -help\" to get usage for that subcommand.\n") 90 } 91 92 // Start by parsing just the global flags before we get to the subcommand, if 93 // they're present. 94 configFile := flag.String("config", "", "Path to the configuration file for this service (required)") 95 dryRun := flag.Bool("dry-run", true, "Print actions instead of mutating the database") 96 flag.Parse() 97 98 // Figure out which subcommand they want us to run. 99 unparsedArgs := flag.Args() 100 if len(unparsedArgs) == 0 { 101 flag.Usage() 102 os.Exit(1) 103 } 104 105 subcommand, ok := subcommands[unparsedArgs[0]] 106 if !ok { 107 flag.Usage() 108 os.Exit(1) 109 } 110 111 // Then parse the rest of the args according to the selected subcommand's 112 // flags, and allow the global flags to be placed after the subcommand name. 113 subflags := flag.NewFlagSet(unparsedArgs[0], flag.ExitOnError) 114 subcommand.Flags(subflags) 115 flag.VisitAll(func(f *flag.Flag) { 116 // For each flag registered at the global/package level, also register it on 117 // the subflags FlagSet. The `f.Value` here is a pointer to the same var 118 // that the original global flag would populate, so the same variable can 119 // be set either way. 120 subflags.Var(f.Value, f.Name, f.Usage) 121 }) 122 _ = subflags.Parse(unparsedArgs[1:]) 123 124 // With the flags all parsed, now we can parse our config and set up our admin 125 // object. 126 if *configFile == "" { 127 flag.Usage() 128 os.Exit(1) 129 } 130 131 a, err := newAdmin(*configFile, *dryRun) 132 cmd.FailOnError(err, "creating admin object") 133 134 // Finally, run the selected subcommand. 135 if a.dryRun { 136 a.log.AuditInfof("admin tool executing a dry-run with the following arguments: %q", strings.Join(os.Args, " ")) 137 } else { 138 a.log.AuditInfof("admin tool executing with the following arguments: %q", strings.Join(os.Args, " ")) 139 } 140 141 err = subcommand.Run(context.Background(), a) 142 cmd.FailOnError(err, "executing subcommand") 143 144 if a.dryRun { 145 a.log.AuditInfof("admin tool has successfully completed executing a dry-run with the following arguments: %q", strings.Join(os.Args, " ")) 146 a.log.Info("Dry run complete. Pass -dry-run=false to mutate the database.") 147 } else { 148 a.log.AuditInfof("admin tool has successfully completed executing with the following arguments: %q", strings.Join(os.Args, " ")) 149 } 150 }