Compare commits

..

3 Commits

6 changed files with 43 additions and 38 deletions

View File

@@ -745,6 +745,9 @@ endpoints:
- "[STATUS] == 200"
```
> ⚠️ **WARNING**:: Tunneling may introduce additional latency, especially if the connection to the tunnel is retried frequently.
> This may lead to inaccurate response time measurements.
### Alerting
Gatus supports multiple alerting providers, such as Slack and PagerDuty, and supports different alerts for each

View File

@@ -575,11 +575,8 @@ func ValidateUniqueKeys(config *Config) error {
func ValidateSecurityConfig(config *Config) error {
if config.Security != nil {
if config.Security.ValidateAndSetDefaults() {
if !config.Security.ValidateAndSetDefaults() {
logr.Debug("[config.ValidateSecurityConfig] Basic security configuration has been validated")
} else {
// If there was an attempt to configure security, then it must mean that some confidential or private
// data are exposed. As a result, we'll force a panic because it's better to be safe than sorry.
return ErrInvalidSecurityConfig
}
}

View File

@@ -31,17 +31,17 @@ var (
//
// Uses UTC by default.
type Config struct {
Enabled *bool `yaml:"enabled"` // Whether the maintenance period is enabled. Enabled by default if nil.
Start string `yaml:"start"` // Time at which the maintenance period starts (e.g. 23:00)
Duration time.Duration `yaml:"duration"` // Duration of the maintenance period (e.g. 4h)
Timezone string `yaml:"timezone"` // Timezone in string format which the maintenance period is configured (e.g. America/Sao_Paulo)
Enabled *bool `yaml:"enabled"` // Whether the maintenance period is enabled. Enabled by default if nil.
Start string `yaml:"start,omitempty"` // Time at which the maintenance period starts (e.g. 23:00)
Duration time.Duration `yaml:"duration,omitempty"` // Duration of the maintenance period (e.g. 4h)
Timezone string `yaml:"timezone,omitempty"` // Timezone in string format which the maintenance period is configured (e.g. America/Sao_Paulo)
// Every is a list of days of the week during which maintenance period applies.
// See longDayNames for list of valid values.
// Every day if empty.
Every []string `yaml:"every"`
Every []string `yaml:"every,omitempty"`
TimezoneLocation *time.Location // Timezone in location format which the maintenance period is configured
timezoneLocation *time.Location
durationToStartFromMidnight time.Duration
}
@@ -90,13 +90,13 @@ func (c *Config) ValidateAndSetDefaults() error {
return errInvalidMaintenanceDuration
}
if c.Timezone != "" {
c.TimezoneLocation, err = time.LoadLocation(c.Timezone)
c.timezoneLocation, err = time.LoadLocation(c.Timezone)
if err != nil {
return fmt.Errorf("%w: %w", errInvalidTimezone, err)
}
} else {
c.Timezone = "UTC"
c.TimezoneLocation = time.UTC
c.timezoneLocation = time.UTC
}
return nil
}
@@ -107,8 +107,8 @@ func (c *Config) IsUnderMaintenance() bool {
return false
}
now := time.Now()
if c.TimezoneLocation != nil {
now = now.In(c.TimezoneLocation)
if c.timezoneLocation != nil {
now = now.In(c.timezoneLocation)
}
adjustedDate := now.Day()
if now.Hour() < int(c.durationToStartFromMidnight.Hours()) {

View File

@@ -54,7 +54,6 @@ func New(config *Config) *SSHTunnel {
tunnel := &SSHTunnel{
config: config,
}
// Parse authentication methods once during initialization to avoid
// expensive cryptographic operations on every connection attempt
if config.PrivateKey != "" {
@@ -66,7 +65,6 @@ func New(config *Config) *SSHTunnel {
} else if config.Password != "" {
tunnel.authMethods = []ssh.AuthMethod{ssh.Password(config.Password)}
}
return tunnel
}
@@ -131,27 +129,34 @@ func (t *SSHTunnel) Dial(network, addr string) (net.Conn, error) {
client = t.client
t.mu.Unlock()
}
// Create connection through SSH tunnel
conn, err := client.Dial(network, addr)
if err != nil {
// Close stale connection before retry to prevent leak
t.mu.Lock()
if t.client != nil {
t.client.Close()
t.client = nil
// Attempt dial with exponential backoff retry
const maxRetries = 3
const baseDelay = 500 * time.Millisecond
var lastErr error
for attempt := 0; attempt < maxRetries; attempt++ {
if attempt > 0 {
// Exponential backoff: 500ms, 1s, 2s
delay := baseDelay << (attempt - 1)
time.Sleep(delay)
// Close stale connection and reconnect
t.mu.Lock()
if t.client != nil {
_ = t.client.Close()
t.client = nil
}
if err := t.connectUnsafe(); err != nil {
t.mu.Unlock()
lastErr = fmt.Errorf("reconnect attempt %d failed: %w", attempt, err)
continue
}
client = t.client
t.mu.Unlock()
}
t.mu.Unlock()
// Retry once - connection might be stale
if connErr := t.Connect(); connErr != nil {
return nil, fmt.Errorf("SSH tunnel dial failed: %w (retry failed: %v)", err, connErr)
}
t.mu.RLock()
client = t.client
t.mu.RUnlock()
conn, err = client.Dial(network, addr)
if err != nil {
return nil, fmt.Errorf("SSH tunnel dial failed after retry: %w", err)
conn, err := client.Dial(network, addr)
if err == nil {
return conn, nil
}
lastErr = err
}
return conn, nil
return nil, fmt.Errorf("SSH tunnel dial failed after %d attempts: %w", maxRetries, lastErr)
}

View File

@@ -13,7 +13,7 @@ type Config struct {
// Tunnels is a map of SSH tunnel configurations in which the key is the name of the tunnel
Tunnels map[string]*sshtunnel.Config `yaml:",inline"`
mu sync.RWMutex `yaml:"-"`
mu sync.RWMutex `yaml:"-"`
connections map[string]*sshtunnel.SSHTunnel `yaml:"-"`
}

View File

@@ -188,4 +188,4 @@ func TestConfig_Close(t *testing.T) {
if len(config.connections) != 0 {
t.Errorf("Close() did not clear connections map, got %d connections", len(config.connections))
}
}
}