Creation of a server-less function based on the docs fails with 0.14 rc3
siamak ~ $ curl -i -X POST http://localhost:8001/services/plugin-testing/plugins --data "name=pre-function" --data "config.functions[][email protected]"
HTTP/1.1 400 Bad Request
Date: Wed, 04 Jul 2018 06:46:58 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.14.0rc3
Content-Length: 49
{"config.functions":"functions is not an array"}`
`
Version 0.1.1
0.14.0rc3)`
{
"plugins": {
"enabled_in_cluster": [
],
"available_on_server": {
"response-transformer": true,
"oauth2": true,
"acl": true,
"correlation-id": true,
"pre-function": true,
"jwt": true,
"cors": true,
"ip-restriction": true,
"basic-auth": true,
"key-auth": true,
"rate-limiting": true,
"request-transformer": true,
"http-log": true,
"file-log": true,
"hmac-auth": true,
"ldap-auth": true,
"datadog": true,
"tcp-log": true,
"zipkin": true,
"post-function": true,
"request-size-limiting": true,
"bot-detection": true,
"syslog": true,
"loggly": true,
"azure-functions": true,
"udp-log": true,
"response-ratelimiting": true,
"aws-lambda": true,
"statsd": true,
"prometheus": true,
"request-termination": true
}
},
"tagline": "Welcome to kong",
"configuration": {
"plugins": [
"bundled"
],
"admin_ssl_enabled": true,
"lua_ssl_verify_depth": 1,
"trusted_ips": {
},
"prefix": "/usr/local/kong",
"loaded_plugins": {
"response-transformer": true,
"request-termination": true,
"prometheus": true,
"ip-restriction": true,
"pre-function": true,
"jwt": true,
"cors": true,
"statsd": true,
"basic-auth": true,
"key-auth": true,
"ldap-auth": true,
"aws-lambda": true,
"http-log": true,
"response-ratelimiting": true,
"hmac-auth": true,
"request-size-limiting": true,
"datadog": true,
"tcp-log": true,
"zipkin": true,
"post-function": true,
"bot-detection": true,
"acl": true,
"loggly": true,
"syslog": true,
"azure-functions": true,
"udp-log": true,
"file-log": true,
"request-transformer": true,
"correlation-id": true,
"rate-limiting": true,
"oauth2": true
},
"cassandra_username": "kong",
"admin_ssl_cert_csr_default": "/usr/local/kong/ssl/admin-kong-default.csr",
"ssl_cert_key": "/usr/local/kong/ssl/kong-default.key",
"admin_ssl_cert_key": "/usr/local/kong/ssl/admin-kong-default.key",
"dns_resolver": {
},
"pg_user": "kong",
"mem_cache_size": "128m",
"cassandra_data_centers": [
"dc1:2",
"dc2:3"
],
"nginx_admin_directives": {
},
"custom_plugins": {
},
"pg_host": "127.0.0.1",
"nginx_acc_logs": "/usr/local/kong/logs/access.log",
"proxy_listen": [
"0.0.0.0:8000",
"0.0.0.0:8443 ssl"
],
"client_ssl_cert_default": "/usr/local/kong/ssl/kong-default.crt",
"ssl_cert_key_default": "/usr/local/kong/ssl/kong-default.key",
"dns_no_sync": false,
"db_update_propagation": 0,
"nginx_err_logs": "/usr/local/kong/logs/error.log",
"cassandra_port": 9042,
"dns_order": [
"LAST",
"SRV",
"A",
"CNAME"
],
"dns_error_ttl": 1,
"headers": [
"server_tokens",
"latency_tokens"
],
"dns_stale_ttl": 4,
"nginx_optimizations": true,
"database": "cassandra",
"pg_database": "kong",
"nginx_worker_processes": "auto",
"lua_package_cpath": "",
"admin_acc_logs": "/usr/local/kong/logs/admin_access.log",
"lua_package_path": "./?.lua;./?/init.lua;",
"nginx_pid": "/usr/local/kong/pids/nginx.pid",
"upstream_keepalive": 60,
"cassandra_contact_points": [
"127.0.0.1"
],
"client_ssl_cert_csr_default": "/usr/local/kong/ssl/kong-default.csr",
"proxy_listeners": [
{
"ssl": false,
"ip": "0.0.0.0",
"proxy_protocol": false,
"port": 8000,
"http2": false,
"listener": "0.0.0.0:8000"
},
{
"ssl": true,
"ip": "0.0.0.0",
"proxy_protocol": false,
"port": 8443,
"http2": false,
"listener": "0.0.0.0:8443 ssl"
}
],
"proxy_ssl_enabled": true,
"admin_access_log": "logs/admin_access.log",
"ssl_ciphers": "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256",
"enabled_headers": {
"latency_tokens": true,
"X-Kong-Proxy-Latency": true,
"Via": true,
"server_tokens": true,
"Server": true,
"X-Kong-Upstream-Latency": true,
"X-Kong-Upstream-Status": false
},
"cassandra_ssl": false,
"ssl_cert_csr_default": "/usr/local/kong/ssl/kong-default.csr",
"db_resurrect_ttl": 30,
"client_max_body_size": "0",
"cassandra_consistency": "ONE",
"db_cache_ttl": 0,
"admin_error_log": "logs/error.log",
"pg_ssl_verify": false,
"dns_not_found_ttl": 30,
"pg_ssl": false,
"client_ssl": false,
"db_update_frequency": 5,
"cassandra_repl_strategy": "SimpleStrategy",
"nginx_kong_conf": "/usr/local/kong/nginx-kong.conf",
"cassandra_repl_factor": 1,
"nginx_http_directives": [
{
"value": "prometheus_metrics 5m",
"name": "lua_shared_dict"
}
],
"error_default_type": "text/plain",
"kong_env": "/usr/local/kong/.kong_env",
"real_ip_header": "X-Real-IP",
"dns_hostsfile": "/etc/hosts",
"admin_listeners": [
{
"ssl": false,
"ip": "127.0.0.1",
"proxy_protocol": false,
"port": 8001,
"http2": false,
"listener": "127.0.0.1:8001"
},
{
"ssl": true,
"ip": "127.0.0.1",
"proxy_protocol": false,
"port": 8444,
"http2": false,
"listener": "127.0.0.1:8444 ssl"
}
],
"admin_ssl_cert": "/usr/local/kong/ssl/admin-kong-default.crt",
"ssl_cert": "/usr/local/kong/ssl/kong-default.crt",
"proxy_access_log": "logs/access.log",
"admin_ssl_cert_key_default": "/usr/local/kong/ssl/admin-kong-default.key",
"cassandra_ssl_verify": false,
"cassandra_lb_policy": "RoundRobin",
"ssl_cipher_suite": "modern",
"real_ip_recursive": "off",
"proxy_error_log": "logs/error.log",
"client_ssl_cert_key_default": "/usr/local/kong/ssl/kong-default.key",
"nginx_daemon": "on",
"anonymous_reports": true,
"cassandra_timeout": 5000,
"nginx_proxy_directives": {
},
"pg_port": 5432,
"log_level": "notice",
"client_body_buffer_size": "8k",
"cassandra_schema_consensus_timeout": 10000,
"lua_socket_pool_size": 30,
"admin_ssl_cert_default": "/usr/local/kong/ssl/admin-kong-default.crt",
"cassandra_keyspace": "kong",
"ssl_cert_default": "/usr/local/kong/ssl/kong-default.crt",
"nginx_conf": "/usr/local/kong/nginx.conf",
"admin_listen": [
"127.0.0.1:8001",
"127.0.0.1:8444 ssl"
]
},
"version": "0.14.0rc3",
"node_id": "53bf3f70-b888-4304-bb3c-fa2f1c31ea65",
"lua_version": "LuaJIT 2.1.0-beta3",
"prng_seeds": {
"pid: 3209": 175111114184,
"pid: 3206": 107229206922,
"pid: 3208": 129146795423,
"pid: 3207": 334158391492
},
"timers": {
"pending": 3,
"running": 0
},
"hostname": "siamak"
}
`
I could bypass the error by calling
` curl -X POST http://localhost:8001/services/plugin-testing/plugins -F 'name=pre-function' -F '[email protected]'
`
But the suggested lua-code
-- Get list of request headers
local custom_auth = kong.request.get_header("x-custom-auth")
-- Terminate request early if our custom authentication header
-- does not exist
if not custom_auth then
return kong.response.exit(401, "Invalid Credentials")
end
-- Remove custom authentication header from request
kong.request.clear_header('x-custom-auth')
is not fine, Lua does not pass schema because of this line:
return kong.response.exit(401, "Invalid Credentials")
It says
' expected near '<eof>'"}
Removing this line creates the plugin, but without this line, it is not useful
@rahimimo The issue arises because of a comma character in the LOC.
Please escape the comma as follows:
return kong.response.exit(401\, "Invalid Credentials")
The doc does state this but in spirit of reducing friction, we should update the doc to make it more clear or the snippet should have an escaped comma.
We value PRs on our documentation site.
Thank you.
@rahimimo thank you for pointing this out, and @hbagdi thank you for assisting in debugging the issue. It is indeed an issue with escaping, and sending information using form-data.
I've gone ahead and updated the plugin documentation in this PR:
https://github.com/Kong/docs.konghq.com/pull/776
I will make an attempt in another PR to highlight / better address the escaping.
Thanks @nijikokun , After sending the form I noticed I keep getting error 500 Internal server error and this is because there is no Lua require in the code present in the documentation, pnce I looked at the plugins in the kong and I started copy pasting blindly because I do not know Lua, I could bypass that error 500 but I could not achieve what I wanted in the docs,
This is my dummy code
local singletons = require "kong.singletons"
local BasePlugin = require "kong.plugins.base_plugin"
local responses = require "kong.tools.responses"
local constants = require "kong.constants"
local jwt_decoder = require "kong.plugins.jwt.jwt_parser"
local app_helpers = require "lapis.application"
local ipairs = ipairs
local string_format = string.format
local ngx_re_gmatch = ngx.re.gmatch
local ngx_set_header = ngx.req.set_header
local get_method = ngx.req.get_method
local JwtHandler = BasePlugin:extend()
JwtHandler.PRIORITY = 1005
JwtHandler.VERSION = "0.1.0"
local custom_auth = kong.request.get_header("x-custom-auth")
local bar = "http://nordix.fi"
responses.send_HTTP_OK {
data = bar
}
I could not use kong.request, I checked basic-auth and noticed, it is acutally using ngx.req for returning.
All in all, I wonder how the docs for serverless function has been written with sth which could not work at all :)
@rahimimo I just followed the updated documentation and I am receiving the correct responses: "Invalid Credentials" or the upstream response as intended. Here is the updated custom-auth.lua from that document:
-- Get list of request headers
local custom_auth = kong.request.get_header("x-custom-auth")
-- Terminate request early if our custom authentication header
-- does not exist
if not custom_auth then
return kong.response.exit(401\, "Invalid Credentials")
end
-- Remove custom authentication header from request
kong.service.request.clear_header('x-custom-auth')
As far as basic-auth using ngx.req where serverless-functions do not, the documentation for serverless-functions is taking advantage of the newly released Plugin Development Kit (PDK) in 0.14.
In regards to the code you posted... Are you trying to return a JSON body with the the value bar? If that's the case, this is all you would need (replacing the entire contents of the custom-auth.lua):
kong.response.exit(200\, {
data = "http://nordix.fi"
})
I've gone ahead and updated your comment to properly format your code as well.
@nijikokun the above code was just a test trying to avoid internal server error.
Now I just try to implement the docs to test.
This is my custo-auth.lua code
-- Get list of request headers
local custom_auth = kong.request.get_header("x-custom-auth")
-- Terminate request early if our custom authentication header
-- does not exist
if not custom_auth then
return kong.response.exit(401\, "Invalid Credentials")
end
-- Remove custom authentication header from request
kong.service.request.clear_header('x-custom-auth')
I get Invalid credentials no matter the request contain x-custom-auth or not.
siamak ~ $ curl -i -X GET http://localhost:8000/test --header "x-custom-auth=key"
HTTP/1.1 401 Unauthorized
Date: Thu, 05 Jul 2018 08:45:48 GMT
Connection: keep-alive
Server: kong/0.14.0rc3
Content-Length: 19
@nijikokun Why there is kong.service.request in your last line but not in the first line ?
@rahimimo you'll want to use : instead of =:
â–¶ curl -i -H "x-custom-auth:demo" http://localhost:8000/test
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 129
Connection: keep-alive
Server: gunicorn/19.8.1
Date: Thu, 05 Jul 2018 08:49:36 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Via: kong/0.14.0rc1
X-Kong-Upstream-Latency: 176
X-Kong-Proxy-Latency: 0
{"headers":{"Accept":"*/*","Connection":"close","Host":"httpbin.org","User-Agent":"curl/7.54.0","X-Forwarded-Host":"localhost"}}
Why there is kong.service.request in your last line but not in the first line ?
Good question, very good answer as well, it also caught me off-guard:
kong.request - A set of functions to retrieve information about the incoming requests made by clients.kong.service.request - The service.request module contains a set of functions for dealing with the request made by Kong to the Service.The first will read the request made to the proxy, and the second modifies the request made upstream to ensure the header isn't passed. Hopefully this will improve once documentation for the PDK is publicly released, for now the code is the best source of reference.
@nijikokun thanks, the docs have the = one. It is now fine with :. thanks.
So now I have the docs working. :)
I have some questions
The original need was to mix jwt & acl so that my jwt contains the service it wants to achieve.
This is needed because there is a security bug with using jwt & acl. The bug in simple words is if a consumer wants to access two protected services (service1 & service2) , For monetization purposes the jwt1 has 1 month expiry and the jwt2 has 2 month. So after 1,5 month he can simply change the tokens, because the jwt is expired but ACL does not have any expiration time. So by changing the tokens he can access the service which he should not.
So I tried to use the serverless functions instead of writing the custom-plugin from scratch. But I did not find any docs of both development kit and which phase of kong it is and what access and what scope does the serverless function or even a custom plugin have.
So lots to unpack there:
state I assume you mean phase?pre-function is the first plugin to runpost-function is always the last plugin to runpre-function or a post-function to attach the service id (ngx.ctx.service.id) as a header or some other property.lua
kong.service.request.set_header('x-service-id', ngx.ctx.service.id)
post-function you have access to an authenticated_credential if it exists:lua
kong.response.exit(200, ngx.ctx.authenticated_credential.consumer_id)
Thanks a lot it is clear. Just about question 2 and scopes:
When a request come to kong, kong does route matching and as a result a service will be selected. We have also enabled plugins going on. A plugin such as ACL must know which service is chosen, so that it checks from db if it exists in its whitelist. A plugin such as basic-auth does not need to do so.
I am plannig to write a (serverless function or custom plugin) where I mix JWT and ACL, this is needed because there is a security problem in using timed jwt and acl.
Simply the idea is to bind jwt tokens to both consumer and target service. Now they are bound to consumer and ACL is bound to consumer and service with no password.
However, I do not know or have access to a documentation of how to write this plugin, and even I do now know if in a serverless function, post-function at the very end of call sequence before making the actual upstream API call, I have access to matched service or jwt or other plugins data or kong code data or not.
By explaining the problem, I do not want a soloution of course, I want to explain what info are missing from a developer point of view :)
To make it a little more clearer, Kong runs plugins during multiple phases, each phase corresponds to different nginx phases.
They are in the following order:
ngx.ctx.servicengx.ctx.apingx.ctx.routepre-function (has access to all variables declared above, IF SET)ngx.ctx.consumer / ngx.ctx.authenticated_consumerngx.ctx.authenticated_credentialngx.ctx.authenticated_jwt_tokenpost-function (has access to all variables declared above, IF SET)Hopefully this makes it a little more clearer, the state that exists when the plugins are ran.
IF SET mostly refers to the authentication plugins, when there is no authentication, there will be no consumer defined or credential used. Also, if you're not using the apis entity, you won't get the api variable either.
Documentation has been updated and merged https://github.com/Kong/docs.konghq.com/pull/776
Considering this issue closed