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

extend cli with server and client command #1

Merged
merged 5 commits into from
Sep 30, 2018
Merged
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
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ WORKDIR /root/
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
COPY --from=builder /go/src/github.com/project0/certjunkie/certjunkie .

ENTRYPOINT ["./certjunkie"]
ENTRYPOINT ["./certjunkie"]

CMD [ "server" ]
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# CertJunkie

This project is inspired by [acme-dns](https://github.com/joohoi/acme-dns). While acme-dns is awesome to use with other acme clients, it lacks of capabilities of shared certs and anonymous usage.

I want to have a simple http server to create, challenge and receive my (lets encrypt) certs from an central point.
Expand All @@ -7,6 +8,7 @@ As it is intended to be used within an private and closed context, optional auth
## Usage

```
server
--dns.domain string The NS domain name of this server (default "ns.local")
--dns.listen string Bind on this port to run the DNS server on (tcp and udp) (default ":53")
--dns.zone string The zone we are using to provide the txt records for challenge (default "acme.local")
Expand All @@ -22,27 +24,43 @@ As it is intended to be used within an private and closed context, optional auth
For combatible dns provdider look at https://github.com/xenolf/lego/tree/master/providers/dns

### Docker

[Image DockerHub](https://hub.docker.com/r/project0de/certjunkie)

```bash
docker run -ti -p 80:80 -p 53:53 -p 53:53/udp \
-v $(pwd)/certjunkie:/storage project0de/certjunkie \
--storage.local /storage --email your@domain.com --dns.zone certjunkie.domain.com --dns.domain thisserver.domain.com
server --storage.local /storage --email your@domain.com --dns.zone certjunkie.domain.com --dns.domain thisserver.domain.com
```

### Client

certjunkie has a built in client to write certificate easy to file.

```bash
certjunkie client --address "http://localhost:8080" --domain "my.domain.de" \
--file.cert my.domain.de.crt \
--file.key my.domain.de.key \
--file.ca my.domain.de.ca \
--file.bundle my.domain.de.bundle
```

### Client example with curl

```bash
curl http://localhost:8080/cert/my.domain.de/cert -Fo my.domain.de.crt && \
curl http://localhost:8080/cert/my.domain.de/key -Fo my.domain.de.key && \
curl http://localhost:8080/cert/my.domain.de/ca -Fo my.domain.de.ca
```

## `dnscname` DNS redirect with CNAME

This is actually `$challengeDomain.$dnsDomain.`.
Ensure the NS record is set to this server

### Example
Asume starting with `certjunkie --dns.domain certjunkiens.example.com --dns.zone certjunkie.example.com --email your@registration.mail`

Asume starting with `certjunkie server --dns.domain certjunkiens.example.com --dns.zone certjunkie.example.com --email your@registration.mail`

1. Delegate a subdomain to the server running certbot on your remote hosted DNS `example.com`:
```
Expand All @@ -60,6 +78,7 @@ _acme-challenge.service.cloud.yourdomain.com CNAME service.cloud.yourdomain.com
```

## API

* `domain`: Get an cert which matches this domain.

### GET /cert/{domain}
Expand All @@ -68,18 +87,23 @@ Get JSON of an cert with CA and key
If the cert does not exist (or is not valid anymore) it will request a new one (sync).

#### Optional query parameters

* `san`: Comma separated list of subject alternative names the cert must have.
* `onlycn`: Get only a cert which matches the CommonName
* `valid`: How long needs the cert to be valid in days before requesting a new one. Defaults to 30

### GET /cert/{domain}/cert

Retrieve only the certificate pem encoded.

### GET /cert/{domain}/ca

Retrieve only the Issuer Certificate (CA) pem encoded.

### GET /cert/{domain}/bundle

Retrieve bundled cert with ca pem encoded.

### GET /cert/{domain}/key

Retrieve the private key pem encoded.
4 changes: 2 additions & 2 deletions api/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (a *apiCert) getCert(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(http.StatusOK)
w.Write(cert.Certificate)
w.Write(cert.GetNoBundleCertificate())
}

func (a *apiCert) getCA(w http.ResponseWriter, r *http.Request) {
Expand All @@ -98,5 +98,5 @@ func (a *apiCert) getBundle(w http.ResponseWriter, r *http.Request) {
return
}
w.WriteHeader(http.StatusOK)
w.Write(append(cert.Certificate, cert.IssuerCertificate...))
w.Write(append(cert.GetNoBundleCertificate(), cert.IssuerCertificate...))
}
84 changes: 84 additions & 0 deletions api/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package api

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"

"github.com/project0/certjunkie/certstore"
)

// Client talks with the API
type Client struct {
Address string
}

// Get retrieves the cert, private key and ca bundle
func (c *Client) Get(domain string, san []string, onlyCN bool, valid int) (cert *certstore.CertificateResource, err error) {

var (
resp *http.Response
u *url.URL
)
client := http.DefaultClient

u, err = url.Parse(c.Address + "/cert/" + domain)
if err != nil {
return
}

// Add queries
q := u.Query()
if onlyCN {
q.Set("onlycn", "1")
}
if valid != 0 {
q.Set("valid", strconv.Itoa(valid))
}
if len(san) > 0 {
q.Set("san", strings.Join(san, ","))
}
u.RawQuery = q.Encode()

resp, err = client.Get(u.String())
if err != nil {
return
}

if resp.StatusCode != http.StatusOK {
respBody, _ := ioutil.ReadAll(resp.Body)
return nil, fmt.Errorf("Failed to retrieve cert: %s", string(respBody))
}

cert = &certstore.CertificateResource{}
err = json.NewDecoder(resp.Body).Decode(cert)
return
}

// WriteCert writes the cert to file
func (c *Client) WriteCert(cert *certstore.CertificateResource, filepath string) (err error) {
return c.writeFile(cert.GetNoBundleCertificate(), filepath)
}

// WriteBundle writes the cert + ca to file
func (c *Client) WriteBundle(cert *certstore.CertificateResource, filepath string) (err error) {
return c.writeFile(append(cert.GetNoBundleCertificate(), cert.IssuerCertificate...), filepath)
}

// WriteKey writes the privte key to file
func (c *Client) WriteKey(cert *certstore.CertificateResource, filepath string) (err error) {
return c.writeFile(cert.PrivateKey, filepath)
}

// WriteCA writes the ca chain to file
func (c *Client) WriteCA(cert *certstore.CertificateResource, filepath string) (err error) {
return c.writeFile(cert.IssuerCertificate, filepath)
}

func (c *Client) writeFile(data []byte, filepath string) error {
return ioutil.WriteFile(filepath, data, 0644)
}
6 changes: 6 additions & 0 deletions certstore/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ func (c *CertificateResource) parseCert() (*x509.Certificate, error) {
block, _ := pem.Decode(c.Certificate)
return x509.ParseCertificate(block.Bytes)
}

// GetNoBundleCertificate ensures to return the cert without ca
func (c *CertificateResource) GetNoBundleCertificate() []byte {
block, _ := pem.Decode(c.Certificate)
return pem.EncodeToMemory(block)
}
Loading