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

alts: add ALTS config and integration test #4468

Closed
wants to merge 7 commits into from
Closed
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
11 changes: 11 additions & 0 deletions api/envoy/config/transport_socket/alts/v2alpha/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
load("//bazel:api_build_system.bzl", "api_proto_library")

licenses(["notice"]) # Apache 2

api_proto_library(
name = "alts",
srcs = ["alts.proto"],
deps = [
"//envoy/api/v2/core:base",
],
)
20 changes: 20 additions & 0 deletions api/envoy/config/transport_socket/alts/v2alpha/alts.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
syntax = "proto3";

package envoy.config.transport_socket.alts.v2alpha;
option go_package = "v2";

// [#protodoc-title: ALTS]

import "validate/validate.proto";

// Configuration for ALTS transport socket. This provides Google's ALTS protocol to Envoy.
// https://cloud.google.com/security/encryption-in-transit/application-layer-transport-security/
message Alts {
// The location of a handshaker service, this is usually 169.254.169.254:8080
// on GCE
string handshaker_service = 1 [(validate.rules).string.min_bytes = 1];

// The acceptable service accounts from peer, peers not in the list will be rejected in the
// handshake validation step. If empty, no validation will be performed.
repeated string peer_service_accounts = 2;
}
13 changes: 13 additions & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,14 @@ def _com_google_protobuf():
name = "protobuf",
actual = "@com_google_protobuf//:protobuf",
)
native.bind(
name = "protobuf_clib",
actual = "@com_google_protobuf//:protoc_lib",
)
native.bind(
name = "protocol_compiler",
actual = "@com_google_protobuf//:protoc",
)
native.bind(
name = "protoc",
actual = "@com_google_protobuf_cc//:protoc",
Expand Down Expand Up @@ -527,6 +535,11 @@ def _com_github_grpc_grpc():
actual = "@envoy//bazel:grpc_health_proto",
)

native.bind(
name = "grpc_alts_fake_handshaker_server",
actual = "@com_github_grpc_grpc//test/core/tsi/alts/fake_handshaker:fake_handshaker_lib",
)

def _com_github_nanopb_nanopb():
_repository_impl(
name = "com_github_nanopb_nanopb",
Expand Down
7 changes: 4 additions & 3 deletions bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ REPOSITORY_LOCATIONS = dict(
remote = "https://github.com/google/libprotobuf-mutator",
),
com_github_grpc_grpc = dict(
sha256 = "013cc34f3c51c0f87e059a12ea203087a7a15dca2e453295345e1d02e2b9634b",
strip_prefix = "grpc-1.15.0",
urls = ["https://github.com/grpc/grpc/archive/v1.15.0.tar.gz"],
# TODO(lizan): Use release once new version released.
sha256 = "c3954b27d7cd44265b9f5eff423c115f7ccd00f871c0f5162957fc6c77d23b23",
strip_prefix = "grpc-4b1aa50e37c8e5bc1e4319b16827c8ff4507abc1",
urls = ["https://github.com/grpc/grpc/archive/4b1aa50e37c8e5bc1e4319b16827c8ff4507abc1.tar.gz"],
),
com_github_nanopb_nanopb = dict(
# From: https://github.com/grpc/grpc/blob/v1.14.0/bazel/grpc_deps.bzl#L123
Expand Down
3 changes: 1 addition & 2 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ EXTENSIONS = {
# Transport sockets
#

# TODO(lizan): switch to config target once a transport socket exists
"envoy.transport_sockets.alts": "//source/extensions/transport_sockets/alts:tsi_handshaker",
"envoy.transport_sockets.alts": "//source/extensions/transport_sockets/alts:config",
"envoy.transport_sockets.capture": "//source/extensions/transport_sockets/capture:config",

# Retry host predicates
Expand Down
21 changes: 20 additions & 1 deletion source/extensions/transport_sockets/alts/BUILD
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
licenses(["notice"]) # Apache 2

# TODO(lizan): Add link to docs when it is ready.
# ALTS transport socket. This provides Google's ALTS protocol support in GCP to Envoy.
# https://cloud.google.com/security/encryption-in-transit/application-layer-transport-security/

load(
"//bazel:envoy_build_system.bzl",
Expand All @@ -24,6 +25,24 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "config",
srcs = [
"config.cc",
],
hdrs = [
"config.h",
],
deps = [
":tsi_handshaker",
":tsi_socket",
"//include/envoy/registry",
"//include/envoy/server:transport_socket_config_interface",
"//source/extensions/transport_sockets:well_known_names",
"@envoy_api//envoy/config/transport_socket/alts/v2alpha:alts_cc",
],
)

envoy_cc_library(
name = "tsi_frame_protector",
srcs = [
Expand Down
151 changes: 151 additions & 0 deletions source/extensions/transport_sockets/alts/config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#include "extensions/transport_sockets/alts/config.h"

#include "envoy/config/transport_socket/alts/v2alpha/alts.pb.h"
#include "envoy/config/transport_socket/alts/v2alpha/alts.pb.validate.h"
#include "envoy/registry/registry.h"
#include "envoy/server/transport_socket_config.h"

#include "common/common/assert.h"
#include "common/protobuf/protobuf.h"
#include "common/protobuf/utility.h"

#include "extensions/transport_sockets/alts/grpc_tsi.h"
#include "extensions/transport_sockets/alts/tsi_socket.h"

#include "absl/strings/str_join.h"

namespace Envoy {
namespace Extensions {
namespace TransportSockets {
namespace Alts {

// smart pointer for grpc_alts_credentials_options that will be automatically freed.
typedef CSmartPtr<grpc_alts_credentials_options, grpc_alts_credentials_options_destroy>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment on this CSmartPtr above? How does it behave, i.e. is it functionally a unique_ptr for C malloc/free?

GrpcAltsCredentialsOptionsPtr;

namespace {

// Returns true if the peer's service account is found in peers, otherwise
// returns false and fills out err with an error message.
bool doValidate(const tsi_peer& peer, const std::unordered_set<std::string>& peers,
std::string& err) {
for (size_t i = 0; i < peer.property_count; ++i) {
const std::string name = std::string(peer.properties[i].name);
const std::string value =
std::string(peer.properties[i].value.data, peer.properties[i].value.length);
if (name.compare(TSI_ALTS_SERVICE_ACCOUNT_PEER_PROPERTY) == 0 &&
peers.find(value) != peers.end()) {
return true;
}
}

err =
"Couldn't find peer's service account in peer_service_accounts: " + absl::StrJoin(peers, ",");
return false;
}

} // namespace

ProtobufTypes::MessagePtr AltsTransportSocketConfigFactory::createEmptyConfigProto() {
return std::make_unique<envoy::config::transport_socket::alts::v2alpha::Alts>();
}

Network::TransportSocketFactoryPtr
UpstreamAltsTransportSocketConfigFactory::createTransportSocketFactory(
const Protobuf::Message& message, Server::Configuration::TransportSocketFactoryContext&) {
auto config =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a templated typed wrapper here similar to https://github.com/envoyproxy/envoy/blob/master/source/extensions/filters/http/common/factory_base.h#L15 to eliminate boilerplate for transport socket filters?

MessageUtil::downcastAndValidate<const envoy::config::transport_socket::alts::v2alpha::Alts&>(
message);

std::string handshaker_service = config.handshaker_service();
lizan marked this conversation as resolved.
Show resolved Hide resolved
const auto& peer_service_accounts = config.peer_service_accounts();
std::unordered_set<std::string> peers(peer_service_accounts.cbegin(),
peer_service_accounts.cend());

HandshakeValidator validator;
// Skip validation if peers is empty.
if (!peers.empty()) {
validator = [peers](const tsi_peer& peer, std::string& err) {
return doValidate(peer, peers, err);
};
}

HandshakerFactory factory =
[handshaker_service](Event::Dispatcher& dispatcher,
lizan marked this conversation as resolved.
Show resolved Hide resolved
const Network::Address::InstanceConstSharedPtr&,
const Network::Address::InstanceConstSharedPtr&) -> TsiHandshakerPtr {
GrpcAltsCredentialsOptionsPtr options{grpc_alts_credentials_client_options_create()};

tsi_handshaker* handshaker = nullptr;
// Specifying target name as empty since TSI won't take care of validating peer identity
// in this use case. The validation will be performed by TsiSocket with the validator.
tsi_result status = alts_tsi_handshaker_create(options.get(), "", handshaker_service.c_str(),
true /* is_client */, &handshaker);
CHandshakerPtr handshaker_ptr{handshaker};

if (status != TSI_OK) {
ENVOY_LOG_MISC(warn, "Cannot create ATLS client handshaker, status: {}", status);
return nullptr;
}

return std::make_unique<TsiHandshaker>(std::move(handshaker_ptr), dispatcher);
};

return std::make_unique<TsiSocketFactory>(factory, validator);
}

Network::TransportSocketFactoryPtr
DownstreamAltsTransportSocketConfigFactory::createTransportSocketFactory(
const Protobuf::Message& message, Server::Configuration::TransportSocketFactoryContext&,
const std::vector<std::string>&) {
auto config =
MessageUtil::downcastAndValidate<const envoy::config::transport_socket::alts::v2alpha::Alts&>(
message);

const std::string handshaker_service = config.handshaker_service();
const auto& peer_service_accounts = config.peer_service_accounts();
std::unordered_set<std::string> peers(peer_service_accounts.cbegin(),
peer_service_accounts.cend());

HandshakeValidator validator;
// Skip validation if peers is empty.
if (!peers.empty()) {
validator = [peers](const tsi_peer& peer, std::string& err) {
return doValidate(peer, peers, err);
};
}

HandshakerFactory factory =
[handshaker_service](Event::Dispatcher& dispatcher,
const Network::Address::InstanceConstSharedPtr&,
const Network::Address::InstanceConstSharedPtr&) -> TsiHandshakerPtr {
GrpcAltsCredentialsOptionsPtr options{grpc_alts_credentials_server_options_create()};

tsi_handshaker* handshaker = nullptr;
tsi_result status = alts_tsi_handshaker_create(
options.get(), nullptr, handshaker_service.c_str(), false /* is_client */, &handshaker);
CHandshakerPtr handshaker_ptr{handshaker};

if (status != TSI_OK) {
ENVOY_LOG_MISC(warn, "Cannot create ATLS server handshaker, status: {}", status);
return nullptr;
}

return std::make_unique<TsiHandshaker>(std::move(handshaker_ptr), dispatcher);
};

return std::make_unique<TsiSocketFactory>(factory, validator);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream & downstream seem quite similar; any way to reasonably refactor?


static Registry::RegisterFactory<UpstreamAltsTransportSocketConfigFactory,
Server::Configuration::UpstreamTransportSocketConfigFactory>
upstream_registered_;

static Registry::RegisterFactory<DownstreamAltsTransportSocketConfigFactory,
Server::Configuration::DownstreamTransportSocketConfigFactory>
downstream_registered_;

} // namespace Alts
} // namespace TransportSockets
} // namespace Extensions
} // namespace Envoy
42 changes: 42 additions & 0 deletions source/extensions/transport_sockets/alts/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include "envoy/server/transport_socket_config.h"

#include "extensions/transport_sockets/well_known_names.h"

namespace Envoy {
namespace Extensions {
namespace TransportSockets {
namespace Alts {

// ALTS config registry
class AltsTransportSocketConfigFactory
: public virtual Server::Configuration::TransportSocketConfigFactory {
public:
ProtobufTypes::MessagePtr createEmptyConfigProto() override;
std::string name() const override { return TransportSocketNames::get().Alts; }
};

class UpstreamAltsTransportSocketConfigFactory
: public AltsTransportSocketConfigFactory,
public Server::Configuration::UpstreamTransportSocketConfigFactory {
public:
Network::TransportSocketFactoryPtr
createTransportSocketFactory(const Protobuf::Message&,
Server::Configuration::TransportSocketFactoryContext&) override;
};

class DownstreamAltsTransportSocketConfigFactory
: public AltsTransportSocketConfigFactory,
public Server::Configuration::DownstreamTransportSocketConfigFactory {
public:
Network::TransportSocketFactoryPtr
createTransportSocketFactory(const Protobuf::Message&,
Server::Configuration::TransportSocketFactoryContext&,
const std::vector<std::string>&) override;
};

} // namespace Alts
} // namespace TransportSockets
} // namespace Extensions
} // namespace Envoy
29 changes: 20 additions & 9 deletions source/extensions/transport_sockets/alts/tsi_socket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,7 @@ Network::PostIoAction TsiSocket::doHandshake() {
ASSERT(!handshake_complete_);
ENVOY_CONN_LOG(debug, "TSI: doHandshake", callbacks_->connection());

if (!handshaker_) {
handshaker_ = handshaker_factory_(callbacks_->connection().dispatcher(),
callbacks_->connection().localAddress(),
callbacks_->connection().remoteAddress());
handshaker_->setHandshakerCallbacks(*this);
}

if (!handshaker_next_calling_) {
if (!handshaker_next_calling_ && raw_read_buffer_.length() > 0) {
doHandshakeNext();
}
return Network::PostIoAction::KeepOpen;
Expand All @@ -54,6 +47,20 @@ Network::PostIoAction TsiSocket::doHandshake() {
void TsiSocket::doHandshakeNext() {
ENVOY_CONN_LOG(debug, "TSI: doHandshake next: received: {}", callbacks_->connection(),
raw_read_buffer_.length());

if (!handshaker_) {
handshaker_ = handshaker_factory_(callbacks_->connection().dispatcher(),
callbacks_->connection().localAddress(),
callbacks_->connection().remoteAddress());
if (!handshaker_) {
ENVOY_CONN_LOG(warn, "TSI: failed to create handshaker", callbacks_->connection());
callbacks_->connection().close(Network::ConnectionCloseType::NoFlush);
return;
}

handshaker_->setHandshakerCallbacks(*this);
}

handshaker_next_calling_ = true;
Buffer::OwnedImpl handshaker_buffer;
handshaker_buffer.move(raw_read_buffer_);
Expand Down Expand Up @@ -212,12 +219,16 @@ Network::IoResult TsiSocket::doWrite(Buffer::Instance& buffer, bool end_stream)
}

void TsiSocket::closeSocket(Network::ConnectionEvent) {
ENVOY_CONN_LOG(debug, "TSI: closing socket", callbacks_->connection());
if (handshaker_) {
handshaker_.release()->deferredDelete();
}
}

void TsiSocket::onConnected() { ASSERT(!handshake_complete_); }
void TsiSocket::onConnected() {
ASSERT(!handshake_complete_);
doHandshakeNext();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that handshaker creation was moved to doHandshakeNext() because you called doHandhsakeNext() here.
But what's the purpose of making a call to doHandshakeNext() earlier at this point?
My worry is that localAddress() may not be initialized so far, can you check that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By doing this it is more efficient to not repeatedly call onHandshakerNext when the transport socket didn't received new data. The first time here we skip the empty check on read_buffer but the rest have. (The first next call for client need to be done when it is empty)

No worries about localAddress, since this is called at same stack that doWrite is called.
https://github.com/envoyproxy/envoy/blob/master/source/common/network/connection_impl.cc#L468

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation! To prevent future changes break this condition, can you add an assert before creating handshaker_ in line:52 to check local_address is initialized?

}

void TsiSocket::onNextDone(NextResultPtr&& result) {
handshaker_next_calling_ = false;
Expand Down
1 change: 1 addition & 0 deletions source/extensions/transport_sockets/well_known_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace TransportSockets {
*/
class TransportSocketNameValues {
public:
const std::string Alts = "envoy.transport_sockets.alts";
const std::string Capture = "envoy.transport_sockets.capture";
const std::string RawBuffer = "raw_buffer";
const std::string Tls = "tls";
Expand Down
Loading