Skip to content

Commit 8491469

Browse files
authored
feat(misconf): API Gateway V1 support for CloudFormation (#6874)
1 parent bb88937 commit 8491469

File tree

4 files changed

+229
-9
lines changed

4 files changed

+229
-9
lines changed

pkg/iac/adapters/cloudformation/aws/apigateway/apigateway.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import (
1111
func Adapt(cfFile parser.FileContext) apigateway.APIGateway {
1212
return apigateway.APIGateway{
1313
V1: v1.APIGateway{
14-
APIs: nil,
15-
DomainNames: nil,
14+
APIs: adaptAPIsV1(cfFile),
15+
DomainNames: adaptDomainNamesV1(cfFile),
1616
},
1717
V2: v2.APIGateway{
18-
APIs: getApis(cfFile),
18+
APIs: adaptAPIsV2(cfFile),
19+
DomainNames: adaptDomainNamesV2(cfFile),
1920
},
2021
}
2122
}

pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil"
77
"github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway"
8+
v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1"
89
v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2"
910
"github.com/aquasecurity/trivy/pkg/iac/types"
1011
)
@@ -19,24 +20,105 @@ func TestAdapt(t *testing.T) {
1920
name: "complete",
2021
source: `AWSTemplateFormatVersion: 2010-09-09
2122
Resources:
22-
MyApi:
23+
MyRestApi:
24+
Type: 'AWS::ApiGateway::RestApi'
25+
Properties:
26+
Description: A test API
27+
Name: MyRestAPI
28+
ApiResource:
29+
Type: AWS::ApiGateway::Resource
30+
Properties:
31+
RestApiId: !Ref MyRestApi
32+
MethodPOST:
33+
Type: AWS::ApiGateway::Method
34+
Properties:
35+
RestApiId: !Ref MyRestApi
36+
ResourceId: !Ref ApiResource
37+
HttpMethod: POST
38+
AuthorizationType: COGNITO_USER_POOLS
39+
ApiKeyRequired: true
40+
Stage:
41+
Type: AWS::ApiGateway::Stage
42+
Properties:
43+
StageName: Prod
44+
RestApiId: !Ref MyRestApi
45+
TracingEnabled: true
46+
AccessLogSetting:
47+
DestinationArn: test-arn
48+
MethodSettings:
49+
- CacheDataEncrypted: true
50+
CachingEnabled: true
51+
HttpMethod: POST
52+
MyDomainName:
53+
Type: AWS::ApiGateway::DomainName
54+
Properties:
55+
DomainName: mydomainame.us-east-1.com
56+
SecurityPolicy: "TLS_1_2"
57+
58+
MyApi2:
2359
Type: 'AWS::ApiGatewayV2::Api'
2460
Properties:
25-
Name: MyApi
61+
Name: MyApi2
2662
ProtocolType: WEBSOCKET
27-
MyStage:
63+
MyStage2:
2864
Type: 'AWS::ApiGatewayV2::Stage'
2965
Properties:
3066
StageName: Prod
31-
ApiId: !Ref MyApi
67+
ApiId: !Ref MyApi2
3268
AccessLogSettings:
3369
DestinationArn: some-arn
70+
MyDomainName2:
71+
Type: 'AWS::ApiGatewayV2::DomainName'
72+
Properties:
73+
DomainName: mydomainame.us-east-1.com
74+
DomainNameConfigurations:
75+
- SecurityPolicy: "TLS_1_2"
3476
`,
3577
expected: apigateway.APIGateway{
78+
V1: v1.APIGateway{
79+
APIs: []v1.API{
80+
{
81+
Name: types.StringTest("MyRestAPI"),
82+
Stages: []v1.Stage{
83+
{
84+
Name: types.StringTest("Prod"),
85+
XRayTracingEnabled: types.BoolTest(true),
86+
AccessLogging: v1.AccessLogging{
87+
CloudwatchLogGroupARN: types.StringTest("test-arn"),
88+
},
89+
RESTMethodSettings: []v1.RESTMethodSettings{
90+
{
91+
Method: types.StringTest("POST"),
92+
CacheDataEncrypted: types.BoolTest(true),
93+
CacheEnabled: types.BoolTest(true),
94+
},
95+
},
96+
},
97+
},
98+
Resources: []v1.Resource{
99+
{
100+
Methods: []v1.Method{
101+
{
102+
HTTPMethod: types.StringTest("POST"),
103+
AuthorizationType: types.StringTest("COGNITO_USER_POOLS"),
104+
APIKeyRequired: types.BoolTest(true),
105+
},
106+
},
107+
},
108+
},
109+
},
110+
},
111+
DomainNames: []v1.DomainName{
112+
{
113+
Name: types.StringTest("mydomainame.us-east-1.com"),
114+
SecurityPolicy: types.StringTest("TLS_1_2"),
115+
},
116+
},
117+
},
36118
V2: v2.APIGateway{
37119
APIs: []v2.API{
38120
{
39-
Name: types.StringTest("MyApi"),
121+
Name: types.StringTest("MyApi2"),
40122
ProtocolType: types.StringTest("WEBSOCKET"),
41123
Stages: []v2.Stage{
42124
{
@@ -48,6 +130,12 @@ Resources:
48130
},
49131
},
50132
},
133+
DomainNames: []v2.DomainName{
134+
{
135+
Name: types.StringTest("mydomainame.us-east-1.com"),
136+
SecurityPolicy: types.StringTest("TLS_1_2"),
137+
},
138+
},
51139
},
52140
},
53141
},
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package apigateway
2+
3+
import (
4+
v1 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v1"
5+
"github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser"
6+
)
7+
8+
func adaptAPIsV1(fctx parser.FileContext) []v1.API {
9+
var apis []v1.API
10+
11+
stages := make(map[string]*parser.Resource)
12+
for _, stageResource := range fctx.GetResourcesByType("AWS::ApiGateway::Stage") {
13+
restApiID := stageResource.GetStringProperty("RestApiId")
14+
if restApiID.IsEmpty() {
15+
continue
16+
}
17+
18+
stages[restApiID.Value()] = stageResource
19+
}
20+
21+
resources := make(map[string]*parser.Resource)
22+
for _, resource := range fctx.GetResourcesByType("AWS::ApiGateway::Resource") {
23+
restApiID := resource.GetStringProperty("RestApiId")
24+
if restApiID.IsEmpty() {
25+
continue
26+
}
27+
28+
resources[restApiID.Value()] = resource
29+
}
30+
31+
for _, apiResource := range fctx.GetResourcesByType("AWS::ApiGateway::RestApi") {
32+
33+
api := v1.API{
34+
Metadata: apiResource.Metadata(),
35+
Name: apiResource.GetStringProperty("Name"),
36+
}
37+
38+
if stageResource, exists := stages[apiResource.ID()]; exists {
39+
stage := v1.Stage{
40+
Metadata: stageResource.Metadata(),
41+
Name: stageResource.GetStringProperty("StageName"),
42+
XRayTracingEnabled: stageResource.GetBoolProperty("TracingEnabled"),
43+
}
44+
45+
if logSetting := stageResource.GetProperty("AccessLogSetting"); logSetting.IsNotNil() {
46+
stage.AccessLogging = v1.AccessLogging{
47+
Metadata: logSetting.Metadata(),
48+
CloudwatchLogGroupARN: logSetting.GetStringProperty("DestinationArn"),
49+
}
50+
}
51+
52+
if methodSettings := stageResource.GetProperty("MethodSettings"); methodSettings.IsList() {
53+
for _, methodSetting := range methodSettings.AsList() {
54+
stage.RESTMethodSettings = append(stage.RESTMethodSettings, v1.RESTMethodSettings{
55+
Metadata: methodSetting.Metadata(),
56+
Method: methodSetting.GetStringProperty("HttpMethod"),
57+
CacheDataEncrypted: methodSetting.GetBoolProperty("CacheDataEncrypted"),
58+
CacheEnabled: methodSetting.GetBoolProperty("CachingEnabled"),
59+
})
60+
}
61+
}
62+
63+
api.Stages = append(api.Stages, stage)
64+
}
65+
66+
if resource, exists := resources[apiResource.ID()]; exists {
67+
res := v1.Resource{
68+
Metadata: resource.Metadata(),
69+
}
70+
71+
for _, methodResource := range fctx.GetResourcesByType("AWS::ApiGateway::Method") {
72+
resourceID := methodResource.GetStringProperty("ResourceId")
73+
// TODO: handle RootResourceId
74+
if resourceID.Value() != resource.ID() {
75+
continue
76+
}
77+
78+
res.Methods = append(res.Methods, v1.Method{
79+
Metadata: methodResource.Metadata(),
80+
HTTPMethod: methodResource.GetStringProperty("HttpMethod"),
81+
AuthorizationType: methodResource.GetStringProperty("AuthorizationType"),
82+
APIKeyRequired: methodResource.GetBoolProperty("ApiKeyRequired"),
83+
})
84+
85+
}
86+
87+
api.Resources = append(api.Resources, res)
88+
}
89+
90+
apis = append(apis, api)
91+
}
92+
93+
return apis
94+
}
95+
96+
func adaptDomainNamesV1(fctx parser.FileContext) []v1.DomainName {
97+
var domainNames []v1.DomainName
98+
99+
for _, domainNameResource := range fctx.GetResourcesByType("AWS::ApiGateway::DomainName") {
100+
domainNames = append(domainNames, v1.DomainName{
101+
Metadata: domainNameResource.Metadata(),
102+
Name: domainNameResource.GetStringProperty("DomainName"),
103+
SecurityPolicy: domainNameResource.GetStringProperty("SecurityPolicy"),
104+
})
105+
}
106+
107+
return domainNames
108+
}

pkg/iac/adapters/cloudformation/aws/apigateway/stage.go renamed to pkg/iac/adapters/cloudformation/aws/apigateway/apiv2.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"github.com/aquasecurity/trivy/pkg/iac/types"
77
)
88

9-
func getApis(cfFile parser.FileContext) (apis []v2.API) {
9+
func adaptAPIsV2(cfFile parser.FileContext) (apis []v2.API) {
1010

1111
apiResources := cfFile.GetResourcesByType("AWS::ApiGatewayV2::Api")
1212
for _, apiRes := range apiResources {
@@ -66,3 +66,26 @@ func getAccessLogging(r *parser.Resource) v2.AccessLogging {
6666
CloudwatchLogGroupARN: destinationProp.AsStringValue(),
6767
}
6868
}
69+
70+
func adaptDomainNamesV2(fctx parser.FileContext) []v2.DomainName {
71+
var domainNames []v2.DomainName
72+
73+
for _, domainNameResource := range fctx.GetResourcesByType("AWS::ApiGateway::DomainName") {
74+
75+
domainName := v2.DomainName{
76+
Metadata: domainNameResource.Metadata(),
77+
Name: domainNameResource.GetStringProperty("DomainName"),
78+
SecurityPolicy: domainNameResource.GetStringProperty("SecurityPolicy"),
79+
}
80+
81+
if domainNameCfgs := domainNameResource.GetProperty("DomainNameConfigurations"); domainNameCfgs.IsList() {
82+
for _, domainNameCfg := range domainNameCfgs.AsList() {
83+
domainName.SecurityPolicy = domainNameCfg.GetStringProperty("SecurityPolicy")
84+
}
85+
}
86+
87+
domainNames = append(domainNames, domainName)
88+
}
89+
90+
return domainNames
91+
}

0 commit comments

Comments
 (0)