feat(gap): IST-Zustand Assessment — IACE + Normen + Prozesse
Gap Analysis v2: statt 500 generische Gaps → nur die ECHTEN Lücken. Backend: - ProductProfile um 15 IST-Felder erweitert (Normen, Doku, Prozesse, CE) - assessGapStatus prüft: IACE-Mitigations → Zertifizierungen → Normen → IST-Felder - norm_mapping.go: 20 Normen → MC-Topic Mapping (ISO 12100, IEC 62443, etc.) - IACE-Integration: CheckIACECoverage() matcht verified Mitigations gegen MCs Frontend: - 2-Step Wizard: Produkt beschreiben → IST-Zustand erfassen - IstAssessment.tsx: CE-Jahr, Normen-Multiselect, Doku+Prozess Checkboxen - Step-Navigation mit visuellen Indikatoren Migration 025 erweitert um IST-Felder. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,7 @@ func (e *Engine) Analyze(profile *ProductProfile) (*GapReport, error) {
|
||||
// Step 4: Assess gaps
|
||||
gaps := make([]GapItem, 0, len(mcGroups))
|
||||
for _, mc := range mcGroups {
|
||||
status := e.assessGapStatus(mc, profile.ExistingCertifications)
|
||||
status := e.assessGapStatus(mc, profile)
|
||||
item := GapItem{
|
||||
MCID: mc.MasterControlID,
|
||||
MCName: mc.CanonicalName,
|
||||
@@ -77,27 +77,80 @@ func (e *Engine) Analyze(profile *ProductProfile) (*GapReport, error) {
|
||||
return report, nil
|
||||
}
|
||||
|
||||
// assessGapStatus determines if a MC is fulfilled based on existing certs.
|
||||
func (e *Engine) assessGapStatus(mc MCGroup, certs []string) GapStatus {
|
||||
// If customer has ISO 27001, many security controls are likely fulfilled
|
||||
for _, cert := range certs {
|
||||
// assessGapStatus determines if a MC is fulfilled based on IST-Zustand:
|
||||
// IACE project data, applied norms, certifications, and existing processes.
|
||||
func (e *Engine) assessGapStatus(mc MCGroup, profile *ProductProfile) GapStatus {
|
||||
name := mc.CanonicalName
|
||||
|
||||
// A) IACE-Projekt vorhanden → aus verified Mitigations ableiten
|
||||
if profile.IACEProjectID != nil {
|
||||
status := e.store.CheckIACECoverage(*profile.IACEProjectID, name)
|
||||
if status == "verified" {
|
||||
return GapFulfilled
|
||||
}
|
||||
if status == "implemented" {
|
||||
return GapPartial
|
||||
}
|
||||
}
|
||||
|
||||
// B) Bestehende Zertifizierungen
|
||||
for _, cert := range profile.ExistingCertifications {
|
||||
switch cert {
|
||||
case "ISO27001":
|
||||
if isSecurityTopic(mc.CanonicalName) {
|
||||
return GapPartial // Likely partially covered
|
||||
}
|
||||
case "CE":
|
||||
if isMachineryTopic(mc.CanonicalName) {
|
||||
if isMachineryTopic(name) {
|
||||
return GapFulfilled
|
||||
}
|
||||
case "ISO27001":
|
||||
if isSecurityTopic(name) {
|
||||
return GapPartial
|
||||
}
|
||||
case "SOC2":
|
||||
if isSecurityTopic(mc.CanonicalName) {
|
||||
if isSecurityTopic(name) {
|
||||
return GapPartial
|
||||
}
|
||||
case "ISO13485":
|
||||
if contains(name, "risk_management") || contains(name, "documentation") {
|
||||
return GapPartial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default: missing (customer must verify)
|
||||
// C) Angewandte Normen → Controls als fulfilled erkennen
|
||||
if normCoversControl(profile.AppliedNorms, name) {
|
||||
return GapFulfilled
|
||||
}
|
||||
|
||||
// D) IST-Felder direkt matchen
|
||||
if profile.HasSBOM && contains(name, "asset_management_inventory") {
|
||||
return GapFulfilled
|
||||
}
|
||||
if profile.HasVulnManagement && contains(name, "vulnerability") {
|
||||
return GapFulfilled
|
||||
}
|
||||
if profile.HasUpdateMechanism && contains(name, "patch_management") {
|
||||
return GapFulfilled
|
||||
}
|
||||
if profile.HasIncidentResponse && contains(name, "incident") {
|
||||
return GapFulfilled
|
||||
}
|
||||
if profile.HasRiskAssessment && contains(name, "risk_management") {
|
||||
return GapFulfilled
|
||||
}
|
||||
if profile.HasTechnicalFile && contains(name, "documentation") {
|
||||
return GapFulfilled
|
||||
}
|
||||
if profile.HasOperatingManual && contains(name, "operating_instructions") {
|
||||
return GapFulfilled
|
||||
}
|
||||
if profile.HasSupplyChainMgmt && contains(name, "third_party_management") {
|
||||
return GapFulfilled
|
||||
}
|
||||
|
||||
// E) CE-Kennzeichnung vorhanden → Produktsicherheit fulfilled
|
||||
if profile.CEMarkingSince != nil && isMachineryTopic(name) {
|
||||
return GapFulfilled
|
||||
}
|
||||
|
||||
return GapMissing
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,30 @@ type ProductProfile struct {
|
||||
// Existing certifications (reduces gap count)
|
||||
ExistingCertifications []string `json:"existing_certifications" db:"-"` // ISO27001, CE, SOC2
|
||||
|
||||
// ── IST-Zustand (was hat der Hersteller bereits?) ──────────────
|
||||
|
||||
// Verbindung zu bestehendem IACE Projekt
|
||||
IACEProjectID *uuid.UUID `json:"iace_project_id" db:"iace_project_id"`
|
||||
|
||||
// Angewandte Normen
|
||||
AppliedNorms []string `json:"applied_norms" db:"-"` // ISO12100, EN61326, EN62368
|
||||
|
||||
// Bestehende Dokumentation
|
||||
HasRiskAssessment bool `json:"has_risk_assessment" db:"has_risk_assessment"`
|
||||
HasTechnicalFile bool `json:"has_technical_file" db:"has_technical_file"`
|
||||
HasOperatingManual bool `json:"has_operating_manual" db:"has_operating_manual"`
|
||||
HasSBOM bool `json:"has_sbom" db:"has_sbom"`
|
||||
|
||||
// Bestehende Prozesse
|
||||
HasVulnManagement bool `json:"has_vuln_management" db:"has_vuln_management"`
|
||||
HasUpdateMechanism bool `json:"has_update_mechanism" db:"has_update_mechanism"`
|
||||
HasIncidentResponse bool `json:"has_incident_response" db:"has_incident_response"`
|
||||
HasSupplyChainMgmt bool `json:"has_supply_chain_mgmt" db:"has_supply_chain_mgmt"`
|
||||
|
||||
// CE/Produktsicherheit
|
||||
CEMarkingSince *string `json:"ce_marking_since" db:"ce_marking_since"`
|
||||
ProductAge string `json:"product_age" db:"product_age"`
|
||||
|
||||
// Metadata
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package gap
|
||||
|
||||
// NormToControlMapping maps applied norms to MC topic prefixes they cover.
|
||||
// If a manufacturer has applied a norm, all matching MC topics are "fulfilled".
|
||||
var NormToControlMapping = map[string][]string{
|
||||
// Machine Safety
|
||||
"ISO12100": {"risk_management_assessment", "risk_management_documentation", "product_safety"},
|
||||
"ENISO13849": {"product_safety", "risk_management_assessment", "secure_development"},
|
||||
"IEC61508": {"product_safety", "risk_management", "secure_development"},
|
||||
"IEC62061": {"product_safety", "risk_management"},
|
||||
|
||||
// EMC / Electrical Safety
|
||||
"EN61326": {"network_security", "physical_security"},
|
||||
"EN62368": {"physical_security", "product_safety"},
|
||||
"IEC60204": {"physical_security", "product_safety"},
|
||||
|
||||
// Information Security
|
||||
"ISO27001": {
|
||||
"access_control", "encryption", "incident", "audit_logging",
|
||||
"vulnerability", "patch_management", "risk_management",
|
||||
"human_resources_security", "physical_security", "backup",
|
||||
"disaster_recovery", "change_management", "asset_management",
|
||||
"monitoring", "network_security",
|
||||
},
|
||||
"ISO27002": {
|
||||
"access_control", "encryption", "audit_logging",
|
||||
"vulnerability", "patch_management",
|
||||
},
|
||||
|
||||
// Industrial Cybersecurity
|
||||
"IEC62443": {
|
||||
"network_security", "network_segmentation", "access_control",
|
||||
"monitoring", "vulnerability", "patch_management",
|
||||
"incident", "secure_development",
|
||||
},
|
||||
|
||||
// Medical Devices
|
||||
"ISO13485": {"risk_management", "documentation", "change_management", "training"},
|
||||
"IEC60601": {"physical_security", "product_safety"},
|
||||
"ISO14971": {"risk_management_assessment", "risk_management_documentation"},
|
||||
"IEC62304": {"secure_development", "change_management", "documentation"},
|
||||
|
||||
// Crypto/Fintech
|
||||
"ISO22301": {"disaster_recovery", "backup", "incident"},
|
||||
"PCIDSS": {"encryption", "access_control", "audit_logging", "vulnerability", "network_segmentation"},
|
||||
|
||||
// Quality / Environmental
|
||||
"ISO9001": {"change_management", "documentation", "training", "compliance_audit"},
|
||||
"ISO14001": {"compliance_audit", "documentation", "risk_management"},
|
||||
|
||||
// Product Safety / RoHS / REACH
|
||||
"EN50581": {"supply_chain_due_diligence", "product_safety"},
|
||||
|
||||
// Functional Safety (software)
|
||||
"ASPICE": {"secure_development", "change_management", "documentation"},
|
||||
"ISO26262": {"secure_development", "risk_management", "product_safety"},
|
||||
}
|
||||
|
||||
// normCoversControl checks if any applied norm covers a given MC topic.
|
||||
func normCoversControl(appliedNorms []string, mcTopic string) bool {
|
||||
for _, norm := range appliedNorms {
|
||||
topics, ok := NormToControlMapping[norm]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, topic := range topics {
|
||||
if contains(mcTopic, topic) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -35,19 +35,29 @@ func (s *Store) CreateProfile(p *ProductProfile) error {
|
||||
marketsJSON, _ := json.Marshal(p.Markets)
|
||||
certsJSON, _ := json.Marshal(p.ExistingCertifications)
|
||||
|
||||
normsJSON, _ := json.Marshal(p.AppliedNorms)
|
||||
|
||||
_, err := s.pool.Exec(ctx, `
|
||||
INSERT INTO compliance.gap_projects
|
||||
(id, tenant_id, name, description, product_type,
|
||||
technologies, data_processing, markets,
|
||||
connected_to_internet, has_software_updates, uses_ai,
|
||||
processes_personal_data, is_critical_infra_supplier,
|
||||
existing_certifications, created_at, updated_at)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)`,
|
||||
existing_certifications, applied_norms,
|
||||
has_risk_assessment, has_technical_file, has_operating_manual, has_sbom,
|
||||
has_vuln_management, has_update_mechanism, has_incident_response, has_supply_chain_mgmt,
|
||||
ce_marking_since, product_age, iace_project_id,
|
||||
created_at, updated_at)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28)`,
|
||||
p.ID, p.TenantID, p.Name, p.Description, p.ProductType,
|
||||
techJSON, dataJSON, marketsJSON,
|
||||
p.ConnectedToInternet, p.HasSoftwareUpdates, p.UsesAI,
|
||||
p.ProcessesPersonalData, p.IsCriticalInfraSupplier,
|
||||
certsJSON, p.CreatedAt, p.UpdatedAt,
|
||||
certsJSON, normsJSON,
|
||||
p.HasRiskAssessment, p.HasTechnicalFile, p.HasOperatingManual, p.HasSBOM,
|
||||
p.HasVulnManagement, p.HasUpdateMechanism, p.HasIncidentResponse, p.HasSupplyChainMgmt,
|
||||
p.CEMarkingSince, p.ProductAge, p.IACEProjectID,
|
||||
p.CreatedAt, p.UpdatedAt,
|
||||
)
|
||||
return err
|
||||
}
|
||||
@@ -227,6 +237,58 @@ func sourceToRegID(source string) RegulationID {
|
||||
}
|
||||
}
|
||||
|
||||
// CheckIACECoverage checks if an IACE project has verified mitigations
|
||||
// covering the given MC topic.
|
||||
func (s *Store) CheckIACECoverage(projectID uuid.UUID, mcTopic string) string {
|
||||
ctx := context.Background()
|
||||
|
||||
// Map MC topics to IACE hazard categories
|
||||
iaceCategory := mcTopicToIACECategory(mcTopic)
|
||||
if iaceCategory == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var verifiedCount, implementedCount int
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT
|
||||
COUNT(CASE WHEN m.status = 'verified' THEN 1 END),
|
||||
COUNT(CASE WHEN m.status = 'implemented' THEN 1 END)
|
||||
FROM iace_mitigations m
|
||||
JOIN iace_hazards h ON h.id = m.hazard_id
|
||||
WHERE h.project_id = $1
|
||||
AND (h.category ILIKE $2 OR h.sub_category ILIKE $2)`,
|
||||
projectID, "%"+iaceCategory+"%",
|
||||
).Scan(&verifiedCount, &implementedCount)
|
||||
|
||||
if err != nil || (verifiedCount == 0 && implementedCount == 0) {
|
||||
return ""
|
||||
}
|
||||
if verifiedCount > 0 {
|
||||
return "verified"
|
||||
}
|
||||
return "implemented"
|
||||
}
|
||||
|
||||
func mcTopicToIACECategory(topic string) string {
|
||||
mapping := map[string]string{
|
||||
"encryption": "cyber",
|
||||
"access_control": "software",
|
||||
"network_security": "cyber",
|
||||
"vulnerability": "cyber",
|
||||
"product_safety": "mechanical",
|
||||
"physical_security": "electrical",
|
||||
"monitoring": "software",
|
||||
"incident": "organizational",
|
||||
"risk_management": "general",
|
||||
}
|
||||
for prefix, cat := range mapping {
|
||||
if strings.HasPrefix(topic, prefix) {
|
||||
return cat
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func formatTitle(name string) string {
|
||||
return strings.ReplaceAll(
|
||||
strings.ReplaceAll(name, "_", " "),
|
||||
|
||||
Reference in New Issue
Block a user