3

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;
    }
})
doktoroblivion
  • 428
  • 3
  • 14
  • 1
    Luasec is a pain... sadly. I've been using curl or libcurl - eg. https://luarocks.org/modules/luarocks/luacurl (though I don't know which one I actually used) – dualed Feb 24 '18 at 07:46
  • 1
    you should use https://github.com/pintsized/lua-resty-http#ssl_handshake – Alexander Altshuler Feb 24 '18 at 10:07
  • @AlexanderAltshuler I looked at that and also luasec (which looks like a pain), if I use ssl_handshake, which parameter do I use for the json payload containing the cert/ca/key? – doktoroblivion Feb 24 '18 at 21:27
  • @ErickGriffin you have a choice - verify the server certificate or not. When set to true, the server certificate will be verified according to the CA certificates specified by the lua_ssl_trusted_certificate directive. You may also need to adjust the lua_ssl_verify_depth. If your server requires client certificate (very seldom use case IMO) I don't know the way to do it using resty.http. – Alexander Altshuler Feb 25 '18 at 10:22
  • @AlexanderAltshuler, I tried handshake and it appears not to work. I checked my lua_ssl_protocols and its not set, so I cannot explain the error that its throwing which would indicate a protocol issue. I am updating the issue above for any additional comments/ideas by you. – doktoroblivion Feb 25 '18 at 19:48
  • @ErickGriffin Obviously you must connect before handshake – Alexander Altshuler Feb 26 '18 at 08:44
  • @AlexanderAltshuler if I put the ssl_handshake after the connect it appears the connect tries to do it on my behalf and of course fails. Therefore I am assuming I must go with something like luasec to gain full control over the actual progression of events here? – doktoroblivion Feb 26 '18 at 12:00

1 Answers1

5

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.

Alexander Altshuler
  • 2,930
  • 1
  • 17
  • 27
  • I agree and would like to use the above approach, but as I have mentioned in my update above, the connection requires not only a CA, but also a cert, key and password file. How are they provided on the above call? – doktoroblivion Feb 26 '18 at 22:56
  • I have used the above implementation and with the fact that the location/server must match the CN it will work. I will post my official fix in a bit for others. – doktoroblivion Feb 27 '18 at 18:28