chore: normalize line endings and apply formatting

- Convert CRLF to LF across Go, panel, and plugin files
- Add Cloudflare API token template URL to breakGlass TUI edge intro
- Verify API token in cfsetup before creating tunnel, DNS, or Access app
This commit is contained in:
flyemoji committed 2026-06-28 16:43:10 +09:00
1 parent 9c46632929
commit 5450c268f4
3 files changed
+65 -6

No files matched your search

+18 -5
View File
@@ -314,6 +314,10 @@ func (p Preconditions) check() error {
// Runner is the integration seam: every side-effecting step of the setup. The
// real implementation (ExecRunner in runner.go) shells out to cloudflared and
// calls the Cloudflare API and is INTEGRATION-ONLY; tests pass a fake.
type apiTokenVerifier interface {
VerifyAPIToken(ctx context.Context) error
}
type Runner interface {
// CreateTunnel creates (or, idempotently, returns the existing) named tunnel,
// yielding its UUID and the path to its credentials file.
@@ -385,6 +389,15 @@ func Setup(ctx context.Context, runner Runner, p Params) (*Result, error) {
if err := validateFailClosed(policy); err != nil {
return nil, err // belt-and-suspenders: never POST an open policy
}
// 3. When the real runner can verify the token, do that read-only Cloudflare API
// check before creating tunnels or DNS records. It catches expired/invalid
// tokens earlier; Access account/permission failures can still surface on the
// Access app/policy calls below.
if verifier, ok := runner.(apiTokenVerifier); ok {
if err := verifier.VerifyAPIToken(ctx); err != nil {
return nil, fmt.Errorf("cfsetup: verify Cloudflare API token: %w", err)
}
}
hostnames := webHostnames(p)
origin := p.PanelOrigin
@@ -392,18 +405,18 @@ func Setup(ctx context.Context, runner Runner, p Params) (*Result, error) {
origin = defaultPanelOrigin
}
// 3. Create the tunnel.
// 4. Create the tunnel.
id, cred, err := runner.CreateTunnel(ctx, p.TunnelName)
if err != nil {
return nil, fmt.Errorf("cfsetup: create tunnel: %w", err)
}
// 4. Route DNS for each WEB hostname only (the game host stays off the tunnel).
// 5. Route DNS for each WEB hostname only (the game host stays off the tunnel).
for _, h := range hostnames {
if err := runner.RouteDNS(ctx, id, h); err != nil {
return nil, fmt.Errorf("cfsetup: route dns %s: %w", h, err)
}
}
// 5. Render and persist the ingress config.
// 6. Render and persist the ingress config.
cfgBytes, err := BuildTunnelConfig(id, cred, origin, hostnames)
if err != nil {
return nil, err
@@ -413,13 +426,13 @@ func Setup(ctx context.Context, runner Runner, p Params) (*Result, error) {
return nil, fmt.Errorf("cfsetup: write config: %w", err)
}
}
// 6. Front the admin face with a self-hosted Access app.
// 7. Front the admin face with a self-hosted Access app.
app := BuildAccessApplication(p.AdminHostname, "Felis SysAdmin Console", p.SessionDuration, p.AllowedIdPs)
appID, aud, err := runner.CreateAccessApplication(ctx, app)
if err != nil {
return nil, fmt.Errorf("cfsetup: create access application: %w", err)
}
// 7. Attach the guarded fail-closed policy.
// 8. Attach the guarded fail-closed policy.
if err := runner.CreateAccessPolicy(ctx, appID, policy); err != nil {
return nil, fmt.Errorf("cfsetup: create access policy: %w", err)
}
+33
View File
@@ -35,6 +35,17 @@ type recordingRunner struct {
aud string
}
type verifyingRunner struct {
recordingRunner
verified bool
verifyErr error
}
func (r *verifyingRunner) VerifyAPIToken(_ context.Context) error {
r.verified = true
return r.verifyErr
}
func (r *recordingRunner) CreateTunnel(_ context.Context, name string) (string, string, error) {
r.calls = append(r.calls, "CreateTunnel:"+name)
id := r.tunnelID
@@ -249,6 +260,28 @@ func TestSetupGatingHasNoSideEffects(t *testing.T) {
// every precondition met, an empty AccessIdentity (which would yield a public
// policy) aborts Setup BEFORE any tunnel/DNS/app is created. The fail-closed guard
// is wired into the orchestrator, not merely a standalone helper.
func TestSetupVerifiesAPITokenBeforeCloudflareMutations(t *testing.T) {
tokenErr := errors.New("token inactive")
runner := &verifyingRunner{verifyErr: tokenErr}
p := Params{
PanelHostname: "console." + testRoot,
AdminHostname: "op.console." + testRoot,
TunnelName: "felis",
AccessIdentity: AccessIdentity{Emails: []string{"[email protected]"}},
Pre: goodPreconditions(),
}
_, err := Setup(context.Background(), runner, p)
if !errors.Is(err, tokenErr) {
t.Fatalf("err = %v, want token verifier error", err)
}
if !runner.verified {
t.Fatal("Setup did not verify the API token")
}
if len(runner.calls) != 0 {
t.Fatalf("token verification failure made Cloudflare mutations: %v", runner.calls)
}
}
func TestSetupRefusesUnscopedPolicyBeforeSideEffects(t *testing.T) {
runner := &recordingRunner{}
p := Params{