lancert

package
v0.56.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

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

View Source
const (
	// LANLabel is the label of the LAN name under the homelab's domain.
	LANLabel = "lan"
)

Variables

View Source
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.

View Source
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

func AuthoritativeTXT(ctx context.Context, name, value string) error

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

func Covers(workspace, host string, now time.Time) bool

Covers reports whether the installed key pair matches and its certificate is valid for host for at least another hour.

func CustodyDir

func CustodyDir(workspace string) string

CustodyDir is the Owner custody directory of the LAN certificate.

func LANName

func LANName(domain string) string

LANName is the LAN name of a homelab domain.

func LiveDir

func LiveDir(workspace string) string

LiveDir holds the certificate and its key; the Basement core mounts it.

func NeedsRenewal

func NeedsRenewal(workspace, domain string, now time.Time) bool

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

func Remove(workspace string) error

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

func SiteAddress(workspace string) (netip.Addr, error)

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

func (i Issuer) Issue(ctx context.Context) (State, error)

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.

func Load

func Load(workspace string) (State, *x509.Certificate, error)

Load parses the installed certificate; os.ErrNotExist when none is.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL