-
Notifications
You must be signed in to change notification settings - Fork 380
/
oscap-ssh
executable file
·344 lines (309 loc) · 13 KB
/
oscap-ssh
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
#!/usr/bin/env bash
# Copyright 2015 Martin Preisler <martin@preisler.me>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
function die()
{
echo "$*" >&2
exit 1
}
function invalid()
{
echo -e "$*\n" >&2
usage
exit 1
}
function usage()
{
echo "oscap-ssh -- Tool for running oscap over SSH and collecting results."
echo
echo "Usage:"
echo
echo "$ oscap-ssh user@host 22 info INPUT_CONTENT"
echo "$ oscap-ssh user@host 22 xccdf eval [options] INPUT_CONTENT"
echo
echo "Only source data streams are supported as INPUT_CONTENT!"
echo
echo "supported oscap xccdf eval options are:"
echo " --profile"
echo " --tailoring-file"
echo " --tailoring-id"
echo " --cpe (external OVAL dependencies are not supported yet!)"
# --check-engine-results is not supported
# use --results-arf instead
echo " --oval-results"
echo " --results"
echo " --results-arf"
echo " --report"
echo " --skip-validation"
echo " --fetch-remote-resources"
echo " --local-files"
echo " --progress"
echo " --datastream-id"
echo " --xccdf-id"
echo " --benchmark-id"
echo " --remediate"
echo
echo "$ oscap-ssh user@host 22 oval eval [options] INPUT_CONTENT"
echo
echo "supported oscap oval eval options are:"
echo " --id"
echo " --variables"
echo " --directives"
echo " --results"
echo " --report"
echo " --skip-validation"
echo " --datastream-id"
echo " --oval-id"
echo
echo "$ oscap-ssh user@host 22 oval collect [options] INPUT_CONTENT"
echo
echo "supported oscap oval collect options are:"
echo " --id"
echo " --syschar"
echo " --variables"
echo " --skip-validation"
echo
echo "specific option for oscap-ssh (must be first argument):"
echo " --sudo"
echo
echo "To supply additional options to ssh/scp, define the SSH_ADDITIONAL_OPTIONS variable"
echo "For instance, to ignore known hosts records, define SSH_ADDITIONAL_OPTIONS='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null'"
echo
echo "specific option for oscap-ssh (must be first argument):"
echo
echo "See \`man oscap\` to learn more about semantics of these options."
}
# $1, $2, ... SSH options (pass them as separate arguments)
function ssh_execute_with_options {
ssh -o ControlPath="$CONTROL_SOCKET" $SSH_ADDITIONAL_OPTIONS "$@" -p "$SSH_PORT" "$SSH_HOST"
}
# $1: The SSH command.
# $2: More of additional options (optional, pass one space-separated string)
function ssh_execute_with_command_and_options {
ssh -o ControlPath="$CONTROL_SOCKET" $SSH_ADDITIONAL_OPTIONS $2 -p "$SSH_PORT" "$SSH_HOST" "$1"
}
# $1: Local filename to copy
# $2: Remote destination
function scp_copy_to_temp_dir {
scp -o ControlPath="$CONTROL_SOCKET" -P "$SSH_PORT" $SSH_ADDITIONAL_OPTIONS "$1" "$SSH_HOST:$REMOTE_TEMP_DIR/$2"
}
# $1: Local directory name to copy
# $2: Remote destination
function scp_copy_dir_to_temp_dir {
scp -r -o ControlPath="$CONTROL_SOCKET" -P "$SSH_PORT" $SSH_ADDITIONAL_OPTIONS "$1" "$SSH_HOST:$REMOTE_TEMP_DIR/$2"
}
# $1: Remote filename to get
# $2: Local destination
function scp_retreive_from_temp_dir {
scp -o ControlPath="$CONTROL_SOCKET" -P "$SSH_PORT" $SSH_ADDITIONAL_OPTIONS "$SSH_HOST:$REMOTE_TEMP_DIR/$1" "$2"
}
# $1: The name of the array holding command elements
# Returns: String, where individual command components are double-quoted, so they are not interpreted by the shell.
# For example, an array ('-p' '(all)') will be transformed to "\"-p\" \"(all)\"", so after the shell expansion, it will end up as "-p" "(all)".
function command_array_to_string {
eval "printf '\"%s\" ' \"\${$1[@]}\""
}
function first_argument_is_sudo {
[ "$1" == "sudo" ] || [ "$1" == "--sudo" ]
return $?
}
function sanity_check_arguments {
if [ $# -lt 1 ]; then
invalid "No arguments provided."
elif [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
usage
exit 0
elif first_argument_is_sudo "$@"; then
OSCAP_SUDO="sudo"
# force pseudo-tty allocation so that users can type their password if necessary
SSH_TTY_ALLOCATION_OPTION="-t"
shift
fi
if [ $# -lt 2 ]; then
invalid "Missing ssh host and ssh port."
fi
}
function check_oscap_arguments {
if [ "$1" == "--v" ] || [ "$1" == "--version" ]; then
true
elif [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
true
elif [ "$1" == "info" ]; then
true
elif [ "$1 $2" == "xccdf eval" ]; then
true
elif [ "$1 $2" == "oval eval" ]; then
true
elif [ "$1 $2" == "oval collect" ]; then
true
else
die "This script only supports 'sudo' as first argument, '-h', '--help', '--v', '--version', 'info', 'xccdf eval', 'oval eval' and 'oval collect'."
fi
}
hash ssh 2> /dev/null || die "Cannot find ssh, please install the OpenSSH client."
hash scp 2> /dev/null || die "Cannot find scp, please install the OpenSSH client."
hash mktemp 2> /dev/null || die "Cannot find mktemp, please install coreutils."
OSCAP_SUDO=""
# SSH_ADDITIONAL_OPTIONS may be defined in the calling shell
SSH_TTY_ALLOCATION_OPTION=""
sanity_check_arguments "$@"
first_argument_is_sudo "$@" && shift
SSH_HOST="$1"
SSH_PORT="$2"
shift 2
check_oscap_arguments "$@"
CONTROL_SOCKET_DIR=$(mktemp -d)
CONTROL_SOCKET="$CONTROL_SOCKET_DIR/ssh_socket"
echo "Connecting to '$SSH_HOST' on port '$SSH_PORT'..."
ssh_execute_with_options -M -f -N -o ServerAliveInterval=60 || die "Failed to connect!"
echo "Connected!"
REMOTE_TEMP_DIR=$(ssh_execute_with_command_and_options "mktemp -d") || die "Failed to create remote temporary directory!"
oscap_args=("$@")
LOCAL_CONTENT_PATH=""
LOCAL_TAILORING_PATH=""
LOCAL_LOCAL_FILES_PATH=""
LOCAL_CPE_PATH=""
LOCAL_VARIABLES_PATH=""
LOCAL_DIRECTIVES_PATH=""
TARGET_RESULTS=""
TARGET_RESULTS_ARF=""
TARGET_REPORT=""
TARGET_SYSCHAR=""
OVAL_RESULTS=""
STIG_RESULTS=""
# We have to rewrite various paths to a remote temp dir
for i in $(seq 0 `expr $# - 1`); do
let j=i+1
case "${oscap_args[i]}" in
("--tailoring-file")
LOCAL_TAILORING_PATH=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/tailoring.xml"
;;
("--stig-viewer")
STIG_RESULTS=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/stig_results.xml"
;;
("--local-files")
LOCAL_LOCAL_FILES_PATH=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/local_files"
;;
("--cpe")
LOCAL_CPE_PATH=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/cpe.xml"
;;
("--variables")
LOCAL_VARIABLES_PATH=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/variables.xml"
;;
("--directives")
LOCAL_DIRECTIVES_PATH=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/directives.xml"
;;
("--results")
TARGET_RESULTS=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/results.xml"
;;
("--results-arf")
TARGET_RESULTS_ARF=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/results-arf.xml"
;;
("--report")
TARGET_REPORT=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/report.html"
;;
("--syschar")
TARGET_SYSCHAR=${oscap_args[j]}
oscap_args[j]="$REMOTE_TEMP_DIR/syschar.xml"
;;
("--oval-results")
OVAL_RESULTS="yes"
;;
*)
;;
esac
done
if [ "$1" != "--v" ] && [ "$1" != "--version" ] && [ "$1" != "-h" ] && [ "$1" != "--help" ]; then
# Last argument should be the content path
LOCAL_CONTENT_PATH="${oscap_args[`expr $# - 1`]}"
oscap_args[`expr $# - 1`]="$REMOTE_TEMP_DIR/input.xml"
fi
[ "$LOCAL_CONTENT_PATH" == "" ] || [ -f "$LOCAL_CONTENT_PATH" ] || die "Expected the last argument to be an input file, '$LOCAL_CONTENT_PATH' isn't a valid file path or the file doesn't exist!"
[ "$LOCAL_TAILORING_PATH" == "" ] || [ -f "$LOCAL_TAILORING_PATH" ] || die "Tailoring file path '$LOCAL_TAILORING_PATH' isn't a valid file path or the file doesn't exist!"
[ "$LOCAL_LOCAL_FILES_PATH" == "" ] || [ -d "$LOCAL_LOCAL_FILES_PATH" ] || die "Directory '$LOCAL_LOCAL_FILES_PATH' isn't a valid directory path or the directory doesn't exist!"
[ "$LOCAL_CPE_PATH" == "" ] || [ -f "$LOCAL_CPE_PATH" ] || die "CPE file path '$LOCAL_CPE_PATH' isn't a valid file path or the file doesn't exist!"
[ "$LOCAL_VARIABLES_PATH" == "" ] || [ -f "$LOCAL_VARIABLES_PATH" ] || die "OVAL variables file path '$LOCAL_VARIABLES_PATH' isn't a valid file path or the file doesn't exist!"
[ "$LOCAL_DIRECTIVES_PATH" == "" ] || [ -f "$LOCAL_DIRECTIVES_PATH" ] || die "OVAL directives file path '$LOCAL_DIRECTIVES_PATH' isn't a valid file path or the file doesn't exist!"
if [ "$LOCAL_CONTENT_PATH" != "" ]; then
echo "Copying input file '$LOCAL_CONTENT_PATH' to remote working directory '$REMOTE_TEMP_DIR'..."
scp_copy_to_temp_dir "$LOCAL_CONTENT_PATH" input.xml || die "Failed to copy input file to remote temporary directory!"
fi
if [ "$LOCAL_TAILORING_PATH" != "" ]; then
echo "Copying tailoring file '$LOCAL_TAILORING_PATH' to remote working directory '$REMOTE_TEMP_DIR'..."
scp_copy_to_temp_dir "$LOCAL_TAILORING_PATH" tailoring.xml || die "Failed to copy tailoring file to remote temporary directory!"
fi
if [ "$LOCAL_LOCAL_FILES_PATH" != "" ]; then
echo "Copying directory '$LOCAL_LOCAL_FILES_PATH' to remote working directory '$REMOTE_TEMP_DIR'..."
scp_copy_dir_to_temp_dir "$LOCAL_LOCAL_FILES_PATH" local_files || die "Failed to copy directory $LOCAL_LOCAL_FILES_PATH to remote temporary directory!"
fi
if [ "$LOCAL_CPE_PATH" != "" ]; then
echo "Copying CPE file '$LOCAL_CPE_PATH' to remote working directory '$REMOTE_TEMP_DIR'..."
scp_copy_to_temp_dir "$LOCAL_CPE_PATH" cpe.xml || die "Failed to copy CPE file to remote temporary directory!"
fi
if [ "$LOCAL_VARIABLES_PATH" != "" ]; then
echo "Copying OVAL variables file '$LOCAL_VARIABLES_PATH' to remote working directory '$REMOTE_TEMP_DIR'..."
scp_copy_to_temp_dir "$LOCAL_VARIABLES_PATH" variables.xml || die "Failed to copy OVAL variables file to remote temporary directory!"
fi
if [ "$LOCAL_DIRECTIVES_PATH" != "" ]; then
echo "Copying OVAL directives file '$LOCAL_DIRECTIVES_PATH' to remote working directory '$REMOTE_TEMP_DIR'..."
scp_copy_to_temp_dir "$LOCAL_DIRECTIVES_PATH" directives.xml || die "Failed to copy OVAL directives file to remote temporary directory!"
fi
echo "Starting the evaluation..."
# changing directory because of --oval-results support. oval results files are
# dumped into PWD, and we can't be sure by the file names - we need controlled
# environment
if [ -z "$OSCAP_SUDO" ]; then
ssh_execute_with_command_and_options "cd $REMOTE_TEMP_DIR; oscap $(command_array_to_string oscap_args)" "$SSH_TTY_ALLOCATION_OPTION"
else
OSCAP_CMD="oscap $(command_array_to_string oscap_args); rc=\$?; chown \$SUDO_USER $REMOTE_TEMP_DIR/*; exit \$rc"
ssh_execute_with_command_and_options "cd $REMOTE_TEMP_DIR; $OSCAP_SUDO sh -c '$OSCAP_CMD'" "$SSH_TTY_ALLOCATION_OPTION"
fi
OSCAP_EXIT_CODE=$?
echo "oscap exit code: $OSCAP_EXIT_CODE"
echo "Copying back requested files..."
if [ "$TARGET_RESULTS" != "" ]; then
scp_retreive_from_temp_dir results.xml "$TARGET_RESULTS" || die "Failed to copy the results file back to local machine!"
fi
if [ "$STIG_RESULTS" != "" ]; then
scp_retreive_from_temp_dir stig_results.xml "$STIG_RESULTS" || die "Failed to copy the STIG results file back to local machine!"
fi
if [ "$TARGET_RESULTS_ARF" != "" ]; then
scp_retreive_from_temp_dir results-arf.xml "$TARGET_RESULTS_ARF" || die "Failed to copy the ARF file back to local machine!"
fi
if [ "$TARGET_REPORT" != "" ]; then
scp_retreive_from_temp_dir report.html "$TARGET_REPORT" || die "Failed to copy the HTML report back to local machine!"
fi
if [ "$TARGET_SYSCHAR" != "" ]; then
scp_retreive_from_temp_dir syschar.xml "$TARGET_SYSCHAR" || die "Failed to copy the OVAL syschar file back to local machine!"
fi
if [ "$OVAL_RESULTS" == "yes" ]; then
scp_retreive_from_temp_dir '*.result.xml' "./" || die "Failed to copy OVAL result files back to local machine!"
fi
echo "Removing remote temporary directory..."
ssh_execute_with_command_and_options "rm -r $REMOTE_TEMP_DIR" || die "Failed to remove remote temporary directory!"
echo "Disconnecting ssh and removing control ssh socket directory..."
ssh_execute_with_options -O exit || die "Failed to disconnect!"
rm -r "$CONTROL_SOCKET_DIR" || die "Failed to remove local control SSH socket directory!"
exit $OSCAP_EXIT_CODE