Exemplo n.º 1
0
// NewServer creates a new *Server with a router and its routes registered
func NewServer(opts *Options, log *logrus.Logger, md metadata.LookupSaver) (*Server, error) {
	var err error

	log.Debug("creating new server")
	srv := &Server{
		opts: opts,
		log:  log,
	}

	srv.setupRouter()
	srv.setupNegroni()

	err = srv.getMd(md)
	if err != nil {
		return nil, err
	}

	err = srv.setupStorer()
	if err != nil {
		return nil, err
	}

	err = srv.setupAuther()
	if err != nil {
		return nil, err
	}

	return srv, nil
}
Exemplo n.º 2
0
// Execute creates an HTTP listener to dispatch execution. Typically
// called via Main() via command line arguments.
func Execute(lambdaAWSInfos []*LambdaAWSInfo, port int, parentProcessPID int, logger *logrus.Logger) error {
	if port <= 0 {
		port = defaultHTTPPort
	}

	server := &http.Server{
		Addr:         fmt.Sprintf(":%d", port),
		Handler:      NewLambdaHTTPHandler(lambdaAWSInfos, logger),
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	if 0 != parentProcessPID {
		logger.Debug("Sending SIGUSR2 to parent process: ", parentProcessPID)
		syscall.Kill(parentProcessPID, syscall.SIGUSR2)
	}
	logger.WithFields(logrus.Fields{
		"URL": fmt.Sprintf("http://localhost:%d", port),
	}).Info("Starting Sparta server")

	err := server.ListenAndServe()
	if err != nil {
		logger.WithFields(logrus.Fields{
			"Error": err.Error(),
		}).Error("Failed to launch server")
		return err
	}

	return nil
}
Exemplo n.º 3
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,
	}
}
Exemplo n.º 4
0
func ensureConfiguratorLambdaResource(awsPrincipalName string, sourceArn string, resources ArbitraryJSONObject, S3Bucket string, S3Key string, logger *logrus.Logger) (string, error) {
	// AWS service basename
	awsServiceName := awsPrincipalToService(awsPrincipalName)
	configuratorExportName := strings.ToLower(awsServiceName)

	//////////////////////////////////////////////////////////////////////////////
	// IAM Role definition
	// TODO - Check sourceArn for equivalence
	iamResourceName, err := ensureIAMRoleResource(awsPrincipalName, sourceArn, resources, logger)
	if nil != err {
		return "", err
	}

	iamRoleRef := ArbitraryJSONObject{
		"Fn::GetAtt": []string{iamResourceName, "Arn"},
	}
	// Custom handler resource for this service type
	subscriberHandlerName := fmt.Sprintf("%sSubscriber", awsServiceName)
	_, exists := resources[subscriberHandlerName]
	if !exists {
		logger.Info("Creating Subscription Lambda Resource for AWS service: ", awsServiceName)

		//////////////////////////////////////////////////////////////////////////////
		// Custom Resource Lambda Handler
		// NOTE: This brittle function name has an analog in ./resources/index.js b/c the
		// AWS Lamba execution treats the entire ZIP file as a module.  So all module exports
		// need to be forwarded through the module's index.js file.
		handlerName := fmt.Sprintf("index.%sConfiguration", configuratorExportName)
		logger.Debug("Lambda Configuration handler: ", handlerName)

		customResourceHandlerDef := ArbitraryJSONObject{
			"Type": "AWS::Lambda::Function",
			"Properties": ArbitraryJSONObject{
				"Code": ArbitraryJSONObject{
					"S3Bucket": S3Bucket,
					"S3Key":    S3Key,
				},
				"Role":    iamRoleRef,
				"Handler": handlerName,
				"Runtime": "nodejs",
				"Timeout": "30",
			},
		}
		resources[subscriberHandlerName] = customResourceHandlerDef
	}
	return subscriberHandlerName, nil
}
Exemplo n.º 5
0
// Does a given stack exist?
func stackExists(stackNameOrID string, cf *cloudformation.CloudFormation, logger *logrus.Logger) (bool, error) {
	describeStacksInput := &cloudformation.DescribeStacksInput{
		StackName: aws.String(stackNameOrID),
	}
	describeStacksOutput, err := cf.DescribeStacks(describeStacksInput)
	logger.Debug("DescribeStackOutput: ", describeStacksOutput)
	exists := false
	if err != nil {
		logger.Info("DescribeStackOutputError: ", err)
		// If the stack doesn't exist, then no worries
		if strings.Contains(err.Error(), "does not exist") {
			exists = false
		} else {
			return false, err
		}
	} else {
		exists = true
	}
	return exists, nil
}
Exemplo n.º 6
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")),
			},
		},
	}
}
Exemplo n.º 7
0
// Execute creates an HTTP listener to dispatch execution. Typically
// called via Main() via command line arguments.
func Execute(lambdaAWSInfos []*LambdaAWSInfo, port int, parentProcessPID int, logger *logrus.Logger) error {
	if port <= 0 {
		port = defaultHTTPPort
	}
	logger.Info("Execute!")

	lookupMap := make(dispatchMap, 0)
	for _, eachLambdaInfo := range lambdaAWSInfos {
		lookupMap[eachLambdaInfo.lambdaFnName] = eachLambdaInfo
	}
	server := &http.Server{
		Addr:         fmt.Sprintf(":%d", port),
		Handler:      &lambdaHandler{lookupMap, logger},
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	if 0 != parentProcessPID {
		logger.Debug("Sending SIGUSR2 to parent process: ", parentProcessPID)
		syscall.Kill(parentProcessPID, syscall.SIGUSR2)
	}
	logger.Debug("Binding to port: ", port)
	err := server.ListenAndServe()
	if err != nil {
		logger.Error("FAILURE: " + err.Error())
		return err
	}
	logger.Debug("Server available at: ", port)
	return nil
}
Exemplo n.º 8
0
// NewS3Store initializes an *S3Store.  Wow!
func NewS3Store(key, secret, bucket, regionName string,
	log *logrus.Logger, md metadata.LookupSaver) (*S3Store, error) {

	log.Debug("getting aws auth")
	auth, err := aws.GetAuth(key, secret)
	if err != nil {
		log.WithField("err", err).Error("failed to get auth")
		return nil, err
	}

	region, ok := aws.Regions[regionName]
	if !ok {
		log.WithFields(logrus.Fields{
			"region": regionName,
		}).Warn(fmt.Sprintf("nonexistent region, falling back to %s", aws.USEast.Name))
		region = aws.USEast
	}

	log.Debug("getting new s3 connection")
	s3Conn := s3.New(auth, region)
	b := s3Conn.Bucket(bucket)

	if b == nil || b.Name == "" {
		return nil, errNoBucket
	}

	log.WithFields(logrus.Fields{
		"bucket": b.Name,
	}).Debug("got back this bucket")

	return &S3Store{
		key:    key,
		secret: secret,
		bucket: bucket,

		log: log,
		md:  md,
		b:   b,
	}, nil
}
Exemplo n.º 9
0
// Returns an IAM::Role policy entry for this definition
func (roleDefinition *IAMRoleDefinition) rolePolicy(eventSourceMappings []*lambda.CreateEventSourceMappingInput, logger *logrus.Logger) ArbitraryJSONObject {
	statements := CommonIAMStatements["core"]
	for _, eachPrivilege := range roleDefinition.Privileges {
		statements = append(statements, ArbitraryJSONObject{
			"Effect":   "Allow",
			"Action":   eachPrivilege.Actions,
			"Resource": eachPrivilege.Resource,
		})
	}

	// // 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"] = *eachEventSourceMapping.EventSourceArn
			}
		}
	}
	iamPolicy := ArbitraryJSONObject{"Type": "AWS::IAM::Role",
		"Properties": ArbitraryJSONObject{
			"AssumeRolePolicyDocument": AssumePolicyDocument,
			"Policies": []ArbitraryJSONObject{
				{
					"PolicyName": CloudFormationResourceName("LambdaPolicy"),
					"PolicyDocument": ArbitraryJSONObject{
						"Version":   "2012-10-17",
						"Statement": statements,
					},
				},
			},
		},
	}
	return iamPolicy
}
Exemplo n.º 10
0
// MigratorMain is the entry point for the "migrate" cli command
func MigratorMain(log *logrus.Logger) {
	opts := NewOptions()
	if opts.Debug {
		log.Level = logrus.DebugLevel
	}

	log.Debug("spinning up database")

	db, err := metadata.NewDatabase(opts.DatabaseURL, log)
	if err != nil {
		log.Fatal(err)
	}

	log.Debug("migrating")

	err = db.Migrate(log)
	if err != nil {
		log.Fatal(err)
	}

	log.Info("database migration complete")
}
Exemplo n.º 11
0
// Execute creates an HTTP listener to dispatch execution. Typically
// called via Main() via command line arguments.
func Execute(lambdaAWSInfos []*LambdaAWSInfo, port int, parentProcessPID int, logger *logrus.Logger) error {
	if port <= 0 {
		port = defaultHTTPPort
	}

	server := &http.Server{
		Addr:         fmt.Sprintf(":%d", port),
		Handler:      NewLambdaHTTPHandler(lambdaAWSInfos, logger),
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	if 0 != parentProcessPID {
		logger.Debug("Sending SIGUSR2 to parent process: ", parentProcessPID)
		syscall.Kill(parentProcessPID, syscall.SIGUSR2)
	}
	logger.Debug("Binding to port: ", port)
	err := server.ListenAndServe()
	if err != nil {
		logger.Error("FAILURE: " + err.Error())
		return err
	}
	logger.Debug("Server available at: ", port)
	return nil
}
Exemplo n.º 12
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,
	roleNameMap map[string]*gocf.StringExpr,
	template *gocf.Template,
	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()
		dependsOn = append(dependsOn, info.RoleDefinition.logicalName())
	}
	lambdaDescription := info.Options.Description
	if "" == lambdaDescription {
		lambdaDescription = fmt.Sprintf("%s: %s", serviceName, info.lambdaFnName)
	}

	// 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("nodejs"),
		Timeout:     gocf.Integer(info.Options.Timeout),
	}
	cfResource := template.AddResource(info.logicalName(), lambdaResource)
	cfResource.DependsOn = append(cfResource.DependsOn, dependsOn...)
	safeMetadataInsert(cfResource, "golangFunc", info.lambdaFnName)

	// 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.logicalName(),
			template,
			S3Bucket,
			S3Key,
			logger)
		if nil != err {
			return err
		}
	}

	// Event Source Mappings
	hash := sha1.New()
	for _, eachEventSourceMapping := range info.EventSourceMappings {
		eventSourceMappingResource := gocf.LambdaEventSourceMapping{
			EventSourceArn:   gocf.String(eachEventSourceMapping.EventSourceArn),
			FunctionName:     functionAttr,
			StartingPosition: gocf.String(eachEventSourceMapping.StartingPosition),
			BatchSize:        gocf.Integer(eachEventSourceMapping.BatchSize),
			Enabled:          gocf.Bool(!eachEventSourceMapping.Disabled),
		}

		hash.Write([]byte(eachEventSourceMapping.EventSourceArn))
		binary.Write(hash, binary.LittleEndian, eachEventSourceMapping.BatchSize)
		hash.Write([]byte(eachEventSourceMapping.StartingPosition))
		resourceName := fmt.Sprintf("LambdaES%s", hex.EncodeToString(hash.Sum(nil)))
		template.AddResource(resourceName, eventSourceMappingResource)
	}

	// Decorator
	if nil != info.Decorator {
		logger.Debug("Decorator found for Lambda: ", info.lambdaFnName)
		// Create an empty template so that we can track whether things
		// are overwritten
		decoratorProxyTemplate := gocf.NewTemplate()
		err := info.Decorator(info.logicalName(),
			lambdaResource,
			decoratorProxyTemplate,
			logger)
		if nil != err {
			return err
		}
		// Append the custom resources
		err = safeMergeTemplates(decoratorProxyTemplate, template, logger)
		if nil != err {
			return fmt.Errorf("Lambda (%s) decorator created conflicting resources", info.lambdaFnName)
		}
	}
	return nil
}
Exemplo n.º 13
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,
	roleNameMap map[string]interface{},
	resources ArbitraryJSONObject,
	outputs ArbitraryJSONObject,
	logger *logrus.Logger) error {

	// If we have RoleName, then get the ARN, otherwise get the Ref
	var dependsOn []string

	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 existing.
	if iamRoleArnName == "" {
		iamRoleArnName = info.RoleDefinition.logicalName()
		dependsOn = append(dependsOn, iamRoleArnName)
	}
	lambdaDescription := info.Options.Description
	if "" == lambdaDescription {
		lambdaDescription = fmt.Sprintf("%s: %s", serviceName, info.lambdaFnName)
	}
	// Create the primary resource
	primaryResource := ArbitraryJSONObject{
		"Type": "AWS::Lambda::Function",
		"Properties": ArbitraryJSONObject{
			"Code": ArbitraryJSONObject{
				"S3Bucket": S3Bucket,
				"S3Key":    S3Key,
			},
			"Description": lambdaDescription,
			"Handler":     fmt.Sprintf("index.%s", info.jsHandlerName()),
			"MemorySize":  info.Options.MemorySize,
			"Role":        roleNameMap[iamRoleArnName],
			"Runtime":     "nodejs",
			"Timeout":     info.Options.Timeout,
		},
		"DependsOn": dependsOn,
		"Metadata": ArbitraryJSONObject{
			"golangFunc": info.lambdaFnName,
		},
	}

	// Get the resource name we're going to use s.t. we can tie it to the rest of the
	// lambda definition
	resourceName := info.logicalName()
	resources[resourceName] = primaryResource

	// Create the lambda Ref in case we need a permission or event mapping
	functionAttr := ArbitraryJSONObject{
		"Fn::GetAtt": []string{resourceName, "Arn"},
	}

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

	// Event Source Mappings
	// TODO: verify that the event source ARN actually exists.
	for _, eachEventSourceMapping := range info.EventSourceMappings {
		properties := ArbitraryJSONObject{
			"EventSourceArn":   eachEventSourceMapping.EventSourceArn,
			"FunctionName":     functionAttr,
			"StartingPosition": eachEventSourceMapping.StartingPosition,
			"BatchSize":        eachEventSourceMapping.BatchSize,
		}
		if nil != eachEventSourceMapping.Enabled {
			properties["Enabled"] = *eachEventSourceMapping.Enabled
		}

		primaryEventSourceMapping := ArbitraryJSONObject{
			"Type":       "AWS::Lambda::EventSourceMapping",
			"DependsOn":  dependsOn,
			"Properties": properties,
		}
		hash := sha1.New()
		hash.Write([]byte(*eachEventSourceMapping.EventSourceArn))
		binary.Write(hash, binary.LittleEndian, *eachEventSourceMapping.BatchSize)
		hash.Write([]byte(*eachEventSourceMapping.StartingPosition))
		resourceName := fmt.Sprintf("LambdaES%s", hex.EncodeToString(hash.Sum(nil)))
		resources[resourceName] = primaryEventSourceMapping
	}

	// Decorator
	if nil != info.Decorator {
		logger.Debug("Decorator found for Lambda: ", info.lambdaFnName)
		lambdaResources := make(ArbitraryJSONObject, 0)
		lambdaOutputs := make(ArbitraryJSONObject, 0)
		err := info.Decorator(resourceName, primaryResource, lambdaResources, lambdaOutputs, logger)
		if nil != err {
			return err
		}
		// Append the custom resources
		for eachKey, eachLambdaResource := range lambdaResources {
			_, exists := resources[eachKey]
			if exists {
				errorMsg := fmt.Sprintf("Duplicate CloudFormation resource name (%s) defined by Lambda: %s",
					eachKey,
					info.lambdaFnName)
				return errors.New(errorMsg)
			}
			resources[eachKey] = eachLambdaResource
		}
		// Append the custom outputs
		for eachKey, eachLambdaOutput := range lambdaOutputs {
			_, exists := outputs[eachKey]
			if exists {
				errorMsg := fmt.Sprintf("Duplicate CloudFormation output key name (%s) defined by Lambda: %s",
					eachKey,
					info.lambdaFnName)
				return errors.New(errorMsg)
			}
			outputs[eachKey] = eachLambdaOutput
		}
	}

	return nil
}
Exemplo n.º 14
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
}
Exemplo n.º 15
0
// TODO - Refactor ensure Lambdaconfigurator, then finish
// implementing the CloudWatchEvents Principal type.
func ensureConfiguratorLambdaResource(awsPrincipalName string,
	sourceArn *gocf.StringExpr,
	dependsOn []string,
	template *gocf.Template,
	S3Bucket string,
	S3Key string,
	logger *logrus.Logger) (string, error) {

	// AWS service basename
	awsServiceName := awsPrincipalToService(awsPrincipalName)
	configuratorExportName := strings.ToLower(awsServiceName)

	logger.WithFields(logrus.Fields{
		"ServiceName":      awsServiceName,
		"NodeJSExportName": configuratorExportName,
	}).Debug("Ensuring AWS push service configurator CustomResource")

	// 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":   awsPrincipalName,
		"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(awsPrincipalName, sourceArn, template, logger)
	if nil != err {
		return "", err
	}
	iamRoleRef := gocf.GetAtt(iamResourceName, "Arn")
	_, exists := template.Resources[subscriberHandlerName]
	if !exists {
		logger.WithFields(logrus.Fields{
			"Service": awsServiceName,
		}).Debug("Including Lambda CustomResource for AWS Service")

		configuratorDescription := fmt.Sprintf("Sparta created Lambda CustomResource to configure %s service",
			awsServiceName)

		//////////////////////////////////////////////////////////////////////////////
		// Custom Resource Lambda Handler
		// NOTE: This brittle function name has an analog in ./resources/index.js b/c the
		// AWS Lamba execution treats the entire ZIP file as a module.  So all module exports
		// need to be forwarded through the module's index.js file.
		handlerName := nodeJSHandlerName(configuratorExportName)
		logger.Debug("Lambda Configuration handler: ", handlerName)

		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("nodejs"),
			Timeout:     gocf.Integer(30),
		}
		cfResource := template.AddResource(subscriberHandlerName, customResourceHandlerDef)
		if nil != dependsOn && (len(dependsOn) > 0) {
			cfResource.DependsOn = append(cfResource.DependsOn, dependsOn...)
		}

	}
	return subscriberHandlerName, nil
}
Exemplo n.º 16
0
func ensureConfiguratorLambdaResource(awsPrincipalName string,
	sourceArn interface{},
	resources ArbitraryJSONObject,
	S3Bucket string,
	S3Key string,
	logger *logrus.Logger) (string, error) {

	// AWS service basename
	awsServiceName := awsPrincipalToService(awsPrincipalName)
	configuratorExportName := strings.ToLower(awsServiceName)

	// Create a unique name that we can use for the configuration info
	keyName, err := json.Marshal(ArbitraryJSONObject{
		"Principal": awsPrincipalName,
		"Arn":       sourceArn,
	})
	if err != nil {
		logger.Error("Failed to create configurator resource name: ", err.Error())
		return "", err
	}
	subscriberHandlerName := CloudFormationResourceName(fmt.Sprintf("%sSubscriber", awsServiceName), string(keyName))

	//////////////////////////////////////////////////////////////////////////////
	// IAM Role definition
	principalActions, exists := PushSourceConfigurationActions[awsPrincipalName]
	if !exists {
		return "", fmt.Errorf("Unsupported principal for IAM role creation: %s", awsPrincipalName)
	}
	// Create a Role that enables this resource management
	iamResourceName, err := ensureIAMRoleResource(principalActions, sourceArn, resources, logger)
	if nil != err {
		return "", err
	}
	iamRoleRef := ArbitraryJSONObject{
		"Fn::GetAtt": []string{iamResourceName, "Arn"},
	}
	_, exists = resources[subscriberHandlerName]
	if !exists {
		logger.WithFields(logrus.Fields{
			"Service": awsServiceName,
		}).Info("Creating configuration Lambda for AWS service")

		//////////////////////////////////////////////////////////////////////////////
		// Custom Resource Lambda Handler
		// NOTE: This brittle function name has an analog in ./resources/index.js b/c the
		// AWS Lamba execution treats the entire ZIP file as a module.  So all module exports
		// need to be forwarded through the module's index.js file.
		handlerName := nodeJSHandlerName(configuratorExportName)
		logger.Debug("Lambda Configuration handler: ", handlerName)

		customResourceHandlerDef := ArbitraryJSONObject{
			"Type": "AWS::Lambda::Function",
			"Properties": ArbitraryJSONObject{
				"Code": ArbitraryJSONObject{
					"S3Bucket": S3Bucket,
					"S3Key":    S3Key,
				},
				"Role":    iamRoleRef,
				"Handler": handlerName,
				"Runtime": "nodejs",
				"Timeout": "30",
			},
		}
		resources[subscriberHandlerName] = customResourceHandlerDef
	}
	return subscriberHandlerName, nil
}
Exemplo n.º 17
0
func ensureIAMRoleResource(awsPrincipalName string, sourceArn string, resources ArbitraryJSONObject, logger *logrus.Logger) (string, error) {
	principalActions, exists := PushSourceConfigurationActions[awsPrincipalName]
	if !exists {
		return "", fmt.Errorf("Unsupported principal for IAM role creation: %s", awsPrincipalName)
	}

	hash := sha1.New()
	hash.Write([]byte(fmt.Sprintf("%s%s", awsPrincipalName, salt)))
	roleName := fmt.Sprintf("ConfigIAMRole%s", hex.EncodeToString(hash.Sum(nil)))

	logger.WithFields(logrus.Fields{
		"PrincipalActions": principalActions,
		"Principal":        awsPrincipalName,
	}).Debug("Ensuring IAM Role results")

	existingResource, exists := resources[roleName]

	// If it exists, make sure these permissions are enabled on it...
	if exists {
		statementExists := false
		properties := existingResource.(ArbitraryJSONObject)["Properties"]
		policies := properties.(ArbitraryJSONObject)["Policies"]
		for _, eachPolicy := range policies.([]ArbitraryJSONObject) {
			statements := eachPolicy["PolicyDocument"].(ArbitraryJSONObject)["Statement"]
			for _, eachStatement := range statements.([]ArbitraryJSONObject) {
				if eachStatement["Resource"] == sourceArn {
					statementExists = true
				}
			}
		}
		if !statementExists {
			properties := existingResource.(ArbitraryJSONObject)["Properties"]
			policies := properties.(ArbitraryJSONObject)["Policies"]
			rootPolicy := policies.([]ArbitraryJSONObject)[0]
			policyDocument := rootPolicy["PolicyDocument"].(ArbitraryJSONObject)
			statements := policyDocument["Statement"].([]ArbitraryJSONObject)
			policyDocument["Statement"] = append(statements, ArbitraryJSONObject{
				"Effect":   "Allow",
				"Action":   principalActions,
				"Resource": sourceArn,
			})
		}
		logger.Debug("Using prexisting IAM Role: " + roleName)
		return roleName, nil
	}

	// Create a new IAM Role resource
	logger.WithFields(logrus.Fields{
		"RoleName": roleName,
		"Actions":  principalActions,
	}).Debug("Inserting IAM Role")

	// Provision a new one and add it...
	statements := CommonIAMStatements["core"]
	statements = append(statements, ArbitraryJSONObject{
		"Effect":   "Allow",
		"Action":   principalActions,
		"Resource": sourceArn,
	})

	iamPolicy := ArbitraryJSONObject{"Type": "AWS::IAM::Role",
		"Properties": ArbitraryJSONObject{
			"AssumeRolePolicyDocument": AssumePolicyDocument,
			"Policies": []ArbitraryJSONObject{
				{
					"PolicyName": fmt.Sprintf("Configurator%s", CloudFormationResourceName(awsPrincipalName)),
					"PolicyDocument": ArbitraryJSONObject{
						"Version":   "2012-10-17",
						"Statement": statements,
					},
				},
			},
		},
	}
	resources[roleName] = iamPolicy
	return roleName, nil
}
Exemplo n.º 18
0
func StampImage(reader io.Reader, logger *logrus.Logger) (io.ReadSeeker, error) {

	target, imageType, err := image.Decode(reader)
	if err != nil {
		logger.WithFields(logrus.Fields{
			"Error": err,
		}).Info("Failed to decode image")
		return nil, err
	}

	// Pick the longer edge and a reasonably sized stamp
	maxEdge := math.Max(float64(target.Bounds().Max.X), float64(target.Bounds().Max.Y))
	edgeLog := int(math.Floor(math.Log2(maxEdge))) - 1
	logger.WithFields(logrus.Fields{
		"ImageType":    imageType,
		"MaxEdge":      maxEdge,
		"EdgeLog":      edgeLog,
		"TargetBounds": target.Bounds(),
	}).Info("Target Dimensions")
	watermarkSuffix := int(math.Max(32, math.Pow(2, math.Min(float64(8), float64(edgeLog)))))
	resourceName := watermarkName(watermarkSuffix)
	logger.Info("Watermark resource: ", resourceName)

	byteSource, err := assets.FSByte(false, resourceName)
	if err != nil {
		logger.WithFields(logrus.Fields{
			"Error":        err,
			"Name":         resourceName,
			"TargetBounds": target.Bounds(),
		}).Warn("Failed to load computed watermark. Falling to default.")
		byteSource = assets.FSMustByte(false, watermarkName(16))
	}
	stampReader := bytes.NewReader(byteSource)
	stamp, _, err := image.Decode(stampReader)
	if err != nil {
		logger.WithFields(logrus.Fields{
			"Error": err,
		}).Info("Failed to load stamp image")
		return nil, err
	}

	// Save it...
	compositedImage := image.NewRGBA(image.Rect(0, 0, target.Bounds().Max.X, target.Bounds().Max.Y))
	draw.Draw(compositedImage, compositedImage.Bounds(), target, image.Point{0, 0}, draw.Src)

	// Bottom right corner
	targetRect := target.Bounds()
	targetRect.Min.X = (targetRect.Max.X - stamp.Bounds().Max.X)
	targetRect.Min.Y = (targetRect.Max.Y - stamp.Bounds().Max.Y)

	logger.WithFields(logrus.Fields{
		"TargetBounds": target.Bounds(),
		"StampBounds":  stamp.Bounds(),
		"TargetRect":   targetRect,
	}).Info("Drawing")

	logger.Debug("Composing image")
	draw.Draw(compositedImage, targetRect, stamp, image.Point{0, 0}, draw.Over)
	buf := new(bytes.Buffer)
	err = png.Encode(buf, compositedImage)
	return bytes.NewReader(buf.Bytes()), nil
}