From f60afbe59b1b3618ccecf0f8d7ae099ac0f450ab Mon Sep 17 00:00:00 2001 From: Amaury Chamayou <amchamay@microsoft.com> Date: Mon, 17 Jun 2024 11:24:18 +0100 Subject: [PATCH] Audit in programmability sample (#6258) --- samples/apps/programmability/audit_info.h | 40 +++++++++++++++++++ .../apps/programmability/programmability.cpp | 37 +++++++++++++---- 2 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 samples/apps/programmability/audit_info.h diff --git a/samples/apps/programmability/audit_info.h b/samples/apps/programmability/audit_info.h new file mode 100644 index 000000000000..e485a39c2224 --- /dev/null +++ b/samples/apps/programmability/audit_info.h @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. + +#pragma once +#include "ccf/ds/json.h" +#include "ccf/entity_id.h" + +#include <vector> + +namespace programmabilityapp +{ + enum class AuditInputFormat + { + COSE = 0, + JSON = 1 + }; + DECLARE_JSON_ENUM( + AuditInputFormat, + {{AuditInputFormat::COSE, "COSE"}, {AuditInputFormat::JSON, "JSON"}}); + + enum class AuditInputContent + { + BUNDLE = 0, + OPTIONS = 1 + }; + DECLARE_JSON_ENUM( + AuditInputContent, + {{AuditInputContent::BUNDLE, "BUNDLE"}, + {AuditInputContent::OPTIONS, "OPTIONS"}}); + + struct AuditInfo + { + AuditInputFormat format; + AuditInputContent content; + ccf::UserId user_id; + }; + + DECLARE_JSON_TYPE(AuditInfo) + DECLARE_JSON_REQUIRED_FIELDS(AuditInfo, format, content, user_id) +} \ No newline at end of file diff --git a/samples/apps/programmability/programmability.cpp b/samples/apps/programmability/programmability.cpp index f2b2800ccd95..b93aff4c9d45 100644 --- a/samples/apps/programmability/programmability.cpp +++ b/samples/apps/programmability/programmability.cpp @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 License. // CCF +#include "audit_info.h" #include "ccf/app_interface.h" #include "ccf/common_auth_policies.h" #include "ccf/ds/hash.h" @@ -19,7 +20,10 @@ using namespace nlohmann; namespace programmabilityapp { using RecordsMap = kv::Map<std::string, std::vector<uint8_t>>; + using AuditInputValue = kv::Value<std::vector<uint8_t>>; + using AuditInfoValue = kv::Value<AuditInfo>; static constexpr auto PRIVATE_RECORDS = "programmability.records"; + static constexpr auto CUSTOM_ENDPOINTS_NAMESPACE = "public:custom_endpoints"; // This sample shows the features of DynamicJSEndpointRegistry. This sample // adds a PUT /app/custom_endpoints, which calls install_custom_endpoints(), @@ -49,17 +53,18 @@ namespace programmabilityapp return std::nullopt; } - std::span<const uint8_t> get_body(ccf::endpoints::EndpointContext& ctx) + std::pair<AuditInputFormat, std::span<const uint8_t>> get_body( + ccf::endpoints::EndpointContext& ctx) { if ( const auto* cose_ident = ctx.try_get_caller<ccf::UserCOSESign1AuthnIdentity>()) { - return cose_ident->content; + return {AuditInputFormat::COSE, cose_ident->content}; } else { - return ctx.rpc_ctx->get_request_body(); + return {AuditInputFormat::JSON, ctx.rpc_ctx->get_request_body()}; } } @@ -67,8 +72,8 @@ namespace programmabilityapp ProgrammabilityHandlers(ccfapp::AbstractNodeContext& context) : ccf::js::DynamicJSEndpointRegistry( context, - "public:custom_endpoints" // Internal KV space will be under - // public:custom_endpoints.* + CUSTOM_ENDPOINTS_NAMESPACE // Internal KV space will be under + // public:custom_endpoints.* ) { openapi_info.title = "CCF Programmabilit App"; @@ -223,10 +228,19 @@ namespace programmabilityapp } // End of Authorization Check - const auto bundle = get_body(ctx); + const auto [format, bundle] = get_body(ctx); const auto j = nlohmann::json::parse(bundle.begin(), bundle.end()); const auto parsed_bundle = j.get<ccf::js::Bundle>(); + // Make operation auditable by writing user-supplied + // document to the ledger + auto audit_input = ctx.tx.template rw<AuditInputValue>( + fmt::format("{}.audit.input", CUSTOM_ENDPOINTS_NAMESPACE)); + audit_input->put(ctx.rpc_ctx->get_request_body()); + auto audit_info = ctx.tx.template rw<AuditInfoValue>( + fmt::format("{}.audit.info", CUSTOM_ENDPOINTS_NAMESPACE)); + audit_info->put({format, AuditInputContent::BUNDLE, user_id.value()}); + result = install_custom_endpoints_v1(ctx.tx, parsed_bundle); if (result != ccf::ApiResult::OK) { @@ -377,7 +391,7 @@ namespace programmabilityapp // - Convert current options to JSON auto j_options = nlohmann::json(options); - const auto body = get_body(ctx); + const auto [format, body] = get_body(ctx); // - Parse argument as JSON body const auto arg_body = nlohmann::json::parse(body.begin(), body.end()); @@ -389,6 +403,15 @@ namespace programmabilityapp // - Parse patched options from JSON options = j_options.get<ccf::JSRuntimeOptions>(); + // Make operation auditable by writing user-supplied + // document to the ledger + auto audit = ctx.tx.template rw<AuditInputValue>( + fmt::format("{}.audit.input", CUSTOM_ENDPOINTS_NAMESPACE)); + audit->put(ctx.rpc_ctx->get_request_body()); + auto audit_info = ctx.tx.template rw<AuditInfoValue>( + fmt::format("{}.audit.info", CUSTOM_ENDPOINTS_NAMESPACE)); + audit_info->put({format, AuditInputContent::BUNDLE, user_id.value()}); + result = set_js_runtime_options_v1(ctx.tx, options); if (result != ccf::ApiResult::OK) {