diff --git a/3.0/test/test-fips/Gemfile b/3.0/test/test-fips/Gemfile new file mode 100644 index 00000000..21fff265 --- /dev/null +++ b/3.0/test/test-fips/Gemfile @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'sinatra' + +gem 'rackup' + +# Use webrick for simple HTTP transport. +# this is not a production grade server, but gets the job done for the +# purpose of just sending something over for a request. +# Additionally there is an option to add SSL later here. +gem 'webrick' diff --git a/3.0/test/test-fips/app.rb b/3.0/test/test-fips/app.rb new file mode 100644 index 00000000..8c5a9eb0 --- /dev/null +++ b/3.0/test/test-fips/app.rb @@ -0,0 +1,72 @@ +require 'sinatra' +require 'openssl' + +set :server, 'webrick' +set :bind, '0.0.0.0' +set :port, 8080 + +MESSAGE = "My secret text\n".freeze + +get '/symmetric/aes-256-cbc' do + fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled' + # This should pass with and without FIPS. + cipher = OpenSSL::Cipher.new('aes-256-cfb') + cipher.encrypt + cipher.random_key + cipher.random_iv + enc = cipher.update(MESSAGE) + cipher.final + return 200, enc +rescue => e + return 409, "Unexpected failure with aes-256-cbc, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +end + +get '/symmetric/des-ede-cbc' do + status = 200 + fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled' + + cipher = OpenSSL::Cipher.new('des-ede-cbc') + cipher.encrypt + # This fails in FIPS only once we try to get a key for the 3DES. + cipher.random_key + cipher.random_iv + cipher.update(MESSAGE) + cipher.final +rescue OpenSSL::Cipher::CipherError => e + return status, "Failed with fips #{fips_state} #{e.inspect}\n" if OpenSSL.fips_mode + + return 500, "Failed with fips #{fips_state} #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +rescue => e + return 409, "Unexpected failure with des-ede-cbc, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +end + +get '/hash/sha256' do + status = 200 + + fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled' + message = "SHA256 succeeded, fips is #{fips_state}" + + OpenSSL::Digest.digest('SHA256', MESSAGE) + + return status, message +rescue => e + return 409, "Unexpected failure with SHA256, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +end + +get '/hash/md5' do + status = 200 + + fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled' + message = "MD5 succeeded, fips is #{fips_state}" + + OpenSSL::Digest.digest('MD5', MESSAGE) + + # FIPS is on, but this passed, that shouldn't be the case. + status = 500 if OpenSSL.fips_mode + + return status, message +rescue OpenSSL::Digest::DigestError => e + return status, "Failed with fips #{fips_state} #{e.inspect}\n" if OpenSSL.fips_mode + + return 500, "Failed with fips #{fips_state} #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +rescue => e + return 409, "Unexpected failure with MD5, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +end diff --git a/3.0/test/test-fips/config.ru b/3.0/test/test-fips/config.ru new file mode 100644 index 00000000..76a6edff --- /dev/null +++ b/3.0/test/test-fips/config.ru @@ -0,0 +1,2 @@ +require './app' +run Sinatra::Application diff --git a/3.3/test/test-fips/Gemfile b/3.3/test/test-fips/Gemfile new file mode 100644 index 00000000..21fff265 --- /dev/null +++ b/3.3/test/test-fips/Gemfile @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'sinatra' + +gem 'rackup' + +# Use webrick for simple HTTP transport. +# this is not a production grade server, but gets the job done for the +# purpose of just sending something over for a request. +# Additionally there is an option to add SSL later here. +gem 'webrick' diff --git a/3.3/test/test-fips/app.rb b/3.3/test/test-fips/app.rb new file mode 100644 index 00000000..8c5a9eb0 --- /dev/null +++ b/3.3/test/test-fips/app.rb @@ -0,0 +1,72 @@ +require 'sinatra' +require 'openssl' + +set :server, 'webrick' +set :bind, '0.0.0.0' +set :port, 8080 + +MESSAGE = "My secret text\n".freeze + +get '/symmetric/aes-256-cbc' do + fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled' + # This should pass with and without FIPS. + cipher = OpenSSL::Cipher.new('aes-256-cfb') + cipher.encrypt + cipher.random_key + cipher.random_iv + enc = cipher.update(MESSAGE) + cipher.final + return 200, enc +rescue => e + return 409, "Unexpected failure with aes-256-cbc, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +end + +get '/symmetric/des-ede-cbc' do + status = 200 + fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled' + + cipher = OpenSSL::Cipher.new('des-ede-cbc') + cipher.encrypt + # This fails in FIPS only once we try to get a key for the 3DES. + cipher.random_key + cipher.random_iv + cipher.update(MESSAGE) + cipher.final +rescue OpenSSL::Cipher::CipherError => e + return status, "Failed with fips #{fips_state} #{e.inspect}\n" if OpenSSL.fips_mode + + return 500, "Failed with fips #{fips_state} #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +rescue => e + return 409, "Unexpected failure with des-ede-cbc, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +end + +get '/hash/sha256' do + status = 200 + + fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled' + message = "SHA256 succeeded, fips is #{fips_state}" + + OpenSSL::Digest.digest('SHA256', MESSAGE) + + return status, message +rescue => e + return 409, "Unexpected failure with SHA256, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +end + +get '/hash/md5' do + status = 200 + + fips_state = OpenSSL.fips_mode ? 'enabled' : 'disabled' + message = "MD5 succeeded, fips is #{fips_state}" + + OpenSSL::Digest.digest('MD5', MESSAGE) + + # FIPS is on, but this passed, that shouldn't be the case. + status = 500 if OpenSSL.fips_mode + + return status, message +rescue OpenSSL::Digest::DigestError => e + return status, "Failed with fips #{fips_state} #{e.inspect}\n" if OpenSSL.fips_mode + + return 500, "Failed with fips #{fips_state} #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +rescue => e + return 409, "Unexpected failure with MD5, fips #{fips_state}, #{e.inspect}\nBacktrace:\n#{e.backtrace}\n" +end diff --git a/3.3/test/test-fips/config.ru b/3.3/test/test-fips/config.ru new file mode 100644 index 00000000..76a6edff --- /dev/null +++ b/3.3/test/test-fips/config.ru @@ -0,0 +1,2 @@ +require './app' +run Sinatra::Application diff --git a/test/run b/test/run index 9f33f63c..9c6d5823 100755 --- a/test/run +++ b/test/run @@ -25,6 +25,12 @@ test_connection test_scl_usage test_npm_functionality " + +TEST_LIST_FIPS="\ +test_ruby_fips_mode +test_ruby_fips_s2i_app +" + source "${test_dir}/test-lib.sh" # Read exposed port from image meta data @@ -51,7 +57,8 @@ run_s2i_build() { } run_test_application() { - docker run --user=100001 --rm --cidfile=${cid_file} ${IMAGE_NAME}-testapp + local appname=${1:-testapp} + docker run --user=100001 --rm --cidfile=${cid_file} ${IMAGE_NAME}-${appname} } test_s2i_usage() { @@ -169,10 +176,12 @@ app_cleanup() { } function cleanup() { - info "Cleaning up the test application image ${IMAGE_NAME}-testapp" - if image_exists ${IMAGE_NAME}-testapp; then - docker rmi -f ${IMAGE_NAME}-testapp - fi + for image in testapp testfips; do + info "Cleaning up the test application image ${IMAGE_NAME}-${image}" + if image_exists ${IMAGE_NAME}-${image}; then + docker rmi -f ${IMAGE_NAME}-${image} + fi + done } # Positive test & non-zero exit status = ERROR. @@ -210,6 +219,53 @@ evaluate_build_result() { return $_ret_code } +# "0" if system is not FIPS enabled, "1" if it is. +function fips_enabled() { + local is_fips_enabled + + # Read fips mode from host in case exists + if [[ -f /proc/sys/crypto/fips_enabled ]]; then + echo "$(cat /proc/sys/crypto/fips_enabled)" + else + echo "0" + fi +} + +function run_s2i_build_fips() { + ct_s2i_build_as_df file://${test_dir}/test-fips ${IMAGE_NAME} ${IMAGE_NAME}-testfips ${s2i_args} $1 +} + +function test_ruby_fips_mode() { + if [[ "$(fips_enabled)" == "0" ]]; then + # FIPS disabled -> OpenSSL#fips_mode returns false + echo "Fips should be disabled" + docker run --rm "$IMAGE_NAME" /bin/bash -c 'ruby -ropenssl -e "exit !OpenSSL.fips_mode"' + ct_check_testcase_result "$?" + else + echo "Fips should be enabled" + # FIPS enabled -> OpenSSL#fips_mode returns true + docker run --rm "$IMAGE_NAME" /bin/bash -c 'ruby -ropenssl -e "exit OpenSSL.fips_mode"' + ct_check_testcase_result "$?" + fi +} + +function test_ruby_fips_s2i_app() { + run_s2i_build_fips + evaluate_build_result $? "default" + # Verify that the HTTP connection can be established to test application container + run_test_application testfips & + # Wait for the container to write its CID file + ct_wait_for_cid "${cid_file}" + ct_test_response "http://$(container_ip):8080/symmetric/aes-256-cbc" 200 "" + ct_check_testcase_result $? + ct_test_response "http://$(container_ip):8080/symmetric/des-ede-cbc" 200 "" + ct_check_testcase_result $? + ct_test_response "http://$(container_ip):8080/hash/sha256" 200 "" + ct_check_testcase_result $? + ct_test_response "http://$(container_ip):8080/hash/md5" 200 "" + ct_check_testcase_result $? +} + # Prepare dependencies for tests pushd ${test_dir} # db test dependencies @@ -247,3 +303,12 @@ echo "Removing test dependencies" rm -rf ${test_dir}/db-test-app app_cleanup + +if [[ "$OS" == "rhel8" ]]; then + echo "Not executing fips tests on RHEL 8" +else + echo "Testing fips mode" + TEST_SET=${TESTS:-$TEST_LIST_FIPS} ct_run_tests_from_testset "fips" + echo "Testing the production image build for fips" + cleanup +fi