App-FargateStack
view release on metacpan or search on metacpan
lib/App/FargateStack/Checker.pm view on Meta::CPAN
my $ec2 = new_client( 'App::EC2', $opt );
my ( $ok_ep, $eps, $err_ep ) = try_aws(
sub {
$ec2->command( 'describe-vpc-endpoints' => [ '--query', 'VpcEndpoints[].ServiceName' ] );
}
);
if ( !$ok_ep ) {
return row_warn( 'Secrets Manager', 'Could not confirm VPC endpoints for Secrets Manager' );
}
my %have = map { $_ => 1 } @{ $eps || [] };
my $svc = sprintf 'com.amazonaws.%s.secretsmanager', ( $opt->{region} || 'us-east-1' );
if ( !$have{$svc} ) {
return row_warn( 'Secrets Manager', 'Missing VPC endpoint for Secrets Manager or rely on NAT' );
}
return row_ok( 'Secrets Manager', 'Endpoint present' );
}
########################################################################
sub check_passrole {
########################################################################
my ($opt) = @_;
my $role_names = $opt->get_role_names;
# role_names: arrayref of the exact role names your framework will create,
# e.g., [ 'FargateStack/my-svc/TaskExecutionRole', 'FargateStack/my-svc/TaskRole',
# 'FargateStack/my-svc/EventsInvokeRole' ]
my $sts = new_client( 'App::STS', $opt );
my $iam = new_client( 'App::IAM', $opt );
my $id = $sts->get_caller_identity();
my $acct = $id->{Account} || q{};
my $caller_arn = $id->{Arn} || q{};
if ( !$acct || !$caller_arn ) {
return row_fail( 'iam:PassRole', 'Cannot resolve caller identity' );
}
my $policy_source_arn = _principal_to_policy_source_arn( $caller_arn, $acct );
if ( !$policy_source_arn ) {
return row_fail( 'iam:PassRole', 'Cannot derive policy-source ARN for simulation' );
}
my $role_arns = _role_arns_from_names( $acct, $role_names || [] );
if ( !$role_arns || !@{$role_arns} ) {
return row_warn( 'iam:PassRole', 'No target roles specified; skipping simulation' );
}
my @services = qw(ecs-tasks.amazonaws.com events.amazonaws.com);
my %decisions_by_service;
foreach my $svc (@services) {
my $result = eval { _simulate_passrole_once( $iam, $policy_source_arn, $role_arns, $svc ) };
if ( $EVAL_ERROR || !$result ) {
# If org blocks simulation, warn rather than fail
return row_warn( 'iam:PassRole', 'Simulation not permitted; verify PassRole manually' );
}
# result is an arrayref of EvalDecision strings: allowed | explicitDeny | implicitDeny
my @dec = @{ $result || [] };
$decisions_by_service{$svc} = \@dec;
}
# Aggregate decisions: any explicitDeny -> FAIL
# otherwise any implicitDeny -> WARN
# otherwise all allowed -> PASS
my $detail = q{};
my $status = 'PASS';
foreach my $svc (@services) {
my $dec = $decisions_by_service{$svc} || [];
my $svc_summary = sprintf '%s: %s', $svc, ( join q{,}, @{$dec} );
if ( grep { $_ eq 'explicitDeny' } @{$dec} ) {
$status = 'FAIL';
}
elsif ( $status ne 'FAIL' && grep { $_ eq 'implicitDeny' } @{$dec} ) {
$status = 'WARN';
}
$detail .= $svc_summary . q{ };
}
$detail =~ s/\s+\z//;
if ( $status eq 'FAIL' ) { return row_fail( 'iam:PassRole', $detail ); }
if ( $status eq 'WARN' ) { return row_warn( 'iam:PassRole', $detail ); }
return row_ok( 'iam:PassRole', $detail || 'allowed' );
}
########################################################################
sub _principal_to_policy_source_arn {
########################################################################
my ( $sts_arn, $account_id ) = @_;
# Examples:
# arn:aws:sts::123456789012:assumed-role/DeployerRole/SESSION -> arn:aws:iam::123456789012:role/DeployerRole
# arn:aws:iam::123456789012:user/someuser -> arn:aws:iam::123456789012:user/someuser
return q{} if !$sts_arn || !$account_id;
if ( $sts_arn =~ m{\A arn:aws:sts::\Q$account_id\E:assumed-role/([^/]+)/}xsm ) {
my $role = $1;
return sprintf 'arn:aws:iam::%s:role/%s', $account_id, $role;
}
if ( $sts_arn =~ m{\A arn:aws:iam::\Q$account_id\E:(user|role)/}xsm ) {
return $sts_arn;
}
# Fallback: try to coerce to iam::role if it looks like an sts assumed role
return $sts_arn;
}
########################################################################
sub _role_arns_from_names {
lib/App/FargateStack/Checker.pm view on Meta::CPAN
log_level => 'info',
dns => $TRUE,
secrets => $TRUE,
https => $TRUE,
);
my @option_specs = qw(
profile=s
region=s
https!
help|h
dns!
secrets!
dns-profile=s
log_level=s
);
my %commands = ( default => \&check_fargate_env );
my $cli = App::FargateStack::Checker->new(
commands => \%commands,
default_options => \%default_options,
option_specs => \@option_specs,
extra_options => [qw(account global_options role_names route53_profile)],
);
my @expected_roles = (
# Execution role and task role your builder will create
'FargateStack/my-svc/TaskExecutionRole',
'FargateStack/my-svc/TaskRole',
'FargateStack/my-svc/EventsInvokeRole',
);
$cli->set_role_names( \@expected_roles );
$cli->set_global_options(
{ profile => $cli->get_profile,
region => $cli->get_region
}
);
return $cli->run();
}
1;
__END__
=pod
=head1 NAME
app-FargateStack-env.pl - Preflight checker for ECS Fargate environments
=head1 USAGE
app-FargateStack-env.pl [options]
=head1 DESCRIPTION
Runs read-only checks against the target AWS account and region to verify
that common ECS Fargate deployment scenarios are feasible. Produces an
ASCII table with PASS/WARN/FAIL rows and a capabilities summary for:
- HTTP services
- HTTPS service
- Scheduled tasks
- One-shot tasks
- Daemon services
No resources are created or modified. Intended as a fast âcan I deploy here?â
probe for humans and CI.
=head2 Options
=over 4
=item B<--profile> I<STR>
AWS config/credentials profile to use. Defaults to C<$ENV{AWS_PROFILE}> or the
SDKâs default behavior if unset.
=item B<--region> I<STR>
AWS region to target (e.g. C<us-east-1>). Defaults to C<$ENV{AWS_REGION}> if set.
=item B<--dns> | B<--no-dns>
Enable or disable Route 53 checks. Default: B<enabled>.
Use B<--no-dns> (or B<--nodns>) to skip DNS checks.
=item B<--dns-profile> I<STR>
Alternate AWS profile for Route 53 lookups. Useful when DNS is managed in a
separate account. Falls back to C<--profile> if not provided.
=item B<--https> | B<--no-https>
Enable or disable ACM certificate checks (same region as the load balancer).
Default: B<disabled>. Turn on if you plan to deploy HTTPS.
=item B<--secrets> | B<--no-secrets>
Enable or disable Secrets Manager reachability checks. Default: B<disabled>.
When enabled, the checker verifies control-plane reachability (e.g., VPC
endpoint present or NAT available). It does not validate individual
C<GetSecretValue> permissions for task roles.
=back
=head1 OUTPUT
The main table includes rows like:
Credentials
Service-linked roles
VPC/Subnets
Egress
ECS perms
ELBv2 perms
CloudWatch Logs perms
( run in 0.501 second using v1.01-cache-2.11-cpan-e1769b4cff6 )