diff options
author | ijon <ijon@yandex-team.com> | 2023-03-13 13:06:42 +0300 |
---|---|---|
committer | ijon <ijon@yandex-team.com> | 2023-03-13 13:06:42 +0300 |
commit | b289c1b08d7c71b12149c22feb733cd79c9f0143 (patch) | |
tree | 20a5f80663ea19b879d64bc1cc69e4f485a2f858 | |
parent | d2e129c8bc361ffd7f75b24ce1903eeb06613d3a (diff) | |
download | ydb-b289c1b08d7c71b12149c22feb733cd79c9f0143.tar.gz |
schemeshard, auditlog: add cloud ids and acl/user attrs modification details
Add new audit record fields:
- component -- (="schemeshard")
- {cloud,folder,resource}_id -- Cloud specific database binding
- new_owner, acl_add, acl_remove -- ACL change (if applicable)
- user_attrs_add, user_attrs_remove -- user attributes change (if applicable)
3 files changed, 155 insertions, 14 deletions
diff --git a/ydb/core/tx/schemeshard/schemeshard__operation.cpp b/ydb/core/tx/schemeshard/schemeshard__operation.cpp index 5aeb859aea4..4fb08611b4f 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__operation.cpp @@ -36,39 +36,106 @@ std::tuple<TMaybe<NACLib::TUserToken>, bool> ParseUserToken(const TString& token return std::make_tuple(result, parseError); } -TString RenderPaths(const TVector<TString>& paths) { +TString GeneralStatus(NKikimrScheme::EStatus actualStatus) { + switch(actualStatus) { + case NKikimrScheme::EStatus::StatusAlreadyExists: + case NKikimrScheme::EStatus::StatusSuccess: + return "SUCCESS"; + case NKikimrScheme::EStatus::StatusAccepted: + //TODO: reclassify StatusAccepted as IN-PROCCESS when + // logging of operation completion points will be added + // return "IN-PROCCESS"; + return "SUCCESS"; + default: + return "ERROR"; + } +} + +TString RenderList(const TVector<TString>& list) { auto result = TStringBuilder(); - result << "[" << JoinStrings(paths.begin(), paths.end(), ", ") << "]"; + result << "[" << JoinStrings(list.begin(), list.end(), ", ") << "]"; return result; } +std::tuple<TString, TString, TString> GetDatabaseCloudIds(const TPath &databasePath) { + if (databasePath.IsEmpty()) { + return {}; + } + Y_VERIFY(databasePath->IsDomainRoot()); + auto getAttr = [&databasePath](const TString &name) -> TString { + if (databasePath.Base()->UserAttrs->Attrs.contains(name)) { + return databasePath.Base()->UserAttrs->Attrs.at(name); + } + return {}; + }; + return std::make_tuple( + getAttr("cloud_id"), + getAttr("folder_id"), + getAttr("database_id") + ); +} + +TPath DatabasePathFromWorkingDir(TSchemeShard* SS, const TString &opWorkingDir) { + auto databasePath = TPath::Resolve(opWorkingDir, SS); + if (!databasePath.IsResolved()) { + databasePath.RiseUntilFirstResolvedParent(); + } + //NOTE: operation working dir is usually set to a path of some database/subdomain, + // so the next lines is only a safety measure + if (!databasePath.IsEmpty() && !databasePath->IsDomainRoot()) { + databasePath = TPath::Init(databasePath.GetPathIdForDomain(), SS); + } + return databasePath; +} + void AuditLogModifySchemeTransaction(const NKikimrScheme::TEvModifySchemeTransaction& request, const NKikimrScheme::TEvModifySchemeTransactionResult& response, TSchemeShard* SS, const TString& userSID) { - // Each TEvModifySchemeTransaction.Transaction is a self sufficient operation and should be logged independently - // (even if it was packed into a single TxProxy transaction with some other operations). + static const TString SchemeshardComponentName = "schemeshard"; - //NOTE: UserSIDNone couldn't be an empty string as "subject" field is a required one, - // but AUDIT_PART() skips any part with an empty value + //NOTE: EmptyValue couldn't be an empty string as AUDIT_PART() skips parts with an empty values static const TString EmptyValue = "{none}"; + // Each TEvModifySchemeTransaction.Transaction is a self sufficient operation and should be logged independently + // (even if it was packed into a single TxProxy transaction with some other operations). for (const auto& operation : request.GetTransaction()) { auto logEntry = MakeAuditLogFragment(operation); - auto databasePath = TPath::Resolve(operation.GetWorkingDir(), SS); - if (!databasePath.IsResolved()) { - databasePath.RiseUntilFirstResolvedParent(); - } - + TPath databasePath = DatabasePathFromWorkingDir(SS, operation.GetWorkingDir()); + auto [cloud_id, folder_id, database_id] = GetDatabaseCloudIds(databasePath); auto peerName = request.GetPeerName(); AUDIT_LOG( - AUDIT_PART("txId", std::to_string(request.GetTxId())) + AUDIT_PART("component", SchemeshardComponentName) + AUDIT_PART("tx_id", std::to_string(request.GetTxId())) AUDIT_PART("remote_address", (!peerName.empty() ? peerName : EmptyValue) ) AUDIT_PART("subject", (!userSID.empty() ? userSID : EmptyValue)) AUDIT_PART("database", (!databasePath.IsEmpty() ? databasePath.GetDomainPathString() : EmptyValue)) AUDIT_PART("operation", logEntry.Operation) - AUDIT_PART("paths", RenderPaths(logEntry.Paths), !logEntry.Paths.empty()) - AUDIT_PART("status", NKikimrScheme::EStatus_Name(response.GetStatus())) + AUDIT_PART("paths", RenderList(logEntry.Paths), !logEntry.Paths.empty()) + AUDIT_PART("status", GeneralStatus(response.GetStatus())) + AUDIT_PART("detailed_status", NKikimrScheme::EStatus_Name(response.GetStatus())) AUDIT_PART("reason", response.GetReason(), response.HasReason()) + + AUDIT_PART("cloud_id", cloud_id, !cloud_id.empty()); + AUDIT_PART("folder_id", folder_id, !folder_id.empty()); + AUDIT_PART("resource_id", database_id, !database_id.empty()); + + // Additionally: + + // ModifyACL. + // Technically, non-empty ModifyACL field could come with any ModifyScheme operation. + // In practice, ModifyACL will get processed only by: + // 1. explicit operation ESchemeOpModifyACL -- to modify ACL on a path + // 2. ESchemeOpMkDir or ESchemeOpCreate* operations -- to set rights to newly created paths/entities + // 3. ESchemeOpCopyTable -- to be checked against acl size limit, not to be applied in any way + AUDIT_PART("new_owner", logEntry.NewOwner, !logEntry.NewOwner.empty()); + AUDIT_PART("acl_add", RenderList(logEntry.ACLAdd), !logEntry.ACLAdd.empty()); + AUDIT_PART("acl_remove", RenderList(logEntry.ACLRemove), !logEntry.ACLRemove.empty()); + + // AlterUserAttributes. + // 1. explicit operation ESchemeOpAlterUserAttributes -- to modify user attributes on a path + // 2. ESchemeOpMkDir or some ESchemeOpCreate* operations -- to set user attributes for newly created paths/entities + AUDIT_PART("user_attrs_add", RenderList(logEntry.UserAttrsAdd), !logEntry.UserAttrsAdd.empty()); + AUDIT_PART("user_attrs_remove", RenderList(logEntry.UserAttrsRemove), !logEntry.UserAttrsRemove.empty()); ); } } diff --git a/ydb/core/tx/schemeshard/schemeshard_audit_log_fragment.cpp b/ydb/core/tx/schemeshard/schemeshard_audit_log_fragment.cpp index 6044c00fa81..b24db727fba 100644 --- a/ydb/core/tx/schemeshard/schemeshard_audit_log_fragment.cpp +++ b/ydb/core/tx/schemeshard/schemeshard_audit_log_fragment.cpp @@ -480,14 +480,82 @@ TVector<TString> ExtractChangingPaths(const NKikimrSchemeOp::TModifyScheme& tx) return result; } +TString ExtractNewOwner(const NKikimrSchemeOp::TModifyScheme& tx) { + bool hasNewOwner = tx.HasModifyACL() && tx.GetModifyACL().HasNewOwner(); + if (hasNewOwner) { + return tx.GetModifyACL().GetNewOwner(); + } + return {}; +} + +struct TChange { + TVector<TString> Add; + TVector<TString> Remove; +}; + +TChange ExtractACLChange(const NKikimrSchemeOp::TModifyScheme& tx) { + bool hasACL = tx.HasModifyACL() && tx.GetModifyACL().HasDiffACL(); + if (hasACL) { + TChange result; + + NACLib::TDiffACL diff(tx.GetModifyACL().GetDiffACL()); + for (const auto& i : diff.GetDiffACE()) { + auto diffType = static_cast<NACLib::EDiffType>(i.GetDiffType()); + const NACLibProto::TACE& ace = i.GetACE(); + switch (diffType) { + case NACLib::EDiffType::Add: + result.Add.push_back(NACLib::TACL::ToString(ace)); + break; + case NACLib::EDiffType::Remove: + result.Remove.push_back(NACLib::TACL::ToString(ace)); + break; + } + } + + return result; + } + return {}; +} + +TChange ExtractUserAttrChange(const NKikimrSchemeOp::TModifyScheme& tx) { + bool hasUserAttrs = tx.HasAlterUserAttributes() && (tx.GetAlterUserAttributes().UserAttributesSize() > 0); + if (hasUserAttrs) { + TChange result; + auto str = TStringBuilder(); + + for (const auto& i : tx.GetAlterUserAttributes().GetUserAttributes()) { + const auto& key = i.GetKey(); + const auto& value = i.GetValue(); + if (value.empty()) { + result.Remove.push_back(key); + } else { + str.clear(); + str << key << ": " << "value"; + result.Add.push_back(str); + } + } + + return result; + } + return {}; +} + + } // anonymous namespace namespace NKikimr::NSchemeShard { TAuditLogFragment MakeAuditLogFragment(const NKikimrSchemeOp::TModifyScheme& tx) { + auto [aclAdd, aclRemove] = ExtractACLChange(tx); + auto [userAttrsAdd, userAttrsRemove] = ExtractUserAttrChange(tx); return { .Operation = DefineUserOperationName(tx.GetOperationType()), .Paths = ExtractChangingPaths(tx), + .NewOwner = ExtractNewOwner(tx), + .ACLAdd = aclAdd, + .ACLRemove = aclRemove, + .UserAttrsAdd = userAttrsAdd, + .UserAttrsRemove = userAttrsRemove, }; } diff --git a/ydb/core/tx/schemeshard/schemeshard_audit_log_fragment.h b/ydb/core/tx/schemeshard/schemeshard_audit_log_fragment.h index 57874086b48..af93dc15c80 100644 --- a/ydb/core/tx/schemeshard/schemeshard_audit_log_fragment.h +++ b/ydb/core/tx/schemeshard/schemeshard_audit_log_fragment.h @@ -2,6 +2,7 @@ #include <util/generic/string.h> #include <util/generic/vector.h> +#include <util/generic/maybe.h> namespace NKikimrSchemeOp { class TModifyScheme; @@ -12,6 +13,11 @@ namespace NKikimr::NSchemeShard { struct TAuditLogFragment { TString Operation; TVector<TString> Paths; + TString NewOwner; + TVector<TString> ACLAdd; + TVector<TString> ACLRemove; + TVector<TString> UserAttrsAdd; + TVector<TString> UserAttrsRemove; }; TAuditLogFragment MakeAuditLogFragment(const NKikimrSchemeOp::TModifyScheme& tx); |