I'm using Cloud Run, Endpoints and Cloud Functions to build an API service. There are multiple endpoints running completely fine, but I'm no longer able to deploy any new endpoints.
The Cloud Run environment has an error that prevents it from making a call to the corresponding Cloud Function. Oddly enough, all other endpoints work fine, but I'm unable to create new endpoints.
I found this article: https://cloud.google.com/endpoints/docs/openapi/troubleshoot-response-errors but it's only for the BAD_GATEWAY error code.
All code is deployed completely fine. No errors in deploying the Cloud Function, Cloud Run or Open API yaml file.
Error in response:
{
"code": 13,
"message": "INTERNAL_SERVER_ERROR",
"details": [
{
"#type": "type.googleapis.com/google.rpc.DebugInfo",
"stackEntries": [],
"detail": "application"
}
]
}
Error in Cloud Run:
5#5: *33 invalid URL prefix in "", client: xxxxx, server: , request: "GET /user HTTP/1.1", host: "[my cloud run host]"
GET 500 404 B4 ms python-requests/2.22.0 [cloud run host]/user
The main.py file:
def user(request):
return "Ok"
The yaml file:
/user:
x-google-backend:
address: https://[cloud functions host]/user
get:
summary: Retrieves a user.
operationId: getUser
responses:
'200':
description: A successful response
'400':
description: BAD_REQUEST
If we look at your YAML:
/user:
x-google-backend:
address: https://[cloud functions host]/user
get:
summary: Retrieves a user.
operationId: getUser
responses:
'200':
description: A successful response
'400':
description: BAD_REQUEST
... pay particular attention to the x-google-backend section. Notice that this exists within the /user path section. Now notice that the address is a URL with a path. You don't want a path part in the URL, just the address of the host (and optional port). Change your start of your YAML to:
/user:
x-google-backend:
address: https://[cloud functions host]
(The /user portion was removed from the address line)
Related
I am using API Gateway with firebase JWT authorisation (so that users can use google sign in) that is forwarding the requests to cloud run services and one cloud function service.
Here is how my API Gateway config looks like:
swagger: '2.0'
info:
version: '1.0.0'
title: 'BFF'
description: Backend For Frontend
schemes:
- https
security:
- firebase: []
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/${PROJECT}"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: ${PROJECT}
paths:
/test/auth:
post:
operationId: testAuth
summary: Test auth
produces:
- application/json
x-google-backend:
address: https://${REGION}-${PROJECT}.cloudfunctions.net/auth-test
responses:
'200':
description: 'Response returns user related data from JWT'
/record/new:
post:
operationId: crateRecord
summary: Create new record
x-google-backend:
address: ${RUN_SERVICE_URL}:${RUN_SERVICE_PORT}/new
produces:
- application/json
parameters:
- in: body
name: data
description: Data for new record
schema:
$ref: '#/definitions/Record'
responses:
'200':
description: New record data
schema:
$ref: '#/definitions/Record'
'400':
description: Invalid input data
The problem is that API Gateway for some reason can't invoke cloud run service but it can invoke cloud functions:
┍ Client is passing authorization token in header
|
| ┍ Auth is successful and request is forwarded to cloud run
| |
| | ┍ 401 unauthorized to invoke cloud run
| | |
↓ ↓ ↓
Client -----------> API Gateway -----X-----> Cloud run service
API Gateway service account has these relevant roles: roles/cloudfunctions.invoker, roles/run.invoker and roles/iam.serviceAccountUser
Run service also has IAM binding for gateway service account with the role roles/run.invoker
When I use /test/auth route I can see that firebase auth is working as expected and I can trigger cloud functions without any problem and in response cloud function returns data from x-apigateway-api-userinfo as expected. But when i make request with same authorization token to run service route /record/new I get:
www-authenticate: Bearer error="invalid_token" error_description="The access token could not be verified"
401 Unauthorized
Your client does not have permission to the requested URL /new.
I am running out of ideas on what might be the problem, any advice would be helpful.
With Cloud Functions, the identity token created contains automatically the correct audience. It's not the case when you invoke Cloud Run, you have to explicitly mention the Cloud Run audience
/record/new:
post:
operationId: crateRecord
summary: Create new record
x-google-backend:
address: ${RUN_SERVICE_URL}:${RUN_SERVICE_PORT}/new
jwt_audience: ${RUN_SERVICE_URL}
Have a try, it should work now.
I'm trying to setup Google API Gateway to use an API key that callers send in the header.
My api config yaml looks like this:
...
securityDefinitions:
api_key_header:
type: apiKey
name: key
in: header
api_key_query:
type: apiKey
name: key
in: query
paths:
/foo-header:
get:
summary: Test foo endpoint
operationId: testGet-header
x-google-backend:
address: "<backend address>"
protocol: h2
path_translation: APPEND_PATH_TO_ADDRESS
security:
- api_key_header: []
responses:
204:
description: A successful response
/foo-query:
get:
summary: Test foo endpoint
operationId: testGet-header
x-google-backend:
address: "<backend address>"
protocol: h2
path_translation: APPEND_PATH_TO_ADDRESS
security:
- api_key_query: []
responses:
204:
description: A successful response
I expect both calls, /foo-header and /foo-query to fail with 401 status if a valid API key is not provided via header or query parameter.
But in a fact only /foo-query behaves as expected.
Requests to /foo-header pass to the backend even when the API key is not provided in request header.
Do I have issue with the config, or is it the Google API Gateway that doesn't work properly when API key is provided in request header?
When in is header, the name should be x-api-key.
https://cloud.google.com/endpoints/docs/openapi/openapi-limitations#api_key_definition_limitations
It seems that the Google API Gateway should work fine when the API key is provided in request header since the Google API Gateway documentation states:
A developer generates an API key in a project in the Cloud Console and embeds that key in every call to your API as a query parameter or in a request header.
However, I was able to reproduce the behavior you reported, thus I don't think that there is something wrong in your configuration.
For that I'd been following the GCP quickstart for the Google API Gateway, modifying it slightly so that my OpenAPI spec would also have 2 paths: one is looking for a key in query parameters, while another in the request header.
paths:
/foo-header:
get:
summary: Test security
operationId: headerkey
x-google-backend:
address: [MY_CLOUD_FUNCTION_1]
security:
- api_key_header: []
responses:
'200':
description: A successful response
schema:
type: string
/foo-query:
get:
summary: Test security
operationId: querykey
x-google-backend:
address: [MY_CLOUD_FUNCTION_2]
security:
- api_key_query: []
responses:
'200':
description: A successful response
schema:
type: string
securityDefinitions:
# This section configures basic authentication with an API key.
api_key_header:
type: "apiKey"
name: "key"
in: "header"
api_key_query:
type: "apiKey"
name: "key"
in: "query"
Just like you, I could see the requests to the /foo-header pass to the backend even when there was no API key provided.
I would suggest you to report this issue on the Public Issue Tracker, so that it would be reviewed by an appropriate GCP engineering team.
I was trying GCP API Gateway using firebase authentication. I can see my request has been processed from the logs and completed with response code 200. However, I am not getting the response back to my client. I am getting the response when I call the function directly. Am I missing something ?
API Config
swagger: "2.0"
info:
title: API Endpoints
description: API Endpoints
version: 1.0.1
schemes:
- https
produces:
- application/json
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/my-project"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "my-project"
paths:
/hello:
get:
summary: Test link
operationId: hello
x-google-backend:
address: https://us-central1-my-project.cloudfunctions.net/hello
security:
- firebase: []
responses:
"200":
description: A successful response
schema:
type: string
"403":
description: Failed to authenticate
Function
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
exports.helloWorld = (req, res) => {
let message = req.query.message || req.body.message || 'Hello World!';
res.status(200).send(message);
};
Logs
Additional Query
Initially, I had my functions private and was getting 403. It gave me 200 once I added allUsers with Cloud Functions Invoker to the permissions to the function I am trying to invoke. So I am a bit confused here. Part of the reason I am using API gateway with firebase auth is to protect it against unauthorised calls. And for firebase auth to work, I had to add allUsers, making it public. My understanding was that the API gateway alone would be public while all the backend services that it invokes would be private. In this case, the function can be directly invoked by anyone, rendering API Gateway useless. How can I setup the backend to private and only respond to authenticated calls through API gateway ?
Additional Logs
{
insertId: "8c13b49c-2752-4216-8188-d445f4724ef14850908905639612439#a1"
jsonPayload: {
api_key_state: "NOT CHECKED"
api_method: "1.myapi.GenerateRTCToken"
api_name: "1.myapi"
api_version: "1.0.1"
client_ip: "999.999.999.999"
http_method: "GET"
http_response_code: 200
location: "us-central1"
log_message: "1.myapi.GenerateRTCToken is called"
producer_project_id: "myproject"
request_latency_in_ms: 161
request_size_in_bytes: 4020
response_size_in_bytes: 579
service_agent: "ESPv2/2.17.0"
service_config_id: "myapi-config"
timestamp: 1606313914.9804168
url: "/generateRTCToken"
}
logName: "projects/myproject/logs/myapi%2Fendpoints_log"
receiveTimestamp: "2020-11-25T14:18:36.521292489Z"
resource: {
labels: {
location: "us-central1"
method: "1.myapi.GenerateRTCToken"
project_id: "myproject"
service: "myapi"
version: "1.0.1"
}
type: "api"
}
severity: "INFO"
timestamp: "2020-11-25T14:18:34.980416865Z"
}
To call the Google Cloud Function from the Api-Gateway without making it public you can grant the service account used by the API-Gateway the role CloudFunctions Invoker
On Creating an API config (4) : you set a service Account, take note of this service account.
Once you have identified the service account you can grant it the Cloud Funtions Invoker role to the service account. For these you can follow these steps:
Go to IAM&Admin
Look for the service account, and click on the pencil next to it( If you don't see the service account click on Add and type the service account email)
For role Select Cloud Functions Invoker
Click on Save
This will grant the service account the permission to call the functions without having them public.
I have an similar issue with API Gateway throwing the same response validating a token against keycloak.
The issue was with the JWT, it was too long, we remove unused roles from the user and work perfectly.
Hope it helps.
Take a look at this document where it is explained in detail how to use Firebase to authenticate in API Gateway.
In general there are two conditions that we need to keep on mind in order to configure these services:
When your client application sends an HTTP request, the authorization header in the request must contain the following JWT claims:
iss (issuer)
sub (subject)
aud (audience)
iat (issued at)
exp (expiration time)
To support Firebase authentication, add the following to the security definition in your API config:
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
# Replace YOUR-PROJECT-ID with your project ID
x-google-issuer: "https://securetoken.google.com/YOUR-PROJECT-ID"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "YOUR-PROJECT-ID"
I am new in API Gateway and trying to enable security on my API. I follow some instruction which I found on the internet like this:
https://medium.com/swlh/manage-serverless-apis-with-api-gateway-in-gcp-b7f906efec1a
Here is my YAML file:
# openapi2-functions.yaml
swagger: '2.0'
info:
title: simple-test
description: Sample API on API Gateway with a Google Cloud Functions backend
version: 1.0.0
schemes:
- https
produces:
- application/json
paths:
/direcciones:
get:
summary: get direcciones
operationId: direcciones
x-google-backend:
address: http://publicIP/api/v1/app/catalogos/direcciones
security:
- api_key: []
responses:
'200':
description: A successful response
schema:
type: string
securityDefinitions:
api_key:
type: "apiKey"
name: "key"
in: "query"
While deploying this config file in API gateway config, I am getting the following error:
INVALID_ARGUMENT Cannot convert to service config. 'location: "evva.yaml: x-google-backend" kind: ERROR message: "Extension x-google-backend cannot be converted into proto type google.api.BackendRule. Details: Cannot find field: security in message google.api.BackendRule" location: "evva.yaml: x-google-backend" message: "Address field in extension x-google-backend is empty. In this case, the backend address must be provided to the proxy via a runtime flag." location: "evva.yaml: Operation \'get\' in path \'/direcciones\'" message: "Operation does not require an API key; callers may invoke the method without specifying an associated API-consuming project. To enable API key all the SecurityRequirement Objects (https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-requirement-object) inside security definition must reference at least one SecurityDefinition of type : \'apiKey\'." ' com.google.apps.framework.request.BadRequestException: Cannot convert to service config. 'location: "evva.yaml: x-google-backend" kind: ERROR message: "Extension x-google-backend cannot be converted into proto type google.api.BackendRule. Details: Cannot find field: security in message google.api.BackendRule" location: "evva.yaml: x-google-backend" message: "Address field in extension x-google-backend is empty. In this case, the backend address must be provided to the proxy via a runtime flag." location: "evva.yaml: Operation \'get\' in path \'/direcciones\'" message: "Operation does not require an API key; callers may invoke the method without specifying an associated API-consuming project. To enable API key all the SecurityRequirement Objects (https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-requirement-object) inside security definition must reference at least one SecurityDefinition of type : \'apiKey\'." '
I don't understand this error what change should i need to do in YAML file to make it acceptable while deploying.
The security entry mustn't be "in" the x-google-backend, but bellow the get:. Like this.
paths:
/direcciones:
get:
summary: get direcciones
operationId: direcciones
x-google-backend:
address: http://publicIP/api/v1/app/catalogos/direcciones
security:
- api_key: []
I have been reviewing Google Cloud Extensible Service Proxy, which promises to be a serverless NGINX instance, however I am unsure on how to proxy to multiple services running in app engine through it, and essentially use it as a gateway. It seems to demand a host in the swagger JSON configuration and an environment variable that points to the endpoint service name, and I don't see how it could proxy to multiple services given this constraint.
My understanding is that you could host the Endpoints ESP using Cloud Run. This would then give you a single URL from which to access it but would spin up enough instances of the ESP if needed. The Open API specification document you would then register with it would contain paths corresponding to each instance of a service you want to expose. For each path, you would then define an x-google-backend pointing to the service URL that each path would resolve against.
EDIT:
the yaml file will look somehow like this:
info:
title: Cloud Endpoints with API Keys
description: Sample API on Cloud Endpoints with multiple App Engine with IAP backend
version: 1.0.0
host: <ENDPOINT_URL>
schemes:
- https
produces:
- application/json
paths:
/hello-gae1:
get:
summary: Greet a user from App Engine
operationId: hello_gae
x-google-backend:
address: https://<PROJECT_ID>.appspot.com
parameters:
- in: query
name: name
required: false
type: string
responses:
'200':
description: A successful response
schema:
type: string
/hello-gae2:
get:
summary: Greet a user from App Engine
operationId: hello_gae
x-google-backend:
address: https://<SERVICE-dot-PROJECT_ID>.appspot.com
parameters:
- in: query
name: name
required: false
type: string
responses:
'200':
description: A successful response
schema:
type: string
/hello-gae3:
get:
summary: Greet a user from App Engine
operationId: hello_gae
x-google-backend:
address: https://<SERVICE-dot-PROJECT_ID>.appspot.com
parameters:
- in: query
name: name
required: false
type: string
responses:
'200':
description: A successful response
schema:
type: string
securityDefinitions:
# This section configures basic authentication with an API key.
api_key:
type: "apiKey"
name: "key"
in: "query"
References:
Getting Started with Endpoints for Cloud Run