Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
Signed-off-by: xinau <[email protected]>
  • Loading branch information
xinau committed Sep 5, 2020
0 parents commit 20f5736
Show file tree
Hide file tree
Showing 25 changed files with 2,047 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bin/*
out/*

24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM golang:1.15 as build

COPY . /usr/share/repo
WORKDIR /usr/share/repo

RUN apt-get update && apt-get install -y \
ca-certificates
RUN make

FROM debian:stable
LABEL maintainer="Felix Ehrenpfort <[email protected]>"

COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=build /usr/share/repo/out/certspotter-sd /usr/local/bin/certspotter-sd
COPY example/certspotter-sd.yml /etc/prometheus/certspotter-sd.yml

RUN mkdir -p /var/lib/certspotter-sd && \
chown -R nobody:nogroup etc/prometheus /var/lib/certspotter-sd

USER nobody
VOLUME [ "/var/lib/certspotter-sd" ]
ENTRYPOINT [ "/usr/local/bin/certspotter-sd" ]
CMD [ "--config.file=/etc/prometheus/certspotter-sd.yml" ]

19 changes: 19 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2020 codecentric AG

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
126 changes: 126 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
MODULE = $(shell env GO111MODULE=on $(GO) list -m)
DATE ?= $(shell date -u +%F)
VERSION ?= $(shell git describe --tags --abbrev=0 --match=v* 2> /dev/null)
PKGS = $(or $(PKG),$(shell env GO111MODULE=on $(GO) list ./...))
TESTPKGS = $(shell env GO111MODULE=on $(GO) list -f \
'{{ if or .TestGoFiles .XTestGoFiles }}{{ .ImportPath }}{{ end }}' \
$(PKGS))
BIN = $(CURDIR)/bin
OUT = $(CURDIR)/out

GO = go
TIMEOUT = 15
LDFLAGS = "-X $(MODULE)/internal/version.Version=$(VERSION) -X $(MODULE)/internal/version.BuildDate=$(DATE)"

PREV_VERSION = $(shell git describe --abbrev=0 --match=v* $(VERSION)^ 2> /dev/null)

DEBUG = 0
Q = $(if $(filter 1,$DEBUG),,@)
M = $(shell printf ">_ ")

export GO111MODULE=on

.DEFAULT_GOAL:=all
.PHONY: all
all: clean fmt lint static test build

# Tools

$(BIN):
@mkdir -p $@
$(BIN)/%: | $(BIN) ; $(info $(M) building $(PACKAGE)...)
$Q tmp=$$(mktemp -d); \
env GO111MODULE=off GOPATH=$$tmp GOBIN=$(BIN) $(GO) get $(PACKAGE) \
|| ret=$$?; \
rm -rf $$tmp ; exit $$ret

GOIMPORTS = $(BIN)/goimports
$(BIN)/goimports: PACKAGE=golang.org/x/tools/cmd/goimports

GOLINT = $(BIN)/golint
$(BIN)/golint: PACKAGE=golang.org/x/lint/golint

GOSTATICCHECK = $(BIN)/staticcheck
$(BIN)/staticcheck: PACKAGE=honnef.co/go/tools/cmd/staticcheck

GOX = $(BIN)/gox
$(BIN)/gox: PACKAGE=github.com/mitchellh/gox

# Tests

TEST_TARGETS := test-default test-bench test-short test-verbose test-race
.PHONY: $(TEST_TARGETS) test
test-bench: ARGS=-run=__absolutelynothing__ -bench=. ## run test benchmarks
test-short: ARGS=-short ## run only short tests
test-verbose: ARGS=-v ## run tests with verbose
test-race: ARGS=-race ## run tests with race detector
$(TEST_TARGETS): NAME=$(MAKECMDGOALS:test-%=%)
$(TEST_TARGETS): test
test: fmt lint static ; $(info $(M) running $(NAME:%=% )tests...) @ ## run tests
$Q $(GO) test -timeout $(TIMEOUT)s $(ARGS) $(TESTPKGS)

.PHONY: static
static: fmt lint | $(GOSTATICCHECK) ; $(info $(M) running staticcheck...) @ ## run staticcheck
$Q $(GOSTATICCHECK) $(PKGS)

.PHONY: lint
lint: | $(GOLINT) ; $(info $(M) running golint...) @ ## run golint
$Q $(GOLINT) -set_exit_status $(PKGS)

.PHONY: fmt
fmt: | $(GOIMPORTS) ; $(info $(M) running goimports...) @ ## run goimports
$Q $(GOIMPORTS) -l -w -local $(MODULE) $(subst $(MODULE),.,$(PKGS))

# Build

.PHONY: build
build: fmt lint static ; $(info $(M) building local executable...) @ ## build local executable
$Q $(GO) build \
-ldflags $(LDFLAGS) \
-o $(OUT)/bin/certspotter-sd \
$(MODULE)/cmd/certspotter-sd

.PHONY: build-release
build-release: fmt lint static | $(GOX) ; $(info $(M) building all executables...) @ ## build release executables
$Q $(GOX) \
-osarch "freebsd/amd64 freebsd/arm linux/amd64 linux/arm linux/arm64" \
-ldflags $(LDFLAGS) \
-output "$(OUT)/bin/{{.Dir}}.{{.OS}}-{{.Arch}}" \
$(MODULE)/cmd/certspotter-sd

# Release

.PHONY: release
release: fmt lint static test build-release changelog ; $(info $(M) creating releases...) @ ## creating release archives
$Q cd $(OUT); for file in ./bin/certspotter-sd.* ; do \
rm -rf certspotter-sd ; \
mkdir -p certspotter-sd ; \
cp -f $$file certspotter-sd/certspotter-sd ; \
tar -czf $$file.tar.gz certspotter-sd ; \
mv -f $$file.tar.gz ./ ; \
done
$Q cd $(OUT); sha256sum *.tar.gz > sha256sums.txt

# Misc

.PHONY: changelog
changelog: ## print changelog
$Q printf "<!--title:$(VERSION) / $(DATE)-->\n# changelog\n"
ifneq "$(PREV_VERSION)" ""
$Q git log --pretty="format:[\`%h\`](https://$(MODULE)/commit/%h) %s" $(PREV_VERSION)..$(VERSION)
else
$Q git log --pretty="format:[\`%h\`](https://$(MODULE)/commit/%h) %s" $(VERSION)
endif

.PHONY: clean
clean: ; $(info $(M) cleaning...) @ ## clean up everything
$Q rm -rf $(BIN) $(OUT)

.PHONY: help
help:
$Q grep -hE '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "%-17s %s\n", $$1, $$2}'

.PHONY: version
version: ## print current version
$Q echo $(VERSION)
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
This repository contains code for a prometheus service discovery on top of the
[SSLMate Cert Spotter][1]. The service discovery can be used to implement a
automatic certificate expiration monitoring using the prometheus
blackbox-exporter.

## Installation

The certspotter discovery can be installed by downloading the executable from
the [releases page][2] or by building it locally using make or docker.

```bash
make
# or
docker build -t certspotter-sd .
```

## Configuration

The certspotter service discovery can be configured using a configuration file
and command-line flags (configuration file to load and setting the logging
severity).

The configuration uses the following format.
```yaml
# global configuartion
global:
# interval to use between polling the certspotter api.
polling_interval: <duration>
# rate limit to use for certspotter api (configured in Hz).
rate_limit: <number>
# token to used for authenticating againts certspotter api.
token: <string>

# domains to query
domains:
# domain to request certificate issuances for
- domain: <string>
# if sub domains should be included
include_subdomains: <bool>

# files to export targets to
files:
# filename to export targets to
- file: <string>
# labels to add to matching targets
labels:
<string>: <string>
# target labels to match to be included in file
match_re:
<string>: <regex>
```
The certspotter service discovey is intended to be used with prometheus and the
blackbox-exporter this can be configured in prometheus as follows. A complete
configuration of certspotter-sd, blackbox-exporter and prometheus can be found
in the [example][3] folder.
```yaml
- job_name: "blackbox:tcp"
metrics_path: /probe
params:
module: [tcp]
file_sd_configs:
- files:
- /etc/prometheus/targets.json
refresh_interval: 15s
relabel_configs:
- source_labels: [__address__, __port__]
separator: ":"
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: "localhost:9115"
```
Atm. configuration can't be reloaded by sending a `SIGHUP` and must be
terminated and restarted instead.

[1]: https://sslmate.com/certspotter/
[2]: https://github.com/codecentric/certspotter-sd/releases
[3]: https://github.com/codecentric/certspotter-sd/tree/master/example
106 changes: 106 additions & 0 deletions cmd/certspotter-sd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package main

import (
"context"
"flag"
"fmt"
"log"
"os"
"os/signal"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"

"github.com/codecentric/certspotter-sd/internal/config"
"github.com/codecentric/certspotter-sd/internal/discovery"
"github.com/codecentric/certspotter-sd/internal/export"
"github.com/codecentric/certspotter-sd/internal/version"
)

type arguments struct {
ConfigFile string
LogLevel *zapcore.Level
}

func main() {
args := argsparse()

logger := getlogger(*args.LogLevel)
defer logger.Sync()
sugar := logger.Sugar()

cfg, err := config.LoadFile(args.ConfigFile)
if err != nil {
sugar.Fatalw("can't read configuration", "err", err)
}

dc := discovery.NewDiscovery(
logger.With(zap.String("component", "discovery")),
&cfg.GlobalConfig,
)
exporter := export.NewExporter(
logger.With(zap.String("component", "exporter")),
&cfg.GlobalConfig,
)

ctx, cancel := context.WithCancel(context.Background())
go sighandler(ctx, func(sig os.Signal) {
sugar.Infow("stopping service discovery", "signal", sig)
cancel()
os.Exit(0)
})

sugar.Info("starting service discovery")
ch := dc.Discover(ctx, cfg.DomainConfigs)
exporter.Export(ctx, ch, cfg.FileConfigs)
}

func argsparse() *arguments {
var fversion bool

args := arguments{}
flag.StringVar(&args.ConfigFile, "config.file",
"/etc/prometheus/certspotter-sd.yml",
"configuration file to use.",
)
args.LogLevel = zap.LevelFlag("log.level",
zap.InfoLevel,
"severity of log to write. (default info)",
)
flag.BoolVar(&fversion, "version",
false,
"print certspotter-sd version.",
)
flag.Parse()

if fversion {
fmt.Println(version.Print())
os.Exit(0)
}

return &args
}

func getlogger(lvl zapcore.Level) *zap.Logger {
cfg := zap.NewProductionConfig()
cfg.Level.SetLevel(lvl)

logger, err := cfg.Build()
if err != nil {
log.Fatalf("can't initialize logger: %v", err)
}
return logger
}

func sighandler(ctx context.Context, handler func(os.Signal)) {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
for {
select {
case sig := <-ch:
handler(sig)
case <-ctx.Done():
return
}
}
}
10 changes: 10 additions & 0 deletions example/blackbox.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
modules:
tcp:
prober: tcp
tcp:
preferred_ip_protocol: ip4
tls:
prober: tcp
tcp:
preferred_ip_protocol: ip4
tls: true
13 changes: 13 additions & 0 deletions example/certspotter-sd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
global:
polling_interval: 1h
rate_limit: 1.25
token: ""

domains:
- domain: example.com
include_subdomains: true

files:
- file: /var/lib/certspotter-sd/targets.json
match_re:
dns_names: .*example.*
Loading

0 comments on commit 20f5736

Please sign in to comment.