Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions deploy/aws/cloudformation/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ Metadata:
- SubnetId
- AllowedApiCidr
- ApiPort
- EnableHttpIngress
- EnableHttpsIngress
- AllowedIngressCidr
- Label:
default: Instance
Parameters:
Expand Down Expand Up @@ -43,6 +46,12 @@ Metadata:
default: Hypeman API access CIDR
ApiPort:
default: Hypeman API port
EnableHttpIngress:
default: Enable HTTP ingress
EnableHttpsIngress:
default: Enable HTTPS ingress
AllowedIngressCidr:
default: Hypeman ingress access CIDR
EnableSSH:
default: Enable SSH
AllowedSshCidr:
Expand Down Expand Up @@ -89,6 +98,21 @@ Parameters:
MinValue: 1
MaxValue: 65535
Description: Hypeman API port exposed to AllowedApiCidr.
EnableHttpIngress:
Type: String
Default: "false"
AllowedValues: ["true", "false"]
Description: Open port 80 from AllowedIngressCidr for Hypeman HTTP ingress traffic.
EnableHttpsIngress:
Type: String
Default: "false"
AllowedValues: ["true", "false"]
Description: Open port 443 from AllowedIngressCidr for Hypeman HTTPS ingress traffic.
AllowedIngressCidr:
Type: String
Default: 127.0.0.1/32
Description: Client CIDR allowed to reach Hypeman ingress ports when enabled. Use your current public IP /32 or a trusted VPN CIDR; avoid 0.0.0.0/0.
AllowedPattern: "^([0-9]{1,3}\\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$"
EnableSSH:
Type: String
Default: "false"
Expand Down Expand Up @@ -144,6 +168,8 @@ Parameters:

Conditions:
UseSSH: !Equals [!Ref EnableSSH, "true"]
UseHttpIngress: !Equals [!Ref EnableHttpIngress, "true"]
UseHttpsIngress: !Equals [!Ref EnableHttpsIngress, "true"]

Resources:
HypemanSecurityGroup:
Expand All @@ -157,6 +183,22 @@ Resources:
ToPort: !Ref ApiPort
CidrIp: !Ref AllowedApiCidr
Description: Hypeman API
- !If
- UseHttpIngress
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: !Ref AllowedIngressCidr
Description: Hypeman HTTP ingress
- !Ref AWS::NoValue
- !If
- UseHttpsIngress
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref AllowedIngressCidr
Description: Hypeman HTTPS ingress
- !Ref AWS::NoValue
- !If
- UseSSH
- IpProtocol: tcp
Expand Down Expand Up @@ -202,9 +244,10 @@ Resources:
Roles:
- !Ref HypemanInstanceRole

# CloudFormation's typed EC2 resources do not expose CpuOptions.NestedVirtualization yet.
# This helper creates only the launch template that carries that EC2 API option;
# the Hypeman EC2 instance itself remains a normal stack-managed resource.
# CloudFormation's typed EC2 instance block device mapping does not expose gp3
# throughput, and EC2 resources do not expose CpuOptions.NestedVirtualization
# yet. This helper creates a minimal launch template for those fields; the
# Hypeman EC2 instance itself remains a normal stack-managed resource.
NestedVirtualizationLaunchTemplateRole:
Type: AWS::IAM::Role
Properties:
Expand Down
36 changes: 33 additions & 3 deletions deploy/aws/cloudformation/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ func TestQuickstartParameters(t *testing.T) {
assertDefault(t, parameters, "InstanceType", "c8i.2xlarge")
assertDefault(t, parameters, "AllowedApiCidr", "127.0.0.1/32")
assertDefault(t, parameters, "ApiPort", "8080")
assertDefault(t, parameters, "EnableHttpIngress", "false")
assertDefault(t, parameters, "EnableHttpsIngress", "false")
assertDefault(t, parameters, "AllowedIngressCidr", "127.0.0.1/32")
assertDefault(t, parameters, "EnableSSH", "false")
assertDefault(t, parameters, "AllowedSshCidr", "127.0.0.1/32")
assertDefault(t, parameters, "RootVolumeSize", "30")
Expand All @@ -33,6 +36,10 @@ func TestQuickstartParameters(t *testing.T) {
assertContains(t, scalar(t, apiCidr["Description"]), "current public IP /32")
assertContains(t, scalar(t, apiCidr["Description"]), "avoid 0.0.0.0/0")

ingressCidr := requireMapping(t, parameters["AllowedIngressCidr"])
assertContains(t, scalar(t, ingressCidr["Description"]), "current public IP /32")
assertContains(t, scalar(t, ingressCidr["Description"]), "avoid 0.0.0.0/0")

metadata := requireMapping(t, requireField(t, root, "Metadata"))
cfnInterface := requireMapping(t, requireField(t, metadata, "AWS::CloudFormation::Interface"))
groups := requireSequence(t, requireField(t, cfnInterface, "ParameterGroups"))
Expand All @@ -56,16 +63,19 @@ func TestCloudFormationLaunchContract(t *testing.T) {
securityGroup := requireMapping(t, requireField(t, resources, "HypemanSecurityGroup"))
sgProperties := requireMapping(t, requireField(t, securityGroup, "Properties"))
ingress := requireSequence(t, requireField(t, sgProperties, "SecurityGroupIngress"))
if len(ingress.Content) != 2 {
t.Fatalf("expected API ingress and conditional SSH ingress, got %d entries", len(ingress.Content))
if len(ingress.Content) != 4 {
t.Fatalf("expected API ingress, HTTP ingress, HTTPS ingress, and SSH ingress, got %d entries", len(ingress.Content))
}

apiIngress := requireMapping(t, ingress.Content[0])
assertRef(t, requireField(t, apiIngress, "FromPort"), "ApiPort")
assertRef(t, requireField(t, apiIngress, "ToPort"), "ApiPort")
assertRef(t, requireField(t, apiIngress, "CidrIp"), "AllowedApiCidr")

sshIngress := ingress.Content[1]
assertConditionalIngress(t, ingress.Content[1], "UseHttpIngress", "80", "AllowedIngressCidr")
assertConditionalIngress(t, ingress.Content[2], "UseHttpsIngress", "443", "AllowedIngressCidr")

sshIngress := ingress.Content[3]
if sshIngress.Tag != "!If" {
t.Fatalf("expected SSH ingress to be conditional !If, got %s", sshIngress.Tag)
}
Expand Down Expand Up @@ -147,6 +157,26 @@ func TestQuickstartOutputs(t *testing.T) {
assertContains(t, scalar(t, requireField(t, requireMapping(t, outputs["CreateTokenCommand"]), "Value")), "hypeman-create-token")
}

func assertConditionalIngress(t *testing.T, node *yaml.Node, condition, port, cidrRef string) {
t.Helper()

if node.Tag != "!If" {
t.Fatalf("expected ingress to be conditional !If, got %s", node.Tag)
}
parts := requireSequence(t, node)
if got := scalar(t, parts.Content[0]); got != condition {
t.Fatalf("expected condition %q, got %q", condition, got)
}
rule := requireMapping(t, parts.Content[1])
if got := scalar(t, requireField(t, rule, "FromPort")); got != port {
t.Fatalf("expected FromPort %s, got %q", port, got)
}
if got := scalar(t, requireField(t, rule, "ToPort")); got != port {
t.Fatalf("expected ToPort %s, got %q", port, got)
}
assertRef(t, requireField(t, rule, "CidrIp"), cidrRef)
}

func loadTemplate(t *testing.T) *yaml.Node {
t.Helper()

Expand Down
Loading