summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarco Kawajiri <kawajiri@meta.com>2023-10-31 23:36:58 +0300
committerEd Tanous <ed@tanous.net>2023-12-09 01:59:39 +0300
commit0e373b53f81fc1720050571755ecfcdc6dd9ba9b (patch)
tree2aef16a7aca2a2bc12e7b086ebdde17b178daf14
parent23f1c96e6bc9060b54ff08a6b4d6cf8b8e0c3b23 (diff)
downloadbmcweb-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.in2
-rw-r--r--config/meson.build3
-rw-r--r--http/mutual_tls.hpp14
-rw-r--r--http/mutual_tls_meta.hpp73
-rw-r--r--meson.build1
-rw-r--r--meson_options.txt14
-rw-r--r--test/http/mutual_tls_meta.cpp49
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