Skip to content

Commit

Permalink
Handle address arguments more flexibly
Browse files Browse the repository at this point in the history
- Support hostnames without explicit ports (default to 443)
- Allow overriding hostname in address via pattern hostname:IP:port
  - This sets the SNI hostname separately from the target IP/port
  - Useful for checking certs when hostname and IP don't match
- Add README documentation about more flexible address handling
  • Loading branch information
ei-grad committed Jan 2, 2024
1 parent 71d75b1 commit f083115
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 5 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ check-expiring-certs [2a02:6b8:a::a]:443
Exits with return code 1 if any certificates are expiring soon or connections
failed.

## Address Arguments

The tool accepts hostname:port arguments for specifying hosts to check. As a
convenience, if only a hostname is provided, port 443 will be used by default.

Address arguments may also be specified in the form `hostname:IPaddress:port`
to override the hostname used for TLS SNI (Server Name Indication). This allows
checking certificates on hosts with mismatching hostnames or IPs.

For example:

```
check-expiring-certs github.com:1.2.3.4:443
```

This will check the certificate presented by the server at 1.2.3.4:443, but use
"github.com" as the expected hostname for certificate validation.

## Installation

```
Expand Down
25 changes: 20 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"net"
"os"
"regexp"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -34,6 +36,8 @@ func main() {
wg := new(sync.WaitGroup)
wg.Add(len(endpoints))

warn_if_expired_at := time.Now().AddDate(0, 0, *warning_period)

for _, i := range endpoints {

// sleep 1ms to avoid hitting DNS resolver limits
Expand All @@ -50,7 +54,7 @@ func main() {
// release semaphore
defer func() { <-semaphore }()

is_expired, err := checkHost(dialer, i, *warning_period)
is_expired, err := checkHost(dialer, i, warn_if_expired_at)
if err != nil {
fmt.Printf("can't check %s: %s\n", i, err)
exitcode = 1
Expand All @@ -66,20 +70,31 @@ func main() {
os.Exit(exitcode)
}

var addrOverride = regexp.MustCompile(`^([^:]+):(((\[[0-9a-f:]+\])|([^:]+)):\d+)$`)

func checkHost(
dialer *net.Dialer,
host string,
warning_period int,
warn_if_expired_at time.Time,
) (is_expired bool, err error) {
conn, err := tls.DialWithDialer(dialer, "tcp", host, &tls.Config{
config := tls.Config{
InsecureSkipVerify: true,
})
}
if !strings.Contains(host, ":") {
host = host + ":443"
} else if match := addrOverride.FindStringSubmatch(host); match != nil {
fmt.Printf("Match %s: %s\n", host, match)
config.ServerName = match[1]
host = match[2]
}

conn, err := tls.DialWithDialer(dialer, "tcp", host, &config)
if err != nil {
return
}
conn.Close()
for _, cert := range conn.ConnectionState().PeerCertificates {
if time.Now().AddDate(0, 0, warning_period).After(cert.NotAfter) {
if warn_if_expired_at.After(cert.NotAfter) {
is_expired = true
fmt.Printf("Certificate for %s (%s) expires in %s\n",
host, cert.Subject.CommonName,
Expand Down

0 comments on commit f083115

Please sign in to comment.