989 lines
30 KiB
C#
989 lines
30 KiB
C#
// -----------------------------------------------------------------------------
|
|
// IacBoundaryExtractorTests.cs
|
|
// Sprint: SPRINT_3800_0002_0004_boundary_iac
|
|
// Description: Unit tests for IacBoundaryExtractor.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using StellaOps.Scanner.Reachability.Boundary;
|
|
using StellaOps.Scanner.Reachability.Gates;
|
|
using Xunit;
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.Scanner.Reachability.Tests;
|
|
|
|
public class IacBoundaryExtractorTests
|
|
{
|
|
private readonly IacBoundaryExtractor _extractor;
|
|
|
|
public IacBoundaryExtractorTests()
|
|
{
|
|
_extractor = new IacBoundaryExtractor(
|
|
NullLogger<IacBoundaryExtractor>.Instance);
|
|
}
|
|
|
|
#region Priority and CanHandle
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Priority_Returns150_BetweenBaseAndK8s()
|
|
{
|
|
Assert.Equal(150, _extractor.Priority);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Theory]
|
|
[InlineData("terraform", true)]
|
|
[InlineData("Terraform", true)]
|
|
[InlineData("cloudformation", true)]
|
|
[InlineData("cfn", true)]
|
|
[InlineData("pulumi", true)]
|
|
[InlineData("helm", true)]
|
|
[InlineData("iac", true)]
|
|
[InlineData("k8s", false)]
|
|
[InlineData("static", false)]
|
|
[InlineData("kong", false)]
|
|
public void CanHandle_WithSource_ReturnsExpected(string source, bool expected)
|
|
{
|
|
var context = BoundaryExtractionContext.CreateEmpty() with { Source = source };
|
|
Assert.Equal(expected, _extractor.CanHandle(context));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CanHandle_WithTerraformAnnotations_ReturnsTrue()
|
|
{
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.resource.aws_security_group"] = "sg-123"
|
|
}
|
|
};
|
|
|
|
Assert.True(_extractor.CanHandle(context));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CanHandle_WithCloudFormationAnnotations_ReturnsTrue()
|
|
{
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["cloudformation.AWS::EC2::SecurityGroup"] = "sg-123"
|
|
}
|
|
};
|
|
|
|
Assert.True(_extractor.CanHandle(context));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CanHandle_WithHelmAnnotations_ReturnsTrue()
|
|
{
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["helm.values.ingress.enabled"] = "true"
|
|
}
|
|
};
|
|
|
|
Assert.True(_extractor.CanHandle(context));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CanHandle_WithEmptyAnnotations_ReturnsFalse()
|
|
{
|
|
var context = BoundaryExtractionContext.CreateEmpty();
|
|
Assert.False(_extractor.CanHandle(context));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IaC Type Detection
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithTerraformSource_ReturnsTerraformIacSource()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("iac:terraform", result.Source);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithCloudFormationSource_ReturnsCloudFormationIacSource()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "cloudformation", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "cloudformation"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("iac:cloudformation", result.Source);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithCfnSource_ReturnsCloudFormationIacSource()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "cfn", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "cfn"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("iac:cloudformation", result.Source);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithPulumiSource_ReturnsPulumiIacSource()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "pulumi", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "pulumi"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("iac:pulumi", result.Source);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithHelmSource_ReturnsHelmIacSource()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "helm", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "helm"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("iac:helm", result.Source);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Terraform Exposure Detection
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithTerraformPublicSecurityGroup_ReturnsPublicExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_security_group.ingress.cidr"] = "0.0.0.0/0"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("public", result.Exposure.Level);
|
|
Assert.True(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithTerraformInternetFacingAlb_ReturnsPublicExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_lb.internal"] = "false"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("public", result.Exposure.Level);
|
|
Assert.True(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithTerraformPublicIp_ReturnsPublicExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_eip.public_ip"] = "true"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("public", result.Exposure.Level);
|
|
Assert.True(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithTerraformPrivateResource_ReturnsInternalExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_vpc.private_subnets"] = "10.0.0.0/24"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("internal", result.Exposure.Level);
|
|
Assert.False(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CloudFormation Exposure Detection
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithCloudFormationPublicSecurityGroup_ReturnsPublicExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "cloudformation", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "cloudformation",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["cloudformation.AWS::EC2::SecurityGroup.Ingress"] = "0.0.0.0/0"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("public", result.Exposure.Level);
|
|
Assert.True(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithCloudFormationInternetFacingElb_ReturnsPublicExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "cloudformation", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "cloudformation",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["cloudformation.AWS::ElasticLoadBalancingV2::LoadBalancer.Scheme"] = "internet-facing"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("public", result.Exposure.Level);
|
|
Assert.True(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithCloudFormationApiGateway_ReturnsPublicExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "cloudformation", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "cloudformation",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["cloudformation.AWS::ApiGateway::RestApi"] = "my-api"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("public", result.Exposure.Level);
|
|
Assert.True(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helm Exposure Detection
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithHelmIngressEnabled_ReturnsPublicExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "helm", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "helm",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["helm.values.ingress.enabled"] = "true"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("public", result.Exposure.Level);
|
|
Assert.True(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithHelmLoadBalancerService_ReturnsPublicExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "helm", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "helm",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["helm.values.service.type"] = "LoadBalancer"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("public", result.Exposure.Level);
|
|
Assert.True(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithHelmClusterIpService_ReturnsPrivateExposure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "helm", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "helm",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["helm.values.service.type"] = "ClusterIP"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.Equal("private", result.Exposure.Level);
|
|
Assert.False(result.Exposure.InternetFacing);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Auth Detection
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithIamAuth_ReturnsIamAuthType()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_iam_policy.auth"] = "enabled"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Auth);
|
|
Assert.True(result.Auth.Required);
|
|
Assert.Equal("iam", result.Auth.Type);
|
|
Assert.Equal("aws-iam", result.Auth.Provider);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithCognitoAuth_ReturnsOAuth2AuthType()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "cloudformation", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "cloudformation",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["cloudformation.AWS::Cognito::UserPool"] = "my-pool"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Auth);
|
|
Assert.True(result.Auth.Required);
|
|
Assert.Equal("oauth2", result.Auth.Type);
|
|
Assert.Equal("cognito", result.Auth.Provider);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithAzureAdAuth_ReturnsOAuth2AuthType()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.azurerm_azure_ad_application"] = "my-app"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Auth);
|
|
Assert.True(result.Auth.Required);
|
|
Assert.Equal("oauth2", result.Auth.Type);
|
|
Assert.Equal("azure-ad", result.Auth.Provider);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithMtlsAuth_ReturnsMtlsAuthType()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_acm_certificate.mtls"] = "enabled"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Auth);
|
|
Assert.True(result.Auth.Required);
|
|
Assert.Equal("mtls", result.Auth.Type);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithNoAuth_ReturnsNullAuth()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Null(result.Auth);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Controls Detection
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithSecurityGroup_ReturnsSecurityGroupControl()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_security_group.main"] = "sg-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Controls);
|
|
Assert.Contains(result.Controls, c => c.Type == "security_group");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithWaf_ReturnsWafControl()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_wafv2_web_acl.main"] = "waf-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Controls);
|
|
Assert.Contains(result.Controls, c => c.Type == "waf");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithVpc_ReturnsNetworkIsolationControl()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_vpc.main"] = "vpc-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Controls);
|
|
Assert.Contains(result.Controls, c => c.Type == "network_isolation");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithNacl_ReturnsNetworkAclControl()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_network_acl.main"] = "nacl-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Controls);
|
|
Assert.Contains(result.Controls, c => c.Type == "network_acl");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithDdosProtection_ReturnsDdosControl()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_shield_protection.main"] = "shield-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Controls);
|
|
Assert.Contains(result.Controls, c => c.Type == "ddos_protection");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithTls_ReturnsEncryptionControl()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_acm_certificate.tls"] = "cert-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Controls);
|
|
Assert.Contains(result.Controls, c => c.Type == "encryption_in_transit");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithPrivateEndpoint_ReturnsPrivateEndpointControl()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_vpc_endpoint.main"] = "vpce-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Controls);
|
|
Assert.Contains(result.Controls, c => c.Type == "private_endpoint");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithMultipleControls_ReturnsAllControls()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_security_group.main"] = "sg-123",
|
|
["terraform.aws_wafv2_web_acl.main"] = "waf-123",
|
|
["terraform.aws_vpc.main"] = "vpc-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Controls);
|
|
Assert.Equal(3, result.Controls.Count);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithNoControls_ReturnsNullControls()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Null(result.Controls);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Surface Detection
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithHelmIngressPath_ReturnsSurfaceWithPath()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "helm", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "helm",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["helm.values.ingress.path"] = "/api/v1"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Surface);
|
|
Assert.Equal("/api/v1", result.Surface.Path);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithHelmIngressHost_ReturnsSurfaceWithHost()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "helm", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "helm",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["helm.values.ingress.host"] = "api.example.com"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Surface);
|
|
Assert.Equal("api.example.com", result.Surface.Host);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_DefaultSurfaceType_ReturnsInfrastructure()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Surface);
|
|
Assert.Equal("infrastructure", result.Surface.Type);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_DefaultProtocol_ReturnsHttps()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Surface);
|
|
Assert.Equal("https", result.Surface.Protocol);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Confidence and Metadata
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_BaseConfidence_Returns0Point6()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "iac", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "iac"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(0.6, result.Confidence, precision: 2);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithKnownIacType_IncreasesConfidence()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(0.7, result.Confidence, precision: 2);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithSecurityResources_IncreasesConfidence()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_security_group.main"] = "sg-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(0.8, result.Confidence, precision: 2);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_MaxConfidence_CapsAt0Point85()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_security_group.main"] = "sg-123",
|
|
["terraform.aws_wafv2_web_acl.main"] = "waf-123",
|
|
["terraform.aws_vpc.main"] = "vpc-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.True(result.Confidence <= 0.85);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_ReturnsNetworkKind()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("network", result.Kind);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_BuildsEvidenceRef_WithIacType()
|
|
{
|
|
var root = new RichGraphRoot("root-123", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Namespace = "production",
|
|
EnvironmentId = "env-456"
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("iac/terraform/production/env-456/root-123", result.EvidenceRef);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ExtractAsync
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task ExtractAsync_ReturnsSameResultAsExtract()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_security_group.main"] = "sg-123"
|
|
}
|
|
};
|
|
|
|
var syncResult = _extractor.Extract(root, null, context);
|
|
var asyncResult = await _extractor.ExtractAsync(root, null, context);
|
|
|
|
Assert.NotNull(syncResult);
|
|
Assert.NotNull(asyncResult);
|
|
Assert.Equal(syncResult.Kind, asyncResult.Kind);
|
|
Assert.Equal(syncResult.Source, asyncResult.Source);
|
|
Assert.Equal(syncResult.Confidence, asyncResult.Confidence);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Edge Cases
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithNullRoot_ThrowsArgumentNullException()
|
|
{
|
|
var context = BoundaryExtractionContext.CreateEmpty() with { Source = "terraform" };
|
|
Assert.Throws<ArgumentNullException>(() => _extractor.Extract(null!, null, context));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WhenCannotHandle_ReturnsNull()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "k8s", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with { Source = "k8s" };
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void Extract_WithLoadBalancer_SetsBehindProxyTrue()
|
|
{
|
|
var root = new RichGraphRoot("root-1", "terraform", null);
|
|
var context = BoundaryExtractionContext.CreateEmpty() with
|
|
{
|
|
Source = "terraform",
|
|
Annotations = new Dictionary<string, string>
|
|
{
|
|
["terraform.aws_alb.main"] = "alb-123"
|
|
}
|
|
};
|
|
|
|
var result = _extractor.Extract(root, null, context);
|
|
|
|
Assert.NotNull(result);
|
|
Assert.NotNull(result.Exposure);
|
|
Assert.True(result.Exposure.BehindProxy);
|
|
}
|
|
|
|
#endregion
|
|
}
|