summaryrefslogtreecommitdiff
path: root/modules/caddypki
diff options
context:
space:
mode:
authorMatthew Holt <mholt@users.noreply.github.com>2022-03-02 13:00:37 -0700
committerMatthew Holt <mholt@users.noreply.github.com>2022-03-02 13:00:37 -0700
commit78e381b29f64c07a0572a172ff5f89876e8d84db (patch)
treece6802c3728614b693be17aa40ae9695361f119f /modules/caddypki
parentde490c7cadc8c17759f747f5bb554967454c6529 (diff)
caddypki: Refactor /pki/ admin endpoints
Remove /pki/certificates/<ca> endpoint and split into two endpoints: - GET /pki/ca/<id> to get CA info and certs in JSON format - GET /pki/ca/<id>/certificates to get cert in PEM chain
Diffstat (limited to 'modules/caddypki')
-rw-r--r--modules/caddypki/adminpki.go189
-rw-r--r--modules/caddypki/command.go13
2 files changed, 132 insertions, 70 deletions
diff --git a/modules/caddypki/adminpki.go b/modules/caddypki/adminpki.go
index 5933bcd..a2939d1 100644
--- a/modules/caddypki/adminpki.go
+++ b/modules/caddypki/adminpki.go
@@ -16,7 +16,6 @@ package caddypki
import (
"encoding/json"
- "encoding/pem"
"fmt"
"net/http"
"strings"
@@ -26,27 +25,27 @@ import (
)
func init() {
- caddy.RegisterModule(adminPKI{})
+ caddy.RegisterModule(adminAPI{})
}
-// adminPKI is a module that serves a PKI endpoint to retrieve
+// adminAPI is a module that serves PKI endpoints to retrieve
// information about the CAs being managed by Caddy.
-type adminPKI struct {
+type adminAPI struct {
ctx caddy.Context
log *zap.Logger
pkiApp *PKI
}
// CaddyModule returns the Caddy module information.
-func (adminPKI) CaddyModule() caddy.ModuleInfo {
+func (adminAPI) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "admin.api.pki",
- New: func() caddy.Module { return new(adminPKI) },
+ New: func() caddy.Module { return new(adminAPI) },
}
}
-// Provision sets up the adminPKI module.
-func (a *adminPKI) Provision(ctx caddy.Context) error {
+// Provision sets up the adminAPI module.
+func (a *adminAPI) Provision(ctx caddy.Context) error {
a.ctx = ctx
a.log = ctx.Logger(a)
@@ -69,52 +68,128 @@ func (a *adminPKI) Provision(ctx caddy.Context) error {
}
// Routes returns the admin routes for the PKI app.
-func (a *adminPKI) Routes() []caddy.AdminRoute {
+func (a *adminAPI) Routes() []caddy.AdminRoute {
return []caddy.AdminRoute{
{
- Pattern: adminPKICertificatesEndpoint,
- Handler: caddy.AdminHandlerFunc(a.handleCertificates),
+ Pattern: adminPKIEndpointBase,
+ Handler: caddy.AdminHandlerFunc(a.handleAPIEndpoints),
},
}
}
-// handleCertificates returns certificate information about a particular
-// CA, by its ID. If the CA ID is the default, then the CA will be
+// handleAPIEndpoints routes API requests within adminPKIEndpointBase.
+func (a *adminAPI) handleAPIEndpoints(w http.ResponseWriter, r *http.Request) error {
+ uri := strings.TrimPrefix(r.URL.Path, "/pki/")
+ parts := strings.Split(uri, "/")
+ switch {
+ case len(parts) == 2 && parts[0] == "ca" && parts[1] != "":
+ return a.handleCAInfo(w, r)
+ case len(parts) == 3 && parts[0] == "ca" && parts[1] != "" && parts[2] == "certificates":
+ return a.handleCACerts(w, r)
+ }
+ return caddy.APIError{
+ HTTPStatus: http.StatusNotFound,
+ Err: fmt.Errorf("resource not found: %v", r.URL.Path),
+ }
+}
+
+// handleCAInfo returns cinformation about a particular
+// CA by its ID. If the CA ID is the default, then the CA will be
// provisioned if it has not already been. Other CA IDs will return an
// error if they have not been previously provisioned.
-func (a *adminPKI) handleCertificates(w http.ResponseWriter, r *http.Request) error {
+func (a *adminAPI) handleCAInfo(w http.ResponseWriter, r *http.Request) error {
if r.Method != http.MethodGet {
return caddy.APIError{
HTTPStatus: http.StatusMethodNotAllowed,
- Err: fmt.Errorf("method not allowed"),
+ Err: fmt.Errorf("method not allowed: %v", r.Method),
+ }
+ }
+
+ ca, err := a.getCAFromAPIRequestPath(r)
+ if err != nil {
+ return err
+ }
+
+ rootCert, interCert, err := rootAndIntermediatePEM(ca)
+ if err != nil {
+ return caddy.APIError{
+ HTTPStatus: http.StatusInternalServerError,
+ Err: fmt.Errorf("failed to get root and intermediate cert for CA %s: %v", ca.ID, err),
+ }
+ }
+
+ repl := ca.newReplacer()
+
+ response := caInfo{
+ ID: ca.ID,
+ Name: ca.Name,
+ RootCN: repl.ReplaceAll(ca.RootCommonName, ""),
+ IntermediateCN: repl.ReplaceAll(ca.IntermediateCommonName, ""),
+ RootCert: string(rootCert),
+ IntermediateCert: string(interCert),
+ }
+
+ encoded, err := json.Marshal(response)
+ if err != nil {
+ return caddy.APIError{
+ HTTPStatus: http.StatusInternalServerError,
+ Err: err,
}
}
- // Prep for a JSON response
w.Header().Set("Content-Type", "application/json")
- enc := json.NewEncoder(w)
+ w.Write(encoded)
- idPath := r.URL.Path
+ return nil
+}
- // Grab the CA ID from the request path, it should be the 4th segment
- parts := strings.Split(idPath, "/")
- if len(parts) < 4 || parts[3] == "" {
+// handleCACerts returns cinformation about a particular
+// CA by its ID. If the CA ID is the default, then the CA will be
+// provisioned if it has not already been. Other CA IDs will return an
+// error if they have not been previously provisioned.
+func (a *adminAPI) handleCACerts(w http.ResponseWriter, r *http.Request) error {
+ if r.Method != http.MethodGet {
return caddy.APIError{
- HTTPStatus: http.StatusBadRequest,
- Err: fmt.Errorf("request path is missing the CA ID"),
+ HTTPStatus: http.StatusMethodNotAllowed,
+ Err: fmt.Errorf("method not allowed: %v", r.Method),
}
}
- if parts[0] != "" || parts[1] != "pki" || parts[2] != "certificates" {
+
+ ca, err := a.getCAFromAPIRequestPath(r)
+ if err != nil {
+ return err
+ }
+
+ rootCert, interCert, err := rootAndIntermediatePEM(ca)
+ if err != nil {
return caddy.APIError{
+ HTTPStatus: http.StatusInternalServerError,
+ Err: fmt.Errorf("failed to get root and intermediate cert for CA %s: %v", ca.ID, err),
+ }
+ }
+
+ w.Header().Set("Content-Type", "application/pem-certificate-chain")
+ _, err = w.Write(interCert)
+ if err == nil {
+ w.Write(rootCert)
+ }
+
+ return nil
+}
+
+func (a *adminAPI) getCAFromAPIRequestPath(r *http.Request) (*CA, error) {
+ // Grab the CA ID from the request path, it should be the 4th segment (/pki/ca/<ca>)
+ id := strings.Split(r.URL.Path, "/")[3]
+ if id == "" {
+ return nil, caddy.APIError{
HTTPStatus: http.StatusBadRequest,
- Err: fmt.Errorf("malformed object path"),
+ Err: fmt.Errorf("missing CA in path"),
}
}
- id := parts[3]
// Find the CA by ID, if PKI is configured
var ca *CA
- ok := false
+ var ok bool
if a.pkiApp != nil {
ca, ok = a.pkiApp.CAs[id]
}
@@ -127,7 +202,7 @@ func (a *adminPKI) handleCertificates(w http.ResponseWriter, r *http.Request) er
// if they actually requested the local CA ID.
if !ok {
if id != DefaultCAID {
- return caddy.APIError{
+ return nil, caddy.APIError{
HTTPStatus: http.StatusNotFound,
Err: fmt.Errorf("no certificate authority configured with id: %s", id),
}
@@ -138,57 +213,43 @@ func (a *adminPKI) handleCertificates(w http.ResponseWriter, r *http.Request) er
ca = new(CA)
err := ca.Provision(a.ctx, id, a.log)
if err != nil {
- return caddy.APIError{
+ return nil, caddy.APIError{
HTTPStatus: http.StatusInternalServerError,
Err: fmt.Errorf("failed to provision CA %s, %w", id, err),
}
}
}
- // Convert the root certificate to PEM
- rootPem := string(pem.EncodeToMemory(&pem.Block{
- Type: "CERTIFICATE",
- Bytes: ca.RootCertificate().Raw,
- }))
-
- // Convert the intermediate certificate to PEM
- interPem := string(pem.EncodeToMemory(&pem.Block{
- Type: "CERTIFICATE",
- Bytes: ca.IntermediateCertificate().Raw,
- }))
+ return ca, nil
+}
- // Build the response
- response := CAInfo{
- ID: ca.ID,
- Name: ca.Name,
- Root: rootPem,
- Intermediate: interPem,
+func rootAndIntermediatePEM(ca *CA) (root, inter []byte, err error) {
+ root, err = pemEncodeCert(ca.RootCertificate().Raw)
+ if err != nil {
+ return
}
-
- // Encode and write the JSON response
- err := enc.Encode(response)
+ inter, err = pemEncodeCert(ca.IntermediateCertificate().Raw)
if err != nil {
- return caddy.APIError{
- HTTPStatus: http.StatusInternalServerError,
- Err: err,
- }
+ return
}
-
- return nil
+ return
}
-// CAInfo is the response from the certificates API endpoint
-type CAInfo struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Root string `json:"root"`
- Intermediate string `json:"intermediate"`
+// caInfo is the response structure for the CA info API endpoint.
+type caInfo struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ RootCN string `json:"root_common_name"`
+ IntermediateCN string `json:"intermediate_common_name"`
+ RootCert string `json:"root_certificate"`
+ IntermediateCert string `json:"intermediate_certificate"`
}
-const adminPKICertificatesEndpoint = "/pki/certificates/"
+// adminPKIEndpointBase is the base admin endpoint under which all PKI admin endpoints exist.
+const adminPKIEndpointBase = "/pki/"
// Interface guards
var (
- _ caddy.AdminRouter = (*adminPKI)(nil)
- _ caddy.Provisioner = (*adminPKI)(nil)
+ _ caddy.AdminRouter = (*adminAPI)(nil)
+ _ caddy.Provisioner = (*adminAPI)(nil)
)
diff --git a/modules/caddypki/command.go b/modules/caddypki/command.go
index fa37ab0..4744c68 100644
--- a/modules/caddypki/command.go
+++ b/modules/caddypki/command.go
@@ -22,6 +22,7 @@ import (
"fmt"
"net/http"
"os"
+ "path"
"github.com/caddyserver/caddy/v2"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
@@ -132,7 +133,7 @@ func cmdTrust(fl caddycmd.Flags) (int, error) {
ca := CA{
log: caddy.Log(),
root: rootCert,
- rootCertPath: adminAddr + adminPKICertificatesEndpoint + caID,
+ rootCertPath: adminAddr + path.Join(adminPKIEndpointBase, caID, "certificates"),
}
// Install the cert!
@@ -204,9 +205,9 @@ func cmdUntrust(fl caddycmd.Flags) (int, error) {
return caddy.ExitCodeSuccess, nil
}
-// rootCertFromAdmin makes the API request to fetch the
+// rootCertFromAdmin makes the API request to fetch the root certificate for the named CA via admin API.
func rootCertFromAdmin(adminAddr string, caID string) (*x509.Certificate, error) {
- uri := adminPKICertificatesEndpoint + caID
+ uri := path.Join(adminPKIEndpointBase, caID, "certificates")
// Make the request to fetch the CA info
resp, err := caddycmd.AdminAPIRequest(adminAddr, http.MethodGet, uri, make(http.Header), nil)
@@ -216,14 +217,14 @@ func rootCertFromAdmin(adminAddr string, caID string) (*x509.Certificate, error)
defer resp.Body.Close()
// Decode the resposne
- caInfo := new(CAInfo)
+ caInfo := new(caInfo)
err = json.NewDecoder(resp.Body).Decode(caInfo)
if err != nil {
return nil, fmt.Errorf("failed to decode JSON response: %v", err)
}
- // Decode the root
- rootBlock, _ := pem.Decode([]byte(caInfo.Root))
+ // Decode the root cert
+ rootBlock, _ := pem.Decode([]byte(caInfo.RootCert))
if rootBlock == nil {
return nil, fmt.Errorf("failed to decode root certificate: %v", err)
}