Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dnsdist: provide an easy way to flush a cache by domain & RR type #10468

Closed
dch opened this issue Jun 4, 2021 · 7 comments
Closed

dnsdist: provide an easy way to flush a cache by domain & RR type #10468

dch opened this issue Jun 4, 2021 · 7 comments

Comments

@dch
Copy link

dch commented Jun 4, 2021

  • Program: dnsdist
  • Issue type: Feature request

how to expose lua functions over HTTP API?

Usecase

  • flush DNS caches safely via RESTish API
  • this functionality is completely possible by the console socket already with getPool(""):GetCache():expungeByName(...)
  • but there is no safety/granularity here, if you can get the socket it's security game over

Description

The actual method/url/response etc is not important, this is just an example:

# drop all cached NS RR for some.doma.in 
DELETE /api/v1/cache/some.doma.in/NS
200 OK
{"purged": true, "records": "300"}
# drop all cached RR for some.doma.in
DELETE /api/v1/cache/some.doma.in
200 OK
{"purged": true, "records": "5000"}

Actually you can already do 90% of this in lua

I did actually succeed in doing this with lua (thanks IRC folks) already, but I may have "deviated somewhat from acceptable HTTP usage". This was necessary because:

  • all non-GET methods return 405 Invalid Method
  • my original URL path mapping is not possible as no wild-card matching of URLs is possible
  • I tried really hard to return the number of expunged entries, but lua type defeated me
webserver('[::1]:10053')
setWebserverConfig({
        password='...',
        apiKey='...',
        statsRequireAuthentication=false,
        custom_headers={},
        acl='.../40'
    })
-- listen out for custom URL handlers
function flushCacheHandler(req, resp)
    local get = req.getvars
    local headers = req.headers
    local rr = get['rr']
    local name = get['name']
    local purged

    resp.headers = { ['content-type']='application/json'}

    if req.method ~= 'GET' or name == '' then
        print('400  GET /api/v1/cache')
        resp.status = 400
        resp.body = '{"status": "denied", "error": "query parameter <name> must be supplied, and method must be GET"}'
        return
    end

    if rr ~= 'A' and rr ~= 'AAAA' and rr ~= 'CNAME' and rr ~= 'TXT' and rr ~= 'NS' and rr ~= nil then
        resp.status = 400
        resp.body = '{"status": "denied", "error": "query parameter <rr> must be one of A, AAAA, CNAME, TXT, NS, or not supplied"}'
        return
    end

    if rr == nil then
        print('GET /api/v1/cache - expiring * for '..name)
        purged = getPool(""):getCache():expungeByName(name, DNSQType.ANY, true)
        print(type(purged))
        resp.status = 202
    else
        print('GET /api/v1/cache - expiring '..rr..' for '..name)
        purged = getPool(""):getCache():expungeByName(name, DNSQType[rr], true)
        print(type(purged))
        resp.status = 202
    end

--    if purged == nil then
--        resp.body = '{"status": "purged","count":0}'
--        print('cache purged 0 records for '..name)
--    else
-- I tried really hard to get the number of expired entries (purged) printed but lua defeated me
        resp.body = '{"status": "purged","count":'..tostring(purged)..'}'
        print('cache purged '..tostring(purged)..' records for '..name)
--    end
    resp.headers = { ['content-type']='application/json'}
end

this produces the following output in the console:

Got a connection to the webserver from [ip6::1]:61970
Webserver handling connection from [ip6::1]:61970
GET /api/v1/cache - expiring NS for example.net
cache purged nil records for example.net
Got query for example.net|NS from ...:61923, relayed to i09
Got answer from [ip6::10]:10053, relayed to ...:61923, took 25829.7 usec

Got a connection to the webserver from [ip6::1]:62155
Webserver handling connection from [ip6::1]:62155
GET /api/v1/cache - expiring * for example.net
cache purged nil records for example.net
Got query for www.example.com|A from 176.9.23.4:25947, relayed to i09
Got answer from [ip6::10]:10053, relayed to 176.9.23.4:25947, took 25334.4 usec
Got query for .|ANY from 139.99.125.133:18966, relayed to i09
Got query for .|ANY from 139.99.125.133:18966, relayed to i09

This was so incredibly easy & fun, I really hope we can find more ways (methods!) to extend dnsdist, this was a great friday afternoon hack.

@dch
Copy link
Author

dch commented Jun 4, 2021

wrt the comment about lua defeating me, after IRC enlightenment.

The c++ for expungeByName() seems to return an int-y sort of thing,
I even tried print(type(purged)) and got ... nil.

@Habbie explains that the c++ function returns void/nil and not the
number of expunged records.

g_outputBuffer="Expunged " + std::to_string(cache->expungeByName(qname, qtype ? *qtype : QType(QType::ANY).getCode(), suffixMatch ? *suffixMatch : false)) + " records\n";

It would be trivial to change this so that the function also returns something
.. in which case the console would give you the number twice.

@rgacogne
Copy link
Member

rgacogne commented Jun 7, 2021

Seems like a duplicated of #6154, except with a nice piece of Lua as a work-around :-) For what it's worth, I think we should consider adding this to the API for 1.7.0, since the v2 of the API has not made any progress.

@dch
Copy link
Author

dch commented Jun 7, 2021

A more generic option might be useful for extending via lua in general #10480 for that.

@rgacogne rgacogne added this to the dnsdist-1.8.0 milestone Aug 30, 2022
@mayeter
Copy link

mayeter commented Dec 30, 2022

Hi, @dch I would like to try your work at my setup, but since I am a Lua noob I couldn't figure out myself, how do you make this function be called actually 😓 since there is still time for 1.8.0 I tought I could use this code for now.

@dch
Copy link
Author

dch commented Dec 30, 2022

@callMe-Root let's move this out of the issue. https://gist.github.com/dch/6f4fe79db178393130346719e7dff6ff is as much of the config as I can share, and a usage example. You need to create the hashed API key via dnsdist console, but the rest is basically copy-pasta to the end of your existing config. BTW this is the first lua I wrote, so the learning bar is reasonably low here.

@ryoshou
Copy link

ryoshou commented Jan 13, 2023

you can workaround with this solution:

-- save current cache entries
local beforeClearCache = getPool(""):getCache():toString()
beforeClearCache = string.match(beforeClearCache, "(.-)/")

getPool(""):getCache():expungeByName(name, DNSQType.ANY, true)

-- save cache entries after clear
afterClearCache = getPool(""):getCache():toString()
afterClearCache = string.match(afterClearCache, "(.-)/")

-- total cache entries flushed
local count = tonumber(beforeClearCache) - tonumber(afterClearCache)

@rgacogne
Copy link
Member

Implemented in #12473.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants