Documentation
¶
Overview ¶
Package lancert holds the publicly trusted certificate of a homelab's LAN name under its kombify.me base (homelab identity target §5, §14 decision 3): one certificate for lan.<base>.kombify.me only, with which the Authority's LAN enrollment listener serves the LAN name the installed Hub PWA opens from home.
The node generates the certificate key and its ACME account key and keeps both in Owner custody; it runs its own ACME client against a second issuer (ZeroSSL or Google Trust Services with external account binding, until kombify.me is on the Public Suffix List). kombify Me publishes only the DNS-01 value and, as the LAN name's one DNS-only record, the node's private site address. Neither key, the CSR's key nor the EAB key is printed, logged or written to a receipt.
Index ¶
- Constants
- Variables
- func AuthoritativeTXT(ctx context.Context, name, value string) error
- func Covers(workspace, host string, now time.Time) bool
- func CustodyDir(workspace string) string
- func LANName(domain string) string
- func LiveDir(workspace string) string
- func NeedsRenewal(workspace, domain string, now time.Time) bool
- func PublishAddress(ctx context.Context, workspace, domain string, publisher Publisher) (netip.Addr, error)
- func Remove(workspace string) error
- func Renew(ctx context.Context, workspace, domain string, publisher Publisher, ...) (bool, error)
- func SiteAddress(workspace string) (netip.Addr, error)
- type Issuer
- type Publisher
- type Source
- type State
Constants ¶
const (
// LANLabel is the label of the LAN name under the homelab's domain.
LANLabel = "lan"
)
Variables ¶
var ErrAddressNotPublished = errors.New("lancert: the LAN address is not published")
ErrAddressNotPublished reports that kombify Me did not publish the LAN name's address (or the node had none to publish). Nothing was ordered: a caller may retry soon without touching the ACME issuer.
var Issuers = map[string]string{
"zerossl": "https://acme.zerossl.com/v2/DV90",
"gts": "https://dv.acme-v02.api.pki.goog/directory",
"letsencrypt-staging": "https://acme-staging-v02.api.letsencrypt.org/directory",
}
Issuer directories. ZeroSSL and Google Trust Services need external account binding. Let's Encrypt production is not offered: its 50 certificates per week for the registered domain kombify.me are shared by every homelab until the Public Suffix List entry takes effect (target §14 decision 3). Let's Encrypt staging serves journeys only; its chain is not trusted.
Functions ¶
func AuthoritativeTXT ¶
AuthoritativeTXT waits until every authoritative name server of the zone answers value for name, querying them directly: a shared recursive resolver asked too early caches the missing answer.
func Covers ¶
Covers reports whether the installed key pair matches and its certificate is valid for host for at least another hour.
func CustodyDir ¶
CustodyDir is the Owner custody directory of the LAN certificate.
func NeedsRenewal ¶
NeedsRenewal reports a missing certificate, one that does not cover the domain's names, and one within 30 days of expiry.
func PublishAddress ¶
func PublishAddress(ctx context.Context, workspace, domain string, publisher Publisher) (netip.Addr, error)
PublishAddress publishes the node site address for lan.<domain> through kombify Me before any ACME order: without that record the base's proxied wildcard answers the LAN name with Cloudflare addresses. A router that drops private answers (DNS rebinding protection) still leaves the Hub on the away flow. Every failure wraps ErrAddressNotPublished.
func Remove ¶
Remove deletes the certificate, its key and the ACME account key under the custody lock. The live directory itself stays: the Basement core mounts it.
func Renew ¶
func Renew(ctx context.Context, workspace, domain string, publisher Publisher, keyUID, keyGID int) (bool, error)
Renew renews an issued LAN certificate within 30 days of its expiry with the account in custody (no external account binding is needed again), and otherwise republishes the LAN name's address: Me removes that record with every tunnel teardown, and the site address can move. A node that never issued one has nothing to renew or publish.
func SiteAddress ¶
SiteAddress is the address lan.<domain> must resolve to: the node site address the owner-signed LAN resolver custody binds, which the LAN enrollment listener (9443) is published on and announces. Only an RFC 1918 or IPv6 ULA address is answered (narrower than Me, which also admits 100.64.0.0/10); a public, loopback, link-local, IPv4-mapped or zoned address never leaves the node.
Types ¶
type Issuer ¶
type Issuer struct {
Workspace string
Domain string
// Directory is the ACME directory URL of the issuer.
Directory string
// EAB is required for a new account at ZeroSSL and Google Trust Services.
EAB *acme.ExternalAccountBinding
// Contact is the optional ACME account contact address; Google Trust
// Services and ZeroSSL refuse an account without one.
Contact string
Publisher Publisher
// KeyUID and KeyGID own the certificate key file: the core server's
// account, which serves the LAN enrollment listener.
KeyUID, KeyGID int
// Propagated waits until the authoritative servers answer the value.
Propagated func(ctx context.Context, name, value string) error
}
Issuer obtains the LAN certificate.
func (Issuer) Issue ¶
Issue publishes the LAN name's address, registers or reuses the node's ACME account, proves control of the LAN name lan.<domain> (and nothing else: the key lives in the core, so it must not be able to impersonate the identity origin or any service) through the publisher and installs the certificate. Nothing is ordered unless Me read the address back. One issuance runs at a time.
type Publisher ¶
type Publisher interface {
SetChallenge(ctx context.Context, identifier, value string) error
RemoveChallenge(ctx context.Context, identifier, value string) error
SetLANAddress(ctx context.Context, name string, address netip.Addr) error
}
Publisher is kombify Me's side of the LAN name: it publishes and removes one DNS-01 value, and publishes the node's private address as the DNS-only record of the LAN name, answering nil only after Me's read-back shows that name, DNS-only, with exactly that address.
type Source ¶
type Source struct {
Workspace string
// contains filtered or unexported fields
}
Source serves the installed certificate to a TLS listener, reloading it when the file changes.
func (*Source) Certificate ¶
func (s *Source) Certificate() *tls.Certificate
Certificate answers the installed key pair, or nil when none is installed.
type State ¶
type State struct {
Names []string `json:"names"`
Issuer string `json:"issuer"`
NotBefore string `json:"notBefore"`
NotAfter string `json:"notAfter"`
SerialHex string `json:"serialHex"`
CertificateSHA256 string `json:"certificateSha256"`
}
State is the public identity of the installed certificate.