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

authz_filter: configuration to support Ambassador authorization flow #563

Merged
merged 16 commits into from
Apr 2, 2018

Conversation

gsagula
Copy link
Member

@gsagula gsagula commented Mar 19, 2018

This PR includes the necessary modifications in support of envoyproxy/envoy#2828.

  1. Added additional configuration to ext_authz.proto so that the filter is able to call an HTTP/1.1 authorization service.

  2. In external_auth.proto, added a nested message to CheckResponse that allows the authorization service to pass additional HTTP response attributes back to the authz filter.

@@ -23,4 +23,25 @@ message ExtAuthz {
// is NOT denied then traffic will be permitted.
// Defaults to false.
bool failure_mode_allow = 2;

// When this value is set to true, the filter must call an HTTP authorization service only.
bool use_http_service = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think an enum instead of a bool would allow future extensibility

Copy link
Member

Choose a reason for hiding this comment

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

Better IMO would actually be to make this a oneof and just merge HttpService as one of the options inside the oneof. This is in general what we are trying to do elsewhere. This brings the enum and the "only use if enum is set" logic together. If this impacts the already implemented non-HTTP code I would just go ahead and change it now since IMO I still consider this filter under development.

Copy link
Member Author

@gsagula gsagula Mar 20, 2018

Choose a reason for hiding this comment

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

Unless I'm missing something, It seems that the impact on the already implemented non-HTTP code would be the same with either enum or oneof.

The ExtAuthz message would look something like this:

message ExtAuthz {

  oneof services {

    envoy.api.v2.core.GrpcService grpc_service = 1;

    HttpService http_service = 3;
  }

  bool failure_mode_allow = 2;
}

And the decision on which client to use, somewhere here: https://github.com/envoyproxy/envoy/blob/da0dc5f5dac12651f211e321f9cd7b2c70bedde9/source/server/config/http/ext_authz.cc#L17

Copy link
Member

Choose a reason for hiding this comment

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

The oneof is a simpler way to represent mutually exclusive config when it's more than just a scalar value, i.e. the message. What you have there is basically what it would look like.

string cluster = 1;

// Sets the time in milliseconds within the service should respond to an authorization request.
uint32 timeout_ms = 2;
Copy link
Member

Choose a reason for hiding this comment

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

This should be a google.protobuf.Duration.


// Sets a list of authorization response headers that are allowed to be injected in the
// downstream client request before dispatching it to the upstream.
repeated string allowed_headers = 3;
Copy link
Member

Choose a reason for hiding this comment

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

Why is this necessary? Don't we trust the authz server to do the right thing?

Copy link
Member

Choose a reason for hiding this comment

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

+1

Copy link
Member Author

Choose a reason for hiding this comment

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

I just copied this from the Datawire's filter. It seems to prevent that all authz headers get copied over to the original request. E.g., content-length.

  if (!config_->allowed_headers_.empty()) {
    // Yup. Let's see if any of them are present.

    for (const std::string& allowed_header : config_->allowed_headers_) {
      LowerCaseString key(allowed_header);

      // OK, do we have this header?

      const HeaderEntry* hdr = response->headers().get(key);

      if (hdr) {
        // Well, the header exists at all. Does it have a value?

        const HeaderString& value = hdr->value();

        if (!value.empty()) {
          // Not empty! Copy it into our request_headers_.

          std::string valstr{value.c_str()};

          ENVOY_STREAM_LOG(trace, "ExtAuth allowing response header {}: {}", *callbacks_,
                           allowed_header, valstr);
          addedHeaders = true;
          request_headers_->addCopy(key, valstr);
        }
      }
    }

https://github.com/datawire/envoy/blob/546f6bbe3b74249d871073ef514cf71144e88dbf/source/common/http/filter/extauth.cc#L215

@kflynn Would you mind to help us with this one? Thanks!

Copy link

Choose a reason for hiding this comment

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

@htuch @mattklein123 Folks we've talked to have been less comfortable with given an auth service carte blanche to change anything it wants going upstream, more comfortable with only allowing it to change known things (cf some of the comments around trust on https://docs.google.com/document/d/1L3aRk9yaT6Lsb4epI6rdjCdStOuwTfhoeSP82zunkGg/edit).

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I really buy this. Why would the filter then trust anything the auth service does? Whether the decision, the response, etc.?

Copy link

Choose a reason for hiding this comment

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

@mattklein123 I think there were three underlying concerns:

  • Bugs in the auth service are always a thing; having some protection in the upstream-modification path is a bit of a safeguard. Same idea as not wanting even your favorite Unix developer run everything as root.

  • Policy enforcement is sometimes a thing. It may be necessary to be able to tell auditors that only this one specific set of modifications can ever happen, unless the following audited process is followed, etc.

  • Finally, sure, malicious auth services might someday be a thing somewhere? but they clearly do not need unchecked upstream-header modification to wreak havoc, so I don't consider protecting against that case to be a major driver for this config element.

Copy link
Member

Choose a reason for hiding this comment

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

I agree that it seems a bit arbitrary just policing the headers. If we don't trust the auth service, it can do a lot bad things, e.g. inject content in the reply for the body.

I can see that in some applications, you do want to strip headers unconditionally, when something passes between internal <-> external on edge proxies. We could use a separate filter in the stack for this, would that work in your application? It seems if the auth server can't set that header, backends probably shouldn't be trusted to do it either?

// modified in the original request before it gets sent to the upstream.
message HttpResponse {
// Http status code.
int32 status_code = 1;
Copy link
Member

Choose a reason for hiding this comment

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

You probably want to make this consistent with other uses of status code in the API, e.g.

uint32 status = 1 [(validate.rules).uint32 = {gte: 100, lt: 600}];

@@ -26,4 +26,22 @@ message CheckRequest {
message CheckResponse {
// Status `OK` allows the request. Any other status indicates the request should be denied.
google.rpc.Status status = 1;

// An optional message which contains the attributes of an HTTP response object. This message is
Copy link
Member

Choose a reason for hiding this comment

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

This PR doesn't make clear how exactly this will work. Does the HTTP service send back a proto with this? Or is this an enhancement for the normal gRPC authz service to provide it the same capabilities as the HTTP authz service?

Copy link
Member Author

@gsagula gsagula Mar 20, 2018

Choose a reason for hiding this comment

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

@htuch This should be an enhancement for the standard gRPC authz service to be able to respond to HTTP downstream client. I'm not sure if we need map<string, string> headers though since gRPC metadata could be used to accomplish the same thing. In this case, have a list of headers that are allowed to be copied or to be modified seems reasonable to me.

Copy link
Member

Choose a reason for hiding this comment

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

I think it's definitely the right thing for the gRPC authz service to make it explicit here. Metadata is really about the channel between the gRPC authz server and Envoy, it shouldn't be used to provide additional response information.

message HttpService {

// Sets the cluster name which the authorization request must be sent to.
string cluster = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

you can mandate this be set. see https://github.com/gsagula/data-plane-api/blob/339db0e1225d31e13ea3d35e7a8459abfbda3970/envoy/api/v2/core/grpc_service.proto#L25

Do you think there would ever be a need for a TLS configuration and also could the http service be made a core service (like grpc_service). (Not asking for you to change anything but thinking out loud).

Copy link
Member

Choose a reason for hiding this comment

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

TLS config is configured at the cluster level, so I think this is covered.

// E.g., when the authorization status is not OK, this message can be used by the authorization
// service to the tell the filter to respond with specific HTTP headers, body, and status code.
// When the authorization status is OK, this object may include the headers that should be
// modified in the original request before it gets sent to the upstream.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the desired behavior that the headers specified here would ONLY modify existing headers in the original request? Could it be that the service could add new headers to the request.
Would it make sense for the authz server to send all the headers that should be sent upstream. So if headers is there then those are the only headers that would get forwarded to upstream? Or can the authz server not have enough context to do 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.

@saumoh Sorry for these confusing words. Yes, it should allow modifying the existing headers as well as inject new ones. I will rephrase all this comment. Thanks for pointing this out.

// An optional message which contains the attributes of an HTTP response object. This message is
// particularly useful when the authorization service needs to send specific responses to the
// downstream client or to modify the request headers before dispatching them to the upstream.
// E.g., when the authorization status is not OK, this message can be used by the authorization
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you clarify the behavior when the response does not include a HttpResponse at all for both OK and not OK cases. I assume that there is no expectation for a HttpResponse to be there and in that case the behavior will not change from what is there today... right?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's right if no extra attributes are passed from a gRPC authz service to the filter, the behavior should continue the same.

Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Some quick skim comments. Not a full review.

@@ -23,4 +23,25 @@ message ExtAuthz {
// is NOT denied then traffic will be permitted.
// Defaults to false.
bool failure_mode_allow = 2;

// When this value is set to true, the filter must call an HTTP authorization service only.
bool use_http_service = 3;
Copy link
Member

Choose a reason for hiding this comment

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

Better IMO would actually be to make this a oneof and just merge HttpService as one of the options inside the oneof. This is in general what we are trying to do elsewhere. This brings the enum and the "only use if enum is set" logic together. If this impacts the already implemented non-HTTP code I would just go ahead and change it now since IMO I still consider this filter under development.

message HttpService {

// Sets the cluster name which the authorization request must be sent to.
string cluster = 1;
Copy link
Member

Choose a reason for hiding this comment

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

TLS config is configured at the cluster level, so I think this is covered.


// Sets a list of authorization response headers that are allowed to be injected in the
// downstream client request before dispatching it to the upstream.
repeated string allowed_headers = 3;
Copy link
Member

Choose a reason for hiding this comment

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

+1

@gsagula
Copy link
Member Author

gsagula commented Mar 20, 2018

@htuch @saumoh @mattklein123 @ggreenway Thank you for the review. These are all super valuable questions. Give me some time to review the design, and start to answer all the above.

@htuch htuch changed the title authz_filter: configuration to support Embassador authorization flow authz_filter: configuration to support Ambassador authorization flow Mar 20, 2018
@kyessenov
Copy link
Contributor

Per https://github.com/envoyproxy/data-plane-api/issues/458, do you mind moving the config definition to v2alpha, assuming the implementation is not following the API update immediately, or if there is a chance of backwards-incompatible changes.

@gsagula
Copy link
Member Author

gsagula commented Mar 21, 2018

@kyessenov I will do it.

@gsagula
Copy link
Member Author

gsagula commented Mar 25, 2018

@htuch @kyessenov @mattklein123 Thanks for the initial review. Please, feel free to take another look whenever you get a chance.

// The external authorization HTTP service configuration.
message HttpService {
// Sets the cluster name which the authorization request must be sent to.
string cluster = 1;
Copy link
Member

Choose a reason for hiding this comment

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

Wondering if https://github.com/envoyproxy/data-plane-api/blob/master/envoy/api/v2/core/http_uri.proto#L9 would be appropriate here. Leaning towards saying "no", since we are probably not making arbitrary HTTP calls and can statically configure the service, but if we want to leave room for growing to a dynamic auth service resolution later, then HttpUri might be appropriate.

Copy link
Member Author

@gsagula gsagula Mar 27, 2018

Choose a reason for hiding this comment

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

As far as it is compatible with the previous design, which seems that it is, I think HttpUri makes sense. I'm not entirely sure about what this change though.

HttpService http_service = 3;
}

bool failure_mode_allow = 2;
Copy link
Member

Choose a reason for hiding this comment

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

Comment?

Copy link
Member Author

Choose a reason for hiding this comment

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

👍


// [#proto-status: draft]

package envoy.service.auth.v2alpha;
Copy link
Member

Choose a reason for hiding this comment

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

These are just copies from the existing protos? How come the existing ones aren't being deleted (i.e. this isn't a move)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I just copied them. I wasn't 100% sure if I should delete the old ones.

Gabriel added 2 commits March 27, 2018 00:57
@htuch
Copy link
Member

htuch commented Mar 27, 2018

LGTM, thanks for the patience during iteration. @saumoh @kyessenov @kflynn I'll merge once you folks sign-off.


// Sets the time, in milliseconds, within the service should respond to an authorization
// request.
google.protobuf.Duration timeout = 2 [(gogoproto.stdduration) = true];
Copy link
Member

Choose a reason for hiding this comment

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

Thoughts on merging this into HttpUri? Everyone needs this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point. I will do it.

@mattklein123
Copy link
Member

Also, heads up on this PR: envoyproxy/envoy#2915

Copy link
Contributor

@saumoh saumoh left a comment

Choose a reason for hiding this comment

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

lgtm.
one minor nit (i think)

envoy.api.v2.core.GrpcService grpc_service = 1;

// The external authorization HTTP service configuration.
HttpService http_service = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: should it be 2.

Copy link
Member Author

Choose a reason for hiding this comment

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

Is it ok to re-index them? bool failure_mode_allow = 3?

Copy link
Contributor

Choose a reason for hiding this comment

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

you are right oneof doesn't encapsulate the messages under it. We can leave it as is.

syntax = "proto3";

package envoy.config.filter.http.ext_authz.v2alpha;
option go_package = "v2";
Copy link
Contributor

Choose a reason for hiding this comment

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

can you please change this to v2alpha?

@@ -2,13 +2,14 @@ syntax = "proto3";

// [#proto-status: draft]

package envoy.service.auth.v2;
package envoy.service.auth.v2alpha;
option go_package = "v2";
Copy link
Contributor

Choose a reason for hiding this comment

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

same here, v2alpha instead of v2

Copy link
Contributor

@kyessenov kyessenov left a comment

Choose a reason for hiding this comment

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

lgtm, a couple of nits

@gsagula
Copy link
Member Author

gsagula commented Mar 27, 2018

Thanks, everyone for the reviews! I will do these changes later today.

@gsagula
Copy link
Member Author

gsagula commented Mar 28, 2018

Please, feel free to take another look. Thanks!

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

LGTM, one last question.

@@ -34,4 +38,7 @@ message HttpUri {
//
string cluster = 2 [(validate.rules).string.min_bytes = 1];
}

// Sets the maximum duration in milliseconds that a response can take to arrive upon request.
Copy link
Member

Choose a reason for hiding this comment

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

What's the default if not specified?

Copy link
Member Author

@gsagula gsagula Mar 29, 2018

Choose a reason for hiding this comment

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

Would be better to make this required and >= 0? It's not clear to me when/how to enforce the default value.

Copy link
Member

Choose a reason for hiding this comment

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

SGTM

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

LGTM other than @htuch comment. Thanks!

@htuch htuch merged commit f88047c into envoyproxy:master Apr 2, 2018
htuch pushed a commit to envoyproxy/envoy that referenced this pull request Jun 27, 2018
This PR extends the current Ext_Authz filter to allow optional HTTP attributes being passed from the Authorization service down to client or, to the upstream services. I would like to get some feedback on the changes to the current gRPC async client and filter before moving to implementation of HTTP part of this extension and tests.

*issue: #2828

Risk Level: Medium
Testing: Manual, unit testing.
Docs Changes: envoyproxy/data-plane-api#563

Signed-off-by: Gabriel <[email protected]>
mattklein123 pushed a commit that referenced this pull request Jun 27, 2018
This PR extends the current Ext_Authz filter to allow optional HTTP attributes being passed from the Authorization service down to client or, to the upstream services. I would like to get some feedback on the changes to the current gRPC async client and filter before moving to implementation of HTTP part of this extension and tests.

*issue: #2828

Risk Level: Medium
Testing: Manual, unit testing.
Docs Changes: #563

Signed-off-by: Gabriel <[email protected]>

Mirrored from https://github.com/envoyproxy/envoy @ 5244597e93c70b4945c03a9fc55f8924a2da6fbc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants