Skip to content

gRPC: add the ability to use a custom SSL certificate in integration tests #1942

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

Merged
merged 5 commits into from
Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
#include "Firestore/core/src/firebase/firestore/remote/grpc_connection.h"
#include "Firestore/core/src/firebase/firestore/util/autoid.h"
#include "Firestore/core/src/firebase/firestore/util/filesystem.h"
#include "Firestore/core/src/firebase/firestore/util/path.h"
Expand All @@ -57,6 +58,7 @@
using firebase::firestore::auth::EmptyCredentialsProvider;
using firebase::firestore::model::DatabaseId;
using firebase::firestore::testutil::AppForUnitTesting;
using firebase::firestore::remote::GrpcConnection;
using firebase::firestore::util::CreateAutoId;
using firebase::firestore::util::Path;
using firebase::firestore::util::Status;
Expand Down Expand Up @@ -164,6 +166,8 @@ + (void)setUpDefaults {
"setup_integration_tests.py to properly configure testing SSL certificates.");
}
[GRPCCall useTestCertsPath:certsPath testName:@"test_cert_2" forHost:defaultSettings.host];
GrpcConnection::UseTestCertificate([certsPath cStringUsingEncoding:NSASCIIStringEncoding],
"test_cert_2");
}

+ (NSString *)projectID {
Expand Down
21 changes: 21 additions & 0 deletions Firestore/core/src/firebase/firestore/remote/grpc_connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_REMOTE_GRPC_CONNECTION_H_

#include <memory>
#include <string>
#include <vector>

#include "Firestore/core/src/firebase/firestore/auth/token.h"
Expand Down Expand Up @@ -78,7 +79,27 @@ class GrpcConnection {
void Register(GrpcCall* call);
void Unregister(GrpcCall* call);

/**
* For tests only: use a custom root certificate file and the given SSL
* target name for all connections. Call before creating any streams or calls.
*/
static void UseTestCertificate(absl::string_view certificate_path,
absl::string_view target_name);

/**
* For tests only: don't use SSL, send all traffic unencrypted. Call before
* creating any streams or calls. Overrides a test certificate.
*/
static void UseInsecureChannel();

private:
struct TestCredentials {
std::string certificate_path;
std::string target_name;
bool use_insecure_channel = false;
};
static TestCredentials* test_credentials_;

std::unique_ptr<grpc::ClientContext> CreateContext(
const auth::Token& credential) const;
std::shared_ptr<grpc::Channel> CreateChannel() const;
Expand Down
59 changes: 56 additions & 3 deletions Firestore/core/src/firebase/firestore/remote/grpc_connection.mm
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include "Firestore/core/src/firebase/firestore/remote/grpc_connection.h"

#include <algorithm>
#include <fstream>
#include <sstream>
#include <string>
#include <utility>

Expand Down Expand Up @@ -52,6 +54,8 @@

} // namespace

GrpcConnection::TestCredentials* GrpcConnection::test_credentials_ = nullptr;

GrpcConnection::GrpcConnection(const DatabaseInfo& database_info,
util::AsyncQueue* worker_queue,
grpc::CompletionQueue* grpc_queue,
Expand Down Expand Up @@ -114,9 +118,31 @@
}

std::shared_ptr<grpc::Channel> GrpcConnection::CreateChannel() const {
return grpc::CreateChannel(
database_info_->host(),
grpc::SslCredentials(grpc::SslCredentialsOptions()));
if (!test_credentials_) {
return grpc::CreateChannel(
database_info_->host(),
grpc::SslCredentials(grpc::SslCredentialsOptions()));
}

if (test_credentials_->use_insecure_channel) {
return grpc::CreateChannel(database_info_->host(),
grpc::InsecureChannelCredentials());
}

std::ifstream cert_file{test_credentials_->certificate_path};
HARD_ASSERT(cert_file.good(),
StringFormat("Unable to open root certificates at file path %s",
test_credentials_->certificate_path)
.c_str());
std::stringstream cert_buffer;
cert_buffer << cert_file.rdbuf();
grpc::SslCredentialsOptions options;
options.pem_root_certs = cert_buffer.str();

grpc::ChannelArguments args;
args.SetSslTargetNameOverride(test_credentials_->target_name);
return grpc::CreateCustomChannel(database_info_->host(),
grpc::SslCredentials(options), args);
}

std::unique_ptr<GrpcStream> GrpcConnection::CreateStream(
Expand Down Expand Up @@ -181,6 +207,33 @@
active_calls_.erase(found);
}

/*static*/ void GrpcConnection::UseTestCertificate(
absl::string_view certificate_path, absl::string_view target_name) {
HARD_ASSERT(!certificate_path.empty(), "Empty path to test certificate");
HARD_ASSERT(!target_name.empty(), "Empty SSL target name");

if (!test_credentials_) {
// Deliberately never deleted.
test_credentials_ = new TestCredentials{};
}

test_credentials_->certificate_path =
std::string{certificate_path.data(), certificate_path.size()};
test_credentials_->target_name =
std::string{target_name.data(), target_name.size()};
// TODO(varconst): hostname if necessary.
}

/*static*/ void GrpcConnection::UseInsecureChannel() {
if (!test_credentials_) {
// Deliberately never deleted.
test_credentials_ = new TestCredentials{};
}

test_credentials_->use_insecure_channel = true;
// TODO(varconst): hostname if necessary.
}

} // namespace remote
} // namespace firestore
} // namespace firebase