// This file is part of the happyDomain (R) project. // Copyright (c) 2020-2026 happyDomain // Authors: Pierre-Olivier Mercier, et al. // // This program is offered under a commercial and under the AGPL license. // For commercial licensing, contact us at . // // For AGPL licensing: // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package checker import ( "context" sdk "git.happydns.org/checker-sdk-go/checker" ) // sshfpAlignmentRule compares the published SSHFP records with the // observed host keys (match, missing, mismatch, uncovered family). type sshfpAlignmentRule struct{} func (r *sshfpAlignmentRule) Name() string { return "ssh.sshfp_alignment" } func (r *sshfpAlignmentRule) Description() string { return "Compares published SSHFP records against the observed host keys (match, missing, mismatch)." } func (r *sshfpAlignmentRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState { data, errSt := loadSSHData(ctx, obs) if errSt != nil { return []sdk.CheckState{*errSt} } var issues []Issue sawKey := false for _, ep := range data.Endpoints { if len(ep.HostKeys) == 0 { continue } sawKey = true issues = append(issues, analyseSSHFPAlignment(ep.Address, ep.HostKeys, data.SSHFP)...) } if !sawKey { return []sdk.CheckState{notTestedState("ssh.sshfp_alignment.skipped", "No host key observed; SSHFP alignment cannot be assessed.")} } if len(issues) == 0 { return []sdk.CheckState{passState("ssh.sshfp_alignment.ok", "Published SSHFP records match the observed host keys.")} } return statesFromIssues(issues) } // sshfpHashRule flags an SSHFP set that uses only the deprecated SHA-1 // (type 1) hash variant. type sshfpHashRule struct{} func (r *sshfpHashRule) Name() string { return "ssh.sshfp_hash" } func (r *sshfpHashRule) Description() string { return "Flags SSHFP record sets that only publish SHA-1 (type 1) fingerprints instead of SHA-256." } func (r *sshfpHashRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, _ sdk.CheckerOptions) []sdk.CheckState { data, errSt := loadSSHData(ctx, obs) if errSt != nil { return []sdk.CheckState{*errSt} } if !data.SSHFP.Present { return []sdk.CheckState{notTestedState("ssh.sshfp_hash.skipped", "No SSHFP records published.")} } var issues []Issue for _, ep := range data.Endpoints { issues = append(issues, analyseSSHFPHashes(ep.Address, ep.HostKeys, data.SSHFP)...) } if len(issues) == 0 { return []sdk.CheckState{passState("ssh.sshfp_hash.ok", "SSHFP records include a SHA-256 (type 2) fingerprint.")} } return statesFromIssues(issues) }