Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev/ssl tls pki #420

Open
wants to merge 62 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
35f0ead
feat: external tls
huerni Oct 15, 2024
3f179e6
feat: Separate the CraneCtldForCrane gRPC service and connect Crane t…
huerni Oct 16, 2024
8a4fbd5
feat: Separate the CraneCtldForCfored gRPC service and connect Cfored…
huerni Oct 17, 2024
1bb6759
feat: Move the SubmitTaskToScheduler function to TaskScheduler.
huerni Oct 17, 2024
1756234
feat: update config.yaml
huerni Oct 21, 2024
acb1a60
feat: jwt util
huerni Oct 31, 2024
0607191
feat: jwt config
huerni Oct 31, 2024
1a6b4a4
feat: User adds password information.
huerni Nov 1, 2024
0cc8127
refactor: jwt-cpp uses in-project json dependency
huerni Nov 14, 2024
ab44844
feat: token
huerni Nov 14, 2024
d420808
refactor
huerni Nov 15, 2024
8b372b6
refactor
huerni Nov 15, 2024
8c9112d
feat: login
huerni Nov 15, 2024
3dc9387
feat: login allowlist
huerni Nov 15, 2024
6b8653b
fix: The backend retrieves the token with control characters present.
huerni Nov 18, 2024
9c20e43
refactor: config
huerni Nov 18, 2024
56f8ce4
feat: grpc shutdown
huerni Nov 19, 2024
2afefbb
refactor
huerni Nov 19, 2024
655ba67
feat: refactor grpc server
huerni Nov 21, 2024
2f1c5d0
refactor: login
huerni Nov 21, 2024
a3f6fa0
refactor
huerni Dec 3, 2024
b0f6860
feat: UID is extracted from the token.
huerni Dec 4, 2024
41aec59
refactor
huerni Dec 4, 2024
3f75aa0
refactor
huerni Dec 4, 2024
e516e7a
feat: Add Vault dependencies and Vault encapsulation.
huerni Dec 25, 2024
cad24fa
feat: vault config
huerni Dec 26, 2024
b1924d0
feat: SignUserCertificate grpc interface
huerni Dec 26, 2024
47e6791
feat: vault client auto init external
huerni Dec 30, 2024
b9e1676
fix: cert content empty
huerni Dec 30, 2024
b44f5d6
feat: add permissions with save file
huerni Dec 30, 2024
ce1ac85
refactor
huerni Dec 30, 2024
a192f72
merge master
huerni Dec 30, 2024
05a756a
feat: user database add serial number
huerni Dec 31, 2024
f94f175
feat: revoke cert
huerni Dec 31, 2024
084c721
feat: extract uid from cert
huerni Dec 31, 2024
86f828d
feat: use uid from cert
huerni Dec 31, 2024
1f23cc0
feat: delete jwt
huerni Dec 31, 2024
cf97f98
refactor
huerni Dec 31, 2024
f70098d
feat: task interface use uid from cert
huerni Jan 2, 2025
aac7cee
feat: Login node restriction
huerni Jan 2, 2025
6838d73
fix: Login node restriction
huerni Jan 3, 2025
c29ec62
feat: revoke user Credential
huerni Jan 3, 2025
f073134
feat: vault health
huerni Jan 3, 2025
85ff080
fix: When revoked is empty, a 404 error is returned, causing the requ…
huerni Jan 6, 2025
73f4eb7
refactor config
huerni Jan 6, 2025
03fffee
refactor vault client
huerni Jan 6, 2025
18467ec
refactor
huerni Jan 6, 2025
5dcee26
refactor domainsuffix config
huerni Jan 6, 2025
00c4538
feat: SignServer
huerni Jan 6, 2025
3bad1f7
fix: Add force to handle mismatches between the database and Vault.
huerni Jan 7, 2025
726b5d6
fix
huerni Jan 7, 2025
6e9ad7b
feat: add reset multi op
huerni Jan 7, 2025
9d84d2a
refactor
huerni Jan 8, 2025
1995a12
feat: add grpc::UNAUTHENTICATED errcode
huerni Jan 8, 2025
c96dd94
feat: Use parallel containers to improve performance.
huerni Jan 8, 2025
14cd67b
refactor set_external_certificate
huerni Feb 12, 2025
b2c7187
feat: Distinguish between secure and public interfaces
huerni Feb 13, 2025
90da9c1
fix: delete user, the cert not revoke
huerni Feb 13, 2025
c3689dc
fix: Revoking a non-existent certificate does not throw an error.
huerni Feb 17, 2025
fee150f
fix: revoke empty serial_number
huerni Feb 17, 2025
5c98d94
refactor
huerni Feb 20, 2025
dbf59f2
feat: add log
huerni Feb 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dependencies/cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_subdirectory(ranges-v3)
add_subdirectory(backward-cpp)
add_subdirectory(fpm)
add_subdirectory(parallel-hashmap)
add_subdirectory(libvault)


include(${CMAKE_SOURCE_DIR}/CMakeModule/SuppressHeaderWarning.cmake)
Expand Down
36 changes: 36 additions & 0 deletions dependencies/cmake/libvault/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
include(FetchContent)

set(LIBVAULT_SRC_URL "https://github.com/abedra/libvault/archive/refs/tags/0.61.0.tar.gz")
# set(CURL_SRC_URL "https://github.com/curl/curl/releases/download/curl-8_11_1/curl-8.11.1.tar.xz")


# FetchContent_Declare(curl
# URL ${CURL_SRC_URL}
# URL_HASH SHA256=c7ca7db48b0909743eaef34250da02c19bc61d4f1dcedd6603f109409536ab56
# INACTIVITY_TIMEOUT 5
# )

find_package(PkgConfig REQUIRED)
pkg_check_modules(libcurl REQUIRED IMPORTED_TARGET libcurl)

if (libcurl_FOUND)
message(STATUS "Found libcurl ${libcurl_VERSION} in system using pkg-config.")
else ()
message(FATAL_ERROR "libcurl in system is not found using pkg-config.")
endif()

add_library(libcurl INTERFACE)
target_link_libraries(libcurl INTERFACE PkgConfig::libcurl)
target_include_directories(libcurl INTERFACE ${libcurl_INCLUDE_DIRS})

set(LIBCURL_BUILD_PRODUCTS ${libcurl_LIBRARY_DIRS}/libcurl.so CACHE STRING "Path to libcurl library" FORCE)
message(STATUS "Using libcurl from system at ${LIBCURL_BUILD_PRODUCTS}.")

FetchContent_Declare(libvault
URL ${LIBVAULT_SRC_URL}
URL_HASH SHA256=9718ec91157daeb59a47b77447328900d4b9020d0a370d06a2ec012c7017ff78
INACTIVITY_TIMEOUT 5
)

set(ENABLE_TEST OFF)
FetchContent_MakeAvailable(libvault)
29 changes: 24 additions & 5 deletions etc/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,36 @@ DbConfigPath: /etc/crane/database.yaml
CraneBaseDir: /var/crane/

# Tls settings
UseTls: false
ServerCertFilePath: /etc/crane/server.crt
ServerKeyFilePath: /etc/crane/server.key
CaCertFilePath: /etc/crane/ca.crt
DomainSuffix: riley.local
UseTls: true
SSL:
CranectldInternalCertFilePath: /etc/crane/tls/internal.pem
CranectldInternalKeyFilePath: /etc/crane/tls/internal.key
CranedCertFilePath: /etc/crane/tls/craned.pem
CranedKeyFilePath: /etc/crane/tls/craned.key
CforedCertFilePath: /etc/crane/tls/cfored.pem
CforedKeyFilePath: /etc/crane/tls/cfored.key
InternalCaFilePath: /etc/crane/tls/internal_ca.pem

Vault:
Addr: 127.0.0.1
Port: 8200
TokenPath: /etc/crane/vault_token.txt # Token cannot be leaked
Tls: false
# Nodes: "cn[15-18]"
ExternalCertFilePath: /etc/crane/pki/external.pem
ExternalKeyFilePath: /etc/crane/pki/external.key
ExternalCaFilePath: /etc/crane/pki/external_ca.pem

DomainSuffix: crane.com

# Ctld settings
# the listening address of control machine
CraneCtldListenAddr: 0.0.0.0
# the port of control machine to listen
CraneCtldListenPort: 10011
CraneCtldForCranedListenPort: 10013
CraneCtldForCforedListenPort: 10014
CraneCtldPlainListenPort: 10015
# debug level of cranectld
CraneCtldDebugLevel: trace
# file path of cranectld log file (relative to CraneBaseDir)
Expand Down
60 changes: 48 additions & 12 deletions protos/Crane.proto
Original file line number Diff line number Diff line change
Expand Up @@ -745,17 +745,31 @@ message StreamTaskIOReply {
}
}

// Todo: Divide service into two parts: one for Craned and one for Crun
// We need to distinguish the message sender
// and have some kind of authentication
service CraneCtld {
/* RPCs called from Craned */
rpc TaskStatusChange(TaskStatusChangeRequest) returns (TaskStatusChangeReply);
rpc CranedRegister(CranedRegisterRequest) returns (CranedRegisterReply);
message SignUserCertificateRequest {
uint32 uid = 1;
string csr_content = 2;
string alt_names = 3; // "localhost, *.domainSuffix"
}

/* RPCs called from Cfored */
rpc CforedStream(stream StreamCforedRequest) returns(stream StreamCtldReply);
message SignUserCertificateResponse {
bool ok = 1;
string certificate = 2;
string external_certificate = 3;
ErrCode reason = 4;
}

message ResetUserCredentialRequest {
repeated string user_list = 1;
bool is_force = 2;
}

message ResetUserCredentialReply {
bool ok = 1;
ErrCode reason = 2;
}

// The service interface performs encryption and authentication.
service CraneCtldSecure {
/* RPCs called from ccancel */
rpc CancelTask(CancelTaskRequest) returns (CancelTaskReply);

Expand All @@ -764,8 +778,6 @@ service CraneCtld {
rpc SubmitBatchTasks(SubmitBatchTasksRequest) returns (SubmitBatchTasksReply);

/* PRCs called from ccontrol */
rpc QueryCranedInfo(QueryCranedInfoRequest) returns (QueryCranedInfoReply);
rpc QueryPartitionInfo(QueryPartitionInfoRequest) returns (QueryPartitionInfoReply);
rpc ModifyTask(ModifyTaskRequest) returns (ModifyTaskReply);
rpc ModifyNode(ModifyCranedStateRequest) returns (ModifyCranedStateReply);

Expand All @@ -785,16 +797,40 @@ service CraneCtld {
rpc ModifyAccount(ModifyAccountRequest) returns (ModifyAccountReply);
rpc ModifyUser(ModifyUserRequest) returns (ModifyUserReply);
rpc ModifyQos(ModifyQosRequest) returns (ModifyQosReply);

rpc BlockAccountOrUser(BlockAccountOrUserRequest) returns (BlockAccountOrUserReply);

rpc ResetUserCredential(ResetUserCredentialRequest) returns (ResetUserCredentialReply);
}

// The service is completely open.
service CraneCtldPlain {
/* PRCs called from ccontrol */
rpc QueryCranedInfo(QueryCranedInfoRequest) returns (QueryCranedInfoReply);
rpc QueryPartitionInfo(QueryPartitionInfoRequest) returns (QueryPartitionInfoReply);

/* RPCs called from cinfo */
rpc QueryClusterInfo(QueryClusterInfoRequest) returns (QueryClusterInfoReply);

/* common RPCs */
rpc QueryTasksInfo(QueryTasksInfoRequest) returns (QueryTasksInfoReply);

/* RPCS called from PKI request */
rpc SignUserCertificate(SignUserCertificateRequest) returns (SignUserCertificateResponse);
}

service CraneCtldForCraned {
/* RPCs called from Craned */
rpc TaskStatusChange(TaskStatusChangeRequest) returns (TaskStatusChangeReply);
rpc CranedRegister(CranedRegisterRequest) returns (CranedRegisterReply);
}

service CraneCtldForCfored {
/* RPCs called from Cfored */
rpc CforedStream(stream StreamCforedRequest) returns(stream StreamCtldReply);
}


service Craned {
/* ----------------------------------- Called from CraneCtld ---------------------------------------------------- */
rpc ExecuteTask(ExecuteTasksRequest) returns(ExecuteTasksReply);
Expand Down
66 changes: 35 additions & 31 deletions protos/PublicDefs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ message TaskToCtld {
string extra_attr = 23;

string cmd_line = 31;
string cwd = 32; // Current working directory
string cwd = 32; // Current working directory
map<string, string> env = 33;

string excludes = 34;
Expand Down Expand Up @@ -183,7 +183,7 @@ message TaskToD {
// If this task is PENDING, start_time is either not set (default constructed)
// or an estimated start time.
// If this task is RUNNING, start_time is the actual starting time.
google.protobuf.Timestamp start_time = 5; // Currently Only used in CraneCtld
google.protobuf.Timestamp start_time = 5; // Currently Only used in CraneCtld
google.protobuf.Duration time_limit = 6;

string partition = 8;
Expand Down Expand Up @@ -342,35 +342,35 @@ enum ErrCode {
ERR_USER_ACCOUNT_MISMATCH = 10008; // User does not belong to the account
ERR_NO_ACCOUNT_SPECIFIED = 10009;

ERR_INVALID_ACCOUNT = 10010; // Invalid account
ERR_DUPLICATE_ACCOUNT = 10011; // Duplicate account insertion
ERR_INVALID_PARENTACCOUNT = 10012; // Invalid parent account
ERR_DELETE_ACCOUNT = 10013; // Account has child nodes

ERR_INVALID_PARTITION = 10014; // Invalid partition, partition does not exist
ERR_ALLOWED_PARTITION = 10015; // Account/user does not include this partition
ERR_DUPLICATE_PARTITION = 10016; // Account/user duplicate insertion
ERR_PARENT_ALLOWED_PARTITION = 10017; // Parent account does not include this partition
ERR_USER_EMPTY_PARTITION = 10018; // Cannot add QoS when user has no partition
ERR_CHILD_HAS_PARTITION = 10019; // Partition '{}' is used by some descendant node of the account '{}'. Ignoring this constraint with forced operation.

ERR_INVALID_QOS = 10020; // Invalid QoS, QoS does not exist
ERR_DB_DUPLICATE_QOS = 10021; // Duplicate QoS insertion in the database.
ERR_DELETE_QOS = 10022; // QoS reference count is not zero.
ERR_CONVERT_TO_INTERGER = 10023; // String to integer conversion failed
ERR_TIME_LIMIT = 10024; // Invalid time value
ERR_ALLOWED_QOS = 10025; // Account/user does not include this QoS.
ERR_DUPLICATE_QOS = 10026; // Account/user duplicate insertion.
ERR_PARENT_ALLOWED_QOS = 10027; // Parent account does not include this QoS.
ERR_SET_ALLOWED_QOS = 10028; // QoS '{}' is the default QoS of partition '{}', but not found in the new QoS list.
ERR_ALLOWED_DEFAULT_QOS = 10029; // Default QoS is not in the allowed QoS list
ERR_DUPLICATE_DEFAULT_QOS = 10030; // Duplicate default QoS setting
ERR_CHILD_HAS_DEFAULT_QOS = 10031; // Someone is using QoS '{}' as default QoS. Ignoring this constraint with forced deletion, the deleted default QoS is randomly replaced with one of the remaining items in the QoS list.
ERR_SET_ACCOUNT_QOS = 10032; // QoS '{}' is used by some descendant node or itself of the account '{}'. Ignoring this constraint with forced operation.
ERR_SET_DEFAULT_QOS = 10033; // Qos '{}' not in allowed qos list or is already the default qos
ERR_IS_DEFAULT_QOS = 10034;

ERR_UPDATE_DATABASE = 10035; // Database update failed
ERR_INVALID_ACCOUNT = 10011;
ERR_DUPLICATE_ACCOUNT = 10012;
ERR_INVALID_PARENTACCOUNT = 10013;
ERR_DELETE_ACCOUNT = 10014;

ERR_INVALID_PARTITION = 10015;
ERR_ALLOWED_PARTITION = 10016;
ERR_DUPLICATE_PARTITION = 10017;
ERR_PARENT_ALLOWED_PARTITION = 10018;
ERR_USER_EMPTY_PARTITION = 10019;
ERR_CHILD_HAS_PARTITION = 10020;

ERR_INVALID_QOS = 10021;
ERR_DB_DUPLICATE_QOS = 10022;
ERR_DELETE_QOS = 10023;
ERR_CONVERT_TO_INTERGER = 10024;
ERR_TIME_LIMIT = 10025;
ERR_ALLOWED_QOS = 10026;
ERR_DUPLICATE_QOS = 10027;
ERR_PARENT_ALLOWED_QOS = 10028;
ERR_SET_ALLOWED_QOS = 10029;
ERR_ALLOWED_DEFAULT_QOS = 10030;
ERR_DUPLICATE_DEFAULT_QOS = 10031;
ERR_CHILD_HAS_DEFAULT_QOS = 10032;
ERR_SET_ACCOUNT_QOS = 10033;
ERR_SET_DEFAULT_QOS = 10034;
ERR_IS_DEFAULT_QOS = 10035;

ERR_UPDATE_DATABASE = 10036;

ERR_GENERIC_FAILURE = 10100;
ERR_NO_RESOURCE = 10101;
Expand All @@ -391,6 +391,10 @@ enum ErrCode {
ERR_PROTOBUF = 10116;
ERR_LIB_EVENT = 10117;
ERR_NO_AVAIL_NODE = 10118;

ERR_SIGN_CERTIFICATE = 10119;
ERR_DUPLICATE_CERTIFICATE = 10120;
ERR_REVOKE_CERTIFICATE = 10121;
}

enum EntityType {
Expand Down
100 changes: 100 additions & 0 deletions src/CraneCtld/AccountManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "AccountManager.h"

#include "AccountMetaContainer.h"
#include "crane/Logger.h"
#include "protos/PublicDefs.pb.h"
#include "range/v3/algorithm/contains.hpp"

Expand Down Expand Up @@ -1029,6 +1030,101 @@ AccountManager::CraneExpected<void> AccountManager::CheckIfUidHasPermOnUser(
return CheckIfUserHasPermOnUserNoLock_(*op_user, user, read_only_priv);
}

AccountManager::CraneExpected<std::string> AccountManager::SignUserCertificate(
uint32_t uid, const std::string& csr_content,
const std::string& alt_names) {
util::write_lock_guard user_guard(m_rw_user_mutex_);

auto user_result = GetUserInfoByUidNoLock_(uid);
if (!user_result) return std::unexpected(user_result.error());
const User* op_user = user_result.value();

// Verify whether the serial number already exists in the user database.
if (!op_user->serial_number.empty())
return std::unexpected(CraneErrCode::ERR_DUPLICATE_CERTIFICATE);

std::string common_name = std::format("{}.{}", uid, g_config.DomainSuffix);
auto sign_response =
g_vault_client->Sign(csr_content, common_name, alt_names);
if (!sign_response)
return std::unexpected(CraneErrCode::ERR_SIGN_CERTIFICATE);

// Save the serial number in the database.
mongocxx::client_session::with_transaction_cb callback =
[&](mongocxx::client_session* session) {
g_db_client->UpdateEntityOne(MongodbClient::EntityType::USER, "$set",
op_user->name, "serial_number",
sign_response->serial_number);
};

// Update to database
if (!g_db_client->CommitTransaction(callback)) {
return std::unexpected(CraneErrCode::ERR_UPDATE_DATABASE);
}

m_user_map_[op_user->name]->serial_number = sign_response->serial_number;

return sign_response->certificate;
}

AccountManager::CraneExpected<void> AccountManager::ResetUserCertificate(
uint32_t uid, const std::vector<std::string>& user_list, bool force) {
util::write_lock_guard user_guard(m_rw_user_mutex_);
CraneExpected<void> result{};

auto user_result = GetUserInfoByUidNoLock_(uid);
if (!user_result) return std::unexpected(user_result.error());
const User* op_user = user_result.value();

result = CheckIfUserHasHigherPrivThan_(*op_user, User::None);
if (!result) return result;

std::vector<const User*> user_ptr_list;
if (user_list.empty()) {
for (const auto& [username, user] : m_user_map_) {
user_ptr_list.emplace_back(user.get());
}
} else {
for (const auto& username : user_list) {
const User* user = GetExistedUserInfoNoLock_(username);
if (!user) return std::unexpected(CraneErrCode::ERR_INVALID_USER);
user_ptr_list.emplace_back(user);
}
}

for (const auto& user : user_ptr_list) {
if (!user->serial_number.empty() &&
!g_vault_client->RevokeCert(user->serial_number))
if (!force) return std::unexpected(CraneErrCode::ERR_REVOKE_CERTIFICATE);
CRANE_DEBUG("Reset User {} Certificate {}", user->name,
user->serial_number);
}

// Save the serial number in the database.
mongocxx::client_session::with_transaction_cb callback =
[&](mongocxx::client_session* session) {
for (const auto& user : user_ptr_list) {
g_db_client->UpdateEntityOne(MongodbClient::EntityType::USER, "$set",
user->name, "serial_number", "");
}
};

// Update to database
if (!g_db_client->CommitTransaction(callback)) {
return std::unexpected(CraneErrCode::ERR_UPDATE_DATABASE);
}

for (const auto& user : user_ptr_list) {
m_user_map_[user->name]->serial_number = "";
}

return result;
}

/******************************************
* NOLOCK
******************************************/

AccountManager::CraneExpected<void>
AccountManager::CheckAddUserAllowedPartitionNoLock_(
const User* user, const Account* account, const std::string& partition) {
Expand Down Expand Up @@ -1764,6 +1860,10 @@ AccountManager::CraneExpected<void> AccountManager::DeleteUser_(
}
}

if (res_user.deleted && !res_user.serial_number.empty() &&
!g_vault_client->RevokeCert(res_user.serial_number))
return std::unexpected(CraneErrCode::ERR_REVOKE_CERTIFICATE);

mongocxx::client_session::with_transaction_cb callback =
[&](mongocxx::client_session* session) {
// delete form the parent accounts' users list
Expand Down
Loading
Loading