App-FargateStack
view release on metacpan or search on metacpan
lib/App/FargateStack/Builder/WafV2.pm view on Meta::CPAN
push @rule_list, $rule;
}
return \@rule_list;
}
########################################################################
sub build_waf {
########################################################################
my ($self) = @_;
######################################################################
## init
######################################################################
my $config = $self->get_config;
my $dryrun = $self->get_dryrun;
# if no alb or no waf section skip this build (avoid autovivification)
return
if !exists $config->{alb};
my $alb = $config->{alb};
return
if !exists $alb->{waf};
my $waf_config = $alb->{waf};
return
if !$waf_config->{enabled};
log_die( $self, 'ERROR: no ALB ARN? you cannot have a WAF without an ALB' )
if !$alb->{arn};
$waf_config->{name} //= $self->create_default('web-acl-name');
my $name = $waf_config->{name};
my $waf = $self->fetch_wafv2;
######################################################################
## create or update web-acl
######################################################################
my $web_acl = $waf->list_web_acls(
scope => 'REGIONAL',
query => sprintf 'WebACLs[?Name==`%s`]',
$name
);
$waf->check_result( message => 'ERROR: could not list web acls' );
if ( $web_acl && @{$web_acl} ) {
my $web_acl_id = $web_acl->[0]->{Id};
$web_acl = $waf->get_web_acl( name => $name, id => $web_acl_id );
$waf->check_result( message => 'ERROR: could not get web-acl: [%s]', $name );
my $web_acl_arn = $web_acl->{WebACL}->{ARN};
# check to see if rules have been updated
my $new_rules = $self->check_rules( waf => $waf, waf_config => $waf_config );
# check to see if someone has mucked with web-acl.json
my $needs_update = $self->check_web_acl_state(
name => $name,
id => $web_acl_id,
waf_config => $waf_config,
waf => $waf,
web_acl => $web_acl,
rules => $new_rules,
);
# we would have died already if there was a conflict, so now we
# have either FALSE = no update needed or TRUE = need to update web-acl
if ($needs_update) {
$self->log_warn( 'waf: web-acl: [%s] has changed...will be updated...%s', $name, $dryrun );
if ( !$dryrun ) {
$self->update_web_acl( $waf_config->{lock_token} );
$self->save_web_acl(
id => $waf_config->{id},
scope => 'REGIONAL',
name => $name,
waf_config => $waf_config,
waf => $waf,
);
}
}
else {
$self->log_info( 'waf: web-acl: [%s] has not changed...skipping', $name );
$self->inc_existing_resources( waf => $waf_config->{arn} );
}
my $arns = $waf->list_resources_for_web_acl( $web_acl_arn, 'ResourceArns' );
$waf->check_result( message => 'ERROR: could not list resources for web acl: [%s]', $web_acl_arn );
if ( !@{$arns} ) {
$self->log_warn( 'waf: web-acl: [%s] not associated...will be associated with ALB: [%s]...%s',
$name, $alb->{name}, $dryrun );
if ( !$dryrun ) {
$self->associate_web_acl(
web_acl_arn => $web_acl_arn,
resource_arn => $alb->{arn},
waf => $waf,
name => $name
);
}
}
}
else {
$self->log_warn( 'waf: web-acl: [%s] does not exist...will be created...%s', $name, $dryrun );
$self->inc_required_resources(
waf => [
sub {
my ($dryrun) = @_;
return $dryrun ? 'arn:???' : $waf_config->{arn};
}
]
);
my $rules = $self->create_rule_list($waf_config);
$self->log_warn( 'waf: rule list from: [%s]', join q{,}, @{ $waf_config->{managed_rules} } );
$self->log_debug( "waf: managed rules: %s\n", join "\n", Dumper $rules );
if ( !$dryrun ) {
$self->create_web_acl(
name => $name,
waf_config => $waf_config,
waf => $waf,
rules => $rules
);
}
lib/App/FargateStack/Builder/WafV2.pm view on Meta::CPAN
########################################################################
sub check_rules {
########################################################################
my ( $self, %args ) = @_;
my ( $waf, $waf_config ) = @args{qw(waf waf_config web_acl)};
my $web_acl = $self->fetch_web_acl;
my $current_rules = $web_acl->{WebACL}->{Rules};
my $rules = $self->create_rule_list($waf_config);
return $rules
if JSON->new->pretty->canonical->encode($rules) ne JSON->new->pretty->canonical->encode($current_rules);
return;
}
########################################################################
sub check_web_acl_state {
########################################################################
my ( $self, %args ) = @_;
my ( $name, $id, $waf_config, $waf, $web_acl, $rules ) = @args{qw(name id waf_config waf web_acl rules)};
my ( $aws_id, $aws_arn ) = @{ $web_acl->{WebACL} }{qw(Id ARN)};
my $aws_lock_token = $web_acl->{LockToken};
my $local_web_acl = $self->fetch_web_acl;
if ($rules) {
$self->log_warn('waf: rules have changed, updating web-acl.json');
$local_web_acl->{WebACL}->{Rules} = $rules;
$self->save_web_acl( name => $name, web_acl => $local_web_acl, waf => $waf, waf_config => $waf_config );
}
# determine if the web-acl has been modified "out-of-band"
# and/or our local web-acl.json file has been modified
my $md5 = $self->calculate_md5($web_acl);
my $md5_match = $md5 eq $waf_config->{md5};
if ( $md5 ne $waf_config->{md5} ) {
$self->log_warn( 'waf: MD5 hashes do not match: [%s] != [%s]', $waf_config->{md5}, $md5 );
}
my $lock_token_match = $aws_lock_token eq $waf_config->{lock_token};
return $FALSE # no need to update, everything OK
if $md5_match && $lock_token_match;
return $TRUE # update OK
if !$md5_match && $lock_token_match;
if ( $md5_match && !$lock_token_match ) {
my $err_msg = <<'END_OF_ERR_MESSAGE';
WAF configuration appears to be in sync, but the remote resource has
abeen modified out-of-band. The local configuration file is being updated with
the latest LockToken. Please run 'plan' again to confirm.
END_OF_ERR_MESSAGE
$waf_config->{id} = $aws_id;
$waf_config->{arn} = $aws_arn;
$waf_config->{lock_token} = $aws_lock_token;
return $FALSE; # no need to update, just update local state
}
# if we are here, then conflict !$md5_match && !$lock_token_match
# rut-roh...conflict
my $diff = $self->web_acl_diffs($web_acl);
my $err_msg = <<'END_OF_ERR_MESSAGE';
ERROR: State conflict detected for Web ACL %s
A change has been made to the local 'web-acl.json' file, but the
Web ACL has also been modified in AWS since the last run.
Applying local changes now would overwrite the remote modifications.
%s
To resolve this conflict:
1. Review the diff above to understand the remote changes.
2. Manually merge the desired remote changes into your local 'web-acl.json' file.
3. Once the local file represents the true desired state, run 'app-FargateStack plan' again.
END_OF_ERR_MESSAGE
log_die( $self, $err_msg, $name, $diff, $name )
if !$self->get_force;
$waf_config->{lock_token} = $aws_lock_token;
return $TRUE;
}
########################################################################
sub create_web_acl {
########################################################################
my ( $self, %args ) = @_;
my ( $name, $waf_config, $waf, $rule_list ) = @args{qw(name waf_config waf rules)};
my $web_acl_summary = $waf->create_web_acl(
name => $name,
rules => $rule_list,
query => 'Summary',
scope => 'REGIONAL',
metric_name => $self->get_config->{app}->{name},
);
$waf->check_result( message => 'ERROR: unable to create web-acl: [%s]', $name );
@{$waf_config}{qw(id lock_token arn)} = @{$web_acl_summary}{qw(Id LockToken ARN)};
my $web_acl = $self->save_web_acl(
id => $waf_config->{id},
scope => 'REGIONAL',
( run in 1.575 second using v1.01-cache-2.11-cpan-e1769b4cff6 )