Skip to content
This repository has been archived by the owner on Mar 3, 2020. It is now read-only.

Commit

Permalink
KEP-508 secure the whitelist functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
rnistuk committed Sep 3, 2018
1 parent 68a6d12 commit c6841b2
Show file tree
Hide file tree
Showing 18 changed files with 652 additions and 44 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ find_package(

if(Boost_FOUND)
message(STATUS "Boost: ${Boost_INCLUDE_DIRS}")

message(STATUS "***boost root ${BOOST_ROOT}")
include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
endif()

Expand Down
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ The configuration file is a JSON format file, as seen in the following example:
"logfile_dir" : "logs/",
"logfile_rotation_size" : "64K",
"logfile_max_size" : "640K",
"debug_logging" : false
"debug_logging" : false,
"security_enabled" : false
}

where the properties are:
Expand All @@ -167,6 +168,7 @@ where the properties are:
- "logfile_rotation_size" - approximate size of log file must be before rotation (default: 64K)
- "max_storage" - the approximate maximum limit for the storage that SwarmDB will use in the current instance (default: 2G)
- "uuid" - the universally unique identifier that this instance of SwarmDB will use to uniquely identify itself.
- "security_enabled" - set this to true to enable blacklisting and uuid signature verification

All size entries use the same notation as storage: B, K, M, G & T or none
(bytes)
Expand Down Expand Up @@ -612,15 +614,17 @@ following JSON objects:
"http_port":<HTTPPORT>,
"name":"<NODE-NAME>",
"port":<PORT>,
"uuid":"<UUID>"
"uuid":"<UUID>",
"signature" : ["<signature line>", ..., "<last signature line>"]
}
}
}
The "name" object, <NODE-NAME>, can be a human readable name for the node, "Fluffy" for example.
The "uuid" object must be a universally unique identifer that uniquely identifies the node within the swarm, or any
other swarm. This value can be generated online at a site like: https://www.uuidgenerator.net/
The "signature" object is a signature string associated with your node's UUID provided by a Bluzelle representative.
Remove an existing peer:
{
Expand All @@ -634,21 +638,30 @@ following JSON objects:
Given a swarm of nodes, a new node can be added via the command line with a
WebSocket client such as wscat (https://www.npmjs.com/package/wscat).
If the swarm has security enabled, before a new node can participate in a swarm
it must be validated by the leader against a cryptographic signature. You can
obtain this signature by providing the nodes' UUID to a Bluzelle representative
who will cryptographically sign the UUID and send you a signature file whose
contents must be included in the add_peer command to be sent to the swarm leader.
Start the node that you want to add to the swarm, remember that the local peers
list must include the information for the local node for your node to be able
to start. When your node does start, it will not be able to participate in the
swarm.
swarm until you add it to the swarm.
Create your add_peer JSON object, and use wscat to send it to the swarm leader:
Create your add_peer JSON object, and use wscat to send it to the swarm leader,
note that the signature object is only required for swarms whose nodes have set
the security_enabled object to true in thier config files:
$ wscat -c http://<leader-address>:<port>
connected (press CTRL+C to quit)
>{"bzn-api":"raft","cmd":"add_peer","data":{"peer":{"host":"104.25.178.61","http_port":84,"name":"peer3","port":49154,"uuid":"7dda1fcb-d494-4fc1-8645-a14056d13afd"}}}
>{"bzn-api":"raft","cmd":"add_peer","data":{"peer":{"host":"104.25.178.61","http_port":84,"name":"peer3","port":49154,"uuid":"7dda1fcb-d494-4fc1-8645-a14056d13afd","signature":"Dprtbr<...>4vk="}}}
>
disconnected
$
the new node will now start participating in the swarm.
the leader will validate the new node, and if successful, add the new node
to the swarm.
To remove the node, create a remove_peer JSON object, and use wscat to send it
to the swarm leader:
Expand Down
15 changes: 15 additions & 0 deletions bootstrap/bootstrap_peers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.


#include <utils/blacklist.hpp>
#include <utils/crypto.hpp>
#include <utils/http_get.hpp>
#include <bootstrap/bootstrap_peers.hpp>
#include <fstream>
Expand Down Expand Up @@ -103,6 +106,18 @@ bootstrap_peers::initialize_peer_list(const Json::Value& root, bzn::peers_list_t
continue;
}

if (this->is_security_enabled())
{
// At this point we cannot validate uuid's as we do not have
// signatures for all of them, so we simply ignore blacklisted
// uuids
if(bzn::utils::blacklist::is_blacklisted(uuid))
{
LOG(warning) << "Ignoring blacklisted node with uuid: [" << uuid << "]";
continue;
}
}

this->peer_addresses.emplace(host, port, http_port, name, uuid);

LOG(trace) << "Found peer " << host << ":" << port << " (" << name << ")";
Expand Down
6 changes: 6 additions & 0 deletions bootstrap/bootstrap_peers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ namespace bzn
class bootstrap_peers final : public bzn::bootstrap_peers_base
{
public:
explicit bootstrap_peers(bool security_enabled=false) : security_enabled(security_enabled) {}

bool fetch_peers_from_file(const std::string& filename) override;

bool fetch_peers_from_url(const std::string& url) override;
Expand All @@ -36,6 +38,10 @@ namespace bzn
bzn::peers_list_t peer_addresses;

size_t initialize_peer_list(const Json::Value& root, bzn::peers_list_t& peer_addresses);

bool is_security_enabled() { return this->security_enabled; }

bool security_enabled{false};
};

} // namespace bzn
6 changes: 3 additions & 3 deletions options/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ namespace
const std::string PBFT_ENABLED_KEY = "use_pbft";
const std::string STATE_DIR_KEY = "state_dir";
const std::string WS_IDLE_TIMEOUT_KEY = "ws_idle_timeout";
const std::string ENABLE_WHITELIST_KEY = "enable_whitelist";
const std::string SECURITY_ENABLED_KEY = "security_enabled";

// this is 10k error strings in a vector, which is pessimistically 10MB, which is small enough that no one should mind
const size_t DEFAULT_AUDIT_MEM_SIZE = 10000;
Expand Down Expand Up @@ -485,7 +485,7 @@ options::get_http_port() const


bool
options::whitelist_enabled() const
options::security_enabled() const
{
return this->config_data.isMember(ENABLE_WHITELIST_KEY) && this->config_data[ENABLE_WHITELIST_KEY].asBool();
return this->config_data.isMember(SECURITY_ENABLED_KEY) && this->config_data[SECURITY_ENABLED_KEY].asBool();
}
2 changes: 1 addition & 1 deletion options/options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ namespace bzn

uint16_t get_http_port() const override;

bool whitelist_enabled() const override;
bool security_enabled() const override;

private:
bool parse(int argc, const char* argv[]);
Expand Down
4 changes: 2 additions & 2 deletions options/options_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ namespace bzn

/**
* Temporary toggle for the whitelist while in QA. Defaults to false.
* @return boolean
* @return boolean if the security_enabled object is set to true. Default is false.
*/
virtual bool whitelist_enabled() const = 0;
virtual bool security_enabled() const = 0;
};
} // bzn
12 changes: 6 additions & 6 deletions options/test/options_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ TEST_F(options_file_test, test_that_loading_of_default_config_file)
EXPECT_EQ(size_t(2097152), options.get_logfile_rotation_size());
EXPECT_EQ(".", options.get_logfile_dir());
EXPECT_EQ(uint16_t(80), options.get_http_port());
EXPECT_FALSE(options.whitelist_enabled());
EXPECT_FALSE(options.security_enabled());

// defaults..
{
Expand Down Expand Up @@ -177,18 +177,18 @@ TEST_F(options_file_test, test_enable_whitlelist_temporary)
bzn::message json;
config_text_to_json(json);
{
json["enable_whitelist"] = false;
json["security_enabled"] = false;
this->save_options_file(json.toStyledString());
bzn::options options;
options.parse_command_line(1, NO_ARGS);
EXPECT_FALSE(options.whitelist_enabled());
EXPECT_FALSE(options.security_enabled());
}
{
json["enable_whitelist"] = true;
json["security_enabled"] = true;
this->save_options_file(json.toStyledString());
bzn::options options;
options.parse_command_line(1, NO_ARGS);
EXPECT_TRUE(options.whitelist_enabled());
EXPECT_TRUE(options.security_enabled());
}
}

Expand All @@ -211,5 +211,5 @@ TEST_F(options_file_test, test_that_command_line_options_work)
EXPECT_EQ(size_t(2097152), options.get_logfile_rotation_size());
EXPECT_EQ(".", options.get_logfile_dir());
EXPECT_EQ(uint16_t(80), options.get_http_port());
EXPECT_FALSE(options.whitelist_enabled());
EXPECT_FALSE(options.security_enabled());
}
27 changes: 23 additions & 4 deletions raft/raft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
#include <algorithm>
#include <boost/filesystem.hpp>
#include <proto/bluzelle.pb.h>
#include <utils/is_whitelist_member.hpp>
#include <utils/crypto.hpp>
#include <utils/blacklist.hpp>

namespace
{
Expand Down Expand Up @@ -458,10 +459,28 @@ raft::handle_add_peer(std::shared_ptr<bzn::session_base> session, const bzn::mes
return;
}

if(this->whitelist_enabled && !is_whitelist_member(uuid))
if (this->whitelist_enabled)
{
this->send_session_error_message(session, ERROR_PEER_NOT_WHITELISTED);
return;
// uuid's are signed as text files, so we must append the end of line character
const auto uuid{this->get_uuid() + "\x0a"};
if (!peer.isMember("signature") || peer["signature"].asString().empty())
{
this->send_session_error_message(session, ERROR_INVALID_SIGNATURE);
return;
}

const auto signature{peer["signature"].asString()};
if(!bzn::utils::crypto::verify_signature(bzn::utils::crypto::retrieve_bluzelle_public_key_from_contract(), signature, uuid))
{
this->send_session_error_message(session, ERROR_UNABLE_TO_VALIDATE_UUID);
return;
}

if (utils::blacklist::is_blacklisted(this->get_uuid()))
{
this->send_session_error_message(session, ERROR_PEER_HAS_BEEN_BLACKLISTED);
return;
}
}

this->peer_match_index[peer["uuid"].asString()] = 1;
Expand Down
4 changes: 3 additions & 1 deletion raft/raft.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ namespace
const std::string ERROR_INVALID_UUID = "ERROR_INVALID_UUID";
const std::string ERROR_PEER_NOT_FOUND = "ERROR_PEER_NOT_FOUND";
const std::string ERROR_UNABLE_TO_CREATE_LOG_FILE_FOR_WRITING = "Unable to open log file for writing: ";
const std::string ERROR_PEER_NOT_WHITELISTED = "ERROR_PEER_NOT_WHITELISTED";
const std::string ERROR_PEER_HAS_BEEN_BLACKLISTED = "ERROR_PEER_HAS_BEEN_BLACKLISTED";
const std::string ERROR_INVALID_SIGNATURE = "ERROR_INVALID_SIGNATURE";
const std::string ERROR_UNABLE_TO_VALIDATE_UUID ="ERROR_UNABLE_TO_VALIDATE_UUID";
}


Expand Down
91 changes: 91 additions & 0 deletions scripts/sign_uuid.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env bash



usage()
{
echo "usage: sign_uuid -k path/to/private.pem -u <uuid_of_node>"
echo "The output will be signature.txt which can be provided to the node owner."
}


write_uuid_to_file()
{
uuid_text=$1
uuid_file_name=$2

echo "Writing UUID($uuid_text) to $uuid_file_name"
echo $uuid_text > ${uuid_file_name}
}


verify_input_files()
{
private_key_file_name=$1
uuid_file_name=$2

if [ -f $private_key_file_name ]; then
echo "Found the private key file: $private_key_file_name"
else
echo "Exiting - Could not find the private key file: $private_key_file_name"
exit
fi

if [ -f $uuid_file_name ]; then
echo "Found the UUID file: $uuid_file_name"
else
echo "Exiting - Could not find the UUID file: $uuid_file_name"
exit
fi
}


use_private_pem_to_sign_uuid()
{
private_key_file_name=$1
uuid_file_name=$2
temp_sha256_binary_file=/tmp/sign.sha256

openssl dgst -sha256 -sign ${private_key_file_name} -out ${temp_sha256_binary_file} ${uuid_file_name}
if [ $? -ne 0 ]; then
echo "*** Exiting - Unable to create the binary signature file"
exit
else
echo "Successfully created binary signature file.."
fi

openssl base64 -in ${temp_sha256_binary_file} -out signature.txt
if [ $? -ne 0 ]; then
echo "*** Exiting - Unable to base64 encode the binary signature file"
exit
else
echo "The signature has been written to signature.txt"
fi
}


############
### MAIN ###
private_key_file_name=
uuid_file_name=/tmp/uuid.txt

while [ "$1" != "" ]; do
case $1 in
-k | --key ) shift
private_key_file_name=$1
;;
-u | --uuid ) shift
write_uuid_to_file $1 $uuid_file_name
;;
-h | --help ) usage
exit
;;
* ) usage
exit 1
esac
shift
done

verify_input_files $private_key_file_name $uuid_file_name
use_private_pem_to_sign_uuid $private_key_file_name $uuid_file_name
exit 0
2 changes: 1 addition & 1 deletion swarm/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ main(int argc, const char* argv[])
return 0;
}

bzn::bootstrap_peers peers;
bzn::bootstrap_peers peers(options.security_enabled());
if(!init_peers(peers, options.get_bootstrap_peers_file(), options.get_bootstrap_peers_url()))
throw std::runtime_error("Bootstrap peers initialization failed.");

Expand Down
9 changes: 5 additions & 4 deletions utils/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
add_library(utils STATIC
http_get.cpp
http_get.hpp
is_whitelist_member.cpp
is_whitelist_member.hpp
blacklist.cpp
blacklist.hpp
make_endpoint.hpp
make_endpoint.cpp
)
crypto.cpp
crypto.hpp)

target_link_libraries(utils ${CURL_LIBRARIES} ${JSONCPP_LIBRARIES})
target_link_libraries(utils ${CURL_LIBRARIES} ${JSONCPP_LIBRARIES} OpenSSL::SSL)

add_dependencies(utils jsoncpp)
target_include_directories(utils PRIVATE ${JSONCPP_INCLUDE_DIRS})
Expand Down
5 changes: 3 additions & 2 deletions utils/is_whitelist_member.cpp → utils/blacklist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ namespace
}


namespace bzn
namespace bzn::utils::blacklist
{
bool is_whitelist_member(const bzn::uuid_t& raw_uuid, const std::string& url)
bool
is_blacklisted(const bzn::uuid_t& raw_uuid, const std::string& url)
{
// We get the uuid in the following format:
// "9dc2f619-2e77-49f7-9b20-5b55fd87ea44",
Expand Down
Loading

0 comments on commit c6841b2

Please sign in to comment.