mirror of
https://github.com/PrivateCaptcha/PrivateCaptcha.git
synced 2026-05-13 00:08:34 -05:00
Protect maintenance job endpoints. closes PrivateCaptcha/issues#178
This commit is contained in:
+3
-2
@@ -217,6 +217,7 @@ func run(ctx context.Context, cfg common.ConfigStore, stderr io.Writer, listener
|
||||
CheckInterval: cfg.Get(common.HealthCheckIntervalKey),
|
||||
Metrics: metrics,
|
||||
}
|
||||
jobs := maintenance.NewJobs(businessDB)
|
||||
|
||||
updateConfigFunc := func(ctx context.Context) {
|
||||
cfg.Update(ctx)
|
||||
@@ -225,6 +226,7 @@ func run(ctx context.Context, cfg common.ConfigStore, stderr io.Writer, listener
|
||||
businessDB.UpdateConfig(maintenanceMode)
|
||||
timeSeriesDB.UpdateConfig(maintenanceMode)
|
||||
portalServer.UpdateConfig(ctx, cfg)
|
||||
jobs.UpdateConfig(cfg)
|
||||
verboseLogs := config.AsBool(cfg.Get(common.VerboseKey))
|
||||
common.SetLogLevel(logLevel, verboseLogs)
|
||||
}
|
||||
@@ -308,7 +310,6 @@ func run(ctx context.Context, cfg common.ConfigStore, stderr io.Writer, listener
|
||||
}()
|
||||
|
||||
// start maintenance jobs
|
||||
jobs := maintenance.NewJobs(businessDB)
|
||||
jobs.Add(healthCheck)
|
||||
jobs.Add(&maintenance.SessionsCleanupJob{
|
||||
Session: portalServer.Sessions,
|
||||
@@ -372,7 +373,7 @@ func run(ctx context.Context, cfg common.ConfigStore, stderr io.Writer, listener
|
||||
if localAddress := cfg.Get(common.LocalAddressKey).Value(); len(localAddress) > 0 {
|
||||
localRouter := http.NewServeMux()
|
||||
metrics.Setup(localRouter)
|
||||
jobs.Setup(localRouter)
|
||||
jobs.Setup(localRouter, cfg)
|
||||
localRouter.Handle(http.MethodGet+" /"+common.LiveEndpoint, common.Recovered(http.HandlerFunc(healthCheck.LiveHandler)))
|
||||
localRouter.Handle(http.MethodGet+" /"+common.ReadyEndpoint, common.Recovered(http.HandlerFunc(healthCheck.ReadyHandler)))
|
||||
localServer = &http.Server{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
STAGE=dev
|
||||
PC_LOCAL_ADDRESS=localhost:9090
|
||||
PC_LOCAL_API_KEY=BkdIDPmLPg6MDRbrr1TDLKMkhy
|
||||
PC_PORTAL_BASE_URL=portal.privatecaptcha.local
|
||||
PC_API_BASE_URL=api.privatecaptcha.local
|
||||
PC_CDN_BASE_URL=cdn.privatecaptcha.local
|
||||
|
||||
@@ -39,6 +39,7 @@ const (
|
||||
UserFingerprintIVKey
|
||||
APISaltKey
|
||||
EnterpriseLicenseKeyKey
|
||||
LocalAPIKeyKey
|
||||
// Add new fields _above_
|
||||
COMMON_CONFIG_KEYS_COUNT
|
||||
)
|
||||
|
||||
@@ -60,6 +60,7 @@ func init() {
|
||||
configKeyToEnvName[common.EmailFromKey] = "PC_EMAIL_FROM"
|
||||
configKeyToEnvName[common.ReplyToEmailKey] = "PC_REPLY_TO_EMAIL"
|
||||
configKeyToEnvName[common.LocalAddressKey] = "PC_LOCAL_ADDRESS"
|
||||
configKeyToEnvName[common.LocalAPIKeyKey] = "PC_LOCAL_API_KEY"
|
||||
configKeyToEnvName[common.MaintenanceModeKey] = "PC_MAINTENANCE_MODE"
|
||||
configKeyToEnvName[common.RegistrationAllowedKey] = "PC_REGISTRATION_ALLOWED"
|
||||
configKeyToEnvName[common.HealthCheckIntervalKey] = "PC_HEALTHCHECK_INTERVAL"
|
||||
|
||||
+37
-3
@@ -28,6 +28,7 @@ type jobs struct {
|
||||
oneOffJobs []common.OneOffJob
|
||||
maintenanceCancel context.CancelFunc
|
||||
maintenanceCtx context.Context
|
||||
apiKey string
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
@@ -71,10 +72,43 @@ func (j *jobs) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jobs) Setup(mux *http.ServeMux) {
|
||||
func (j *jobs) UpdateConfig(cfg common.ConfigStore) {
|
||||
j.apiKey = cfg.Get(common.LocalAPIKeyKey).Value()
|
||||
}
|
||||
|
||||
func (j *jobs) Setup(mux *http.ServeMux, cfg common.ConfigStore) {
|
||||
j.apiKey = cfg.Get(common.LocalAPIKeyKey).Value()
|
||||
|
||||
const maxBytes = 256 * 1024
|
||||
mux.Handle(http.MethodPost+" /maintenance/periodic/{job}", common.Recovered(http.MaxBytesHandler(http.HandlerFunc(j.handlePeriodicJob), maxBytes)))
|
||||
mux.Handle(http.MethodPost+" /maintenance/oneoff/{job}", common.Recovered(http.MaxBytesHandler(http.HandlerFunc(j.handleOneoffJob), maxBytes)))
|
||||
mux.Handle(http.MethodPost+" /maintenance/periodic/{job}", common.Recovered(http.MaxBytesHandler(j.security(http.HandlerFunc(j.handlePeriodicJob)), maxBytes)))
|
||||
mux.Handle(http.MethodPost+" /maintenance/oneoff/{job}", common.Recovered(http.MaxBytesHandler(j.security(http.HandlerFunc(j.handleOneoffJob)), maxBytes)))
|
||||
}
|
||||
|
||||
func (j *jobs) security(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if len(j.apiKey) == 0 {
|
||||
slog.WarnContext(ctx, "Endpoint is not allowed without a configured API key")
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
secret := r.Header.Get(common.HeaderAPIKey)
|
||||
if len(secret) == 0 {
|
||||
slog.WarnContext(ctx, "Request API key is empty")
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if secret != j.apiKey {
|
||||
slog.WarnContext(ctx, "Request API key does not match", "value", secret)
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (j *jobs) handlePeriodicJob(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user