Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ Domain-specific nameservers configuration, formatting keep compatible with Dnsma
More cases please refererence [dnsmasq-china-list](https://github.com/felixonmars/dnsmasq-china-list)


#### audit

Only redis storage backend is currently implemented.

Audit logs are in format:

```
{ "remoteaddr": "127.0.0.1", "domain": "domain.com", "qtype": "A", "timestamp": "2019-04-15T12:16:21.875492605Z" }
```

Backend uses lists to store logs in redis.

Logs grouped by hour.

Redis keys have format:

```
audit-YYYY-MM-DDTHH:00
```

Example request to get audit logs from redis:

```
LRANGE audit-2019-04-15T00:00 0 -1
```


#### cache

Only the local memory storage backend is currently implemented. The redis backend is in the todo list
Expand Down
155 changes: 155 additions & 0 deletions audit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package main

import (
"database/sql"
"encoding/json"
"fmt"
"time"

"github.com/hoisie/redis"
_ "github.com/lib/pq"
)

const AUDIT_LOG_OUTPUT_BUFFER = 1024

type AuditLogger interface {
Run()
Write(mesg *AuditMesg)
}

type AuditMesg struct {
RemoteAddr string `json:"remoteaddr"`
Domain string `json:"domain"`
QType string `json:"qtype"`
Timestamp time.Time `json:"timestamp"`
}

func NewAuditMessage(remoteAddr string, domain string, qtype string) *AuditMesg {
return &AuditMesg{
RemoteAddr: remoteAddr,
Domain: domain,
QType: qtype,
Timestamp: time.Now(),
}
}

type RedisAuditLogger struct {
backend *redis.Client
mesgs chan *AuditMesg
expire int64
}

func NewRedisAuditLogger(rs RedisSettings, expire int64) AuditLogger {
rc := &redis.Client{Addr: rs.Addr(), Db: rs.DB, Password: rs.Password}
auditLogger := &RedisAuditLogger{
backend: rc,
mesgs: make(chan *AuditMesg, AUDIT_LOG_OUTPUT_BUFFER),
expire: expire,
}
go auditLogger.Run()
return auditLogger
}

func (rl *RedisAuditLogger) Run() {
for {
select {
case mesg := <-rl.mesgs:
jsonMesg, err := json.Marshal(mesg)
if err != nil {
logger.Error("Can't write to redis audit log: %v", err)
continue
}
redisKey := fmt.Sprintf("audit-%s:00", mesg.Timestamp.Format("2006-01-02T15"))
err = rl.backend.Rpush(redisKey, jsonMesg)
if err != nil {
logger.Error("Can't write to redis audit log: %v", err)
continue
}
_, err = rl.backend.Expire(redisKey, rl.expire)
if err != nil {
logger.Error("Can't set expiration for redis audit log: %v", err)
continue
}
}
}
}

func (rl *RedisAuditLogger) Write(mesg *AuditMesg) {
rl.mesgs <- mesg
}

type PostgresqlAuditLogger struct {
backend *sql.DB
mesgs chan *AuditMesg
expire int64
}

func NewPostgresqlAuditLogger(ps PostgresqlSettings, expire int64) AuditLogger {
connStr := fmt.Sprintf(`
host=%s port=%d
user=%s password=%s
dbname=%s sslmode=%s
sslcert=%s sslkey=%s
sslrootcert=%s
`,
ps.Host, ps.Port,
ps.User, ps.Password,
ps.DB, ps.Sslmode,
ps.Sslcert, ps.Sslkey,
ps.Sslrootcert,
)
pc, err := sql.Open("postgres", connStr)
if err != nil {
logger.Error("Can't connect to audit log postgresql: %v", err)
}
rows, err := pc.Query(`
CREATE TABLE IF NOT EXISTS audit (
id BIGSERIAL NOT NULL,
remoteaddr TEXT,
domain TEXT,
qtype TEXT,
timestamp TIMESTAMP
)
`)
defer rows.Close()
auditLogger := &PostgresqlAuditLogger{
backend: pc,
mesgs: make(chan *AuditMesg, AUDIT_LOG_OUTPUT_BUFFER),
expire: expire,
}
go auditLogger.Run()
go auditLogger.Expire()
return auditLogger
}

func (pl *PostgresqlAuditLogger) Run() {
for {
select {
case mesg := <-pl.mesgs:
rows, err := pl.backend.Query(`INSERT INTO audit (remoteaddr, domain, qtype, timestamp) VALUES ($1, $2, $3, $4)`,
mesg.RemoteAddr, mesg.Domain, mesg.QType, mesg.Timestamp,
)
rows.Close()
if err != nil {
logger.Error("Can't write to postgresql audit log: %v", err)
continue
}
}
}
}

func (pl *PostgresqlAuditLogger) Write(mesg *AuditMesg) {
pl.mesgs <- mesg
}

func (pl *PostgresqlAuditLogger) Expire() {
for {
expireTime := time.Now().Add(time.Duration(-pl.expire) * time.Second)
rows, err := pl.backend.Query(`DELETE FROM audit WHERE timestamp < $1`, expireTime)
rows.Close()
if err != nil {
logger.Error("Can't expire postgresql audit log: %v", err)
}
time.Sleep(time.Duration(pl.expire) * time.Second / 2)
}
}
16 changes: 16 additions & 0 deletions etc/godns.conf
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ port = 6379
db = 0
password =""

[postgresql]
host = "127.0.0.1"
port = 5432
user = "godns"
password = "godns"
db = "godns"
sslmode = "require"
sslcert = ""
sslkey = ""
sslrootcert = ""

[memcache]
servers = ["127.0.0.1:11211"]

Expand All @@ -39,6 +50,11 @@ file = "./godns.log"
level = "INFO" #DEBUG | INFO |NOTICE | WARN | ERROR


# [audit]
# backend option [redis|postgresql|file]
# file backend not implemented yet
# backend = "redis"
# expire = 864000 # 10 days

[cache]
# backend option [memory|memcache|redis]
Expand Down
15 changes: 14 additions & 1 deletion handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type GODNSHandler struct {
resolver *Resolver
cache, negCache Cache
hosts Hosts
audit AuditLogger
}

func NewHandler() *GODNSHandler {
Expand Down Expand Up @@ -76,7 +77,14 @@ func NewHandler() *GODNSHandler {
hosts = NewHosts(settings.Hosts, settings.Redis)
}

return &GODNSHandler{resolver, cache, negCache, hosts}
var auditLogger AuditLogger

switch settings.Audit.Backend {
case "redis":
auditLogger = NewRedisAuditLogger(settings.Redis, settings.Audit.Expire)
}

return &GODNSHandler{resolver, cache, negCache, hosts, auditLogger}
}

func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
Expand All @@ -91,6 +99,11 @@ func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) {
}
logger.Info("%s lookup %s", remote, Q.String())

if h.audit != nil {
auditMesg := NewAuditMessage(remote.String(), Q.qname, Q.qtype)
h.audit.Write(auditMesg)
}

IPQuery := h.isIPQuery(q)

// Query hosts
Expand Down
33 changes: 26 additions & 7 deletions settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ var LogLevelMap = map[string]int{
type Settings struct {
Version string
Debug bool
Server DNSServerSettings `toml:"server"`
ResolvConfig ResolvSettings `toml:"resolv"`
Redis RedisSettings `toml:"redis"`
Memcache MemcacheSettings `toml:"memcache"`
Log LogSettings `toml:"log"`
Cache CacheSettings `toml:"cache"`
Hosts HostsSettings `toml:"hosts"`
Server DNSServerSettings `toml:"server"`
ResolvConfig ResolvSettings `toml:"resolv"`
Redis RedisSettings `toml:"redis"`
Memcache MemcacheSettings `toml:"memcache"`
Postgresql PostgresqlSettings `toml:"postgresql"`
Log LogSettings `toml:"log"`
Cache CacheSettings `toml:"cache"`
Hosts HostsSettings `toml:"hosts"`
Audit AuditSettings `toml:"audit"`
}

type ResolvSettings struct {
Expand All @@ -53,6 +55,18 @@ type RedisSettings struct {
Password string
}

type PostgresqlSettings struct {
Host string
Port int
DB string
User string
Password string
Sslmode string
Sslcert string
Sslkey string
Sslrootcert string
}

type MemcacheSettings struct {
Servers []string
}
Expand All @@ -67,6 +81,11 @@ type LogSettings struct {
Level string
}

type AuditSettings struct {
Expire int64
Backend string
}

func (ls LogSettings) LogLevel() int {
l, ok := LogLevelMap[ls.Level]
if !ok {
Expand Down