Skip to content

Commit

Permalink
add fullchain. update build
Browse files Browse the repository at this point in the history
  • Loading branch information
nett00n committed Aug 25, 2023
1 parent 8430055 commit 79b97ab
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 127 deletions.
26 changes: 18 additions & 8 deletions .github/workflows/docker_push.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
---
name: "Push to Dockerhub"

on:
Expand All @@ -7,17 +6,28 @@ on:
- '*'
branches:
- "main"

jobs:
docker_push:
runs-on: ubuntu-latest
environment: docker-builder

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: |
- uses: actions/checkout@v2

- name: Set up QEMU
uses: docker/setup-qemu-action@v1

- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1

- name: Login to DockerHub
run: |
mkdir ~/.docker/ -p
echo '${{ secrets.DOCKER_CONFIG_JSON }}' > ~/.docker/config.json
- run: |
docker build . -t "nett00n/traefik-certs-exporter:${{ github.ref_name }}"
echo docker push "nett00n/traefik-certs-exporter:${{ github.ref_name }}"
- name: Build and push multi-arch images
run: |
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t "nett00n/traefik-certs-exporter:${{ github.ref_name }}" . --push
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1
## Build
FROM golang:1.19-alpine AS build
FROM golang:1.19 AS build
WORKDIR /usr/local/go/src/traefik-certs-exporter
COPY go.mod go.sum LICENSE README.md ./
RUN go get -u ./...
Expand Down
284 changes: 166 additions & 118 deletions traefik-certs-exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,147 +2,195 @@
package main

import (
"encoding/base64"
"encoding/json"
"flag"
"io/ioutil"
"log"
"os"
"path/filepath"
"time"

"github.com/fsnotify/fsnotify"
"encoding/base64"
"encoding/json"
"flag"
"io/ioutil"
"log"
"os"
"path/filepath"
"time"
"crypto/x509"

"github.com/fsnotify/fsnotify"
)

// ACME is an imported global traefik acme.json structure
type ACME struct {
Le struct {
Account Account
Certificates []Certificates
} `json:"le"`
Le struct {
Account Account
Certificates []Certificates
} `json:"le"`
}

// Account represents the account details in acme.json
type Account struct {
Email string
Email string
}

// Certificates list in acme.json
type Certificates struct {
Domain Domain
Certificate string `json:"certificate"`
Key string `json:"key"`
Domain Domain
Certificate string `json:"certificate"`
Key string `json:"key"`
}

// Domain represents the domain details in acme.json
type Domain struct {
Main string `json:"main"`
Main string `json:"main"`
}

func main() {
// Parse command-line arguments
acmeJSONPath := flag.String("acmejson", "input/acme.json", "Path to the acme.json file")
outputDir := flag.String("output", "output", "Path to the output directory")
serviceMode := flag.Bool("service", false, "Enable service mode to monitor acme.json for changes")

flag.Parse()

if *serviceMode {
// Run in service mode
runServiceMode(*acmeJSONPath, *outputDir)
} else {
// Run once
exportCertificates(*acmeJSONPath, *outputDir)
}
// Parse command-line arguments
acmeJSONPath := flag.String("acmejson", "input/acme.json", "Path to the acme.json file")
outputDir := flag.String("output", "output", "Path to the output directory")
serviceMode := flag.Bool("service", false, "Enable service mode to monitor acme.json for changes")

flag.Parse()

if *serviceMode {
// Run in service mode
runServiceMode(*acmeJSONPath, *outputDir)
} else {
// Run once
exportCertificates(*acmeJSONPath, *outputDir)
}
}

func exportCertificates(acmeJSONPath, outputDir string) {
// Open acme.json file
jsonFile, err := os.Open(acmeJSONPath)
if err != nil {
log.Fatal(err)
}
defer jsonFile.Close()

log.Println("Successfully Opened acme.json")

// Read JSON content
byteValue, err := ioutil.ReadAll(jsonFile)
if err != nil {
log.Fatal(err)
}

var traefik ACME
if err := json.Unmarshal(byteValue, &traefik); err != nil {
log.Fatal(err)
}

// Create output directory if not exists
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
log.Fatal(err)
}

for _, cert := range traefik.Le.Certificates {
domainDir := filepath.Join(outputDir, cert.Domain.Main)
if err := os.MkdirAll(domainDir, os.ModePerm); err != nil {
log.Fatal(err)
}

// Decode certificate and key
certBody, err := base64.StdEncoding.DecodeString(cert.Certificate)
if err != nil {
log.Println(err)
continue
}

certKey, err := base64.StdEncoding.DecodeString(cert.Key)
if err != nil {
log.Println(err)
continue
}

// Write certificate and key to files
certPath := filepath.Join(domainDir, "cert.pem")
if err := ioutil.WriteFile(certPath, append(certBody, []byte("\n")...), os.ModePerm); err != nil {
log.Println(err)
}

keyPath := filepath.Join(domainDir, "privkey.pem")
if err := ioutil.WriteFile(keyPath, append(certKey, []byte("\n")...), os.ModePerm); err != nil {
log.Println(err)
}

log.Println("Saved certificate and key for", cert.Domain.Main)
}
// Open acme.json file
jsonFile, err := os.Open(acmeJSONPath)
if err != nil {
log.Fatal(err)
}
defer jsonFile.Close()

log.Println("Successfully Opened acme.json")

// Read JSON content
byteValue, err := ioutil.ReadAll(jsonFile)
if err != nil {
log.Fatal(err)
}

var traefik ACME
if err := json.Unmarshal(byteValue, &traefik); err != nil {
log.Fatal(err)
}

// Create output directory if not exists
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
log.Fatal(err)
}

for _, cert := range traefik.Le.Certificates {
domainDir := filepath.Join(outputDir, cert.Domain.Main)
if err := os.MkdirAll(domainDir, os.ModePerm); err != nil {
log.Fatal(err)
}

// Decode certificate and key
certBody, err := base64.StdEncoding.DecodeString(cert.Certificate)
if err != nil {
log.Println(err)
continue
}

certKey, err := base64.StdEncoding.DecodeString(cert.Key)
if err != nil {
log.Println(err)
continue
}

// Write certificate and key to files
certPath := filepath.Join(domainDir, "cert.pem")
if err := ioutil.WriteFile(certPath, append(certBody, []byte("\n")...), os.ModePerm); err != nil {
log.Println(err)
}

// Build and write fullchain.pem
chain, err := buildFullChain(certBody, traefik.Le.Certificates)
if err != nil {
log.Println(err)
} else {
chainPath := filepath.Join(domainDir, "fullchain.pem")
if err := ioutil.WriteFile(chainPath, append(chain, []byte("\n")...), os.ModePerm); err != nil {
log.Println(err)
}
}

keyPath := filepath.Join(domainDir, "privkey.pem")
if err := ioutil.WriteFile(keyPath, append(certKey, []byte("\n")...), os.ModePerm); err != nil {
log.Println(err)
}

log.Println("Saved certificate and key for", cert.Domain.Main)
}
}


func buildFullChain(cert []byte, certificates []Certificates) ([]byte, error) {
certChain := append([]byte{}, cert...)

for {
var parentCert *Certificates
for _, c := range certificates {
if c.Domain.Main == certIssuer(certChain) {
parentCert = &c
break
}
}

if parentCert == nil {
break
}

certBody, err := base64.StdEncoding.DecodeString(parentCert.Certificate)
if err != nil {
return nil, err
}

certChain = append(certChain, certBody...)
}

return certChain, nil
}

func certIssuer(certPEM []byte) string {
cert, err := x509.ParseCertificate(certPEM)
if err != nil {
return ""
}
return cert.Issuer.CommonName
}

func runServiceMode(acmeJSONPath, outputDir string) {
exportCertificates(acmeJSONPath, outputDir)

watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()

err = watcher.Add(acmeJSONPath)
if err != nil {
log.Fatal(err)
}

log.Printf("Monitoring %s for changes...\n", acmeJSONPath)

for {
select {
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("acme.json has been modified. Exporting certificates...")
exportCertificates(acmeJSONPath, outputDir)
}
case err := <-watcher.Errors:
log.Println("Error:", err)
}
// Sleep for a moment to avoid high CPU usage in the loop
time.Sleep(1000 * time.Millisecond)
}
exportCertificates(acmeJSONPath, outputDir)

watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()

err = watcher.Add(acmeJSONPath)
if err != nil {
log.Fatal(err)
}

log.Printf("Monitoring %s for changes...\n", acmeJSONPath)

for {
select {
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("acme.json has been modified. Exporting certificates...")
exportCertificates(acmeJSONPath, outputDir)
}
case err := <-watcher.Errors:
log.Println("Error:", err)
}
// Sleep for a moment to avoid high CPU usage in the loop
time.Sleep(1000 * time.Millisecond)
}
}

0 comments on commit 79b97ab

Please sign in to comment.