tls

v0.1.0 collector

Collects public TLS certificate and protocol posture

Install

epack install collector tls

Adds to epack.yaml, resolves dependencies, downloads binary.

Usage

Run all configured collectors and build a pack:

epack collect

Runs all collectors in epack.yaml and outputs an evidence pack.

Configuration

Or add manually to epack.yaml:

collectors:
  tls:
    source: https://github.com/locktivity/epack-collector-tls

Then run epack install to lock and sync.

TLS Posture Collector

The TLS Posture Collector checks configured public endpoints for certificate health and protocol support.

What It Collects

This collector connects to each configured endpoint and records transport security posture:

  • Reachability: Whether the endpoint completes a TLS 1.2+ handshake
  • Negotiated TLS: The protocol version and cipher suite chosen by the server
  • Protocol Support: Individual probes for TLS 1.0, 1.1, 1.2, and 1.3
  • Certificate: Leaf certificate details including:
    • Subject and issuer common names
    • Validity window and days until expiry
    • DNS and IP subject alternative names
    • SHA256 fingerprint
    • Chain trust against system roots
    • Hostname match for the configured host
  • Assessment: Per-endpoint evaluation (valid certificate, modern protocol, legacy protocol, issues)

What It Does Not Collect

  • HTTP response bodies, cookies, or authenticated content
  • Subdomains discovered from DNS, certificate transparency, or search
  • Private keys, secrets, or credentials
  • STARTTLS posture for SMTP, IMAP, LDAP, or database protocols

The collector only connects to endpoints you explicitly configure. There is no discovery.

Use Cases

  • Third-party risk assessments: Provide evidence of transport security posture to customers and auditors
  • Compliance monitoring: Verify public endpoints use trusted certificates and modern TLS
  • Expiry tracking: Catch certificates entering the 30-day renewal window
  • Legacy protocol cleanup: Find endpoints still accepting TLS 1.0 or 1.1

How It Works

  1. The collector receives a list of endpoints from your configuration
  2. For each endpoint, it performs a TLS 1.2+ handshake and records the negotiated version, cipher suite, and certificate chain
  3. It probes each remaining TLS version individually to build the protocol support matrix
  4. The certificate chain is verified against system roots and the configured hostname
  5. Per-endpoint assessment is computed (certificate validity, protocol posture, issues)
  6. Summary statistics are aggregated across all configured endpoints
  7. Output is wrapped in the epack collector protocol envelope

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.

Authentication

This collector does not require authentication - it only performs TLS handshakes against public endpoints.

See Configuration for setup instructions.

Configuration

Basic Setup

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

Configuration Options

Field Type Required Default Description
endpoints array Yes - Endpoints to scan, as strings or objects

Endpoint Formats

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.

Network Requirements

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.

Troubleshooting

"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.

Examples

Basic Usage

Single Endpoint

stream: myorg/tls-posture

collectors:
  tls:
    source: locktivity/epack-collector-tls@^0.1
    config:
      endpoints:
        - app.example.com

Then run:

epack collect

Multiple Endpoints and Ports

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

Running Locally Through the SDK

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

Sample Output

{
  "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
    }
  }
}

Understanding the Output

Assessment

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

Summary Metrics

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

Diagnostics

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"
  ]
}

Pass Criteria

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

CI/CD Integration

GitHub Actions

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"
v0.1.0 Latest
2026-07-12

**Full Changelog**: https://github.com/locktivity/epack-collector-tls/commits/v0.1.0

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64

Details

Publisher
locktivity
Latest
v0.1.0
Protocol
v1

Platforms

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64

Links