Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stelligent/cfn_nag#74 Reworking Serverless transform to more closely match how SAM transforms templates #64

Merged
2 commits merged into from
Feb 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 65 additions & 10 deletions lib/cfn-model/transforms/serverless.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ def serverless_function_property(serverless_function, cfn_hash, property_name)
resolve_globals_function_property(cfn_hash, property_name)
end

def format_function_role(serverless_function, function_name)
getatt_hash = { 'Fn::GetAtt' => %W[#{function_name}Role Arn] }
serverless_function['Properties']['Role'] || getatt_hash
end

# i question whether we need to carry out the transform this far given cfn_nag
# likely won't ever opine on bucket names or object keys
def transform_code_uri(lambda_fn_params, code_uri)
Expand All @@ -81,11 +86,12 @@ def transform_code_uri(lambda_fn_params, code_uri)
lambda_fn_params
end

def serverless_function_properties(cfn_hash, serverless_function, with_line_numbers)
def serverless_function_properties(cfn_hash, serverless_function, fn_name, with_line_numbers)
code_uri = serverless_function_property(serverless_function, cfn_hash, 'CodeUri')

lambda_fn_params = {
handler: serverless_function_property(serverless_function, cfn_hash, 'Handler'),
role: format_function_role(serverless_function, fn_name),
runtime: serverless_function_property(serverless_function, cfn_hash, 'Runtime'),
with_line_numbers: with_line_numbers
}
Expand All @@ -101,10 +107,18 @@ def serverless_function_properties(cfn_hash, serverless_function, with_line_numb
def replace_serverless_function(cfn_hash, resource_name, with_line_numbers)
serverless_function = cfn_hash['Resources'][resource_name]

lambda_fn_params = serverless_function_properties(cfn_hash, serverless_function, with_line_numbers)
lambda_fn_params = serverless_function_properties(cfn_hash,
serverless_function,
resource_name,
with_line_numbers)

cfn_hash['Resources'][resource_name] = lambda_function lambda_fn_params
cfn_hash['Resources']['FunctionNameRole'] = function_name_role with_line_numbers

unless serverless_function['Properties']['Role']
cfn_hash['Resources'][resource_name + 'Role'] = function_role(serverless_function,
resource_name,
with_line_numbers)
end

transform_function_events(cfn_hash, serverless_function, resource_name, with_line_numbers) if \
serverless_function['Properties']['Events']
Expand All @@ -123,18 +137,58 @@ def lambda_service_can_assume_role
}
end

# Return the hash structure of the 'FunctionNameRole'
# Return the hash structure of the '<function_name>Role'
# AWS::IAM::Role resource as created by Serverless transform
def function_name_role(with_line_numbers)
{
def function_role(serverless_function, function_name, with_line_numbers)
fn_role = {
'Type' => format_resource_type('AWS::IAM::Role', -1, with_line_numbers),
'Properties' => {
'ManagedPolicyArns' => [
'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
],
'ManagedPolicyArns' => function_role_managed_policies(serverless_function['Properties']),
'AssumeRolePolicyDocument' => lambda_service_can_assume_role
}
}
function_role_policies(fn_role, serverless_function['Properties'], function_name)
fn_role
end

def function_role_managed_policies(function_properties)
# Always set AWSLambdaBasicExecutionRole policy
base_policies = %w[arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]

# Return base_policies if no policies assigned to the function
return base_policies unless function_properties['Policies']

# If the SAM function Policies property is a string, append and return
return base_policies | %W[arn:aws:iam::aws:policy/#{function_properties['Policies']}] if \
function_properties['Policies'].is_a? String

# Iterate on Policies property and add if String
policy_names = function_properties['Policies'].select { |policy| policy.is_a? String }
base_policies | policy_names.map { |name| "arn:aws:iam::aws:policy/#{name}" }
end

def function_role_policies(role, function_properties, fn_name)
# Return if no policies assigned to the function
return unless function_properties['Policies']

# Process inline policies from SAM function
return if function_properties['Policies'].is_a? String

# Iterate on Policies property and add if Hash
policy_hashes = function_properties['Policies'].select do |policy|
policy.is_a?(Hash) && policy.keys.first !~ /Policy/
end
return if policy_hashes.empty?

# Create policy documents
policy_documents = policy_hashes.map.with_index do |policy, index|
{
'PolicyDocument' => policy,
'PolicyName' => "#{fn_name}RolePolicy#{index}"
}
end

role['Properties']['Policies'] = policy_documents
end

def lambda_function_code(fn_resource, code_bucket, code_key)
Expand All @@ -152,13 +206,14 @@ def lambda_function_code(fn_resource, code_bucket, code_key)
def lambda_function(handler:,
code_bucket: nil,
code_key: nil,
role:,
runtime:,
with_line_numbers: false)
fn_resource = {
'Type' => format_resource_type('AWS::Lambda::Function', -1, with_line_numbers),
'Properties' => {
'Handler' => handler,
'Role' => { 'Fn::GetAtt' => %w[FunctionNameRole Arn] },
'Role' => role,
'Runtime' => runtime
}
}
Expand Down
2 changes: 1 addition & 1 deletion spec/transforms/serverless_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
yaml_test_template('sam/valid_simple_lambda_fn')
actual_cfn_model = @cfn_parser.parse cloudformation_template_yml
expect(
actual_cfn_model.raw_model['Resources']['FunctionNameRole']['Type']
actual_cfn_model.raw_model['Resources']['MyServerlessFunctionLogicalIDRole']['Type']
).to(
eq 'AWS::IAM::Role'
)
Expand Down