diff --git a/CHANGELOG.md b/CHANGELOG.md
index e2e51326..9e8b9f0f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.90.41 (05-20-2020)
+- Added TLS Client Cert/Key feature for HTTP and TCP/UDP services
+- Replaced environment variable ADMIN_PASS to ADMIN_PASSWORD.
+
# 0.90.40 (05-18-2020)
- Fixed issues with MySQL and Postgres taking forever to insert sample data (now run in bulk)
- Removed API Authentication for /api/logout route
diff --git a/Makefile b/Makefile
index 30b9ac1f..d9f3c8be 100644
--- a/Makefile
+++ b/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
diff --git a/cmd/cli.go b/cmd/cli.go
index ffbed998..083b55dd 100644
--- a/cmd/cli.go
+++ b/cmd/cli.go
@@ -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
}
diff --git a/dev/docker-compose.full.yml b/dev/docker-compose.full.yml
index 6c326ecb..740c75b8 100644
--- a/dev/docker-compose.full.yml
+++ b/dev/docker-compose.full.yml
@@ -36,7 +36,7 @@ services:
DOMAIN: http://localhost:8080
DESCRIPTION: This is a dev environment on SQLite!
ADMIN_USER: admin
- ADMIN_PASS: admin
+ ADMIN_PASSWORD: admin
SAMPLE_DATA: 'false'
ports:
- 8080:8080
@@ -69,7 +69,7 @@ services:
DOMAIN: http://localhost:8081
DESCRIPTION: This is a dev environment on MySQL!
ADMIN_USER: admin
- ADMIN_PASS: admin
+ ADMIN_PASSWORD: admin
SAMPLE_DATA: 'false'
ports:
- 8081:8080
@@ -105,7 +105,7 @@ services:
DOMAIN: http://localhost:8082
DESCRIPTION: This is a dev environment on Postgres!
ADMIN_USER: admin
- ADMIN_PASS: admin
+ ADMIN_PASSWORD: admin
SAMPLE_DATA: 'false'
ports:
- 8082:8080
@@ -141,7 +141,7 @@ services:
DOMAIN: http://localhost:8083
DESCRIPTION: This is a dev environment on MariaDB!
ADMIN_USER: admin
- ADMIN_PASS: admin
+ ADMIN_PASSWORD: admin
SAMPLE_DATA: 'false'
ports:
- 8083:8080
diff --git a/dev/docker-compose.lite.yml b/dev/docker-compose.lite.yml
index 65c47a68..75e48a54 100644
--- a/dev/docker-compose.lite.yml
+++ b/dev/docker-compose.lite.yml
@@ -24,7 +24,7 @@ services:
DOMAIN: http://localhost:8585
DESCRIPTION: This is a dev environment with auto reloading!
ADMIN_USER: admin
- ADMIN_PASS: admin
+ ADMIN_PASSWORD: admin
PORT: 8585
ports:
- 8888:8888
diff --git a/dev/pwd-stack.yml b/dev/pwd-stack.yml
index 602f5612..71c2a179 100644
--- a/dev/pwd-stack.yml
+++ b/dev/pwd-stack.yml
@@ -18,7 +18,7 @@ services:
DOMAIN: http://localhost:8080
DESCRIPTION: This is a dev environment on SQLite!
ADMIN_USER: admin
- ADMIN_PASS: admin
+ ADMIN_PASSWORD: admin
postgres:
hostname: postgres
diff --git a/frontend/src/forms/Service.vue b/frontend/src/forms/Service.vue
index 1dfb926c..e3f2a267 100644
--- a/frontend/src/forms/Service.vue
+++ b/frontend/src/forms/Service.vue
@@ -156,6 +156,41 @@
+
+
+
+
+
+
+
+
@@ -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)
diff --git a/handlers/oauth.go b/handlers/oauth.go
index 09ceb8d0..ce9158bf 100644
--- a/handlers/oauth.go
+++ b/handlers/oauth.go
@@ -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
}
diff --git a/notifiers/discord.go b/notifiers/discord.go
index 988c7734..4d9ca75a 100644
--- a/notifiers/discord.go
+++ b/notifiers/discord.go
@@ -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
}
diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go
index f6955d92..549449b9 100644
--- a/notifiers/line_notify.go
+++ b/notifiers/line_notify.go
@@ -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
}
diff --git a/notifiers/mobile.go b/notifiers/mobile.go
index edaadb9d..8ade1a8e 100644
--- a/notifiers/mobile.go
+++ b/notifiers/mobile.go
@@ -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
}
diff --git a/notifiers/pushover.go b/notifiers/pushover.go
index ae85eb13..6e93b8e1 100644
--- a/notifiers/pushover.go
+++ b/notifiers/pushover.go
@@ -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
}
diff --git a/notifiers/slack.go b/notifiers/slack.go
index 2eb776cb..8687b481 100644
--- a/notifiers/slack.go
+++ b/notifiers/slack.go
@@ -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
}
diff --git a/notifiers/telegram.go b/notifiers/telegram.go
index 0be97e96..214150e3 100644
--- a/notifiers/telegram.go
+++ b/notifiers/telegram.go
@@ -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 {
diff --git a/notifiers/twilio.go b/notifiers/twilio.go
index e3ec3aaf..ef7bec7e 100644
--- a/notifiers/twilio.go
+++ b/notifiers/twilio.go
@@ -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)
diff --git a/types/configs/load.go b/types/configs/load.go
index 6bb6d79d..335cc01b 100644
--- a/types/configs/load.go
+++ b/types/configs/load.go
@@ -62,7 +62,7 @@ func LoadConfigFile(configFile string) (*DbConfig, error) {
Domain: p.GetString("DOMAIN"),
Email: p.GetString("EMAIL"),
Username: p.GetString("ADMIN_USER"),
- Password: p.GetString("ADMIN_PASS"),
+ Password: p.GetString("ADMIN_PASSWORD"),
Location: utils.Directory,
SqlFile: p.GetString("SQL_FILE"),
}
diff --git a/types/services/methods.go b/types/services/methods.go
index a2e5e84a..471f58f9 100644
--- a/types/services/methods.go
+++ b/types/services/methods.go
@@ -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
}
diff --git a/types/services/routine.go b/types/services/routine.go
index c7956e76..43aa2c7d 100644
--- a/types/services/routine.go
+++ b/types/services/routine.go
@@ -2,6 +2,7 @@ package services
import (
"bytes"
+ "crypto/tls"
"fmt"
"google.golang.org/grpc"
"net"
@@ -181,7 +182,18 @@ func CheckTcp(s *Service, record bool) *Service {
domain = fmt.Sprintf("[%v]:%v", s.Domain, s.Port)
}
}
- conn, err := net.DialTimeout(s.Type, domain, time.Duration(s.Timeout)*time.Second)
+
+ tlsConfig, err := s.LoadTLSCert()
+ if err != nil {
+ log.Errorln(err)
+ }
+
+ dialer := &net.Dialer{
+ KeepAlive: time.Duration(s.Timeout) * time.Second,
+ Timeout: time.Duration(s.Timeout) * time.Second,
+ }
+
+ conn, err := tls.DialWithDialer(dialer, s.Type, domain, tlsConfig)
if err != nil {
if record {
recordFailure(s, fmt.Sprintf("Dial Error %v", err))
@@ -258,8 +270,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))
diff --git a/types/services/struct.go b/types/services/struct.go
index bdd17086..88406f21 100644
--- a/types/services/struct.go
+++ b/types/services/struct.go
@@ -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"`
diff --git a/utils/file.go b/utils/file.go
index 07b77338..1019b6fd 100644
--- a/utils/file.go
+++ b/utils/file.go
@@ -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 {
diff --git a/utils/utils.go b/utils/utils.go
index 2085b8f2..2d6dce27 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -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,
diff --git a/version.txt b/version.txt
index 1d535ecf..fb82a5d2 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-0.90.40
+0.90.41