diff --git a/AGHTechDoc.md b/AGHTechDoc.md
index a91e25c1..a5ad69ad 100644
--- a/AGHTechDoc.md
+++ b/AGHTechDoc.md
@@ -1287,12 +1287,22 @@ Request:
{
"enabled": true | false
"interval": 1 | 7 | 30 | 90
+ "anonymize_client_ip": true | false // anonymize clients' IP addresses
}
Response:
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
@@ -1307,6 +1317,7 @@ Response:
{
"enabled": true | false
"interval": 1 | 7 | 30 | 90
+ "anonymize_client_ip": true | false
}
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index f0bfb04f..17ebac91 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -199,6 +199,8 @@
"query_log_disabled": "The query log is disabled and can be configured in the <0>settings0>",
"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",
+ "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",
"blocking_mode": "Blocking mode",
"default": "Default",
diff --git a/client/src/components/Settings/LogsConfig/Form.js b/client/src/components/Settings/LogsConfig/Form.js
index 3daf2b8d..a05c4f10 100644
--- a/client/src/components/Settings/LogsConfig/Form.js
+++ b/client/src/components/Settings/LogsConfig/Form.js
@@ -42,6 +42,16 @@ const Form = (props) => {
disabled={processing}
/>
+
+
+
diff --git a/client/src/components/Settings/LogsConfig/index.js b/client/src/components/Settings/LogsConfig/index.js
index e45b785c..a4af6d36 100644
--- a/client/src/components/Settings/LogsConfig/index.js
+++ b/client/src/components/Settings/LogsConfig/index.js
@@ -30,7 +30,7 @@ class LogsConfig extends Component {
render() {
const {
- t, enabled, interval, processing, processingClear,
+ t, enabled, interval, processing, processingClear, anonymize_client_ip,
} = this.props;
return (
@@ -44,6 +44,7 @@ class LogsConfig extends Component {
initialValues={{
enabled,
interval,
+ anonymize_client_ip,
}}
onSubmit={this.handleFormSubmit}
processing={processing}
@@ -59,6 +60,7 @@ class LogsConfig extends Component {
LogsConfig.propTypes = {
interval: PropTypes.number.isRequired,
enabled: PropTypes.bool.isRequired,
+ anonymize_client_ip: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
processingClear: PropTypes.bool.isRequired,
setLogsConfig: PropTypes.func.isRequired,
diff --git a/client/src/components/Settings/index.js b/client/src/components/Settings/index.js
index 0603f2cd..33901605 100644
--- a/client/src/components/Settings/index.js
+++ b/client/src/components/Settings/index.js
@@ -106,6 +106,7 @@ class Settings extends Component {
0 {
@@ -277,7 +302,7 @@ func logEntryToJSONEntry(entry *logEntry) map[string]interface{} {
"reason": entry.Result.Reason.String(),
"elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64),
"time": entry.Time.Format(time.RFC3339Nano),
- "client": entry.IP,
+ "client": l.getClientIP(entry.IP),
}
jsonEntry["question"] = map[string]interface{}{
"host": entry.QHost,
diff --git a/querylog/qlog_http.go b/querylog/qlog_http.go
index 07a3aadd..fae8dba6 100644
--- a/querylog/qlog_http.go
+++ b/querylog/qlog_http.go
@@ -106,8 +106,9 @@ func (l *queryLog) handleQueryLogClear(w http.ResponseWriter, r *http.Request) {
}
type qlogConfig struct {
- Enabled bool `json:"enabled"`
- Interval uint32 `json:"interval"`
+ Enabled bool `json:"enabled"`
+ Interval uint32 `json:"interval"`
+ AnonymizeClientIP bool `json:"anonymize_client_ip"`
}
// Get configuration
@@ -115,6 +116,7 @@ func (l *queryLog) handleQueryLogInfo(w http.ResponseWriter, r *http.Request) {
resp := qlogConfig{}
resp.Enabled = l.conf.Enabled
resp.Interval = l.conf.Interval
+ resp.AnonymizeClientIP = l.conf.AnonymizeClientIP
jsonVal, err := json.Marshal(resp)
if err != nil {
@@ -151,6 +153,9 @@ func (l *queryLog) handleQueryLogConfig(w http.ResponseWriter, r *http.Request)
if req.Exists("interval") {
conf.Interval = d.Interval
}
+ if req.Exists("anonymize_client_ip") {
+ conf.AnonymizeClientIP = d.AnonymizeClientIP
+ }
l.conf = &conf
l.lock.Unlock()
diff --git a/querylog/querylog.go b/querylog/querylog.go
index dcca14dd..0e079ec3 100644
--- a/querylog/querylog.go
+++ b/querylog/querylog.go
@@ -11,9 +11,10 @@ import (
// DiskConfig - configuration settings that are stored on disk
type DiskConfig struct {
- Enabled bool
- Interval uint32
- MemSize uint32
+ Enabled bool
+ Interval uint32
+ MemSize uint32
+ AnonymizeClientIP bool
}
// QueryLog - main interface
@@ -32,10 +33,11 @@ type QueryLog interface {
// Config - configuration object
type Config struct {
- Enabled bool
- BaseDir string // directory where log file is stored
- Interval uint32 // interval to rotate logs (in days)
- MemSize uint32 // number of entries kept in memory before they are flushed to disk
+ Enabled bool
+ BaseDir string // directory where log file is stored
+ Interval uint32 // interval to rotate logs (in days)
+ 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
ConfigModified func()
diff --git a/stats/stats.go b/stats/stats.go
index 91b6b25f..8f77425f 100644
--- a/stats/stats.go
+++ b/stats/stats.go
@@ -16,9 +16,10 @@ type DiskConfig struct {
// Config - module configuration
type Config struct {
- Filename string // database file name
- LimitDays uint32 // time limit (in days)
- UnitID unitIDCallback // user function to get the current unit ID. If nil, the current time hour is used.
+ Filename string // database file name
+ LimitDays uint32 // time limit (in days)
+ 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
ConfigModified func()
diff --git a/stats/stats_unit.go b/stats/stats_unit.go
index 44aa66d5..5126e69c 100644
--- a/stats/stats_unit.go
+++ b/stats/stats_unit.go
@@ -5,6 +5,7 @@ import (
"encoding/binary"
"encoding/gob"
"fmt"
+ "net"
"os"
"sort"
"sync"
@@ -442,6 +443,25 @@ func (s *statsCtx) clear() {
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) {
if e.Result == 0 ||
e.Result >= rLast ||
@@ -449,7 +469,7 @@ func (s *statsCtx) Update(e Entry) {
!(len(e.Client) == 4 || len(e.Client) == 16) {
return
}
- client := e.Client.String()
+ client := s.getClientIP(e.Client.String())
s.unitLock.Lock()
u := s.unit