Skip to content

Commit

Permalink
feat(ec2): apply launch template to instance (#68)
Browse files Browse the repository at this point in the history
* feat(ec2): apply launch template to instance

* chore: update scheme

* find launch template by id

* chore(deps): bump defsec
  • Loading branch information
nikpivkin authored Jan 19, 2024
1 parent 19ad86b commit 1ebc3f6
Show file tree
Hide file tree
Showing 9 changed files with 347 additions and 75 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/BurntSushi/toml v1.3.2
github.com/Masterminds/semver v1.5.0
github.com/apparentlymart/go-cidr v1.1.0
github.com/aquasecurity/defsec v0.93.2-0.20240104002958-968b8f115bc0
github.com/aquasecurity/defsec v0.93.2-0.20240110230225-29e47649c35d
github.com/aquasecurity/trivy-policies v0.8.0
github.com/aws/smithy-go v1.19.0
github.com/bmatcuk/doublestar/v4 v4.6.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/aquasecurity/defsec v0.93.2-0.20240104002958-968b8f115bc0 h1:K4XwF6joBVlGFtu78TzbhmsRNVojYTWANJWDeqXs50Y=
github.com/aquasecurity/defsec v0.93.2-0.20240104002958-968b8f115bc0/go.mod h1:NBF6hvbQSc4s/WCHdKV5sNNxLl258M2OiIFoUfgEn/k=
github.com/aquasecurity/defsec v0.93.2-0.20240110230225-29e47649c35d h1:veoNgFVDMDCFat+yGX5g+QX4cins/NmoYztdSo14weQ=
github.com/aquasecurity/defsec v0.93.2-0.20240110230225-29e47649c35d/go.mod h1:NBF6hvbQSc4s/WCHdKV5sNNxLl258M2OiIFoUfgEn/k=
github.com/aquasecurity/trivy-policies v0.8.0 h1:LvmIdw/DfTF72Lc8L+CKLYzfb5BFYzLBGFFR95PKC74=
github.com/aquasecurity/trivy-policies v0.8.0/go.mod h1:qF/t59pgK/0JTV6tXaeA3Iw3opzoMgzGCDcTDBmqb30=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
Expand Down
126 changes: 118 additions & 8 deletions internal/adapters/cloudformation/aws/ec2/adapt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestAdapt(t *testing.T) {
expected ec2.EC2
}{
{
name: "EC2 instance",
name: "ec2 instance",
source: `AWSTemplateFormatVersion: 2010-09-09
Resources:
MyEC2Instance:
Expand All @@ -34,7 +34,10 @@ Resources:
Iops: "200"
DeleteOnTermination: "false"
VolumeSize: "20"
Encrypted: "true"`,
Encrypted: true
- DeviceName: "/dev/sdk"
NoDevice: {}
`,
expected: ec2.EC2{
Instances: []ec2.Instance{
{
Expand All @@ -45,22 +48,129 @@ Resources:
},
RootBlockDevice: &ec2.BlockDevice{
Metadata: types.NewTestMetadata(),
Encrypted: types.Bool(true, types.NewTestMetadata()),
Encrypted: types.BoolDefault(true, types.NewTestMetadata()),
},
EBSBlockDevices: []*ec2.BlockDevice{
{
Metadata: types.NewTestMetadata(),
Encrypted: types.BoolDefault(false, types.NewTestMetadata()),
},
},
},
},
},
},
{
name: "ec2 instance with launch template, ref to name",
source: `AWSTemplateFormatVersion: 2010-09-09
Resources:
MyLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: MyTemplate
LaunchTemplateData:
MetadataOptions:
HttpEndpoint: enabled
HttpTokens: required
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: "ami-79fd7eee"
LaunchTemplate:
LaunchTemplateName: MyTemplate
`,
expected: ec2.EC2{
LaunchTemplates: []ec2.LaunchTemplate{
{
Metadata: types.NewTestMetadata(),
Name: types.String("MyTemplate", types.NewTestMetadata()),
Instance: ec2.Instance{
Metadata: types.NewTestMetadata(),
MetadataOptions: ec2.MetadataOptions{
HttpEndpoint: types.String("enabled", types.NewTestMetadata()),
HttpTokens: types.String("required", types.NewTestMetadata()),
},
},
},
},
Instances: []ec2.Instance{
{
Metadata: types.NewTestMetadata(),
MetadataOptions: ec2.MetadataOptions{
HttpEndpoint: types.String("enabled", types.NewTestMetadata()),
HttpTokens: types.String("required", types.NewTestMetadata()),
},
RootBlockDevice: &ec2.BlockDevice{
Metadata: types.NewTestMetadata(),
Encrypted: types.Bool(false, types.NewTestMetadata()),
},
},
},
},
},
{
name: "ec2 instance with launch template, ref to id",
source: `AWSTemplateFormatVersion: 2010-09-09
Resources:
MyLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: MyTemplate
LaunchTemplateData:
MetadataOptions:
HttpEndpoint: enabled
HttpTokens: required
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
ImageId: "ami-79fd7eee"
LaunchTemplate:
LaunchTemplateId: !Ref MyLaunchTemplate
`,
expected: ec2.EC2{
LaunchTemplates: []ec2.LaunchTemplate{
{
Metadata: types.NewTestMetadata(),
Name: types.String("MyTemplate", types.NewTestMetadata()),
Instance: ec2.Instance{
Metadata: types.NewTestMetadata(),
MetadataOptions: ec2.MetadataOptions{
HttpEndpoint: types.String("enabled", types.NewTestMetadata()),
HttpTokens: types.String("required", types.NewTestMetadata()),
},
},
},
},
Instances: []ec2.Instance{
{
Metadata: types.NewTestMetadata(),
MetadataOptions: ec2.MetadataOptions{
HttpEndpoint: types.String("enabled", types.NewTestMetadata()),
HttpTokens: types.String("required", types.NewTestMetadata()),
},
RootBlockDevice: &ec2.BlockDevice{
Metadata: types.NewTestMetadata(),
Encrypted: types.Bool(false, types.NewTestMetadata()),
},
},
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"template.yaml": tt.source,

fsys := testutil.CreateFS(t, map[string]string{
"main.yaml": tt.source,
})
p := parser.New()
fctx, err := p.ParseFile(context.TODO(), fs, "template.yaml")

fctx, err := parser.New().ParseFile(context.TODO(), fsys, "main.yaml")
require.NoError(t, err)
testutil.AssertDefsecEqual(t, tt.expected, Adapt(*fctx))

adapted := Adapt(*fctx)
testutil.AssertDefsecEqual(t, tt.expected, adapted)
})
}

}
54 changes: 49 additions & 5 deletions internal/adapters/cloudformation/aws/ec2/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
)

func getInstances(ctx parser.FileContext) (instances []ec2.Instance) {

instanceResources := ctx.GetResourcesByType("AWS::EC2::Instance")

for _, r := range instanceResources {
Expand All @@ -20,11 +19,20 @@ func getInstances(ctx parser.FileContext) (instances []ec2.Instance) {
HttpTokens: defsecTypes.StringDefault("optional", r.Metadata()),
HttpEndpoint: defsecTypes.StringDefault("enabled", r.Metadata()),
},
UserData: r.GetStringProperty("UserData"),
SecurityGroups: nil,
RootBlockDevice: nil,
EBSBlockDevices: nil,
UserData: r.GetStringProperty("UserData"),
}

if launchTemplate, ok := findRelatedLaunchTemplate(ctx, r); ok {
instance = launchTemplate.Instance
}

if instance.RootBlockDevice == nil {
instance.RootBlockDevice = &ec2.BlockDevice{
Metadata: r.Metadata(),
Encrypted: defsecTypes.BoolDefault(false, r.Metadata()),
}
}

blockDevices := getBlockDevices(r)
for i, device := range blockDevices {
copyDevice := device
Expand All @@ -40,6 +48,42 @@ func getInstances(ctx parser.FileContext) (instances []ec2.Instance) {
return instances
}

func findRelatedLaunchTemplate(fctx parser.FileContext, r *parser.Resource) (ec2.LaunchTemplate, bool) {
launchTemplateRef := r.GetProperty("LaunchTemplate.LaunchTemplateName")
if launchTemplateRef.IsString() {
res := findLaunchTemplateByName(fctx, launchTemplateRef)
if res != nil {
return adaptLaunchTemplate(res), true
}
}

launchTemplateRef = r.GetProperty("LaunchTemplate.LaunchTemplateId")
if !launchTemplateRef.IsString() {
return ec2.LaunchTemplate{}, false
}

resource := fctx.GetResourceByLogicalID(launchTemplateRef.AsString())
if resource == nil {
return ec2.LaunchTemplate{}, false
}
return adaptLaunchTemplate(resource), true
}

func findLaunchTemplateByName(fctx parser.FileContext, prop *parser.Property) *parser.Resource {
for _, res := range fctx.GetResourcesByType("AWS::EC2::LaunchTemplate") {
templateName := res.GetProperty("LaunchTemplateName")
if templateName.IsNotString() {
continue
}

if prop.EqualTo(templateName.AsString()) {
return res
}
}

return nil
}

func getBlockDevices(r *parser.Resource) []*ec2.BlockDevice {
var blockDevices []*ec2.BlockDevice

Expand Down
64 changes: 32 additions & 32 deletions internal/adapters/cloudformation/aws/ec2/launch_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,47 @@ func getLaunchTemplates(file parser.FileContext) (templates []ec2.LaunchTemplate
launchConfigResources := file.GetResourcesByType("AWS::EC2::LaunchTemplate")

for _, r := range launchConfigResources {
templates = append(templates, adaptLaunchTemplate(r))
}
return templates
}

launchTemplate := ec2.LaunchTemplate{
func adaptLaunchTemplate(r *parser.Resource) ec2.LaunchTemplate {
launchTemplate := ec2.LaunchTemplate{
Metadata: r.Metadata(),
Name: r.GetStringProperty("LaunchTemplateName", ""),
Instance: ec2.Instance{
Metadata: r.Metadata(),
Instance: ec2.Instance{
Metadata: r.Metadata(),
MetadataOptions: ec2.MetadataOptions{
Metadata: r.Metadata(),
HttpTokens: types.StringDefault("optional", r.Metadata()),
HttpEndpoint: types.StringDefault("enabled", r.Metadata()),
},
UserData: types.StringDefault("", r.Metadata()),
SecurityGroups: nil,
RootBlockDevice: nil,
EBSBlockDevices: nil,
MetadataOptions: ec2.MetadataOptions{
Metadata: r.Metadata(),
HttpTokens: types.StringDefault("optional", r.Metadata()),
HttpEndpoint: types.StringDefault("enabled", r.Metadata()),
},
}
UserData: types.StringDefault("", r.Metadata()),
},
}

if data := r.GetProperty("LaunchTemplateData"); data.IsNotNil() {
if opts := data.GetProperty("MetadataOptions"); opts.IsNotNil() {
launchTemplate.MetadataOptions = ec2.MetadataOptions{
Metadata: opts.Metadata(),
HttpTokens: opts.GetStringProperty("HttpTokens", "optional"),
HttpEndpoint: opts.GetStringProperty("HttpEndpoint", "enabled"),
}
if data := r.GetProperty("LaunchTemplateData"); data.IsNotNil() {
if opts := data.GetProperty("MetadataOptions"); opts.IsNotNil() {
launchTemplate.MetadataOptions = ec2.MetadataOptions{
Metadata: opts.Metadata(),
HttpTokens: opts.GetStringProperty("HttpTokens", "optional"),
HttpEndpoint: opts.GetStringProperty("HttpEndpoint", "enabled"),
}
}

launchTemplate.Instance.UserData = data.GetStringProperty("UserData", "")
launchTemplate.Instance.UserData = data.GetStringProperty("UserData", "")

blockDevices := getBlockDevices(r)
for i, device := range blockDevices {
copyDevice := device
if i == 0 {
launchTemplate.RootBlockDevice = copyDevice
continue
}
blockDevices := getBlockDevices(r)
for i, device := range blockDevices {
copyDevice := device
if i == 0 {
launchTemplate.RootBlockDevice = copyDevice
} else {
launchTemplate.EBSBlockDevices = append(launchTemplate.EBSBlockDevices, device)
}
}

templates = append(templates, launchTemplate)

}
return templates

return launchTemplate
}
Loading

0 comments on commit 1ebc3f6

Please sign in to comment.