require 'sfn' module Sfn class Callback class StackPolicy < Callback # Policy to apply prior to stack deletion DEFENSELESS_POLICY = { 'Statement' => [{ 'Effect' => 'Allow', 'Action' => 'Update:*', 'Resource' => '*', 'Principal' => '*', }], } # @return [Smash] cached policies attr_reader :policies # Overload to init policy cache # # @return [self] def initialize(*args) super @policies = Smash.new end # Submit all cached policies # # @param args [Hash] def submit_policy(args) ui.info 'Submitting stack policy documents' stack = args[:api_stack] ([stack] + stack.nested_stacks).compact.each do |p_stack| run_action "Applying stack policy to #{ui.color(p_stack.name, :yellow)}" do save_stack_policy(p_stack) end end ui.info 'Stack policy documents successfully submitted!' end alias_method :after_create, :submit_policy alias_method :after_update, :submit_policy # Disable all existing policies prior to update # # @param args [Hash] def before_update(args) if config.get(:stack_policy, :update).to_s == 'defenseless' ui.warn 'Disabling all stack policies for update.' stack = args[:api_stack] ([stack] + stack.nested_stacks).compact.each do |p_stack| @policies[p_stack.name] = DEFENSELESS_POLICY run_action "Disabling stack policy for #{ui.color(p_stack.name, :yellow)}" do save_stack_policy(p_stack) end end end end # Generate stack policy for stack and cache for the after hook # to handle # # @param info [Hash] def template(info) if info[:sparkle_stack] @policies.set(info.fetch(:stack_name, 'unknown'), info[:sparkle_stack].generate_policy) end end # Save the cached policy for the given stack # # @param p_stack [Miasma::Models::Orchestration::Stack] # @return [NilClass] def save_stack_policy(p_stack) valid_logical_ids = p_stack.resources.reload.all.map(&:logical_id) stack_policy = @policies.fetch(p_stack.id, @policies.fetch(p_stack.data[:logical_id]), @policies[p_stack.name]).to_smash if stack_policy stack_policy[:Statement].delete_if do |policy_item| policy_match = policy_item[:Resource].to_s.match( %r{LogicalResourceId/(?.+)$} ) if policy_match !valid_logical_ids.include?(policy_match["logical_id"]) end end end result = p_stack.api.request( :path => '/', :method => :post, :form => Smash.new( 'Action' => 'SetStackPolicy', 'StackName' => p_stack.id, 'StackPolicyBody' => MultiJson.dump(stack_policy), ), ) end end end end