github.com/mweagle/Sparta@v1.15.0/resource_references.go (about) 1 // +build !lambdabinary 2 3 package sparta 4 5 import ( 6 "encoding/json" 7 "reflect" 8 "strings" 9 10 gocf "github.com/mweagle/go-cloudformation" 11 "github.com/pkg/errors" 12 "github.com/sirupsen/logrus" 13 ) 14 15 type resourceRefType int 16 17 const ( 18 resourceLiteral resourceRefType = iota 19 resourceRefFunc 20 resourceGetAttrFunc 21 resourceStringFunc 22 ) 23 24 type resourceRef struct { 25 RefType resourceRefType 26 ResourceName string 27 } 28 29 // resolvedResourceVisitor represents the signature of a function that 30 // visits 31 type resolvedResourceVisitor func(lambdaAWSInfo *LambdaAWSInfo, 32 eventSourceMapping *EventSourceMapping, 33 mappingIndex int, 34 resource *resourceRef) error 35 36 // resolveResourceRef takes an interface representing a dynamic ARN 37 // and tries to determine the CloudFormation resource name it resolves to 38 func resolveResourceRef(expr interface{}) (*resourceRef, error) { 39 40 // Is there any chance it's just a string? 41 typedString, typedStringOk := expr.(string) 42 if typedStringOk { 43 return &resourceRef{ 44 RefType: resourceLiteral, 45 ResourceName: typedString, 46 }, nil 47 } 48 // Some type of intrinsic function? 49 marshalled, marshalledErr := json.Marshal(expr) 50 if marshalledErr != nil { 51 return nil, errors.Errorf("Failed to unmarshal dynamic resource ref %v", expr) 52 } 53 var refFunc gocf.RefFunc 54 if json.Unmarshal(marshalled, &refFunc) == nil && 55 len(refFunc.Name) != 0 { 56 return &resourceRef{ 57 RefType: resourceRefFunc, 58 ResourceName: refFunc.Name, 59 }, nil 60 } 61 62 var getAttFunc gocf.GetAttFunc 63 if json.Unmarshal(marshalled, &getAttFunc) == nil && len(getAttFunc.Resource) != 0 { 64 return &resourceRef{ 65 RefType: resourceGetAttrFunc, 66 ResourceName: getAttFunc.Resource, 67 }, nil 68 } 69 // Any chance it's a string? 70 var stringExprFunc gocf.StringExpr 71 if json.Unmarshal(marshalled, &stringExprFunc) == nil && len(stringExprFunc.Literal) != 0 { 72 return &resourceRef{ 73 RefType: resourceStringFunc, 74 ResourceName: stringExprFunc.Literal, 75 }, nil 76 } 77 78 // Nope 79 return nil, nil 80 } 81 82 // isResolvedResourceType is a utility function to determine if a resolved 83 // reference is a given type. If it is a literal, the literalTokenIndicator 84 // substring match is used for the predicate. If it is a resource provisioned 85 // by this template, the &gocf.RESOURCE_TYPE{} will be used via reflection 86 // Example: 87 // isResolvedResourceType(resourceRef, template, ":dynamodb:", &gocf.DynamoDBTable{}) { 88 // 89 func isResolvedResourceType(resource *resourceRef, 90 template *gocf.Template, 91 literalTokenIndicator string, 92 templateType gocf.ResourceProperties) bool { 93 if resource.RefType == resourceLiteral || 94 resource.RefType == resourceStringFunc { 95 return strings.Contains(resource.ResourceName, literalTokenIndicator) 96 } 97 98 // Dynamically provisioned resource included in the template definition? 99 existingResource, existingResourceExists := template.Resources[resource.ResourceName] 100 if existingResourceExists { 101 if reflect.TypeOf(existingResource.Properties) == reflect.TypeOf(templateType) { 102 return true 103 } 104 } 105 return false 106 } 107 108 // visitResolvedEventSourceMapping is a utility function that visits all the EventSourceMapping 109 // entries for the given lambdaAWSInfo struct 110 func visitResolvedEventSourceMapping(visitor resolvedResourceVisitor, 111 lambdaAWSInfos []*LambdaAWSInfo, 112 template *gocf.Template, 113 logger *logrus.Logger) error { 114 115 // 116 // BEGIN 117 // Inline closure to wrap the visitor function so that we can provide 118 // specific error messages 119 visitEventSourceMappingRef := func(lambdaAWSInfo *LambdaAWSInfo, 120 eventSourceMapping *EventSourceMapping, 121 mappingIndex int, 122 resource *resourceRef) error { 123 124 annotateStatementsErr := visitor(lambdaAWSInfo, 125 eventSourceMapping, 126 mappingIndex, 127 resource) 128 129 // Early exit? 130 if annotateStatementsErr != nil { 131 return errors.Wrapf(annotateStatementsErr, 132 "Visiting event source mapping: %#v", 133 eventSourceMapping) 134 } 135 return nil 136 } 137 // 138 // END 139 140 // Iterate through every lambda function. If there is an EventSourceMapping 141 // that points to a piece of infastructure provisioned by this stack, 142 // find the referred resource and supply it to the visitor 143 for _, eachLambda := range lambdaAWSInfos { 144 for eachIndex, eachEventSource := range eachLambda.EventSourceMappings { 145 resourceRef, resourceRefErr := resolveResourceRef(eachEventSource.EventSourceArn) 146 if resourceRefErr != nil { 147 return errors.Wrapf(resourceRefErr, 148 "Failed to resolve EventSourceArn: %#v", eachEventSource) 149 } 150 151 // At this point everything is a string, so we need to unmarshall 152 // and see if the Arn is supplied by either a Ref or a GetAttr 153 // function. In those cases, we need to look around in the template 154 // to go from: EventMapping -> Type -> Lambda -> LambdaIAMRole 155 // so that we can add the permissions 156 if resourceRef != nil { 157 annotationErr := visitEventSourceMappingRef(eachLambda, 158 eachEventSource, 159 eachIndex, 160 resourceRef) 161 // Anything go wrong? 162 if annotationErr != nil { 163 return errors.Wrapf(annotationErr, 164 "Failed to annotate template for EventSourceMapping: %#v", 165 eachEventSource) 166 } 167 } 168 } 169 } 170 return nil 171 }