All checks were successful
continuous-integration/drone/push Build is passing
The probe stops at scheme-changing redirects, so FinalURL keeps the pre-redirect http:// URL while the chain records the real https:// target. Prefer the last redirect hop's destination so a correct http→https redirect is no longer mis-flagged as no_https_redirect.
74 lines
2.6 KiB
Go
74 lines
2.6 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"
|
|
"net/url"
|
|
"strings"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
func init() { RegisterRule(&httpsRedirectRule{}) }
|
|
|
|
// httpsRedirectRule verifies that plain HTTP either redirects to HTTPS or
|
|
// fails (which is acceptable when HTTP is intentionally not served).
|
|
type httpsRedirectRule struct{}
|
|
|
|
func (r *httpsRedirectRule) Name() string { return "http.https_redirect" }
|
|
func (r *httpsRedirectRule) Description() string {
|
|
return "Plain HTTP responses must redirect to an HTTPS URL on the same host."
|
|
}
|
|
|
|
func (r *httpsRedirectRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
|
data, errSt := loadHTTPData(ctx, obs)
|
|
if errSt != nil {
|
|
return []sdk.CheckState{*errSt}
|
|
}
|
|
require := sdk.GetBoolOption(opts, OptionRequireHTTPS, true)
|
|
|
|
const okMsg = "HTTP redirects to HTTPS on every reachable IP."
|
|
return EvalAggregateByScheme(data, "http", "http.https_redirect", okMsg, func(p HTTPProbe, emit func(sdk.CheckState)) {
|
|
if !p.TCPConnected || p.StatusCode == 0 {
|
|
// Reachability rule handles this; an HTTP server that is
|
|
// simply not running is fine for redirect-purposes.
|
|
return
|
|
}
|
|
// Prefer the last redirect hop's destination over FinalURL. When the
|
|
// probe stops at a scheme-changing redirect (http→https), it does not
|
|
// follow the hop, so FinalURL stays the pre-redirect http:// URL while
|
|
// the chain records the real https:// target. Judging by FinalURL alone
|
|
// would mis-flag a correct http→https redirect as no_https_redirect.
|
|
final := p.FinalURL
|
|
if len(p.RedirectChain) > 0 {
|
|
final = p.RedirectChain[len(p.RedirectChain)-1].To
|
|
}
|
|
isHTTPS := false
|
|
if u, err := url.Parse(final); err == nil {
|
|
isHTTPS = strings.EqualFold(u.Scheme, "https")
|
|
}
|
|
switch {
|
|
case isHTTPS:
|
|
// Good. Aggregated below as a single OK.
|
|
case require:
|
|
emit(sdk.CheckState{
|
|
Status: sdk.StatusWarn,
|
|
Code: "http.no_https_redirect",
|
|
Subject: p.Address,
|
|
Message: fmt.Sprintf("HTTP response on %s did not redirect to HTTPS (final URL: %s, status %d)", p.Address, final, p.StatusCode),
|
|
Meta: map[string]any{"fix": "Configure your web server to redirect every plain-HTTP request to https://."},
|
|
})
|
|
default:
|
|
emit(sdk.CheckState{
|
|
Status: sdk.StatusInfo,
|
|
Code: "http.plain_http_served",
|
|
Subject: p.Address,
|
|
Message: fmt.Sprintf("HTTP responded directly without redirect (status %d)", p.StatusCode),
|
|
})
|
|
}
|
|
})
|
|
}
|