App-FargateStack/lib/App/FargateStack/Builder/IAM.pm
package App::FargateStack::Builder::IAM;
use strict;
use warnings;
use Carp;
use Data::Dumper;
use Data::Compare;
use English qw(-no_match_vars);
use App::FargateStack::Constants;
use App::FargateStack::Builder::Utils qw(choose log_die);
use Text::Diff;
use JSON;
use Role::Tiny;
########################################################################
sub build_iam_role {
########################################################################
my ($self) = @_;
my ( $config, $tasks, $dryrun ) = $self->common_args(qw(config tasks dryrun));
my $iam = $self->fetch_iam;
######################################################################
# create role
######################################################################
my $role = $config->{role} // {};
$config->{role} = $role;
$self->log_trace( sub { return Dumper( [ role => $role ] ) } );
my ( $role_name, $role_arn ) = $self->create_fargate_role();
my $policy_name = $role->{policy_name} // $self->create_default( 'policy-name', 'ecs' );
@{$role}{qw(name arn policy_name)} = ( $role_name, $role_arn, $policy_name );
######################################################################
# create policy - see if policy needs to be created or updated
######################################################################
$self->create_policy( $iam, 'ecs' );
######################################################################
# create task role
######################################################################
my $task_role = $config->{task_role} // {};
$config->{task_role} = $task_role;
$self->log_trace( sub { return Dumper( [ task_role => $task_role ] ) } );
my ( $task_role_name, $task_role_arn ) = $self->create_fargate_task_role();
my $task_policy_name = $task_role->{policy_name} // $self->create_default( 'policy-name', 'task' );
@{$task_role}{qw(name arn policy_name)} = ( $task_role_name, $task_role_arn, $task_policy_name );
######################################################################
# create task policy
######################################################################
$self->create_policy( $iam, 'task' );
return;
}
########################################################################
sub create_policy {
########################################################################
my ( $self, $iam, $type ) = @_;
my ( $config, $dryrun ) = $self->common_args(qw(config dryrun));
my $role = $type eq 'ecs' ? $config->{role} : $config->{task_role};
my ( $policy_name, $role_name ) = @{$role}{qw(policy_name name)};
######################################################################
# create policy - see if policy needs to be created or updated
######################################################################
# if we turned caching off OR we don't have an ARN yet, check to see
# if the policy exists
my $policy = $iam->get_role_policy( $role_name, $policy_name );
$iam->check_result(
message => 'ERROR: could not get role policy: [%s] for role [%s]',
params => [ $policy_name, $role_name ],
regexp => qr/cannot\sbe\sfound/xsmi
);
$self->log_trace(
sub {
return Dumper(
[ policy => $policy,
role => $role
]
);
}
);
my $role_policy = choose {
return $self->create_fargate_policy
if $type eq 'ecs';
return $self->create_fargate_task_policy;
};
if ( !$role_policy ) {
$self->log_info( 'iam: policy: [%s] ... not required...skipping', $policy_name );
return;
}
my $policy_exists = $FALSE;
if ($policy) {
$policy_exists = Compare( $policy, $role_policy ) ? 1 : -1;
}
$self->log_trace(
sub {
return Dumper(
[ existing_policy => $policy,
new_policy => $role_policy,
role => $role,
policy_exists => $policy_exists,
]
);
}
);
if ( $policy_exists && $policy_exists != -1 ) {
$self->log_info( 'iam: policy: [%s] exists...%s', $policy_name, 'skipping' );
$self->inc_existing_resources( 'iam:role-policy' => [$policy_name] );
return;
}
elsif ( $policy_exists == -1 ) {
my $title = sprintf 'iam: role policy [%s] differs', $policy_name;
$self->display_diffs( $policy, $role_policy, { title => $title } );
$self->log_warn( 'iam: policy: [%s] will be replaced...%s', $policy_name, $dryrun );
}
else {
$self->log_warn( 'iam: policy: [%s] will be created...%s', $policy_name, $dryrun );
}
$self->inc_required_resources( 'iam:policy' => [$policy_name] );
return
if $dryrun;
# -- policy exists but differs
if ( $policy_exists == -1 ) {
$self->log_warn( 'iam: deleting policy [%s] for role [%s]...', $policy_name, $role_name );
$iam->delete_role_policy( $role_name, $policy_name );
}
$self->log_trace( sub { return Dumper( [ 'iam:policy' => $role_policy ] ); } );
$self->log_warn( 'iam: creating policy [%s] for role [%s]...', $policy_name, $role_name );
$iam->put_role_policy( $role_name, $policy_name, $role_policy );
$iam->check_result(
{ message => "ERROR: could not create policy %s for %s\n%s",
params => [ $role_name, $policy_name, $iam->get_error ]
}
);
return;
}
########################################################################
sub create_fargate_policy {
########################################################################
my ($self) = @_;
my $config = $self->get_config;
my @statement;
my $role_policy = {
Version => $IAM_POLICY_VERSION,
Statement => \@statement,
};
push @statement, $self->add_ecr_policy();
push @statement, $self->add_efs_policy(); # force rebuild of policy
push @statement, $self->add_log_group_policy();
if ( my $secrets = $self->get_secrets ) {
push @statement, $self->add_secrets_policy($secrets);
}
return $role_policy;
}
########################################################################
sub create_fargate_task_policy {
########################################################################
my ($self) = @_;
my $config = $self->get_config;
my @statement;
my $role_task_policy = {
Version => $IAM_POLICY_VERSION,
Statement => \@statement,
};
if ( $config->{bucket} ) {
push @statement, $self->add_bucket_policy;
}
if ( $config->{queue} ) {
push @statement, $self->add_queue_policy();
}
if ( my $secrets = $self->get_secrets ) {
push @statement, $self->add_secrets_policy($secrets);
}
return @statement ? $role_task_policy : $EMPTY;
}
########################################################################
sub create_fargate_role { return shift->create_role( @_, 'ecs' ); }
########################################################################
sub create_fargate_task_role { return shift->create_role( @_, 'task' ); }
########################################################################
########################################################################
sub create_role {
########################################################################
my ( $self, $type ) = @_;
my $config = $self->get_config;
my $dryrun = $self->get_dryrun;
my $iam = $self->fetch_iam;
my $service_domain = $AWS_SERVICE_DOMAINS{$type};
log_die( $self, 'iam: invalid task type: %s', $type )
if !$service_domain;
my $trust_policy = {
Version => $IAM_POLICY_VERSION,
Statement => [
{ Effect => 'Allow',
Principal => { Service => $service_domain },
Action => 'sts:AssumeRole'
}
]
};
my $role_config = choose {
if ( $type eq 'events' ) {
$config->{events_role} //= {};
return $config->{events_role};
}
elsif ( $type eq 'ecs' ) {
$config->{role} //= {};
return $config->{role};
}
elsif ( $type eq 'task' ) {
$config->{task_role} //= {};
return $config->{task_role};
}
};
my $role_name = $role_config->{name} // $self->create_default( 'role-name', $type );
my $role = choose {
return { Role => { Arn => $role_config->{arn} } }
if $role_config->{arn};
return $iam->role_exists($role_name);
};
$self->log_trace( sub { return Dumper( [ role => $role ] ) } );
if ( $role->{Role}->{Arn} ) {
$self->log_info( 'iam: role: [%s] exists...%s', $role_name, $self->get_cache || 'skipping' );
$self->inc_existing_resources( 'iam:role' => [ $role->{Role}->{Arn} ] );
return ( $role_name, $role->{Role}->{Arn} );
}
$self->inc_required_resources(
'iam:role' => [
sub {
my ($dryrun) = @_;
return $dryrun ? "arn:???/$role_name" : $role_config->{arn};
}
]
);
$self->log_warn( 'iam: role: [%s] will be created...%s', $role_name, $self->get_dryrun );
my $role_arn;
if ( !$dryrun ) {
$role_arn = $iam->create_role( $role_name, $trust_policy );
$self->log_trace( sub { return Dumper( [ 'iam: policy' => $trust_policy ] ); } );
log_die( $self, "ERROR: could not create role: %s\n%s", $role_name, $iam->get_error )
if $iam->get_error;
}
return ( $role_name, $role_arn );
}
1;