-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Copy pathossec-batch-manager.pl
executable file
·376 lines (341 loc) · 10.7 KB
/
ossec-batch-manager.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
#!/usr/bin/perl
# vim:shiftwidth=2:tabstop=2:expandtab:textwidth=80:softtabstop=2:ai:
#########################################################
# Written Aug 4, 2007 and released under the GNU/GPLv2 ##
# by Jeff Schroeder (jeffschroeder@computer.org) # #
######################################################### #
# # #
# ossec-batch-manager.pl - Add and extract agents from # #
# the ossec client.keys file non-interactively. This # #
# started as a hack to properly script manage_agents. # #
# # #
##########################################################
# Modified by Tim Meader (Timothy.A.Meader@nasa.gov)
# on 2013/07/01
#
# - corrected a MAJOR logic error in the remove
# function. The comparison was being done across the
# entire line of the agent keys file, so both IPs
# and the SSH keys at the end could be matched against
# the 'agent ID' wanting to be removed. Changed the
# match to only compare the first column of the file
# - added an error output message to the remove
# function if it's fed an 'agent ID' that doesn't
# exist
# - the script now also removes the corresponding
# associated agent rid files after a successful remove
# operation, or gives an error on failure
#
##########################################################
# Modified by Tim Meader (Timothy.A.Meader@nasa.gov)
# on 2010/12/08
#
# - fixed two errors that were popping up during add or
# remove operations due to the code not taking into
# account the old key entries that have the "#*#*#*"
# pattern after the ID number. Simple fix was to do
# a "if (defined(xxx))" on the vars
# - fixed the "list" operation to only show valid key
# entries
# - changed the extract operation to store options
# in an array, and subsequently rewrote the
# "extract_key" (now called "extract_keys") func
# to accept this new behavior
# - modified "extract_keys" func to accept either ID,
# name, or IP address as the argument after the
# "-e" operator. Output of key extraction now
# include the name and IP address by default in the
# format: "name,IP extracted_key"
#
#########################################################
#$Id$
# TODO:
# - Add check for ossec 1.4 and support longer agent names
# - Add in eval so that older version of perl without
# Time::HiRes still can use this script.
use strict;
use warnings;
require 5.8.2; # Time::HiRes is standard from this version forth
#use diagnostics;
use MIME::Base64;
use Digest::MD5 qw(md5_hex);
use Getopt::Long;
use Regexp::Common qw(net);
use constant AUTH_KEY_FILE => "/var/ossec/etc/client.keys";
use constant RIDS_PATH => "/var/ossec/queue/rids/";
my ($key, $add, $remove, @extracts, $import, $listagents);
my ($agentid, $agentname, $ipaddress);
GetOptions(
'k|key=s' => \$key, # Unencoded ssh key
'a|add' => \$add, # Add a new agent
'r|remove=s' => \$remove, # Remove an agent
'e|extract=s' => \@extracts, # Extract a key
'm|import' => \$import, # Import a key
'l|list' => \$listagents, # List all agents
'i|id=s' => \$agentid, # Unique agent id
'n|name=s' => \$agentname, # Agent name. 32 char max
'p|ip=s' => \$ipaddress # IP Address in "dotted quad" notation
);
# Spit out a list of available agents, their names, and ip information
if ($listagents) {
list_agents();
}
# Decode and extract the key for $agentid
elsif (@extracts) {
if (@extracts) {
extract_keys(@extracts);
}
else {
usage();
}
}
# Adding a new agent
elsif ($add) {
if ($agentname && $ipaddress &&
(
$ipaddress =~ m/$RE{net}{IPv4}/
||
$ipaddress =~ m/$RE{net}{IPv6}/
||
$ipaddress eq 'any'
) &&
# ossec doesn't like agent names > 32 characters.
length($agentname) <= 32) {
# Autogenerate an id incremented 1 from the last in a sorted list of
# all current ones if it isn't specified from the command line.
if (!$agentid) {
# Make a list of all of the used agentids and then sort it.
if (-r AUTH_KEY_FILE) {
my @used_agent_ids = ();
open (FH, "<", AUTH_KEY_FILE);
while (<FH>) {
my ($id, $name, $ip, $key) = split;
push(@used_agent_ids, $id);
}
close(FH);
if (@used_agent_ids) {
@used_agent_ids = sort {$a <=> $b} @used_agent_ids;
$agentid = sprintf("%03d", $used_agent_ids[-1] + 1);
}
}
# If the client.keys is empty or doesn't exist set the id to 001
$agentid = sprintf("%03d", 001) if (!$agentid);
}
# Autogenerate a key unless one was specified on the command line
if (!$key) {
use Time::HiRes; # Standard with perl >= 5.8.2
my $rand_str1 = time() . $agentname . rand(10000);
my $rand_str2 = Time::HiRes::time . $ipaddress . $agentid . rand(10000);
$key = md5_hex($rand_str1) . md5_hex($rand_str2);
}
add_agent($agentid, $agentname, $ipaddress, $key);
}
else {
warn "Error: adding agents requires: --name and --ip options.\n";
usage();
}
}
elsif ($remove) {
if ($agentid) {
remove_agent($agentid);
}
else {
remove_agent($remove)
}
}
elsif ($import) {
# Every option needs to be specified and NOT autogenerated because what
# is autogenerated on the server and the agent will likely be different
if (!$agentid || !$agentname || !$ipaddress || !$key) {
warn "Error: importing requires: --id, --name, --ip, and --key\n";
usage();
}
else {
# The key extracted from the server needs to be decoded before being put
# into the client.keys
$key = MIME::Base64::decode($key);
add_agent($agentid, $agentname, $ipaddress, $key);
}
}
else {
warn "Error: no options specified!\n";
usage();
}
sub usage {
warn "Usage: $0 [OPERATION] [OPTIONS]\n";
warn " [operations]\n";
warn " -a or --add = Add a new agent\n";
warn " -r or --remove [id] = Remove agent\n";
warn " -e or --extract [id|name|ip] = Extract key\n";
warn " -m or --import [keydata] = Import key\n";
warn " -l or --list = List available agents\n";
warn " [options]\n";
warn " -k or --key [keydata] = Key data\n";
warn " -n or --name [name] = Agent name (32 character max)\n";
warn " -i or --id [id] = Agent identification (integer)\n";
warn " -p or --ip [ip] = IP address\n\n";
exit 1;
}
sub list_agents {
if (-r AUTH_KEY_FILE) {
open (FH, "<", AUTH_KEY_FILE);
}
else {
die "Error reading ".AUTH_KEY_FILE.": $!\n";
}
print "Available Agents:\n";
print "ID", " " x (25 - length('ID')),
"NAME", " " x (25 - length('NAME')),
"IP", " " x (25 - length('IP'));
print "\n";
while (<FH>) {
chomp;
my ($id, $name, $ip, $key) = split;
if (defined($key)) {
print "$id", " " x (25 - length($id)),
"$name", " " x (25 - length($name)),
"$ip", " " x (25 - length($ip)) . "\n";
}
}
close(FH);
exit 0;
}
sub extract_keys {
if (-r AUTH_KEY_FILE) {
open (FH, "<", AUTH_KEY_FILE);
}
else {
die "No ".AUTH_KEY_FILE."!\n";
}
foreach my $extract (@_) {
my ($encoded, $decoded);
my $found = 0;
while (<FH>) {
chomp;
my ($id, $name, $ip, $key) = split;
# Check to make sure it's a valid entry
if (defined($key)) {
if (($extract =~ /^\d+$/) && ($id == $extract)) {
$found = 1;
}
elsif ($name eq $extract) {
$found = 1;
}
elsif ($ip eq $extract) {
$found = 1;
}
else {
next;
}
# Newlines are valid base64 characters so use '' instead for \n
$decoded = MIME::Base64::encode($_, '');
print "$name,$ip $decoded\n";
next;
}
}
if (!$found) {
warn "Error: Agent $extract doesn't exist!\n";
}
seek FH,0,0;
}
}
sub add_agent {
my $id = shift;
my $name = shift;
my $ip = shift;
my $agentkey = shift;
if ($name && $ip && $agentkey) {
# Valid example key:
# 5a832efb8f93660857ce2acf8eec66a19fd9d4fa58e3221bbd2927ca8a0b40c3
if ($agentkey !~ m/[a-z0-9]{64}/) {
warn "Error: invalid keydata! Let this script autogenerate it.\n";
usage();
}
my @newagent = ($id, $name, $ip, $agentkey);
my $exists = check_if_exists(\@newagent);
if ($exists == 0) {
# Append if client.keys exists and create it if it doesn't
if (-e AUTH_KEY_FILE) {
open(FH, ">>", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
}
else {
open(FH, ">", AUTH_KEY_FILE) or die AUTH_KEY_FILE." error: $!\n";
}
print FH join(' ', @newagent), "\n";
close(FH);
}
elsif ($exists == 1) {
warn "ID: $id already in ".AUTH_KEY_FILE."!\n";
}
elsif ($exists == 2) {
warn "Agent: $name already in ".AUTH_KEY_FILE."!\n";
}
elsif ($exists == 3) {
warn "IP: $ip already in ".AUTH_KEY_FILE."!\n";
}
}
else {
warn "Missing options to --add or problem with ".AUTH_KEY_FILE.": $!\n";
usage();
}
}
sub remove_agent {
my $removeid = shift;
my @agent_array;
if (-r AUTH_KEY_FILE) {
open (FH, "<", AUTH_KEY_FILE);
}
else {
die "Error: with ".AUTH_KEY_FILE.": $!\n";
}
while (<FH>) {
push(@agent_array, $_);
}
close(FH);
if (-w AUTH_KEY_FILE) {
open (FHRW, ">", AUTH_KEY_FILE);
}
else {
die "Error writing ".AUTH_KEY_FILE.": $!\n";
}
my $key_found = 0;
foreach my $line (@agent_array) {
my @split_line = split(/\s/,$line);
if ($split_line[0] ne $removeid) {
print FHRW "$line";
}
else {
my $rids_file = RIDS_PATH.$removeid;
$key_found = 1;
unlink $rids_file or warn "Could not remove rids file for Agent ID \'".$removeid."\'!\n";
}
}
close(FHRW);
if (!$key_found) {
die "Agent ID \'".$removeid."\' not found! Nothing removed.\n";
}
exit(0);
}
sub check_if_exists {
my $agentlist_ref = shift;
my ($newid, $newname, $newip);
my $rval = 0;
$newid = $agentlist_ref->[0];
$newname = $agentlist_ref->[1];
$newip = $agentlist_ref->[2];
# If the file isn't readable, the id probably isn't already in it
if (-r AUTH_KEY_FILE) {
open (FH, "<", AUTH_KEY_FILE);
while (<FH>) {
chomp;
my ($id, $name, $ip, $key) = split;
if(defined($key)) {
$rval = 1 if ($id == $newid && $rval == 0);
$rval = 2 if ($name eq $newname && $rval == 0);
$rval = 3 if ($ip ne 'any' && $ip eq $newip && $rval == 0);
}
}
close(FH);
}
return $rval;
}