chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
package kmsrotationconformance
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
CaptureSchema = "kms-rotation-capture-v1"
|
||||
ReportSchema = "kms-rotation-conformance-report-v1"
|
||||
)
|
||||
|
||||
var digestPattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
|
||||
|
||||
type Capture struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
EvidenceSource string `json:"evidence_source"`
|
||||
CandidateVersion string `json:"candidate_version"`
|
||||
BuildDigest string `json:"build_digest"`
|
||||
Environment string `json:"environment"`
|
||||
ProviderRef string `json:"provider_ref"`
|
||||
WorkloadIdentityRef string `json:"workload_identity_ref"`
|
||||
ProviderAuditRef string `json:"provider_audit_ref"`
|
||||
AttestedByRef string `json:"attested_by_ref"`
|
||||
AttestedAt time.Time `json:"attested_at"`
|
||||
CollectedAt time.Time `json:"collected_at"`
|
||||
Rotations []RotationEvidence `json:"rotations"`
|
||||
}
|
||||
|
||||
type RotationEvidence struct {
|
||||
SecretRef string `json:"secret_ref"`
|
||||
ConsumerRef string `json:"consumer_ref"`
|
||||
ConsumptionMode string `json:"consumption_mode"`
|
||||
OldVersionRef string `json:"old_version_ref"`
|
||||
NewVersionRef string `json:"new_version_ref"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
CompletedAt time.Time `json:"completed_at"`
|
||||
NewCredentialVerified bool `json:"new_credential_verified"`
|
||||
OldCredentialRejected bool `json:"old_credential_rejected"`
|
||||
BusinessContinuityVerified bool `json:"business_continuity_verified"`
|
||||
SecretValueNotExported bool `json:"secret_value_not_exported"`
|
||||
PreRotationProbeRef string `json:"pre_rotation_probe_ref"`
|
||||
NewCredentialProbeRef string `json:"new_credential_probe_ref"`
|
||||
OldRejectionProbeRef string `json:"old_rejection_probe_ref"`
|
||||
ContinuityProbeRef string `json:"continuity_probe_ref"`
|
||||
AccessAuditRef string `json:"access_audit_ref"`
|
||||
}
|
||||
|
||||
type Check struct {
|
||||
Code string `json:"code"`
|
||||
Status string `json:"status"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
type Report struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Result string `json:"result"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
CandidateVersion string `json:"candidate_version"`
|
||||
BuildDigest string `json:"build_digest"`
|
||||
EvidenceScope string `json:"evidence_scope"`
|
||||
ExternalValidationRequired bool `json:"external_validation_required"`
|
||||
Environment string `json:"environment"`
|
||||
ProviderRef string `json:"provider_ref"`
|
||||
RotationCount int `json:"rotation_count"`
|
||||
ConsumptionModes []string `json:"consumption_modes"`
|
||||
InputSHA256 map[string]string `json:"input_sha256"`
|
||||
Checks []Check `json:"checks"`
|
||||
Disclaimer string `json:"disclaimer"`
|
||||
}
|
||||
|
||||
func ParseCapture(content []byte) (Capture, error) {
|
||||
var capture Capture
|
||||
if err := decodeStrict(content, &capture); err != nil {
|
||||
return Capture{}, fmt.Errorf("decode KMS rotation capture: %w", err)
|
||||
}
|
||||
return capture, nil
|
||||
}
|
||||
|
||||
func ParseReport(content []byte) (Report, error) {
|
||||
var report Report
|
||||
if err := decodeStrict(content, &report); err != nil {
|
||||
return Report{}, fmt.Errorf("decode KMS rotation report: %w", err)
|
||||
}
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func Evaluate(capture Capture, candidateVersion, buildDigest string, generatedAt time.Time) Report {
|
||||
candidateVersion = strings.TrimSpace(candidateVersion)
|
||||
buildDigest = strings.ToLower(strings.TrimSpace(buildDigest))
|
||||
report := Report{
|
||||
SchemaVersion: ReportSchema, Result: "PASS", GeneratedAt: generatedAt.UTC(), CandidateVersion: candidateVersion,
|
||||
BuildDigest: buildDigest, EvidenceScope: "LOCAL_SECRET", ExternalValidationRequired: true,
|
||||
Environment: capture.Environment, ProviderRef: capture.ProviderRef, RotationCount: len(capture.Rotations),
|
||||
ConsumptionModes: []string{}, InputSHA256: map[string]string{}, Checks: []Check{},
|
||||
Disclaimer: "轮换一致性报告只证明所提供的身份、版本、认证探针、业务连续性和审计引用相互一致;不复制密钥正文,也不替代生产发布审批。",
|
||||
}
|
||||
if capture.EvidenceSource == "KMS_HSM" {
|
||||
report.EvidenceScope = "REAL_KMS_HSM"
|
||||
}
|
||||
add := func(code string, ok bool, detail string) {
|
||||
status := "PASS"
|
||||
if !ok {
|
||||
status, report.Result = "FAIL", "FAIL"
|
||||
}
|
||||
report.Checks = append(report.Checks, Check{Code: code, Status: status, Detail: detail})
|
||||
}
|
||||
add("CAPTURE_SCHEMA", capture.SchemaVersion == CaptureSchema, "轮换证据必须使用"+CaptureSchema)
|
||||
add("EVIDENCE_SOURCE", capture.EvidenceSource == "LOCAL" || capture.EvidenceSource == "KMS_HSM", "证据来源必须明确为LOCAL或KMS_HSM")
|
||||
add("CANDIDATE_IDENTITY", candidateVersion != "" && digestPattern.MatchString(buildDigest), "候选版本和sha256构建摘要必须完整")
|
||||
add("CAPTURE_CANDIDATE_BINDING", capture.CandidateVersion == candidateVersion && strings.ToLower(capture.BuildDigest) == buildDigest, "轮换采集必须绑定同一候选版本和构建摘要")
|
||||
|
||||
real := capture.EvidenceSource == "KMS_HSM"
|
||||
providerOK := capture.ProviderRef != "" && capture.AttestedByRef != "" && !capture.AttestedAt.IsZero() && !capture.CollectedAt.IsZero() && !capture.AttestedAt.After(capture.CollectedAt)
|
||||
if real {
|
||||
providerOK = providerOK && (capture.Environment == "STAGING" || capture.Environment == "PRODUCTION") && capture.WorkloadIdentityRef != "" && capture.ProviderAuditRef != ""
|
||||
} else {
|
||||
providerOK = providerOK && capture.Environment == "LOCAL"
|
||||
}
|
||||
add("PROVIDER_IDENTITY", providerOK, "真实证据必须绑定云KMS/HSM、工作负载身份、提供器审计和安全复核引用")
|
||||
|
||||
rotationsOK, versionOK, newOK, oldOK, continuityOK, refsOK := len(capture.Rotations) > 0, true, true, true, true, true
|
||||
seenSecrets, modes := map[string]bool{}, map[string]bool{}
|
||||
for _, rotation := range capture.Rotations {
|
||||
if rotation.SecretRef == "" || rotation.ConsumerRef == "" || seenSecrets[rotation.SecretRef+"|"+rotation.ConsumerRef] || !slices.Contains([]string{"DYNAMIC_READ", "ROLLING_RESTART"}, rotation.ConsumptionMode) || rotation.StartedAt.IsZero() || rotation.CompletedAt.IsZero() || rotation.CompletedAt.Before(rotation.StartedAt) || rotation.CompletedAt.After(capture.CollectedAt) || !rotation.SecretValueNotExported {
|
||||
rotationsOK = false
|
||||
}
|
||||
seenSecrets[rotation.SecretRef+"|"+rotation.ConsumerRef] = true
|
||||
modes[rotation.ConsumptionMode] = true
|
||||
versionOK = versionOK && rotation.OldVersionRef != "" && rotation.NewVersionRef != "" && rotation.OldVersionRef != rotation.NewVersionRef
|
||||
newOK = newOK && rotation.NewCredentialVerified
|
||||
oldOK = oldOK && rotation.OldCredentialRejected
|
||||
continuityOK = continuityOK && rotation.BusinessContinuityVerified
|
||||
refsOK = refsOK && rotation.PreRotationProbeRef != "" && rotation.NewCredentialProbeRef != "" && rotation.OldRejectionProbeRef != "" && rotation.ContinuityProbeRef != "" && rotation.AccessAuditRef != ""
|
||||
}
|
||||
for mode := range modes {
|
||||
report.ConsumptionModes = append(report.ConsumptionModes, mode)
|
||||
}
|
||||
sortStrings(report.ConsumptionModes)
|
||||
if real {
|
||||
rotationsOK = rotationsOK && modes["DYNAMIC_READ"] && modes["ROLLING_RESTART"]
|
||||
}
|
||||
add("ROTATION_SCOPE", rotationsOK, "真实KMS/HSM必须覆盖逐请求动态读取和受控滚动重启两种消费模式")
|
||||
add("VERSION_TRANSITION", versionOK, "每次轮换必须从非空旧版本切换到不同的新版本")
|
||||
add("NEW_CREDENTIAL_ACCEPTED", newOK, "新凭据必须通过实际认证探针")
|
||||
add("OLD_CREDENTIAL_REJECTED", oldOK, "旧凭据必须在轮换窗口结束后稳定拒绝")
|
||||
add("BUSINESS_CONTINUITY", continuityOK, "轮换期间业务连续性探针必须通过")
|
||||
add("AUDIT_REFERENCES", refsOK, "每次轮换必须保存基线、新凭据、旧凭据拒绝、连续性和访问审计引用")
|
||||
if real && providerOK && rotationsOK && versionOK && newOK && oldOK && continuityOK && refsOK && report.Result == "PASS" {
|
||||
report.ExternalValidationRequired = false
|
||||
}
|
||||
return report
|
||||
}
|
||||
|
||||
func SHA256(content []byte) string {
|
||||
digest := sha256.Sum256(content)
|
||||
return hex.EncodeToString(digest[:])
|
||||
}
|
||||
|
||||
func decodeStrict(content []byte, target any) error {
|
||||
decoder := json.NewDecoder(bytes.NewReader(content))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(target); err != nil {
|
||||
return err
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
|
||||
return errors.New("trailing JSON data")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortStrings(values []string) {
|
||||
slices.Sort(values)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package kmsrotationconformance
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEvaluateRealKMSRequiresBothConsumptionModes(t *testing.T) {
|
||||
t.Parallel()
|
||||
capture, now := rotationFixture("KMS_HSM")
|
||||
report := Evaluate(capture, "0.64.0-rc.1", "sha256:"+strings.Repeat("a", 64), now)
|
||||
if report.Result != "PASS" || report.EvidenceScope != "REAL_KMS_HSM" || report.ExternalValidationRequired || report.RotationCount != 2 || len(report.ConsumptionModes) != 2 {
|
||||
t.Fatalf("unexpected report: %+v", report)
|
||||
}
|
||||
capture.Rotations = capture.Rotations[:1]
|
||||
report = Evaluate(capture, "0.64.0-rc.1", "sha256:"+strings.Repeat("a", 64), now)
|
||||
if report.Result != "FAIL" || !report.ExternalValidationRequired {
|
||||
t.Fatal("real KMS evidence without rolling restart mode was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateLocalRotationPreservesExternalGate(t *testing.T) {
|
||||
t.Parallel()
|
||||
capture, now := rotationFixture("LOCAL")
|
||||
capture.Environment = "LOCAL"
|
||||
capture.Rotations = capture.Rotations[:1]
|
||||
report := Evaluate(capture, "0.64.0-rc.1", "sha256:"+strings.Repeat("a", 64), now)
|
||||
if report.Result != "PASS" || report.EvidenceScope != "LOCAL_SECRET" || !report.ExternalValidationRequired {
|
||||
t.Fatalf("local evidence boundary changed: %+v", report)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStrictKMSCaptureAndReport(t *testing.T) {
|
||||
t.Parallel()
|
||||
if _, err := ParseCapture([]byte(`{"schema_version":"kms-rotation-capture-v1","unknown":true}`)); err == nil {
|
||||
t.Fatal("unknown capture field was accepted")
|
||||
}
|
||||
if _, err := ParseReport([]byte(`{"schema_version":"kms-rotation-conformance-report-v1","unknown":true}`)); err == nil {
|
||||
t.Fatal("unknown report field was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func rotationFixture(source string) (Capture, time.Time) {
|
||||
now := time.Date(2026, 8, 25, 12, 0, 0, 0, time.UTC)
|
||||
rotation := func(secret, consumer, mode string, offset int) RotationEvidence {
|
||||
return RotationEvidence{
|
||||
SecretRef: secret, ConsumerRef: consumer, ConsumptionMode: mode, OldVersionRef: secret + ":v1", NewVersionRef: secret + ":v2",
|
||||
StartedAt: now.Add(time.Duration(-10+offset) * time.Minute), CompletedAt: now.Add(time.Duration(-8+offset) * time.Minute),
|
||||
NewCredentialVerified: true, OldCredentialRejected: true, BusinessContinuityVerified: true, SecretValueNotExported: true,
|
||||
PreRotationProbeRef: "probe:before:" + secret, NewCredentialProbeRef: "probe:new:" + secret,
|
||||
OldRejectionProbeRef: "probe:old-rejected:" + secret, ContinuityProbeRef: "probe:continuity:" + secret, AccessAuditRef: "audit:" + secret,
|
||||
}
|
||||
}
|
||||
return Capture{
|
||||
SchemaVersion: CaptureSchema, EvidenceSource: source, CandidateVersion: "0.64.0-rc.1", BuildDigest: "sha256:" + strings.Repeat("a", 64),
|
||||
Environment: "STAGING", ProviderRef: "cloud-secret-manager:voice-platform", WorkloadIdentityRef: "workload-identity:voice-platform-api", ProviderAuditRef: "provider-audit:masked-001",
|
||||
AttestedByRef: "security-reviewer:masked-001", AttestedAt: now.Add(-time.Minute), CollectedAt: now,
|
||||
Rotations: []RotationEvidence{rotation("device-secret:terminal-001", "api", "DYNAMIC_READ", 0), rotation("provider-callback-secret", "callbacks", "ROLLING_RESTART", 3)},
|
||||
}, now
|
||||
}
|
||||
Reference in New Issue
Block a user