mirror of https://github.com/statping/statping
mTLS service check (http)
parent
47e8039761
commit
4618d56c18
8
Makefile
8
Makefile
|
@ -322,5 +322,13 @@ postman: clean compile
|
|||
newman run -e dev/postman_environment.json dev/postman.json
|
||||
killall statping
|
||||
|
||||
certs:
|
||||
openssl req -newkey rsa:2048 \
|
||||
-new -nodes -x509 \
|
||||
-days 3650 \
|
||||
-out cert.pem \
|
||||
-keyout key.pem \
|
||||
-subj "/C=US/ST=California/L=Santa Monica/O=Statping/OU=Development/CN=localhost"
|
||||
|
||||
.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite sentry-release snapcraft build-linux build-mac build-win build-all postman
|
||||
.SILENT: travis_s3_creds
|
||||
|
|
|
@ -285,7 +285,7 @@ func runOnce() error {
|
|||
|
||||
func checkGithubUpdates() (githubResponse, error) {
|
||||
url := "https://api.github.com/repos/statping/statping/releases/latest"
|
||||
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(2*time.Second), true)
|
||||
contents, _, err := utils.HttpRequest(url, "GET", nil, nil, nil, time.Duration(2*time.Second), true, nil)
|
||||
if err != nil {
|
||||
return githubResponse{}, err
|
||||
}
|
||||
|
|
|
@ -156,6 +156,41 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.type.match(/^(http)$/)" class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">Use TLS Certificate</label>
|
||||
<div class="col-8 mt-1">
|
||||
<span @click="service.use_tls = !!service.use_tls" class="switch float-left">
|
||||
<input v-model="service.use_tls" type="checkbox" name="verify_ssl-option" class="switch" id="switch-use-tls" v-bind:checked="service.use_tls">
|
||||
<label for="switch-use-tls" v-if="service.use_tls">Custom TLS Certificates for mTLS services</label>
|
||||
<label for="switch-use-tls" v-if="!service.use_tls">Ignore TLS Certificates</label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.use_tls" class="form-group row">
|
||||
<label for="service_tls_cert" class="col-sm-4 col-form-label">TLS Client Certificate</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea v-model="service.tls_cert" name="tls_cert" class="form-control" id="service_tls_cert"></textarea>
|
||||
<small class="form-text text-muted">Absolute path to TLS Client Certificate file or in PEM format</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.use_tls" class="form-group row">
|
||||
<label for="service_tls_cert_key" class="col-sm-4 col-form-label">TLS Client Key</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea v-model="service.tls_cert_key" name="tls_cert_key" class="form-control" id="service_tls_cert_key"></textarea>
|
||||
<small class="form-text text-muted">Absolute path to TLS Client Key file or in PEM format</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="service.use_tls" class="form-group row">
|
||||
<label for="service_tls_cert_chain" class="col-sm-4 col-form-label">Root CA</label>
|
||||
<div class="col-sm-8">
|
||||
<textarea v-model="service.tls_cert_root" name="tls_cert_key" class="form-control" id="service_tls_cert_chain"></textarea>
|
||||
<small class="form-text text-muted">Absolute path to Root CA file or in PEM format (optional)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -235,6 +270,10 @@
|
|||
notify_all_changes: true,
|
||||
notify_after: 2,
|
||||
public: true,
|
||||
use_tls: false,
|
||||
tls_cert: "",
|
||||
tls_cert_key: "",
|
||||
tls_cert_root: "",
|
||||
},
|
||||
groups: [],
|
||||
}
|
||||
|
@ -247,12 +286,15 @@
|
|||
watch: {
|
||||
in_service () {
|
||||
this.service = this.in_service
|
||||
if (this.service.tls_cert) {
|
||||
this.service.use_tls = true
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
if (!this.$store.getters.groups) {
|
||||
const groups = await Api.groups()
|
||||
this.$store.commit('setGroups', groups)
|
||||
const groups = await Api.groups()
|
||||
this.$store.commit('setGroups', groups)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -289,6 +331,7 @@
|
|||
delete s.last_success
|
||||
delete s.latency
|
||||
delete s.online_24_hours
|
||||
delete s.use_tls
|
||||
s.check_interval = parseInt(s.check_interval)
|
||||
s.timeout = parseInt(s.timeout)
|
||||
s.port = parseInt(s.port)
|
||||
|
|
|
@ -143,7 +143,7 @@ func slackOAuth(r *http.Request) (*oAuth, error) {
|
|||
// slackIdentity will query the Slack API to fetch the users ID, username, and email address.
|
||||
func (a *oAuth) slackIdentity() (*oAuth, error) {
|
||||
url := fmt.Sprintf("https://slack.com/api/users.identity?token=%s", a.Token)
|
||||
out, resp, err := utils.HttpRequest(url, "GET", "application/x-www-form-urlencoded", nil, nil, 10*time.Second, true)
|
||||
out, resp, err := utils.HttpRequest(url, "GET", "application/x-www-form-urlencoded", nil, nil, 10*time.Second, true, nil)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ var Discorder = &discord{¬ifications.Notification{
|
|||
|
||||
// Send will send a HTTP Post to the discord API. It accepts type: []byte
|
||||
func (d *discord) sendRequest(msg string) error {
|
||||
_, _, err := utils.HttpRequest(Discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true)
|
||||
_, _, err := utils.HttpRequest(Discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ func (d *discord) OnSuccess(s *services.Service) error {
|
|||
func (d *discord) OnTest() (string, error) {
|
||||
outError := errors.New("Incorrect discord URL, please confirm URL is correct")
|
||||
message := `{"content": "Testing the discord notifier"}`
|
||||
contents, _, err := utils.HttpRequest(Discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second), true)
|
||||
contents, _, err := utils.HttpRequest(Discorder.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(message)), time.Duration(10*time.Second), true, nil)
|
||||
if string(contents) == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ func (l *lineNotifier) sendMessage(message string) (string, error) {
|
|||
v := url.Values{}
|
||||
v.Set("message", message)
|
||||
headers := []string{fmt.Sprintf("Authorization=Bearer %v", l.ApiSecret)}
|
||||
content, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second), true)
|
||||
content, _, err := utils.HttpRequest("https://notify-api.line.me/api/notify", "POST", "application/x-www-form-urlencoded", headers, strings.NewReader(v.Encode()), time.Duration(10*time.Second), true, nil)
|
||||
return string(content), err
|
||||
}
|
||||
|
||||
|
|
|
@ -142,7 +142,7 @@ func pushRequest(msg *pushArray) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
url := "https://push.statping.com/api/push"
|
||||
body, _, err = utils.HttpRequest(url, "POST", "application/json", nil, bytes.NewBuffer(body), time.Duration(20*time.Second), true)
|
||||
body, _, err = utils.HttpRequest(url, "POST", "application/json", nil, bytes.NewBuffer(body), time.Duration(20*time.Second), true, nil)
|
||||
return body, err
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ func (t *pushover) sendMessage(message string) (string, error) {
|
|||
v.Set("message", message)
|
||||
rb := strings.NewReader(v.Encode())
|
||||
|
||||
content, _, err := utils.HttpRequest(pushoverUrl, "POST", "application/x-www-form-urlencoded", nil, rb, time.Duration(10*time.Second), true)
|
||||
content, _, err := utils.HttpRequest(pushoverUrl, "POST", "application/x-www-form-urlencoded", nil, rb, time.Duration(10*time.Second), true, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ var slacker = &slack{¬ifications.Notification{
|
|||
|
||||
// Send will send a HTTP Post to the slack webhooker API. It accepts type: string
|
||||
func (s *slack) sendSlack(msg string) error {
|
||||
_, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true)
|
||||
_, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ func (s *slack) sendSlack(msg string) error {
|
|||
|
||||
func (s *slack) OnTest() (string, error) {
|
||||
testMsg := ReplaceVars(failingTemplate, exampleService, exampleFailure)
|
||||
contents, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(testMsg)), time.Duration(10*time.Second), true)
|
||||
contents, resp, err := utils.HttpRequest(s.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(testMsg)), time.Duration(10*time.Second), true, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ func (t *telegram) sendMessage(message string) (string, error) {
|
|||
v.Set("text", message)
|
||||
rb := *strings.NewReader(v.Encode())
|
||||
|
||||
contents, _, err := utils.HttpRequest(apiEndpoint, "GET", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second), true)
|
||||
contents, _, err := utils.HttpRequest(apiEndpoint, "GET", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second), true, nil)
|
||||
|
||||
success, _ := telegramSuccess(contents)
|
||||
if !success {
|
||||
|
|
|
@ -72,7 +72,7 @@ func (t *twilio) sendMessage(message string) (string, error) {
|
|||
|
||||
authHeader := utils.Base64(fmt.Sprintf("%s:%s", t.ApiKey, t.ApiSecret))
|
||||
|
||||
contents, _, err := utils.HttpRequest(twilioUrl, "POST", "application/x-www-form-urlencoded", []string{"Authorization=Basic " + authHeader}, rb, 10*time.Second, true)
|
||||
contents, _, err := utils.HttpRequest(twilioUrl, "POST", "application/x-www-form-urlencoded", []string{"Authorization=Basic " + authHeader}, rb, 10*time.Second, true, nil)
|
||||
success, _ := twilioSuccess(contents)
|
||||
if !success {
|
||||
errorOut := twilioError(contents)
|
||||
|
|
|
@ -2,13 +2,16 @@ package services
|
|||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/statping/statping/types"
|
||||
"github.com/statping/statping/types/errors"
|
||||
"github.com/statping/statping/types/failures"
|
||||
"github.com/statping/statping/types/hits"
|
||||
"github.com/statping/statping/utils"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
@ -16,6 +19,43 @@ import (
|
|||
|
||||
const limitedFailures = 25
|
||||
|
||||
func (s *Service) LoadTLSCert() (*tls.Config, error) {
|
||||
if !s.TLSCert.Valid && !s.TLSCertKey.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// load TLS cert and key from file path or PEM format
|
||||
var cert tls.Certificate
|
||||
var err error
|
||||
tlsCertExtension := utils.FileExtension(s.TLSCert.String)
|
||||
tlsCertKeyExtension := utils.FileExtension(s.TLSCertKey.String)
|
||||
if tlsCertExtension == "" && tlsCertKeyExtension == "" {
|
||||
cert, err = tls.X509KeyPair([]byte(s.TLSCert.String), []byte(s.TLSCertKey.String))
|
||||
} else {
|
||||
cert, err = tls.LoadX509KeyPair(s.TLSCert.String, s.TLSCertKey.String)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "issue loading X509KeyPair")
|
||||
}
|
||||
|
||||
// create Root CA pool or use Root CA provided
|
||||
chainFile := s.TLSCert.String
|
||||
if s.TLSCertRoot.String != "" {
|
||||
chainFile = s.TLSCertRoot.String
|
||||
}
|
||||
caCert, err := ioutil.ReadFile(chainFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "issue reading cert file: "+chainFile)
|
||||
}
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) Duration() time.Duration {
|
||||
return time.Duration(s.Interval) * time.Second
|
||||
}
|
||||
|
|
|
@ -258,8 +258,12 @@ func CheckHttp(s *Service, record bool) *Service {
|
|||
contentType = "application/json"
|
||||
}
|
||||
|
||||
content, res, err = utils.HttpRequest(s.Domain, s.Method, contentType,
|
||||
headers, data, timeout, s.VerifySSL.Bool)
|
||||
customTLS, err := s.LoadTLSCert()
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
}
|
||||
|
||||
content, res, err = utils.HttpRequest(s.Domain, s.Method, contentType, headers, data, timeout, s.VerifySSL.Bool, customTLS)
|
||||
if err != nil {
|
||||
if record {
|
||||
recordFailure(s, fmt.Sprintf("HTTP Error %v", err))
|
||||
|
|
|
@ -37,6 +37,9 @@ type Service struct {
|
|||
VerifySSL null.NullBool `gorm:"default:false;column:verify_ssl" json:"verify_ssl" scope:"user,admin" yaml:"verify_ssl"`
|
||||
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"`
|
||||
TLSCertKey null.NullString `gorm:"column:tls_cert_key" json:"tls_cert_key" scope:"user,admin" yaml:"tls_cert_key"`
|
||||
TLSCertRoot null.NullString `gorm:"column:tls_cert_root" json:"tls_cert_root" scope:"user,admin" yaml:"tls_cert_root"`
|
||||
Headers null.NullString `gorm:"column:headers" json:"headers" scope:"user,admin" yaml:"headers"`
|
||||
Permalink null.NullString `gorm:"column:permalink;unique;" json:"permalink" yaml:"permalink"`
|
||||
Redirect null.NullBool `gorm:"default:false;column:redirect" json:"redirect" scope:"user,admin" yaml:"redirect"`
|
||||
|
|
|
@ -3,6 +3,7 @@ package utils
|
|||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DeleteDirectory will attempt to delete a directory and all contents inside
|
||||
|
@ -30,6 +31,15 @@ func FolderExists(folder string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// FileExtension returns the file extension based on a file path
|
||||
func FileExtension(path string) string {
|
||||
s := strings.Split(path, ".")
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return s[len(s)-1]
|
||||
}
|
||||
|
||||
// FileExists returns true if a file exists
|
||||
// exists := FileExists("assets/css/base.css")
|
||||
func FileExists(name string) bool {
|
||||
|
|
|
@ -197,7 +197,7 @@ func DurationReadable(d time.Duration) string {
|
|||
// // body - The body or form data to send with HTTP request
|
||||
// // timeout - Specific duration to timeout on. time.Duration(30 * time.Seconds)
|
||||
// // You can use a HTTP Proxy if you HTTP_PROXY environment variable
|
||||
func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration, verifySSL bool) ([]byte, *http.Response, error) {
|
||||
func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration, verifySSL bool, customTLS *tls.Config) ([]byte, *http.Response, error) {
|
||||
var err error
|
||||
var req *http.Request
|
||||
t1 := Now()
|
||||
|
@ -247,6 +247,10 @@ func HttpRequest(url, method string, content interface{}, headers []string, body
|
|||
return dialer.DialContext(ctx, network, addr)
|
||||
},
|
||||
}
|
||||
if customTLS != nil {
|
||||
transport.TLSClientConfig.RootCAs = customTLS.RootCAs
|
||||
transport.TLSClientConfig.Certificates = customTLS.Certificates
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: timeout,
|
||||
|
|
Loading…
Reference in New Issue