diff --git a/README.md b/README.md index 5b35327..0cd10f6 100644 --- a/README.md +++ b/README.md @@ -105,26 +105,6 @@ make plugin | `probeTimeoutMs` | number | `10000` | Per-endpoint dial + handshake timeout. | | `includeAuthProbe` | bool | `true` | Open a second connection to enumerate auth methods. | -## Rules - -| Code | Description | Severity | -|-------------------------------|---------------------------------------------------------------------------------------------------|---------------------| -| `ssh.tcp_reachable` | Verifies that every probed (address, port) pair accepts a TCP connection. | CRITICAL | -| `ssh.handshake` | Verifies that the SSH banner exchange and KEXINIT parse succeed on every reachable endpoint. | CRITICAL | -| `ssh.protocol_version` | Verifies every endpoint advertises SSH-2 and rejects the legacy SSH-1 protocol. | CRITICAL | -| `ssh.banner_software` | Flags servers whose banner is not a recognised OpenSSH build. | INFO | -| `ssh.known_vulnerabilities` | Matches the advertised OpenSSH version against a curated catalog of remotely-observable CVEs. | CRITICAL | -| `ssh.host_key_strength` | Flags SSH host keys whose size is below the currently accepted minimum (e.g. RSA < 2048 bits). | CRITICAL | -| `ssh.kex_algorithms` | Flags key-exchange algorithms advertised by the server that are weak or broken. | CRITICAL | -| `ssh.host_key_algorithms` | Flags server host-key algorithms that are weak or deprecated (ssh-rsa/SHA-1, ssh-dss, ...). | CRITICAL | -| `ssh.cipher_algorithms` | Flags symmetric ciphers advertised by the server that are weak or broken (CBC, 3DES, RC4, ...). | CRITICAL | -| `ssh.mac_algorithms` | Flags MAC algorithms advertised by the server that are weak (SHA-1, non-ETM, ...). | CRITICAL | -| `ssh.strict_kex` | Verifies the server advertises the strict-KEX marker (CVE-2023-48795 Terrapin mitigation). | WARNING | -| `ssh.preauth_compression` | Flags servers that offer pre-authentication zlib compression (prefer zlib@openssh.com). | INFO | -| `ssh.auth_methods` | Reviews the advertised authentication methods (password exposure, public-key availability). | WARNING | -| `ssh.sshfp_alignment` | Compares published SSHFP records against the observed host keys (match, missing, mismatch). | CRITICAL | -| `ssh.sshfp_hash` | Flags SSHFP record sets that only publish SHA-1 (type 1) fingerprints instead of SHA-256. | WARNING | - ## Observation key Writes a single observation under `ssh`: diff --git a/checker/collect.go b/checker/collect.go index 9a45d95..f383b02 100644 --- a/checker/collect.go +++ b/checker/collect.go @@ -62,22 +62,7 @@ func (p *sshProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any ports = append([]uint16{DefaultSSHPort}, ports...) } - // Origin is the FQDN where the service is mounted: svc.Domain holds the - // subdomain (relative to apex; "@" for apex), and the domain_name - // autofill carries the zone apex. - apex := "" - if v, ok := sdk.GetOption[string](opts, OptionDomainName); ok { - apex = strings.TrimSuffix(v, ".") - } - subdomain := "" - if svc, ok := sdk.GetOption[happydns.ServiceMessage](opts, OptionService); ok { - subdomain = strings.TrimSuffix(svc.Domain, ".") - } - origin := sdk.JoinRelative(subdomain, apex) - host, ips := addressesFromServer(server, origin) - if host == "" { - host = origin - } + host, ips := addressesFromServer(server) if len(ips) == 0 { return nil, fmt.Errorf("abstract.Server service has no A/AAAA records") } @@ -139,16 +124,17 @@ func resolveServer(opts sdk.CheckerOptions) (*abstract.Server, error) { // addressesFromServer returns the service's owner domain name (used // for SNI-like purposes in SSH banner/hostname exchange) and the list // of IPs to probe. -func addressesFromServer(server *abstract.Server, origin string) (host string, ips []string) { - // happyDomain encodes service-embedded record owners relative to the - // parent zone, so we must join with origin before treating as FQDN. +func addressesFromServer(server *abstract.Server) (host string, ips []string) { + // We can't know the service's owner domain from the Server payload + // alone. The host value we use here is purely informational for + // the report; the ssh handshake itself doesn't need it. if server.A != nil && len(server.A.A) > 0 { - host = sdk.JoinRelative(server.A.Hdr.Name, origin) + host = strings.TrimSuffix(server.A.Hdr.Name, ".") ips = append(ips, server.A.A.String()) } if server.AAAA != nil && len(server.AAAA.AAAA) > 0 { if host == "" { - host = sdk.JoinRelative(server.AAAA.Hdr.Name, origin) + host = strings.TrimSuffix(server.AAAA.Hdr.Name, ".") } ips = append(ips, server.AAAA.AAAA.String()) } diff --git a/checker/definition.go b/checker/definition.go index 7d26191..3cc7548 100644 --- a/checker/definition.go +++ b/checker/definition.go @@ -75,12 +75,6 @@ func (p *sshProvider) Definition() *sdk.CheckerDefinition { AutoFill: sdk.AutoFillService, Hide: true, }, - { - Id: OptionDomainName, - Label: "Parent domain name", - AutoFill: sdk.AutoFillDomainName, - Hide: true, - }, }, }, Rules: Rules(), diff --git a/checker/types.go b/checker/types.go index a927c6e..25661f2 100644 --- a/checker/types.go +++ b/checker/types.go @@ -35,7 +35,6 @@ const ObservationKeySSH = "ssh" // Option ids on CheckerOptions. const ( OptionService = "service" - OptionDomainName = "domain_name" OptionPorts = "ports" OptionProbeTimeoutMs = "probeTimeoutMs" OptionIncludeAuthProbe = "includeAuthProbe"