openresty / lua-resty-mysql

Nonblocking Lua MySQL driver library for ngx_lua or OpenResty
708 stars 236 forks source link

Hardcoded query works but API disabled in the current context when using a variable #124

Closed luvpreetsingh closed 3 years ago

luvpreetsingh commented 3 years ago

Details

Scenario

I have my ssl certs in a mysql database. I am trying to set the ssl certs based on the domain names which are being accessed. So, I wanted to use lua-resty-mysql. I tested it with a single domain first and it was working fine. Here is the code,

server {
    listen       443 ssl;
    server_name _;

    access_log /etc/nginx/access.log;
    error_log /etc/nginx/error.log;
    ssl_certificate /etc/nginx/conf.d/cert; # init dummy cert
    ssl_certificate_key /etc/nginx/conf.d/key; # init dummy key
    ssl_certificate_by_lua_block {

    local mysql = require "resty.mysql"

    -- initiating a new mysql connection
    local db, err = mysql:new()
    if not db then
        ngx.say("failed to instantiate mysql: ", err)
        return
    end

    db:set_timeout(1000) -- 1 sec

    -- connecting to mysql db
    local ok, err, errcode, sqlstate = db:connect{
        host = "****",
        port = 3306,
        database = "ssl_certs",
        user = "root",
        password = "****",
        charset = "utf8",
        max_packet_size = 1024 * 1024,
    }

    if not ok then
        ngx.say("failed to connect: ", err, ": ", errcode, " ", sqlstate)
        return
    end

     -- .. is the concatenate operator of strings in lua
    local db_query = "select pkey, cert_chain from domains where name = myown.domain.com"
    -- run query to get cert and key from mysql
    res, err, errcode, sqlstate =
        db:query(db_query, 1)
    if not res then
        ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
        return
    end

    -- capturing cert_key and cert_chain from the query output
    local cert_key_pem = res[1]["pkey"]
    local cert_chain_pem = res[1]["cert_chain"]

    -- sending connection back to the connection pool
    local ok, err = db:set_keepalive(10000, 100)
    if not ok then
        ngx.say("failed to set keepalive: ", err)
        return
    end

    local ssl = require "ngx.ssl"

    -- clearing the current certs which are set by default
    local ok, err = ssl.clear_certs()
    if not ok then
        ngx.log(ngx.ERR, "failed to clear existing (fallback) certificates")
       return ngx.exit(ngx.ERROR)
    end

    -- converting cert chain from pem to der format
    local cert_chain_der, err = ssl.cert_pem_to_der(cert_chain_pem)
    if not cert_chain_der then
        ngx.log(ngx.ERR, "failed to convert certificate chain ",
                "from PEM to DER: ", err)
        return ngx.exit(ngx.ERROR)
    end

    -- converting private key from pem to der format
    local cert_key_der, err = ssl.priv_key_pem_to_der(cert_key_pem)
    if not cert_key_der then
        ngx.log(ngx.ERR, "failed to convert private key ",
                "from PEM to DER: ", err)
        return ngx.exit(ngx.ERROR)
    end

    -- setting cert chain
    local ok, err = ssl.set_der_cert(cert_chain_der)
    if not ok then
        ngx.log(ngx.ERR, "failed to set DER cert: ", err)
        return ngx.exit(ngx.ERROR)
    end

    -- setting private key
    local ok, err = ssl.set_der_priv_key(cert_key_der)
    if not ok then
        ngx.log(ngx.ERR, "failed to set DER private key: ", err)
        return ngx.exit(ngx.ERROR)
    end
    }

    location / {
        root   /usr/local/openresty/nginx/html;
        index  index.html index.htm;
    }
}

This worked very well for me. But then I made a single change in my query. Here is the change,

    local domain_name = ngx.quote_sql_str(ngx.var.host)
    -- .. is the concatenate operator of strings in lua
    local db_query = "select pkey, cert_chain from domains where name = " .. domain_name

I just wanted to take the domain name from the host variable so that all the domains in my db can get served. But I started getting the error below,

2021/06/14 06:05:07 [error] 95#95: *2 lua entry thread aborted: runtime error: ssl_certificate_by_lua:31: API disabled in the current context
stack traceback:
coroutine 0:
    [C]: in function 'error'
    /usr/local/openresty/lualib/resty/core/var.lua:103: in function '__index'
    ssl_certificate_by_lua:31: in main chunk, context: ssl_certificate_by_lua*, client: 10.128.0.82, server: 0.0.0.0:443

This error is repeated 4 times during a single call.

I have seen this issue here. I get that this should not work in the set_by_lua context. And I think in my code, when I am using set_der_cert and set_der_priv_key, it might be using the set context.

Firstly, I have a question as to why it works when the domain name is fixed in the query? And what changes when I start to take the domain name from the variable?

Secondly, in the issue I mentioned, they suggested using rewrite_by_lua. But I am not able to figure out how can I use rewrite_by_lua in my scenario.

Thank you for your time. Any help is appreciated.

luvpreetsingh commented 3 years ago

I think the issue comes when I try to access ngx.var.host

luvpreetsingh commented 3 years ago

Document ssl_session_fetch_by_lua_block

Lua APIs that may yield, like ngx.sleep and cosockets, are enabled in this context.

luvpreetsingh commented 3 years ago

Hmm, maybe I was quick to open this issue here but I think the error lead me to think there was an issue with the library and not my code.

I have got this covered. I don't know why I was using ngx.var.host but ngx.ssl provide a built in method to get the domain name inside this block, here which I was not aware of earlier. I did not read the documentation clearly.

Sorry to create an issue for this. I feel stupid but also not so stupid because I have finally got it working.

Thank you for this repo.