Ereshkigal

 view release on metacpan or  search on metacpan

docs/kurs/checkpoint.md  view on Meta::CPAN

init POSTs `/web_api/login` and carries the returned session id as
`X-chkp-sid` thereafter. Per-IP host objects are named
`<prefix>_<name>_<ip>` with dots/colons flattened to dashes:

| operation  | API traffic                                                            |
|------------|-----------------------------------------------------------------------------|
| `init`     | `POST /web_api/login`                                                  |
| `ban`      | `POST /web_api/add-host` with `{"name":"<obj>","ip-address":"<ip>","groups":["<group>"]}`, then `POST /web_api/publish` |
| `unban`    | `POST /web_api/delete-host` with `{"name":"<obj>"}`, then publish      |
| `list`     | no API call — the kur's own ban book                                   |
| `check`    | `POST /web_api/keepalive` — session still valid (and refreshed)        |
| `flush`    | delete-host + publish per banned IP                                    |
| `re_init`  | teardown (best effort), init (fresh login), re-add + publish per IP    |
| `teardown` | delete-host + publish per banned IP (ban book kept)                    |

## self_heal

`check` is a session keepalive — it validates (and refreshes) the
session, nothing about objects, group, policy, or installation. A
timed-out session fails checks and bans; the `re_init` that
self_heal then triggers performs a fresh login, so session expiry
heals itself at the next ban/unban. With `self_heal` on, every ban's
keepalive also keeps the session alive.

## Gotchas

- The install-policy caveat dominates — publish ≠ enforce.
- Two API calls per ban and unban (change + publish); publishes are
  not cheap on busy management servers. High-churn ban sources will
  make SmartConsole's audit log very talkative.
- The kur never logs out; sessions die by server-side timeout.
  Combined with per-user session caps, give the kur its own API user
  and don't restart it in a tight loop.

lib/Ereshkigal.pm  view on Meta::CPAN

	log_drek( 'err', 'kur "' . $name . '" ' . $how . ', restarting in ' . $delay . ' seconds' );

	$kernel->delay_set( 'restart_kur', $delay, $name );

	return;
} ## end sub _poe_kur_reaped

# The manager session's remove_kur handler, which stops one kur and drops it
# from the registry for good. The actual removal has to happen in the manager
# session as destroying a POE::Wheel::Run from within another session leaves
# its watchers behind, keeping the manager session alive forever... which is
# why _cmd_remove_kur only marks the entry disabled and posts here rather
# than doing the work itself.
#
# A running kur is asked to stop over its own socket, so it checkpoints its
# tablets and tears its firewall setup down properly. Only if that fails is
# it sent a TERM, which gets the process gone but leaves whatever it was
# holding in the firewall. Either way the entry is then unwired... its wheel
# dropped from wheel_to_kur, its PID from pid_to_kur, and the registry entry
# deleted.
#

lib/Ereshkigal.pm  view on Meta::CPAN

# same dangling state at runtime... every registered kur is scanned for gates
# naming this one, and if any do the removal is refused naming them. Remove
# the gate first, or the member stays.
#
# Setting enabled to 0 before posting matters as well... it is what stops the
# reap handler restarting the kur when the stop it is about to be sent
# actually kills it.
#
# The real work is left to the manager session, as a POE::Wheel::Run must be
# destroyed in the session watching it or its watchers outlive it and keep
# the session alive forever.
#
# The config file is not rewritten, so a kur defined there returns at the
# next manager start.
#
# Note this takes no context and authorizes nothing of its own... the
# dispatch entry has already run _authorize with no kur names.
#
# Args...
#
#     $request :: Required. The decoded request hash ref. Must carry args

lib/Ereshkigal/Kur.pm  view on Meta::CPAN

			},
			'sig_shutdown' => sub {
				my $signal = $_[ARG0];
				$_[KERNEL]->sig_handled;
				if ( $self->{stopping} ) {
					return;
				}
				log_drek( 'info', 'SIG' . $signal . ' received, tearing the backend down', undef, $ident );
				$self->_stop_guts;
				# _stop_guts set stopping, so the pending sweep alarm is the
				# only thing keeping this session alive... clear it and fire
				# the server session's shutdown so the kernel can exit
				$_[KERNEL]->delay('sweep');
				$_[KERNEL]->post( $ident, 'shutdown' );
			},
		},
	);

	log_drek( 'info', 'started... socket=' . $self->socket_path . ' backend=' . $self->{backend}, undef, $ident );

	$poe_kernel->run;

t/kur-checkpoint.t  view on Meta::CPAN

$disabled->_tick;
is( slurp( $disabled->state_path ), $old_raw, 'checkpoint 0 disables the periodic rewrite' );
is( $disabled->{last_checkpoint},   0,        'and last_checkpoint stays put' );

#
# loading... the row time is compared against now to decide restoration
#

my $now = time;
open( my $csv_fh, '>', $dir . '/cache/kur.loader.csv' ) || die($!);
print $csv_fh "ip,time,ban_time_left\n" . '10.0.0.1,' . $now . ',500' . "\n"    # timed, alive
	. '10.0.0.2,' . ( $now - 60 ) . ',10' . "\n"                                # expired while down
	. '10.0.0.3,' . $now . ',0' . "\n"                                          # permanent
	. "not,enough\n"                                                            # malformed... field count
	. '10.0.0.4,junk,50' . "\n"                                                 # malformed... time
	. '10.0.0.5,' . $now . ',junk' . "\n"                                       # malformed... left
	. '010.0.0.7,' . $now . ',500' . "\n"                                       # will not normalize... leading zero octet
	. 'notanip,' . $now . ',500' . "\n"                                         # will not normalize... not an IP at all
	. "\n"                                                                      # blank
	. '10.0.0.6,' . $now . ',500' . "\n";                                       # good row after the junk
close($csv_fh);

t/kur-server.t  view on Meta::CPAN


my $raw = IO::Socket::UNIX->new(
	'Type' => IO::Socket::UNIX::SOCK_STREAM(),
	'Peer' => $socket,
) || die($!);
print $raw "this is not json\n";
my $raw_line = <$raw>;
close($raw);
like( $raw_line, qr/invalid JSON/, 'malformed JSON gets an error response' );

is( $client->call_ok('ping')->{pong}, 1, 'server still alive after the bad input' );

#
# a second kur with the same name refuses to clobber the live socket
#

my $second_pid  = spawn_kur( %spawn_opts, 'quiet' => 1 );
my $second_exit = wait_for_exit($second_pid);
ok( defined($second_exit) && $second_exit != 0, 'second kur with the same name exits nonzero' );

is( $client->call_ok('ping')->{pong}, 1, 'first kur survived the second one' );

t/kur-server.t  view on Meta::CPAN


$result = $client->call_ok( 'ban', { 'ips' => ['9.9.9.1'], 'ban_time' => 3600 } );
is( $result->{ips}{'9.9.9.1'}{status}, 'ok', 'long timed ban ok' );

kill( 'KILL', $pid );
wait_for_exit($pid);
$pid = spawn_kur(%spawn_opts);

# the SIGKILLed kur left a stale socket file behind, so poll till the
# respawned one actually answers
my $alive = 0;
$waited = 0;
while ( $waited < 10 ) {
	my $pong = eval { $client->call_ok('ping'); };
	if ( defined($pong) && $pong->{pong} ) {
		$alive = 1;
		last;
	}
	select( undef, undef, undef, 0.5 );
	$waited += 0.5;
}
ok( $alive, 'kur back up after being killed' );

$result = $client->call_ok('banned');
ok( ( grep { $_ eq '9.9.9.1' } @{ $result->{banned} } ), 'timed ban still banned after the restart' );
ok( $result->{expires}{'9.9.9.1'} > time, 'and still tracked with it\'s expiry' );
ok( ( grep { $_ eq '8.8.8.8' } @{ $result->{banned} } ), 'permanent ban still banned after the restart' );

#
# stop
#

t/manager.t  view on Meta::CPAN

		last;
	}
	select( undef, undef, undef, 0.5 );
	$waited += 0.5;
} ## end while ( $waited < 20 )
ok( $respawned, 'sshd respawned after being killed' );
is( $status->{kurs}{sshd}{restarts}, 1, 'restart counted' );

# can't just wait on the socket path as the SIGKILLed kur left a stale
# socket file behind, so poll till the respawned one actually answers
my $alive = 0;
$waited = 0;
while ( $waited < 20 ) {
	my $pong = eval { $sshd_client->call_ok('ping'); };
	if ( defined($pong) && $pong->{pong} ) {
		$alive = 1;
		last;
	}
	select( undef, undef, undef, 0.5 );
	$waited += 0.5;
}
ok( $alive, 'respawned sshd answers on it\'s socket' );

$result = $client->call_ok( 'ban', { 'ips' => ['6.6.6.6'], 'kur' => 'sshd' } );
is( $result->{kurs}{sshd}{ips}{'6.6.6.6'}{status}, 'ok', 'bans work on the respawned kur' );

#
# stop... this is also the regression test for wheel destruction from the
# wrong session, as the add/remove above has to not keep the manager alive
#

$result = $client->call_ok('stop');
is( $result->{stopping}, 1, 'stop response' );
ok( wait_for_gone($manager_socket),               'manager socket gone' );
ok( wait_for_gone( $dir . '/run/kur/sshd.sock' ), 'sshd socket gone' );
ok( wait_for_gone( $dir . '/run/kur/smtp.sock' ), 'smtp socket gone' );
ok( wait_for_gone( $dir . '/run/pid' ),           'manager pid file gone' );
is( wait_for_exit( $manager_pid, 20 ), 0, 'manager exited 0' );



( run in 1.849 second using v1.01-cache-2.11-cpan-14f38c9f855 )