You've already forked ssl_exporter
mirror of
https://github.com/ribbybibby/ssl_exporter.git
synced 2025-07-15 23:54:18 +02:00
Add file prober
This commit is contained in:
101
README.md
101
README.md
@ -28,14 +28,16 @@ meaningful visualisations and consoles.
|
|||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Metrics](#metrics)
|
- [Metrics](#metrics)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
|
- [TCP](#tcp)
|
||||||
|
- [HTTPS](#https)
|
||||||
|
- [File](#file)
|
||||||
- [Configuration file](#configuration-file)
|
- [Configuration file](#configuration-file)
|
||||||
- [<module>](#module)
|
- [<module>](#module)
|
||||||
- [<tls_config>](#tls_config)
|
- [<tls_config>](#tls_config)
|
||||||
- [<https_probe>](#https_probe)
|
- [<https_probe>](#https_probe)
|
||||||
- [<tcp_probe>](#tcp_probe)
|
- [<tcp_probe>](#tcp_probe)
|
||||||
- [Example Queries](#example-queries)
|
- [Example Queries](#example-queries)
|
||||||
- [Peer Cerificates vs Verified Chain Certificates](#peer-cerificates-vs-verified-chain-certificates)
|
- [Peer Certificates vs Verified Chain Certificates](#peer-certificates-vs-verified-chain-certificates)
|
||||||
- [Proxying](#proxying)
|
|
||||||
- [Grafana](#grafana)
|
- [Grafana](#grafana)
|
||||||
|
|
||||||
Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
|
Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
|
||||||
@ -47,7 +49,7 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
|
|||||||
|
|
||||||
Similarly to the blackbox_exporter, visiting
|
Similarly to the blackbox_exporter, visiting
|
||||||
[http://localhost:9219/probe?target=example.com:443](http://localhost:9219/probe?target=example.com:443)
|
[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`
|
will return certificate metrics for example.com. The `ssl_probe_success`
|
||||||
metric indicates if the probe has been successful.
|
metric indicates if the probe has been successful.
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
@ -89,23 +91,27 @@ Flags:
|
|||||||
## Metrics
|
## Metrics
|
||||||
|
|
||||||
| Metric | Meaning | Labels |
|
| Metric | Meaning | Labels |
|
||||||
| ----------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
| ----------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
||||||
| ssl_cert_not_after | The date after which a peer certificate expires. Expressed as a Unix Epoch Time. | serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
| ssl_cert_not_after | The date after which a peer certificate expires. Expressed as a Unix Epoch Time. | serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
||||||
| ssl_cert_not_before | The date before which a peer certificate is not valid. Expressed as a Unix Epoch Time. | serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
| ssl_cert_not_before | The date before which a peer certificate is not valid. Expressed as a Unix Epoch Time. | serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
||||||
|
| ssl_file_cert_not_after | The date after which a certificate found by the file prober expires. Expressed as a Unix Epoch Time. | file, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
||||||
|
| ssl_file_cert_not_before | The date before which a certificate found by the file prober is not valid. Expressed as a Unix Epoch Time. | file, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
||||||
| ssl_ocsp_response_next_update | The nextUpdate value in the OCSP response. Expressed as a Unix Epoch Time | |
|
| ssl_ocsp_response_next_update | The nextUpdate value in the OCSP response. Expressed as a Unix Epoch Time | |
|
||||||
| ssl_ocsp_response_produced_at | The producedAt value in the OCSP response. Expressed as a Unix Epoch Time | |
|
| ssl_ocsp_response_produced_at | The producedAt value in the OCSP response. Expressed as a Unix Epoch Time | |
|
||||||
| ssl_ocsp_response_revoked_at | The revocationTime value in the OCSP response. Expressed as a Unix Epoch Time | |
|
| ssl_ocsp_response_revoked_at | The revocationTime value in the OCSP response. Expressed as a Unix Epoch Time | |
|
||||||
| ssl_ocsp_response_status | The status in the OCSP response. 0=Good 1=Revoked 2=Unknown | |
|
| ssl_ocsp_response_status | The status in the OCSP response. 0=Good 1=Revoked 2=Unknown | |
|
||||||
| ssl_ocsp_response_stapled | Does the connection state contain a stapled OCSP response? Boolean. | |
|
| ssl_ocsp_response_stapled | Does the connection state contain a stapled OCSP response? Boolean. | |
|
||||||
| ssl_ocsp_response_this_update | The thisUpdate value in the OCSP response. Expressed as a Unix Epoch Time | |
|
| ssl_ocsp_response_this_update | The thisUpdate value in the OCSP response. Expressed as a Unix Epoch Time | |
|
||||||
|
| ssl_probe_success | Was the probe successful? Boolean. | |
|
||||||
| ssl_prober | The prober used by the exporter to connect to the target. Boolean. | prober |
|
| ssl_prober | The prober used by the exporter to connect to the target. Boolean. | prober |
|
||||||
| ssl_tls_connect_success | Was the TLS connection successful? Boolean. | |
|
|
||||||
| ssl_tls_version_info | The TLS version used. Always 1. | version |
|
| ssl_tls_version_info | The TLS version used. Always 1. | version |
|
||||||
| ssl_verified_cert_not_after | The date after which a certificate in the verified chain expires. Expressed as a Unix Epoch Time. | chain_no, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
| ssl_verified_cert_not_after | The date after which a certificate in the verified chain expires. Expressed as a Unix Epoch Time. | chain_no, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
||||||
| ssl_verified_cert_not_before | The date before which a certificate in the verified chain is not valid. Expressed as a Unix Epoch Time. | chain_no, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
| ssl_verified_cert_not_before | The date before which a certificate in the verified chain is not valid. Expressed as a Unix Epoch Time. | chain_no, serial_no, issuer_cn, cn, dnsnames, ips, emails, ou |
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
### TCP
|
||||||
|
|
||||||
Just like with the blackbox_exporter, you should pass the targets to a single
|
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.
|
instance of the exporter in a scrape config with a clever bit of relabelling.
|
||||||
This allows you to leverage service discovery and keeps configuration
|
This allows you to leverage service discovery and keeps configuration
|
||||||
@ -128,8 +134,11 @@ scrape_configs:
|
|||||||
replacement: 127.0.0.1:9219 # SSL exporter.
|
replacement: 127.0.0.1:9219 # SSL exporter.
|
||||||
```
|
```
|
||||||
|
|
||||||
By default the exporter will make a TCP connection to the target. You can change
|
### HTTPS
|
||||||
this to https by setting the module parameter:
|
|
||||||
|
By default the exporter will make a TCP connection to the target. This will be
|
||||||
|
suitable for most cases but if you want to take advantage of http proxying you
|
||||||
|
can use a HTTPS client by setting the `https` module parameter:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
scrape_configs:
|
scrape_configs:
|
||||||
@ -150,7 +159,53 @@ scrape_configs:
|
|||||||
replacement: 127.0.0.1:9219
|
replacement: 127.0.0.1:9219
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration file
|
This will use proxy servers discovered by the environment variables `HTTP_PROXY`,
|
||||||
|
`HTTPS_PROXY` and `ALL_PROXY`. Or, you can set the `proxy_url` option in the module
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
The latter takes precedence.
|
||||||
|
|
||||||
|
### File
|
||||||
|
|
||||||
|
The `file` prober exports `ssl_file_cert_not_after` and
|
||||||
|
`ssl_file_cert_not_before` for PEM encoded certificates found in local files.
|
||||||
|
|
||||||
|
Files local to the exporter can be scraped by providing them as the target
|
||||||
|
parameter:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl "localhost:9219/probe?module=file&target=/etc/ssl/cert.pem"
|
||||||
|
```
|
||||||
|
|
||||||
|
The target parameter supports globbing (as provided by the
|
||||||
|
[doublestar](https://github.com/bmatcuk/doublestar) package),
|
||||||
|
which allows you to capture multiple files at once:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl "localhost:9219/probe?module=file&target=/etc/ssl/**/*.pem"
|
||||||
|
```
|
||||||
|
|
||||||
|
One specific usage of this prober could be to run the exporter as a DaemonSet in
|
||||||
|
Kubernetes and then scrape each instance to check the expiry of certificates on
|
||||||
|
each node:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: "ssl"
|
||||||
|
metrics_path: /probe
|
||||||
|
params:
|
||||||
|
module: ["file"]
|
||||||
|
target: ["/etc/kubernetes/**/*.crt"]
|
||||||
|
kubernetes_sd_configs:
|
||||||
|
- role: node
|
||||||
|
relabel_configs:
|
||||||
|
- source_labels: [__address__]
|
||||||
|
regex: ^(.*):(.*)$
|
||||||
|
target_label: __address__
|
||||||
|
replacement: ${1}:9219
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration file
|
||||||
|
|
||||||
You can provide further module configuration by providing the path to a
|
You can provide further module configuration by providing the path to a
|
||||||
configuration file with `--config.file`. The file is written in yaml format,
|
configuration file with `--config.file`. The file is written in yaml format,
|
||||||
@ -160,10 +215,10 @@ defined by the schema below.
|
|||||||
modules: [<module>]
|
modules: [<module>]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### \<module\>
|
### \<module\>
|
||||||
|
|
||||||
```
|
```
|
||||||
# The protocol over which the probe will take place (https, tcp)
|
# The type of probe (https, tcp, file)
|
||||||
prober: <prober_string>
|
prober: <prober_string>
|
||||||
|
|
||||||
# How long the probe will wait before giving up.
|
# How long the probe will wait before giving up.
|
||||||
@ -177,7 +232,7 @@ prober: <prober_string>
|
|||||||
[ tcp: <tcp_probe> ]
|
[ tcp: <tcp_probe> ]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### <tls_config>
|
### <tls_config>
|
||||||
|
|
||||||
```
|
```
|
||||||
# Disable target certificate validation.
|
# Disable target certificate validation.
|
||||||
@ -196,14 +251,14 @@ prober: <prober_string>
|
|||||||
[ server_name: <string> ]
|
[ server_name: <string> ]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### <https_probe>
|
### <https_probe>
|
||||||
|
|
||||||
```
|
```
|
||||||
# HTTP proxy server to use to connect to the targets.
|
# HTTP proxy server to use to connect to the targets.
|
||||||
[ proxy_url: <string> ]
|
[ proxy_url: <string> ]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### <tcp_probe>
|
### <tcp_probe>
|
||||||
|
|
||||||
```
|
```
|
||||||
# Use the STARTTLS command before starting TLS for those protocols that support it (smtp, ftp, imap)
|
# Use the STARTTLS command before starting TLS for those protocols that support it (smtp, ftp, imap)
|
||||||
@ -237,13 +292,13 @@ Number of certificates presented by the server:
|
|||||||
count(ssl_cert_not_after) by (instance)
|
count(ssl_cert_not_after) by (instance)
|
||||||
```
|
```
|
||||||
|
|
||||||
Identify instances that have failed to create a valid SSL connection:
|
Identify failed probes:
|
||||||
|
|
||||||
```
|
```
|
||||||
ssl_tls_connect_success == 0
|
ssl_probe_success == 0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Peer Cerificates vs Verified Chain Certificates
|
## Peer Certificates vs Verified Chain Certificates
|
||||||
|
|
||||||
Metrics are exported for the `NotAfter` and `NotBefore` fields for peer
|
Metrics are exported for the `NotAfter` and `NotBefore` fields for peer
|
||||||
certificates as well as for the verified chain that is
|
certificates as well as for the verified chain that is
|
||||||
@ -277,20 +332,6 @@ of trust between the exporter and the target. Genuine clients may hold different
|
|||||||
root certs than the exporter and therefore have different verified chains of
|
root certs than the exporter and therefore have different verified chains of
|
||||||
trust.
|
trust.
|
||||||
|
|
||||||
## Proxying
|
|
||||||
|
|
||||||
The `https` prober 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
|
|
||||||
|
|
||||||
Or, you can set the `proxy_url` option in the module.
|
|
||||||
|
|
||||||
The latter takes precedence.
|
|
||||||
|
|
||||||
## Grafana
|
## Grafana
|
||||||
|
|
||||||
You can find a simple dashboard [here](grafana/dashboard.json) that tracks
|
You can find a simple dashboard [here](grafana/dashboard.json) that tracks
|
||||||
|
@ -22,6 +22,9 @@ var (
|
|||||||
"https": Module{
|
"https": Module{
|
||||||
Prober: "https",
|
Prober: "https",
|
||||||
},
|
},
|
||||||
|
"file": Module{
|
||||||
|
Prober: "file",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -42,7 +45,6 @@ func LoadConfig(confFile string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -18,3 +18,11 @@ scrape_configs:
|
|||||||
target_label: instance
|
target_label: instance
|
||||||
- target_label: __address__
|
- target_label: __address__
|
||||||
replacement: 127.0.0.1:9219 # SSL exporter.
|
replacement: 127.0.0.1:9219 # SSL exporter.
|
||||||
|
- job_name: 'ssl-files'
|
||||||
|
metrics_path: /probe
|
||||||
|
params:
|
||||||
|
module: ["file"]
|
||||||
|
target: ["/etc/ssl/**/*.pem"]
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- 127.0.0.1:9219
|
||||||
|
2
go.mod
2
go.mod
@ -1,7 +1,9 @@
|
|||||||
module github.com/ribbybibby/ssl_exporter
|
module github.com/ribbybibby/ssl_exporter
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/bmatcuk/doublestar/v2 v2.0.3
|
||||||
github.com/prometheus/client_golang v1.8.0
|
github.com/prometheus/client_golang v1.8.0
|
||||||
|
github.com/prometheus/client_model v0.2.0
|
||||||
github.com/prometheus/common v0.14.0
|
github.com/prometheus/common v0.14.0
|
||||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
|
2
go.sum
2
go.sum
@ -30,6 +30,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
|
|||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/bmatcuk/doublestar/v2 v2.0.3 h1:D6SI8MzWzXXBXZFS87cFL6s/n307lEU+thM2SUnge3g=
|
||||||
|
github.com/bmatcuk/doublestar/v2 v2.0.3/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw=
|
||||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
@ -240,7 +240,7 @@
|
|||||||
"steppedLine": false,
|
"steppedLine": false,
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "(up{job=~\"$job\", instance=~\"$instance\"} == 0 or ssl_tls_connect_success{job=~\"$job\", instance=~\"$instance\"} == 0)^0",
|
"expr": "(up{job=~\"$job\", instance=~\"$instance\"} == 0 or ssl_probe_success{job=~\"$job\", instance=~\"$instance\"} == 0)^0",
|
||||||
"format": "time_series",
|
"format": "time_series",
|
||||||
"instant": false,
|
"instant": false,
|
||||||
"legendFormat": "{{instance}}",
|
"legendFormat": "{{instance}}",
|
||||||
@ -407,7 +407,7 @@
|
|||||||
],
|
],
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"expr": "ssl_tls_connect_success{instance=~\"$instance\",job=~\"$job\"} == 0",
|
"expr": "ssl_probe_success{instance=~\"$instance\",job=~\"$job\"} == 0",
|
||||||
"format": "table",
|
"format": "table",
|
||||||
"instant": true,
|
"instant": true,
|
||||||
"intervalFactor": 1,
|
"intervalFactor": 1,
|
||||||
@ -585,14 +585,14 @@
|
|||||||
"value": ["$__all"]
|
"value": ["$__all"]
|
||||||
},
|
},
|
||||||
"datasource": "Prometheus",
|
"datasource": "Prometheus",
|
||||||
"definition": "label_values(ssl_tls_connect_success, job)",
|
"definition": "label_values(ssl_probe_success, job)",
|
||||||
"hide": 0,
|
"hide": 0,
|
||||||
"includeAll": true,
|
"includeAll": true,
|
||||||
"label": "Job",
|
"label": "Job",
|
||||||
"multi": true,
|
"multi": true,
|
||||||
"name": "job",
|
"name": "job",
|
||||||
"options": [],
|
"options": [],
|
||||||
"query": "label_values(ssl_tls_connect_success, job)",
|
"query": "label_values(ssl_probe_success, job)",
|
||||||
"refresh": 1,
|
"refresh": 1,
|
||||||
"regex": "",
|
"regex": "",
|
||||||
"skipUrlSync": false,
|
"skipUrlSync": false,
|
||||||
|
35
prober/file.go
Normal file
35
prober/file.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package prober
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bmatcuk/doublestar/v2"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/ribbybibby/ssl_exporter/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ProbeFile(ctx context.Context, target string, module config.Module, registry *prometheus.Registry) error {
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
files, err := doublestar.Glob(target)
|
||||||
|
if err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(files) == 0 {
|
||||||
|
errCh <- fmt.Errorf("No files found")
|
||||||
|
} else {
|
||||||
|
errCh <- collectFileMetrics(files, registry)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return fmt.Errorf("context timeout, ran out of time")
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
279
prober/file_test.go
Normal file
279
prober/file_test.go
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
package prober
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ribbybibby/ssl_exporter/config"
|
||||||
|
"github.com/ribbybibby/ssl_exporter/test"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestProbeFile tests a file
|
||||||
|
func TestProbeFile(t *testing.T) {
|
||||||
|
cert, certFile, err := createTestFile("", "tls*.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
defer os.Remove(certFile)
|
||||||
|
|
||||||
|
module := config.Module{}
|
||||||
|
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeFile(ctx, certFile, module, registry); err != nil {
|
||||||
|
t.Fatalf("error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFileMetrics(cert, certFile, registry, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestProbeFileGlob tests matching a file with a glob
|
||||||
|
func TestProbeFileGlob(t *testing.T) {
|
||||||
|
cert, certFile, err := createTestFile("", "tls*.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
defer os.Remove(certFile)
|
||||||
|
|
||||||
|
module := config.Module{}
|
||||||
|
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
glob := filepath.Dir(certFile) + "/*.crt"
|
||||||
|
|
||||||
|
if err := ProbeFile(ctx, glob, module, registry); err != nil {
|
||||||
|
t.Fatalf("error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFileMetrics(cert, certFile, registry, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestProbeFileGlobDoubleStar tests matching a file with a ** glob
|
||||||
|
func TestProbeFileGlobDoubleStar(t *testing.T) {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "testdir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
cert, certFile, err := createTestFile(tmpDir, "tls*.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
defer os.Remove(certFile)
|
||||||
|
|
||||||
|
module := config.Module{}
|
||||||
|
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
glob := filepath.Dir(filepath.Dir(certFile)) + "/**/*.crt"
|
||||||
|
|
||||||
|
if err := ProbeFile(ctx, glob, module, registry); err != nil {
|
||||||
|
t.Fatalf("error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFileMetrics(cert, certFile, registry, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestProbeFileGlobDoubleStarMultiple tests matching multiple files with a ** glob
|
||||||
|
func TestProbeFileGlobDoubleStarMultiple(t *testing.T) {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "testdir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
tmpDir1, err := ioutil.TempDir(tmpDir, "testdir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
cert1, certFile1, err := createTestFile(tmpDir1, "1*.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir2, err := ioutil.TempDir(tmpDir, "testdir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
cert2, certFile2, err := createTestFile(tmpDir2, "2*.crt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
module := config.Module{}
|
||||||
|
|
||||||
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
glob := tmpDir + "/**/*.crt"
|
||||||
|
|
||||||
|
if err := ProbeFile(ctx, glob, module, registry); err != nil {
|
||||||
|
t.Fatalf("error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFileMetrics(cert1, certFile1, registry, t)
|
||||||
|
checkFileMetrics(cert2, certFile2, registry, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a certificate and write it to a file
|
||||||
|
func createTestFile(dir, filename string) (*x509.Certificate, string, error) {
|
||||||
|
certPEM, _ := test.GenerateTestCertificate(time.Now().Add(time.Hour * 1))
|
||||||
|
block, _ := pem.Decode([]byte(certPEM))
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
tmpFile, err := ioutil.TempFile(dir, filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, tmpFile.Name(), err
|
||||||
|
}
|
||||||
|
if _, err := tmpFile.Write(certPEM); err != nil {
|
||||||
|
return nil, tmpFile.Name(), err
|
||||||
|
}
|
||||||
|
if err := tmpFile.Close(); err != nil {
|
||||||
|
return nil, tmpFile.Name(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, tmpFile.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check metrics
|
||||||
|
func checkFileMetrics(cert *x509.Certificate, certFile string, registry *prometheus.Registry, t *testing.T) {
|
||||||
|
mfs, err := registry.Gather()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ips := ","
|
||||||
|
for _, ip := range cert.IPAddresses {
|
||||||
|
ips = ips + ip.String() + ","
|
||||||
|
}
|
||||||
|
expectedLabels := map[string]map[string]map[string]string{
|
||||||
|
certFile: {
|
||||||
|
"ssl_file_cert_not_after": {
|
||||||
|
"file": certFile,
|
||||||
|
"serial_no": cert.SerialNumber.String(),
|
||||||
|
"issuer_cn": cert.Issuer.CommonName,
|
||||||
|
"cn": cert.Subject.CommonName,
|
||||||
|
"dnsnames": "," + strings.Join(cert.DNSNames, ",") + ",",
|
||||||
|
"ips": ips,
|
||||||
|
"emails": "," + strings.Join(cert.EmailAddresses, ",") + ",",
|
||||||
|
"ou": "," + strings.Join(cert.Subject.OrganizationalUnit, ",") + ",",
|
||||||
|
},
|
||||||
|
"ssl_file_cert_not_before": {
|
||||||
|
"file": certFile,
|
||||||
|
"serial_no": cert.SerialNumber.String(),
|
||||||
|
"issuer_cn": cert.Issuer.CommonName,
|
||||||
|
"cn": cert.Subject.CommonName,
|
||||||
|
"dnsnames": "," + strings.Join(cert.DNSNames, ",") + ",",
|
||||||
|
"ips": ips,
|
||||||
|
"emails": "," + strings.Join(cert.EmailAddresses, ",") + ",",
|
||||||
|
"ou": "," + strings.Join(cert.Subject.OrganizationalUnit, ",") + ",",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
checkFileRegistryLabels(expectedLabels, mfs, t)
|
||||||
|
|
||||||
|
expectedResults := map[string]map[string]float64{
|
||||||
|
certFile: {
|
||||||
|
"ssl_file_cert_not_after": float64(cert.NotAfter.Unix()),
|
||||||
|
"ssl_file_cert_not_before": float64(cert.NotBefore.Unix()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
checkFileRegistryResults(expectedResults, mfs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if expected results are in the registry
|
||||||
|
func checkFileRegistryResults(expRes map[string]map[string]float64, mfs []*dto.MetricFamily, t *testing.T) {
|
||||||
|
results := make(map[string]map[string]float64)
|
||||||
|
for _, mf := range mfs {
|
||||||
|
for _, metric := range mf.Metric {
|
||||||
|
for _, l := range metric.GetLabel() {
|
||||||
|
if l.GetName() == "file" {
|
||||||
|
if _, ok := results[l.GetValue()]; !ok {
|
||||||
|
results[l.GetValue()] = make(map[string]float64)
|
||||||
|
}
|
||||||
|
results[l.GetValue()][mf.GetName()] = metric.GetGauge().GetValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for expf, expr := range expRes {
|
||||||
|
for expm, expv := range expr {
|
||||||
|
if _, ok := results[expf]; !ok {
|
||||||
|
t.Fatalf("Could not find results for file %v", expf)
|
||||||
|
}
|
||||||
|
v, ok := results[expf][expm]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected metric %v not found in returned metrics for file %v", expm, expf)
|
||||||
|
}
|
||||||
|
if v != expv {
|
||||||
|
t.Fatalf("Expected: %v: %v, got: %v: %v for file %v", expm, expv, expm, v, expf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if expected labels are in the registry
|
||||||
|
func checkFileRegistryLabels(expRes map[string]map[string]map[string]string, mfs []*dto.MetricFamily, t *testing.T) {
|
||||||
|
results := make(map[string]map[string]map[string]string)
|
||||||
|
for _, mf := range mfs {
|
||||||
|
for _, metric := range mf.Metric {
|
||||||
|
for _, l := range metric.GetLabel() {
|
||||||
|
if l.GetName() == "file" {
|
||||||
|
if _, ok := results[l.GetValue()]; !ok {
|
||||||
|
results[l.GetValue()] = make(map[string]map[string]string)
|
||||||
|
}
|
||||||
|
results[l.GetValue()][mf.GetName()] = make(map[string]string)
|
||||||
|
for _, sl := range metric.GetLabel() {
|
||||||
|
results[l.GetValue()][mf.GetName()][sl.GetName()] = sl.GetValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for expf, expr := range expRes {
|
||||||
|
for expm, expl := range expr {
|
||||||
|
if _, ok := results[expf]; !ok {
|
||||||
|
t.Fatalf("Could not find results for file %v", expf)
|
||||||
|
}
|
||||||
|
l, ok := results[expf][expm]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected metric %v not found in returned metrics for file %v", expm, expf)
|
||||||
|
}
|
||||||
|
for expk, expv := range expl {
|
||||||
|
v, ok := l[expk]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected label %v for metric %v not found in returned metrics for file %v", expk, expm, expf)
|
||||||
|
}
|
||||||
|
if v != expv {
|
||||||
|
t.Fatalf("Expected %v{%q=%q}, got: %v{%q=%q} for file %v", expm, expk, expv, expm, expk, v, expf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(l) != len(expl) {
|
||||||
|
t.Fatalf("Expected %v labels but got %v for metric %v and file %v", len(expl), len(l), expm, expf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
package prober
|
package prober
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/common/log"
|
"github.com/prometheus/common/log"
|
||||||
@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ProbeHTTPS performs a https probe
|
// ProbeHTTPS performs a https probe
|
||||||
func ProbeHTTPS(target string, module config.Module, timeout time.Duration, registry *prometheus.Registry) error {
|
func ProbeHTTPS(ctx context.Context, target string, module config.Module, registry *prometheus.Registry) error {
|
||||||
tlsConfig, err := newTLSConfig("", registry, &module.TLSConfig)
|
tlsConfig, err := newTLSConfig("", registry, &module.TLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -48,11 +48,15 @@ func ProbeHTTPS(target string, module config.Module, timeout time.Duration, regi
|
|||||||
Proxy: proxy,
|
Proxy: proxy,
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
},
|
},
|
||||||
Timeout: timeout,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue a GET request to the target
|
// Issue a GET request to the target
|
||||||
resp, err := client.Get(targetURL.String())
|
request, err := http.NewRequest(http.MethodGet, targetURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request = request.WithContext(ctx)
|
||||||
|
resp, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prober
|
package prober
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -37,7 +38,10 @@ func TestProbeHTTPS(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, server.URL, module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +73,10 @@ func TestProbeHTTPSInvalidName(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS("https://localhost:"+u.Port(), module, 5*time.Second, registry); err == nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, "https://localhost:"+u.Port(), module, registry); err == nil {
|
||||||
t.Fatalf("expected error, but err was nil")
|
t.Fatalf("expected error, but err was nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +107,10 @@ func TestProbeHTTPSNoScheme(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS(u.Host, module, 5*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, u.Host, module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,7 +142,10 @@ func TestProbeHTTPSServerName(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS("https://localhost:"+u.Port(), module, 5*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, "https://localhost:"+u.Port(), module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,7 +160,10 @@ func TestProbeHTTPSHTTP(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS(server.URL, config.Module{}, 5*time.Second, registry); err == nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, server.URL, config.Module{}, registry); err == nil {
|
||||||
t.Fatalf("expected error, but err was nil")
|
t.Fatalf("expected error, but err was nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,7 +212,10 @@ func TestProbeHTTPSClientAuth(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, server.URL, module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,7 +268,10 @@ func TestProbeHTTPSClientAuthWrongClientCert(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err == nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, server.URL, module, registry); err == nil {
|
||||||
t.Fatalf("expected error but err is nil")
|
t.Fatalf("expected error but err is nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,7 +304,10 @@ func TestProbeHTTPSExpired(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err == nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, server.URL, module, registry); err == nil {
|
||||||
t.Fatalf("expected error but err is nil")
|
t.Fatalf("expected error but err is nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,7 +341,10 @@ func TestProbeHTTPSExpiredInsecure(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, server.URL, module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -362,14 +390,17 @@ func TestProbeHTTPSProxy(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err == nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeHTTPS(ctx, server.URL, module, registry); err == nil {
|
||||||
t.Fatalf("expected error but err was nil")
|
t.Fatalf("expected error but err was nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with the proxy url, this shouldn't return an error
|
// Test with the proxy url, this shouldn't return an error
|
||||||
module.HTTPS.ProxyURL = config.URL{URL: proxyURL}
|
module.HTTPS.ProxyURL = config.URL{URL: proxyURL}
|
||||||
|
|
||||||
if err := ProbeHTTPS(server.URL, module, 5*time.Second, registry); err != nil {
|
if err := ProbeHTTPS(ctx, server.URL, module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,12 +3,16 @@ package prober
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/common/log"
|
||||||
"golang.org/x/crypto/ocsp"
|
"golang.org/x/crypto/ocsp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -82,7 +86,13 @@ func collectCertificateMetrics(certs []*x509.Certificate, registry *prometheus.R
|
|||||||
)
|
)
|
||||||
registry.MustRegister(notAfter, notBefore)
|
registry.MustRegister(notAfter, notBefore)
|
||||||
|
|
||||||
for _, cert := range uniq(certs) {
|
certs = uniq(certs)
|
||||||
|
|
||||||
|
if len(certs) == 0 {
|
||||||
|
return fmt.Errorf("No certificates found")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cert := range certs {
|
||||||
labels := labelValues(cert)
|
labels := labelValues(cert)
|
||||||
|
|
||||||
if !cert.NotAfter.IsZero() {
|
if !cert.NotAfter.IsZero() {
|
||||||
@ -219,6 +229,65 @@ func collectOCSPMetrics(ocspResponse []byte, registry *prometheus.Registry) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func collectFileMetrics(files []string, registry *prometheus.Registry) error {
|
||||||
|
var (
|
||||||
|
totalCerts []*x509.Certificate
|
||||||
|
fileNotAfter = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: prometheus.BuildFQName(namespace, "", "file_cert_not_after"),
|
||||||
|
Help: "NotAfter expressed as a Unix Epoch Time for a certificate found in a file",
|
||||||
|
},
|
||||||
|
[]string{"file", "serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"},
|
||||||
|
)
|
||||||
|
fileNotBefore = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: prometheus.BuildFQName(namespace, "", "file_cert_not_before"),
|
||||||
|
Help: "NotBefore expressed as a Unix Epoch Time for a certificate found in a file",
|
||||||
|
},
|
||||||
|
[]string{"file", "serial_no", "issuer_cn", "cn", "dnsnames", "ips", "emails", "ou"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
registry.MustRegister(fileNotAfter, fileNotBefore)
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
data, err := ioutil.ReadFile(f)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Error reading file: %s error=%s", f, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var certs []*x509.Certificate
|
||||||
|
for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) {
|
||||||
|
if block.Type == "CERTIFICATE" {
|
||||||
|
cert, err := x509.ParseCertificate(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !contains(certs, cert) {
|
||||||
|
certs = append(certs, cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalCerts = append(totalCerts, certs...)
|
||||||
|
for _, cert := range certs {
|
||||||
|
labels := append([]string{f}, labelValues(cert)...)
|
||||||
|
|
||||||
|
if !cert.NotAfter.IsZero() {
|
||||||
|
fileNotAfter.WithLabelValues(labels...).Set(float64(cert.NotAfter.Unix()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cert.NotBefore.IsZero() {
|
||||||
|
fileNotBefore.WithLabelValues(labels...).Set(float64(cert.NotBefore.Unix()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(totalCerts) == 0 {
|
||||||
|
return fmt.Errorf("No certificates found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func labelValues(cert *x509.Certificate) []string {
|
func labelValues(cert *x509.Certificate) []string {
|
||||||
return []string{
|
return []string{
|
||||||
cert.SerialNumber.String(),
|
cert.SerialNumber.String(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package prober
|
package prober
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"context"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/ribbybibby/ssl_exporter/config"
|
"github.com/ribbybibby/ssl_exporter/config"
|
||||||
@ -13,8 +13,9 @@ var (
|
|||||||
"https": ProbeHTTPS,
|
"https": ProbeHTTPS,
|
||||||
"http": ProbeHTTPS,
|
"http": ProbeHTTPS,
|
||||||
"tcp": ProbeTCP,
|
"tcp": ProbeTCP,
|
||||||
|
"file": ProbeFile,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProbeFn probes
|
// ProbeFn probes
|
||||||
type ProbeFn func(target string, module config.Module, timeout time.Duration, registry *prometheus.Registry) error
|
type ProbeFn func(ctx context.Context, target string, module config.Module, registry *prometheus.Registry) error
|
||||||
|
@ -2,11 +2,11 @@ package prober
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/common/log"
|
"github.com/prometheus/common/log"
|
||||||
@ -14,21 +14,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ProbeTCP performs a tcp probe
|
// ProbeTCP performs a tcp probe
|
||||||
func ProbeTCP(target string, module config.Module, timeout time.Duration, registry *prometheus.Registry) error {
|
func ProbeTCP(ctx context.Context, target string, module config.Module, registry *prometheus.Registry) error {
|
||||||
tlsConfig, err := newTLSConfig(target, registry, &module.TLSConfig)
|
tlsConfig, err := newTLSConfig(target, registry, &module.TLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer := &net.Dialer{Timeout: timeout}
|
dialer := &net.Dialer{}
|
||||||
|
conn, err := dialer.DialContext(ctx, "tcp", target)
|
||||||
conn, err := dialer.Dial("tcp", target)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
|
deadline, _ := ctx.Deadline()
|
||||||
|
if err := conn.SetDeadline(deadline); err != nil {
|
||||||
return fmt.Errorf("Error setting deadline")
|
return fmt.Errorf("Error setting deadline")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package prober
|
package prober
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
@ -33,7 +34,10 @@ func TestProbeTCP(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeTCP(ctx, server.Listener.Addr().String(), module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,7 +65,10 @@ func TestProbeTCPInvalidName(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeTCP("localhost:"+listenPort, module, 10*time.Second, registry); err == nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeTCP(ctx, "localhost:"+listenPort, module, registry); err == nil {
|
||||||
t.Fatalf("expected error but err was nil")
|
t.Fatalf("expected error but err was nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +97,10 @@ func TestProbeTCPServerName(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeTCP("localhost:"+listenPort, module, 10*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeTCP(ctx, "localhost:"+listenPort, module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +133,10 @@ func TestProbeTCPExpired(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 5*time.Second, registry); err == nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeTCP(ctx, server.Listener.Addr().String(), module, registry); err == nil {
|
||||||
t.Fatalf("expected error but err is nil")
|
t.Fatalf("expected error but err is nil")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,7 +170,10 @@ func TestProbeTCPExpiredInsecure(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 5*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeTCP(ctx, server.Listener.Addr().String(), module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +202,10 @@ func TestProbeTCPStartTLSSMTP(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeTCP(ctx, server.Listener.Addr().String(), module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,7 +233,10 @@ func TestProbeTCPStartTLSFTP(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeTCP(ctx, server.Listener.Addr().String(), module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,7 +264,10 @@ func TestProbeTCPStartTLSIMAP(t *testing.T) {
|
|||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
|
|
||||||
if err := ProbeTCP(server.Listener.Addr().String(), module, 10*time.Second, registry); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := ProbeTCP(ctx, server.Listener.Addr().String(), module, registry); err != nil {
|
||||||
t.Fatalf("error: %s", err)
|
t.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -52,6 +53,9 @@ func probeHandler(w http.ResponseWriter, r *http.Request, conf *config.Config) {
|
|||||||
timeout = time.Duration((timeoutSeconds) * 1e9)
|
timeout = time.Duration((timeoutSeconds) * 1e9)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
target := r.URL.Query().Get("target")
|
target := r.URL.Query().Get("target")
|
||||||
if target == "" {
|
if target == "" {
|
||||||
http.Error(w, "Target parameter is missing", http.StatusBadRequest)
|
http.Error(w, "Target parameter is missing", http.StatusBadRequest)
|
||||||
@ -65,10 +69,10 @@ func probeHandler(w http.ResponseWriter, r *http.Request, conf *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tlsConnectSuccess = prometheus.NewGauge(
|
probeSuccess = prometheus.NewGauge(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: prometheus.BuildFQName(namespace, "", "tls_connect_success"),
|
Name: prometheus.BuildFQName(namespace, "", "probe_success"),
|
||||||
Help: "If the TLS connection was a success",
|
Help: "If the probe was a success",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
proberType = prometheus.NewGaugeVec(
|
proberType = prometheus.NewGaugeVec(
|
||||||
@ -81,16 +85,15 @@ func probeHandler(w http.ResponseWriter, r *http.Request, conf *config.Config) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
registry.MustRegister(tlsConnectSuccess, proberType)
|
registry.MustRegister(probeSuccess, proberType)
|
||||||
proberType.WithLabelValues(module.Prober).Set(1)
|
proberType.WithLabelValues(module.Prober).Set(1)
|
||||||
|
|
||||||
err := probeFunc(target, module, timeout, registry)
|
err := probeFunc(ctx, target, module, registry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error=%s target=%s prober=%s timeout=%s", err, target, module.Prober, timeout)
|
log.Errorf("error=%s target=%s prober=%s timeout=%s", err, target, module.Prober, timeout)
|
||||||
tlsConnectSuccess.Set(0)
|
probeSuccess.Set(0)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
tlsConnectSuccess.Set(1)
|
probeSuccess.Set(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve
|
// Serve
|
||||||
|
@ -52,8 +52,8 @@ func TestProbeHandlerHTTPS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check success metric
|
// Check success metric
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 1"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 1`")
|
t.Errorf("expected `ssl_probe_success 1`")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check probe metric
|
// Check probe metric
|
||||||
@ -112,8 +112,8 @@ func TestProbeHandlerHTTPSTimeout(t *testing.T) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_prober{prober=\"https\"} 1"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_prober{prober=\"https\"} 1"); !ok {
|
||||||
@ -211,8 +211,8 @@ func TestProbeHandlerHTTPSNoServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check success metric
|
// Check success metric
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,9 +235,9 @@ func TestProbeHandlerHTTPSSpaces(t *testing.T) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
|
ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,9 +260,9 @@ func TestProbeHandlerHTTPSHTTP(t *testing.T) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
|
ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,9 +319,9 @@ func TestProbeHandlerHTTPSClientAuthWrongClientCert(t *testing.T) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
|
ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,8 +353,8 @@ func TestProbeHandlerTCP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check success metric
|
// Check success metric
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 1"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 1`")
|
t.Errorf("expected `ssl_probe_success 1`")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check probe metric
|
// Check probe metric
|
||||||
@ -402,8 +402,8 @@ func TestProbeHandlerTCPTimeout(t *testing.T) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_prober{prober=\"tcp\"} 1"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_prober{prober=\"tcp\"} 1"); !ok {
|
||||||
@ -583,8 +583,8 @@ func TestProbeHandlerTCPNoServer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check success metric
|
// Check success metric
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,9 +607,9 @@ func TestProbeHandlerTCPSpaces(t *testing.T) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
|
ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,9 +632,9 @@ func TestProbeHandlerTCPHTTP(t *testing.T) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
|
ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,9 +672,9 @@ func TestProbeHandlerTCPExpired(t *testing.T) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0")
|
ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -713,9 +713,9 @@ func TestProbeHandlerTCPExpiredInsecure(t *testing.T) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1")
|
ok := strings.Contains(rr.Body.String(), "ssl_probe_success 1")
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 1`")
|
t.Errorf("expected `ssl_probe_success 1`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -769,8 +769,8 @@ func TestProbeHandlerProxy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check success metric
|
// Check success metric
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 0"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 0"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 0`")
|
t.Errorf("expected `ssl_probe_success 0`")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with an actual proxy server
|
// Test with an actual proxy server
|
||||||
@ -808,8 +808,8 @@ func TestProbeHandlerProxy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check success metric
|
// Check success metric
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 1"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 1`")
|
t.Errorf("expected `ssl_probe_success 1`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -844,8 +844,8 @@ func TestProbeHandlerTCPStartTLSSMTP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check success metric
|
// Check success metric
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 1"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 1`")
|
t.Errorf("expected `ssl_probe_success 1`")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check probe metric
|
// Check probe metric
|
||||||
@ -890,8 +890,8 @@ func TestProbeHandlerTCPStartTLSFTP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check success metric
|
// Check success metric
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 1"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 1`")
|
t.Errorf("expected `ssl_probe_success 1`")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check probe metric
|
// Check probe metric
|
||||||
@ -936,8 +936,8 @@ func TestProbeHandlerTCPStartTLSIMAP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check success metric
|
// Check success metric
|
||||||
if ok := strings.Contains(rr.Body.String(), "ssl_tls_connect_success 1"); !ok {
|
if ok := strings.Contains(rr.Body.String(), "ssl_probe_success 1"); !ok {
|
||||||
t.Errorf("expected `ssl_tls_connect_success 1`")
|
t.Errorf("expected `ssl_probe_success 1`")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check probe metric
|
// Check probe metric
|
||||||
|
32
vendor/github.com/bmatcuk/doublestar/v2/.gitignore
generated
vendored
Normal file
32
vendor/github.com/bmatcuk/doublestar/v2/.gitignore
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# vi
|
||||||
|
*~
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
# test directory
|
||||||
|
test/
|
20
vendor/github.com/bmatcuk/doublestar/v2/.travis.yml
generated
vendored
Normal file
20
vendor/github.com/bmatcuk/doublestar/v2/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.12
|
||||||
|
- 1.13
|
||||||
|
- 1.14
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- windows
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get -t -v ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
|
22
vendor/github.com/bmatcuk/doublestar/v2/LICENSE
generated
vendored
Normal file
22
vendor/github.com/bmatcuk/doublestar/v2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Bob Matcuk
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
143
vendor/github.com/bmatcuk/doublestar/v2/README.md
generated
vendored
Normal file
143
vendor/github.com/bmatcuk/doublestar/v2/README.md
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
# doublestar
|
||||||
|
|
||||||
|
Path pattern matching and globbing supporting `doublestar` (`**`) patterns.
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/bmatcuk/doublestar/v2)
|
||||||
|
[](https://github.com/bmatcuk/doublestar/releases)
|
||||||
|
[](https://travis-ci.org/bmatcuk/doublestar)
|
||||||
|
[](https://codecov.io/github/bmatcuk/doublestar?branch=master)
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
#### [Updating from v1 to v2?](UPGRADING.md)
|
||||||
|
|
||||||
|
**doublestar** is a [golang](http://golang.org/) implementation of path pattern
|
||||||
|
matching and globbing with support for "doublestar" (aka globstar: `**`)
|
||||||
|
patterns.
|
||||||
|
|
||||||
|
doublestar patterns match files and directories recursively. For example, if
|
||||||
|
you had the following directory structure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grandparent
|
||||||
|
`-- parent
|
||||||
|
|-- child1
|
||||||
|
`-- child2
|
||||||
|
```
|
||||||
|
|
||||||
|
You could find the children with patterns such as: `**/child*`,
|
||||||
|
`grandparent/**/child?`, `**/parent/*`, or even just `**` by itself (which will
|
||||||
|
return all files and directories recursively).
|
||||||
|
|
||||||
|
Bash's globstar is doublestar's inspiration and, as such, works similarly.
|
||||||
|
Note that the doublestar must appear as a path component by itself. A pattern
|
||||||
|
such as `/path**` is invalid and will be treated the same as `/path*`, but
|
||||||
|
`/path*/**` should achieve the desired result. Additionally, `/path/**` will
|
||||||
|
match all directories and files under the path directory, but `/path/**/` will
|
||||||
|
only match directories.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
**doublestar** can be installed via `go get`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/bmatcuk/doublestar/v2
|
||||||
|
```
|
||||||
|
|
||||||
|
To use it in your code, you must import it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/bmatcuk/doublestar/v2"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Match
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Match(pattern, name string) (bool, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
Match returns true if `name` matches the file name `pattern`
|
||||||
|
([see below](#patterns)). `name` and `pattern` are split on forward slash (`/`)
|
||||||
|
characters and may be relative or absolute.
|
||||||
|
|
||||||
|
Note: `Match()` is meant to be a drop-in replacement for `path.Match()`. As
|
||||||
|
such, it always uses `/` as the path separator. If you are writing code that
|
||||||
|
will run on systems where `/` is not the path separator (such as Windows), you
|
||||||
|
want to use `PathMatch()` (below) instead.
|
||||||
|
|
||||||
|
|
||||||
|
### PathMatch
|
||||||
|
|
||||||
|
```go
|
||||||
|
func PathMatch(pattern, name string) (bool, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
PathMatch returns true if `name` matches the file name `pattern`
|
||||||
|
([see below](#patterns)). The difference between Match and PathMatch is that
|
||||||
|
PathMatch will automatically use your system's path separator to split `name`
|
||||||
|
and `pattern`.
|
||||||
|
|
||||||
|
`PathMatch()` is meant to be a drop-in replacement for `filepath.Match()`.
|
||||||
|
|
||||||
|
### Glob
|
||||||
|
|
||||||
|
```go
|
||||||
|
func Glob(pattern string) ([]string, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
Glob finds all files and directories in the filesystem that match `pattern`
|
||||||
|
([see below](#patterns)). `pattern` may be relative (to the current working
|
||||||
|
directory), or absolute.
|
||||||
|
|
||||||
|
`Glob()` is meant to be a drop-in replacement for `filepath.Glob()`.
|
||||||
|
|
||||||
|
### Patterns
|
||||||
|
|
||||||
|
**doublestar** supports the following special terms in the patterns:
|
||||||
|
|
||||||
|
Special Terms | Meaning
|
||||||
|
------------- | -------
|
||||||
|
`*` | matches any sequence of non-path-separators
|
||||||
|
`**` | matches any sequence of characters, including path separators
|
||||||
|
`?` | matches any single non-path-separator character
|
||||||
|
`[class]` | matches any single non-path-separator character against a class of characters ([see below](#character-classes))
|
||||||
|
`{alt1,...}` | matches a sequence of characters if one of the comma-separated alternatives matches
|
||||||
|
|
||||||
|
Any character with a special meaning can be escaped with a backslash (`\`).
|
||||||
|
|
||||||
|
#### Character Classes
|
||||||
|
|
||||||
|
Character classes support the following:
|
||||||
|
|
||||||
|
Class | Meaning
|
||||||
|
---------- | -------
|
||||||
|
`[abc]` | matches any single character within the set
|
||||||
|
`[a-z]` | matches any single character in the range
|
||||||
|
`[^class]` | matches any single character which does *not* match the class
|
||||||
|
|
||||||
|
### Abstracting the `os` package
|
||||||
|
|
||||||
|
**doublestar** by default uses the `Open`, `Stat`, and `Lstat`, functions and
|
||||||
|
`PathSeparator` value from the standard library's `os` package. To abstract
|
||||||
|
this, for example to be able to perform tests of Windows paths on Linux, or to
|
||||||
|
interoperate with your own filesystem code, it includes the functions `GlobOS`
|
||||||
|
and `PathMatchOS` which are identical to `Glob` and `PathMatch` except that they
|
||||||
|
operate on an `OS` interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type OS interface {
|
||||||
|
Lstat(name string) (os.FileInfo, error)
|
||||||
|
Open(name string) (*os.File, error)
|
||||||
|
PathSeparator() rune
|
||||||
|
Stat(name string) (os.FileInfo, error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`StandardOS` is a value that implements this interface by calling functions in
|
||||||
|
the standard library's `os` package.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[MIT License](LICENSE)
|
13
vendor/github.com/bmatcuk/doublestar/v2/UPGRADING.md
generated
vendored
Normal file
13
vendor/github.com/bmatcuk/doublestar/v2/UPGRADING.md
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Upgrading from v1 to v2
|
||||||
|
|
||||||
|
The change from v1 to v2 was fairly minor: the return type of the `Open` method
|
||||||
|
on the `OS` interface was changed from `*os.File` to `File`, a new interface
|
||||||
|
exported by doublestar. The new `File` interface only defines the functionality
|
||||||
|
doublestar actually needs (`io.Closer` and `Readdir`), making it easier to use
|
||||||
|
doublestar with [go-billy](https://github.com/src-d/go-billy),
|
||||||
|
[afero](https://github.com/spf13/afero), or something similar. If you were
|
||||||
|
using this functionality, updating should be as easy as updating `Open's`
|
||||||
|
return type, since `os.File` already implements `doublestar.File`.
|
||||||
|
|
||||||
|
If you weren't using this functionality, updating should be as easy as changing
|
||||||
|
your dependencies to point to v2.
|
630
vendor/github.com/bmatcuk/doublestar/v2/doublestar.go
generated
vendored
Normal file
630
vendor/github.com/bmatcuk/doublestar/v2/doublestar.go
generated
vendored
Normal file
@ -0,0 +1,630 @@
|
|||||||
|
package doublestar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File defines a subset of file operations
|
||||||
|
type File interface {
|
||||||
|
io.Closer
|
||||||
|
Readdir(count int) ([]os.FileInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An OS abstracts functions in the standard library's os package.
|
||||||
|
type OS interface {
|
||||||
|
Lstat(name string) (os.FileInfo, error)
|
||||||
|
Open(name string) (File, error)
|
||||||
|
PathSeparator() rune
|
||||||
|
Stat(name string) (os.FileInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A standardOS implements OS by calling functions in the standard library's os
|
||||||
|
// package.
|
||||||
|
type standardOS struct{}
|
||||||
|
|
||||||
|
func (standardOS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) }
|
||||||
|
func (standardOS) Open(name string) (File, error) { return os.Open(name) }
|
||||||
|
func (standardOS) PathSeparator() rune { return os.PathSeparator }
|
||||||
|
func (standardOS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }
|
||||||
|
|
||||||
|
// StandardOS is a value that implements the OS interface by calling functions
|
||||||
|
// in the standard libray's os package.
|
||||||
|
var StandardOS OS = standardOS{}
|
||||||
|
|
||||||
|
// ErrBadPattern indicates a pattern was malformed.
|
||||||
|
var ErrBadPattern = path.ErrBadPattern
|
||||||
|
|
||||||
|
// Find the first index of a rune in a string,
|
||||||
|
// ignoring any times the rune is escaped using "\".
|
||||||
|
func indexRuneWithEscaping(s string, r rune) int {
|
||||||
|
end := strings.IndexRune(s, r)
|
||||||
|
if end == -1 || r == '\\' {
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
if end > 0 && s[end-1] == '\\' {
|
||||||
|
start := end + utf8.RuneLen(r)
|
||||||
|
end = indexRuneWithEscaping(s[start:], r)
|
||||||
|
if end != -1 {
|
||||||
|
end += start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the last index of a rune in a string,
|
||||||
|
// ignoring any times the rune is escaped using "\".
|
||||||
|
func lastIndexRuneWithEscaping(s string, r rune) int {
|
||||||
|
end := strings.LastIndex(s, string(r))
|
||||||
|
if end == -1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if end > 0 && s[end-1] == '\\' {
|
||||||
|
end = lastIndexRuneWithEscaping(s[:end-1], r)
|
||||||
|
}
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the index of the first instance of one of the unicode characters in
|
||||||
|
// chars, ignoring any times those characters are escaped using "\".
|
||||||
|
func indexAnyWithEscaping(s, chars string) int {
|
||||||
|
end := strings.IndexAny(s, chars)
|
||||||
|
if end == -1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if end > 0 && s[end-1] == '\\' {
|
||||||
|
_, adj := utf8.DecodeRuneInString(s[end:])
|
||||||
|
start := end + adj
|
||||||
|
end = indexAnyWithEscaping(s[start:], chars)
|
||||||
|
if end != -1 {
|
||||||
|
end += start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split a set of alternatives such as {alt1,alt2,...} and returns the index of
|
||||||
|
// the rune after the closing curly brace. Respects nested alternatives and
|
||||||
|
// escaped runes.
|
||||||
|
func splitAlternatives(s string) (ret []string, idx int) {
|
||||||
|
ret = make([]string, 0, 2)
|
||||||
|
idx = 0
|
||||||
|
slen := len(s)
|
||||||
|
braceCnt := 1
|
||||||
|
esc := false
|
||||||
|
start := 0
|
||||||
|
for braceCnt > 0 {
|
||||||
|
if idx >= slen {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
sRune, adj := utf8.DecodeRuneInString(s[idx:])
|
||||||
|
if esc {
|
||||||
|
esc = false
|
||||||
|
} else if sRune == '\\' {
|
||||||
|
esc = true
|
||||||
|
} else if sRune == '{' {
|
||||||
|
braceCnt++
|
||||||
|
} else if sRune == '}' {
|
||||||
|
braceCnt--
|
||||||
|
} else if sRune == ',' && braceCnt == 1 {
|
||||||
|
ret = append(ret, s[start:idx])
|
||||||
|
start = idx + adj
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += adj
|
||||||
|
}
|
||||||
|
ret = append(ret, s[start:idx-1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the pattern is "zero length", meaning
|
||||||
|
// it could match zero or more characters.
|
||||||
|
func isZeroLengthPattern(pattern string) (ret bool, err error) {
|
||||||
|
// * can match zero
|
||||||
|
if pattern == "" || pattern == "*" || pattern == "**" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// an alternative with zero length can match zero, for example {,x} - the
|
||||||
|
// first alternative has zero length
|
||||||
|
r, adj := utf8.DecodeRuneInString(pattern)
|
||||||
|
if r == '{' {
|
||||||
|
options, endOptions := splitAlternatives(pattern[adj:])
|
||||||
|
if endOptions == -1 {
|
||||||
|
return false, ErrBadPattern
|
||||||
|
}
|
||||||
|
if ret, err = isZeroLengthPattern(pattern[adj+endOptions:]); !ret || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, o := range options {
|
||||||
|
if ret, err = isZeroLengthPattern(o); ret || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match returns true if name matches the shell file name pattern.
|
||||||
|
// The pattern syntax is:
|
||||||
|
//
|
||||||
|
// pattern:
|
||||||
|
// { term }
|
||||||
|
// term:
|
||||||
|
// '*' matches any sequence of non-path-separators
|
||||||
|
// '**' matches any sequence of characters, including
|
||||||
|
// path separators.
|
||||||
|
// '?' matches any single non-path-separator character
|
||||||
|
// '[' [ '^' ] { character-range } ']'
|
||||||
|
// character class (must be non-empty)
|
||||||
|
// '{' { term } [ ',' { term } ... ] '}'
|
||||||
|
// c matches character c (c != '*', '?', '\\', '[')
|
||||||
|
// '\\' c matches character c
|
||||||
|
//
|
||||||
|
// character-range:
|
||||||
|
// c matches character c (c != '\\', '-', ']')
|
||||||
|
// '\\' c matches character c
|
||||||
|
// lo '-' hi matches character c for lo <= c <= hi
|
||||||
|
//
|
||||||
|
// Match requires pattern to match all of name, not just a substring.
|
||||||
|
// The path-separator defaults to the '/' character. The only possible
|
||||||
|
// returned error is ErrBadPattern, when pattern is malformed.
|
||||||
|
//
|
||||||
|
// Note: this is meant as a drop-in replacement for path.Match() which
|
||||||
|
// always uses '/' as the path separator. If you want to support systems
|
||||||
|
// which use a different path separator (such as Windows), what you want
|
||||||
|
// is the PathMatch() function below.
|
||||||
|
//
|
||||||
|
func Match(pattern, name string) (bool, error) {
|
||||||
|
return doMatching(pattern, name, '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathMatch is like Match except that it uses your system's path separator.
|
||||||
|
// For most systems, this will be '/'. However, for Windows, it would be '\\'.
|
||||||
|
// Note that for systems where the path separator is '\\', escaping is
|
||||||
|
// disabled.
|
||||||
|
//
|
||||||
|
// Note: this is meant as a drop-in replacement for filepath.Match().
|
||||||
|
//
|
||||||
|
func PathMatch(pattern, name string) (bool, error) {
|
||||||
|
return PathMatchOS(StandardOS, pattern, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathMatchOS is like PathMatch except that it uses vos's path separator.
|
||||||
|
func PathMatchOS(vos OS, pattern, name string) (bool, error) {
|
||||||
|
pattern = filepath.ToSlash(pattern)
|
||||||
|
return doMatching(pattern, name, vos.PathSeparator())
|
||||||
|
}
|
||||||
|
|
||||||
|
func doMatching(pattern, name string, separator rune) (matched bool, err error) {
|
||||||
|
// check for some base-cases
|
||||||
|
patternLen, nameLen := len(pattern), len(name)
|
||||||
|
if patternLen == 0 {
|
||||||
|
return nameLen == 0, nil
|
||||||
|
} else if nameLen == 0 {
|
||||||
|
return isZeroLengthPattern(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
separatorAdj := utf8.RuneLen(separator)
|
||||||
|
|
||||||
|
patIdx := indexRuneWithEscaping(pattern, '/')
|
||||||
|
lastPat := patIdx == -1
|
||||||
|
if lastPat {
|
||||||
|
patIdx = len(pattern)
|
||||||
|
}
|
||||||
|
if pattern[:patIdx] == "**" {
|
||||||
|
// if our last pattern component is a doublestar, we're done -
|
||||||
|
// doublestar will match any remaining name components, if any.
|
||||||
|
if lastPat {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, try matching remaining components
|
||||||
|
nameIdx := 0
|
||||||
|
patIdx += 1
|
||||||
|
for {
|
||||||
|
if m, _ := doMatching(pattern[patIdx:], name[nameIdx:], separator); m {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nextNameIdx := 0
|
||||||
|
if nextNameIdx = indexRuneWithEscaping(name[nameIdx:], separator); nextNameIdx == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nameIdx += separatorAdj + nextNameIdx
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nameIdx := indexRuneWithEscaping(name, separator)
|
||||||
|
lastName := nameIdx == -1
|
||||||
|
if lastName {
|
||||||
|
nameIdx = nameLen
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches []string
|
||||||
|
matches, err = matchComponent(pattern, name[:nameIdx])
|
||||||
|
if matches == nil || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(matches) == 0 && lastName {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lastName {
|
||||||
|
nameIdx += separatorAdj
|
||||||
|
for _, alt := range matches {
|
||||||
|
matched, err = doMatching(alt, name[nameIdx:], separator)
|
||||||
|
if matched || err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glob returns the names of all files matching pattern or nil
|
||||||
|
// if there is no matching file. The syntax of pattern is the same
|
||||||
|
// as in Match. The pattern may describe hierarchical names such as
|
||||||
|
// /usr/*/bin/ed (assuming the Separator is '/').
|
||||||
|
//
|
||||||
|
// Glob ignores file system errors such as I/O errors reading directories.
|
||||||
|
// The only possible returned error is ErrBadPattern, when pattern
|
||||||
|
// is malformed.
|
||||||
|
//
|
||||||
|
// Your system path separator is automatically used. This means on
|
||||||
|
// systems where the separator is '\\' (Windows), escaping will be
|
||||||
|
// disabled.
|
||||||
|
//
|
||||||
|
// Note: this is meant as a drop-in replacement for filepath.Glob().
|
||||||
|
//
|
||||||
|
func Glob(pattern string) (matches []string, err error) {
|
||||||
|
return GlobOS(StandardOS, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobOS is like Glob except that it operates on vos.
|
||||||
|
func GlobOS(vos OS, pattern string) (matches []string, err error) {
|
||||||
|
if len(pattern) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the pattern starts with alternatives, we need to handle that here - the
|
||||||
|
// alternatives may be a mix of relative and absolute
|
||||||
|
if pattern[0] == '{' {
|
||||||
|
options, endOptions := splitAlternatives(pattern[1:])
|
||||||
|
if endOptions == -1 {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
}
|
||||||
|
for _, o := range options {
|
||||||
|
m, e := Glob(o + pattern[endOptions+1:])
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
matches = append(matches, m...)
|
||||||
|
}
|
||||||
|
return matches, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pattern is relative or absolute and we're on a non-Windows machine,
|
||||||
|
// volumeName will be an empty string. If it is absolute and we're on a
|
||||||
|
// Windows machine, volumeName will be a drive letter ("C:") for filesystem
|
||||||
|
// paths or \\<server>\<share> for UNC paths.
|
||||||
|
isAbs := filepath.IsAbs(pattern) || pattern[0] == '\\' || pattern[0] == '/'
|
||||||
|
volumeName := filepath.VolumeName(pattern)
|
||||||
|
isWindowsUNC := strings.HasPrefix(volumeName, `\\`)
|
||||||
|
if isWindowsUNC || isAbs {
|
||||||
|
startIdx := len(volumeName) + 1
|
||||||
|
return doGlob(vos, fmt.Sprintf("%s%s", volumeName, string(vos.PathSeparator())), filepath.ToSlash(pattern[startIdx:]), matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, it's a relative pattern
|
||||||
|
return doGlob(vos, ".", filepath.ToSlash(pattern), matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform a glob
|
||||||
|
func doGlob(vos OS, basedir, pattern string, matches []string) (m []string, e error) {
|
||||||
|
m = matches
|
||||||
|
e = nil
|
||||||
|
|
||||||
|
// if the pattern starts with any path components that aren't globbed (ie,
|
||||||
|
// `path/to/glob*`), we can skip over the un-globbed components (`path/to` in
|
||||||
|
// our example).
|
||||||
|
globIdx := indexAnyWithEscaping(pattern, "*?[{\\")
|
||||||
|
if globIdx > 0 {
|
||||||
|
globIdx = lastIndexRuneWithEscaping(pattern[:globIdx], '/')
|
||||||
|
} else if globIdx == -1 {
|
||||||
|
globIdx = lastIndexRuneWithEscaping(pattern, '/')
|
||||||
|
}
|
||||||
|
if globIdx > 0 {
|
||||||
|
basedir = filepath.Join(basedir, pattern[:globIdx])
|
||||||
|
pattern = pattern[globIdx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lstat will return an error if the file/directory doesn't exist
|
||||||
|
fi, err := vos.Lstat(basedir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the pattern is empty, we've found a match
|
||||||
|
if len(pattern) == 0 {
|
||||||
|
m = append(m, basedir)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, we need to check each item in the directory...
|
||||||
|
|
||||||
|
// first, if basedir is a symlink, follow it...
|
||||||
|
if (fi.Mode() & os.ModeSymlink) != 0 {
|
||||||
|
fi, err = vos.Stat(basedir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm it's a directory...
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := filesInDir(vos, basedir)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
|
||||||
|
|
||||||
|
slashIdx := indexRuneWithEscaping(pattern, '/')
|
||||||
|
lastComponent := slashIdx == -1
|
||||||
|
if lastComponent {
|
||||||
|
slashIdx = len(pattern)
|
||||||
|
}
|
||||||
|
if pattern[:slashIdx] == "**" {
|
||||||
|
// if the current component is a doublestar, we'll try depth-first
|
||||||
|
for _, file := range files {
|
||||||
|
// if symlink, we may want to follow
|
||||||
|
if (file.Mode() & os.ModeSymlink) != 0 {
|
||||||
|
file, err = vos.Stat(filepath.Join(basedir, file.Name()))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.IsDir() {
|
||||||
|
// recurse into directories
|
||||||
|
if lastComponent {
|
||||||
|
m = append(m, filepath.Join(basedir, file.Name()))
|
||||||
|
}
|
||||||
|
m, e = doGlob(vos, filepath.Join(basedir, file.Name()), pattern, m)
|
||||||
|
} else if lastComponent {
|
||||||
|
// if the pattern's last component is a doublestar, we match filenames, too
|
||||||
|
m = append(m, filepath.Join(basedir, file.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lastComponent {
|
||||||
|
return // we're done
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern = pattern[slashIdx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// check items in current directory and recurse
|
||||||
|
var match []string
|
||||||
|
for _, file := range files {
|
||||||
|
match, e = matchComponent(pattern, file.Name())
|
||||||
|
if e != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if match != nil {
|
||||||
|
if len(match) == 0 {
|
||||||
|
m = append(m, filepath.Join(basedir, file.Name()))
|
||||||
|
} else {
|
||||||
|
for _, alt := range match {
|
||||||
|
m, e = doGlob(vos, filepath.Join(basedir, file.Name()), alt, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesInDir(vos OS, dirPath string) (files []os.FileInfo, e error) {
|
||||||
|
dir, err := vos.Open(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := dir.Close(); e == nil {
|
||||||
|
e = err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
files, err = dir.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to match a single path component with a pattern. Note that the
|
||||||
|
// pattern may include multiple components but that the "name" is just a single
|
||||||
|
// path component. The return value is a slice of patterns that should be
|
||||||
|
// checked against subsequent path components or nil, indicating that the
|
||||||
|
// pattern does not match this path. It is assumed that pattern components are
|
||||||
|
// separated by '/'
|
||||||
|
func matchComponent(pattern, name string) ([]string, error) {
|
||||||
|
// check for matches one rune at a time
|
||||||
|
patternLen, nameLen := len(pattern), len(name)
|
||||||
|
patIdx, nameIdx := 0, 0
|
||||||
|
for patIdx < patternLen && nameIdx < nameLen {
|
||||||
|
patRune, patAdj := utf8.DecodeRuneInString(pattern[patIdx:])
|
||||||
|
nameRune, nameAdj := utf8.DecodeRuneInString(name[nameIdx:])
|
||||||
|
if patRune == '/' {
|
||||||
|
patIdx++
|
||||||
|
break
|
||||||
|
} else if patRune == '\\' {
|
||||||
|
// handle escaped runes, only if separator isn't '\\'
|
||||||
|
patIdx += patAdj
|
||||||
|
patRune, patAdj = utf8.DecodeRuneInString(pattern[patIdx:])
|
||||||
|
if patRune == utf8.RuneError {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
} else if patRune == nameRune {
|
||||||
|
patIdx += patAdj
|
||||||
|
nameIdx += nameAdj
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
} else if patRune == '*' {
|
||||||
|
// handle stars - a star at the end of the pattern or before a separator
|
||||||
|
// will always match the rest of the path component
|
||||||
|
if patIdx += patAdj; patIdx >= patternLen {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
if patRune, patAdj = utf8.DecodeRuneInString(pattern[patIdx:]); patRune == '/' {
|
||||||
|
return []string{pattern[patIdx+patAdj:]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we can make any matches
|
||||||
|
for ; nameIdx < nameLen; nameIdx += nameAdj {
|
||||||
|
if m, e := matchComponent(pattern[patIdx:], name[nameIdx:]); m != nil || e != nil {
|
||||||
|
return m, e
|
||||||
|
}
|
||||||
|
_, nameAdj = utf8.DecodeRuneInString(name[nameIdx:])
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
} else if patRune == '[' {
|
||||||
|
// handle character sets
|
||||||
|
patIdx += patAdj
|
||||||
|
endClass := indexRuneWithEscaping(pattern[patIdx:], ']')
|
||||||
|
if endClass == -1 {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
}
|
||||||
|
endClass += patIdx
|
||||||
|
classRunes := []rune(pattern[patIdx:endClass])
|
||||||
|
classRunesLen := len(classRunes)
|
||||||
|
if classRunesLen > 0 {
|
||||||
|
classIdx := 0
|
||||||
|
matchClass := false
|
||||||
|
if classRunes[0] == '^' {
|
||||||
|
classIdx++
|
||||||
|
}
|
||||||
|
for classIdx < classRunesLen {
|
||||||
|
low := classRunes[classIdx]
|
||||||
|
if low == '-' {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
}
|
||||||
|
classIdx++
|
||||||
|
if low == '\\' {
|
||||||
|
if classIdx < classRunesLen {
|
||||||
|
low = classRunes[classIdx]
|
||||||
|
classIdx++
|
||||||
|
} else {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
high := low
|
||||||
|
if classIdx < classRunesLen && classRunes[classIdx] == '-' {
|
||||||
|
// we have a range of runes
|
||||||
|
if classIdx++; classIdx >= classRunesLen {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
}
|
||||||
|
high = classRunes[classIdx]
|
||||||
|
if high == '-' {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
}
|
||||||
|
classIdx++
|
||||||
|
if high == '\\' {
|
||||||
|
if classIdx < classRunesLen {
|
||||||
|
high = classRunes[classIdx]
|
||||||
|
classIdx++
|
||||||
|
} else {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if low <= nameRune && nameRune <= high {
|
||||||
|
matchClass = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matchClass == (classRunes[0] == '^') {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
}
|
||||||
|
patIdx = endClass + 1
|
||||||
|
nameIdx += nameAdj
|
||||||
|
} else if patRune == '{' {
|
||||||
|
// handle alternatives such as {alt1,alt2,...}
|
||||||
|
patIdx += patAdj
|
||||||
|
options, endOptions := splitAlternatives(pattern[patIdx:])
|
||||||
|
if endOptions == -1 {
|
||||||
|
return nil, ErrBadPattern
|
||||||
|
}
|
||||||
|
patIdx += endOptions
|
||||||
|
|
||||||
|
results := make([][]string, 0, len(options))
|
||||||
|
totalResults := 0
|
||||||
|
for _, o := range options {
|
||||||
|
m, e := matchComponent(o+pattern[patIdx:], name[nameIdx:])
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
if m != nil {
|
||||||
|
results = append(results, m)
|
||||||
|
totalResults += len(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(results) > 0 {
|
||||||
|
lst := make([]string, 0, totalResults)
|
||||||
|
for _, m := range results {
|
||||||
|
lst = append(lst, m...)
|
||||||
|
}
|
||||||
|
return lst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
} else if patRune == '?' || patRune == nameRune {
|
||||||
|
// handle single-rune wildcard
|
||||||
|
patIdx += patAdj
|
||||||
|
nameIdx += nameAdj
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nameIdx >= nameLen {
|
||||||
|
if patIdx >= patternLen {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern = pattern[patIdx:]
|
||||||
|
slashIdx := indexRuneWithEscaping(pattern, '/')
|
||||||
|
testPattern := pattern
|
||||||
|
if slashIdx >= 0 {
|
||||||
|
testPattern = pattern[:slashIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
zeroLength, err := isZeroLengthPattern(testPattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if zeroLength {
|
||||||
|
if slashIdx == -1 {
|
||||||
|
return []string{}, nil
|
||||||
|
} else {
|
||||||
|
return []string{pattern[slashIdx+1:]}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
3
vendor/github.com/bmatcuk/doublestar/v2/go.mod
generated
vendored
Normal file
3
vendor/github.com/bmatcuk/doublestar/v2/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/bmatcuk/doublestar/v2
|
||||||
|
|
||||||
|
go 1.12
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@ -5,6 +5,9 @@ github.com/alecthomas/template/parse
|
|||||||
github.com/alecthomas/units
|
github.com/alecthomas/units
|
||||||
# github.com/beorn7/perks v1.0.1
|
# github.com/beorn7/perks v1.0.1
|
||||||
github.com/beorn7/perks/quantile
|
github.com/beorn7/perks/quantile
|
||||||
|
# github.com/bmatcuk/doublestar/v2 v2.0.3
|
||||||
|
## explicit
|
||||||
|
github.com/bmatcuk/doublestar/v2
|
||||||
# github.com/cespare/xxhash/v2 v2.1.1
|
# github.com/cespare/xxhash/v2 v2.1.1
|
||||||
github.com/cespare/xxhash/v2
|
github.com/cespare/xxhash/v2
|
||||||
# github.com/golang/protobuf v1.4.3
|
# github.com/golang/protobuf v1.4.3
|
||||||
@ -25,6 +28,7 @@ github.com/prometheus/client_golang/prometheus
|
|||||||
github.com/prometheus/client_golang/prometheus/internal
|
github.com/prometheus/client_golang/prometheus/internal
|
||||||
github.com/prometheus/client_golang/prometheus/promhttp
|
github.com/prometheus/client_golang/prometheus/promhttp
|
||||||
# github.com/prometheus/client_model v0.2.0
|
# github.com/prometheus/client_model v0.2.0
|
||||||
|
## explicit
|
||||||
github.com/prometheus/client_model/go
|
github.com/prometheus/client_model/go
|
||||||
# github.com/prometheus/common v0.14.0
|
# github.com/prometheus/common v0.14.0
|
||||||
## explicit
|
## explicit
|
||||||
|
Reference in New Issue
Block a user