> ## Documentation Index
> Fetch the complete documentation index at: https://docs.certgovernance.app/llms.txt
> Use this file to discover all available pages before exploring further.

# certforge-discovery

> Open-source certificate discovery agent. Finds TLS certificates across CT logs, live endpoints, filesystems, and Kubernetes — and reports them to CertForge for governance.

`certforge-discovery` is an open-source agent that scans your infrastructure for TLS certificates and surfaces them in CertForge. It finds certificates you didn't know you had — shadow certs from acquisitions, forgotten wildcards, certs issued by shadow IT — before they expire undetected.

GitHub: [CertForge-LLC/certforge-discovery](https://github.com/CertForge-LLC/certforge-discovery)

## What it scans

| Source               | What it finds                                                         |
| -------------------- | --------------------------------------------------------------------- |
| **CT log**           | All certificates ever issued for your domains (via crt.sh)            |
| **TLS scan**         | Certificates live on network endpoints — hostnames, IPs, CIDR ranges  |
| **Local filesystem** | Cert files on the host (`/etc/ssl`, nginx, apache, letsencrypt paths) |
| **Kubernetes**       | All `kubernetes.io/tls` secrets across every namespace                |

## Installation

Download the binary for your platform from the [latest release](https://github.com/CertForge-LLC/certforge-discovery/releases/latest):

```bash theme={null}
# Linux (AMD64)
curl -Lo certforge-discovery https://github.com/CertForge-LLC/certforge-discovery/releases/latest/download/certforge-discovery-linux-amd64
chmod +x certforge-discovery
sudo mv certforge-discovery /usr/local/bin/
```

Verify the download:

```bash theme={null}
curl -Lo checksums.txt https://github.com/CertForge-LLC/certforge-discovery/releases/latest/download/checksums.txt
sha256sum -c checksums.txt --ignore-missing
```

Or build from source (requires Go 1.22+):

```bash theme={null}
go install github.com/certforge-llc/certforge-discovery/cmd/certforge-discovery@latest
```

## Quick start — no account required

Run a scan locally without a CertForge account. Results print to stdout or write to a file:

```bash theme={null}
# CT log scan for a domain
certforge-discovery scan -domain example.com

# CT log + live TLS scan of a network range → CSV
certforge-discovery scan -domain example.com -target 10.0.1.0/24 -out certs.csv

# Local filesystem scan → JSON
certforge-discovery scan -local -out certs.json

# All sources → JSON
certforge-discovery scan -domain example.com -target 10.0.1.0/24 -local -k8s -out certs.json
```

Use `-dry-run` with any scan to print the exact JSON payload that *would* be sent to CertForge — without posting anything:

```bash theme={null}
certforge-discovery scan -domain example.com -dry-run | jq .
```

## Connect to CertForge

Run `setup` to pick your data region and connect the agent to your CertForge account:

```bash theme={null}
certforge-discovery setup
```

This writes a config file to `~/.certforge-discovery/config.yaml`. Then run a scan:

```bash theme={null}
certforge-discovery scan
```

Discovered certificates appear in CertForge under **Discovery** with `source: ct_log`, `tls_scan`, `local`, or `k8s`. They are automatically evaluated against your Domain Trust Profiles and flagged for policy mismatches.

## Continuous agent mode

Run the agent continuously so CertForge always has a current picture of your cert inventory:

```bash theme={null}
certforge-discovery agent -domain example.com
```

The agent re-scans on the `poll_interval` set in its config (default 6 hours).

## Configuration

Setup writes `~/.certforge-discovery/config.yaml`:

```yaml theme={null}
certforge_url: https://app.certgovernance.app  # your region's URL
api_key: cf_...                                 # from CertForge Settings → API Keys
poll_interval: 6h

# Optional
scan_local: false        # scan local filesystem for cert files
scan_k8s: false          # scan Kubernetes TLS secrets
kubeconfig: ""           # path to kubeconfig; empty = in-cluster config
storage_paths:           # additional filesystem paths to scan
  - /opt/app/certs

# Optional: identify certs signed by your private CA(s)
# When configured, discovered certs are tagged issuer_type=internal_ca in CertForge
# so your governance team can distinguish internal issuance from shadow IT or public CAs.
known_internal_cas:
  - cert: /etc/ssl/private/corp-ca.crt   # PEM CA certificate (same file as certforge-connector private_ca.cert)
    label: "Corp Internal CA"
```

**EU West (GDPR):**

```yaml theme={null}
certforge_url: https://eu.certgovernance.app
api_key: cf_...
poll_interval: 6h
```

### Private CA awareness

If your environment uses `certforge-connector` with a `private_ca:` configured, point `known_internal_cas` at the same CA cert file. Discovery will then:

1. Cryptographically verify (TLS scan, local filesystem, Kubernetes) or name-match (CT log) each discovered cert against the CA
2. Tag matching certs as `issuer_type: internal_ca` in CertForge
3. Show them distinctly in the Discovery view so your team can govern internal issuance separately from public CA certs

This is a read-only classification — discovery never contacts the CA or generates certificates.

## Scan flags

| Flag                  | Default    | Description                                     |
| --------------------- | ---------- | ----------------------------------------------- |
| `-domain <domain>`    | —          | Domain to query in CT logs (crt.sh)             |
| `-target <host/cidr>` | —          | Hostname, IP, or CIDR range for live TLS scan   |
| `-ports <ports>`      | `443,8443` | Ports for TLS scan                              |
| `-local`              | false      | Scan local filesystem for cert files            |
| `-k8s`                | false      | Scan Kubernetes TLS secrets                     |
| `-out <file>`         | stdout     | Write to file (`.csv` or `.json`)               |
| `-dry-run`            | false      | Print JSON payload without posting to CertForge |

## Running as a service

**systemd:**

```ini theme={null}
[Unit]
Description=CertForge Discovery Agent
After=network.target

[Service]
ExecStart=/usr/local/bin/certforge-discovery agent -domain example.com
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target
```

```bash theme={null}
sudo systemctl enable --now certforge-discovery
```

**Kubernetes:**

```yaml theme={null}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: certforge-discovery
  namespace: certforge-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: certforge-discovery
  template:
    metadata:
      labels:
        app: certforge-discovery
    spec:
      serviceAccountName: certforge-discovery
      containers:
        - name: agent
          image: ghcr.io/certforge-llc/certforge-discovery:latest
          env:
            - name: CERTFORGE_URL
              value: https://app.certgovernance.app
            - name: API_KEY
              valueFrom:
                secretKeyRef:
                  name: certforge-discovery-credentials
                  key: api_key
```

Grant the service account `secrets: [get, list]` cluster-wide so the agent can read `kubernetes.io/tls` secrets across all namespaces.

## What is sent to CertForge

The agent posts **certificate metadata only** — no private keys, no plaintext traffic, no filesystem contents beyond recognized cert formats.

| Field                      | Example                              |
| -------------------------- | ------------------------------------ |
| `fingerprint`              | `sha256:3f2a...`                     |
| `serial`                   | `0x1A2B3C...`                        |
| `issuer`                   | `C=US, O=Let's Encrypt, CN=R11`      |
| `subject`                  | `example.com`                        |
| `sans`                     | `example.com, www.example.com`       |
| `not_before` / `not_after` | RFC 3339 timestamps                  |
| `source`                   | `ct_log`, `tls_scan`, `local`, `k8s` |
| `source_detail`            | `192.168.1.50:443`                   |
| `seen_deployed`            | `true` — live TLS confirmed          |
| `eku`                      | `["TLS Web Server Authentication"]`  |

For Kubernetes secrets: only the `tls.crt` field is read — `tls.key` is never accessed or transmitted.

## Corporate proxy

```bash theme={null}
export HTTPS_PROXY=http://proxy.corp.com:8080
export NO_PROXY=localhost,127.0.0.1,10.0.0.0/8
certforge-discovery scan -domain example.com
```

Live TLS scanning (`-target`) makes direct TCP connections and is not proxied — run the agent on a host with direct access to the targets.

## What happens after discovery

Discovered certs land in CertForge with `governance_status = untracked`. Your team can:

* **Track** — acknowledge the cert; CertForge monitors it for expiry and includes it in compliance reports
* **Dismiss** — mark as a known false positive; excluded from open issues
* Leave as untracked; it contributes to the Ungoverned KPI on the Dashboard

See [Certificate Discovery](/concepts/discovery) for the full governance workflow.
