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

auth: AttributeContext utilities #3325

Closed
wants to merge 2 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
22 changes: 22 additions & 0 deletions source/extensions/filters/common/auth/BUILD
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",
],
)
91 changes: 91 additions & 0 deletions source/extensions/filters/common/auth/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();
Copy link
Member

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.

Envoy::Network::Utility::addressToProtobufAddress(*addr, *peer.mutable_address());

if (!service.empty()) {
peer.set_service(service);
Copy link
Member

Choose a reason for hiding this comment

The 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());
Copy link
Member

Choose a reason for hiding this comment

The 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);
Copy link
Member

Choose a reason for hiding this comment

The 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());
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 decodeHeaders, this field isn't exactly helpful. Just porting the existing functionality. Perhaps it'd be best to set this to -1 for now per the docs on the proto message?

Copy link
Member Author

Choose a reason for hiding this comment

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

Or look for the content-length header?

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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(
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm. yeah that makes sense. Early discussions about RBAC about consuming a AttributeContext directly, which would require this conversion. If we want to avoid that, we'd need to create a similar AuthContext that just holds references to the connection, request info, and headers. This AuthContext could be shared between RBAC/ext_authz, and expose a method to populate the proto message for ext_authz's use. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The 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.

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 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.

Copy link
Member

Choose a reason for hiding this comment

The 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).

Copy link
Contributor

Choose a reason for hiding this comment

The 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
38 changes: 38 additions & 0 deletions source/extensions/filters/common/auth/attribute_context.h
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);
Copy link
Member

Choose a reason for hiding this comment

The 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
3 changes: 1 addition & 2 deletions source/extensions/filters/common/ext_authz/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ envoy_cc_library(
"//source/common/common:assert_lib",
"//source/common/grpc:async_client_lib",
"//source/common/http:headers_lib",
"//source/common/http:utility_lib",
"//source/common/network:utility_lib",
"//source/common/protobuf",
"//source/common/tracing:http_tracer_lib",
"//source/extensions/filters/common/auth:attribute_context_lib",
],
)
115 changes: 9 additions & 106 deletions source/extensions/filters/common/ext_authz/ext_authz_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
Copy link
Member

Choose a reason for hiding this comment

The 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()) : "";
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't you use AttributeContextUtils::getHeaderStr here?

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

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

Just have setSourcePeer take a string view.


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,
Expand All @@ -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
Expand Down
13 changes: 0 additions & 13 deletions source/extensions/filters/common/ext_authz/ext_authz_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,6 @@ class CheckRequestUtils {
*/
static void createTcpCheck(const Network::ReadFilterCallbacks* callbacks,
envoy::service::auth::v2alpha::CheckRequest& request);

private:
static void setAttrContextPeer(envoy::service::auth::v2alpha::AttributeContext_Peer& peer,
const Network::Connection& connection, const std::string& service,
const bool local);
static void setHttpRequest(::envoy::service::auth::v2alpha::AttributeContext_HttpRequest& httpreq,
const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
const Envoy::Http::HeaderMap& headers);
static void setAttrContextRequest(::envoy::service::auth::v2alpha::AttributeContext_Request& req,
const Envoy::Http::StreamDecoderFilterCallbacks* callbacks,
const Envoy::Http::HeaderMap& headers);
static std::string getHeaderStr(const Envoy::Http::HeaderEntry* entry);
static Envoy::Http::HeaderMap::Iterate fillHttpHeaders(const Envoy::Http::HeaderEntry&, void*);
};

} // namespace ExtAuthz
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ TEST_F(CheckRequestUtilsTest, BasicHttp) {
EXPECT_CALL(Const(connection_), ssl()).Times(2).WillRepeatedly(Return(&ssl_));
EXPECT_CALL(callbacks_, streamId()).WillOnce(Return(0));
EXPECT_CALL(callbacks_, requestInfo()).Times(3).WillRepeatedly(ReturnRef(req_info_));
EXPECT_CALL(req_info_, protocol()).Times(2).WillRepeatedly(ReturnPointee(&protocol_));
EXPECT_CALL(req_info_, protocol()).WillOnce(ReturnPointee(&protocol_));
CheckRequestUtils::createHttpCheck(&callbacks_, headers, request);
}

Expand Down