+ qlog: hide_client_ip setting
This commit is contained in:
parent
c5a39b942f
commit
2e845e4f4d
|
@ -1287,12 +1287,22 @@ Request:
|
||||||
{
|
{
|
||||||
"enabled": true | false
|
"enabled": true | false
|
||||||
"interval": 1 | 7 | 30 | 90
|
"interval": 1 | 7 | 30 | 90
|
||||||
|
"anonymize_client_ip": true | false // anonymize clients' IP addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
Response:
|
Response:
|
||||||
|
|
||||||
200 OK
|
200 OK
|
||||||
|
|
||||||
|
`anonymize_client_ip`:
|
||||||
|
1. New log entries written to a log file will contain modified client IP addresses. Note that there's no way to obtain the full IP address later for these entries.
|
||||||
|
2. `GET /control/querylog` response data will contain modified client IP addresses (masked /24 or /112).
|
||||||
|
3. Searching by client IP won't work for the previously stored entries.
|
||||||
|
|
||||||
|
How `anonymize_client_ip` affects Stats:
|
||||||
|
1. After AGH restart, new stats entries will contain modified client IP addresses.
|
||||||
|
2. Existing entries are not affected.
|
||||||
|
|
||||||
|
|
||||||
### API: Get querylog parameters
|
### API: Get querylog parameters
|
||||||
|
|
||||||
|
@ -1307,6 +1317,7 @@ Response:
|
||||||
{
|
{
|
||||||
"enabled": true | false
|
"enabled": true | false
|
||||||
"interval": 1 | 7 | 30 | 90
|
"interval": 1 | 7 | 30 | 90
|
||||||
|
"anonymize_client_ip": true | false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@ type dnsConfig struct {
|
||||||
QueryLogEnabled bool `yaml:"querylog_enabled"` // if true, query log is enabled
|
QueryLogEnabled bool `yaml:"querylog_enabled"` // if true, query log is enabled
|
||||||
QueryLogInterval uint32 `yaml:"querylog_interval"` // time interval for query log (in days)
|
QueryLogInterval uint32 `yaml:"querylog_interval"` // time interval for query log (in days)
|
||||||
QueryLogMemSize uint32 `yaml:"querylog_size_memory"` // number of entries kept in memory before they are flushed to disk
|
QueryLogMemSize uint32 `yaml:"querylog_size_memory"` // number of entries kept in memory before they are flushed to disk
|
||||||
|
AnonymizeClientIP bool `yaml:"anonymize_client_ip"` // anonymize clients' IP addresses in logs and stats
|
||||||
|
|
||||||
dnsforward.FilteringConfig `yaml:",inline"`
|
dnsforward.FilteringConfig `yaml:",inline"`
|
||||||
|
|
||||||
|
@ -242,6 +243,7 @@ func (c *configuration) write() error {
|
||||||
config.DNS.QueryLogEnabled = dc.Enabled
|
config.DNS.QueryLogEnabled = dc.Enabled
|
||||||
config.DNS.QueryLogInterval = dc.Interval
|
config.DNS.QueryLogInterval = dc.Interval
|
||||||
config.DNS.QueryLogMemSize = dc.MemSize
|
config.DNS.QueryLogMemSize = dc.MemSize
|
||||||
|
config.DNS.AnonymizeClientIP = dc.AnonymizeClientIP
|
||||||
}
|
}
|
||||||
|
|
||||||
if Context.dnsFilter != nil {
|
if Context.dnsFilter != nil {
|
||||||
|
|
|
@ -31,6 +31,7 @@ func initDNSServer() error {
|
||||||
statsConf := stats.Config{
|
statsConf := stats.Config{
|
||||||
Filename: filepath.Join(baseDir, "stats.db"),
|
Filename: filepath.Join(baseDir, "stats.db"),
|
||||||
LimitDays: config.DNS.StatsInterval,
|
LimitDays: config.DNS.StatsInterval,
|
||||||
|
AnonymizeClientIP: config.DNS.AnonymizeClientIP,
|
||||||
ConfigModified: onConfigModified,
|
ConfigModified: onConfigModified,
|
||||||
HTTPRegister: httpRegister,
|
HTTPRegister: httpRegister,
|
||||||
}
|
}
|
||||||
|
@ -43,6 +44,7 @@ func initDNSServer() error {
|
||||||
BaseDir: baseDir,
|
BaseDir: baseDir,
|
||||||
Interval: config.DNS.QueryLogInterval,
|
Interval: config.DNS.QueryLogInterval,
|
||||||
MemSize: config.DNS.QueryLogMemSize,
|
MemSize: config.DNS.QueryLogMemSize,
|
||||||
|
AnonymizeClientIP: config.DNS.AnonymizeClientIP,
|
||||||
ConfigModified: onConfigModified,
|
ConfigModified: onConfigModified,
|
||||||
HTTPRegister: httpRegister,
|
HTTPRegister: httpRegister,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1585,6 +1585,9 @@ definitions:
|
||||||
interval:
|
interval:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
description: "Time period to keep data (1 | 7 | 30 | 90)"
|
description: "Time period to keep data (1 | 7 | 30 | 90)"
|
||||||
|
anonymize_client_ip:
|
||||||
|
type: "boolean"
|
||||||
|
description: "Anonymize clients' IP addresses"
|
||||||
|
|
||||||
TlsConfig:
|
TlsConfig:
|
||||||
type: "object"
|
type: "object"
|
||||||
|
|
|
@ -2,6 +2,7 @@ package querylog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -66,6 +67,7 @@ func (l *queryLog) WriteDiskConfig(dc *DiskConfig) {
|
||||||
dc.Enabled = l.conf.Enabled
|
dc.Enabled = l.conf.Enabled
|
||||||
dc.Interval = l.conf.Interval
|
dc.Interval = l.conf.Interval
|
||||||
dc.MemSize = l.conf.MemSize
|
dc.MemSize = l.conf.MemSize
|
||||||
|
dc.AnonymizeClientIP = l.conf.AnonymizeClientIP
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear memory buffer and remove log files
|
// Clear memory buffer and remove log files
|
||||||
|
@ -123,7 +125,7 @@ func (l *queryLog) Add(params AddParams) {
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
entry := logEntry{
|
entry := logEntry{
|
||||||
IP: params.ClientIP.String(),
|
IP: l.getClientIP(params.ClientIP.String()),
|
||||||
Time: now,
|
Time: now,
|
||||||
|
|
||||||
Result: *params.Result,
|
Result: *params.Result,
|
||||||
|
@ -196,6 +198,10 @@ const (
|
||||||
func (l *queryLog) getData(params getDataParams) map[string]interface{} {
|
func (l *queryLog) getData(params getDataParams) map[string]interface{} {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
|
if len(params.Client) != 0 && l.conf.AnonymizeClientIP {
|
||||||
|
params.Client = l.getClientIP(params.Client)
|
||||||
|
}
|
||||||
|
|
||||||
// add from file
|
// add from file
|
||||||
fileEntries, oldest, total := l.searchFiles(params)
|
fileEntries, oldest, total := l.searchFiles(params)
|
||||||
|
|
||||||
|
@ -246,7 +252,7 @@ func (l *queryLog) getData(params getDataParams) map[string]interface{} {
|
||||||
// the elements order is already reversed (from newer to older)
|
// the elements order is already reversed (from newer to older)
|
||||||
for i := 0; i < len(entries); i++ {
|
for i := 0; i < len(entries); i++ {
|
||||||
entry := entries[i]
|
entry := entries[i]
|
||||||
jsonEntry := logEntryToJSONEntry(entry)
|
jsonEntry := l.logEntryToJSONEntry(entry)
|
||||||
data = append(data, jsonEntry)
|
data = append(data, jsonEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +268,26 @@ func (l *queryLog) getData(params getDataParams) map[string]interface{} {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
|
// Get Client IP address
|
||||||
|
func (l *queryLog) getClientIP(clientIP string) string {
|
||||||
|
if l.conf.AnonymizeClientIP {
|
||||||
|
ip := net.ParseIP(clientIP)
|
||||||
|
if ip != nil {
|
||||||
|
ip4 := ip.To4()
|
||||||
|
const AnonymizeClientIP4Mask = 24
|
||||||
|
const AnonymizeClientIP6Mask = 112
|
||||||
|
if ip4 != nil {
|
||||||
|
clientIP = ip4.Mask(net.CIDRMask(AnonymizeClientIP4Mask, 32)).String()
|
||||||
|
} else {
|
||||||
|
clientIP = ip.Mask(net.CIDRMask(AnonymizeClientIP6Mask, 128)).String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientIP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
|
||||||
var msg *dns.Msg
|
var msg *dns.Msg
|
||||||
|
|
||||||
if len(entry.Answer) > 0 {
|
if len(entry.Answer) > 0 {
|
||||||
|
@ -277,7 +302,7 @@ func logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
|
||||||
"reason": entry.Result.Reason.String(),
|
"reason": entry.Result.Reason.String(),
|
||||||
"elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64),
|
"elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64),
|
||||||
"time": entry.Time.Format(time.RFC3339Nano),
|
"time": entry.Time.Format(time.RFC3339Nano),
|
||||||
"client": entry.IP,
|
"client": l.getClientIP(entry.IP),
|
||||||
}
|
}
|
||||||
jsonEntry["question"] = map[string]interface{}{
|
jsonEntry["question"] = map[string]interface{}{
|
||||||
"host": entry.QHost,
|
"host": entry.QHost,
|
||||||
|
|
|
@ -108,6 +108,7 @@ func (l *queryLog) handleQueryLogClear(w http.ResponseWriter, r *http.Request) {
|
||||||
type qlogConfig struct {
|
type qlogConfig struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Interval uint32 `json:"interval"`
|
Interval uint32 `json:"interval"`
|
||||||
|
AnonymizeClientIP bool `json:"anonymize_client_ip"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get configuration
|
// Get configuration
|
||||||
|
@ -115,6 +116,7 @@ func (l *queryLog) handleQueryLogInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
resp := qlogConfig{}
|
resp := qlogConfig{}
|
||||||
resp.Enabled = l.conf.Enabled
|
resp.Enabled = l.conf.Enabled
|
||||||
resp.Interval = l.conf.Interval
|
resp.Interval = l.conf.Interval
|
||||||
|
resp.AnonymizeClientIP = l.conf.AnonymizeClientIP
|
||||||
|
|
||||||
jsonVal, err := json.Marshal(resp)
|
jsonVal, err := json.Marshal(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -151,6 +153,9 @@ func (l *queryLog) handleQueryLogConfig(w http.ResponseWriter, r *http.Request)
|
||||||
if req.Exists("interval") {
|
if req.Exists("interval") {
|
||||||
conf.Interval = d.Interval
|
conf.Interval = d.Interval
|
||||||
}
|
}
|
||||||
|
if req.Exists("anonymize_client_ip") {
|
||||||
|
conf.AnonymizeClientIP = d.AnonymizeClientIP
|
||||||
|
}
|
||||||
l.conf = &conf
|
l.conf = &conf
|
||||||
l.lock.Unlock()
|
l.lock.Unlock()
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ type DiskConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Interval uint32
|
Interval uint32
|
||||||
MemSize uint32
|
MemSize uint32
|
||||||
|
AnonymizeClientIP bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryLog - main interface
|
// QueryLog - main interface
|
||||||
|
@ -36,6 +37,7 @@ type Config struct {
|
||||||
BaseDir string // directory where log file is stored
|
BaseDir string // directory where log file is stored
|
||||||
Interval uint32 // interval to rotate logs (in days)
|
Interval uint32 // interval to rotate logs (in days)
|
||||||
MemSize uint32 // number of entries kept in memory before they are flushed to disk
|
MemSize uint32 // number of entries kept in memory before they are flushed to disk
|
||||||
|
AnonymizeClientIP bool // anonymize clients' IP addresses
|
||||||
|
|
||||||
// Called when the configuration is changed by HTTP request
|
// Called when the configuration is changed by HTTP request
|
||||||
ConfigModified func()
|
ConfigModified func()
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Config struct {
|
||||||
Filename string // database file name
|
Filename string // database file name
|
||||||
LimitDays uint32 // time limit (in days)
|
LimitDays uint32 // time limit (in days)
|
||||||
UnitID unitIDCallback // user function to get the current unit ID. If nil, the current time hour is used.
|
UnitID unitIDCallback // user function to get the current unit ID. If nil, the current time hour is used.
|
||||||
|
AnonymizeClientIP bool // anonymize clients' IP addresses
|
||||||
|
|
||||||
// Called when the configuration is changed by HTTP request
|
// Called when the configuration is changed by HTTP request
|
||||||
ConfigModified func()
|
ConfigModified func()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -442,6 +443,25 @@ func (s *statsCtx) clear() {
|
||||||
log.Debug("Stats: cleared")
|
log.Debug("Stats: cleared")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get Client IP address
|
||||||
|
func (s *statsCtx) getClientIP(clientIP string) string {
|
||||||
|
if s.conf.AnonymizeClientIP {
|
||||||
|
ip := net.ParseIP(clientIP)
|
||||||
|
if ip != nil {
|
||||||
|
ip4 := ip.To4()
|
||||||
|
const AnonymizeClientIP4Mask = 24
|
||||||
|
const AnonymizeClientIP6Mask = 112
|
||||||
|
if ip4 != nil {
|
||||||
|
clientIP = ip4.Mask(net.CIDRMask(AnonymizeClientIP4Mask, 32)).String()
|
||||||
|
} else {
|
||||||
|
clientIP = ip.Mask(net.CIDRMask(AnonymizeClientIP6Mask, 128)).String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientIP
|
||||||
|
}
|
||||||
|
|
||||||
func (s *statsCtx) Update(e Entry) {
|
func (s *statsCtx) Update(e Entry) {
|
||||||
if e.Result == 0 ||
|
if e.Result == 0 ||
|
||||||
e.Result >= rLast ||
|
e.Result >= rLast ||
|
||||||
|
@ -449,7 +469,7 @@ func (s *statsCtx) Update(e Entry) {
|
||||||
!(len(e.Client) == 4 || len(e.Client) == 16) {
|
!(len(e.Client) == 4 || len(e.Client) == 16) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client := e.Client.String()
|
client := s.getClientIP(e.Client.String())
|
||||||
|
|
||||||
s.unitLock.Lock()
|
s.unitLock.Lock()
|
||||||
u := s.unit
|
u := s.unit
|
||||||
|
|
Loading…
Reference in New Issue