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 )