All checks were successful
continuous-integration/drone/push Build is passing
Probes 20 known-bad paths (/.env, /.git/config, /actuator/env, etc.) that CT-log scanners hit immediately after a new certificate is issued. Critical credential/source-leak paths raise StatusCrit; other exposed paths raise StatusWarn; 401/403 responses raise StatusInfo. Fixes: #1
81 lines
2.5 KiB
Go
81 lines
2.5 KiB
Go
// This file is part of the happyDomain (R) project.
|
|
// Copyright (c) 2020-2026 happyDomain
|
|
// Authors: Pierre-Olivier Mercier, et al.
|
|
|
|
package checker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
func init() { RegisterRule(&honeypotRule{}) }
|
|
|
|
// honeypotRule reports any honeypot path that returned a non-404/410
|
|
// response, signalling a potentially exposed sensitive endpoint.
|
|
type honeypotRule struct{}
|
|
|
|
func (r *honeypotRule) Name() string { return "http.honeypot" }
|
|
func (r *honeypotRule) Description() string {
|
|
return "Reports sensitive paths (/.env, /.git/config, /actuator/env, …) that are reachable from the internet."
|
|
}
|
|
|
|
func (r *honeypotRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState {
|
|
data, errSt := loadHTTPData(ctx, obs)
|
|
if errSt != nil {
|
|
return []sdk.CheckState{*errSt}
|
|
}
|
|
hp, ok, err := LoadExtension[HoneypotData](data, ObservationKeyHoneypot)
|
|
if err != nil {
|
|
return []sdk.CheckState{{Status: sdk.StatusError, Code: "http.honeypot.decode_error", Message: err.Error()}}
|
|
}
|
|
if !ok {
|
|
return []sdk.CheckState{unknownState("http.honeypot.no_data", "Honeypot collector did not run.")}
|
|
}
|
|
|
|
var states []sdk.CheckState
|
|
successfulProbes := 0
|
|
for path, probe := range hp.Probes {
|
|
if probe.Error != "" || probe.StatusCode == 0 {
|
|
continue
|
|
}
|
|
successfulProbes++
|
|
|
|
st := sdk.CheckState{
|
|
Subject: path,
|
|
Meta: map[string]any{"url": probe.URL, "status_code": probe.StatusCode},
|
|
}
|
|
switch {
|
|
case probe.StatusCode == 200 || (probe.StatusCode >= 301 && probe.StatusCode <= 308):
|
|
st.Status = sdk.StatusWarn
|
|
st.Code = "http.honeypot.exposed"
|
|
st.Message = fmt.Sprintf("%s is accessible (HTTP %d, %d bytes).", path, probe.StatusCode, probe.Bytes)
|
|
if probe.Critical {
|
|
st.Status = sdk.StatusCrit
|
|
st.Code = "http.honeypot.critical_exposed"
|
|
}
|
|
case probe.StatusCode == 401 || probe.StatusCode == 403:
|
|
st.Status = sdk.StatusInfo
|
|
st.Code = "http.honeypot.protected"
|
|
st.Message = fmt.Sprintf("%s exists but is access-controlled (HTTP %d).", path, probe.StatusCode)
|
|
default:
|
|
continue
|
|
}
|
|
states = append(states, st)
|
|
}
|
|
|
|
if successfulProbes == 0 {
|
|
return []sdk.CheckState{unknownState("http.honeypot.no_response", "All honeypot probes failed or timed out.")}
|
|
}
|
|
if len(states) == 0 {
|
|
return []sdk.CheckState{{
|
|
Status: sdk.StatusOK,
|
|
Code: "http.honeypot.clean",
|
|
Subject: data.Domain,
|
|
Message: "No sensitive honeypot paths are reachable.",
|
|
}}
|
|
}
|
|
return states
|
|
}
|