v2.31.0-dev12 6a38a35

config.go RawHistoryFolder

1package publictls
2
3import (
4	"context"
5	"fmt"
6
7	"github.com/bnema/gordon/internal/boundaries/out"
8	"github.com/bnema/gordon/internal/domain"
9)
10
11// Config holds the resolved configuration for public ACME TLS.
12type Config struct {
13	Enabled         bool
14	Email           string
15	Challenge       string
16	HTTPPort        int
17	TLSPort         int
18	DataDir         string
19	ObtainBatchSize int
20}
21
22// EffectiveChallenge represents the resolved ACME challenge configuration.
23type EffectiveChallenge struct {
24	ConfiguredMode domain.ACMEChallengeMode
25	Mode           domain.ACMEChallengeMode
26	Token          string
27	TokenSource    domain.ACMETokenSource
28	Reason         string
29}
30
31// validPort checks that port is in the valid TCP port range 1..65535.
32func validPort(p int) bool {
33	return p >= 1 && p <= 65535
34}
35
36// ResolveEffectiveChallenge resolves the effective ACME challenge configuration
37// from the given Config and optional token resolver.
38func ResolveEffectiveChallenge(ctx context.Context, cfg Config, resolver out.SecretResolver) (EffectiveChallenge, error) {
39	if !cfg.Enabled {
40		configuredMode := domain.ACMEChallengeAuto
41		if parsed, err := domain.ParseACMEChallengeMode(cfg.Challenge); err == nil {
42			configuredMode = parsed
43		}
44		return EffectiveChallenge{
45			ConfiguredMode: configuredMode,
46			Mode:           domain.ACMEChallengeAuto,
47			TokenSource:    domain.ACMETokenSourceNone,
48			Reason:         "ACME disabled",
49		}, nil
50	}
51
52	if cfg.Email == "" {
53		return EffectiveChallenge{}, domain.ErrACMEEmailRequired
54	}
55
56	if !validPort(cfg.TLSPort) {
57		return EffectiveChallenge{}, fmt.Errorf("%w: tls_port must be in range 1..65535", domain.ErrACMEChallengeInvalid)
58	}
59
60	parsedMode, err := domain.ParseACMEChallengeMode(cfg.Challenge)
61	if err != nil {
62		return EffectiveChallenge{}, err
63	}
64
65	switch parsedMode {
66	case domain.ACMEChallengeHTTP01:
67		if !validPort(cfg.HTTPPort) {
68			return EffectiveChallenge{}, fmt.Errorf("%w: http_port must be in range 1..65535 for http-01 challenge", domain.ErrACMEChallengeInvalid)
69		}
70		return EffectiveChallenge{
71			ConfiguredMode: domain.ACMEChallengeHTTP01,
72			Mode:           domain.ACMEChallengeHTTP01,
73			TokenSource:    domain.ACMETokenSourceNone,
74			Reason:         "http-01 challenge selected",
75		}, nil
76
77	case domain.ACMEChallengeCloudflareDNS01:
78		token, source, err := resolveToken(ctx, resolver, true)
79		if err != nil {
80			return EffectiveChallenge{}, fmt.Errorf("resolve Cloudflare token: %w", err)
81		}
82		if token == "" {
83			return EffectiveChallenge{}, domain.ErrCloudflareTokenMissing
84		}
85		return EffectiveChallenge{
86			ConfiguredMode: domain.ACMEChallengeCloudflareDNS01,
87			Mode:           domain.ACMEChallengeCloudflareDNS01,
88			Token:          token,
89			TokenSource:    source,
90			Reason:         "cloudflare dns-01 challenge selected",
91		}, nil
92
93	case domain.ACMEChallengeAuto:
94		token, source, _ := resolveToken(ctx, resolver, false)
95		if token != "" {
96			return EffectiveChallenge{
97				ConfiguredMode: domain.ACMEChallengeAuto,
98				Mode:           domain.ACMEChallengeCloudflareDNS01,
99				Token:          token,
100				TokenSource:    source,
101				Reason:         "auto selected cloudflare dns-01 (token available)",
102			}, nil
103		}
104		if !validPort(cfg.HTTPPort) {
105			return EffectiveChallenge{}, fmt.Errorf("%w: http_port must be in range 1..65535 for http-01 fallback in auto mode", domain.ErrACMEChallengeInvalid)
106		}
107		return EffectiveChallenge{
108			ConfiguredMode: domain.ACMEChallengeAuto,
109			Mode:           domain.ACMEChallengeHTTP01,
110			TokenSource:    domain.ACMETokenSourceNone,
111			Reason:         "auto selected http-01 (no cloudflare token)",
112		}, nil
113
114	default:
115		return EffectiveChallenge{}, fmt.Errorf("%w: %q", domain.ErrACMEChallengeInvalid, parsedMode)
116	}
117}
118
119// resolveToken resolves the Cloudflare token via the resolver. When strict is
120// false, resolver errors are ignored so auto mode can fall back to HTTP-01.
121func resolveToken(ctx context.Context, resolver out.SecretResolver, strict bool) (string, domain.ACMETokenSource, error) {
122	if resolver == nil {
123		return "", domain.ACMETokenSourceNone, nil
124	}
125	sv, err := resolver.ResolveCloudflareToken(ctx)
126	if err != nil {
127		if strict {
128			return "", domain.ACMETokenSourceNone, err
129		}
130		return "", domain.ACMETokenSourceNone, nil
131	}
132	if sv.Value == "" {
133		return "", domain.ACMETokenSourceNone, nil
134	}
135	return sv.Value, sv.Source, nil
136}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 106

↑↓ moveenter openesc close