Skip to content
This repository has been archived by the owner on May 26, 2024. It is now read-only.

Working raw mode and acme/v3 #16

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
certmagic/
traefik/
config.yml
docker-compose.yml
.git/
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@
cover.out
dist/
builds/
certmagic/
traefik/
docker-compose.yml
config.yml
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
FROM golang:1.13.4-alpine3.10 as builder
FROM golang:1.21-alpine as builder

RUN apk --update upgrade \
&& apk --no-cache --no-progress add make git \
&& rm -rf /var/cache/apk/*
&& apk --no-cache --no-progress add make git \
&& rm -rf /var/cache/apk/*

WORKDIR /go/src/github.com/mdbraber/acmeproxy
COPY . .
RUN make build

FROM alpine:3.8
FROM alpine
RUN apk update && apk add --no-cache --virtual ca-certificates
COPY --from=builder /go/src/github.com/mdbraber/acmeproxy/dist/acmeproxy /usr/bin/acmeproxy
ENTRYPOINT [ "/usr/bin/acmeproxy" ]
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ Use the makefile to `make` the executables. Use `make install` to also install t

If you want to build a Debian package / installer, use `dch` to update the changelog and create your own package using `make debian`.


## Running in Docker
An example docker-compose file is provided with a method for serving it's own ssl or sitting behind traefik

- Copy docker-compose-example.yml to docker-compose.yml and adjust hostnames/settings.
- Copy config-example.yml to config.yml and adjust your settings
- Use docker-compose up -d to start
- Use docker-compose logs t0 view logs

# Configure

## Adjust configuration file
Expand Down
207 changes: 125 additions & 82 deletions acmeproxy/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"strings"
"time"
"io"

auth "github.com/abbot/go-http-auth"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -89,6 +90,7 @@ func GetHandler(config *Config) http.Handler {
}

mux.Handle("/", HomeHandler())
mux.Handle("/health", HealthHandler())
mux.Handle("/present", handlerPresent)
mux.Handle("/cleanup", handlerCleanup)

Expand Down Expand Up @@ -117,6 +119,14 @@ func HomeHandler() http.Handler {

}

func HealthHandler() http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "OK")
})

}

func ActionHandler(action string, config *Config) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -150,14 +160,18 @@ func ActionHandler(action string, config *Config) http.Handler {
// See https://github.com/go-acme/lego/tree/master/providers/dns/httpreq
var mode string
var checkDomain string
if incoming.FQDN != "" && incoming.Value != "" {

var isModeDefault = incoming.FQDN != "" && incoming.Value != ""
var isModeRaw = incoming.Domain != "" && (incoming.Token != "" || incoming.KeyAuth != "")

if isModeDefault {
mode = ModeDefault
checkDomain = dns01.UnFqdn(strings.TrimPrefix(incoming.FQDN, "_acme-challenge."))
alog.WithFields(log.Fields{
"fqdn": incoming.FQDN,
"value": incoming.Value,
}).Debug("Received JSON payload (default mode)")
} else if incoming.Domain != "" && (incoming.Token != "" || incoming.KeyAuth != "") {
} else if isModeRaw {
mode = ModeRaw
checkDomain = incoming.Domain
alog.WithFields(log.Fields{
Expand Down Expand Up @@ -195,75 +209,124 @@ func ActionHandler(action string, config *Config) http.Handler {

// Check if this provider supports the selected mode
// We assume that all providers support MODE_RAW (which is lego default)
if mode == ModeDefault {
provider, ok := config.Provider.(providerSolved)
if ok {
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"mode": mode,
}).Debug("Provider supports requested mode")

if action == ActionPresent {
err = provider.CreateRecord(incoming.FQDN, incoming.Value)
} else if action == ActionCleanup {
err = provider.RemoveRecord(incoming.FQDN, incoming.Value)
switch mode {
case ModeDefault:
provider, ok := config.Provider.(providerSolved)
if ok {
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"mode": mode,
}).Debug("Provider supports requested mode")

switch action {
case ActionPresent:
err = provider.CreateRecord(incoming.FQDN, incoming.Value)

case ActionCleanup:
err = provider.RemoveRecord(incoming.FQDN, incoming.Value)

default:
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"fqdn": incoming.FQDN,
"value": incoming.Value,
"mode": mode,
"error": err.Error(),
}).Error("Wrong action specified")
http.Error(w, "Wrong action specified", http.StatusInternalServerError)
return

}

if err != nil {
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"fqdn": incoming.FQDN,
"value": incoming.Value,
"mode": mode,
"error": err.Error(),
}).Error("Failed to update TXT record")
http.Error(w, "Failed to update TXT record", http.StatusInternalServerError)
return
}
} else {
http.Error(w, "Provider does not support requested mode", http.StatusInternalServerError)
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"fqdn": incoming.FQDN,
"value": incoming.Value,
"mode": mode,
"error": err.Error(),
}).Error("Wrong action specified")
http.Error(w, "Wrong action specified", http.StatusInternalServerError)
}).Debug("Provider does not support requested mode")
return
}

// Send back the original JSON to confirm success
m := messageDefault{FQDN: incoming.FQDN, Value: incoming.Value}
w.Header().Set("Content-Type", "application/json")
returnErr := json.NewEncoder(w).Encode(m)
if returnErr != nil {
log.Error("Problem encoding return message")
}

// Succes!
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"fqdn": incoming.FQDN,
"value": incoming.Value,
"mode": mode,
}).Info("Sucessfully updated TXT record")
// All lego providers should support raw mode

case ModeRaw:
fqdn, value := dns01.GetRecord(incoming.Domain, incoming.KeyAuth)
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"mode": mode,
}).Debug("Provider supports requested mode")
provider := config.Provider

// Run action
switch action {

case ActionPresent:
err = provider.Present(incoming.Domain, incoming.Token, incoming.KeyAuth)

case ActionCleanup:
err = provider.CleanUp(incoming.Domain, incoming.Token, incoming.KeyAuth)

default:
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"fqdn": incoming.FQDN,
"value": incoming.Value,
"mode": mode,
"error": err.Error(),
}).Error("Wrong action specified")
http.Error(w, "Wrong action specified", http.StatusInternalServerError)
return
}

if err != nil {
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"fqdn": incoming.FQDN,
"value": incoming.Value,
"domain": incoming.Domain,
"fqdn": fqdn,
"token": incoming.Token,
"keyAuth": incoming.KeyAuth,
"value": value,
"mode": mode,
"error": err.Error(),
}).Error("Failed to update TXT record")
http.Error(w, "Failed to update TXT record", http.StatusInternalServerError)
return
}
} else {
http.Error(w, "Provider does not support requested mode", http.StatusInternalServerError)
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"mode": mode,
}).Debug("Provider does not support requested mode")
return
}

// Send back the original JSON to confirm success
m := messageDefault{FQDN: incoming.FQDN, Value: incoming.Value}
w.Header().Set("Content-Type", "application/json")
returnErr := json.NewEncoder(w).Encode(m)
if returnErr != nil {
log.Error("Problem encoding return message")
}
// Send back the original JSON to confirm success
m := messageRaw{Domain: incoming.Domain, Token: incoming.Token, KeyAuth: incoming.KeyAuth}
w.Header().Set("Content-Type", "application/json")
returnErr := json.NewEncoder(w).Encode(m)
if returnErr != nil {
log.Error("Problem encoding return message")
}

// Succes!
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"fqdn": incoming.FQDN,
"value": incoming.Value,
"mode": mode,
}).Info("Sucessfully updated TXT record")
// All lego providers should support raw mode
} else if mode == ModeRaw {
fqdn, value := dns01.GetRecord(incoming.Domain, incoming.KeyAuth)
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"mode": mode,
}).Debug("Provider supports requested mode")
provider := config.Provider
err = provider.Present(incoming.Domain, incoming.Token, incoming.KeyAuth)
if err != nil {
// Success!
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"domain": incoming.Domain,
Expand All @@ -272,35 +335,15 @@ func ActionHandler(action string, config *Config) http.Handler {
"keyAuth": incoming.KeyAuth,
"value": value,
"mode": mode,
}).Error("Failed to update TXT record")
http.Error(w, "Failed to update TXT record", http.StatusInternalServerError)
return
}
// Send back the original JSON to confirm success
m := messageRaw{Domain: incoming.Domain, Token: incoming.Token, KeyAuth: incoming.KeyAuth}
w.Header().Set("Content-Type", "application/json")
returnErr := json.NewEncoder(w).Encode(m)
if returnErr != nil {
log.Error("Problem encoding return message")
}
}).Info("Sucessfully updated TXT record")

// Succes!
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"domain": incoming.Domain,
"fqdn": fqdn,
"token": incoming.Token,
"keyAuth": incoming.KeyAuth,
"value": value,
"mode": mode,
}).Info("Sucessfully updated TXT record")
} else {
http.Error(w, "Unkown mode requested", http.StatusInternalServerError)
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"mode": mode,
}).Info("Unknown mode requested")
return
default:
http.Error(w, "Unkown mode requested", http.StatusInternalServerError)
alog.WithFields(log.Fields{
"provider": config.ProviderName,
"mode": mode,
}).Info("Unknown mode requested")
return
}

})
Expand Down
File renamed without changes.
66 changes: 66 additions & 0 deletions docker-compose-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
version: '3.2'

services:
acmeproxy:
build:
context: .
dockerfile: Dockerfile
container_name: ACMEDNSProxy
restart: unless-stopped
network_mode: bridge
volumes:
- ./config.yml:/etc/acmeproxy/config.yml
- ./certmagic:/etc/acmeproxy/certmagic
environment: # Set variables for your dns provider here or config.yml
- 'CF_API_KEY=xxxxxxx'
ports:
- 9096:9096
# To enable traefik proxy uncomment the below and set the Host to be the domain to access acmeproxy, also comment out the ports above and comment out ssl: in config.yml
# This enables the labels for traefik to read and route to the container
# labels:
# - 'traefik.enable=true'
# - 'traefik.http.routers.acmeproxy.rule=Host(`acmeproxy.example.com`)'
# - 'traefik.http.services.acmeproxy.loadbalancer.server.port=9096'
# - 'traefik.http.routers.acmeproxy.service=acmeproxy'
# - 'traefik.http.routers.acmeproxy.tls=true'
# - 'traefik.http.routers.acmeproxy.tls.certresolver=letsencrypt'

# traefik:
# image: traefik
# container_name: traefik
# restart: unless-stopped
# volumes:
# - ./traefik:/etc/traefik
# - /var/run/docker.sock:/var/run/docker.sock
# labels:
# - 'traefik.enable=true'
# - 'traefik.http.routers.api.rule=Host(`traefik.example.com`)' # Domain used to access traefik status panel
# - 'traefik.http.routers.api.entrypoints=https'
# - 'traefik.http.routers.api.service=api@internal'
# - 'traefik.http.routers.api.tls=true'
# - 'traefik.http.routers.api.tls.certresolver=letsencrypt'
# ports:
# - '80:80' # External http
# - '443:443' # External https
# network_mode: bridge
# links:
# - acmeproxy:acmeproxy
# command:
# - '--api'
# - '--providers.docker=true'
# - '--providers.docker.exposedByDefault=false'
# - '--entrypoints.http=true'
# - '--entrypoints.http.address=:80'
# - '--entrypoints.http.http.redirections.entrypoint.to=https'
# - '--entrypoints.http.http.redirections.entrypoint.scheme=https'
# - '--entrypoints.https=true'
# - '--entrypoints.https.address=:443'
# - '--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=httpreq'
# - '--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory' # Staging
# # - '--certificatesresolvers.letsencrypt.acme.caserver=https://acme-v02.api.letsencrypt.org/directory' # Production
# - '--log=true'
# - '--log.level=DEBUG'
# # - '--accesslog'
# environment:
# - "HTTPREQ_ENDPOINT=http://acmeproxy:9096" #When replicating this on other servers use https://acmeproxy.example.com instead
# - "HTTPREQ_MODE=RAW"
Loading