feat: support tls configuration

This commit is contained in:
Reinaldy Rafli 2023-02-13 10:33:33 +07:00
parent 44d57f9d88
commit c81b4df0f1
Signed by: aldy505
GPG Key ID: A3F8A7E23DA2AD94
5 changed files with 160 additions and 10 deletions

View File

@ -70,6 +70,48 @@ then it's up and healthy. If the value is 0, then it's down.
`uptime_latency_seconds` indicates the time it took to do a HTTP request to that specified `endpoint_address`
field in second.
## Configuration file schema
If you want to do config.json.
```json
{
"endpoints": [
{
"name": "string",
"address": "https://url",
"method": "GET",
"timeout": 123,
"successful_status_code": "2xx",
"inverse_status": false,
"tls_configuration": {
"certificate_authority_path": "/path/to/certificate_authority.pem",
"client_certificate_path": "/path/to/client_certificate.pem",
"client_key_path": "/path/to/client_key.pem",
"insecure_skip_verify": true
}
}
]
}
```
If you want to do config.yaml (or .yml if you prefer).
```yaml
endpoints:
- name: "string"
address: "https://url"
method: "GET"
timeout: 123
successful_status_code: 2xx
inverse_status: false
tls_configuration:
certificate_authority_path: "/path/to/certificate_authority.pem",
client_certificate_path: "/path/to/client_certificate.pem",
client_key_path: "/path/to/client_key.pem",
insecure_skip_verify: true
```
## Build from source
Install [Go](https://go.dev/dl).

View File

@ -22,6 +22,13 @@
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
)
type Configuration struct {
Endpoints []Endpoint `required:"true"`
}
@ -31,6 +38,89 @@ type Endpoint struct {
Address string `required:"true"`
Method string `required:"false" default:"GET"`
Timeout uint16 `required:"false" default:"30"`
SuccessfulStatusCode string `required:"false" default:"2xx"`
InverseStatus bool `required:"false" default:"false"`
SuccessfulStatusCode string `required:"false" default:"2xx" yaml:"successful_status_code" json:"successful_status_code" toml:"successful_status_code"`
InverseStatus bool `required:"false" default:"false" yaml:"inverse_status" json:"inverse_status" toml:"inverse_status"`
TLSConfiguration TLSConfiguration `required:"false" yaml:"tls_configuration" json:"tls_configuration" toml:"tls_configuration"`
}
type TLSConfiguration struct {
CertificateAuthorityPath string `required:"false" yaml:"certificate_authority_path" json:"certificate_authority_path" toml:"certificate_authority_path"`
ClientCertificatePath string `required:"false" yaml:"client_certificate_path" json:"client_certificate_path" toml:"client_certificate_path"`
ClientKeyPath string `required:"false" yaml:"client_key_path" json:"client_key_path" toml:"client_key_path"`
InsecureSkipVerify bool `required:"false" default:"false" yaml:"insecure_skip_verify" json:"insecure_skip_verify" toml:"insecure_skip_verify"`
}
type PreprocessedEndpoint struct {
Name string
Address string
Method string
Timeout uint16
SuccessfulStatusCode string
InverseStatus bool
TLSConfiguration PreproccessedTLSConfiguration
}
type PreproccessedTLSConfiguration struct {
Certificates []tls.Certificate
RootCA *x509.CertPool
InsecureSkipVerify bool
}
func processConfiguration(endpoints []Endpoint) ([]PreprocessedEndpoint, error) {
var preprocessedEndpoints []PreprocessedEndpoint
for _, endpoint := range endpoints {
var certificates []tls.Certificate = nil
var rootCA *x509.CertPool = nil
if endpoint.TLSConfiguration.CertificateAuthorityPath != "" {
// Read CA file
file, err := os.ReadFile(endpoint.TLSConfiguration.CertificateAuthorityPath)
if err != nil {
return nil, fmt.Errorf("reading certificate authority file: %s", err.Error())
}
rootCA = x509.NewCertPool()
ok := rootCA.AppendCertsFromPEM(file)
if !ok {
return nil, fmt.Errorf("invalid pem format for certificate authority")
}
}
if endpoint.TLSConfiguration.ClientCertificatePath != "" && endpoint.TLSConfiguration.ClientKeyPath != "" {
// Read certificate file
certificateFile, err := os.ReadFile(endpoint.TLSConfiguration.ClientCertificatePath)
if err != nil {
return nil, fmt.Errorf("reading certificate file: %s", err.Error())
}
keyFile, err := os.ReadFile(endpoint.TLSConfiguration.ClientKeyPath)
if err != nil {
return nil, fmt.Errorf("reading key file: %s", err.Error())
}
certificate, err := tls.X509KeyPair(certificateFile, keyFile)
if err != nil {
return nil, fmt.Errorf("creating x509 key pair: %s", err.Error())
}
certificates = append(certificates, certificate)
}
preprocessedEndpoints = append(preprocessedEndpoints, PreprocessedEndpoint{
Name: endpoint.Name,
Address: endpoint.Address,
Method: endpoint.Method,
Timeout: endpoint.Timeout,
SuccessfulStatusCode: endpoint.SuccessfulStatusCode,
InverseStatus: endpoint.InverseStatus,
TLSConfiguration: PreproccessedTLSConfiguration{
Certificates: certificates,
RootCA: rootCA,
InsecureSkipVerify: endpoint.TLSConfiguration.InsecureSkipVerify,
},
})
}
return preprocessedEndpoints, nil
}

View File

@ -23,6 +23,9 @@
endpoints:
- name: "GitHub"
address: https://github.com/healthz
tls_configuration:
insecure_skip_verify: true
- name: "Reinaldy"
address: https://code.reinaldyrafli.com
timeout: 60

View File

@ -24,6 +24,7 @@ package main
import (
"context"
"crypto/tls"
"net/http"
"strconv"
"sync"
@ -54,7 +55,7 @@ var (
)
type Exporter struct {
Endpoints []Endpoint
Endpoints []PreprocessedEndpoint
Logger *onelog.Logger
}
@ -106,7 +107,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
wg.Add(len(e.Endpoints))
for _, endpoint := range e.Endpoints {
go func(endpoint Endpoint) {
go func(endpoint PreprocessedEndpoint) {
defer wg.Done()
// Create HTTP calls
@ -118,6 +119,13 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
httpClient := http.Client{
Timeout: time.Duration(uint64(endpoint.Timeout) * 1e+9),
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: endpoint.TLSConfiguration.Certificates,
RootCAs: endpoint.TLSConfiguration.RootCA,
InsecureSkipVerify: endpoint.TLSConfiguration.InsecureSkipVerify,
},
},
}
response, err := httpClient.Do(request)

11
main.go
View File

@ -49,14 +49,21 @@ func main() {
var config Configuration
err := configor.Load(&config, *configFile)
if err != nil {
log.Fatalln(err)
log.Fatalln(err.Error())
}
preprocessedConfig, err := processConfiguration(config.Endpoints)
if err != nil {
log.Fatalln(err.Error())
}
log.Printf("%+v", preprocessedConfig)
// Initiate the logger instance. It should be zero-allocated, so we'll use onelog
logger := onelog.New(os.Stdout, onelog.ALL)
exporter := &Exporter{
Endpoints: config.Endpoints,
Endpoints: preprocessedConfig,
Logger: logger,
}