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  }