◐ Off-By-One · answer catalog

go-auth-default-bootstrap-token

2 answer(s)godockergodocker

go-auth-default-bootstrap-token

📦 Source in repository (JSON)

Answer 1

GAP-019 required three changes to an auth-gated Go API. Implemented at ~/go-auth-default-bootstrap-token/ (module go-auth-default-bootstrap-token, stdlib only):

1. --require-auth now defaults to true (main.go):

// GAP-019: the API must be authenticated by default. Exported as a var so
// tests can assert the default; do not flip this back to false.
var defaultRequireAuth = true

requireAuth = flag.Bool("require-auth", defaultRequireAuth,
    "require authentication for /v1/ and /api/ routes (GAP-019: default is now true)")

2. First-run bootstrap token (iam/bootstrap.go) — generates a 32-hex token only when <data-dir>/iam/principals.yaml is missing; subsequent runs load the persisted token and never regenerate (unless --regenerate-token):

func EnsureBootstrap(dataDir string, force bool) (*Result, error) {
    iamDir := filepath.Join(dataDir, "iam")
    principalsPath := filepath.Join(iamDir, "principals.yaml")
    policiesPath := filepath.Join(iamDir, "policies.yaml")

    if !force {
        if tok, err := loadToken(principalsPath); err == nil {   // rerun: reuse, no regen
            return &Result{Token: tok, Created: false, IAMDir: iamDir}, nil
        }
    }
    tok, err := generateToken() // 16 random bytes → 32 lowercase hex chars
    ...
    if err := writeIAMFiles(iamDir, principalsPath, policiesPath, tok); err != nil {
        return nil, err
    }
    return &Result{Token: tok, Created: true, IAMDir: iamDir}, nil
}

Files are written atomically (temp file + rename, mode 0600) so a crash mid-write can't leave a half-written principals.yaml that would trigger an unwanted regen. On Created=true the token is printed to stdout once, with --print-token as the ops escape hatch to recover it, and --regenerate-token for explicit rotation.

3. IAMAuth path exemption (iam/iam.go) — only /v1/... and /api/... are gated; everything else (healthchecks, SPA) stays open:

func Middleware(requireAuth bool, token string, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !requireAuth || !IsAPIPath(r.URL.Path) { // escape hatch: non-API paths stay open
            next.ServeHTTP(w, r)
            return
        }
        if !validBearer(r, token) { // constant-time compare via crypto/subtle
            w.Header().Set("WWW-Authenticate", `Bearer realm="api", error="invalid_token"`)
            http.Error(w, "unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func IsAPIPath(p string) bool {
    if i := strings.IndexByte(p, '?'); i >= 0 { p = p[:i] }
    if p == "/v1" || strings.HasPrefix(p, "/v1/") { return true }
    return p == "/api" || strings.HasPrefix(p, "/api/")
}

Routes: /healthz (open), / catch-all SPA (open; authenticated-but-unknown /v1|x//api|x → 404, not SPA fallthrough), /v1/status + /api/status (bearer-gated).

Evidence & signatures

**Unit tests (14, all passing, `go vet` clean):** bootstrap creates `0600` files + valid 32-hex token; no regen on rerun (token identical); force regenerates; operator-edited token is picked up; deleted `iam/` dir regenerates; format validation; middleware 401 anonymous / 200 with token / 401 wrong token & bad scheme (incl. lowercase `bearer`) / health + SPA + `/v1x`-style paths open / `--require-auth=false` anonymous 200 / `IsAPIPath` boundaries (incl. query strings); end-to-end matrix; default-flag regression guard.

**Live verification with the real binary** (all four required checks reproduced, then cleaned up):

```
401-anonymous       /v1/status      -> 401      ✓ (was anonymous before GAP-019)
200-token           /v1/status      -> 200      ✓
200-health          /healthz        -> 200      ✓ (exempt)
200-escape-hatch-SPA /               -> 200      ✓ (exempt, SPA stays open)
```

Plus: `/api/status` 401/200 ✓, wrong token 401 ✓, `/index.html` + `/static/app.js` open ✓, **no regen on rerun** (0 bootstrap banners, `--print-token` returned the identical token across runs) ✓, files persisted `-rw-------` with both YAMLs ✓, `--require-auth=false` → anonymous API 200 (opt-in escape hatch) ✓, `--regenerate-token` rotates ✓.

**Edge cases caught during verification:** an unrelated "Helios" gateway service already bound to port 18080 silently hijacked every curl with its own 401s — I moved `net.Listen` before the "listening" log so bind failures are loud, and re-verified on a free port.
{"model": "deepseek-v4-flash", "problem_class": "go-auth-default-bootstrap-token", "result": "passed", "tests": 14}

Answer 2

GAP-019 required three changes to an auth-gated Go API. Implemented at ~/go-auth-default-bootstrap-token/ (module go-auth-default-bootstrap-token, stdlib only):

1. --require-auth now defaults to true (main.go):

// GAP-019: the API must be authenticated by default. Exported as a var so
// tests can assert the default; do not flip this back to false.
var defaultRequireAuth = true

requireAuth = flag.Bool("require-auth", defaultRequireAuth,
    "require authentication for /v1/ and /api/ routes (GAP-019: default is now true)")

2. First-run bootstrap token (iam/bootstrap.go) — generates a 32-hex token only when <data-dir>/iam/principals.yaml is missing; subsequent runs load the persisted token and never regenerate (unless --regenerate-token):

func EnsureBootstrap(dataDir string, force bool) (*Result, error) {
    iamDir := filepath.Join(dataDir, "iam")
    principalsPath := filepath.Join(iamDir, "principals.yaml")
    policiesPath := filepath.Join(iamDir, "policies.yaml")

    if !force {
        if tok, err := loadToken(principalsPath); err == nil {   // rerun: reuse, no regen
            return &Result{Token: tok, Created: false, IAMDir: iamDir}, nil
        }
    }
    tok, err := generateToken() // 16 random bytes → 32 lowercase hex chars
    ...
    if err := writeIAMFiles(iamDir, principalsPath, policiesPath, tok); err != nil {
        return nil, err
    }
    return &Result{Token: tok, Created: true, IAMDir: iamDir}, nil
}

Files are written atomically (temp file + rename, mode 0600) so a crash mid-write can't leave a half-written principals.yaml that would trigger an unwanted regen. On Created=true the token is printed to stdout once, with --print-token as the ops escape hatch to recover it, and --regenerate-token for explicit rotation.

3. IAMAuth path exemption (iam/iam.go) — only /v1/... and /api/... are gated; everything else (healthchecks, SPA) stays open:

func Middleware(requireAuth bool, token string, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !requireAuth || !IsAPIPath(r.URL.Path) { // escape hatch: non-API paths stay open
            next.ServeHTTP(w, r)
            return
        }
        if !validBearer(r, token) { // constant-time compare via crypto/subtle
            w.Header().Set("WWW-Authenticate", `Bearer realm="api", error="invalid_token"`)
            http.Error(w, "unauthorized", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func IsAPIPath(p string) bool {
    if i := strings.IndexByte(p, '?'); i >= 0 { p = p[:i] }
    if p == "/v1" || strings.HasPrefix(p, "/v1/") { return true }
    return p == "/api" || strings.HasPrefix(p, "/api/")
}

Routes: /healthz (open), / catch-all SPA (open; authenticated-but-unknown /v1|x//api|x → 404, not SPA fallthrough), /v1/status + /api/status (bearer-gated).

Evidence & signatures

**Unit tests (14, all passing, `go vet` clean):** bootstrap creates `0600` files + valid 32-hex token; no regen on rerun (token identical); force regenerates; operator-edited token is picked up; deleted `iam/` dir regenerates; format validation; middleware 401 anonymous / 200 with token / 401 wrong token & bad scheme (incl. lowercase `bearer`) / health + SPA + `/v1x`-style paths open / `--require-auth=false` anonymous 200 / `IsAPIPath` boundaries (incl. query strings); end-to-end matrix; default-flag regression guard.

**Live verification with the real binary** (all four required checks reproduced, then cleaned up):

```
401-anonymous       /v1/status      -> 401      ✓ (was anonymous before GAP-019)
200-token           /v1/status      -> 200      ✓
200-health          /healthz        -> 200      ✓ (exempt)
200-escape-hatch-SPA /               -> 200      ✓ (exempt, SPA stays open)
```

Plus: `/api/status` 401/200 ✓, wrong token 401 ✓, `/index.html` + `/static/app.js` open ✓, **no regen on rerun** (0 bootstrap banners, `--print-token` returned the identical token across runs) ✓, files persisted `-rw-------` with both YAMLs ✓, `--require-auth=false` → anonymous API 200 (opt-in escape hatch) ✓, `--regenerate-token` rotates ✓.

**Edge cases caught during verification:** an unrelated "Helios" gateway service already bound to port 18080 silently hijacked every curl with its own 401s — I moved `net.Listen` before the "listening" log so bind failures are loud, and re-verified on a free port.
{"model": "deepseek-v4-flash", "problem_class": "go-auth-default-bootstrap-token", "result": "passed", "tests": 14}
Generated from the verified corpus · MIT licensedBack to the catalog