6 Commits

Author SHA1 Message Date
Rob Best
606f4f6032 0.6.0 2019-09-21 10:40:45 +01:00
Rob Best
f91d97c220 Add TOC to README 2019-09-21 10:28:49 +01:00
Rob Best
cfab972f8f Use https or tcp client based on target address
There are some advantages to using a http client over tcp. For instance,
using http allows you to take advatange of a http proxy, which may be necessary
in some environments.

This commit puts the http client back, alongside tcp, and decides which one to use
based on the target address.
2019-09-21 10:28:49 +01:00
Rob Best
10353fe7fb Merge pull request #7 from ricardbejarano/docker-image
Rewritten Dockerfile into a better image
2019-08-21 12:55:24 +01:00
Ricard Bejarano
5a1b013445 rewritten dockerfile 2019-07-05 15:44:06 +02:00
Rob Best
215029534e Improve tests and remove reliance on external websites 2019-04-10 14:24:18 +01:00
7 changed files with 761 additions and 206 deletions

View File

@@ -1,5 +1,21 @@
FROM quay.io/prometheus/busybox:latest
FROM golang:stretch AS build
COPY ssl_exporter /bin/ssl_exporter
ADD . /tmp/ssl_exporter
ENTRYPOINT ["/bin/ssl_exporter"]
RUN cd /tmp/ssl_exporter && \
echo "ssl:*:100:ssl" > group && \
echo "ssl:*:100:100::/:/ssl_exporter" > passwd && \
make
FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /tmp/ssl_exporter/group \
/tmp/ssl_exporter/passwd \
/etc/
COPY --from=build /tmp/ssl_exporter/ssl_exporter /
USER ssl:ssl
EXPOSE 9219/tcp
ENTRYPOINT ["/ssl_exporter"]

137
README.md
View File

@@ -1,80 +1,136 @@
# SSL Certificate Exporter
The [blackbox_exporter](https://github.com/prometheus/blackbox_exporter) allows you to test the expiry date of a certificate as part of its HTTP(S) probe - which is great. It doesn't, however, tell you which certificate in the chain is nearing expiry or give you any other information that might be useful when sending alerts.
The [blackbox_exporter](https://github.com/prometheus/blackbox_exporter) allows you to test the expiry date of a certificate as part of its HTTP(S) probe - which is great. It doesn't, however, tell you which certificate in the chain is nearing expiry or give you any other information that might be useful when sending alerts.
For instance, there's a definite value in knowing, upon first receiving an alert, if it's a certificate you manage directly or one further up the chain. It's also not always necessarily clear from the address you're polling what kind of certificate renewal you're looking at. Is it a Let's Encrypt, in which case it should be handled by automation? Or your organisation's wildcard? Maybe the domain is managed by a third-party and you need to submit a ticket to get it renewed.
For instance, there's a definite value in knowing, upon first receiving an alert, if it's a certificate you manage directly or one further up the chain. It's also not always necessarily clear from the address you're polling what kind of certificate renewal you're looking at. Is it a Let's Encrypt, in which case it should be handled by automation? Or your organisation's wildcard? Maybe the domain is managed by a third-party and you need to submit a ticket to get it renewed.
Whatever it is, the SSL exporter gives you visibility over those dimensions at the point at which you receive an alert. It also allows you to produce more meaningful visualisations and consoles.
## Table of Contents
* [SSL Certificate Exporter](#ssl-certificate-exporter)
* [Building](#building)
* [Docker](#docker)
* [Flags](#flags)
* [Metrics](#metrics)
* [Prometheus](#prometheus)
* [Configuration](#configuration)
* [Targets](#targets)
* [Valid targets](#valid-targets)
* [Invalid targets](#invalid-targets)
* [Example Queries](#example-queries)
* [Client authentication](#client-authentication)
* [Proxying](#proxying)
* [Limitations](#limitations)
* [Acknowledgements](#acknowledgements)
Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
## Building
make
./ssl_exporter <flags>
Similarly to the blackbox_exporter, visiting [http://localhost:9219/probe?target=example.com:443](http://localhost:9219/probe?target=example.com:443) will return certificate metrics for example.com. The ```ssl_tls_connect_success``` metric indicates if the probe has been successful.
Similarly to the blackbox_exporter, visiting [http://localhost:9219/probe?target=example.com:443](http://localhost:9219/probe?target=example.com:443) will return certificate metrics for example.com. The `ssl_tls_connect_success` metric indicates if the probe has been successful.
## Docker
docker pull ribbybibby/ssl-exporter
docker run -p 9219:9219 ssl-exporter:latest <flags>
## Flags
./ssl_exporter --help
* __`--tls.insecure`:__ Skip certificate verification (default false). This is insecure but does allow you to collect metrics in the case where a certificate has expired. That being said, I feel that it's more important to catch verification failures than it is to identify an expired certificate, especially as the former includes the latter.
* __`--tls.cacert`:__ Provide the path to an alternative bundle of root CA certificates. By default the exporter will use the host's root CA set.
* __`--tls.client-auth`:__ Enable client authentication (default false). When enabled the exporter will present the certificate and key configured by `--tls.cert` and `tls.key` to the other side of the connection.
* __`--tls.cert`:__ The path to a local certificate for client authentication (default "cert.pem"). Only used when `--tls.client-auth` is toggled on.
* __`--tls.key`:__ The path to a local key for client authentication (default "key.pem"). Only used when `--tls.client-auth` is toggled on.
* __`--web.listen-address`:__ The port (default ":9219").
* __`--web.metrics-path`:__ The path metrics are exposed under (default "/metrics")
* __`--web.probe-path`:__ The path the probe endpoint is exposed under (default "/probe")
- **`--tls.insecure`:** Skip certificate verification (default false). This is insecure but does allow you to collect metrics in the case where a certificate has expired. That being said, I feel that it's more important to catch verification failures than it is to identify an expired certificate, especially as the former includes the latter.
- **`--tls.cacert`:** Provide the path to an alternative bundle of root CA certificates. By default the exporter will use the host's root CA set.
- **`--tls.client-auth`:** Enable client authentication (default false). When enabled the exporter will present the certificate and key configured by `--tls.cert` and `tls.key` to the other side of the connection.
- **`--tls.cert`:** The path to a local certificate for client authentication (default "cert.pem"). Only used when `--tls.client-auth` is toggled on.
- **`--tls.key`:** The path to a local key for client authentication (default "key.pem"). Only used when `--tls.client-auth` is toggled on.
- **`--web.listen-address`:** The port (default ":9219").
- **`--web.metrics-path`:** The path metrics are exposed under (default "/metrics")
- **`--web.probe-path`:** The path the probe endpoint is exposed under (default "/probe")
## Metrics
Metrics are exported for each certificate in the chain individually. All of the metrics are labelled with the Issuer's Common Name and the Serial ID, which is pretty much a unique identifier.
I considered having a series for each ```ssl_cert_subject_alternative_*``` value but these labels aren't actually very cardinal, considering the most frequently they'll change is probably every three months, which is longer than most metric retention times anyway. Joining them within commas as I've done allows for easy parsing and relabelling.
I considered having a series for each `ssl_cert_subject_alternative_*` value but these labels aren't actually very cardinal, considering the most frequently they'll change is probably every three months, which is longer than most metric retention times anyway. Joining them within commas as I've done allows for easy parsing and relabelling.
| Metric | Meaning | Labels |
| ------ | ------- | ------ |
| ssl_cert_not_after | The date after which the certificate expires. Expressed as a Unix Epoch Time. | issuer_cn, serial_no |
| ssl_cert_not_before | The date before which the certificate is not valid. Expressed as a Unix Epoch Time. | issuer_cn, serial_no |
| ssl_cert_subject_common_name | The common name of the certificate. Always has a value of 1 | issuer_cn, serial_no, subject_cn |
| ssl_cert_subject_alternative_dnsnames | The subject alternative names (if any). Always has a value of 1 | issuer_cn, serial_no, dnsnames |
| ssl_cert_subject_alternative_emails | The subject alternative email addresses (if any). Always has a value of 1 | issuer_cn, serial_no, emails |
| ssl_cert_subject_alternative_ips | The subject alternative IP addresses (if any). Always has a value of 1 | issuer_cn, serial_no, ips |
| ssl_cert_subject_organization_units | The subject organization names (if any). Always has a value of 1. | issuer_cn, serial_no, subject_ou |
| ssl_tls_connect_success | Was the TLS connection successful? Boolean. | |
| Metric | Meaning | Labels |
| ------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------- |
| ssl_cert_not_after | The date after which the certificate expires. Expressed as a Unix Epoch Time. | issuer_cn, serial_no |
| ssl_cert_not_before | The date before which the certificate is not valid. Expressed as a Unix Epoch Time. | issuer_cn, serial_no |
| ssl_cert_subject_common_name | The common name of the certificate. Always has a value of 1 | issuer_cn, serial_no, subject_cn |
| ssl_cert_subject_alternative_dnsnames | The subject alternative names (if any). Always has a value of 1 | issuer_cn, serial_no, dnsnames |
| ssl_cert_subject_alternative_emails | The subject alternative email addresses (if any). Always has a value of 1 | issuer_cn, serial_no, emails |
| ssl_cert_subject_alternative_ips | The subject alternative IP addresses (if any). Always has a value of 1 | issuer_cn, serial_no, ips |
| ssl_cert_subject_organization_units | The subject organization names (if any). Always has a value of 1. | issuer_cn, serial_no, subject_ou |
| ssl_client_protocol | The protocol used by the exporter to connect to the target. Boolean. | protocol |
| ssl_tls_connect_success | Was the TLS connection successful? Boolean. | |
## Prometheus
### Configuration
Just like with the blackbox_exporter, you should pass the targets to a single instance of the exporter in a scrape config with a clever bit of relabelling. This allows you to leverage service discovery and keeps configuration centralised to your Prometheus config.
```yml
scrape_configs:
- job_name: 'ssl'
- job_name: "ssl"
metrics_path: /probe
static_configs:
- targets:
- example.com:443
- prometheus.io:443
- example.com:443
- prometheus.io:443
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: 127.0.0.1:9219 # SSL exporter.
replacement: 127.0.0.1:9219 # SSL exporter.
```
### Example Queries
Certificates that expire within 7 days, with Subject Common Name and Subject Alternative Names joined on:
((ssl_cert_not_after - time() < 86400 * 7) * on (instance,issuer_cn,serial_no) group_left (dnsnames) ssl_cert_subject_alternative_dnsnames) * on (instance,issuer_cn,serial_no) group_left (subject_cn) ssl_cert_subject_common_name
### Targets
The exporter uses the provided uri to decide which client (http or tcp) to use when connecting to the target. The uri must contain
either a protocol scheme (`https://`), a port (`:443`), or both (`https://example.com:443`).
If the `https://` scheme is provided then the exporter will use a http client to connect to the target. This allows you to take
advatange of some features not available when using tcp, like host-based proxying. The exporter doesn't understand any other L7
protocols, so it will produce an error for others, like `ldaps://` or `ftps://`.
If there's only a port, then a tcp client is used to make the TLS connection. This should allow you to connect to any TLS target, regardless
of L7 protocol.
If neither are given, the exporter assumes a https connection on port `443` (the most common case).
#### Valid targets
- `https://example.com`
- `https://example.com:443`
- `example.com:443`
- `example.com:636`
- `example.com`
#### Invalid targets
- `ldaps://example.com`
- `ldaps://example.com:636`
### Example Queries
Certificates that expire within 7 days, with Subject Common Name and Subject Alternative Names joined on:
((ssl*cert_not_after - time() < 86400 * 7) \_ on (instance,issuer_cn,serial_no) group_left (dnsnames) ssl_cert_subject_alternative_dnsnames) \* on (instance,issuer_cn,serial_no) group_left (subject_cn) ssl_cert_subject_common_name
Only return wildcard certificates that are expiring:
((ssl_cert_not_after - time() < 86400 * 7) * on (instance,issuer_cn,serial_no) group_left (subject_cn) ssl_cert_subject_common_name{subject_cn=~"\\*.*"})
Number of certificates in the chain:
count(ssl_cert_subject_common_name) by (instance)
Identify instances that have failed to create a valid SSL connection:
@@ -82,14 +138,29 @@ Identify instances that have failed to create a valid SSL connection:
ssl_tls_connect_success == 0
## Client authentication
The exporter optionally supports client authentication, which can be toggled on by providing the `--tls.client-auth` flag. By default, it will use the host system's root CA bundle and attempt to use `./cert.pem` and `./key.pem` as the client certificate and key, respectively. You can override these defaults with `--tls.cacert`, `--tls.cert` and `--tls.key`.
If you do enable client authentication, keep in mind that the certificate will be passed to all targets, even those that don't necessarily require client authentication. I'm not sure what the implications of that are but I think you'd probably want to avoid passing a certificate to an unrelated server.
Also, if you want to scrape targets with different client certificate requirements, you'll need to run different instances of the exporter for each. This seemed like a better approach than overloading the exporter with the ability to pass different certificates per-target.
## Proxying
The https client used by the exporter supports the use of proxy servers discovered by the environment variables `HTTP_PROXY`,
`HTTPS_PROXY` and `ALL_PROXY`.
For instance:
$ export HTTPS_PROXY=localhost:8888
$ ./ssl_exporter
In order to use the https client, targets must be provided to the exporter with the protocol in the uri (`https://<host>:<optional port>`).
## Limitations
I've only exported a subset of the information you could extract from a certificate. It would be simple to add more, for instance organisational information, if there's a need.
## Acknowledgements
The overall structure and implementation of this exporter is based on the [consul_exporter](https://github.com/prometheus/consul_exporter). The probing functionality borrows from the blackbox_exporter.

View File

@@ -1 +1 @@
0.5.0
0.6.0

View File

@@ -3,6 +3,7 @@ package main
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net"
@@ -29,6 +30,11 @@ var (
"If the TLS connection was a success",
nil, nil,
)
clientProtocol = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "client_protocol"),
"The protocol used by the exporter to connect to the target",
[]string{"protocol"}, nil,
)
notBefore = prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "cert_not_before"),
"NotBefore expressed as a Unix Epoch Time",
@@ -76,6 +82,7 @@ type Exporter struct {
// Describe metrics
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
ch <- tlsConnectSuccess
ch <- clientProtocol
ch <- notAfter
ch <- commonName
ch <- subjectAlernativeDNSNames
@@ -86,8 +93,10 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
// Collect metrics
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
var peerCertificates []*x509.Certificate
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: e.timeout}, "tcp", e.target, e.tlsConfig)
// Parse the target and return the appropriate connection protocol and target address
target, proto, err := parseTarget(e.target)
if err != nil {
log.Errorln(err)
ch <- prometheus.MustNewConstMetric(
@@ -96,10 +105,75 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
return
}
state := conn.ConnectionState()
ch <- prometheus.MustNewConstMetric(
clientProtocol, prometheus.GaugeValue, 1, proto,
)
if len(state.PeerCertificates) < 1 {
log.Errorln("No certificates found in connection state")
if proto == "https" {
ch <- prometheus.MustNewConstMetric(
clientProtocol, prometheus.GaugeValue, 0, "tcp",
)
// Create the http client
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Transport: &http.Transport{
TLSClientConfig: e.tlsConfig,
Proxy: http.ProxyFromEnvironment,
},
Timeout: e.timeout,
}
// Issue a GET request to the target
resp, err := client.Get(e.target)
if err != nil {
log.Errorln(err)
ch <- prometheus.MustNewConstMetric(
tlsConnectSuccess, prometheus.GaugeValue, 0,
)
return
}
// Check if the response from the target is encrypted
if resp.TLS == nil {
log.Errorln("The response from " + target + " is unencrypted")
ch <- prometheus.MustNewConstMetric(
tlsConnectSuccess, prometheus.GaugeValue, 0,
)
return
}
peerCertificates = resp.TLS.PeerCertificates
} else if proto == "tcp" {
ch <- prometheus.MustNewConstMetric(
clientProtocol, prometheus.GaugeValue, 0, "https",
)
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: e.timeout}, "tcp", target, e.tlsConfig)
if err != nil {
log.Errorln(err)
ch <- prometheus.MustNewConstMetric(
tlsConnectSuccess, prometheus.GaugeValue, 0,
)
return
}
state := conn.ConnectionState()
peerCertificates = state.PeerCertificates
if len(peerCertificates) < 1 {
log.Errorln("No certificates found in connection state for " + target)
ch <- prometheus.MustNewConstMetric(
tlsConnectSuccess, prometheus.GaugeValue, 0,
)
return
}
} else {
log.Errorln("Unrecognised protocol: " + string(proto) + " for target: " + target)
ch <- prometheus.MustNewConstMetric(
tlsConnectSuccess, prometheus.GaugeValue, 0,
)
@@ -111,7 +185,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
)
// Remove duplicate certificates from the response
peerCertificates := uniq(state.PeerCertificates)
peerCertificates = uniq(peerCertificates)
// Loop through returned certificates and create metrics
for _, cert := range peerCertificates {
@@ -173,7 +247,6 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
}
func probeHandler(w http.ResponseWriter, r *http.Request, tlsConfig *tls.Config) {
target := r.URL.Query().Get("target")
// The following timeout block was taken wholly from the blackbox exporter
@@ -195,13 +268,8 @@ func probeHandler(w http.ResponseWriter, r *http.Request, tlsConfig *tls.Config)
timeout := time.Duration((timeoutSeconds) * 1e9)
t, err := parseTarget(target)
if err != nil {
t = target
}
exporter := &Exporter{
target: t,
target: target,
timeout: timeout,
tlsConfig: tlsConfig,
}
@@ -235,33 +303,25 @@ func contains(certs []*x509.Certificate, cert *x509.Certificate) bool {
return false
}
// parseTarget makes an attempt at converting URLs of the form scheme://host
// into host:port
func parseTarget(target string) (parsedTarget string, err error) {
func parseTarget(target string) (parsedTarget string, proto string, err error) {
if !strings.Contains(target, "://") {
target = "//" + target
}
u, err := url.Parse(target)
if err != nil {
log.Errorln(err)
return
return "", proto, err
}
if u.Port() == "" {
switch scheme := u.Scheme; scheme {
case "https":
parsedTarget = u.Host + ":443"
case "ldaps":
parsedTarget = u.Host + ":636"
default:
parsedTarget = u.Host + ":443"
if u.Scheme != "" {
if u.Scheme == "https" {
return u.String(), "https", nil
}
} else {
parsedTarget = u.Host
return "", proto, errors.New("can't handle the scheme '" + u.Scheme + "' - try providing the target in the format <host>:<port>")
} else if u.Port() == "" {
return "https://" + u.Host, "https", nil
}
return parsedTarget, nil
return u.Host, "tcp", nil
}
func init() {

View File

@@ -3,115 +3,580 @@ package main
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"testing"
)
func TestProbeHandler(t *testing.T) {
certContent, err := ioutil.ReadFile("test/badssl.com-client.pem")
if err != nil {
t.Fatalf("Can't read test client certificate from disk")
}
var clientCert = `-----BEGIN CERTIFICATE-----
MIIC6jCCApCgAwIBAgIQPbn1oJJ0lvHOxk3BbnhGMTAKBggqhkjOPQQDAjCBhTEL
MAkGA1UEBhMCR0IxEDAOBgNVBAgTB0VuZ2xhbmQxDzANBgNVBAcTBkxvbmRvbjEU
MBIGA1UECRMLMTIzIEZha2UgU3QxEDAOBgNVBBETB1NXMThYWFgxEzARBgNVBAoT
CnJpYmJ5YmliYnkxFjAUBgNVBAMTDXJpYmJ5YmliYnkubWUwHhcNMTkwMzI5MDc1
MjI5WhcNMjAwMzI4MDc1MjI5WjAdMRswGQYDVQQDExJjZXJ0LnJpYmJ5YmliYnku
bWUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASlHGGsAAEMpyBVkgSZazMcYmHH
4K8+m9VI9nSnD4t1b01jYuNAsJjvnRI2iGLOxQ1i8KgzgeZz6ud1mJLIudTzo4IB
RzCCAUMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
BQcDAjAMBgNVHRMBAf8EAjAAMGgGA1UdDgRhBF9mNzphMzo4NDo0ZDo0NjowOTpl
Nzo5ZDpiNzo3MjphMTo5ZTpkOTpjMDoxYTpmYzpjMzplODplZDozOTozMTo5Mzox
MjpmMDplZTowODo2YTo2Mzo3NzphNjplMDoyMjBqBgNVHSMEYzBhgF8xNTpkZDo0
MTo4ODoxODo0YjoxOTo2NToyYjo2ZTo0Njo1NTozZTo3MTo0MzpjYjphMjo3Nzpk
YzpiNTpjZToxMTpiZTo2NDo3ODo3Zjo1OTo2NzpiYTpmMDo0YTowNTAuBgNVHREE
JzAlghJjZXJ0LnJpYmJ5YmliYnkubWWCCWxvY2FsaG9zdIcEfwAAATAKBggqhkjO
PQQDAgNIADBFAiEAq5AUjiAQxMy0g0f2KyFshTu5QPXXSPo+VTBSQcYuEzICIAWr
JxpZXB4hH2+sEZ4z+bH6l47wbYqOT02d/VNbk3vw
-----END CERTIFICATE-----`
keyContent, err := ioutil.ReadFile("test/badssl.com-client-key.pem")
if err != nil {
t.Fatalf("Can't read test client certificate key from disk")
}
var clientKey = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIPfP8yJatMwUfCyNdIQiQANO2vd3QQIoHJ6g+o8kb7PJoAoGCCqGSM49
AwEHoUQDQgAEpRxhrAABDKcgVZIEmWszHGJhx+CvPpvVSPZ0pw+LdW9NY2LjQLCY
750SNohizsUNYvCoM4Hmc+rndZiSyLnU8w==
-----END EC PRIVATE KEY-----`
keyBlock, _ := pem.Decode(keyContent)
var clientCertWrong = `-----BEGIN CERTIFICATE-----
MIIC7zCCApWgAwIBAgIRAPx4XNhgs5QfvE6FHnYa3uQwCgYIKoZIzj0EAwIwgYUx
CzAJBgNVBAYTAkdCMRAwDgYDVQQIEwdFbmdsYW5kMQ8wDQYDVQQHEwZMb25kb24x
FDASBgNVBAkTCzEyMyBGYWtlIFN0MRAwDgYDVQQREwdTVzE4WFhYMRMwEQYDVQQK
EwpyaWJieWJpYmJ5MRYwFAYDVQQDEw1yaWJieWJpYmJ5Lm1lMB4XDTE5MDMyNzE2
MTgzOVoXDTIwMDMyNjE2MTgzOVowHzEdMBsGA1UEAxMUY2xpZW50LnJpYmJ5Ymli
YnkubWUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQtlqtCTzZNCdDiMHKD/p1F
97/I1MnkRK+QdUxEDnRhHAuMOhypxJ6NruZz+wXLnJEmUYmTsHkz1a4tKz2YJCUp
o4IBSTCCAUUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
BgEFBQcDATAMBgNVHRMBAf8EAjAAMGgGA1UdDgRhBF9kYzowNDozMjo0ZTpkOTo4
YjphNTplMDpmNjo5MjpkYzpiYzoxOTo1NTo0ZDo0YjpiNTo5YTo5OTpjYjo4Zjoz
ZjplMTpkNzo3MDoyMTo2MzpmZDo4YTo4MDpjMzpiNzBqBgNVHSMEYzBhgF82YTo0
MDozNTowZjpmZTowMjpkNzo0Zjo5ODozZTo3ODoyMTpjMDo0YTo5YzpjZTo2Nzoz
NDpiZDo4MjowYTo3MjpkMzpjOTo3Njo5MDo3Nzo5ODpmMDo2NTpmYzpkMDAwBgNV
HREEKTAnghRjbGllbnQucmliYnliaWJieS5tZYIJbG9jYWxob3N0hwR/AAABMAoG
CCqGSM49BAMCA0gAMEUCIQCa7ru0f0/HVoGa7aBJqACMBfiXWCI159WGt2B7Mxvf
VAIgX9O8fOl6qmsJyfMkfdmv6lo9oAWIecDLpVtqEj5i2Qc=
-----END CERTIFICATE-----`
keyBlockDecrypted, err := x509.DecryptPEMBlock(keyBlock, []byte("badssl.com"))
if err != nil {
t.Fatalf("Issue decrypting test client key")
}
var clientKeyWrong = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEILnEJttULi+2cupO4ta6IB9bEeul6rMGFSpPMB7kPuSwoAoGCCqGSM49
AwEHoUQDQgAELZarQk82TQnQ4jByg/6dRfe/yNTJ5ESvkHVMRA50YRwLjDocqcSe
ja7mc/sFy5yRJlGJk7B5M9WuLSs9mCQlKQ==
-----END EC PRIVATE KEY-----`
keyContent = pem.EncodeToMemory(&pem.Block{Type: keyBlock.Type, Bytes: keyBlockDecrypted})
var serverCert = `-----BEGIN CERTIFICATE-----
MIIC6jCCApGgAwIBAgIRAO+sgyd/vcnDgfmafkgALKwwCgYIKoZIzj0EAwIwgYUx
CzAJBgNVBAYTAkdCMRAwDgYDVQQIEwdFbmdsYW5kMQ8wDQYDVQQHEwZMb25kb24x
FDASBgNVBAkTCzEyMyBGYWtlIFN0MRAwDgYDVQQREwdTVzE4WFhYMRMwEQYDVQQK
EwpyaWJieWJpYmJ5MRYwFAYDVQQDEw1yaWJieWJpYmJ5Lm1lMB4XDTE5MDMyOTA3
NTIyN1oXDTIwMDMyODA3NTIyN1owHTEbMBkGA1UEAxMSY2VydC5yaWJieWJpYmJ5
Lm1lMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEY5nQFSmpZnFvjbAicuElYlT2
xQvO+LgYt+5bcGfemT5HRq63tljiGlsyNXAysAmMwT9+blu8sLqkyh6PMFesJ6OC
AUcwggFDMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
BQUHAwIwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfZmI6NDM6NWY6M2Y6NTE6NGI6
NjA6YTI6YzQ6NzI6ZjE6MGQ6OTM6ZDA6YjQ6ODA6N2Y6Mjc6NjM6Yjk6NWI6NTQ6
ZGQ6NzI6NzU6N2Q6MDU6N2U6ZTc6Y2U6OTM6YTMwagYDVR0jBGMwYYBfMTU6ZGQ6
NDE6ODg6MTg6NGI6MTk6NjU6MmI6NmU6NDY6NTU6M2U6NzE6NDM6Y2I6YTI6Nzc6
ZGM6YjU6Y2U6MTE6YmU6NjQ6Nzg6N2Y6NTk6Njc6YmE6ZjA6NGE6MDUwLgYDVR0R
BCcwJYISY2VydC5yaWJieWJpYmJ5Lm1lgglsb2NhbGhvc3SHBH8AAAEwCgYIKoZI
zj0EAwIDRwAwRAIgI6w7Px0UnI3AAP4n9ApO1gNIhY+ECEb0EZvKopmNUn0CIHN4
MEaXLzEfNdNi7E521qIR+bhV/mu8nubZIsG4K383
-----END CERTIFICATE-----`
emptyRootCAs := x509.NewCertPool()
var serverKey = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAeLgH2jonGdCgdG1MpEy9wAgxvCSC4N7sK3hC0GZM7MoAoGCCqGSM49
AwEHoUQDQgAEY5nQFSmpZnFvjbAicuElYlT2xQvO+LgYt+5bcGfemT5HRq63tlji
GlsyNXAysAmMwT9+blu8sLqkyh6PMFesJw==
-----END EC PRIVATE KEY-----`
certificate, err := tls.X509KeyPair(certContent, keyContent)
var expiredCert = `-----BEGIN CERTIFICATE-----
MIIC2DCCAn6gAwIBAgIQeP4wyiBMCZ5TLpM40Ho6UzAKBggqhkjOPQQDAjCBhTEL
MAkGA1UEBhMCR0IxEDAOBgNVBAgTB0VuZ2xhbmQxDzANBgNVBAcTBkxvbmRvbjEU
MBIGA1UECRMLMTIzIEZha2UgU3QxEDAOBgNVBBETB1NXMThYWFgxEzARBgNVBAoT
CnJpYmJ5YmliYnkxFjAUBgNVBAMTDXJpYmJ5YmliYnkubWUwHhcNMTkwMzI5MDgw
MTM4WhcNMTkwMzI4MDgwMTM4WjAdMRswGQYDVQQDExJjZXJ0LnJpYmJ5YmliYnku
bWUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASjDs0ehi0miAKmDnuCmRyWaKOY
+h0MugoFngChyygYCY+mOb/+HV5AYUEf1NFJLz4DtYnNKyWNHnX7vUPEh+Ico4IB
NTCCATEwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
BQcDAjAMBgNVHRMBAf8EAjAAMGgGA1UdDgRhBF9mNTo1NDpmYzphNTo1ZjplMzo5
YTo3MzplNzo1YTo0ZDowNzo0MTo4YjoyOTo2ZDpiNzpiNTpjMDpiZjowMzpkZTo5
Zjo5NTozNzphMjphNDo4MDo2YTo3MDozNDpmNjBqBgNVHSMEYzBhgF9iOTpjMDo2
NzoyYjo2YTpiNzowMToyMjo2Zjo1NTplMjpiMDphNDoyNDo1YTo5NzplMzpjYzpi
MTo3Yjo4ZjoyNDpiNTo1NToxYzpiMDo3NTozMDplNToxZDo3OTpmZDAcBgNVHREE
FTATggCCCWxvY2FsaG9zdIcEfwAAATAKBggqhkjOPQQDAgNIADBFAiB+ZGtScM5Y
QHra5d+lqFRJOd7WXkoU03QHWOP3pSqbCAIhAJreqVQ3dUME4j9LYbQWmD96agdL
2uxG31qfCa/T5TCq
-----END CERTIFICATE-----`
var expiredKey = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIFDlw65IF8NLdgIWU1ipkMffcE6MgZ5DHTGzf0WN09EJoAoGCCqGSM49
AwEHoUQDQgAEow7NHoYtJogCpg57gpkclmijmPodDLoKBZ4AocsoGAmPpjm//h1e
QGFBH9TRSS8+A7WJzSsljR51+71DxIfiHA==
-----END EC PRIVATE KEY-----`
var caCert = `-----BEGIN CERTIFICATE-----
MIIDBjCCAqygAwIBAgIRAJxzFmvhp8ef68W7SQrt5KwwCgYIKoZIzj0EAwIwgYUx
CzAJBgNVBAYTAkdCMRAwDgYDVQQIEwdFbmdsYW5kMQ8wDQYDVQQHEwZMb25kb24x
FDASBgNVBAkTCzEyMyBGYWtlIFN0MRAwDgYDVQQREwdTVzE4WFhYMRMwEQYDVQQK
EwpyaWJieWJpYmJ5MRYwFAYDVQQDEw1yaWJieWJpYmJ5Lm1lMB4XDTE5MDMyOTA3
NTIyMloXDTI0MDMyNzA3NTIyMlowgYUxCzAJBgNVBAYTAkdCMRAwDgYDVQQIEwdF
bmdsYW5kMQ8wDQYDVQQHEwZMb25kb24xFDASBgNVBAkTCzEyMyBGYWtlIFN0MRAw
DgYDVQQREwdTVzE4WFhYMRMwEQYDVQQKEwpyaWJieWJpYmJ5MRYwFAYDVQQDEw1y
aWJieWJpYmJ5Lm1lMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE94APL4adMA7A
tSSfxcHzzxdVBCwJju6jVCf5qRqG4Qz0neXlde6jIXocZvoboZJiA2e7BadnjoPN
2sTB8mgg4KOB+jCB9zAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zBo
BgNVHQ4EYQRfMTU6ZGQ6NDE6ODg6MTg6NGI6MTk6NjU6MmI6NmU6NDY6NTU6M2U6
NzE6NDM6Y2I6YTI6Nzc6ZGM6YjU6Y2U6MTE6YmU6NjQ6Nzg6N2Y6NTk6Njc6YmE6
ZjA6NGE6MDUwagYDVR0jBGMwYYBfMTU6ZGQ6NDE6ODg6MTg6NGI6MTk6NjU6MmI6
NmU6NDY6NTU6M2U6NzE6NDM6Y2I6YTI6Nzc6ZGM6YjU6Y2U6MTE6YmU6NjQ6Nzg6
N2Y6NTk6Njc6YmE6ZjA6NGE6MDUwCgYIKoZIzj0EAwIDSAAwRQIhANycTcKTH1DU
eu3Xuz8CdtgT67yqUTxDy0O5kS8fFPUVAiAV0u1M7dQYV+buY8oOLYnZxondrb7/
BNltD7A8Y0S0hw==
-----END CERTIFICATE-----`
// Test the basic case: a typical HTTPS server
func TestProbeHandlerConnectSuccess(t *testing.T) {
server, err := server()
if err != nil {
t.Fatalf(err.Error())
}
// Test the behaviour of various target URIs
// 'ok' denotes whether we expect a succesful tls connection
cases := []struct {
uri string
ok bool
tlsConfig *tls.Config
}{
// Test against an assumed valid, reachable and functioning HTTPS address
{uri: "google.com:443", ok: true, tlsConfig: &tls.Config{}},
// Test against a HTTP address
{uri: "google.com:80", ok: false, tlsConfig: &tls.Config{}},
// Test against an expired certificate when we're rejecting invalid certs
{uri: "expired.badssl.com:443", ok: false, tlsConfig: &tls.Config{}},
// Test against an expired certificate when we're accepting invalid certs
{uri: "expired.badssl.com:443", ok: true, tlsConfig: &tls.Config{InsecureSkipVerify: true}},
// Test against a target with no port
{uri: "google.com", ok: true, tlsConfig: &tls.Config{}},
// Test against a string with spaces
{uri: "with spaces", ok: false, tlsConfig: &tls.Config{}},
// Test against nothing
{uri: "", ok: false, tlsConfig: &tls.Config{}},
// Test with client authentication
{uri: "client.badssl.com:443", ok: true, tlsConfig: &tls.Config{Certificates: []tls.Certificate{certificate}}},
// Test with an empty root CA bundle
{uri: "google.com:443", ok: false, tlsConfig: &tls.Config{RootCAs: emptyRootCAs}},
// Test with a https scheme
{uri: "https://google.com", ok: true, tlsConfig: &tls.Config{}},
// Test with a https scheme and port
{uri: "https://google.com:443", ok: true, tlsConfig: &tls.Config{}},
}
fmt.Println("Note: The error logs in these tests are expected. One of the important tests is that we return the expected body, even in the face of errors.")
successMetricRegexp, err := regexp.Compile("(ssl_tls_connect_success [0-1])")
rr, err := probe(server.URL)
if err != nil {
t.Fatalf("Error compiling success metric: " + err.Error())
t.Fatalf(err.Error())
}
for _, test := range cases {
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 1`")
}
uri := "/probe?target=" + test.uri
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
t.Fatal(err)
}
server.Close()
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
probeHandler(w, r, test.tlsConfig)
})
// Test against a non-existent server
func TestProbeHandlerConnectSuccessFalse(t *testing.T) {
rr, err := probe("localhost:6666")
if err != nil {
t.Fatalf(err.Error())
}
handler.ServeHTTP(rr, req)
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 0`")
}
// We should always return a 200, no matter what
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
}
// Make sure we're getting the ssl_tls_connect_success metric back
if !successMetricRegexp.MatchString(rr.Body.String()) {
t.Errorf("can't find ssl_tls_connect_success metric in response body w/ %q", uri)
}
// Test with an empty target
func TestProbeHandlerEmptyTarget(t *testing.T) {
rr, err := probe("")
if err != nil {
t.Fatalf(err.Error())
}
// Make sure we're getting the result we expect from ssl_tls_connect_success
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1")
if test.ok && !ok {
t.Errorf("expected tls connection to succeed but it failed w/ %q", uri)
}
if !test.ok && ok {
t.Errorf("expected tls connection to fail but it succeeded w/ %q", uri)
}
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 0`")
}
}
// Test with spaces in the target
func TestProbeHandlerSpaces(t *testing.T) {
rr, err := probe("with spaces")
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 0`")
}
}
// Test with a uri protocol the exporter doesn't implement a client for
func TestProbeHandlerBadScheme(t *testing.T) {
rr, err := probe("ldaps://example.com")
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 0`")
}
}
// Test that probe uses a http client when the scheme is https://
func TestProbeHandlerHTTPSClient(t *testing.T) {
rr, err := probe("https://example.com")
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_client_protocol{protocol=\"https\"} 1")
if !ok {
t.Errorf("expected `ssl_client_protocol{protocol=\"https\"} 1`")
}
ok = strings.Contains(rr.Body.String(), "ssl_client_protocol{protocol=\"tcp\"} 0")
if !ok {
t.Errorf("expected `ssl_client_protocol{protocol=\"tcp\"} 0`")
}
}
// Test that probe uses a tcp client when the host is of the form <host>:<port>
func TestProbeHandlerTCPClient(t *testing.T) {
rr, err := probe("example.com:443")
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_client_protocol{protocol=\"tcp\"} 1")
if !ok {
t.Errorf("expected `ssl_client_protocol{protocol=\"tcp\"} 1`")
}
ok = strings.Contains(rr.Body.String(), "ssl_client_protocol{protocol=\"https\"} 0")
if !ok {
t.Errorf("expected `ssl_client_protocol{protocol=\"https\"} 0`")
}
}
// Test that a https client is used when there is no protocol or port in the target address
func TestProbeHandlerNoProtocolNoPort(t *testing.T) {
rr, err := probe("example.com")
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_client_protocol{protocol=\"https\"} 1")
if !ok {
t.Errorf("expected `ssl_client_protocol{protocol=\"https\"} 1`")
}
}
// Test against a HTTP server
func TestProbeHandlerHTTP(t *testing.T) {
server, err := serverHTTP()
if err != nil {
t.Fatalf(err.Error())
}
rr, err := probe(server.URL)
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 0`")
}
server.Close()
}
// Test that the exporter returns the correct list of IPs
func TestProbeHandlerIPs(t *testing.T) {
server, err := server()
if err != nil {
t.Fatalf(err.Error())
}
rr, err := probe(server.URL)
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_cert_subject_alternative_ips{ips=\",127.0.0.1,\"")
if !ok {
t.Errorf("expected `ssl_cert_subject_alternative_ips{ips=\",127.0.0.1,\"`")
}
server.Close()
}
// Test that the exporter returns the correct CN
func TestProbeHandlerCommonName(t *testing.T) {
server, err := server()
if err != nil {
t.Fatalf(err.Error())
}
rr, err := probe(server.URL)
if err != nil {
t.Fatalf(err.Error())
}
log.Println(rr.Body.String())
ok := strings.Contains(rr.Body.String(), "ssl_cert_subject_common_name{issuer_cn=\"ribbybibby.me\",serial_no=\"318581226177353336430613662595136105644\",subject_cn=\"cert.ribbybibby.me\"} 1")
if !ok {
t.Errorf("expected `ssl_cert_subject_common_name{issuer_cn=\"ribbybibby.me\",serial_no=\"318581226177353336430613662595136105644\",subject_cn=\"cert.ribbybibby.me\"} 1`")
}
server.Close()
}
// Test that the exporter returns the correct list of DNS names
func TestProbeHandlerDNSNames(t *testing.T) {
server, err := server()
if err != nil {
t.Fatalf(err.Error())
}
rr, err := probe(server.URL)
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_cert_subject_alternative_dnsnames{dnsnames=\",cert.ribbybibby.me,localhost,\"")
if !ok {
t.Errorf("expected `ssl_cert_subject_alternative_dnsnames{dnsnames=\",cert.ribbybibby.me,localhost,\"`")
}
server.Close()
}
// Test client authentication
func TestProbeHandlerClientAuth(t *testing.T) {
server, err := serverClientAuth()
if err != nil {
t.Fatalf(err.Error())
}
rr, err := probeClientAuth(server.URL)
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 1`")
}
server.Close()
}
// Test client authentication with a bad client certificate
func TestProbeHandlerClientAuthWrongClientCert(t *testing.T) {
server, err := serverClientAuth()
if err != nil {
t.Fatalf(err.Error())
}
rr, err := probeClientAuthBad(server.URL)
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 0`")
}
server.Close()
}
// Test against a server with an expired certificate
func TestProbeHandlerExpired(t *testing.T) {
server, err := serverExpired()
if err != nil {
t.Fatalf(err.Error())
}
rr, err := probe(server.URL)
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 0`")
}
server.Close()
}
// Test against a server with an expired certificate with an insecure probe
func TestProbeHandlerExpiredInsecure(t *testing.T) {
server, err := serverExpired()
if err != nil {
t.Fatalf(err.Error())
}
rr, err := probeInsecure(server.URL)
if err != nil {
t.Fatalf(err.Error())
}
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1")
if !ok {
t.Errorf("expected `ssl_tls_connect_success 1`")
}
server.Close()
}
func probe(url string) (*httptest.ResponseRecorder, error) {
uri := "/probe?target=" + url
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
probeHandler(w, r, &tls.Config{
RootCAs: certPool(),
})
})
handler.ServeHTTP(rr, req)
return rr, nil
}
func probeInsecure(url string) (*httptest.ResponseRecorder, error) {
uri := "/probe?target=" + url
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
probeHandler(w, r, &tls.Config{
RootCAs: certPool(),
InsecureSkipVerify: true,
})
})
handler.ServeHTTP(rr, req)
return rr, nil
}
func probeClientAuth(url string) (*httptest.ResponseRecorder, error) {
clientCertificate, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
if err != nil {
return nil, err
}
uri := "/probe?target=" + url
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
probeHandler(w, r, &tls.Config{
Certificates: []tls.Certificate{clientCertificate},
RootCAs: certPool(),
})
})
handler.ServeHTTP(rr, req)
return rr, nil
}
func probeClientAuthBad(url string) (*httptest.ResponseRecorder, error) {
clientCertificate, err := tls.X509KeyPair([]byte(clientCertWrong), []byte(clientKeyWrong))
if err != nil {
return nil, err
}
uri := "/probe?target=" + url
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return nil, err
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
probeHandler(w, r, &tls.Config{
Certificates: []tls.Certificate{clientCertificate},
RootCAs: certPool(),
})
})
handler.ServeHTTP(rr, req)
return rr, nil
}
func server() (*httptest.Server, error) {
serverCertificate, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey))
if err != nil {
return nil, err
}
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world")
}))
server.TLS = &tls.Config{
Certificates: []tls.Certificate{serverCertificate},
}
server.StartTLS()
return server, nil
}
func serverClientAuth() (*httptest.Server, error) {
certPool := certPool()
serverCertificate, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey))
if err != nil {
return nil, err
}
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world")
}))
server.TLS = &tls.Config{
Certificates: []tls.Certificate{serverCertificate},
ClientAuth: tls.RequireAndVerifyClientCert,
RootCAs: certPool,
ClientCAs: certPool,
}
server.StartTLS()
return server, nil
}
func serverExpired() (*httptest.Server, error) {
certPool := certPool()
serverCertificate, err := tls.X509KeyPair([]byte(expiredCert), []byte(expiredKey))
if err != nil {
return nil, err
}
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world")
}))
server.TLS = &tls.Config{
Certificates: []tls.Certificate{serverCertificate},
RootCAs: certPool,
ClientCAs: certPool,
}
server.StartTLS()
return server, nil
}
func serverHTTP() (*httptest.Server, error) {
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world")
}))
server.Start()
return server, nil
}
func certPool() *x509.CertPool {
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM([]byte(caCert))
return certPool
}

View File

@@ -1,30 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,8906E5B87ECB8682
cY/EOwDILOiCPzyqZanlrhrb5h+fCnS/mHhsNme0Zyrk9udaBZKwxkPtUC6rJZ9/
rX4HQch9tuVy992CDe1bl4l+3EfqshUhxtivG8GHiFj7TNo5Ia9LaE+KwugB2rdX
J8odwWmwE8Y9TX09WyPKHikjg2nAOgN3Gf+GeJW6fL9xcEVmIgDyX6kBusQYWIQ3
PMYqyaBhL8UCZVRpiUINTrJXCnNAI4t6K0i4J+hjyekRnA5PQ+A/0+C9J+kQW+wj
uDWkQtyyBNsscGhXrL9ita7/UtJa+/aS99FLTnxD+fqyU4svzNIQWmZBcIxb9Lxq
LHcxNaQCtFycD1iU8Tsq9HY1apKrdPBjaaYne6qCg8nIPWpo3gWoffu+R3EfVcdZ
lNgKL3Cd1oCJy/Vcz+u0iayCPT0zfP1dLlGpL2U7L7+8pJ88FbXt/F1th9QEUp5z
dISg/6ukHyIkfXwoxYXkin9fW2clo99+fuXkJaVPEOnRmr0+kf+1S0Uw8fkete4v
f0IUxUwydxCXFN2ZFZ6o/LFLMLRfNAqGSGjqGeY1L0ILkJPo3KBqlmUhl2FIROOW
4rSrfpujjsOqlPncJ9apW04RnqhY7t0YOtp0rMK+gIDteKD+utCyh+UAetiiqTiB
ZDup/kzPNDClSDU6cgRbZUf/Nt8RiBcVeX4TfDb1eEhqV0BTHrLOBo8yp6ETDlJM
CKO5i9kb5fUaFPFD5VTc5rnY4qV/hUj/uyAVK757A+m9nn7fYOMeaRAgOEvChYSf
Qam0VPgpJxfjsaw0BFOMyfsHNJqvTbPXA+hIKD3fhP6jNcgpKTLHJp89J4XUOeIR
tvh/u7vljhVygP7ZQOpCoX1xSMdJMO7Qhrh3O/2fq/EJertJD2PQ0Zgqb8wosHn/
Aw9VWT6849jL55Xd5l3zXmI9vU0Le4HP3NstV9jjpcp91dU9yfQpcuIo8U26u+4r
LR1VmhZJjo7FBOpyJZ1Jb3vyp+nPI+tH214DhM9LuXvMbf3ORhXTMOlqSABUmo/+
t+QjVfcEuhFR9CTVWZUIXflJk/euvzqTQdm8iz7JuFzQOhoXjiPIq0GqtCk10SeL
zqHz1s0TZNcrZyzkmiHuWjGVwHN/XZA3dW67uj522hD7EzucKE9CoCdJ3f4JEWmS
CQwEbba6SKAR6iBlouIYLVdkOBgimGiF13rCwvdN234hQeJT8Wc87iG+uDw73PJL
+amDglATH4wIpBk4xmjh/GTRDK0yH9jp7Dv9iwShbjk0yOuoz5yDn8VPqRIREn8d
9sAaiFUUQ/9XrdlA5F+49OznClDWLKHK8sSAAFyrzvoCcqseSKbvLyrlHGT0fiot
obgDu/W+K2xEOjQeaIyVI5J1qOi6k78fyv1vutjEs6mcTRtDAIxi+V5y5lXUEj0v
OWYsbp9yb8Yq602vV8UYSROd+1xdE+7Td3ENLYE7MnVqju7a5NRfnZYgAU03NgIf
nHGFZC6/tMz/PXS+D0dqzXxwEjH5JQzGBjvSQHK09gHtCfcyshMQQWtXZGQViZX8
QdYXiaq67nJex0DjWTt56a4EgsdYC1J28bJ3GAkrWNkDFRmlx49zvA==
-----END RSA PRIVATE KEY-----

View File

@@ -1,27 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEnTCCAoWgAwIBAgIJAPC7KMFjfslXMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp
c2NvMQ8wDQYDVQQKDAZCYWRTU0wxMTAvBgNVBAMMKEJhZFNTTCBDbGllbnQgUm9v
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTcxMTE2MDUzNjMzWhcNMTkxMTE2
MDUzNjMzWjBvMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG
A1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0GA1UECgwGQmFkU1NMMSIwIAYDVQQDDBlC
YWRTU0wgQ2xpZW50IENlcnRpZmljYXRlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxzdfEeseTs/rukjly6MSLHM+Rh0enA3Ai4Mj2sdl31x3SbPoen08
utVhjPmlxIUdkiMG4+ffe7N+JtDLG75CaxZp9CxytX7kywooRBJsRnQhmQPca8MR
WAJBIz+w/L+3AFkTIqWBfyT+1VO8TVKPkEpGdLDovZOmzZAASi9/sj+j6gM7AaCi
DeZTf2ES66abA5pOp60Q6OEdwg/vCUJfarhKDpi9tj3P6qToy9Y4DiBUhOct4MG8
w5XwmKAC+Vfm8tb7tMiUoU0yvKKOcL6YXBXxB2kPcOYxYNobXavfVBEdwSrjQ7i/
s3o6hkGQlm9F7JPEuVgbl/Jdwa64OYIqjQIDAQABoy0wKzAJBgNVHRMEAjAAMBEG
CWCGSAGG+EIBAQQEAwIHgDALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQELBQADggIB
AKpzk1ZTunWuof3DIer2Abq7IV3STGeFaoH4TuHdSbmXwC0KuPkv7wVPgPekyRaH
b9CBnsreRF7eleD1M63kakhdnA1XIbdJw8sfSDlKdI4emmb4fzdaaPxbrkQ5IxOB
QDw5rTUFVPPqFWw1bGP2zrKD1/i1pxUtGM0xem1jR7UZYpsSPs0JCOHKZOmk8OEW
Uy+Jp4gRzbMLZ0TrvajGEZXRepjOkXObR81xZGtvTNP2wl1zm13ffwIYdqJUrf1H
H4miU9lVX+3/Z+2mVHBWhzBgbTmo06s3uwUE6JsxUGm2/w4NNblRit0uQcGw7ba8
kl2d5rZQscFsqNFz2vRjj1G0dO8S3owmuF0izZO9Fqvq0jB6oaUkxcAcTKFSjs2z
wy1oy+cu8iO3GRbfAW7U0xzGp9MnkdPS5dHzvhod3/DK0YVskfxZF7M8GhkjT7Qm
2EUBQNNMNXC3g/GXTdXOgqqjW5GXahI8Z6Q4OYN6xZwuEhizwKkgojwaww2YgYT9
MJXciJZWr3QXvFdBH7m0zwpKgQ1wm6j3yeyuRphq2lEtU3OQl55A3tXtvqyMXsxk
xMCCNQdmKQt0WYmMS3Xj/AfAY2sjCWziDflvW5mGCUjSYdZ+r3JIIF4m/FNCIO1d
Ioacp9qb0qL9duFlVHtFiPgoKrEdJaNVUL7NG9ppF8pR
-----END CERTIFICATE-----