Skip to content

Commit d981bb0

Browse files
committed
Add simple container check and app on FIPS.
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, 418 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, and 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 60391e6 commit d981bb0

File tree

7 files changed

+237
-5
lines changed

7 files changed

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

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

178187
# Positive test & non-zero exit status = ERROR.
@@ -210,6 +219,50 @@ evaluate_build_result() {
210219
return $_ret_code
211220
}
212221

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

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

0 commit comments

Comments
 (0)