An open API service indexing awesome lists of open source software.

https://github.com/atc0005/check-cert

Go-based tooling to check/verify certs
https://github.com/atc0005/check-cert

certificate cli discovery golang inspect nagios nagios-plugin plugin scanner

Last synced: 4 months ago
JSON representation

Go-based tooling to check/verify certs

Awesome Lists containing this project

README

          

# check-cert

Go-based tooling to check/verify certs (e.g., as part of a Nagios service check)

[![Latest Release](https://img.shields.io/github/release/atc0005/check-cert.svg?style=flat-square)](https://github.com/atc0005/check-cert/releases/latest)
[![Go Reference](https://pkg.go.dev/badge/github.com/atc0005/check-cert.svg)](https://pkg.go.dev/github.com/atc0005/check-cert)
[![go.mod Go version](https://img.shields.io/github/go-mod/go-version/atc0005/check-cert)](https://github.com/atc0005/check-cert)
[![Lint and Build](https://github.com/atc0005/check-cert/actions/workflows/lint-and-build.yml/badge.svg)](https://github.com/atc0005/check-cert/actions/workflows/lint-and-build.yml)
[![Project Analysis](https://github.com/atc0005/check-cert/actions/workflows/project-analysis.yml/badge.svg)](https://github.com/atc0005/check-cert/actions/workflows/project-analysis.yml)

## Table of Contents

- [Project home](#project-home)
- [Overview](#overview)
- [`check_cert`](#check_cert)
- [Performance Data](#performance-data)
- [`lscert`](#lscert)
- [`cpcert`](#cpcert)
- [`certsum`](#certsum)
- [Features](#features)
- [`check_cert`](#check_cert-1)
- [`lscert`](#lscert-1)
- [`cpcert`](#cpcert-1)
- [`certsum`](#certsum-1)
- [common](#common)
- [Changelog](#changelog)
- [Requirements](#requirements)
- [Building source code](#building-source-code)
- [Running](#running)
- [Installation](#installation)
- [From source](#from-source)
- [Quick Start guide](#quick-start-guide)
- [Detailed guide](#detailed-guide)
- [Using release binaries](#using-release-binaries)
- [Configuration options](#configuration-options)
- [Expiration threshold calculations](#expiration-threshold-calculations)
- [Asserting that expected Subject Alternate Names (SANs) are present](#asserting-that-expected-subject-alternate-names-sans-are-present)
- [Skip hostname verification when leaf cert is missing SANs entries](#skip-hostname-verification-when-leaf-cert-is-missing-sans-entries)
- [Applying or ignoring validation check results](#applying-or-ignoring-validation-check-results)
- [`check_cert` plugin](#check_cert-plugin)
- [`lscert` CLI tool](#lscert-cli-tool)
- [`cpcert` CLI tool](#cpcert-cli-tool)
- [`certsum` CLI tool](#certsum-cli-tool)
- [Command-line arguments](#command-line-arguments)
- [`check_cert`](#check_cert-2)
- [`lscert`](#lscert-2)
- [Flags](#flags)
- [Positional Argument](#positional-argument)
- [`cpcert`](#cpcert-2)
- [Flags](#flags-1)
- [Positional Arguments](#positional-arguments)
- [`certsum`](#certsum-2)
- [Configuration file](#configuration-file)
- [Examples](#examples)
- [`check_cert` Nagios plugin](#check_cert-nagios-plugin)
- [OK results](#ok-results)
- [WARNING results](#warning-results)
- [CRITICAL results](#critical-results)
- [Expiring certificate](#expiring-certificate)
- [Expired certificate](#expired-certificate)
- [Explicitly applying validation check results](#explicitly-applying-validation-check-results)
- [`expiration`](#expiration)
- [`hostname`](#hostname)
- [`sans`](#sans)
- [Explicitly ignoring validation check results](#explicitly-ignoring-validation-check-results)
- [`expiration`](#expiration-1)
- [`hostname`](#hostname-1)
- [`sans`](#sans-1)
- [`expiration`, `hostname`, `sans`](#expiration-hostname-sans)
- [Reviewing a certificate file](#reviewing-a-certificate-file)
- [`lscert` CLI tool](#lscert-cli-tool-1)
- [Positional Argument](#positional-argument-1)
- [Simple](#simple)
- [Flags and Argument](#flags-and-argument)
- [OK results](#ok-results-1)
- [WARNING results](#warning-results-1)
- [CRITICAL results](#critical-results-1)
- [Reviewing a certificate file](#reviewing-a-certificate-file-1)
- [`cpcert` CLI tool](#cpcert-cli-tool-1)
- [Using positional arguments](#using-positional-arguments)
- [Copying certificates from server](#copying-certificates-from-server)
- [Copying certificates from file](#copying-certificates-from-file)
- [Using flags](#using-flags)
- [Copy everything](#copy-everything)
- [Leaf certificate only](#leaf-certificate-only)
- [Intermediate certificates only](#intermediate-certificates-only)
- [Root certificates only](#root-certificates-only)
- [`certsum` CLI tool](#certsum-cli-tool-1)
- [Certificates Overview](#certificates-overview)
- [CIDR range](#cidr-range)
- [Partial range](#partial-range)
- [Partial range and a single IP Address](#partial-range-and-a-single-ip-address)
- [Partial range, CIDR range and a single IP Address](#partial-range-cidr-range-and-a-single-ip-address)
- [Single IP Address and a FQDN](#single-ip-address-and-a-fqdn)
- [Show all scan results](#show-all-scan-results)
- [Troubleshooting](#troubleshooting)
- [General](#general)
- [Encoded payloads](#encoded-payloads)
- [License](#license)
- [References](#references)

## Project home

See [our GitHub repo][repo-url] for the latest code, to file an issue or
submit improvements for review and potential inclusion into the project.

## Overview

This repo contains various tools used to review, copy, monitor & validate
certificates.

| Tool Name | Description |
| ------------ | -------------------------------------------------------------------------------------------------------------------------- |
| `check_cert` | Nagios plugin used to monitor & validate certificate chains. |
| `lscert` | CLI app used to generate a summary of certificate chain metadata and validation results. |
| `cpcert` | CLI app used to copy and manipulate certificates. |
| `certsum` | CLI app used to scan one or more given IP ranges or collection of name/FQDN values for certs and provide a summary report. |

### `check_cert`

Nagios plugin used to monitor & perform validation checks of certificate
chains.

The output is designed to provide the one-line summary needed by Nagios for
quick identification of a problem while providing longer, more detailed
information for use in email and Teams notifications
([atc0005/send2teams](https://github.com/atc0005/send2teams)).

Validation checks are applied in layers, with support for explicitly marking
or flagging specific validation check results as "ignored". Ignored results
are still listed, but in a separate section of the check results output (aka,
"report") and are not considered when performing final plugin state (i.e.,
`OK`, `WARNING`, `CRITICAL`).

Some validation check results are ignored by default unless additional
information is supplied. For example, the SANs list validation check result is
ignored unless the sysadmin provides a list of required SANs entries. Other
check results may be ignored by default, but can be explicitly requested via a
supported flag keyword (see [configuration options](#configuration-options)
for more information).

See the [features list](#features) for the validation checks currently
supported by this plugin.

---

NOTE: The validation check behavior changes for `v0.8.0` are intended to be
fully compatible with existing deployments. Please file a bug report if you
find that this is not the case.

For future releases, please review the release notes carefully for any
breaking changes.

---

#### Performance Data

Initial support has been added for emitting Performance Data / Metrics, but
refinement suggestions are welcome.

Consult the tables below for the metrics implemented thus far.

Please add to an existing
[Discussion](https://github.com/atc0005/check-cert/discussions) thread
(if applicable) or [open a new
one](https://github.com/atc0005/check-cert/discussions/new) with any
feedback that you may have. Thanks in advance!

| Emitted Performance Data / Metric | Meaning |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `time` | Runtime for plugin |
| `plugin_output_size` | Total size of generated plugin output. Total content must fall within [max plugin output length restrictions](https://github.com/NagiosEnterprises/nagioscore/blob/a30a89e0a493da49416e32ed770e294b1fe800f5/include/nagios.h#L274-L280). |
| `expires_leaf` | Days remaining before leaf (aka, "server") certificate expires. If multiple leaf certificates are present (invalid configuration), the one expiring soonest is reported. |
| `expires_intermediate` | Days remaining before the next to expire intermediate certificate expires. |
| `certs_present_leaf` | Number of leaf (aka, "server") certificates present in the chain. |
| `certs_present_intermediate` | Number of intermediate certificates present in the chain. |
| `certs_present_root` | Number of root certificates present in the chain. |
| `certs_present_unknown` | Number of certificates present in the chain with an unknown scope (i.e., the plugin cannot determine whether a leaf, intermediate or root). Please [report this scenario](https://github.com/atc0005/check-cert/issues/new/choose). |
| `life_remaining_leaf` | Percentage of remaining time before leaf (aka, "server") certificate expires. If multiple leaf certificates are present (invalid configuration), the one expiring soonest is reported. |
| `life_remaining_intermediate` | Percentage of remaining time before the next to expire intermediate certificate expires. |

### `lscert`

The `lscert` CLI app is used to generate a summary of certificate chain
metadata and validation results for quick review.

It can be used to quickly review the results of replacing a certificate
and/or troubleshoot why connections to a certificate-enabled service may be
failing.

Certificate metadata can be retrieved from:

- a remote service at a specified fully-qualified domain name (e.g.,
`www.github.com`) or IP Address and port (e.g., 443)
- a local certificate "bundle" or standalone leaf certificate file

If specifying a host via IP Address, a hostname validation failure will be
noted unless:

- you also specify the `DNS Name` or `hostname` that you wish to retrieve the
certificate for
- the IP Address is in the Subject Alternate Name (SANs) list for the
certificate

This hostname validation failure can be ignored if you are only interested in
viewing the details for the default certificate associated with the IP
Address.

### `cpcert`

The `cpcert` CLI app is used to copy and manipulate certificates.

Certificates can be copied from:

- a remote service at a specified fully-qualified domain name (e.g.,
`www.github.com`) or IP Address and port (e.g., 443)
- a local certificate "bundle" or standalone leaf certificate file

If specifying a host via IP Address, the default certificate chain (instead of
the one specific to a dedicated virtual host) will be retrieved unless:

- you also specify the `DNS Name` or `hostname` that you wish to retrieve the
certificate for
- the IP Address is in the Subject Alternate Name (SANs) list for the
certificate

Support is provided to filter the given input certificate chain to specified
certificate types.

### `certsum`

`certsum` is a cert scanner prototype. This tool is currently of "beta" level
quality; many of the exposed flags, help text and summary output are subject
to change significantly in later releases.

This tool is intended for scanning one or more given IP ranges or collection
of name/FQDN values in order to generate a report for discovered certificates.
While intended for mass discovery this tool may be used to scan as few as one
target.

Performance is likely to be acceptable as-is for smaller IP ranges, but may be
adjusted as needed using the rate limit tuning flag (see the [configuration
options](#configuration-options) section for details). The current default
value is an attempt to balance scanning speed against OS limitations on the
number of open file handles. If adjusting this value, start with small
increments to determine best results for your environment.

A default inactivity timeout is used to terminate the application if scanning
attempts stall for a specified period of time. See the [configuration
options](#configuration-options) section for details.

IP Addresses may be specified as comma-separated values:

- individual IP Addresses
- CIDR IP ranges
- partial ranges
- using partial implementation of octet range addressing (e.g.,
192.168.2.10-15)
- Fully-qualified domain names (FQDNs)
- needed if retrieving a non-default certificate chain (via
[SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) support)
- Hostnames (**fragile**)
- this is highly dependent on your DNS configuration, particularly any
configured search list (aka, `DNS Suffix Search List` in Windows
terminology) entries used to qualify short/hostname values

Support is present (though limited) for filtering "OK" status hosts and certs
to either increase or reduce the amount of information provided in the
generated summary output. Two summary modes are provided to control the level
of detail in the provided output.

NOTE: If using IP Addresses (or ranges), only the default certificate will be
accessible to this tool. Use FQDNs in order to retrieve certificates using
[SNI](https://en.wikipedia.org/wiki/Server_Name_Indication).

## Features

### `check_cert`

- Verify certificate used by specified service

- Verify local certificate "bundle" or standalone leaf certificate file

- Detailed "report" of findings
- certificate order
- certificate type
- status (OK, CRITICAL, WARNING)
- SANs entries
- serial number
- issuer

- Multiple certificate validation checks
- Expiration status for all certificates in a chain
- not expired
- expiring "soon"
- warning threshold
- critical threshold
- Hostname value for the leaf certificate in a chain
- see subsection for skipping hostname verification when the leaf
certificate is missing SANs entries in the [configuration
options](#configuration-options) section for details
- Subject Alternate Names (SANs) for the leaf certificate in a chain
- if `SKIPSANSCHECKS` keyword is supplied as the value no SANs entry
checks will be performed; this keyword is useful for defining a shared
Nagios check command and service check where SANs list validation may
not be desired for some certificate chains (e.g., those with a very long
list of entries)

- Optional support for skipping hostname verification for a certificate when
the SANs list is empty
- Optional support for ignoring expiring intermediate certificates
- Optional support for ignoring expired intermediate certificates
- Optional support for ignoring expiring root certificates
- Optional support for ignoring expired root certificates
- Optional support for omitting Subject Alternate Names (SANs) entries from
plugin output
- Optional support for embedding an encoded certificate metadata payload
- disabled by default to retain existing plugin behavior
- the intent is to "shuttle" a payload of certificate metadata in structured
format from the plugin, to the monitoring system and to downstream tools
(e.g., via API call) so that the payload can be retrieved, decoded, &
unmarshalled to a supported data structure for further certificate
evaluation
- see also the and
projects for the data structures
and supporting logic used in the encoding/decoding process
- Optional support for embedding an encoded certificate metadata payload *with
the original certificate chain included* in PEM encoded format
- this is not enabled by default due to the significant increase in plugin
output size
- Optional support for overriding the default certificate metadata format
version used when generating payloads

### `lscert`

- Verify certificate used by specified service

- Verify local certificate "bundle" or standalone leaf certificate file

- Optional generation of OpenSSL-like text output from target cert-enabled
service or filename
- thanks to the `grantae/certinfo` package

- Detailed "report" of findings
- certificate order
- certificate type
- status (OK, CRITICAL, WARNING)
- SANs entries
- serial number
- issuer

- Multiple certificate validation checks
- Expiration status for all certificates in a chain
- Hostname value for the leaf certificate in a chain
- Subject Alternate Names (SANs) for the leaf certificate in a chain

### `cpcert`

- Copy certificate chain as-is from remote server

- Filter given input file to specified types of certificates
- e.g., "keep leaf cert only"
- e.g., "keep intermediates only"

### `certsum`

- Generate summary of discovered certificates from given hosts (single or IP
Address ranges, hostnames or FQDNs) and ports

- Configurable rate limit

- Specify one or many ports to scan for certificate chains

- Configurable display of just "problem" results or all results

- Choice of high-level summary/overview or separate output for each
certificate in a chain

- Configurable application timeout (i.e., help prevent stalling out)

### common

Features common to all tools provided by this project.

- Retrieve certificate chain using
[SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) support
- attempted by default if the given server name/FQDN value was resolvable
- server value can be overridden via the `dns-name` flag (see the
[configuration options](#configuration-options) section for details)

- Optional, leveled logging using `rs/zerolog` package
- [`logfmt`][logfmt] format output
- to `stderr` for `check_cert`
- to `stdout` for `lscert` & `certsum`
- choice of `disabled`, `panic`, `fatal`, `error`, `warn`, `info` (the
default), `debug` or `trace`.

- Optional, user-specified timeout value for TCP connection attempt

## Changelog

See the [`CHANGELOG.md`](CHANGELOG.md) file for the changes associated with
each release of this application. Changes that have been merged to `master`,
but not yet an official release may also be noted in the file under the
`Unreleased` section. A helpful link to the Git commit history since the last
official release is also provided for further review.

## Requirements

The following is a loose guideline. Other combinations of Go and operating
systems for building and running tools from this repo may work, but have not
been tested.

### Building source code

- Go
- see this project's `go.mod` file for *preferred* version
- this project tests against [officially supported Go
releases][go-supported-releases]
- the most recent stable release (aka, "stable")
- the prior, but still supported release (aka, "oldstable")
- GCC
- if building with custom options (as the provided `Makefile` does)
- `make`
- if using the provided `Makefile`

### Running

- Windows 10
- Ubuntu Linux 18.04+
- Red Hat Enterprise Linux 7+
- macOS 11 Big Sur
- NOTE: You may need to build from source using an older Go release if your
version of macOS is not supported by the current "oldstable" version of Go
- "stable" release builds for this project are usually generated from the
most recent "oldstable" Go release version while "dev" or "unstable"
release builds are generated from the most recent "stable" or release
candidate Go release

## Installation

### From source

#### Quick Start guide

This provides binaries based on the latest stable tag generated using the
equivalent of `go build`.

1. [Download][go-docs-download] Go
1. [Install][go-docs-install] Go
1. `go install github.com/atc0005/check-cert/cmd/certsum@latest`
1. `go install github.com/atc0005/check-cert/cmd/lscert@latest`
1. `go install github.com/atc0005/check-cert/cmd/cpcert@latest`
1. `GOBIN="${PWD}" go install github.com/atc0005/check-cert/cmd/check_cert@latest`
1. `sudo mv check_cert /path/to/plugins/`
- e.g., `/usr/lib/nagios/plugins/` or `/usr/lib64/nagios/plugins/`,
depending on what distro you are running

Per `go help install`:

> Executables are installed in the directory named by the GOBIN environment
> variable, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH
> environment variable is not set. Executables in $GOROOT
> are installed in $GOROOT/bin or $GOTOOLDIR instead of $GOBIN.

#### Detailed guide

This provides binaries based on the state of the current checked out branch
(or tag) using either `go build` or if using the provided Makefile, build
settings intended to optimize for size and to prevent dynamic linkage.

1. [Download][go-docs-download] Go
1. [Install][go-docs-install] Go
- NOTE: Pay special attention to the remarks about `$HOME/.profile`
1. Clone the repo
1. `cd /tmp`
1. `git clone https://github.com/atc0005/check-cert`
1. `cd check-cert`
1. (Optional) `git checkout vX.Y.Z`
- where `vX.Y.Z` is a tag such as `v0.8.0`
1. Install dependencies (optional)
- for Ubuntu Linux
- `sudo apt-get install make gcc`
- for CentOS Linux
- `sudo yum install make gcc`
- for Windows
- Emulated environments (*easier*)
- Skip all of this and build using the default `go build` command in
Windows (see below for use of the `-mod=vendor` flag)
- build using Windows Subsystem for Linux Ubuntu environment and just
copy out the Windows binaries from that environment
- If already running a Docker environment, use a container with the Go
tool-chain already installed
- If already familiar with LXD, create a container and follow the
installation steps given previously to install required dependencies
- Native tooling (*harder*)
- see the StackOverflow Question `32127524` link in the
[References](references.md) section for potential options for
installing `make` on Windows
- see the mingw-w64 project homepage link in the
[References](references.md) section for options for installing `gcc`
and related packages on Windows
1. Build binaries
- for the detected current operating system and architecture, explicitly
using bundled dependencies in top-level `vendor` folder
- `go build -mod=vendor ./cmd/check_cert/`
- `go build -mod=vendor ./cmd/lscert/`
- `go build -mod=vendor ./cmd/cpcert/`
- `go build -mod=vendor ./cmd/certsum/`
- for all supported platforms (where `make` is installed)
- `make all`
- for use on Windows amd64
- `make windows-x64-build`
- for use on Linux amd64
- `make linux-x64-build`
- for use on Linux arm64
- `make linux-arm64-build`
- for use on macOS amd64
- `make darwin-amd64-build`
- for use on macOS arm64
- `make darwin-arm64-build`
1. Copy the newly compiled binary from the applicable `/tmp` subdirectory path
(based on the clone instructions in this section) below and deploy where
needed.
- if using `Makefile`
- look in `/tmp/check-cert/release_assets/check_cert/`
- look in `/tmp/check-cert/release_assets/lscert/`
- look in `/tmp/check-cert/release_assets/cpcert/`
- look in `/tmp/check-cert/release_assets/certsum/`
- if using `go build`
- look in `/tmp/check-cert/`

**NOTE**: Depending on which `Makefile` recipe you use the generated binary
may be compressed and have an `xz` extension. If so, you should decompress the
binary first before deploying it (e.g., `xz -d check_cert-linux-amd64.xz`).

### Using release binaries

1. Download the [latest
release](https://github.com/atc0005/check-cert/releases/latest) binaries
1. Decompress binaries
- e.g., `xz -d check_cert-linux-amd64.xz`
1. Rename binaries
- e.g., `mv check_cert-linux-amd64 check_cert`
1. Deploy
- Place `check_cert` alongside your other Nagios plugins
- e.g., `/usr/lib/nagios/plugins/` or `/usr/lib64/nagios/plugins/`
- Place `lscert`,, `cpcert`, `certsum` in a location of your choice
- e.g., `/usr/local/bin/`

**NOTE**:

As of the v0.11.0 release, DEB and RPM packages are provided as an alternative
to manually deploying binaries.

## Configuration options

### Expiration threshold calculations

This applies to all tools provided by this project.

The behavior of the `check_cert`plugin differs somewhat from `check_http`
`v2.1.2`; this plugin triggers a whole day *later* than `check_http` does for
the same `WARNING` and `CRITICAL` threshold values.

For example, if we use the default values of 30 days for `WARNING` threshold
and 15 days for the `CRITICAL` threshold:

1. The thresholds are calculated
- `WARNING`: Now (exact time in UTC) + 30 days
- `CRITICAL`: Now (exact time in UTC) + 15 days
1. The certificate expiration date is checked and the very first match (in
order) determines the status of the service check
1. if the certificate expires *before* the current time, the status is
`EXPIRED`
1. if the certificate expires *before* the CRITICAL threshold, the status
is `CRITICAL`
1. if the certificate expires *before* the WARNING threshold, the status
is `WARNING`
1. otherwise, the certificate is assumed to have a status of `OK`

No rounding is performed.

See GH-32 for additional info.

### Asserting that expected Subject Alternate Names (SANs) are present

Among other validation checks, the `check_cert` plugin and `lscert` CLI tool
both support SANs list validation by accepting a CSV list of expected SANs
entries and assert that:

- all provided SANs entries are present on the leaf certificate
- all SANs entries present on the leaf certificate are in the provided SANs
entries list

Problem scenarios covered:

- the cert provider omitted a requested/expected SANs entry
- the monitoring configuration has not been updated to look for new SANs
entries present on the leaf cert

As a real-world use case, applying SANs list validation helped catch an
unapproved DNS record (CNAME) change for a public service. The DNS record
change resulted in the service redirecting from the original (intended)
pre-production system to a development box used by a different team in another
business unit. While this would have likely been detected before the system
was deployed to production, it would have caused unnecessary confusion/delays
while the issue was worked out. Instead, the monitoring system caught the
issue and the service owner was able to reach out immediately and coordinate
reverting the unauthorized change.

### Skip hostname verification when leaf cert is missing SANs entries

This is specific to the `check_cert` plugin.

Optional support is available to skip hostname verification if a certificate
is missing SANs entries.

- in version v0.5.3 and earlier, support was available for validating a given
hostname against the Common Name field of a certificate, regardless of
whether SANs entries were present
- Go 1.15 marked this support as deprecated
- Go 1.16 noted that it would be dropped in Go 1.17
- Go 1.17 dropped this support
- in version 0.6.0 and later, support is available (if specified) to skip
hostname verification if a certificate is missing Subject Alternate Names
(SANs) entries
- this support is intended as a temporary workaround until the certificate
expires and is replaced with a certificate containing a valid SANs list

See the flags table for the `check_cert` plugin for more information.

### Applying or ignoring validation check results

#### `check_cert` plugin

As of v0.8.0, all available validation checks are now performed regardless of
what flags and flag values are specified.

Whereas the previous behavior was to both apply a validation check *and*
hard-code the behavior of applying the result against the final plugin state,
support has been added to explicitly *apply* or *ignore* individual validation
check results.

This support is provided via new flags and a set of keywords that may be
specified as a comma-separated value list.

Most validation check results are applied by default, provided that required
configuration settings are applied. Some are ignored by default.

| Validation Check Result | Applied by default | Requirements |
| ----------------------- | ------------------ | ------------------------- |
| `Expiration` | Yes | Expiration thresholds |
| `Hostname` | Yes | Server or DNS Name values |
| `SANs list` | Yes`*` | SANs entries |

The certificate expiration validation check is applied using default
thresholds if not specified by the sysadmin. The hostname verification check
is applied using either the server (fallback) or DNS Name (preferred) value.

The SANs list validation check`*` is applied *if* SANs entries are provided.
If SANs entries are not specified, this validation check is performed, but
noted as ignored in the output (and not used when determining final plugin
state); without SANs entries to validate the SANs list validation check result
is of limited value. If explicitly requested and SANs entries are not provided
a configuration error is emitted and the plugin terminates.

#### `lscert` CLI tool

All validation checks are applied with output streamlined for quick pass/fail
evaluation. While flags are not currently offered to explicitly *apply* or
*ignore* validation check results this support may be added in the future if
there is sufficient interest.

#### `cpcert` CLI tool

Not applicable to this tool.

#### `certsum` CLI tool

No changes to validation check results have been made as of the v0.8.0
release. This tool continues to focus on identifying problem certificates by
way of expiration date thresholds. Future versions may incorporate additional
validation checks and any behavior changes at that time noted.

### Command-line arguments

- Use the `-h` or `--help` flag to display current usage information.
- Flags marked as **`required`** must be set via CLI flag.
- Flags *not* marked as required are for settings where a useful default is
already defined, but may be overridden if desired.

#### `check_cert`

| Flag | Required | Default | Repeat | Possible | Description |
| -------------------------------------------- | --------- | ------- | ------ | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `f`, `filename` | No | | No | *valid file name characters* | Fully-qualified path to a PEM (text) or binary DER formatted certificate file containing one or more certificates. |
| `branding` | No | `false` | No | `branding` | Toggles emission of branding details with plugin status details. This output is disabled by default. |
| `h`, `help` | No | `false` | No | `h`, `help` | Show Help text along with the list of supported flags. |
| `v`, `verbose` | No | `false` | No | `v`, `verbose` | Toggles emission of detailed certificate metadata. This level of output is disabled by default. |
| `payload` | No | `false` | No | `true`, `false` | Toggles emission of encoded certificate chain payload. This output is disabled by default. |
| `payload-with-full-chain` | No | `false` | No | `true`, `false` | Toggles emission of encoded certificate chain payload with the full certificate chain included. This option is disabled by default due to the significant increase in payload size. |
| `payload-format` | No | `1` | No | *positive whole number for valid payload format version* | Specifies the format version to use when generating the (optional) certificate metadata payload. Format version `0` is unstable and intended for development purposes only. |
| `omit-sans-list`, `omit-sans-entries` | No | `false` | No | `true`, `false` | Toggles listing of SANs entries list items in certificate metadata output. This list is included by default. |
| `version` | No | `false` | No | `version` | Whether to display application version and then immediately exit application. |
| `c`, `age-critical` | No | 15 | No | *positive whole number of days* | The threshold for the certificate check's `CRITICAL` state. If the certificate expires before this number of days then the service check will be considered in a `CRITICAL` state. |
| `w`, `age-warning` | No | 30 | No | *positive whole number of days* | The threshold for the certificate check's `WARNING` state. If the certificate expires before this number of days, but not before the `age-critical` value, then the service check will be considered in a `WARNING` state. |
| `ll`, `log-level` | No | `info` | No | `disabled`, `panic`, `fatal`, `error`, `warn`, `info`, `debug`, `trace` | Log message priority filter. Log messages with a lower level are ignored. |
| `p`, `port` | No | `443` | No | *positive whole number between 1-65535, inclusive* | TCP port of the remote certificate-enabled service. This is usually 443 (HTTPS) or 636 (LDAPS). |
| `t`, `timeout` | No | `10` | No | *positive whole number of seconds* | Timeout value in seconds allowed before a connection attempt to a remote certificate-enabled service (in order to retrieve the certificate) is abandoned and an error returned. |
| `se`, `sans-entries` | No | | No | *comma-separated list of values* | One or many names required to be in the Subject Alternate Names (SANs) list for a leaf certificate. If provided, this list of comma-separated values is required for the certificate to pass validation. If the case-insensitive `SKIPSANSCHECKS` keyword is provided the results from this validation check will be flagged as ignored. |
| `s`, `server` | **Maybe** | | No | *fully-qualified domain name or IP Address* | The fully-qualified domain name or IP Address used for certificate chain retrieval. This value should appear in the Subject Alternate Names (SANs) list for the leaf certificate unless also using the `dns-name` flag. |
| `dn`, `dns-name` | **Maybe** | | No | *fully-qualified domain name or IP Address* | A fully-qualified domain name or IP Address in the Subject Alternate Names (SANs) list for the leaf certificate. If specified, this value will be used when retrieving the certificate chain (SNI support) and for hostname verification. Required when evaluating certificate files. See the `server` flag description for more information. |
| `ignore-hostname-verification-if-empty-sans` | No | `false` | No | `true`, `false` | Whether a hostname verification failure should be ignored if Subject Alternate Names (SANs) list is empty. |
| `ignore-expired-intermediate-certs` | No | `false` | No | `true`, `false` | Whether expired intermediate certificates should be ignored. |
| `ignore-expired-root-certs` | No | `false` | No | `true`, `false` | Whether expired root certificates should be ignored. |
| `ignore-expiring-intermediate-certs` | No | `false` | No | `true`, `false` | Whether expiring intermediate certificates should be ignored. |
| `ignore-expiring-root-certs` | No | `false` | No | `true`, `false` | Whether expiring root certificates should be ignored. |
| `ignore-validation-result` | No | | No | `sans`, `expiration`, `hostname` | List of keywords for certificate chain validation check result that should be explicitly ignored and not used to determine final validation state. |
| `apply-validation-result` | No | | No | `sans`, `expiration`, `hostname` | List of keywords for certificate chain validation check results that should be explicitly applied and used to determine final validation state. |
| `list-ignored-errors` | No | `false` | No | `true`, `false` | Toggles emission of ignored validation check result errors. Disabled by default to reduce confusion. |

#### `lscert`

##### Flags

| Flag | Required | Default | Repeat | Possible | Description |
| ------------------------------------- | --------- | ------- | ------ | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `f`, `filename` | No | | No | *valid file name characters* | Fully-qualified path to a PEM (text) or binary DER formatted certificate file containing one or more certificates. |
| `text` | No | `false` | No | `true`, `false` | Toggles emission of x509 TLS certificates in an OpenSSL-inspired text format. This output is disabled by default. |
| `h`, `help` | No | `false` | No | `h`, `help` | Show Help text along with the list of supported flags. |
| `v`, `verbose` | No | `false` | No | `v`, `verbose` | Toggles emission of detailed certificate metadata. This level of output is disabled by default. |
| `omit-sans-list`, `omit-sans-entries` | No | `false` | No | `true`, `false` | Toggles listing of SANs entries list items in certificate metadata output. This list is included by default. |
| `version` | No | `false` | No | `version` | Whether to display application version and then immediately exit application. |
| `c`, `age-critical` | No | 15 | No | *positive whole number of days* | The threshold for the certificate check's `CRITICAL` state. If the certificate expires before this number of days then the service check will be considered in a `CRITICAL` state. |
| `w`, `age-warning` | No | 30 | No | *positive whole number of days* | The threshold for the certificate check's `WARNING` state. If the certificate expires before this number of days, but not before the `age-critical` value, then the service check will be considered in a `WARNING` state. |
| `ll`, `log-level` | No | `info` | No | `disabled`, `panic`, `fatal`, `error`, `warn`, `info`, `debug`, `trace` | Log message priority filter. Log messages with a lower level are ignored. |
| `p`, `port` | No | `443` | No | *positive whole number between 1-65535, inclusive* | TCP port of the remote certificate-enabled service. This is usually 443 (HTTPS) or 636 (LDAPS). |
| `t`, `timeout` | No | `10` | No | *positive whole number of seconds* | Timeout value in seconds allowed before a connection attempt to a remote certificate-enabled service (in order to retrieve the certificate) is abandoned and an error returned. |
| `se`, `sans-entries` | No | | No | *comma-separated list of values* | One or many names required to be in the Subject Alternate Names (SANs) list for a leaf certificate. If provided, this list of comma-separated values is required for the certificate to pass validation. If the case-insensitive `SKIPSANSCHECKS` keyword is provided the results from this validation check will be flagged as ignored. |
| `s`, `server` | **Maybe** | | No | *fully-qualified domain name or IP Address* | The fully-qualified domain name or IP Address used for certificate chain retrieval. This value should appear in the Subject Alternate Names (SANs) list for the leaf certificate unless also using the `dns-name` flag. |
| `dn`, `dns-name` | **Maybe** | | No | *fully-qualified domain name or IP Address* | A fully-qualified domain name or IP Address in the Subject Alternate Names (SANs) list for the leaf certificate. If specified, this value will be used when retrieving the certificate chain (SNI support) and for hostname verification. Required when evaluating certificate files. See the `server` flag description for more information. |

##### Positional Argument

As of the v0.9.0 release the `lscert` tool accepts a URL pattern as a single
positional argument. This positional argument value can be any of:

- URL
- resolvable name
- IP Address

---

**NOTE**: Due to limitations in the Go standard library's support for
command-line argument handling all flags must come before positional arguments
on the command line.

---

Valid syntax:

- `lscert -flag1 value1 INPUT_PATTERN`

Invalid syntax:

- `lscert INPUT_PATTERN -flag1 value1`

Some valid examples:

- `lscert google.com`
- `lscert https://www.google.com`
- `lscert https://www.google.com:443`
- `lscert --log-level debug google.com`
- `lscert --dns-name one.one.one.one 1.1.1.1`

Aside from the required order of flags and positional argument noted above,
there are additional requirements to be aware of:

- if the `server` or `filename` flags are specified, the positional argument
is ignored
- if the `port` flag is specified, its value will be ignored if a port is
provided in the given URL pattern positional argument

#### `cpcert`

##### Flags

| Flag | Required | Default | Repeat | Possible | Description |
| ----------------------- | --------- | ------- | ------ | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `if`, `input-filename` | No | | No | *valid file name characters* | Fully-qualified path to a PEM (text) or binary DER formatted certificate file containing one or more certificates. |
| `of`, `output-filename` | Yes | | No | *valid file name characters* | Fully-qualified path to an output file to write one or more PEM (text) encoded certificates. |
| `h`, `help` | No | `false` | No | `h`, `help` | Show Help text along with the list of supported flags. |
| `v`, `verbose` | No | `false` | No | `v`, `verbose` | Toggles emission of detailed certificate metadata. This level of output is disabled by default. |
| `version` | No | `false` | No | `version` | Whether to display application version and then immediately exit application. |
| `ll`, `log-level` | No | `info` | No | `disabled`, `panic`, `fatal`, `error`, `warn`, `info`, `debug`, `trace` | Log message priority filter. Log messages with a lower level are ignored. |
| `p`, `port` | No | `443` | No | *positive whole number between 1-65535, inclusive* | TCP port of the remote certificate-enabled service. This is usually 443 (HTTPS) or 636 (LDAPS). |
| `t`, `timeout` | No | `10` | No | *positive whole number of seconds* | Timeout value in seconds allowed before a connection attempt to a remote certificate-enabled service (in order to retrieve the certificate) is abandoned and an error returned. |
| `s`, `server` | **Maybe** | | No | *fully-qualified domain name or IP Address* | The fully-qualified domain name or IP Address used for certificate chain retrieval. This value should appear in the Subject Alternate Names (SANs) list for the leaf certificate unless also using the `dns-name` flag. |
| `dn`, `dns-name` | **Maybe** | | No | *fully-qualified domain name or IP Address* | A fully-qualified domain name or IP Address in the Subject Alternate Names (SANs) list for the leaf certificate. If specified, this value will be used when retrieving the certificate chain (SNI support) and for hostname verification. Required when evaluating certificate files. See the `server` flag description for more information. |
| `keep` | No | `all` | No | `all`, `leaf`, `intermediate`, `root` | List of keywords for certificate types that should be kept from the input certificate chain when saving the output file. |

##### Positional Arguments

In addition to input and output filename flags, the `cpcert` tool accepts two
positional arguments:

1. URL pattern or input filename
1. output filename

"URL patterns" can be any of:

- URL
- resolvable name
- IP Address

---

**NOTE**: Due to limitations in the Go standard library's support for
command-line argument handling all flags must come before positional arguments
on the command line.

---

Valid syntax:

- `cpcert -flag1 value1 INPUT_PATTERN OUTPUT_FILE`

Invalid syntax:

- `cpcert INPUT_PATTERN OUTPUT_FILE -flag1 value1`

Some valid examples:

- `cpcert google.com google_cert_chain.pem`
- `cpcert https://www.google.com google_cert_chain.pem`
- `cpcert https://www.google.com:443 google_cert_chain.pem`
- `cpcert --log-level debug google.com google_cert_chain.pem`
- `cpcert --dns-name one.one.one.one 1.1.1.1 cf_dns_1111_cert_chain.pem`
- `cpcert --keep leaf cf_dns_1111_cert_chain.pem cf_dns_1111_leaf_cert_only.pem`

Aside from the required order of flags and positional argument noted above,
there are additional requirements to be aware of:

- specifying the `server`, `input-filename` or `output-filename` flags
alongside positional arguments is unsupported
- if the `port` flag is specified, its value will be ignored if a port is
provided in the given URL pattern positional argument

#### `certsum`

This tool is in early development. Options for this tool are subject to
change, perhaps even significantly, in future releases.

| Flag | Required | Default | Repeat | Possible | Description |
| -------------------------------------- | -------- | ------- | ------ | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `h`, `help` | No | `false` | No | `h`, `help` | Show Help text along with the list of supported flags. |
| `version` | No | `false` | No | `version` | Whether to display application version and then immediately exit application. |
| `c`, `age-critical` | No | 15 | No | *positive whole number of days* | The threshold for the certificate check's `CRITICAL` state. If the certificate expires before this number of days then the service check will be considered in a `CRITICAL` state. |
| `w`, `age-warning` | No | 30 | No | *positive whole number of days* | The threshold for the certificate check's `WARNING` state. If the certificate expires before this number of days, but not before the `age-critical` value, then the service check will be considered in a `WARNING` state. |
| `ll`, `log-level` | No | `info` | No | `disabled`, `panic`, `fatal`, `error`, `warn`, `info`, `debug`, `trace` | Log message priority filter. Log messages with a lower level are ignored. |
| `t`, `timeout` | No | `10` | No | *positive whole number of seconds* | Timeout value in seconds allowed before a connection attempt to a remote certificate-enabled service (in order to retrieve the certificate) is abandoned and an error returned. |
| `se`, `sans-entries` | No | | No | *comma-separated list of values* | One or many Subject Alternate Names (SANs) expected for the certificate used by the remote service. If provided, this list of comma-separated (optional) values is required for the certificate to pass validation. If the case-insensitive SKIPSANSCHECKS keyword is provided this validation will be skipped, effectively turning the use of this flag into a NOOP. |
| `st`, `scan-timeout` | No | 200 | No | *positive whole number of milliseconds, minimum 1* | The number of milliseconds before a connection attempt during a port scan is abandoned and an error returned. This timeout value is separate from the general `timeout` value used when retrieving certificates. This setting is used specifically to quickly determine port state as part of bulk operations where speed is crucial. |
| `at`, `app-timeout` | No | 30 | No | *positive whole number of seconds, minimum 2* | The number of seconds the application is allowed to remain inactive (i.e., "hung") before it is automatically terminated. |
| `srl`, `scan-rate-limit` | No | 100 | No | *positive whole number* | Maximum concurrent port and certificate scans. Remaining scans are queued until an existing scan completes. |
| `ips`, `hosts` | No | | No | *one or more valid, comma-separated IP Addresses (single or range), hostnames or FQDNs* | List of comma-separated individual IP Addresses, CIDR IP ranges, partial (dash-separated) ranges (e.g., 192.168.2.10-15), hostnames or FQDNs to scan for certificates. |
| `p`, `ports` | No | 443 | No | *one or more valid, comma-separated TCP ports* | List of comma-separated TCP ports to check for certificates. If not specified, the list defaults to 443 only. |
| `spsr`, `show-port-scan-results` | No | `false` | No | `true`, `false` | Toggles listing host port scan results. |
| `scp`, `show-closed-ports` | No | `false` | No | `true`, `false` | Toggles listing all host port scan results, even for hosts without any specified ports in an open state. |
| `shwvc`, `show-hosts-with-valid-certs` | No | `false` | No | `true`, `false` | Toggles listing all cert check results in overview output, even for hosts with valid certificates. |
| `svc`, `show-valid-certs` | No | `false` | No | `true`, `false` | Toggles listing all certificates in output summary, even certificates which have passed all validity checks. |
| `so`, `show-overview` | No | `false` | No | `true`, `false` | Toggles summary output view from detailed to overview. |

### Configuration file

Not currently supported. This feature may be added later if there is
sufficient interest.

## Examples

### `check_cert` Nagios plugin

#### OK results

This example shows using the Nagios plugin to manually check a remote
certificate-enabled port on `www.google.com`. We override the default
`WARNING` and `CRITICAL` age threshold values with somewhat arbitrary numbers.

NOTE: Use the `--verbose` flag to expose further details.

```ShellSession
$ check_cert --server www.google.com --port 443 --age-critical 30 --age-warning 50
OK: Expiration validation successful: leaf cert "www.google.com" expires next with 65d 23h remaining (until 2022-08-29 09:39:59 +0000 UTC) [checks: 1 IGNORED (SANs List), 0 FAILED, 2 SUCCESSFUL (Hostname, Expiration)]

3 certs retrieved for service running on www.google.com (64.233.185.105) at port 443 using host value "www.google.com"

PROBLEM RESULTS:

* None

IGNORED RESULTS:

[--] SANs List validation ignored: 0 SANs entries specified, 1 SANs entries on leaf cert [0 EXPECTED, 0 MISSING, 0 UNEXPECTED]

SUCCESS RESULTS:

[OK] Expiration validation successful: leaf cert "www.google.com" expires next with 65d 23h remaining (until 2022-08-29 09:39:59 +0000 UTC)

Certificate 1 of 3 (leaf):
Name: CN=www.google.com
SANs entries: [www.google.com]
Issuer: CN=GTS CA 1C3,O=Google Trust Services LLC,C=US
Serial: 9F:07:4B:11:74:5F:16:FC:12:23:75:FA:58:79:93:F0
Issued On: 2022-06-06 09:40:00 +0000 UTC
Expiration: 2022-08-29 09:39:59 +0000 UTC
Status: [OK] 65d 23h remaining

Certificate 2 of 3 (intermediate):
Name: CN=GTS CA 1C3,O=Google Trust Services LLC,C=US
SANs entries: []
Issuer: CN=GTS Root R1,O=Google Trust Services LLC,C=US
Serial: 02:03:BC:53:59:6B:34:C7:18:F5:01:50:66
Issued On: 2020-08-13 00:00:42 +0000 UTC
Expiration: 2027-09-30 00:00:42 +0000 UTC
Status: [OK] 1923d 13h remaining

Certificate 3 of 3 (intermediate):
Name: CN=GTS Root R1,O=Google Trust Services LLC,C=US
SANs entries: []
Issuer: CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE
Serial: 77:BD:0D:6C:DB:36:F9:1A:EA:21:0F:C4:F0:58:D3:0D
Issued On: 2020-06-19 00:00:42 +0000 UTC
Expiration: 2028-01-28 00:00:42 +0000 UTC
Status: [OK] 2043d 13h remaining

[OK] Hostname validation using value "www.google.com" successful for leaf certificate

| 'time'=305ms;;;;
```

See the `WARNING` example output for additional details.

#### WARNING results

Here we do the same thing again, but using the expiration date values returned
earlier as a starting point, we intentionally move the threshold values in
order to trigger a `WARNING` state for the leaf certificate.

NOTE: Use the `--verbose` flag to expose further details.

```ShellSession
$ check_cert --server www.google.com --port 443 --age-critical 30 --age-warning 70
5:32AM ERR cmd/check_cert/main.go:413 > validation checks failed for certificate chain error="summary: 1 of 3 validation checks failed" age_critical=30 age_warning=70 app_type=plugin apply_expiration_validation_results=true apply_hostname_validation_results=true apply_sans_list_validation_results=false cert_check_timeout=10s checks_failed=1 checks_ignored=1 checks_successful=1 checks_total=3 expected_sans_entries= filename= logging_level=info port=443 server=www.google.com version="check-cert x.y.z (https://github.com/atc0005/check-cert)"
WARNING: Expiration validation failed: leaf cert "www.google.com" expires next with 65d 23h remaining (until 2022-08-29 09:39:59 +0000 UTC) [checks: 1 IGNORED (SANs List), 1 FAILED (Expiration), 1 SUCCESSFUL (Hostname)]

**VALIDATION ERRORS**

* expiration validation failed: expiring certificates found

**VALIDATION CHECKS REPORT**

3 certs retrieved for service running on www.google.com (64.233.185.105) at port 443 using host value "www.google.com"

PROBLEM RESULTS:

[!!] Expiration validation failed: leaf cert "www.google.com" expires next with 65d 23h remaining (until 2022-08-29 09:39:59 +0000 UTC)

Certificate 1 of 3 (leaf):
Name: CN=www.google.com
SANs entries: [www.google.com]
Issuer: CN=GTS CA 1C3,O=Google Trust Services LLC,C=US
Serial: 9F:07:4B:11:74:5F:16:FC:12:23:75:FA:58:79:93:F0
Issued On: 2022-06-06 09:40:00 +0000 UTC
Expiration: 2022-08-29 09:39:59 +0000 UTC
Status: [WARNING] 65d 23h remaining

Certificate 2 of 3 (intermediate):
Name: CN=GTS CA 1C3,O=Google Trust Services LLC,C=US
SANs entries: []
Issuer: CN=GTS Root R1,O=Google Trust Services LLC,C=US
Serial: 02:03:BC:53:59:6B:34:C7:18:F5:01:50:66
Issued On: 2020-08-13 00:00:42 +0000 UTC
Expiration: 2027-09-30 00:00:42 +0000 UTC
Status: [OK] 1923d 13h remaining

Certificate 3 of 3 (intermediate):
Name: CN=GTS Root R1,O=Google Trust Services LLC,C=US
SANs entries: []
Issuer: CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE
Serial: 77:BD:0D:6C:DB:36:F9:1A:EA:21:0F:C4:F0:58:D3:0D
Issued On: 2020-06-19 00:00:42 +0000 UTC
Expiration: 2028-01-28 00:00:42 +0000 UTC
Status: [OK] 2043d 13h remaining

IGNORED RESULTS:

[--] SANs List validation ignored: 0 SANs entries specified, 1 SANs entries on leaf cert [0 EXPECTED, 0 MISSING, 0 UNEXPECTED]

SUCCESS RESULTS:

[OK] Hostname validation using value "www.google.com" successful for leaf certificate

| 'time'=144ms;;;;
```

Some items to note (in order of appearance):

1. `logfmt` output providing human-readable, structured logging information
- this is sent to `stderr`
- Nagios ignores `stderr` output from plugins; `stdout` is for Nagios,
`stderr` is for humans
1. The one-line status output on the second line
- this is used by Nagios for display in an overview view for all service
checkout for a host
- this is used by Nagios for text, email and whatever else notifications
(if configured)
1. The `VALIDATION ERRORS` section notes briefly what is wrong with the cert
1. The `VALIDATION CHECKS REPORT` section provides an overview of the specific
validation checks performed along with a summary of the certificate chain
evaluated
- this is used by Nagios for display on the detailed service check-specific
page (e.g., shows last check time, frequency, current state, etc)
- as for the one-line output, this is used by Nagios for text, email and
whatever other notifications may be configured
1. The `Status` field for the leaf certificate changed from `OK` to `WARNING`
and this plugin set the appropriate exit code to let Nagios know of the
state change.

#### CRITICAL results

##### Expiring certificate

As with the `WARNING` example, we use the expiration date values returned from
the initial check as a starting point and intentionally move the threshold
values in order to trigger a `CRITICAL` state for the leaf certificate.

NOTE: Use the `--verbose` flag to expose further details.

```ShellSession
$ check_cert --server www.google.com --port 443 --age-critical 70 --age-warning 90
5:36AM ERR cmd/check_cert/main.go:413 > validation checks failed for certificate chain error="summary: 1 of 3 validation checks failed" age_critical=70 age_warning=90 app_type=plugin apply_expiration_validation_results=true apply_hostname_validation_results=true apply_sans_list_validation_results=false cert_check_timeout=10s checks_failed=1 checks_ignored=1 checks_successful=1 checks_total=3 expected_sans_entries= filename= logging_level=info port=443 server=www.google.com version="check-cert x.y.z (https://github.com/atc0005/check-cert)"
CRITICAL: Expiration validation failed: leaf cert "www.google.com" expires next with 65d 23h remaining (until 2022-08-29 09:39:59 +0000 UTC) [checks: 1 IGNORED (SANs List), 1 FAILED (Expiration), 1 SUCCESSFUL (Hostname)]

**VALIDATION ERRORS**

* expiration validation failed: expiring certificates found

**VALIDATION CHECKS REPORT**

3 certs retrieved for service running on www.google.com (64.233.185.105) at port 443 using host value "www.google.com"

PROBLEM RESULTS:

[!!] Expiration validation failed: leaf cert "www.google.com" expires next with 65d 23h remaining (until 2022-08-29 09:39:59 +0000 UTC)

Certificate 1 of 3 (leaf):
Name: CN=www.google.com
SANs entries: [www.google.com]
Issuer: CN=GTS CA 1C3,O=Google Trust Services LLC,C=US
Serial: 9F:07:4B:11:74:5F:16:FC:12:23:75:FA:58:79:93:F0
Issued On: 2022-06-06 09:40:00 +0000 UTC
Expiration: 2022-08-29 09:39:59 +0000 UTC
Status: [CRITICAL] 65d 23h remaining

Certificate 2 of 3 (intermediate):
Name: CN=GTS CA 1C3,O=Google Trust Services LLC,C=US
SANs entries: []
Issuer: CN=GTS Root R1,O=Google Trust Services LLC,C=US
Serial: 02:03:BC:53:59:6B:34:C7:18:F5:01:50:66
Issued On: 2020-08-13 00:00:42 +0000 UTC
Expiration: 2027-09-30 00:00:42 +0000 UTC
Status: [OK] 1923d 13h remaining

Certificate 3 of 3 (intermediate):
Name: CN=GTS Root R1,O=Google Trust Services LLC,C=US
SANs entries: []
Issuer: CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE
Serial: 77:BD:0D:6C:DB:36:F9:1A:EA:21:0F:C4:F0:58:D3:0D
Issued On: 2020-06-19 00:00:42 +0000 UTC
Expiration: 2028-01-28 00:00:42 +0000 UTC
Status: [OK] 2043d 13h remaining

IGNORED RESULTS:

[--] SANs List validation ignored: 0 SANs entries specified, 1 SANs entries on leaf cert [0 EXPECTED, 0 MISSING, 0 UNEXPECTED]

SUCCESS RESULTS:

[OK] Hostname validation using value "www.google.com" successful for leaf certificate

| 'time'=303ms;;;;
```

##### Expired certificate

Here we use the expired.badssl.com subdomain to demo the results of
encountering one or more (in this case more) expired certificates in a chain.
Aside from the FQDN, all default options (including the port) are used.

NOTE: Use the `--verbose` flag to expose further details.

```ShellSession
$ check_cert --server expired.badssl.com
5:36AM ERR cmd/check_cert/main.go:413 > validation checks failed for certificate chain error="summary: 1 of 3 validation checks failed" age_critical=15 age_warning=30 app_type=plugin apply_expiration_validation_results=true apply_hostname_validation_results=true apply_sans_list_validation_results=false cert_check_timeout=10s checks_failed=1 checks_ignored=1 checks_successful=1 checks_total=3 expected_sans_entries= filename= logging_level=info port=443 server=expired.badssl.com version="check-cert x.y.z (https://github.com/atc0005/check-cert)"
CRITICAL: Expiration validation failed: leaf cert "*.badssl.com" expired 2629d 10h ago (on 2015-04-12 23:59:59 +0000 UTC) [checks: 1 IGNORED (SANs List), 1 FAILED (Expiration), 1 SUCCESSFUL (Hostname)]

**VALIDATION ERRORS**

* expiration validation failed: expired certificates found

**VALIDATION CHECKS REPORT**

3 certs retrieved for service running on expired.badssl.com (104.154.89.105) at port 443 using host value "expired.badssl.com"

PROBLEM RESULTS:

[!!] Expiration validation failed: leaf cert "*.badssl.com" expired 2629d 10h ago (on 2015-04-12 23:59:59 +0000 UTC)

Certificate 1 of 3 (leaf):
Name: CN=*.badssl.com,OU=Domain Control Validated+OU=PositiveSSL Wildcard
SANs entries: [*.badssl.com badssl.com]
Issuer: CN=COMODO RSA Domain Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
Serial: 4A:E7:95:49:FA:9A:BE:3F:10:0F:17:A4:78:E1:69:09
Issued On: 2015-04-09 00:00:00 +0000 UTC
Expiration: 2015-04-12 23:59:59 +0000 UTC
Status: [EXPIRED] 2629d 10h ago

Certificate 2 of 3 (intermediate):
Name: CN=COMODO RSA Domain Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
SANs entries: []
Issuer: CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
Serial: 2B:2E:6E:EA:D9:75:36:6C:14:8A:6E:DB:A3:7C:8C:07
Issued On: 2014-02-12 00:00:00 +0000 UTC
Expiration: 2029-02-11 23:59:59 +0000 UTC
Status: [OK] 2424d 13h remaining

Certificate 3 of 3 (intermediate):
Name: CN=COMODO RSA Certification Authority,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
SANs entries: []
Issuer: CN=AddTrust External CA Root,OU=AddTrust External TTP Network,O=AddTrust AB,C=SE
Serial: 27:66:EE:56:EB:49:F3:8E:AB:D7:70:A2:FC:84:DE:22
Issued On: 2000-05-30 10:48:38 +0000 UTC
Expiration: 2020-05-30 10:48:38 +0000 UTC
Status: [EXPIRED] 754d 23h ago

IGNORED RESULTS:

[--] SANs List validation ignored: 0 SANs entries specified, 2 SANs entries on leaf cert [0 EXPECTED, 0 MISSING, 0 UNEXPECTED]

SUCCESS RESULTS:

[OK] Hostname validation using value "expired.badssl.com" successful for leaf certificate

| 'time'=391ms;;;;
```

#### Explicitly applying validation check results

##### `expiration`

Here we use the `--apply-validation-result` flag with the `expiration` keyword
in order to *explicitly* apply expiration date validation results when
determining the final plugin state.

This doesn't have much of a direct effect because this validation check result
is applied by default, but it may be useful as a means of documenting a
specific service check command definition's intent.

```console
$ check_cert --server www.google.com --port 443 --age-critical 30 --age-warning 50 --apply-validation-result expiration
OK: Expiration validation successful: leaf cert "www.google.com" expires next with 63d 20h remaining (until 2022-08-29 09:39:59 +0000 UTC) [checks: 1 IGNORED (SANs List), 0 FAILED, 2 SUCCESSFUL (Hostname, Expiration)]

3 certs retrieved for service running on www.google.com (142.251.15.99) at port 443 using host value "www.google.com"

PROBLEM RESULTS:

* None

IGNORED RESULTS:

[--] SANs List validation ignored: 0 SANs entries specified, 1 SANs entries on leaf cert [0 EXPECTED, 0 MISSING, 0 UNEXPECTED]

SUCCESS RESULTS:

[OK] Expiration validation successful: leaf cert "www.google.com" expires next with 63d 20h remaining (until 2022-08-29 09:39:59 +0000 UTC)

Certificate 1 of 3 (leaf):
Name: CN=www.google.com
SANs entries: [www.google.com]
Issuer: CN=GTS CA 1C3,O=Google Trust Services LLC,C=US
Serial: 9F:07:4B:11:74:5F:16:FC:12:23:75:FA:58:79:93:F0
Issued On: 2022-06-06 09:40:00 +0000 UTC
Expiration: 2022-08-29 09:39:59 +0000 UTC
Status: [OK] 63d 20h remaining

Certificate 2 of 3 (intermediate):
Name: CN=GTS CA 1C3,O=Google Trust Services LLC,C=US
SANs entries: []
Issuer: CN=GTS Root R1,O=Google Trust Services LLC,C=US
Serial: 02:03:BC:53:59:6B:34:C7:18:F5:01:50:66
Issued On: 2020-08-13 00:00:42 +0000 UTC
Expiration: 2027-09-30 00:00:42 +0000 UTC
Status: [OK] 1921d 10h remaining

Certificate 3 of 3 (intermediate):
Name: CN=GTS Root R1,O=Google Trust Services LLC,C=US
SANs entries: []
Issuer: CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE
Serial: 77:BD:0D:6C:DB:36:F9:1A:EA:21:0F:C4:F0:58:D3:0D
Issued On: 2020-06-19 00:00:42 +0000 UTC
Expiration: 2028-01-28 00:00:42 +0000 UTC
Status: [OK] 2041d 10h remaining

[OK] Hostname validation using value "www.google.com" successful for leaf certificate

| 'time'=141ms;;;;
```

##### `hostname`

Here we use the `--apply-validation-result` flag with the `hostname` keyword
in order to *explicitly* apply hostname verification/validation results when
determining the final plugin state.

This doesn't have much of a direct effect because this validation check result
is applied by default, but it may be useful as a means of documenting a
specific service check command definition's intent.

```console
$ check_cert --server wrong.host.badssl.com --port 443 --apply-validation-result hostname
8:47AM ERR cmd/check_cert/main.go:413 > validation checks failed for certificate chain error="summary: 1 of 3 validation checks failed" age_critical=15 age_warning=30 app_type=plugin apply_expiration_validation_results=true apply_hostname_validation_results=true apply_sans_list_validation_results=false cert_check_timeout=10s checks_failed=1 checks_ignored=1 checks_successful=1 checks_total=3 expected_sans_entries= filename= logging_level=info port=443 server=wrong.host.badssl.com version="check-cert x.y.z (https://github.com/atc0005/check-cert)"
CRITICAL: Hostname validation using value "wrong.host.badssl.com" failed for leaf certificate [checks: 1 IGNORED (SANs List), 1 FAILED (Hostname), 1 SUCCESSFUL (Expiration)]

**VALIDATION ERRORS**

* hostname verification failed: x509: certificate is valid for *.badssl.com, badssl.com, not wrong.host.badssl.com

**VALIDATION CHECKS REPORT**

3 certs retrieved for service running on wrong.host.badssl.com (104.154.89.105) at port 443 using host value "wrong.host.badssl.com"

PROBLEM RESULTS:

[!!] Hostname validation using value "wrong.host.badssl.com" failed for leaf certificate

Consider updating the service check or command definition to specify the website FQDN instead of the host FQDN using the DNS Name or server flags. E.g., use 'www.example.org' instead of 'host7.example.com' in order to allow the remote server to select the correct certificate instead of using the default certificate.

IGNORED RESULTS:

[--] SANs List validation ignored: 0 SANs entries specified, 2 SANs entries on leaf cert [0 EXPECTED, 0 MISSING, 0 UNEXPECTED]

SUCCESS RESULTS:

[OK] Expiration validation successful: leaf cert "*.badssl.com" expires next with 50d 0h remaining (until 2022-08-15 14:07:55 +0000 UTC)

Certificate 1 of 3 (leaf):
Name: CN=*.badssl.com
SANs entries: [*.badssl.com badssl.com]
Issuer: CN=R3,O=Let's Encrypt,C=US
Serial: 04:B7:56:01:59:46:10:A8:D8:36:17:C8:06:C2:F9:8D:2A:46
Issued On: 2022-05-17 14:07:56 +0000 UTC
Expiration: 2022-08-15 14:07:55 +0000 UTC
Status: [OK] 50d 0h remaining

Certificate 2 of 3 (intermediate):
Name: CN=R3,O=Let's Encrypt,C=US
SANs entries: []
Issuer: CN=ISRG Root X1,O=Internet Security Research Group,C=US
Serial: 91:2B:08:4A:CF:0C:18:A7:53:F6:D6:2E:25:A7:5F:5A
Issued On: 2020-09-04 00:00:00 +0000 UTC
Expiration: 2025-09-15 16:00:00 +0000 UTC
Status: [OK] 1177d 2h remaining

Certificate 3 of 3 (intermediate):
Name: CN=ISRG Root X1,O=Internet Security Research Group,C=US
SANs entries: []
Issuer: CN=DST Root CA X3,O=Digital Signature Trust Co.
Serial: 40:01:77:21:37:D4:E9:42:B8:EE:76:AA:3C:64:0A:B7
Issued On: 2021-01-20 19:14:03 +0000 UTC
Expiration: 2024-09-30 18:14:03 +0000 UTC
Status: [OK] 827d 4h remaining

| 'time'=541ms;;;;
```

If you wish to connect using a server's FQDN value that isn't associated with
the certificate (e.g., testing a backend system with a unique FQDN), but wish
to use a specific DNS Name (aka, virtual host name) you can use the `dns-name`
flag to specify a valid hostname value for the leaf certificate.

```console
$ check_cert --server wrong.host.badssl.com --dns-name badssl.com --port 443
OK: Expiration validation successful: leaf cert "*.badssl.com" expires next with 50d 0h remaining (until 2022-08-15 14:07:55 +0000 UTC) [checks: 1 IGNORED (SANs List), 0 FAILED, 2 SUCCESSFUL (Hostname, Expiration)]

3 certs retrieved for service running on wrong.host.badssl.com (104.154.89.105) at port 443 using host value "badssl.com"

PROBLEM RESULTS:

* None

IGNORED RESULTS:

[--] SANs List validation ignored: 0 SANs entries specified, 2 SANs entries on leaf cert [0 EXPECTED, 0 MISSING, 0 UNEXPECTED]

SUCCESS RESULTS:

[OK] Expiration validation successful: leaf cert "*.badssl.com" expires next with 50d 0h remaining (until 2022-08-15 14:07:55 +0000 UTC)

Certificate 1 of 3 (leaf):
Name: CN=*.badssl.com
SANs entries: [*.badssl.com badssl.com]
Issuer: CN=R3,O=Let's Encrypt,C=US
Serial: 04:B7:56:01:59:46:10:A8:D8:36:17:C8:06:C2:F9:8D:2A:46
Issued On: 2022-05-17 14:07:56 +0000 UTC
Expiration: 2022-08-15 14:07:55 +0000 UTC
Status: [OK] 50d 0h remaining

Certificate 2 of 3 (intermediate):
Name: CN=R3,O=Let's Encrypt,C=US
SANs entries: []
Issuer: CN=ISRG Root X1,O=Internet Security Research Group,C=US
Serial: 91:2B:08:4A:CF:0C:18:A7:53:F6:D6:2E:25:A7:5F:5A
Issued On: 2020-09-04 00:00:00 +0000 UTC
Expiration: 2025-09-15 16:00:00 +0000 UTC
Status: [OK] 1177d 2h remaining

Certificate 3 of 3 (intermediate):
Name: CN=ISRG Root X1,O=Internet Security Research Group,C=US
SANs entries: []
Issuer: CN=DST Root CA X3,O=Digital Signature Trust Co.
Serial: 40:01:77:21:37:D4:E9:42:B8:EE:76:AA:3C:64:0A:B7
Issued On: 2021-01-20 19:14:03 +0000 UTC
Expiration: 2024-09-30 18:14:03 +0000 UTC
Status: [OK] 827d 4h remaining

[OK] Hostname validation using value "badssl.com" successful for leaf certificate
```

##### `sans`

Here we use the `--apply-validation-result` flag with the `sans` keyword
in order to *explicitly* apply hostname verification/validation results when
determining the final plugin state.

If you do not specify a list of SANs entries to validate, configuration
validation will cause the plugin to abort:

```console
$ check_cert --server wrong.host.badssl.com --dns-name badssl.com --port 443 --apply-validation-result sans
8:53AM ERR cmd/check_cert/main.go:59 > Error initializing application error="configuration validation failed: unsupported setting for certificate SANs list validation; providing SANs entries via the \"sans-entries\" flag is required when specifying the \"sans\" keyword via the \"apply-validation-result\" flag"
CRITICAL: Error initializing application

**VALIDATION ERRORS**

* configuration validation failed: unsupported setting for certificate SANs list validation; providing SANs entries via the "sans-entries" flag is required when specifying the "sans" keyword via the "apply-validation-result" flag
```

If providing a list of SANs entries to validate, this doesn't have much of a
direct effect because this validation check result is applied by default, but
it may be useful as a means of documenting a specific service check command
definition's intent.

```console
$ check_cert --server wrong.host.badssl.com --dns-name badssl.com --port 443 --apply-validation-result sans --sans-entries "*.badssl.com, badssl.com"
OK: Expiration validation successful: leaf cert "*.badssl.com" expires next with 50d 0h remaining (until 2022-08-15 14:07:55 +0000 UTC) [checks: 0 IGNORED, 0 FAILED, 3 SUCCESSFUL (Expiration, Hostname, SANs List)]

3 certs retrieved for service running on wrong.host.badssl.com (104.154.89.105) at port 443 using host value "badssl.com"

PROBLEM RESULTS:

* None

IGNORED RESULTS:

* None

SUCCESS RESULTS:

[OK] Expiration validation successful: leaf cert "*.badssl.com" expires next with 50d 0h remaining (until 2022-08-15 14:07:55 +0000 UTC)

Certificate 1 of 3 (leaf):
Name: CN=*.badssl.com
SANs entries: [*.badssl.com badssl.com]
Issuer: CN=R3,O=Let's Encrypt,C=US
Serial: 04:B7:56:01:59:46:10:A8:D8:36:17:C8:06:C2:F9:8D:2A:46
Issued On: 2022-05-17 14:07:56 +0000 UTC
Expiration: 2022-08-15 14:07:55 +0000 UTC
Status: [OK] 50d 0h remaining

Certificate 2 of 3 (intermediate):
Name: CN=R3,O=Let's Encrypt,C=US
SANs entries: []
Issuer: CN=ISRG Root X1,O=Internet Security Research Group,C=US
Serial: 91:2B:08:4A:CF:0C:18:A7:53:F6:D6:2E:25:A7:5F:5A
Issued On: 2020-09-04 00:00:00 +0000 UTC
Expiration: 2025-09-15 16:00:00 +0000 UTC
Status: [OK] 1177d 2h remaining

Certificate 3 of 3 (intermediate):
Name: CN=ISRG Root X1,O=Internet Security Research Group,C=US
SANs entries: []
Issuer: CN=DST Root CA X3,O=Digital Signature Trust Co.
Serial: 40:01:77:21:37:D4:E9:42:B8:EE:76:AA:3C:64:0A:B7
Issued On: 2021-01-20 19:14:03 +0000 UTC
Expiration: 2024-09-30 18:14:03 +0000 UTC
Status: [OK] 827d 4h remaining

[OK] Hostname validation using value "badssl.com" successful for leaf certificate

[OK] SANs List validation successful: expected and confirmed (2) SANs entries present for leaf certificate [2 EXPECTED, 0 MISSING, 0 UNEXPECTED]

| 'time'=384ms;;;;
```

If for example you fail to provide a SANs entry, the plugin will flag this as
a problem and reflect this in the final plugin state:

```console
$ check_cert --server wrong.host.badssl.com --dns-name badssl.com --port 443 --apply-validation-result sans --sans-entries "badssl.com"
8:56AM ERR cmd/check_cert/main.go:413 > validation checks failed for certificate chain error="summary: 1 of 3 validation checks failed" age_critical=15 age_warning=30 app_type=plugin apply_expiration_validation_results=true apply_hostname_validation_results=true apply_sans_list_validation_results=true cert_check_timeout=10s checks_failed=1 checks_ignored=0 checks_successful=2 checks_total=3 expected_sans_entries=badssl.com filename= logging_level=info port=443 server=wrong.host.badssl.com version="check-cert x.y.z (https://github.com/atc0005/check-cert)"
CRITICAL: SANs List validation failed: "leaf" certificate has unexpected SANs entries [checks: 0 IGNORED, 1 FAILED (SANs List), 2 SUCCESSFUL (Hostname, Expiration)]

**VALIDATION ERRORS**

* certificate has unexpected SANs entries

**VALIDATION CHECKS REPORT**

3 certs retrieved for service running on wrong.host.badssl.com (104.154.89.105) at port 443 using host value "badssl.com"

PROBLEM RESULTS:

[!!] SANs List validation failed: "leaf" certificate has unexpected SANs entries [1 EXPECTED, 0 MISSING, 1 UNEXPECTED]; missing: [N/A], unexpected: [*.badssl.com]

IGNORED RESULTS:

* None

SUCCESS RESULTS:

[OK] Expiration validation successful: leaf cert "*.badssl.com" expires next with 50d 0h remaining (until 2022-08-15 14:07:55 +0000 UTC)

Certificate 1 of 3 (leaf):
Name: CN=*.badssl.com
SANs entries: [*.badssl.com badssl.com]
Issuer: CN=R3,O=Let's Encrypt,C=US
Serial: 04:B7:56:01:59:46:10:A8:D8:36:17:C8:06:C2:F9:8D:2A:46
Issued On: 2022-05-17 14:07:56 +0000 UTC
Expiration: 2022-08-15 14:07:55 +0000 UTC
Status: [OK] 50d 0h remaining

Certificate 2 of 3 (intermediate):
Name: CN=R3,O=Let's Encrypt,C=US
SANs entries: []
Issuer: CN=ISRG Root X1,O=Internet Security Research Group,C=US
Serial: 91:2B:08:4A:CF:0C:18:A7:53:F6:D6:2E:25:A7:5F:5A
Issued On: 2020-09-04 00:00:00 +0000 UTC
Expiration: 2025-09-15 16:00:00 +0000 UTC
Status: [OK] 1177d 2h remaining

Certificate 3 of 3 (intermediate):
Name: CN=ISRG Root X1,O=Internet Security Research Group,C=US
SANs entries: []
Issuer: CN=DST Root CA X3,O=Digital Signature Trust Co.
Serial: 40:01:77:21:37:D4:E9:42:B8:EE:76:AA:3C:64:0A:B7
Issued On: 2021-01-20 19:14:03 +0000 UTC
Expiration: 2024-09-30 18:14:03 +0000 UTC
Status: [OK] 827d 4h remaining

[OK] Hostname validation using value "badssl.com" successful for leaf certificate
```

#### Explicitly ignoring validation check results

##### `expiration`

Here we use the `--ignore-validation-result` flag with the `expiration`
keyword in order to *explicitly* ignore expiration date validation results
when determining the final plugin state.

This could be useful for setting up a service check that focuses exclusively
on another validation criteria such as hostname or SANs list entries; instead
of having a comprehensive "check everything" certificate check, this could
allow a sysadmin to check criteria separately.

```console
$ check_cert --server expired.badssl.com --port 443 --ignore-validation-result expiration
OK: Hostname validation using value "expired.badssl.com" successful for leaf certificate [checks: 2 IGNORED (SANs List, Expiration), 0 FAILED, 1 SUCCESSFUL (Hostname)]

3 certs retrieve