GoogleCloudPlatform / esp-v2

A service proxy that provides API management capabilities using Google Service Infrastructure.
https://cloud.google.com/endpoints/
Apache License 2.0
271 stars 169 forks source link

Best practices for Auth on the backend #548

Open DazWilkin opened 3 years ago

DazWilkin commented 3 years ago

I'm wondering how to implement authentication in a Cloud Endpoints backend on the principle that I don't want to make the implementation specific to Endpoints.

I'm using gRPC and Cloud Run.

My current, naive implementation uses a gRPC interceptor and this expects x-endpoint-api-userinfo in the call's metadata (obtained from the context).

I've configured Firebase Authentication and Google Identity Tokens. The latter to short-circuit my workflow and provide me an easier way to authenticate requests through Endpoints.

However, I'm thinking that it would be better for the Interceptor to look for Cloud Endpoints specific metadata (?) to authenticate and, if not found, fall back to authenticating using the Authorization header (assuming this means the service is running on e.g. Cloud Run without Endpoints, for testing) and, if that's not found, permit without authentication (assuming the service is running locally).

Under this configuration, I must always ensure I deploy the backend to Cloud Run with --no-allow-unauthenticated but that should always be the case and it means the service is loosely-coupled to Endpoints.

Is this approach reasonable? What (other) flaws am I overlooking?

Is there a set of documented best practices for Cloud Endpoints?

qiwzhang commented 3 years ago

You mentioned two separate requirements: 1) Where your application is deployed, with or without Cloud Endpoint

2) only allow ESP to call your application by using flag --no-allow-unauthenticated

It seems that they are specific to your requirements. I don't think we have any better practices to offer.

DazWilkin commented 3 years ago

Thanks for the always prompt and thoughtful replies!

Is my approach reasonable? Are those headers appropriate signals for e.g. "proxied by Cloud Endpoints"? Does this approach too-easily fail-unsafe?

It feels that there are general best practices here:

The documentation for Cloud Endpoints is comprehensive and accurate (well done!) but - as is common with Google -- there's an assumption that everyone is as smart as a Google engineer and there's a lack of documentation for those of us that's aren't.

I think, even if only "guard rails", this could be helpful to other developers.

qiwzhang commented 3 years ago

I don't see issues with 2). But I do see some issues with 1):

nareddyt commented 3 years ago

My 2 cents: The goal of a ESPv2 (and API Proxies in general) is to reduce complexity in your API backend. You move common functionality (admission control, rate limiting, observability, authn, authz, etc.), to the proxy. Your API backends can then be simple, you don't need to reinvent the wheel for every backend you write.

Your proposal is to re-implement some authentication and authorization checks in your backend, and then expose your API backend so anyone can call it. IMO this defeats the goal of API proxies and increases the security risk for your API backend. For example:

I do understand your concern about platform lock-in. But you are making a tradeoff in terms of simplicity and security. Given that ESPv2 is an open-source proxy that can run on any platform and that Cloud Endpoints has very generous pricing, I would prefer to go with the simple and secure approach :)

DazWilkin commented 3 years ago

Every opinion is helpful, thank you!

I'm genuinely trying to understand this and to keep my life simpler.

Rereading my initial question, I realize I could have worded it more precisely. I'm looking to outsource authentication (AuthN) entirely but I want to handle authorization (AuthZ) consistently, regardless of which authentication approach has been deployed.

You make a good point about the portability of ESPv2; I'd not considered that and should.

I'm not trying to reimplement authentication and -- unless I'm mistaken -- I must implement authorization since that isn't a capability of Cloud Endpoints.

My premise is: If Cloud Endpoints manages authentication for me then my backend should not handle authentication. If my backend does not handle authentication then I should run it without authentication and I can test it without authentication (to test the non-auth functionality).

I will always deploy the backend to Cloud Run with --no-allow-unauthenticated and then the Cloud Run proxy (for there is yet another proxy) still, always authenticates requests... Cloud Endpoints authenticates using a service accounts and for testing, as a project member, I can simply Authorization: Bearer $(gcloud print-identity-token) to obtain a Google Identity Token that will pass authentication.

This makes life easier and my backend only then uses the JWT for authorization. If the user gets to my service, they are authenticated. The remaining question is what permissions do they have on the backend service.

Given the above, for testing, I can run the backend locally or deploy to Cloud Run and authenticate painlessly. And, when Cloud Endpoints is in the mix, I can test it authenticating too.

But, I must juggle headers for authorization which I must implement and which I'd like to do so as a filter similar to authentication that I can enable and disable for testing. When it's disabled, everything can be run. When it's enabled it authorizes based on a JWT.

But, Cloud Endpoints provides the JWT|claims using X-API-Endpoint-UserInfo, without Endpoints, Cloud Run provides the JWT|claims as Authorization Bearer (!?). I've not yet tried this but, given the above configuration, I'd like an authorization service that, when enabled, works regardless of whether Cloud Endpoints, Cloud Run or some other auth mechanism is in the picture.

nareddyt commented 3 years ago

I see your confusion, you are mixing up the authentication provided by Cloud Endpoints vs the authentication provided by Cloud Run (when you deploy with --no-allow-unauthenticated).

You configured ESPv2 to work with Firebase Authentication and Google Identity Tokens. ESPv2 will authenticate those end-user tokens that come in the Authorization header. If the token is valid (authentication successful), ESPv2 will fill in X-API-Endpoint-UserInfo. Then your backend can use this for authorization.

Now, you mention you deploy Cloud Run with --no-allow-unauthenticated. Cloud Run is also performing authentication, but it is not authenticating end-user tokens. Cloud Run doesn't know anything about Firebase. Cloud Run can only authenticate Google Identity Tokens for service-to-service authentication and authorization, as described here.

Technically you can use Cloud Run to authenticate end users, but it only works with Google Identity Tokens via Google Sign in. Any other methods (Firebase, Auth0, Okta) will not work with Cloud Run.

So the best practice we recommend is:

If you are ok with the limitation of Cloud Run only authenticating and authorizing Google Identity Tokens, then I can share how to configure ESPv2 to provide no authentication, and have Cloud Run do all authn/authz.