Skip to content

Commit

Permalink
[database] Make migration script between version 1 and 0
Browse files Browse the repository at this point in the history
Signed-off-by: Mykola Kobets <[email protected]>
  • Loading branch information
mykola-kobets-epam authored and al1img committed Feb 3, 2025
1 parent cf34891 commit ad37988
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 16 deletions.
34 changes: 28 additions & 6 deletions src/config/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <Poco/JSON/Object.h>
#include <Poco/JSON/Parser.h>

#include <aos/common/pkcs11/pkcs11.hpp>
#include <aos/common/tools/fs.hpp>
#include <utils/exception.hpp>
#include <utils/json.hpp>

Expand All @@ -31,12 +33,14 @@ constexpr auto cDefaultNodeIDPath = "/etc/machine-id";
* Static
**********************************************************************************************************************/

static Identifier ParseIdentifier(const aos::common::utils::CaseInsensitiveObjectWrapper& object)
namespace {

Identifier ParseIdentifier(const aos::common::utils::CaseInsensitiveObjectWrapper& object)
{
return Identifier {object.GetValue<std::string>("plugin"), object.Get("params")};
}

static ModuleConfig ParseModuleConfig(const aos::common::utils::CaseInsensitiveObjectWrapper& object)
ModuleConfig ParseModuleConfig(const aos::common::utils::CaseInsensitiveObjectWrapper& object)
{
return ModuleConfig {
object.GetValue<std::string>("id"),
Expand All @@ -54,7 +58,7 @@ static ModuleConfig ParseModuleConfig(const aos::common::utils::CaseInsensitiveO
};
}

static PartitionInfoConfig ParsePartitionInfoConfig(const aos::common::utils::CaseInsensitiveObjectWrapper& object)
PartitionInfoConfig ParsePartitionInfoConfig(const aos::common::utils::CaseInsensitiveObjectWrapper& object)
{
PartitionInfoConfig partitionInfoConfig {};

Expand All @@ -71,7 +75,7 @@ static PartitionInfoConfig ParsePartitionInfoConfig(const aos::common::utils::Ca
return partitionInfoConfig;
}

static NodeInfoConfig ParseNodeInfoConfig(const aos::common::utils::CaseInsensitiveObjectWrapper& object)
NodeInfoConfig ParseNodeInfoConfig(const aos::common::utils::CaseInsensitiveObjectWrapper& object)
{
NodeInfoConfig nodeInfoConfig {};

Expand Down Expand Up @@ -102,16 +106,33 @@ static NodeInfoConfig ParseNodeInfoConfig(const aos::common::utils::CaseInsensit
return nodeInfoConfig;
}

static MigrationConfig ParseMigrationConfig(const aos::common::utils::CaseInsensitiveObjectWrapper& migration)
MigrationConfig ParseMigrationConfig(
const aos::common::utils::CaseInsensitiveObjectWrapper& migration, const std::vector<ModuleConfig>& moduleConfigs)
{
MigrationConfig config {};

config.mMigrationPath = migration.GetValue<std::string>("migrationPath");
config.mMergedMigrationPath = migration.GetValue<std::string>("mergedMigrationPath");

for (const auto& moduleConfig : moduleConfigs) {
aos::common::utils::CaseInsensitiveObjectWrapper object(moduleConfig.mParams);

std::string pinPath = object.GetValue<std::string>("userPinPath");
aos::StaticString<aos::pkcs11::cPINLen> userPIN;

auto err = aos::FS::ReadFileToString(pinPath.c_str(), userPIN);
if (!err.IsNone()) {
continue;
}

config.mPathToPin[pinPath] = userPIN.CStr();
}

return config;
}

} // namespace

/***********************************************************************************************************************
* Public functions
**********************************************************************************************************************/
Expand Down Expand Up @@ -141,7 +162,6 @@ aos::RetWithError<Config> ParseConfig(const std::string& filename)
config.mCertStorage = object.GetValue<std::string>("certStorage");
config.mWorkingDir = object.GetValue<std::string>("workingDir");
config.mEnablePermissionsHandler = object.GetValue<bool>("enablePermissionsHandler");
config.mMigration = ParseMigrationConfig(object.GetObject("migration"));

config.mStartProvisioningCmdArgs = aos::common::utils::GetArrayValue<std::string>(object,
"startProvisioningCmdArgs", [](const Poco::Dynamic::Var& value) { return value.convert<std::string>(); });
Expand All @@ -161,6 +181,8 @@ aos::RetWithError<Config> ParseConfig(const std::string& filename)
aos::common::utils::CaseInsensitiveObjectWrapper(value.extract<Poco::JSON::Object::Ptr>()));
});

config.mMigration = ParseMigrationConfig(object.GetObject("migration"), config.mCertModules);

if (object.Has("identifier")) {
config.mIdentifier = ParseIdentifier(object.GetObject("identifier"));
}
Expand Down
5 changes: 3 additions & 2 deletions src/config/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ struct NodeInfoConfig {
* Migration configuration.
*/
struct MigrationConfig {
std::string mMigrationPath;
std::string mMergedMigrationPath;
std::string mMigrationPath;
std::string mMergedMigrationPath;
std::map<std::string, std::string> mPathToPin;
};

/*
Expand Down
34 changes: 33 additions & 1 deletion src/database/database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ aos::Error Database::Init(const std::string& workDir, const MigrationConfig& mig
CreateTables();

mMigration.emplace(*mSession, migration.mMigrationPath, migration.mMergedMigrationPath);
mMigration->MigrateToVersion(cVersion);

CreateMigrationData(migration);
mMigration->MigrateToVersion(GetVersion());
DropMigrationData();
} catch (const std::exception& e) {
return AOS_ERROR_WRAP(aos::common::utils::ToAosError(e));
}
Expand Down Expand Up @@ -252,6 +255,35 @@ aos::Error Database::RemoveNodeInfo(const aos::String& nodeID)
* Private
**********************************************************************************************************************/

int Database::GetVersion() const
{
return cVersion;
}

void Database::CreateMigrationData(const MigrationConfig& config)
{
DropMigrationData();

*mSession << "CREATE TABLE IF NOT EXISTS pins (path TEXT NOT NULL, value TEXT NOT NULL);", now;

std::string pin, path;
Poco::Data::Statement insert(*mSession);

insert << "INSERT INTO pins (path, value) VALUES(?, ?);", use(path), use(pin);

for (const auto& [key, value] : config.mPathToPin) {
path = key;
pin = value;

insert.execute();
}
}

void Database::DropMigrationData()
{
*mSession << "DROP TABLE IF EXISTS pins;", now;
}

void Database::CreateTables()
{
*mSession << "CREATE TABLE IF NOT EXISTS certificates ("
Expand Down
8 changes: 7 additions & 1 deletion src/database/database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,15 @@ class Database : public aos::iam::certhandler::StorageItf, public aos::iam::node
enum CertColumns { eType = 0, eIssuer, eSerial, eCertURL, eKeyURL, eNotAfter };
using CertInfo = Poco::Tuple<std::string, Poco::Data::BLOB, Poco::Data::BLOB, std::string, std::string, uint64_t>;

constexpr static int cVersion = 0;
constexpr static int cVersion = 1;
constexpr static auto cDBFileName = "iamanager.db";

// to be used in unit tests
virtual int GetVersion() const;

void CreateMigrationData(const MigrationConfig& config);
void DropMigrationData();

void CreateTables();
CertInfo ToAosCertInfo(const aos::String& certType, const aos::iam::certhandler::CertInfo& certInfo);
void FromAosCertInfo(const CertInfo& certInfo, aos::iam::certhandler::CertInfo& result);
Expand Down
Empty file.
6 changes: 6 additions & 0 deletions src/database/migration/1_update.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
UPDATE certificates
SET
keyURL = SUBSTR(keyURL, 1, INSTR(keyURL, 'pin-source=') - 1) ||
'pin-value=' || (SELECT value FROM pins WHERE path = SUBSTR(keyURL, INSTR(keyURL, 'pin-source=') + 11) LIMIT 1),
certURL = SUBSTR(certURL, 1, INSTR(certURL, 'pin-source=') - 1) ||
'pin-value=' || (SELECT value FROM pins WHERE path = SUBSTR(certURL, INSTR(certURL, 'pin-source=') + 11) LIMIT 1);
6 changes: 6 additions & 0 deletions src/database/migration/1_update.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
UPDATE certificates
SET
keyURL = SUBSTR(keyURL, 1, INSTR(keyURL, 'pin-value=') - 1) ||
'pin-source=' || (SELECT path FROM pins WHERE value = SUBSTR(keyURL, INSTR(keyURL, 'pin-value=') + 10) LIMIT 1),
certURL = SUBSTR(certURL, 1, INSTR(certURL, 'pin-value=') - 1) ||
'pin-source=' || (SELECT path FROM pins WHERE value = SUBSTR(certURL, INSTR(certURL, 'pin-value=') + 10) LIMIT 1);
174 changes: 168 additions & 6 deletions tests/database/database_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,61 @@ static aos::NodeInfo DefaultNodeInfo(const char* id = "node0")
return nodeInfo;
}

static void CreateSessionTable(Poco::Data::Session& session)
{
session << "CREATE TABLE IF NOT EXISTS certificates ("
"type TEXT NOT NULL,"
"issuer BLOB NOT NULL,"
"serial BLOB NOT NULL,"
"certURL TEXT,"
"keyURL TEXT,"
"notAfter TIMESTAMP,"
"PRIMARY KEY (issuer, serial));",
Poco::Data::Keywords::now;
}

void CreateVersionTable(Poco::Data::Session& session, int version)
{
session << "CREATE TABLE IF NOT EXISTS SchemaVersion (version INTEGER);", Poco::Data::Keywords::now;
session << "INSERT INTO SchemaVersion (version) VALUES(?);", Poco::Data::Keywords::use(version),
Poco::Data::Keywords::now;
}

static void AddCertificate(Poco::Data::Session& session, const std::string& type, const std::vector<uint8_t>& issuer,
const std::vector<uint8_t>& serial, const std::string& certURL, const std::string& keyURL)
{
using Poco::Data::Keywords::bind;
session << "INSERT INTO certificates (type, issuer, serial, certURL, keyURL, notAfter) "
"VALUES (?, ?, ?, ?, ?, ?)",
bind(type), bind(Poco::Data::BLOB {issuer.data(), issuer.size()}),
bind(Poco::Data::BLOB {serial.data(), serial.size()}), bind(certURL), bind(keyURL), bind(uint64_t(1000)),
Poco::Data::Keywords::now;
}

std::string GetMigrationSourceDir()
{
std::filesystem::path curFilePath(__FILE__);
std::filesystem::path migrationSourceDir = curFilePath.parent_path() / "../.." / "src/database/migration/";

return std::filesystem::canonical(migrationSourceDir).string();
}

template <typename T>
const aos::Array<T> ToArray(std::vector<T>& src)
{
return aos::Array<T>(src.data(), src.size());
}

class TestDatabase : public Database {
public:
void SetVersion(int version) { mVersion = version; }

private:
int GetVersion() const override { return mVersion; }

int mVersion = 1;
};

/***********************************************************************************************************************
* Suite
**********************************************************************************************************************/
Expand All @@ -85,10 +140,27 @@ class DatabaseTest : public Test {
protected:
void SetUp() override
{
// Preliminary clean up.
TearDown();

namespace fs = std::filesystem;

auto migrationSrc = GetMigrationSourceDir();
auto migrationDst = fs::current_path() / cMigrationPath;
auto workingDir = fs::current_path() / cWorkingDir;

mMigrationConfig.mMigrationPath = cMigrationPath;
mMigrationConfig.mMergedMigrationPath = cMergedMigrationPath;

std::filesystem::create_directories(cMigrationPath);
fs::create_directories(cMigrationPath);

mCMPinPath = workingDir / "cm.path.txt";
mSMPinPath = (workingDir / "sm.path.txt");

mMigrationConfig.mPathToPin[mCMPinPath] = "ca3b303c3c3f572e87c97a753cc7f5";
mMigrationConfig.mPathToPin[mSMPinPath] = "ca3b303c3c3f572e87c97a753cc7f6";

fs::copy(migrationSrc, migrationDst, fs::copy_options::recursive | fs::copy_options::overwrite_existing);
}

void TearDown() override { std::filesystem::remove_all(cWorkingDir); }
Expand All @@ -103,8 +175,10 @@ class DatabaseTest : public Test {
static constexpr auto cMigrationPath = "database/migration";
static constexpr auto cMergedMigrationPath = "database/merged-migration";

std::string mCMPinPath, mSMPinPath;

MigrationConfig mMigrationConfig;
Database mDB;
TestDatabase mDB;
};

/***********************************************************************************************************************
Expand Down Expand Up @@ -252,10 +326,6 @@ TEST_F(DatabaseTest, GetCertsInfo)
EXPECT_TRUE(certsInfoNotEnoughMemory[0] == certInfo || certsInfoNotEnoughMemory[0] == certInfo2);
}

/***********************************************************************************************************************
* Tests
**********************************************************************************************************************/

TEST_F(DatabaseTest, GetNodeInfo)
{
const auto& nodeInfo = DefaultNodeInfo();
Expand Down Expand Up @@ -325,3 +395,95 @@ TEST_F(DatabaseTest, RemoveNodeInfo)
ASSERT_TRUE(mDB.GetAllNodeIds(resultNodeIds).IsNone());
ASSERT_EQ(expectedNodeIds, resultNodeIds);
}

TEST_F(DatabaseTest, MigrateVer0To1)
{
// Create Version 0 db
std::vector<unsigned char> cCM = {0x1};
std::vector<unsigned char> cSM = {0x2};

constexpr auto cCMVer0URL = "pkcs11:token=aoscore;object=sm;id=%2C%38%6B%2F%64%1D%6A%5E%92%2E%74%55%51%5D%93%4F?"
"module-path=/usr/lib/softhsm/libsofthsm2.so&pin-value=ca3b303c3c3f572e87c97a753cc7f5";
constexpr auto cSMVer0URL = "pkcs11:token=aoscore;object=cm;id=%2A%AD%9F%7E%2A%33%15%1F%22%39%F1%57%F4%E8%CF%3A?"
"module-path=/usr/lib/softhsm/libsofthsm2.so&pin-value=ca3b303c3c3f572e87c97a753cc7f6";

auto cDbPath = std::filesystem::path(cWorkingDir) / "iamanager.db";
auto session = std::make_unique<Poco::Data::Session>("SQLite", cDbPath.c_str());

CreateSessionTable(*session);
CreateVersionTable(*session, 0);

AddCertificate(*session, "sm", cSM, cSM, cSMVer0URL, cSMVer0URL);
AddCertificate(*session, "cm", cCM, cCM, cCMVer0URL, cCMVer0URL);

session.reset();

// Migrate to Version1
mDB.SetVersion(1);
ASSERT_TRUE(mDB.Init(cWorkingDir, mMigrationConfig).IsNone());

// Check certificates
const std::string cCMVer1URL = "pkcs11:token=aoscore;object=sm;id=%2C%38%6B%2F%64%1D%6A%5E%92%2E%74%55%51%5D%93%4F?"
"module-path=/usr/lib/softhsm/libsofthsm2.so&pin-source="
+ mCMPinPath;
const std::string cSMVer1URL = "pkcs11:token=aoscore;object=cm;id=%2A%AD%9F%7E%2A%33%15%1F%22%39%F1%57%F4%E8%CF%3A?"
"module-path=/usr/lib/softhsm/libsofthsm2.so&pin-source="
+ mSMPinPath;

aos::iam::certhandler::CertInfo certInfo {};

ASSERT_TRUE(mDB.GetCertInfo(ToArray(cCM), ToArray(cCM), certInfo).IsNone());
EXPECT_EQ(certInfo.mCertURL.CStr(), cCMVer1URL);
EXPECT_EQ(certInfo.mKeyURL.CStr(), cCMVer1URL);

ASSERT_TRUE(mDB.GetCertInfo(ToArray(cSM), ToArray(cSM), certInfo).IsNone());
EXPECT_EQ(certInfo.mCertURL.CStr(), cSMVer1URL);
EXPECT_EQ(certInfo.mKeyURL.CStr(), cSMVer1URL);
}

TEST_F(DatabaseTest, MigrateVer1To0)
{
// Create Version 0 db
std::vector<unsigned char> cCM = {0x1};
std::vector<unsigned char> cSM = {0x2};

const std::string cCMVer1URL = "pkcs11:token=aoscore;object=sm;id=%2C%38%6B%2F%64%1D%6A%5E%92%2E%74%55%51%5D%93%4F?"
"module-path=/usr/lib/softhsm/libsofthsm2.so&pin-source="
+ mCMPinPath;
const std::string cSMVer1URL = "pkcs11:token=aoscore;object=cm;id=%2A%AD%9F%7E%2A%33%15%1F%22%39%F1%57%F4%E8%CF%3A?"
"module-path=/usr/lib/softhsm/libsofthsm2.so&pin-source="
+ mSMPinPath;

auto cDbPath = std::filesystem::path(cWorkingDir) / "iamanager.db";
auto session = std::make_unique<Poco::Data::Session>("SQLite", cDbPath.c_str());

CreateSessionTable(*session);
CreateVersionTable(*session, 1);

AddCertificate(*session, "sm", cSM, cSM, cSMVer1URL, cSMVer1URL);
AddCertificate(*session, "cm", cCM, cCM, cCMVer1URL, cCMVer1URL);

session.reset();

// Migrate to Version0
mDB.SetVersion(0);
ASSERT_TRUE(mDB.Init(cWorkingDir, mMigrationConfig).IsNone());

// Check certificates
const std::string cCMVer0URL
= "pkcs11:token=aoscore;object=sm;id=%2C%38%6B%2F%64%1D%6A%5E%92%2E%74%55%51%5D%93%4F?"
"module-path=/usr/lib/softhsm/libsofthsm2.so&pin-value=ca3b303c3c3f572e87c97a753cc7f5";
const std::string cSMVer0URL
= "pkcs11:token=aoscore;object=cm;id=%2A%AD%9F%7E%2A%33%15%1F%22%39%F1%57%F4%E8%CF%3A?"
"module-path=/usr/lib/softhsm/libsofthsm2.so&pin-value=ca3b303c3c3f572e87c97a753cc7f6";

aos::iam::certhandler::CertInfo certInfo {};

ASSERT_TRUE(mDB.GetCertInfo(ToArray(cCM), ToArray(cCM), certInfo).IsNone());
EXPECT_EQ(certInfo.mCertURL.CStr(), cCMVer0URL);
EXPECT_EQ(certInfo.mKeyURL.CStr(), cCMVer0URL);

ASSERT_TRUE(mDB.GetCertInfo(ToArray(cSM), ToArray(cSM), certInfo).IsNone());
EXPECT_EQ(certInfo.mCertURL.CStr(), cSMVer0URL);
EXPECT_EQ(certInfo.mKeyURL.CStr(), cSMVer0URL);
}

0 comments on commit ad37988

Please sign in to comment.