aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml11
-rw-r--r--INSTALL21
-rw-r--r--Makefile45
-rw-r--r--README.md14
-rw-r--r--cmd/silentct-mac/examples.help2man6
-rw-r--r--cmd/silentct-mac/main.go (renamed from cmd/silent-ctnode/main.go)42
-rw-r--r--cmd/silentct-mac/name.help2man2
-rw-r--r--cmd/silentct-mac/see-also.help2man2
-rw-r--r--cmd/silentct-mon/examples.help2man38
-rw-r--r--cmd/silentct-mon/main.go (renamed from cmd/silent-ctmoon/main.go)60
-rw-r--r--cmd/silentct-mon/name.help2man2
-rw-r--r--cmd/silentct-mon/see-also.help2man2
-rw-r--r--docs/help2man/reporting-bugs.help2man12
-rw-r--r--docs/help2man/return-codes.help2man2
15 files changed, 198 insertions, 62 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c64b172..96f63e8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,6 +2,7 @@ image: golang:1.19
stages:
- test
+ - build
go-fmt:
stage: test
@@ -14,3 +15,13 @@ go-vet:
go-test:
stage: test
script: go test -race ./...
+
+install:
+ stage: build
+ before_script:
+ - apt update
+ - apt install make help2man
+ script:
+ - make DESTDIR=out PREFIX= install
+ - make DESTDIR=out PREFIX= uninstall
+ - make clean
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..c324a16
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,21 @@
+Type
+
+ make install
+
+to install silentct in a standard location.
+
+You may override the following Makefile variables:
+
+ DESTDIR default:
+ PREFIX default: $HOME/.local
+ BINDIR default: $(PREFIX)/bin
+ MANDIR default: $(PREFIX)/share/man
+ VERSION default: the latest git-commit
+
+Expect the programs in cmd/ to be installed with man pages.
+
+Type
+
+ make uninstall
+
+to uninstall silentct.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7da46b1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,45 @@
+DESTDIR ?=
+PREFIX ?= $(HOME)/.local
+BINDIR ?= $(PREFIX)/bin
+MANDIR ?= $(PREFIX)/share/man
+
+VERSION ?= $(shell git rev-parse HEAD)
+
+PROGRAMS = silentct-mac silentct-mon
+SRC_DIRS = $(patsubst %,cmd/%,$(PROGRAMS))
+
+all: build man
+
+.PHONY: build
+build: $(PROGRAMS)
+
+$(PROGRAMS):
+ @mkdir -p build
+ go build -o build/$@ $(patsubst %,cmd/%/main.go,$@)
+
+man: $(patsubst %,man-%,$(PROGRAMS))
+
+man-%: build
+ help2man \
+ --no-info --version-string=$(VERSION) \
+ --include=cmd/$*/name.help2man \
+ --include=cmd/$*/examples.help2man \
+ --include=cmd/$*/see-also.help2man \
+ --include=docs/help2man/return-codes.help2man \
+ --include=docs/help2man/reporting-bugs.help2man \
+ -o build/$*.1 build/$*
+
+install: all
+ @mkdir -p $(DESTDIR)$(BINDIR)
+ @mkdir -p $(DESTDIR)$(MANDIR)/man1
+ install -m 755 $(patsubst %,build/%,$(PROGRAMS)) $(DESTDIR)$(BINDIR)
+ install -m 644 $(patsubst %,build/%.1,$(PROGRAMS)) $(DESTDIR)$(MANDIR)/man1
+
+.PHONY: uninstall
+uninstall:
+ rm -f $(patsubst %,$(DESTDIR)$(BINDIR)/%,$(PROGRAMS))
+ rm -f $(patsubst %,$(DESTDIR)$(MANDIR)/man1/%.1,$(PROGRAMS))
+
+.PHONY: clean
+clean:
+ rm -rf build
diff --git a/README.md b/README.md
index d60d933..eb0e74d 100644
--- a/README.md
+++ b/README.md
@@ -25,15 +25,15 @@ certificate is found that no node submitted, only then is an alert printed.
### Setup a node
-You will need the `silent-ctnode` tool to create submissions that the monitor
-can pull. Install:
+You will need the `silentct-mac` tool to create submissions that the monitor can
+pull. Install:
- $ go install rgdd.se/silent-ct/cmd/silent-ctnode@latest
+ $ go install rgdd.se/silent-ct/cmd/silentct-mac@latest
Locate the node's certificates that are still valid (i.e., not expired) and
prepare a submission for them:
- $ silent-ctnode -n NAME -s SECRET /path/to/chain-1.pem /path/to/chain-2.pem ...
+ $ silentct-mac -n NAME -s SECRET /path/to/chain-1.pem /path/to/chain-2.pem ...
`NAME` is an arbitrary name of the node.
@@ -56,7 +56,7 @@ Repeat this setup if there are multiple nodes.
Install on the system that will run the monitor:
- $ go install rgdd.se/silent-ct/cmd/silent-ctmoon@latest
+ $ go install rgdd.se/silent-ct/cmd/silentct-mon@latest
Create a monitor policy file in JSON format. Below is an example that looks for
all certificates related to `example.org`, expect for certificates that are
@@ -95,12 +95,12 @@ your setup. Also add the domains each node is allowed to put into certificates.
Bootstrap the monitor in a non-existent directory:
- $ silent-ctmoon --bootstrap -f policy.json -d /path/to/directory -v INFO
+ $ silentct-mon --bootstrap -f policy.json -d /path/to/directory -v INFO
...
Leave the monitor running:
- $ silent-ctmoon -f policy.json -d /path/to/directory
+ $ silentct-mon -f policy.json -d /path/to/directory
Any noteworthy events (like a potentially mis-issued certificate that no node
submitted) will be printed on stdout. If you prefer to get the monitor's output
diff --git a/cmd/silentct-mac/examples.help2man b/cmd/silentct-mac/examples.help2man
new file mode 100644
index 0000000..f7bbff5
--- /dev/null
+++ b/cmd/silentct-mac/examples.help2man
@@ -0,0 +1,6 @@
+[EXAMPLES]
+Allowlist the current certificate in a Let's Encrypt deployment:
+
+.B $ silentct-mac -n example.org -s sikritpassword -o /var/www/example.org/silent-ct/allowlist /etc/letsencrypt/live/example.org/fullchain.pem
+
+You may run the above as part of your crontab or certbot renewal configuration.
diff --git a/cmd/silent-ctnode/main.go b/cmd/silentct-mac/main.go
index 99f4437..2add812 100644
--- a/cmd/silent-ctnode/main.go
+++ b/cmd/silentct-mac/main.go
@@ -16,32 +16,26 @@ import (
)
const usage = `
-A utility that generates a submission of one or more certificate chains.
-The generated submission is protected by a message authentication code.
+silentct-mac is a utility that helps allowlist legitimately issued certificates
+while monitoring Certificate Transparency logs. One or more certificate chains
+are bundled with a message authentication code, such that the silentct-mon tool
+can fetch them over an insecure channel or from untrusted intermediary storage.
-Usage:
-
- silent-ctnode --help
- silent-ctnode [Options] -n NAME -s SECRET FILE [FILE ...]
+Usage: silentct-mac [Options] -n NAME -s SECRET CRT-FILE [CRT-FILE ...]
Options:
-
- -h, --help: Output usage message and exit
+ -n, --name Name of the system that allowlists certificates
+ -o, --output Filename to write allowlisted certificates to (default: stdout)
+ -s, --secret Shared secret between the allowlisting system and its monitor
-v, --verbosity Leveled logging output (default: NOTICE)
-
- -n, --name: Name of the node generating the submission
- -s, --secret: Shared secret between the node and its monitor
- -o, --output: File to write submission to (default: stdout)
-
-Each trailing FILE argument must contain a single certificate chain.
`
type config struct {
// Options
- verbosity string
name string
- secret string
output string
+ secret string
+ verbosity string
// Extracted
log *logger.Logger
@@ -51,25 +45,25 @@ type config struct {
func configure(cmd string, args []string) (cfg config, err error) {
fs := flag.NewFlagSet(cmd, flag.ContinueOnError)
fs.Usage = func() {}
- flagopt.StringOpt(fs, &cfg.verbosity, "verbosity", "v", logger.LevelNotice.String())
flagopt.StringOpt(fs, &cfg.name, "name", "n", "")
- flagopt.StringOpt(fs, &cfg.secret, "secret", "s", "")
flagopt.StringOpt(fs, &cfg.output, "output", "o", "")
+ flagopt.StringOpt(fs, &cfg.secret, "secret", "s", "")
+ flagopt.StringOpt(fs, &cfg.verbosity, "verbosity", "v", logger.LevelNotice.String())
if err = fs.Parse(args); err != nil {
return cfg, err
}
// Options
- lv, err := logger.NewLevel(cfg.verbosity)
- if err != nil {
- return cfg, fmt.Errorf("invalid verbosity: %v", err)
- }
if cfg.name == "" {
return cfg, fmt.Errorf("node name is required")
}
if cfg.secret == "" {
return cfg, fmt.Errorf("node secret is required")
}
+ 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: os.Stderr})
// Arguments
@@ -85,11 +79,11 @@ func main() {
cfg, err := configure(os.Args[0], os.Args[1:])
if err != nil {
if errors.Is(err, flag.ErrHelp) {
- fmt.Fprintf(os.Stderr, "%s", usage[1:])
+ fmt.Fprintf(os.Stdout, "%s", usage[1:])
os.Exit(0)
}
if !strings.Contains(err.Error(), "flag provided but not defined") {
- fmt.Fprintf(os.Stderr, "%v\n", err)
+ fmt.Fprintf(os.Stdout, "%v\n", err)
}
os.Exit(1)
}
diff --git a/cmd/silentct-mac/name.help2man b/cmd/silentct-mac/name.help2man
new file mode 100644
index 0000000..d7f6a28
--- /dev/null
+++ b/cmd/silentct-mac/name.help2man
@@ -0,0 +1,2 @@
+[NAME]
+silentct-mac - allowlist certificates with message authentication codes
diff --git a/cmd/silentct-mac/see-also.help2man b/cmd/silentct-mac/see-also.help2man
new file mode 100644
index 0000000..02987dc
--- /dev/null
+++ b/cmd/silentct-mac/see-also.help2man
@@ -0,0 +1,2 @@
+[SEE ALSO]
+.BR silentct-mon (1)
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/silent-ctmoon/main.go b/cmd/silentct-mon/main.go
index c651e68..2d070fb 100644
--- a/cmd/silent-ctmoon/main.go
+++ b/cmd/silentct-mon/main.go
@@ -23,28 +23,27 @@ import (
)
const usage = `
-A utility that follows relevant Certificate Transparency logs to
-discover certificates that may be mis-issued. To be silent, any
-legitimately issued certificates are pulled from trusted nodes.
+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.
-Usage:
+The same list of Certificate Transparency logs as Google Chrome is used. This
+list can be overridden in the silentct-mon configuration file.
- silent-ctmoon --help
- silent-ctmoon [Opts] -d DIRECTORY -f POLICY_FILE
+Usage: silentct-mon [Options] -d DIRECTORY -f POLICY-FILE
Options:
- -h, --help: Output usage message and exit
- -v, --verbosity: Leveled logging output (default: NOTICE)
-
- -b, --bootstrap: Initializate 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
- -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
+ -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)
- -p, --pull-interval: How often nodes are pulled for certificates (Default: 15m)
- -w, --num-workers: Number of parallel workers to fetch each log with (Default: 1)
+ -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 {
@@ -67,29 +66,25 @@ type config struct {
func configure(cmd string, args []string) (cfg config, err error) {
fs := flag.NewFlagSet(cmd, flag.ContinueOnError)
fs.Usage = func() {}
- flagopt.StringOpt(fs, &cfg.verbosity, "verbosity", "v", logger.LevelNotice.String())
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.StringOpt(fs, &cfg.outputFile, "output-file", "o", "")
flagopt.DurationOpt(fs, &cfg.pullInterval, "pull-interval", "p", 15*time.Minute)
- flagopt.UintOpt(fs, &cfg.numWorkers, "num-workers", "w", 1)
+ flagopt.StringOpt(fs, &cfg.verbosity, "verbosity", "v", logger.LevelNotice.String())
if err = fs.Parse(args); err != nil {
return cfg, err
}
// Options
- lv, err := logger.NewLevel(cfg.verbosity)
- if err != nil {
- return cfg, fmt.Errorf("invalid verbosity: %v", err)
- }
if cfg.directory == "" {
return cfg, fmt.Errorf("directory is a required option")
}
- if cfg.policyFile == "" {
- return cfg, fmt.Errorf("policy file 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 != "" {
@@ -97,17 +92,20 @@ func configure(cmd string, args []string) (cfg config, err error) {
return cfg, fmt.Errorf("failed to open output file: %v", err)
}
}
- if cfg.numWorkers == 0 || cfg.numWorkers > 4 {
- return cfg, fmt.Errorf("number of workers must be in [1, 4]")
+ if cfg.policyFile == "" {
+ return cfg, fmt.Errorf("policy file is a required option")
}
-
- cfg.log = logger.New(logger.Config{Level: lv, File: output})
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 {
@@ -121,11 +119,11 @@ func main() {
cfg, err := configure(os.Args[0], os.Args[1:])
if err != nil {
if errors.Is(err, flag.ErrHelp) {
- fmt.Fprintf(os.Stderr, "%s", usage[1:])
+ fmt.Fprintf(os.Stdout, "%s", usage[1:])
os.Exit(0)
}
if !strings.Contains(err.Error(), "flag provided but not defined") {
- fmt.Fprintf(os.Stderr, "%v\n", err)
+ fmt.Fprintf(os.Stdout, "%v\n", err)
}
os.Exit(1)
}
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)
diff --git a/docs/help2man/reporting-bugs.help2man b/docs/help2man/reporting-bugs.help2man
new file mode 100644
index 0000000..893bb0f
--- /dev/null
+++ b/docs/help2man/reporting-bugs.help2man
@@ -0,0 +1,12 @@
+[REPORTING BUGS]
+Use
+.B https://git.glasklar.is/rgdd/silent-ct/-/issues
+for filing issues.
+.br
+Reach out to
+.B rgdd
+in room
+.B #certificate-transparency
+at
+.B OFTC.net
+and Matrix.
diff --git a/docs/help2man/return-codes.help2man b/docs/help2man/return-codes.help2man
new file mode 100644
index 0000000..0a14310
--- /dev/null
+++ b/docs/help2man/return-codes.help2man
@@ -0,0 +1,2 @@
+[RETURN CODES]
+A non-zero return code is used to indicate failure.