diff --git a/cmd/api/gen_warning_test.go b/cmd/api/gen_warning_test.go new file mode 100644 index 00000000..52bd07b7 --- /dev/null +++ b/cmd/api/gen_warning_test.go @@ -0,0 +1,38 @@ +package api + +import ( + "testing" + + "github.com/go-admin-team/go-admin-core/v2/sdk/config" +) + +// The warning fires exactly where the generator's writing endpoints are served +// and nothing else refuses them. +// +// dev is the case the warning exists for: it is the shipped default, so it is +// the mode a deployment that changed nothing is running in. demo serves the +// routes too, but DemoEvn refuses all three by name, so warning there would +// describe an exposure that is not there. +func TestGeneratorWriteRoutesWarningFiresWhereTheExposureIs(t *testing.T) { + for _, tc := range []struct { + mode string + want bool + why string + }{ + {"dev", true, "shipped default, endpoints served and not refused"}, + {"demo", false, "served, but DemoEvn refuses all three"}, + {"test", false, "not served"}, + {"prod", false, "not served"}, + {"", false, "not served"}, + } { + t.Run("mode="+tc.mode, func(t *testing.T) { + previous := config.ApplicationConfig.Mode + t.Cleanup(func() { config.ApplicationConfig.Mode = previous }) + config.ApplicationConfig.Mode = tc.mode + + if got := generatorWriteRoutesNeedWarning(); got != tc.want { + t.Errorf("mode %q: warning = %v, want %v (%s)", tc.mode, got, tc.want, tc.why) + } + }) + } +} diff --git a/cmd/api/server.go b/cmd/api/server.go index e084e49b..7d000d48 100644 --- a/cmd/api/server.go +++ b/cmd/api/server.go @@ -177,6 +177,7 @@ func run() error { gin.SetMode(gin.ReleaseMode) } buildRouter() + reportGeneratorWriteRoutes() srv := &http.Server{ Addr: fmt.Sprintf("%s:%d", config.ApplicationConfig.Host, config.ApplicationConfig.Port), @@ -356,6 +357,37 @@ const ( kubernetesGraceSeconds = 30 ) +// reportGeneratorWriteRoutes says whether this process serves the code +// generator's writing endpoints, and to whom. +// +// The endpoints are gated on the mode, and the shipped configuration says dev - +// so the deployment most likely to be exposed is the one that changed nothing, +// and the one least likely to go looking. Silence there would leave the gate +// technically correct and practically useless. +// +// Nothing is said in demo mode. The routes are registered, but DemoEvn refuses +// all three by name, so a warning would describe an exposure that is not there. +func reportGeneratorWriteRoutes() { + if !generatorWriteRoutesNeedWarning() { + return + } + log.Warnf("the code generator's writing endpoints are served in mode %q: "+ + "/api/v1/gen/{toproject,apitofile,todb} write Go and Vue source onto this host and rows "+ + "into this database, and they are in CasbinExclude, so any account that can log in may "+ + "call them. Set application.mode to prod or test on anything that is not a workstation.", + config.ApplicationConfig.Mode) +} + +// generatorWriteRoutesNeedWarning reports whether there is an exposure to warn +// about: the endpoints are served, and nothing else is refusing them. +// +// Split from the logging so the decision can be tested. A warning nobody can +// make fire is indistinguishable from no warning at all, and this one exists +// precisely for the case nobody is looking at. +func generatorWriteRoutesNeedWarning() bool { + return otherrouter.GenWriteRoutesEnabled() && config.ApplicationConfig.Mode != "demo" +} + // reportShutdownBudget states what a shutdown will spend and whether it fits. // // The sum is taken from the resolved values, not from the configuration file: