diff --git a/include/fluent-bit/flb_config.h b/include/fluent-bit/flb_config.h index 3ebcb5edbae..5e3a5f747f6 100644 --- a/include/fluent-bit/flb_config.h +++ b/include/fluent-bit/flb_config.h @@ -291,6 +291,9 @@ struct flb_config { int enable_chunk_trace; #endif /* FLB_HAVE_CHUNK_TRACE */ + int fips_mode; + int fips_mode_active; + int enable_hot_reload; int ensure_thread_safety_on_hot_reloading; unsigned int hot_reloaded_count; @@ -378,6 +381,7 @@ enum conf_type { #define FLB_CONF_STR_STREAMS_FILE "Streams_File" #define FLB_CONF_STR_STREAMS_STR_CONV "sp.convert_from_str_to_num" #define FLB_CONF_STR_CONV_NAN "json.convert_nan_to_null" +#define FLB_CONF_STR_FIPS_MODE "security.fips_mode" /* FLB_HAVE_HTTP_SERVER */ #ifdef FLB_HAVE_HTTP_SERVER diff --git a/include/fluent-bit/flb_fips.h b/include/fluent-bit/flb_fips.h new file mode 100644 index 00000000000..4c14ab40ca4 --- /dev/null +++ b/include/fluent-bit/flb_fips.h @@ -0,0 +1,25 @@ +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_FIPS_H +#define FLB_FIPS_H + +struct flb_config; + +int flb_fips_init(struct flb_config *config); + +#endif diff --git a/plugins/out_azure_blob/azure_blob.c b/plugins/out_azure_blob/azure_blob.c index 57ffd90ad43..4b230339579 100644 --- a/plugins/out_azure_blob/azure_blob.c +++ b/plugins/out_azure_blob/azure_blob.c @@ -1127,6 +1127,16 @@ static int send_blob(struct flb_config *config, } else if (event_type == FLB_EVENT_TYPE_BLOBS) { block_id = azb_block_blob_id_blob(ctx, name, part_id); + if (!block_id) { + flb_plg_error(ctx->ins, "could not generate block id"); + flb_free(generated_random_string); + generated_random_string = NULL; + flb_sds_destroy(ref_name); + if (tmp_path_prefix) { + flb_sds_destroy(tmp_path_prefix); + } + return FLB_RETRY; + } uri = azb_block_blob_uri(ctx, path_prefix, name, block_id, 0, generated_random_string); ref_name = flb_sds_printf(&ref_name, "file=%s:%" PRIu64, name, part_id); diff --git a/plugins/out_azure_blob/azure_blob_blockblob.c b/plugins/out_azure_blob/azure_blob_blockblob.c index 356c0ad24d6..5f145724481 100644 --- a/plugins/out_azure_blob/azure_blob_blockblob.c +++ b/plugins/out_azure_blob/azure_blob_blockblob.c @@ -25,6 +25,7 @@ #include #include +#include #include #include "azure_blob.h" @@ -220,18 +221,21 @@ char *azb_block_blob_id_logs(uint64_t *ms) /* * Generate a block id for blob type events: * - * Azure Blob requires that Blobs IDs do not exceed 64 bytes, so we generate a MD5 - * of the path and append the part number to it, we add some zeros for padding since - * all blocks id MUST have the same length. + * Azure Blob requires block IDs to have the same length and the base64-encoded + * value must not exceed 64 bytes. The non-FIPS path keeps the original + * MD5-derived format for compatibility. When FIPS mode is enabled, use the + * first 128 bits of SHA-256 rendered as hex plus the same fixed-width suffix. */ -char *azb_block_blob_id_blob(struct flb_azure_blob *ctx, char *path, int64_t part_id) +char *azb_block_blob_id_blob(struct flb_azure_blob *ctx, char *path, uint64_t part_id) { int i; int len; int ret; + int fips_mode; unsigned char md5[16] = {0}; + unsigned char sha256[32] = {0}; char tmp[128]; - flb_sds_t md5_hex; + flb_sds_t digest_hex; size_t size; size_t o_len; char *b64; @@ -240,27 +244,57 @@ char *azb_block_blob_id_blob(struct flb_azure_blob *ctx, char *path, int64_t par * block ids in base64 cannot exceed 64 bytes, so we hash the path to avoid * exceeding the lenght and then just append the part number. */ - ret = flb_hash_simple(FLB_HASH_MD5, (unsigned char *) path, strlen(path), - md5, sizeof(md5)); - if (ret != 0) { - flb_plg_error(ctx->ins, "cannot hash block id for path %s", path); - return NULL; + fips_mode = FLB_FALSE; + if (ctx->config != NULL && ctx->config->fips_mode_active == FLB_TRUE) { + fips_mode = FLB_TRUE; } - /* convert md5 to hex string (32 byte hex string) */ - md5_hex = flb_sds_create_size(32); - if (!md5_hex) { - return NULL; - } + if (fips_mode == FLB_TRUE) { + ret = flb_hash_simple(FLB_HASH_SHA256, (unsigned char *) path, strlen(path), + sha256, sizeof(sha256)); + if (ret != 0) { + flb_plg_error(ctx->ins, "cannot hash block id for path %s", path); + return NULL; + } + + digest_hex = flb_sds_create_size(32); + if (!digest_hex) { + return NULL; + } + + for (i = 0; i < 16; i++) { + snprintf(digest_hex + (i * 2), 3, "%02x", sha256[i]); + } + flb_sds_len_set(digest_hex, 32); - for (i = 0; i < 16; i++) { - snprintf(md5_hex + (i * 2), 3, "%02x", md5[i]); + len = snprintf(tmp, sizeof(tmp) - 1, "%s.flb-part.%06" PRIu64, + digest_hex, part_id); + flb_sds_destroy(digest_hex); } - flb_sds_len_set(md5_hex, 32); + else { + ret = flb_hash_simple(FLB_HASH_MD5, (unsigned char *) path, strlen(path), + md5, sizeof(md5)); + if (ret != 0) { + flb_plg_error(ctx->ins, "cannot hash block id for path %s", path); + return NULL; + } + + /* convert md5 to hex string (32 byte hex string) */ + digest_hex = flb_sds_create_size(32); + if (!digest_hex) { + return NULL; + } - /* append part number */ - len = snprintf(tmp, sizeof(tmp) - 1, "%s.flb-part.%06ld", md5_hex, part_id); - flb_sds_destroy(md5_hex); + for (i = 0; i < 16; i++) { + snprintf(digest_hex + (i * 2), 3, "%02x", md5[i]); + } + flb_sds_len_set(digest_hex, 32); + + /* append part number */ + len = snprintf(tmp, sizeof(tmp) - 1, "%s.flb-part.%06" PRIu64, + digest_hex, part_id); + flb_sds_destroy(digest_hex); + } size = 64 + 1; b64 = flb_calloc(1, size); @@ -441,6 +475,15 @@ int azb_block_blob_commit_file_parts(struct flb_azure_blob *ctx, uint64_t file_i id = atol(sentry->value); block_id = azb_block_blob_id_blob(ctx, path, id); + if (block_id == NULL) { + flb_plg_error(ctx->ins, + "could not generate block id for file id=%" PRIu64 + " name %s part=%" PRIu64, + file_id, path, id); + flb_sds_destroy(payload); + flb_utils_split_free(list); + return -1; + } cfl_sds_cat_safe(&payload, " ", 2); cfl_sds_cat_safe(&payload, "", 13); diff --git a/plugins/out_s3/s3.c b/plugins/out_s3/s3.c index c12c6c79ac9..e82179d8b32 100644 --- a/plugins/out_s3/s3.c +++ b/plugins/out_s3/s3.c @@ -673,7 +673,6 @@ static int cb_s3_init(struct flb_output_instance *ins, const char *tmp; struct flb_s3 *ctx = NULL; struct flb_aws_client_generator *generator; - (void) config; (void) data; char *ep; struct flb_split_entry *tok; @@ -720,6 +719,15 @@ static int cb_s3_init(struct flb_output_instance *ins, return -1; } + if (config->fips_mode_active == FLB_TRUE && ctx->send_content_md5 == FLB_TRUE) { + flb_plg_error(ctx->ins, + "send_content_md5 uses MD5 and is not available " + "when security.fips_mode is enabled"); + s3_context_destroy(ctx); + flb_output_set_context(ins, NULL); + return -1; + } + action = s3_get_retry_exhausted_action(ctx->retry_exhausted_action_str); if (action == -1) { flb_plg_error(ctx->ins, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a984ecc6b70..521a693f222 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,7 @@ set(src flb_output.c flb_output_thread.c flb_config.c + flb_fips.c flb_config_map.c flb_socket.c flb_network.c diff --git a/src/flb_config.c b/src/flb_config.c index 221349227e1..e7f418d3ea5 100644 --- a/src/flb_config.c +++ b/src/flb_config.c @@ -63,6 +63,10 @@ struct flb_service_config service_configs[] = { FLB_CONF_TYPE_BOOL, offsetof(struct flb_config, convert_nan_to_null)}, + {FLB_CONF_STR_FIPS_MODE, + FLB_CONF_TYPE_BOOL, + offsetof(struct flb_config, fips_mode)}, + {FLB_CONF_STR_DAEMON, FLB_CONF_TYPE_BOOL, offsetof(struct flb_config, daemon)}, @@ -288,6 +292,10 @@ struct flb_config *flb_config_init() /* json */ config->convert_nan_to_null = FLB_FALSE; + /* FIPS */ + config->fips_mode = FLB_FALSE; + config->fips_mode_active = FLB_FALSE; + #ifdef FLB_HAVE_HTTP_SERVER config->http_ctx = NULL; config->http_server = FLB_FALSE; diff --git a/src/flb_engine.c b/src/flb_engine.c index 63178da843c..d986f521dec 100644 --- a/src/flb_engine.c +++ b/src/flb_engine.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -919,6 +920,11 @@ int flb_engine_start(struct flb_config *config) return -1; } + ret = flb_fips_init(config); + if (ret != 0) { + return -1; + } + if (engine_has_fluentbit_logs_input(config)) { flb_log_pipeline_enable(config); } diff --git a/src/flb_fips.c b/src/flb_fips.c new file mode 100644 index 00000000000..5341a9257b6 --- /dev/null +++ b/src/flb_fips.c @@ -0,0 +1,227 @@ +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#ifdef FLB_HAVE_OPENSSL +#include +#include +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#endif +#endif + +#if defined(FLB_HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L +#define FLB_FIPS_PROCESS_MODE_UNSET -1 + +static pthread_mutex_t fips_init_mutex = PTHREAD_MUTEX_INITIALIZER; +static OSSL_PROVIDER *fips_provider; +static OSSL_PROVIDER *base_provider; +static int fips_process_mode = FLB_FIPS_PROCESS_MODE_UNSET; + +static void log_openssl_errors(void) +{ + char errbuf[256]; + unsigned long err; + + while ((err = ERR_get_error()) != 0) { + ERR_error_string_n(err, errbuf, sizeof(errbuf)); + flb_error("[fips] OpenSSL error: %s", errbuf); + } +} + +static int fips_provider_is_active(void) +{ + EVP_MD *sha256; + + if (EVP_default_properties_is_fips_enabled(NULL) != 1) { + return FLB_FALSE; + } + + sha256 = EVP_MD_fetch(NULL, "SHA256", "fips=yes,provider=fips"); + if (sha256 == NULL) { + return FLB_FALSE; + } + + EVP_MD_free(sha256); + + return FLB_TRUE; +} +#endif + +int flb_fips_init(struct flb_config *config) +{ +#if defined(FLB_HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L + int ret; + int properties_changed; + OSSL_PROVIDER *new_fips_provider; + OSSL_PROVIDER *new_base_provider; +#endif + + if (config == NULL) { + return -1; + } + +#ifndef FLB_HAVE_OPENSSL + if (config->fips_mode != FLB_TRUE) { + return 0; + } + + flb_error("[fips] security.fips_mode requires Fluent Bit to be built with OpenSSL"); + return -1; +#else +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (config->fips_mode != FLB_TRUE) { + return 0; + } + + flb_error("[fips] security.fips_mode requires OpenSSL 3.0 or later"); + return -1; +#else + pthread_mutex_lock(&fips_init_mutex); + + properties_changed = FLB_FALSE; + new_fips_provider = NULL; + new_base_provider = NULL; + + if (fips_provider_is_active() == FLB_TRUE) { + if (fips_process_mode == FLB_FALSE) { + flb_error("[fips] OpenSSL FIPS mode changed after process initialization"); + pthread_mutex_unlock(&fips_init_mutex); + return -1; + } + + if (fips_provider == NULL || base_provider == NULL) { + ERR_clear_error(); + new_fips_provider = OSSL_PROVIDER_try_load(NULL, "fips", 1); + if (new_fips_provider == NULL) { + flb_error("[fips] failed to retain the active OpenSSL FIPS provider"); + log_openssl_errors(); + goto error; + } + + ERR_clear_error(); + new_base_provider = OSSL_PROVIDER_try_load(NULL, "base", 1); + if (new_base_provider == NULL) { + flb_error("[fips] failed to load OpenSSL base provider"); + log_openssl_errors(); + goto error; + } + + fips_provider = new_fips_provider; + base_provider = new_base_provider; + } + + fips_process_mode = FLB_TRUE; + config->fips_mode_active = FLB_TRUE; + pthread_mutex_unlock(&fips_init_mutex); + return 0; + } + + if (EVP_default_properties_is_fips_enabled(NULL) == 1) { + flb_error("[fips] OpenSSL FIPS properties are enabled without an active provider"); + pthread_mutex_unlock(&fips_init_mutex); + return -1; + } + + if (fips_process_mode == FLB_TRUE) { + flb_error("[fips] OpenSSL FIPS mode changed after process initialization"); + pthread_mutex_unlock(&fips_init_mutex); + return -1; + } + + if (config->fips_mode != FLB_TRUE) { + fips_process_mode = FLB_FALSE; + config->fips_mode_active = FLB_FALSE; + pthread_mutex_unlock(&fips_init_mutex); + return 0; + } + + if (fips_process_mode == FLB_FALSE) { + flb_error("[fips] cannot enable FIPS mode after non-FIPS process initialization"); + pthread_mutex_unlock(&fips_init_mutex); + return -1; + } + + ERR_clear_error(); + new_fips_provider = OSSL_PROVIDER_try_load(NULL, "fips", 1); + if (new_fips_provider == NULL) { + flb_error("[fips] failed to load OpenSSL FIPS provider"); + log_openssl_errors(); + goto error; + } + + ERR_clear_error(); + new_base_provider = OSSL_PROVIDER_try_load(NULL, "base", 1); + if (new_base_provider == NULL) { + flb_error("[fips] failed to load OpenSSL base provider"); + log_openssl_errors(); + goto error; + } + + if (EVP_default_properties_is_fips_enabled(NULL) != 1) { + ERR_clear_error(); + ret = EVP_default_properties_enable_fips(NULL, 1); + if (ret != 1) { + flb_error("[fips] failed to enable OpenSSL FIPS default properties"); + log_openssl_errors(); + goto error; + } + properties_changed = FLB_TRUE; + } + + if (fips_provider_is_active() != FLB_TRUE) { + flb_error("[fips] OpenSSL FIPS provider is not active"); + log_openssl_errors(); + goto error; + } + + /* Keep the providers loaded because the default OpenSSL context is process-wide. */ + fips_provider = new_fips_provider; + base_provider = new_base_provider; + fips_process_mode = FLB_TRUE; + config->fips_mode_active = FLB_TRUE; + + pthread_mutex_unlock(&fips_init_mutex); + flb_info("[fips] OpenSSL FIPS mode enabled"); + + return 0; + +error: + if (properties_changed == FLB_TRUE) { + ret = EVP_default_properties_enable_fips(NULL, 0); + if (ret != 1) { + flb_error("[fips] failed to restore OpenSSL default properties"); + log_openssl_errors(); + } + } + if (new_base_provider != NULL) { + OSSL_PROVIDER_unload(new_base_provider); + } + if (new_fips_provider != NULL) { + OSSL_PROVIDER_unload(new_fips_provider); + } + + pthread_mutex_unlock(&fips_init_mutex); + return -1; +#endif +#endif +} diff --git a/src/flb_hash.c b/src/flb_hash.c index eef18d86878..5325ff4b90e 100644 --- a/src/flb_hash.c +++ b/src/flb_hash.c @@ -47,6 +47,10 @@ int flb_hash_init(struct flb_hash *context, int hash_type) return FLB_CRYPTO_INVALID_ARGUMENT; } + context->backend_context = NULL; + context->digest_size = 0; + context->last_error = 0; + digest_algorithm = flb_crypto_get_digest_algorithm_instance_by_id(hash_type); if (digest_algorithm == NULL) { @@ -65,6 +69,8 @@ int flb_hash_init(struct flb_hash *context, int hash_type) if (result == 0) { context->last_error = ERR_get_error(); + EVP_MD_CTX_destroy(context->backend_context); + context->backend_context = NULL; return FLB_CRYPTO_BACKEND_ERROR; } diff --git a/src/flb_hmac.c b/src/flb_hmac.c index 9c159c6351e..7882a918d6f 100644 --- a/src/flb_hmac.c +++ b/src/flb_hmac.c @@ -110,6 +110,8 @@ int flb_hmac_init(struct flb_hmac *context, if (result == 0) { context->last_error = ERR_get_error(); + flb_hmac_cleanup(context); + return FLB_CRYPTO_BACKEND_ERROR; } @@ -195,6 +197,8 @@ int flb_hmac_init(struct flb_hmac *context, if (result != 1) { context->last_error = ERR_get_error(); + flb_hmac_cleanup(context); + return FLB_CRYPTO_BACKEND_ERROR; } diff --git a/src/flb_lib.c b/src/flb_lib.c index 7257480b063..59bfa298e13 100644 --- a/src/flb_lib.c +++ b/src/flb_lib.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -940,8 +941,15 @@ int static do_start(flb_ctx_t *ctx) /* set context as the last active one */ - /* spawn worker thread */ config = ctx->config; + + /* OpenSSL default properties must be configured before worker threads start. */ + ret = flb_fips_init(config); + if (ret != 0) { + return -1; + } + + /* spawn worker thread */ ret = mk_utils_worker_spawn(flb_lib_worker, ctx, &tid); if (ret == -1) { return -1; diff --git a/src/flb_reload.c b/src/flb_reload.c index 76d38abac68..9f44034e2c4 100644 --- a/src/flb_reload.c +++ b/src/flb_reload.c @@ -595,6 +595,19 @@ int flb_reload(flb_ctx_t *ctx, struct flb_cf *cf_opts) return FLB_RELOAD_HALTED; } + if (new_config->fips_mode != old_config->fips_mode) { + if (file != NULL) { + flb_sds_destroy(file); + } + flb_cf_destroy(new_cf); + flb_destroy(new_ctx); + old_config->hot_reloading = FLB_FALSE; + + flb_error("[reload] security.fips_mode cannot be changed by hot reload"); + flb_reload_watchdog_cleanup(watchdog_ctx); + return FLB_RELOAD_HALTED; + } + /* Validate plugin properites before fluent-bit stops the old context. */ ret = flb_reload_property_check_all(new_config); if (ret != 0) { diff --git a/src/fluent-bit.c b/src/fluent-bit.c index 14e5f5682a4..b74d68313f9 100644 --- a/src/fluent-bit.c +++ b/src/fluent-bit.c @@ -60,6 +60,7 @@ #include #include #include +#include #ifdef FLB_HAVE_MTRACE #include @@ -92,6 +93,7 @@ struct flb_stacktrace flb_st; #endif #define FLB_LONG_SUPERVISOR (1024 + 5) +#define FLB_LONG_ENABLE_FIPS (1024 + 6) #define FLB_HELP_TEXT 0 #define FLB_HELP_JSON 1 @@ -173,6 +175,7 @@ static void flb_help(int rc, struct flb_config *config) print_opt("-q, --quiet", "quiet mode"); print_opt("-S, --sosreport", "support report for Enterprise customers"); print_opt("-Y, --enable-hot-reload", "enable for hot reloading"); + print_opt(" --enable-fips", "require OpenSSL FIPS mode at startup"); print_opt("-W, --disable-thread-safety-on-hot-reloading", "disable thread safety on hot reloading"); print_opt("-V, --version", "show version number"); print_opt("-h, --help", "print this help"); @@ -1079,6 +1082,7 @@ static int flb_main_run(int argc, char **argv) { "http_port", required_argument, NULL, 'P' }, #endif { "enable-hot-reload", no_argument, NULL, 'Y' }, + { "enable-fips", no_argument, NULL, FLB_LONG_ENABLE_FIPS }, #ifdef FLB_SYSTEM_WINDOWS { "windows_maxstdio", required_argument, NULL, 'M' }, #endif @@ -1300,6 +1304,10 @@ static int flb_main_run(int argc, char **argv) case 'Y': flb_cf_section_property_add(cf_opts, service->properties, FLB_CONF_STR_HOT_RELOAD, 0, "on", 0); break; + case FLB_LONG_ENABLE_FIPS: + flb_cf_section_property_add(cf_opts, service->properties, + FLB_CONF_STR_FIPS_MODE, 0, "on", 0); + break; case 'W': flb_cf_section_property_add(cf_opts, service->properties, FLB_CONF_STR_HOT_RELOAD_ENSURE_THREAD_SAFETY, 0, "off", 0); @@ -1412,6 +1420,12 @@ static int flb_main_run(int argc, char **argv) flb_utils_error(FLB_ERR_CFG_FLUSH); } + if (flb_fips_init(config) != 0) { + flb_cf_destroy(cf_opts); + flb_destroy(ctx); + return -1; + } + /* debug or trace */ if (config->verbose >= FLB_LOG_DEBUG) { flb_utils_print_setup(config); diff --git a/tests/internal/CMakeLists.txt b/tests/internal/CMakeLists.txt index 5fd3f1b10e4..322b17e49c3 100644 --- a/tests/internal/CMakeLists.txt +++ b/tests/internal/CMakeLists.txt @@ -10,6 +10,7 @@ set(UNIT_TESTS_FILES hmac.c crypto.c hash.c + fips.c kv.c slist.c router.c @@ -195,6 +196,7 @@ if(FLB_RIPSER) endif() set(UNIT_TESTS_DATA + data/fips/disabled.cnf data/tls/certificate.pem data/tls/private_key.pem data/pack/json_single_map_001.json @@ -270,6 +272,49 @@ endfunction(prepare_unit_tests) prepare_unit_tests(flb-it- "${UNIT_TESTS_FILES}") +option(FLB_TESTS_FIPS_ENABLED "Run tests that require an installed OpenSSL FIPS provider" OFF) +if(TARGET flb-it-fips) + if(FLB_OUT_S3) + target_compile_definitions(flb-it-fips PRIVATE FLB_TEST_FIPS_S3) + endif() + + set(FLB_TEST_FIPS_DISABLED_CONFIG + "${CMAKE_CURRENT_BINARY_DIR}/data/fips/disabled.cnf") + set(FLB_TEST_FIPS_MISSING_MODULES + "${CMAKE_CURRENT_BINARY_DIR}/data/fips/missing-modules") + set(FLB_TEST_FIPS_UNAVAILABLE_ENV + "FLB_TEST_FIPS_MODE=unavailable" + "OPENSSL_CONF=${FLB_TEST_FIPS_DISABLED_CONFIG}" + "OPENSSL_MODULES=${FLB_TEST_FIPS_MISSING_MODULES}") + set(FLB_TEST_FIPS_DISABLED_ENV + "FLB_TEST_FIPS_MODE=disabled" + "OPENSSL_CONF=${FLB_TEST_FIPS_DISABLED_CONFIG}" + "OPENSSL_MODULES=${FLB_TEST_FIPS_MISSING_MODULES}") + + set_tests_properties(flb-it-fips PROPERTIES + ENVIRONMENT "${FLB_TEST_FIPS_DISABLED_ENV}") + + add_test(NAME flb-it-fips-unavailable + COMMAND $) + set_tests_properties(flb-it-fips-unavailable PROPERTIES + LABELS "internal" + ENVIRONMENT "${FLB_TEST_FIPS_UNAVAILABLE_ENV}") + + if(FLB_TESTS_FIPS_ENABLED) + add_test(NAME flb-it-fips-enabled + COMMAND $) + set_tests_properties(flb-it-fips-enabled PROPERTIES + LABELS "internal" + ENVIRONMENT "FLB_TEST_FIPS_MODE=enabled") + + add_test(NAME flb-it-fips-preenabled + COMMAND $) + set_tests_properties(flb-it-fips-preenabled PROPERTIES + LABELS "internal" + ENVIRONMENT "FLB_TEST_FIPS_MODE=preenabled") + endif() +endif() + if(FLB_METRICS AND FLB_PROCESSOR_CUMULATIVE_TO_DELTA) set(CUMULATIVE_TO_DELTA_PLUGIN_DIR ${PROJECT_SOURCE_DIR}/plugins/processor_cumulative_to_delta) diff --git a/tests/internal/azure_blob_path.c b/tests/internal/azure_blob_path.c index d89117ad29b..1ddf3d1556d 100644 --- a/tests/internal/azure_blob_path.c +++ b/tests/internal/azure_blob_path.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "../../plugins/out_azure_blob/azure_blob.h" #include "../../plugins/out_azure_blob/azure_blob_uri.h" @@ -478,6 +479,45 @@ void test_block_blob_commit_requires_suffix(void) ctx_cleanup(&ctx); } +void test_block_blob_id_uses_sha256_in_fips_mode(void) +{ + struct flb_config config; + struct flb_azure_blob ctx; + char *default_id; + char *fips_id; + const char *expected_id; + + memset(&config, 0, sizeof(config)); + memset(&ctx, 0, sizeof(ctx)); + + ctx.config = &config; + + config.fips_mode_active = FLB_FALSE; + default_id = azb_block_blob_id_blob(&ctx, "blob/path.log", 1); + TEST_CHECK(default_id != NULL); + if (default_id == NULL) { + return; + } + + config.fips_mode_active = FLB_TRUE; + fips_id = azb_block_blob_id_blob(&ctx, "blob/path.log", 1); + TEST_CHECK(fips_id != NULL); + if (fips_id == NULL) { + flb_free(default_id); + return; + } + + TEST_CHECK(strlen(default_id) == 64); + TEST_CHECK(strlen(fips_id) == 64); + TEST_CHECK(strcmp(default_id, fips_id) != 0); + + expected_id = "NjM1NTgzYjViZjZhMDU4Y2VmNjhkMWNjMjZlM2ZmNzcuZmxiLXBhcnQuMDAwMDAx"; + TEST_CHECK(strcmp(fips_id, expected_id) == 0); + + flb_free(default_id); + flb_free(fips_id); +} + TEST_LIST = { {"resolve_path_basic_tag", test_resolve_path_basic_tag}, {"resolve_path_custom_delimiter", test_resolve_path_custom_delimiter}, @@ -491,5 +531,6 @@ TEST_LIST = { {"commit_prefix_fallback_static_path", test_commit_prefix_fallback_static_path}, {"uri_create_static_prefix_fallback", test_uri_create_static_prefix_fallback}, {"block_blob_commit_requires_suffix", test_block_blob_commit_requires_suffix}, + {"block_blob_id_uses_sha256_in_fips_mode", test_block_blob_id_uses_sha256_in_fips_mode}, {0} }; diff --git a/tests/internal/data/fips/disabled.cnf b/tests/internal/data/fips/disabled.cnf new file mode 100644 index 00000000000..af2a5fee067 --- /dev/null +++ b/tests/internal/data/fips/disabled.cnf @@ -0,0 +1,4 @@ +# Intentionally empty OpenSSL initialization for non-FIPS test processes. +openssl_conf = openssl_init + +[openssl_init] diff --git a/tests/internal/fips.c b/tests/internal/fips.c new file mode 100644 index 00000000000..3a9d32a0cc9 --- /dev/null +++ b/tests/internal/fips.c @@ -0,0 +1,295 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef FLB_TEST_FIPS_S3 +#include +#endif + +#if defined(FLB_HAVE_OPENSSL) +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#endif +#endif + +#include "flb_tests_internal.h" + +static int hash_payload(int hash_type, unsigned char *digest, size_t digest_size) +{ + const char *payload = "fips-provider-state"; + + return flb_hash_simple(hash_type, + (unsigned char *) payload, strlen(payload), + digest, digest_size); +} + +static void check_md5_available(void) +{ + int ret; + struct flb_hmac hmac_context; + unsigned char digest[32]; + const unsigned char key[] = "fips-test-key"; + + ret = hash_payload(FLB_HASH_MD5, digest, sizeof(digest)); + TEST_CHECK(ret == FLB_CRYPTO_SUCCESS); + + ret = flb_hmac_init(&hmac_context, FLB_HASH_MD5, + (unsigned char *) key, sizeof(key) - 1); + TEST_CHECK(ret == FLB_CRYPTO_SUCCESS); + if (ret == FLB_CRYPTO_SUCCESS) { + flb_hmac_cleanup(&hmac_context); + } +} + +static void check_fips_algorithms(void) +{ + int ret; + struct flb_hash hash_context; + struct flb_hmac hmac_context; + unsigned char digest[32]; + const unsigned char key[] = "fips-test-key"; + + ret = hash_payload(FLB_HASH_SHA256, digest, sizeof(digest)); + TEST_CHECK(ret == FLB_CRYPTO_SUCCESS); + + ret = flb_hash_init(&hash_context, FLB_HASH_MD5); + TEST_CHECK(ret == FLB_CRYPTO_BACKEND_ERROR); + TEST_CHECK(hash_context.backend_context == NULL); + + ret = flb_hmac_init(&hmac_context, FLB_HASH_MD5, + (unsigned char *) key, sizeof(key) - 1); + TEST_CHECK(ret == FLB_CRYPTO_BACKEND_ERROR); + TEST_CHECK(hmac_context.backend_context == NULL); +#if FLB_CRYPTO_OPENSSL_COMPAT_MODE >= 3 + TEST_CHECK(hmac_context.mac_algorithm == NULL); +#endif +} + +#ifdef FLB_TEST_FIPS_S3 +static void check_s3_content_md5_rejected(void) +{ + int ret; + int input_id; + int output_id; + flb_ctx_t *ctx; + + ctx = flb_create(); + TEST_CHECK(ctx != NULL); + if (ctx == NULL) { + return; + } + + input_id = flb_input(ctx, (char *) "lib", NULL); + TEST_CHECK(input_id >= 0); + if (input_id < 0) { + flb_destroy(ctx); + return; + } + ret = flb_input_set(ctx, input_id, "tag", "fips.test", NULL); + TEST_CHECK(ret == 0); + + output_id = flb_output(ctx, (char *) "s3", NULL); + TEST_CHECK(output_id >= 0); + if (output_id < 0) { + flb_destroy(ctx); + return; + } + + ret = flb_output_set(ctx, output_id, + "match", "*", + "region", "us-east-1", + "bucket", "fips-test", + "send_content_md5", "true", + NULL); + TEST_CHECK(ret == 0); + + ret = flb_start(ctx); + TEST_CHECK(ret == -1); + if (ret == 0) { + flb_stop(ctx); + } + flb_destroy(ctx); +} +#endif + +#if defined(FLB_HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L +struct fips_test_provider_state { + int base_loaded; + int fips_loaded; +}; + +static int collect_provider_state(OSSL_PROVIDER *provider, void *data) +{ + const char *name; + struct fips_test_provider_state *state; + + state = data; + name = OSSL_PROVIDER_get0_name(provider); + if (name == NULL) { + return 1; + } + + if (strcmp(name, "base") == 0) { + state->base_loaded = FLB_TRUE; + } + else if (strcmp(name, "fips") == 0) { + state->fips_loaded = FLB_TRUE; + } + + return 1; +} +#endif + +void test_fips_environment(void) +{ + int ret; + struct flb_config config; + struct flb_config second_config; + const char *mode; +#if defined(FLB_HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PROVIDER *preenabled_provider; + struct fips_test_provider_state provider_state; +#endif + + memset(&config, 0, sizeof(config)); + memset(&second_config, 0, sizeof(second_config)); + + TEST_CHECK(flb_fips_init(NULL) == -1); + + mode = getenv("FLB_TEST_FIPS_MODE"); + if (mode == NULL || mode[0] == '\0') { + config.fips_mode = FLB_FALSE; + ret = flb_fips_init(&config); + TEST_CHECK(ret == 0); + + if (config.fips_mode_active == FLB_TRUE) { + check_fips_algorithms(); + } + else { + check_md5_available(); + } + return; + } + + if (strcmp(mode, "disabled") == 0) { + config.fips_mode = FLB_FALSE; + ret = flb_fips_init(&config); + TEST_CHECK(ret == 0); + TEST_CHECK(config.fips_mode_active == FLB_FALSE); + + check_md5_available(); + + config.fips_mode = FLB_TRUE; + ret = flb_fips_init(&config); + TEST_CHECK(ret == -1); + return; + } + + if (strcmp(mode, "preenabled") == 0) { +#if defined(FLB_HAVE_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x30000000L + preenabled_provider = OSSL_PROVIDER_load(NULL, "fips"); + TEST_CHECK(preenabled_provider != NULL); + if (preenabled_provider == NULL) { + return; + } + + ret = EVP_default_properties_enable_fips(NULL, 1); + TEST_CHECK(ret == 1); + if (ret != 1) { + OSSL_PROVIDER_unload(preenabled_provider); + return; + } + + config.fips_mode = FLB_FALSE; + ret = flb_fips_init(&config); + TEST_CHECK(ret == 0); + TEST_CHECK(config.fips_mode_active == FLB_TRUE); + if (ret != 0) { + OSSL_PROVIDER_unload(preenabled_provider); + return; + } + + OSSL_PROVIDER_unload(preenabled_provider); + + memset(&provider_state, 0, sizeof(provider_state)); + ret = OSSL_PROVIDER_do_all(NULL, collect_provider_state, &provider_state); + TEST_CHECK(ret == 1); + TEST_CHECK(provider_state.fips_loaded == FLB_TRUE); + TEST_CHECK(provider_state.base_loaded == FLB_TRUE); + + check_fips_algorithms(); +#ifdef FLB_TEST_FIPS_S3 + check_s3_content_md5_rejected(); +#endif +#else + TEST_MSG("preenabled mode requires OpenSSL 3.0 or later"); + TEST_CHECK(0); +#endif + return; + } + + config.fips_mode = FLB_TRUE; + ret = flb_fips_init(&config); + + if (strcmp(mode, "unavailable") == 0) { + TEST_CHECK(ret == -1); + TEST_CHECK(config.fips_mode_active == FLB_FALSE); + + config.fips_mode = FLB_FALSE; + ret = flb_fips_init(&config); + TEST_CHECK(ret == 0); + + check_md5_available(); + return; + } + + if (strcmp(mode, "enabled") == 0) { + TEST_CHECK(ret == 0); + TEST_CHECK(config.fips_mode_active == FLB_TRUE); + + check_fips_algorithms(); +#ifdef FLB_TEST_FIPS_S3 + check_s3_content_md5_rejected(); +#endif + + second_config.fips_mode = FLB_FALSE; + ret = flb_fips_init(&second_config); + TEST_CHECK(ret == 0); + TEST_CHECK(second_config.fips_mode_active == FLB_TRUE); + return; + } + + TEST_MSG("unknown FLB_TEST_FIPS_MODE value: %s", mode); + TEST_CHECK(0); +} + +TEST_LIST = { + {"fips_environment", test_fips_environment}, + {0} +};