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