epack install collector tls
Adds to epack.yaml, resolves dependencies, downloads binary.
Run all configured collectors and build a pack:
epack collect
Runs all collectors in epack.yaml and outputs an evidence pack.
Or add manually to epack.yaml:
collectors:
tls:
source: https://github.com/locktivity/epack-collector-tls
Then run epack install to lock and sync.
The TLS Posture Collector checks configured public endpoints for certificate health and protocol support.
This collector connects to each configured endpoint and records transport security posture:
The collector only connects to endpoints you explicitly configure. There is no discovery.
Certificate verification happens after the handshake, so invalid, expired, or mismatched certificates are reported as evidence rather than causing collection to fail.
Probes offer every cipher suite Go implements, including legacy suites excluded from modern client defaults, so servers that only speak old cipher suites are still detected. A false protocol support value means no handshake succeeded at that version with any implemented suite.
Summary percentages use all configured endpoints as the denominator. An unreachable endpoint counts against certificate_valid_pct and modern_protocol_pct, since its posture could not be demonstrated.
This collector does not require authentication - it only performs TLS handshakes against public endpoints.
See Configuration for setup instructions.
The TLS collector connects to public endpoints and does not require authentication.
collectors:
tls:
source: locktivity/epack-collector-tls@^0.1
config:
endpoints:
- app.example.com
- api.example.com:8443
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
endpoints |
array | Yes | - | Endpoints to scan, as strings or objects |
Each endpoint can be a string or an object.
String form:
endpoints:
- app.example.com # port defaults to 443
- api.example.com:8443 # explicit port
- https://login.example.com # scheme prefix is stripped
- "[2001:db8::1]:8443" # IPv6 addresses use brackets with a port
Object form:
endpoints:
- host: app.example.com
port: 443
- host: api.example.com # port defaults to 443
Hosts must be DNS names or IP addresses. DNS names are normalized to lowercase and must use valid DNS labels. port must be between 1 and 65535.
The runner needs outbound TCP access to every configured endpoint. Each endpoint is checked with several short handshakes (one per TLS version not already established), each bounded by a 10 second timeout.
"endpoints is required"
The endpoints field must be set in your config:
config:
endpoints:
- app.example.com # Required - at least one endpoint
"TLS handshake failed"
If an endpoint reports a handshake failure:
- Verify the host and port are correct and the service is running
- Check that the runner has outbound network access to the endpoint
- Confirm the endpoint actually speaks TLS on that port (not plain HTTP)
A failed modern handshake still triggers the per-version probes, so a TLS 1.0-only server shows up as reachable at TLS 1.0 with legacy_protocol_supported: true.
Protocol support shows false unexpectedly
Probes offer every cipher suite Go implements, including legacy suites, but a server requiring a cipher suite Go does not implement will still probe as unsupported. A false means no handshake succeeded at that version, not proof the version is disabled.
Percentages look low
Summary percentages count all configured endpoints. An unreachable endpoint counts against certificate_valid_pct and modern_protocol_pct because its posture could not be demonstrated. Check diagnostics.endpoint_errors for the failing endpoints.
stream: myorg/tls-posture
collectors:
tls:
source: locktivity/epack-collector-tls@^0.1
config:
endpoints:
- app.example.com
Then run:
epack collect
stream: myorg/tls-posture
collectors:
tls:
source: locktivity/epack-collector-tls@^0.1
config:
endpoints:
- app.example.com # Primary web app
- api.example.com:8443 # API on a custom port
- login.example.com # SSO entry point
- status.example.com # Status page
EPACK_COLLECTOR_CONFIG takes the path to a JSON config file:
echo '{"endpoints":[{"host":"app.example.com","port":443}]}' > config.json
EPACK_COLLECTOR_CONFIG=config.json epack sdk run ./epack-collector-tls
{
"protocol_version": 1,
"data": {
"schema_version": "1.0.0",
"collected_at": "2026-07-12T10:30:00Z",
"endpoints": [
{
"host": "app.example.com",
"port": 443,
"reachable": true,
"tls": {
"negotiated_version": "TLS 1.3",
"negotiated_cipher_suite": "TLS_AES_128_GCM_SHA256",
"protocol_support": {
"tls_1_0": false,
"tls_1_1": false,
"tls_1_2": true,
"tls_1_3": true
}
},
"certificate": {
"subject_common_name": "app.example.com",
"issuer_common_name": "E5",
"serial_number": "301856080525954483946262561144304836989",
"not_before": "2026-06-01T00:00:00Z",
"not_after": "2026-09-28T23:59:59Z",
"expires_in_days": 78,
"dns_names": ["app.example.com"],
"fingerprint_sha256": "9F2A417C6B8E0D3F5A1B2C4D6E8F0A1B3C5D7E9F1A3B5C7D9E1F3A5B7C9D0E2F",
"signature_algorithm": "ECDSA-SHA384",
"public_key_algorithm": "ECDSA",
"chain_length": 2,
"trusted_chain": true,
"hostname_valid": true
},
"assessment": {
"certificate_valid": true,
"hostname_valid": true,
"expired": false,
"expiring_soon": false,
"modern_protocol": true,
"legacy_protocol_supported": false
}
},
{
"host": "api.example.com",
"port": 8443,
"reachable": true,
"tls": {
"negotiated_version": "TLS 1.2",
"negotiated_cipher_suite": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"protocol_support": {
"tls_1_0": false,
"tls_1_1": false,
"tls_1_2": true,
"tls_1_3": false
}
},
"certificate": {
"subject_common_name": "api.example.com",
"issuer_common_name": "R11",
"serial_number": "274377854701857781766707579337729355821",
"not_before": "2026-05-04T00:00:00Z",
"not_after": "2026-08-02T23:59:59Z",
"expires_in_days": 21,
"dns_names": ["api.example.com"],
"fingerprint_sha256": "1D4E7A0C3F6B9E2D5A8C1F4B7E0A3D6C9F2B5E8A1D4C7F0B3E6A9D2C5F8B1E4A",
"signature_algorithm": "SHA256-RSA",
"public_key_algorithm": "RSA",
"chain_length": 2,
"trusted_chain": true,
"hostname_valid": true
},
"assessment": {
"certificate_valid": true,
"hostname_valid": true,
"expired": false,
"expiring_soon": true,
"modern_protocol": true,
"legacy_protocol_supported": false,
"issues": [
"Certificate expires within 30 days"
]
}
}
],
"summary": {
"total_endpoints": 2,
"reachable_pct": 100,
"certificate_valid_pct": 100,
"hostname_valid_pct": 100,
"modern_protocol_pct": 100,
"expiring_soon_pct": 50,
"legacy_protocol_supported_pct": 0,
"all_certificates_valid": true,
"all_modern_protocol": true
}
}
}
Each endpoint includes an assessment object:
| Field | Description |
|---|---|
certificate_valid |
true if the chain is trusted, the hostname matches, and the certificate is within its validity window |
hostname_valid |
true if the certificate is valid for the configured host |
expired |
true if the certificate is past its not_after date |
expiring_soon |
true if the certificate expires within 30 days |
modern_protocol |
true if TLS 1.2 or TLS 1.3 is supported |
legacy_protocol_supported |
true if TLS 1.0 or TLS 1.1 is accepted |
issues |
List of problems found |
All percentages use configured endpoints as the denominator, so an unreachable endpoint counts against certificate and protocol percentages.
| Field | Description |
|---|---|
total_endpoints |
Number of endpoints scanned |
reachable_pct |
Percentage that completed a modern TLS handshake |
certificate_valid_pct |
Percentage with trusted, hostname-valid, unexpired certificates |
hostname_valid_pct |
Percentage whose certificate matches the configured host |
modern_protocol_pct |
Percentage supporting TLS 1.2 or TLS 1.3 |
expiring_soon_pct |
Percentage with certificates expiring within 30 days |
legacy_protocol_supported_pct |
Percentage accepting TLS 1.0 or TLS 1.1 |
all_certificates_valid |
true only if ALL endpoints have valid certificates |
all_modern_protocol |
true only if ALL endpoints support modern TLS |
When endpoints fail to respond, a diagnostics block records one entry per failure:
"diagnostics": {
"endpoint_errors": [
"old.example.com:443: TLS handshake failed: dial tcp: connection refused"
]
}
For a clean TLS posture:
- all_certificates_valid should be true
- all_modern_protocol should be true
- legacy_protocol_supported_pct should be 0
- expiring_soon_pct should be 0
name: Collect Evidence
on:
schedule:
- cron: "0 0 * * 1" # Weekly on Monday
workflow_dispatch:
jobs:
collect:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install epack
run: |
curl -sSL https://github.com/locktivity/epack/releases/latest/download/epack-linux-amd64 -o epack
chmod +x epack
sudo mv epack /usr/local/bin/
- name: Collect evidence
run: epack collect --frozen
- name: Upload pack
uses: actions/upload-artifact@v4
with:
name: evidence-pack
path: "*.pack"
**Full Changelog**: https://github.com/locktivity/epack-collector-tls/commits/v0.1.0