diff --git a/pkg/security/risks/builtin/missing_identity_provider_isolation_rule_test.go b/pkg/security/risks/builtin/missing_identity_provider_isolation_rule_test.go
new file mode 100644
index 00000000..7a0d319f
--- /dev/null
+++ b/pkg/security/risks/builtin/missing_identity_provider_isolation_rule_test.go
@@ -0,0 +1,280 @@
+package builtin
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/threagile/threagile/pkg/security/types"
+)
+
+func TestMissingIdentityProviderIsolationRuleGenerateRisksEmptyModelNotRisksCreated(t *testing.T) {
+ rule := NewMissingIdentityProviderIsolationRule()
+
+ risks, err := rule.GenerateRisks(&types.Model{})
+
+ assert.Nil(t, err)
+ assert.Empty(t, risks)
+}
+
+func TestMissingIdentityProviderIsolationRuleGenerateRisksOutOfScopeNoRisksCreated(t *testing.T) {
+ rule := NewMissingIdentityProviderIsolationRule()
+ risks, err := rule.GenerateRisks(&types.Model{
+ TechnicalAssets: map[string]*types.TechnicalAsset{
+ "ta1": {
+ Title: "Test Technical Asset",
+ OutOfScope: true,
+ Technologies: types.TechnologyList{
+ {
+ Name: "some-technology",
+ Attributes: map[string]bool{
+ types.IsIdentityRelated: true,
+ },
+ },
+ },
+ },
+ },
+ })
+
+ assert.Nil(t, err)
+ assert.Empty(t, risks)
+}
+
+func TestMissingIdentityProviderIsolationRuleGenerateRisksNotIdentityRelatedNoRisksCreated(t *testing.T) {
+ rule := NewMissingIdentityProviderIsolationRule()
+ risks, err := rule.GenerateRisks(&types.Model{
+ TechnicalAssets: map[string]*types.TechnicalAsset{
+ "ta1": {
+ Title: "Test Technical Asset",
+ OutOfScope: false,
+ Technologies: types.TechnologyList{
+ {
+ Name: "some-technology",
+ Attributes: map[string]bool{
+ types.IsIdentityRelated: false,
+ },
+ },
+ },
+ },
+ },
+ })
+
+ assert.Nil(t, err)
+ assert.Empty(t, risks)
+}
+
+func TestMissingIdentityProviderIsolationRuleGenerateRisksSparringAssetIsIdentityRelatedRelatedNoRisksCreated(t *testing.T) {
+ rule := NewMissingIdentityProviderIsolationRule()
+ risks, err := rule.GenerateRisks(&types.Model{
+ TechnicalAssets: map[string]*types.TechnicalAsset{
+ "ta1": {
+ Id: "ta1",
+ Title: "Test Technical Asset",
+ OutOfScope: false,
+ Technologies: types.TechnologyList{
+ {
+ Name: "some-technology",
+ Attributes: map[string]bool{
+ types.IsIdentityRelated: true,
+ },
+ },
+ },
+ },
+ "ta2": {
+ Id: "ta2",
+ Title: "Test Sparring Technical Asset",
+ Technologies: types.TechnologyList{
+ {
+ Name: "some-technology",
+ Attributes: map[string]bool{
+ types.IsIdentityRelated: true,
+ },
+ },
+ },
+ },
+ },
+ })
+
+ assert.Nil(t, err)
+ assert.Empty(t, risks)
+}
+
+func TestMissingIdentityProviderIsolationRuleGenerateRisksSparringAssetIsCloseToHighValueTargetsToleratedNoRisksCreated(t *testing.T) {
+ rule := NewMissingIdentityProviderIsolationRule()
+ risks, err := rule.GenerateRisks(&types.Model{
+ TechnicalAssets: map[string]*types.TechnicalAsset{
+ "ta1": {
+ Id: "ta1",
+ Title: "Test Technical Asset",
+ OutOfScope: false,
+ Technologies: types.TechnologyList{
+ {
+ Name: "some-technology",
+ Attributes: map[string]bool{
+ types.IsIdentityRelated: true,
+ },
+ },
+ },
+ },
+ "ta2": {
+ Id: "ta2",
+ Title: "Test Sparring Technical Asset",
+ Technologies: types.TechnologyList{
+ {
+ Name: "some-technology",
+ Attributes: map[string]bool{
+ types.IsCloseToHighValueTargetsTolerated: true,
+ },
+ },
+ },
+ },
+ },
+ })
+
+ assert.Nil(t, err)
+ assert.Empty(t, risks)
+}
+
+func TestMissingIdentityProviderIsolationRuleGenerateRisksSparringAssetNotInTheSameTrustBoundaryOrExecutionEnvironmentNoRisksCreated(t *testing.T) {
+ rule := NewMissingIdentityProviderIsolationRule()
+ tb1 := &types.TrustBoundary{
+ Id: "tb1",
+ }
+ tb2 := &types.TrustBoundary{
+ Id: "tb2",
+ }
+ risks, err := rule.GenerateRisks(&types.Model{
+ TechnicalAssets: map[string]*types.TechnicalAsset{
+ "ta1": {
+ Id: "ta1",
+ Title: "Test Technical Asset",
+ OutOfScope: false,
+ Technologies: types.TechnologyList{
+ {
+ Name: "some-technology",
+ Attributes: map[string]bool{
+ types.IsIdentityRelated: true,
+ },
+ },
+ },
+ },
+ "ta2": {
+ Id: "ta2",
+ Title: "Test Sparring Technical Asset",
+ },
+ },
+ DirectContainingTrustBoundaryMappedByTechnicalAssetId: map[string]*types.TrustBoundary{
+ "ta1": tb1,
+ "ta2": tb2,
+ },
+ })
+
+ assert.Nil(t, err)
+ assert.Empty(t, risks)
+}
+
+type MissingIdentityProviderIsolationRuleTest struct {
+ confidentiality types.Confidentiality
+ integrity types.Criticality
+ availability types.Criticality
+ tbType types.TrustBoundaryType
+
+ expectedImpact types.RiskExploitationImpact
+ expectedLikelihood types.RiskExploitationLikelihood
+ expectedTrustBoundaryMessage string
+}
+
+func TestMissingIdentityProviderIsolationRule(t *testing.T) {
+ testCases := map[string]MissingIdentityProviderIsolationRuleTest{
+ "same execution environment": {
+ confidentiality: types.Confidential,
+ integrity: types.Critical,
+ availability: types.Critical,
+ tbType: types.ExecutionEnvironment,
+ expectedImpact: types.HighImpact,
+ expectedLikelihood: types.Likely,
+ expectedTrustBoundaryMessage: "execution environment",
+ },
+ "more impact for strictly confidential asset": {
+ confidentiality: types.StrictlyConfidential,
+ integrity: types.Critical,
+ availability: types.Critical,
+ tbType: types.ExecutionEnvironment,
+ expectedImpact: types.VeryHighImpact,
+ expectedLikelihood: types.Likely,
+ expectedTrustBoundaryMessage: "execution environment",
+ },
+ "more impact for mission critical integrity asset": {
+ confidentiality: types.Confidential,
+ integrity: types.MissionCritical,
+ availability: types.Critical,
+ tbType: types.ExecutionEnvironment,
+ expectedImpact: types.VeryHighImpact,
+ expectedLikelihood: types.Likely,
+ expectedTrustBoundaryMessage: "execution environment",
+ },
+ "more impact for mission critical availability asset": {
+ confidentiality: types.Confidential,
+ integrity: types.Critical,
+ availability: types.MissionCritical,
+ tbType: types.ExecutionEnvironment,
+ expectedImpact: types.VeryHighImpact,
+ expectedLikelihood: types.Likely,
+ expectedTrustBoundaryMessage: "execution environment",
+ },
+ "same network trust boundary": {
+ confidentiality: types.Confidential,
+ integrity: types.Critical,
+ availability: types.Critical,
+ tbType: types.NetworkOnPrem,
+ expectedImpact: types.HighImpact,
+ expectedLikelihood: types.Unlikely,
+ expectedTrustBoundaryMessage: "network segment",
+ },
+ }
+
+ for name, testCase := range testCases {
+ t.Run(name, func(t *testing.T) {
+ rule := NewMissingIdentityProviderIsolationRule()
+ tb := &types.TrustBoundary{
+ Id: "tb1",
+ Type: testCase.tbType,
+ }
+ risks, err := rule.GenerateRisks(&types.Model{
+ TechnicalAssets: map[string]*types.TechnicalAsset{
+ "ta1": {
+ Id: "ta1",
+ Title: "Test Technical Asset",
+ OutOfScope: false,
+ Confidentiality: testCase.confidentiality,
+ Integrity: testCase.integrity,
+ Availability: testCase.availability,
+ Technologies: types.TechnologyList{
+ {
+ Name: "some-identity-related-technology",
+ Attributes: map[string]bool{
+ types.IsIdentityRelated: true,
+ },
+ },
+ },
+ },
+ "ta2": {
+ Id: "ta2",
+ Title: "Test Sparring Technical Asset",
+ },
+ },
+ DirectContainingTrustBoundaryMappedByTechnicalAssetId: map[string]*types.TrustBoundary{
+ "ta1": tb,
+ "ta2": tb,
+ },
+ })
+
+ assert.Nil(t, err)
+ assert.Len(t, risks, 1)
+ assert.Equal(t, testCase.expectedImpact, risks[0].ExploitationImpact)
+ assert.Equal(t, testCase.expectedLikelihood, risks[0].ExploitationLikelihood)
+ expTitle := fmt.Sprintf("Missing Identity Provider Isolation to further encapsulate and protect identity-related asset Test Technical Asset against unrelated lower protected assets in the same %s, which might be easier to compromise by attackers", testCase.expectedTrustBoundaryMessage)
+ assert.Equal(t, expTitle, risks[0].Title)
+ })
+ }
+}