-
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
auth: AttributeContext utilities #3325
Changes from all commits
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,22 @@ | ||
licenses(["notice"]) # Apache 2 | ||
|
||
load( | ||
"//bazel:envoy_build_system.bzl", | ||
"envoy_cc_library", | ||
"envoy_package", | ||
) | ||
|
||
envoy_package() | ||
|
||
envoy_cc_library( | ||
name = "attribute_context_lib", | ||
srcs = ["attribute_context.cc"], | ||
hdrs = ["attribute_context.h"], | ||
deps = [ | ||
"//include/envoy/http:filter_interface", | ||
"//include/envoy/http:header_map_interface", | ||
"//source/common/http:utility_lib", | ||
"//source/common/network:utility_lib", | ||
"@envoy_api//envoy/service/auth/v2alpha:attribute_context_cc", | ||
], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#include "extensions/filters/common/auth/attribute_context.h" | ||
|
||
#include "common/http/utility.h" | ||
#include "common/network/utility.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace Filters { | ||
namespace Common { | ||
namespace Auth { | ||
|
||
void AttributeContextUtils::setSourcePeer(envoy::service::auth::v2alpha::AttributeContext& context, | ||
const Network::Connection& connection, | ||
const std::string& service) { | ||
setPeer(*context.mutable_source(), connection, service, false); | ||
} | ||
|
||
void AttributeContextUtils::setDestinationPeer( | ||
envoy::service::auth::v2alpha::AttributeContext& context, | ||
const Network::Connection& connection) { | ||
setPeer(*context.mutable_destination(), connection, "", true); | ||
} | ||
|
||
void AttributeContextUtils::setPeer(envoy::service::auth::v2alpha::AttributeContext_Peer& peer, | ||
const Network::Connection& connection, | ||
const std::string& service, const bool local) { | ||
|
||
auto addr = local ? connection.localAddress() : connection.remoteAddress(); | ||
Envoy::Network::Utility::addressToProtobufAddress(*addr, *peer.mutable_address()); | ||
|
||
if (!service.empty()) { | ||
peer.set_service(service); | ||
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. This can just be set universally I think. The empty check doesn't do much. |
||
} | ||
|
||
auto ssl = const_cast<Ssl::Connection*>(connection.ssl()); | ||
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. const_cast is an anti-pattern. Why is this needed? Fix the constness of methods if needed. |
||
if (ssl) { | ||
std::string principal = local ? ssl->uriSanLocalCertificate() : ssl->uriSanPeerCertificate(); | ||
if (principal.empty()) { | ||
principal = local ? ssl->subjectLocalCertificate() : ssl->subjectPeerCertificate(); | ||
} | ||
peer.set_principal(principal); | ||
} | ||
} | ||
|
||
void AttributeContextUtils::setHttpRequest( | ||
envoy::service::auth::v2alpha::AttributeContext& context, | ||
const Envoy::Http::StreamDecoderFilterCallbacks* callbacks, | ||
const Envoy::Http::HeaderMap& headers) { | ||
|
||
auto req = context.mutable_request()->mutable_http(); | ||
auto sdfc = const_cast<Envoy::Http::StreamDecoderFilterCallbacks*>(callbacks); | ||
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. nit: Please go through and make local variables longer and easier to read. We prefer easier to read code than short names. This comment applies everywhere. |
||
|
||
auto start = ProtobufUtil::TimeUtil::MicrosecondsToTimestamp( | ||
sdfc->requestInfo().startTime().time_since_epoch().count()); | ||
context.mutable_request()->mutable_time()->MergeFrom(start); | ||
|
||
auto proto = sdfc->requestInfo().protocol(); | ||
if (proto) { | ||
req->set_protocol(Envoy::Http::Utility::getProtocolString(proto.value())); | ||
} | ||
|
||
req->set_id(std::to_string(sdfc->streamId())); | ||
req->set_size(sdfc->requestInfo().bytesReceived()); | ||
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. Is this capturing the entire request size or only what has been received at the time of the call? 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. It will be at the time of the call. If ext_authz and RBAC are applied in 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. Or look for the content-length header? 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'm not sure. This could be a setting in the RBAC filter to buffer requests, although I'm not sure why we would want to use request size in RBAC at all. 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 can't think of a use case that RBAC uses request.size. Maybe use "content-length header" makes more sense. The request.size value should not depend on where the filter is placed. 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. works for me! |
||
|
||
req->set_method(getHeaderStr(headers.Method())); | ||
req->set_path(getHeaderStr(headers.Path())); | ||
req->set_host(getHeaderStr(headers.Host())); | ||
req->set_scheme(getHeaderStr(headers.Scheme())); | ||
// TODO(rodaine): add query & fragment fields | ||
|
||
auto mutable_headers = req->mutable_headers(); | ||
headers.iterate( | ||
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 why this is being done for the external auth code, but I'm just throwing it out there now that this type of copy will not be acceptable for the inline auth code. There is no reason to copy all the headers just to look through them again. So I'm just throwing this out there now in case that changes how you want to do things. (In general we won't do a layer of indirection that is not needed for the inline code). Given that, does this shared code add value? Just trying to understand the future intent 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. Hm. yeah that makes sense. Early discussions about RBAC about consuming a 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. TBH I would abandon this change and just start writing the filter. I don't see the value in copying to an intermediary when the check logic is trivial and can be done inline in a nice way. If you find there is coding sharing possible when you go do that, that's the time to do it IMO. 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 the point to avoid copying data to an intermediary. This totally makes sense. But the benefit here is that the code is more modularized, and potentially more reusable. The goal of using AttributeContext is to standardize the interface to any authorization module. If RBAC is implemented as a standalone library, it can potentially be reused in other environment, like shared between different filters, or used in a pure gRPC environment where there is no filter concept. Also, the AttributeContext could be filled directly from connection/request info (like being done in this PR), or some fields could be filled by an authentication filter in the future. IMO, it just makes the interface cleaner. 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. @liminw let's cross this bridge when we come to it. This feature is so simple IMO that I really think we are greatly overcomplicating it. I would like to get the filter written since we need it at Lyft and then we can always come back and refactor/share code as needed if it makes sense (given that it's an internal implementation detail and I think we did a great job of getting the proto right). 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. @mattklein123 Sounds good! |
||
[](const Envoy::Http::HeaderEntry& e, void* ctx) { | ||
auto mutable_headers = static_cast< | ||
Envoy::Protobuf::Map<Envoy::ProtobufTypes::String, Envoy::ProtobufTypes::String>*>(ctx); | ||
(*mutable_headers)[std::string(e.key().getStringView())] = | ||
std::string(e.value().getStringView()); | ||
return Envoy::Http::HeaderMap::Iterate::Continue; | ||
}, | ||
mutable_headers); | ||
} | ||
|
||
std::string AttributeContextUtils::getHeaderStr(const Envoy::Http::HeaderEntry* entry) { | ||
return entry ? std::string(entry->value().getStringView()) : ""; | ||
} | ||
|
||
} // namespace Auth | ||
} // namespace Common | ||
} // namespace Filters | ||
} // namespace Extensions | ||
} // namespace Envoy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#pragma once | ||
|
||
#include <string> | ||
|
||
#include "envoy/http/filter.h" | ||
#include "envoy/http/header_map.h" | ||
#include "envoy/network/connection.h" | ||
#include "envoy/service/auth/v2alpha/attribute_context.pb.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace Filters { | ||
namespace Common { | ||
namespace Auth { | ||
|
||
class AttributeContextUtils { | ||
public: | ||
static void setSourcePeer(envoy::service::auth::v2alpha::AttributeContext& context, | ||
const Network::Connection& connection, const std::string& service); | ||
static void setDestinationPeer(envoy::service::auth::v2alpha::AttributeContext& context, | ||
const Network::Connection& connection); | ||
static void setHttpRequest(envoy::service::auth::v2alpha::AttributeContext& context, | ||
const Envoy::Http::StreamDecoderFilterCallbacks* callbacks, | ||
const Envoy::Http::HeaderMap& headers); | ||
|
||
private: | ||
static void setPeer(envoy::service::auth::v2alpha::AttributeContext_Peer& peer, | ||
const Network::Connection& connection, const std::string& service, | ||
const bool local); | ||
|
||
static std::string getHeaderStr(const Envoy::Http::HeaderEntry* entry); | ||
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. return string_view here |
||
}; | ||
|
||
} // namespace Auth | ||
} // namespace Common | ||
} // namespace Filters | ||
} // namespace Extensions | ||
} // namespace Envoy |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,10 +11,10 @@ | |
#include "common/common/assert.h" | ||
#include "common/grpc/async_client_impl.h" | ||
#include "common/http/headers.h" | ||
#include "common/http/utility.h" | ||
#include "common/network/utility.h" | ||
#include "common/protobuf/protobuf.h" | ||
|
||
#include "extensions/filters/common/auth/attribute_context.h" | ||
|
||
namespace Envoy { | ||
namespace Extensions { | ||
namespace Filters { | ||
|
@@ -67,104 +67,6 @@ void GrpcClientImpl::onFailure(Grpc::Status::GrpcStatus status, const std::strin | |
callbacks_ = nullptr; | ||
} | ||
|
||
void CheckRequestUtils::setAttrContextPeer( | ||
envoy::service::auth::v2alpha::AttributeContext_Peer& peer, | ||
const Network::Connection& connection, const std::string& service, const bool local) { | ||
|
||
// Set the address | ||
auto addr = peer.mutable_address(); | ||
if (local) { | ||
Envoy::Network::Utility::addressToProtobufAddress(*connection.localAddress(), *addr); | ||
} else { | ||
Envoy::Network::Utility::addressToProtobufAddress(*connection.remoteAddress(), *addr); | ||
} | ||
|
||
// Set the principal | ||
// Preferably the SAN from the peer's cert or | ||
// Subject from the peer's cert. | ||
Ssl::Connection* ssl = const_cast<Ssl::Connection*>(connection.ssl()); | ||
if (ssl != nullptr) { | ||
if (local) { | ||
peer.set_principal(ssl->uriSanLocalCertificate()); | ||
|
||
if (peer.principal().empty()) { | ||
peer.set_principal(ssl->subjectLocalCertificate()); | ||
} | ||
} else { | ||
peer.set_principal(ssl->uriSanPeerCertificate()); | ||
|
||
if (peer.principal().empty()) { | ||
peer.set_principal(ssl->subjectPeerCertificate()); | ||
} | ||
} | ||
} | ||
|
||
if (!service.empty()) { | ||
peer.set_service(service); | ||
} | ||
} | ||
|
||
std::string CheckRequestUtils::getHeaderStr(const Envoy::Http::HeaderEntry* entry) { | ||
if (entry) { | ||
// TODO(jmarantz): plumb absl::string_view further here; there's no need | ||
// to allocate a temp string in the local uses. | ||
return std::string(entry->value().getStringView()); | ||
} | ||
return ""; | ||
} | ||
|
||
void CheckRequestUtils::setHttpRequest( | ||
::envoy::service::auth::v2alpha::AttributeContext_HttpRequest& httpreq, | ||
const Envoy::Http::StreamDecoderFilterCallbacks* callbacks, | ||
const Envoy::Http::HeaderMap& headers) { | ||
|
||
// Set id | ||
// The streamId is not qualified as a const. Although it is as it does not modify the object. | ||
Envoy::Http::StreamDecoderFilterCallbacks* sdfc = | ||
const_cast<Envoy::Http::StreamDecoderFilterCallbacks*>(callbacks); | ||
httpreq.set_id(std::to_string(sdfc->streamId())); | ||
|
||
// Set method | ||
httpreq.set_method(getHeaderStr(headers.Method())); | ||
// Set path | ||
httpreq.set_path(getHeaderStr(headers.Path())); | ||
// Set host | ||
httpreq.set_host(getHeaderStr(headers.Host())); | ||
// Set scheme | ||
httpreq.set_scheme(getHeaderStr(headers.Scheme())); | ||
|
||
// Set size | ||
// need to convert to google buffer 64t; | ||
httpreq.set_size(sdfc->requestInfo().bytesReceived()); | ||
|
||
// Set protocol | ||
if (sdfc->requestInfo().protocol()) { | ||
httpreq.set_protocol( | ||
Envoy::Http::Utility::getProtocolString(sdfc->requestInfo().protocol().value())); | ||
} | ||
|
||
// Fill in the headers | ||
auto mutable_headers = httpreq.mutable_headers(); | ||
headers.iterate( | ||
[](const Envoy::Http::HeaderEntry& e, void* ctx) { | ||
Envoy::Protobuf::Map<Envoy::ProtobufTypes::String, Envoy::ProtobufTypes::String>* | ||
mutable_headers = static_cast< | ||
Envoy::Protobuf::Map<Envoy::ProtobufTypes::String, Envoy::ProtobufTypes::String>*>( | ||
ctx); | ||
(*mutable_headers)[std::string(e.key().getStringView())] = | ||
std::string(e.value().getStringView()); | ||
return Envoy::Http::HeaderMap::Iterate::Continue; | ||
}, | ||
mutable_headers); | ||
} | ||
|
||
void CheckRequestUtils::setAttrContextRequest( | ||
::envoy::service::auth::v2alpha::AttributeContext_Request& req, | ||
const Envoy::Http::StreamDecoderFilterCallbacks* callbacks, | ||
const Envoy::Http::HeaderMap& headers) { | ||
setHttpRequest(*req.mutable_http(), callbacks, headers); | ||
} | ||
|
||
void CheckRequestUtils::createHttpCheck(const Envoy::Http::StreamDecoderFilterCallbacks* callbacks, | ||
const Envoy::Http::HeaderMap& headers, | ||
envoy::service::auth::v2alpha::CheckRequest& request) { | ||
|
@@ -174,11 +76,12 @@ void CheckRequestUtils::createHttpCheck(const Envoy::Http::StreamDecoderFilterCa | |
Envoy::Http::StreamDecoderFilterCallbacks* cb = | ||
const_cast<Envoy::Http::StreamDecoderFilterCallbacks*>(callbacks); | ||
|
||
const std::string service = getHeaderStr(headers.EnvoyDownstreamServiceCluster()); | ||
auto hdr = headers.EnvoyDownstreamServiceCluster(); | ||
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. const here and next line |
||
std::string service = hdr ? std::string(hdr->value().getStringView()) : ""; | ||
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. Why don't you use AttributeContextUtils::getHeaderStr 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. it's private and seemed a weird thing to reach into the utils for. 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. Just have |
||
|
||
setAttrContextPeer(*attrs->mutable_source(), *cb->connection(), service, false); | ||
setAttrContextPeer(*attrs->mutable_destination(), *cb->connection(), "", true); | ||
setAttrContextRequest(*attrs->mutable_request(), callbacks, headers); | ||
Auth::AttributeContextUtils::setSourcePeer(*attrs, *cb->connection(), service); | ||
Auth::AttributeContextUtils::setDestinationPeer(*attrs, *cb->connection()); | ||
Auth::AttributeContextUtils::setHttpRequest(*attrs, callbacks, headers); | ||
} | ||
|
||
void CheckRequestUtils::createTcpCheck(const Network::ReadFilterCallbacks* callbacks, | ||
|
@@ -187,8 +90,8 @@ void CheckRequestUtils::createTcpCheck(const Network::ReadFilterCallbacks* callb | |
auto attrs = request.mutable_attributes(); | ||
|
||
Network::ReadFilterCallbacks* cb = const_cast<Network::ReadFilterCallbacks*>(callbacks); | ||
setAttrContextPeer(*attrs->mutable_source(), cb->connection(), "", false); | ||
setAttrContextPeer(*attrs->mutable_destination(), cb->connection(), "", true); | ||
Auth::AttributeContextUtils::setSourcePeer(*attrs, cb->connection(), ""); | ||
Auth::AttributeContextUtils::setDestinationPeer(*attrs, cb->connection()); | ||
} | ||
|
||
} // namespace ExtAuthz | ||
|
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.
Please go through and constify local variables. Many of them can be const, won't comment on all of them.