ud3tn/components/agents/storage/sqlite_agent.c
Felix Walter 2dcf3b35d9 aap2: Secure the control of the storage and BDMs through AAP 2.0
With the addition of the storage agent and the compat. BDM we have the
issue that two new agents accept configuration through bundles, which
cannot check that those bundles come from trustworthy sources. In the
past we restricted contact configuration messages to local clients and
performed an "EID spoofing detection" so that we could check the source
EID - if it is the same as the local node ID, we allowed the
configuration bundle to be processed. With AAPv2 and potentially more
security-relevant components (such as BDMs) appearing in the future, we
need a new mechanism.

The idea behind the implemented mechanism is to reuse the existing AAP
2.0 shared-secret authentication that is applied for BDMs themselves
also for sending configuration messages: We add the possibility to
register an AAP 2.0 RPC agent (one that sends commands *toward* uD3TN)
with the "dispatch" authorization flag. This client can then request a
special flag to be added when sending bundles. The new flag is only
added internally by uD3TN to its in-memory data structure and is
delivered to all internal agents as well as AAP 2.0 clients receiving
the marked bundles. Those agents and clients (such as the sqlite/storage
agent) can then easily check for the flag to be present and thus
determine whether the bundle comes from an authenticated and authorized
source.

Note: The `adu_flags` field for the BundleADU AAP 2.0 message is now a
`repeated` field to represent the option of multiple flags being present
(Protobuf does not support bit fields for this purpose).

Fixes: #187

Signed-off-by: Felix Walter <felix.walter@d3tn.com>
2024-06-26 10:23:17 +02:00

321 lines
8.3 KiB
C

// SPDX-License-Identifier: AGPL-3.0-or-later
#include "agents/sqlite_agent.h"
#include "agents/storage/storage_agent.pb.h"
#include "cla/storage/cla_sqlite.h"
#include "platform/hal_io.h"
#include "platform/hal_queue.h"
#include "platform/posix/hal_types.h"
#include "platform/hal_task.h"
#include "ud3tn/bundle_processor.h"
#include "ud3tn/common.h"
#include "ud3tn/eid.h"
#include "ud3tn/result.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <pb_decode.h>
static enum ud3tn_result delete_bundles_by_id(sqlite3 *db, CompoundBundleId *id)
{
sqlite3_stmt *stmt;
const char *sql = (
"DELETE FROM bundles WHERE source=? AND creation_timestamp=? AND sequence_number=? AND fragment_offset=? AND payload_length=?;"
);
if (!id->source_eid) {
LOG_ERROR("SQLiteAgent: id.source_eid is NULL");
return UD3TN_FAIL;
}
if (sqlite3_exec(db, "BEGIN IMMEDIATE TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to BEGIN IMMEDIATE TRANSACTION: %s",
sqlite3_errmsg(db)
);
return UD3TN_FAIL;
}
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
LOGF_ERROR("SQLiteAgent: Failed to prepare SQL statement: %s", sqlite3_errmsg(db));
goto rollback;
}
if (sqlite3_bind_text(
stmt, 1, id->source_eid, strlen(id->source_eid), SQLITE_TRANSIENT
) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to bind source column: %s",
sqlite3_errmsg(db)
);
goto rollback;
}
if (sqlite3_bind_int64(stmt, 2, id->creation_timestamp) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to bind creation_timestamp column: %llu",
sqlite3_errmsg(db)
);
goto rollback;
}
if (sqlite3_bind_int64(stmt, 3, id->sequence_number) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to bind sequence_number column: %llu",
sqlite3_errmsg(db)
);
goto rollback;
}
if (sqlite3_bind_int64(stmt, 4, id->fragment_offset) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to bind fragment_offset column: %llu",
sqlite3_errmsg(db)
);
goto rollback;
}
if (sqlite3_bind_int64(stmt, 5, id->payload_length) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to bind payload_length column: %llu",
sqlite3_errmsg(db)
);
goto rollback;
}
// Retry to execute the delete statement
if (sqlite3_step(stmt) != SQLITE_DONE)
goto rollback;
if (sqlite3_finalize(stmt) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to finalize delete statement: %s",
sqlite3_errmsg(db)
);
goto rollback;
}
if (sqlite3_exec(db, "END TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK) {
LOGF_ERROR("SQLiteAgent: Failed to END TRANSACTION: %s", sqlite3_errmsg(db));
goto rollback;
}
return UD3TN_OK;
rollback:
sqlite3_clear_bindings(stmt);
if (sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK)
LOGF_ERROR("SQLiteAgent: Failed to ROLLBACK TRANSACTION: %s", sqlite3_errmsg(db));
return UD3TN_FAIL;
}
static enum ud3tn_result delete_bundles_by_metadata(sqlite3 *db, BundleMetadataFilter *metadata)
{
sqlite3_stmt *stmt;
const char *sql = "DELETE FROM bundles WHERE destination GLOB ?";
char *eid_glob = metadata->eid_glob;
if (!eid_glob) {
LOG_ERROR("SQLiteAgent: metadata.eid_glob is NULL");
return UD3TN_FAIL;
}
if (sqlite3_exec(db, "BEGIN IMMEDIATE TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to BEGIN IMMEDIATE TRANSACTION: %s",
sqlite3_errmsg(db)
);
return UD3TN_FAIL;
}
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
LOGF_ERROR("SQLiteAgent: Failed to prepare SQL statement: %s", sqlite3_errmsg(db));
goto rollback;
}
if (sqlite3_bind_text(stmt, 1, eid_glob, strlen(eid_glob), NULL) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to bind destination column: %s",
sqlite3_errmsg(db)
);
goto rollback;
}
// Retry to execute the delete statement
if (sqlite3_step(stmt) != SQLITE_DONE)
goto rollback;
if (sqlite3_finalize(stmt) != SQLITE_OK) {
LOGF_ERROR(
"SQLiteAgent: Failed to finalize delete statement: %s",
sqlite3_errmsg(db)
);
goto rollback;
}
if (sqlite3_exec(db, "END TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK) {
LOGF_ERROR("SQLiteAgent: Failed to END TRANSACTION: %s", sqlite3_errmsg(db));
goto rollback;
}
return UD3TN_OK;
rollback:
sqlite3_clear_bindings(stmt);
if (sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL) != SQLITE_OK)
LOGF_ERROR("SQLiteAgent: Failed to ROLLBACK TRANSACTION: %s", sqlite3_errmsg(db));
return UD3TN_FAIL;
}
static enum ud3tn_result storage_operation_delete_bundles(sqlite3 *db, StorageCall *cmd)
{
switch (cmd->which_bundle_filter) {
case StorageCall_id_tag:
if (delete_bundles_by_id(db, &cmd->bundle_filter.id) != UD3TN_OK)
return UD3TN_FAIL;
break;
case StorageCall_metadata_tag:
if (delete_bundles_by_metadata(db, &cmd->bundle_filter.metadata) != UD3TN_OK)
return UD3TN_FAIL;
break;
default:
LOG_DEBUG("SQLiteAgent: Invalid bundle_filter option");
return UD3TN_FAIL;
};
return UD3TN_OK;
}
static enum ud3tn_result storage_operation_push_bundles(
sqlite3 *db,
QueueIdentifier_t cla_queue,
StorageCall *storage_call)
{
struct cla_sqlite_command sqlite_cmd = {
.type = SQLITE_COMMAND_STORAGE_CALL,
.storage_call = *storage_call,
};
if (hal_queue_try_push_to_back(cla_queue, &sqlite_cmd, 0) != UD3TN_OK) {
LOG_ERROR("SQLiteAgent: Failed to push the StorageCall into the agent_queue");
return UD3TN_FAIL;
}
return UD3TN_OK;
}
static enum ud3tn_result storage_operation_list_bundles(
sqlite3 *db,
StorageCall *cmd)
{
LOG_WARN("SQLiteAgent: STORAGE_OPERATION_LIST_BUNDLES unimplemented!");
return UD3TN_OK;
}
static enum ud3tn_result parse_storage_call(uint8_t *data, size_t length, StorageCall *storage_call)
{
pb_istream_t stream = pb_istream_from_buffer(data, length);
if (!pb_decode_ex(&stream, StorageCall_fields, storage_call, PB_DECODE_DELIMITED)) {
LOGF_ERROR("SQLiteAgent: Protobuf decode error: %s", PB_GET_ERROR(&stream));
return UD3TN_FAIL;
}
return UD3TN_OK;
}
static void sqlite_agent_callback(struct bundle_adu data, void *p, const void *bp_context)
{
sqlite3 *db = ((struct sqlite_agent_params *)p)->db;
QueueIdentifier_t cla_queue = ((struct sqlite_agent_params *)p)->cla_queue;
StorageCall storage_call = StorageCall_init_default;
if (!data.bdm_auth_validated) {
LOGF_WARN(
"SQLiteAgent: Incoming bundle ADU (from: \"%s\") not authorized",
data.source
);
pb_release(StorageCall_fields, &storage_call);
goto done;
}
parse_storage_call(data.payload, data.length, &storage_call);
char *eid_glob = storage_call.bundle_filter.metadata.eid_glob;
switch (storage_call.operation) {
case StorageOperation_STORAGE_OPERATION_DELETE_BUNDLES:
LOGF_DEBUG(
"SQLiteAgent: Received STORAGE_OPERATION_DELETE_BUNDLES (filter.eid_glob = %s)",
eid_glob
);
storage_operation_delete_bundles(db, &storage_call);
pb_release(StorageCall_fields, &storage_call);
break;
case StorageOperation_STORAGE_OPERATION_PUSH_BUNDLES:
LOGF_DEBUG(
"SQLiteAgent: Received STORAGE_OPERATION_PUSH_BUNDLES (filter.eid_glob = %s)",
eid_glob
);
storage_operation_push_bundles(db, cla_queue, &storage_call);
// BundleFilter is freed in SQLiteCLA
break;
case StorageOperation_STORAGE_OPERATION_LIST_BUNDLES:
LOGF_DEBUG(
"SQLiteAgent: Received STORAGE_OPERATION_LIST_BUNDLES (filter.eid_glob = %s)",
eid_glob
);
storage_operation_list_bundles(db, &storage_call);
pb_release(StorageCall_fields, &storage_call);
break;
default:
(void)eid_glob;
LOG_WARN("SQLiteAgent: Unsupported StorageOperation");
pb_release(StorageCall_fields, &storage_call);
break;
}
done:
// Free allocated data
bundle_adu_free_members(data);
}
enum ud3tn_result sqlite_agent_setup(
const struct bundle_agent_interface *const bai,
struct sqlite_agent_params *params)
{
const struct agent agent = {
.auth_trx = true,
.is_subscriber = true,
.sink_identifier = params->sink_identifier,
.trx_callback = sqlite_agent_callback,
.param = params,
};
agent_no_t agent_no = bundle_processor_perform_agent_action(
bai->bundle_signaling_queue,
BP_SIGNAL_AGENT_REGISTER,
agent
);
if (agent_no == AGENT_NO_INVALID) {
LOG_ERROR("SQLiteAgent: Failed to register with the bundle processor");
free(params);
return UD3TN_FAIL;
}
params->registered_agent_no = agent_no;
return UD3TN_OK;
}