diff --git a/cmd/raa/main.go b/cmd/raa/main.go index 769992ee..44833820 100644 --- a/cmd/raa/main.go +++ b/cmd/raa/main.go @@ -36,7 +36,7 @@ func main() { // _ = os.WriteFile("raa_in.json", data, 0644) - var input types.ParsedModel + var input types.Model parseError := json.Unmarshal(data, &input) if parseError != nil { _, _ = fmt.Fprintf(os.Stderr, "failed to parse model: %v\n", parseError) @@ -75,7 +75,7 @@ func closeFile(file io.Closer) { _ = file.Close() } -func CalculateRAA(input *types.ParsedModel) string { +func CalculateRAA(input *types.Model) string { for techAssetID, techAsset := range input.TechnicalAssets { aa := calculateAttackerAttractiveness(input, techAsset) aa += calculatePivotingNeighbourEffectAdjustment(input, techAsset) @@ -95,7 +95,7 @@ func CalculateRAA(input *types.ParsedModel) string { var attackerAttractivenessMinimum, attackerAttractivenessMaximum, spread float64 = 0, 0, 0 // set the concrete value in relation to the minimum and maximum of all -func calculateRelativeAttackerAttractiveness(input *types.ParsedModel, attractiveness float64) float64 { +func calculateRelativeAttackerAttractiveness(input *types.Model, attractiveness float64) float64 { if attackerAttractivenessMinimum == 0 || attackerAttractivenessMaximum == 0 { attackerAttractivenessMinimum, attackerAttractivenessMaximum = 9223372036854775807, -9223372036854775808 // determine (only one time required) the min/max of all @@ -130,7 +130,7 @@ func calculateRelativeAttackerAttractiveness(input *types.ParsedModel, attractiv } // increase the RAA (relative attacker attractiveness) by one third (1/3) of the delta to the highest outgoing neighbour (if positive delta) -func calculatePivotingNeighbourEffectAdjustment(input *types.ParsedModel, techAsset *types.TechnicalAsset) float64 { +func calculatePivotingNeighbourEffectAdjustment(input *types.Model, techAsset *types.TechnicalAsset) float64 { if techAsset.OutOfScope { return 0 } @@ -141,7 +141,7 @@ func calculatePivotingNeighbourEffectAdjustment(input *types.ParsedModel, techAs delta := calculateRelativeAttackerAttractiveness(input, calculateAttackerAttractiveness(input, outgoingNeighbour)) - calculateRelativeAttackerAttractiveness(input, calculateAttackerAttractiveness(input, techAsset)) if delta > 0 { potentialIncrease := delta / 3 - //fmt.Println("Positive delta from", techAsset.Id, "to", outgoingNeighbour.Id, "is", delta, "yields to pivoting neighbour effect of an increase of", potentialIncrease) + //fmt.Println("Positive delta from", techAsset.ID, "to", outgoingNeighbour.ID, "is", delta, "yields to pivoting neighbour effect of an increase of", potentialIncrease) if potentialIncrease > adjustment { adjustment = potentialIncrease } @@ -153,7 +153,7 @@ func calculatePivotingNeighbourEffectAdjustment(input *types.ParsedModel, techAs // The sum of all CIAs of the asset itself (fibonacci scale) plus the sum of the comm-links' transferred CIAs // Multiplied by the quantity values of the data asset for C and I (not A) -func calculateAttackerAttractiveness(input *types.ParsedModel, techAsset *types.TechnicalAsset) float64 { +func calculateAttackerAttractiveness(input *types.Model, techAsset *types.TechnicalAsset) float64 { if techAsset.OutOfScope { return 0 } diff --git a/cmd/raa_dummy/main.go b/cmd/raa_dummy/main.go index 2e974b0e..c43779a4 100644 --- a/cmd/raa_dummy/main.go +++ b/cmd/raa_dummy/main.go @@ -23,7 +23,7 @@ func main() { os.Exit(-2) } - var input types.ParsedModel + var input types.Model inError := json.Unmarshal(inData, &input) if inError != nil { _, _ = fmt.Fprintf(os.Stderr, "failed to parse model: %v\n", inError) @@ -44,7 +44,7 @@ func main() { // used from run caller: -func CalculateRAA(input *types.ParsedModel) string { +func CalculateRAA(input *types.Model) string { for techAssetID, techAsset := range input.TechnicalAssets { nBig, randError := rand.Int(rand.Reader, big.NewInt(100)) if randError != nil { diff --git a/cmd/risk_demo/main.go b/cmd/risk_demo/main.go index c220ff1a..049222a2 100644 --- a/cmd/risk_demo/main.go +++ b/cmd/risk_demo/main.go @@ -16,8 +16,6 @@ type customRiskRule string // exported as symbol (here simply as variable to interface to bundle many functions under one symbol) named "RiskRule" -var CustomRiskRule customRiskRule - func main() { getInfo := flag.Bool("get-info", false, "get rule info") generateRisks := flag.Bool("generate-risks", false, "generate risks") @@ -25,8 +23,7 @@ func main() { if *getInfo { rule := new(customRiskRule) - category := rule.Category() - riskData, marshalError := json.Marshal(new(model.CustomRisk).Init(category.Id, category, rule.SupportedTags())) + riskData, marshalError := json.Marshal(new(model.CustomRiskCategory).Init(rule.Category(), rule.SupportedTags())) if marshalError != nil { _, _ = fmt.Fprintf(os.Stderr, "failed to print risk data: %v", marshalError) @@ -45,7 +42,7 @@ func main() { os.Exit(-2) } - var input types.ParsedModel + var input types.Model inError := json.Unmarshal(inData, &input) if inError != nil { _, _ = fmt.Fprintf(os.Stderr, "failed to parse model: %v\n", inError) @@ -67,9 +64,9 @@ func main() { os.Exit(-2) } -func (r customRiskRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "demo", +func (r customRiskRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "demo", Title: "Just a Demo", Description: "Demo Description", Impact: "Demo Impact", @@ -92,7 +89,7 @@ func (r customRiskRule) SupportedTags() []string { return []string{"demo tag"} } -func (r customRiskRule) GenerateRisks(parsedModel *types.ParsedModel) []types.Risk { +func (r customRiskRule) GenerateRisks(parsedModel *types.Model) []types.Risk { generatedRisks := make([]types.Risk, 0) for _, techAsset := range parsedModel.TechnicalAssets { generatedRisks = append(generatedRisks, createRisk(techAsset)) @@ -101,8 +98,9 @@ func (r customRiskRule) GenerateRisks(parsedModel *types.ParsedModel) []types.Ri } func createRisk(technicalAsset *types.TechnicalAsset) types.Risk { + category := new(customRiskRule).Category() risk := types.Risk{ - CategoryId: CustomRiskRule.Category().Id, + CategoryId: category.ID, Severity: types.CalculateSeverity(types.VeryLikely, types.MediumImpact), ExploitationLikelihood: types.VeryLikely, ExploitationImpact: types.MediumImpact, diff --git a/cmd/script/main.go b/cmd/script/main.go index 5c7a4faf..0de1ee2c 100644 --- a/cmd/script/main.go +++ b/cmd/script/main.go @@ -2,22 +2,24 @@ package main import ( "fmt" + "github.com/threagile/threagile/pkg/common" + "github.com/threagile/threagile/pkg/input" + "github.com/threagile/threagile/pkg/model" "github.com/threagile/threagile/pkg/script" - "github.com/threagile/threagile/pkg/script/common" - "github.com/threagile/threagile/pkg/security/types" + "github.com/threagile/threagile/pkg/security/risks" "gopkg.in/yaml.v3" "os" "path/filepath" ) func main() { - riskData, riskReadError := os.ReadFile(filepath.Join("test", "risk-category.yaml")) - if riskReadError != nil { - fmt.Printf("error reading risk category: %v\n", riskReadError) + ruleData, ruleReadError := os.ReadFile(filepath.Join("test", "risk-category.yaml")) + if ruleReadError != nil { + fmt.Printf("error reading risk category: %v\n", ruleReadError) return } - scripts, parseError := new(script.Script).Parse(riskData) + scripts, parseError := new(script.Script).ParseScripts(ruleData) if parseError != nil { fmt.Printf("error parsing scripts: %v\n", parseError) return @@ -29,52 +31,70 @@ func main() { return } - model := new(types.ParsedModel) - modelUnmarshalError := yaml.Unmarshal(modelData, model) + inputModel := new(input.Model) + modelUnmarshalError := yaml.Unmarshal(modelData, inputModel) if modelUnmarshalError != nil { fmt.Printf("error parsing model: %v\n", modelUnmarshalError) return } - categoriesModel := new(types.ParsedModel) - riskUnmarshalError := yaml.Unmarshal(riskData, categoriesModel) - if riskUnmarshalError != nil { - fmt.Printf("error parsing risk category: %v\n", riskUnmarshalError) + /* + categoriesModel := new(input.Model) + riskUnmarshalError := yaml.Unmarshal(riskData, categoriesModel) + if riskUnmarshalError != nil { + fmt.Printf("error parsing risk category: %v\n", riskUnmarshalError) + return + } + */ + + parsedModel, modelError := model.ParseModel(&common.Config{}, inputModel, make(risks.RiskRules), make(risks.RiskRules)) + if modelError != nil { + fmt.Printf("error importing model: %v\n", modelError) return } - var risk types.RiskCategory - if categoriesModel.IndividualRiskCategories != nil { - for _, item := range categoriesModel.IndividualRiskCategories { - risk = item + _ = parsedModel + _ = scripts + /* + var risk types.RiskCategory + if categoriesModel.CustomRiskCategories != nil { + for _, item := range categoriesModel.CustomRiskCategories { + risk = item + } } - } - for name, script := range scripts { - scope := new(common.Scope) - addError := scope.Init(model, &risk, script.Utils()) - if addError != nil { - fmt.Printf("error adding model to scope for %q: %v\n", name, addError) + if len(categoriesModel.CustomRiskCategories) == 0 { + fmt.Printf("no risk categories\n") return } - risks, errorLiteral, riskError := script.GenerateRisks(scope) - if riskError != nil { - fmt.Printf("error generating risks for %q: %v\n", name, riskError) + for name, script := range scripts { + scope := new(script.Scope) + addError := scope.Init(parsedModel, &risk, script.Utils()) + if addError != nil { + fmt.Printf("error adding model to scope for %q: %v\n", name, addError) + return + } + + risks, errorLiteral, riskError := script.GenerateRisks(scope) + if riskError != nil { + fmt.Printf("error generating risks for %q: %v\n", name, riskError) - if len(errorLiteral) > 0 { - fmt.Printf("in:\n%v\n", script.IndentPrintf(1, errorLiteral)) + if len(errorLiteral) > 0 { + fmt.Printf("in:\n%v\n", script.IndentPrintf(1, errorLiteral)) + } + + return } - return - } + printedRisks, printError := yaml.Marshal(risks) + if printError != nil { + fmt.Printf("error printing risks for %q: %v\n", name, printError) + return + } - printedRisks, printError := yaml.Marshal(risks) - if printError != nil { - fmt.Printf("error printing risks for %q: %v\n", name, printError) - return + fmt.Printf("generated risks for %q: \n%v\n", name, string(printedRisks)) } - fmt.Printf("generated risks for %q: \n%v\n", name, string(printedRisks)) - } + */ } diff --git a/internal/threagile/explain.go b/internal/threagile/explain.go index 98eea3dc..b0c3023d 100644 --- a/internal/threagile/explain.go +++ b/internal/threagile/explain.go @@ -78,7 +78,7 @@ func (what *Threagile) explainRules(cmd *cobra.Command, _ []string) error { cmd.Println("----------------------") customRiskRules := model.LoadCustomRiskRules(strings.Split(what.flags.customRiskRulesPluginFlag, ","), common.DefaultProgressReporter{Verbose: what.flags.verboseFlag}) for _, rule := range customRiskRules { - cmd.Printf("%v: %v\n", rule.Category().Id, rule.Category().Description) + cmd.Printf("%v: %v\n", rule.Category().ID, rule.Category().Description) } cmd.Println() cmd.Println("--------------------") @@ -86,7 +86,7 @@ func (what *Threagile) explainRules(cmd *cobra.Command, _ []string) error { cmd.Println("--------------------") cmd.Println() for _, rule := range risks.GetBuiltInRiskRules() { - cmd.Printf("%v: %v\n", rule.Category().Id, rule.Category().Description) + cmd.Printf("%v: %v\n", rule.Category().ID, rule.Category().Description) } cmd.Println() diff --git a/internal/threagile/list.go b/internal/threagile/list.go index b49e964a..a76eabaa 100644 --- a/internal/threagile/list.go +++ b/internal/threagile/list.go @@ -33,7 +33,7 @@ func (what *Threagile) initList() *Threagile { cmd.Println("--------------------") cmd.Println() for _, rule := range risks.GetBuiltInRiskRules() { - cmd.Println(rule.Category().Id, "-->", rule.Category().Title, "--> with tags:", rule.SupportedTags()) + cmd.Println(rule.Category().ID, "-->", rule.Category().Title, "--> with tags:", rule.SupportedTags()) } return nil diff --git a/internal/threagile/root.go b/internal/threagile/root.go index 3005ce40..5d47a8e5 100644 --- a/internal/threagile/root.go +++ b/internal/threagile/root.go @@ -72,7 +72,7 @@ func (what *Threagile) initRoot() *Threagile { what.rootCmd.PersistentFlags().StringVar(&what.flags.customRiskRulesPluginFlag, customRiskRulesPluginFlagName, strings.Join(defaultConfig.RiskRulesPlugins, ","), "comma-separated list of plugins file names with custom risk rules to load") what.rootCmd.PersistentFlags().IntVar(&what.flags.diagramDpiFlag, diagramDpiFlagName, defaultConfig.DiagramDPI, "DPI used to render: maximum is "+fmt.Sprintf("%d", common.MaxGraphvizDPI)+"") - what.rootCmd.PersistentFlags().StringVar(&what.flags.skipRiskRulesFlag, skipRiskRulesFlagName, defaultConfig.SkipRiskRules, "comma-separated list of risk rules (by their ID) to skip") + what.rootCmd.PersistentFlags().StringVar(&what.flags.skipRiskRulesFlag, skipRiskRulesFlagName, strings.Join(defaultConfig.SkipRiskRules, ","), "comma-separated list of risk rules (by their ID) to skip") what.rootCmd.PersistentFlags().BoolVar(&what.flags.ignoreOrphanedRiskTrackingFlag, ignoreOrphanedRiskTrackingFlagName, defaultConfig.IgnoreOrphanedRiskTracking, "ignore orphaned risk tracking (just log them) not matching a concrete risk") what.rootCmd.PersistentFlags().StringVar(&what.flags.templateFileNameFlag, templateFileNameFlagName, defaultConfig.TemplateFilename, "background pdf file") @@ -264,7 +264,7 @@ func (what *Threagile) readConfig(cmd *cobra.Command, buildTimestamp string) *co cfg.RiskRulesPlugins = strings.Split(what.flags.customRiskRulesPluginFlag, ",") } if isFlagOverridden(flags, skipRiskRulesFlagName) { - cfg.SkipRiskRules = what.flags.skipRiskRulesFlag + cfg.SkipRiskRules = strings.Split(what.flags.skipRiskRulesFlag, ",") } if isFlagOverridden(flags, ignoreOrphanedRiskTrackingFlagName) { cfg.IgnoreOrphanedRiskTracking = what.flags.ignoreOrphanedRiskTrackingFlag diff --git a/pkg/common/config.go b/pkg/common/config.go index 3205935f..6235241a 100644 --- a/pkg/common/config.go +++ b/pkg/common/config.go @@ -38,7 +38,7 @@ type Config struct { RAAPlugin string RiskRulesPlugins []string - SkipRiskRules string + SkipRiskRules []string ExecuteModelMacro string RiskExcel RiskExcelConfig @@ -92,7 +92,7 @@ func (c *Config) Defaults(buildTimestamp string) *Config { RAAPlugin: RAAPluginName, RiskRulesPlugins: make([]string, 0), - SkipRiskRules: "", + SkipRiskRules: make([]string, 0), ExecuteModelMacro: "", RiskExcel: RiskExcelConfig{ HideColumns: make([]string, 0), diff --git a/pkg/input/model.go b/pkg/input/model.go index 9b7d4782..c5feddf0 100644 --- a/pkg/input/model.go +++ b/pkg/input/model.go @@ -21,47 +21,47 @@ import ( // === Model Type Stuff ====================================== type Model struct { // TODO: Eventually remove this and directly use ParsedModelRoot? But then the error messages for model errors are not quite as good anymore... - ThreagileVersion string `yaml:"threagile_version,omitempty" json:"threagile_version,omitempty"` - Includes []string `yaml:"includes,omitempty" json:"includes,omitempty"` - Title string `yaml:"title,omitempty" json:"title,omitempty"` - Author Author `yaml:"author,omitempty" json:"author,omitempty"` - Contributors []Author `yaml:"contributors,omitempty" json:"contributors,omitempty"` - Date string `yaml:"date,omitempty" json:"date,omitempty"` - AppDescription Overview `yaml:"application_description,omitempty" json:"application_description,omitempty"` - BusinessOverview Overview `yaml:"business_overview,omitempty" json:"business_overview,omitempty"` - TechnicalOverview Overview `yaml:"technical_overview,omitempty" json:"technical_overview,omitempty"` - BusinessCriticality string `yaml:"business_criticality,omitempty" json:"business_criticality,omitempty"` - ManagementSummaryComment string `yaml:"management_summary_comment,omitempty" json:"management_summary_comment,omitempty"` - SecurityRequirements map[string]string `yaml:"security_requirements,omitempty" json:"security_requirements,omitempty"` - Questions map[string]string `yaml:"questions,omitempty" json:"questions,omitempty"` - AbuseCases map[string]string `yaml:"abuse_cases,omitempty" json:"abuse_cases,omitempty"` - TagsAvailable []string `yaml:"tags_available,omitempty" json:"tags_available,omitempty"` - DataAssets map[string]DataAsset `yaml:"data_assets,omitempty" json:"data_assets,omitempty"` - TechnicalAssets map[string]TechnicalAsset `yaml:"technical_assets,omitempty" json:"technical_assets,omitempty"` - TrustBoundaries map[string]TrustBoundary `yaml:"trust_boundaries,omitempty" json:"trust_boundaries,omitempty"` - SharedRuntimes map[string]SharedRuntime `yaml:"shared_runtimes,omitempty" json:"shared_runtimes,omitempty"` - IndividualRiskCategories map[string]IndividualRiskCategory `yaml:"individual_risk_categories,omitempty" json:"individual_risk_categories,omitempty"` - RiskTracking map[string]RiskTracking `yaml:"risk_tracking,omitempty" json:"risk_tracking,omitempty"` - DiagramTweakNodesep int `yaml:"diagram_tweak_nodesep,omitempty" json:"diagram_tweak_nodesep,omitempty"` - DiagramTweakRanksep int `yaml:"diagram_tweak_ranksep,omitempty" json:"diagram_tweak_ranksep,omitempty"` - DiagramTweakEdgeLayout string `yaml:"diagram_tweak_edge_layout,omitempty" json:"diagram_tweak_edge_layout,omitempty"` - DiagramTweakSuppressEdgeLabels bool `yaml:"diagram_tweak_suppress_edge_labels,omitempty" json:"diagram_tweak_suppress_edge_labels,omitempty"` - DiagramTweakLayoutLeftToRight bool `yaml:"diagram_tweak_layout_left_to_right,omitempty" json:"diagram_tweak_layout_left_to_right,omitempty"` - DiagramTweakInvisibleConnectionsBetweenAssets []string `yaml:"diagram_tweak_invisible_connections_between_assets,omitempty" json:"diagram_tweak_invisible_connections_between_assets,omitempty"` - DiagramTweakSameRankAssets []string `yaml:"diagram_tweak_same_rank_assets,omitempty" json:"diagram_tweak_same_rank_assets,omitempty"` + ThreagileVersion string `yaml:"threagile_version,omitempty" json:"threagile_version,omitempty"` + Includes []string `yaml:"includes,omitempty" json:"includes,omitempty"` + Title string `yaml:"title,omitempty" json:"title,omitempty"` + Author Author `yaml:"author,omitempty" json:"author,omitempty"` + Contributors []Author `yaml:"contributors,omitempty" json:"contributors,omitempty"` + Date string `yaml:"date,omitempty" json:"date,omitempty"` + AppDescription Overview `yaml:"application_description,omitempty" json:"application_description,omitempty"` + BusinessOverview Overview `yaml:"business_overview,omitempty" json:"business_overview,omitempty"` + TechnicalOverview Overview `yaml:"technical_overview,omitempty" json:"technical_overview,omitempty"` + BusinessCriticality string `yaml:"business_criticality,omitempty" json:"business_criticality,omitempty"` + ManagementSummaryComment string `yaml:"management_summary_comment,omitempty" json:"management_summary_comment,omitempty"` + SecurityRequirements map[string]string `yaml:"security_requirements,omitempty" json:"security_requirements,omitempty"` + Questions map[string]string `yaml:"questions,omitempty" json:"questions,omitempty"` + AbuseCases map[string]string `yaml:"abuse_cases,omitempty" json:"abuse_cases,omitempty"` + TagsAvailable []string `yaml:"tags_available,omitempty" json:"tags_available,omitempty"` + DataAssets map[string]DataAsset `yaml:"data_assets,omitempty" json:"data_assets,omitempty"` + TechnicalAssets map[string]TechnicalAsset `yaml:"technical_assets,omitempty" json:"technical_assets,omitempty"` + TrustBoundaries map[string]TrustBoundary `yaml:"trust_boundaries,omitempty" json:"trust_boundaries,omitempty"` + SharedRuntimes map[string]SharedRuntime `yaml:"shared_runtimes,omitempty" json:"shared_runtimes,omitempty"` + CustomRiskCategories RiskCategories `yaml:"custom_risk_categories,omitempty" json:"custom_risk_categories,omitempty"` + RiskTracking map[string]RiskTracking `yaml:"risk_tracking,omitempty" json:"risk_tracking,omitempty"` + DiagramTweakNodesep int `yaml:"diagram_tweak_nodesep,omitempty" json:"diagram_tweak_nodesep,omitempty"` + DiagramTweakRanksep int `yaml:"diagram_tweak_ranksep,omitempty" json:"diagram_tweak_ranksep,omitempty"` + DiagramTweakEdgeLayout string `yaml:"diagram_tweak_edge_layout,omitempty" json:"diagram_tweak_edge_layout,omitempty"` + DiagramTweakSuppressEdgeLabels bool `yaml:"diagram_tweak_suppress_edge_labels,omitempty" json:"diagram_tweak_suppress_edge_labels,omitempty"` + DiagramTweakLayoutLeftToRight bool `yaml:"diagram_tweak_layout_left_to_right,omitempty" json:"diagram_tweak_layout_left_to_right,omitempty"` + DiagramTweakInvisibleConnectionsBetweenAssets []string `yaml:"diagram_tweak_invisible_connections_between_assets,omitempty" json:"diagram_tweak_invisible_connections_between_assets,omitempty"` + DiagramTweakSameRankAssets []string `yaml:"diagram_tweak_same_rank_assets,omitempty" json:"diagram_tweak_same_rank_assets,omitempty"` } func (model *Model) Defaults() *Model { *model = Model{ - Questions: make(map[string]string), - AbuseCases: make(map[string]string), - SecurityRequirements: make(map[string]string), - DataAssets: make(map[string]DataAsset), - TechnicalAssets: make(map[string]TechnicalAsset), - TrustBoundaries: make(map[string]TrustBoundary), - SharedRuntimes: make(map[string]SharedRuntime), - IndividualRiskCategories: make(map[string]IndividualRiskCategory), - RiskTracking: make(map[string]RiskTracking), + Questions: make(map[string]string), + AbuseCases: make(map[string]string), + SecurityRequirements: make(map[string]string), + DataAssets: make(map[string]DataAsset), + TechnicalAssets: make(map[string]TechnicalAsset), + TrustBoundaries: make(map[string]TrustBoundary), + SharedRuntimes: make(map[string]SharedRuntime), + CustomRiskCategories: make(RiskCategories, 0), + RiskTracking: make(map[string]RiskTracking), } return model @@ -219,8 +219,8 @@ func (model *Model) Merge(dir string, includeFilename string) error { return fmt.Errorf("failed to merge shared runtimes: %v", mergeError) } - case strings.ToLower("individual_risk_categories"): - model.IndividualRiskCategories, mergeError = new(IndividualRiskCategory).MergeMap(model.IndividualRiskCategories, includedModel.IndividualRiskCategories) + case strings.ToLower("custom_risk_categories"): + mergeError = model.CustomRiskCategories.Add(includedModel.CustomRiskCategories...) if mergeError != nil { return fmt.Errorf("failed to merge risk categories: %v", mergeError) } diff --git a/pkg/input/risk-category.go b/pkg/input/risk-category.go index 17ecedb2..59336e96 100644 --- a/pkg/input/risk-category.go +++ b/pkg/input/risk-category.go @@ -2,10 +2,12 @@ package input import ( "fmt" + "strings" ) -type IndividualRiskCategory struct { +type RiskCategory struct { ID string `yaml:"id,omitempty" json:"id,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` Description string `yaml:"description,omitempty" json:"description,omitempty"` Impact string `yaml:"impact,omitempty" json:"impact,omitempty"` ASVS string `yaml:"asvs,omitempty" json:"asvs,omitempty"` @@ -23,7 +25,9 @@ type IndividualRiskCategory struct { RisksIdentified map[string]RiskIdentified `yaml:"risks_identified,omitempty" json:"risks_identified,omitempty"` } -func (what *IndividualRiskCategory) Merge(other IndividualRiskCategory) error { +type RiskCategories []*RiskCategory + +func (what *RiskCategory) Merge(other RiskCategory) error { var mergeError error what.ID, mergeError = new(Strings).MergeSingleton(what.ID, other.ID) if mergeError != nil { @@ -106,20 +110,16 @@ func (what *IndividualRiskCategory) Merge(other IndividualRiskCategory) error { return nil } -func (what *IndividualRiskCategory) MergeMap(first map[string]IndividualRiskCategory, second map[string]IndividualRiskCategory) (map[string]IndividualRiskCategory, error) { - for mapKey, mapValue := range second { - mapItem, ok := first[mapKey] - if ok { - mergeError := mapItem.Merge(mapValue) - if mergeError != nil { - return first, fmt.Errorf("failed to merge risk category %q: %v", mapKey, mergeError) +func (what *RiskCategories) Add(items ...*RiskCategory) error { + for _, item := range items { + for _, value := range *what { + if strings.EqualFold(value.ID, item.ID) { + return fmt.Errorf("duplicate item %q in risk category list", value.ID) } - - first[mapKey] = mapItem - } else { - first[mapKey] = mapValue } + + *what = append(*what, item) } - return first, nil + return nil } diff --git a/pkg/input/strings.go b/pkg/input/strings.go index d1dfeea3..17174544 100644 --- a/pkg/input/strings.go +++ b/pkg/input/strings.go @@ -2,6 +2,7 @@ package input import ( "fmt" + "gopkg.in/yaml.v3" "slices" "strings" ) @@ -62,3 +63,37 @@ func (what *Strings) MergeUniqueSlice(first []string, second []string) []string return first } + +func (what *Strings) AddLineNumbers(script any) string { + text, isString := script.(string) + if !isString { + data, _ := yaml.Marshal(script) + text = string(data) + } + + lines := strings.Split(text, "\n") + for n, line := range lines { + lines[n] = fmt.Sprintf("%3d:\t%v", n+1, line) + } + + return strings.Join(lines, "\n") +} + +func (what *Strings) IndentPrintf(level int, script any) string { + text, isString := script.(string) + if !isString { + data, _ := yaml.Marshal(script) + text = string(data) + } + + lines := strings.Split(text, "\n") + for n, line := range lines { + lines[n] = strings.Repeat(" ", level) + line + } + + return strings.Join(lines, "\n") +} + +func (what *Strings) IndentLine(level int, format string, params ...any) string { + return strings.Repeat(" ", level) + fmt.Sprintf(format, params...) +} diff --git a/pkg/macros/add-build-pipeline-macro.go b/pkg/macros/add-build-pipeline-macro.go index 82b8214c..b4a30c49 100644 --- a/pkg/macros/add-build-pipeline-macro.go +++ b/pkg/macros/add-build-pipeline-macro.go @@ -41,7 +41,7 @@ func (m *AddBuildPipeline) GetMacroDetails() MacroDetails { // TODO add question for type of machine (either physical, virtual, container, etc.) -func (m *AddBuildPipeline) GetNextQuestion(model *types.ParsedModel) (nextQuestion MacroQuestion, err error) { +func (m *AddBuildPipeline) GetNextQuestion(model *types.Model) (nextQuestion MacroQuestion, err error) { counter := len(m.questionsAnswered) if counter > 3 && !m.codeInspectionUsed { counter++ @@ -257,19 +257,19 @@ func (m *AddBuildPipeline) GoBack() (message string, validResult bool, err error return "Undo successful", true, nil } -func (m *AddBuildPipeline) GetFinalChangeImpact(modelInput *input.Model, model *types.ParsedModel) (changes []string, message string, validResult bool, err error) { +func (m *AddBuildPipeline) GetFinalChangeImpact(modelInput *input.Model, model *types.Model) (changes []string, message string, validResult bool, err error) { changeLogCollector := make([]string, 0) message, validResult, err = m.applyChange(modelInput, model, &changeLogCollector, true) return changeLogCollector, message, validResult, err } -func (m *AddBuildPipeline) Execute(modelInput *input.Model, model *types.ParsedModel) (message string, validResult bool, err error) { +func (m *AddBuildPipeline) Execute(modelInput *input.Model, model *types.Model) (message string, validResult bool, err error) { changeLogCollector := make([]string, 0) message, validResult, err = m.applyChange(modelInput, model, &changeLogCollector, false) return message, validResult, err } -func (m *AddBuildPipeline) applyChange(modelInput *input.Model, parsedModel *types.ParsedModel, changeLogCollector *[]string, dryRun bool) (message string, validResult bool, err error) { +func (m *AddBuildPipeline) applyChange(modelInput *input.Model, parsedModel *types.Model, changeLogCollector *[]string, dryRun bool) (message string, validResult bool, err error) { var serverSideTechAssets = make([]string, 0) // ################################################ modelInput.AddTagToModelInput(m.macroState["source-repository"][0], dryRun, changeLogCollector) diff --git a/pkg/macros/add-vault-macro.go b/pkg/macros/add-vault-macro.go index c07bac0e..1d4e0a63 100644 --- a/pkg/macros/add-vault-macro.go +++ b/pkg/macros/add-vault-macro.go @@ -48,7 +48,7 @@ func (m *AddVaultMacro) GetMacroDetails() MacroDetails { } } -func (m *AddVaultMacro) GetNextQuestion(parsedModel *types.ParsedModel) (nextQuestion MacroQuestion, err error) { +func (m *AddVaultMacro) GetNextQuestion(parsedModel *types.Model) (nextQuestion MacroQuestion, err error) { counter := len(m.questionsAnswered) if counter > 5 && !m.withinTrustBoundary { counter++ @@ -173,19 +173,19 @@ func (m *AddVaultMacro) GoBack() (message string, validResult bool, err error) { return "Undo successful", true, nil } -func (m *AddVaultMacro) GetFinalChangeImpact(modelInput *input.Model, parsedModel *types.ParsedModel) (changes []string, message string, validResult bool, err error) { +func (m *AddVaultMacro) GetFinalChangeImpact(modelInput *input.Model, parsedModel *types.Model) (changes []string, message string, validResult bool, err error) { changeLogCollector := make([]string, 0) message, validResult, err = m.applyChange(modelInput, parsedModel, &changeLogCollector, true) return changeLogCollector, message, validResult, err } -func (m *AddVaultMacro) Execute(modelInput *input.Model, parsedModel *types.ParsedModel) (message string, validResult bool, err error) { +func (m *AddVaultMacro) Execute(modelInput *input.Model, parsedModel *types.Model) (message string, validResult bool, err error) { changeLogCollector := make([]string, 0) message, validResult, err = m.applyChange(modelInput, parsedModel, &changeLogCollector, false) return message, validResult, err } -func (m *AddVaultMacro) applyChange(modelInput *input.Model, parsedModel *types.ParsedModel, changeLogCollector *[]string, dryRun bool) (message string, validResult bool, err error) { +func (m *AddVaultMacro) applyChange(modelInput *input.Model, parsedModel *types.Model, changeLogCollector *[]string, dryRun bool) (message string, validResult bool, err error) { modelInput.AddTagToModelInput(m.macroState["vault-name"][0], dryRun, changeLogCollector) var serverSideTechAssets = make([]string, 0) diff --git a/pkg/macros/macros.go b/pkg/macros/macros.go index 58079827..af13aa26 100644 --- a/pkg/macros/macros.go +++ b/pkg/macros/macros.go @@ -20,11 +20,11 @@ import ( type Macros interface { GetMacroDetails() MacroDetails - GetNextQuestion(model *types.ParsedModel) (nextQuestion MacroQuestion, err error) + GetNextQuestion(model *types.Model) (nextQuestion MacroQuestion, err error) ApplyAnswer(questionID string, answer ...string) (message string, validResult bool, err error) GoBack() (message string, validResult bool, err error) - GetFinalChangeImpact(modelInput *input.Model, model *types.ParsedModel) (changes []string, message string, validResult bool, err error) - Execute(modelInput *input.Model, model *types.ParsedModel) (message string, validResult bool, err error) + GetFinalChangeImpact(modelInput *input.Model, model *types.Model) (changes []string, message string, validResult bool, err error) + Execute(modelInput *input.Model, model *types.Model) (message string, validResult bool, err error) } func ListBuiltInMacros() []Macros { @@ -55,7 +55,7 @@ func GetMacroByID(id string) (Macros, error) { return nil, fmt.Errorf("unknown macro id: %v", id) } -func ExecuteModelMacro(modelInput *input.Model, inputFile string, parsedModel *types.ParsedModel, macroID string) error { +func ExecuteModelMacro(modelInput *input.Model, inputFile string, parsedModel *types.Model, macroID string) error { macros, err := GetMacroByID(macroID) if err != nil { return err diff --git a/pkg/macros/pretty-print-macro.go b/pkg/macros/pretty-print-macro.go index 262f18eb..5735c09f 100644 --- a/pkg/macros/pretty-print-macro.go +++ b/pkg/macros/pretty-print-macro.go @@ -20,7 +20,7 @@ func (*PrettyPrintMacro) GetMacroDetails() MacroDetails { } } -func (*PrettyPrintMacro) GetNextQuestion(_ *types.ParsedModel) (nextQuestion MacroQuestion, err error) { +func (*PrettyPrintMacro) GetNextQuestion(_ *types.Model) (nextQuestion MacroQuestion, err error) { return NoMoreQuestions(), nil } @@ -32,10 +32,10 @@ func (*PrettyPrintMacro) GoBack() (message string, validResult bool, err error) return "Cannot go back further", false, nil } -func (*PrettyPrintMacro) GetFinalChangeImpact(_ *input.Model, _ *types.ParsedModel) (changes []string, message string, validResult bool, err error) { +func (*PrettyPrintMacro) GetFinalChangeImpact(_ *input.Model, _ *types.Model) (changes []string, message string, validResult bool, err error) { return []string{"pretty-printing the model file"}, "Changeset valid", true, err } -func (*PrettyPrintMacro) Execute(_ *input.Model, _ *types.ParsedModel) (message string, validResult bool, err error) { +func (*PrettyPrintMacro) Execute(_ *input.Model, _ *types.Model) (message string, validResult bool, err error) { return "Model pretty printing successful", true, nil } diff --git a/pkg/macros/remove-unused-tags-macro.go b/pkg/macros/remove-unused-tags-macro.go index 28c6b3eb..f60834a6 100644 --- a/pkg/macros/remove-unused-tags-macro.go +++ b/pkg/macros/remove-unused-tags-macro.go @@ -24,7 +24,7 @@ func (*removeUnusedTagsMacro) GetMacroDetails() MacroDetails { } } -func (*removeUnusedTagsMacro) GetNextQuestion(*types.ParsedModel) (nextQuestion MacroQuestion, err error) { +func (*removeUnusedTagsMacro) GetNextQuestion(*types.Model) (nextQuestion MacroQuestion, err error) { return NoMoreQuestions(), nil } @@ -36,11 +36,11 @@ func (*removeUnusedTagsMacro) GoBack() (message string, validResult bool, err er return "Cannot go back further", false, nil } -func (*removeUnusedTagsMacro) GetFinalChangeImpact(_ *input.Model, _ *types.ParsedModel) (changes []string, message string, validResult bool, err error) { +func (*removeUnusedTagsMacro) GetFinalChangeImpact(_ *input.Model, _ *types.Model) (changes []string, message string, validResult bool, err error) { return []string{"remove unused tags from the model file"}, "Changeset valid", true, err } -func (*removeUnusedTagsMacro) Execute(modelInput *input.Model, parsedModel *types.ParsedModel) (message string, validResult bool, err error) { +func (*removeUnusedTagsMacro) Execute(modelInput *input.Model, parsedModel *types.Model) (message string, validResult bool, err error) { modelInput.TagsAvailable = parsedModel.TagsAvailable for _, asset := range parsedModel.DataAssets { modelInput.TagsAvailable = append(modelInput.TagsAvailable, asset.Tags...) diff --git a/pkg/macros/seed-risk-tracking-macro.go b/pkg/macros/seed-risk-tracking-macro.go index 45561656..ede9f91e 100644 --- a/pkg/macros/seed-risk-tracking-macro.go +++ b/pkg/macros/seed-risk-tracking-macro.go @@ -23,7 +23,7 @@ func (*SeedRiskTrackingMacro) GetMacroDetails() MacroDetails { } } -func (*SeedRiskTrackingMacro) GetNextQuestion(*types.ParsedModel) (nextQuestion MacroQuestion, err error) { +func (*SeedRiskTrackingMacro) GetNextQuestion(*types.Model) (nextQuestion MacroQuestion, err error) { return NoMoreQuestions(), nil } @@ -35,11 +35,11 @@ func (*SeedRiskTrackingMacro) GoBack() (message string, validResult bool, err er return "Cannot go back further", false, nil } -func (*SeedRiskTrackingMacro) GetFinalChangeImpact(_ *input.Model, _ *types.ParsedModel) (changes []string, message string, validResult bool, err error) { +func (*SeedRiskTrackingMacro) GetFinalChangeImpact(_ *input.Model, _ *types.Model) (changes []string, message string, validResult bool, err error) { return []string{"seed the model file with with initial risk tracking entries for all untracked risks"}, "Changeset valid", true, err } -func (*SeedRiskTrackingMacro) Execute(modelInput *input.Model, parsedModel *types.ParsedModel) (message string, validResult bool, err error) { +func (*SeedRiskTrackingMacro) Execute(modelInput *input.Model, parsedModel *types.Model) (message string, validResult bool, err error) { syntheticRiskIDsToCreateTrackingFor := make([]string, 0) for id, risk := range parsedModel.GeneratedRisksBySyntheticId { if !risk.IsRiskTracked(parsedModel) { diff --git a/pkg/macros/seed-tags-macro.go b/pkg/macros/seed-tags-macro.go index ab953284..172b403d 100644 --- a/pkg/macros/seed-tags-macro.go +++ b/pkg/macros/seed-tags-macro.go @@ -24,7 +24,7 @@ func (*SeedTagsMacro) GetMacroDetails() MacroDetails { } } -func (*SeedTagsMacro) GetNextQuestion(parsedModel *types.ParsedModel) (nextQuestion MacroQuestion, err error) { +func (*SeedTagsMacro) GetNextQuestion(parsedModel *types.Model) (nextQuestion MacroQuestion, err error) { return NoMoreQuestions(), nil } @@ -36,11 +36,11 @@ func (*SeedTagsMacro) GoBack() (message string, validResult bool, err error) { return "Cannot go back further", false, nil } -func (*SeedTagsMacro) GetFinalChangeImpact(_ *input.Model, _ *types.ParsedModel) (changes []string, message string, validResult bool, err error) { +func (*SeedTagsMacro) GetFinalChangeImpact(_ *input.Model, _ *types.Model) (changes []string, message string, validResult bool, err error) { return []string{"seed the model file with supported tags from all risk rules"}, "Changeset valid", true, err } -func (*SeedTagsMacro) Execute(modelInput *input.Model, parsedModel *types.ParsedModel) (message string, validResult bool, err error) { +func (*SeedTagsMacro) Execute(modelInput *input.Model, parsedModel *types.Model) (message string, validResult bool, err error) { modelInput.TagsAvailable = parsedModel.TagsAvailable for tag := range parsedModel.AllSupportedTags { modelInput.TagsAvailable = append(modelInput.TagsAvailable, tag) diff --git a/pkg/model/custom-risk.go b/pkg/model/custom-risk-category.go similarity index 53% rename from pkg/model/custom-risk.go rename to pkg/model/custom-risk-category.go index 613273d5..8dba3c54 100644 --- a/pkg/model/custom-risk.go +++ b/pkg/model/custom-risk-category.go @@ -2,71 +2,71 @@ package model import ( "fmt" + "github.com/threagile/threagile/pkg/security/risks" "log" "strings" "github.com/threagile/threagile/pkg/security/types" ) -type CustomRisk struct { - ID string `json:"id,omitempty" yaml:"id,omitempty"` - RiskCategory types.RiskCategory `json:"risk_category" yaml:"risk_category,omitempty"` - Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` - runner *runner +type CustomRiskCategory struct { + types.RiskCategory `json:"risk_category" yaml:"risk_category,omitempty"` + + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + runner *runner } -func (what *CustomRisk) Init(id string, category types.RiskCategory, tags []string) *CustomRisk { - *what = CustomRisk{ - ID: id, - RiskCategory: category, +func (what *CustomRiskCategory) Init(category *types.RiskCategory, tags []string) *CustomRiskCategory { + *what = CustomRiskCategory{ + RiskCategory: *category, Tags: tags, } return what } -func (what *CustomRisk) Category() types.RiskCategory { - return what.RiskCategory +func (what *CustomRiskCategory) Category() *types.RiskCategory { + return &what.RiskCategory } -func (what *CustomRisk) SupportedTags() []string { +func (what *CustomRiskCategory) SupportedTags() []string { return what.Tags } -func (what *CustomRisk) GenerateRisks(parsedModel *types.ParsedModel) []types.Risk { +func (what *CustomRiskCategory) GenerateRisks(parsedModel *types.Model) []*types.Risk { if what.runner == nil { return nil } - risks := make([]types.Risk, 0) - runError := what.runner.Run(parsedModel, &risks, "-generate-risks") + generatedRisks := make([]*types.Risk, 0) + runError := what.runner.Run(parsedModel, &generatedRisks, "-generate-risks") if runError != nil { log.Fatalf("Failed to generate risks for custom risk rule %q: %v\n", what.runner.Filename, runError) } - return risks + return generatedRisks } -func LoadCustomRiskRules(pluginFiles []string, reporter types.ProgressReporter) map[string]*CustomRisk { +func LoadCustomRiskRules(pluginFiles []string, reporter types.ProgressReporter) risks.RiskRules { customRiskRuleList := make([]string, 0) - customRiskRules := make(map[string]*CustomRisk) + customRiskRules := make(risks.RiskRules) if len(pluginFiles) > 0 { reporter.Info("Loading custom risk rules:", strings.Join(pluginFiles, ", ")) for _, pluginFile := range pluginFiles { if len(pluginFile) > 0 { - runner, loadError := new(runner).Load(pluginFile) + newRunner, loadError := new(runner).Load(pluginFile) if loadError != nil { reporter.Error(fmt.Sprintf("WARNING: Custom risk rule %q not loaded: %v\n", pluginFile, loadError)) } - risk := new(CustomRisk) - runError := runner.Run(nil, &risk, "-get-info") + risk := new(CustomRiskCategory) + runError := newRunner.Run(nil, &risk, "-get-info") if runError != nil { reporter.Error(fmt.Sprintf("WARNING: Failed to get info for custom risk rule %q: %v\n", pluginFile, runError)) } - risk.runner = runner + risk.runner = newRunner customRiskRules[risk.ID] = risk customRiskRuleList = append(customRiskRuleList, risk.ID) reporter.Info("Custom risk rule loaded:", risk.ID) diff --git a/pkg/model/parse.go b/pkg/model/parse.go index c7f4aaae..ef360da8 100644 --- a/pkg/model/parse.go +++ b/pkg/model/parse.go @@ -12,7 +12,7 @@ import ( "time" ) -func ParseModel(config *common.Config, modelInput *input.Model, builtinRiskRules map[string]risks.RiskRule, customRiskRules map[string]*CustomRisk) (*types.ParsedModel, error) { +func ParseModel(config *common.Config, modelInput *input.Model, builtinRiskRules risks.RiskRules, customRiskRules risks.RiskRules) (*types.Model, error) { technologies := make(types.TechnologyMap) technologiesLoadError := technologies.LoadWithConfig(config, "technologies.yaml") if technologiesLoadError != nil { @@ -35,7 +35,7 @@ func ParseModel(config *common.Config, modelInput *input.Model, builtinRiskRules } } - parsedModel := types.ParsedModel{ + parsedModel := types.Model{ ThreagileVersion: modelInput.ThreagileVersion, Title: modelInput.Title, Author: modelInput.Author, @@ -63,8 +63,8 @@ func ParseModel(config *common.Config, modelInput *input.Model, builtinRiskRules parsedModel.AllSupportedTags = make(map[string]bool) parsedModel.IncomingTechnicalCommunicationLinksMappedByTargetId = make(map[string][]*types.CommunicationLink) parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId = make(map[string]*types.TrustBoundary) - parsedModel.GeneratedRisksByCategory = make(map[string][]types.Risk) - parsedModel.GeneratedRisksBySyntheticId = make(map[string]types.Risk) + parsedModel.GeneratedRisksByCategory = make(map[string][]*types.Risk) + parsedModel.GeneratedRisksBySyntheticId = make(map[string]*types.Risk) if parsedModel.DiagramTweakNodesep == 0 { parsedModel.DiagramTweakNodesep = 2 @@ -471,7 +471,7 @@ func ParseModel(config *common.Config, modelInput *input.Model, builtinRiskRules parsedModel.TrustBoundaries[id] = trustBoundary for _, technicalAsset := range trustBoundary.TechnicalAssetsInside { parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId[technicalAsset] = trustBoundary - //fmt.Println("Asset "+technicalAsset+" is directly in trust boundary "+trustBoundary.Id) + //fmt.Println("Asset "+technicalAsset+" is directly in trust boundary "+trustBoundary.ID) } } err = parsedModel.CheckNestedTrustBoundariesExisting() @@ -518,62 +518,62 @@ func ParseModel(config *common.Config, modelInput *input.Model, builtinRiskRules parsedModel.SharedRuntimes[id] = sharedRuntime } - parsedModel.BuiltInRiskCategories = make(map[string]types.RiskCategory) for _, rule := range builtinRiskRules { - category := rule.Category() - parsedModel.BuiltInRiskCategories[category.Id] = category + parsedModel.BuiltInRiskCategories = append(parsedModel.BuiltInRiskCategories, rule.Category()) } - parsedModel.IndividualRiskCategories = make(map[string]types.RiskCategory) for _, rule := range customRiskRules { - parsedModel.IndividualRiskCategories[rule.RiskCategory.Id] = rule.RiskCategory + parsedModel.CustomRiskCategories = append(parsedModel.CustomRiskCategories, rule.Category()) } // Individual Risk Categories (just used as regular risk categories) =============================================================================== - // parsedModel.IndividualRiskCategories = make(map[string]types.RiskCategory) - for title, individualCategory := range modelInput.IndividualRiskCategories { - id := fmt.Sprintf("%v", individualCategory.ID) - - function, err := types.ParseRiskFunction(individualCategory.Function) + for _, customRiskCategoryCategory := range modelInput.CustomRiskCategories { + function, err := types.ParseRiskFunction(customRiskCategoryCategory.Function) if err != nil { - return nil, fmt.Errorf("unknown 'function' value of individual risk category %q: %v", title, individualCategory.Function) + return nil, fmt.Errorf("unknown 'function' value of individual risk category %q: %v", customRiskCategoryCategory.Title, customRiskCategoryCategory.Function) } - stride, err := types.ParseSTRIDE(individualCategory.STRIDE) + + stride, err := types.ParseSTRIDE(customRiskCategoryCategory.STRIDE) if err != nil { - return nil, fmt.Errorf("unknown 'stride' value of individual risk category %q: %v", title, individualCategory.STRIDE) - } - - cat := types.RiskCategory{ - Id: id, - Title: title, - Description: withDefault(fmt.Sprintf("%v", individualCategory.Description), title), - Impact: fmt.Sprintf("%v", individualCategory.Impact), - ASVS: fmt.Sprintf("%v", individualCategory.ASVS), - CheatSheet: fmt.Sprintf("%v", individualCategory.CheatSheet), - Action: fmt.Sprintf("%v", individualCategory.Action), - Mitigation: fmt.Sprintf("%v", individualCategory.Mitigation), - Check: fmt.Sprintf("%v", individualCategory.Check), - DetectionLogic: fmt.Sprintf("%v", individualCategory.DetectionLogic), - RiskAssessment: fmt.Sprintf("%v", individualCategory.RiskAssessment), - FalsePositives: fmt.Sprintf("%v", individualCategory.FalsePositives), + return nil, fmt.Errorf("unknown 'stride' value of individual risk category %q: %v", customRiskCategoryCategory.Title, customRiskCategoryCategory.STRIDE) + } + + cat := &types.RiskCategory{ + ID: customRiskCategoryCategory.ID, + Title: customRiskCategoryCategory.Title, + Description: customRiskCategoryCategory.Description, + Impact: customRiskCategoryCategory.Impact, + ASVS: customRiskCategoryCategory.ASVS, + CheatSheet: customRiskCategoryCategory.CheatSheet, + Action: customRiskCategoryCategory.Action, + Mitigation: customRiskCategoryCategory.Mitigation, + Check: customRiskCategoryCategory.Check, + DetectionLogic: customRiskCategoryCategory.DetectionLogic, + RiskAssessment: customRiskCategoryCategory.RiskAssessment, + FalsePositives: customRiskCategoryCategory.FalsePositives, Function: function, STRIDE: stride, - ModelFailurePossibleReason: individualCategory.ModelFailurePossibleReason, - CWE: individualCategory.CWE, + ModelFailurePossibleReason: customRiskCategoryCategory.ModelFailurePossibleReason, + CWE: customRiskCategoryCategory.CWE, } - err = checkIdSyntax(id) + + if cat.Description == "" { + cat.Description = customRiskCategoryCategory.Title + } + + err = checkIdSyntax(customRiskCategoryCategory.ID) if err != nil { return nil, err } - if _, exists := parsedModel.IndividualRiskCategories[id]; exists { - return nil, fmt.Errorf("duplicate id used: %v", id) + + if !parsedModel.CustomRiskCategories.Add(cat) { + return nil, fmt.Errorf("duplicate id used: %v", customRiskCategoryCategory.ID) } - parsedModel.IndividualRiskCategories[id] = cat // NOW THE INDIVIDUAL RISK INSTANCES: //individualRiskInstances := make([]model.Risk, 0) - if individualCategory.RisksIdentified != nil { // TODO: also add syntax checks of input YAML when linked asset is not found or when synthetic-id is already used... - for title, individualRiskInstance := range individualCategory.RisksIdentified { + if customRiskCategoryCategory.RisksIdentified != nil { // TODO: also add syntax checks of input YAML when linked asset is not found or when synthetic-id is already used... + for title, individualRiskInstance := range customRiskCategoryCategory.RisksIdentified { var mostRelevantDataAssetId, mostRelevantTechnicalAssetId, mostRelevantCommunicationLinkId, mostRelevantTrustBoundaryId, mostRelevantSharedRuntimeId string var dataBreachProbability types.DataBreachProbability var dataBreachTechnicalAssetIDs []string @@ -647,10 +647,10 @@ func ParseModel(config *common.Config, modelInput *input.Model, builtinRiskRules } } - parsedModel.GeneratedRisksByCategory[cat.Id] = append(parsedModel.GeneratedRisksByCategory[cat.Id], types.Risk{ - SyntheticId: createSyntheticId(cat.Id, mostRelevantDataAssetId, mostRelevantTechnicalAssetId, mostRelevantCommunicationLinkId, mostRelevantTrustBoundaryId, mostRelevantSharedRuntimeId), - Title: fmt.Sprintf("%v", title), - CategoryId: cat.Id, + parsedModel.GeneratedRisksByCategory[cat.ID] = append(parsedModel.GeneratedRisksByCategory[cat.ID], &types.Risk{ + SyntheticId: createSyntheticId(cat.ID, mostRelevantDataAssetId, mostRelevantTechnicalAssetId, mostRelevantCommunicationLinkId, mostRelevantTrustBoundaryId, mostRelevantSharedRuntimeId), + Title: title, + CategoryId: cat.ID, Severity: severity, ExploitationLikelihood: exploitationLikelihood, ExploitationImpact: exploitationImpact, @@ -667,7 +667,7 @@ func ParseModel(config *common.Config, modelInput *input.Model, builtinRiskRules } // Risk Tracking =============================================================================== - parsedModel.RiskTracking = make(map[string]types.RiskTracking) + parsedModel.RiskTracking = make(map[string]*types.RiskTracking) for syntheticRiskId, riskTracking := range modelInput.RiskTracking { justification := fmt.Sprintf("%v", riskTracking.Justification) checkedBy := fmt.Sprintf("%v", riskTracking.CheckedBy) @@ -686,7 +686,7 @@ func ParseModel(config *common.Config, modelInput *input.Model, builtinRiskRules return nil, fmt.Errorf("unknown 'status' value of risk tracking %q: %v", syntheticRiskId, riskTracking.Status) } - tracking := types.RiskTracking{ + tracking := &types.RiskTracking{ SyntheticRiskId: strings.TrimSpace(syntheticRiskId), Justification: justification, CheckedBy: checkedBy, diff --git a/pkg/model/parse_test.go b/pkg/model/parse_test.go index 1cd3c92b..7353a844 100644 --- a/pkg/model/parse_test.go +++ b/pkg/model/parse_test.go @@ -17,7 +17,7 @@ import ( ) func TestDefaultInputNotFail(t *testing.T) { - parsedModel, err := ParseModel(&common.Config{}, createInputModel(make(map[string]input.TechnicalAsset), make(map[string]input.DataAsset)), make(map[string]risks.RiskRule), make(map[string]*CustomRisk)) + parsedModel, err := ParseModel(&common.Config{}, createInputModel(make(map[string]input.TechnicalAsset), make(map[string]input.DataAsset)), make(risks.RiskRules), make(risks.RiskRules)) assert.NoError(t, err) assert.NotNil(t, parsedModel) @@ -27,7 +27,7 @@ func TestInferConfidentiality_NotSet_NoOthers_ExpectTODO(t *testing.T) { ta := make(map[string]input.TechnicalAsset) da := make(map[string]input.DataAsset) - _, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(map[string]risks.RiskRule), make(map[string]*CustomRisk)) + _, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(risks.RiskRules), make(risks.RiskRules)) // TODO: rename test and check if everyone agree that by default it should be public if there are no other assets assert.NoError(t, err) @@ -58,7 +58,7 @@ func TestInferConfidentiality_ExpectHighestConfidentiality(t *testing.T) { taWithPublicConfidentialityDataAsset.DataAssetsProcessed = append(taWithPublicConfidentialityDataAsset.DataAssetsProcessed, daPublicConfidentiality.ID) ta[taWithPublicConfidentialityDataAsset.ID] = taWithPublicConfidentialityDataAsset - parsedModel, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(map[string]risks.RiskRule), make(map[string]*CustomRisk)) + parsedModel, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(risks.RiskRules), make(risks.RiskRules)) assert.NoError(t, err) assert.Equal(t, types.Confidential, parsedModel.TechnicalAssets[taWithConfidentialConfidentialityDataAsset.ID].Confidentiality) @@ -70,7 +70,7 @@ func TestInferIntegrity_NotSet_NoOthers_ExpectTODO(t *testing.T) { ta := make(map[string]input.TechnicalAsset) da := make(map[string]input.DataAsset) - _, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(map[string]risks.RiskRule), make(map[string]*CustomRisk)) + _, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(risks.RiskRules), make(risks.RiskRules)) // TODO: rename test and check if everyone agree that by default it should be public if there are no other assets assert.NoError(t, err) @@ -101,7 +101,7 @@ func TestInferIntegrity_ExpectHighestIntegrity(t *testing.T) { taWithArchiveIntegrityDataAsset.DataAssetsProcessed = append(taWithArchiveIntegrityDataAsset.DataAssetsProcessed, daArchiveIntegrity.ID) ta[taWithArchiveIntegrityDataAsset.ID] = taWithArchiveIntegrityDataAsset - parsedModel, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(map[string]risks.RiskRule), make(map[string]*CustomRisk)) + parsedModel, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(risks.RiskRules), make(risks.RiskRules)) assert.NoError(t, err) assert.Equal(t, types.Critical, parsedModel.TechnicalAssets[taWithCriticalIntegrityDataAsset.ID].Integrity) @@ -113,7 +113,7 @@ func TestInferAvailability_NotSet_NoOthers_ExpectTODO(t *testing.T) { ta := make(map[string]input.TechnicalAsset) da := make(map[string]input.DataAsset) - _, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(map[string]risks.RiskRule), make(map[string]*CustomRisk)) + _, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(risks.RiskRules), make(risks.RiskRules)) assert.NoError(t, err) } @@ -143,7 +143,7 @@ func TestInferAvailability_ExpectHighestAvailability(t *testing.T) { taWithArchiveAvailabilityDataAsset.DataAssetsProcessed = append(taWithArchiveAvailabilityDataAsset.DataAssetsProcessed, daArchiveAvailability.ID) ta[taWithArchiveAvailabilityDataAsset.ID] = taWithArchiveAvailabilityDataAsset - parsedModel, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(map[string]risks.RiskRule), make(map[string]*CustomRisk)) + parsedModel, err := ParseModel(&common.Config{}, createInputModel(ta, da), make(risks.RiskRules), make(risks.RiskRules)) assert.NoError(t, err) assert.Equal(t, types.Critical, parsedModel.TechnicalAssets[taWithCriticalAvailabilityDataAsset.ID].Availability) diff --git a/pkg/model/read.go b/pkg/model/read.go index de2e7178..f6a90cbf 100644 --- a/pkg/model/read.go +++ b/pkg/model/read.go @@ -13,10 +13,10 @@ import ( type ReadResult struct { ModelInput *input.Model - ParsedModel *types.ParsedModel + ParsedModel *types.Model IntroTextRAA string - BuiltinRiskRules map[string]risks.RiskRule - CustomRiskRules map[string]*CustomRisk + BuiltinRiskRules risks.RiskRules + CustomRiskRules risks.RiskRules } func (what ReadResult) ExplainRisk(cfg *common.Config, risk string, reporter common.DefaultProgressReporter) error { @@ -29,10 +29,7 @@ func ReadAndAnalyzeModel(config *common.Config, progressReporter types.ProgressR progressReporter.Infof("Writing into output directory: %v", config.OutputFolder) progressReporter.Infof("Parsing model: %v", config.InputFile) - builtinRiskRules := make(map[string]risks.RiskRule) - for _, rule := range risks.GetBuiltInRiskRules() { - builtinRiskRules[rule.Category().Id] = rule - } + builtinRiskRules := risks.GetBuiltInRiskRules() customRiskRules := LoadCustomRiskRules(config.RiskRulesPlugins, progressReporter) modelInput := new(input.Model).Defaults() @@ -48,7 +45,7 @@ func ReadAndAnalyzeModel(config *common.Config, progressReporter types.ProgressR introTextRAA := applyRAA(parsedModel, config.PluginFolder, config.RAAPlugin, progressReporter) - applyRiskGeneration(parsedModel, customRiskRules, builtinRiskRules, config.SkipRiskRules, progressReporter) + applyRiskGeneration(parsedModel, builtinRiskRules.Merge(customRiskRules), config.SkipRiskRules, progressReporter) err := parsedModel.ApplyWildcardRiskTrackingEvaluation(config.IgnoreOrphanedRiskTracking, progressReporter) if err != nil { return nil, fmt.Errorf("unable to apply wildcard risk tracking evaluation: %v", err) @@ -76,59 +73,30 @@ func ReadAndAnalyzeModel(config *common.Config, progressReporter types.ProgressR }, nil } -func applyRisk(parsedModel *types.ParsedModel, rule risks.RiskRule, skippedRules *map[string]bool) { - id := rule.Category().Id - _, ok := (*skippedRules)[id] - - if ok { - fmt.Printf("Skipping risk rule %q\n", rule.Category().Id) - delete(*skippedRules, rule.Category().Id) - } else { - parsedModel.AddToListOfSupportedTags(rule.SupportedTags()) - generatedRisks := rule.GenerateRisks(parsedModel) - if generatedRisks != nil { - if len(generatedRisks) > 0 { - parsedModel.GeneratedRisksByCategory[rule.Category().Id] = generatedRisks - } - } else { - fmt.Printf("Failed to generate risks for %q\n", id) - } - } -} - -// TODO: refactor skipRiskRules to be a string array instead of a comma-separated string -func applyRiskGeneration(parsedModel *types.ParsedModel, customRiskRules map[string]*CustomRisk, - builtinRiskRules map[string]risks.RiskRule, - skipRiskRules string, +func applyRiskGeneration(parsedModel *types.Model, rules risks.RiskRules, + skipRiskRules []string, progressReporter types.ProgressReporter) { progressReporter.Info("Applying risk generation") skippedRules := make(map[string]bool) if len(skipRiskRules) > 0 { - for _, id := range strings.Split(skipRiskRules, ",") { + for _, id := range skipRiskRules { skippedRules[id] = true } } - for _, rule := range builtinRiskRules { - applyRisk(parsedModel, rule, &skippedRules) - } - - // NOW THE CUSTOM RISK RULES (if any) - for id, customRule := range customRiskRules { + for id, rule := range rules { _, ok := skippedRules[id] if ok { - progressReporter.Infof("Skipping custom risk rule: %v", id) + progressReporter.Infof("Skipping risk rule: %v", id) delete(skippedRules, id) - } else { - progressReporter.Infof("Executing custom risk rule: %v", id) - parsedModel.AddToListOfSupportedTags(customRule.SupportedTags()) - customRisks := customRule.GenerateRisks(parsedModel) - if len(customRisks) > 0 { - parsedModel.GeneratedRisksByCategory[customRule.RiskCategory.Id] = customRisks - } - - progressReporter.Infof("Added custom risks: %v", len(customRisks)) + continue + } + + parsedModel.AddToListOfSupportedTags(rule.SupportedTags()) + newRisks := rule.GenerateRisks(parsedModel) + if len(newRisks) > 0 { + parsedModel.GeneratedRisksByCategory[id] = newRisks } } @@ -151,7 +119,7 @@ func applyRiskGeneration(parsedModel *types.ParsedModel, customRiskRules map[str } } -func applyRAA(parsedModel *types.ParsedModel, binFolder, raaPlugin string, progressReporter types.ProgressReporter) string { +func applyRAA(parsedModel *types.Model, binFolder, raaPlugin string, progressReporter types.ProgressReporter) string { progressReporter.Infof("Applying RAA calculation: %v", raaPlugin) runner, loadError := new(runner).Load(filepath.Join(binFolder, raaPlugin)) diff --git a/pkg/report/excel.go b/pkg/report/excel.go index 10e2ee14..eb34e086 100644 --- a/pkg/report/excel.go +++ b/pkg/report/excel.go @@ -12,7 +12,7 @@ import ( "unicode/utf8" ) -func WriteRisksExcelToFile(parsedModel *types.ParsedModel, filename string, config *common.Config) error { +func WriteRisksExcelToFile(parsedModel *types.Model, filename string, config *common.Config) error { columns := new(ExcelColumns).GetColumns() excel := excelize.NewFile() sheetName := parsedModel.Title @@ -98,13 +98,11 @@ func WriteRisksExcelToFile(parsedModel *types.ParsedModel, filename string, conf } date := "" - riskTracking := risk.GetRiskTracking(parsedModel) + riskTracking := risk.GetRiskTrackingWithDefault(parsedModel) if !riskTracking.Date.IsZero() { date = riskTracking.Date.Format("2006-01-02") } - riskTrackingStatus := risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) - riskItems = append(riskItems, RiskItem{ Columns: []string{ risk.Severity.Title(), @@ -122,13 +120,13 @@ func WriteRisksExcelToFile(parsedModel *types.ParsedModel, filename string, conf category.Mitigation, category.Check, risk.SyntheticId, - riskTrackingStatus.Title(), + riskTracking.Status.Title(), riskTracking.Justification, date, riskTracking.CheckedBy, riskTracking.Ticket, }, - Status: riskTrackingStatus, + Status: riskTracking.Status, Severity: risk.Severity, }) } @@ -246,7 +244,7 @@ func WriteRisksExcelToFile(parsedModel *types.ParsedModel, filename string, conf return nil } -func WriteTagsExcelToFile(parsedModel *types.ParsedModel, filename string) error { // TODO: eventually when len(sortedTagsAvailable) == 0 is: write a hint in the Excel that no tags are used +func WriteTagsExcelToFile(parsedModel *types.Model, filename string) error { // TODO: eventually when len(sortedTagsAvailable) == 0 is: write a hint in the Excel that no tags are used excelRow := 0 excel := excelize.NewFile() sheetName := parsedModel.Title @@ -377,7 +375,7 @@ func WriteTagsExcelToFile(parsedModel *types.ParsedModel, filename string) error return nil } -func sortedTrustBoundariesByTitle(parsedModel *types.ParsedModel) []*types.TrustBoundary { +func sortedTrustBoundariesByTitle(parsedModel *types.Model) []*types.TrustBoundary { boundaries := make([]*types.TrustBoundary, 0) for _, boundary := range parsedModel.TrustBoundaries { boundaries = append(boundaries, boundary) @@ -386,7 +384,7 @@ func sortedTrustBoundariesByTitle(parsedModel *types.ParsedModel) []*types.Trust return boundaries } -func sortedDataAssetsByTitle(parsedModel *types.ParsedModel) []*types.DataAsset { +func sortedDataAssetsByTitle(parsedModel *types.Model) []*types.DataAsset { assets := make([]*types.DataAsset, 0) for _, asset := range parsedModel.DataAssets { assets = append(assets, asset) @@ -395,7 +393,7 @@ func sortedDataAssetsByTitle(parsedModel *types.ParsedModel) []*types.DataAsset return assets } -func writeRow(excel *excelize.File, excelRow *int, sheetName string, axis string, styleBlackLeftBold int, styleBlackCenter int, +func writeRow(excel *excelize.File, excelRow *int, sheetName string, _ string, styleBlackLeftBold int, styleBlackCenter int, sortedTags []string, assetTitle string, tagsUsed []string) error { *excelRow++ diff --git a/pkg/report/graphviz.go b/pkg/report/graphviz.go index 07644026..8180734e 100644 --- a/pkg/report/graphviz.go +++ b/pkg/report/graphviz.go @@ -14,7 +14,7 @@ import ( "github.com/threagile/threagile/pkg/security/types" ) -func WriteDataFlowDiagramGraphvizDOT(parsedModel *types.ParsedModel, +func WriteDataFlowDiagramGraphvizDOT(parsedModel *types.Model, diagramFilenameDOT string, dpi int, addModelTitle bool, progressReporter progressReporter) (*os.File, error) { progressReporter.Info("Writing data flow diagram input") @@ -209,7 +209,7 @@ func WriteDataFlowDiagramGraphvizDOT(parsedModel *types.ParsedModel, for _, dataFlow := range technicalAsset.CommunicationLinks { sourceId := technicalAsset.Id targetId := dataFlow.TargetId - //log.Println("About to add link from", sourceId, "to", targetId, "with id", dataFlow.Id) + //log.Println("About to add link from", sourceId, "to", targetId, "with id", dataFlow.ID) var arrowStyle, arrowColor, readOrWriteHead, readOrWriteTail string if dataFlow.Readonly { readOrWriteHead = "empty" @@ -272,7 +272,7 @@ func WriteDataFlowDiagramGraphvizDOT(parsedModel *types.ParsedModel, // Pen Widths: -func determineArrowPenWidth(cl *types.CommunicationLink, parsedModel *types.ParsedModel) string { +func determineArrowPenWidth(cl *types.CommunicationLink, parsedModel *types.Model) string { if determineArrowColor(cl, parsedModel) == Pink { return fmt.Sprintf("%f", 3.0) } @@ -282,7 +282,7 @@ func determineArrowPenWidth(cl *types.CommunicationLink, parsedModel *types.Pars return fmt.Sprintf("%f", 1.5) } -func determineLabelColor(cl *types.CommunicationLink, parsedModel *types.ParsedModel) string { +func determineLabelColor(cl *types.CommunicationLink, parsedModel *types.Model) string { // TODO: Just move into main.go and let the generated risk determine the color, don't duplicate the logic here /* if dataFlow.Protocol.IsEncrypted() { @@ -325,7 +325,7 @@ func determineArrowLineStyle(cl *types.CommunicationLink) string { } // pink when model forgery attempt (i.e. nothing being sent and received) -func determineArrowColor(cl *types.CommunicationLink, parsedModel *types.ParsedModel) string { +func determineArrowColor(cl *types.CommunicationLink, parsedModel *types.Model) string { // TODO: Just move into main.go and let the generated risk determine the color, don't duplicate the logic here if len(cl.DataAssetsSent) == 0 && len(cl.DataAssetsReceived) == 0 || cl.Protocol == types.UnknownProtocol { @@ -444,7 +444,7 @@ func GenerateDataFlowDiagramGraphvizImage(dotFile *os.File, targetDir string, return nil } -func makeDiagramSameRankNodeTweaks(parsedModel *types.ParsedModel) (string, error) { +func makeDiagramSameRankNodeTweaks(parsedModel *types.Model) (string, error) { // see https://stackoverflow.com/questions/25734244/how-do-i-place-nodes-on-the-same-level-in-dot tweak := "" if len(parsedModel.DiagramTweakSameRankAssets) > 0 { @@ -470,7 +470,7 @@ func makeDiagramSameRankNodeTweaks(parsedModel *types.ParsedModel) (string, erro return tweak, nil } -func makeDiagramInvisibleConnectionsTweaks(parsedModel *types.ParsedModel) (string, error) { +func makeDiagramInvisibleConnectionsTweaks(parsedModel *types.Model) (string, error) { // see https://stackoverflow.com/questions/2476575/how-to-control-node-placement-in-graphviz-i-e-avoid-edge-crossings tweak := "" if len(parsedModel.DiagramTweakInvisibleConnectionsBetweenAssets) > 0 { @@ -493,7 +493,7 @@ func makeDiagramInvisibleConnectionsTweaks(parsedModel *types.ParsedModel) (stri return tweak, nil } -func WriteDataAssetDiagramGraphvizDOT(parsedModel *types.ParsedModel, diagramFilenameDOT string, dpi int, +func WriteDataAssetDiagramGraphvizDOT(parsedModel *types.Model, diagramFilenameDOT string, dpi int, progressReporter progressReporter) (*os.File, error) { progressReporter.Info("Writing data asset diagram input") @@ -584,7 +584,7 @@ func WriteDataAssetDiagramGraphvizDOT(parsedModel *types.ParsedModel, diagramFil return file, nil } -func makeDataAssetNode(parsedModel *types.ParsedModel, dataAsset *types.DataAsset) string { +func makeDataAssetNode(parsedModel *types.Model, dataAsset *types.DataAsset) string { var color string switch dataAsset.IdentifiedDataBreachProbabilityStillAtRisk(parsedModel) { case types.Probable: @@ -602,7 +602,7 @@ func makeDataAssetNode(parsedModel *types.ParsedModel, dataAsset *types.DataAsse return " " + hash(dataAsset.Id) + ` [ label=<` + encode(dataAsset.Title) + `> penwidth="3.0" style="filled" fillcolor="` + color + `" color="` + color + "\"\n ]; " } -func makeTechAssetNode(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset, simplified bool) string { +func makeTechAssetNode(parsedModel *types.Model, technicalAsset *types.TechnicalAsset, simplified bool) string { if simplified { color := rgbHexColorOutOfScope() if !technicalAsset.OutOfScope { @@ -676,7 +676,7 @@ func determineShapeStyle(ta *types.TechnicalAsset) string { return "filled" } -func determineShapeFillColor(ta *types.TechnicalAsset, parsedModel *types.ParsedModel) string { +func determineShapeFillColor(ta *types.TechnicalAsset, parsedModel *types.Model) string { fillColor := VeryLightGray if (len(ta.DataAssetsProcessed) == 0 && len(ta.DataAssetsStored) == 0) || ta.Technologies.IsUnknown() { fillColor = LightPink // lightPink, because it's strange when too many technical assets process no data... some ok, but many in a diagram ist a sign of model forgery... @@ -701,7 +701,7 @@ func determineShapeFillColor(ta *types.TechnicalAsset, parsedModel *types.Parsed return fillColor } -func determineShapeBorderPenWidth(ta *types.TechnicalAsset, parsedModel *types.ParsedModel) string { +func determineShapeBorderPenWidth(ta *types.TechnicalAsset, parsedModel *types.Model) string { if determineShapeBorderColor(ta, parsedModel) == Pink { return fmt.Sprintf("%f", 3.5) } @@ -714,7 +714,7 @@ func determineShapeBorderPenWidth(ta *types.TechnicalAsset, parsedModel *types.P // red when mission-critical integrity, but still unauthenticated (non-readonly) channels access it // amber when critical integrity, but still unauthenticated (non-readonly) channels access it // pink when model forgery attempt (i.e. nothing being processed) -func determineShapeBorderColor(ta *types.TechnicalAsset, parsedModel *types.ParsedModel) string { +func determineShapeBorderColor(ta *types.TechnicalAsset, parsedModel *types.Model) string { // Check for red if ta.Confidentiality == types.StrictlyConfidential { return Red @@ -736,7 +736,7 @@ func determineShapeBorderColor(ta *types.TechnicalAsset, parsedModel *types.Pars return Black /* if what.Integrity == MissionCritical { - for _, dataFlow := range IncomingTechnicalCommunicationLinksMappedByTargetId[what.Id] { + for _, dataFlow := range IncomingTechnicalCommunicationLinksMappedByTargetId[what.ID] { if !dataFlow.Readonly && dataFlow.Authentication == NoneAuthentication { return Red } @@ -744,7 +744,7 @@ func determineShapeBorderColor(ta *types.TechnicalAsset, parsedModel *types.Pars } if what.Integrity == Critical { - for _, dataFlow := range IncomingTechnicalCommunicationLinksMappedByTargetId[what.Id] { + for _, dataFlow := range IncomingTechnicalCommunicationLinksMappedByTargetId[what.ID] { if !dataFlow.Readonly && dataFlow.Authentication == NoneAuthentication { return Amber } @@ -775,7 +775,7 @@ func determineShapeBorderLineStyle(ta *types.TechnicalAsset) string { } // red when >= confidential data stored in unencrypted technical asset -func determineTechnicalAssetLabelColor(ta *types.TechnicalAsset, model *types.ParsedModel) string { +func determineTechnicalAssetLabelColor(ta *types.TechnicalAsset, model *types.Model) string { // TODO: Just move into main.go and let the generated risk determine the color, don't duplicate the logic here // Check for red if ta.Integrity == types.MissionCritical { diff --git a/pkg/report/json.go b/pkg/report/json.go index 8a27d655..1bd73ce4 100644 --- a/pkg/report/json.go +++ b/pkg/report/json.go @@ -8,7 +8,7 @@ import ( "github.com/threagile/threagile/pkg/security/types" ) -func WriteRisksJSON(parsedModel *types.ParsedModel, filename string) error { +func WriteRisksJSON(parsedModel *types.Model, filename string) error { /* remainingRisks := make([]model.Risk, 0) for _, category := range model.SortedRiskCategories() { @@ -31,7 +31,7 @@ func WriteRisksJSON(parsedModel *types.ParsedModel, filename string) error { // TODO: also a "data assets" json? -func WriteTechnicalAssetsJSON(parsedModel *types.ParsedModel, filename string) error { +func WriteTechnicalAssetsJSON(parsedModel *types.Model, filename string) error { jsonBytes, err := json.Marshal(parsedModel.TechnicalAssets) if err != nil { return fmt.Errorf("failed to marshal technical assets to JSON: %w", err) @@ -43,7 +43,7 @@ func WriteTechnicalAssetsJSON(parsedModel *types.ParsedModel, filename string) e return nil } -func WriteStatsJSON(parsedModel *types.ParsedModel, filename string) error { +func WriteStatsJSON(parsedModel *types.Model, filename string) error { jsonBytes, err := json.Marshal(types.OverallRiskStatistics(parsedModel)) if err != nil { return fmt.Errorf("failed to marshal stats to JSON: %w", err) diff --git a/pkg/report/report.go b/pkg/report/report.go index 25c699da..017311c7 100644 --- a/pkg/report/report.go +++ b/pkg/report/report.go @@ -16,7 +16,6 @@ import ( "github.com/jung-kurt/gofpdf" "github.com/jung-kurt/gofpdf/contrib/gofpdi" "github.com/threagile/threagile/pkg/docs" - "github.com/threagile/threagile/pkg/model" "github.com/threagile/threagile/pkg/security/risks" "github.com/threagile/threagile/pkg/security/types" "github.com/wcharczuk/go-chart" @@ -54,13 +53,13 @@ func (r *pdfReporter) WriteReportPDF(reportFilename string, dataFlowDiagramFilenamePNG string, dataAssetDiagramFilenamePNG string, modelFilename string, - skipRiskRules string, + skipRiskRules []string, buildTimestamp string, modelHash string, introTextRAA string, - customRiskRules map[string]*model.CustomRisk, + customRiskRules risks.RiskRules, tempFolder string, - model *types.ParsedModel) error { + model *types.Model) error { defer func() { value := recover() if value != nil { @@ -113,7 +112,7 @@ func (r *pdfReporter) WriteReportPDF(reportFilename string, return nil } -func (r *pdfReporter) createPdfAndInitMetadata(model *types.ParsedModel) { +func (r *pdfReporter) createPdfAndInitMetadata(model *types.Model) { r.pdf = gofpdf.New("P", "mm", "A4", "") r.pdf.SetCreator(model.Author.Homepage, true) r.pdf.SetAuthor(model.Author.Name, true) @@ -148,7 +147,7 @@ func (r *pdfReporter) createPdfAndInitMetadata(model *types.ParsedModel) { r.linkCounter = 1 // link counting starts at 1 via r.pdf.AddLink } -func (r *pdfReporter) addBreadcrumb(parsedModel *types.ParsedModel) { +func (r *pdfReporter) addBreadcrumb(parsedModel *types.Model) { if len(r.currentChapterTitleBreadcrumb) > 0 { uni := r.pdf.UnicodeTranslatorFromDescriptor("") r.pdf.SetFont("Helvetica", "", 10) @@ -173,7 +172,7 @@ func (r *pdfReporter) parseBackgroundTemplate(templateFilename string) { r.diagramLegendTemplateId = gofpdi.ImportPage(r.pdf, templateFilename, 3, "/MediaBox") } -func (r *pdfReporter) createCover(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createCover(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") r.pdf.AddPage() gofpdi.UseImportedTemplate(r.pdf, r.coverTemplateId, 0, 0, 0, 300) @@ -195,7 +194,7 @@ func (r *pdfReporter) createCover(parsedModel *types.ParsedModel) { r.pdf.SetTextColor(0, 0, 0) } -func (r *pdfReporter) createTableOfContents(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createTableOfContents(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") r.pdf.AddPage() r.currentChapterTitleBreadcrumb = "Table of Contents" @@ -418,10 +417,10 @@ func (r *pdfReporter) createTableOfContents(parsedModel *types.ParsedModel) { suffix += "s" } r.pdf.Text(11, y, " "+uni(category.Title)+": "+suffix) - r.pdf.Text(175, y, "{"+category.Id+"}") + r.pdf.Text(175, y, "{"+category.ID+"}") r.pdf.Line(15.6, y+1.3, 11+171.5, y+1.3) - r.tocLinkIdByAssetId[category.Id] = r.pdf.AddLink() - r.pdf.Link(10, y-5, 172.5, 6.5, r.tocLinkIdByAssetId[category.Id]) + r.tocLinkIdByAssetId[category.ID] = r.pdf.AddLink() + r.pdf.Link(10, y-5, 172.5, 6.5, r.tocLinkIdByAssetId[category.ID]) } } @@ -638,7 +637,7 @@ func (r *pdfReporter) createTableOfContents(parsedModel *types.ParsedModel) { // by the current page number. --> See the "r.pdf.RegisterAlias()" calls during the PDF creation in this file } -func sortedTechnicalAssetsByRiskSeverityAndTitle(parsedModel *types.ParsedModel) []*types.TechnicalAsset { +func sortedTechnicalAssetsByRiskSeverityAndTitle(parsedModel *types.Model) []*types.TechnicalAsset { assets := make([]*types.TechnicalAsset, 0) for _, asset := range parsedModel.TechnicalAssets { assets = append(assets, asset) @@ -647,7 +646,7 @@ func sortedTechnicalAssetsByRiskSeverityAndTitle(parsedModel *types.ParsedModel) return assets } -func sortedDataAssetsByDataBreachProbabilityAndTitle(parsedModel *types.ParsedModel) []*types.DataAsset { +func sortedDataAssetsByDataBreachProbabilityAndTitle(parsedModel *types.Model) []*types.DataAsset { assets := make([]*types.DataAsset, 0) for _, asset := range parsedModel.DataAssets { assets = append(assets, asset) @@ -669,7 +668,7 @@ func (r *pdfReporter) defineLinkTarget(alias string) { r.linkCounter++ } -func (r *pdfReporter) createDisclaimer(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createDisclaimer(parsedModel *types.Model) { r.pdf.AddPage() r.currentChapterTitleBreadcrumb = "Disclaimer" r.defineLinkTarget("{disclaimer}") @@ -722,7 +721,7 @@ func (r *pdfReporter) createDisclaimer(parsedModel *types.ParsedModel) { r.pdfColorBlack() } -func (r *pdfReporter) createManagementSummary(parsedModel *types.ParsedModel, tempFolder string) error { +func (r *pdfReporter) createManagementSummary(parsedModel *types.Model, tempFolder string) error { uni := r.pdf.UnicodeTranslatorFromDescriptor("") r.pdf.SetTextColor(0, 0, 0) title := "Management Summary" @@ -917,7 +916,7 @@ func (r *pdfReporter) createManagementSummary(parsedModel *types.ParsedModel, te return nil } -func (r *pdfReporter) createRiskMitigationStatus(parsedModel *types.ParsedModel, tempFolder string) error { +func (r *pdfReporter) createRiskMitigationStatus(parsedModel *types.Model, tempFolder string) error { r.pdf.SetTextColor(0, 0, 0) stillAtRisk := types.FilteredByStillAtRisk(parsedModel) count := len(stillAtRisk) @@ -1274,15 +1273,15 @@ func makeColor(hexColor string) drawing.Color { return drawing.ColorFromHex(hexColor[i:]) // = remove first char, which is # in rgb hex here } -func (r *pdfReporter) createImpactInitialRisks(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createImpactInitialRisks(parsedModel *types.Model) { r.renderImpactAnalysis(parsedModel, true) } -func (r *pdfReporter) createImpactRemainingRisks(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createImpactRemainingRisks(parsedModel *types.Model) { r.renderImpactAnalysis(parsedModel, false) } -func (r *pdfReporter) renderImpactAnalysis(parsedModel *types.ParsedModel, initialRisks bool) { +func (r *pdfReporter) renderImpactAnalysis(parsedModel *types.Model, initialRisks bool) { r.pdf.SetTextColor(0, 0, 0) count, catCount := types.TotalRiskCount(parsedModel), len(parsedModel.GeneratedRisksByCategory) if !initialRisks { @@ -1342,7 +1341,7 @@ func (r *pdfReporter) renderImpactAnalysis(parsedModel *types.ParsedModel, initi r.pdf.SetDashPattern([]float64{}, 0) } -func (r *pdfReporter) createOutOfScopeAssets(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createOutOfScopeAssets(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") r.pdf.SetTextColor(0, 0, 0) assets := "Assets" @@ -1405,7 +1404,7 @@ func (r *pdfReporter) createOutOfScopeAssets(parsedModel *types.ParsedModel) { r.pdf.SetDashPattern([]float64{}, 0) } -func sortedTechnicalAssetsByRAAAndTitle(parsedModel *types.ParsedModel) []*types.TechnicalAsset { +func sortedTechnicalAssetsByRAAAndTitle(parsedModel *types.Model) []*types.TechnicalAsset { assets := make([]*types.TechnicalAsset, 0) for _, asset := range parsedModel.TechnicalAssets { assets = append(assets, asset) @@ -1414,7 +1413,7 @@ func sortedTechnicalAssetsByRAAAndTitle(parsedModel *types.ParsedModel) []*types return assets } -func (r *pdfReporter) createModelFailures(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createModelFailures(parsedModel *types.Model) { r.pdf.SetTextColor(0, 0, 0) modelFailures := types.FlattenRiskSlice(types.FilterByModelFailures(parsedModel, parsedModel.GeneratedRisksByCategory)) risksStr := "Risks" @@ -1465,7 +1464,7 @@ func (r *pdfReporter) createModelFailures(parsedModel *types.ParsedModel) { r.pdf.SetDashPattern([]float64{}, 0) } -func (r *pdfReporter) createRAA(parsedModel *types.ParsedModel, introTextRAA string) { +func (r *pdfReporter) createRAA(parsedModel *types.Model, introTextRAA string) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") r.pdf.SetTextColor(0, 0, 0) chapTitle := "RAA Analysis" @@ -1603,7 +1602,7 @@ func createDataRiskQuickWins() { strBuilder.WriteString(uni(technicalAsset.Description)) html.Write(5, strBuilder.String()) strBuilder.Reset() - r.pdf.Link(9, posY, 190, r.pdf.GetY()-posY+4, tocLinkIdByAssetId[technicalAsset.Id]) + r.pdf.Link(9, posY, 190, r.pdf.GetY()-posY+4, tocLinkIdByAssetId[technicalAsset.ID]) } r.pdf.SetDrawColor(0, 0, 0) @@ -1611,12 +1610,12 @@ func createDataRiskQuickWins() { } */ -func (r *pdfReporter) addCategories(parsedModel *types.ParsedModel, riskCategories []types.RiskCategory, severity types.RiskSeverity, bothInitialAndRemainingRisks bool, initialRisks bool, describeImpact bool, describeDescription bool) { +func (r *pdfReporter) addCategories(parsedModel *types.Model, riskCategories []*types.RiskCategory, severity types.RiskSeverity, bothInitialAndRemainingRisks bool, initialRisks bool, describeImpact bool, describeDescription bool) { html := r.pdf.HTMLBasicNew() var strBuilder strings.Builder sort.Sort(types.ByRiskCategoryTitleSort(riskCategories)) for _, riskCategory := range riskCategories { - risksStr := parsedModel.GeneratedRisksByCategory[riskCategory.Id] + risksStr := parsedModel.GeneratedRisksByCategory[riskCategory.ID] if !initialRisks { risksStr = types.ReduceToOnlyStillAtRisk(parsedModel, risksStr) } @@ -1704,7 +1703,7 @@ func (r *pdfReporter) addCategories(parsedModel *types.ParsedModel, riskCategori } html.Write(5, strBuilder.String()) strBuilder.Reset() - r.pdf.Link(9, posY, 190, r.pdf.GetY()-posY+4, r.tocLinkIdByAssetId[riskCategory.Id]) + r.pdf.Link(9, posY, 190, r.pdf.GetY()-posY+4, r.tocLinkIdByAssetId[riskCategory.ID]) } } @@ -1717,7 +1716,7 @@ func firstParagraph(text string) string { return match[1] } -func (r *pdfReporter) createAssignmentByFunction(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createAssignmentByFunction(parsedModel *types.Model) { r.pdf.SetTextColor(0, 0, 0) title := "Assignment by Function" r.addHeadline(title, false) @@ -1863,7 +1862,7 @@ func (r *pdfReporter) createAssignmentByFunction(parsedModel *types.ParsedModel) r.pdf.SetDashPattern([]float64{}, 0) } -func (r *pdfReporter) createSTRIDE(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createSTRIDE(parsedModel *types.Model) { r.pdf.SetTextColor(0, 0, 0) title := "STRIDE Classification of Identified Risks" r.addHeadline(title, false) @@ -2068,7 +2067,7 @@ func (r *pdfReporter) createSTRIDE(parsedModel *types.ParsedModel) { r.pdf.SetDashPattern([]float64{}, 0) } -func (r *pdfReporter) createSecurityRequirements(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createSecurityRequirements(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") r.pdf.SetTextColor(0, 0, 0) chapTitle := "Security Requirements" @@ -2100,7 +2099,7 @@ func (r *pdfReporter) createSecurityRequirements(parsedModel *types.ParsedModel) "taken into account as well. Also custom individual security requirements might exist for the project.") } -func sortedKeysOfSecurityRequirements(parsedModel *types.ParsedModel) []string { +func sortedKeysOfSecurityRequirements(parsedModel *types.Model) []string { keys := make([]string, 0) for k := range parsedModel.SecurityRequirements { keys = append(keys, k) @@ -2109,7 +2108,7 @@ func sortedKeysOfSecurityRequirements(parsedModel *types.ParsedModel) []string { return keys } -func (r *pdfReporter) createAbuseCases(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createAbuseCases(parsedModel *types.Model) { r.pdf.SetTextColor(0, 0, 0) chapTitle := "Abuse Cases" r.addHeadline(chapTitle, false) @@ -2140,7 +2139,7 @@ func (r *pdfReporter) createAbuseCases(parsedModel *types.ParsedModel) { "taken into account as well. Also custom individual abuse cases might exist for the project.") } -func sortedKeysOfAbuseCases(parsedModel *types.ParsedModel) []string { +func sortedKeysOfAbuseCases(parsedModel *types.Model) []string { keys := make([]string, 0) for k := range parsedModel.AbuseCases { keys = append(keys, k) @@ -2149,7 +2148,7 @@ func sortedKeysOfAbuseCases(parsedModel *types.ParsedModel) []string { return keys } -func (r *pdfReporter) createQuestions(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createQuestions(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") r.pdf.SetTextColor(0, 0, 0) questions := "Questions" @@ -2197,7 +2196,7 @@ func (r *pdfReporter) createQuestions(parsedModel *types.ParsedModel) { } } -func sortedKeysOfQuestions(parsedModel *types.ParsedModel) []string { +func sortedKeysOfQuestions(parsedModel *types.Model) []string { keys := make([]string, 0) for k := range parsedModel.Questions { keys = append(keys, k) @@ -2206,7 +2205,7 @@ func sortedKeysOfQuestions(parsedModel *types.ParsedModel) []string { return keys } -func (r *pdfReporter) createTagListing(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createTagListing(parsedModel *types.Model) { r.pdf.SetTextColor(0, 0, 0) chapTitle := "Tag Listing" r.addHeadline(chapTitle, false) @@ -2274,7 +2273,7 @@ func (r *pdfReporter) createTagListing(parsedModel *types.ParsedModel) { } } -func sortedSharedRuntimesByTitle(parsedModel *types.ParsedModel) []*types.SharedRuntime { +func sortedSharedRuntimesByTitle(parsedModel *types.Model) []*types.SharedRuntime { result := make([]*types.SharedRuntime, 0) for _, runtime := range parsedModel.SharedRuntimes { result = append(result, runtime) @@ -2283,7 +2282,7 @@ func sortedSharedRuntimesByTitle(parsedModel *types.ParsedModel) []*types.Shared return result } -func sortedTechnicalAssetsByTitle(parsedModel *types.ParsedModel) []*types.TechnicalAsset { +func sortedTechnicalAssetsByTitle(parsedModel *types.Model) []*types.TechnicalAsset { assets := make([]*types.TechnicalAsset, 0) for _, asset := range parsedModel.TechnicalAssets { assets = append(assets, asset) @@ -2292,7 +2291,7 @@ func sortedTechnicalAssetsByTitle(parsedModel *types.ParsedModel) []*types.Techn return assets } -func (r *pdfReporter) createRiskCategories(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createRiskCategories(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") // category title title := "Identified Risks by Vulnerability category" @@ -2344,7 +2343,7 @@ func (r *pdfReporter) createRiskCategories(parsedModel *types.ParsedModel) { title := category.Title + ": " + suffix r.addHeadline(uni(title), true) r.pdfColorBlack() - r.defineLinkTarget("{" + category.Id + "}") + r.defineLinkTarget("{" + category.ID + "}") r.currentChapterTitleBreadcrumb = title // category details @@ -2482,7 +2481,7 @@ func (r *pdfReporter) createRiskCategories(parsedModel *types.ParsedModel) { default: r.pdfColorBlack() } - if !risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if !risk.RiskStatus.IsStillAtRisk() { r.pdfColorBlack() } posY := r.pdf.GetY() @@ -2512,9 +2511,9 @@ func (r *pdfReporter) createRiskCategories(parsedModel *types.ParsedModel) { } } -func (r *pdfReporter) writeRiskTrackingStatus(parsedModel *types.ParsedModel, risk types.Risk) { +func (r *pdfReporter) writeRiskTrackingStatus(parsedModel *types.Model, risk *types.Risk) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") - tracking := risk.GetRiskTracking(parsedModel) + tracking := risk.GetRiskTrackingWithDefault(parsedModel) r.pdfColorBlack() r.pdf.CellFormat(10, 6, "", "0", 0, "", false, 0, "") switch tracking.Status { @@ -2559,7 +2558,7 @@ func (r *pdfReporter) writeRiskTrackingStatus(parsedModel *types.ParsedModel, ri r.pdfColorBlack() } -func (r *pdfReporter) createTechnicalAssets(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createTechnicalAssets(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") // category title title := "Identified Risks by Technical Asset" @@ -2706,7 +2705,7 @@ func (r *pdfReporter) createTechnicalAssets(parsedModel *types.ParsedModel) { default: r.pdfColorBlack() } - if !risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if !risk.RiskStatus.IsStillAtRisk() { r.pdfColorBlack() } posY := r.pdf.GetY() @@ -3371,7 +3370,7 @@ func (r *pdfReporter) createTechnicalAssets(parsedModel *types.ParsedModel) { } } -func (r *pdfReporter) createDataAssets(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createDataAssets(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") title := "Identified Data Breach Probabilities by Data Asset" r.pdfColorBlack() @@ -3707,7 +3706,7 @@ func (r *pdfReporter) createDataAssets(parsedModel *types.ParsedModel) { r.pdf.SetFont("Helvetica", "", fontSizeSmall) r.pdf.MultiCell(185, 6, uni(techAssetResponsible.Title)+": "+strconv.Itoa(len(risksResponsibleStillAtRisk))+" / "+strconv.Itoa(len(risksResponsible))+" "+riskStr, "0", "0", false) r.pdf.SetFont("Helvetica", "", fontSizeBody) - r.pdf.Link(20, posY, 180, r.pdf.GetY()-posY, tocLinkIdByAssetId[techAssetResponsible.Id]) + r.pdf.Link(20, posY, 180, r.pdf.GetY()-posY, tocLinkIdByAssetId[techAssetResponsible.ID]) } r.pdfColorBlack() } @@ -3777,7 +3776,7 @@ func (r *pdfReporter) createDataAssets(parsedModel *types.ParsedModel) { default: r.pdfColorBlack() } - if !dataBreachRisk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if !dataBreachRisk.RiskStatus.IsStillAtRisk() { r.pdfColorBlack() } r.pdf.CellFormat(10, 6, "", "0", 0, "", false, 0, "") @@ -3792,7 +3791,7 @@ func (r *pdfReporter) createDataAssets(parsedModel *types.ParsedModel) { } } -func (r *pdfReporter) createTrustBoundaries(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createTrustBoundaries(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") title := "Trust Boundaries" r.pdfColorBlack() @@ -3911,7 +3910,7 @@ func (r *pdfReporter) createTrustBoundaries(parsedModel *types.ParsedModel) { } } -func questionsUnanswered(parsedModel *types.ParsedModel) int { +func questionsUnanswered(parsedModel *types.Model) int { result := 0 for _, answer := range parsedModel.Questions { if len(strings.TrimSpace(answer)) == 0 { @@ -3921,7 +3920,7 @@ func questionsUnanswered(parsedModel *types.ParsedModel) int { return result } -func (r *pdfReporter) createSharedRuntimes(parsedModel *types.ParsedModel) { +func (r *pdfReporter) createSharedRuntimes(parsedModel *types.Model) { uni := r.pdf.UnicodeTranslatorFromDescriptor("") title := "Shared Runtimes" r.pdfColorBlack() @@ -4002,7 +4001,7 @@ func (r *pdfReporter) createSharedRuntimes(parsedModel *types.ParsedModel) { } } -func (r *pdfReporter) createRiskRulesChecked(parsedModel *types.ParsedModel, modelFilename string, skipRiskRules string, buildTimestamp string, modelHash string, customRiskRules map[string]*model.CustomRisk) { +func (r *pdfReporter) createRiskRulesChecked(parsedModel *types.Model, modelFilename string, skipRiskRules []string, buildTimestamp string, modelHash string, customRiskRules risks.RiskRules) { r.pdf.SetTextColor(0, 0, 0) title := "Risk Rules Checked by Threagile" r.addHeadline(title, false) @@ -4031,14 +4030,13 @@ func (r *pdfReporter) createRiskRulesChecked(parsedModel *types.ParsedModel, mod strBuilder.Reset() // TODO use the new run system to discover risk rules instead of hard-coding them here: - skippedRules := strings.Split(skipRiskRules, ",") skipped := "" r.pdf.Ln(-1) for id, customRule := range customRiskRules { r.pdf.Ln(-1) r.pdf.SetFont("Helvetica", "B", fontSizeBody) - if contains(skippedRules, id) { + if contains(skipRiskRules, id) { skipped = "SKIPPED - " } else { skipped = "" @@ -4074,14 +4072,14 @@ func (r *pdfReporter) createRiskRulesChecked(parsedModel *types.ParsedModel, mod r.pdf.MultiCell(160, 6, customRule.Category().RiskAssessment, "0", "0", false) } - for _, key := range sortedKeysOfIndividualRiskCategories(parsedModel) { - individualRiskCategory := parsedModel.IndividualRiskCategories[key] + sort.Sort(types.ByRiskCategoryTitleSort(parsedModel.CustomRiskCategories)) + for _, individualRiskCategory := range parsedModel.CustomRiskCategories { r.pdf.Ln(-1) r.pdf.SetFont("Helvetica", "B", fontSizeBody) r.pdf.CellFormat(190, 3, individualRiskCategory.Title, "0", 0, "", false, 0, "") r.pdf.Ln(-1) r.pdf.SetFont("Helvetica", "", fontSizeSmall) - r.pdf.CellFormat(190, 6, individualRiskCategory.Id, "0", 0, "", false, 0, "") + r.pdf.CellFormat(190, 6, individualRiskCategory.ID, "0", 0, "", false, 0, "") r.pdf.Ln(-1) r.pdf.SetFont("Helvetica", "I", fontSizeBody) r.pdf.CellFormat(190, 6, "Individual Risk category", "0", 0, "", false, 0, "") @@ -4112,7 +4110,7 @@ func (r *pdfReporter) createRiskRulesChecked(parsedModel *types.ParsedModel, mod for _, rule := range risks.GetBuiltInRiskRules() { r.pdf.Ln(-1) r.pdf.SetFont("Helvetica", "B", fontSizeBody) - if contains(skippedRules, rule.Category().Id) { + if contains(skipRiskRules, rule.Category().ID) { skipped = "SKIPPED - " } else { skipped = "" @@ -4120,7 +4118,7 @@ func (r *pdfReporter) createRiskRulesChecked(parsedModel *types.ParsedModel, mod r.pdf.CellFormat(190, 3, skipped+rule.Category().Title, "0", 0, "", false, 0, "") r.pdf.Ln(-1) r.pdf.SetFont("Helvetica", "", fontSizeSmall) - r.pdf.CellFormat(190, 6, rule.Category().Id, "0", 0, "", false, 0, "") + r.pdf.CellFormat(190, 6, rule.Category().ID, "0", 0, "", false, 0, "") r.pdf.Ln(-1) r.pdf.SetFont("Helvetica", "", fontSizeBody) r.pdfColorGray() @@ -4146,7 +4144,7 @@ func (r *pdfReporter) createRiskRulesChecked(parsedModel *types.ParsedModel, mod } } -func (r *pdfReporter) createTargetDescription(parsedModel *types.ParsedModel, baseFolder string) error { +func (r *pdfReporter) createTargetDescription(parsedModel *types.Model, baseFolder string) error { uni := r.pdf.UnicodeTranslatorFromDescriptor("") r.pdf.SetTextColor(0, 0, 0) title := "Application Overview" @@ -4398,15 +4396,6 @@ func (r *pdfReporter) embedDataFlowDiagram(diagramFilenamePNG string, tempFolder } } -func sortedKeysOfIndividualRiskCategories(parsedModel *types.ParsedModel) []string { - keys := make([]string, 0) - for k := range parsedModel.IndividualRiskCategories { - keys = append(keys, k) - } - sort.Strings(keys) - return keys -} - func (r *pdfReporter) embedDataRiskMapping(diagramFilenamePNG string, tempFolder string) { r.pdf.SetTextColor(0, 0, 0) title := "Data Mapping" diff --git a/pkg/script/common/scope.go b/pkg/script/common/scope.go index 4a3d0e2b..0f67905b 100644 --- a/pkg/script/common/scope.go +++ b/pkg/script/common/scope.go @@ -16,7 +16,7 @@ type Scope struct { returnValue Value } -func (what *Scope) Init(model *types.ParsedModel, risk *types.RiskCategory, methods map[string]Statement) error { +func (what *Scope) Init(model *types.Model, risk *types.RiskCategory, methods map[string]Statement) error { if model != nil { data, marshalError := json.Marshal(model) if marshalError != nil { diff --git a/pkg/script/script.go b/pkg/script/script.go index 0e619a2e..e8f9359b 100644 --- a/pkg/script/script.go +++ b/pkg/script/script.go @@ -2,6 +2,7 @@ package script import ( "fmt" + "github.com/threagile/threagile/pkg/input" "github.com/threagile/threagile/pkg/script/common" "github.com/threagile/threagile/pkg/script/expressions" "github.com/threagile/threagile/pkg/script/statements" @@ -16,7 +17,7 @@ type Script struct { utils map[string]*statements.MethodStatement } -func (what *Script) Parse(text []byte) (map[string]Script, error) { +func (what *Script) ParseScripts(text []byte) (map[string]Script, error) { items := make(map[string]any) parseError := yaml.Unmarshal(text, &items) if parseError != nil { @@ -26,12 +27,27 @@ func (what *Script) Parse(text []byte) (map[string]Script, error) { for key, value := range items { switch strings.ToLower(key) { case "individual_risk_categories": - risk, ok := value.(map[string]any) + riskScripts, ok := value.(map[string]any) if !ok { return nil, fmt.Errorf("unexpected format %T in risk definition", value) } - return what.parseRiskScripts(risk) + scripts := make(map[string]Script) + for scriptID, riskScript := range riskScripts { + risk, ok := riskScript.(map[string]any) + if !ok { + return nil, fmt.Errorf("unexpected format %T in risk definition for %q", riskScript, scriptID) + } + + script, scriptError := new(Script).ParseScript(risk) + if scriptError != nil { + return nil, fmt.Errorf("failed to parse script of risk definition for %q: %v", scriptID, scriptError) + } + + scripts[scriptID] = *script + } + + return scripts, nil default: return nil, fmt.Errorf("unexpected key %q in risk definition", key) @@ -41,6 +57,39 @@ func (what *Script) Parse(text []byte) (map[string]Script, error) { return nil, fmt.Errorf("no scripts found") } +func (what *Script) ParseScript(script map[string]any) (*Script, error) { + for key, value := range script { + switch strings.ToLower(key) { + case common.Risk: + switch castValue := value.(type) { + case map[string]any: + what.risk = castValue + + default: + return what, fmt.Errorf("failed to parse %q: unexpected script type %T\nscript:\n%v", key, value, new(input.Strings).AddLineNumbers(value)) + } + + case common.Match: + item, errorScript, itemError := new(statements.MethodStatement).Parse(value) + if itemError != nil { + return what, fmt.Errorf("failed to parse %q: %v\nscript:\n%v", key, itemError, new(input.Strings).AddLineNumbers(errorScript)) + } + + what.match = item + + case common.Utils: + item, errorScript, itemError := what.parseUtils(value) + if itemError != nil { + return what, fmt.Errorf("failed to parse %q: %v\nscript:\n%v", key, itemError, new(input.Strings).AddLineNumbers(errorScript)) + } + + what.utils = item + } + } + + return what, nil +} + func (what *Script) GenerateRisks(scope *common.Scope) (map[string]*types.Risk, string, error) { value, valueOk := what.getItem(scope.Model, "technical_assets") if !valueOk { @@ -66,8 +115,6 @@ func (what *Script) GenerateRisks(scope *common.Scope) (map[string]*types.Risk, } if isMatch { - fmt.Printf("risk: %v\n", what.getRiskID(matchScope, techAsset)) - riskScope, riskCloneError := scope.Clone() if riskCloneError != nil { return nil, "", fmt.Errorf("failed to clone scope: %v", riskCloneError) @@ -86,101 +133,6 @@ func (what *Script) GenerateRisks(scope *common.Scope) (map[string]*types.Risk, return risks, "", nil } -func (what *Script) Utils() map[string]common.Statement { - utils := make(map[string]common.Statement) - for name, item := range what.utils { - utils[name] = item - } - - return utils -} - -func (what *Script) AddLineNumbers(script any) string { - text, isString := script.(string) - if !isString { - data, _ := yaml.Marshal(script) - text = string(data) - } - - lines := strings.Split(text, "\n") - for n, line := range lines { - lines[n] = fmt.Sprintf("%3d:\t%v", n+1, line) - } - - return strings.Join(lines, "\n") -} - -func (what *Script) IndentPrintf(level int, script any) string { - text, isString := script.(string) - if !isString { - data, _ := yaml.Marshal(script) - text = string(data) - } - - lines := strings.Split(text, "\n") - for n, line := range lines { - lines[n] = strings.Repeat(" ", level) + line - } - - return strings.Join(lines, "\n") -} - -func (what *Script) IndentLine(level int, format string, params ...any) string { - return strings.Repeat(" ", level) + fmt.Sprintf(format, params...) -} - -func (what *Script) parseRiskScripts(item map[string]any) (map[string]Script, error) { - scripts := make(map[string]Script) - for key, value := range item { - risk, ok := value.(map[string]any) - if !ok { - return nil, fmt.Errorf("unexpected format %T in risk definition for %q", value, key) - } - - script, parseError := new(Script).parseScript(risk) - if parseError != nil { - return nil, fmt.Errorf("failed to parse script of risk definition for %q: %v", key, parseError) - } - - scripts[key] = *script - } - - return scripts, nil -} - -func (what *Script) parseScript(script map[string]any) (*Script, error) { - for key, value := range script { - switch strings.ToLower(key) { - case common.Risk: - switch castValue := value.(type) { - case map[string]any: - what.risk = castValue - - default: - return what, fmt.Errorf("failed to parse %q: unexpected script type %T\nscript:\n%v", key, value, what.AddLineNumbers(value)) - } - - case common.Match: - item, errorScript, itemError := new(statements.MethodStatement).Parse(value) - if itemError != nil { - return what, fmt.Errorf("failed to parse %q: %v\nscript:\n%v", key, itemError, what.AddLineNumbers(errorScript)) - } - - what.match = item - - case common.Utils: - item, errorScript, itemError := what.parseUtils(value) - if itemError != nil { - return what, fmt.Errorf("failed to parse %q: %v\nscript:\n%v", key, itemError, what.AddLineNumbers(errorScript)) - } - - what.utils = item - } - } - - return what, nil -} - func (what *Script) matchRisk(scope *common.Scope) (bool, string, error) { if what.match == nil { return false, "", nil @@ -199,30 +151,6 @@ func (what *Script) matchRisk(scope *common.Scope) (bool, string, error) { return false, "", nil } -func (what *Script) getRiskID(scope *common.Scope, techAsset any) string { - riskIdValue, riskIdValueOk := what.getItem(scope.Risk, "id") - if !riskIdValueOk { - return "" - } - - riskId, riskIdOk := riskIdValue.(string) - if !riskIdOk { - return "" - } - - assetIdValue, assetIdValueOk := what.getItem(techAsset, "id") - if !assetIdValueOk { - return "" - } - - assetId, assetIdOk := assetIdValue.(string) - if !assetIdOk { - return "" - } - - return fmt.Sprintf("%v@%v", riskId, assetId) -} - func (what *Script) generateRisk(scope *common.Scope) (*types.Risk, string, error) { if what.risk == nil { return nil, "", fmt.Errorf("no risk template") diff --git a/pkg/security/risks/builtin/accidental-secret-leak-rule.go b/pkg/security/risks/builtin/accidental-secret-leak-rule.go index cba9aa55..5c257611 100644 --- a/pkg/security/risks/builtin/accidental-secret-leak-rule.go +++ b/pkg/security/risks/builtin/accidental-secret-leak-rule.go @@ -12,9 +12,9 @@ func NewAccidentalSecretLeakRule() *AccidentalSecretLeakRule { return &AccidentalSecretLeakRule{} } -func (*AccidentalSecretLeakRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "accidental-secret-leak", +func (*AccidentalSecretLeakRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "accidental-secret-leak", Title: "Accidental Secret Leak", Description: "Sourcecode repositories (including their histories) as well as artifact registries can accidentally contain secrets like " + "checked-in or packaged-in passwords, API tokens, certificates, crypto keys, etc.", @@ -43,12 +43,12 @@ func (*AccidentalSecretLeakRule) SupportedTags() []string { return []string{"git", "nexus"} } -func (r *AccidentalSecretLeakRule) GenerateRisks(parsedModel *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *AccidentalSecretLeakRule) GenerateRisks(parsedModel *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range parsedModel.SortedTechnicalAssetIDs() { techAsset := parsedModel.TechnicalAssets[id] if !techAsset.OutOfScope && techAsset.Technologies.GetAttribute(types.MayContainSecrets) { - var risk types.Risk + var risk *types.Risk if techAsset.IsTaggedWithAny("git") { risk = r.createRisk(parsedModel, techAsset, "Git", "Git Leak Prevention") } else { @@ -60,7 +60,7 @@ func (r *AccidentalSecretLeakRule) GenerateRisks(parsedModel *types.ParsedModel) return risks } -func (r *AccidentalSecretLeakRule) createRisk(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset, prefix, details string) types.Risk { +func (r *AccidentalSecretLeakRule) createRisk(parsedModel *types.Model, technicalAsset *types.TechnicalAsset, prefix, details string) *types.Risk { if len(prefix) > 0 { prefix = " (" + prefix + ")" } @@ -80,8 +80,8 @@ func (r *AccidentalSecretLeakRule) createRisk(parsedModel *types.ParsedModel, te impact = types.HighImpact } // create risk - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, @@ -94,8 +94,8 @@ func (r *AccidentalSecretLeakRule) createRisk(parsedModel *types.ParsedModel, te return risk } -func (r *AccidentalSecretLeakRule) MatchRisk(parsedModel *types.ParsedModel, risk string) bool { - categoryId := r.Category().Id +func (r *AccidentalSecretLeakRule) MatchRisk(parsedModel *types.Model, risk string) bool { + categoryId := r.Category().ID for _, id := range parsedModel.SortedTechnicalAssetIDs() { techAsset := parsedModel.TechnicalAssets[id] if strings.EqualFold(risk, categoryId+"@"+techAsset.Id) || strings.EqualFold(risk, categoryId+"@*") { @@ -106,8 +106,8 @@ func (r *AccidentalSecretLeakRule) MatchRisk(parsedModel *types.ParsedModel, ris return false } -func (r *AccidentalSecretLeakRule) ExplainRisk(parsedModel *types.ParsedModel, risk string) []string { - categoryId := r.Category().Id +func (r *AccidentalSecretLeakRule) ExplainRisk(parsedModel *types.Model, risk string) []string { + categoryId := r.Category().ID explanation := make([]string, 0) for _, id := range parsedModel.SortedTechnicalAssetIDs() { techAsset := parsedModel.TechnicalAssets[id] @@ -138,7 +138,7 @@ func (r *AccidentalSecretLeakRule) ExplainRisk(parsedModel *types.ParsedModel, r return explanation } -func (r *AccidentalSecretLeakRule) explainRisk(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset) []string { +func (r *AccidentalSecretLeakRule) explainRisk(parsedModel *types.Model, technicalAsset *types.TechnicalAsset) []string { explanation := make([]string, 0) impact := types.LowImpact if technicalAsset.HighestProcessedConfidentiality(parsedModel) == types.StrictlyConfidential || diff --git a/pkg/security/risks/builtin/code-backdooring-rule.go b/pkg/security/risks/builtin/code-backdooring-rule.go index 79c664d2..784f5eb0 100644 --- a/pkg/security/risks/builtin/code-backdooring-rule.go +++ b/pkg/security/risks/builtin/code-backdooring-rule.go @@ -10,9 +10,9 @@ func NewCodeBackdooringRule() *CodeBackdooringRule { return &CodeBackdooringRule{} } -func (*CodeBackdooringRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "code-backdooring", +func (*CodeBackdooringRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "code-backdooring", Title: "Code Backdooring", Description: "For each build-pipeline component Code Backdooring risks might arise where attackers compromise the build-pipeline " + "in order to let backdoored artifacts be shipped into production. Aside from direct code backdooring this includes " + @@ -46,8 +46,8 @@ func (*CodeBackdooringRule) SupportedTags() []string { return []string{} } -func (r *CodeBackdooringRule) GenerateRisks(parsedModel *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *CodeBackdooringRule) GenerateRisks(parsedModel *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range parsedModel.SortedTechnicalAssetIDs() { technicalAsset := parsedModel.TechnicalAssets[id] if !technicalAsset.OutOfScope && technicalAsset.Technologies.GetAttribute(types.IsDevelopmentRelevant) { @@ -58,12 +58,10 @@ func (r *CodeBackdooringRule) GenerateRisks(parsedModel *types.ParsedModel) []ty // TODO: ensure that even internet or unmanaged clients coming over a reverse-proxy or load-balancer like component are treated as if it was directly accessed/exposed on the internet or towards unmanaged dev clients - //riskByLinkAdded := false for _, callerLink := range parsedModel.IncomingTechnicalCommunicationLinksMappedByTargetId[technicalAsset.Id] { caller := parsedModel.TechnicalAssets[callerLink.SourceId] if (!callerLink.VPN && caller.Internet) || caller.OutOfScope { risks = append(risks, r.createRisk(parsedModel, technicalAsset, true)) - //riskByLinkAdded = true break } } @@ -72,7 +70,7 @@ func (r *CodeBackdooringRule) GenerateRisks(parsedModel *types.ParsedModel) []ty return risks } -func (r *CodeBackdooringRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, elevatedRisk bool) types.Risk { +func (r *CodeBackdooringRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, elevatedRisk bool) *types.Risk { title := "Code Backdooring risk at " + technicalAsset.Title + "" impact := types.LowImpact if !technicalAsset.Technologies.GetAttribute(types.CodeInspectionPlatform) { @@ -106,8 +104,8 @@ func (r *CodeBackdooringRule) createRisk(input *types.ParsedModel, technicalAsse dataBreachTechnicalAssetIDs = append(dataBreachTechnicalAssetIDs, key) } // create risk - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/container-baseimage-backdooring-rule.go b/pkg/security/risks/builtin/container-baseimage-backdooring-rule.go index 54caef5b..4fb48df5 100644 --- a/pkg/security/risks/builtin/container-baseimage-backdooring-rule.go +++ b/pkg/security/risks/builtin/container-baseimage-backdooring-rule.go @@ -10,9 +10,9 @@ func NewContainerBaseImageBackdooringRule() *ContainerBaseImageBackdooringRule { return &ContainerBaseImageBackdooringRule{} } -func (*ContainerBaseImageBackdooringRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "container-baseimage-backdooring", +func (*ContainerBaseImageBackdooringRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "container-baseimage-backdooring", Title: "Container Base Image Backdooring", Description: "When a technical asset is built using container technologies, Base Image Backdooring risks might arise where " + "base images and other layers used contain vulnerable components or backdoors." + @@ -41,8 +41,8 @@ func (*ContainerBaseImageBackdooringRule) SupportedTags() []string { return []string{} } -func (r *ContainerBaseImageBackdooringRule) GenerateRisks(parsedModel *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *ContainerBaseImageBackdooringRule) GenerateRisks(parsedModel *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range parsedModel.SortedTechnicalAssetIDs() { technicalAsset := parsedModel.TechnicalAssets[id] if !technicalAsset.OutOfScope && technicalAsset.Machine == types.Container { @@ -52,7 +52,7 @@ func (r *ContainerBaseImageBackdooringRule) GenerateRisks(parsedModel *types.Par return risks } -func (r *ContainerBaseImageBackdooringRule) createRisk(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset) types.Risk { +func (r *ContainerBaseImageBackdooringRule) createRisk(parsedModel *types.Model, technicalAsset *types.TechnicalAsset) *types.Risk { title := "Container Base Image Backdooring risk at " + technicalAsset.Title + "" impact := types.MediumImpact if technicalAsset.HighestProcessedConfidentiality(parsedModel) == types.StrictlyConfidential || @@ -60,8 +60,8 @@ func (r *ContainerBaseImageBackdooringRule) createRisk(parsedModel *types.Parsed technicalAsset.HighestProcessedAvailability(parsedModel) == types.MissionCritical { impact = types.HighImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/container-platform-escape-rule.go b/pkg/security/risks/builtin/container-platform-escape-rule.go index 95a85d88..9ac30773 100644 --- a/pkg/security/risks/builtin/container-platform-escape-rule.go +++ b/pkg/security/risks/builtin/container-platform-escape-rule.go @@ -10,9 +10,9 @@ func NewContainerPlatformEscapeRule() *ContainerPlatformEscapeRule { return &ContainerPlatformEscapeRule{} } -func (*ContainerPlatformEscapeRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "container-platform-escape", +func (*ContainerPlatformEscapeRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "container-platform-escape", Title: "Container Platform Escape", Description: "Container platforms are especially interesting targets for attackers as they host big parts of a containerized runtime infrastructure. " + "When not configured and operated with security best practices in mind, attackers might exploit a vulnerability inside an container and escape towards " + @@ -46,8 +46,8 @@ func (*ContainerPlatformEscapeRule) SupportedTags() []string { return []string{"docker", "kubernetes", "openshift"} } -func (r *ContainerPlatformEscapeRule) GenerateRisks(parsedModel *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *ContainerPlatformEscapeRule) GenerateRisks(parsedModel *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range parsedModel.SortedTechnicalAssetIDs() { technicalAsset := parsedModel.TechnicalAssets[id] if !technicalAsset.OutOfScope && technicalAsset.Technologies.GetAttribute(types.ContainerPlatform) { @@ -57,7 +57,7 @@ func (r *ContainerPlatformEscapeRule) GenerateRisks(parsedModel *types.ParsedMod return risks } -func (r *ContainerPlatformEscapeRule) createRisk(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset) types.Risk { +func (r *ContainerPlatformEscapeRule) createRisk(parsedModel *types.Model, technicalAsset *types.TechnicalAsset) *types.Risk { title := "Container Platform Escape risk at " + technicalAsset.Title + "" impact := types.MediumImpact if technicalAsset.HighestProcessedConfidentiality(parsedModel) == types.StrictlyConfidential || @@ -73,8 +73,8 @@ func (r *ContainerPlatformEscapeRule) createRisk(parsedModel *types.ParsedModel, } } // create risk - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/cross-site-request-forgery-rule.go b/pkg/security/risks/builtin/cross-site-request-forgery-rule.go index 0112b8e1..4c60b304 100644 --- a/pkg/security/risks/builtin/cross-site-request-forgery-rule.go +++ b/pkg/security/risks/builtin/cross-site-request-forgery-rule.go @@ -10,9 +10,9 @@ func NewCrossSiteRequestForgeryRule() *CrossSiteRequestForgeryRule { return &CrossSiteRequestForgeryRule{} } -func (*CrossSiteRequestForgeryRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "cross-site-request-forgery", +func (*CrossSiteRequestForgeryRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "cross-site-request-forgery", Title: "Cross-Site Request Forgery (CSRF)", Description: "When a web application is accessed via web protocols Cross-Site Request Forgery (CSRF) risks might arise.", Impact: "If this risk remains unmitigated, attackers might be able to trick logged-in victim users into unwanted actions within the web application " + @@ -42,8 +42,8 @@ func (*CrossSiteRequestForgeryRule) SupportedTags() []string { return []string{} } -func (r *CrossSiteRequestForgeryRule) GenerateRisks(parsedModel *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *CrossSiteRequestForgeryRule) GenerateRisks(parsedModel *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range parsedModel.SortedTechnicalAssetIDs() { technicalAsset := parsedModel.TechnicalAssets[id] if technicalAsset.OutOfScope || !technicalAsset.Technologies.GetAttribute(types.WebApplication) { @@ -63,15 +63,15 @@ func (r *CrossSiteRequestForgeryRule) GenerateRisks(parsedModel *types.ParsedMod return risks } -func (r *CrossSiteRequestForgeryRule) createRisk(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink, likelihood types.RiskExploitationLikelihood) types.Risk { +func (r *CrossSiteRequestForgeryRule) createRisk(parsedModel *types.Model, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink, likelihood types.RiskExploitationLikelihood) *types.Risk { sourceAsset := parsedModel.TechnicalAssets[incomingFlow.SourceId] title := "Cross-Site Request Forgery (CSRF) risk at " + technicalAsset.Title + " via " + incomingFlow.Title + " from " + sourceAsset.Title + "" impact := types.LowImpact if incomingFlow.HighestIntegrity(parsedModel) == types.MissionCritical { impact = types.MediumImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/cross-site-scripting-rule.go b/pkg/security/risks/builtin/cross-site-scripting-rule.go index a1b9a4eb..9ce709e4 100644 --- a/pkg/security/risks/builtin/cross-site-scripting-rule.go +++ b/pkg/security/risks/builtin/cross-site-scripting-rule.go @@ -10,9 +10,9 @@ func NewCrossSiteScriptingRule() *CrossSiteScriptingRule { return &CrossSiteScriptingRule{} } -func (*CrossSiteScriptingRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "cross-site-scripting", +func (*CrossSiteScriptingRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "cross-site-scripting", Title: "Cross-Site Scripting (XSS)", Description: "For each web application Cross-Site Scripting (XSS) risks might arise. In terms " + "of the overall risk level take other applications running on the same domain into account as well.", @@ -40,8 +40,8 @@ func (*CrossSiteScriptingRule) SupportedTags() []string { return []string{} } -func (r *CrossSiteScriptingRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *CrossSiteScriptingRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.OutOfScope || !technicalAsset.Technologies.GetAttribute(types.WebApplication) { // TODO: also mobile clients or rich-clients as long as they use web-view... @@ -52,14 +52,14 @@ func (r *CrossSiteScriptingRule) GenerateRisks(input *types.ParsedModel) []types return risks } -func (r *CrossSiteScriptingRule) createRisk(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset) types.Risk { +func (r *CrossSiteScriptingRule) createRisk(parsedModel *types.Model, technicalAsset *types.TechnicalAsset) *types.Risk { title := "Cross-Site Scripting (XSS) risk at " + technicalAsset.Title + "" impact := types.MediumImpact if technicalAsset.HighestProcessedConfidentiality(parsedModel) == types.StrictlyConfidential || technicalAsset.HighestProcessedIntegrity(parsedModel) == types.MissionCritical { impact = types.HighImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Likely, impact), ExploitationLikelihood: types.Likely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/dos-risky-access-across-trust-boundary-rule.go b/pkg/security/risks/builtin/dos-risky-access-across-trust-boundary-rule.go index 66445b84..100b2eeb 100644 --- a/pkg/security/risks/builtin/dos-risky-access-across-trust-boundary-rule.go +++ b/pkg/security/risks/builtin/dos-risky-access-across-trust-boundary-rule.go @@ -10,9 +10,9 @@ func NewDosRiskyAccessAcrossTrustBoundaryRule() *DosRiskyAccessAcrossTrustBounda return &DosRiskyAccessAcrossTrustBoundaryRule{} } -func (*DosRiskyAccessAcrossTrustBoundaryRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "dos-risky-access-across-trust-boundary", +func (*DosRiskyAccessAcrossTrustBoundaryRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "dos-risky-access-across-trust-boundary", Title: "DoS-risky Access Across Trust-Boundary", Description: "Assets accessed across trust boundaries with critical or mission-critical availability rating " + "are more prone to Denial-of-Service (DoS) risks.", @@ -44,8 +44,8 @@ func (*DosRiskyAccessAcrossTrustBoundaryRule) SupportedTags() []string { return []string{} } -func (r *DosRiskyAccessAcrossTrustBoundaryRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *DosRiskyAccessAcrossTrustBoundaryRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if !technicalAsset.OutOfScope && !technicalAsset.Technologies.GetAttribute(types.LoadBalancer) && @@ -67,7 +67,7 @@ func (r *DosRiskyAccessAcrossTrustBoundaryRule) GenerateRisks(input *types.Parse return risks } -func (r *DosRiskyAccessAcrossTrustBoundaryRule) checkRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, incomingAccess *types.CommunicationLink, linkId string, hopBetween string, risks []types.Risk) []types.Risk { +func (r *DosRiskyAccessAcrossTrustBoundaryRule) checkRisk(input *types.Model, technicalAsset *types.TechnicalAsset, incomingAccess *types.CommunicationLink, linkId string, hopBetween string, risks []*types.Risk) []*types.Risk { if incomingAccess.IsAcrossTrustBoundaryNetworkOnly(input) && !incomingAccess.Protocol.IsProcessLocal() && incomingAccess.Usage != types.DevOps { highRisk := technicalAsset.Availability == types.MissionCritical && @@ -79,7 +79,7 @@ func (r *DosRiskyAccessAcrossTrustBoundaryRule) checkRisk(input *types.ParsedMod } func (r *DosRiskyAccessAcrossTrustBoundaryRule) createRisk(techAsset *types.TechnicalAsset, dataFlow *types.CommunicationLink, linkId string, hopBetween string, - clientOutsideTrustBoundary *types.TechnicalAsset, moreRisky bool) types.Risk { + clientOutsideTrustBoundary *types.TechnicalAsset, moreRisky bool) *types.Risk { impact := types.LowImpact if moreRisky { impact = types.MediumImpact @@ -87,8 +87,8 @@ func (r *DosRiskyAccessAcrossTrustBoundaryRule) createRisk(techAsset *types.Tech if len(hopBetween) > 0 { hopBetween = " forwarded via " + hopBetween + "" } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/incomplete-model-rule.go b/pkg/security/risks/builtin/incomplete-model-rule.go index 1b1642d5..ddcc1b5e 100644 --- a/pkg/security/risks/builtin/incomplete-model-rule.go +++ b/pkg/security/risks/builtin/incomplete-model-rule.go @@ -10,9 +10,9 @@ func NewIncompleteModelRule() *IncompleteModelRule { return &IncompleteModelRule{} } -func (*IncompleteModelRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "incomplete-model", +func (*IncompleteModelRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "incomplete-model", Title: "Incomplete Model", Description: "When the threat model contains unknown technologies or transfers data over unknown protocols, this is " + "an indicator for an incomplete model.", @@ -36,8 +36,8 @@ func (*IncompleteModelRule) SupportedTags() []string { return []string{} } -func (r *IncompleteModelRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *IncompleteModelRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if !technicalAsset.OutOfScope { @@ -54,10 +54,10 @@ func (r *IncompleteModelRule) GenerateRisks(input *types.ParsedModel) []types.Ri return risks } -func (r *IncompleteModelRule) createRiskTechAsset(technicalAsset *types.TechnicalAsset) types.Risk { +func (r *IncompleteModelRule) createRiskTechAsset(technicalAsset *types.TechnicalAsset) *types.Risk { title := "Unknown Technology specified at technical asset " + technicalAsset.Title + "" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, types.LowImpact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: types.LowImpact, @@ -70,10 +70,10 @@ func (r *IncompleteModelRule) createRiskTechAsset(technicalAsset *types.Technica return risk } -func (r *IncompleteModelRule) createRiskCommLink(technicalAsset *types.TechnicalAsset, commLink *types.CommunicationLink) types.Risk { +func (r *IncompleteModelRule) createRiskCommLink(technicalAsset *types.TechnicalAsset, commLink *types.CommunicationLink) *types.Risk { title := "Unknown Protocol specified for communication link " + commLink.Title + " at technical asset " + technicalAsset.Title + "" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, types.LowImpact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: types.LowImpact, diff --git a/pkg/security/risks/builtin/ldap-injection-rule.go b/pkg/security/risks/builtin/ldap-injection-rule.go index 2e68bd54..744e090f 100644 --- a/pkg/security/risks/builtin/ldap-injection-rule.go +++ b/pkg/security/risks/builtin/ldap-injection-rule.go @@ -10,9 +10,9 @@ func NewLdapInjectionRule() *LdapInjectionRule { return &LdapInjectionRule{} } -func (*LdapInjectionRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "ldap-injection", +func (*LdapInjectionRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "ldap-injection", Title: "LDAP-Injection", Description: "When an LDAP server is accessed LDAP-Injection risks might arise. " + "The risk rating depends on the sensitivity of the LDAP server itself and of the data assets processed.", @@ -39,8 +39,8 @@ func (*LdapInjectionRule) SupportedTags() []string { return []string{} } -func (r *LdapInjectionRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *LdapInjectionRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, technicalAsset := range input.TechnicalAssets { incomingFlows := input.IncomingTechnicalCommunicationLinksMappedByTargetId[technicalAsset.Id] for _, incomingFlow := range incomingFlows { @@ -59,7 +59,7 @@ func (r *LdapInjectionRule) GenerateRisks(input *types.ParsedModel) []types.Risk return risks } -func (r *LdapInjectionRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink, likelihood types.RiskExploitationLikelihood) types.Risk { +func (r *LdapInjectionRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink, likelihood types.RiskExploitationLikelihood) *types.Risk { caller := input.TechnicalAssets[incomingFlow.SourceId] title := "LDAP-Injection risk at " + caller.Title + " against LDAP server " + technicalAsset.Title + "" + " via " + incomingFlow.Title + "" @@ -67,8 +67,8 @@ func (r *LdapInjectionRule) createRisk(input *types.ParsedModel, technicalAsset if technicalAsset.HighestProcessedConfidentiality(input) == types.StrictlyConfidential || technicalAsset.HighestProcessedIntegrity(input) == types.MissionCritical { impact = types.HighImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-authentication-rule.go b/pkg/security/risks/builtin/missing-authentication-rule.go index e13ea97e..5d0917ec 100644 --- a/pkg/security/risks/builtin/missing-authentication-rule.go +++ b/pkg/security/risks/builtin/missing-authentication-rule.go @@ -10,9 +10,9 @@ func NewMissingAuthenticationRule() *MissingAuthenticationRule { return &MissingAuthenticationRule{} } -func (*MissingAuthenticationRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-authentication", +func (*MissingAuthenticationRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-authentication", Title: "Missing Authentication", Description: "Technical assets (especially multi-tenant systems) should authenticate incoming requests when the asset processes sensitive data. ", Impact: "If this risk is unmitigated, attackers might be able to access or modify sensitive data in an unauthenticated way.", @@ -39,8 +39,8 @@ func (*MissingAuthenticationRule) SupportedTags() []string { return []string{} } -func (r *MissingAuthenticationRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingAuthenticationRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.OutOfScope || technicalAsset.Technologies.GetAttribute(types.NoAuthenticationRequired) { @@ -77,8 +77,8 @@ func (r *MissingAuthenticationRule) GenerateRisks(input *types.ParsedModel) []ty return risks } -func (r *MissingAuthenticationRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, incomingAccess, incomingAccessOrigin *types.CommunicationLink, hopBetween string, - impact types.RiskExploitationImpact, likelihood types.RiskExploitationLikelihood, twoFactor bool, category types.RiskCategory) types.Risk { +func (r *MissingAuthenticationRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, incomingAccess, incomingAccessOrigin *types.CommunicationLink, hopBetween string, + impact types.RiskExploitationImpact, likelihood types.RiskExploitationLikelihood, twoFactor bool, category *types.RiskCategory) *types.Risk { factorString := "" if twoFactor { factorString = "Two-Factor " @@ -86,8 +86,8 @@ func (r *MissingAuthenticationRule) createRisk(input *types.ParsedModel, technic if len(hopBetween) > 0 { hopBetween = "forwarded via " + hopBetween + " " } - risk := types.Risk{ - CategoryId: category.Id, + risk := &types.Risk{ + CategoryId: category.ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-authentication-second-factor-rule.go b/pkg/security/risks/builtin/missing-authentication-second-factor-rule.go index 5e1a1198..02c9c6f4 100644 --- a/pkg/security/risks/builtin/missing-authentication-second-factor-rule.go +++ b/pkg/security/risks/builtin/missing-authentication-second-factor-rule.go @@ -12,9 +12,9 @@ func NewMissingAuthenticationSecondFactorRule(missingAuthenticationRule *Missing return &MissingAuthenticationSecondFactorRule{missingAuthenticationRule: missingAuthenticationRule} } -func (*MissingAuthenticationSecondFactorRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-authentication-second-factor", +func (*MissingAuthenticationSecondFactorRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-authentication-second-factor", Title: "Missing Two-Factor Authentication (2FA)", Description: "Technical assets (especially multi-tenant systems) should authenticate incoming requests with " + "two-factor (2FA) authentication when the asset processes or stores highly sensitive data (in terms of confidentiality, integrity, and availability) and is accessed by humans.", @@ -41,8 +41,8 @@ func (*MissingAuthenticationSecondFactorRule) SupportedTags() []string { return []string{} } -func (r *MissingAuthenticationSecondFactorRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingAuthenticationSecondFactorRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.OutOfScope || diff --git a/pkg/security/risks/builtin/missing-build-infrastructure-rule.go b/pkg/security/risks/builtin/missing-build-infrastructure-rule.go index b116fdc4..b2a1230b 100644 --- a/pkg/security/risks/builtin/missing-build-infrastructure-rule.go +++ b/pkg/security/risks/builtin/missing-build-infrastructure-rule.go @@ -10,9 +10,9 @@ func NewMissingBuildInfrastructureRule() *MissingBuildInfrastructureRule { return &MissingBuildInfrastructureRule{} } -func (*MissingBuildInfrastructureRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-build-infrastructure", +func (*MissingBuildInfrastructureRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-build-infrastructure", Title: "Missing Build Infrastructure", Description: "The modeled architecture does not contain a build infrastructure (devops-client, sourcecode-repo, build-pipeline, etc.), " + "which might be the risk of a model missing critical assets (and thus not seeing their risks). " + @@ -41,8 +41,8 @@ func (*MissingBuildInfrastructureRule) SupportedTags() []string { return []string{} } -func (r *MissingBuildInfrastructureRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingBuildInfrastructureRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) hasCustomDevelopedParts, hasBuildPipeline, hasSourcecodeRepo, hasDevOpsClient := false, false, false, false impact := types.LowImpact var mostRelevantAsset *types.TechnicalAsset @@ -85,10 +85,10 @@ func (r *MissingBuildInfrastructureRule) GenerateRisks(input *types.ParsedModel) return risks } -func (r *MissingBuildInfrastructureRule) createRisk(technicalAsset *types.TechnicalAsset, impact types.RiskExploitationImpact) types.Risk { +func (r *MissingBuildInfrastructureRule) createRisk(technicalAsset *types.TechnicalAsset, impact types.RiskExploitationImpact) *types.Risk { title := "Missing Build Infrastructure in the threat model (referencing asset " + technicalAsset.Title + " as an example)" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-cloud-hardening-rule.go b/pkg/security/risks/builtin/missing-cloud-hardening-rule.go index e3e1f323..59a7a38f 100644 --- a/pkg/security/risks/builtin/missing-cloud-hardening-rule.go +++ b/pkg/security/risks/builtin/missing-cloud-hardening-rule.go @@ -13,9 +13,9 @@ func NewMissingCloudHardeningRule() *MissingCloudHardeningRule { return &MissingCloudHardeningRule{} } -func (*MissingCloudHardeningRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-cloud-hardening", +func (*MissingCloudHardeningRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-cloud-hardening", Title: "Missing Cloud Hardening", Description: "Cloud components should be hardened according to the cloud vendor best practices. This affects their " + "configuration, auditing, and further areas.", @@ -57,8 +57,8 @@ func (*MissingCloudHardeningRule) SupportedTags() []string { return res } -func (r *MissingCloudHardeningRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingCloudHardeningRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) sharedRuntimesWithUnspecificCloudRisks := make(map[string]bool) trustBoundariesWithUnspecificCloudRisks := make(map[string]bool) @@ -348,7 +348,7 @@ func addAccordingToBaseTag(techAsset *types.TechnicalAsset, tags []string, } } -func findMostSensitiveTechnicalAsset(input *types.ParsedModel, techAssets map[string]bool) *types.TechnicalAsset { +func findMostSensitiveTechnicalAsset(input *types.Model, techAssets map[string]bool) *types.TechnicalAsset { var mostRelevantAsset *types.TechnicalAsset keys := make([]string, 0, len(techAssets)) for k := range techAssets { @@ -364,7 +364,7 @@ func findMostSensitiveTechnicalAsset(input *types.ParsedModel, techAssets map[st return mostRelevantAsset } -func (r *MissingCloudHardeningRule) createRiskForSharedRuntime(input *types.ParsedModel, sharedRuntime *types.SharedRuntime, prefix, details string) types.Risk { +func (r *MissingCloudHardeningRule) createRiskForSharedRuntime(input *types.Model, sharedRuntime *types.SharedRuntime, prefix, details string) *types.Risk { id := "" if len(prefix) > 0 { id = "@" + strings.ToLower(prefix) @@ -386,8 +386,8 @@ func (r *MissingCloudHardeningRule) createRiskForSharedRuntime(input *types.Pars impact = types.VeryHighImpact } // create risk - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, @@ -400,7 +400,7 @@ func (r *MissingCloudHardeningRule) createRiskForSharedRuntime(input *types.Pars return risk } -func (r *MissingCloudHardeningRule) createRiskForTrustBoundary(parsedModel *types.ParsedModel, trustBoundary *types.TrustBoundary, prefix, details string) types.Risk { +func (r *MissingCloudHardeningRule) createRiskForTrustBoundary(parsedModel *types.Model, trustBoundary *types.TrustBoundary, prefix, details string) *types.Risk { id := "" if len(prefix) > 0 { id = "@" + strings.ToLower(prefix) @@ -422,8 +422,8 @@ func (r *MissingCloudHardeningRule) createRiskForTrustBoundary(parsedModel *type impact = types.VeryHighImpact } // create risk - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, @@ -436,7 +436,7 @@ func (r *MissingCloudHardeningRule) createRiskForTrustBoundary(parsedModel *type return risk } -func (r *MissingCloudHardeningRule) createRiskForTechnicalAsset(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset, prefix, details string) types.Risk { +func (r *MissingCloudHardeningRule) createRiskForTechnicalAsset(parsedModel *types.Model, technicalAsset *types.TechnicalAsset, prefix, details string) *types.Risk { id := "" if len(prefix) > 0 { id = "@" + strings.ToLower(prefix) @@ -458,8 +458,8 @@ func (r *MissingCloudHardeningRule) createRiskForTechnicalAsset(parsedModel *typ impact = types.VeryHighImpact } // create risk - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-file-validation-rule.go b/pkg/security/risks/builtin/missing-file-validation-rule.go index 955629cf..01344da1 100644 --- a/pkg/security/risks/builtin/missing-file-validation-rule.go +++ b/pkg/security/risks/builtin/missing-file-validation-rule.go @@ -10,9 +10,9 @@ func NewMissingFileValidationRule() *MissingFileValidationRule { return &MissingFileValidationRule{} } -func (*MissingFileValidationRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-file-validation", +func (*MissingFileValidationRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-file-validation", Title: "Missing File Validation", Description: "When a technical asset accepts files, these input files should be strictly validated about filename and type.", Impact: "If this risk is unmitigated, attackers might be able to provide malicious files to the application.", @@ -40,8 +40,8 @@ func (*MissingFileValidationRule) SupportedTags() []string { return []string{} } -func (r *MissingFileValidationRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingFileValidationRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.OutOfScope || !technicalAsset.CustomDevelopedParts { @@ -56,7 +56,7 @@ func (r *MissingFileValidationRule) GenerateRisks(input *types.ParsedModel) []ty return risks } -func (r *MissingFileValidationRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset) types.Risk { +func (r *MissingFileValidationRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset) *types.Risk { title := "Missing File Validation risk at " + technicalAsset.Title + "" impact := types.LowImpact if technicalAsset.HighestProcessedConfidentiality(input) == types.StrictlyConfidential || @@ -64,8 +64,8 @@ func (r *MissingFileValidationRule) createRisk(input *types.ParsedModel, technic technicalAsset.HighestProcessedAvailability(input) == types.MissionCritical { impact = types.MediumImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.VeryLikely, impact), ExploitationLikelihood: types.VeryLikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-hardening-rule.go b/pkg/security/risks/builtin/missing-hardening-rule.go index a6b970c8..06cb0091 100644 --- a/pkg/security/risks/builtin/missing-hardening-rule.go +++ b/pkg/security/risks/builtin/missing-hardening-rule.go @@ -15,9 +15,9 @@ func NewMissingHardeningRule() *MissingHardeningRule { return &MissingHardeningRule{raaLimit: 55, raaLimitReduced: 40} } -func (r *MissingHardeningRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-hardening", +func (r *MissingHardeningRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-hardening", Title: "Missing Hardening", Description: "Technical assets with a Relative Attacker Attractiveness (RAA) value of " + strconv.Itoa(r.raaLimit) + " % or higher should be " + "explicitly hardened taking best practices and vendor hardening guides into account.", @@ -43,8 +43,8 @@ func (*MissingHardeningRule) SupportedTags() []string { return []string{"tomcat"} } -func (r *MissingHardeningRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingHardeningRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if !technicalAsset.OutOfScope { @@ -57,14 +57,14 @@ func (r *MissingHardeningRule) GenerateRisks(input *types.ParsedModel) []types.R return risks } -func (r *MissingHardeningRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset) types.Risk { +func (r *MissingHardeningRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset) *types.Risk { title := "Missing Hardening risk at " + technicalAsset.Title + "" impact := types.LowImpact if technicalAsset.HighestProcessedConfidentiality(input) == types.StrictlyConfidential || technicalAsset.HighestProcessedIntegrity(input) == types.MissionCritical { impact = types.MediumImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Likely, impact), ExploitationLikelihood: types.Likely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-identity-propagation-rule.go b/pkg/security/risks/builtin/missing-identity-propagation-rule.go index 54794594..86de9351 100644 --- a/pkg/security/risks/builtin/missing-identity-propagation-rule.go +++ b/pkg/security/risks/builtin/missing-identity-propagation-rule.go @@ -10,9 +10,9 @@ func NewMissingIdentityPropagationRule() *MissingIdentityPropagationRule { return &MissingIdentityPropagationRule{} } -func (*MissingIdentityPropagationRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-identity-propagation", +func (*MissingIdentityPropagationRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-identity-propagation", Title: "Missing Identity Propagation", Description: "Technical assets (especially multi-tenant systems), which usually process data for end users should " + "authorize every request based on the identity of the end user when the data flow is authenticated (i.e. non-public). " + @@ -45,8 +45,8 @@ func (*MissingIdentityPropagationRule) SupportedTags() []string { return []string{} } -func (r *MissingIdentityPropagationRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingIdentityPropagationRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.OutOfScope { @@ -83,13 +83,13 @@ func (r *MissingIdentityPropagationRule) GenerateRisks(input *types.ParsedModel) return risks } -func (r *MissingIdentityPropagationRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, incomingAccess *types.CommunicationLink, moreRisky bool) types.Risk { +func (r *MissingIdentityPropagationRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, incomingAccess *types.CommunicationLink, moreRisky bool) *types.Risk { impact := types.LowImpact if moreRisky { impact = types.MediumImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-identity-provider-isolation-rule.go b/pkg/security/risks/builtin/missing-identity-provider-isolation-rule.go index 10398793..df3906f0 100644 --- a/pkg/security/risks/builtin/missing-identity-provider-isolation-rule.go +++ b/pkg/security/risks/builtin/missing-identity-provider-isolation-rule.go @@ -10,9 +10,9 @@ func NewMissingIdentityProviderIsolationRule() *MissingIdentityProviderIsolation return &MissingIdentityProviderIsolationRule{} } -func (*MissingIdentityProviderIsolationRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-identity-provider-isolation", +func (*MissingIdentityProviderIsolationRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-identity-provider-isolation", Title: "Missing Identity Provider Isolation", Description: "Highly sensitive identity provider assets and their identity data stores should be isolated from other assets " + "by their own network segmentation trust-boundary (" + types.ExecutionEnvironment.String() + " boundaries do not count as network isolation).", @@ -41,8 +41,8 @@ func (*MissingIdentityProviderIsolationRule) SupportedTags() []string { return []string{} } -func (r *MissingIdentityProviderIsolationRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingIdentityProviderIsolationRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, technicalAsset := range input.TechnicalAssets { if !technicalAsset.OutOfScope && technicalAsset.Technologies.GetAttribute(types.IsIdentityRelated) { moreImpact := technicalAsset.Confidentiality == types.StrictlyConfidential || @@ -72,7 +72,7 @@ func (r *MissingIdentityProviderIsolationRule) GenerateRisks(input *types.Parsed return risks } -func (r *MissingIdentityProviderIsolationRule) createRisk(techAsset *types.TechnicalAsset, moreImpact bool, sameExecutionEnv bool) types.Risk { +func (r *MissingIdentityProviderIsolationRule) createRisk(techAsset *types.TechnicalAsset, moreImpact bool, sameExecutionEnv bool) *types.Risk { impact := types.HighImpact likelihood := types.Unlikely others := "in the same network segment" @@ -83,8 +83,8 @@ func (r *MissingIdentityProviderIsolationRule) createRisk(techAsset *types.Techn likelihood = types.Likely others = "in the same execution environment" } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-identity-store-rule.go b/pkg/security/risks/builtin/missing-identity-store-rule.go index 1b528e32..9571bdcc 100644 --- a/pkg/security/risks/builtin/missing-identity-store-rule.go +++ b/pkg/security/risks/builtin/missing-identity-store-rule.go @@ -10,9 +10,9 @@ func NewMissingIdentityStoreRule() *MissingIdentityStoreRule { return &MissingIdentityStoreRule{} } -func (*MissingIdentityStoreRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-identity-store", +func (*MissingIdentityStoreRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-identity-store", Title: "Missing Identity Store", Description: "The modeled architecture does not contain an identity store, which might be the risk of a model missing " + "critical assets (and thus not seeing their risks).", @@ -39,8 +39,8 @@ func (*MissingIdentityStoreRule) SupportedTags() []string { return []string{} } -func (r *MissingIdentityStoreRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingIdentityStoreRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, technicalAsset := range input.TechnicalAssets { if !technicalAsset.OutOfScope && technicalAsset.Technologies.GetAttribute(types.IsIdentityStore) { // everything fine, no risk, as we have an in-scope identity store in the model @@ -83,10 +83,10 @@ func (r *MissingIdentityStoreRule) GenerateRisks(input *types.ParsedModel) []typ return risks } -func (r *MissingIdentityStoreRule) createRisk(technicalAsset *types.TechnicalAsset, impact types.RiskExploitationImpact) types.Risk { +func (r *MissingIdentityStoreRule) createRisk(technicalAsset *types.TechnicalAsset, impact types.RiskExploitationImpact) *types.Risk { title := "Missing Identity Store in the threat model (referencing asset " + technicalAsset.Title + " as an example)" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-network-segmentation-rule.go b/pkg/security/risks/builtin/missing-network-segmentation-rule.go index 4163bc34..317ff54a 100644 --- a/pkg/security/risks/builtin/missing-network-segmentation-rule.go +++ b/pkg/security/risks/builtin/missing-network-segmentation-rule.go @@ -14,9 +14,9 @@ func NewMissingNetworkSegmentationRule() *MissingNetworkSegmentationRule { return &MissingNetworkSegmentationRule{raaLimit: 50} } -func (*MissingNetworkSegmentationRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-network-segmentation", +func (*MissingNetworkSegmentationRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-network-segmentation", Title: "Missing Network Segmentation", Description: "Highly sensitive assets and/or data stores residing in the same network segment than other " + "lower sensitive assets (like webservers or content management systems etc.) should be better protected " + @@ -48,8 +48,8 @@ func (*MissingNetworkSegmentationRule) SupportedTags() []string { return []string{} } -func (r *MissingNetworkSegmentationRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingNetworkSegmentationRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) // first create them in memory (see the link replacement below for nested trust boundaries) - otherwise in Go ranging over map is random order // range over them in sorted (hence re-producible) way: keys := make([]string, 0) @@ -92,13 +92,13 @@ func (r *MissingNetworkSegmentationRule) GenerateRisks(input *types.ParsedModel) return risks } -func (r *MissingNetworkSegmentationRule) createRisk(techAsset *types.TechnicalAsset, moreRisky bool) types.Risk { +func (r *MissingNetworkSegmentationRule) createRisk(techAsset *types.TechnicalAsset, moreRisky bool) *types.Risk { impact := types.LowImpact if moreRisky { impact = types.MediumImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-vault-isolation-rule.go b/pkg/security/risks/builtin/missing-vault-isolation-rule.go index fdc6f541..0c2e21a3 100644 --- a/pkg/security/risks/builtin/missing-vault-isolation-rule.go +++ b/pkg/security/risks/builtin/missing-vault-isolation-rule.go @@ -10,9 +10,9 @@ func NewMissingVaultIsolationRule() *MissingVaultIsolationRule { return &MissingVaultIsolationRule{} } -func (*MissingVaultIsolationRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-vault-isolation", +func (*MissingVaultIsolationRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-vault-isolation", Title: "Missing Vault Isolation", Description: "Highly sensitive vault assets and their data stores should be isolated from other assets " + "by their own network segmentation trust-boundary (" + types.ExecutionEnvironment.String() + " boundaries do not count as network isolation).", @@ -41,8 +41,8 @@ func (*MissingVaultIsolationRule) SupportedTags() []string { return []string{} } -func (r *MissingVaultIsolationRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingVaultIsolationRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, technicalAsset := range input.TechnicalAssets { if !technicalAsset.OutOfScope && technicalAsset.Technologies.GetAttribute(types.Vault) { moreImpact := technicalAsset.Confidentiality == types.StrictlyConfidential || @@ -72,11 +72,11 @@ func (r *MissingVaultIsolationRule) GenerateRisks(input *types.ParsedModel) []ty return risks } -func isVaultStorage(parsedModel *types.ParsedModel, vault *types.TechnicalAsset, storage *types.TechnicalAsset) bool { +func isVaultStorage(parsedModel *types.Model, vault *types.TechnicalAsset, storage *types.TechnicalAsset) bool { return storage.Type == types.Datastore && vault.HasDirectConnection(parsedModel, storage.Id) } -func (r *MissingVaultIsolationRule) createRisk(techAsset *types.TechnicalAsset, moreImpact bool, sameExecutionEnv bool) types.Risk { +func (r *MissingVaultIsolationRule) createRisk(techAsset *types.TechnicalAsset, moreImpact bool, sameExecutionEnv bool) *types.Risk { impact := types.MediumImpact likelihood := types.Unlikely others := "in the same network segment" @@ -87,8 +87,8 @@ func (r *MissingVaultIsolationRule) createRisk(techAsset *types.TechnicalAsset, likelihood = types.Likely others = "in the same execution environment" } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-vault-rule.go b/pkg/security/risks/builtin/missing-vault-rule.go index 5974d404..02a7f926 100644 --- a/pkg/security/risks/builtin/missing-vault-rule.go +++ b/pkg/security/risks/builtin/missing-vault-rule.go @@ -10,9 +10,9 @@ func NewMissingVaultRule() *MissingVaultRule { return &MissingVaultRule{} } -func (*MissingVaultRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-vault", +func (*MissingVaultRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-vault", Title: "Missing Vault (Secret Storage)", Description: "In order to avoid the risk of secret leakage via config files (when attacked through vulnerabilities being able to " + "read files like Path-Traversal and others), it is best practice to use a separate hardened process with proper authentication, " + @@ -40,8 +40,8 @@ func (*MissingVaultRule) SupportedTags() []string { return []string{} } -func (r *MissingVaultRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingVaultRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) hasVault := false var mostRelevantAsset *types.TechnicalAsset impact := types.LowImpact @@ -71,10 +71,10 @@ func (r *MissingVaultRule) GenerateRisks(input *types.ParsedModel) []types.Risk return risks } -func (r *MissingVaultRule) createRisk(technicalAsset *types.TechnicalAsset, impact types.RiskExploitationImpact) types.Risk { +func (r *MissingVaultRule) createRisk(technicalAsset *types.TechnicalAsset, impact types.RiskExploitationImpact) *types.Risk { title := "Missing Vault (Secret Storage) in the threat model (referencing asset " + technicalAsset.Title + " as an example)" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/missing-waf-rule.go b/pkg/security/risks/builtin/missing-waf-rule.go index b7775d55..60207e1f 100644 --- a/pkg/security/risks/builtin/missing-waf-rule.go +++ b/pkg/security/risks/builtin/missing-waf-rule.go @@ -10,9 +10,9 @@ func NewMissingWafRule() *MissingWafRule { return &MissingWafRule{} } -func (*MissingWafRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "missing-waf", +func (*MissingWafRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "missing-waf", Title: "Missing Web Application Firewall (WAF)", Description: "To have a first line of filtering defense, security architectures with web-services or web-applications should include a WAF in front of them. " + "Even though a WAF is not a replacement for security (all components must be secure even without a WAF) it adds another layer of defense to the overall " + @@ -39,8 +39,8 @@ func (*MissingWafRule) SupportedTags() []string { return []string{} } -func (r *MissingWafRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MissingWafRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, technicalAsset := range input.TechnicalAssets { if !technicalAsset.OutOfScope && (technicalAsset.Technologies.GetAttribute(types.WebApplication) || technicalAsset.Technologies.GetAttribute(types.IsWebService)) { @@ -57,7 +57,7 @@ func (r *MissingWafRule) GenerateRisks(input *types.ParsedModel) []types.Risk { return risks } -func (r *MissingWafRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset) types.Risk { +func (r *MissingWafRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset) *types.Risk { title := "Missing Web Application Firewall (WAF) risk at " + technicalAsset.Title + "" likelihood := types.Unlikely impact := types.LowImpact @@ -66,8 +66,8 @@ func (r *MissingWafRule) createRisk(input *types.ParsedModel, technicalAsset *ty technicalAsset.HighestProcessedAvailability(input) == types.MissionCritical { impact = types.MediumImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/mixed-targets-on-shared-runtime-rule.go b/pkg/security/risks/builtin/mixed-targets-on-shared-runtime-rule.go index 8e225f3b..5084b6d0 100644 --- a/pkg/security/risks/builtin/mixed-targets-on-shared-runtime-rule.go +++ b/pkg/security/risks/builtin/mixed-targets-on-shared-runtime-rule.go @@ -12,9 +12,9 @@ func NewMixedTargetsOnSharedRuntimeRule() *MixedTargetsOnSharedRuntimeRule { return &MixedTargetsOnSharedRuntimeRule{} } -func (*MixedTargetsOnSharedRuntimeRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "mixed-targets-on-shared-runtime", +func (*MixedTargetsOnSharedRuntimeRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "mixed-targets-on-shared-runtime", Title: "Mixed Targets on Shared Runtime", Description: "Different attacker targets (like frontend and backend/datastore components) should not be running on the same " + "shared (underlying) runtime.", @@ -44,8 +44,8 @@ func (*MixedTargetsOnSharedRuntimeRule) SupportedTags() []string { return []string{} } -func (r *MixedTargetsOnSharedRuntimeRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *MixedTargetsOnSharedRuntimeRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) // as in Go ranging over map is random order, range over them in sorted (hence reproducible) way: keys := make([]string, 0) for k := range input.SharedRuntimes { @@ -79,13 +79,13 @@ func (r *MixedTargetsOnSharedRuntimeRule) GenerateRisks(input *types.ParsedModel return risks } -func (r *MixedTargetsOnSharedRuntimeRule) createRisk(input *types.ParsedModel, sharedRuntime *types.SharedRuntime) types.Risk { +func (r *MixedTargetsOnSharedRuntimeRule) createRisk(input *types.Model, sharedRuntime *types.SharedRuntime) *types.Risk { impact := types.LowImpact if isMoreRisky(input, sharedRuntime) { impact = types.MediumImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, @@ -99,7 +99,7 @@ func (r *MixedTargetsOnSharedRuntimeRule) createRisk(input *types.ParsedModel, s return risk } -func isMoreRisky(input *types.ParsedModel, sharedRuntime *types.SharedRuntime) bool { +func isMoreRisky(input *types.Model, sharedRuntime *types.SharedRuntime) bool { for _, techAssetId := range sharedRuntime.TechnicalAssetsRunning { techAsset := input.TechnicalAssets[techAssetId] if techAsset.Confidentiality == types.StrictlyConfidential || techAsset.Integrity == types.MissionCritical || diff --git a/pkg/security/risks/builtin/path-traversal-rule.go b/pkg/security/risks/builtin/path-traversal-rule.go index d1f91db9..630271eb 100644 --- a/pkg/security/risks/builtin/path-traversal-rule.go +++ b/pkg/security/risks/builtin/path-traversal-rule.go @@ -10,9 +10,9 @@ func NewPathTraversalRule() *PathTraversalRule { return &PathTraversalRule{} } -func (*PathTraversalRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "path-traversal", +func (*PathTraversalRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "path-traversal", Title: "Path-Traversal", Description: "When a filesystem is accessed Path-Traversal or Local-File-Inclusion (LFI) risks might arise. " + "The risk rating depends on the sensitivity of the technical asset itself and of the data assets processed.", @@ -41,8 +41,8 @@ func (*PathTraversalRule) SupportedTags() []string { return []string{} } -func (r *PathTraversalRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *PathTraversalRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if !technicalAsset.Technologies.GetAttribute(types.IsFileStorage) { @@ -63,7 +63,7 @@ func (r *PathTraversalRule) GenerateRisks(input *types.ParsedModel) []types.Risk return risks } -func (r *PathTraversalRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink, likelihood types.RiskExploitationLikelihood) types.Risk { +func (r *PathTraversalRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink, likelihood types.RiskExploitationLikelihood) *types.Risk { caller := input.TechnicalAssets[incomingFlow.SourceId] title := "Path-Traversal risk at " + caller.Title + " against filesystem " + technicalAsset.Title + "" + " via " + incomingFlow.Title + "" @@ -71,8 +71,8 @@ func (r *PathTraversalRule) createRisk(input *types.ParsedModel, technicalAsset if technicalAsset.HighestProcessedConfidentiality(input) == types.StrictlyConfidential || technicalAsset.HighestProcessedIntegrity(input) == types.MissionCritical { impact = types.HighImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/push-instead-of-pull-deployment-rule.go b/pkg/security/risks/builtin/push-instead-of-pull-deployment-rule.go index 1da63a1d..8d523fea 100644 --- a/pkg/security/risks/builtin/push-instead-of-pull-deployment-rule.go +++ b/pkg/security/risks/builtin/push-instead-of-pull-deployment-rule.go @@ -10,9 +10,9 @@ func NewPushInsteadPullDeploymentRule() *PushInsteadPullDeploymentRule { return &PushInsteadPullDeploymentRule{} } -func (*PushInsteadPullDeploymentRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "push-instead-of-pull-deployment", +func (*PushInsteadPullDeploymentRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "push-instead-of-pull-deployment", Title: "Push instead of Pull Deployment", Description: "When comparing push-based vs. pull-based deployments from a security perspective, pull-based " + "deployments improve the overall security of the deployment targets. Every exposed interface of a production system to accept a deployment " + @@ -41,8 +41,8 @@ func (*PushInsteadPullDeploymentRule) SupportedTags() []string { return []string{} } -func (r *PushInsteadPullDeploymentRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *PushInsteadPullDeploymentRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) impact := types.LowImpact for _, buildPipeline := range input.TechnicalAssets { if buildPipeline.Technologies.GetAttribute(types.BuildPipeline) { @@ -63,10 +63,10 @@ func (r *PushInsteadPullDeploymentRule) GenerateRisks(input *types.ParsedModel) return risks } -func (r *PushInsteadPullDeploymentRule) createRisk(buildPipeline *types.TechnicalAsset, deploymentTarget *types.TechnicalAsset, deploymentCommLink *types.CommunicationLink, impact types.RiskExploitationImpact) types.Risk { +func (r *PushInsteadPullDeploymentRule) createRisk(buildPipeline *types.TechnicalAsset, deploymentTarget *types.TechnicalAsset, deploymentCommLink *types.CommunicationLink, impact types.RiskExploitationImpact) *types.Risk { title := "Push instead of Pull Deployment at " + deploymentTarget.Title + " via build pipeline asset " + buildPipeline.Title + "" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/search-query-injection-rule.go b/pkg/security/risks/builtin/search-query-injection-rule.go index 34c96bfc..cec0f4f1 100644 --- a/pkg/security/risks/builtin/search-query-injection-rule.go +++ b/pkg/security/risks/builtin/search-query-injection-rule.go @@ -10,9 +10,9 @@ func NewSearchQueryInjectionRule() *SearchQueryInjectionRule { return &SearchQueryInjectionRule{} } -func (*SearchQueryInjectionRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "search-query-injection", +func (*SearchQueryInjectionRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "search-query-injection", Title: "Search-Query Injection", Description: "When a search engine server is accessed Search-Query Injection risks might arise." + "

See for example https://github.com/veracode-research/solr-injection and " + @@ -42,8 +42,8 @@ func (*SearchQueryInjectionRule) SupportedTags() []string { return []string{} } -func (r *SearchQueryInjectionRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *SearchQueryInjectionRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.Technologies.GetAttribute(types.IsSearchRelated) { @@ -66,7 +66,7 @@ func (r *SearchQueryInjectionRule) GenerateRisks(input *types.ParsedModel) []typ return risks } -func (r *SearchQueryInjectionRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink, likelihood types.RiskExploitationLikelihood) types.Risk { +func (r *SearchQueryInjectionRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink, likelihood types.RiskExploitationLikelihood) *types.Risk { caller := input.TechnicalAssets[incomingFlow.SourceId] title := "Search Query Injection risk at " + caller.Title + " against search engine server " + technicalAsset.Title + "" + " via " + incomingFlow.Title + "" @@ -76,8 +76,8 @@ func (r *SearchQueryInjectionRule) createRisk(input *types.ParsedModel, technica } else if technicalAsset.HighestProcessedConfidentiality(input) <= types.Internal && technicalAsset.HighestProcessedIntegrity(input) == types.Operational { impact = types.LowImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/server-side-request-forgery-rule.go b/pkg/security/risks/builtin/server-side-request-forgery-rule.go index 5e6cd0a0..02a9b03a 100644 --- a/pkg/security/risks/builtin/server-side-request-forgery-rule.go +++ b/pkg/security/risks/builtin/server-side-request-forgery-rule.go @@ -10,9 +10,9 @@ func NewServerSideRequestForgeryRule() *ServerSideRequestForgeryRule { return &ServerSideRequestForgeryRule{} } -func (*ServerSideRequestForgeryRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "server-side-request-forgery", +func (*ServerSideRequestForgeryRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "server-side-request-forgery", Title: "Server-Side Request Forgery (SSRF)", Description: "When a server system (i.e. not a client) is accessing other server systems via typical web protocols " + "Server-Side Request Forgery (SSRF) or Local-File-Inclusion (LFI) or Remote-File-Inclusion (RFI) risks might arise. ", @@ -41,8 +41,8 @@ func (*ServerSideRequestForgeryRule) SupportedTags() []string { return []string{} } -func (r *ServerSideRequestForgeryRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *ServerSideRequestForgeryRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.OutOfScope || technicalAsset.Technologies.GetAttribute(types.IsClient) || technicalAsset.Technologies.GetAttribute(types.LoadBalancer) { @@ -57,7 +57,7 @@ func (r *ServerSideRequestForgeryRule) GenerateRisks(input *types.ParsedModel) [ return risks } -func (r *ServerSideRequestForgeryRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, outgoingFlow *types.CommunicationLink) types.Risk { +func (r *ServerSideRequestForgeryRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, outgoingFlow *types.CommunicationLink) *types.Risk { target := input.TechnicalAssets[outgoingFlow.TargetId] title := "Server-Side Request Forgery (SSRF) risk at " + technicalAsset.Title + " server-side web-requesting " + "the target " + target.Title + " via " + outgoingFlow.Title + "" @@ -93,8 +93,8 @@ func (r *ServerSideRequestForgeryRule) createRisk(input *types.ParsedModel, tech if outgoingFlow.Usage == types.DevOps { likelihood = types.Unlikely } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/service-registry-poisoning-rule.go b/pkg/security/risks/builtin/service-registry-poisoning-rule.go index 60df4f7d..c47ef7ea 100644 --- a/pkg/security/risks/builtin/service-registry-poisoning-rule.go +++ b/pkg/security/risks/builtin/service-registry-poisoning-rule.go @@ -10,9 +10,9 @@ func NewServiceRegistryPoisoningRule() *ServiceRegistryPoisoningRule { return &ServiceRegistryPoisoningRule{} } -func (*ServiceRegistryPoisoningRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "service-registry-poisoning", +func (*ServiceRegistryPoisoningRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "service-registry-poisoning", Title: "Service Registry Poisoning", Description: "When a service registry used for discovery of trusted service endpoints Service Registry Poisoning risks might arise.", Impact: "If this risk remains unmitigated, attackers might be able to poison the service registry with malicious service endpoints or " + @@ -38,8 +38,8 @@ func (*ServiceRegistryPoisoningRule) SupportedTags() []string { return []string{} } -func (r *ServiceRegistryPoisoningRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *ServiceRegistryPoisoningRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if !technicalAsset.OutOfScope && technicalAsset.Technologies.GetAttribute(types.ServiceRegistry) { @@ -50,7 +50,7 @@ func (r *ServiceRegistryPoisoningRule) GenerateRisks(input *types.ParsedModel) [ return risks } -func (r *ServiceRegistryPoisoningRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, incomingFlows []*types.CommunicationLink) types.Risk { +func (r *ServiceRegistryPoisoningRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, incomingFlows []*types.CommunicationLink) *types.Risk { title := "Service Registry Poisoning risk at " + technicalAsset.Title + "" impact := types.LowImpact @@ -64,8 +64,8 @@ func (r *ServiceRegistryPoisoningRule) createRisk(input *types.ParsedModel, tech } } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/sql-nosql-injection-rule.go b/pkg/security/risks/builtin/sql-nosql-injection-rule.go index 29886175..d32a58fd 100644 --- a/pkg/security/risks/builtin/sql-nosql-injection-rule.go +++ b/pkg/security/risks/builtin/sql-nosql-injection-rule.go @@ -10,9 +10,9 @@ func NewSqlNoSqlInjectionRule() *SqlNoSqlInjectionRule { return &SqlNoSqlInjectionRule{} } -func (*SqlNoSqlInjectionRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "sql-nosql-injection", +func (*SqlNoSqlInjectionRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "sql-nosql-injection", Title: "SQL/NoSQL-Injection", Description: "When a database is accessed via database access protocols SQL/NoSQL-Injection risks might arise. " + "The risk rating depends on the sensitivity technical asset itself and of the data assets processed.", @@ -38,8 +38,8 @@ func (*SqlNoSqlInjectionRule) SupportedTags() []string { return []string{} } -func (r *SqlNoSqlInjectionRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *SqlNoSqlInjectionRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] incomingFlows := input.IncomingTechnicalCommunicationLinksMappedByTargetId[technicalAsset.Id] @@ -56,7 +56,7 @@ func (r *SqlNoSqlInjectionRule) GenerateRisks(input *types.ParsedModel) []types. return risks } -func (r *SqlNoSqlInjectionRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink) types.Risk { +func (r *SqlNoSqlInjectionRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, incomingFlow *types.CommunicationLink) *types.Risk { caller := input.TechnicalAssets[incomingFlow.SourceId] title := "SQL/NoSQL-Injection risk at " + caller.Title + " against database " + technicalAsset.Title + "" + " via " + incomingFlow.Title + "" @@ -68,8 +68,8 @@ func (r *SqlNoSqlInjectionRule) createRisk(input *types.ParsedModel, technicalAs if incomingFlow.Usage == types.DevOps { likelihood = types.Likely } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/unchecked-deployment-rule.go b/pkg/security/risks/builtin/unchecked-deployment-rule.go index 484f509d..61edff11 100644 --- a/pkg/security/risks/builtin/unchecked-deployment-rule.go +++ b/pkg/security/risks/builtin/unchecked-deployment-rule.go @@ -10,9 +10,9 @@ func NewUncheckedDeploymentRule() *UncheckedDeploymentRule { return &UncheckedDeploymentRule{} } -func (*UncheckedDeploymentRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "unchecked-deployment", +func (*UncheckedDeploymentRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "unchecked-deployment", Title: "Unchecked Deployment", Description: "For each build-pipeline component Unchecked Deployment risks might arise when the build-pipeline " + "does not include established DevSecOps best-practices. DevSecOps best-practices scan as part of CI/CD pipelines for " + @@ -41,8 +41,8 @@ func (*UncheckedDeploymentRule) SupportedTags() []string { return []string{} } -func (r *UncheckedDeploymentRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UncheckedDeploymentRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, technicalAsset := range input.TechnicalAssets { if technicalAsset.Technologies.GetAttribute(types.IsDevelopmentRelevant) { risks = append(risks, r.createRisk(input, technicalAsset)) @@ -51,7 +51,7 @@ func (r *UncheckedDeploymentRule) GenerateRisks(input *types.ParsedModel) []type return risks } -func (r *UncheckedDeploymentRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset) types.Risk { +func (r *UncheckedDeploymentRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset) *types.Risk { title := "Unchecked Deployment risk at " + technicalAsset.Title + "" // impact is depending on highest rating impact := types.LowImpact @@ -81,8 +81,8 @@ func (r *UncheckedDeploymentRule) createRisk(input *types.ParsedModel, technical dataBreachTechnicalAssetIDs = append(dataBreachTechnicalAssetIDs, key) } // create risk - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/unencrypted-asset-rule.go b/pkg/security/risks/builtin/unencrypted-asset-rule.go index ed8cbde3..63773b12 100644 --- a/pkg/security/risks/builtin/unencrypted-asset-rule.go +++ b/pkg/security/risks/builtin/unencrypted-asset-rule.go @@ -10,9 +10,9 @@ func NewUnencryptedAssetRule() *UnencryptedAssetRule { return &UnencryptedAssetRule{} } -func (*UnencryptedAssetRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "unencrypted-asset", +func (*UnencryptedAssetRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "unencrypted-asset", Title: "Unencrypted Technical Assets", Description: "Due to the confidentiality rating of the technical asset itself and/or the stored data assets " + "this technical asset must be encrypted. The risk rating depends on the sensitivity technical asset itself and of the data assets stored.", @@ -44,8 +44,8 @@ func (*UnencryptedAssetRule) SupportedTags() []string { // check for technical assets that should be encrypted due to their confidentiality -func (r *UnencryptedAssetRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UnencryptedAssetRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if !technicalAsset.OutOfScope && !isEncryptionWaiver(technicalAsset) && len(technicalAsset.DataAssetsStored) > 0 && @@ -76,13 +76,13 @@ func isEncryptionWaiver(asset *types.TechnicalAsset) bool { return asset.Technologies.GetAttribute(types.IsNoStorageAtRest) || asset.Technologies.GetAttribute(types.IsEmbeddedComponent) } -func (r *UnencryptedAssetRule) createRisk(technicalAsset *types.TechnicalAsset, impact types.RiskExploitationImpact, requiresEndUserKey bool) types.Risk { +func (r *UnencryptedAssetRule) createRisk(technicalAsset *types.TechnicalAsset, impact types.RiskExploitationImpact, requiresEndUserKey bool) *types.Risk { title := "Unencrypted Technical Asset named " + technicalAsset.Title + "" if requiresEndUserKey { title += " missing end user individual encryption with " + types.DataWithEndUserIndividualKey.String() } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/unencrypted-communication-rule.go b/pkg/security/risks/builtin/unencrypted-communication-rule.go index 59d6d64c..08254a22 100644 --- a/pkg/security/risks/builtin/unencrypted-communication-rule.go +++ b/pkg/security/risks/builtin/unencrypted-communication-rule.go @@ -10,9 +10,9 @@ func NewUnencryptedCommunicationRule() *UnencryptedCommunicationRule { return &UnencryptedCommunicationRule{} } -func (*UnencryptedCommunicationRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "unencrypted-communication", +func (*UnencryptedCommunicationRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "unencrypted-communication", Title: "Unencrypted Communication", Description: "Due to the confidentiality and/or integrity rating of the data assets transferred over the " + "communication link this connection must be encrypted.", @@ -40,8 +40,8 @@ func (*UnencryptedCommunicationRule) SupportedTags() []string { // check for communication links that should be encrypted due to their confidentiality and/or integrity -func (r *UnencryptedCommunicationRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UnencryptedCommunicationRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, technicalAsset := range input.TechnicalAssets { for _, dataFlow := range technicalAsset.CommunicationLinks { transferringAuthData := dataFlow.Authentication != types.NoneAuthentication @@ -83,7 +83,7 @@ func (r *UnencryptedCommunicationRule) GenerateRisks(input *types.ParsedModel) [ return risks } -func (r *UnencryptedCommunicationRule) createRisk(input *types.ParsedModel, technicalAsset *types.TechnicalAsset, dataFlow *types.CommunicationLink, highRisk bool, transferringAuthData bool) types.Risk { +func (r *UnencryptedCommunicationRule) createRisk(input *types.Model, technicalAsset *types.TechnicalAsset, dataFlow *types.CommunicationLink, highRisk bool, transferringAuthData bool) *types.Risk { impact := types.MediumImpact if highRisk { impact = types.HighImpact @@ -101,8 +101,8 @@ func (r *UnencryptedCommunicationRule) createRisk(input *types.ParsedModel, tech if dataFlow.IsAcrossTrustBoundaryNetworkOnly(input) { likelihood = types.Likely } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/unguarded-access-from-internet-rule.go b/pkg/security/risks/builtin/unguarded-access-from-internet-rule.go index cc66cdd2..67fb0fd7 100644 --- a/pkg/security/risks/builtin/unguarded-access-from-internet-rule.go +++ b/pkg/security/risks/builtin/unguarded-access-from-internet-rule.go @@ -12,9 +12,9 @@ func NewUnguardedAccessFromInternetRule() *UnguardedAccessFromInternetRule { return &UnguardedAccessFromInternetRule{} } -func (*UnguardedAccessFromInternetRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "unguarded-access-from-internet", +func (*UnguardedAccessFromInternetRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "unguarded-access-from-internet", Title: "Unguarded Access From Internet", Description: "Internet-exposed assets must be guarded by a protecting service, application, " + "or reverse-proxy.", @@ -50,8 +50,8 @@ func (*UnguardedAccessFromInternetRule) SupportedTags() []string { return []string{} } -func (r *UnguardedAccessFromInternetRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UnguardedAccessFromInternetRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if !technicalAsset.OutOfScope { @@ -88,13 +88,13 @@ func (r *UnguardedAccessFromInternetRule) GenerateRisks(input *types.ParsedModel } func (r *UnguardedAccessFromInternetRule) createRisk(dataStore *types.TechnicalAsset, dataFlow *types.CommunicationLink, - clientFromInternet *types.TechnicalAsset, moreRisky bool) types.Risk { + clientFromInternet *types.TechnicalAsset, moreRisky bool) *types.Risk { impact := types.LowImpact if moreRisky || dataStore.RAA > 40 { impact = types.MediumImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.VeryLikely, impact), ExploitationLikelihood: types.VeryLikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/unguarded-direct-datastore-access-rule.go b/pkg/security/risks/builtin/unguarded-direct-datastore-access-rule.go index 157ba27d..68a28f40 100644 --- a/pkg/security/risks/builtin/unguarded-direct-datastore-access-rule.go +++ b/pkg/security/risks/builtin/unguarded-direct-datastore-access-rule.go @@ -10,9 +10,9 @@ func NewUnguardedDirectDatastoreAccessRule() *UnguardedDirectDatastoreAccessRule return &UnguardedDirectDatastoreAccessRule{} } -func (*UnguardedDirectDatastoreAccessRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "unguarded-direct-datastore-access", +func (*UnguardedDirectDatastoreAccessRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "unguarded-direct-datastore-access", Title: "Unguarded Direct Datastore Access", Description: "Data stores accessed across trust boundaries must be guarded by some protecting service or application.", Impact: "If this risk is unmitigated, attackers might be able to directly attack sensitive data stores without any protecting components in-between.", @@ -42,8 +42,8 @@ func (*UnguardedDirectDatastoreAccessRule) SupportedTags() []string { // check for data stores that should not be accessed directly across trust boundaries -func (r *UnguardedDirectDatastoreAccessRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UnguardedDirectDatastoreAccessRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if !technicalAsset.OutOfScope && technicalAsset.Type == types.Datastore { @@ -68,7 +68,7 @@ func (r *UnguardedDirectDatastoreAccessRule) GenerateRisks(input *types.ParsedMo return risks } -func isSharingSameParentTrustBoundary(input *types.ParsedModel, left, right *types.TechnicalAsset) bool { +func isSharingSameParentTrustBoundary(input *types.Model, left, right *types.TechnicalAsset) bool { tbIDLeft, tbIDRight := left.GetTrustBoundaryId(input), right.GetTrustBoundaryId(input) if len(tbIDLeft) == 0 && len(tbIDRight) > 0 { return false @@ -99,13 +99,13 @@ func fileServerAccessViaFTP(technicalAsset *types.TechnicalAsset, incomingAccess (incomingAccess.Protocol == types.FTP || incomingAccess.Protocol == types.FTPS || incomingAccess.Protocol == types.SFTP) } -func (r *UnguardedDirectDatastoreAccessRule) createRisk(dataStore *types.TechnicalAsset, dataFlow *types.CommunicationLink, clientOutsideTrustBoundary *types.TechnicalAsset, moreRisky bool) types.Risk { +func (r *UnguardedDirectDatastoreAccessRule) createRisk(dataStore *types.TechnicalAsset, dataFlow *types.CommunicationLink, clientOutsideTrustBoundary *types.TechnicalAsset, moreRisky bool) *types.Risk { impact := types.LowImpact if moreRisky || dataStore.RAA > 40 { impact = types.MediumImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Likely, impact), ExploitationLikelihood: types.Likely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/unnecessary-communication-link-rule.go b/pkg/security/risks/builtin/unnecessary-communication-link-rule.go index e2b8c030..6725cc68 100644 --- a/pkg/security/risks/builtin/unnecessary-communication-link-rule.go +++ b/pkg/security/risks/builtin/unnecessary-communication-link-rule.go @@ -10,9 +10,9 @@ func NewUnnecessaryCommunicationLinkRule() *UnnecessaryCommunicationLinkRule { return &UnnecessaryCommunicationLinkRule{} } -func (*UnnecessaryCommunicationLinkRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "unnecessary-communication-link", +func (*UnnecessaryCommunicationLinkRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "unnecessary-communication-link", Title: "Unnecessary Communication Link", Description: "When a technical communication link does not send or receive any data assets, this is " + "an indicator for an unnecessary communication link (or for an incomplete model).", @@ -36,8 +36,8 @@ func (*UnnecessaryCommunicationLinkRule) SupportedTags() []string { return []string{} } -func (r *UnnecessaryCommunicationLinkRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UnnecessaryCommunicationLinkRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] for _, commLink := range technicalAsset.CommunicationLinks { @@ -51,10 +51,10 @@ func (r *UnnecessaryCommunicationLinkRule) GenerateRisks(input *types.ParsedMode return risks } -func (r *UnnecessaryCommunicationLinkRule) createRisk(technicalAsset *types.TechnicalAsset, commLink *types.CommunicationLink) types.Risk { +func (r *UnnecessaryCommunicationLinkRule) createRisk(technicalAsset *types.TechnicalAsset, commLink *types.CommunicationLink) *types.Risk { title := "Unnecessary Communication Link titled " + commLink.Title + " at technical asset " + technicalAsset.Title + "" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, types.LowImpact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: types.LowImpact, diff --git a/pkg/security/risks/builtin/unnecessary-data-asset-rule.go b/pkg/security/risks/builtin/unnecessary-data-asset-rule.go index dcb6cc3d..64c1aab7 100644 --- a/pkg/security/risks/builtin/unnecessary-data-asset-rule.go +++ b/pkg/security/risks/builtin/unnecessary-data-asset-rule.go @@ -12,9 +12,9 @@ func NewUnnecessaryDataAssetRule() *UnnecessaryDataAssetRule { return &UnnecessaryDataAssetRule{} } -func (*UnnecessaryDataAssetRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "unnecessary-data-asset", +func (*UnnecessaryDataAssetRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "unnecessary-data-asset", Title: "Unnecessary Data Asset", Description: "When a data asset is not processed by any data assets and also not transferred by any " + "communication links, this is an indicator for an unnecessary data asset (or for an incomplete model).", @@ -40,8 +40,8 @@ func (*UnnecessaryDataAssetRule) SupportedTags() []string { return []string{} } -func (r *UnnecessaryDataAssetRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UnnecessaryDataAssetRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) // first create them in memory - otherwise in Go ranging over map is random order // range over them in sorted (hence re-producible) way: unusedDataAssetIDs := make(map[string]bool) @@ -75,11 +75,11 @@ func (r *UnnecessaryDataAssetRule) GenerateRisks(input *types.ParsedModel) []typ return risks } -func (r *UnnecessaryDataAssetRule) createRisk(input *types.ParsedModel, unusedDataAssetID string) types.Risk { +func (r *UnnecessaryDataAssetRule) createRisk(input *types.Model, unusedDataAssetID string) *types.Risk { unusedDataAsset := input.DataAssets[unusedDataAssetID] title := "Unnecessary Data Asset named " + unusedDataAsset.Title + "" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, types.LowImpact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: types.LowImpact, diff --git a/pkg/security/risks/builtin/unnecessary-data-transfer-rule.go b/pkg/security/risks/builtin/unnecessary-data-transfer-rule.go index 88a4d36b..bb31b2dc 100644 --- a/pkg/security/risks/builtin/unnecessary-data-transfer-rule.go +++ b/pkg/security/risks/builtin/unnecessary-data-transfer-rule.go @@ -12,9 +12,9 @@ func NewUnnecessaryDataTransferRule() *UnnecessaryDataTransferRule { return &UnnecessaryDataTransferRule{} } -func (*UnnecessaryDataTransferRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "unnecessary-data-transfer", +func (*UnnecessaryDataTransferRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "unnecessary-data-transfer", Title: "Unnecessary Data Transfer", Description: "When a technical asset sends or receives data assets, which it neither processes or stores this is " + "an indicator for unnecessarily transferred data (or for an incomplete model). When the unnecessarily " + @@ -45,8 +45,8 @@ func (*UnnecessaryDataTransferRule) SupportedTags() []string { return []string{} } -func (r *UnnecessaryDataTransferRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UnnecessaryDataTransferRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.OutOfScope { @@ -74,12 +74,11 @@ func (r *UnnecessaryDataTransferRule) GenerateRisks(input *types.ParsedModel) [] return risks } -func (r *UnnecessaryDataTransferRule) checkRisksAgainstTechnicalAsset(input *types.ParsedModel, risks []types.Risk, technicalAsset *types.TechnicalAsset, - dataFlow *types.CommunicationLink, inverseDirection bool) []types.Risk { +func (r *UnnecessaryDataTransferRule) checkRisksAgainstTechnicalAsset(input *types.Model, risks []*types.Risk, technicalAsset *types.TechnicalAsset, dataFlow *types.CommunicationLink, inverseDirection bool) []*types.Risk { for _, transferredDataAssetId := range dataFlow.DataAssetsSent { if !technicalAsset.ProcessesOrStoresDataAsset(transferredDataAssetId) { transferredDataAsset := input.DataAssets[transferredDataAssetId] - //fmt.Print("--->>> Checking "+technicalAsset.Id+": "+transferredDataAsset.Id+" sent via "+dataFlow.Id+"\n") + //fmt.Print("--->>> Checking "+technicalAsset.ID+": "+transferredDataAsset.ID+" sent via "+dataFlow.ID+"\n") if transferredDataAsset.Confidentiality >= types.Confidential || transferredDataAsset.Integrity >= types.Critical { commPartnerId := dataFlow.TargetId if inverseDirection { @@ -96,7 +95,7 @@ func (r *UnnecessaryDataTransferRule) checkRisksAgainstTechnicalAsset(input *typ for _, transferredDataAssetId := range dataFlow.DataAssetsReceived { if !technicalAsset.ProcessesOrStoresDataAsset(transferredDataAssetId) { transferredDataAsset := input.DataAssets[transferredDataAssetId] - //fmt.Print("--->>> Checking "+technicalAsset.Id+": "+transferredDataAsset.Id+" received via "+dataFlow.Id+"\n") + //fmt.Print("--->>> Checking "+technicalAsset.ID+": "+transferredDataAsset.ID+" received via "+dataFlow.ID+"\n") if transferredDataAsset.Confidentiality >= types.Confidential || transferredDataAsset.Integrity >= types.Critical { commPartnerId := dataFlow.TargetId if inverseDirection { @@ -113,7 +112,7 @@ func (r *UnnecessaryDataTransferRule) checkRisksAgainstTechnicalAsset(input *typ return risks } -func isNewRisk(risks []types.Risk, risk types.Risk) bool { +func isNewRisk(risks []*types.Risk, risk *types.Risk) bool { for _, check := range risks { if check.SyntheticId == risk.SyntheticId { return false @@ -122,7 +121,7 @@ func isNewRisk(risks []types.Risk, risk types.Risk) bool { return true } -func (r *UnnecessaryDataTransferRule) createRisk(technicalAsset *types.TechnicalAsset, dataAssetTransferred *types.DataAsset, commPartnerAsset *types.TechnicalAsset) types.Risk { +func (r *UnnecessaryDataTransferRule) createRisk(technicalAsset *types.TechnicalAsset, dataAssetTransferred *types.DataAsset, commPartnerAsset *types.TechnicalAsset) *types.Risk { moreRisky := dataAssetTransferred.Confidentiality == types.StrictlyConfidential || dataAssetTransferred.Integrity == types.MissionCritical impact := types.LowImpact @@ -132,8 +131,8 @@ func (r *UnnecessaryDataTransferRule) createRisk(technicalAsset *types.Technical title := "Unnecessary Data Transfer of " + dataAssetTransferred.Title + " data at " + technicalAsset.Title + " " + "from/to " + commPartnerAsset.Title + "" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, impact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/unnecessary-technical-asset-rule.go b/pkg/security/risks/builtin/unnecessary-technical-asset-rule.go index 7a3470e6..9caca9f1 100644 --- a/pkg/security/risks/builtin/unnecessary-technical-asset-rule.go +++ b/pkg/security/risks/builtin/unnecessary-technical-asset-rule.go @@ -10,9 +10,9 @@ func NewUnnecessaryTechnicalAssetRule() *UnnecessaryTechnicalAssetRule { return &UnnecessaryTechnicalAssetRule{} } -func (*UnnecessaryTechnicalAssetRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "unnecessary-technical-asset", +func (*UnnecessaryTechnicalAssetRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "unnecessary-technical-asset", Title: "Unnecessary Technical Asset", Description: "When a technical asset does not process any data assets, this is " + "an indicator for an unnecessary technical asset (or for an incomplete model). " + @@ -37,8 +37,8 @@ func (*UnnecessaryTechnicalAssetRule) SupportedTags() []string { return []string{} } -func (r *UnnecessaryTechnicalAssetRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UnnecessaryTechnicalAssetRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if len(technicalAsset.DataAssetsProcessed) == 0 && len(technicalAsset.DataAssetsStored) == 0 || @@ -49,10 +49,10 @@ func (r *UnnecessaryTechnicalAssetRule) GenerateRisks(input *types.ParsedModel) return risks } -func (r *UnnecessaryTechnicalAssetRule) createRisk(technicalAsset *types.TechnicalAsset) types.Risk { +func (r *UnnecessaryTechnicalAssetRule) createRisk(technicalAsset *types.TechnicalAsset) *types.Risk { title := "Unnecessary Technical Asset named " + technicalAsset.Title + "" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, types.LowImpact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: types.LowImpact, diff --git a/pkg/security/risks/builtin/untrusted-deserialization-rule.go b/pkg/security/risks/builtin/untrusted-deserialization-rule.go index 742ed31c..d7f3ff02 100644 --- a/pkg/security/risks/builtin/untrusted-deserialization-rule.go +++ b/pkg/security/risks/builtin/untrusted-deserialization-rule.go @@ -10,9 +10,9 @@ func NewUntrustedDeserializationRule() *UntrustedDeserializationRule { return &UntrustedDeserializationRule{} } -func (*UntrustedDeserializationRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "untrusted-deserialization", +func (*UntrustedDeserializationRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "untrusted-deserialization", Title: "Untrusted Deserialization", Description: "When a technical asset accepts data in a specific serialized form (like Java or .NET serialization), " + "Untrusted Deserialization risks might arise." + @@ -42,8 +42,8 @@ func (*UntrustedDeserializationRule) SupportedTags() []string { return []string{} } -func (r *UntrustedDeserializationRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *UntrustedDeserializationRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.OutOfScope { @@ -77,7 +77,7 @@ func (r *UntrustedDeserializationRule) GenerateRisks(input *types.ParsedModel) [ return risks } -func (r *UntrustedDeserializationRule) createRisk(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset, acrossTrustBoundary bool, commLinkTitle string) types.Risk { +func (r *UntrustedDeserializationRule) createRisk(parsedModel *types.Model, technicalAsset *types.TechnicalAsset, acrossTrustBoundary bool, commLinkTitle string) *types.Risk { title := "Untrusted Deserialization risk at " + technicalAsset.Title + "" impact := types.HighImpact likelihood := types.Likely @@ -90,8 +90,8 @@ func (r *UntrustedDeserializationRule) createRisk(parsedModel *types.ParsedModel technicalAsset.HighestProcessedAvailability(parsedModel) == types.MissionCritical { impact = types.VeryHighImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(likelihood, impact), ExploitationLikelihood: likelihood, ExploitationImpact: impact, diff --git a/pkg/security/risks/builtin/wrong-communication-link-content-rule.go b/pkg/security/risks/builtin/wrong-communication-link-content-rule.go index de1422e2..2205d0df 100644 --- a/pkg/security/risks/builtin/wrong-communication-link-content-rule.go +++ b/pkg/security/risks/builtin/wrong-communication-link-content-rule.go @@ -10,9 +10,9 @@ func NewWrongCommunicationLinkContentRule() *WrongCommunicationLinkContentRule { return &WrongCommunicationLinkContentRule{} } -func (*WrongCommunicationLinkContentRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "wrong-communication-link-content", +func (*WrongCommunicationLinkContentRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "wrong-communication-link-content", Title: "Wrong Communication Link Content", Description: "When a communication link is defined as readonly, but does not receive any data asset, " + "or when it is defined as not readonly, but does not send any data asset, it is likely to be a model failure.", @@ -37,8 +37,8 @@ func (*WrongCommunicationLinkContentRule) SupportedTags() []string { return []string{} } -func (r *WrongCommunicationLinkContentRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *WrongCommunicationLinkContentRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, techAsset := range input.TechnicalAssets { for _, commLink := range techAsset.CommunicationLinks { // check readonly consistency @@ -72,11 +72,11 @@ func (r *WrongCommunicationLinkContentRule) GenerateRisks(input *types.ParsedMod return risks } -func (r *WrongCommunicationLinkContentRule) createRisk(technicalAsset *types.TechnicalAsset, commLink *types.CommunicationLink, reason string) types.Risk { +func (r *WrongCommunicationLinkContentRule) createRisk(technicalAsset *types.TechnicalAsset, commLink *types.CommunicationLink, reason string) *types.Risk { title := "Wrong Communication Link Content " + reason + " at " + technicalAsset.Title + " " + "regarding communication link " + commLink.Title + "" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, types.LowImpact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: types.LowImpact, diff --git a/pkg/security/risks/builtin/wrong-trust-boundary-content.go b/pkg/security/risks/builtin/wrong-trust-boundary-content.go index 368c5cae..86fd462f 100644 --- a/pkg/security/risks/builtin/wrong-trust-boundary-content.go +++ b/pkg/security/risks/builtin/wrong-trust-boundary-content.go @@ -10,9 +10,9 @@ func NewWrongTrustBoundaryContentRule() *WrongTrustBoundaryContentRule { return &WrongTrustBoundaryContentRule{} } -func (*WrongTrustBoundaryContentRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "wrong-trust-boundary-content", +func (*WrongTrustBoundaryContentRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "wrong-trust-boundary-content", Title: "Wrong Trust Boundary Content", Description: "When a trust boundary of type " + types.NetworkPolicyNamespaceIsolation.String() + " contains " + "non-container assets it is likely to be a model failure.", @@ -36,8 +36,8 @@ func (*WrongTrustBoundaryContentRule) SupportedTags() []string { return []string{} } -func (r *WrongTrustBoundaryContentRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *WrongTrustBoundaryContentRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, trustBoundary := range input.TrustBoundaries { if trustBoundary.Type == types.NetworkPolicyNamespaceIsolation { for _, techAssetID := range trustBoundary.TechnicalAssetsInside { @@ -51,10 +51,10 @@ func (r *WrongTrustBoundaryContentRule) GenerateRisks(input *types.ParsedModel) return risks } -func (r *WrongTrustBoundaryContentRule) createRisk(technicalAsset *types.TechnicalAsset) types.Risk { +func (r *WrongTrustBoundaryContentRule) createRisk(technicalAsset *types.TechnicalAsset) *types.Risk { title := "Wrong Trust Boundary Content (non-container asset inside container trust boundary) at " + technicalAsset.Title + "" - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.Unlikely, types.LowImpact), ExploitationLikelihood: types.Unlikely, ExploitationImpact: types.LowImpact, diff --git a/pkg/security/risks/builtin/xml-external-entity-rule.go b/pkg/security/risks/builtin/xml-external-entity-rule.go index 9d80fd73..62a4accb 100644 --- a/pkg/security/risks/builtin/xml-external-entity-rule.go +++ b/pkg/security/risks/builtin/xml-external-entity-rule.go @@ -10,9 +10,9 @@ func NewXmlExternalEntityRule() *XmlExternalEntityRule { return &XmlExternalEntityRule{} } -func (*XmlExternalEntityRule) Category() types.RiskCategory { - return types.RiskCategory{ - Id: "xml-external-entity", +func (*XmlExternalEntityRule) Category() *types.RiskCategory { + return &types.RiskCategory{ + ID: "xml-external-entity", Title: "XML External Entity (XXE)", Description: "When a technical asset accepts data in XML format, XML External Entity (XXE) risks might arise.", Impact: "If this risk is unmitigated, attackers might be able to read sensitive files (configuration data, key/credential files, deployment files, " + @@ -40,8 +40,8 @@ func (*XmlExternalEntityRule) SupportedTags() []string { return []string{} } -func (r *XmlExternalEntityRule) GenerateRisks(input *types.ParsedModel) []types.Risk { - risks := make([]types.Risk, 0) +func (r *XmlExternalEntityRule) GenerateRisks(input *types.Model) []*types.Risk { + risks := make([]*types.Risk, 0) for _, id := range input.SortedTechnicalAssetIDs() { technicalAsset := input.TechnicalAssets[id] if technicalAsset.OutOfScope { @@ -56,7 +56,7 @@ func (r *XmlExternalEntityRule) GenerateRisks(input *types.ParsedModel) []types. return risks } -func (r *XmlExternalEntityRule) createRisk(parsedModel *types.ParsedModel, technicalAsset *types.TechnicalAsset) types.Risk { +func (r *XmlExternalEntityRule) createRisk(parsedModel *types.Model, technicalAsset *types.TechnicalAsset) *types.Risk { title := "XML External Entity (XXE) risk at " + technicalAsset.Title + "" impact := types.MediumImpact if technicalAsset.HighestProcessedConfidentiality(parsedModel) == types.StrictlyConfidential || @@ -64,8 +64,8 @@ func (r *XmlExternalEntityRule) createRisk(parsedModel *types.ParsedModel, techn technicalAsset.HighestProcessedAvailability(parsedModel) == types.MissionCritical { impact = types.HighImpact } - risk := types.Risk{ - CategoryId: r.Category().Id, + risk := &types.Risk{ + CategoryId: r.Category().ID, Severity: types.CalculateSeverity(types.VeryLikely, impact), ExploitationLikelihood: types.VeryLikely, ExploitationImpact: impact, diff --git a/pkg/security/risks/risk-rule.go b/pkg/security/risks/risk-rule.go index 536fa9ca..35921b45 100644 --- a/pkg/security/risks/risk-rule.go +++ b/pkg/security/risks/risk-rule.go @@ -3,7 +3,17 @@ package risks import "github.com/threagile/threagile/pkg/security/types" type RiskRule interface { - Category() types.RiskCategory + Category() *types.RiskCategory SupportedTags() []string - GenerateRisks(*types.ParsedModel) []types.Risk + GenerateRisks(*types.Model) []*types.Risk +} + +type RiskRules map[string]RiskRule + +func (what RiskRules) Merge(rules RiskRules) RiskRules { + for key, value := range rules { + what[key] = value + } + + return what } diff --git a/pkg/security/risks/risks.go b/pkg/security/risks/risks.go index d9c788f9..6ee80db9 100644 --- a/pkg/security/risks/risks.go +++ b/pkg/security/risks/risks.go @@ -4,8 +4,9 @@ import ( "github.com/threagile/threagile/pkg/security/risks/builtin" ) -func GetBuiltInRiskRules() []RiskRule { - return []RiskRule{ +func GetBuiltInRiskRules() RiskRules { + rules := make(RiskRules) + for _, rule := range []RiskRule{ builtin.NewAccidentalSecretLeakRule(), builtin.NewCodeBackdooringRule(), builtin.NewContainerBaseImageBackdooringRule(), @@ -48,5 +49,9 @@ func GetBuiltInRiskRules() []RiskRule { builtin.NewWrongCommunicationLinkContentRule(), builtin.NewWrongTrustBoundaryContentRule(), builtin.NewXmlExternalEntityRule(), + } { + rules[rule.Category().ID] = rule } + + return rules } diff --git a/pkg/security/types/authentication.go b/pkg/security/types/authentication.go index fba8fa65..88f47cae 100644 --- a/pkg/security/types/authentication.go +++ b/pkg/security/types/authentication.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -66,3 +68,37 @@ func (what Authentication) Find(value string) (Authentication, error) { return Authentication(0), fmt.Errorf("unknown authentication value %q", value) } + +func (what Authentication) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *Authentication) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what Authentication) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *Authentication) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/authorization.go b/pkg/security/types/authorization.go index 60928d2d..303f0de6 100644 --- a/pkg/security/types/authorization.go +++ b/pkg/security/types/authorization.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -53,3 +55,37 @@ func (what Authorization) Find(value string) (Authorization, error) { return Authorization(0), fmt.Errorf("unknown authorization value %q", value) } + +func (what Authorization) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *Authorization) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what Authorization) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *Authorization) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/communication_link.go b/pkg/security/types/communication_link.go index c7e5396a..a36adb35 100644 --- a/pkg/security/types/communication_link.go +++ b/pkg/security/types/communication_link.go @@ -36,13 +36,13 @@ func (what CommunicationLink) IsTaggedWithBaseTag(baseTag string) bool { return IsTaggedWithBaseTag(what.Tags, baseTag) } -func (what CommunicationLink) IsAcrossTrustBoundary(parsedModel *ParsedModel) bool { +func (what CommunicationLink) IsAcrossTrustBoundary(parsedModel *Model) bool { trustBoundaryOfSourceAsset := parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId[what.SourceId] trustBoundaryOfTargetAsset := parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId[what.TargetId] return trustBoundaryOfSourceAsset.Id != trustBoundaryOfTargetAsset.Id } -func (what CommunicationLink) IsAcrossTrustBoundaryNetworkOnly(parsedModel *ParsedModel) bool { +func (what CommunicationLink) IsAcrossTrustBoundaryNetworkOnly(parsedModel *Model) bool { trustBoundaryOfSourceAsset := parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId[what.SourceId] if !trustBoundaryOfSourceAsset.Type.IsNetworkBoundary() { // find and use the parent boundary then trustBoundaryOfSourceAsset = parsedModel.TrustBoundaries[trustBoundaryOfSourceAsset.ParentTrustBoundaryID(parsedModel)] @@ -54,7 +54,7 @@ func (what CommunicationLink) IsAcrossTrustBoundaryNetworkOnly(parsedModel *Pars return trustBoundaryOfSourceAsset.Id != trustBoundaryOfTargetAsset.Id && trustBoundaryOfTargetAsset.Type.IsNetworkBoundary() } -func (what CommunicationLink) HighestConfidentiality(parsedModel *ParsedModel) Confidentiality { +func (what CommunicationLink) HighestConfidentiality(parsedModel *Model) Confidentiality { highest := Public for _, dataId := range what.DataAssetsSent { dataAsset := parsedModel.DataAssets[dataId] @@ -71,7 +71,7 @@ func (what CommunicationLink) HighestConfidentiality(parsedModel *ParsedModel) C return highest } -func (what CommunicationLink) HighestIntegrity(parsedModel *ParsedModel) Criticality { +func (what CommunicationLink) HighestIntegrity(parsedModel *Model) Criticality { highest := Archive for _, dataId := range what.DataAssetsSent { dataAsset := parsedModel.DataAssets[dataId] @@ -88,7 +88,7 @@ func (what CommunicationLink) HighestIntegrity(parsedModel *ParsedModel) Critica return highest } -func (what CommunicationLink) HighestAvailability(parsedModel *ParsedModel) Criticality { +func (what CommunicationLink) HighestAvailability(parsedModel *Model) Criticality { highest := Archive for _, dataId := range what.DataAssetsSent { dataAsset := parsedModel.DataAssets[dataId] @@ -105,7 +105,7 @@ func (what CommunicationLink) HighestAvailability(parsedModel *ParsedModel) Crit return highest } -func (what CommunicationLink) DataAssetsSentSorted(parsedModel *ParsedModel) []*DataAsset { +func (what CommunicationLink) DataAssetsSentSorted(parsedModel *Model) []*DataAsset { result := make([]*DataAsset, 0) for _, assetID := range what.DataAssetsSent { result = append(result, parsedModel.DataAssets[assetID]) @@ -114,7 +114,7 @@ func (what CommunicationLink) DataAssetsSentSorted(parsedModel *ParsedModel) []* return result } -func (what CommunicationLink) DataAssetsReceivedSorted(parsedModel *ParsedModel) []*DataAsset { +func (what CommunicationLink) DataAssetsReceivedSorted(parsedModel *Model) []*DataAsset { result := make([]*DataAsset, 0) for _, assetID := range what.DataAssetsReceived { result = append(result, parsedModel.DataAssets[assetID]) diff --git a/pkg/security/types/confidentiality.go b/pkg/security/types/confidentiality.go index bf97b2c0..f94229d4 100644 --- a/pkg/security/types/confidentiality.go +++ b/pkg/security/types/confidentiality.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -93,3 +95,37 @@ func (what Confidentiality) Find(value string) (Confidentiality, error) { return Confidentiality(0), fmt.Errorf("unknown confidentiality value %q", value) } + +func (what Confidentiality) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *Confidentiality) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what Confidentiality) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *Confidentiality) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/criticality.go b/pkg/security/types/criticality.go index 841d0388..8f38cf6e 100644 --- a/pkg/security/types/criticality.go +++ b/pkg/security/types/criticality.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -93,3 +95,37 @@ func (what Criticality) Find(value string) (Criticality, error) { return Criticality(0), fmt.Errorf("unknown criticality value %q", value) } + +func (what Criticality) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *Criticality) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what Criticality) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *Criticality) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/data_asset.go b/pkg/security/types/data_asset.go index 185a17da..b7d21a72 100644 --- a/pkg/security/types/data_asset.go +++ b/pkg/security/types/data_asset.go @@ -66,7 +66,7 @@ func (what DataAsset) IdentifiedRiskSeverityStillAtRisk() RiskSeverity { } */ -func (what DataAsset) IdentifiedRisksByResponsibleTechnicalAssetId(model *ParsedModel) map[string][]Risk { +func (what DataAsset) IdentifiedRisksByResponsibleTechnicalAssetId(model *Model) map[string][]*Risk { uniqueTechAssetIDsResponsibleForThisDataAsset := make(map[string]interface{}) for _, techAsset := range what.ProcessedByTechnicalAssetsSorted(model) { if len(techAsset.GeneratedRisks(model)) > 0 { @@ -79,14 +79,14 @@ func (what DataAsset) IdentifiedRisksByResponsibleTechnicalAssetId(model *Parsed } } - result := make(map[string][]Risk) + result := make(map[string][]*Risk) for techAssetId := range uniqueTechAssetIDsResponsibleForThisDataAsset { result[techAssetId] = append(result[techAssetId], model.TechnicalAssets[techAssetId].GeneratedRisks(model)...) } return result } -func (what DataAsset) IsDataBreachPotentialStillAtRisk(parsedModel *ParsedModel) bool { +func (what DataAsset) IsDataBreachPotentialStillAtRisk(parsedModel *Model) bool { for _, risk := range FilteredByStillAtRisk(parsedModel) { for _, techAsset := range risk.DataBreachTechnicalAssetIDs { if contains(parsedModel.TechnicalAssets[techAsset].DataAssetsProcessed, what.Id) { @@ -97,7 +97,7 @@ func (what DataAsset) IsDataBreachPotentialStillAtRisk(parsedModel *ParsedModel) return false } -func (what DataAsset) IdentifiedDataBreachProbability(parsedModel *ParsedModel) DataBreachProbability { +func (what DataAsset) IdentifiedDataBreachProbability(parsedModel *Model) DataBreachProbability { highestProbability := Improbable for _, risk := range AllRisks(parsedModel) { for _, techAsset := range risk.DataBreachTechnicalAssetIDs { @@ -112,7 +112,7 @@ func (what DataAsset) IdentifiedDataBreachProbability(parsedModel *ParsedModel) return highestProbability } -func (what DataAsset) IdentifiedDataBreachProbabilityStillAtRisk(parsedModel *ParsedModel) DataBreachProbability { +func (what DataAsset) IdentifiedDataBreachProbabilityStillAtRisk(parsedModel *Model) DataBreachProbability { highestProbability := Improbable for _, risk := range FilteredByStillAtRisk(parsedModel) { for _, techAsset := range risk.DataBreachTechnicalAssetIDs { @@ -127,8 +127,8 @@ func (what DataAsset) IdentifiedDataBreachProbabilityStillAtRisk(parsedModel *Pa return highestProbability } -func (what DataAsset) IdentifiedDataBreachProbabilityRisksStillAtRisk(parsedModel *ParsedModel) []Risk { - result := make([]Risk, 0) +func (what DataAsset) IdentifiedDataBreachProbabilityRisksStillAtRisk(parsedModel *Model) []*Risk { + result := make([]*Risk, 0) for _, risk := range FilteredByStillAtRisk(parsedModel) { for _, techAsset := range risk.DataBreachTechnicalAssetIDs { if contains(parsedModel.TechnicalAssets[techAsset].DataAssetsProcessed, what.Id) { @@ -140,8 +140,8 @@ func (what DataAsset) IdentifiedDataBreachProbabilityRisksStillAtRisk(parsedMode return result } -func (what DataAsset) IdentifiedDataBreachProbabilityRisks(parsedModel *ParsedModel) []Risk { - result := make([]Risk, 0) +func (what DataAsset) IdentifiedDataBreachProbabilityRisks(parsedModel *Model) []*Risk { + result := make([]*Risk, 0) for _, risk := range AllRisks(parsedModel) { for _, techAsset := range risk.DataBreachTechnicalAssetIDs { if contains(parsedModel.TechnicalAssets[techAsset].DataAssetsProcessed, what.Id) { @@ -153,7 +153,7 @@ func (what DataAsset) IdentifiedDataBreachProbabilityRisks(parsedModel *ParsedMo return result } -func (what DataAsset) ProcessedByTechnicalAssetsSorted(parsedModel *ParsedModel) []*TechnicalAsset { +func (what DataAsset) ProcessedByTechnicalAssetsSorted(parsedModel *Model) []*TechnicalAsset { result := make([]*TechnicalAsset, 0) for _, technicalAsset := range parsedModel.TechnicalAssets { for _, candidateID := range technicalAsset.DataAssetsProcessed { @@ -166,7 +166,7 @@ func (what DataAsset) ProcessedByTechnicalAssetsSorted(parsedModel *ParsedModel) return result } -func (what DataAsset) StoredByTechnicalAssetsSorted(parsedModel *ParsedModel) []*TechnicalAsset { +func (what DataAsset) StoredByTechnicalAssetsSorted(parsedModel *Model) []*TechnicalAsset { result := make([]*TechnicalAsset, 0) for _, technicalAsset := range parsedModel.TechnicalAssets { for _, candidateID := range technicalAsset.DataAssetsStored { @@ -179,7 +179,7 @@ func (what DataAsset) StoredByTechnicalAssetsSorted(parsedModel *ParsedModel) [] return result } -func (what DataAsset) SentViaCommLinksSorted(parsedModel *ParsedModel) []*CommunicationLink { +func (what DataAsset) SentViaCommLinksSorted(parsedModel *Model) []*CommunicationLink { result := make([]*CommunicationLink, 0) for _, technicalAsset := range parsedModel.TechnicalAssets { for _, commLink := range technicalAsset.CommunicationLinks { @@ -194,7 +194,7 @@ func (what DataAsset) SentViaCommLinksSorted(parsedModel *ParsedModel) []*Commun return result } -func (what DataAsset) ReceivedViaCommLinksSorted(parsedModel *ParsedModel) []*CommunicationLink { +func (what DataAsset) ReceivedViaCommLinksSorted(parsedModel *Model) []*CommunicationLink { result := make([]*CommunicationLink, 0) for _, technicalAsset := range parsedModel.TechnicalAssets { for _, commLink := range technicalAsset.CommunicationLinks { @@ -209,7 +209,7 @@ func (what DataAsset) ReceivedViaCommLinksSorted(parsedModel *ParsedModel) []*Co return result } -func SortByDataAssetDataBreachProbabilityAndTitle(parsedModel *ParsedModel, assets []*DataAsset) { +func SortByDataAssetDataBreachProbabilityAndTitle(parsedModel *Model, assets []*DataAsset) { sort.Slice(assets, func(i, j int) bool { highestDataBreachProbabilityLeft := assets[i].IdentifiedDataBreachProbability(parsedModel) highestDataBreachProbabilityRight := assets[j].IdentifiedDataBreachProbability(parsedModel) @@ -220,7 +220,7 @@ func SortByDataAssetDataBreachProbabilityAndTitle(parsedModel *ParsedModel, asse }) } -func SortByDataAssetDataBreachProbabilityAndTitleStillAtRisk(parsedModel *ParsedModel, assets []*DataAsset) { +func SortByDataAssetDataBreachProbabilityAndTitleStillAtRisk(parsedModel *Model, assets []*DataAsset) { sort.Slice(assets, func(i, j int) bool { risksLeft := assets[i].IdentifiedDataBreachProbabilityRisksStillAtRisk(parsedModel) risksRight := assets[j].IdentifiedDataBreachProbabilityRisksStillAtRisk(parsedModel) diff --git a/pkg/security/types/data_breach_probability.go b/pkg/security/types/data_breach_probability.go index ae7fa92e..4ed39f81 100644 --- a/pkg/security/types/data_breach_probability.go +++ b/pkg/security/types/data_breach_probability.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -61,3 +63,37 @@ func (what DataBreachProbability) Find(value string) (DataBreachProbability, err return DataBreachProbability(0), fmt.Errorf("unknown data breach probability value %q", value) } + +func (what DataBreachProbability) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *DataBreachProbability) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what DataBreachProbability) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *DataBreachProbability) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/data_format.go b/pkg/security/types/data_format.go index d8493255..12e08774 100644 --- a/pkg/security/types/data_format.go +++ b/pkg/security/types/data_format.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -72,3 +74,47 @@ func (what ByDataFormatAcceptedSort) Swap(i, j int) { what[i], what[j] = what[j] func (what ByDataFormatAcceptedSort) Less(i, j int) bool { return what[i].String() < what[j].String() } + +func (what DataFormat) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *DataFormat) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what DataFormat) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *DataFormat) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what DataFormat) find(value string) (DataFormat, error) { + for index, description := range DataFormatTypeDescription { + if strings.EqualFold(value, description.Name) { + return DataFormat(index), nil + } + } + + return DataFormat(0), fmt.Errorf("unknown data format value %q", value) +} diff --git a/pkg/security/types/date.go b/pkg/security/types/date.go index f430d450..345f5ac2 100644 --- a/pkg/security/types/date.go +++ b/pkg/security/types/date.go @@ -1,9 +1,45 @@ package types import ( + "gopkg.in/yaml.v3" "time" ) +const ( + jsonDateFormat = `"2006-01-02"` + yamlDateFormat = `2006-01-02` +) + type Date struct { time.Time } + +func (what Date) MarshalJSON() ([]byte, error) { + return []byte(what.Format(jsonDateFormat)), nil +} + +func (what *Date) UnmarshalJSON(bytes []byte) error { + date, parseError := time.Parse(jsonDateFormat, string(bytes)) + if parseError != nil { + return parseError + } + + what.Time = date + + return nil +} + +func (what Date) MarshalYAML() (interface{}, error) { + return what.Format(yamlDateFormat), nil +} + +func (what *Date) UnmarshalYAML(node *yaml.Node) error { + date, parseError := time.Parse(yamlDateFormat, node.Value) + if parseError != nil { + return parseError + } + + what.Time = date + + return nil +} diff --git a/pkg/security/types/encryption_style.go b/pkg/security/types/encryption_style.go index 8df8ee7d..2abbb989 100644 --- a/pkg/security/types/encryption_style.go +++ b/pkg/security/types/encryption_style.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -63,3 +65,37 @@ func (what EncryptionStyle) Find(value string) (EncryptionStyle, error) { return EncryptionStyle(0), fmt.Errorf("unknown encryption style value %q", value) } + +func (what EncryptionStyle) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *EncryptionStyle) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what EncryptionStyle) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *EncryptionStyle) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/model.go b/pkg/security/types/model.go index 65129e7d..a1959056 100644 --- a/pkg/security/types/model.go +++ b/pkg/security/types/model.go @@ -18,7 +18,7 @@ import ( // rename parsedModel to model or something like this to emphasize that it's just a model // maybe -type ParsedModel struct { +type Model struct { ThreagileVersion string `yaml:"threagile_version,omitempty" json:"threagile_version,omitempty"` Includes []string `yaml:"includes,omitempty" json:"includes,omitempty"` Title string `json:"title,omitempty" yaml:"title,omitempty"` @@ -38,9 +38,9 @@ type ParsedModel struct { TechnicalAssets map[string]*TechnicalAsset `json:"technical_assets,omitempty" yaml:"technical_assets,omitempty"` TrustBoundaries map[string]*TrustBoundary `json:"trust_boundaries,omitempty" yaml:"trust_boundaries,omitempty"` SharedRuntimes map[string]*SharedRuntime `json:"shared_runtimes,omitempty" yaml:"shared_runtimes,omitempty"` - IndividualRiskCategories map[string]RiskCategory `json:"individual_risk_categories,omitempty" yaml:"individual_risk_categories,omitempty"` - BuiltInRiskCategories map[string]RiskCategory `json:"built_in_risk_categories,omitempty" yaml:"built_in_risk_categories,omitempty"` - RiskTracking map[string]RiskTracking `json:"risk_tracking,omitempty" yaml:"risk_tracking,omitempty"` + CustomRiskCategories RiskCategories `json:"custom_risk_categories,omitempty" yaml:"custom_risk_categories,omitempty"` + BuiltInRiskCategories RiskCategories `json:"built_in_risk_categories,omitempty" yaml:"built_in_risk_categories,omitempty"` + RiskTracking map[string]*RiskTracking `json:"risk_tracking,omitempty" yaml:"risk_tracking,omitempty"` CommunicationLinks map[string]*CommunicationLink `json:"communication_links,omitempty" yaml:"communication_links,omitempty"` AllSupportedTags map[string]bool `json:"all_supported_tags,omitempty" yaml:"all_supported_tags,omitempty"` DiagramTweakNodesep int `json:"diagram_tweak_nodesep,omitempty" yaml:"diagram_tweak_nodesep,omitempty"` @@ -54,18 +54,18 @@ type ParsedModel struct { // TODO: those are generated based on items above and needs to be private IncomingTechnicalCommunicationLinksMappedByTargetId map[string][]*CommunicationLink `json:"incoming_technical_communication_links_mapped_by_target_id,omitempty" yaml:"incoming_technical_communication_links_mapped_by_target_id,omitempty"` DirectContainingTrustBoundaryMappedByTechnicalAssetId map[string]*TrustBoundary `json:"direct_containing_trust_boundary_mapped_by_technical_asset_id,omitempty" yaml:"direct_containing_trust_boundary_mapped_by_technical_asset_id,omitempty"` - GeneratedRisksByCategory map[string][]Risk `json:"generated_risks_by_category,omitempty" yaml:"generated_risks_by_category,omitempty"` - GeneratedRisksBySyntheticId map[string]Risk `json:"generated_risks_by_synthetic_id,omitempty" yaml:"generated_risks_by_synthetic_id,omitempty"` + GeneratedRisksByCategory map[string][]*Risk `json:"generated_risks_by_category,omitempty" yaml:"generated_risks_by_category,omitempty"` + GeneratedRisksBySyntheticId map[string]*Risk `json:"generated_risks_by_synthetic_id,omitempty" yaml:"generated_risks_by_synthetic_id,omitempty"` } -func (parsedModel *ParsedModel) AddToListOfSupportedTags(tags []string) { +func (parsedModel *Model) AddToListOfSupportedTags(tags []string) { for _, tag := range tags { parsedModel.AllSupportedTags[tag] = true } } -func (parsedModel *ParsedModel) GetDeferredRiskTrackingDueToWildcardMatching() map[string]RiskTracking { - deferredRiskTrackingDueToWildcardMatching := make(map[string]RiskTracking) +func (parsedModel *Model) GetDeferredRiskTrackingDueToWildcardMatching() map[string]*RiskTracking { + deferredRiskTrackingDueToWildcardMatching := make(map[string]*RiskTracking) for syntheticRiskId, riskTracking := range parsedModel.RiskTracking { if strings.Contains(syntheticRiskId, "*") { // contains a wildcard char deferredRiskTrackingDueToWildcardMatching[syntheticRiskId] = riskTracking @@ -75,14 +75,14 @@ func (parsedModel *ParsedModel) GetDeferredRiskTrackingDueToWildcardMatching() m return deferredRiskTrackingDueToWildcardMatching } -func (parsedModel *ParsedModel) HasNotYetAnyDirectNonWildcardRiskTracking(syntheticRiskId string) bool { +func (parsedModel *Model) HasNotYetAnyDirectNonWildcardRiskTracking(syntheticRiskId string) bool { if _, ok := parsedModel.RiskTracking[syntheticRiskId]; ok { return false } return true } -func (parsedModel *ParsedModel) CheckTags(tags []string, where string) ([]string, error) { +func (parsedModel *Model) CheckTags(tags []string, where string) ([]string, error) { var tagsUsed = make([]string, 0) if tags != nil { tagsUsed = make([]string, len(tags)) @@ -98,7 +98,7 @@ func (parsedModel *ParsedModel) CheckTags(tags []string, where string) ([]string return tagsUsed, nil } -func (parsedModel *ParsedModel) ApplyWildcardRiskTrackingEvaluation(ignoreOrphanedRiskTracking bool, progressReporter ProgressReporter) error { +func (parsedModel *Model) ApplyWildcardRiskTrackingEvaluation(ignoreOrphanedRiskTracking bool, progressReporter ProgressReporter) error { progressReporter.Info("Executing risk tracking evaluation") for syntheticRiskIdPattern, riskTracking := range parsedModel.GetDeferredRiskTrackingDueToWildcardMatching() { progressReporter.Infof("Applying wildcard risk tracking for risk id: %v", syntheticRiskIdPattern) @@ -108,7 +108,7 @@ func (parsedModel *ParsedModel) ApplyWildcardRiskTrackingEvaluation(ignoreOrphan for syntheticRiskId := range parsedModel.GeneratedRisksBySyntheticId { if matchingRiskIdExpression.Match([]byte(syntheticRiskId)) && parsedModel.HasNotYetAnyDirectNonWildcardRiskTracking(syntheticRiskId) { foundSome = true - parsedModel.RiskTracking[syntheticRiskId] = RiskTracking{ + parsedModel.RiskTracking[syntheticRiskId] = &RiskTracking{ SyntheticRiskId: strings.TrimSpace(syntheticRiskId), Justification: riskTracking.Justification, CheckedBy: riskTracking.CheckedBy, @@ -130,7 +130,7 @@ func (parsedModel *ParsedModel) ApplyWildcardRiskTrackingEvaluation(ignoreOrphan return nil } -func (parsedModel *ParsedModel) CheckRiskTracking(ignoreOrphanedRiskTracking bool, progressReporter ProgressReporter) error { +func (parsedModel *Model) CheckRiskTracking(ignoreOrphanedRiskTracking bool, progressReporter ProgressReporter) error { progressReporter.Info("Checking risk tracking") for _, tracking := range parsedModel.RiskTracking { if _, ok := parsedModel.GeneratedRisksBySyntheticId[tracking.SyntheticRiskId]; !ok { @@ -151,52 +151,45 @@ func (parsedModel *ParsedModel) CheckRiskTracking(ignoreOrphanedRiskTracking boo } } - // save also the risk-category-id and risk-status directly in the risk for better JSON marshalling - for category := range parsedModel.GeneratedRisksByCategory { - for i := range parsedModel.GeneratedRisksByCategory[category] { - // context.parsedModel.GeneratedRisksByCategory[category][i].CategoryId = category - parsedModel.GeneratedRisksByCategory[category][i].RiskStatus = parsedModel.GeneratedRisksByCategory[category][i].GetRiskTrackingStatusDefaultingUnchecked(parsedModel) - } - } return nil } -func (parsedModel *ParsedModel) CheckTagExists(referencedTag, where string) error { +func (parsedModel *Model) CheckTagExists(referencedTag, where string) error { if !slices.Contains(parsedModel.TagsAvailable, referencedTag) { return fmt.Errorf("missing referenced tag in overall tag list at %v: %v", where, referencedTag) } return nil } -func (parsedModel *ParsedModel) CheckDataAssetTargetExists(referencedAsset, where string) error { +func (parsedModel *Model) CheckDataAssetTargetExists(referencedAsset, where string) error { if _, ok := parsedModel.DataAssets[referencedAsset]; !ok { return fmt.Errorf("missing referenced data asset target at %v: %v", where, referencedAsset) } return nil } -func (parsedModel *ParsedModel) CheckTrustBoundaryExists(referencedId, where string) error { +func (parsedModel *Model) CheckTrustBoundaryExists(referencedId, where string) error { if _, ok := parsedModel.TrustBoundaries[referencedId]; !ok { return fmt.Errorf("missing referenced trust boundary at %v: %v", where, referencedId) } return nil } -func (parsedModel *ParsedModel) CheckSharedRuntimeExists(referencedId, where string) error { +func (parsedModel *Model) CheckSharedRuntimeExists(referencedId, where string) error { if _, ok := parsedModel.SharedRuntimes[referencedId]; !ok { return fmt.Errorf("missing referenced shared runtime at %v: %v", where, referencedId) } return nil } -func (parsedModel *ParsedModel) CheckCommunicationLinkExists(referencedId, where string) error { +func (parsedModel *Model) CheckCommunicationLinkExists(referencedId, where string) error { if _, ok := parsedModel.CommunicationLinks[referencedId]; !ok { return fmt.Errorf("missing referenced communication link at %v: %v", where, referencedId) } return nil } -func (parsedModel *ParsedModel) CheckTechnicalAssetExists(referencedAsset, where string, onlyForTweak bool) error { +func (parsedModel *Model) CheckTechnicalAssetExists(referencedAsset, where string, onlyForTweak bool) error { if _, ok := parsedModel.TechnicalAssets[referencedAsset]; !ok { suffix := "" if onlyForTweak { @@ -207,7 +200,7 @@ func (parsedModel *ParsedModel) CheckTechnicalAssetExists(referencedAsset, where return nil } -func (parsedModel *ParsedModel) CheckNestedTrustBoundariesExisting() error { +func (parsedModel *Model) CheckNestedTrustBoundariesExisting() error { for _, trustBoundary := range parsedModel.TrustBoundaries { for _, nestedId := range trustBoundary.TrustBoundariesNested { if _, ok := parsedModel.TrustBoundaries[nestedId]; !ok { @@ -235,7 +228,7 @@ func CalculateSeverity(likelihood RiskExploitationLikelihood, impact RiskExploit return CriticalSeverity } -func (parsedModel *ParsedModel) InScopeTechnicalAssets() []*TechnicalAsset { +func (parsedModel *Model) InScopeTechnicalAssets() []*TechnicalAsset { result := make([]*TechnicalAsset, 0) for _, asset := range parsedModel.TechnicalAssets { if !asset.OutOfScope { @@ -245,7 +238,7 @@ func (parsedModel *ParsedModel) InScopeTechnicalAssets() []*TechnicalAsset { return result } -func (parsedModel *ParsedModel) SortedTechnicalAssetIDs() []string { +func (parsedModel *Model) SortedTechnicalAssetIDs() []string { res := make([]string, 0) for id := range parsedModel.TechnicalAssets { res = append(res, id) @@ -254,7 +247,7 @@ func (parsedModel *ParsedModel) SortedTechnicalAssetIDs() []string { return res } -func (parsedModel *ParsedModel) TagsActuallyUsed() []string { +func (parsedModel *Model) TagsActuallyUsed() []string { result := make([]string, 0) for _, tag := range parsedModel.TagsAvailable { if len(parsedModel.TechnicalAssetsTaggedWithAny(tag)) > 0 || @@ -268,7 +261,7 @@ func (parsedModel *ParsedModel) TagsActuallyUsed() []string { return result } -func (parsedModel *ParsedModel) TechnicalAssetsTaggedWithAny(tags ...string) []*TechnicalAsset { +func (parsedModel *Model) TechnicalAssetsTaggedWithAny(tags ...string) []*TechnicalAsset { result := make([]*TechnicalAsset, 0) for _, candidate := range parsedModel.TechnicalAssets { if candidate.IsTaggedWithAny(tags...) { @@ -278,7 +271,7 @@ func (parsedModel *ParsedModel) TechnicalAssetsTaggedWithAny(tags ...string) []* return result } -func (parsedModel *ParsedModel) CommunicationLinksTaggedWithAny(tags ...string) []*CommunicationLink { +func (parsedModel *Model) CommunicationLinksTaggedWithAny(tags ...string) []*CommunicationLink { result := make([]*CommunicationLink, 0) for _, asset := range parsedModel.TechnicalAssets { for _, candidate := range asset.CommunicationLinks { @@ -290,7 +283,7 @@ func (parsedModel *ParsedModel) CommunicationLinksTaggedWithAny(tags ...string) return result } -func (parsedModel *ParsedModel) DataAssetsTaggedWithAny(tags ...string) []*DataAsset { +func (parsedModel *Model) DataAssetsTaggedWithAny(tags ...string) []*DataAsset { result := make([]*DataAsset, 0) for _, candidate := range parsedModel.DataAssets { if candidate.IsTaggedWithAny(tags...) { @@ -300,7 +293,7 @@ func (parsedModel *ParsedModel) DataAssetsTaggedWithAny(tags ...string) []*DataA return result } -func (parsedModel *ParsedModel) TrustBoundariesTaggedWithAny(tags ...string) []*TrustBoundary { +func (parsedModel *Model) TrustBoundariesTaggedWithAny(tags ...string) []*TrustBoundary { result := make([]*TrustBoundary, 0) for _, candidate := range parsedModel.TrustBoundaries { if candidate.IsTaggedWithAny(tags...) { @@ -310,7 +303,7 @@ func (parsedModel *ParsedModel) TrustBoundariesTaggedWithAny(tags ...string) []* return result } -func (parsedModel *ParsedModel) SharedRuntimesTaggedWithAny(tags ...string) []*SharedRuntime { +func (parsedModel *Model) SharedRuntimesTaggedWithAny(tags ...string) []*SharedRuntime { result := make([]*SharedRuntime, 0) for _, candidate := range parsedModel.SharedRuntimes { if candidate.IsTaggedWithAny(tags...) { @@ -320,7 +313,7 @@ func (parsedModel *ParsedModel) SharedRuntimesTaggedWithAny(tags ...string) []*S return result } -func (parsedModel *ParsedModel) OutOfScopeTechnicalAssets() []*TechnicalAsset { +func (parsedModel *Model) OutOfScopeTechnicalAssets() []*TechnicalAsset { assets := make([]*TechnicalAsset, 0) for _, asset := range parsedModel.TechnicalAssets { if asset.OutOfScope { @@ -331,7 +324,7 @@ func (parsedModel *ParsedModel) OutOfScopeTechnicalAssets() []*TechnicalAsset { return assets } -func (parsedModel *ParsedModel) RisksOfOnlySTRIDEInformationDisclosure(risksByCategory map[string][]Risk) map[string][]Risk { +func (parsedModel *Model) RisksOfOnlySTRIDEInformationDisclosure(risksByCategory map[string][]Risk) map[string][]Risk { result := make(map[string][]Risk) for categoryId, categoryRisks := range risksByCategory { for _, risk := range categoryRisks { @@ -344,7 +337,7 @@ func (parsedModel *ParsedModel) RisksOfOnlySTRIDEInformationDisclosure(risksByCa return result } -func (parsedModel *ParsedModel) RisksOfOnlySTRIDEDenialOfService(risksByCategory map[string][]Risk) map[string][]Risk { +func (parsedModel *Model) RisksOfOnlySTRIDEDenialOfService(risksByCategory map[string][]Risk) map[string][]Risk { result := make(map[string][]Risk) for categoryId, categoryRisks := range risksByCategory { for _, risk := range categoryRisks { @@ -357,7 +350,7 @@ func (parsedModel *ParsedModel) RisksOfOnlySTRIDEDenialOfService(risksByCategory return result } -func (parsedModel *ParsedModel) RisksOfOnlySTRIDEElevationOfPrivilege(risksByCategory map[string][]Risk) map[string][]Risk { +func (parsedModel *Model) RisksOfOnlySTRIDEElevationOfPrivilege(risksByCategory map[string][]Risk) map[string][]Risk { result := make(map[string][]Risk) for categoryId, categoryRisks := range risksByCategory { for _, risk := range categoryRisks { @@ -370,7 +363,7 @@ func (parsedModel *ParsedModel) RisksOfOnlySTRIDEElevationOfPrivilege(risksByCat return result } -func (parsedModel *ParsedModel) RisksOfOnlyBusinessSide(risksByCategory map[string][]Risk) map[string][]Risk { +func (parsedModel *Model) RisksOfOnlyBusinessSide(risksByCategory map[string][]Risk) map[string][]Risk { result := make(map[string][]Risk) for categoryId, categoryRisks := range risksByCategory { for _, risk := range categoryRisks { @@ -383,7 +376,7 @@ func (parsedModel *ParsedModel) RisksOfOnlyBusinessSide(risksByCategory map[stri return result } -func (parsedModel *ParsedModel) RisksOfOnlyArchitecture(risksByCategory map[string][]Risk) map[string][]Risk { +func (parsedModel *Model) RisksOfOnlyArchitecture(risksByCategory map[string][]Risk) map[string][]Risk { result := make(map[string][]Risk) for categoryId, categoryRisks := range risksByCategory { for _, risk := range categoryRisks { @@ -396,7 +389,7 @@ func (parsedModel *ParsedModel) RisksOfOnlyArchitecture(risksByCategory map[stri return result } -func (parsedModel *ParsedModel) RisksOfOnlyDevelopment(risksByCategory map[string][]Risk) map[string][]Risk { +func (parsedModel *Model) RisksOfOnlyDevelopment(risksByCategory map[string][]Risk) map[string][]Risk { result := make(map[string][]Risk) for categoryId, categoryRisks := range risksByCategory { for _, risk := range categoryRisks { @@ -409,7 +402,7 @@ func (parsedModel *ParsedModel) RisksOfOnlyDevelopment(risksByCategory map[strin return result } -func (parsedModel *ParsedModel) RisksOfOnlyOperation(risksByCategory map[string][]Risk) map[string][]Risk { +func (parsedModel *Model) RisksOfOnlyOperation(risksByCategory map[string][]Risk) map[string][]Risk { result := make(map[string][]Risk) for categoryId, categoryRisks := range risksByCategory { for _, risk := range categoryRisks { diff --git a/pkg/security/types/protocol.go b/pkg/security/types/protocol.go index d538307f..af3a3743 100644 --- a/pkg/security/types/protocol.go +++ b/pkg/security/types/protocol.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -206,3 +208,47 @@ func (what Protocol) IsPotentialDatabaseAccessProtocol(includingLaxDatabaseProto func (what Protocol) IsPotentialWebAccessProtocol() bool { return what == HTTP || what == HTTPS || what == WS || what == WSS || what == ReverseProxyWebProtocol || what == ReverseProxyWebProtocolEncrypted } + +func (what Protocol) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *Protocol) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what Protocol) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *Protocol) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what Protocol) find(value string) (Protocol, error) { + for index, description := range ProtocolTypeDescription { + if strings.EqualFold(value, description.Name) { + return Protocol(index), nil + } + } + + return Protocol(0), fmt.Errorf("unknown protocol value %q", value) +} diff --git a/pkg/security/types/quantity.go b/pkg/security/types/quantity.go index b3c1a432..eb1df364 100644 --- a/pkg/security/types/quantity.go +++ b/pkg/security/types/quantity.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -65,3 +67,37 @@ func (what Quantity) Find(value string) (Quantity, error) { return Quantity(0), fmt.Errorf("unknown quantity value %q", value) } + +func (what Quantity) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *Quantity) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what Quantity) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *Quantity) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/risk-category.go b/pkg/security/types/risk-category.go index 58a17b2c..562a6190 100644 --- a/pkg/security/types/risk-category.go +++ b/pkg/security/types/risk-category.go @@ -1,21 +1,40 @@ package types +import "strings" + type RiskCategory struct { - // TODO: refactor all "Id" here and elsewhere to "ID" - Id string `json:"id,omitempty" yaml:"id,omitempty"` - Title string `json:"title,omitempty" yaml:"title,omitempty"` - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Impact string `json:"impact,omitempty" yaml:"impact,omitempty"` - ASVS string `json:"asvs,omitempty" yaml:"asvs,omitempty"` - CheatSheet string `json:"cheat_sheet,omitempty" yaml:"cheat_sheet,omitempty"` - Action string `json:"action,omitempty" yaml:"action,omitempty"` - Mitigation string `json:"mitigation,omitempty" yaml:"mitigation,omitempty"` - Check string `json:"check,omitempty" yaml:"check,omitempty"` - DetectionLogic string `json:"detection_logic,omitempty" yaml:"detection_logic,omitempty"` - RiskAssessment string `json:"risk_assessment,omitempty" yaml:"risk_assessment,omitempty"` - FalsePositives string `json:"false_positives,omitempty" yaml:"false_positives,omitempty"` - Function RiskFunction `json:"function,omitempty" yaml:"function,omitempty"` - STRIDE STRIDE `json:"stride,omitempty" yaml:"stride,omitempty"` - ModelFailurePossibleReason bool `json:"model_failure_possible_reason,omitempty" yaml:"model_failure_possible_reason,omitempty"` - CWE int `json:"cwe,omitempty" yaml:"cwe,omitempty"` + ID string `json:"id,omitempty" yaml:"id,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Impact string `json:"impact,omitempty" yaml:"impact,omitempty"` + ASVS string `json:"asvs,omitempty" yaml:"asvs,omitempty"` + CheatSheet string `json:"cheat_sheet,omitempty" yaml:"cheat_sheet,omitempty"` + Action string `json:"action,omitempty" yaml:"action,omitempty"` + Mitigation string `json:"mitigation,omitempty" yaml:"mitigation,omitempty"` + Check string `json:"check,omitempty" yaml:"check,omitempty"` + Function RiskFunction `json:"function,omitempty" yaml:"function,omitempty"` + STRIDE STRIDE `json:"stride,omitempty" yaml:"stride,omitempty"` + DetectionLogic string `json:"detection_logic,omitempty" yaml:"detection_logic,omitempty"` + RiskAssessment string `json:"risk_assessment,omitempty" yaml:"risk_assessment,omitempty"` + FalsePositives string `json:"false_positives,omitempty" yaml:"false_positives,omitempty"` + ModelFailurePossibleReason bool `json:"model_failure_possible_reason,omitempty" yaml:"model_failure_possible_reason,omitempty"` + CWE int `json:"cwe,omitempty" yaml:"cwe,omitempty"` + IsBuiltIn bool `json:"is_built_in,omitempty" yaml:"is_built_in,omitempty"` + Script map[string]any `json:"script,omitempty" yaml:"script,omitempty"` +} + +type RiskCategories []*RiskCategory + +func (what *RiskCategories) Add(categories ...*RiskCategory) bool { + for _, newCategory := range categories { + for _, existingCategory := range *what { + if strings.EqualFold(existingCategory.ID, newCategory.ID) { + return false + } + } + + *what = append(*what, newCategory) + } + + return true } diff --git a/pkg/security/types/risk.go b/pkg/security/types/risk.go index c98863ed..26908be1 100644 --- a/pkg/security/types/risk.go +++ b/pkg/security/types/risk.go @@ -15,25 +15,24 @@ type Risk struct { MostRelevantCommunicationLinkId string `yaml:"most_relevant_communication_link,omitempty" json:"most_relevant_communication_link,omitempty"` DataBreachProbability DataBreachProbability `yaml:"data_breach_probability,omitempty" json:"data_breach_probability,omitempty"` DataBreachTechnicalAssetIDs []string `yaml:"data_breach_technical_assets,omitempty" json:"data_breach_technical_assets,omitempty"` - // TODO: refactor all "Id" here to "ID"? + // TODO: refactor all "ID" here to "ID"? } -func (what Risk) GetRiskTracking(model *ParsedModel) RiskTracking { // TODO: Unify function naming regarding Get etc. - var result RiskTracking +func (what Risk) GetRiskTracking(model *Model) *RiskTracking { // TODO: Unify function naming regarding Get etc. if riskTracking, ok := model.RiskTracking[what.SyntheticId]; ok { - result = riskTracking + return riskTracking } - return result + return nil } -func (what Risk) GetRiskTrackingStatusDefaultingUnchecked(model *ParsedModel) RiskStatus { +func (what Risk) GetRiskTrackingWithDefault(model *Model) RiskTracking { // TODO: Unify function naming regarding Get etc. if riskTracking, ok := model.RiskTracking[what.SyntheticId]; ok { - return riskTracking.Status + return *riskTracking } - return Unchecked + return RiskTracking{} } -func (what Risk) IsRiskTracked(model *ParsedModel) bool { +func (what Risk) IsRiskTracked(model *Model) bool { if _, ok := model.RiskTracking[what.SyntheticId]; ok { return true } diff --git a/pkg/security/types/risk_exploitation_impact.go b/pkg/security/types/risk_exploitation_impact.go index 8a7c016d..b27e6240 100644 --- a/pkg/security/types/risk_exploitation_impact.go +++ b/pkg/security/types/risk_exploitation_impact.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -68,3 +70,37 @@ func (what RiskExploitationImpact) Find(value string) (RiskExploitationImpact, e return RiskExploitationImpact(0), fmt.Errorf("unknown risk exploitation impact value %q", value) } + +func (what RiskExploitationImpact) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *RiskExploitationImpact) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what RiskExploitationImpact) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *RiskExploitationImpact) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/risk_exploitation_likelihood.go b/pkg/security/types/risk_exploitation_likelihood.go index 93a5e377..86966d64 100644 --- a/pkg/security/types/risk_exploitation_likelihood.go +++ b/pkg/security/types/risk_exploitation_likelihood.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -68,3 +70,37 @@ func (what RiskExploitationLikelihood) Find(value string) (RiskExploitationLikel return RiskExploitationLikelihood(0), fmt.Errorf("unknown risk exploration likelihood value %q", value) } + +func (what RiskExploitationLikelihood) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *RiskExploitationLikelihood) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what RiskExploitationLikelihood) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *RiskExploitationLikelihood) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/risk_function.go b/pkg/security/types/risk_function.go index 78f6288a..e48e484d 100644 --- a/pkg/security/types/risk_function.go +++ b/pkg/security/types/risk_function.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -56,3 +58,47 @@ func (what RiskFunction) Explain() string { func (what RiskFunction) Title() string { return [...]string{"Business Side", "Architecture", "Development", "Operations"}[what] } + +func (what RiskFunction) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *RiskFunction) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what RiskFunction) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *RiskFunction) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what RiskFunction) find(value string) (RiskFunction, error) { + for index, description := range RiskFunctionTypeDescription { + if strings.EqualFold(value, description.Name) { + return RiskFunction(index), nil + } + } + + return RiskFunction(0), fmt.Errorf("unknown risk function value %q", value) +} diff --git a/pkg/security/types/risk_severity.go b/pkg/security/types/risk_severity.go index d87cce9e..488a4b03 100644 --- a/pkg/security/types/risk_severity.go +++ b/pkg/security/types/risk_severity.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -67,3 +69,37 @@ func (what RiskSeverity) Find(value string) (RiskSeverity, error) { return RiskSeverity(0), fmt.Errorf("unknown risk severity value %q", value) } + +func (what RiskSeverity) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *RiskSeverity) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what RiskSeverity) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *RiskSeverity) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/risk_status.go b/pkg/security/types/risk_status.go index 022f9e45..bdddd221 100644 --- a/pkg/security/types/risk_status.go +++ b/pkg/security/types/risk_status.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -66,3 +68,47 @@ func (what RiskStatus) Title() string { func (what RiskStatus) IsStillAtRisk() bool { return what == Unchecked || what == InDiscussion || what == Accepted || what == InProgress } + +func (what RiskStatus) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *RiskStatus) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what RiskStatus) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *RiskStatus) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what RiskStatus) find(value string) (RiskStatus, error) { + for index, description := range RiskStatusTypeDescription { + if strings.EqualFold(value, description.Name) { + return RiskStatus(index), nil + } + } + + return RiskStatus(0), fmt.Errorf("unknown risk status value %q", value) +} diff --git a/pkg/security/types/risks.go b/pkg/security/types/risks.go index e9018f04..39d1b5ca 100644 --- a/pkg/security/types/risks.go +++ b/pkg/security/types/risks.go @@ -6,36 +6,39 @@ package types import ( "sort" + "strings" ) -func GetRiskCategory(parsedModel *ParsedModel, categoryID string) *RiskCategory { - if len(parsedModel.IndividualRiskCategories) > 0 { - custom, customOk := parsedModel.IndividualRiskCategories[categoryID] - if customOk { - return &custom +func GetRiskCategory(parsedModel *Model, categoryID string) *RiskCategory { + if len(parsedModel.CustomRiskCategories) > 0 { + for _, custom := range parsedModel.CustomRiskCategories { + if strings.EqualFold(custom.ID, categoryID) { + return custom + } } } if len(parsedModel.BuiltInRiskCategories) > 0 { - builtIn, builtInOk := parsedModel.BuiltInRiskCategories[categoryID] - if builtInOk { - return &builtIn + for _, builtIn := range parsedModel.BuiltInRiskCategories { + if strings.EqualFold(builtIn.ID, categoryID) { + return builtIn + } } } return nil } -func GetRiskCategories(parsedModel *ParsedModel, categoryIDs []string) []RiskCategory { - categoryMap := make(map[string]RiskCategory) +func GetRiskCategories(parsedModel *Model, categoryIDs []string) []*RiskCategory { + categoryMap := make(map[string]*RiskCategory) for _, categoryId := range categoryIDs { category := GetRiskCategory(parsedModel, categoryId) if category != nil { - categoryMap[categoryId] = *category + categoryMap[categoryId] = category } } - categories := make([]RiskCategory, 0) + categories := make([]*RiskCategory, 0) for categoryId := range categoryMap { categories = append(categories, categoryMap[categoryId]) } @@ -43,25 +46,25 @@ func GetRiskCategories(parsedModel *ParsedModel, categoryIDs []string) []RiskCat return categories } -func AllRisks(parsedModel *ParsedModel) []Risk { - result := make([]Risk, 0) +func AllRisks(parsedModel *Model) []*Risk { + result := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { result = append(result, risks...) } return result } -func ReduceToOnlyStillAtRisk(parsedModel *ParsedModel, risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyStillAtRisk(parsedModel *Model, risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if risk.RiskStatus.IsStillAtRisk() { filteredRisks = append(filteredRisks, risk) } } return filteredRisks } -func HighestExploitationLikelihood(risks []Risk) RiskExploitationLikelihood { +func HighestExploitationLikelihood(risks []*Risk) RiskExploitationLikelihood { result := Unlikely for _, risk := range risks { if risk.ExploitationLikelihood > result { @@ -71,7 +74,7 @@ func HighestExploitationLikelihood(risks []Risk) RiskExploitationLikelihood { return result } -func HighestExploitationImpact(risks []Risk) RiskExploitationImpact { +func HighestExploitationImpact(risks []*Risk) RiskExploitationImpact { result := LowImpact for _, risk := range risks { if risk.ExploitationImpact > result { @@ -81,17 +84,17 @@ func HighestExploitationImpact(risks []Risk) RiskExploitationImpact { return result } -func HighestSeverityStillAtRisk(model *ParsedModel, risks []Risk) RiskSeverity { +func HighestSeverityStillAtRisk(model *Model, risks []*Risk) RiskSeverity { result := LowSeverity for _, risk := range risks { - if risk.Severity > result && risk.GetRiskTrackingStatusDefaultingUnchecked(model).IsStillAtRisk() { + if risk.Severity > result && risk.RiskStatus.IsStillAtRisk() { result = risk.Severity } } return result } -type ByRiskCategoryTitleSort []RiskCategory +type ByRiskCategoryTitleSort []*RiskCategory func (what ByRiskCategoryTitleSort) Len() int { return len(what) } func (what ByRiskCategoryTitleSort) Swap(i, j int) { @@ -101,10 +104,10 @@ func (what ByRiskCategoryTitleSort) Less(i, j int) bool { return what[i].Title < what[j].Title } -func SortByRiskCategoryHighestContainingRiskSeveritySortStillAtRisk(parsedModel *ParsedModel, riskCategories []RiskCategory) { +func SortByRiskCategoryHighestContainingRiskSeveritySortStillAtRisk(parsedModel *Model, riskCategories []*RiskCategory) { sort.Slice(riskCategories, func(i, j int) bool { - risksLeft := ReduceToOnlyStillAtRisk(parsedModel, parsedModel.GeneratedRisksByCategory[riskCategories[i].Id]) - risksRight := ReduceToOnlyStillAtRisk(parsedModel, parsedModel.GeneratedRisksByCategory[riskCategories[j].Id]) + risksLeft := ReduceToOnlyStillAtRisk(parsedModel, parsedModel.GeneratedRisksByCategory[riskCategories[i].ID]) + risksRight := ReduceToOnlyStillAtRisk(parsedModel, parsedModel.GeneratedRisksByCategory[riskCategories[j].ID]) highestLeft := HighestSeverityStillAtRisk(parsedModel, risksLeft) highestRight := HighestSeverityStillAtRisk(parsedModel, risksRight) if highestLeft == highestRight { @@ -125,11 +128,11 @@ type RiskStatistics struct { Risks map[string]map[string]int `yaml:"risks" json:"risks"` } -func SortByRiskSeverity(risks []Risk, parsedModel *ParsedModel) { +func SortByRiskSeverity(risks []*Risk, parsedModel *Model) { sort.Slice(risks, func(i, j int) bool { if risks[i].Severity == risks[j].Severity { - trackingStatusLeft := risks[i].GetRiskTrackingStatusDefaultingUnchecked(parsedModel) - trackingStatusRight := risks[j].GetRiskTrackingStatusDefaultingUnchecked(parsedModel) + trackingStatusLeft := risks[i].RiskStatus + trackingStatusRight := risks[j].RiskStatus if trackingStatusLeft == trackingStatusRight { impactLeft := risks[i].ExploitationImpact impactRight := risks[j].ExploitationImpact @@ -153,12 +156,12 @@ func SortByRiskSeverity(risks []Risk, parsedModel *ParsedModel) { }) } -func SortByDataBreachProbability(risks []Risk, parsedModel *ParsedModel) { +func SortByDataBreachProbability(risks []*Risk, parsedModel *Model) { sort.Slice(risks, func(i, j int) bool { if risks[i].DataBreachProbability == risks[j].DataBreachProbability { - trackingStatusLeft := risks[i].GetRiskTrackingStatusDefaultingUnchecked(parsedModel) - trackingStatusRight := risks[j].GetRiskTrackingStatusDefaultingUnchecked(parsedModel) + trackingStatusLeft := risks[i].RiskStatus + trackingStatusRight := risks[j].RiskStatus if trackingStatusLeft == trackingStatusRight { return risks[i].Title < risks[j].Title } else { @@ -171,16 +174,16 @@ func SortByDataBreachProbability(risks []Risk, parsedModel *ParsedModel) { // as in Go ranging over map is random order, range over them in sorted (hence reproducible) way: -func SortedRiskCategories(parsedModel *ParsedModel) []RiskCategory { - categoryMap := make(map[string]RiskCategory) +func SortedRiskCategories(parsedModel *Model) []*RiskCategory { + categoryMap := make(map[string]*RiskCategory) for categoryId := range parsedModel.GeneratedRisksByCategory { category := GetRiskCategory(parsedModel, categoryId) if category != nil { - categoryMap[categoryId] = *category + categoryMap[categoryId] = category } } - categories := make([]RiskCategory, 0) + categories := make([]*RiskCategory, 0) for categoryId := range categoryMap { categories = append(categories, categoryMap[categoryId]) } @@ -189,13 +192,13 @@ func SortedRiskCategories(parsedModel *ParsedModel) []RiskCategory { return categories } -func SortedRisksOfCategory(parsedModel *ParsedModel, category RiskCategory) []Risk { - risks := parsedModel.GeneratedRisksByCategory[category.Id] +func SortedRisksOfCategory(parsedModel *Model, category *RiskCategory) []*Risk { + risks := parsedModel.GeneratedRisksByCategory[category.ID] SortByRiskSeverity(risks, parsedModel) return risks } -func CountRisks(risksByCategory map[string][]Risk) int { +func CountRisks(risksByCategory map[string][]*Risk) int { result := 0 for _, risks := range risksByCategory { result += len(risks) @@ -203,8 +206,8 @@ func CountRisks(risksByCategory map[string][]Risk) int { return result } -func RisksOfOnlySTRIDESpoofing(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlySTRIDESpoofing(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -218,8 +221,8 @@ func RisksOfOnlySTRIDESpoofing(parsedModel *ParsedModel, risksByCategory map[str return result } -func RisksOfOnlySTRIDETampering(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlySTRIDETampering(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -233,8 +236,8 @@ func RisksOfOnlySTRIDETampering(parsedModel *ParsedModel, risksByCategory map[st return result } -func RisksOfOnlySTRIDERepudiation(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlySTRIDERepudiation(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -246,8 +249,8 @@ func RisksOfOnlySTRIDERepudiation(parsedModel *ParsedModel, risksByCategory map[ return result } -func RisksOfOnlySTRIDEInformationDisclosure(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlySTRIDEInformationDisclosure(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -259,8 +262,8 @@ func RisksOfOnlySTRIDEInformationDisclosure(parsedModel *ParsedModel, risksByCat return result } -func RisksOfOnlySTRIDEDenialOfService(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlySTRIDEDenialOfService(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -272,8 +275,8 @@ func RisksOfOnlySTRIDEDenialOfService(parsedModel *ParsedModel, risksByCategory return result } -func RisksOfOnlySTRIDEElevationOfPrivilege(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlySTRIDEElevationOfPrivilege(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -285,8 +288,8 @@ func RisksOfOnlySTRIDEElevationOfPrivilege(parsedModel *ParsedModel, risksByCate return result } -func RisksOfOnlyBusinessSide(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlyBusinessSide(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -298,8 +301,8 @@ func RisksOfOnlyBusinessSide(parsedModel *ParsedModel, risksByCategory map[strin return result } -func RisksOfOnlyArchitecture(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlyArchitecture(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -311,8 +314,8 @@ func RisksOfOnlyArchitecture(parsedModel *ParsedModel, risksByCategory map[strin return result } -func RisksOfOnlyDevelopment(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlyDevelopment(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -324,8 +327,8 @@ func RisksOfOnlyDevelopment(parsedModel *ParsedModel, risksByCategory map[string return result } -func RisksOfOnlyOperation(parsedModel *ParsedModel, risksByCategory map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func RisksOfOnlyOperation(parsedModel *Model, risksByCategory map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -337,11 +340,11 @@ func RisksOfOnlyOperation(parsedModel *ParsedModel, risksByCategory map[string][ return result } -func CategoriesOfOnlyRisksStillAtRisk(parsedModel *ParsedModel, risksByCategory map[string][]Risk) []string { +func CategoriesOfOnlyRisksStillAtRisk(parsedModel *Model, risksByCategory map[string][]*Risk) []string { categories := make(map[string]struct{}) // Go's trick of unique elements is a map for categoryId, risks := range risksByCategory { for _, risk := range risks { - if !risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if !risk.RiskStatus.IsStillAtRisk() { continue } categories[categoryId] = struct{}{} @@ -351,11 +354,11 @@ func CategoriesOfOnlyRisksStillAtRisk(parsedModel *ParsedModel, risksByCategory return keysAsSlice(categories) } -func CategoriesOfOnlyCriticalRisks(parsedModel *ParsedModel, risksByCategory map[string][]Risk, initialRisks bool) []string { +func CategoriesOfOnlyCriticalRisks(parsedModel *Model, risksByCategory map[string][]*Risk, initialRisks bool) []string { categories := make(map[string]struct{}) // Go's trick of unique elements is a map for categoryId, risks := range risksByCategory { for _, risk := range risks { - if !initialRisks && !risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if !initialRisks && !risk.RiskStatus.IsStillAtRisk() { continue } if risk.Severity == CriticalSeverity { @@ -367,11 +370,11 @@ func CategoriesOfOnlyCriticalRisks(parsedModel *ParsedModel, risksByCategory map return keysAsSlice(categories) } -func CategoriesOfOnlyHighRisks(parsedModel *ParsedModel, risksByCategory map[string][]Risk, initialRisks bool) []string { +func CategoriesOfOnlyHighRisks(parsedModel *Model, risksByCategory map[string][]*Risk, initialRisks bool) []string { categories := make(map[string]struct{}) // Go's trick of unique elements is a map for categoryId, risks := range risksByCategory { for _, risk := range risks { - if !initialRisks && !risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if !initialRisks && !risk.RiskStatus.IsStillAtRisk() { continue } highest := HighestSeverity(parsedModel.GeneratedRisksByCategory[categoryId]) @@ -387,11 +390,11 @@ func CategoriesOfOnlyHighRisks(parsedModel *ParsedModel, risksByCategory map[str return keysAsSlice(categories) } -func CategoriesOfOnlyElevatedRisks(parsedModel *ParsedModel, risksByCategory map[string][]Risk, initialRisks bool) []string { +func CategoriesOfOnlyElevatedRisks(parsedModel *Model, risksByCategory map[string][]*Risk, initialRisks bool) []string { categories := make(map[string]struct{}) // Go's trick of unique elements is a map for categoryId, risks := range risksByCategory { for _, risk := range risks { - if !initialRisks && !risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if !initialRisks && !risk.RiskStatus.IsStillAtRisk() { continue } highest := HighestSeverity(parsedModel.GeneratedRisksByCategory[categoryId]) @@ -407,11 +410,11 @@ func CategoriesOfOnlyElevatedRisks(parsedModel *ParsedModel, risksByCategory map return keysAsSlice(categories) } -func CategoriesOfOnlyMediumRisks(parsedModel *ParsedModel, risksByCategory map[string][]Risk, initialRisks bool) []string { +func CategoriesOfOnlyMediumRisks(parsedModel *Model, risksByCategory map[string][]*Risk, initialRisks bool) []string { categories := make(map[string]struct{}) // Go's trick of unique elements is a map for categoryId, risks := range risksByCategory { for _, risk := range risks { - if !initialRisks && !risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if !initialRisks && !risk.RiskStatus.IsStillAtRisk() { continue } highest := HighestSeverity(parsedModel.GeneratedRisksByCategory[categoryId]) @@ -427,11 +430,11 @@ func CategoriesOfOnlyMediumRisks(parsedModel *ParsedModel, risksByCategory map[s return keysAsSlice(categories) } -func CategoriesOfOnlyLowRisks(parsedModel *ParsedModel, risksByCategory map[string][]Risk, initialRisks bool) []string { +func CategoriesOfOnlyLowRisks(parsedModel *Model, risksByCategory map[string][]*Risk, initialRisks bool) []string { categories := make(map[string]struct{}) // Go's trick of unique elements is a map for categoryId, risks := range risksByCategory { for _, risk := range risks { - if !initialRisks && !risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if !initialRisks && !risk.RiskStatus.IsStillAtRisk() { continue } highest := HighestSeverity(parsedModel.GeneratedRisksByCategory[categoryId]) @@ -447,7 +450,7 @@ func CategoriesOfOnlyLowRisks(parsedModel *ParsedModel, risksByCategory map[stri return keysAsSlice(categories) } -func HighestSeverity(risks []Risk) RiskSeverity { +func HighestSeverity(risks []*Risk) RiskSeverity { result := LowSeverity for _, risk := range risks { if risk.Severity > result { @@ -465,8 +468,8 @@ func keysAsSlice(categories map[string]struct{}) []string { return result } -func FilteredByOnlyBusinessSide(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByOnlyBusinessSide(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for categoryId, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -478,8 +481,8 @@ func FilteredByOnlyBusinessSide(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByOnlyArchitecture(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByOnlyArchitecture(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for categoryId, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -491,8 +494,8 @@ func FilteredByOnlyArchitecture(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByOnlyDevelopment(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByOnlyDevelopment(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for categoryId, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -504,8 +507,8 @@ func FilteredByOnlyDevelopment(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByOnlyOperation(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByOnlyOperation(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for categoryId, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { category := GetRiskCategory(parsedModel, categoryId) @@ -517,8 +520,8 @@ func FilteredByOnlyOperation(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByOnlyCriticalRisks(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByOnlyCriticalRisks(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { if risk.Severity == CriticalSeverity { @@ -529,8 +532,8 @@ func FilteredByOnlyCriticalRisks(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByOnlyHighRisks(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByOnlyHighRisks(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { if risk.Severity == HighSeverity { @@ -541,8 +544,8 @@ func FilteredByOnlyHighRisks(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByOnlyElevatedRisks(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByOnlyElevatedRisks(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { if risk.Severity == ElevatedSeverity { @@ -553,8 +556,8 @@ func FilteredByOnlyElevatedRisks(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByOnlyMediumRisks(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByOnlyMediumRisks(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { if risk.Severity == MediumSeverity { @@ -565,8 +568,8 @@ func FilteredByOnlyMediumRisks(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByOnlyLowRisks(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByOnlyLowRisks(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { if risk.Severity == LowSeverity { @@ -577,8 +580,8 @@ func FilteredByOnlyLowRisks(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilterByModelFailures(parsedModel *ParsedModel, risksByCat map[string][]Risk) map[string][]Risk { - result := make(map[string][]Risk) +func FilterByModelFailures(parsedModel *Model, risksByCat map[string][]*Risk) map[string][]*Risk { + result := make(map[string][]*Risk) for categoryId, risks := range risksByCat { category := GetRiskCategory(parsedModel, categoryId) if category.ModelFailurePossibleReason { @@ -589,15 +592,15 @@ func FilterByModelFailures(parsedModel *ParsedModel, risksByCat map[string][]Ris return result } -func FlattenRiskSlice(risksByCat map[string][]Risk) []Risk { - result := make([]Risk, 0) +func FlattenRiskSlice(risksByCat map[string][]*Risk) []*Risk { + result := make([]*Risk, 0) for _, risks := range risksByCat { result = append(result, risks...) } return result } -func TotalRiskCount(parsedModel *ParsedModel) int { +func TotalRiskCount(parsedModel *Model) int { count := 0 for _, risks := range parsedModel.GeneratedRisksByCategory { count += len(risks) @@ -605,11 +608,11 @@ func TotalRiskCount(parsedModel *ParsedModel) int { return count } -func FilteredByRiskTrackingUnchecked(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByRiskTrackingUnchecked(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == Unchecked { + if risk.RiskStatus == Unchecked { filteredRisks = append(filteredRisks, risk) } } @@ -617,11 +620,11 @@ func FilteredByRiskTrackingUnchecked(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByRiskTrackingInDiscussion(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByRiskTrackingInDiscussion(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == InDiscussion { + if risk.RiskStatus == InDiscussion { filteredRisks = append(filteredRisks, risk) } } @@ -629,11 +632,11 @@ func FilteredByRiskTrackingInDiscussion(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByRiskTrackingAccepted(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByRiskTrackingAccepted(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == Accepted { + if risk.RiskStatus == Accepted { filteredRisks = append(filteredRisks, risk) } } @@ -641,11 +644,11 @@ func FilteredByRiskTrackingAccepted(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByRiskTrackingInProgress(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByRiskTrackingInProgress(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == InProgress { + if risk.RiskStatus == InProgress { filteredRisks = append(filteredRisks, risk) } } @@ -653,11 +656,11 @@ func FilteredByRiskTrackingInProgress(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByRiskTrackingMitigated(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByRiskTrackingMitigated(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == Mitigated { + if risk.RiskStatus == Mitigated { filteredRisks = append(filteredRisks, risk) } } @@ -665,11 +668,11 @@ func FilteredByRiskTrackingMitigated(parsedModel *ParsedModel) []Risk { return filteredRisks } -func FilteredByRiskTrackingFalsePositive(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByRiskTrackingFalsePositive(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == FalsePositive { + if risk.RiskStatus == FalsePositive { filteredRisks = append(filteredRisks, risk) } } @@ -677,8 +680,8 @@ func FilteredByRiskTrackingFalsePositive(parsedModel *ParsedModel) []Risk { return filteredRisks } -func ReduceToOnlyHighRisk(risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyHighRisk(risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { if risk.Severity == HighSeverity { filteredRisks = append(filteredRisks, risk) @@ -687,8 +690,8 @@ func ReduceToOnlyHighRisk(risks []Risk) []Risk { return filteredRisks } -func ReduceToOnlyMediumRisk(risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyMediumRisk(risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { if risk.Severity == MediumSeverity { filteredRisks = append(filteredRisks, risk) @@ -697,8 +700,8 @@ func ReduceToOnlyMediumRisk(risks []Risk) []Risk { return filteredRisks } -func ReduceToOnlyLowRisk(risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyLowRisk(risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { if risk.Severity == LowSeverity { filteredRisks = append(filteredRisks, risk) @@ -707,71 +710,71 @@ func ReduceToOnlyLowRisk(risks []Risk) []Risk { return filteredRisks } -func ReduceToOnlyRiskTrackingUnchecked(parsedModel *ParsedModel, risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyRiskTrackingUnchecked(parsedModel *Model, risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == Unchecked { + if risk.RiskStatus == Unchecked { filteredRisks = append(filteredRisks, risk) } } return filteredRisks } -func ReduceToOnlyRiskTrackingInDiscussion(parsedModel *ParsedModel, risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyRiskTrackingInDiscussion(parsedModel *Model, risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == InDiscussion { + if risk.RiskStatus == InDiscussion { filteredRisks = append(filteredRisks, risk) } } return filteredRisks } -func ReduceToOnlyRiskTrackingAccepted(parsedModel *ParsedModel, risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyRiskTrackingAccepted(parsedModel *Model, risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == Accepted { + if risk.RiskStatus == Accepted { filteredRisks = append(filteredRisks, risk) } } return filteredRisks } -func ReduceToOnlyRiskTrackingInProgress(parsedModel *ParsedModel, risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyRiskTrackingInProgress(parsedModel *Model, risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == InProgress { + if risk.RiskStatus == InProgress { filteredRisks = append(filteredRisks, risk) } } return filteredRisks } -func ReduceToOnlyRiskTrackingMitigated(parsedModel *ParsedModel, risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyRiskTrackingMitigated(parsedModel *Model, risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == Mitigated { + if risk.RiskStatus == Mitigated { filteredRisks = append(filteredRisks, risk) } } return filteredRisks } -func ReduceToOnlyRiskTrackingFalsePositive(parsedModel *ParsedModel, risks []Risk) []Risk { - filteredRisks := make([]Risk, 0) +func ReduceToOnlyRiskTrackingFalsePositive(parsedModel *Model, risks []*Risk) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel) == FalsePositive { + if risk.RiskStatus == FalsePositive { filteredRisks = append(filteredRisks, risk) } } return filteredRisks } -func FilteredByStillAtRisk(parsedModel *ParsedModel) []Risk { - filteredRisks := make([]Risk, 0) +func FilteredByStillAtRisk(parsedModel *Model) []*Risk { + filteredRisks := make([]*Risk, 0) for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { - if risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).IsStillAtRisk() { + if risk.RiskStatus.IsStillAtRisk() { filteredRisks = append(filteredRisks, risk) } } @@ -779,7 +782,7 @@ func FilteredByStillAtRisk(parsedModel *ParsedModel) []Risk { return filteredRisks } -func OverallRiskStatistics(parsedModel *ParsedModel) RiskStatistics { +func OverallRiskStatistics(parsedModel *Model) RiskStatistics { result := RiskStatistics{} result.Risks = make(map[string]map[string]int) result.Risks[CriticalSeverity.String()] = make(map[string]int) @@ -819,7 +822,7 @@ func OverallRiskStatistics(parsedModel *ParsedModel) RiskStatistics { result.Risks[LowSeverity.String()][FalsePositive.String()] = 0 for _, risks := range parsedModel.GeneratedRisksByCategory { for _, risk := range risks { - result.Risks[risk.Severity.String()][risk.GetRiskTrackingStatusDefaultingUnchecked(parsedModel).String()]++ + result.Risks[risk.Severity.String()][risk.RiskStatus.String()]++ } } return result diff --git a/pkg/security/types/shared_runtime.go b/pkg/security/types/shared_runtime.go index 05060656..bd591846 100644 --- a/pkg/security/types/shared_runtime.go +++ b/pkg/security/types/shared_runtime.go @@ -24,7 +24,7 @@ func (what SharedRuntime) IsTaggedWithBaseTag(baseTag string) bool { return IsTaggedWithBaseTag(what.Tags, baseTag) } -func (what SharedRuntime) HighestConfidentiality(model *ParsedModel) Confidentiality { +func (what SharedRuntime) HighestConfidentiality(model *Model) Confidentiality { highest := Public for _, id := range what.TechnicalAssetsRunning { techAsset := model.TechnicalAssets[id] @@ -35,7 +35,7 @@ func (what SharedRuntime) HighestConfidentiality(model *ParsedModel) Confidentia return highest } -func (what SharedRuntime) HighestIntegrity(model *ParsedModel) Criticality { +func (what SharedRuntime) HighestIntegrity(model *Model) Criticality { highest := Archive for _, id := range what.TechnicalAssetsRunning { techAsset := model.TechnicalAssets[id] @@ -46,7 +46,7 @@ func (what SharedRuntime) HighestIntegrity(model *ParsedModel) Criticality { return highest } -func (what SharedRuntime) HighestAvailability(model *ParsedModel) Criticality { +func (what SharedRuntime) HighestAvailability(model *Model) Criticality { highest := Archive for _, id := range what.TechnicalAssetsRunning { techAsset := model.TechnicalAssets[id] @@ -57,7 +57,7 @@ func (what SharedRuntime) HighestAvailability(model *ParsedModel) Criticality { return highest } -func (what SharedRuntime) TechnicalAssetWithHighestRAA(model *ParsedModel) *TechnicalAsset { +func (what SharedRuntime) TechnicalAssetWithHighestRAA(model *Model) *TechnicalAsset { result := model.TechnicalAssets[what.TechnicalAssetsRunning[0]] for _, asset := range what.TechnicalAssetsRunning { candidate := model.TechnicalAssets[asset] @@ -70,7 +70,7 @@ func (what SharedRuntime) TechnicalAssetWithHighestRAA(model *ParsedModel) *Tech // as in Go ranging over map is random order, range over them in sorted (hence reproducible) way: -func SortedKeysOfSharedRuntime(model *ParsedModel) []string { +func SortedKeysOfSharedRuntime(model *Model) []string { keys := make([]string, 0) for k := range model.SharedRuntimes { keys = append(keys, k) diff --git a/pkg/security/types/stride.go b/pkg/security/types/stride.go index 9697d8fb..3602072e 100644 --- a/pkg/security/types/stride.go +++ b/pkg/security/types/stride.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -62,3 +64,47 @@ func (what STRIDE) Explain() string { func (what STRIDE) Title() string { return [...]string{"Spoofing", "Tampering", "Repudiation", "Information Disclosure", "Denial of Service", "Elevation of Privilege"}[what] } + +func (what STRIDE) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *STRIDE) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what STRIDE) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *STRIDE) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what STRIDE) find(value string) (STRIDE, error) { + for index, description := range StrideTypeDescription { + if strings.EqualFold(value, description.Name) { + return STRIDE(index), nil + } + } + + return STRIDE(0), fmt.Errorf("unknown STRIDE value %q", value) +} diff --git a/pkg/security/types/technical_asset.go b/pkg/security/types/technical_asset.go index fe1f144a..45973ee9 100644 --- a/pkg/security/types/technical_asset.go +++ b/pkg/security/types/technical_asset.go @@ -51,7 +51,7 @@ func (what TechnicalAsset) IsTaggedWithBaseTag(baseTag string) bool { // first use the tag(s) of the asset itself, then their trust boundaries (recursively up) and then their shared runtime -func (what TechnicalAsset) IsTaggedWithAnyTraversingUp(model *ParsedModel, tags ...string) bool { +func (what TechnicalAsset) IsTaggedWithAnyTraversingUp(model *Model, tags ...string) bool { if containsCaseInsensitiveAny(what.Tags, tags...) { return true } @@ -69,7 +69,7 @@ func (what TechnicalAsset) IsTaggedWithAnyTraversingUp(model *ParsedModel, tags return false } -func (what TechnicalAsset) IsSameTrustBoundary(parsedModel *ParsedModel, otherAssetId string) bool { +func (what TechnicalAsset) IsSameTrustBoundary(parsedModel *Model, otherAssetId string) bool { trustBoundaryOfMyAsset := parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId[what.Id] trustBoundaryOfOtherAsset := parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId[otherAssetId] if trustBoundaryOfMyAsset == nil || trustBoundaryOfOtherAsset == nil { @@ -78,7 +78,7 @@ func (what TechnicalAsset) IsSameTrustBoundary(parsedModel *ParsedModel, otherAs return trustBoundaryOfMyAsset.Id == trustBoundaryOfOtherAsset.Id } -func (what TechnicalAsset) IsSameExecutionEnvironment(parsedModel *ParsedModel, otherAssetId string) bool { +func (what TechnicalAsset) IsSameExecutionEnvironment(parsedModel *Model, otherAssetId string) bool { trustBoundaryOfMyAsset := parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId[what.Id] trustBoundaryOfOtherAsset := parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId[otherAssetId] if trustBoundaryOfMyAsset != nil && trustBoundaryOfMyAsset.Type == ExecutionEnvironment && trustBoundaryOfOtherAsset != nil && trustBoundaryOfOtherAsset.Type == ExecutionEnvironment { @@ -87,7 +87,7 @@ func (what TechnicalAsset) IsSameExecutionEnvironment(parsedModel *ParsedModel, return false } -func (what TechnicalAsset) IsSameTrustBoundaryNetworkOnly(parsedModel *ParsedModel, otherAssetId string) bool { +func (what TechnicalAsset) IsSameTrustBoundaryNetworkOnly(parsedModel *Model, otherAssetId string) bool { trustBoundaryOfMyAsset := parsedModel.DirectContainingTrustBoundaryMappedByTechnicalAssetId[what.Id] if trustBoundaryOfMyAsset != nil && !trustBoundaryOfMyAsset.Type.IsNetworkBoundary() { // find and use the parent boundary then trustBoundaryOfMyAsset = parsedModel.TrustBoundaries[trustBoundaryOfMyAsset.ParentTrustBoundaryID(parsedModel)] @@ -108,7 +108,7 @@ func (what TechnicalAsset) HighestSensitivityScore() float64 { what.Availability.AttackerAttractivenessForAsset() } -func (what TechnicalAsset) HighestConfidentiality(model *ParsedModel) Confidentiality { +func (what TechnicalAsset) HighestConfidentiality(model *Model) Confidentiality { highest := what.Confidentiality highestProcessed := what.HighestProcessedConfidentiality(model) if highest < highestProcessed { @@ -123,7 +123,7 @@ func (what TechnicalAsset) HighestConfidentiality(model *ParsedModel) Confidenti return highest } -func (what TechnicalAsset) HighestProcessedConfidentiality(parsedModel *ParsedModel) Confidentiality { +func (what TechnicalAsset) HighestProcessedConfidentiality(parsedModel *Model) Confidentiality { highest := what.Confidentiality for _, dataId := range what.DataAssetsProcessed { dataAsset := parsedModel.DataAssets[dataId] @@ -134,7 +134,7 @@ func (what TechnicalAsset) HighestProcessedConfidentiality(parsedModel *ParsedMo return highest } -func (what TechnicalAsset) HighestStoredConfidentiality(parsedModel *ParsedModel) Confidentiality { +func (what TechnicalAsset) HighestStoredConfidentiality(parsedModel *Model) Confidentiality { highest := what.Confidentiality for _, dataId := range what.DataAssetsStored { dataAsset := parsedModel.DataAssets[dataId] @@ -145,7 +145,7 @@ func (what TechnicalAsset) HighestStoredConfidentiality(parsedModel *ParsedModel return highest } -func (what TechnicalAsset) DataAssetsProcessedSorted(parsedModel *ParsedModel) []*DataAsset { +func (what TechnicalAsset) DataAssetsProcessedSorted(parsedModel *Model) []*DataAsset { result := make([]*DataAsset, 0) for _, assetID := range what.DataAssetsProcessed { result = append(result, parsedModel.DataAssets[assetID]) @@ -154,7 +154,7 @@ func (what TechnicalAsset) DataAssetsProcessedSorted(parsedModel *ParsedModel) [ return result } -func (what TechnicalAsset) DataAssetsStoredSorted(parsedModel *ParsedModel) []*DataAsset { +func (what TechnicalAsset) DataAssetsStoredSorted(parsedModel *Model) []*DataAsset { result := make([]*DataAsset, 0) for _, assetID := range what.DataAssetsStored { result = append(result, parsedModel.DataAssets[assetID]) @@ -177,7 +177,7 @@ func (what TechnicalAsset) CommunicationLinksSorted() []*CommunicationLink { return result } -func (what TechnicalAsset) HighestIntegrity(model *ParsedModel) Criticality { +func (what TechnicalAsset) HighestIntegrity(model *Model) Criticality { highest := what.Integrity highestProcessed := what.HighestProcessedIntegrity(model) if highest < highestProcessed { @@ -192,7 +192,7 @@ func (what TechnicalAsset) HighestIntegrity(model *ParsedModel) Criticality { return highest } -func (what TechnicalAsset) HighestProcessedIntegrity(model *ParsedModel) Criticality { +func (what TechnicalAsset) HighestProcessedIntegrity(model *Model) Criticality { highest := what.Integrity for _, dataId := range what.DataAssetsProcessed { dataAsset := model.DataAssets[dataId] @@ -203,7 +203,7 @@ func (what TechnicalAsset) HighestProcessedIntegrity(model *ParsedModel) Critica return highest } -func (what TechnicalAsset) HighestStoredIntegrity(model *ParsedModel) Criticality { +func (what TechnicalAsset) HighestStoredIntegrity(model *Model) Criticality { highest := what.Integrity for _, dataId := range what.DataAssetsStored { dataAsset := model.DataAssets[dataId] @@ -214,7 +214,7 @@ func (what TechnicalAsset) HighestStoredIntegrity(model *ParsedModel) Criticalit return highest } -func (what TechnicalAsset) HighestAvailability(model *ParsedModel) Criticality { +func (what TechnicalAsset) HighestAvailability(model *Model) Criticality { highest := what.Availability highestProcessed := what.HighestProcessedAvailability(model) if highest < highestProcessed { @@ -229,7 +229,7 @@ func (what TechnicalAsset) HighestAvailability(model *ParsedModel) Criticality { return highest } -func (what TechnicalAsset) HighestProcessedAvailability(model *ParsedModel) Criticality { +func (what TechnicalAsset) HighestProcessedAvailability(model *Model) Criticality { highest := what.Availability for _, dataId := range what.DataAssetsProcessed { dataAsset := model.DataAssets[dataId] @@ -240,7 +240,7 @@ func (what TechnicalAsset) HighestProcessedAvailability(model *ParsedModel) Crit return highest } -func (what TechnicalAsset) HighestStoredAvailability(model *ParsedModel) Criticality { +func (what TechnicalAsset) HighestStoredAvailability(model *Model) Criticality { highest := what.Availability for _, dataId := range what.DataAssetsStored { dataAsset := model.DataAssets[dataId] @@ -251,7 +251,7 @@ func (what TechnicalAsset) HighestStoredAvailability(model *ParsedModel) Critica return highest } -func (what TechnicalAsset) HasDirectConnection(parsedModel *ParsedModel, otherAssetId string) bool { +func (what TechnicalAsset) HasDirectConnection(parsedModel *Model, otherAssetId string) bool { for _, dataFlow := range parsedModel.IncomingTechnicalCommunicationLinksMappedByTargetId[what.Id] { if dataFlow.SourceId == otherAssetId { return true @@ -266,8 +266,8 @@ func (what TechnicalAsset) HasDirectConnection(parsedModel *ParsedModel, otherAs return false } -func (what TechnicalAsset) GeneratedRisks(parsedModel *ParsedModel) []Risk { - resultingRisks := make([]Risk, 0) +func (what TechnicalAsset) GeneratedRisks(parsedModel *Model) []*Risk { + resultingRisks := make([]*Risk, 0) if len(SortedRiskCategories(parsedModel)) == 0 { fmt.Println("Uh, strange, no risks generated (yet?) and asking for them by tech asset...") } @@ -340,7 +340,7 @@ func (what TechnicalAsset) QuickWins() float64 { } */ -func (what TechnicalAsset) GetTrustBoundaryId(model *ParsedModel) string { +func (what TechnicalAsset) GetTrustBoundaryId(model *Model) string { for _, trustBoundary := range model.TrustBoundaries { for _, techAssetInside := range trustBoundary.TechnicalAssetsInside { if techAssetInside == what.Id { @@ -351,7 +351,7 @@ func (what TechnicalAsset) GetTrustBoundaryId(model *ParsedModel) string { return "" } -func SortByTechnicalAssetRiskSeverityAndTitleStillAtRisk(assets []*TechnicalAsset, parsedModel *ParsedModel) { +func SortByTechnicalAssetRiskSeverityAndTitleStillAtRisk(assets []*TechnicalAsset, parsedModel *Model) { sort.Slice(assets, func(i, j int) bool { risksLeft := ReduceToOnlyStillAtRisk(parsedModel, assets[i].GeneratedRisks(parsedModel)) risksRight := ReduceToOnlyStillAtRisk(parsedModel, assets[j].GeneratedRisks(parsedModel)) diff --git a/pkg/security/types/technical_asset_machine.go b/pkg/security/types/technical_asset_machine.go index 921073ff..75f2be4d 100644 --- a/pkg/security/types/technical_asset_machine.go +++ b/pkg/security/types/technical_asset_machine.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -51,3 +53,47 @@ func (what TechnicalAssetMachine) String() string { func (what TechnicalAssetMachine) Explain() string { return TechnicalAssetMachineTypeDescription[what].Description } + +func (what TechnicalAssetMachine) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *TechnicalAssetMachine) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what TechnicalAssetMachine) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *TechnicalAssetMachine) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what TechnicalAssetMachine) find(value string) (TechnicalAssetMachine, error) { + for index, description := range TechnicalAssetMachineTypeDescription { + if strings.EqualFold(value, description.Name) { + return TechnicalAssetMachine(index), nil + } + } + + return TechnicalAssetMachine(0), fmt.Errorf("unknown technical asset machine value %q", value) +} diff --git a/pkg/security/types/technical_asset_size.go b/pkg/security/types/technical_asset_size.go index 45d9d607..bf81519d 100644 --- a/pkg/security/types/technical_asset_size.go +++ b/pkg/security/types/technical_asset_size.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -56,3 +58,37 @@ func (what TechnicalAssetSize) Find(value string) (TechnicalAssetSize, error) { return TechnicalAssetSize(0), fmt.Errorf("unknown technical asset size value %q", value) } + +func (what TechnicalAssetSize) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *TechnicalAssetSize) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.Find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what TechnicalAssetSize) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *TechnicalAssetSize) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.Find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} diff --git a/pkg/security/types/technical_asset_type.go b/pkg/security/types/technical_asset_type.go index 8a2a0c35..0d08d03e 100644 --- a/pkg/security/types/technical_asset_type.go +++ b/pkg/security/types/technical_asset_type.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -49,3 +51,47 @@ func ParseTechnicalAssetType(value string) (technicalAssetType TechnicalAssetTyp } return technicalAssetType, fmt.Errorf("unable to parse into type: %v", value) } + +func (what TechnicalAssetType) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *TechnicalAssetType) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what TechnicalAssetType) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *TechnicalAssetType) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what TechnicalAssetType) find(value string) (TechnicalAssetType, error) { + for index, description := range TechnicalAssetTypeDescription { + if strings.EqualFold(value, description.Name) { + return TechnicalAssetType(index), nil + } + } + + return TechnicalAssetType(0), fmt.Errorf("unknown technical asset type value %q", value) +} diff --git a/pkg/security/types/trust_boundary.go b/pkg/security/types/trust_boundary.go index 0c9811af..3cad0f47 100644 --- a/pkg/security/types/trust_boundary.go +++ b/pkg/security/types/trust_boundary.go @@ -18,7 +18,7 @@ type TrustBoundary struct { TrustBoundariesNested []string `json:"trust_boundaries_nested,omitempty" yaml:"trust_boundaries_nested,omitempty"` } -func (what TrustBoundary) RecursivelyAllTechnicalAssetIDsInside(model *ParsedModel) []string { +func (what TrustBoundary) RecursivelyAllTechnicalAssetIDsInside(model *Model) []string { result := make([]string, 0) what.addAssetIDsRecursively(model, &result) return result @@ -32,7 +32,7 @@ func (what TrustBoundary) IsTaggedWithBaseTag(baseTag string) bool { return IsTaggedWithBaseTag(what.Tags, baseTag) } -func (what TrustBoundary) IsTaggedWithAnyTraversingUp(model *ParsedModel, tags ...string) bool { +func (what TrustBoundary) IsTaggedWithAnyTraversingUp(model *Model, tags ...string) bool { if what.IsTaggedWithAny(tags...) { return true } @@ -43,7 +43,7 @@ func (what TrustBoundary) IsTaggedWithAnyTraversingUp(model *ParsedModel, tags . return false } -func (what TrustBoundary) ParentTrustBoundaryID(model *ParsedModel) string { +func (what TrustBoundary) ParentTrustBoundaryID(model *Model) string { var result string for _, candidate := range model.TrustBoundaries { if contains(candidate.TrustBoundariesNested, what.Id) { @@ -54,7 +54,7 @@ func (what TrustBoundary) ParentTrustBoundaryID(model *ParsedModel) string { return result } -func (what TrustBoundary) HighestConfidentiality(model *ParsedModel) Confidentiality { +func (what TrustBoundary) HighestConfidentiality(model *Model) Confidentiality { highest := Public for _, id := range what.RecursivelyAllTechnicalAssetIDsInside(model) { techAsset := model.TechnicalAssets[id] @@ -65,7 +65,7 @@ func (what TrustBoundary) HighestConfidentiality(model *ParsedModel) Confidentia return highest } -func (what TrustBoundary) HighestIntegrity(model *ParsedModel) Criticality { +func (what TrustBoundary) HighestIntegrity(model *Model) Criticality { highest := Archive for _, id := range what.RecursivelyAllTechnicalAssetIDsInside(model) { techAsset := model.TechnicalAssets[id] @@ -76,7 +76,7 @@ func (what TrustBoundary) HighestIntegrity(model *ParsedModel) Criticality { return highest } -func (what TrustBoundary) HighestAvailability(model *ParsedModel) Criticality { +func (what TrustBoundary) HighestAvailability(model *Model) Criticality { highest := Archive for _, id := range what.RecursivelyAllTechnicalAssetIDsInside(model) { techAsset := model.TechnicalAssets[id] @@ -87,20 +87,20 @@ func (what TrustBoundary) HighestAvailability(model *ParsedModel) Criticality { return highest } -func (what TrustBoundary) AllParentTrustBoundaryIDs(model *ParsedModel) []string { +func (what TrustBoundary) AllParentTrustBoundaryIDs(model *Model) []string { result := make([]string, 0) what.addTrustBoundaryIDsRecursively(model, &result) return result } -func (what TrustBoundary) addAssetIDsRecursively(model *ParsedModel, result *[]string) { +func (what TrustBoundary) addAssetIDsRecursively(model *Model, result *[]string) { *result = append(*result, what.TechnicalAssetsInside...) for _, nestedBoundaryID := range what.TrustBoundariesNested { model.TrustBoundaries[nestedBoundaryID].addAssetIDsRecursively(model, result) } } -func (what TrustBoundary) addTrustBoundaryIDsRecursively(model *ParsedModel, result *[]string) { +func (what TrustBoundary) addTrustBoundaryIDsRecursively(model *Model, result *[]string) { *result = append(*result, what.Id) parentID := what.ParentTrustBoundaryID(model) if len(parentID) > 0 { @@ -110,7 +110,7 @@ func (what TrustBoundary) addTrustBoundaryIDsRecursively(model *ParsedModel, res // as in Go ranging over map is random order, range over them in sorted (hence reproducible) way: -func SortedKeysOfTrustBoundaries(model *ParsedModel) []string { +func SortedKeysOfTrustBoundaries(model *Model) []string { keys := make([]string, 0) for k := range model.TrustBoundaries { keys = append(keys, k) diff --git a/pkg/security/types/trust_boundary_type.go b/pkg/security/types/trust_boundary_type.go index 2c109356..90711ce7 100644 --- a/pkg/security/types/trust_boundary_type.go +++ b/pkg/security/types/trust_boundary_type.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -70,3 +72,47 @@ func (what TrustBoundaryType) IsNetworkBoundary() bool { func (what TrustBoundaryType) IsWithinCloud() bool { return what == NetworkCloudProvider || what == NetworkCloudSecurityGroup } + +func (what TrustBoundaryType) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *TrustBoundaryType) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what TrustBoundaryType) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *TrustBoundaryType) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what TrustBoundaryType) find(value string) (TrustBoundaryType, error) { + for index, description := range TrustBoundaryTypeDescription { + if strings.EqualFold(value, description.Name) { + return TrustBoundaryType(index), nil + } + } + + return TrustBoundaryType(0), fmt.Errorf("unknown trust boundary type value %q", value) +} diff --git a/pkg/security/types/usage.go b/pkg/security/types/usage.go index ec70908f..863f5c99 100644 --- a/pkg/security/types/usage.go +++ b/pkg/security/types/usage.go @@ -5,7 +5,9 @@ Copyright © 2023 NAME HERE package types import ( + "encoding/json" "fmt" + "gopkg.in/yaml.v3" "strings" ) @@ -51,3 +53,47 @@ func (what Usage) Explain() string { func (what Usage) Title() string { return [...]string{"Business", "DevOps"}[what] } + +func (what Usage) MarshalJSON() ([]byte, error) { + return json.Marshal(what.String()) +} + +func (what *Usage) UnmarshalJSON(data []byte) error { + var text string + unmarshalError := json.Unmarshal(data, &text) + if unmarshalError != nil { + return unmarshalError + } + + value, findError := what.find(text) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what Usage) MarshalYAML() (interface{}, error) { + return what.String(), nil +} + +func (what *Usage) UnmarshalYAML(node *yaml.Node) error { + value, findError := what.find(node.Value) + if findError != nil { + return findError + } + + *what = value + return nil +} + +func (what Usage) find(value string) (Usage, error) { + for index, description := range UsageTypeDescription { + if strings.EqualFold(value, description.Name) { + return Usage(index), nil + } + } + + return Usage(0), fmt.Errorf("unknown usage type value %q", value) +} diff --git a/pkg/server/execute.go b/pkg/server/execute.go index 6f145ebe..d3c29d5c 100644 --- a/pkg/server/execute.go +++ b/pkg/server/execute.go @@ -179,7 +179,7 @@ func (s *server) doItViaRuntimeCall(modelFile string, outputDir string, dpi int) { // Remember to also add the same args to the exec based sub-process calls! var cmd *exec.Cmd - args := []string{"-model", modelFile, "-output", outputDir, "-execute-model-macro", s.config.ExecuteModelMacro, "-raa-run", s.config.RAAPlugin, "-custom-risk-rules-plugins", strings.Join(s.config.RiskRulesPlugins, ","), "-skip-risk-rules", s.config.SkipRiskRules, "-diagram-dpi", strconv.Itoa(dpi)} + args := []string{"-model", modelFile, "-output", outputDir, "-execute-model-macro", s.config.ExecuteModelMacro, "-raa-run", s.config.RAAPlugin, "-custom-risk-rules-plugins", strings.Join(s.config.RiskRulesPlugins, ","), "-skip-risk-rules", strings.Join(s.config.SkipRiskRules, ","), "-diagram-dpi", strconv.Itoa(dpi)} if s.config.Verbose { args = append(args, "-verbose") } diff --git a/pkg/server/model.go b/pkg/server/model.go index 1af70560..b6901513 100644 --- a/pkg/server/model.go +++ b/pkg/server/model.go @@ -507,14 +507,14 @@ func (s *server) deleteDataAsset(ginContext *gin.Context) { } } } - for individualRiskCatTitle, individualRiskCat := range modelInput.IndividualRiskCategories { + for _, individualRiskCat := range modelInput.CustomRiskCategories { if individualRiskCat.RisksIdentified != nil { for individualRiskInstanceTitle, individualRiskInstance := range individualRiskCat.RisksIdentified { if individualRiskInstance.MostRelevantDataAsset == dataAsset.ID { // apply the removal referencesDeleted = true - x := modelInput.IndividualRiskCategories[individualRiskCatTitle].RisksIdentified[individualRiskInstanceTitle] + x := individualRiskCat.RisksIdentified[individualRiskInstanceTitle] x.MostRelevantDataAsset = "" // TODO needs more testing - modelInput.IndividualRiskCategories[individualRiskCatTitle].RisksIdentified[individualRiskInstanceTitle] = x + individualRiskCat.RisksIdentified[individualRiskInstanceTitle] = x } } } @@ -603,13 +603,13 @@ func (s *server) setDataAsset(ginContext *gin.Context) { } } } - for individualRiskCatTitle, individualRiskCat := range modelInput.IndividualRiskCategories { + for _, individualRiskCat := range modelInput.CustomRiskCategories { if individualRiskCat.RisksIdentified != nil { for individualRiskInstanceTitle, individualRiskInstance := range individualRiskCat.RisksIdentified { if individualRiskInstance.MostRelevantDataAsset == dataAsset.ID { // apply the ID change - x := modelInput.IndividualRiskCategories[individualRiskCatTitle].RisksIdentified[individualRiskInstanceTitle] + x := individualRiskCat.RisksIdentified[individualRiskInstanceTitle] x.MostRelevantDataAsset = dataAssetInput.ID // TODO needs more testing - modelInput.IndividualRiskCategories[individualRiskCatTitle].RisksIdentified[individualRiskInstanceTitle] = x + individualRiskCat.RisksIdentified[individualRiskInstanceTitle] = x } } } @@ -777,13 +777,13 @@ func (s *server) setSharedRuntime(ginContext *gin.Context) { modelInput.SharedRuntimes[payload.Title] = sharedRuntimeInput idChanged := sharedRuntimeInput.ID != sharedRuntime.ID if idChanged { // ID-CHANGE-PROPAGATION - for individualRiskCatTitle, individualRiskCat := range modelInput.IndividualRiskCategories { + for _, individualRiskCat := range modelInput.CustomRiskCategories { if individualRiskCat.RisksIdentified != nil { for individualRiskInstanceTitle, individualRiskInstance := range individualRiskCat.RisksIdentified { if individualRiskInstance.MostRelevantSharedRuntime == sharedRuntime.ID { // apply the ID change - x := modelInput.IndividualRiskCategories[individualRiskCatTitle].RisksIdentified[individualRiskInstanceTitle] + x := individualRiskCat.RisksIdentified[individualRiskInstanceTitle] x.MostRelevantSharedRuntime = sharedRuntimeInput.ID // TODO needs more testing - modelInput.IndividualRiskCategories[individualRiskCatTitle].RisksIdentified[individualRiskInstanceTitle] = x + individualRiskCat.RisksIdentified[individualRiskInstanceTitle] = x } } } @@ -928,14 +928,14 @@ func (s *server) deleteSharedRuntime(ginContext *gin.Context) { for title, sharedRuntime := range modelInput.SharedRuntimes { if sharedRuntime.ID == ginContext.Param("shared-runtime-id") { // also remove all usages of this shared runtime !! - for individualRiskCatTitle, individualRiskCat := range modelInput.IndividualRiskCategories { + for _, individualRiskCat := range modelInput.CustomRiskCategories { if individualRiskCat.RisksIdentified != nil { for individualRiskInstanceTitle, individualRiskInstance := range individualRiskCat.RisksIdentified { if individualRiskInstance.MostRelevantSharedRuntime == sharedRuntime.ID { // apply the removal referencesDeleted = true - x := modelInput.IndividualRiskCategories[individualRiskCatTitle].RisksIdentified[individualRiskInstanceTitle] + x := individualRiskCat.RisksIdentified[individualRiskInstanceTitle] x.MostRelevantSharedRuntime = "" // TODO needs more testing - modelInput.IndividualRiskCategories[individualRiskCatTitle].RisksIdentified[individualRiskInstanceTitle] = x + individualRiskCat.RisksIdentified[individualRiskInstanceTitle] = x } } } diff --git a/pkg/server/server.go b/pkg/server/server.go index b9747b3f..201ba8ec 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -35,7 +35,7 @@ type server struct { mapFolderNameToTokenHash map[string]string extremeShortTimeoutsForTesting bool locksByFolderName map[string]*sync.Mutex - customRiskRules map[string]*model.CustomRisk + customRiskRules risks.RiskRules } func RunServer(config *common.Config) { diff --git a/test/all.json b/test/all.json index b33364e3..e1bb05cf 100644 --- a/test/all.json +++ b/test/all.json @@ -1051,8 +1051,8 @@ ] } }, - "individual_risk_categories": { - "something-strange": { + "custom_risk_categories": [ + { "id": "something-strange", "title": "Some Individual Risk Example", "description": "Some text describing the risk category...", @@ -1066,9 +1066,10 @@ "risk_assessment": "Some text describing the risk assessment...", "false_positives": "Some text describing the most common types of false positives...", "stride": "repudiation", - "cwe": 693 + "cwe": 693, + "is_built_in": true } - }, + ], "built_in_risk_categories": { "accidental-secret-leak": { "id": "accidental-secret-leak", diff --git a/test/all.yaml b/test/all.yaml index 73264af8..b26af1a7 100644 --- a/test/all.yaml +++ b/test/all.yaml @@ -1239,12 +1239,10 @@ shared_runtimes: +custom_risk_categories: # used for adding custom risk categories -individual_risk_categories: # used for adding custom manually identified risks - - - Some Individual Risk Example: - id: something-strange + - id: something-strange + title: Some Individual Risk Example description: Some text describing the risk category... impact: Some text describing the impact... asvs: V0 - Something Strange @@ -1259,6 +1257,7 @@ individual_risk_categories: # used for adding custom manually identified risks false_positives: Some text describing the most common types of false positives... model_failure_possible_reason: false cwe: 693 + is_built_in: true risks_identified: Example Individual Risk at Database: severity: critical # values: low, medium, elevated, high, critical @@ -1290,8 +1289,8 @@ individual_risk_categories: # used for adding custom manually identified risks # For risk tracking each risk-id needs to be defined (the string with the @ sign in it). These unique risk IDs # are visible in the PDF report (the small grey string under each risk), the Excel (column "ID"), as well as the JSON responses. # Some risk IDs have only one @ sign in them, while others multiple. The idea is to allow for unique but still speaking IDs. -# Therefore each risk instance creates its individual ID by taking all affected elements causing the risk to be within an @-delimited part. -# Using wildcards (the * sign) for parts delimited by @ signs allows to handle groups of certain risks at once. Best is to lookup the IDs +# Therefore, each risk instance creates its individual ID by taking all affected elements causing the risk to be within an @-delimited part. +# Using wildcards (the * sign) for parts delimited by @ signs allows to handle groups of certain risks at once. Best is to look up the IDs # to use in the created Excel file. Alternatively a model macro "seed-risk-tracking" is available that helps in initially # seeding the risk tracking part here based on already identified and not yet handled risks. risk_tracking: diff --git a/test/risk-category.yaml b/test/risk-category.yaml index feae907b..40ad149b 100644 --- a/test/risk-category.yaml +++ b/test/risk-category.yaml @@ -1,140 +1,140 @@ -individual_risk_categories: - accidental-secret-leak: - id: accidental-secret-leak - title: Accidental Secret Leak - function: operations - stride: information-disclosure - cwe: 200 - description: - Sourcecode repositories (including their histories) as well as artifact registries can accidentally contain - secrets like checked-in or packaged-in passwords, API tokens, certificates, crypto keys, etc. - impact: - If this risk is unmitigated, attackers which have access to affected sourcecode repositories or artifact - registries might find secrets accidentally checked-in. - asvs: - V14 - Configuration Verification Requirements - cheat_sheet: - https://cheatsheetseries.owasp.org/cheatsheets/Attack_Surface_Analysis_Cheat_Sheet.html - action: - Build Pipeline Hardening - mitigation: - Establish measures preventing accidental check-in or package-in of secrets into sourcecode repositories and - artifact registries. This starts by using good .gitignore and .dockerignore files, but does not stop there. - See for example tools like \"git-secrets\" or \"Talisman\" to have check-in preventive measures for - secrets. Consider also to regularly scan your repositories for secrets accidentally checked-in using - scanning tools like "gitleaks" or "gitrob". - check: - Are recommendations from the linked cheat sheet and referenced ASVS chapter applied? - detection_logic: - In-scope sourcecode repositories and artifact registries. - risk_assessment: - The risk rating depends on the sensitivity of the technical asset itself and of the data assets processed. - false_positives: - Usually no false positives. +id: accidental-secret-leak +title: Accidental Secret Leak +function: operations +stride: information-disclosure +cwe: 200 +description: + Sourcecode repositories (including their histories) as well as artifact registries can accidentally contain + secrets like checked-in or packaged-in passwords, API tokens, certificates, crypto keys, etc. +impact: + If this risk is unmitigated, attackers which have access to affected sourcecode repositories or artifact + registries might find secrets accidentally checked-in. +asvs: + V14 - Configuration Verification Requirements +cheat_sheet: + https://cheatsheetseries.owasp.org/cheatsheets/Attack_Surface_Analysis_Cheat_Sheet.html +action: + Build Pipeline Hardening +mitigation: + Establish measures preventing accidental check-in or package-in of secrets into sourcecode repositories and + artifact registries. This starts by using good .gitignore and .dockerignore files, but does not stop there. + See for example tools like \"git-secrets\" or \"Talisman\" to have check-in preventive measures for + secrets. Consider also to regularly scan your repositories for secrets accidentally checked-in using + scanning tools like "gitleaks" or "gitrob". +check: + Are recommendations from the linked cheat sheet and referenced ASVS chapter applied? +detection_logic: + In-scope sourcecode repositories and artifact registries. +risk_assessment: + The risk rating depends on the sensitivity of the technical asset itself and of the data assets processed. +false_positives: + Usually no false positives. +is_built_in: true - risk: - parameter: tech_asset - id: "{$risk.id}@{tech_asset.id}" - title: "get_title({tech_asset})" - severity: "calculate_severity(unlikely, get_impact({tech_asset}))" - exploitation_likelihood: unlikely - exploitation_impact: "get_impact({tech_asset})" - data_breach_probability: probable - data_breach_technical_assets: - - "{tech_asset.id}" - most_relevant_data_asset: "{tech_asset.id}" +script: + risk: + parameter: tech_asset + id: "{$risk.id}@{tech_asset.id}" + title: "get_title({tech_asset})" + severity: "calculate_severity(unlikely, get_impact({tech_asset}))" + exploitation_likelihood: unlikely + exploitation_impact: "get_impact({tech_asset})" + data_breach_probability: probable + data_breach_technical_assets: + - "{tech_asset.id}" + most_relevant_data_asset: "{tech_asset.id}" - match: - parameter: tech_asset - do: - if: - and: - - false: "{tech_asset.out_of_scope}" - - contains: - item: "{tech_asset.technology}" - in: - - sourcecode-repository - - artifact-registry - then: - return: true + match: + parameter: tech_asset + do: + if: + and: + - false: "{tech_asset.out_of_scope}" + - contains: + item: "{tech_asset.technology}" + in: + - sourcecode-repository + - artifact-registry + then: + return: true - utils: - get_title: - parameters: - - tech_asset - do: - - if: - contains: - item: git - in: "{tech_asset.tags}" - then: - - return: - "Accidental Secret Leak(Git) risk at {tech_asset.title}: Git Leak Prevention" - else: - - return: - "Accidental Secret Leak risk at {tech_asset.title}" + utils: + get_title: + parameters: + - tech_asset + do: + - if: + contains: + item: git + in: "{tech_asset.tags}" + then: + - return: + "Accidental Secret Leak(Git) risk at {tech_asset.title}: Git Leak Prevention" + else: + - return: + "Accidental Secret Leak risk at {tech_asset.title}" - get_impact: - parameters: - - tech_asset - do: - - assign: - - impact: low - - highest_confidentiality: "get_highest({tech_asset}, confidentiality)" - - highest_integrity: "get_highest({tech_asset}, integrity)" - - highest_availability: "get_highest({tech_asset}, availability)" - - if: - or: - - equal-or-greater: - as: confidentiality - first: "{highest_confidentiality}" - second: confidential - - equal-or-greater: - as: integrity - first: "{highest_integrity}" - second: critical - - equal-or-greater: - as: availability - first: "{highest_availability}" - second: critical - then: - - assign: - impact: medium - - if: - or: - - equal-or-greater: - as: confidentiality - first: "{highest_confidentiality}" - second: strictly-confidential - - equal-or-greater: - as: integrity - first: "{highest_integrity}" - second: mission-critical - - equal-or-greater: - as: availability - first: "{highest_availability}" - second: mission-critical - then: - - assign: - impact: high - - return: "{impact}" + get_impact: + parameters: + - tech_asset + do: + - assign: + - impact: low + - highest_confidentiality: "get_highest({tech_asset}, confidentiality)" + - highest_integrity: "get_highest({tech_asset}, integrity)" + - highest_availability: "get_highest({tech_asset}, availability)" + - if: + or: + - equal-or-greater: + as: confidentiality + first: "{highest_confidentiality}" + second: confidential + - equal-or-greater: + as: integrity + first: "{highest_integrity}" + second: critical + - equal-or-greater: + as: availability + first: "{highest_availability}" + second: critical + then: + - assign: + impact: medium + - if: + or: + - equal-or-greater: + as: confidentiality + first: "{highest_confidentiality}" + second: strictly-confidential + - equal-or-greater: + as: integrity + first: "{highest_integrity}" + second: mission-critical + - equal-or-greater: + as: availability + first: "{highest_availability}" + second: mission-critical + then: + - assign: + impact: high + - return: "{impact}" - get_highest: - parameters: - - tech_asset - - "type" - do: - - assign: - - value: "{tech_asset.{type}}" - - loop: - in: "{tech_asset.data_assets_processed}" - item: data_id - do: - if: - greater: - first: "{$model.data_assets.{data_id}.{type}}" - second: "{value}" - then: - - assign: - value: "{$model.data_assets.{data_id}.{type}}" - - return: "{value}" + get_highest: + parameters: + - tech_asset + - "type" + do: + - assign: + - value: "{tech_asset.{type}}" + - loop: + in: "{tech_asset.data_assets_processed}" + item: data_id + do: + if: + greater: + first: "{$model.data_assets.{data_id}.{type}}" + second: "{value}" + then: + - assign: + value: "{$model.data_assets.{data_id}.{type}}" + - return: "{value}" diff --git a/test/risk_categories.yaml b/test/risk_categories.yaml index 572534c3..49b99b8e 100644 --- a/test/risk_categories.yaml +++ b/test/risk_categories.yaml @@ -1,8 +1,7 @@ +custom_risk_categories: # used for adding custom risk categories -individual_risk_categories: # used for adding custom manually identified risks - - Some Individual Risk Example: - id: something-strange + - id: something-strange + title: Some Individual Risk Example description: Some text describing the risk category... impact: Some text describing the impact... asvs: V0 - Something Strange