Skip to content

Commit

Permalink
24-3: Add basic dynconfig audit (#8155)
Browse files Browse the repository at this point in the history
  • Loading branch information
Enjection authored Aug 22, 2024
1 parent d1693e4 commit cf0bcd6
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 0 deletions.
21 changes: 21 additions & 0 deletions ydb/core/cms/console/console__replace_yaml_config.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "console_configs_manager.h"
#include "console_configs_provider.h"
#include "console_audit.h"

#include <ydb/core/tablet_flat/tablet_flat_executed.h>
#include <ydb/library/aclib/aclib.h>
Expand All @@ -16,6 +17,7 @@ class TConfigsManager::TTxReplaceYamlConfig : public TTransactionBase<TConfigsMa
bool force)
: TBase(self)
, Config(ev->Get()->Record.GetRequest().config())
, Peer(ev->Get()->Record.GetPeerName())
, Sender(ev->Sender)
, UserSID(NACLib::TUserToken(ev->Get()->Record.GetUserToken()).GetUserSID())
, Force(force)
Expand Down Expand Up @@ -146,6 +148,7 @@ class TConfigsManager::TTxReplaceYamlConfig : public TTransactionBase<TConfigsMa
auto *issue = ev->Record.AddIssues();
issue->set_severity(NYql::TSeverityIds::S_ERROR);
issue->set_message(ex.what());
ErrorReason = ex.what();
Response = MakeHolder<NActors::IEventHandle>(Sender, ctx.SelfID, ev.Release());
}

Expand All @@ -159,6 +162,14 @@ class TConfigsManager::TTxReplaceYamlConfig : public TTransactionBase<TConfigsMa
ctx.Send(Response.Release());

if (!Error && Modify && !DryRun) {
AuditLogReplaceConfigTransaction(
/* peer = */ Peer,
/* userSID = */ UserSID,
/* oldConfig = */ Self->YamlConfig,
/* newConfig = */ Config,
/* reason = */ {},
/* success = */ true);

Self->YamlVersion = Version + 1;
Self->YamlConfig = UpdatedConfig;
Self->YamlDropped = false;
Expand All @@ -167,20 +178,30 @@ class TConfigsManager::TTxReplaceYamlConfig : public TTransactionBase<TConfigsMa

auto resp = MakeHolder<TConfigsProvider::TEvPrivate::TEvUpdateYamlConfig>(Self->YamlConfig);
ctx.Send(Self->ConfigsProvider, resp.Release());
} else if (Error && !DryRun) {
AuditLogReplaceConfigTransaction(
/* peer = */ Peer,
/* userSID = */ UserSID,
/* oldConfig = */ Self->YamlConfig,
/* newConfig = */ Config,
/* reason = */ ErrorReason,
/* success = */ false);
}

Self->TxProcessor->TxCompleted(this, ctx);
}

private:
const TString Config;
const TString Peer;
const TActorId Sender;
const TString UserSID;
const bool Force = false;
const bool AllowUnknownFields = false;
const bool DryRun = false;
THolder<NActors::IEventHandle> Response;
bool Error = false;
TString ErrorReason;
bool Modify = false;
TSimpleSharedPtr<NYamlConfig::TBasicUnknownFieldsCollector> UnknownFieldsCollector = nullptr;
ui32 Version;
Expand Down
34 changes: 34 additions & 0 deletions ydb/core/cms/console/console_audit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "console_audit.h"

#include <ydb/core/audit/audit_log.h>
#include <ydb/core/util/address_classifier.h>

namespace NKikimr::NConsole {

void AuditLogReplaceConfigTransaction(
const TString& peer,
const TString& userSID,
const TString& oldConfig,
const TString& newConfig,
const TString& reason,
bool success)
{
static const TString COMPONENT_NAME = "console";

static const TString EMPTY_VALUE = "{none}";

auto peerName = NKikimr::NAddressClassifier::ExtractAddress(peer);

AUDIT_LOG(
AUDIT_PART("component", COMPONENT_NAME)
AUDIT_PART("remote_address", (!peerName.empty() ? peerName : EMPTY_VALUE))
AUDIT_PART("subject", (!userSID.empty() ? userSID : EMPTY_VALUE))
AUDIT_PART("status", TString(success ? "SUCCESS" : "ERROR"))
AUDIT_PART("reason", reason, !reason.empty())
AUDIT_PART("operation", TString("REPLACE DYNCONFIG"))
AUDIT_PART("old_config", oldConfig)
AUDIT_PART("new_config", newConfig)
);
}

} // namespace NKikimr::NConsole
15 changes: 15 additions & 0 deletions ydb/core/cms/console/console_audit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include <util/generic/string.h>

namespace NKikimr::NConsole {

void AuditLogReplaceConfigTransaction(
const TString& peer,
const TString& userSID,
const TString& oldConfig,
const TString& newConfig,
const TString& reason,
bool success);

} // namespace NKikimr::NConsole
21 changes: 21 additions & 0 deletions ydb/core/cms/console/console_configs_manager.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "console_configs_manager.h"

#include "configs_dispatcher.h"
#include "console_audit.h"
#include "console_configs_provider.h"
#include "console_impl.h"
#include "http.h"
Expand Down Expand Up @@ -974,4 +975,24 @@ void TConfigsManager::ScheduleLogCleanup(const TActorContext &ctx)
LogCleanupTimerCookieHolder.Get());
}

void TConfigsManager::HandleUnauthorized(TEvConsole::TEvReplaceYamlConfigRequest::TPtr &ev, const TActorContext &) {
AuditLogReplaceConfigTransaction(
/* peer = */ ev->Get()->Record.GetPeerName(),
/* userSID = */ ev->Get()->Record.GetUserToken(),
/* oldConfig = */ YamlConfig,
/* newConfig = */ ev->Get()->Record.GetRequest().config(),
/* reason = */ "Unauthorized.",
/* success = */ false);
}

void TConfigsManager::HandleUnauthorized(TEvConsole::TEvSetYamlConfigRequest::TPtr &ev, const TActorContext &) {
AuditLogReplaceConfigTransaction(
/* peer = */ ev->Get()->Record.GetPeerName(),
/* userSID = */ ev->Get()->Record.GetUserToken(),
/* oldConfig = */ YamlConfig,
/* newConfig = */ ev->Get()->Record.GetRequest().config(),
/* reason = */ "Unauthorized.",
/* success = */ false);
}

} // namespace NKikimr::NConsole
9 changes: 9 additions & 0 deletions ydb/core/cms/console/console_configs_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ class TConfigsManager : public TActorBootstrapped<TConfigsManager> {
void Handle(TEvInterconnect::TEvNodesInfo::TPtr &ev, const TActorContext &ctx);
void Handle(TEvConsole::TEvReplaceYamlConfigRequest::TPtr & ev, const TActorContext & ctx);
void Handle(TEvConsole::TEvSetYamlConfigRequest::TPtr & ev, const TActorContext & ctx);
void HandleUnauthorized(TEvConsole::TEvReplaceYamlConfigRequest::TPtr & ev, const TActorContext & ctx);
void HandleUnauthorized(TEvConsole::TEvSetYamlConfigRequest::TPtr & ev, const TActorContext & ctx);
void Handle(TEvConsole::TEvDropConfigRequest::TPtr & ev, const TActorContext & ctx);
void Handle(TEvPrivate::TEvStateLoaded::TPtr &ev, const TActorContext &ctx);
void Handle(TEvPrivate::TEvCleanupSubscriptions::TPtr &ev, const TActorContext &ctx);
Expand All @@ -160,9 +162,16 @@ class TConfigsManager : public TActorBootstrapped<TConfigsManager> {

template <class T>
void HandleWithRights(T &ev, const TActorContext &ctx) {
constexpr bool HasHandleUnauthorized = requires(T &ev) {
HandleUnauthorized(ev, ctx);
};

if (CheckRights(ev->Get()->Record.GetUserToken())) {
Handle(ev, ctx);
} else {
if constexpr (HasHandleUnauthorized) {
HandleUnauthorized(ev, ctx);
}
auto req = MakeHolder<TEvConsole::TEvUnauthorized>();
ctx.Send(ev->Sender, req.Release());
}
Expand Down
2 changes: 2 additions & 0 deletions ydb/core/cms/console/ya.make
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ SRCS(
configs_dispatcher.h
console.cpp
console.h
console_audit.cpp
console_audit.h
console_configs_manager.cpp
console_configs_manager.h
console_configs_provider.cpp
Expand Down
2 changes: 2 additions & 0 deletions ydb/core/protos/console_config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ message TConfigureResponse {
message TSetYamlConfigRequest {
optional Ydb.DynamicConfig.SetConfigRequest Request = 1;
optional bytes UserToken = 2;
optional string PeerName = 3;
}

message TSetYamlConfigResponse {
Expand All @@ -267,6 +268,7 @@ message TSetYamlConfigResponse {
message TReplaceYamlConfigRequest {
optional Ydb.DynamicConfig.ReplaceConfigRequest Request = 1;
optional bytes UserToken = 2;
optional string PeerName = 3;
}

message TReplaceYamlConfigResponse {
Expand Down
75 changes: 75 additions & 0 deletions ydb/tests/functional/audit/test_auditlog.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import json
import logging
import os
import subprocess
Expand All @@ -10,6 +11,7 @@
from ydb.tests.oss.ydb_sdk_import import ydb

from ydb import Driver, DriverConfig, SessionPool
from ydb.draft import DynamicConfigClient
from ydb.tests.library.harness.util import LogLevels
from ydb.tests.library.harness.ydb_fixtures import ydb_database_ctx

Expand Down Expand Up @@ -365,3 +367,76 @@ def test_cloud_ids_are_logged(ydb_cluster, _database, prepared_test_env, _client
for k, v in attrs.items():
name = k if k != 'database_id' else 'resource_id'
assert fr'''"{name}":"{v}"''' in capture_audit.captured


def apply_config(pool, config):
client = DynamicConfigClient(pool._driver)
client.set_config(config, dry_run=False, allow_unknown_fields=False)


@pytest.fixture(scope='module')
def _good_dynconfig():
return '''
---
metadata:
kind: MainConfig
cluster: ""
version: 0
config:
yaml_config_enabled: true
allowed_labels:
node_id:
type: string
host:
type: string
tenant:
type: string
selector_config: []
'''


@pytest.fixture(scope='module')
def _bad_dynconfig():
return '''
---
123metadata:
kind: MainConfig
cluster: ""
version: %s
config:
yaml_config_enabled: true
allowed_labels:
node_id:
type: string
host:
type: string
tenant:
type: string
selector_config: []
'''


def test_dynconfig(ydb_cluster, prepared_test_env, _client_session_pool_with_auth_root, _good_dynconfig):
config = _good_dynconfig
_table_path, capture_audit = prepared_test_env
with capture_audit:
_client_session_pool_with_auth_root.retry_operation_sync(apply_config, config=config)

print(capture_audit.captured, file=sys.stderr)
assert json.dumps(config) in capture_audit.captured


@pytest.mark.parametrize('config_fixture', ["_bad_dynconfig", "_good_dynconfig"])
@pytest.mark.parametrize('pool_fixture', ["_client_session_pool_with_auth_root", "_client_session_pool_no_auth", "_client_session_pool_bad_auth", "_client_session_pool_with_auth_other"])
def test_broken_dynconfig(ydb_cluster, prepared_test_env, pool_fixture, config_fixture, request):
pool = request.getfixturevalue(pool_fixture)
config = request.getfixturevalue(config_fixture)
_table_path, capture_audit = prepared_test_env
with capture_audit:
try:
pool.retry_operation_sync(apply_config, config=config)
except ydb.issues.BadRequest:
pass

print(capture_audit.captured, file=sys.stderr)
assert json.dumps(config) in capture_audit.captured

0 comments on commit cf0bcd6

Please sign in to comment.