Go API Documentation

github.com/caddyserver/caddy/v2/modules/caddypki/acmeserver

No package summary is available.

Package

Files: 4. Third party imports: 11. Imports from organisation: 0. Tests: 0. Benchmarks: 0.

Constants

const (
	HTTP_01		ACMEChallenge	= "http-01"
	DNS_01		ACMEChallenge	= "dns-01"
	TLS_ALPN_01	ACMEChallenge	= "tls-alpn-01"
)
const defaultPathPrefix = "/acme/"

Vars

var (
	keyCleaner	= regexp.MustCompile(`[^\w.-_]`)
	databasePool	= caddy.NewUsagePool()
)
var (
	_	caddyhttp.MiddlewareHandler	= (*Handler)(nil)
	_	caddy.Provisioner		= (*Handler)(nil)
)

Types

ACMEChallenge

ACMEChallenge is an opaque string that represents supported ACME challenges.

type ACMEChallenge string

ACMEChallenges

ACMEChallenges is a list of ACME challenges.

type ACMEChallenges []ACMEChallenge

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 lifetime for issued certificates
	Lifetime	caddy.Duration	`json:"lifetime,omitempty"`

	// The hostname or IP address by which ACME clients
	// will access the server. This is used to populate
	// the ACME directory endpoint. If not set, the Host
	// header of the request will be used.
	// COMPATIBILITY NOTE / TODO: This property may go away in the
	// future. Do not rely on this property long-term; check release notes.
	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/".
	// COMPATIBILITY NOTE / TODO: This property may go away in the
	// future, as it is currently only required due to
	// limitations in the underlying library. Do not rely
	// on this property long-term; check release notes.
	PathPrefix	string	`json:"path_prefix,omitempty"`

	// If true, the CA's root will be the issuer instead of
	// the intermediate. This is NOT recommended and should
	// only be used when devices/clients do not properly
	// validate certificate chains. EXPERIMENTAL: Might be
	// changed or removed in the future.
	SignWithRoot	bool	`json:"sign_with_root,omitempty"`

	// The addresses of DNS resolvers to use when looking up
	// the TXT records for solving DNS challenges.
	// It accepts [network addresses](/docs/conventions#network-addresses)
	// with port range of only 1. If the host is an IP address,
	// it will be dialed directly to resolve the upstream server.
	// If the host is not an IP address, the addresses are resolved
	// using the [name resolution convention](https://golang.org/pkg/net/#hdr-Name_Resolution)
	// of the Go standard library. If the array contains more
	// than 1 resolver address, one is chosen at random.
	Resolvers	[]string	`json:"resolvers,omitempty"`

	// Specify the set of enabled ACME challenges. An empty or absent value
	// means all challenges are enabled. Accepted values are:
	// "http-01", "dns-01", "tls-alpn-01"
	Challenges	ACMEChallenges	`json:"challenges,omitempty" `

	// The policy to use for issuing certificates
	Policy	*Policy	`json:"policy,omitempty"`

	logger		*zap.Logger
	resolvers	[]caddy.NetworkAddress
	ctx		caddy.Context

	acmeDB		acme.DB
	acmeAuth	*authority.Authority
	acmeClient	acme.Client
	acmeLinker	acme.Linker
	acmeEndpoints	http.Handler
}

Policy

Policy defines the criteria for the ACME server of when to issue a certificate. Refer to the Certificate Issuance Policy on Smallstep website for the evaluation criteria.

type Policy struct {
	// If a rule set is configured to allow a certain type of name,
	// all other types of names are automatically denied.
	Allow	*RuleSet	`json:"allow,omitempty"`

	// If a rule set is configured to deny a certain type of name,
	// all other types of names are still allowed.
	Deny	*RuleSet	`json:"deny,omitempty"`

	// If set to true, the ACME server will allow issuing wildcard certificates.
	AllowWildcardNames	bool	`json:"allow_wildcard_names,omitempty"`
}

RuleSet

RuleSet is the specific set of SAN criteria for a certificate to be issued or denied.

type RuleSet struct {
	// Domains is a list of DNS domains that are allowed to be issued.
	// It can be in the form of FQDN for specific domain name, or
	// a wildcard domain name format, e.g. *.example.com, to allow
	// sub-domains of a domain.
	Domains	[]string	`json:"domains,omitempty"`

	// IP ranges in the form of CIDR notation or specific IP addresses
	// to be approved or denied for certificates. Non-CIDR IP addresses
	// are matched exactly.
	IPRanges	[]string	`json:"ip_ranges,omitempty"`
}

databaseCloser

This type doesn't have documentation.

type databaseCloser struct {
	DB *db.AuthDB
}

resolverClient

This type doesn't have documentation.

type resolverClient struct {
	acme.Client

	resolver	*net.Resolver
	ctx		context.Context
}

Functions

func (*ACMEChallenge) UnmarshalJSON

The unmarshaller first marshals the value into a string. Then it trims any space around it and lowercase it for normaliztion. The method does not and should not validate the value within accepted enums.

func (c *ACMEChallenge) UnmarshalJSON(b []byte) error {
	var s string
	if err := json.Unmarshal(b, &s); err != nil {
		return err
	}
	*c = ACMEChallenge(strings.ToLower(strings.TrimSpace(s)))
	return nil
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: json.Unmarshal, strings.ToLower, strings.TrimSpace.

func (*Handler) Provision

Provision sets up the ACME server handler.

func (ash *Handler) Provision(ctx caddy.Context) error {
	ash.ctx = ctx
	ash.logger = ctx.Logger()

	// set some defaults
	if ash.CA == "" {
		ash.CA = caddypki.DefaultCAID
	}
	if ash.PathPrefix == "" {
		ash.PathPrefix = defaultPathPrefix
	}
	if ash.Lifetime == 0 {
		ash.Lifetime = caddy.Duration(12 * time.Hour)
	}
	if len(ash.Challenges) > 0 {
		if err := ash.Challenges.validate(); err != nil {
			return err
		}
	}

	// get a reference to the configured CA
	appModule, err := ctx.App("pki")
	if err != nil {
		return err
	}
	pkiApp := appModule.(*caddypki.PKI)
	ca, err := pkiApp.GetCA(ctx, ash.CA)
	if err != nil {
		return err
	}

	// make sure leaf cert lifetime is less than the intermediate cert lifetime. this check only
	// applies for caddy-managed intermediate certificates
	if ca.Intermediate == nil && ash.Lifetime >= ca.IntermediateLifetime {
		return fmt.Errorf("certificate lifetime (%s) should be less than intermediate certificate lifetime (%s)", time.Duration(ash.Lifetime), time.Duration(ca.IntermediateLifetime))
	}

	database, err := ash.openDatabase()
	if err != nil {
		return err
	}

	authorityConfig := caddypki.AuthorityConfig{
		SignWithRoot:	ash.SignWithRoot,
		AuthConfig: &authority.AuthConfig{
			Provisioners: provisioner.List{
				&provisioner.ACME{
					Name:		ash.CA,
					Challenges:	ash.Challenges.toSmallstepType(),
					Options: &provisioner.Options{
						X509: ash.Policy.normalizeRules(),
					},
					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: time.Duration(ash.Lifetime)},
					},
				},
			},
		},
		DB:	database,
	}

	ash.acmeAuth, err = ca.NewAuthority(authorityConfig)
	if err != nil {
		return err
	}

	ash.acmeDB, err = acmeNoSQL.New(ash.acmeAuth.GetDatabase().(nosql.DB))
	if err != nil {
		return fmt.Errorf("configuring ACME DB: %v", err)
	}

	ash.acmeClient, err = ash.makeClient()
	if err != nil {
		return err
	}

	ash.acmeLinker = acme.NewLinker(
		ash.Host,
		strings.Trim(ash.PathPrefix, "/"),
	)

	// extract its http.Handler so we can use it directly
	r := chi.NewRouter()
	r.Route(ash.PathPrefix, func(r chi.Router) {
		api.Route(r)
	})
	ash.acmeEndpoints = r

	return nil
}

Cognitive complexity: 34, Cyclomatic complexity: 14

Uses: acme.NewLinker, api.Route, authority.AuthConfig, caddypki.AuthorityConfig, caddypki.DefaultCAID, caddypki.PKI, fmt.Errorf, nosql.DB, provisioner.ACME, provisioner.Claims, provisioner.Duration, provisioner.List, provisioner.Options, provisioner.TypeACME, strings.Trim, time.Duration, time.Hour, time.Minute.

func (ACMEChallenge) String

String returns a string representation of the challenge.

func (c ACMEChallenge) String() string {
	return strings.ToLower(string(c))
}

Cognitive complexity: 0, Cyclomatic complexity: 1

Uses: strings.ToLower.

func (Handler) CaddyModule

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) },
	}
}

Cognitive complexity: 2, Cyclomatic complexity: 1

func (Handler) Cleanup

Cleanup implements caddy.CleanerUpper and closes any idle databases.

func (ash Handler) Cleanup() error {
	key := ash.getDatabaseKey()
	deleted, err := databasePool.Delete(key)
	if deleted {
		if c := ash.logger.Check(zapcore.DebugLevel, "unloading unused CA database"); c != nil {
			c.Write(zap.String("db_key", key))
		}
	}
	if err != nil {
		if c := ash.logger.Check(zapcore.ErrorLevel, "closing CA database"); c != nil {
			c.Write(zap.String("db_key", key), zap.Error(err))
		}
	}
	return err
}

Cognitive complexity: 8, Cyclomatic complexity: 5

Uses: zap.Error, zap.String, zapcore.DebugLevel, zapcore.ErrorLevel.

func (Handler) ServeHTTP

func (ash Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	if strings.HasPrefix(r.URL.Path, ash.PathPrefix) {
		acmeCtx := acme.NewContext(
			r.Context(),
			ash.acmeDB,
			ash.acmeClient,
			ash.acmeLinker,
			nil,
		)
		acmeCtx = authority.NewContext(acmeCtx, ash.acmeAuth)
		r = r.WithContext(acmeCtx)

		ash.acmeEndpoints.ServeHTTP(w, r)
		return nil
	}
	return next.ServeHTTP(w, r)
}

Cognitive complexity: 2, Cyclomatic complexity: 2

Uses: acme.NewContext, authority.NewContext, strings.HasPrefix.

func (databaseCloser) Destruct

func (closer databaseCloser) Destruct() error {
	return (*closer.DB).Shutdown()
}

Cognitive complexity: 0, Cyclomatic complexity: 1

func (resolverClient) LookupTxt

func (c resolverClient) LookupTxt(name string) ([]string, error) {
	return c.resolver.LookupTXT(c.ctx, name)
}

Cognitive complexity: 0, Cyclomatic complexity: 1

Private functions

func init

init ()

func parseACMEServer

parseACMEServer sets up an ACME server handler from Caddyfile tokens.

acme_server [<matcher>] {
	ca        <id>
	lifetime  <duration>
	resolvers <addresses...>
	challenges <challenges...>
	allow_wildcard_names
	allow {
		domains <domains...>
		ip_ranges <addresses...>
	}
	deny {
		domains <domains...>
		ip_ranges <addresses...>
	}
	sign_with_root
}

parseACMEServer (h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
References: caddypki.CA, httpcaddyfile.ConfigValue, time.Duration.

func stringToChallenges

stringToChallenges (chs []string) ACMEChallenges

func normalizeAllowRules

normalizeAllowRules returns nil if policy is nil, the Allow rule is nil, or all rules within the Allow rule are empty. Otherwise, it returns the X509NameOptions with the content of the Allow rule.

normalizeAllowRules () *policy.X509NameOptions
References: policy.X509NameOptions.

func normalizeDenyRules

normalizeDenyRules returns nil if policy is nil, the Deny rule is nil, or all rules within the Deny rule are empty. Otherwise, it returns the X509NameOptions with the content of the Deny rule.

normalizeDenyRules () *policy.X509NameOptions
References: policy.X509NameOptions.

func normalizeRules

normalizeRules returns nil if policy is nil, the Allow and Deny rules are nil,

normalizeRules () *provisioner.X509Options
References: provisioner.X509Options.

func validate

validate checks if the given challenge is supported.

validate () error
References: fmt.Errorf.

func toSmallstepType

toSmallstepType () []provisioner.ACMEChallenge
References: provisioner.ACMEChallenge.

func getDatabaseKey

getDatabaseKey () string
References: strings.ToLower, strings.TrimSpace.

func makeClient

makeClient creates an ACME client which will use a custom resolver instead of net.DefaultResolver.

makeClient () (acme.Client, error)
References: acme.NewClient, context.Context, fmt.Errorf, net.Conn, net.DefaultResolver, net.Dialer, net.Resolver, time.Second, weakrand.Intn.

func openDatabase

openDatabase () (*db.AuthDB, error)
References: db.Config, db.New, filepath.Join, fmt.Errorf, os.MkdirAll, zap.String, zapcore.DebugLevel.


Tests

Files: 1. Third party imports: 2. Imports from organisation: 0. Tests: 3. Benchmarks: 0.

Test functions

TestPolicyNormalizeAllowRules

References: policy.X509NameOptions, reflect.DeepEqual, testing.T.

TestPolicy_normalizeDenyRules

References: policy.X509NameOptions, reflect.DeepEqual, testing.T.

TestPolicy_normalizeRules

References: provisioner.X509Options, reflect.DeepEqual, testing.T.