-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Changes from all commits
9c14ac2
6eee37a
be8ce3d
f02c977
3018c8e
dd504fb
15229f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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", | ||
], | ||
) |
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; | ||
} |
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> | ||
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 = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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_); | ||
|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
There was a problem hiding this comment.
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 aunique_ptr
for C malloc/free?