Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add acme-proxy provider #708

Closed
wants to merge 1 commit into from
Closed

Add acme-proxy provider #708

wants to merge 1 commit into from

Conversation

mdbraber
Copy link
Contributor

@mdbraber mdbraber commented Nov 8, 2018

In my effort to centralize certificate requests for hosts in my network (and not having to spread out credentials) I've written an acme-proxy provider to be used with http://github.com/mdbraber/acme-proxy. It works fine (see below) and the nice thing is because it plugs in to lego, you can simply use any provider that lego offers.

I'm submitting this PR to get comments on tips for the suggested implementation. I'm not sure what's the best way to do this, as I had to change some imports to work with my local branch that obviously shouldn't be in the PR.

Running the proxy:

$ ACMEPROXY_PROVIDER="transip" TRANSIP_ACCOUNT_NAME="mdbraber" TRANSIP_PRIVATE_KEY_PATH="/Users/mdbraber/transip.key" go run acme-proxy.go

Requesting a certificate:

$ ACMEPROXY_URL="http://127.0.0.1:9095/" ./lego -m m@mdbraber.com -a -x http-01 -x tls-alpn-01 --dns acme-proxy --dns-resolvers ns0.transip.nl -s https://acme-staging-v02.api.letsencrypt.org/directory -d mdbraber.net -d *.mdbraber.net run
2018/11/08 10:11:38 [INFO] acme: Registering account for m@mdbraber.com
2018/11/08 10:11:38 !!!! HEADS UP !!!!
2018/11/08 10:11:38
		Your account credentials have been saved in your Let's Encrypt
		configuration directory at "/Users/mdbraber/go/src/github.com/mdbraber/lego/.lego/accounts/acme-staging-v02.api.letsencrypt.org/m@mdbraber.com".
		You should make a secure backup	of this folder now. This
		configuration directory will also contain certificates and
		private keys obtained from Let's Encrypt so making regular
		backups of this folder is ideal.
2018/11/08 10:11:38 [INFO] [mdbraber.net, *.mdbraber.net] acme: Obtaining bundled SAN certificate
2018/11/08 10:11:39 [INFO] [*.mdbraber.net] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/lBFxlzA3lbOJ8a7cAmIv-vP-Qe1OU4ZSR_q4tmD5Af4
2018/11/08 10:11:39 [INFO] [mdbraber.net] AuthURL: https://acme-staging-v02.api.letsencrypt.org/acme/authz/RY19RDOYqi2UbTBqmU5FmU1ZVx5FT7kP1xsO5dkodIc
2018/11/08 10:11:39 [INFO] [mdbraber.net] acme: Could not find solver for: tls-alpn-01
2018/11/08 10:11:39 [INFO] [mdbraber.net] acme: Could not find solver for: http-01
2018/11/08 10:11:39 [INFO] [mdbraber.net] acme: Preparing to solve DNS-01
2018/11/08 10:11:41 [INFO] [mdbraber.net] acme: Preparing to solve DNS-01
2018/11/08 10:11:43 [INFO] [mdbraber.net] acme: Trying to solve DNS-01
2018/11/08 10:11:43 [INFO] [mdbraber.net] Checking DNS record propagation using [ns0.transip.nl:53]
2018/11/08 10:11:43 [INFO] Wait [timeout: 10m0s, interval: 10s]
2018/11/08 10:14:34 [INFO] [mdbraber.net] The server validated our request
2018/11/08 10:14:34 [INFO] [mdbraber.net] acme: Trying to solve DNS-01
2018/11/08 10:14:34 [INFO] [mdbraber.net] Checking DNS record propagation using [ns0.transip.nl:53]
2018/11/08 10:14:34 [INFO] Wait [timeout: 10m0s, interval: 10s]
2018/11/08 10:14:40 [INFO] [mdbraber.net] The server validated our request
2018/11/08 10:14:42 [INFO] [mdbraber.net, *.mdbraber.net] acme: Validations succeeded; requesting certificates
2018/11/08 10:14:44 [INFO] [mdbraber.net] Server responded with a certificate.

@ldez
Copy link
Member

ldez commented Nov 8, 2018

Please don't use your master to open PR.

@ldez
Copy link
Member

ldez commented Nov 8, 2018

For me the provider exec already do what you want.

@mdbraber
Copy link
Contributor Author

mdbraber commented Nov 8, 2018

Please don't use your master to open PR.

Should I best create a branch? (sorry, I'm not well-versed yet in git)

For me the provider exec already do what you want.

Hmm, I didn't notice that one before. Indeed it does. Although I think an integration with lego for this specific purpose seem logical. Would you be willing to consider this?

@ldez
Copy link
Member

ldez commented Nov 8, 2018

You can also look at the acme-dns provider https://github.com/joohoi/acme-dns

@ldez
Copy link
Member

ldez commented Nov 8, 2018

More information about the exec provider: https://github.com/xenolf/lego/blob/master/providers/dns/exec/doc.go


When you want to open a PR, I recommend that you create a dedicated branch.

@ldez
Copy link
Member

ldez commented Nov 8, 2018

I rebased your branch and clean your code (amend).

To open a PR:

  • Lego is in Go, so you have to clone the project into a directory that have the following path
    • $GOPATH/src/github.com/xenolf
    • as result you will have a directory $GOPATH/src/github.com/xenolf/lego
  • create a dedicated branch (base on the current master of Lego)
  • your code must pass linters (so use gofmt to format your code)
    • make checks
  • your code must be tested:
    • make test
  • your code have to compile:
    • make build
# Create the root folder
mkdir $GOPATH/src/github.com/xenolf
cd $GOPATH/src/github.com/xenolf

# clone your fork
git clone git@github.com:yourusername/lego.git
cd lego

# Add the xenolf/lego remote
git remote add upstream git@github.com:xenolf/lego.git
git fetch upstream

# Create your branch
git checkout -b my-feature

## Create your code ##

# Linters 
make checks
# Tests
make test
# Complle
make build

# push your branch
git push -u origin my-feature

# create a pull request

@ldez
Copy link
Member

ldez commented Nov 8, 2018

I read the code of your acme-proxy and you can write something like that:

refactor
package main

import (
	"encoding/json"
	"log"
	"net"
	"net/http"
	"strconv"

	"github.com/xenolf/lego/platform/config/env"
	"github.com/xenolf/lego/providers/dns"
)

type Message struct {
	Domain  string `json:"domain"`
	Token   string `json:"token"`
	KeyAuth string `json:"keyAuth"`
}

type Config struct {
	Host     string
	Port     int
	Provider string
}

func NewDefaultConfig() *Config {
	return &Config{
		Host: env.GetOrDefaultString("ACMEPROXY_HOST", "127.0.0.1"),
		Port: env.GetOrDefaultInt("ACMEPROXY_PORT", 9095),
	}
}

func main() {
	values, err := env.Get("ACMEPROXY_PROVIDER")
	if err != nil {
		panic(err)
	}

	config := NewDefaultConfig()
	config.Provider = values["ACMEPROXY_PROVIDER"]

	mux := http.NewServeMux()

	mux.HandleFunc("/present", func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPost {
			http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
			return
		}

		msg := &Message{}
		err := json.NewDecoder(req.Body).Decode(msg)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}

		provider, err := dns.NewDNSChallengeProviderByName(config.Provider)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}

		err = provider.Present(msg.Domain, msg.Token, msg.KeyAuth)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	mux.HandleFunc("/cleanup", func(rw http.ResponseWriter, req *http.Request) {
		if req.Method != http.MethodPost {
			http.Error(rw, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
			return
		}

		msg := &Message{}
		err := json.NewDecoder(req.Body).Decode(msg)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}

		provider, err := dns.NewDNSChallengeProviderByName(config.Provider)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusBadRequest)
			return
		}

		err = provider.CleanUp(msg.Domain, msg.Token, msg.KeyAuth)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
			return
		}
	})

	log.Fatal(http.ListenAndServe(net.JoinHostPort(config.Host, strconv.Itoa(config.Port)), mux))
}

But I still think that what you are trying to do can be obtained by either the exec provider or the acme-dns provider.

simple exec example
#!/usr/bin/env bash
# Simple DNS challenge exec solver.

set -e

case "$1" in
  "present")
    echo  "Present"
    payload="{\"host\":\"$2\", \"value\":\"$3\"}"
    echo "payload=${payload}"
    curl -s -X POST -d "${payload}" http://my-host:8055/present
    ;;
  "cleanup")
    echo  "cleanup"
    payload="{\"host\":\"$2\"}"
    echo "payload=${payload}"
    curl -s -X POST -d "${payload}" http://my-host:8055/cleanup
    ;;
  *)
    echo "OOPS"
    ;;
esac

@mdbraber
Copy link
Contributor Author

mdbraber commented Nov 8, 2018

@ldez thanks so much for these very detailed instructions. This helps me a lot making better code and PR. I'm amazed at the speed and details you can refactor things like this. Are you doing this by hand or is it a combination of linting and other tools?

It would be good to maybe add this to the documentation / wiki to show other developers the best way to suggest changes to the code (or this is just known by everyone and I'm the one that's lagging ;-)

You can also look at the acme-dns provider https://github.com/joohoi/acme-dns

I did and I even wrote a functional CNAME record updater. See the discussion here: joohoi/acme-dns#126. But practically it's overload as I don't necessarily need the acme-dns method, "just" a proxy would be enough as I do have an API + credentials but I don't want the credentials to be on all the hosts requesting certificates.

But I still think that what you are trying to do can be obtained by either the exec provider or the acme-dns provider.

I'm thinking about this and technically you're absolutely right. I've started this to be able to integrate this "proxy" functionality into Caddy in the end (which integrates directly with lego). To have this contained in a single binary would be the preferred way. Also this would need two extra dependencies: curl and a separate bash script (very possible, but if you'd be willing to consider this I'd be very happy as I think it's a relevant option)

mdbraber referenced this pull request in mdbraber/acmeproxy Nov 8, 2018
@mdbraber
Copy link
Contributor Author

mdbraber commented Nov 8, 2018

@ldez I've created a new branch with updated code for the acme-proxy provider. Should I create a new PR and close this one? https://github.com/mdbraber/lego/tree/add-acmeproxy-provider

@ldez
Copy link
Member

ldez commented Nov 8, 2018

After further thoughts, I understand your need but I want to implement it differently.
Could you authorize me to create a new PR based on your idea?

@mdbraber
Copy link
Contributor Author

mdbraber commented Nov 8, 2018

@ldez thanks - I'd be happy to see your implementation. Do I need to do something specific or do you just want to create your own PR based on this idea (which is fine)?

@ldez
Copy link
Member

ldez commented Nov 8, 2018

I will create my own PR based on your idea.
I close this one, thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

None yet

2 participants