github.com/mweagle/Sparta@v1.15.0/provision_annotations.go (about) 1 // +build !lambdabinary 2 3 package sparta 4 5 import ( 6 "reflect" 7 "runtime" 8 9 spartaCF "github.com/mweagle/Sparta/aws/cloudformation" 10 spartaIAM "github.com/mweagle/Sparta/aws/iam" 11 gocf "github.com/mweagle/go-cloudformation" 12 "github.com/pkg/errors" 13 "github.com/sirupsen/logrus" 14 ) 15 16 // eventSourceMappingPoliciesForResource returns the IAM specific privileges for each 17 // type of supported AWS Lambda EventSourceMapping 18 func eventSourceMappingPoliciesForResource(resource *resourceRef, 19 template *gocf.Template, 20 logger *logrus.Logger) ([]spartaIAM.PolicyStatement, error) { 21 22 policyStatements := []spartaIAM.PolicyStatement{} 23 24 if isResolvedResourceType(resource, template, ":dynamodb:", &gocf.DynamoDBTable{}) { 25 policyStatements = append(policyStatements, CommonIAMStatements.DynamoDB...) 26 } else if isResolvedResourceType(resource, template, ":kinesis:", &gocf.KinesisStream{}) { 27 policyStatements = append(policyStatements, CommonIAMStatements.Kinesis...) 28 } else if isResolvedResourceType(resource, template, ":sqs:", &gocf.SQSQueue{}) { 29 policyStatements = append(policyStatements, CommonIAMStatements.SQS...) 30 } else { 31 logger.WithFields(logrus.Fields{ 32 "Resource": resource, 33 }).Debug("No additional EventSource IAM permissions found for event type") 34 } 35 return policyStatements, nil 36 } 37 38 // annotationFunc represents an internal annotation function 39 // called to stich the template together 40 type annotationFunc func(lambdaAWSInfos []*LambdaAWSInfo, 41 template *gocf.Template, 42 logger *logrus.Logger) error 43 44 func annotateBuildInformation(lambdaAWSInfo *LambdaAWSInfo, 45 template *gocf.Template, 46 buildID string, 47 logger *logrus.Logger) (*gocf.Template, error) { 48 49 // Add the build id s.t. the logger can get stamped... 50 if lambdaAWSInfo.Options == nil { 51 lambdaAWSInfo.Options = &LambdaFunctionOptions{} 52 } 53 lambdaEnvironment := lambdaAWSInfo.Options.Environment 54 if lambdaEnvironment == nil { 55 lambdaAWSInfo.Options.Environment = make(map[string]*gocf.StringExpr) 56 } 57 return template, nil 58 } 59 60 func annotateDiscoveryInfo(lambdaAWSInfo *LambdaAWSInfo, 61 template *gocf.Template, 62 logger *logrus.Logger) (*gocf.Template, error) { 63 depMap := make(map[string]string) 64 65 // Update the metdata with a reference to the output of each 66 // depended on item... 67 for _, eachDependsKey := range lambdaAWSInfo.DependsOn { 68 dependencyText, dependencyTextErr := discoveryResourceInfoForDependency(template, eachDependsKey, logger) 69 if dependencyTextErr != nil { 70 return nil, errors.Wrapf(dependencyTextErr, "Failed to determine discovery info for resource") 71 } 72 depMap[eachDependsKey] = string(dependencyText) 73 } 74 if lambdaAWSInfo.Options == nil { 75 lambdaAWSInfo.Options = &LambdaFunctionOptions{} 76 } 77 lambdaEnvironment := lambdaAWSInfo.Options.Environment 78 if lambdaEnvironment == nil { 79 lambdaAWSInfo.Options.Environment = make(map[string]*gocf.StringExpr) 80 } 81 82 discoveryInfo, discoveryInfoErr := discoveryInfoForResource(lambdaAWSInfo.LogicalResourceName(), 83 depMap) 84 if discoveryInfoErr != nil { 85 return nil, errors.Wrap(discoveryInfoErr, "Failed to create resource discovery info") 86 } 87 88 // Update the env map 89 lambdaAWSInfo.Options.Environment[envVarDiscoveryInformation] = discoveryInfo 90 return template, nil 91 } 92 93 func annotateCodePipelineEnvironments(lambdaAWSInfo *LambdaAWSInfo, logger *logrus.Logger) { 94 if nil != codePipelineEnvironments { 95 if nil == lambdaAWSInfo.Options { 96 lambdaAWSInfo.Options = defaultLambdaFunctionOptions() 97 } 98 if nil == lambdaAWSInfo.Options.Environment { 99 lambdaAWSInfo.Options.Environment = make(map[string]*gocf.StringExpr) 100 } 101 for _, eachEnvironment := range codePipelineEnvironments { 102 103 logger.WithFields(logrus.Fields{ 104 "Environment": eachEnvironment, 105 "LambdaFunction": lambdaAWSInfo.lambdaFunctionName(), 106 }).Debug("Annotating Lambda environment for CodePipeline") 107 108 for eachKey := range eachEnvironment { 109 lambdaAWSInfo.Options.Environment[eachKey] = gocf.Ref(eachKey).String() 110 } 111 } 112 } 113 } 114 115 func annotateEventSourceMappings(lambdaAWSInfos []*LambdaAWSInfo, 116 template *gocf.Template, 117 logger *logrus.Logger) error { 118 119 // 120 // BEGIN 121 // Inline closure to handle the update of a lambda function that includes 122 // an eventSourceMapping entry. 123 annotatePermissions := func(lambdaAWSInfo *LambdaAWSInfo, 124 eventSourceMapping *EventSourceMapping, 125 mappingIndex int, 126 resource *resourceRef) error { 127 128 annotateStatements, annotateStatementsErr := eventSourceMappingPoliciesForResource(resource, 129 template, 130 logger) 131 132 // Early exit? 133 if annotateStatementsErr != nil { 134 return annotateStatementsErr 135 } else if len(annotateStatements) <= 0 { 136 return nil 137 } 138 // If we have statements, let's go ahead and ensure they 139 // include a reference to our ARN 140 populatedStatements := []spartaIAM.PolicyStatement{} 141 for _, eachStatement := range annotateStatements { 142 populatedStatements = append(populatedStatements, 143 spartaIAM.PolicyStatement{ 144 Action: eachStatement.Action, 145 Effect: "Allow", 146 Resource: spartaCF.DynamicValueToStringExpr(eventSourceMapping.EventSourceArn).String(), 147 }) 148 } 149 150 // Something to push onto the resource. The resource 151 // is hopefully defined in this template. It technically 152 // could be a string literal, in which case we're not going 153 // to have a lot of luck with that... 154 cfResource, cfResourceOk := template.Resources[lambdaAWSInfo.LogicalResourceName()] 155 if !cfResourceOk { 156 return errors.Errorf("Unable to locate lambda function for annotation") 157 } 158 lambdaResource, lambdaResourceOk := cfResource.Properties.(gocf.LambdaFunction) 159 if !lambdaResourceOk { 160 return errors.Errorf("CloudFormation resource exists, but is incorrect type: %s (%v)", 161 cfResource.Properties.CfnResourceType(), 162 cfResource.Properties) 163 } 164 // Ok, go get the IAM Role 165 resourceRef, resourceRefErr := resolveResourceRef(lambdaResource.Role) 166 if resourceRefErr != nil { 167 return errors.Wrapf(resourceRefErr, "Failed to resolve IAM Role for event source mappings: %#v", 168 lambdaResource.Role) 169 } 170 // If it's not nil and also not a literal, go ahead and try and update it 171 if resourceRef != nil && 172 resourceRef.RefType != resourceLiteral && 173 resourceRef.RefType != resourceStringFunc { 174 // Excellent, go ahead and find the role in the template 175 // and stitch things together 176 iamRole, iamRoleExists := template.Resources[resourceRef.ResourceName] 177 if !iamRoleExists { 178 return errors.Errorf("IAM role not found: %s", resourceRef.ResourceName) 179 } 180 // Coerce to the IAMRole and update the statements 181 typedIAMRole, typedIAMRoleOk := iamRole.Properties.(gocf.IAMRole) 182 if !typedIAMRoleOk { 183 return errors.Errorf("Failed to type convert iamRole to proper IAMRole resource") 184 } 185 policyList := typedIAMRole.Policies 186 if policyList == nil { 187 policyList = &gocf.IAMRolePolicyList{} 188 } 189 *policyList = append(*policyList, 190 gocf.IAMRolePolicy{ 191 PolicyDocument: ArbitraryJSONObject{ 192 "Version": "2012-10-17", 193 "Statement": populatedStatements, 194 }, 195 PolicyName: gocf.String("LambdaEventSourceMappingPolicy"), 196 }) 197 typedIAMRole.Policies = policyList 198 } 199 return nil 200 } 201 // 202 // END 203 204 annotationErr := visitResolvedEventSourceMapping(annotatePermissions, 205 lambdaAWSInfos, 206 template, 207 logger) 208 209 if annotationErr != nil { 210 return errors.Wrapf(annotationErr, 211 "Failed to annotate template for EventSourceMappings") 212 } 213 return nil 214 } 215 216 func annotateMaterializedTemplate( 217 lambdaAWSInfos []*LambdaAWSInfo, 218 template *gocf.Template, 219 logger *logrus.Logger) (*gocf.Template, error) { 220 // Setup the annotation functions 221 annotationFuncs := []annotationFunc{ 222 annotateEventSourceMappings, 223 } 224 for _, eachAnnotationFunc := range annotationFuncs { 225 funcName := runtime.FuncForPC(reflect.ValueOf(eachAnnotationFunc).Pointer()).Name() 226 logger.WithFields(logrus.Fields{ 227 "Annotator": funcName, 228 }).Debug("Evaluating annotator") 229 230 annotationErr := eachAnnotationFunc(lambdaAWSInfos, 231 template, 232 logger) 233 if annotationErr != nil { 234 return nil, errors.Wrapf(annotationErr, 235 "Function %s failed to annotate template", 236 funcName) 237 } 238 } 239 return template, nil 240 }