Compare commits
No commits in common. "main" and "v2.7.3" have entirely different histories.
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@ -1,4 +1 @@
|
|||||||
github: ldez
|
github: ldez
|
||||||
ko_fi: ldez_oss
|
|
||||||
liberapay: ldez
|
|
||||||
thanks_dev: u/gh/ldez
|
|
||||||
|
|||||||
33
.github/workflows/go-cross.yml
vendored
33
.github/workflows/go-cross.yml
vendored
@ -1,12 +1,5 @@
|
|||||||
name: Go Matrix
|
name: Go Matrix
|
||||||
|
on: [push, pull_request]
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@ -18,20 +11,32 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [ stable ]
|
go-version: [ 1.14, 1.15, 1.x ]
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
# https://github.com/marketplace/actions/setup-go-environment
|
||||||
|
- name: Set up Go ${{ matrix.go-version }}
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
# https://github.com/marketplace/actions/checkout
|
# https://github.com/marketplace/actions/checkout
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# https://github.com/marketplace/actions/setup-go-environment
|
# https://github.com/marketplace/actions/cache
|
||||||
- name: Set up Go ${{ matrix.go-version }}
|
- name: Cache Go modules
|
||||||
uses: actions/setup-go@v5
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
path: |
|
||||||
|
~/go/pkg/mod # Module download cache
|
||||||
|
~/.cache/go-build # Build cache (Linux)
|
||||||
|
~/Library/Caches/go-build # Build cache (Mac)
|
||||||
|
'%LocalAppData%\go-build' # Build cache (Windows)
|
||||||
|
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ matrix.go-version }}-go-
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -v -cover ./...
|
run: go test -v -cover ./...
|
||||||
|
|||||||
65
.github/workflows/main.yml
vendored
65
.github/workflows/main.yml
vendored
@ -3,10 +3,10 @@ name: Main
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- master
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@ -14,28 +14,75 @@ jobs:
|
|||||||
name: Main Process
|
name: Main Process
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
GO_VERSION: stable
|
GO_VERSION: 1.15
|
||||||
GOLANGCI_LINT_VERSION: v2.0.1
|
GOLANGCI_LINT_VERSION: v1.33.0
|
||||||
|
SEIHON_VERSION: v0.5.1
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v5
|
# https://github.com/marketplace/actions/setup-go-environment
|
||||||
|
- name: Set up Go ${{ env.GO_VERSION }}
|
||||||
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
|
||||||
|
# https://github.com/marketplace/actions/checkout
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# https://github.com/marketplace/actions/cache
|
||||||
|
- name: Cache Go modules
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
- name: Check and get dependencies
|
- name: Check and get dependencies
|
||||||
run: |
|
run: |
|
||||||
go mod download
|
|
||||||
go mod tidy
|
go mod tidy
|
||||||
git diff --exit-code go.mod
|
git diff --exit-code go.mod
|
||||||
git diff --exit-code go.sum
|
git diff --exit-code go.sum
|
||||||
|
go mod download
|
||||||
|
|
||||||
# https://golangci-lint.run/usage/install#other-ci
|
# https://golangci-lint.run/usage/install#other-ci
|
||||||
- name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }}
|
- name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }}
|
||||||
run: |
|
run: |
|
||||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION}
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION}
|
||||||
golangci-lint --version
|
golangci-lint --version
|
||||||
|
|
||||||
- name: Make
|
- name: Make
|
||||||
run: make
|
run: make
|
||||||
|
|
||||||
|
# Install Docker image multi-arch builder
|
||||||
|
- name: Install seihon ${{ env.SEIHON_VERSION }}
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
run: |
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/ldez/seihon/master/godownloader.sh | sh -s -- -b $(go env GOPATH)/bin ${SEIHON_VERSION}
|
||||||
|
seihon --version
|
||||||
|
|
||||||
|
- name: Docker Login
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
env:
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
|
||||||
|
|
||||||
|
- name: Deploy Docker Images (seihon)
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
run: make publish-images
|
||||||
|
|
||||||
|
# https://goreleaser.com/ci/actions/
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v2
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: release --rm-dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
|||||||
70
.github/workflows/release.yml
vendored
70
.github/workflows/release.yml
vendored
@ -1,70 +0,0 @@
|
|||||||
name: "Release a tag"
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Release Process
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
GO_VERSION: stable
|
|
||||||
CGO_ENABLED: 0
|
|
||||||
|
|
||||||
steps:
|
|
||||||
# temporary workaround for an error in free disk space action
|
|
||||||
# https://github.com/jlumbroso/free-disk-space/issues/14
|
|
||||||
- name: Update Package List and Remove Dotnet
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get remove -y '^dotnet-.*'
|
|
||||||
|
|
||||||
# https://github.com/marketplace/actions/free-disk-space-ubuntu
|
|
||||||
- name: Free Disk Space
|
|
||||||
uses: jlumbroso/free-disk-space@main
|
|
||||||
with:
|
|
||||||
# this might remove tools that are actually needed
|
|
||||||
tool-cache: false
|
|
||||||
|
|
||||||
# all of these default to true
|
|
||||||
android: true
|
|
||||||
dotnet: true
|
|
||||||
haskell: true
|
|
||||||
large-packages: true
|
|
||||||
docker-images: true
|
|
||||||
swap-storage: false
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ${{ env.GO_VERSION }}
|
|
||||||
|
|
||||||
- name: dockerhub-login
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: ghcr-login
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Run GoReleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v6
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: release -p 1 --clean --timeout=90m
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
51
.golangci.toml
Normal file
51
.golangci.toml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
[run]
|
||||||
|
timeout = "5m"
|
||||||
|
skip-files = []
|
||||||
|
|
||||||
|
[linters-settings]
|
||||||
|
|
||||||
|
[linters-settings.govet]
|
||||||
|
check-shadowing = true
|
||||||
|
|
||||||
|
[linters-settings.gocyclo]
|
||||||
|
min-complexity = 12.0
|
||||||
|
|
||||||
|
[linters-settings.maligned]
|
||||||
|
suggest-new = true
|
||||||
|
|
||||||
|
[linters-settings.goconst]
|
||||||
|
min-len = 3.0
|
||||||
|
min-occurrences = 3.0
|
||||||
|
|
||||||
|
[linters-settings.misspell]
|
||||||
|
locale = "US"
|
||||||
|
|
||||||
|
[linters]
|
||||||
|
enable-all = true
|
||||||
|
disable = [
|
||||||
|
"maligned",
|
||||||
|
"lll",
|
||||||
|
"gosec",
|
||||||
|
"dupl",
|
||||||
|
"prealloc",
|
||||||
|
"scopelint",
|
||||||
|
"wsl",
|
||||||
|
"nlreturn",
|
||||||
|
"gomnd",
|
||||||
|
"testpackage",
|
||||||
|
"paralleltest",
|
||||||
|
"tparallel",
|
||||||
|
"goerr113",
|
||||||
|
"wrapcheck",
|
||||||
|
"exhaustive",
|
||||||
|
"exhaustivestruct",
|
||||||
|
]
|
||||||
|
|
||||||
|
[issues]
|
||||||
|
exclude-use-default = false
|
||||||
|
max-per-linter = 0
|
||||||
|
max-same-issues = 0
|
||||||
|
exclude = ["ST1000: at least one file in a package should have a package comment"]
|
||||||
|
[[issues.exclude-rules]]
|
||||||
|
path = "cmd/"
|
||||||
|
linters = ["gochecknoglobals", "gochecknoinits"]
|
||||||
108
.golangci.yml
108
.golangci.yml
@ -1,108 +0,0 @@
|
|||||||
version: "2"
|
|
||||||
|
|
||||||
formatters:
|
|
||||||
enable:
|
|
||||||
- gci
|
|
||||||
- gofumpt
|
|
||||||
settings:
|
|
||||||
gofumpt:
|
|
||||||
extra-rules: true
|
|
||||||
|
|
||||||
linters:
|
|
||||||
default: all
|
|
||||||
disable:
|
|
||||||
- cyclop # duplicate of gocyclo
|
|
||||||
- dupl
|
|
||||||
- err113
|
|
||||||
- exhaustive
|
|
||||||
- exhaustruct
|
|
||||||
- lll
|
|
||||||
- mnd
|
|
||||||
- nilnil
|
|
||||||
- nlreturn
|
|
||||||
- paralleltest
|
|
||||||
- prealloc
|
|
||||||
- rowserrcheck # not relevant (SQL)
|
|
||||||
- sqlclosecheck # not relevant (SQL)
|
|
||||||
- testpackage
|
|
||||||
- tparallel
|
|
||||||
- varnamelen
|
|
||||||
- wrapcheck
|
|
||||||
- wsl
|
|
||||||
settings:
|
|
||||||
depguard:
|
|
||||||
rules:
|
|
||||||
main:
|
|
||||||
deny:
|
|
||||||
- pkg: github.com/instana/testify
|
|
||||||
desc: not allowed
|
|
||||||
- pkg: github.com/pkg/errors
|
|
||||||
desc: Should be replaced by standard lib errors package
|
|
||||||
forbidigo:
|
|
||||||
forbid:
|
|
||||||
- pattern: ^print(ln)?$
|
|
||||||
- pattern: ^spew\.Print(f|ln)?$
|
|
||||||
- pattern: ^spew\.Dump$
|
|
||||||
funlen:
|
|
||||||
lines: -1
|
|
||||||
statements: 40
|
|
||||||
goconst:
|
|
||||||
min-len: 3
|
|
||||||
min-occurrences: 3
|
|
||||||
gocritic:
|
|
||||||
disabled-checks:
|
|
||||||
- sloppyReassign
|
|
||||||
- rangeValCopy
|
|
||||||
- octalLiteral
|
|
||||||
- paramTypeCombine # already handle by gofumpt.extra-rules
|
|
||||||
enabled-tags:
|
|
||||||
- diagnostic
|
|
||||||
- style
|
|
||||||
- performance
|
|
||||||
settings:
|
|
||||||
hugeParam:
|
|
||||||
sizeThreshold: 100
|
|
||||||
gocyclo:
|
|
||||||
min-complexity: 12
|
|
||||||
godox:
|
|
||||||
keywords:
|
|
||||||
- FIXME
|
|
||||||
gomoddirectives:
|
|
||||||
replace-allow-list:
|
|
||||||
- github.com/abbot/go-http-auth
|
|
||||||
- github.com/go-check/check
|
|
||||||
- github.com/gorilla/mux
|
|
||||||
- github.com/mailgun/minheap
|
|
||||||
- github.com/mailgun/multibuf
|
|
||||||
- github.com/jaguilar/vt100
|
|
||||||
gosec:
|
|
||||||
excludes:
|
|
||||||
- G204 # Subprocess launched with a potential tainted input or cmd arguments
|
|
||||||
- G301 # Expect directory permissions to be 0750 or less
|
|
||||||
- G306 # Expect WriteFile permissions to be 0600 or less
|
|
||||||
govet:
|
|
||||||
disable:
|
|
||||||
- fieldalignment
|
|
||||||
enable-all: true
|
|
||||||
misspell:
|
|
||||||
locale: US
|
|
||||||
|
|
||||||
exclusions:
|
|
||||||
presets:
|
|
||||||
- comments
|
|
||||||
rules:
|
|
||||||
- linters:
|
|
||||||
- gochecknoglobals
|
|
||||||
- gochecknoinits
|
|
||||||
path: cmd/
|
|
||||||
- linters:
|
|
||||||
- tagalign
|
|
||||||
path: internal/traefikv[1-3]/
|
|
||||||
- path: (.+)\.go$
|
|
||||||
text: 'ST1000: at least one file in a package should have a package comment'
|
|
||||||
- path: (.+)\.go$
|
|
||||||
text: 'package-comments: should have a package comment'
|
|
||||||
|
|
||||||
issues:
|
|
||||||
max-issues-per-linter: 0
|
|
||||||
max-same-issues: 0
|
|
||||||
180
.goreleaser.yml
180
.goreleaser.yml
@ -1,4 +1,3 @@
|
|||||||
version: 2
|
|
||||||
project_name: traefik-certs-dumper
|
project_name: traefik-certs-dumper
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
@ -6,7 +5,7 @@ builds:
|
|||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X github.com/ldez/traefik-certs-dumper/cmd.version={{.Version}} -X github.com/ldez/traefik-certs-dumper/cmd.commit={{.ShortCommit}} -X github.com/ldez/traefik-certs-dumper/cmd.date={{.Date}}
|
- -s -w -X github.com/ldez/traefik-certs-dumper/cmd.version={{.Version}} -X github.com/ldez/traefik-certs-dumper/cmd.commit={{.ShortCommit}} -X github.com/ldez/traefik-certs-dumper/cmd.date={{.Date}}
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- GO111MODULE=on
|
||||||
goos:
|
goos:
|
||||||
- linux
|
- linux
|
||||||
- darwin
|
- darwin
|
||||||
@ -41,182 +40,9 @@ changelog:
|
|||||||
archives:
|
archives:
|
||||||
- id: tcd
|
- id: tcd
|
||||||
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||||
formats: [ 'tar.gz' ]
|
format: tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
formats: [ 'zip' ]
|
format: zip
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
|
|
||||||
docker_manifests:
|
|
||||||
- name_template: 'ldez/traefik-certs-dumper:{{ .Tag }}'
|
|
||||||
image_templates:
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-386'
|
|
||||||
- name_template: 'ldez/traefik-certs-dumper:latest'
|
|
||||||
image_templates:
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-386'
|
|
||||||
- name_template: 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}'
|
|
||||||
image_templates:
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-amd64'
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-arm64'
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv7'
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv6'
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-386'
|
|
||||||
- name_template: 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}'
|
|
||||||
image_templates:
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-386'
|
|
||||||
- name_template: 'ghcr.io/ldez/traefik-certs-dumper:latest'
|
|
||||||
image_templates:
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-386'
|
|
||||||
- name_template: 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}'
|
|
||||||
image_templates:
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-amd64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-arm64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv7'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv6'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-386'
|
|
||||||
|
|
||||||
dockers:
|
|
||||||
- use: buildx
|
|
||||||
goos: linux
|
|
||||||
goarch: amd64
|
|
||||||
dockerfile: buildx.Dockerfile
|
|
||||||
image_templates:
|
|
||||||
- 'ldez/traefik-certs-dumper:latest-amd64'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-amd64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:latest-amd64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-amd64'
|
|
||||||
build_flag_templates:
|
|
||||||
- '--pull'
|
|
||||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
|
|
||||||
- '--label=org.opencontainers.image.title={{.ProjectName}}'
|
|
||||||
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
|
|
||||||
- '--label=org.opencontainers.image.source={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.url={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
|
|
||||||
- '--label=org.opencontainers.image.created={{.Date}}'
|
|
||||||
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
|
|
||||||
- '--label=org.opencontainers.image.version={{.Version}}'
|
|
||||||
- '--platform=linux/amd64'
|
|
||||||
|
|
||||||
- use: buildx
|
|
||||||
goos: linux
|
|
||||||
goarch: arm64
|
|
||||||
dockerfile: buildx.Dockerfile
|
|
||||||
image_templates:
|
|
||||||
- 'ldez/traefik-certs-dumper:latest-arm64'
|
|
||||||
- 'ldez/traefik-certs-dumper:latest-arm.v8' # only for compatibility with Seihon
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm.v8' # only for compatibility with Seihon
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-arm64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:latest-arm64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-arm64'
|
|
||||||
build_flag_templates:
|
|
||||||
- '--pull'
|
|
||||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
|
|
||||||
- '--label=org.opencontainers.image.title={{.ProjectName}}'
|
|
||||||
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
|
|
||||||
- '--label=org.opencontainers.image.source={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.url={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
|
|
||||||
- '--label=org.opencontainers.image.created={{.Date}}'
|
|
||||||
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
|
|
||||||
- '--label=org.opencontainers.image.version={{.Version}}'
|
|
||||||
- '--platform=linux/arm64'
|
|
||||||
|
|
||||||
- use: buildx
|
|
||||||
goos: linux
|
|
||||||
goarch: arm
|
|
||||||
goarm: '7'
|
|
||||||
dockerfile: buildx.Dockerfile
|
|
||||||
image_templates:
|
|
||||||
- 'ldez/traefik-certs-dumper:latest-armv7'
|
|
||||||
- 'ldez/traefik-certs-dumper:latest-arm.v7' # only for compatibility with Seihon
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm.v7' # only for compatibility with Seihon
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv7'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:latest-armv7'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv7'
|
|
||||||
build_flag_templates:
|
|
||||||
- '--pull'
|
|
||||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
|
|
||||||
- '--label=org.opencontainers.image.title={{.ProjectName}}'
|
|
||||||
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
|
|
||||||
- '--label=org.opencontainers.image.source={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.url={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
|
|
||||||
- '--label=org.opencontainers.image.created={{.Date}}'
|
|
||||||
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
|
|
||||||
- '--label=org.opencontainers.image.version={{.Version}}'
|
|
||||||
- '--platform=linux/arm/v7'
|
|
||||||
|
|
||||||
- use: buildx
|
|
||||||
goos: linux
|
|
||||||
goarch: arm
|
|
||||||
goarm: '6'
|
|
||||||
dockerfile: buildx.Dockerfile
|
|
||||||
image_templates:
|
|
||||||
- 'ldez/traefik-certs-dumper:latest-armv6'
|
|
||||||
- 'ldez/traefik-certs-dumper:latest-arm.v6' # only for compatibility with Seihon
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm.v6' # only for compatibility with Seihon
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv6'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:latest-armv6'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv6'
|
|
||||||
build_flag_templates:
|
|
||||||
- '--pull'
|
|
||||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
|
|
||||||
- '--label=org.opencontainers.image.title={{.ProjectName}}'
|
|
||||||
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
|
|
||||||
- '--label=org.opencontainers.image.source={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.url={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
|
|
||||||
- '--label=org.opencontainers.image.created={{.Date}}'
|
|
||||||
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
|
|
||||||
- '--label=org.opencontainers.image.version={{.Version}}'
|
|
||||||
- '--platform=linux/arm/v6'
|
|
||||||
|
|
||||||
- use: buildx
|
|
||||||
goos: linux
|
|
||||||
goarch: '386'
|
|
||||||
dockerfile: buildx.Dockerfile
|
|
||||||
image_templates:
|
|
||||||
- 'ldez/traefik-certs-dumper:latest-386'
|
|
||||||
- 'ldez/traefik-certs-dumper:{{ .Tag }}-386'
|
|
||||||
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-386'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:latest-386'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-386'
|
|
||||||
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-386'
|
|
||||||
build_flag_templates:
|
|
||||||
- '--pull'
|
|
||||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
|
|
||||||
- '--label=org.opencontainers.image.title={{.ProjectName}}'
|
|
||||||
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
|
|
||||||
- '--label=org.opencontainers.image.source={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.url={{.GitURL}}'
|
|
||||||
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
|
|
||||||
- '--label=org.opencontainers.image.created={{.Date}}'
|
|
||||||
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
|
|
||||||
- '--label=org.opencontainers.image.version={{.Version}}'
|
|
||||||
- '--platform=linux/386'
|
|
||||||
|
|||||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
FROM golang:1-alpine as builder
|
||||||
|
|
||||||
|
RUN apk --update upgrade \
|
||||||
|
&& apk --no-cache --no-progress add git make gcc musl-dev
|
||||||
|
|
||||||
|
WORKDIR /go/src/github.com/ldez/traefik-certs-dumper
|
||||||
|
|
||||||
|
ENV GO111MODULE on
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN make build
|
||||||
|
|
||||||
|
FROM alpine:3.10
|
||||||
|
RUN apk --update upgrade \
|
||||||
|
&& apk --no-cache --no-progress add ca-certificates
|
||||||
|
|
||||||
|
COPY --from=builder /go/src/github.com/ldez/traefik-certs-dumper/traefik-certs-dumper /usr/bin/traefik-certs-dumper
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/traefik-certs-dumper"]
|
||||||
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
Copyright 2019-2024 Fernandez Ludovic
|
Copyright 2019 Fernandez Ludovic
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
3
Makefile
3
Makefile
@ -25,3 +25,6 @@ checks:
|
|||||||
|
|
||||||
doc:
|
doc:
|
||||||
go run . doc
|
go run . doc
|
||||||
|
|
||||||
|
publish-images:
|
||||||
|
seihon publish -v "$(TAG_NAME)" -v "latest" --image-name ldez/traefik-certs-dumper --dry-run=false
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
|
||||||
FROM alpine:3
|
|
||||||
|
|
||||||
RUN apk --no-cache --no-progress add git ca-certificates tzdata jq \
|
|
||||||
&& rm -rf /var/cache/apk/*
|
|
||||||
|
|
||||||
COPY traefik-certs-dumper /usr/bin/traefik-certs-dumper
|
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/traefik-certs-dumper"]
|
|
||||||
@ -1,10 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/abronan/valkeyrie/store"
|
||||||
"time"
|
"github.com/abronan/valkeyrie/store/boltdb"
|
||||||
|
|
||||||
"github.com/kvtools/boltdb"
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -31,20 +29,11 @@ func boltdbRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout")
|
config.Options.Bucket = cmd.Flag("bucket").Value.String()
|
||||||
if err != nil {
|
config.Options.PersistConnection, _ = cmd.Flags().GetBool("persist-connection")
|
||||||
return err
|
|
||||||
}
|
config.Backend = store.BOLTDB
|
||||||
|
boltdb.Register()
|
||||||
persistConnection, _ := cmd.Flags().GetBool("persist-connection")
|
|
||||||
|
return kv.Dump(config, baseConfig)
|
||||||
config.Options = &boltdb.Config{
|
|
||||||
Bucket: cmd.Flag("bucket").Value.String(),
|
|
||||||
PersistConnection: persistConnection,
|
|
||||||
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
config.StoreName = boltdb.StoreName
|
|
||||||
|
|
||||||
return kv.Dump(context.Background(), config, baseConfig)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/abronan/valkeyrie/store"
|
||||||
"time"
|
"github.com/abronan/valkeyrie/store/consul"
|
||||||
|
|
||||||
"github.com/kvtools/consul"
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -30,24 +28,10 @@ func consulRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig, err := createTLSConfig(cmd)
|
config.Options.Token = cmd.Flag("token").Value.String()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout")
|
config.Backend = store.CONSUL
|
||||||
if err != nil {
|
consul.Register()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Options = &consul.Config{
|
return kv.Dump(config, baseConfig)
|
||||||
TLS: tlsConfig,
|
|
||||||
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
|
|
||||||
Token: cmd.Flag("token").Value.String(),
|
|
||||||
Namespace: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
config.StoreName = consul.StoreName
|
|
||||||
|
|
||||||
return kv.Dump(context.Background(), config, baseConfig)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ var docCmd = &cobra.Command{
|
|||||||
Use: "doc",
|
Use: "doc",
|
||||||
Short: "Generate documentation",
|
Short: "Generate documentation",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
RunE: func(_ *cobra.Command, _ []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return doc.GenMarkdownTree(rootCmd, "./docs")
|
return doc.GenMarkdownTree(rootCmd, "./docs")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
42
cmd/etcd.go
42
cmd/etcd.go
@ -1,11 +1,11 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kvtools/etcdv2"
|
"github.com/abronan/valkeyrie/store"
|
||||||
"github.com/kvtools/etcdv3"
|
etcdv2 "github.com/abronan/valkeyrie/store/etcd/v2"
|
||||||
|
etcdv3 "github.com/abronan/valkeyrie/store/etcd/v3"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -32,47 +32,25 @@ func etcdRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
backend, err := cmd.Flags().GetString("etcd-version")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig, err := createTLSConfig(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
synPeriod, err := cmd.Flags().GetInt("sync-period")
|
synPeriod, err := cmd.Flags().GetInt("sync-period")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
config.Options.SyncPeriod = time.Duration(synPeriod) * time.Second
|
||||||
|
|
||||||
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout")
|
backend, err := cmd.Flags().GetString("etcd-version")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch backend {
|
switch backend {
|
||||||
case "etcdv3":
|
case "etcdv3":
|
||||||
config.Options = &etcdv3.Config{
|
config.Backend = store.ETCDV3
|
||||||
TLS: tlsConfig,
|
etcdv3.Register()
|
||||||
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
|
|
||||||
SyncPeriod: time.Duration(synPeriod) * time.Second,
|
|
||||||
Username: cmd.Flag("password").Value.String(),
|
|
||||||
Password: cmd.Flag("username").Value.String(),
|
|
||||||
}
|
|
||||||
config.StoreName = etcdv3.StoreName
|
|
||||||
default:
|
default:
|
||||||
config.Options = &etcdv2.Config{
|
config.Backend = store.ETCD
|
||||||
TLS: tlsConfig,
|
etcdv2.Register()
|
||||||
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
|
|
||||||
SyncPeriod: time.Duration(synPeriod) * time.Second,
|
|
||||||
Username: cmd.Flag("password").Value.String(),
|
|
||||||
Password: cmd.Flag("username").Value.String(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config.StoreName = etcdv2.StoreName
|
return kv.Dump(config, baseConfig)
|
||||||
}
|
|
||||||
|
|
||||||
return kv.Dump(context.Background(), config, baseConfig)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper/file"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper/file"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// fileCmd represents the file command.
|
||||||
var fileCmd = &cobra.Command{
|
var fileCmd = &cobra.Command{
|
||||||
Use: "file",
|
Use: "file",
|
||||||
Short: `Dump the content of the "acme.json" file.`,
|
Short: `Dump the content of the "acme.json" file.`,
|
||||||
@ -17,7 +16,7 @@ var fileCmd = &cobra.Command{
|
|||||||
|
|
||||||
baseConfig.Version = cmd.Flag("version").Value.String()
|
baseConfig.Version = cmd.Flag("version").Value.String()
|
||||||
|
|
||||||
return file.Dump(context.Background(), acmeFile, baseConfig)
|
return file.Dump(acmeFile, baseConfig)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,5 +24,5 @@ func init() {
|
|||||||
rootCmd.AddCommand(fileCmd)
|
rootCmd.AddCommand(fileCmd)
|
||||||
|
|
||||||
fileCmd.Flags().String("source", "./acme.json", "Path to 'acme.json' file.")
|
fileCmd.Flags().String("source", "./acme.json", "Path to 'acme.json' file.")
|
||||||
fileCmd.Flags().String("version", "", "Traefik version. If empty use v1. Possible values: 'v2', 'v3'.")
|
fileCmd.Flags().String("version", "", "Traefik version. If empty use v1. Possible values: 'v2'.")
|
||||||
}
|
}
|
||||||
|
|||||||
39
cmd/kv.go
39
cmd/kv.go
@ -3,11 +3,13 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/abronan/valkeyrie/store"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@ -43,10 +45,26 @@ func getKvConfig(cmd *cobra.Command) (*kv.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := createTLSConfig(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &kv.Config{
|
return &kv.Config{
|
||||||
Endpoints: endpoints,
|
Endpoints: endpoints,
|
||||||
Prefix: cmd.Flag("prefix").Value.String(),
|
Prefix: cmd.Flag("prefix").Value.String(),
|
||||||
Suffix: cmd.Flag("suffix").Value.String(),
|
Suffix: cmd.Flag("suffix").Value.String(),
|
||||||
|
Options: &store.Config{
|
||||||
|
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
|
||||||
|
Username: cmd.Flag("password").Value.String(),
|
||||||
|
Password: cmd.Flag("username").Value.String(),
|
||||||
|
TLS: tlsConfig,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,8 +87,8 @@ func createTLSConfig(cmd *cobra.Command) (*tls.Config, error) {
|
|||||||
privateKey := cmd.Flag("tls.key").Value.String()
|
privateKey := cmd.Flag("tls.key").Value.String()
|
||||||
certContent := cmd.Flag("tls.cert").Value.String()
|
certContent := cmd.Flag("tls.cert").Value.String()
|
||||||
|
|
||||||
if !insecureSkipVerify && (certContent == "" || privateKey == "") {
|
if !insecureSkipVerify && (len(certContent) == 0 || len(privateKey) == 0) {
|
||||||
return nil, errors.New("TLS Certificate or Key file must be set when TLS configuration is created")
|
return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
|
||||||
}
|
}
|
||||||
|
|
||||||
cert, err := getCertificate(privateKey, certContent)
|
cert, err := getCertificate(privateKey, certContent)
|
||||||
@ -81,7 +99,7 @@ func createTLSConfig(cmd *cobra.Command) (*tls.Config, error) {
|
|||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
Certificates: []tls.Certificate{cert},
|
Certificates: []tls.Certificate{cert},
|
||||||
RootCAs: caPool,
|
RootCAs: caPool,
|
||||||
InsecureSkipVerify: insecureSkipVerify, //nolint:gosec // it's a CLI option.
|
InsecureSkipVerify: insecureSkipVerify,
|
||||||
ClientAuth: clientAuth,
|
ClientAuth: clientAuth,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -96,7 +114,7 @@ func getCertPool(ca string) (*x509.CertPool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !caPool.AppendCertsFromPEM(caContent) {
|
if !caPool.AppendCertsFromPEM(caContent) {
|
||||||
return nil, errors.New("failed to parse CA")
|
return nil, fmt.Errorf("failed to parse CA")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,14 +122,11 @@ func getCertPool(ca string) (*x509.CertPool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getCAContent(ca string) ([]byte, error) {
|
func getCAContent(ca string) ([]byte, error) {
|
||||||
if _, err := os.Stat(ca); err != nil {
|
if _, errCA := os.Stat(ca); errCA != nil {
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return []byte(ca), nil
|
return []byte(ca), nil
|
||||||
}
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
caContent, err := os.ReadFile(filepath.Clean(ca))
|
caContent, err := ioutil.ReadFile(filepath.Clean(ca))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -138,11 +153,11 @@ func getCertificate(privateKey, certContent string) (tls.Certificate, error) {
|
|||||||
_, errCertIsFile := os.Stat(certContent)
|
_, errCertIsFile := os.Stat(certContent)
|
||||||
|
|
||||||
if errCertIsFile == nil && os.IsNotExist(errKeyIsFile) {
|
if errCertIsFile == nil && os.IsNotExist(errKeyIsFile) {
|
||||||
return tls.Certificate{}, errors.New("tls cert is a file, but tls key is not")
|
return tls.Certificate{}, fmt.Errorf("tls cert is a file, but tls key is not")
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.IsNotExist(errCertIsFile) && errKeyIsFile == nil {
|
if os.IsNotExist(errCertIsFile) && errKeyIsFile == nil {
|
||||||
return tls.Certificate{}, errors.New("TLS key is a file, but tls cert is not")
|
return tls.Certificate{}, fmt.Errorf("TLS key is a file, but tls cert is not")
|
||||||
}
|
}
|
||||||
|
|
||||||
// string
|
// string
|
||||||
|
|||||||
49
cmd/root.go
49
cmd/root.go
@ -1,18 +1,15 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
"github.com/mitchellh/go-homedir"
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@ -24,7 +21,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Use: "traefik-certs-dumper",
|
Use: "traefik-certs-dumper",
|
||||||
Short: "Dump Let's Encrypt certificates from Traefik.",
|
Short: "Dump Let's Encrypt certificates from Traefik.",
|
||||||
Long: `Dump Let's Encrypt certificates from Traefik.`,
|
Long: `Dump Let's Encrypt certificates from Traefik.`,
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if cmd.Name() == "version" {
|
if cmd.Name() == "version" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -38,7 +35,6 @@ var rootCmd = &cobra.Command{
|
|||||||
return fmt.Errorf("--crt-ext (%q) and --key-ext (%q) are identical, in this case --domain-subdir is required", crtExt, keyExt)
|
return fmt.Errorf("--crt-ext (%q) and --key-ext (%q) are identical, in this case --domain-subdir is required", crtExt, keyExt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -50,8 +46,6 @@ func Execute() {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
help()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -123,7 +117,7 @@ func tree(root, indent string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fis, err := os.ReadDir(root)
|
fis, err := ioutil.ReadDir(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not read dir %s: %w", root, err)
|
return fmt.Errorf("could not read dir %s: %w", root, err)
|
||||||
}
|
}
|
||||||
@ -138,10 +132,10 @@ func tree(root, indent string) error {
|
|||||||
for i, name := range names {
|
for i, name := range names {
|
||||||
add := "│ "
|
add := "│ "
|
||||||
if i == len(names)-1 {
|
if i == len(names)-1 {
|
||||||
fmt.Print(indent + "└──")
|
fmt.Printf(indent + "└──")
|
||||||
add = " "
|
add = " "
|
||||||
} else {
|
} else {
|
||||||
fmt.Print(indent + "├──")
|
fmt.Printf(indent + "├──")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tree(filepath.Join(root, name), indent+add); err != nil {
|
if err := tree(filepath.Join(root, name), indent+add); err != nil {
|
||||||
@ -184,34 +178,3 @@ func getBaseConfig(cmd *cobra.Command) (*dumper.BaseConfig, error) {
|
|||||||
Hook: cmd.Flag("post-hook").Value.String(),
|
Hook: cmd.Flag("post-hook").Value.String(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func help() {
|
|
||||||
var maxInt int64 = 2 // -> 50%
|
|
||||||
if time.Now().Month() == time.December {
|
|
||||||
maxInt = 1 // -> 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
n, _ := rand.Int(rand.Reader, big.NewInt(maxInt))
|
|
||||||
if n.Cmp(big.NewInt(0)) != 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
pStyle := lipgloss.NewStyle().
|
|
||||||
Padding(1).
|
|
||||||
BorderStyle(lipgloss.RoundedBorder()).
|
|
||||||
BorderForeground(lipgloss.Color("161")).
|
|
||||||
Align(lipgloss.Center)
|
|
||||||
|
|
||||||
hStyle := lipgloss.NewStyle().Bold(true)
|
|
||||||
|
|
||||||
s := fmt.Sprintln(hStyle.Render("Request for Donation."))
|
|
||||||
s += `
|
|
||||||
I need your help!
|
|
||||||
Donations fund the maintenance and development of traefik-certs-dumper.
|
|
||||||
Click on this link to donate: https://donate.ldez.dev`
|
|
||||||
|
|
||||||
log.Println(pStyle.Render(s))
|
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ var (
|
|||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Display version",
|
Short: "Display version",
|
||||||
Run: func(_ *cobra.Command, _ []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
displayVersion(rootCmd.Name())
|
displayVersion(rootCmd.Name())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/abronan/valkeyrie/store"
|
||||||
"time"
|
"github.com/abronan/valkeyrie/store/zookeeper"
|
||||||
|
|
||||||
"github.com/kvtools/zookeeper"
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -28,19 +26,8 @@ func zookeeperRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout")
|
config.Backend = store.ZK
|
||||||
if err != nil {
|
zookeeper.Register()
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Options = &zookeeper.Config{
|
return kv.Dump(config, baseConfig)
|
||||||
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
|
|
||||||
Username: cmd.Flag("password").Value.String(),
|
|
||||||
Password: cmd.Flag("username").Value.String(),
|
|
||||||
MaxBufferSize: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
config.StoreName = zookeeper.StoreName
|
|
||||||
|
|
||||||
return kv.Dump(context.Background(), config, baseConfig)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
This directory content external contributions that are not maintain by @ldez.
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=traefik certs dumper
|
|
||||||
; If you do not start traefik via systemd, choose network.target or docker.target
|
|
||||||
After=traefik.target
|
|
||||||
Wants=network-online.target systemd-networkd-wait-online.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Restart=on-abnormal
|
|
||||||
User=root
|
|
||||||
ExecStart=/usr/local/bin/traefik-certs-dumper file --version v2 --source /etc/traefik/acme/acme.json --dest /etc/ssl --watch
|
|
||||||
RestartSec=30
|
|
||||||
TimeoutSec=30
|
|
||||||
;WatchdogSec=30
|
|
||||||
|
|
||||||
; Limit the number of file descriptors; see `man systemd.exec` for more limit settings.
|
|
||||||
; LimitNOFILE=1048576
|
|
||||||
; Limit number of processes in this unit
|
|
||||||
LimitNPROC=1
|
|
||||||
|
|
||||||
; Use private /tmp and /var/tmp, which are discarded after traefik stops.
|
|
||||||
PrivateTmp=true
|
|
||||||
; Use a minimal /dev (May bring additional security if switched to 'true', but it may not work on Raspberry Pis or other devices)
|
|
||||||
PrivateDevices=true
|
|
||||||
; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
|
|
||||||
ProtectHome=true
|
|
||||||
; Make cgroups /sys/fs/cgroup read-only
|
|
||||||
ProtectControlGroups=true
|
|
||||||
; Make kernel settings (procfs and sysfs) read-only
|
|
||||||
ProtectKernelTunables=true
|
|
||||||
; Make /usr, /boot, /etc and possibly some more folders read-only.
|
|
||||||
ProtectSystem=full
|
|
||||||
; This merely retains r/w access rights, it does not add any new. Must still be writable on the host!
|
|
||||||
ReadWriteDirectories=/etc/ssl
|
|
||||||
ReadOnlyPaths=/etc/traefik/acme/acme.json
|
|
||||||
|
|
||||||
; The following additional security directives only work with systemd v229 or later.
|
|
||||||
NoNewPrivileges=true
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:v1.7
|
image: traefik:v1.7
|
||||||
@ -23,9 +25,10 @@ services:
|
|||||||
- ./letsencrypt:/letsencrypt
|
- ./letsencrypt:/letsencrypt
|
||||||
|
|
||||||
traefik-certs-dumper:
|
traefik-certs-dumper:
|
||||||
image: ldez/traefik-certs-dumper:v2.9.3
|
image: ldez/traefik-certs-dumper:v2.7.0
|
||||||
entrypoint: sh -c '
|
entrypoint: sh -c '
|
||||||
while ! [ -e /data/acme.json ]
|
apk add jq
|
||||||
|
; while ! [ -e /data/acme.json ]
|
||||||
|| ! [ `jq ".Certificates | length" /data/acme.json` != 0 ]; do
|
|| ! [ `jq ".Certificates | length" /data/acme.json` != 0 ]; do
|
||||||
sleep 1
|
sleep 1
|
||||||
; done
|
; done
|
||||||
@ -33,10 +36,9 @@ services:
|
|||||||
--source /data/acme.json --dest /data/certs'
|
--source /data/acme.json --dest /data/certs'
|
||||||
volumes:
|
volumes:
|
||||||
- ./letsencrypt:/data
|
- ./letsencrypt:/data
|
||||||
network_mode: "none"
|
|
||||||
|
|
||||||
whoami:
|
whoami:
|
||||||
image: traefik/whoami:v1.8.1
|
image: containous/whoami
|
||||||
labels:
|
labels:
|
||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
traefik.frontend.rule: Host:example.com
|
traefik.frontend.rule: Host:example.com
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
services:
|
|
||||||
|
|
||||||
traefik:
|
|
||||||
image: traefik:v2.11.3
|
|
||||||
command:
|
|
||||||
- --log.level=INFO
|
|
||||||
- --entrypoints.web.address=:80
|
|
||||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
|
||||||
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
|
||||||
- --entrypoints.websecure.address=:443
|
|
||||||
- --entrypoints.websecure.http.tls=true
|
|
||||||
- --entrypoints.websecure.http.tls.certResolver=le
|
|
||||||
- --providers.docker.exposedbydefault=false
|
|
||||||
- --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
|
|
||||||
- --certificatesresolvers.le.acme.email=email@example.com
|
|
||||||
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
|
|
||||||
- --certificatesresolvers.le.acme.tlsChallenge=true
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
- "443:443"
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- ./letsencrypt/:/letsencrypt
|
|
||||||
|
|
||||||
traefik-certs-dumper:
|
|
||||||
image: ldez/traefik-certs-dumper:v2.9.3
|
|
||||||
entrypoint: sh -c '
|
|
||||||
while ! [ -e /data/acme.json ]
|
|
||||||
|| ! [ `jq ".[] | .Certificates | length" /data/acme.json | jq -s "add" ` != 0 ]; do
|
|
||||||
sleep 1
|
|
||||||
; done
|
|
||||||
&& traefik-certs-dumper file --version v2 --watch
|
|
||||||
--source /data/acme.json --dest /data/certs'
|
|
||||||
volumes:
|
|
||||||
- ./letsencrypt:/data
|
|
||||||
network_mode: "none"
|
|
||||||
|
|
||||||
whoami:
|
|
||||||
image: traefik/whoami:v1.8.1
|
|
||||||
labels:
|
|
||||||
traefik.enable: 'true'
|
|
||||||
|
|
||||||
traefik.http.routers.app.rule: Host(`example.com`)
|
|
||||||
traefik.http.routers.app.entrypoints: websecure
|
|
||||||
@ -24,9 +24,8 @@ Dump Let's Encrypt certificates from Traefik.
|
|||||||
|
|
||||||
### SEE ALSO
|
### SEE ALSO
|
||||||
|
|
||||||
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
|
|
||||||
* [traefik-certs-dumper file](traefik-certs-dumper_file.md) - Dump the content of the "acme.json" file.
|
* [traefik-certs-dumper file](traefik-certs-dumper_file.md) - Dump the content of the "acme.json" file.
|
||||||
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
||||||
* [traefik-certs-dumper version](traefik-certs-dumper_version.md) - Display version
|
* [traefik-certs-dumper version](traefik-certs-dumper_version.md) - Display version
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
###### Auto generated by spf13/cobra on 9-Oct-2019
|
||||||
|
|||||||
@ -1,40 +0,0 @@
|
|||||||
## traefik-certs-dumper completion
|
|
||||||
|
|
||||||
Generate the autocompletion script for the specified shell
|
|
||||||
|
|
||||||
### Synopsis
|
|
||||||
|
|
||||||
Generate the autocompletion script for traefik-certs-dumper for the specified shell.
|
|
||||||
See each sub-command's help for details on how to use the generated script.
|
|
||||||
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
```
|
|
||||||
-h, --help help for completion
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options inherited from parent commands
|
|
||||||
|
|
||||||
```
|
|
||||||
--clean Clean destination folder before dumping content. (default true)
|
|
||||||
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
|
|
||||||
--crt-ext string The file extension of the generated certificates. (default ".crt")
|
|
||||||
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
|
|
||||||
--dest string Path to store the dump content. (default "./dump")
|
|
||||||
--domain-subdir Use domain as sub-directory.
|
|
||||||
--key-ext string The file extension of the generated private keys. (default ".key")
|
|
||||||
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
|
|
||||||
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
|
|
||||||
--watch Enable watching changes.
|
|
||||||
```
|
|
||||||
|
|
||||||
### SEE ALSO
|
|
||||||
|
|
||||||
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
|
|
||||||
* [traefik-certs-dumper completion bash](traefik-certs-dumper_completion_bash.md) - Generate the autocompletion script for bash
|
|
||||||
* [traefik-certs-dumper completion fish](traefik-certs-dumper_completion_fish.md) - Generate the autocompletion script for fish
|
|
||||||
* [traefik-certs-dumper completion powershell](traefik-certs-dumper_completion_powershell.md) - Generate the autocompletion script for powershell
|
|
||||||
* [traefik-certs-dumper completion zsh](traefik-certs-dumper_completion_zsh.md) - Generate the autocompletion script for zsh
|
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
## traefik-certs-dumper completion bash
|
|
||||||
|
|
||||||
Generate the autocompletion script for bash
|
|
||||||
|
|
||||||
### Synopsis
|
|
||||||
|
|
||||||
Generate the autocompletion script for the bash shell.
|
|
||||||
|
|
||||||
This script depends on the 'bash-completion' package.
|
|
||||||
If it is not installed already, you can install it via your OS's package manager.
|
|
||||||
|
|
||||||
To load completions in your current shell session:
|
|
||||||
|
|
||||||
source <(traefik-certs-dumper completion bash)
|
|
||||||
|
|
||||||
To load completions for every new session, execute once:
|
|
||||||
|
|
||||||
#### Linux:
|
|
||||||
|
|
||||||
traefik-certs-dumper completion bash > /etc/bash_completion.d/traefik-certs-dumper
|
|
||||||
|
|
||||||
#### macOS:
|
|
||||||
|
|
||||||
traefik-certs-dumper completion bash > $(brew --prefix)/etc/bash_completion.d/traefik-certs-dumper
|
|
||||||
|
|
||||||
You will need to start a new shell for this setup to take effect.
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
traefik-certs-dumper completion bash
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
```
|
|
||||||
-h, --help help for bash
|
|
||||||
--no-descriptions disable completion descriptions
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options inherited from parent commands
|
|
||||||
|
|
||||||
```
|
|
||||||
--clean Clean destination folder before dumping content. (default true)
|
|
||||||
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
|
|
||||||
--crt-ext string The file extension of the generated certificates. (default ".crt")
|
|
||||||
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
|
|
||||||
--dest string Path to store the dump content. (default "./dump")
|
|
||||||
--domain-subdir Use domain as sub-directory.
|
|
||||||
--key-ext string The file extension of the generated private keys. (default ".key")
|
|
||||||
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
|
|
||||||
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
|
|
||||||
--watch Enable watching changes.
|
|
||||||
```
|
|
||||||
|
|
||||||
### SEE ALSO
|
|
||||||
|
|
||||||
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
|
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
## traefik-certs-dumper completion fish
|
|
||||||
|
|
||||||
Generate the autocompletion script for fish
|
|
||||||
|
|
||||||
### Synopsis
|
|
||||||
|
|
||||||
Generate the autocompletion script for the fish shell.
|
|
||||||
|
|
||||||
To load completions in your current shell session:
|
|
||||||
|
|
||||||
traefik-certs-dumper completion fish | source
|
|
||||||
|
|
||||||
To load completions for every new session, execute once:
|
|
||||||
|
|
||||||
traefik-certs-dumper completion fish > ~/.config/fish/completions/traefik-certs-dumper.fish
|
|
||||||
|
|
||||||
You will need to start a new shell for this setup to take effect.
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
traefik-certs-dumper completion fish [flags]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
```
|
|
||||||
-h, --help help for fish
|
|
||||||
--no-descriptions disable completion descriptions
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options inherited from parent commands
|
|
||||||
|
|
||||||
```
|
|
||||||
--clean Clean destination folder before dumping content. (default true)
|
|
||||||
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
|
|
||||||
--crt-ext string The file extension of the generated certificates. (default ".crt")
|
|
||||||
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
|
|
||||||
--dest string Path to store the dump content. (default "./dump")
|
|
||||||
--domain-subdir Use domain as sub-directory.
|
|
||||||
--key-ext string The file extension of the generated private keys. (default ".key")
|
|
||||||
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
|
|
||||||
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
|
|
||||||
--watch Enable watching changes.
|
|
||||||
```
|
|
||||||
|
|
||||||
### SEE ALSO
|
|
||||||
|
|
||||||
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
|
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
## traefik-certs-dumper completion powershell
|
|
||||||
|
|
||||||
Generate the autocompletion script for powershell
|
|
||||||
|
|
||||||
### Synopsis
|
|
||||||
|
|
||||||
Generate the autocompletion script for powershell.
|
|
||||||
|
|
||||||
To load completions in your current shell session:
|
|
||||||
|
|
||||||
traefik-certs-dumper completion powershell | Out-String | Invoke-Expression
|
|
||||||
|
|
||||||
To load completions for every new session, add the output of the above command
|
|
||||||
to your powershell profile.
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
traefik-certs-dumper completion powershell [flags]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
```
|
|
||||||
-h, --help help for powershell
|
|
||||||
--no-descriptions disable completion descriptions
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options inherited from parent commands
|
|
||||||
|
|
||||||
```
|
|
||||||
--clean Clean destination folder before dumping content. (default true)
|
|
||||||
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
|
|
||||||
--crt-ext string The file extension of the generated certificates. (default ".crt")
|
|
||||||
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
|
|
||||||
--dest string Path to store the dump content. (default "./dump")
|
|
||||||
--domain-subdir Use domain as sub-directory.
|
|
||||||
--key-ext string The file extension of the generated private keys. (default ".key")
|
|
||||||
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
|
|
||||||
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
|
|
||||||
--watch Enable watching changes.
|
|
||||||
```
|
|
||||||
|
|
||||||
### SEE ALSO
|
|
||||||
|
|
||||||
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
|
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
## traefik-certs-dumper completion zsh
|
|
||||||
|
|
||||||
Generate the autocompletion script for zsh
|
|
||||||
|
|
||||||
### Synopsis
|
|
||||||
|
|
||||||
Generate the autocompletion script for the zsh shell.
|
|
||||||
|
|
||||||
If shell completion is not already enabled in your environment you will need
|
|
||||||
to enable it. You can execute the following once:
|
|
||||||
|
|
||||||
echo "autoload -U compinit; compinit" >> ~/.zshrc
|
|
||||||
|
|
||||||
To load completions in your current shell session:
|
|
||||||
|
|
||||||
source <(traefik-certs-dumper completion zsh)
|
|
||||||
|
|
||||||
To load completions for every new session, execute once:
|
|
||||||
|
|
||||||
#### Linux:
|
|
||||||
|
|
||||||
traefik-certs-dumper completion zsh > "${fpath[1]}/_traefik-certs-dumper"
|
|
||||||
|
|
||||||
#### macOS:
|
|
||||||
|
|
||||||
traefik-certs-dumper completion zsh > $(brew --prefix)/share/zsh/site-functions/_traefik-certs-dumper
|
|
||||||
|
|
||||||
You will need to start a new shell for this setup to take effect.
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
traefik-certs-dumper completion zsh [flags]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
```
|
|
||||||
-h, --help help for zsh
|
|
||||||
--no-descriptions disable completion descriptions
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options inherited from parent commands
|
|
||||||
|
|
||||||
```
|
|
||||||
--clean Clean destination folder before dumping content. (default true)
|
|
||||||
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
|
|
||||||
--crt-ext string The file extension of the generated certificates. (default ".crt")
|
|
||||||
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
|
|
||||||
--dest string Path to store the dump content. (default "./dump")
|
|
||||||
--domain-subdir Use domain as sub-directory.
|
|
||||||
--key-ext string The file extension of the generated private keys. (default ".key")
|
|
||||||
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
|
|
||||||
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
|
|
||||||
--watch Enable watching changes.
|
|
||||||
```
|
|
||||||
|
|
||||||
### SEE ALSO
|
|
||||||
|
|
||||||
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
|
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
|
||||||
@ -15,7 +15,7 @@ traefik-certs-dumper file [flags]
|
|||||||
```
|
```
|
||||||
-h, --help help for file
|
-h, --help help for file
|
||||||
--source string Path to 'acme.json' file. (default "./acme.json")
|
--source string Path to 'acme.json' file. (default "./acme.json")
|
||||||
--version string Traefik version. If empty use v1. Possible values: 'v2', 'v3'.
|
--version string Traefik version. If empty use v1. Possible values: 'v2'.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options inherited from parent commands
|
### Options inherited from parent commands
|
||||||
@ -37,4 +37,4 @@ traefik-certs-dumper file [flags]
|
|||||||
|
|
||||||
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
|
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
###### Auto generated by spf13/cobra on 9-Oct-2019
|
||||||
|
|||||||
@ -47,4 +47,4 @@ Dump the content of a KV store.
|
|||||||
* [traefik-certs-dumper kv etcd](traefik-certs-dumper_kv_etcd.md) - Dump the content of etcd.
|
* [traefik-certs-dumper kv etcd](traefik-certs-dumper_kv_etcd.md) - Dump the content of etcd.
|
||||||
* [traefik-certs-dumper kv zookeeper](traefik-certs-dumper_kv_zookeeper.md) - Dump the content of zookeeper.
|
* [traefik-certs-dumper kv zookeeper](traefik-certs-dumper_kv_zookeeper.md) - Dump the content of zookeeper.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
###### Auto generated by spf13/cobra on 9-Oct-2019
|
||||||
|
|||||||
@ -49,4 +49,4 @@ traefik-certs-dumper kv boltdb [flags]
|
|||||||
|
|
||||||
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
###### Auto generated by spf13/cobra on 9-Oct-2019
|
||||||
|
|||||||
@ -48,4 +48,4 @@ traefik-certs-dumper kv consul [flags]
|
|||||||
|
|
||||||
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
###### Auto generated by spf13/cobra on 9-Oct-2019
|
||||||
|
|||||||
@ -49,4 +49,4 @@ traefik-certs-dumper kv etcd [flags]
|
|||||||
|
|
||||||
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
###### Auto generated by spf13/cobra on 9-Oct-2019
|
||||||
|
|||||||
@ -47,4 +47,4 @@ traefik-certs-dumper kv zookeeper [flags]
|
|||||||
|
|
||||||
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
###### Auto generated by spf13/cobra on 9-Oct-2019
|
||||||
|
|||||||
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
Display version
|
Display version
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Display version
|
||||||
|
|
||||||
```
|
```
|
||||||
traefik-certs-dumper version [flags]
|
traefik-certs-dumper version [flags]
|
||||||
```
|
```
|
||||||
@ -31,4 +35,4 @@ traefik-certs-dumper version [flags]
|
|||||||
|
|
||||||
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
|
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 21-Feb-2025
|
###### Auto generated by spf13/cobra on 9-Oct-2019
|
||||||
|
|||||||
@ -2,8 +2,7 @@ package file
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"crypto/md5"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -15,95 +14,61 @@ import (
|
|||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
dumperv1 "github.com/ldez/traefik-certs-dumper/v2/dumper/v1"
|
v1 "github.com/ldez/traefik-certs-dumper/v2/dumper/v1"
|
||||||
dumperv2 "github.com/ldez/traefik-certs-dumper/v2/dumper/v2"
|
v2 "github.com/ldez/traefik-certs-dumper/v2/dumper/v2"
|
||||||
dumperv3 "github.com/ldez/traefik-certs-dumper/v2/dumper/v3"
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/hook"
|
"github.com/ldez/traefik-certs-dumper/v2/hook"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv1"
|
"github.com/traefik/traefik/v2/pkg/provider/acme"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv2"
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dump Dumps "acme.json" file to certificates.
|
// Dump Dumps "acme.json" file to certificates.
|
||||||
func Dump(ctx context.Context, acmeFile string, baseConfig *dumper.BaseConfig) error {
|
func Dump(acmeFile string, baseConfig *dumper.BaseConfig) error {
|
||||||
err := dump(acmeFile, baseConfig)
|
err := dump(acmeFile, baseConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseConfig.Watch {
|
if baseConfig.Watch {
|
||||||
hook.Exec(ctx, baseConfig.Hook)
|
hook.Exec(baseConfig.Hook)
|
||||||
|
|
||||||
return watch(ctx, acmeFile, baseConfig)
|
return watch(acmeFile, baseConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dump(acmeFile string, baseConfig *dumper.BaseConfig) error {
|
func dump(acmeFile string, baseConfig *dumper.BaseConfig) error {
|
||||||
switch baseConfig.Version {
|
if baseConfig.Version == "v2" {
|
||||||
case "v3":
|
|
||||||
err := dumpV3(acmeFile, baseConfig)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("v3: dump failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case "v2":
|
|
||||||
err := dumpV2(acmeFile, baseConfig)
|
err := dumpV2(acmeFile, baseConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("v2: dump failed: %w", err)
|
return fmt.Errorf("v2: dump failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
case "v1":
|
|
||||||
err := dumpV1(acmeFile, baseConfig)
|
err := dumpV1(acmeFile, baseConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("v1: dump failed: %w", err)
|
return fmt.Errorf("v1: dump failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
default:
|
|
||||||
err := dumpV1(acmeFile, baseConfig)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("v1: dump failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpV1(acmeFile string, baseConfig *dumper.BaseConfig) error {
|
func dumpV1(acmeFile string, baseConfig *dumper.BaseConfig) error {
|
||||||
data := &traefikv1.StoredData{}
|
data := &v1.StoredData{}
|
||||||
err := readJSONFile(acmeFile, data)
|
err := readJSONFile(acmeFile, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return dumperv1.Dump(data, baseConfig)
|
return v1.Dump(data, baseConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpV2(acmeFile string, baseConfig *dumper.BaseConfig) error {
|
func dumpV2(acmeFile string, baseConfig *dumper.BaseConfig) error {
|
||||||
data := map[string]*traefikv2.StoredData{}
|
data := map[string]*acme.StoredData{}
|
||||||
err := readJSONFile(acmeFile, &data)
|
err := readJSONFile(acmeFile, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return dumperv2.Dump(data, baseConfig)
|
return v2.Dump(data, baseConfig)
|
||||||
}
|
|
||||||
|
|
||||||
func dumpV3(acmeFile string, baseConfig *dumper.BaseConfig) error {
|
|
||||||
data := map[string]*traefikv3.StoredData{}
|
|
||||||
err := readJSONFile(acmeFile, &data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dumperv3.Dump(data, baseConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func readJSONFile(acmeFile string, data interface{}) error {
|
func readJSONFile(acmeFile string, data interface{}) error {
|
||||||
@ -125,7 +90,7 @@ func readJSONFile(acmeFile string, data interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func watch(ctx context.Context, acmeFile string, baseConfig *dumper.BaseConfig) error {
|
func watch(acmeFile string, baseConfig *dumper.BaseConfig) error {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create new watcher: %w", err)
|
return fmt.Errorf("failed to create new watcher: %w", err)
|
||||||
@ -148,7 +113,7 @@ func watch(ctx context.Context, acmeFile string, baseConfig *dumper.BaseConfig)
|
|||||||
log.Println("event:", event)
|
log.Println("event:", event)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, errW := manageEvent(ctx, watcher, event, acmeFile, previousHash, baseConfig)
|
hash, errW := manageEvent(watcher, event, acmeFile, previousHash, baseConfig)
|
||||||
if errW != nil {
|
if errW != nil {
|
||||||
log.Println("error:", errW)
|
log.Println("error:", errW)
|
||||||
done <- true
|
done <- true
|
||||||
@ -179,7 +144,7 @@ func watch(ctx context.Context, acmeFile string, baseConfig *dumper.BaseConfig)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func manageEvent(ctx context.Context, watcher *fsnotify.Watcher, event fsnotify.Event, acmeFile string, previousHash []byte, baseConfig *dumper.BaseConfig) ([]byte, error) {
|
func manageEvent(watcher *fsnotify.Watcher, event fsnotify.Event, acmeFile string, previousHash []byte, baseConfig *dumper.BaseConfig) ([]byte, error) {
|
||||||
err := manageRename(watcher, event, acmeFile)
|
err := manageRename(watcher, event, acmeFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("watcher renewal failed: %w", err)
|
return nil, fmt.Errorf("watcher renewal failed: %w", err)
|
||||||
@ -203,7 +168,7 @@ func manageEvent(ctx context.Context, watcher *fsnotify.Watcher, event fsnotify.
|
|||||||
log.Println("Dumped new certificate data.")
|
log.Println("Dumped new certificate data.")
|
||||||
}
|
}
|
||||||
|
|
||||||
hook.Exec(ctx, baseConfig.Hook)
|
hook.Exec(baseConfig.Hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash, nil
|
return hash, nil
|
||||||
@ -228,7 +193,7 @@ func calculateHash(acmeFile string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
defer func() { _ = file.Close() }()
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
h := sha256.New()
|
h := md5.New()
|
||||||
_, err = io.Copy(h, file)
|
_, err = io.Copy(h, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package file
|
package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
@ -26,18 +28,16 @@ func TestDump(t *testing.T) {
|
|||||||
acmeFile: "./fixtures/acme-v2.json",
|
acmeFile: "./fixtures/acme-v2.json",
|
||||||
version: "v2",
|
version: "v2",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "should dump traefik v3 file content",
|
|
||||||
acmeFile: "./fixtures/acme-v3.json",
|
|
||||||
version: "v3",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir, err := ioutil.TempDir("", "traefik-cert-dumper")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() { _ = os.RemoveAll(dir) }()
|
||||||
|
|
||||||
cfg := &dumper.BaseConfig{
|
cfg := &dumper.BaseConfig{
|
||||||
DumpPath: dir,
|
DumpPath: dir,
|
||||||
@ -53,7 +53,7 @@ func TestDump(t *testing.T) {
|
|||||||
Version: test.version,
|
Version: test.version,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := Dump(t.Context(), test.acmeFile, cfg)
|
err = Dump(test.acmeFile, cfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"default": {
|
|
||||||
"Account": {
|
|
||||||
"Email": "test@email.com",
|
|
||||||
"Registration": {
|
|
||||||
"body": {
|
|
||||||
"status": "valid",
|
|
||||||
"contact": [
|
|
||||||
"mailto:test@email.com"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/12345678"
|
|
||||||
},
|
|
||||||
"PrivateKey": "Q2VydGlmaWNhdGUgS2V5",
|
|
||||||
"KeyType": "4096"
|
|
||||||
},
|
|
||||||
"Certificates": [
|
|
||||||
{
|
|
||||||
"domain": {
|
|
||||||
"main": "my.domain.com"
|
|
||||||
},
|
|
||||||
"certificate": "Q2VydGlmaWNhdGU=",
|
|
||||||
"key": "Q2VydGlmaWNhdGUgS2V5",
|
|
||||||
"Store": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"domain": {
|
|
||||||
"main": "my.domain2.com"
|
|
||||||
},
|
|
||||||
"certificate": "Q2VydGlmaWNhdGU=",
|
|
||||||
"key": "Q2VydGlmaWNhdGUgS2V5",
|
|
||||||
"Store": "default"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +1,12 @@
|
|||||||
package kv
|
package kv
|
||||||
|
|
||||||
import (
|
import "github.com/abronan/valkeyrie/store"
|
||||||
"github.com/kvtools/valkeyrie"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config KV configuration.
|
// Config KV configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
StoreName string
|
Backend store.Backend
|
||||||
Prefix string
|
Prefix string
|
||||||
Suffix string
|
Suffix string
|
||||||
Endpoints []string
|
Endpoints []string
|
||||||
Options valkeyrie.Config
|
Options *store.Config
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package kv
|
|||||||
import (
|
import (
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv1"
|
v1 "github.com/ldez/traefik-certs-dumper/v2/dumper/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CertificateOld is used to store certificate info.
|
// CertificateOld is used to store certificate info.
|
||||||
@ -39,14 +39,14 @@ type ChallengeCert struct {
|
|||||||
|
|
||||||
// DomainsCertificate contains a certificate for multiple domains.
|
// DomainsCertificate contains a certificate for multiple domains.
|
||||||
type DomainsCertificate struct {
|
type DomainsCertificate struct {
|
||||||
Domains traefikv1.Domain
|
Domains v1.Domain
|
||||||
Certificate *CertificateOld
|
Certificate *CertificateOld
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertOldAccount converts account information from old account format.
|
// convertOldAccount converts account information from old account format.
|
||||||
func convertOldAccount(account *AccountOld) *traefikv1.StoredData {
|
func convertOldAccount(account *AccountOld) *v1.StoredData {
|
||||||
storedData := &traefikv1.StoredData{
|
storedData := &v1.StoredData{
|
||||||
Account: &traefikv1.Account{
|
Account: &v1.Account{
|
||||||
PrivateKey: account.PrivateKey,
|
PrivateKey: account.PrivateKey,
|
||||||
Registration: account.Registration,
|
Registration: account.Registration,
|
||||||
Email: account.Email,
|
Email: account.Email,
|
||||||
@ -54,9 +54,9 @@ func convertOldAccount(account *AccountOld) *traefikv1.StoredData {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var certs []*traefikv1.Certificate
|
var certs []*v1.Certificate
|
||||||
for _, oldCert := range account.DomainsCertificate.Certs {
|
for _, oldCert := range account.DomainsCertificate.Certs {
|
||||||
certs = append(certs, &traefikv1.Certificate{
|
certs = append(certs, &v1.Certificate{
|
||||||
Certificate: oldCert.Certificate.Certificate,
|
Certificate: oldCert.Certificate.Certificate,
|
||||||
Domain: oldCert.Domains,
|
Domain: oldCert.Domains,
|
||||||
Key: oldCert.Certificate.PrivateKey,
|
Key: oldCert.Certificate.PrivateKey,
|
||||||
|
|||||||
@ -3,28 +3,26 @@ package kv
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/kvtools/valkeyrie"
|
"github.com/abronan/valkeyrie"
|
||||||
"github.com/kvtools/valkeyrie/store"
|
"github.com/abronan/valkeyrie/store"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
v1 "github.com/ldez/traefik-certs-dumper/v2/dumper/v1"
|
v1 "github.com/ldez/traefik-certs-dumper/v2/dumper/v1"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/hook"
|
"github.com/ldez/traefik-certs-dumper/v2/hook"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultStoreKeySuffix is the default suffix/storage.
|
// DefaultStoreKeySuffix is the default suffix/storage.
|
||||||
const DefaultStoreKeySuffix = "/acme/account/object"
|
const DefaultStoreKeySuffix = "/acme/account/object"
|
||||||
|
|
||||||
// Dump Dumps KV content to certificates.
|
// Dump Dumps KV content to certificates.
|
||||||
func Dump(ctx context.Context, config *Config, baseConfig *dumper.BaseConfig) error {
|
func Dump(config *Config, baseConfig *dumper.BaseConfig) error {
|
||||||
kvStore, err := valkeyrie.NewStore(ctx, config.StoreName, config.Endpoints, config.Options)
|
kvStore, err := valkeyrie.NewStore(config.Backend, config.Endpoints, config.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create client of the store: %w", err)
|
return fmt.Errorf("unable to create client of the store: %w", err)
|
||||||
}
|
}
|
||||||
@ -32,10 +30,10 @@ func Dump(ctx context.Context, config *Config, baseConfig *dumper.BaseConfig) er
|
|||||||
storeKey := config.Prefix + config.Suffix
|
storeKey := config.Prefix + config.Suffix
|
||||||
|
|
||||||
if baseConfig.Watch {
|
if baseConfig.Watch {
|
||||||
return watch(ctx, kvStore, storeKey, baseConfig)
|
return watch(kvStore, storeKey, baseConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
pair, err := kvStore.Get(ctx, storeKey, nil)
|
pair, err := kvStore.Get(storeKey, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to retrieve %s value: %w", storeKey, err)
|
return fmt.Errorf("unable to retrieve %s value: %w", storeKey, err)
|
||||||
}
|
}
|
||||||
@ -43,8 +41,10 @@ func Dump(ctx context.Context, config *Config, baseConfig *dumper.BaseConfig) er
|
|||||||
return dumpPair(pair, baseConfig)
|
return dumpPair(pair, baseConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func watch(ctx context.Context, kvStore store.Store, storeKey string, baseConfig *dumper.BaseConfig) error {
|
func watch(kvStore store.Store, storeKey string, baseConfig *dumper.BaseConfig) error {
|
||||||
pairs, err := kvStore.Watch(ctx, storeKey, nil)
|
stopCh := make(<-chan struct{})
|
||||||
|
|
||||||
|
pairs, err := kvStore.Watch(storeKey, stopCh, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -64,7 +64,7 @@ func watch(ctx context.Context, kvStore store.Store, storeKey string, baseConfig
|
|||||||
log.Println("Dumped new certificate data.")
|
log.Println("Dumped new certificate data.")
|
||||||
}
|
}
|
||||||
|
|
||||||
hook.Exec(ctx, baseConfig.Hook)
|
hook.Exec(baseConfig.Hook)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,19 +77,18 @@ func dumpPair(pair *store.KVPair, baseConfig *dumper.BaseConfig) error {
|
|||||||
return v1.Dump(data, baseConfig)
|
return v1.Dump(data, baseConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStoredDataFromGzip(pair *store.KVPair) (*traefikv1.StoredData, error) {
|
func getStoredDataFromGzip(pair *store.KVPair) (*v1.StoredData, error) {
|
||||||
reader, err := gzip.NewReader(bytes.NewBuffer(pair.Value))
|
reader, err := gzip.NewReader(bytes.NewBuffer(pair.Value))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fail to create GZip reader: %w", err)
|
return nil, fmt.Errorf("fail to create GZip reader: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
acmeData, err := io.ReadAll(reader)
|
acmeData, err := ioutil.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to read the pair content: %w", err)
|
return nil, fmt.Errorf("unable to read the pair content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
account := &AccountOld{}
|
account := &AccountOld{}
|
||||||
//nolint:musttag // old format
|
|
||||||
if err := json.Unmarshal(acmeData, &account); err != nil {
|
if err := json.Unmarshal(acmeData, &account); err != nil {
|
||||||
return nil, fmt.Errorf("unable marshal AccountOld: %w", err)
|
return nil, fmt.Errorf("unable marshal AccountOld: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,12 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -17,7 +17,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Dump Dumps data to certificates.
|
// Dump Dumps data to certificates.
|
||||||
func Dump(data *traefikv1.StoredData, baseConfig *dumper.BaseConfig) error {
|
func Dump(data *StoredData, baseConfig *dumper.BaseConfig) error {
|
||||||
if baseConfig.Clean {
|
if baseConfig.Clean {
|
||||||
err := cleanDir(baseConfig.DumpPath)
|
err := cleanDir(baseConfig.DumpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -26,12 +26,12 @@ func Dump(data *traefikv1.StoredData, baseConfig *dumper.BaseConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !baseConfig.DomainSubDir {
|
if !baseConfig.DomainSubDir {
|
||||||
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0755); err != nil {
|
||||||
return fmt.Errorf("certs folder creation failure: %w", err)
|
return fmt.Errorf("certs folder creation failure: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0755); err != nil {
|
||||||
return fmt.Errorf("keys folder creation failure: %w", err)
|
return fmt.Errorf("keys folder creation failure: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,34 +52,34 @@ func Dump(data *traefikv1.StoredData, baseConfig *dumper.BaseConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
privateKeyPem := extractPEMPrivateKey(data.Account)
|
privateKeyPem := extractPEMPrivateKey(data.Account)
|
||||||
return os.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0o600)
|
return ioutil.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCert(dumpPath string, cert *traefikv1.Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
func writeCert(dumpPath string, cert *Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
||||||
certPath := filepath.Join(dumpPath, certsSubDir, safeName(cert.Domain.Main+info.Ext))
|
certPath := filepath.Join(dumpPath, certsSubDir, safeName(cert.Domain.Main+info.Ext))
|
||||||
if domainSubDir {
|
if domainSubDir {
|
||||||
certPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
certPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
||||||
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.WriteFile(certPath, cert.Certificate, 0o666)
|
return ioutil.WriteFile(certPath, cert.Certificate, 0666)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeKey(dumpPath string, cert *traefikv1.Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
func writeKey(dumpPath string, cert *Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
||||||
keyPath := filepath.Join(dumpPath, keysSubDir, safeName(cert.Domain.Main+info.Ext))
|
keyPath := filepath.Join(dumpPath, keysSubDir, safeName(cert.Domain.Main+info.Ext))
|
||||||
if domainSubDir {
|
if domainSubDir {
|
||||||
keyPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
keyPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
||||||
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.WriteFile(keyPath, cert.Key, 0o600)
|
return ioutil.WriteFile(keyPath, cert.Key, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractPEMPrivateKey(account *traefikv1.Account) []byte {
|
func extractPEMPrivateKey(account *Account) []byte {
|
||||||
var block *pem.Block
|
var block *pem.Block
|
||||||
switch account.KeyType {
|
switch account.KeyType {
|
||||||
case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192:
|
case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192:
|
||||||
@ -93,7 +93,7 @@ func extractPEMPrivateKey(account *traefikv1.Account) []byte {
|
|||||||
Bytes: account.PrivateKey,
|
Bytes: account.PrivateKey,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unsupported key type: '%v'", account.KeyType))
|
panic("unsupported key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return pem.EncodeToMemory(block)
|
return pem.EncodeToMemory(block)
|
||||||
@ -109,7 +109,7 @@ func cleanDir(dumpPath string) error {
|
|||||||
return errExists
|
return errExists
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := os.ReadDir(dumpPath)
|
dir, err := ioutil.ReadDir(dumpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
//go:build !windows
|
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package v1
|
package v1
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package traefikv1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
@ -3,12 +3,13 @@ package v2
|
|||||||
import (
|
import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv2"
|
"github.com/traefik/traefik/v2/pkg/provider/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -17,7 +18,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Dump Dumps data to certificates.
|
// Dump Dumps data to certificates.
|
||||||
func Dump(data map[string]*traefikv2.StoredData, baseConfig *dumper.BaseConfig) error {
|
func Dump(data map[string]*acme.StoredData, baseConfig *dumper.BaseConfig) error {
|
||||||
if baseConfig.Clean {
|
if baseConfig.Clean {
|
||||||
err := cleanDir(baseConfig.DumpPath)
|
err := cleanDir(baseConfig.DumpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -26,12 +27,12 @@ func Dump(data map[string]*traefikv2.StoredData, baseConfig *dumper.BaseConfig)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !baseConfig.DomainSubDir {
|
if !baseConfig.DomainSubDir {
|
||||||
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0755); err != nil {
|
||||||
return fmt.Errorf("certs folder creation failure: %w", err)
|
return fmt.Errorf("certs folder creation failure: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0755); err != nil {
|
||||||
return fmt.Errorf("keys folder creation failure: %w", err)
|
return fmt.Errorf("keys folder creation failure: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ func Dump(data map[string]*traefikv2.StoredData, baseConfig *dumper.BaseConfig)
|
|||||||
|
|
||||||
privateKeyPem := extractPEMPrivateKey(store.Account)
|
privateKeyPem := extractPEMPrivateKey(store.Account)
|
||||||
|
|
||||||
err := os.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0o600)
|
err := ioutil.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write private key: %w", err)
|
return fmt.Errorf("failed to write private key: %w", err)
|
||||||
}
|
}
|
||||||
@ -63,31 +64,31 @@ func Dump(data map[string]*traefikv2.StoredData, baseConfig *dumper.BaseConfig)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCert(dumpPath string, cert traefikv2.Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
func writeCert(dumpPath string, cert acme.Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
||||||
certPath := filepath.Join(dumpPath, certsSubDir, safeName(cert.Domain.Main+info.Ext))
|
certPath := filepath.Join(dumpPath, certsSubDir, safeName(cert.Domain.Main+info.Ext))
|
||||||
if domainSubDir {
|
if domainSubDir {
|
||||||
certPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
certPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
||||||
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.WriteFile(certPath, cert.Certificate, 0o666)
|
return ioutil.WriteFile(certPath, cert.Certificate, 0666)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeKey(dumpPath string, cert traefikv2.Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
func writeKey(dumpPath string, cert acme.Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
||||||
keyPath := filepath.Join(dumpPath, keysSubDir, safeName(cert.Domain.Main+info.Ext))
|
keyPath := filepath.Join(dumpPath, keysSubDir, safeName(cert.Domain.Main+info.Ext))
|
||||||
if domainSubDir {
|
if domainSubDir {
|
||||||
keyPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
keyPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
||||||
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return os.WriteFile(keyPath, cert.Key, 0o600)
|
return ioutil.WriteFile(keyPath, cert.Key, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractPEMPrivateKey(account *traefikv2.Account) []byte {
|
func extractPEMPrivateKey(account *acme.Account) []byte {
|
||||||
var block *pem.Block
|
var block *pem.Block
|
||||||
switch account.KeyType {
|
switch account.KeyType {
|
||||||
case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192:
|
case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192:
|
||||||
@ -101,7 +102,7 @@ func extractPEMPrivateKey(account *traefikv2.Account) []byte {
|
|||||||
Bytes: account.PrivateKey,
|
Bytes: account.PrivateKey,
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unsupported key type: '%v'", account.KeyType))
|
panic("unsupported key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
return pem.EncodeToMemory(block)
|
return pem.EncodeToMemory(block)
|
||||||
@ -117,7 +118,7 @@ func cleanDir(dumpPath string) error {
|
|||||||
return errExists
|
return errExists
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, err := os.ReadDir(dumpPath)
|
dir, err := ioutil.ReadDir(dumpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
//go:build !windows
|
|
||||||
// +build !windows
|
// +build !windows
|
||||||
|
|
||||||
package v2
|
package v2
|
||||||
|
|||||||
@ -1,132 +0,0 @@
|
|||||||
package v3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/dumper"
|
|
||||||
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
certsSubDir = "certs"
|
|
||||||
keysSubDir = "private"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dump Dumps data to certificates.
|
|
||||||
func Dump(data map[string]*traefikv3.StoredData, baseConfig *dumper.BaseConfig) error {
|
|
||||||
if baseConfig.Clean {
|
|
||||||
err := cleanDir(baseConfig.DumpPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("folder cleaning failed: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !baseConfig.DomainSubDir {
|
|
||||||
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0o755); err != nil {
|
|
||||||
return fmt.Errorf("certs folder creation failure: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0o755); err != nil {
|
|
||||||
return fmt.Errorf("keys folder creation failure: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, store := range data {
|
|
||||||
for _, cert := range store.Certificates {
|
|
||||||
err := writeCert(baseConfig.DumpPath, cert.Certificate, baseConfig.CrtInfo, baseConfig.DomainSubDir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write certificates: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = writeKey(baseConfig.DumpPath, cert.Certificate, baseConfig.KeyInfo, baseConfig.DomainSubDir)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write certificate keys: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if store.Account == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKeyPem := extractPEMPrivateKey(store.Account)
|
|
||||||
|
|
||||||
err := os.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0o600)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write private key: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeCert(dumpPath string, cert traefikv3.Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
|
||||||
certPath := filepath.Join(dumpPath, certsSubDir, safeName(cert.Domain.Main+info.Ext))
|
|
||||||
if domainSubDir {
|
|
||||||
certPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
|
||||||
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.WriteFile(certPath, cert.Certificate, 0o666)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeKey(dumpPath string, cert traefikv3.Certificate, info dumper.FileInfo, domainSubDir bool) error {
|
|
||||||
keyPath := filepath.Join(dumpPath, keysSubDir, safeName(cert.Domain.Main+info.Ext))
|
|
||||||
if domainSubDir {
|
|
||||||
keyPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
|
|
||||||
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.WriteFile(keyPath, cert.Key, 0o600)
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractPEMPrivateKey(account *traefikv3.Account) []byte {
|
|
||||||
var block *pem.Block
|
|
||||||
switch account.KeyType {
|
|
||||||
case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192:
|
|
||||||
block = &pem.Block{
|
|
||||||
Type: "RSA PRIVATE KEY",
|
|
||||||
Bytes: account.PrivateKey,
|
|
||||||
}
|
|
||||||
case certcrypto.EC256, certcrypto.EC384:
|
|
||||||
block = &pem.Block{
|
|
||||||
Type: "EC PRIVATE KEY",
|
|
||||||
Bytes: account.PrivateKey,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unsupported key type: '%v'", account.KeyType))
|
|
||||||
}
|
|
||||||
|
|
||||||
return pem.EncodeToMemory(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanDir(dumpPath string) error {
|
|
||||||
_, errExists := os.Stat(dumpPath)
|
|
||||||
if os.IsNotExist(errExists) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if errExists != nil {
|
|
||||||
return errExists
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := os.ReadDir(dumpPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range dir {
|
|
||||||
if err := os.RemoveAll(filepath.Join(dumpPath, f.Name())); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package v3
|
|
||||||
|
|
||||||
func safeName(filename string) string {
|
|
||||||
return filename
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package v3
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
func safeName(filename string) string {
|
|
||||||
return strings.ReplaceAll(filename, "*", "_")
|
|
||||||
}
|
|
||||||
101
go.mod
101
go.mod
@ -1,90 +1,27 @@
|
|||||||
module github.com/ldez/traefik-certs-dumper/v2
|
module github.com/ldez/traefik-certs-dumper/v2
|
||||||
|
|
||||||
go 1.24.0
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/lipgloss v1.0.0
|
github.com/abronan/valkeyrie v0.1.0
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/go-acme/lego/v4 v4.25.2
|
github.com/go-acme/lego/v4 v4.1.3
|
||||||
github.com/kvtools/boltdb v1.0.2
|
|
||||||
github.com/kvtools/consul v1.0.2
|
|
||||||
github.com/kvtools/etcdv2 v1.0.2
|
|
||||||
github.com/kvtools/etcdv3 v1.0.2
|
|
||||||
github.com/kvtools/valkeyrie v1.0.0
|
|
||||||
github.com/kvtools/zookeeper v1.0.2
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.1.1
|
||||||
github.com/spf13/viper v1.19.0
|
github.com/spf13/viper v1.7.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.6.1
|
||||||
|
github.com/traefik/traefik/v2 v2.3.4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
// related to Traefik
|
||||||
github.com/armon/go-metrics v0.4.1 // indirect
|
replace github.com/docker/docker => github.com/docker/engine v0.0.0-20190725163905-fa8dd90ceb7b
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.4.2 // indirect
|
|
||||||
github.com/coreos/go-semver v0.3.1 // indirect
|
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
|
||||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
|
||||||
github.com/go-zookeeper/zk v1.0.4 // indirect
|
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
|
||||||
github.com/hashicorp/consul/api v1.28.2 // indirect
|
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
|
||||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
|
||||||
github.com/hashicorp/go-metrics v0.5.4 // indirect
|
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
|
||||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
|
||||||
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
|
|
||||||
github.com/hashicorp/serf v0.10.2 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
||||||
github.com/magiconair/properties v1.8.9 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
|
||||||
github.com/spf13/afero v1.11.0 // indirect
|
|
||||||
github.com/spf13/cast v1.7.1 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
|
||||||
go.etcd.io/bbolt v1.3.6 // indirect
|
|
||||||
go.etcd.io/etcd/api/v3 v3.5.14 // indirect
|
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect
|
|
||||||
go.etcd.io/etcd/client/v2 v2.305.12 // indirect
|
|
||||||
go.etcd.io/etcd/client/v3 v3.5.14 // indirect
|
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
|
||||||
golang.org/x/crypto v0.40.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
|
|
||||||
golang.org/x/net v0.42.0 // indirect
|
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
|
||||||
golang.org/x/text v0.27.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
|
||||||
google.golang.org/grpc v1.73.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible
|
// related to Traefik: Containous forks
|
||||||
|
replace (
|
||||||
|
github.com/abbot/go-http-auth => github.com/containous/go-http-auth v0.4.1-0.20180112153951-65b0cdae8d7f
|
||||||
|
github.com/go-check/check => github.com/containous/check v0.0.0-20170915194414-ca0bf163426a
|
||||||
|
github.com/gorilla/mux => github.com/containous/mux v0.0.0-20181024131434-c33f32e26898
|
||||||
|
github.com/mailgun/minheap => github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595
|
||||||
|
github.com/mailgun/multibuf => github.com/containous/multibuf v0.0.0-20190809014333-8b6c9a7e6bba
|
||||||
|
github.com/rancher/go-rancher-metadata => github.com/containous/go-rancher-metadata v0.0.0-20190402144056-c6a65f8b7a28
|
||||||
|
)
|
||||||
|
|||||||
@ -11,21 +11,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Exec Execute a command on a go routine.
|
// Exec Execute a command on a go routine.
|
||||||
func Exec(ctx context.Context, command string) {
|
func Exec(command string) {
|
||||||
if command == "" {
|
if command == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
errH := execute(ctx, command)
|
errH := execute(command)
|
||||||
if errH != nil {
|
if errH != nil {
|
||||||
panic(errH)
|
panic(errH)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func execute(ctx context.Context, command string) error {
|
func execute(command string) error {
|
||||||
ctxCmd, cancel := context.WithTimeout(ctx, 30*time.Second)
|
ctxCmd, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
parts := strings.Fields(os.ExpandEnv(command))
|
parts := strings.Fields(os.ExpandEnv(command))
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
package hook
|
package hook
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_execute(t *testing.T) {
|
func Test_execute(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
@ -21,7 +19,7 @@ func Test_execute(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
err := execute(t.Context(), test.command)
|
err := execute(test.command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,17 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kvtools/boltdb"
|
"github.com/abronan/valkeyrie"
|
||||||
"github.com/kvtools/consul"
|
"github.com/abronan/valkeyrie/store"
|
||||||
"github.com/kvtools/etcdv3"
|
"github.com/abronan/valkeyrie/store/boltdb"
|
||||||
"github.com/kvtools/valkeyrie"
|
"github.com/abronan/valkeyrie/store/consul"
|
||||||
"github.com/kvtools/zookeeper"
|
etcdv3 "github.com/abronan/valkeyrie/store/etcd/v3"
|
||||||
|
"github.com/abronan/valkeyrie/store/zookeeper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const storeKey = "traefik/acme/account/object"
|
const storeKey = "traefik/acme/account/object"
|
||||||
@ -22,43 +22,38 @@ func main() {
|
|||||||
log.SetFlags(log.Lshortfile)
|
log.SetFlags(log.Lshortfile)
|
||||||
|
|
||||||
source := "./acme.json"
|
source := "./acme.json"
|
||||||
err := loadData(context.Background(), source)
|
err := loadData(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadData(ctx context.Context, source string) error {
|
func loadData(source string) error {
|
||||||
content, err := readFile(source)
|
content, err := readFile(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consul
|
// Consul
|
||||||
|
err = putData(store.CONSUL, []string{"localhost:8500"}, content)
|
||||||
err = putData(ctx, consul.StoreName, []string{"localhost:8500"},
|
|
||||||
&consul.Config{ConnectionTimeout: 3 * time.Second}, content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ETCD v3
|
// ETCD v3
|
||||||
err = putData(ctx, etcdv3.StoreName, []string{"localhost:2379"},
|
err = putData(store.ETCDV3, []string{"localhost:2379"}, content)
|
||||||
&etcdv3.Config{ConnectionTimeout: 3 * time.Second}, content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zookeeper
|
// Zookeeper
|
||||||
err = putData(ctx, zookeeper.StoreName, []string{"localhost:2181"},
|
err = putData(store.ZK, []string{"localhost:2181"}, content)
|
||||||
&zookeeper.Config{ConnectionTimeout: 3 * time.Second}, content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoltDB
|
// BoltDB
|
||||||
err = putData(ctx, boltdb.StoreName, []string{"/tmp/test-traefik-certs-dumper.db"},
|
err = putData(store.BOLTDB, []string{"/tmp/test-traefik-certs-dumper.db"}, content)
|
||||||
&boltdb.Config{ConnectionTimeout: 3 * time.Second, Bucket: "traefik"}, content)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -66,13 +61,29 @@ func loadData(ctx context.Context, source string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func putData(ctx context.Context, backend string, addrs []string, options valkeyrie.Config, content []byte) error {
|
func putData(backend store.Backend, addrs []string, content []byte) error {
|
||||||
kvStore, err := valkeyrie.NewStore(ctx, backend, addrs, options)
|
storeConfig := &store.Config{
|
||||||
|
ConnectionTimeout: 3 * time.Second,
|
||||||
|
Bucket: "traefik",
|
||||||
|
}
|
||||||
|
|
||||||
|
switch backend {
|
||||||
|
case store.CONSUL:
|
||||||
|
consul.Register()
|
||||||
|
case store.ETCDV3:
|
||||||
|
etcdv3.Register()
|
||||||
|
case store.ZK:
|
||||||
|
zookeeper.Register()
|
||||||
|
case store.BOLTDB:
|
||||||
|
boltdb.Register()
|
||||||
|
}
|
||||||
|
|
||||||
|
kvStore, err := valkeyrie.NewStore(backend, addrs, storeConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := kvStore.Put(ctx, storeKey, content, nil); err != nil {
|
if err := kvStore.Put(storeKey, content, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +92,7 @@ func putData(ctx context.Context, backend string, addrs []string, options valkey
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readFile(source string) ([]byte, error) {
|
func readFile(source string) ([]byte, error) {
|
||||||
content, err := os.ReadFile(filepath.Clean(source))
|
content, err := ioutil.ReadFile(filepath.Clean(source))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,101 +0,0 @@
|
|||||||
package traefikv2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/registration"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StoredData represents the data managed by Store.
|
|
||||||
type StoredData struct {
|
|
||||||
Account *Account
|
|
||||||
Certificates []*CertAndStore
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account is used to store lets encrypt registration info.
|
|
||||||
type Account struct {
|
|
||||||
Email string
|
|
||||||
Registration *registration.Resource
|
|
||||||
PrivateKey []byte
|
|
||||||
KeyType certcrypto.KeyType
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEmail returns email.
|
|
||||||
func (a *Account) GetEmail() string {
|
|
||||||
return a.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRegistration returns lets encrypt registration resource.
|
|
||||||
func (a *Account) GetRegistration() *registration.Resource {
|
|
||||||
return a.Registration
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPrivateKey returns private key.
|
|
||||||
func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
|
||||||
privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return privateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertAndStore allows mapping a TLS certificate to a TLS store.
|
|
||||||
type CertAndStore struct {
|
|
||||||
Certificate
|
|
||||||
Store string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certificate is a struct which contains all data needed from an ACME certificate.
|
|
||||||
type Certificate struct {
|
|
||||||
Domain Domain `json:"domain,omitempty" toml:"domain,omitempty" yaml:"domain,omitempty"`
|
|
||||||
Certificate []byte `json:"certificate,omitempty" toml:"certificate,omitempty" yaml:"certificate,omitempty"`
|
|
||||||
Key []byte `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domain holds a domain name with SANs.
|
|
||||||
type Domain struct {
|
|
||||||
// Main defines the main domain name.
|
|
||||||
Main string `description:"Default subject name." json:"main,omitempty" toml:"main,omitempty" yaml:"main,omitempty"`
|
|
||||||
// SANs defines the subject alternative domain names.
|
|
||||||
SANs []string `description:"Subject alternative names." json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToStrArray convert a domain into an array of strings.
|
|
||||||
func (d *Domain) ToStrArray() []string {
|
|
||||||
var domains []string
|
|
||||||
if d.Main != "" {
|
|
||||||
domains = []string{d.Main}
|
|
||||||
}
|
|
||||||
return append(domains, d.SANs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets a domains from an array of strings.
|
|
||||||
func (d *Domain) Set(domains []string) {
|
|
||||||
if len(domains) > 0 {
|
|
||||||
d.Main = domains[0]
|
|
||||||
d.SANs = domains[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (d *Domain) DeepCopyInto(out *Domain) {
|
|
||||||
*out = *d
|
|
||||||
if d.SANs != nil {
|
|
||||||
in, out := &d.SANs, &out.SANs
|
|
||||||
*out = make([]string, len(*in))
|
|
||||||
copy(*out, *in)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Domain.
|
|
||||||
func (d *Domain) DeepCopy() *Domain {
|
|
||||||
if d == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(Domain)
|
|
||||||
d.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
package traefikv3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/registration"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StoredData represents the data managed by Store.
|
|
||||||
type StoredData struct {
|
|
||||||
Account *Account
|
|
||||||
Certificates []*CertAndStore
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account is used to store lets encrypt registration info.
|
|
||||||
type Account struct {
|
|
||||||
Email string
|
|
||||||
Registration *registration.Resource
|
|
||||||
PrivateKey []byte
|
|
||||||
KeyType certcrypto.KeyType
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEmail returns email.
|
|
||||||
func (a *Account) GetEmail() string {
|
|
||||||
return a.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRegistration returns lets encrypt registration resource.
|
|
||||||
func (a *Account) GetRegistration() *registration.Resource {
|
|
||||||
return a.Registration
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPrivateKey returns private key.
|
|
||||||
func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
|
||||||
privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return privateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// CertAndStore allows mapping a TLS certificate to a TLS store.
|
|
||||||
type CertAndStore struct {
|
|
||||||
Certificate
|
|
||||||
Store string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certificate is a struct which contains all data needed from an ACME certificate.
|
|
||||||
type Certificate struct {
|
|
||||||
Domain Domain `json:"domain,omitempty" toml:"domain,omitempty" yaml:"domain,omitempty"`
|
|
||||||
Certificate []byte `json:"certificate,omitempty" toml:"certificate,omitempty" yaml:"certificate,omitempty"`
|
|
||||||
Key []byte `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Domain holds a domain name with SANs.
|
|
||||||
type Domain struct {
|
|
||||||
// Main defines the main domain name.
|
|
||||||
Main string `description:"Default subject name." json:"main,omitempty" toml:"main,omitempty" yaml:"main,omitempty"`
|
|
||||||
// SANs defines the subject alternative domain names.
|
|
||||||
SANs []string `description:"Subject alternative names." json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToStrArray convert a domain into an array of strings.
|
|
||||||
func (d *Domain) ToStrArray() []string {
|
|
||||||
var domains []string
|
|
||||||
if d.Main != "" {
|
|
||||||
domains = []string{d.Main}
|
|
||||||
}
|
|
||||||
return append(domains, d.SANs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets a domains from an array of strings.
|
|
||||||
func (d *Domain) Set(domains []string) {
|
|
||||||
if len(domains) > 0 {
|
|
||||||
d.Main = domains[0]
|
|
||||||
d.SANs = domains[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (d *Domain) DeepCopyInto(out *Domain) {
|
|
||||||
*out = *d
|
|
||||||
if d.SANs != nil {
|
|
||||||
in, out := &d.SANs, &out.SANs
|
|
||||||
*out = make([]string, len(*in))
|
|
||||||
copy(*out, *in)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Domain.
|
|
||||||
func (d *Domain) DeepCopy() *Domain {
|
|
||||||
if d == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(Domain)
|
|
||||||
d.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
56
readme.md
56
readme.md
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://github.com/ldez/traefik-certs-dumper/releases/latest)
|
[](https://github.com/ldez/traefik-certs-dumper/releases/latest)
|
||||||
[](https://github.com/ldez/traefik-certs-dumper/actions)
|
[](https://github.com/ldez/traefik-certs-dumper/actions)
|
||||||
[](https://hub.docker.com/r/ldez/traefik-certs-dumper/)
|
[](https://hub.docker.com/r/ldez/traefik-certs-dumper/)
|
||||||
[](https://goreportcard.com/report/github.com/ldez/traefik-certs-dumper)
|
[](https://goreportcard.com/report/github.com/ldez/traefik-certs-dumper)
|
||||||
|
|
||||||
If you appreciate this project:
|
If you appreciate this project:
|
||||||
@ -18,17 +18,16 @@ If you appreciate this project:
|
|||||||
- from file ("acme.json")
|
- from file ("acme.json")
|
||||||
- from KV stores (Consul, Etcd, Zookeeper)
|
- from KV stores (Consul, Etcd, Zookeeper)
|
||||||
- Output formats:
|
- Output formats:
|
||||||
- use domain as subdirectory (allow custom names and extensions)
|
- use domain as sub-directory (allow custom names and extensions)
|
||||||
- flat (domain as filename)
|
- flat (domain as filename)
|
||||||
- Hook (only with watch mode and if the data source changes)
|
- Hook (only with watch mode and if the data source changes)
|
||||||
- Support Traefik v1, v2, and v3.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Download / CI Integration
|
### Download / CI Integration
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sfL https://raw.githubusercontent.com/ldez/traefik-certs-dumper/master/godownloader.sh | bash -s -- -b $(go env GOPATH)/bin v2.9.3
|
curl -sfL https://raw.githubusercontent.com/ldez/traefik-certs-dumper/master/godownloader.sh | bash -s -- -b $GOPATH/bin v2.7.0
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@ -57,11 +56,7 @@ You can use pre-compiled binaries:
|
|||||||
docker run ldez/traefik-certs-dumper:<tag_name>
|
docker run ldez/traefik-certs-dumper:<tag_name>
|
||||||
```
|
```
|
||||||
|
|
||||||
Examples:
|
Example: [docker-compose](docs/docker-compose-traefik-v1.yml)
|
||||||
|
|
||||||
- Traefik v1: [docker-compose](docs/docker-compose-traefik-v1.yml)
|
|
||||||
- Traefik v2: [docker-compose](docs/docker-compose-traefik-v2.yml)
|
|
||||||
- Traefik v3: TODO
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -71,10 +66,12 @@ Examples:
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
**Note:** to dump data from Traefik v2, the CLI flag `--version v2` must be added.
|
||||||
|
|
||||||
### Simple Dump
|
### Simple Dump
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ traefik-certs-dumper file --version v3
|
$ traefik-certs-dumper file
|
||||||
dump
|
dump
|
||||||
├──certs
|
├──certs
|
||||||
│ └──my.domain.com.key
|
│ └──my.domain.com.key
|
||||||
@ -86,7 +83,7 @@ dump
|
|||||||
### Change source and destination
|
### Change source and destination
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ traefik-certs-dumper file --version v3 --source ./acme.json --dest ./dump/test
|
$ traefik-certs-dumper file --source ./acme.json --dest ./dump/test
|
||||||
test
|
test
|
||||||
├──certs
|
├──certs
|
||||||
│ └──my.domain.com.key
|
│ └──my.domain.com.key
|
||||||
@ -98,7 +95,7 @@ test
|
|||||||
### Use domain as sub-directory
|
### Use domain as sub-directory
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ traefik-certs-dumper file --version v3 --domain-subdir=true
|
$ traefik-certs-dumper file --domain-subdir=true
|
||||||
dump
|
dump
|
||||||
├──my.domain.com
|
├──my.domain.com
|
||||||
│ ├──certificate.crt
|
│ ├──certificate.crt
|
||||||
@ -110,7 +107,7 @@ dump
|
|||||||
#### Change file extension
|
#### Change file extension
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ traefik-certs-dumper file --version v3 --domain-subdir --crt-ext=.pem --key-ext=.pem
|
$ traefik-certs-dumper file --domain-subdir --crt-ext=.pem --key-ext=.pem
|
||||||
dump
|
dump
|
||||||
├──my.domain.com
|
├──my.domain.com
|
||||||
│ ├──certificate.pem
|
│ ├──certificate.pem
|
||||||
@ -122,7 +119,7 @@ dump
|
|||||||
#### Change file name
|
#### Change file name
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ traefik-certs-dumper file --version v3 --domain-subdir --crt-name=fullchain --key-name=privkey
|
$ traefik-certs-dumper file --domain-subdir --crt-name=fullchain --key-name=privkey
|
||||||
dump
|
dump
|
||||||
├──my.domain.com
|
├──my.domain.com
|
||||||
│ ├──fullchain.crt
|
│ ├──fullchain.crt
|
||||||
@ -131,37 +128,6 @@ dump
|
|||||||
└──letsencrypt.key
|
└──letsencrypt.key
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hook
|
|
||||||
|
|
||||||
Hook can be a one-liner passed as a string, or a file for more complex post-hook scenarios.
|
|
||||||
For the former, create a file (ex: `hook.sh`) and mount it, then pass `sh hooksh` as a parameter to `--post-hook`.
|
|
||||||
|
|
||||||
Here is a docker-compose example:
|
|
||||||
|
|
||||||
```yml
|
|
||||||
services:
|
|
||||||
# ...
|
|
||||||
|
|
||||||
traefik-certs-dumper:
|
|
||||||
image: ldez/traefik-certs-dumper:v2.9.3
|
|
||||||
container_name: traefik-certs-dumper
|
|
||||||
entrypoint: sh -c '
|
|
||||||
while ! [ -e /data/acme.json ]
|
|
||||||
|| ! [ `jq ".[] | .Certificates | length" /data/acme.json | jq -s "add" ` != 0 ]; do
|
|
||||||
sleep 1
|
|
||||||
; done
|
|
||||||
&& traefik-certs-dumper file --version v2 --watch
|
|
||||||
--source /data/acme.json --dest /data/certs
|
|
||||||
--post-hook "sh /hook.sh"'
|
|
||||||
labels:
|
|
||||||
traefik.enable: false
|
|
||||||
volumes:
|
|
||||||
- ./letsencrypt:/data
|
|
||||||
- ./hook.sh:/hook.sh
|
|
||||||
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### KV store
|
### KV store
|
||||||
|
|
||||||
#### Consul
|
#### Consul
|
||||||
|
|||||||
24
tmpl.Dockerfile
Normal file
24
tmpl.Dockerfile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
FROM golang:1-alpine as builder
|
||||||
|
|
||||||
|
RUN apk --update upgrade \
|
||||||
|
&& apk --no-cache --no-progress add git make gcc musl-dev ca-certificates tzdata
|
||||||
|
|
||||||
|
WORKDIR /go/src/github.com/ldez/traefik-certs-dumper
|
||||||
|
|
||||||
|
ENV GO111MODULE on
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN GOARCH={{ .GoARCH }} GOARM={{ .GoARM }} make build
|
||||||
|
|
||||||
|
FROM {{ .RuntimeImage }}
|
||||||
|
|
||||||
|
# Not supported for multi-arch without Buildkit or QEMU
|
||||||
|
#RUN apk --update upgrade \
|
||||||
|
# && apk --no-cache --no-progress add ca-certificates
|
||||||
|
|
||||||
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=builder /go/src/github.com/ldez/traefik-certs-dumper/traefik-certs-dumper /usr/bin/traefik-certs-dumper
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/traefik-certs-dumper"]
|
||||||
Loading…
Reference in New Issue
Block a user