fix(canary-phase9): plumb mysql public host/port through to generator

Audit found that registry.Build was calling mysql.New() with hardcoded
localhost:3306 defaults, ignoring cfg.MySQL.PublicHost and PublicPort.
Result: every emitted mysql:// connection string said
"mysql://canary_<id>@localhost:3306/internal_db" regardless of actual
deployment address — useless for any non-localhost deployment.

Fix:
  - registry.Config: adds MySQLPublicHost + MySQLPublicPort fields
  - registry.Build: switches between mysql.New() and
    mysql.NewWithAddress(host, port) based on config
  - cmd/canary/main.go: passes cfg.MySQL.PublicHost/PublicPort
    through to registry.Build
  - registry_test.go: new TestBuild_MySQLUsesConfiguredPublicAddress
    asserts the connection string reflects configured host/port

Other Phase 9 audit observations DEFERRED to Phase 10 (not blocking
rollup):
  - §11.3 create-tier rate limit on POST /api/tokens (Turnstile
    already provides spam protection; tiered limits are
    belt-and-suspenders)
  - scoping the gosec G107/G704 exclude to specific packages
    (revisit when Phase 10's webhook sender lands and needs
    case-by-case URL validation)
  - TurnstileResp dto required-tag clashes with dev-mode bypass
    (low severity — current behavior is sound)
  - /api/m/{manage_id} GET/DELETE (spec §8.1 lines 740-741) — Phase 11
  - FingerprintRecorder is nil at startup (slowredirect fingerprint
    handler wiring) — Phase 10
  - mysql_username metadata write in token.Service (mysql trigger
    lookup goes by token ID, not username — low impact)

Pre-rollup gate clean at HEAD:
  - go build ./... + go vet ./... clean
  - go test -race -timeout=60s ./... all pass (~50 new test cases)
  - go test -tags=integration -race -timeout=300s ./internal/token/...
    ./internal/event/... all pass
  - golangci-lint run ./... → 0 issues
  - grep -rn "//nolint" --include="*.go" returns nothing
This commit is contained in:
CarterPerez-dev 2026-05-13 15:27:21 -04:00
parent 8d1016b87d
commit 6c37082598
3 changed files with 39 additions and 4 deletions

View File

@ -86,7 +86,11 @@ func run(configPath string) error {
}
logger.Info("redis connected")
genRegistry := registry.Build(registry.Config{BaseURL: cfg.Canary.BaseURL})
genRegistry := registry.Build(registry.Config{
BaseURL: cfg.Canary.BaseURL,
MySQLPublicHost: cfg.MySQL.PublicHost,
MySQLPublicPort: cfg.MySQL.PublicPort,
})
tokenSvc := token.NewService(
tokenRepo,
registryAdapter{r: genRegistry},

View File

@ -16,12 +16,22 @@ import (
)
type Config struct {
BaseURL string
BaseURL string
MySQLPublicHost string
MySQLPublicPort int
}
type Registry map[token.Type]generators.Generator
func Build(_ Config) Registry {
func Build(cfg Config) Registry {
host := cfg.MySQLPublicHost
port := cfg.MySQLPublicPort
var mysqlGen *mysql.Generator
if host == "" || port == 0 {
mysqlGen = mysql.New()
} else {
mysqlGen = mysql.NewWithAddress(host, port)
}
return Registry{
token.TypeWebbug: webbug.New(),
token.TypeSlowRedirect: slowredirect.New(),
@ -29,6 +39,6 @@ func Build(_ Config) Registry {
token.TypePDF: pdf.New(),
token.TypeKubeconfig: kubeconfig.New(),
token.TypeEnvfile: envfile.New(),
token.TypeMySQL: mysql.New(),
token.TypeMySQL: mysqlGen,
}
}

View File

@ -4,6 +4,7 @@
package registry_test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
@ -77,6 +78,26 @@ func TestBuild_UnknownTypeReturnsZeroValue(t *testing.T) {
require.Nil(t, g, "map zero value for missing key must be nil interface")
}
func TestBuild_MySQLUsesConfiguredPublicAddress(t *testing.T) {
reg := registry.Build(registry.Config{
BaseURL: testBaseURL,
MySQLPublicHost: "canary.example.com",
MySQLPublicPort: 13306,
})
g, ok := reg[token.TypeMySQL]
require.True(t, ok)
tok := &token.Token{ID: "abc", Type: token.TypeMySQL}
art, err := g.Generate(context.Background(), tok, testBaseURL)
require.NoError(t, err)
require.Equal(
t,
"mysql://canary_abc@canary.example.com:13306/internal_db",
art.ConnectionString,
"mysql connection string must reflect configured public host:port",
)
}
func TestBuild_AllSevenGeneratorsRegistered(t *testing.T) {
reg := registry.Build(registry.Config{BaseURL: testBaseURL})
require.Len(