Reputation: 428
I am trying to find a way to make a secure GET request from within my nginx/lua module out to a server to check an ingress call's authentication. There seems to be very little out there on how to do this. My current attempts center around using resty.http and the following.
-- Create http client
local httpc = http.new();
httpc:set_timeout(500)
ngx.log(ngx.DEBUG, '**** Connect with TLS ****');
ok, err = httpc:connect(my_server, port);
my_server however requires a cert, ca, and key on input, but again, not sure how to do that, using ["ca"] = myca.pem; etc... does not work. If I set
lua_ssl_trusted_certificate=myca.pem
the request fails with the following:
2018/02/23 19:22:17 [crit] 19#0: *4 SSL_shutdown() failed (SSL: error:140E0197:SSL routines:SSL_shutdown:shutdown while in init), client: 127.0.0.1, server: my_server
I have looked at https://github.com/brunoos/luasec/archive/luasec-0.6, but frankly could not get it to compile clean on my alpine linux container. Not sure what module to use or how to proceed at this point, any ideas?
UPDATE Additional information based on comments and an answer I have received. Attempts to use openresty pintsized lua-resty-http with https failed, there is just no way currently to get cert/key/ca mutual TLS flow to work. Using the answer below I was able to configure an upstream proxy call to my backend application microservice to correctly service the requests. My proxy.conf file snippets to get this to work look like the followwing. Note: the upstream server has to match the CN in the certificates, otherwise it will not work, or at least I got nothing by certificate failures indicating such.
http {
# Upstream backend for services
upstream apimy {
server apimy:3003;
}
...
location /api/analytics/token-info {
internal; # Specifies that a given location can only be used for internal requests
set $bearerToken $arg_token;
set $args "";
proxy_pass_request_headers on;
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
proxy_ssl_ciphers HIGH:!aNULL:!MD5;
proxy_ssl_certificate /etc/certs/analytics_proxy_client_public.cert.pem;
proxy_ssl_certificate_key /etc/certs/analytics_proxy_client_private.key.pem;
proxy_ssl_trusted_certificate /etc/certs/analytics_proxy_client_ca.cert.pem;
proxy_ssl_verify on;
proxy_ssl_verify_depth 2;
proxy_ssl_session_reuse off;
proxy_set_header Host apimy;
proxy_set_header Content-Type "application/json";
proxy_set_header Accept "application/json";
proxy_set_header Authorization "Bearer $bearerToken";
proxy_pass https://apimy/api/analytics/token-info; # trailing slash
}
Another note, for whatever I could not get the bearerToken to work passing it in as a vars from the lua side, I had to pass it in as an arg, then clear out the args after that so any others did not get passed into the call. My lua code that calls the path.
-- Connect --
ngx.log(ngx.DEBUG, '**** Connect with TLS ****');
res = ngx.location.capture('/api/analytics/token-info',
{
method = ngx.HTTP_POST,
body = json.encode(query),
args = {
token = accessToken;
}
})
Upvotes: 3
Views: 10600
Reputation: 3064
Forget about luasec, your last code snippet doesn't make sense.
LuaSec is compartible with LuaSocket and definetely not compartible with nginx cosocket API.
Use generic resty-http request_uri()
interface:
local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:request_uri("https://example.com:443/helloworld", {
method = "POST",
body = "a=1&b=2",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
},
ssl_verify = true
})
BTW, request_uri() source is the perfect example how to use generic resty-http API (connect/ssl_handshake/request)
Update:
If you definitely need to authenticate client by certificate you may use the next approach:
Create upstream, configure client certificate:
location /my_upstream/ {
internal; # Specifies that a given location can only be used for internal requests
proxy_pass_request_headers off;
proxy_set_header Host backend.example.com;
proxy_set_header Content-Type "application/json";
proxy_set_header Accept "application/json";
proxy_set_header Authorization "Bearer $bearerToken"
proxy_pass https://backend.example.com/; # trailing slash
proxy_ssl_certificate /etc/nginx/client.pem;
proxy_ssl_certificate_key /etc/nginx/client.key
proxy_ssl_verify on;
proxy_ssl_verify_depth 2; #just example
proxy_ssl_trusted_certificate /etc/nginx/ca.pem;
}
Use ngx.location.capture API to issue a synchronous but still non-blocking Nginx Subrequest:
local res = ngx.location.capture('/my_upstream/login',
{
method = ngx.HTTP_POST,
body = "some text",
vars = {
bearerToken = "12345"
}
})
Please, don't flood, RTFM about all details of ngx.location.capture API and proxy_pass
PS: Looking to your last update - cannot believe that somebody will require both client certificate and authorization by token together. Usually HTTPS without client certificate is used for secure transport and Authorization header with token for authentication.
Upvotes: 5