jarld obtains and renews a certificate for another host; the host asks
for it with one command, waits, downloads it when it is newer than its
copy and runs a hook with the paths in JARL_CERT_* variables.
- Certificates have an owner user; another user can't take the name.
An existing one is obtained again only when a name is added or
removed. A new certificates grant (names=) says which names a user
may have; it is never part of "*". Users may own
certificates.max_per_user certificates (10) or their own cert_limit.
- POST /certificates/{name}/request; owners may list, show, export and
delete theirs; keys only over HTTPS, the socket or loopback; every
download is recorded and shown as "Pulled by".
- CLI: jarl cert get (saved settings, all saved when no name), cert
saved, cert forget, cert schedule (daily systemd timer, Linux, root).
- Migration 14. Tests: server, auth, and e2e against Pebble.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
3.4 KiB
Go
89 lines
3.4 KiB
Go
package model
|
|
|
|
import (
|
|
"slices"
|
|
"time"
|
|
)
|
|
|
|
// Certificate sources.
|
|
const (
|
|
CertSourceACME = "acme" // Jarl obtains and renews it (docs/certificates.md)
|
|
CertSourceFile = "file" // you renew it; Jarl reads it from cert_file and key_file
|
|
)
|
|
|
|
// Certificate uses.
|
|
const (
|
|
CertUseWeb = "web" // jarld's own HTTPS
|
|
CertUseDNS = "dns" // written where named can read it, for DNS over TLS/HTTPS
|
|
)
|
|
|
|
// Challenge routes: where Jarl publishes the DNS-01 challenge.
|
|
const (
|
|
ChallengeAuto = "auto" // decided at every issue from the domain's public name servers
|
|
ChallengeJarl = "jarl" // in a zone Jarl serves (following a CNAME on _acme-challenge)
|
|
ChallengePorkbun = "porkbun" // through Porkbun's DNS API
|
|
)
|
|
|
|
// Certificate is a TLS certificate Jarl manages. Certificates are not
|
|
// named configuration: they are kept outside changesets, like users.
|
|
type Certificate struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Source string `json:"source"`
|
|
Domains []string `json:"domains"` // acme: the names to ask for; file: the names in the certificate
|
|
CertFile string `json:"cert_file,omitempty"` // file source
|
|
KeyFile string `json:"key_file,omitempty"` // file source
|
|
Uses []string `json:"uses"`
|
|
Challenge string `json:"challenge,omitempty"` // acme source
|
|
KeyType string `json:"key_type,omitempty"` // acme source: ec256, ec384, rsa2048, rsa3072, rsa4096
|
|
Comment string `json:"comment"`
|
|
|
|
// The current certificate: acme source only (a file source's is read
|
|
// from its files). The key leaves the server only through the
|
|
// administrators' export (GET /certificates/{name}/pem), audited.
|
|
CertPEM string `json:"-"`
|
|
KeyPEM string `json:"-"`
|
|
|
|
// What the current certificate says (both sources).
|
|
Names []string `json:"names,omitempty"` // the names in it
|
|
NotBefore *time.Time `json:"not_before,omitempty"`
|
|
NotAfter *time.Time `json:"not_after,omitempty"`
|
|
Issuer string `json:"issuer,omitempty"`
|
|
Serial string `json:"serial,omitempty"`
|
|
|
|
// Renewal (acme source). RenewAt is the CA's suggested time (ARI) or,
|
|
// without one, two thirds into the certificate's lifetime.
|
|
RenewAt *time.Time `json:"renew_at,omitempty"`
|
|
ARICheckedAt *time.Time `json:"-"`
|
|
LastAttempt *time.Time `json:"last_attempt,omitempty"`
|
|
LastSuccess *time.Time `json:"last_success,omitempty"`
|
|
LastError string `json:"last_error,omitempty"`
|
|
Failures int `json:"failures,omitempty"`
|
|
// LastRoutes says where the challenges went the last time ("adbraun.lol: porkbun").
|
|
LastRoutes []string `json:"last_routes,omitempty"`
|
|
|
|
// OwnerID is the user a certificate for another host belongs to
|
|
// (jarl cert get); 0 for the administrators' own.
|
|
OwnerID int64 `json:"owner_id,omitempty"`
|
|
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
CreatedBy string `json:"created_by"`
|
|
UpdatedBy string `json:"updated_by"`
|
|
}
|
|
|
|
// CertificatePull records that someone downloaded a certificate: who
|
|
// ("token NAME" or "user NAME"), which one (Serial), when and from where.
|
|
type CertificatePull struct {
|
|
CertID int64 `json:"-"`
|
|
Who string `json:"who"`
|
|
UserID int64 `json:"user_id,omitempty"`
|
|
TokenID int64 `json:"token_id,omitempty"`
|
|
Serial string `json:"serial"`
|
|
IP string `json:"ip,omitempty"`
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// HasUse reports whether c is used for use.
|
|
func (c *Certificate) HasUse(use string) bool { return slices.Contains(c.Uses, use) }
|