Skip to content
This repository was archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1243 from advancedtelematic/feat/OTA-2666/garage-…
Browse files Browse the repository at this point in the history
…tools-mutual-tls

Feat/ota 2666/garage tools mutual tls
  • Loading branch information
pattivacek authored Jul 29, 2019
2 parents 7645037 + 85deb96 commit f5668ae
Show file tree
Hide file tree
Showing 28 changed files with 180 additions and 203 deletions.
3 changes: 2 additions & 1 deletion docker/Dockerfile.debian.testing
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ RUN apt-get update && apt-get -y install --no-install-suggests --no-install-reco
softhsm2 \
sqlite3 \
valgrind \
wget
wget \
zip

RUN ln -s clang-6.0 /usr/bin/clang && \
ln -s clang++-6.0 /usr/bin/clang++
Expand Down
6 changes: 2 additions & 4 deletions docs/provisioning-methods-and-credentialszip.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ The following files are present in credentials.zip:
[options="header"]
|======================
| Filename in zip | Purpose | Used by
| treehub.json | Location and authentications for treehub and Uptane repo | garage-sign, garage-push
| client.crt | Certificate for TLS client authentication | garage-push
| client.key | Private key for TLS client authentication | garage-push
| root.crt | Root CA for TLS client authentication | garage-push
| treehub.json | URL and OAuth2 authentication for treehub and Uptane repo | garage-sign, garage-push, garage-deploy
| client_auth.p12 | TLS client credentials for authentication with treehub | garage-push, garage-deploy
| autoprov_credentials.p12 | TLS client credentials that are required when provisioning devices with shared credentials | aktualizr, aktualizr-cert-provider
| autoprov.url | URL for provisioning server | aktualizr, aktualizr-cert-provider
| root.json | Initial Uptane root.json (for secure bootstrapping) | garage-sign
Expand Down
7 changes: 6 additions & 1 deletion src/libaktualizr/bootstrap/bootstrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include "utilities/utils.h"

Bootstrap::Bootstrap(const boost::filesystem::path& provision_path, const std::string& provision_password)
: ca(""), cert(""), pkey("") {
: ca_(""), cert_(""), pkey_("") {
if (provision_path.empty()) {
LOG_ERROR << "Provision path is empty!";
throw std::runtime_error("Unable to parse bootstrap credentials");
Expand All @@ -26,6 +26,11 @@ Bootstrap::Bootstrap(const boost::filesystem::path& provision_path, const std::s
throw std::runtime_error("Unable to parse bootstrap credentials");
}

readTlsP12(p12_str, provision_password, pkey_, cert_, ca_);
}

void Bootstrap::readTlsP12(const std::string& p12_str, const std::string& provision_password, std::string& pkey,
std::string& cert, std::string& ca) {
StructGuard<BIO> reg_p12(BIO_new_mem_buf(p12_str.c_str(), static_cast<int>(p12_str.size())), BIO_vfree);
if (reg_p12 == nullptr) {
LOG_ERROR << "Unable to open P12 archive: " << std::strerror(errno);
Expand Down
14 changes: 8 additions & 6 deletions src/libaktualizr/bootstrap/bootstrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ class Bootstrap {
public:
Bootstrap(const boost::filesystem::path& provision_path, const std::string& provision_password);

static void readTlsP12(const std::string& p12_str, const std::string& provision_password, std::string& pkey,
std::string& cert, std::string& ca);
static std::string readServerUrl(const boost::filesystem::path& provision_path);
static std::string readServerCa(const boost::filesystem::path& provision_path);

std::string getCa() const { return ca; }
std::string getCert() const { return cert; }
std::string getPkey() const { return pkey; }
std::string getCa() const { return ca_; }
std::string getCert() const { return cert_; }
std::string getPkey() const { return pkey_; }

private:
std::string ca;
std::string cert;
std::string pkey;
std::string ca_;
std::string cert_;
std::string pkey_;
};

#endif // AKTUALIZR_BOOTSTRAP_H
8 changes: 7 additions & 1 deletion src/sota_tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,17 @@ endif(NOT BUILD_SOTA_TOOLS)

##### tests
if (BUILD_SOTA_TOOLS)
add_custom_target(sota_tools_cert_generation
COMMAND ${PROJECT_SOURCE_DIR}/tests/sota_tools/cert_generation/generate-zips.sh
${PROJECT_BINARY_DIR}/sota_tools/certs)

### common tests
add_aktualizr_test(NAME sota_tools_auth_test
SOURCES authenticate_test.cc
LIBRARIES sota_tools_static_lib aktualizr_static_lib
PROJECT_WORKING_DIRECTORY NO_VALGRIND)
PROJECT_WORKING_DIRECTORY NO_VALGRIND
ARGS ${PROJECT_BINARY_DIR}/sota_tools/certs)
add_dependencies(t_sota_tools_auth_test sota_tools_cert_generation)

add_aktualizr_test(NAME ostree_hash
LIBRARIES sota_tools_static_lib
Expand Down
17 changes: 7 additions & 10 deletions src/sota_tools/authenticate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ int authenticate(const string &cacerts, const ServerCredentials &creds, TreehubS
switch (creds.GetMethod()) {
case AuthMethod::kBasic: {
treehub.SetAuthBasic(creds.GetAuthUser(), creds.GetAuthPassword());
treehub.ca_certs(cacerts);
break;
}

case AuthMethod::kOauth2: {
OAuth2 oauth2(creds.GetAuthServer(), creds.GetClientId(), creds.GetClientSecret(), cacerts);

Expand All @@ -27,25 +25,24 @@ int authenticate(const string &cacerts, const ServerCredentials &creds, TreehubS
} else {
LOG_INFO << "Skipping Authentication";
}
// Set ca certificate path, because curl embeds the path to ca-certs that it was built with
// and this breaks under bitbake when sharing sstate cache between machines
treehub.ca_certs(cacerts);
break;
}
case AuthMethod::kCert: {
treehub.SetCerts(creds.GetRootCert(), creds.GetClientCert(), creds.GetClientKey());
case AuthMethod::kTls: {
treehub.SetCerts(creds.GetClientP12());
break;
}
case AuthMethod::kNone:
// Setup ca certificate because curl checks ca certs by default.
treehub.ca_certs(cacerts);
break;

default: {
LOG_FATAL << "Unexpected authentication method value " << static_cast<int>(creds.GetMethod());
return EXIT_FAILURE;
}
}
// Setup ca certificates in all cases. Even with no authentication, curl
// checks ca certs by default. Furthermore, curl embeds the path to ca certs
// that it was built with and this breaks under bitbake when sharing sstate
// cache between machines.
treehub.ca_certs(cacerts);
treehub.root_url(creds.GetOSTreeServer());
treehub.repo_url(creds.GetRepoUrl());

Expand Down
44 changes: 28 additions & 16 deletions src/sota_tools/authenticate_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@
#include "treehub_server.h"
#include "utilities/utils.h"

boost::filesystem::path certs_dir;

/* Authenticate with OAuth2.
* Parse authentication information from treehub.json. */
TEST(authenticate, good_zip) {
// Authenticates with the ATS portal to the SaaS instance.
boost::filesystem::path filepath = "tests/sota_tools/auth_test_good.zip";
ServerCredentials creds(filepath);
EXPECT_EQ(creds.GetMethod(), AuthMethod::kOauth2);
Expand All @@ -23,36 +26,32 @@ TEST(authenticate, good_zip) {
}

/* Authenticate with TLS credentials.
* Parse authentication information from treehub.json.
* Parse images repository URL from a provided archive. */
/* TODO: This used to work, but then when the zip file was updated because of
* expired certs, it was changed to not use cert auth, and there was no check to
* verify the method at that time.
TEST(authenticate, good_cert_zip) {
boost::filesystem::path filepath = "tests/sota_tools/auth_test_cert_good.zip";
// Authenticates with tls_server on port 1443.
boost::filesystem::path filepath = certs_dir / "good.zip";
ServerCredentials creds(filepath);
EXPECT_EQ(creds.GetMethod(), AuthMethod::kCert);
EXPECT_EQ(creds.GetMethod(), AuthMethod::kTls);
TreehubServer treehub;
int r = authenticate("", creds, treehub);
int r = authenticate("tests/fake_http_server/server.crt", creds, treehub);
EXPECT_EQ(0, r);
CurlEasyWrapper curl_handle;
curlEasySetoptWrapper(curl_handle.get(), CURLOPT_VERBOSE, 1);
treehub.InjectIntoCurl("test.txt", curl_handle.get());
CURLcode rc = curl_easy_perform(curl_handle.get());
EXPECT_EQ(CURLE_OK, rc);
}
*/

/* Authenticate with nothing (no auth).
* Parse authentication information from treehub.json.
* Parse images repository URL from a provided archive. */
TEST(authenticate, good_cert_noauth_zip) {
// Authenticates with tls_noauth_server on port 2443.
boost::filesystem::path filepath = "tests/sota_tools/auth_test_noauth_good.zip";
ServerCredentials creds(filepath);
EXPECT_EQ(creds.GetMethod(), AuthMethod::kNone);
TreehubServer treehub;
int r = authenticate("tests/fake_http_server/client.crt", creds, treehub);
int r = authenticate("tests/fake_http_server/server.crt", creds, treehub);
EXPECT_EQ(0, r);
CurlEasyWrapper curl_handle;
curlEasySetoptWrapper(curl_handle.get(), CURLOPT_VERBOSE, 1);
Expand All @@ -63,9 +62,14 @@ TEST(authenticate, good_cert_noauth_zip) {
}

TEST(authenticate, bad_cert_zip) {
boost::filesystem::path filepath = "tests/sota_tools/auth_test_cert_bad.zip";
// Tries to authenticates with tls_server on port 1443.
// Fails because the intermediate cert that signed the client cert was signed
// by a different root cert.
boost::filesystem::path filepath = certs_dir / "bad.zip";
ServerCredentials creds(filepath);
EXPECT_EQ(creds.GetMethod(), AuthMethod::kTls);
TreehubServer treehub;
int r = authenticate("", ServerCredentials(filepath), treehub);
int r = authenticate("", creds, treehub);
EXPECT_EQ(0, r);
CurlEasyWrapper curl_handle;
curlEasySetoptWrapper(curl_handle.get(), CURLOPT_VERBOSE, 1);
Expand All @@ -91,6 +95,7 @@ TEST(authenticate, no_json_zip) {

/* Extract credentials from a provided JSON file. */
TEST(authenticate, good_json) {
// Authenticates with the ATS portal to the SaaS instance.
boost::filesystem::path filepath = "tests/sota_tools/auth_test_good.json";
TreehubServer treehub;
int r = authenticate("", ServerCredentials(filepath), treehub);
Expand Down Expand Up @@ -120,18 +125,25 @@ TEST(authenticate, offline_sign_creds) {
EXPECT_TRUE(creds_offline.CanSignOffline());
}

/* Check if credentials support offline signing. */
/* Check if credentials do not support offline signing. */
TEST(authenticate, online_sign_creds) {
boost::filesystem::path auth_online = "tests/sota_tools/auth_test_cert_good.zip";
// Authenticates with tls_server on port 1443.
boost::filesystem::path auth_online = certs_dir / "good.zip";
ServerCredentials creds_online(auth_online);
EXPECT_FALSE(creds_online.CanSignOffline());
}

#ifndef __NO_MAIN__
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
boost::process::child server_process("tests/fake_http_server/ssl_server.py");
boost::process::child server_noauth_process("tests/fake_http_server/ssl_noauth_server.py");
if (argc != 2) {
std::cerr << "Error: " << argv[0] << " requires the path to the directory with generated certificates.\n";
return EXIT_FAILURE;
}
certs_dir = argv[1];

boost::process::child server_process("tests/fake_http_server/tls_server.py");
boost::process::child server_noauth_process("tests/fake_http_server/tls_noauth_server.py");
// TODO: this do not work because the server expects auth! Let's sleep for now.
// (could be replaced by a check with raw tcp)
// TestUtils::waitForServer("https://localhost:1443/");
Expand Down
2 changes: 1 addition & 1 deletion src/sota_tools/deploy_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ TemporaryDirectory temp_dir;
TEST(deploy, UploadToTreehub) {
OSTreeRepo::ptr src_repo = std::make_shared<OSTreeDirRepo>("tests/sota_tools/repo");
boost::filesystem::path filepath = (temp_dir.Path() / "auth.json").string();
boost::filesystem::path cert_path = "tests/fake_http_server/client.crt";
boost::filesystem::path cert_path = "tests/fake_http_server/server.crt";
auto server_creds = ServerCredentials(filepath);
auto run_mode = RunMode::kDefault;
auto test_ref = src_repo->GetRef("master");
Expand Down
2 changes: 1 addition & 1 deletion src/sota_tools/ostree_http_repo_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ TEST(http_repo, bad_connection) {
TestUtils::waitForServer("https://localhost:" + dp + "/");

boost::filesystem::path filepath = (dst_dir.Path() / "auth.json").string();
boost::filesystem::path cert_path = "tests/fake_http_server/client.crt";
boost::filesystem::path cert_path = "tests/fake_http_server/server.crt";

auto hash = OSTreeHash::Parse("b9ac1e45f9227df8ee191b6e51e09417bd36c6ebbeff999431e3073ac50f0563");
UploadToTreehub(src_repo, ServerCredentials(filepath), hash, cert_path.string(), RunMode::kDefault, 1);
Expand Down
2 changes: 1 addition & 1 deletion src/sota_tools/ostree_object_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ TEST(OstreeObject, UploadSuccess) {
push_server.root_url("https://localhost:" + dp);

boost::filesystem::path filepath = (temp_dir.Path() / "auth.json").string();
boost::filesystem::path cert_path = "tests/fake_http_server/client.crt";
boost::filesystem::path cert_path = "tests/fake_http_server/server.crt";
EXPECT_EQ(authenticate(cert_path.string(), ServerCredentials(filepath), push_server), EXIT_SUCCESS);

OSTreeRepo::ptr src_repo = std::make_shared<OSTreeDirRepo>(repo_path);
Expand Down
73 changes: 37 additions & 36 deletions src/sota_tools/server_credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,18 @@

#include <archive.h>
#include <archive_entry.h>
#include <boost/algorithm/string.hpp> // trim_if
#include <boost/optional.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>

#include "bootstrap/bootstrap.h"
#include "utilities/utils.h"

using boost::optional;
using boost::property_tree::ptree;
using boost::property_tree::json_parser::json_parser_error;

const std::string kBaseUrl = "https://treehub-staging.gw.prod01.advancedtelematic.com/api/v1/";
const std::string kPassword = "quochai1ech5oot5gaeJaifooqu6Saew";

std::unique_ptr<std::stringstream> readArchiveFile(archive *a) {
int r;
const char *buff = nullptr;
Expand All @@ -45,6 +44,7 @@ std::unique_ptr<std::stringstream> readArchiveFile(archive *a) {
ServerCredentials::ServerCredentials(const boost::filesystem::path &credentials_path)
: method_(AuthMethod::kNone), credentials_path_(credentials_path) {
bool found_config = false;
bool use_api_gateway = false;

std::unique_ptr<std::stringstream> json_stream;

Expand All @@ -65,12 +65,13 @@ ServerCredentials::ServerCredentials(const boost::filesystem::path &credentials_
if (strcmp(filename, "treehub.json") == 0) {
json_stream = readArchiveFile(a);
found_config = true;
} else if (strcmp(filename, "client.crt") == 0) {
client_cert_ = readArchiveFile(a)->str();
} else if (strcmp(filename, "client.key") == 0) {
client_key_ = readArchiveFile(a)->str();
} else if (strcmp(filename, "root.crt") == 0) {
root_cert_ = readArchiveFile(a)->str();
} else if (strcmp(filename, "client_auth.p12") == 0) {
client_p12_ = readArchiveFile(a)->str();
method_ = AuthMethod::kTls;
} else if (strcmp(filename, "api_gateway.url") == 0) {
use_api_gateway = true;
ostree_server_ = readArchiveFile(a)->str();
boost::trim_if(ostree_server_, boost::is_any_of(" \t\r\n"));
} else if (strcmp(filename, "tufrepo.url") == 0) {
repo_url_ = readArchiveFile(a)->str();
} else {
Expand All @@ -81,45 +82,45 @@ ServerCredentials::ServerCredentials(const boost::filesystem::path &credentials_
if (r != ARCHIVE_OK) {
throw BadCredentialsArchive(std::string("Error closing zipped credentials file: ") + credentials_path.string());
}
if (!found_config) {
if (!(use_api_gateway && method_ == AuthMethod::kTls) && !found_config) {
throw BadCredentialsContent(std::string("treehub.json not found in zipped credentials file: ") +
credentials_path.string());
}
} else {
archive_read_free(a);
}

try {
ptree pt;
if (use_api_gateway) {
repo_url_ = ostree_server_;
}

if (found_config) {
read_json(*json_stream, pt);
} else {
read_json(credentials_path.string(), pt);
}
if (!(use_api_gateway && method_ == AuthMethod::kTls)) {
try {
ptree pt;

if (optional<ptree &> ap_pt = pt.get_child_optional("oauth2")) {
method_ = AuthMethod::kOauth2;
auth_server_ = ap_pt->get<std::string>("server", "");
client_id_ = ap_pt->get<std::string>("client_id", "");
client_secret_ = ap_pt->get<std::string>("client_secret", "");
} else if (optional<ptree &> ba_pt = pt.get_child_optional("basic_auth")) {
method_ = AuthMethod::kBasic;
auth_user_ = ba_pt->get<std::string>("user", "");
auth_password_ = ba_pt->get<std::string>("password", kPassword);
} else if (pt.get<bool>("certificate_auth", false)) {
if ((client_cert_.size() != 0u) && (client_key_.size() != 0u) && (root_cert_.size() != 0u)) {
method_ = AuthMethod::kCert;
if (found_config) {
read_json(*json_stream, pt);
} else {
throw BadCredentialsContent(
"treehub.json requires certificate authentication, "
"but credentials archive doesn't include the necessary certificate files");
read_json(credentials_path.string(), pt);
}
}
ostree_server_ = pt.get<std::string>("ostree.server", kBaseUrl);

} catch (const json_parser_error &e) {
throw BadCredentialsJson(std::string("Unable to read ") + credentials_path.string() + " as archive or json file.");
if (method_ == AuthMethod::kTls) {
// do nothing
} else if (optional<ptree &> ap_pt = pt.get_child_optional("oauth2")) {
method_ = AuthMethod::kOauth2;
auth_server_ = ap_pt->get<std::string>("server", "");
client_id_ = ap_pt->get<std::string>("client_id", "");
client_secret_ = ap_pt->get<std::string>("client_secret", "");
} else if (optional<ptree &> ba_pt = pt.get_child_optional("basic_auth")) {
method_ = AuthMethod::kBasic;
auth_user_ = ba_pt->get<std::string>("user", "");
auth_password_ = ba_pt->get<std::string>("password", "");
}
ostree_server_ = pt.get<std::string>("ostree.server", "");
} catch (const json_parser_error &e) {
throw BadCredentialsJson(std::string("Unable to read ") + credentials_path.string() +
" as archive or json file.");
}
}
}

Expand Down
Loading

0 comments on commit f5668ae

Please sign in to comment.