Skip to content

Commit dad69ac

Browse files
authored
Add simple container check and app on FIPS. (#592)
Add FIPS test and example application with HTTP. The check is simply executing OpenSSL.fips_mode which returns bool. Based on exit status we can then know whether it failed/succeeded as we expect it to. The app has 4 GET endpoints that executes some OpenSSL capability. 2 of those test symmetric ciphers: * '/symmetric/aes-256-cbc' -- succeeds under FIPS * '/symmetric/des-ede-cbc' -- fails under FIPS 2 of those test digests: * '/hash/sha256' -- succeeds under FIPS * '/hash/md5' -- fails under FIPS The app is prepared so that it tests assumptions of when should what fail, under both FIPS and non-fips environment. These endpoints either return 200 if the case for them succeeded or 5xx for FIPS related failures and 4xx for general failures not accounted for. When a failure happens when it shouldn't, the app also returns backtrace in the response body. A few examples: MD5 succeeds and FIPS is enabled, that's unexpected, returns 500 SHA256 fails in any case, 409 is returned because that shouldn't happen with both FIPS disabled and enabled, something else went wrong. MD5 fails with FIPS enabled, that's desired and expected, returns 200. Since more information is passed within body on response, `curl --fail-with-body` is recommended. 409 is chosen to differentiate 500 returned in cases we might expect. It was chosen firstly as it is "user error", either the code is wrong, or the setup is wrong. For the purpose of building and running the app, an adjustment was made to run_test_application to be able to run a custom named container. Otherwise there is only testapp to be ran and we can have better names.
1 parent 1d75a89 commit dad69ac

File tree

7 files changed

+244
-5
lines changed

7 files changed

+244
-5
lines changed

3.0/test/test-fips/Gemfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
source 'https://rubygems.org'
4+
5+
gem 'sinatra'
6+
7+
gem 'rackup'
8+
9+
# Use webrick for simple HTTP transport.
10+
# this is not a production grade server, but gets the job done for the
11+
# purpose of just sending something over for a request.
12+
# Additionally there is an option to add SSL later here.
13+
gem 'webrick'

3.0/test/test-fips/app.rb

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
require 'sinatra'
2+
require 'openssl'
3+
4+
set :server, 'webrick'
5+
set :bind, '0.0.0.0'
6+
set :port, 8080
7+
8+
MESSAGE = "My secret text\n".freeze
9+
10+
get '/symmetric/aes-256-cbc' do
11+
fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled'
12+
# This should pass with and without FIPS.
13+
cipher = OpenSSL::Cipher.new('aes-256-cfb')
14+
cipher.encrypt
15+
cipher.random_key
16+
cipher.random_iv
17+
enc = cipher.update(MESSAGE) + cipher.final
18+
return 200, enc
19+
rescue => e
20+
return 409, "Unexpected failure with aes-256-cbc, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
21+
end
22+
23+
get '/symmetric/des-ede-cbc' do
24+
status = 200
25+
fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled'
26+
27+
cipher = OpenSSL::Cipher.new('des-ede-cbc')
28+
cipher.encrypt
29+
# This fails in FIPS only once we try to get a key for the 3DES.
30+
cipher.random_key
31+
cipher.random_iv
32+
cipher.update(MESSAGE) + cipher.final
33+
rescue OpenSSL::Cipher::CipherError => e
34+
return status, "Failed with fips #{fips_state} #{e.inspect}\n" if OpenSSL.fips_mode
35+
36+
return 500, "Failed with fips #{fips_state} #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
37+
rescue => e
38+
return 409, "Unexpected failure with des-ede-cbc, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
39+
end
40+
41+
get '/hash/sha256' do
42+
status = 200
43+
44+
fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled'
45+
message = "SHA256 succeeded, fips is #{fips_state}"
46+
47+
OpenSSL::Digest.digest('SHA256', MESSAGE)
48+
49+
return status, message
50+
rescue => e
51+
return 409, "Unexpected failure with SHA256, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
52+
end
53+
54+
get '/hash/md5' do
55+
status = 200
56+
57+
fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled'
58+
message = "MD5 succeeded, fips is #{fips_state}"
59+
60+
OpenSSL::Digest.digest('MD5', MESSAGE)
61+
62+
# FIPS is on, but this passed, that shouldn't be the case.
63+
status = 500 if OpenSSL.fips_mode
64+
65+
return status, message
66+
rescue OpenSSL::Digest::DigestError => e
67+
return status, "Failed with fips #{fips_state} #{e.inspect}\n" if OpenSSL.fips_mode
68+
69+
return 500, "Failed with fips #{fips_state} #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
70+
rescue => e
71+
return 409, "Unexpected failure with MD5, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
72+
end

3.0/test/test-fips/config.ru

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require './app'
2+
run Sinatra::Application

3.3/test/test-fips/Gemfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
source 'https://rubygems.org'
4+
5+
gem 'sinatra'
6+
7+
gem 'rackup'
8+
9+
# Use webrick for simple HTTP transport.
10+
# this is not a production grade server, but gets the job done for the
11+
# purpose of just sending something over for a request.
12+
# Additionally there is an option to add SSL later here.
13+
gem 'webrick'

3.3/test/test-fips/app.rb

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
require 'sinatra'
2+
require 'openssl'
3+
4+
set :server, 'webrick'
5+
set :bind, '0.0.0.0'
6+
set :port, 8080
7+
8+
MESSAGE = "My secret text\n".freeze
9+
10+
get '/symmetric/aes-256-cbc' do
11+
fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled'
12+
# This should pass with and without FIPS.
13+
cipher = OpenSSL::Cipher.new('aes-256-cfb')
14+
cipher.encrypt
15+
cipher.random_key
16+
cipher.random_iv
17+
enc = cipher.update(MESSAGE) + cipher.final
18+
return 200, enc
19+
rescue => e
20+
return 409, "Unexpected failure with aes-256-cbc, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
21+
end
22+
23+
get '/symmetric/des-ede-cbc' do
24+
status = 200
25+
fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled'
26+
27+
cipher = OpenSSL::Cipher.new('des-ede-cbc')
28+
cipher.encrypt
29+
# This fails in FIPS only once we try to get a key for the 3DES.
30+
cipher.random_key
31+
cipher.random_iv
32+
cipher.update(MESSAGE) + cipher.final
33+
rescue OpenSSL::Cipher::CipherError => e
34+
return status, "Failed with fips #{fips_state} #{e.inspect}\n" if OpenSSL.fips_mode
35+
36+
return 500, "Failed with fips #{fips_state} #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
37+
rescue => e
38+
return 409, "Unexpected failure with des-ede-cbc, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
39+
end
40+
41+
get '/hash/sha256' do
42+
status = 200
43+
44+
fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled'
45+
message = "SHA256 succeeded, fips is #{fips_state}"
46+
47+
OpenSSL::Digest.digest('SHA256', MESSAGE)
48+
49+
return status, message
50+
rescue => e
51+
return 409, "Unexpected failure with SHA256, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
52+
end
53+
54+
get '/hash/md5' do
55+
status = 200
56+
57+
fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled'
58+
message = "MD5 succeeded, fips is #{fips_state}"
59+
60+
OpenSSL::Digest.digest('MD5', MESSAGE)
61+
62+
# FIPS is on, but this passed, that shouldn't be the case.
63+
status = 500 if OpenSSL.fips_mode
64+
65+
return status, message
66+
rescue OpenSSL::Digest::DigestError => e
67+
return status, "Failed with fips #{fips_state} #{e.inspect}\n" if OpenSSL.fips_mode
68+
69+
return 500, "Failed with fips #{fips_state} #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
70+
rescue => e
71+
return 409, "Unexpected failure with MD5, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n"
72+
end

3.3/test/test-fips/config.ru

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require './app'
2+
run Sinatra::Application

test/run

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ test_connection
2525
test_scl_usage
2626
test_npm_functionality
2727
"
28+
29+
TEST_LIST_FIPS="\
30+
test_ruby_fips_mode
31+
test_ruby_fips_s2i_app
32+
"
33+
2834
source "${test_dir}/test-lib.sh"
2935

3036
# Read exposed port from image meta data
@@ -51,7 +57,8 @@ run_s2i_build() {
5157
}
5258

5359
run_test_application() {
54-
docker run --user=100001 --rm --cidfile=${cid_file} ${IMAGE_NAME}-testapp
60+
local appname=${1:-testapp}
61+
docker run --user=100001 --rm --cidfile=${cid_file} ${IMAGE_NAME}-${appname}
5562
}
5663

5764
test_s2i_usage() {
@@ -168,10 +175,12 @@ app_cleanup() {
168175
}
169176

170177
function cleanup() {
171-
info "Cleaning up the test application image ${IMAGE_NAME}-testapp"
172-
if image_exists ${IMAGE_NAME}-testapp; then
173-
docker rmi -f ${IMAGE_NAME}-testapp
174-
fi
178+
for image in testapp testfips; do
179+
info "Cleaning up the test application image ${IMAGE_NAME}-${image}"
180+
if image_exists ${IMAGE_NAME}-${image}; then
181+
docker rmi -f ${IMAGE_NAME}-${image}
182+
fi
183+
done
175184
}
176185

177186
# Positive test & non-zero exit status = ERROR.
@@ -209,6 +218,53 @@ evaluate_build_result() {
209218
return $_ret_code
210219
}
211220

221+
# "0" if system is not FIPS enabled, "1" if it is.
222+
function fips_enabled() {
223+
local is_fips_enabled
224+
225+
# Read fips mode from host in case exists
226+
if [[ -f /proc/sys/crypto/fips_enabled ]]; then
227+
echo "$(cat /proc/sys/crypto/fips_enabled)"
228+
else
229+
echo "0"
230+
fi
231+
}
232+
233+
function run_s2i_build_fips() {
234+
ct_s2i_build_as_df file://${test_dir}/test-fips ${IMAGE_NAME} ${IMAGE_NAME}-testfips ${s2i_args} $1
235+
}
236+
237+
function test_ruby_fips_mode() {
238+
if [[ "$(fips_enabled)" == "0" ]]; then
239+
# FIPS disabled -> OpenSSL#fips_mode returns false
240+
echo "Fips should be disabled"
241+
docker run --rm "$IMAGE_NAME" /bin/bash -c 'ruby -ropenssl -e "exit !OpenSSL.fips_mode"'
242+
ct_check_testcase_result "$?"
243+
else
244+
echo "Fips should be enabled"
245+
# FIPS enabled -> OpenSSL#fips_mode returns true
246+
docker run --rm "$IMAGE_NAME" /bin/bash -c 'ruby -ropenssl -e "exit OpenSSL.fips_mode"'
247+
ct_check_testcase_result "$?"
248+
fi
249+
}
250+
251+
function test_ruby_fips_s2i_app() {
252+
run_s2i_build_fips
253+
evaluate_build_result $? "default"
254+
# Verify that the HTTP connection can be established to test application container
255+
run_test_application testfips &
256+
# Wait for the container to write its CID file
257+
ct_wait_for_cid "${cid_file}"
258+
ct_test_response "http://$(container_ip):8080/symmetric/aes-256-cbc" 200 ""
259+
ct_check_testcase_result $?
260+
ct_test_response "http://$(container_ip):8080/symmetric/des-ede-cbc" 200 ""
261+
ct_check_testcase_result $?
262+
ct_test_response "http://$(container_ip):8080/hash/sha256" 200 ""
263+
ct_check_testcase_result $?
264+
ct_test_response "http://$(container_ip):8080/hash/md5" 200 ""
265+
ct_check_testcase_result $?
266+
}
267+
212268
# Prepare dependencies for tests
213269
pushd ${test_dir}
214270
# db test dependencies
@@ -246,3 +302,12 @@ echo "Removing test dependencies"
246302
rm -rf ${test_dir}/db-test-app
247303

248304
app_cleanup
305+
306+
if [[ "$OS" == "rhel8" ]]; then
307+
echo "Not executing fips tests on RHEL 8"
308+
else
309+
echo "Testing fips mode"
310+
TEST_SET=${TESTS:-$TEST_LIST_FIPS} ct_run_tests_from_testset "fips"
311+
echo "Testing the production image build for fips"
312+
cleanup
313+
fi

0 commit comments

Comments
 (0)