aboutsummaryrefslogtreecommitdiff
path: root/cmd/silentct-mon
diff options
context:
space:
mode:
authorRasmus Dahlberg <rgdd@glasklarteknik.se>2024-05-16 12:48:22 +0200
committerRasmus Dahlberg <rgdd@glasklarteknik.se>2024-05-16 17:26:38 +0200
commitaba0f17953c9947bb51e78ed581f4e66b7012518 (patch)
tree6fbbbfe369224ca17088e429e49082f6ce9d5f5a /cmd/silentct-mon
parent6567c8f4ec3eefb855c6ef630a53b0fb8d8bf1e9 (diff)
Add man pages and installer Makefile
Includes renaming of the tools, part one of trying to simplify terminology and letting go of "node" and "moon". Improving the terminology was suggested by Martin H a while back, thank you.
Diffstat (limited to 'cmd/silentct-mon')
-rw-r--r--cmd/silentct-mon/examples.help2man38
-rw-r--r--cmd/silentct-mon/main.go222
-rw-r--r--cmd/silentct-mon/name.help2man2
-rw-r--r--cmd/silentct-mon/see-also.help2man2
4 files changed, 264 insertions, 0 deletions
diff --git a/cmd/silentct-mon/examples.help2man b/cmd/silentct-mon/examples.help2man
new file mode 100644
index 0000000..7a1e8fc
--- /dev/null
+++ b/cmd/silentct-mon/examples.help2man
@@ -0,0 +1,38 @@
+[EXAMPLES]
+
+A basic configuration is shown below.
+
+ {
+ "monitor": [
+ {
+ "bootstrap_at": "2024-05-16T00:00:00Z",
+ "wildcard": "example.org",
+ "excludes": [
+ "test"
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "name": "example.org",
+ "secret": "sikritpassword",
+ "url": "https://www.example.org/silent-ct/allowlist",
+ "issues": [
+ "example.org",
+ "www.example.org"
+ ]
+ }
+ ]
+ }
+
+Bootstrap a new monitor in a non-existent directory:
+
+.B $ silentct-mon -b -d ~/.local/lib/silent-ct -f ~/.config/silent-ct/config.json
+
+Run the monitor continuously:
+
+.B $ silentct-mon -d ~/.local/lib/silent-ct -f ~/.config/silent-ct/config.json
+
+Use
+.B -v DEBUG
+to see what's happening underneath the hood.
diff --git a/cmd/silentct-mon/main.go b/cmd/silentct-mon/main.go
new file mode 100644
index 0000000..2d070fb
--- /dev/null
+++ b/cmd/silentct-mon/main.go
@@ -0,0 +1,222 @@
+package main
+
+import (
+ "context"
+ "errors"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ "rgdd.se/silent-ct/internal/feedback"
+ "rgdd.se/silent-ct/internal/flagopt"
+ "rgdd.se/silent-ct/internal/ioutil"
+ "rgdd.se/silent-ct/internal/logger"
+ "rgdd.se/silent-ct/internal/manager"
+ "rgdd.se/silent-ct/internal/monitor"
+ "rgdd.se/silent-ct/pkg/policy"
+)
+
+const usage = `
+silentct-mon is a tool that monitors Certificate Transparency logs. The tool
+can operate silently, which means there need not be any output unless a
+certificate is possibly mis-issued. This requires use of the silentct-mac
+utility on the trusted systems that legitimately request certificates.
+
+The same list of Certificate Transparency logs as Google Chrome is used. This
+list can be overridden in the silentct-mon configuration file.
+
+Usage: silentct-mon [Options] -d DIRECTORY -f POLICY-FILE
+
+Options:
+
+ -b, --bootstrap Initializes a new state directory (Default: false)
+ -c, --contact A string that helps log operators know who you are (Default: "")
+ -d, --directory Path to a directory where all state will be stored
+ -w, --num-workers Number of parallel workers to fetch each log with (Default: 1)
+ -o, --output-file File that all output will be written to (Default: stdout)
+ -e, --please-exit Toggle to only run until up-to-date (Default: false)
+ -f, --policy-file Path to the monitor's policy file in JSON format
+ -p, --pull-interval How often nodes are pulled for certificates (Default: 15m)
+ -v, --verbosity Leveled logging output (default: NOTICE)
+`
+
+type config struct {
+ // Options
+ verbosity string
+ bootstrap bool
+ contact string
+ directory string
+ pleaseExit bool
+ policyFile string
+ outputFile string
+ pullInterval time.Duration
+ numWorkers uint
+
+ // Extracted
+ log *logger.Logger
+ policy policy.Policy
+}
+
+func configure(cmd string, args []string) (cfg config, err error) {
+ fs := flag.NewFlagSet(cmd, flag.ContinueOnError)
+ fs.Usage = func() {}
+ flagopt.BoolOpt(fs, &cfg.bootstrap, "bootstrap", "b", false)
+ flagopt.StringOpt(fs, &cfg.contact, "contact", "c", "")
+ flagopt.StringOpt(fs, &cfg.directory, "directory", "d", "")
+ flagopt.UintOpt(fs, &cfg.numWorkers, "num-workers", "w", 1)
+ flagopt.StringOpt(fs, &cfg.outputFile, "output-file", "o", "")
+ flagopt.BoolOpt(fs, &cfg.pleaseExit, "please-exit", "e", false)
+ flagopt.StringOpt(fs, &cfg.policyFile, "policy-file", "f", "")
+ flagopt.DurationOpt(fs, &cfg.pullInterval, "pull-interval", "p", 15*time.Minute)
+ flagopt.StringOpt(fs, &cfg.verbosity, "verbosity", "v", logger.LevelNotice.String())
+ if err = fs.Parse(args); err != nil {
+ return cfg, err
+ }
+
+ // Options
+ if cfg.directory == "" {
+ return cfg, fmt.Errorf("directory is a required option")
+ }
+ if cfg.numWorkers == 0 || cfg.numWorkers >= 4 {
+ return cfg, fmt.Errorf("number of workers must be in [1, 4)")
+ }
+ output := os.Stdout
+ if cfg.outputFile != "" {
+ if output, err = os.OpenFile(cfg.outputFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644); err != nil {
+ return cfg, fmt.Errorf("failed to open output file: %v", err)
+ }
+ }
+ if cfg.policyFile == "" {
+ return cfg, fmt.Errorf("policy file is a required option")
+ }
+ if err := ioutil.ReadJSON(cfg.policyFile, &cfg.policy); err != nil {
+ return cfg, err
+ }
+ if len(cfg.policy.Monitor) == 0 {
+ return cfg, fmt.Errorf("policy: need at least one wildcard to monitor")
+ }
+ lv, err := logger.NewLevel(cfg.verbosity)
+ if err != nil {
+ return cfg, fmt.Errorf("invalid verbosity: %v", err)
+ }
+ cfg.log = logger.New(logger.Config{Level: lv, File: output})
+
+ // Arguments
+ if len(fs.Args()) != 0 {
+ return cfg, fmt.Errorf("trailing arguments are not permitted")
+ }
+
+ return cfg, nil
+}
+
+func main() {
+ cfg, err := configure(os.Args[0], os.Args[1:])
+ if err != nil {
+ if errors.Is(err, flag.ErrHelp) {
+ fmt.Fprintf(os.Stdout, "%s", usage[1:])
+ os.Exit(0)
+ }
+ if !strings.Contains(err.Error(), "flag provided but not defined") {
+ fmt.Fprintf(os.Stdout, "%v\n", err)
+ }
+ os.Exit(1)
+ }
+
+ feventCh := make(chan []feedback.Event)
+ defer close(feventCh)
+
+ mconfigCh := make(chan monitor.MonitoredLog)
+ defer close(mconfigCh)
+
+ meventCh := make(chan monitor.Event)
+ defer close(meventCh)
+
+ errorCh := make(chan error)
+ defer close(errorCh)
+
+ mgr, err := manager.New(manager.Config{
+ Policy: cfg.policy,
+ Bootstrap: cfg.bootstrap,
+ Directory: cfg.directory,
+ Logger: cfg.log,
+ AlertDelay: cfg.pullInterval * 3 / 2,
+ }, feventCh, meventCh, mconfigCh, errorCh)
+ if err != nil {
+ cfg.log.Dief("manager: %v\n", err)
+ }
+ mon, err := monitor.New(monitor.Config{
+ Matcher: &cfg.policy.Monitor,
+ Logger: cfg.log,
+ Contact: cfg.contact,
+ NumWorkers: cfg.numWorkers,
+ }, meventCh, mconfigCh, errorCh)
+ if err != nil {
+ cfg.log.Dief("monitor: %v\n", err)
+ }
+ fb, err := feedback.New(feedback.Config{
+ Policy: cfg.policy,
+ Logger: cfg.log,
+ PullInterval: cfg.pullInterval,
+ }, feventCh)
+ if err != nil {
+ cfg.log.Dief("feedback: %v\n", err)
+ }
+
+ if cfg.bootstrap {
+ os.Exit(0)
+ }
+ if cfg.pleaseExit {
+ cfg.log.Dief("the --please-exit option is not supported yet\n")
+ }
+
+ var wg sync.WaitGroup
+ ctx, cancel := context.WithCancel(context.Background())
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ defer cancel()
+ await(ctx)
+ }()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ defer cancel()
+ mon.RunForever(ctx)
+ }()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ defer cancel()
+ fb.RunForever(ctx)
+ }()
+
+ os.Exit(func() int {
+ defer wg.Wait()
+ defer cancel()
+ if err := mgr.Run(ctx); err != nil {
+ log.Fatalf("manager: %v\n", err)
+ return 1
+ }
+ return 0
+ }())
+}
+
+func await(ctx context.Context) {
+ sigs := make(chan os.Signal, 1)
+ defer close(sigs)
+
+ signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
+ select {
+ case <-sigs:
+ case <-ctx.Done():
+ }
+}
diff --git a/cmd/silentct-mon/name.help2man b/cmd/silentct-mon/name.help2man
new file mode 100644
index 0000000..a4edcc1
--- /dev/null
+++ b/cmd/silentct-mon/name.help2man
@@ -0,0 +1,2 @@
+[NAME]
+silentct-mon - monitor Certificate Transparency logs
diff --git a/cmd/silentct-mon/see-also.help2man b/cmd/silentct-mon/see-also.help2man
new file mode 100644
index 0000000..d4b9782
--- /dev/null
+++ b/cmd/silentct-mon/see-also.help2man
@@ -0,0 +1,2 @@
+[SEE ALSO]
+.BR silentct-mac (1)