From 184e8e9f713bf39e82f4677452998bb003de6e6d Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Tue, 5 May 2020 12:35:32 -0600 Subject: pki: Embedded ACME server (#3198) * pki: Initial commit of embedded ACME server (#3021) * reverseproxy: Support auto-managed TLS client certificates (#3021) * A little cleanup after today's review session --- modules/caddypki/acmeserver/acmeserver.go | 165 ++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 modules/caddypki/acmeserver/acmeserver.go (limited to 'modules/caddypki/acmeserver') diff --git a/modules/caddypki/acmeserver/acmeserver.go b/modules/caddypki/acmeserver/acmeserver.go new file mode 100644 index 0000000..8dc0f01 --- /dev/null +++ b/modules/caddypki/acmeserver/acmeserver.go @@ -0,0 +1,165 @@ +// Copyright 2015 Matthew Holt and The Caddy Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package acmeserver + +import ( + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "github.com/caddyserver/caddy/v2/modules/caddypki" + "github.com/go-chi/chi" + "github.com/smallstep/certificates/acme" + acmeAPI "github.com/smallstep/certificates/acme/api" + "github.com/smallstep/certificates/authority" + "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/db" + "github.com/smallstep/nosql" +) + +func init() { + caddy.RegisterModule(Handler{}) +} + +// Handler is an ACME server handler. +type Handler struct { + // The ID of the CA to use for signing. This refers to + // the ID given to the CA in the `pki` app. If omitted, + // the default ID is "local". + CA string `json:"ca,omitempty"` + + // The hostname or IP address by which ACME clients + // will access the server. This is used to populate + // the ACME directory endpoint. Default: localhost. + // TODO: this is probably not needed - check with smallstep + Host string `json:"host,omitempty"` + + // The path prefix under which to serve all ACME + // endpoints. All other requests will not be served + // by this handler and will be passed through to + // the next one. Default: "/acme/" + PathPrefix string `json:"path_prefix,omitempty"` + + acmeEndpoints http.Handler +} + +// CaddyModule returns the Caddy module information. +func (Handler) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "http.handlers.acme_server", + New: func() caddy.Module { return new(Handler) }, + } +} + +// Provision sets up the ACME server handler. +func (ash *Handler) Provision(ctx caddy.Context) error { + // set some defaults + if ash.CA == "" { + ash.CA = caddypki.DefaultCAID + } + if ash.Host == "" { + ash.Host = defaultHost + } + if ash.PathPrefix == "" { + ash.PathPrefix = defaultPathPrefix + } + + // get a reference to the configured CA + appModule, err := ctx.App("pki") + if err != nil { + return err + } + pkiApp := appModule.(*caddypki.PKI) + ca, ok := pkiApp.CAs[ash.CA] + if !ok { + return fmt.Errorf("no certificate authority configured with id: %s", ash.CA) + } + + dbFolder := filepath.Join(caddy.AppDataDir(), "acme_server", "db") + + // TODO: See https://github.com/smallstep/nosql/issues/7 + err = os.MkdirAll(dbFolder, 0755) + if err != nil { + return fmt.Errorf("making folder for ACME server database: %v", err) + } + + authorityConfig := caddypki.AuthorityConfig{ + AuthConfig: &authority.AuthConfig{ + Provisioners: provisioner.List{ + &provisioner.ACME{ + Name: ash.CA, + Type: provisioner.TypeACME.String(), + Claims: &provisioner.Claims{ + MinTLSDur: &provisioner.Duration{Duration: 5 * time.Minute}, + MaxTLSDur: &provisioner.Duration{Duration: 24 * time.Hour * 365}, + DefaultTLSDur: &provisioner.Duration{Duration: 12 * time.Hour}, + }, + }, + }, + }, + DB: &db.Config{ + Type: "badger", + DataSource: dbFolder, + }, + } + + auth, err := ca.NewAuthority(authorityConfig) + if err != nil { + return err + } + + acmeAuth, err := acme.NewAuthority( + auth.GetDatabase().(nosql.DB), // stores all the server state + ash.Host, // used for directory links; TODO: not needed + strings.Trim(ash.PathPrefix, "/"), // used for directory links + auth) // configures the signing authority + if err != nil { + return err + } + + // create the router for the ACME endpoints + acmeRouterHandler := acmeAPI.New(acmeAuth) + r := chi.NewRouter() + r.Route(ash.PathPrefix, func(r chi.Router) { + acmeRouterHandler.Route(r) + }) + ash.acmeEndpoints = r + + return nil +} + +func (ash Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { + if strings.HasPrefix(r.URL.Path, ash.PathPrefix) { + ash.acmeEndpoints.ServeHTTP(w, r) + return nil + } + return next.ServeHTTP(w, r) +} + +const ( + defaultHost = "localhost" + defaultPathPrefix = "/acme/" +) + +// Interface guards +var ( + _ caddyhttp.MiddlewareHandler = (*Handler)(nil) + _ caddy.Provisioner = (*Handler)(nil) +) -- cgit v1.2.3