Merge: + qlog: anonymize_client_ip setting
Close #916 * commit 'a0be7f5566a7d5986eb04ed7ffb79c5230171e2a': + client: handle hide_client_ip + qlog: hide_client_ip setting
This commit is contained in:
commit
5fe984741e
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -199,6 +199,8 @@
|
||||||
"query_log_disabled": "The query log is disabled and can be configured in the <0>settings</0>",
|
"query_log_disabled": "The query log is disabled and can be configured in the <0>settings</0>",
|
||||||
"query_log_strict_search": "Use double quotes for strict search",
|
"query_log_strict_search": "Use double quotes for strict search",
|
||||||
"query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost",
|
"query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost",
|
||||||
|
"anonymize_client_ip": "Anonymize client IP",
|
||||||
|
"anonymize_client_ip_desc": "Don't save the full IP address of the client in logs and statistics",
|
||||||
"dns_config": "DNS server configuration",
|
"dns_config": "DNS server configuration",
|
||||||
"blocking_mode": "Blocking mode",
|
"blocking_mode": "Blocking mode",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
|
|
|
@ -42,6 +42,16 @@ const Form = (props) => {
|
||||||
disabled={processing}
|
disabled={processing}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form__group form__group--settings">
|
||||||
|
<Field
|
||||||
|
name="anonymize_client_ip"
|
||||||
|
type="checkbox"
|
||||||
|
component={renderSelectField}
|
||||||
|
placeholder={t('anonymize_client_ip')}
|
||||||
|
subtitle={t('anonymize_client_ip_desc')}
|
||||||
|
disabled={processing}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<label className="form__label">
|
<label className="form__label">
|
||||||
<Trans>query_log_retention</Trans>
|
<Trans>query_log_retention</Trans>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -30,7 +30,7 @@ class LogsConfig extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
t, enabled, interval, processing, processingClear,
|
t, enabled, interval, processing, processingClear, anonymize_client_ip,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -44,6 +44,7 @@ class LogsConfig extends Component {
|
||||||
initialValues={{
|
initialValues={{
|
||||||
enabled,
|
enabled,
|
||||||
interval,
|
interval,
|
||||||
|
anonymize_client_ip,
|
||||||
}}
|
}}
|
||||||
onSubmit={this.handleFormSubmit}
|
onSubmit={this.handleFormSubmit}
|
||||||
processing={processing}
|
processing={processing}
|
||||||
|
@ -59,6 +60,7 @@ class LogsConfig extends Component {
|
||||||
LogsConfig.propTypes = {
|
LogsConfig.propTypes = {
|
||||||
interval: PropTypes.number.isRequired,
|
interval: PropTypes.number.isRequired,
|
||||||
enabled: PropTypes.bool.isRequired,
|
enabled: PropTypes.bool.isRequired,
|
||||||
|
anonymize_client_ip: PropTypes.bool.isRequired,
|
||||||
processing: PropTypes.bool.isRequired,
|
processing: PropTypes.bool.isRequired,
|
||||||
processingClear: PropTypes.bool.isRequired,
|
processingClear: PropTypes.bool.isRequired,
|
||||||
setLogsConfig: PropTypes.func.isRequired,
|
setLogsConfig: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -106,6 +106,7 @@ class Settings extends Component {
|
||||||
<LogsConfig
|
<LogsConfig
|
||||||
enabled={queryLogs.enabled}
|
enabled={queryLogs.enabled}
|
||||||
interval={queryLogs.interval}
|
interval={queryLogs.interval}
|
||||||
|
anonymize_client_ip={queryLogs.anonymize_client_ip}
|
||||||
processing={queryLogs.processingSetConfig}
|
processing={queryLogs.processingSetConfig}
|
||||||
processingClear={queryLogs.processingClear}
|
processingClear={queryLogs.processingClear}
|
||||||
setLogsConfig={setLogsConfig}
|
setLogsConfig={setLogsConfig}
|
||||||
|
|
|
@ -134,6 +134,7 @@ const queryLogs = handleActions(
|
||||||
oldest: '',
|
oldest: '',
|
||||||
filter: DEFAULT_LOGS_FILTER,
|
filter: DEFAULT_LOGS_FILTER,
|
||||||
isFiltered: false,
|
isFiltered: false,
|
||||||
|
anonymize_client_ip: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -77,9 +77,10 @@ type dnsConfig struct {
|
||||||
// time interval for statistics (in days)
|
// time interval for statistics (in days)
|
||||||
StatsInterval uint32 `yaml:"statistics_interval"`
|
StatsInterval uint32 `yaml:"statistics_interval"`
|
||||||
|
|
||||||
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 {
|
||||||
|
|
22
home/dns.go
22
home/dns.go
|
@ -29,22 +29,24 @@ func initDNSServer() error {
|
||||||
baseDir := Context.getDataDir()
|
baseDir := Context.getDataDir()
|
||||||
|
|
||||||
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,
|
||||||
ConfigModified: onConfigModified,
|
AnonymizeClientIP: config.DNS.AnonymizeClientIP,
|
||||||
HTTPRegister: httpRegister,
|
ConfigModified: onConfigModified,
|
||||||
|
HTTPRegister: httpRegister,
|
||||||
}
|
}
|
||||||
Context.stats, err = stats.New(statsConf)
|
Context.stats, err = stats.New(statsConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Couldn't initialize statistics module")
|
return fmt.Errorf("Couldn't initialize statistics module")
|
||||||
}
|
}
|
||||||
conf := querylog.Config{
|
conf := querylog.Config{
|
||||||
Enabled: config.DNS.QueryLogEnabled,
|
Enabled: config.DNS.QueryLogEnabled,
|
||||||
BaseDir: baseDir,
|
BaseDir: baseDir,
|
||||||
Interval: config.DNS.QueryLogInterval,
|
Interval: config.DNS.QueryLogInterval,
|
||||||
MemSize: config.DNS.QueryLogMemSize,
|
MemSize: config.DNS.QueryLogMemSize,
|
||||||
ConfigModified: onConfigModified,
|
AnonymizeClientIP: config.DNS.AnonymizeClientIP,
|
||||||
HTTPRegister: httpRegister,
|
ConfigModified: onConfigModified,
|
||||||
|
HTTPRegister: httpRegister,
|
||||||
}
|
}
|
||||||
Context.queryLog = querylog.New(conf)
|
Context.queryLog = querylog.New(conf)
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -106,8 +106,9 @@ 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()
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,10 @@ import (
|
||||||
|
|
||||||
// DiskConfig - configuration settings that are stored on disk
|
// DiskConfig - configuration settings that are stored on disk
|
||||||
type DiskConfig struct {
|
type DiskConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Interval uint32
|
Interval uint32
|
||||||
MemSize uint32
|
MemSize uint32
|
||||||
|
AnonymizeClientIP bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryLog - main interface
|
// QueryLog - main interface
|
||||||
|
@ -32,10 +33,11 @@ type QueryLog interface {
|
||||||
|
|
||||||
// Config - configuration object
|
// Config - configuration object
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
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()
|
||||||
|
|
|
@ -16,9 +16,10 @@ type DiskConfig struct {
|
||||||
|
|
||||||
// Config - module configuration
|
// Config - module configuration
|
||||||
type Config struct {
|
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