1+ from six import string_types
12from samtranslator .metrics .method_decorator import cw_timer
23from samtranslator .model import ResourceMacro , PropertyType
34from samtranslator .model .eventsources import FUNCTION_EVETSOURCE_METRIC_PREFIX
4- from samtranslator .model .types import is_type , is_str
5+ from samtranslator .model .types import is_type , is_str , list_of
6+ from samtranslator .model .intrinsics import is_intrinsic
57
68from samtranslator .model .lambda_ import LambdaEventSourceMapping
79from samtranslator .translator .arn_generator import ArnGenerator
@@ -20,6 +22,7 @@ class PullEventSource(ResourceMacro):
2022 """
2123
2224 resource_type = None
25+ requires_stream_queue_broker = True
2326 property_types = {
2427 "Stream" : PropertyType (False , is_str ()),
2528 "Queue" : PropertyType (False , is_str ()),
@@ -39,6 +42,7 @@ class PullEventSource(ResourceMacro):
3942 "SecretsManagerKmsKeyId" : PropertyType (False , is_str ()),
4043 "TumblingWindowInSeconds" : PropertyType (False , is_type (int )),
4144 "FunctionResponseTypes" : PropertyType (False , is_type (list )),
45+ "KafkaBootstrapServers" : PropertyType (False , is_type (list )),
4246 }
4347
4448 def get_policy_arn (self ):
@@ -74,7 +78,7 @@ def to_cloudformation(self, **kwargs):
7478 except NotImplementedError :
7579 function_name_or_arn = function .get_runtime_attr ("arn" )
7680
77- if not self .Stream and not self .Queue and not self .Broker :
81+ if self . requires_stream_queue_broker and not self .Stream and not self .Queue and not self .Broker :
7882 raise InvalidEventException (
7983 self .relative_id ,
8084 "No Queue (for SQS) or Stream (for Kinesis, DynamoDB or MSK) or Broker (for Amazon MQ) provided." ,
@@ -99,6 +103,11 @@ def to_cloudformation(self, **kwargs):
99103 lambda_eventsourcemapping .TumblingWindowInSeconds = self .TumblingWindowInSeconds
100104 lambda_eventsourcemapping .FunctionResponseTypes = self .FunctionResponseTypes
101105
106+ if self .KafkaBootstrapServers :
107+ lambda_eventsourcemapping .SelfManagedEventSource = {
108+ "Endpoints" : {"KafkaBootstrapServers" : self .KafkaBootstrapServers }
109+ }
110+
102111 destination_config_policy = None
103112 if self .DestinationConfig :
104113 # `Type` property is for sam to attach the right policies
@@ -286,3 +295,149 @@ def get_policy_statements(self):
286295 }
287296 document ["PolicyDocument" ]["Statement" ].append (kms_policy )
288297 return [document ]
298+
299+
300+ class SelfManagedKafka (PullEventSource ):
301+ """
302+ SelfManagedKafka event source
303+ """
304+
305+ resource_type = "SelfManagedKafka"
306+ requires_stream_queue_broker = False
307+ AUTH_MECHANISM = ["SASL_SCRAM_256_AUTH" , "SASL_SCRAM_512_AUTH" , "BASIC_AUTH" ]
308+
309+ def get_policy_arn (self ):
310+ return None
311+
312+ def get_policy_statements (self ):
313+ if not self .KafkaBootstrapServers :
314+ raise InvalidEventException (
315+ self .relative_id ,
316+ "No KafkaBootstrapServers provided for self managed kafka as an event source" ,
317+ )
318+
319+ if not self .Topics :
320+ raise InvalidEventException (
321+ self .relative_id ,
322+ "No Topics provided for self managed kafka as an event source" ,
323+ )
324+
325+ if len (self .Topics ) != 1 :
326+ raise InvalidEventException (
327+ self .relative_id ,
328+ "Topics for self managed kafka only supports single configuration entry." ,
329+ )
330+
331+ if not self .SourceAccessConfigurations :
332+ raise InvalidEventException (
333+ self .relative_id ,
334+ "No SourceAccessConfigurations for self managed kafka event provided." ,
335+ )
336+ document = self .generate_policy_document ()
337+ return [document ]
338+
339+ def generate_policy_document (self ):
340+ statements = []
341+ authentication_uri , has_vpc_config = self .get_secret_key ()
342+ if authentication_uri :
343+ secret_manager = self .get_secret_manager_secret (authentication_uri )
344+ statements .append (secret_manager )
345+
346+ if has_vpc_config :
347+ vpc_permissions = self .get_vpc_permission ()
348+ statements .append (vpc_permissions )
349+
350+ if self .SecretsManagerKmsKeyId :
351+ kms_policy = self .get_kms_policy ()
352+ statements .append (kms_policy )
353+
354+ document = {
355+ "PolicyDocument" : {
356+ "Statement" : statements ,
357+ "Version" : "2012-10-17" ,
358+ },
359+ "PolicyName" : "SelfManagedKafkaExecutionRolePolicy" ,
360+ }
361+
362+ return document
363+
364+ def get_secret_key (self ):
365+ authentication_uri = None
366+ has_vpc_subnet = False
367+ has_vpc_security_group = False
368+ for config in self .SourceAccessConfigurations :
369+ if config .get ("Type" ) == "VPC_SUBNET" :
370+ self .validate_uri (config , "VPC_SUBNET" )
371+ has_vpc_subnet = True
372+
373+ elif config .get ("Type" ) == "VPC_SECURITY_GROUP" :
374+ self .validate_uri (config , "VPC_SECURITY_GROUP" )
375+ has_vpc_security_group = True
376+
377+ elif config .get ("Type" ) in self .AUTH_MECHANISM :
378+ if authentication_uri :
379+ raise InvalidEventException (
380+ self .relative_id ,
381+ "Multiple auth mechanism properties specified in SourceAccessConfigurations for self managed kafka event." ,
382+ )
383+ self .validate_uri (config , "auth mechanism" )
384+ authentication_uri = config .get ("URI" )
385+
386+ else :
387+ raise InvalidEventException (
388+ self .relative_id ,
389+ "Invalid SourceAccessConfigurations Type provided for self managed kafka event." ,
390+ )
391+
392+ if (not has_vpc_subnet and has_vpc_security_group ) or (has_vpc_subnet and not has_vpc_security_group ):
393+ raise InvalidEventException (
394+ self .relative_id ,
395+ "VPC_SUBNET and VPC_SECURITY_GROUP in SourceAccessConfigurations for SelfManagedKafka must be both provided." ,
396+ )
397+ return authentication_uri , (has_vpc_subnet and has_vpc_security_group )
398+
399+ def validate_uri (self , config , msg ):
400+ if not config .get ("URI" ):
401+ raise InvalidEventException (
402+ self .relative_id ,
403+ "No {} URI property specified in SourceAccessConfigurations for self managed kafka event." .format (msg ),
404+ )
405+
406+ if not isinstance (config .get ("URI" ), string_types ) and not is_intrinsic (config .get ("URI" )):
407+ raise InvalidEventException (
408+ self .relative_id ,
409+ "Wrong Type for {} URI property specified in SourceAccessConfigurations for self managed kafka event." .format (
410+ msg
411+ ),
412+ )
413+
414+ def get_secret_manager_secret (self , authentication_uri ):
415+ return {
416+ "Action" : ["secretsmanager:GetSecretValue" ],
417+ "Effect" : "Allow" ,
418+ "Resource" : authentication_uri ,
419+ }
420+
421+ def get_vpc_permission (self ):
422+ return {
423+ "Action" : [
424+ "ec2:CreateNetworkInterface" ,
425+ "ec2:DescribeNetworkInterfaces" ,
426+ "ec2:DeleteNetworkInterface" ,
427+ "ec2:DescribeVpcs" ,
428+ "ec2:DescribeSubnets" ,
429+ "ec2:DescribeSecurityGroups" ,
430+ ],
431+ "Effect" : "Allow" ,
432+ "Resource" : "*" ,
433+ }
434+
435+ def get_kms_policy (self ):
436+ return {
437+ "Action" : ["kms:Decrypt" ],
438+ "Effect" : "Allow" ,
439+ "Resource" : {
440+ "Fn::Sub" : "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/"
441+ + self .SecretsManagerKmsKeyId
442+ },
443+ }
0 commit comments