mirror of https://github.com/statping/statping
Add SSH checker
parent
b6ca1a5c66
commit
fdeb7fe31a
|
@ -19,6 +19,7 @@
|
|||
<option value="udp">UDP {{ $t('service') }}</option>
|
||||
<option value="icmp">ICMP Ping</option>
|
||||
<option value="grpc">gRPC {{ $t('service') }}</option>
|
||||
<option value="ssh">SSH {{ $t('service') }}</option>
|
||||
<option value="static">Static {{ $t('service') }}</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">Use HTTP if you are checking a website or use TCP if you are checking a server</small>
|
||||
|
@ -82,7 +83,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(tcp|udp|grpc)$/)" class="form-group row">
|
||||
<div v-if="service.type.match(/^(tcp|udp|grpc|ssh)$/)" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Port</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model.number="service.port" type="number" name="port" class="form-control" id="service_port" placeholder="8080">
|
||||
|
@ -193,6 +194,55 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(ssh)$/)" class="form-group row">
|
||||
<label for="service_username" class="col-sm-4 col-form-label">{{ $t('username') }}</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.username" type="text" name="service_username" class="form-control" id="service_username">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(ssh)$/)" class="form-group row">
|
||||
<label for="service_password" class="col-sm-4 col-form-label">{{ $t('password') }}</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.password" type="text" name="service_password" class="form-control" id="service_password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(ssh)$/)" class="form-group row">
|
||||
<label class="col-12 col-md-4 col-form-label">SSH Health Check</label>
|
||||
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
||||
<span @click="service.ssh_health_check = !!service.ssh_health_check" class="switch float-left">
|
||||
<input v-model="service.ssh_health_check" type="checkbox" name="ssh_health_check-option" class="switch" id="switch-ssh-health-check" v-bind:checked="service.ssh_health_check">
|
||||
<label for="switch-ssh-health-check" v-if="service.ssh_health_check">Check against SSH health check command.</label>
|
||||
<label for="switch-ssh-health-check" v-if="!service.ssh_health_check">Only checks if SSH connection can be established.</label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.ssh_health_check" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Expected Response</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea v-model="service.expected" class="form-control" rows="3" autocapitalize="none" spellcheck="false"></textarea>
|
||||
<small class="form-text text-muted">Only stdout will be parsed</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.ssh_health_check" class="form-group row">
|
||||
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Exit Code</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.expected_status" type="number" name="expected_status" class="form-control" placeholder="1" id="service_response_code">
|
||||
<small class="form-text text-muted">By convention a command exit code 0 indicates success</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(ssh)$/) && service.ssh_health_check" class="form-group row">
|
||||
<label for="check_command" class="col-sm-4 col-form-label">Check Command</label>
|
||||
<div class="col-sm-8">
|
||||
<input v-model="service.check_command" type="text" name="check_command" class="form-control" id="check_command">
|
||||
<small class="form-text text-muted">Check command to be executed on remote server</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(tcp|http)$/)" class="form-group row">
|
||||
<label class="col-12 col-md-4 col-form-label">{{ $t('tls_cert') }}</label>
|
||||
<div class="col-12 col-md-8 mt-1 mb-2 mb-md-0">
|
||||
|
@ -303,6 +353,10 @@
|
|||
order: 1,
|
||||
verify_ssl: true,
|
||||
grpc_health_check: false,
|
||||
ssh_health_check: false,
|
||||
username: "",
|
||||
password: "",
|
||||
check_command: "",
|
||||
redirect: true,
|
||||
allow_notifications: true,
|
||||
notify_all_changes: true,
|
||||
|
@ -351,6 +405,9 @@
|
|||
this.service.port = 50051
|
||||
this.service.verify_ssl = false
|
||||
this.service.method = ""
|
||||
} else if (this.service.type == "ssh") {
|
||||
this.service.port = 22;
|
||||
this.service.expected_status = 0;
|
||||
} else {
|
||||
this.service.expected_status = 200
|
||||
this.service.expected = ""
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/statping/statping/types/metrics"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
|
@ -58,7 +59,7 @@ CheckLoop:
|
|||
}
|
||||
|
||||
func parseHost(s *Service) string {
|
||||
if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" {
|
||||
if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" || s.Type == "ssh" {
|
||||
return s.Domain
|
||||
} else {
|
||||
u, err := url.Parse(s.Domain)
|
||||
|
@ -74,7 +75,7 @@ func dnsCheck(s *Service) (int64, error) {
|
|||
var err error
|
||||
t1 := utils.Now()
|
||||
host := parseHost(s)
|
||||
if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" {
|
||||
if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" || s.Type == "ssh" {
|
||||
_, err = net.LookupHost(host)
|
||||
} else {
|
||||
_, err = net.LookupIP(host)
|
||||
|
@ -393,6 +394,97 @@ func CheckHttp(s *Service, record bool) (*Service, error) {
|
|||
return s, err
|
||||
}
|
||||
|
||||
// CheckSsh will check a SSH service
|
||||
func CheckSsh(s *Service, record bool) (*Service, error) {
|
||||
defer s.updateLastCheck()
|
||||
timer := prometheus.NewTimer(metrics.ServiceTimer(s.Name))
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
dnsLookup, err := dnsCheck(s)
|
||||
if err != nil {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("Could not get IP address for SSH service %v, %v", s.Domain, err), "lookup")
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
s.PingTime = dnsLookup
|
||||
t1 := utils.Now()
|
||||
domain := fmt.Sprintf("%v", s.Domain)
|
||||
if s.Port != 0 {
|
||||
domain = fmt.Sprintf("%v:%v", s.Domain, s.Port)
|
||||
if isIPv6(s.Domain) {
|
||||
domain = fmt.Sprintf("[%v]:%v", s.Domain, s.Port)
|
||||
}
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: s.Username,
|
||||
Auth: []ssh.AuthMethod{ssh.Password(s.Password)},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
Timeout: time.Duration(s.Timeout) * time.Second,
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", domain, sshConfig)
|
||||
if err != nil {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("Dial Error: %v", err), "ssh")
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
defer client.Close()
|
||||
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("Failed to create SSH session: %v", err), "ssh")
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
|
||||
if s.SshHealthCheck.Bool {
|
||||
output, err := session.Output(s.CheckCommand)
|
||||
if err != nil {
|
||||
if exitError, ok := err.(*ssh.ExitError); ok {
|
||||
s.LastStatusCode = exitError.Waitmsg.ExitStatus()
|
||||
} else {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("Failed to execute check command: %v", err), "ssh")
|
||||
return s, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.LastResponse = strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
s.Latency = utils.Now().Sub(t1).Microseconds()
|
||||
s.Online = true
|
||||
|
||||
if s.SshHealthCheck.Bool {
|
||||
if s.ExpectedStatus != s.LastStatusCode {
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("SSH Service: '%s', Exit Code: expected '%v', got '%v'", s.Name, s.ExpectedStatus, s.LastStatusCode), "response_code")
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
if s.Expected.String != s.LastResponse {
|
||||
log.Warnln(fmt.Sprintf("SSH Service: '%s', Command output: expected '%v', got '%v'", s.Name, s.Expected.String, s.LastResponse))
|
||||
if record {
|
||||
RecordFailure(s, fmt.Sprintf("SSH Command output '%v' did not match '%v'", s.LastResponse, s.Expected.String), "response_body")
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
|
||||
if record {
|
||||
RecordSuccess(s)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// RecordSuccess will create a new 'hit' record in the database for a successful/online service
|
||||
func RecordSuccess(s *Service) {
|
||||
s.LastOnline = utils.Now()
|
||||
|
@ -460,5 +552,7 @@ func (s *Service) CheckService(record bool) {
|
|||
CheckGrpc(s, record)
|
||||
case "icmp":
|
||||
CheckIcmp(s, record)
|
||||
case "ssh":
|
||||
CheckSsh(s, record)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,10 @@ type Service struct {
|
|||
Order int `gorm:"default:0;column:order_id" json:"order_id" yaml:"order_id"`
|
||||
VerifySSL null.NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl" scope:"user,admin" yaml:"verify_ssl"`
|
||||
GrpcHealthCheck null.NullBool `gorm:"default:false;column:grpc_health_check" json:"grpc_health_check" scope:"user,admin" yaml:"grpc_health_check"`
|
||||
Username string `gorm:"column:username" json:"username" yaml:"username" scope:"user,admin"`
|
||||
Password string `gorm:"column:password" json:"password" yaml:"password" scope:"user,admin"`
|
||||
CheckCommand string `gorm:"column:check_command" json:"check_command" yaml:"check_command" scope:"user,admin"`
|
||||
SshHealthCheck null.NullBool `gorm:"default:false;column:ssh_health_check" json:"ssh_health_check" scope:"user,admin" yaml:"ssh_health_check"`
|
||||
Public null.NullBool `gorm:"default:true;column:public" json:"public" yaml:"public"`
|
||||
GroupId int `gorm:"default:0;column:group_id" json:"group_id" yaml:"group_id"`
|
||||
TLSCert null.NullString `gorm:"column:tls_cert" json:"tls_cert" scope:"user,admin" yaml:"tls_cert"`
|
||||
|
|
Loading…
Reference in New Issue