Beispiel #1
0
func parseFnJoinExpr(data map[string]interface{}) (*gocf.StringExpr, error) {
	if len(data) <= 0 {
		return nil, fmt.Errorf("FnJoinExpr data is empty")
	}
	for eachKey, eachValue := range data {
		switch eachKey {
		case "Ref":
			return gocf.Ref(eachValue.(string)).String(), nil
		case "Fn::GetAtt":
			attrValues, attrValuesErr := toExpressionSlice(eachValue)
			if nil != attrValuesErr {
				return nil, attrValuesErr
			}
			if len(attrValues) != 2 {
				return nil, fmt.Errorf("Invalid params for Fn::GetAtt: %s", eachValue)
			}
			return gocf.GetAtt(attrValues[0], attrValues[1]).String(), nil
		case "Fn::FindInMap":
			attrValues, attrValuesErr := toExpressionSlice(eachValue)
			if nil != attrValuesErr {
				return nil, attrValuesErr
			}
			if len(attrValues) != 3 {
				return nil, fmt.Errorf("Invalid params for Fn::FindInMap: %s", eachValue)
			}
			return gocf.FindInMap(attrValues[0], gocf.String(attrValues[1]), gocf.String(attrValues[2])), nil
		}
	}
	return nil, fmt.Errorf("Unsupported AWS Function detected: %#v", data)
}
Beispiel #2
0
func integrationResponses(userResponses map[int]*IntegrationResponse,
	corsEnabled bool) *gocf.APIGatewayMethodIntegrationIntegrationResponseList {

	var integrationResponses gocf.APIGatewayMethodIntegrationIntegrationResponseList

	// We've already populated this entire map in the NewMethod call
	for eachHTTPStatusCode, eachMethodIntegrationResponse := range userResponses {

		responseParameters := eachMethodIntegrationResponse.Parameters
		if corsEnabled {
			for eachKey, eachValue := range corsIntegrationResponseParams() {
				responseParameters[eachKey] = eachValue
			}
		}

		integrationResponse := gocf.APIGatewayMethodIntegrationIntegrationResponse{
			ResponseTemplates: eachMethodIntegrationResponse.Templates,
			SelectionPattern:  gocf.String(eachMethodIntegrationResponse.SelectionPattern),
			StatusCode:        gocf.String(strconv.Itoa(eachHTTPStatusCode)),
		}
		if len(responseParameters) != 0 {
			integrationResponse.ResponseParameters = responseParameters
		}
		integrationResponses = append(integrationResponses, integrationResponse)
	}

	return &integrationResponses
}
Beispiel #3
0
func (resourceInfo *customResourceInfo) export(serviceName string,
	targetLambda *gocf.StringExpr,
	S3Bucket string,
	S3Key string,
	roleNameMap map[string]*gocf.StringExpr,
	template *gocf.Template,
	logger *logrus.Logger) error {

	// Figure out the role name
	iamRoleArnName := resourceInfo.roleName

	// If there is no user supplied role, that means that the associated
	// IAMRoleDefinition name has been created and this resource needs to
	// depend on that being created.
	if iamRoleArnName == "" && resourceInfo.roleDefinition != nil {
		iamRoleArnName = resourceInfo.roleDefinition.logicalName(serviceName, resourceInfo.userFunctionName)
	}
	lambdaDescription := resourceInfo.options.Description
	if "" == lambdaDescription {
		lambdaDescription = fmt.Sprintf("%s CustomResource: %s", serviceName, resourceInfo.userFunctionName)
	}

	// Create the Lambda Function
	lambdaResource := gocf.LambdaFunction{
		Code: &gocf.LambdaFunctionCode{
			S3Bucket: gocf.String(S3Bucket),
			S3Key:    gocf.String(S3Key),
		},
		Description: gocf.String(lambdaDescription),
		Handler:     gocf.String(fmt.Sprintf("index.%s", resourceInfo.jsHandlerName())),
		MemorySize:  gocf.Integer(resourceInfo.options.MemorySize),
		Role:        roleNameMap[iamRoleArnName],
		Runtime:     gocf.String(NodeJSVersion),
		Timeout:     gocf.Integer(resourceInfo.options.Timeout),
		VpcConfig:   resourceInfo.options.VpcConfig,
	}

	lambdaFunctionCFName := CloudFormationResourceName("CustomResourceLambda",
		resourceInfo.userFunctionName,
		resourceInfo.logicalName())

	cfResource := template.AddResource(lambdaFunctionCFName, lambdaResource)
	safeMetadataInsert(cfResource, "golangFunc", resourceInfo.userFunctionName)

	// And create the CustomResource that actually invokes it...
	newResource, newResourceError := newCloudFormationResource(cloudFormationLambda, logger)
	if nil != newResourceError {
		return newResourceError
	}
	customResource := newResource.(*cloudFormationLambdaCustomResource)
	customResource.ServiceToken = gocf.GetAtt(lambdaFunctionCFName, "Arn")
	customResource.UserProperties = resourceInfo.properties
	template.AddResource(resourceInfo.logicalName(), customResource)
	return nil
}
Beispiel #4
0
func (rule *ReceiptRule) toResourceRule(serviceName string,
	functionArnRef interface{},
	messageBodyStorage *MessageBodyStorage) *cloudformationresources.SESLambdaEventSourceResourceRule {

	resourceRule := &cloudformationresources.SESLambdaEventSourceResourceRule{
		Name:        gocf.String(rule.Name),
		ScanEnabled: gocf.Bool(!rule.ScanDisabled),
		Enabled:     gocf.Bool(!rule.Disabled),
		Actions:     make([]*cloudformationresources.SESLambdaEventSourceResourceAction, 0),
		Recipients:  make([]*gocf.StringExpr, 0),
	}
	for _, eachRecipient := range rule.Recipients {
		resourceRule.Recipients = append(resourceRule.Recipients, gocf.String(eachRecipient))
	}
	if "" != rule.TLSPolicy {
		resourceRule.TLSPolicy = gocf.String(rule.TLSPolicy)
	}

	// If there is a MessageBodyStorage reference, push that S3Action
	// to the head of the Actions list
	if nil != messageBodyStorage && !rule.BodyStorageOptions.DisableStorage {
		s3Action := &cloudformationresources.SESLambdaEventSourceResourceAction{
			ActionType: gocf.String("S3Action"),
			ActionProperties: map[string]interface{}{
				"BucketName": messageBodyStorage.bucketNameExpr,
			},
		}
		if "" != rule.BodyStorageOptions.ObjectKeyPrefix {
			s3Action.ActionProperties["ObjectKeyPrefix"] = rule.BodyStorageOptions.ObjectKeyPrefix
		}
		if "" != rule.BodyStorageOptions.KmsKeyArn {
			s3Action.ActionProperties["KmsKeyArn"] = rule.BodyStorageOptions.KmsKeyArn
		}
		if "" != rule.BodyStorageOptions.TopicArn {
			s3Action.ActionProperties["TopicArn"] = rule.BodyStorageOptions.TopicArn
		}
		resourceRule.Actions = append(resourceRule.Actions, s3Action)
	}
	// There's always a lambda action
	lambdaAction := &cloudformationresources.SESLambdaEventSourceResourceAction{
		ActionType: gocf.String("LambdaAction"),
		ActionProperties: map[string]interface{}{
			"FunctionArn": functionArnRef,
		},
	}
	lambdaAction.ActionProperties["InvocationType"] = rule.InvocationType
	if "" == rule.InvocationType {
		lambdaAction.ActionProperties["InvocationType"] = "Event"
	}
	if "" != rule.TopicArn {
		lambdaAction.ActionProperties["TopicArn"] = rule.TopicArn
	}
	resourceRule.Actions = append(resourceRule.Actions, lambdaAction)
	return resourceRule
}
Beispiel #5
0
func (roleDefinition *IAMRoleDefinition) toResource(eventSourceMappings []*EventSourceMapping,
	options *LambdaFunctionOptions,
	logger *logrus.Logger) gocf.IAMRole {

	statements := CommonIAMStatements.Core
	for _, eachPrivilege := range roleDefinition.Privileges {
		statements = append(statements, spartaIAM.PolicyStatement{
			Effect:   "Allow",
			Action:   eachPrivilege.Actions,
			Resource: eachPrivilege.resourceExpr(),
		})
	}

	// Add VPC permissions iff needed
	if options != nil && options.VpcConfig != nil {
		for _, eachStatement := range CommonIAMStatements.VPC {
			statements = append(statements, eachStatement)
		}
	}

	// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
	for _, eachEventSourceMapping := range eventSourceMappings {
		arnParts := strings.Split(eachEventSourceMapping.EventSourceArn, ":")
		// 3rd slot is service scope
		if len(arnParts) >= 2 {
			awsService := arnParts[2]
			logger.Debug("Looking up common IAM privileges for EventSource: ", awsService)
			switch awsService {
			case "dynamodb":
				statements = append(statements, CommonIAMStatements.DynamoDB...)
			case "kinesis":
				for _, statement := range CommonIAMStatements.Kinesis {
					statement.Resource = gocf.String(eachEventSourceMapping.EventSourceArn)
					statements = append(statements, statement)
				}
			default:
				logger.Debug("No additional statements found")
			}
		}
	}

	iamPolicies := gocf.IAMPoliciesList{}
	iamPolicies = append(iamPolicies, gocf.IAMPolicies{
		PolicyDocument: ArbitraryJSONObject{
			"Version":   "2012-10-17",
			"Statement": statements,
		},
		PolicyName: gocf.String(CloudFormationResourceName("LambdaPolicy")),
	})
	return gocf.IAMRole{
		AssumeRolePolicyDocument: AssumePolicyDocument,
		Policies:                 &iamPolicies,
	}
}
Beispiel #6
0
func annotateDiscoveryInfo(template *gocf.Template, logger *logrus.Logger) *gocf.Template {
	for eachResourceID, eachResource := range template.Resources {
		// Only apply this to lambda functions
		if eachResource.Properties.CfnResourceType() == "AWS::Lambda::Function" {

			// Update the metdata with a reference to the output of each
			// depended on item...
			for _, eachDependsKey := range eachResource.DependsOn {
				dependencyOutputs, _ := outputsForResource(template, eachDependsKey, logger)
				if nil != dependencyOutputs && len(dependencyOutputs) != 0 {
					logger.WithFields(logrus.Fields{
						"Resource":  eachDependsKey,
						"DependsOn": eachResource.DependsOn,
						"Outputs":   dependencyOutputs,
					}).Debug("Resource metadata")
					safeMetadataInsert(eachResource, eachDependsKey, dependencyOutputs)
				}
			}
			// Also include standard AWS outputs at a resource level if a lambda
			// needs to self-discover other resources
			safeMetadataInsert(eachResource, TagLogicalResourceID, gocf.String(eachResourceID))
			safeMetadataInsert(eachResource, TagStackRegion, gocf.Ref("AWS::Region"))
			safeMetadataInsert(eachResource, TagStackID, gocf.Ref("AWS::StackId"))
			safeMetadataInsert(eachResource, TagStackName, gocf.Ref("AWS::StackName"))
		}
	}
	return template
}
func appendDynamicSNSLambda(api *sparta.API, lambdaFunctions []*sparta.LambdaAWSInfo) []*sparta.LambdaAWSInfo {
	snsTopicName := sparta.CloudFormationResourceName("SNSDynamicTopic")
	lambdaFn := sparta.NewLambda(sparta.IAMRoleDefinition{}, echoDynamicSNSEvent, nil)
	lambdaFn.Permissions = append(lambdaFn.Permissions, sparta.SNSPermission{
		BasePermission: sparta.BasePermission{
			SourceArn: gocf.Ref(snsTopicName),
		},
	})

	lambdaFn.Decorator = func(serviceName string,
		lambdaResourceName string,
		lambdaResource gocf.LambdaFunction,
		resourceMetadata map[string]interface{},
		S3Bucket string,
		S3Key string,
		buildID string,
		template *gocf.Template,
		context map[string]interface{},
		logger *logrus.Logger) error {
		template.AddResource(snsTopicName, &gocf.SNSTopic{
			DisplayName: gocf.String("Sparta Application SNS topic"),
		})
		return nil
	}
	return append(lambdaFunctions, lambdaFn)
}
Beispiel #8
0
// S3ArnForBucket returns a CloudFormation-compatible Arn expression
// (string or Ref) suitable for template reference.  The bucket
// parameter may be either a string or an interface{} ("Ref: "myResource")
// value
func S3ArnForBucket(bucket interface{}) *gocf.StringExpr {
	arnParts := []gocf.Stringable{gocf.String("arn:aws:s3:::")}

	switch bucket.(type) {
	case string:
		// Don't be smart if the Arn value is a user supplied literal
		arnParts = append(arnParts, gocf.String(bucket.(string)))
	case *gocf.StringExpr:
		arnParts = append(arnParts, bucket.(*gocf.StringExpr))
	case gocf.RefFunc:
		arnParts = append(arnParts, bucket.(gocf.RefFunc).String())
	default:
		panic(fmt.Sprintf("Unsupported SourceArn value type: %+v", bucket))
	}
	return gocf.Join("", arnParts...).String()
}
Beispiel #9
0
// DefaultMethodResponses returns the default set of Method HTTPStatus->Response
// pass through responses.  The successfulHTTPStatusCode param is the single
// 2XX response code to use for the method.
func methodResponses(userResponses map[int]*Response, corsEnabled bool) *gocf.APIGatewayMethodMethodResponseList {

	var responses gocf.APIGatewayMethodMethodResponseList
	for eachHTTPStatusCode, eachResponse := range userResponses {
		methodResponseParams := eachResponse.Parameters
		if corsEnabled {
			for eachString, eachBool := range corsMethodResponseParams() {
				methodResponseParams[eachString] = eachBool
			}
		}
		// Then transform them all to strings because internet
		methodResponseStringParams := make(map[string]string, len(methodResponseParams))
		for eachKey, eachBool := range methodResponseParams {
			methodResponseStringParams[eachKey] = fmt.Sprintf("%t", eachBool)
		}
		methodResponse := gocf.APIGatewayMethodMethodResponse{
			StatusCode: gocf.String(strconv.Itoa(eachHTTPStatusCode)),
		}
		if len(methodResponseStringParams) != 0 {
			methodResponse.ResponseParameters = methodResponseStringParams
		}
		responses = append(responses, methodResponse)
	}
	return &responses
}
Beispiel #10
0
func templateDecorator(serviceName string,
	lambdaResourceName string,
	lambdaResource gocf.LambdaFunction,
	resourceMetadata map[string]interface{},
	S3Bucket string,
	S3Key string,
	buildID string,
	cfTemplate *gocf.Template,
	context map[string]interface{},
	logger *logrus.Logger) error {

	// Add an empty resource
	newResource, err := newCloudFormationResource("Custom::ProvisionTestEmpty", logger)
	if nil != err {
		return err
	}
	customResource := newResource.(*cloudFormationProvisionTestResource)
	customResource.ServiceToken = "arn:aws:sns:us-east-1:84969EXAMPLE:CRTest"
	customResource.TestKey = "Hello World"
	cfTemplate.AddResource("ProvisionTestResource", customResource)

	// Add an output
	cfTemplate.Outputs["OutputDecorationTest"] = &gocf.Output{
		Description: "Information about the value",
		Value:       gocf.String("My key"),
	}
	return nil
}
Beispiel #11
0
func (rolePrivilege *IAMRolePrivilege) resourceExpr() *gocf.StringExpr {
	switch rolePrivilege.Resource.(type) {
	case string:
		return gocf.String(rolePrivilege.Resource.(string))
	default:
		return rolePrivilege.Resource.(*gocf.StringExpr)
	}
}
Beispiel #12
0
func (perm SNSPermission) export(serviceName string,
	lambdaFunctionDisplayName string,
	lambdaLogicalCFResourceName string,
	template *gocf.Template,
	S3Bucket string,
	S3Key string,
	logger *logrus.Logger) (string, error) {
	sourceArnExpression := perm.BasePermission.sourceArnExpr(snsSourceArnParts...)

	targetLambdaResourceName, err := perm.BasePermission.export(gocf.String(SNSPrincipal),
		snsSourceArnParts,
		lambdaFunctionDisplayName,
		lambdaLogicalCFResourceName,
		template,
		S3Bucket,
		S3Key,
		logger)
	if nil != err {
		return "", err
	}

	// Make sure the custom lambda that manages s3 notifications is provisioned.
	configuratorResName, err := ensureCustomResourceHandler(serviceName,
		cloudformationresources.SNSLambdaEventSource,
		sourceArnExpression,
		[]string{},
		template,
		S3Bucket,
		S3Key,
		logger)

	if nil != err {
		return "", err
	}

	// Add a custom resource invocation for this configuration
	//////////////////////////////////////////////////////////////////////////////
	newResource, newResourceError := newCloudFormationResource(cloudformationresources.SNSLambdaEventSource, logger)
	if nil != newResourceError {
		return "", newResourceError
	}
	customResource := newResource.(*cloudformationresources.SNSLambdaEventSourceResource)
	customResource.ServiceToken = gocf.GetAtt(configuratorResName, "Arn")
	customResource.LambdaTargetArn = gocf.GetAtt(lambdaLogicalCFResourceName, "Arn")
	customResource.SNSTopicArn = sourceArnExpression

	// Name?
	resourceInvokerName := CloudFormationResourceName("ConfigSNS",
		lambdaLogicalCFResourceName,
		perm.BasePermission.SourceAccount)

	// Add it
	cfResource := template.AddResource(resourceInvokerName, customResource)
	cfResource.DependsOn = append(cfResource.DependsOn,
		targetLambdaResourceName,
		configuratorResName)
	return "", nil
}
func appendDynamicS3BucketLambda(api *sparta.API, lambdaFunctions []*sparta.LambdaAWSInfo) []*sparta.LambdaAWSInfo {

	s3BucketResourceName := sparta.CloudFormationResourceName("S3DynamicBucket")

	lambdaFn := sparta.NewLambda(sparta.IAMRoleDefinition{}, echoS3DynamicBucketEvent, nil)
	lambdaFn.Permissions = append(lambdaFn.Permissions, sparta.S3Permission{
		BasePermission: sparta.BasePermission{
			SourceArn: gocf.Ref(s3BucketResourceName),
		},
		Events: []string{"s3:ObjectCreated:*", "s3:ObjectRemoved:*"},
	})
	lambdaFn.DependsOn = append(lambdaFn.DependsOn, s3BucketResourceName)

	// Add permission s.t. the lambda function could read from the S3 bucket
	lambdaFn.RoleDefinition.Privileges = append(lambdaFn.RoleDefinition.Privileges,
		sparta.IAMRolePrivilege{
			Actions:  []string{"s3:GetObject", "s3:HeadObject"},
			Resource: spartaCF.S3AllKeysArnForBucket(gocf.Ref(s3BucketResourceName)),
		})

	lambdaFn.Decorator = func(serviceName string,
		lambdaResourceName string,
		lambdaResource gocf.LambdaFunction,
		resourceMetadata map[string]interface{},
		S3Bucket string,
		S3Key string,
		buildID string,
		template *gocf.Template,
		context map[string]interface{},
		logger *logrus.Logger) error {
		cfResource := template.AddResource(s3BucketResourceName, &gocf.S3Bucket{
			AccessControl: gocf.String("PublicRead"),
			Tags: []gocf.ResourceTag{
				gocf.ResourceTag{
					Key:   gocf.String("SpecialKey"),
					Value: gocf.String("SpecialValue"),
				},
			},
		})
		cfResource.DeletionPolicy = "Delete"
		return nil
	}
	return append(lambdaFunctions, lambdaFn)
}
Beispiel #14
0
func (perm BasePermission) export(principal *gocf.StringExpr,
	arnPrefixParts []gocf.Stringable,
	lambdaFunctionDisplayName string,
	lambdaLogicalCFResourceName string,
	template *gocf.Template,
	S3Bucket string,
	S3Key string,
	logger *logrus.Logger) (string, error) {

	lambdaPermission := gocf.LambdaPermission{
		Action:       gocf.String("lambda:InvokeFunction"),
		FunctionName: gocf.GetAtt(lambdaLogicalCFResourceName, "Arn"),
		Principal:    principal,
	}
	// If the Arn isn't the wildcard value, then include it.
	if nil != perm.SourceArn {
		switch perm.SourceArn.(type) {
		case string:
			// Don't be smart if the Arn value is a user supplied literal
			if "*" != perm.SourceArn.(string) {
				lambdaPermission.SourceArn = gocf.String(perm.SourceArn.(string))
			}
		default:
			lambdaPermission.SourceArn = perm.sourceArnExpr(arnPrefixParts...)
		}
	}

	if "" != perm.SourceAccount {
		lambdaPermission.SourceAccount = gocf.String(perm.SourceAccount)
	}

	arnLiteral, arnLiteralErr := json.Marshal(lambdaPermission.SourceArn)
	if nil != arnLiteralErr {
		return "", arnLiteralErr
	}
	resourceName := CloudFormationResourceName("LambdaPerm%s",
		principal.Literal,
		string(arnLiteral),
		lambdaLogicalCFResourceName)
	template.AddResource(resourceName, lambdaPermission)
	return resourceName, nil
}
Beispiel #15
0
func (roleDefinition *IAMRoleDefinition) toResource(eventSourceMappings []*EventSourceMapping,
	logger *logrus.Logger) gocf.IAMRole {

	statements := CommonIAMStatements["core"]
	for _, eachPrivilege := range roleDefinition.Privileges {
		statements = append(statements, iamPolicyStatement{
			Effect:   "Allow",
			Action:   eachPrivilege.Actions,
			Resource: eachPrivilege.resourceExpr(),
		})
	}

	// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
	for _, eachEventSourceMapping := range eventSourceMappings {
		arnParts := strings.Split(eachEventSourceMapping.EventSourceArn, ":")
		// 3rd slot is service scope
		if len(arnParts) >= 2 {
			awsService := arnParts[2]
			logger.Debug("Looking up common IAM privileges for EventSource: ", awsService)
			serviceStatements, exists := CommonIAMStatements[awsService]
			if exists {
				statements = append(statements, serviceStatements...)
				statements[len(statements)-1].Resource = gocf.String(eachEventSourceMapping.EventSourceArn)
			}
		}
	}

	return gocf.IAMRole{
		AssumeRolePolicyDocument: AssumePolicyDocument,
		Policies: &gocf.IAMPoliciesList{
			gocf.IAMPolicies{
				PolicyDocument: ArbitraryJSONObject{
					"Version":   "2012-10-17",
					"Statement": statements,
				},
				PolicyName: gocf.String(CloudFormationResourceName("LambdaPolicy")),
			},
		},
	}
}
Beispiel #16
0
func corsOptionsGatewayMethod(restAPIID gocf.Stringable, resourceID gocf.Stringable) *gocf.ApiGatewayMethod {
	methodResponse := gocf.APIGatewayMethodMethodResponse{
		StatusCode:         gocf.String("200"),
		ResponseParameters: corsMethodResponseParams(),
	}

	integrationResponse := gocf.APIGatewayMethodIntegrationIntegrationResponse{
		ResponseTemplates: map[string]string{
			"application/*": "",
			"text/*":        "",
		},
		StatusCode:         gocf.String("200"),
		ResponseParameters: corsIntegrationResponseParams(),
	}

	methodIntegrationIntegrationResponseList := gocf.APIGatewayMethodIntegrationIntegrationResponseList{}
	methodIntegrationIntegrationResponseList = append(methodIntegrationIntegrationResponseList,
		integrationResponse)
	methodResponseList := gocf.APIGatewayMethodMethodResponseList{}
	methodResponseList = append(methodResponseList, methodResponse)

	corsMethod := &gocf.ApiGatewayMethod{
		HttpMethod:        gocf.String("OPTIONS"),
		AuthorizationType: gocf.String("NONE"),
		RestApiId:         restAPIID.String(),
		ResourceId:        resourceID.String(),
		Integration: &gocf.APIGatewayMethodIntegration{
			Type: gocf.String("MOCK"),
			RequestTemplates: map[string]string{
				"application/json": "{\"statusCode\": 200}",
				"text/plain":       "statusCode: 200",
			},
			IntegrationResponses: &methodIntegrationIntegrationResponseList,
		},
		MethodResponses: &methodResponseList,
	}
	return corsMethod
}
Beispiel #17
0
// Verify & cache the IAM rolename to ARN mapping
func verifyIAMRoles(ctx *workflowContext) (workflowStep, error) {
	// The map is either a literal Arn from a pre-existing role name
	// or a gocf.RefFunc() value.
	// Don't verify them, just create them...
	ctx.logger.Info("Verifying IAM Lambda execution roles")
	ctx.lambdaIAMRoleNameMap = make(map[string]*gocf.StringExpr, 0)
	svc := iam.New(ctx.awsSession)

	for _, eachLambda := range ctx.lambdaAWSInfos {
		if "" != eachLambda.RoleName && nil != eachLambda.RoleDefinition {
			return nil, fmt.Errorf("Both RoleName and RoleDefinition defined for lambda: %s", eachLambda.lambdaFnName)
		}

		// Get the IAM role name
		if "" != eachLambda.RoleName {
			_, exists := ctx.lambdaIAMRoleNameMap[eachLambda.RoleName]
			if !exists {
				// Check the role
				params := &iam.GetRoleInput{
					RoleName: aws.String(eachLambda.RoleName),
				}
				ctx.logger.Debug("Checking IAM RoleName: ", eachLambda.RoleName)
				resp, err := svc.GetRole(params)
				if err != nil {
					ctx.logger.Error(err.Error())
					return nil, err
				}
				// Cache it - we'll need it later when we create the
				// CloudFormation template which needs the execution Arn (not role)
				ctx.lambdaIAMRoleNameMap[eachLambda.RoleName] = gocf.String(*resp.Role.Arn)
			}
		} else {
			logicalName := eachLambda.RoleDefinition.logicalName()
			_, exists := ctx.lambdaIAMRoleNameMap[logicalName]
			if !exists {
				// Insert it into the resource creation map and add
				// the "Ref" entry to the hashmap
				ctx.cfTemplate.AddResource(logicalName,
					eachLambda.RoleDefinition.toResource(eachLambda.EventSourceMappings, ctx.logger))

				ctx.lambdaIAMRoleNameMap[logicalName] = gocf.GetAtt(logicalName, "Arn")
			}
		}
	}
	ctx.logger.WithFields(logrus.Fields{
		"Count": len(ctx.lambdaIAMRoleNameMap),
	}).Info("IAM roles verified")

	return createPackageStep(), nil
}
Beispiel #18
0
func (mapping *EventSourceMapping) export(serviceName string,
	targetLambda *gocf.StringExpr,
	S3Bucket string,
	S3Key string,
	template *gocf.Template,
	logger *logrus.Logger) error {

	eventSourceMappingResource := gocf.LambdaEventSourceMapping{
		EventSourceArn:   gocf.String(mapping.EventSourceArn),
		FunctionName:     targetLambda,
		StartingPosition: gocf.String(mapping.StartingPosition),
		BatchSize:        gocf.Integer(mapping.BatchSize),
		Enabled:          gocf.Bool(!mapping.Disabled),
	}

	hash := sha1.New()
	hash.Write([]byte(mapping.EventSourceArn))
	binary.Write(hash, binary.LittleEndian, mapping.BatchSize)
	hash.Write([]byte(mapping.StartingPosition))
	resourceName := fmt.Sprintf("LambdaES%s", hex.EncodeToString(hash.Sum(nil)))
	template.AddResource(resourceName, eventSourceMappingResource)
	return nil
}
Beispiel #19
0
func (perm BasePermission) export(principal string,
	arnPrefixParts []gocf.Stringable,
	lambdaLogicalCFResourceName string,
	template *gocf.Template,
	S3Bucket string,
	S3Key string,
	logger *logrus.Logger) (string, error) {

	lambdaPermission := gocf.LambdaPermission{
		Action:       gocf.String("lambda:InvokeFunction"),
		FunctionName: gocf.GetAtt(lambdaLogicalCFResourceName, "Arn"),
		Principal:    gocf.String(principal),
	}
	// If the Arn isn't the wildcard value, then include it.
	if nil != perm.SourceArn {
		switch perm.SourceArn.(type) {
		case string:
			// Don't be smart if the Arn value is a user supplied literal
			if "*" != perm.SourceArn.(string) {
				lambdaPermission.SourceArn = gocf.String(perm.SourceArn.(string))
			}
		default:
			lambdaPermission.SourceArn = perm.sourceArnExpr(arnPrefixParts...)
		}
	}

	if "" != perm.SourceAccount {
		lambdaPermission.SourceAccount = gocf.String(perm.SourceAccount)
	}

	hash := sha1.New()
	hash.Write([]byte(fmt.Sprintf("%v", lambdaPermission)))
	resourceName := fmt.Sprintf("LambdaPerm%s", hex.EncodeToString(hash.Sum(nil)))
	template.AddResource(resourceName, lambdaPermission)
	return resourceName, nil
}
func TestUnzip(t *testing.T) {
	resUnzip := gocf.NewResourceByType(ZipToS3Bucket)
	zipResource := resUnzip.(*ZipToS3BucketResource)
	zipResource.DestBucket = gocf.String(os.Getenv("TEST_DEST_S3_BUCKET"))
	zipResource.SrcBucket = gocf.String(os.Getenv("TEST_SRC_S3_BUCKET"))
	zipResource.SrcKeyName = gocf.String(os.Getenv("TEST_SRC_S3_KEY"))
	zipResource.Manifest = map[string]interface{}{
		"Some": "Data",
	}
	// Put it
	logger := logrus.New()
	awsSession := awsSession(logger)
	createOutputs, createError := zipResource.create(awsSession, logger)
	if nil != createError {
		t.Errorf("Failed to create Unzip resource: %s", createError)
	}
	t.Logf("TestUnzip outputs: %#v", createOutputs)

	deleteOutputs, deleteError := zipResource.delete(awsSession, logger)
	if nil != deleteError {
		t.Errorf("Failed to create Unzip resource: %s", createError)
	}
	t.Logf("TestUnzip outputs: %#v", deleteOutputs)
}
Beispiel #21
0
func (perm LambdaPermission) export(serviceName string,
	lambdaFunctionDisplayName string,
	lambdaLogicalCFResourceName string,
	template *gocf.Template,
	S3Bucket string,
	S3Key string,
	logger *logrus.Logger) (string, error) {

	return perm.BasePermission.export(gocf.String(perm.Principal),
		lambdaSourceArnParts,
		lambdaFunctionDisplayName,
		lambdaLogicalCFResourceName,
		template,
		S3Bucket,
		S3Key,
		logger)
}
Beispiel #22
0
func (perm *BasePermission) sourceArnExpr(joinParts ...gocf.Stringable) *gocf.StringExpr {
	var parts []gocf.Stringable
	if nil != joinParts {
		parts = append(parts, joinParts...)
	}
	switch perm.SourceArn.(type) {
	case string:
		// Don't be smart if the Arn value is a user supplied literal
		parts = []gocf.Stringable{gocf.String(perm.SourceArn.(string))}
	case *gocf.StringExpr:
		parts = append(parts, perm.SourceArn.(*gocf.StringExpr))
	case gocf.RefFunc:
		parts = append(parts, perm.SourceArn.(gocf.RefFunc).String())
	default:
		panic(fmt.Sprintf("Unsupported SourceArn value type: %+v", perm.SourceArn))
	}
	return gocf.Join("", parts...)
}
Beispiel #23
0
func makeTemplate() *cf.Template {
	t := cf.NewTemplate()
	t.Description = "example production infrastructure"
	t.Parameters["DnsName"] = &cf.Parameter{
		Description: "The top level DNS name for the infrastructure",
		Type:        "String",
		Default:     "preview.example.io",
	}

	t.AddResource("ServerLoadBalancer", cf.ElasticLoadBalancingLoadBalancer{
		ConnectionDrainingPolicy: &cf.ElasticLoadBalancingConnectionDrainingPolicy{
			Enabled: cf.Bool(true),
			Timeout: cf.Integer(30),
		},
		CrossZone: cf.Bool(true),
		HealthCheck: &cf.ElasticLoadBalancingHealthCheck{
			HealthyThreshold:   cf.String("2"),
			Interval:           cf.String("60"),
			Target:             cf.String("HTTP:80/"),
			Timeout:            cf.String("5"),
			UnhealthyThreshold: cf.String("2"),
		},
		Listeners: &cf.ElasticLoadBalancingListenerList{
			cf.ElasticLoadBalancingListener{
				InstancePort:     cf.String("8000"),
				InstanceProtocol: cf.String("TCP"),
				LoadBalancerPort: cf.String("443"),
				Protocol:         cf.String("SSL"),
				SSLCertificateId: cf.Join("",
					cf.String("arn:aws:iam::"),
					cf.Ref("AWS::AccountID"),
					cf.String(":server-certificate/"),
					cf.Ref("DnsName")),
			},
		},
		Policies: &cf.ElasticLoadBalancingPolicyList{
			cf.ElasticLoadBalancingPolicy{
				PolicyName: cf.String("EnableProxyProtocol"),
				PolicyType: cf.String("ProxyProtocolPolicyType"),
				Attributes: []map[string]interface{}{
					map[string]interface{}{
						"Name":  "ProxyProtocol",
						"Value": "true",
					},
				},
				InstancePorts: []int{8000},
			},
		},
		Subnets: cf.StringList(
			cf.Ref("VpcSubnetA"),
			cf.Ref("VpcSubnetB"),
			cf.Ref("VpcSubnetC"),
		),
		SecurityGroups: cf.StringList(cf.Ref("LoadBalancerSecurityGroup")),
	})

	return t
}
Beispiel #24
0
// Marshal this object into 1 or more CloudFormation resource definitions that are accumulated
// in the resources map
func (info *LambdaAWSInfo) export(serviceName string,
	S3Bucket string,
	S3Key string,
	buildID string,
	roleNameMap map[string]*gocf.StringExpr,
	template *gocf.Template,
	context map[string]interface{},
	logger *logrus.Logger) error {

	// If we have RoleName, then get the ARN, otherwise get the Ref
	var dependsOn []string
	if nil != info.DependsOn {
		dependsOn = append(dependsOn, info.DependsOn...)
	}

	iamRoleArnName := info.RoleName

	// If there is no user supplied role, that means that the associated
	// IAMRoleDefinition name has been created and this resource needs to
	// depend on that being created.
	if iamRoleArnName == "" && info.RoleDefinition != nil {
		iamRoleArnName = info.RoleDefinition.logicalName(serviceName, info.lambdaFunctionName())
		dependsOn = append(dependsOn, info.RoleDefinition.logicalName(serviceName, info.lambdaFunctionName()))
	}
	lambdaDescription := info.Options.Description
	if "" == lambdaDescription {
		lambdaDescription = fmt.Sprintf("%s: %s", serviceName, info.lambdaFunctionName())
	}

	// Create the primary resource
	lambdaResource := gocf.LambdaFunction{
		Code: &gocf.LambdaFunctionCode{
			S3Bucket: gocf.String(S3Bucket),
			S3Key:    gocf.String(S3Key),
		},
		Description: gocf.String(lambdaDescription),
		Handler:     gocf.String(fmt.Sprintf("index.%s", info.jsHandlerName())),
		MemorySize:  gocf.Integer(info.Options.MemorySize),
		Role:        roleNameMap[iamRoleArnName],
		Runtime:     gocf.String(NodeJSVersion),
		Timeout:     gocf.Integer(info.Options.Timeout),
		VpcConfig:   info.Options.VpcConfig,
	}
	if "" != info.Options.KmsKeyArn {
		lambdaResource.KmsKeyArn = gocf.String(info.Options.KmsKeyArn)
	}
	if nil != info.Options.Environment {
		lambdaResource.Environment = &gocf.LambdaFunctionEnvironment{
			Variables: info.Options.Environment,
		}
	}
	// Need to check if a functionName exists in the LambdaAwsInfo struct
	// If an empty string is passed, the template will error with invalid
	// function name.
	if "" != info.functionName {
		lambdaResource.FunctionName = gocf.String(info.functionName)
	}

	cfResource := template.AddResource(info.logicalName(), lambdaResource)
	cfResource.DependsOn = append(cfResource.DependsOn, dependsOn...)
	safeMetadataInsert(cfResource, "golangFunc", info.lambdaFunctionName())

	// Create the lambda Ref in case we need a permission or event mapping
	functionAttr := gocf.GetAtt(info.logicalName(), "Arn")

	// Permissions
	for _, eachPermission := range info.Permissions {
		_, err := eachPermission.export(serviceName,
			info.lambdaFunctionName(),
			info.logicalName(),
			template,
			S3Bucket,
			S3Key,
			logger)
		if nil != err {
			return err
		}
	}

	// Event Source Mappings
	for _, eachEventSourceMapping := range info.EventSourceMappings {
		mappingErr := eachEventSourceMapping.export(serviceName,
			functionAttr,
			S3Bucket,
			S3Key,
			template,
			logger)
		if nil != mappingErr {
			return mappingErr
		}
	}

	// CustomResource
	for _, eachCustomResource := range info.customResources {
		resourceErr := eachCustomResource.export(serviceName,
			functionAttr,
			S3Bucket,
			S3Key,
			roleNameMap,
			template,
			logger)
		if nil != resourceErr {
			return resourceErr
		}
	}

	// Decorator
	if nil != info.Decorator {
		logger.Debug("Decorator found for Lambda: ", info.lambdaFunctionName())
		// Create an empty template so that we can track whether things
		// are overwritten
		metadataMap := make(map[string]interface{}, 0)
		decoratorProxyTemplate := gocf.NewTemplate()
		err := info.Decorator(serviceName,
			info.logicalName(),
			lambdaResource,
			metadataMap,
			S3Bucket,
			S3Key,
			buildID,
			decoratorProxyTemplate,
			context,
			logger)
		if nil != err {
			return err
		}

		// This data is marshalled into a DiscoveryInfo struct s.t. it can be
		// unmarshalled via sparta.Discover.  We're going to just stuff it into
		// it's own same named property
		if len(metadataMap) != 0 {
			safeMetadataInsert(cfResource, info.logicalName(), metadataMap)
		}
		// Append the custom resources
		err = safeMergeTemplates(decoratorProxyTemplate, template, logger)
		if nil != err {
			return fmt.Errorf("Lambda (%s) decorator created conflicting resources", info.lambdaFunctionName())
		}
	}
	return nil
}
Beispiel #25
0
func (perm CloudWatchEventsPermission) export(serviceName string,
	lambdaLogicalCFResourceName string,
	template *gocf.Template,
	S3Bucket string,
	S3Key string,
	logger *logrus.Logger) (string, error) {

	// Tell the user we're ignoring any Arns provided, since it doesn't make sense for
	// this.
	if nil != perm.BasePermission.SourceArn &&
		perm.BasePermission.sourceArnExpr(cloudformationEventsSourceArnParts...).String() != wildcardArn.String() {
		logger.WithFields(logrus.Fields{
			"Arn": perm.BasePermission.sourceArnExpr(cloudformationEventsSourceArnParts...),
		}).Warn("CloudWatchEvents do not support literal ARN values")
	}

	arnPermissionForRuleName := func(ruleName string) *gocf.StringExpr {
		return gocf.Join("",
			gocf.String("arn:aws:events:"),
			gocf.Ref("AWS::Region"),
			gocf.String(":"),
			gocf.Ref("AWS::AccountId"),
			gocf.String(":rule/"),
			gocf.String(ruleName))
	}

	// First thing we need to do is uniqueify the rule names s.t. we prevent
	// collisions with other stacks.
	globallyUniqueRules := make(map[string]CloudWatchEventsRule, len(perm.Rules))
	for eachRuleName, eachDefinition := range perm.Rules {
		uniqueRuleName := CloudFormationResourceName(eachRuleName, lambdaLogicalCFResourceName, serviceName)
		// Trim it...
		if len(eachDefinition.Description) <= 0 {
			eachDefinition.Description = fmt.Sprintf("%s CloudWatch Events rule for service: %s", eachRuleName, serviceName)
		}
		globallyUniqueRules[uniqueRuleName] = eachDefinition
	}
	// Self test - there should only be 1 element since we're only ever configuring
	// the same AWS principal service.  If we end up with multiple configuration resource names
	// it means that the stable resource name logic is broken
	configurationResourceNames := make(map[string]int, 0)
	var dependsOn []string
	for eachRuleName := range globallyUniqueRules {
		basePerm := BasePermission{
			SourceArn: arnPermissionForRuleName(eachRuleName),
		}
		dependOn, err := basePerm.export(CloudWatchEventsPrincipal,
			cloudformationEventsSourceArnParts,
			lambdaLogicalCFResourceName,
			template,
			S3Bucket,
			S3Key,
			logger)
		if nil != err {
			return "", err
		}
		dependsOn = append(dependsOn, dependOn)

		// Ensure the configurator for this ARNs
		sourceArnExpression := basePerm.sourceArnExpr(cloudformationEventsSourceArnParts...)

		// Make sure the custom lambda that manages CloudWatch Events is provisioned.
		configuratorResName, err := ensureConfiguratorLambdaResource(CloudWatchEventsPrincipal,
			sourceArnExpression,
			[]string{},
			template,
			S3Bucket,
			S3Key,
			logger)
		if nil != err {
			return "", err
		}
		configurationResourceNames[configuratorResName] = 1
	}
	// Although we ensured multiple configuration resources, they were all for the
	// same AWS principal.  We're only supposed to get a single name back.
	if len(configurationResourceNames) > 1 {
		return "", fmt.Errorf("Multiple configuration resources detected: %#v", configurationResourceNames)
	} else if len(configurationResourceNames) == 0 {
		return "", fmt.Errorf("CloudWatchEvent configuration provider failed")
	}

	// Insert the invocation
	for eachConfigResource := range configurationResourceNames {
		//////////////////////////////////////////////////////////////////////////////
		// And finally the custom resource forwarder
		newResource, err := newCloudFormationResource("Custom::SpartaCloudWatchEventsPermission", logger)
		if nil != err {
			return "", err
		}
		customResource := newResource.(*cloudformationCloudWatchEventsPermissionResource)
		customResource.ServiceToken = gocf.GetAtt(eachConfigResource, "Arn")
		customResource.Rules = globallyUniqueRules
		customResource.LambdaTarget = gocf.GetAtt(lambdaLogicalCFResourceName, "Arn")

		// Name?
		resourceInvokerName := CloudFormationResourceName("ConfigCloudWatchEvents",
			lambdaLogicalCFResourceName,
			perm.BasePermission.SourceAccount)
		// Add it
		cfResource := template.AddResource(resourceInvokerName, customResource)
		cfResource.DependsOn = append(cfResource.DependsOn, dependsOn...)
	}
	return "", nil
}
Beispiel #26
0
func (converter *templateConverter) parseData() *templateConverter {
	if converter.conversionError != nil {
		return converter
	}
	reAWSProp := regexp.MustCompile("\\{\\s*\"\\s*(Ref|Fn::GetAtt|Fn::FindInMap)")
	splitData := strings.Split(converter.expandedTemplate, "\n")
	splitDataLineCount := len(splitData)

	for eachLineIndex, eachLine := range splitData {
		curContents := eachLine
		for len(curContents) != 0 {

			matchInfo := reAWSProp.FindStringSubmatchIndex(curContents)
			if nil != matchInfo {
				// If there's anything at the head, push it.
				if matchInfo[0] != 0 {
					head := curContents[0:matchInfo[0]]
					converter.contents = append(converter.contents, gocf.String(fmt.Sprintf("%s", head)))
					curContents = curContents[len(head):]
				}

				// There's at least one match...find the closing brace...
				var parsed map[string]interface{}
				for indexPos, eachChar := range curContents {
					if string(eachChar) == "}" {
						testBlock := curContents[0 : indexPos+1]
						err := json.Unmarshal([]byte(testBlock), &parsed)
						if err == nil {
							parsedContents, parsedContentsErr := parseFnJoinExpr(parsed)
							if nil != parsedContentsErr {
								converter.conversionError = parsedContentsErr
								return converter
							}
							converter.contents = append(converter.contents, parsedContents)
							curContents = curContents[indexPos+1:]
							if len(curContents) <= 0 && (eachLineIndex < (splitDataLineCount - 1)) {
								converter.contents = append(converter.contents, gocf.String("\n"))
							}
							break
						}
					}
				}
				if nil == parsed {
					// We never did find the end...
					converter.conversionError = fmt.Errorf("Invalid CloudFormation JSON expression on line: %s", eachLine)
					return converter
				}
			} else {
				// No match, just include it iff there is another line afterwards
				newlineValue := ""
				if eachLineIndex < (splitDataLineCount - 1) {
					newlineValue = "\n"
				}
				// Always include a newline at a minimum
				appendLine := fmt.Sprintf("%s%s", curContents, newlineValue)
				if len(appendLine) != 0 {
					converter.contents = append(converter.contents, gocf.String(appendLine))
				}
				break
			}
		}
	}
	return converter
}
Beispiel #27
0
		return nil
	}
}

func init() {
	gocf.RegisterCustomResourceProvider(customResourceProvider)
	rand.Seed(time.Now().Unix())
}

////////////////////////////////////////////////////////////////////////////////
// Variables
////////////////////////////////////////////////////////////////////////////////

// Represents the CloudFormation Arn of this stack, referenced
// in CommonIAMStatements
var cloudFormationThisStackArn = []gocf.Stringable{gocf.String("arn:aws:cloudformation:"),
	gocf.Ref("AWS::Region").String(),
	gocf.String(":"),
	gocf.Ref("AWS::AccountId").String(),
	gocf.String(":stack/"),
	gocf.Ref("AWS::StackName").String(),
	gocf.String("/*")}

// CommonIAMStatements defines common IAM::Role Policy Statement values for different AWS
// service types.  See http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces
// for names.
// http://docs.aws.amazon.com/lambda/latest/dg/monitoring-functions.html
// for more information.
var CommonIAMStatements = struct {
	Core     []spartaIAM.PolicyStatement
	VPC      []spartaIAM.PolicyStatement
Beispiel #28
0
// export marshals the API data to a CloudFormation compatible representation
func (api *API) export(serviceName string,
	session *session.Session,
	S3Bucket string,
	S3Key string,
	roleNameMap map[string]*gocf.StringExpr,
	template *gocf.Template,
	noop bool,
	logger *logrus.Logger) error {

	apiGatewayResourceNameForPath := func(fullPath string) string {
		pathParts := strings.Split(fullPath, "/")
		return CloudFormationResourceName("%sResource", pathParts[0], fullPath)
	}
	apiGatewayResName := CloudFormationResourceName("APIGateway", api.name)

	// Create an API gateway entry
	apiGatewayRes := &gocf.ApiGatewayRestApi{
		Description:    gocf.String(api.Description),
		FailOnWarnings: gocf.Bool(false),
		Name:           gocf.String(api.name),
	}
	if "" != api.CloneFrom {
		apiGatewayRes.CloneFrom = gocf.String(api.CloneFrom)
	}
	if "" == api.Description {
		apiGatewayRes.Description = gocf.String(fmt.Sprintf("%s RestApi", serviceName))
	} else {
		apiGatewayRes.Description = gocf.String(api.Description)
	}

	template.AddResource(apiGatewayResName, apiGatewayRes)
	apiGatewayRestAPIID := gocf.Ref(apiGatewayResName)

	// List of all the method resources we're creating s.t. the
	// deployment can DependOn them
	optionsMethodPathMap := make(map[string]bool)
	var apiMethodCloudFormationResources []string
	for eachResourceMethodKey, eachResourceDef := range api.resources {
		// First walk all the user resources and create intermediate paths
		// to repreesent all the resources
		var parentResource *gocf.StringExpr
		pathParts := strings.Split(strings.TrimLeft(eachResourceDef.pathPart, "/"), "/")
		pathAccumulator := []string{"/"}
		for index, eachPathPart := range pathParts {
			pathAccumulator = append(pathAccumulator, eachPathPart)
			resourcePathName := apiGatewayResourceNameForPath(strings.Join(pathAccumulator, "/"))
			if _, exists := template.Resources[resourcePathName]; !exists {
				cfResource := &gocf.ApiGatewayResource{
					RestApiId: apiGatewayRestAPIID.String(),
					PathPart:  gocf.String(eachPathPart),
				}
				if index <= 0 {
					cfResource.ParentId = gocf.GetAtt(apiGatewayResName, "RootResourceId")
				} else {
					cfResource.ParentId = parentResource
				}
				template.AddResource(resourcePathName, cfResource)
			}
			parentResource = gocf.Ref(resourcePathName).String()
		}

		// Add the lambda permission
		apiGatewayPermissionResourceName := CloudFormationResourceName("APIGatewayLambdaPerm", eachResourceMethodKey)
		lambdaInvokePermission := &gocf.LambdaPermission{
			Action:       gocf.String("lambda:InvokeFunction"),
			FunctionName: gocf.GetAtt(eachResourceDef.parentLambda.logicalName(), "Arn"),
			Principal:    gocf.String(APIGatewayPrincipal),
		}
		template.AddResource(apiGatewayPermissionResourceName, lambdaInvokePermission)

		// BEGIN CORS - OPTIONS verb
		// CORS is API global, but it's possible that there are multiple different lambda functions
		// that are handling the same HTTP resource. In this case, track whether we've already created an
		// OPTIONS entry for this path and only append iff this is the first time through
		if api.CORSEnabled {
			methodResourceName := CloudFormationResourceName(fmt.Sprintf("%s-OPTIONS", eachResourceDef.pathPart), eachResourceDef.pathPart)
			_, resourceExists := optionsMethodPathMap[methodResourceName]
			if !resourceExists {
				template.AddResource(methodResourceName, corsOptionsGatewayMethod(apiGatewayRestAPIID, parentResource))
				apiMethodCloudFormationResources = append(apiMethodCloudFormationResources, methodResourceName)
				optionsMethodPathMap[methodResourceName] = true
			}
		}
		// END CORS - OPTIONS verb

		// BEGIN - user defined verbs
		for eachMethodName, eachMethodDef := range eachResourceDef.Methods {

			apiGatewayMethod := &gocf.ApiGatewayMethod{
				HttpMethod:        gocf.String(eachMethodName),
				AuthorizationType: gocf.String("NONE"),
				ResourceId:        parentResource.String(),
				RestApiId:         apiGatewayRestAPIID.String(),
				Integration: &gocf.APIGatewayMethodIntegration{
					IntegrationHttpMethod: gocf.String("POST"),
					Type:             gocf.String("AWS"),
					RequestTemplates: defaultRequestTemplates(),
					Uri: gocf.Join("",
						gocf.String("arn:aws:apigateway:"),
						gocf.Ref("AWS::Region"),
						gocf.String(":lambda:path/2015-03-31/functions/"),
						gocf.GetAtt(eachResourceDef.parentLambda.logicalName(), "Arn"),
						gocf.String("/invocations")),
				},
			}
			if len(eachMethodDef.Parameters) != 0 {
				requestParams := make(map[string]string, 0)
				for eachKey, eachBool := range eachMethodDef.Parameters {
					requestParams[eachKey] = fmt.Sprintf("%t", eachBool)
				}
				apiGatewayMethod.RequestParameters = requestParams
			}

			// Add the integration response RegExps
			apiGatewayMethod.Integration.IntegrationResponses = integrationResponses(eachMethodDef.Integration.Responses,
				api.CORSEnabled)

			// Add outbound method responses
			apiGatewayMethod.MethodResponses = methodResponses(eachMethodDef.Responses,
				api.CORSEnabled)

			prefix := fmt.Sprintf("%s%s", eachMethodDef.httpMethod, eachResourceMethodKey)
			methodResourceName := CloudFormationResourceName(prefix, eachResourceMethodKey, serviceName)
			res := template.AddResource(methodResourceName, apiGatewayMethod)
			res.DependsOn = append(res.DependsOn, apiGatewayPermissionResourceName)
			apiMethodCloudFormationResources = append(apiMethodCloudFormationResources, methodResourceName)
		}
	}
	// END

	if nil != api.stage {
		// Is the stack already deployed?
		stageName := api.stage.name
		stageInfo, stageInfoErr := apiStageInfo(api.name, stageName, session, noop, logger)
		if nil != stageInfoErr {
			return stageInfoErr
		}
		if nil == stageInfo {
			// Use a stable identifier so that we can update the existing deployment
			apiDeploymentResName := CloudFormationResourceName("APIGatewayDeployment", serviceName)
			apiDeployment := &gocf.ApiGatewayDeployment{
				Description: gocf.String(api.stage.Description),
				RestApiId:   apiGatewayRestAPIID.String(),
				StageName:   gocf.String(stageName),
				StageDescription: &gocf.APIGatewayDeploymentStageDescription{
					StageName:   gocf.String(api.stage.name),
					Description: gocf.String(api.stage.Description),
					Variables:   api.stage.Variables,
				},
			}
			if api.stage.CacheClusterEnabled {
				apiDeployment.StageDescription.CacheClusterEnabled = gocf.Bool(api.stage.CacheClusterEnabled)
			}
			if api.stage.CacheClusterSize != "" {
				apiDeployment.StageDescription.CacheClusterSize = gocf.String(api.stage.CacheClusterSize)
			}
			deployment := template.AddResource(apiDeploymentResName, apiDeployment)
			deployment.DependsOn = append(deployment.DependsOn, apiMethodCloudFormationResources...)
			deployment.DependsOn = append(deployment.DependsOn, apiGatewayResName)
		} else {
			newDeployment := &gocf.ApiGatewayDeployment{
				Description: gocf.String("Sparta deploy"),
				RestApiId:   apiGatewayRestAPIID.String(),
				StageName:   gocf.String(stageName),
			}
			// Use an unstable ID s.t. we can actually create a new deployment event.  Not sure how this
			// is going to work with deletes...
			deploymentResName := CloudFormationResourceName("APIGatewayDeployment")
			deployment := template.AddResource(deploymentResName, newDeployment)
			deployment.DependsOn = append(deployment.DependsOn, apiMethodCloudFormationResources...)
			deployment.DependsOn = append(deployment.DependsOn, apiGatewayResName)
		}

		template.Outputs[OutputAPIGatewayURL] = &gocf.Output{
			Description: "API Gateway URL",
			Value: gocf.Join("",
				gocf.String("https://"),
				apiGatewayRestAPIID,
				gocf.String(".execute-api."),
				gocf.Ref("AWS::Region"),
				gocf.String(".amazonaws.com/"),
				gocf.String(stageName)),
		}
	}
	return nil
}
Beispiel #29
0
// ensureIAMRoleForCustomResource ensures that the single IAM::Role for a single
// AWS principal (eg, s3.*.*) exists, and includes statements for the given
// sourceArn.  Sparta uses a single IAM::Role for the CustomResource configuration
// lambda, which is the union of all Arns in the application.
func ensureIAMRoleForCustomResource(awsPrincipalName string,
	sourceArn *gocf.StringExpr,
	template *gocf.Template,
	logger *logrus.Logger) (string, error) {

	var principalActions []string
	switch awsPrincipalName {
	case cloudformationresources.SNSLambdaEventSource:
		principalActions = PushSourceConfigurationActions.SNSLambdaEventSource
	case cloudformationresources.S3LambdaEventSource:
		principalActions = PushSourceConfigurationActions.S3LambdaEventSource
	case cloudformationresources.SESLambdaEventSource:
		principalActions = PushSourceConfigurationActions.SESLambdaEventSource
	case cloudformationresources.CloudWatchLogsLambdaEventSource:
		principalActions = PushSourceConfigurationActions.CloudWatchLogsLambdaEventSource
	default:
		return "", fmt.Errorf("Unsupported principal for IAM role creation: %s", awsPrincipalName)
	}

	// What's the stable IAMRoleName?
	resourceBaseName := fmt.Sprintf("CustomResource%sIAMRole", awsPrincipalToService(awsPrincipalName))
	stableRoleName := CloudFormationResourceName(resourceBaseName, awsPrincipalName)

	// Ensure it exists, then check to see if this Source ARN is already specified...
	// Checking equality with Stringable?

	// Create a new Role
	var existingIAMRole *gocf.IAMRole
	existingResource, exists := template.Resources[stableRoleName]
	logger.WithFields(logrus.Fields{
		"PrincipalActions": principalActions,
		"SourceArn":        sourceArn,
	}).Debug("Ensuring IAM Role results")

	if !exists {
		// Insert the IAM role here.  We'll walk the policies data in the next section
		// to make sure that the sourceARN we have is in the list
		statements := CommonIAMStatements.Core

		iamPolicyList := gocf.IAMPoliciesList{}
		iamPolicyList = append(iamPolicyList,
			gocf.IAMPolicies{
				PolicyDocument: ArbitraryJSONObject{
					"Version":   "2012-10-17",
					"Statement": statements,
				},
				PolicyName: gocf.String(fmt.Sprintf("%sPolicy", stableRoleName)),
			},
		)

		existingIAMRole = &gocf.IAMRole{
			AssumeRolePolicyDocument: AssumePolicyDocument,
			Policies:                 &iamPolicyList,
		}
		template.AddResource(stableRoleName, existingIAMRole)

		// Create a new IAM Role resource
		logger.WithFields(logrus.Fields{
			"RoleName": stableRoleName,
		}).Debug("Inserting IAM Role")
	} else {
		existingIAMRole = existingResource.Properties.(*gocf.IAMRole)
	}
	// Walk the existing statements
	if nil != existingIAMRole.Policies {
		for _, eachPolicy := range *existingIAMRole.Policies {
			policyDoc := eachPolicy.PolicyDocument.(ArbitraryJSONObject)
			statements := policyDoc["Statement"]
			for _, eachStatement := range statements.([]spartaIAM.PolicyStatement) {
				if sourceArn.String() == eachStatement.Resource.String() {

					logger.WithFields(logrus.Fields{
						"RoleName":  stableRoleName,
						"SourceArn": sourceArn.String(),
					}).Debug("SourceArn already exists for IAM Policy")
					return stableRoleName, nil
				}
			}
		}

		logger.WithFields(logrus.Fields{
			"RoleName": stableRoleName,
			"Action":   principalActions,
			"Resource": sourceArn,
		}).Debug("Inserting Actions for configuration ARN")

		// Add this statement to the first policy, iff the actions are non-empty
		if len(principalActions) > 0 {
			rootPolicy := (*existingIAMRole.Policies)[0]
			rootPolicyDoc := rootPolicy.PolicyDocument.(ArbitraryJSONObject)
			rootPolicyStatements := rootPolicyDoc["Statement"].([]spartaIAM.PolicyStatement)
			rootPolicyDoc["Statement"] = append(rootPolicyStatements, spartaIAM.PolicyStatement{
				Effect:   "Allow",
				Action:   principalActions,
				Resource: sourceArn,
			})
		}

		return stableRoleName, nil
	}

	return "", fmt.Errorf("Unable to find Policies entry for IAM role: %s", stableRoleName)
}
Beispiel #30
0
func ensureCustomResourceHandler(serviceName string,
	customResourceTypeName string,
	sourceArn *gocf.StringExpr,
	dependsOn []string,
	template *gocf.Template,
	S3Bucket string,
	S3Key string,
	logger *logrus.Logger) (string, error) {

	// AWS service basename
	awsServiceName := awsPrincipalToService(customResourceTypeName)

	// Use a stable resource CloudFormation resource name to represent
	// the single CustomResource that can configure the different
	// PushSource's for the given principal.
	keyName, err := json.Marshal(ArbitraryJSONObject{
		"Principal":   customResourceTypeName,
		"ServiceName": awsServiceName,
	})
	if err != nil {
		logger.Error("Failed to create configurator resource name: ", err.Error())
		return "", err
	}
	subscriberHandlerName := CloudFormationResourceName(fmt.Sprintf("%sCustomResource", awsServiceName),
		string(keyName))

	//////////////////////////////////////////////////////////////////////////////
	// IAM Role definition
	iamResourceName, err := ensureIAMRoleForCustomResource(customResourceTypeName, sourceArn, template, logger)
	if nil != err {
		return "", err
	}
	iamRoleRef := gocf.GetAtt(iamResourceName, "Arn")
	_, exists := template.Resources[subscriberHandlerName]
	if !exists {
		logger.WithFields(logrus.Fields{
			"Service": customResourceTypeName,
		}).Debug("Including Lambda CustomResource for AWS Service")

		configuratorDescription := customResourceDescription(serviceName, customResourceTypeName)

		//////////////////////////////////////////////////////////////////////////////
		// Custom Resource Lambda Handler
		// The export name MUST correspond to the createForwarder entry that is dynamically
		// written into the index.js file during compile in createNewSpartaCustomResourceEntry

		handlerName := lambdaExportNameForCustomResourceType(customResourceTypeName)
		logger.WithFields(logrus.Fields{
			"CustomResourceType": customResourceTypeName,
			"NodeJSExport":       handlerName,
		}).Debug("Sparta CloudFormation custom resource handler info")

		customResourceHandlerDef := gocf.LambdaFunction{
			Code: &gocf.LambdaFunctionCode{
				S3Bucket: gocf.String(S3Bucket),
				S3Key:    gocf.String(S3Key),
			},
			Description: gocf.String(configuratorDescription),
			Handler:     gocf.String(handlerName),
			Role:        iamRoleRef,
			Runtime:     gocf.String(NodeJSVersion),
			Timeout:     gocf.Integer(30),
		}

		cfResource := template.AddResource(subscriberHandlerName, customResourceHandlerDef)
		if nil != dependsOn && (len(dependsOn) > 0) {
			cfResource.DependsOn = append(cfResource.DependsOn, dependsOn...)
		}
	}
	return subscriberHandlerName, nil
}