I'm struggling to pass the path params from my gateway to the actual endpoints and I use PUT method to do the update and DELETE method to do a delete action in API which is built with Google Cloud Functions.
Here is my API Gateway Config:
# openapi2-functions.yaml
swagger: '2.0'
info:
title: cpd-api
description: API Gateway Config
version: 1.0.0
schemes:
- https
produces:
- application/json
paths:
/users/{user_id}:
put:
summary: Update an existing user
operationId: update-user-cpd
parameters:
- name: user_id
in: path
description: User ID to be updated
required: true
type: string
x-google-backend:
address: https://asia-southeast2-project-id.cloudfunctions.net/cpd_user
path_translation: APPEND_PATH_TO_ADDRESS
responses:
'200':
description: User updated successfully
schema:
type: object
delete:
summary: Delete an existing user
operationId: delete-user-cpd
parameters:
- name: user_id
in: path
description: User ID to be deleted
required: true
type: string
x-google-backend:
address: https://asia-southeast2-project-id.cloudfunctions.net/cpd_user
path_translation: APPEND_PATH_TO_ADDRESS
responses:
'200':
description: User deleted successfully
schema:
type: object
/users:
get:
summary: Get Users data
operationId: get-users-cpd
x-google-backend:
address: https://asia-southeast2-project-id.cloudfunctions.net/cpd_user
responses:
'200':
description: User retrieved successfully
schema:
type: object
post:
summary: Create a new user
operationId: create-user-cpd
x-google-backend:
address: https://asia-southeast2-project-id.cloudfunctions.net/cpd_user
responses:
'200':
description: User created successfully
schema:
type: object
If I call the Functions URL directly with PUT/DELETE method it's working as expected.
e.g. PUT https://asia-southeast2-project-id.cloudfunctions.net/cpd_user/1
But, when I try to call it by using the API Gateway URL it gives me below errors with 404 HTTP Status:
Cannot PUT /users/1
Cannot DELETE /users/1
Here is the cpd_user index.js file.
I use express JS to define the routes
const _utils = require('./utils');
const _controller = require('./controllers');
const express = require('express');
const app = express();
const UserController = new _controller.UserController();
app.get('/', _utils.jwtFilter, UserController.getUserAction);
app.post('/', UserController.createUserAction);
app.delete('/{user_id}', _utils.jwtFilter, UserController.deleteUserAction);
app.put('/{user_id}', _utils.jwtFilter, UserController.updateUserAction);
exports.cpdUser = app;
I have checked the log but I didn't get any useful info there.
Please help me to fix this issue.
Thanks.
Related
I'm trying to create the following api config for GCP API Gateway (ommitted real backend URL):
swagger: '2.0'
info:
title: upload
description: upload
version: 1.0.0
schemes:
- https
produces:
- application/json
security:
- api_key: [ ]
paths:
/upload:
post:
summary: uploads a file.
consumes:
- multipart/form-data
operationId: uploadFile
parameters:
- in: formData
name: file
description: The file to upload.
required: true
type: file
responses:
'200':
description: upload successful
x-google-backend:
address: https://XXXXX.XXXXX
path_translation: APPEND_PATH_TO_ADDRESS
securityDefinitions:
api_key:
type: "apiKey"
name: "key"
in: "query"
Running gcloud (replaced real variables with placeholders)
gcloud api-gateway api-configs create uploadconfig --api=[API] --openapi-spec=openapi.yaml --project=[MYPROJECT] --backend-auth-service-account=[ACCOUNT]
This results in this error message:
ERROR: (gcloud.api-gateway.api-configs.create) INVALID_ARGUMENT: Cannot convert to service config.
'location: "unknown location"
kind: ERROR
message: "http: repeated message field 'google.protobuf.Struct.fields' referred to by message 'UploadFileRequest' cannot be mapped as an HTTP parameter."
location: "unknown location"
kind: ERROR
message: "http: cyclic message field 'google.protobuf.Struct.FieldsEntry.value' referred to by message 'UploadFileRequest' in method 'method 1.xxxxxxx.UploadFile' cannot be mapped as an HTTP parameter."
I verified the gcloud command with a different api configurations and backends.
The config itself seems fine, i.e. it validates with Swagger editor, still gcloud won't accept it.
How do I define a file upload via API Gateway?
As per file upload Endpoints does not accept the type: file for file upload parameters, type: string should be used instead.And I tried config with changed parameters and it validates with Swagger editor results are here.
With SwaggerHub and OpenAPI 2 spec, this is how I got this file upload to work. Let me know if you have any questions. This is written in YAML as opposed to JSON spec. It took some reading and some experimentation, but here's the Gcloud documentation: https://cloud.google.com/storage/docs/uploading-objects
And the SwaggerHub info as well: https://swagger.io/docs/specification/2-0/file-upload/
/upload/storage/v1/b/{bucket}/o?uploadType=media&name={objectName}:
post:
summary: Upload an object directly to a bucket
description: Upload an object directly to a bucket
consumes:
- multipart/form-data
produces:
- application/json
parameters:
- in: header
name: Authorization
type: string
required: true
- in: path
name: bucket
required: true
type: string
description: The name of the bucket to upload to.
- in: path
name: objectName
required: true
type: string
description: The name the object will receive in the bucket.
- in: header
name: Content-Type
type: string
required: true
description: The content type of the upload. Ex. text/plain
- in: formData
name: fileToUpload
type: file
description: The file to upload.
responses:
200:
description: OK
204:
description: Success - No Content
400:
description: Bad Request
401:
description: Insufficient Privileges
404:
description: Not Found
It DOES accept type: file in the formData request.
I am trying to set up a GraqphQL Server, on Cloud Functions, and I want to set up an API Gateway to handle authentication with auth0 and jwt....
I have it working from the tutorial, the problem is it always requires a JWT token, where as I want some GraphQL queries to be available publicly, and if the user signs in they get more access...
From my understanding the way you do this is by using two authentications in the security settings, where one is the JWT and one is empty, however the API Gateway seems to always want the JWT token... Here is my open API spec, maybe someone has an idea?
swagger: '2.0'
info:
title: <redacted>-graphql-api
description: Basic GraphQL Open APISchema
version: 1.0.0
schemes:
- https
produces:
- application/json
securityDefinitions:
auth0_jwk:
authorizationUrl: "<redacted>"
flow: "implicit"
type: "oauth2"
# Replace YOUR-ACCOUNT-NAME with your Auth0 account name.
x-google-issuer: "<redacted>"
x-google-jwks_uri: "<redacted>"
# Optional. Replace YOUR-CLIENT-ID with your client ID
x-google-audiences: "<redacted>"
paths:
/:
post:
summary: GraphQL endpoint
operationId: gql
x-google-backend:
address: <redacted> # App URL/endpoint
responses:
'200':
description: A successful response
schema:
type: object
security:
- {}
- auth0_jwk: []
get:
summary: GraphQL Playground
operationId: playground
x-google-backend:
address: <redacted> # App URL/endpoint
responses:
'200':
description: A successful response
schema:
type: string
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.
My setup contains google-endpoints with google-cloud-functions as my backend.
Google endpoints is defined with the following swagger v2 yaml:
swagger: "2.0"
info:
description: "yada..."
version: "0.0.1"
title: "yadada.."
termsOfService: "http://swagger.io/terms/"
contact:
name: "blah"
email: "email#mail.com"
url: "https://example.com"
host: "(generated service url by google when endpoints is deployed, i.e. 'api-gateway-xyz123123-ew.a.run.app')"
tags:
- name: "Documents"
description: "blah"
schemes:
- "https"
paths:
/api/documents:
post:
tags:
- "Documents"
summary: "Add a new document"
description: ""
security:
- firebase: []
operationId: "addDocument"
x-google-backend:
address: "(cloud functions http url)/documents"
consumes:
- "application/json"
produces:
- "application/json"
parameters:
- in: "body"
name: "body"
description: "Document supplied"
required: true
schema:
$ref: "#/definitions/Document"
responses:
201:
description: "The document was successfully created."
schema:
$ref: "#/definitions/Document"
400:
description: "Invalid input. See response for details"
schema:
items:
$ref: "#/definitions/Error"
/api/documents/{document_id}:
get:
tags:
- "Documents"
summary: "Get a document with the given ID"
description: ""
security:
- firebase: []
operationId: "getDocument"
x-google-backend:
address: "(cloud function http url)/documents/"
path_translation: APPEND_PATH_TO_ADDRESS
produces:
- "application/json"
parameters:
- in: "path"
name: "document_id"
description: "ID of the document to modify"
required: true
type: "string"
responses:
200:
description: "success."
schema:
type: "array"
items:
$ref: "#/definitions/Document"
404:
description: "Document not found"
schema:
items:
$ref: "#/definitions/Error"
securityDefinitions:
firebase:
authorizationUrl: ""
flow: "implicit"
type: "oauth2"
x-google-issuer: "https://securetoken.google.com/%%GOOGLE_PROJECT_ID%%"
x-google-jwks_uri: "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
x-google-audiences: "%%GOOGLE_PROJECT_ID%%"
definitions:
(a lot of type definitions)
This works with the POST endpoint without any problems.
The problem is with the GET REST endpoint where the path variable is not passed correctly to the backend.
As in https://cloud.google.com/endpoints/docs/openapi/openapi-extensions I tried to add the x-google-backend parameter as in the swagger api above. (path_translation: APPEND_PATH_TO_ADDRESS).
However this does not work.
I get an Unauthorized Error (403) as the cloud function is not hit by the endpoints frontend.
Currently I use an ugly workaround without the path_translation parameter which translates the google endpoints path variable to a query parameter in the cloud function backend with the same name. I.e. in the backend the url /documents?document_id=xyz is called.
(What I try to achieve is to pass the call with the backend url /documents/{document_id})
Does anyone know how to configure path based parameters correctly so that they are passed correctly to the cloud function backend?
Thank you in advance.
Regards,
Sebastian
TL;DR:
I assume that your 403 error isn't the correct error. It should be a 404, but because the endpoint is unknown, I guess that 403 is answered.
Cloud Endpoint is frustrating about this behavior. With the path_translation: APPEND_PATH_TO_ADDRESS, you think that your final called address will be /documents/{document_id}, but NO. The full openAPI path is append to your backend address, in your case: /documents/api/documents/{document_id}
That's why the endpoint doesn't exist and you should have a 404 (and not a 403).
For more details, you can have a look to this page.
Note: I'm in relation with Google team on this topic, and it will take time before having an update on this behavior.
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