github.com/mweagle/Sparta@v1.15.0/decorator/log_aggregator.go (about) 1 package decorator 2 3 import ( 4 "fmt" 5 6 "github.com/aws/aws-sdk-go/aws/session" 7 sparta "github.com/mweagle/Sparta" 8 spartaIAM "github.com/mweagle/Sparta/aws/iam" 9 spartaIAMBuilder "github.com/mweagle/Sparta/aws/iam/builder" 10 gocf "github.com/mweagle/go-cloudformation" 11 "github.com/sirupsen/logrus" 12 ) 13 14 // LogAggregatorAssumePolicyDocument is the document for LogSubscription filters 15 var LogAggregatorAssumePolicyDocument = sparta.ArbitraryJSONObject{ 16 "Version": "2012-10-17", 17 "Statement": []sparta.ArbitraryJSONObject{ 18 { 19 "Action": []string{"sts:AssumeRole"}, 20 "Effect": "Allow", 21 "Principal": sparta.ArbitraryJSONObject{ 22 "Service": []string{ 23 "logs.us-west-2.amazonaws.com", 24 }, 25 }, 26 }, 27 }, 28 } 29 30 /* 31 Inspired by 32 33 https://theburningmonk.com/2018/07/centralised-logging-for-aws-lambda-revised-2018/ 34 35 Create a new LogAggregatorDecorator and then hook up the decorator to the 36 desired lambda functions as in: 37 38 decorator := spartaDecorators.NewLogAggregatorDecorator(kinesisResource, kinesisMapping, loggingRelay) 39 40 // Add the decorator to each function 41 for _, eachLambda := range lambdaFunctions { 42 if eachLambda.Decorators == nil { 43 eachLambda.Decorators = make([]sparta.TemplateDecoratorHandler, 0) 44 } 45 eachLambda.Decorators = append(eachLambda.Decorators, decorator) 46 } 47 48 // Add the decorator to the service 49 workflowHooks.ServiceDecorators = []sparta.ServiceDecoratorHookHandler{decorator} 50 */ 51 52 func logAggregatorResName(baseName string) string { 53 return sparta.CloudFormationResourceName(fmt.Sprintf("LogAggregator%s", baseName), 54 baseName) 55 } 56 57 // LogAggregatorDecorator is the decorator that 58 // satisfies both the ServiceDecoratorHandler and TemplateDecoratorHandler 59 // interfaces. It ensures that each lambda function has a CloudWatch logs 60 // subscription that forwards to a Kinesis stream. That stream is then 61 // subscribed to by the relay lambda function. Only log statements 62 // of level info or higher are published to Kinesis. 63 type LogAggregatorDecorator struct { 64 kinesisStreamResourceName string 65 iamRoleNameResourceName string 66 kinesisResource *gocf.KinesisStream 67 kinesisMapping *sparta.EventSourceMapping 68 logRelay *sparta.LambdaAWSInfo 69 } 70 71 // Ensure compliance 72 var _ sparta.ServiceDecoratorHookHandler = (*LogAggregatorDecorator)(nil) 73 var _ sparta.TemplateDecoratorHandler = (*LogAggregatorDecorator)(nil) 74 75 // KinesisLogicalResourceName returns the name of the Kinesis stream that will be provisioned 76 // by this Decorator 77 func (lad *LogAggregatorDecorator) KinesisLogicalResourceName() string { 78 return lad.kinesisStreamResourceName 79 } 80 81 // DecorateService annotates the service with the Kinesis hook 82 func (lad *LogAggregatorDecorator) DecorateService(context map[string]interface{}, 83 serviceName string, 84 template *gocf.Template, 85 S3Bucket string, 86 S3Key string, 87 buildID string, 88 awsSession *session.Session, 89 noop bool, 90 logger *logrus.Logger) error { 91 92 // Create the Kinesis Stream 93 template.AddResource(lad.kinesisStreamResourceName, lad.kinesisResource) 94 95 // Create the IAM role 96 putRecordPriv := spartaIAMBuilder.Allow("kinesis:PutRecord"). 97 ForResource(). 98 Attr(lad.kinesisStreamResourceName, "Arn"). 99 ToPolicyStatement() 100 passRolePriv := spartaIAMBuilder.Allow("iam:PassRole"). 101 ForResource(). 102 Literal("arn:aws:iam::"). 103 AccountID(":"). 104 Literal("role/"). 105 Literal(lad.iamRoleNameResourceName). 106 ToPolicyStatement() 107 108 statements := make([]spartaIAM.PolicyStatement, 0) 109 statements = append(statements, 110 putRecordPriv, 111 passRolePriv, 112 ) 113 iamPolicyList := gocf.IAMRolePolicyList{} 114 iamPolicyList = append(iamPolicyList, 115 gocf.IAMRolePolicy{ 116 PolicyDocument: sparta.ArbitraryJSONObject{ 117 "Version": "2012-10-17", 118 "Statement": statements, 119 }, 120 PolicyName: gocf.String("LogAggregatorPolicy"), 121 }, 122 ) 123 iamLogAggregatorRole := &gocf.IAMRole{ 124 RoleName: gocf.String(lad.iamRoleNameResourceName), 125 AssumeRolePolicyDocument: LogAggregatorAssumePolicyDocument, 126 Policies: &iamPolicyList, 127 } 128 template.AddResource(lad.iamRoleNameResourceName, iamLogAggregatorRole) 129 130 return nil 131 } 132 133 // DecorateTemplate annotates the lambda with the log forwarding sink info 134 func (lad *LogAggregatorDecorator) DecorateTemplate(serviceName string, 135 lambdaResourceName string, 136 lambdaResource gocf.LambdaFunction, 137 resourceMetadata map[string]interface{}, 138 S3Bucket string, 139 S3Key string, 140 buildID string, 141 template *gocf.Template, 142 context map[string]interface{}, 143 logger *logrus.Logger) error { 144 145 // The relay function should consume the stream 146 if lad.logRelay.LogicalResourceName() == lambdaResourceName { 147 // Need to add a Lambda EventSourceMapping 148 eventSourceMappingResourceName := sparta.CloudFormationResourceName("LogAggregator", 149 "EventSourceMapping", 150 lambdaResourceName) 151 152 template.AddResource(eventSourceMappingResourceName, &gocf.LambdaEventSourceMapping{ 153 StartingPosition: gocf.String(lad.kinesisMapping.StartingPosition), 154 BatchSize: gocf.Integer(lad.kinesisMapping.BatchSize), 155 EventSourceArn: gocf.GetAtt(lad.kinesisStreamResourceName, "Arn"), 156 FunctionName: gocf.GetAtt(lambdaResourceName, "Arn"), 157 }) 158 } else { 159 // The other functions should publish their logs to the stream 160 subscriptionName := logAggregatorResName(fmt.Sprintf("Lambda%s", lambdaResourceName)) 161 subscriptionFilterRes := &gocf.LogsSubscriptionFilter{ 162 DestinationArn: gocf.GetAtt(lad.kinesisStreamResourceName, "Arn"), 163 RoleArn: gocf.GetAtt(lad.iamRoleNameResourceName, "Arn"), 164 LogGroupName: gocf.Join("", 165 gocf.String("/aws/lambda/"), 166 gocf.Ref(lambdaResourceName)), 167 FilterPattern: gocf.String("{$.level = info || $.level = warning || $.level = error }"), 168 } 169 template.AddResource(subscriptionName, subscriptionFilterRes) 170 } 171 return nil 172 } 173 174 // NewLogAggregatorDecorator returns a ServiceDecoratorHook that registers a Kinesis 175 // stream lambda log aggregator 176 func NewLogAggregatorDecorator( 177 kinesisResource *gocf.KinesisStream, 178 kinesisMapping *sparta.EventSourceMapping, 179 relay *sparta.LambdaAWSInfo) *LogAggregatorDecorator { 180 181 return &LogAggregatorDecorator{ 182 kinesisStreamResourceName: logAggregatorResName("Kinesis"), 183 kinesisResource: kinesisResource, 184 kinesisMapping: kinesisMapping, 185 iamRoleNameResourceName: logAggregatorResName("IAMRole"), 186 logRelay: relay, 187 } 188 }