diff --git a/src/v/security/CMakeLists.txt b/src/v/security/CMakeLists.txt index 23c3ec00056d1..f268e00d2d7db 100644 --- a/src/v/security/CMakeLists.txt +++ b/src/v/security/CMakeLists.txt @@ -22,12 +22,14 @@ v_cc_library( krb5.cc krb5_configurator.cc gssapi_principal_mapper.cc + audit/schemas/utils.cc DEPS v::security_config v::config v::bytes v::utils v::rprandom + v::version absl::flat_hash_map absl::flat_hash_set cryptopp diff --git a/src/v/security/audit/schemas/utils.cc b/src/v/security/audit/schemas/utils.cc new file mode 100644 index 0000000000000..f5984cf90246d --- /dev/null +++ b/src/v/security/audit/schemas/utils.cc @@ -0,0 +1,154 @@ +/* + * Copyright 2023 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#include "security/audit/schemas/utils.h" + +#include "security/audit/schemas/application_activity.h" +#include "security/audit/schemas/types.h" +#include "utils/request_auth.h" + +#include + +namespace security::audit { + +namespace { + +api_activity::activity_id http_method_to_activity_id(std::string_view method) { + return string_switch(method) + .match_all("POST", "post", api_activity::activity_id::create) + .match_all("GET", "get", api_activity::activity_id::read) + .match_all("PUT", "put", api_activity::activity_id::update) + .match_all("DELETE", "delete", api_activity::activity_id::delete_id) + .default_match(api_activity::activity_id::unknown); +} + +uniform_resource_locator +uri_from_ss_http_request(const ss::http::request& req) { + return { + .hostname = req.get_header("Host"), + .path = req._url, + .port = port_t{req.get_server_address().port()}, + .scheme = req.get_protocol_name(), + .url_string = req.get_url()}; +} + +http_request from_ss_http_request(const ss::http::request& req) { + using ss_http_headers_t = decltype(req._headers); + using ss_http_headers_value_t = ss_http_headers_t::value_type; + const auto get_headers = [](const ss_http_headers_t& headers) { + const auto sanitize_header = + [](const ss_http_headers_value_t& kv) -> ss::sstring { + constexpr auto sensitive_headers = std::to_array( + {"Authorization", "Cookie"}); + + if ( + std::find_if( + sensitive_headers.begin(), + sensitive_headers.end(), + [&kv](std::string_view s) { + return boost::iequals(s, kv.first); + }) + != sensitive_headers.end()) { + return "******"; + } else { + return kv.second; + } + }; + std::vector audit_headers; + std::transform( + headers.begin(), + headers.end(), + std::back_inserter(audit_headers), + [sanitize_header = std::move(sanitize_header)](const auto& kv) { + return http_header{ + .name = kv.first, .value = sanitize_header(kv)}; + }); + return audit_headers; + }; + return http_request{ + .http_headers = get_headers(req._headers), + .http_method = req._method, + .url = uri_from_ss_http_request(req), + .user_agent = req.get_header("User-Agent"), + .version = req._version}; +} + +network_endpoint from_ss_endpoint(const ss::socket_address& sa) { + return network_endpoint{ + .addr = net::unresolved_address( + fmt::format("{}", sa.addr()), sa.port(), sa.addr().in_family())}; +} + +/// TODO: Via ACLs metadata return correct response +api_activity_unmapped unmapped_data() { return api_activity_unmapped{}; } + +user user_from_request_auth_result(const request_auth_result& r) { + auto& username = r.get_username(); + + return { + .name = username.empty() ? "{{anonymous}}" : username, + .type_id = r.is_authenticated() + ? (r.is_superuser() ? user::type::admin : user::type::user) + : user::type::unknown, + }; +} + +actor actor_from_request_auth_result( + const request_auth_result& r, + bool authorized, + const std::optional& reason) { + auto u = user_from_request_auth_result(r); + std::vector auths{ + {.decision = authorized ? "authorized" : "denied", + .policy = policy{ + .desc = ss::sstring{reason.value_or( + r.is_auth_required() ? "" : "Auth Disabled")}, + .name = "Admin httpd authorizer"}}}; + + return {.authorizations = std::move(auths), .user = std::move(u)}; +} + +template +timestamp_t create_timestamp_t(std::chrono::time_point time_point) { + return timestamp_t(std::chrono::duration_cast( + time_point.time_since_epoch()) + .count()); +} + +template +timestamp_t create_timestamp_t() { + return create_timestamp_t(Clock::now()); +} + +} // namespace + +api_activity make_api_activity_event( + ss::httpd::const_req req, + const request_auth_result& auth_result, + bool authorized, + const std::optional& reason) { + auto act = actor_from_request_auth_result(auth_result, authorized, reason); + return { + http_method_to_activity_id(req._method), + std::move(act), + api{.operation = req._method}, + from_ss_endpoint(req.get_server_address()), + from_ss_http_request(req), + {}, + severity_id::informational, + from_ss_endpoint(req.get_client_address()), + authorized ? api_activity::status_id::success + : api_activity::status_id::failure, + create_timestamp_t(), + unmapped_data()}; +} + +} // namespace security::audit diff --git a/src/v/security/audit/schemas/utils.h b/src/v/security/audit/schemas/utils.h new file mode 100644 index 0000000000000..7d8dfa7525445 --- /dev/null +++ b/src/v/security/audit/schemas/utils.h @@ -0,0 +1,30 @@ +/* + * Copyright 2023 Redpanda Data, Inc. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.md + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0 + */ + +#pragma once +#include "net/unresolved_address.h" +#include "security/audit/schemas/application_activity.h" +#include "security/audit/schemas/types.h" +#include "utils/request_auth.h" +#include "utils/string_switch.h" + +#include +#include + +namespace security::audit { + +api_activity make_api_activity_event( + ss::httpd::const_req req, + const request_auth_result& auth_result, + bool authorized, + const std::optional& reason); + +} // namespace security::audit