diff options
author | Marco Kawajiri <kawajiri@meta.com> | 2023-10-31 23:36:58 +0300 |
---|---|---|
committer | Ed Tanous <ed@tanous.net> | 2023-12-09 01:59:39 +0300 |
commit | 0e373b53f81fc1720050571755ecfcdc6dd9ba9b (patch) | |
tree | 2aef16a7aca2a2bc12e7b086ebdde17b178daf14 | |
parent | 23f1c96e6bc9060b54ff08a6b4d6cf8b8e0c3b23 (diff) | |
download | bmcweb-0e373b53f81fc1720050571755ecfcdc6dd9ba9b.tar.xz |
mutual-tls: Add support for Meta certificates
Meta Inc's client certificates use an internal Subject CN format
which AFAIK is specific to Meta and don't adhere to a known standard:
Subject: CN = <type>:<entity>/<hostname>
Commit adds the `mutual-tls-common-name-parsing=meta` option to, on
Meta builds, parse the Subject CN field and map either the <entity>
to a local user.
The <type> field determines what kind of client identity the cert
represents. Only type="user" is supported for now with <entity> being
the unixname of a Meta employee. For example, the Subject CN string
below maps to a local BMC user named "kawmarco":
Subject CN = "user:kawmarco/dev123.facebook.com"
Tested: Unit tests, built and tested on romulus using the script below:
https://gist.github.com/kawmarco/87170a8250020023d913ed5f7ed5c01f
Flags used in meta-ibm/meta-romulus/conf/layer.conf :
```
-Dbmcweb-logging='enabled'
-Dmutual-tls-common-name-parsing='meta'
```
Change-Id: I35ee9b92d163ce56815a5bd9cce5296ba1a44eef
Signed-off-by: Marco Kawajiri <kawajiri@meta.com>
-rw-r--r-- | config/bmcweb_config.h.in | 2 | ||||
-rw-r--r-- | config/meson.build | 3 | ||||
-rw-r--r-- | http/mutual_tls.hpp | 14 | ||||
-rw-r--r-- | http/mutual_tls_meta.hpp | 73 | ||||
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | meson_options.txt | 14 | ||||
-rw-r--r-- | test/http/mutual_tls_meta.cpp | 49 |
7 files changed, 155 insertions, 1 deletions
diff --git a/config/bmcweb_config.h.in b/config/bmcweb_config.h.in index 3fd3ea8433..a1bd1ba067 100644 --- a/config/bmcweb_config.h.in +++ b/config/bmcweb_config.h.in @@ -24,4 +24,6 @@ constexpr const bool bmcwebEnableProcMemStatus = @BMCWEB_ENABLE_PROC_MEM_STATUS@ constexpr const bool bmcwebEnableMultiHost = @BMCWEB_ENABLE_MULTI_HOST@ == 1; constexpr const bool bmcwebEnableHTTP2 = @BMCWEB_ENABLE_HTTP2@ == 1; + +constexpr const bool bmcwebMTLSCommonNameParsingMeta = @BMCWEB_ENABLE_MTLS_COMMON_NAME_PARSING_META@ == 1; // clang-format on diff --git a/config/meson.build b/config/meson.build index 10ddb2dff2..1ba34afac9 100644 --- a/config/meson.build +++ b/config/meson.build @@ -20,6 +20,8 @@ enable_multi_host = get_option('experimental-redfish-multi-computer-system') conf_data.set10('BMCWEB_ENABLE_MULTI_HOST', enable_multi_host.allowed()) enable_http2 = get_option('experimental-http2') conf_data.set10('BMCWEB_ENABLE_HTTP2', enable_http2.allowed()) +conf_data.set10('BMCWEB_ENABLE_MTLS_COMMON_NAME_PARSING_META', get_option('mutual-tls-common-name-parsing') == 'meta') + # Logging level loglvlopt = get_option('bmcweb-logging') @@ -59,4 +61,3 @@ install_data( install_dir: '/etc/pam.d/', rename: 'webserver', ) - diff --git a/http/mutual_tls.hpp b/http/mutual_tls.hpp index 64bcd490d4..1620054e1c 100644 --- a/http/mutual_tls.hpp +++ b/http/mutual_tls.hpp @@ -1,6 +1,7 @@ #pragma once #include "logging.hpp" +#include "mutual_tls_meta.hpp" #include "persistent_data.hpp" #include <openssl/crypto.h> @@ -88,6 +89,19 @@ inline std::shared_ptr<persistent_data::UserSession> return nullptr; } sslUser.resize(lastChar); + + // Meta Inc. CommonName parsing + if (bmcwebMTLSCommonNameParsingMeta) + { + std::optional<std::string_view> sslUserMeta = + mtlsMetaParseSslUser(sslUser); + if (!sslUserMeta) + { + return nullptr; + } + sslUser = *sslUserMeta; + } + std::string unsupportedClientId; return persistent_data::SessionStore::getInstance().generateUserSession( sslUser, clientIp, unsupportedClientId, diff --git a/http/mutual_tls_meta.hpp b/http/mutual_tls_meta.hpp new file mode 100644 index 0000000000..5e55db8e28 --- /dev/null +++ b/http/mutual_tls_meta.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include "logging.hpp" + +#include <format> +#include <optional> +#include <string> +#include <string_view> + +inline std::optional<std::string_view> + mtlsMetaParseSslUser(std::string_view sslUser) +{ + // Parses a Meta internal TLS client certificate Subject CN in + // '<entityType>:<entity>[/<hostname>]' format and returns the resulting + // POSIX-compatible local user name on success, null otherwise. + // + // Only entityType = "user" is supported for now. + // + // Example client subject CN -> local user name: + // "user:a_username/hostname" -> "a_username" + + // Parse entityType + size_t colonIndex = sslUser.find(':'); + if (colonIndex == std::string_view::npos) + { + BMCWEB_LOG_WARNING("Invalid Meta TLS client cert Subject CN: '{}'", + sslUser); + return std::nullopt; + } + + std::string_view entityType = sslUser.substr(0, colonIndex); + sslUser.remove_prefix(colonIndex + 1); + if (entityType != "user") + { + BMCWEB_LOG_WARNING( + "Invalid/unsupported entityType='{}' in Meta TLS client cert Subject CN: '{}'", + entityType, sslUser); + return std::nullopt; + } + + // Parse entity + size_t slashIndex = sslUser.find('/'); + std::string_view entity; + if (slashIndex == std::string_view::npos) + { + // No '/' character, Subject CN is just '<entityType>:<entity>' + entity = sslUser; + } + else + { + // Subject CN ends with /<hostname> + entity = sslUser.substr(0, slashIndex); + sslUser.remove_prefix(slashIndex + 1); + + if (entity.find_first_not_of( + "abcdefghijklmnopqrstuvwxyz0123456789_-.") != std::string::npos) + { + BMCWEB_LOG_WARNING( + "Invalid entity='{}' in Meta TLS client cert Subject CN: '{}'", + entity, sslUser); + return std::nullopt; + } + } + + if (entity.empty()) + { + BMCWEB_LOG_DEBUG("Invalid Meta TLS client cert Subject CN: '{}'", + sslUser); + return std::nullopt; + } + + return entity; +} diff --git a/meson.build b/meson.build index 26e8fda16f..207d59b025 100644 --- a/meson.build +++ b/meson.build @@ -428,6 +428,7 @@ srcfiles_unittest = files( 'test/http/http_response_test.cpp', 'test/http/utility_test.cpp', 'test/http/verb_test.cpp', + 'test/http/mutual_tls_meta.cpp', 'test/include/dbus_utility_test.cpp', 'test/include/google/google_service_root_test.cpp', 'test/include/json_html_serializer.cpp', diff --git a/meson_options.txt b/meson_options.txt index 017c16bd68..584ca7024c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -185,6 +185,20 @@ option( ) option( + 'mutual-tls-common-name-parsing', + type: 'combo', + choices: ['username', 'meta'], + value: 'username', + description: '''Sets logic to map the Subject Common Name field to a user + in client TLS certificates. + - username: Use the Subject CN field as a BMC username + (default) + - meta: Parses the Subject CN in the format used by + Meta Inc (see mutual_tls_meta.cpp for details) + ''' +) + +option( 'ibm-management-console', type: 'feature', value: 'disabled', diff --git a/test/http/mutual_tls_meta.cpp b/test/http/mutual_tls_meta.cpp new file mode 100644 index 0000000000..5f32cb54a1 --- /dev/null +++ b/test/http/mutual_tls_meta.cpp @@ -0,0 +1,49 @@ +#include "http/mutual_tls_meta.hpp" + +#include <gtest/gtest.h> // IWYU pragma: keep + +namespace redfish +{ +namespace +{ + +TEST(MetaParseSslUser, userTest) +{ + std::string sslUser = "user:kawajiri/hostname.facebook.com"; + EXPECT_EQ(mtlsMetaParseSslUser(sslUser), "kawajiri"); +} + +TEST(MetaParseSslUser, userNohostnameTest) +{ + // hostname is optional + std::string sslUser = "user:kawajiri"; + EXPECT_EQ(mtlsMetaParseSslUser(sslUser), "kawajiri"); +} + +TEST(MetaParseSslUser, invalidUsers) +{ + std::vector<std::string> invalidSslUsers = { + "", + ":", + ":/", + "ijslakd", + "user:", + "user:/", + "user:/hostname.facebook.com", + "user:/hostname.facebook.c om", + "user: space/hostname.facebook.com", + "svc:", + "svc:/", + "svc:/hostname.facebook.com", + "host:/", + "host:unexpected_user/", + }; + + for (const std::string& sslUser : invalidSslUsers) + { + EXPECT_EQ(mtlsMetaParseSslUser(sslUser), std::nullopt); + } +} + +} // namespace +} // namespace redfish |