-
Notifications
You must be signed in to change notification settings - Fork 48
/
Copy pathresource_references.go
194 lines (170 loc) · 6.01 KB
/
resource_references.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package sparta
import (
"encoding/base64"
"fmt"
"reflect"
"strings"
gof "github.com/awslabs/goformation/v5/cloudformation"
gofintrinsics "github.com/awslabs/goformation/v5/intrinsics"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
type resourceRefType int
const (
resourceLiteral resourceRefType = iota
resourceRefFunc
resourceGetAttrFunc
)
type resourceRef struct {
RefType resourceRefType
ResourceName string
DisplayName string
}
// resolvedResourceVisitor represents the signature of a function that
// visits
type resolvedResourceVisitor func(lambdaAWSInfo *LambdaAWSInfo,
eventSourceMapping *EventSourceMapping,
mappingIndex int,
resource *resourceRef) error
// resolveResourceRef takes an interface representing an ARN (either static or explicit)
// and tries to determine the CloudFormation resource name it resolves to
func resolveResourceRef(expr string) (*resourceRef, error) {
// Ref: https://github.com/awslabs/goformation/blob/346053f16b2c9aba3a050c6a2956e18fe3a3f56f/intrinsics/intrinsics.go#L76
// Decode the expression, hand it off to goformation to unmarshall
// with the nested Base64 strings inside.
// then turn it into a Map, look at the key and determine what kind of reference it is.
// Is there a chance it's a Base64 encoded string? That indicates
// it's a goformation reference.
base64Decoded, base64DecodedErr := base64.StdEncoding.DecodeString(expr)
if base64DecodedErr != nil {
// It's possible it's a plain old literal...test it
// Ref: https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
if strings.HasPrefix(expr, "arn:aws") {
return &resourceRef{
RefType: resourceLiteral,
ResourceName: expr,
}, nil
}
}
// Setup the intrinsic handlers so that when we unmarshal
// the encoded functions we keep track of what we found
var hookedResourceRef *resourceRef
hookedOverrides := map[string]gofintrinsics.IntrinsicHandler{
"Ref": func(name string, input interface{}, template interface{}) interface{} {
hookedResourceRef = &resourceRef{
RefType: resourceRefFunc,
ResourceName: input.(string),
DisplayName: fmt.Sprintf("Ref: %s", input.(string)),
}
return nil
},
"Fn::GetAtt": func(name string, input interface{}, template interface{}) interface{} {
// The input should be an array...
inputArr, inputArrOk := input.([]interface{})
if !inputArrOk {
return nil
}
inputStringElemZero, inputStringElemZeroOk := inputArr[0].(string)
if !inputStringElemZeroOk {
return nil
}
hookedResourceRef = &resourceRef{
RefType: resourceGetAttrFunc,
ResourceName: inputStringElemZero,
DisplayName: fmt.Sprintf("GetAtt: %+v", inputArr),
}
return nil
},
}
procOptions := &gofintrinsics.ProcessorOptions{
IntrinsicHandlerOverrides: hookedOverrides,
}
_, processedJSONErr := gofintrinsics.ProcessJSON(base64Decoded, procOptions)
if processedJSONErr != nil {
return nil, processedJSONErr
}
// Whatever we have at this point is what it is...
return hookedResourceRef, nil
}
// isResolvedResourceType is a utility function to determine if a resolved
// reference is a given type. If it is a literal, the literalTokenIndicator
// substring match is used for the predicate. If it is a resource provisioned
// by this template, the &gocf.RESOURCE_TYPE{} will be used via reflection
// Example:
// isResolvedResourceType(resourceRef, template, ":dynamodb:", &gocf.DynamoDBTable{}) {
//
func isResolvedResourceType(resource *resourceRef,
template *gof.Template,
literalTokenIndicator string,
templateType interface{}) bool {
if resource.RefType == resourceLiteral {
return strings.Contains(resource.ResourceName, literalTokenIndicator)
}
// Dynamically provisioned resource included in the template definition?
existingResource, existingResourceExists := template.Resources[resource.ResourceName]
if existingResourceExists {
if reflect.TypeOf(existingResource) == reflect.TypeOf(templateType) {
return true
}
}
return false
}
// visitResolvedEventSourceMapping is a utility function that visits all
// the EventSourceMapping entries for the given lambdaAWSInfo struct
func visitResolvedEventSourceMapping(visitor resolvedResourceVisitor,
lambdaAWSInfos []*LambdaAWSInfo,
template *gof.Template,
logger *zerolog.Logger) error {
//
// BEGIN
// Inline closure to wrap the visitor function so that we can provide
// specific error messages
visitEventSourceMappingRef := func(lambdaAWSInfo *LambdaAWSInfo,
eventSourceMapping *EventSourceMapping,
mappingIndex int,
resource *resourceRef) error {
annotateStatementsErr := visitor(lambdaAWSInfo,
eventSourceMapping,
mappingIndex,
resource)
// Early exit?
if annotateStatementsErr != nil {
return errors.Wrapf(annotateStatementsErr,
"Visiting event source mapping: %#v",
eventSourceMapping)
}
return nil
}
//
// END
// Iterate through every lambda function. If there is an EventSourceMapping
// that points to a piece of infastructure provisioned by this stack,
// find the referred resource and supply it to the visitor
for _, eachLambda := range lambdaAWSInfos {
for eachIndex, eachEventSource := range eachLambda.EventSourceMappings {
resourceRef, resourceRefErr := resolveResourceRef(eachEventSource.EventSourceArn)
if resourceRefErr != nil {
return errors.Wrapf(resourceRefErr,
"Failed to resolve EventSourceArn: %#v", eachEventSource)
}
// At this point everything is a string, so we need to unmarshall
// and see if the Arn is supplied by either a Ref or a GetAttr
// function. In those cases, we need to look around in the template
// to go from: EventMapping -> Type -> Lambda -> LambdaIAMRole
// so that we can add the permissions
if resourceRef != nil {
annotationErr := visitEventSourceMappingRef(eachLambda,
eachEventSource,
eachIndex,
resourceRef)
// Anything go wrong?
if annotationErr != nil {
return errors.Wrapf(annotationErr,
"Failed to annotate template for EventSourceMapping: %#v",
eachEventSource)
}
}
}
}
return nil
}