// 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 }
// 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 }
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, } }
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 }
// 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 }
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")), }, }, } }
// 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 }
// 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 }
// 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 }
// 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") }
// 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 }
// 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 }
// 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 }
// 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 }
// 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 }
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 }
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 }
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 }