fix(e2e): authorize default SG tcp/22 + icmp in single-node fixture

admin init leaves the default SG closed (AWS parity); the Go single suite
never ported the Phase-5 SG-ingress block that run-e2e.sh has had since day
one, so every test runner SSH/ping probe hits the OVN port-group ACL drop.

- ec2helpers: add AuthorizeICMPIngress + EnsureDefaultSGOpen helpers.
- single/main_test: open default SG once per process in pkgFixOnce.Do.
- witness: extend ensureDefaultSGSSHIngress to authorize ICMP too.
- run-e2e.sh: append matching ICMP authorize next to tcp/22 block.

mulga-siv-134
This commit is contained in:
joshsiv-mulga
2026-05-25 11:18:56 +10:00
parent a429012cda
commit ec2bb542e3
4 changed files with 79 additions and 12 deletions
+34
View File
@@ -226,6 +226,40 @@ func AuthorizeSSHIngress(t *testing.T, c *AWSClient, sgID string) {
t.Fatalf("authorize-security-group-ingress tcp/22 on %s: %v", sgID, err)
}
// AuthorizeICMPIngress idempotently authorizes ICMP (all types) ingress from
// 0.0.0.0/0 on sgID. Same Duplicate-tolerant contract as AuthorizeSSHIngress.
func AuthorizeICMPIngress(t *testing.T, c *AWSClient, sgID string) {
t.Helper()
_, err := c.EC2.AuthorizeSecurityGroupIngress(&ec2.AuthorizeSecurityGroupIngressInput{
GroupId: aws.String(sgID),
IpPermissions: []*ec2.IpPermission{{
IpProtocol: aws.String("icmp"),
FromPort: aws.Int64(-1),
ToPort: aws.Int64(-1),
IpRanges: []*ec2.IpRange{{CidrIp: aws.String("0.0.0.0/0")}},
}},
})
if err == nil {
return
}
var aerr awserr.Error
if asErr(err, &aerr) && aerr.Code() == "InvalidPermission.Duplicate" {
return
}
t.Fatalf("authorize-security-group-ingress icmp on %s: %v", sgID, err)
}
// EnsureDefaultSGOpen authorizes tcp/22 + ICMP on the default VPC's default
// SG. AWS default SGs only admit same-SG members; e2e probes come from the
// test runner's external IP, so without this every SSH/ping hits the OVN
// port-group ACL drop. Mirrors the Phase-5 block in run-e2e.sh.
func EnsureDefaultSGOpen(t *testing.T, c *AWSClient) {
t.Helper()
_, sgID, _ := DiscoverDefaultVPC(t, c)
AuthorizeSSHIngress(t, c, sgID)
AuthorizeICMPIngress(t, c, sgID)
}
// asErr is a thin errors.As that lets the helper stay testify-free.
func asErr(err error, target *awserr.Error) bool {
if a, ok := err.(awserr.Error); ok {
+25 -12
View File
@@ -255,11 +255,12 @@ func AssertProgressed(ctx context.Context, t *testing.T, v *WitnessVM) {
// --- internals ------------------------------------------------------------
// ensureDefaultSGSSHIngress authorises tcp/22 from 0.0.0.0/0 on the default
// security group via the structured IpPermissions form. The daemon currently
// drops the top-level shortcut form silently (mulga-siv-79), so without this
// the witness EIP has no inbound :22 and awaitBaseline times out. Idempotent:
// duplicate-rule errors from a prior run are tolerated.
// ensureDefaultSGSSHIngress authorises tcp/22 + ICMP from 0.0.0.0/0 on the
// default security group via the structured IpPermissions form. The daemon
// currently drops the top-level shortcut form silently (mulga-siv-79), so
// without this the witness EIP has no inbound :22 and awaitBaseline times
// out. ICMP is required for downstream gateway-ping probes (mulga-siv-134).
// Idempotent: duplicate-rule errors from a prior run are tolerated.
func (w *Witness) ensureDefaultSGSSHIngress(ctx context.Context) error {
sgs, err := w.ec2.DescribeSecurityGroupsWithContext(ctx, &ec2.DescribeSecurityGroupsInput{
Filters: []*ec2.Filter{{
@@ -275,17 +276,29 @@ func (w *Witness) ensureDefaultSGSSHIngress(ctx context.Context) error {
}
groupID := aws.StringValue(sgs.SecurityGroups[0].GroupId)
_, err = w.ec2.AuthorizeSecurityGroupIngressWithContext(ctx, &ec2.AuthorizeSecurityGroupIngressInput{
GroupId: aws.String(groupID),
IpPermissions: []*ec2.IpPermission{{
rules := []*ec2.IpPermission{
{
IpProtocol: aws.String("tcp"),
FromPort: aws.Int64(22),
ToPort: aws.Int64(22),
IpRanges: []*ec2.IpRange{{CidrIp: aws.String("0.0.0.0/0")}},
}},
})
if err != nil && !strings.Contains(err.Error(), "InvalidPermission.Duplicate") {
return fmt.Errorf("AuthorizeSecurityGroupIngress :22 on %s: %w", groupID, err)
},
{
IpProtocol: aws.String("icmp"),
FromPort: aws.Int64(-1),
ToPort: aws.Int64(-1),
IpRanges: []*ec2.IpRange{{CidrIp: aws.String("0.0.0.0/0")}},
},
}
for _, rule := range rules {
_, err = w.ec2.AuthorizeSecurityGroupIngressWithContext(ctx, &ec2.AuthorizeSecurityGroupIngressInput{
GroupId: aws.String(groupID),
IpPermissions: []*ec2.IpPermission{rule},
})
if err != nil && !strings.Contains(err.Error(), "InvalidPermission.Duplicate") {
return fmt.Errorf("AuthorizeSecurityGroupIngress %s on %s: %w",
aws.StringValue(rule.IpProtocol), groupID, err)
}
}
return nil
}
+15
View File
@@ -285,6 +285,21 @@ if [ $AUTH_EXIT -ne 0 ] && ! echo "$AUTH_OUTPUT" | grep -q 'InvalidPermission.Du
fi
echo " Default SG ingress: tcp/22 from 0.0.0.0/0"
# Phase 5f exercises ICMP egress through OVN; the reply path needs explicit
# ICMP ingress on the default SG even though SGs are stateful in AWS — keeps
# parity with the Go single-suite fixture (mulga-siv-134).
set +e
ICMP_OUTPUT=$(aws ec2 authorize-security-group-ingress \
--group-id "$DEFAULT_SG_PHASE5" \
--protocol icmp --port -1 --cidr 0.0.0.0/0 2>&1)
ICMP_EXIT=$?
set -e
if [ $ICMP_EXIT -ne 0 ] && ! echo "$ICMP_OUTPUT" | grep -q 'InvalidPermission.Duplicate'; then
echo "Failed to authorize ICMP ingress on default SG: $ICMP_OUTPUT"
exit 1
fi
echo " Default SG ingress: icmp from 0.0.0.0/0"
# Launch a VM (run-instances)
echo "Running: aws ec2 run-instances --image-id $AMI_ID --instance-type $INSTANCE_TYPE --key-name test-key-1"
# Capture full output for debugging
+5
View File
@@ -79,6 +79,11 @@ func requireSingleNodeFixture(t *testing.T) *Fixture {
TmpDir: tmpDir,
}
fix.PoolMode = detectPoolMode(env)
// admin init leaves the default SG closed (AWS parity); the e2e suite
// drives SSH + ICMP probes from the test runner's external IP, so open
// both on the default VPC's default SG once per process. Idempotent —
// mirrors run-e2e.sh Phase 5 (mulga-siv-134).
harness.EnsureDefaultSGOpen(t, awsCli)
pkgFix = fix
})
if pkgFixErr != nil {