1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# SightingDB configuration.
#
# Comments here are preserved: the management interface never rewrites this
# file. It writes only the separate acl_file below.
[]
# Set to false to run without the HTTP API (DNS or ZMQ ingest only).
= true
= "0.0.0.0"
= 9999
# Require an API key on the sighting API. The management interface always
# requires one regardless of this setting.
= false
# Detaching is opt-in; under systemd or Docker leave this false and let the
# supervisor do it. See etc/sightingdb.service.
= false
= true
= "ssl/cert.pem"
= "ssl/key.pem"
# Largest accepted bulk POST body, in bytes.
= 2_500_000_000
# Only used when daemonize = true.
= "/var/log/sightingdb/sightingdb.log"
= "/var/log/sightingdb/sightingdb.error.log"
# Where the database is snapshotted. Comment this out to run purely in memory,
# losing everything when the process stops.
= "/var/lib/sighting"
# Seconds between snapshots. The database is always saved on a clean shutdown;
# this bounds how much a crash can lose. 0 saves only on shutdown.
#
# Storage is split into one zstd-compressed file per top-level namespace, so
# "myorg/thisone" lives in myorg-<hash>.json.zst and a periodic snapshot only
# rewrites the files that changed. Everything internal (_all, _shadow, _config)
# shares sightingdb.json.zst.
= 300
# zstd level for those files. 3 is the knee of the curve: measured on a 50 MB
# snapshot it reaches 7.3x in 0.07s, where level 19 takes 25s for 11.3x. Higher
# levels cost compression time on every save for very little space.
= 3
# Seconds between eviction sweeps, which reclaim expired values and the
# namespaces left empty by them. 0 disables the sweeper.
= 60
# Hourly statistics buckets kept per value. 720 is 30 days. Without a limit
# every value gains a bucket per hour for as long as it is written to.
# 0 keeps all of them.
= 720
# How long a shadow sighting (the record that something was searched for) is
# kept, in seconds. 0 keeps them forever, so `_shadow/*` grows without bound.
# 2592000 is 30 days.
= 2_592_000
# How long each top-level namespace stays in memory.
#
# hot never evicted
# warm written out and dropped once untouched for warm_idle seconds; any
# read or write starts the window again
# cold dropped at the next sweep once idle
#
# "cold" does not mean a disk round trip per operation: a cold shard is loaded
# on demand and dropped at the next sweep, so a burst of writes costs one load
# rather than one per write. Internal state (_all, _config) is always hot,
# whatever is configured, because consensus and API keys are consulted
# constantly.
#
# Eviction needs somewhere to write, so it only happens when dbdir is set.
[]
= "hot"
= 3600
# Rewritten by the management interface whenever a tier is changed there, which
# is why it is a file of its own: rewriting this one would discard its comments.
# Without it, tiers come from [storage.tiers] below and the interface can show
# them but not change them.
= "tiers.toml"
# Per-namespace overrides, keyed by the top-level namespace.
# [storage.tiers]
# myorg = "hot"
# archive = "cold"
# occasional = "warm"
# API keys live in their own file because the management interface rewrites it
# whenever a key is created or revoked. Without this, keys can be declared in an
# [acl] table below, but the interface will not be able to edit them.
= "acl.toml"
# API keys, used only when acl_file is not set.
#
# "<apikey>" = "<grant>[, <grant>...]"
#
# A grant is r, w or rw, optionally scoped with :<namespace prefix>, plus
# "admin" for access to the management interface at /_management/. Prefixes
# match whole path segments, so rw:feeds/misp covers feeds/misp/ips but not
# feeds/misp-internal.
#
# [acl]
# changeme = "rw, admin"
# analyst = "r"
# feed-misp = "rw:feeds/misp"
# ---------------------------------------------------------------------------
# Answer sightings over DNS, using the DNSBL conventions security tooling
# already speaks.
#
# READ THIS BEFORE ENABLING: DNS has no authentication. The ACL does not apply,
# so anything reachable here is readable by anyone who can send a UDP packet.
# Only the namespaces listed below answer, the listener binds to loopback unless
# you change it, and rate_limit exists because an open UDP responder is a
# reflection amplifier.
# ---------------------------------------------------------------------------
# [dns]
# enabled = true
# listen_ip = "127.0.0.1"
# listen_port = 5353
# zone = "sdb.example.com"
# TTL on answers, and how long resolvers cache a "never seen" reply.
# ttl = 60
# Queries per second per source address; 0 disables the limit.
# rate_limit = 100
# threads = 2
# Whether a DNS lookup counts as a search. Off by default: it is an
# unauthenticated write path into _shadow/*.
# shadow = false
#
# How a value is spelled in the query name:
# ip 4.3.2.1.malware.sdb.example.com -> 1.2.3.4 (DNSBL convention)
# domain evil.com.domains.sdb.example.com -> evil.com
# base32 <base32 of the value>.hashes.sdb.example.com
# [dns.namespaces]
# malware = { namespace = "malware/ips", encoding = "ip" }
# domains = { namespace = "malware/domains", encoding = "domain" }
# hashes = { namespace = "malware/hashes", encoding = "base32" }
# ---------------------------------------------------------------------------
# Ingest sightings from a ZeroMQ publisher such as MISP's. This is a SUB socket
# that connects out to a publisher you trust; nothing is exposed.
# ---------------------------------------------------------------------------
# [zmq]
# enabled = true
# endpoint = "tcp://misp.example.com:50000"
# MISP publishes "<topic> <json>"; subscribing to a prefix matches it.
# topics = ["misp_json_attribute", "misp_json"]
# "misp" reads MISP attributes and events; "native" reads
# {"items":[{"namespace":..,"value":..,"timestamp":..}]}
# format = "misp"
# Only ingest attributes MISP flagged as actionable.
# require_to_ids = true
# Where unmapped attribute types go; omit to discard them.
# default_namespace = ""
# TTL on ingested sightings, and seconds between reconnection attempts.
# ttl = 0
# reconnect = 5
#
# [zmq.types]
# ip-src = "misp/ips"
# ip-dst = "misp/ips"
# domain = "misp/domains"
# hostname = "misp/domains"
# url = "misp/urls"
# md5 = "misp/hashes"
# sha1 = "misp/hashes"
# sha256 = "misp/hashes"
# ---------------------------------------------------------------------------
# Importing STIX 2.1 bundles with --import-stix <file or directory>. Counts and
# time windows come from the bundle: an observed-data seen 12 times between two
# instants becomes 12 sightings spanning that window.
# ---------------------------------------------------------------------------
# [stix]
# default_namespace = ""
# ttl = 0
#
# Keys containing a dot must be QUOTED, or TOML reads them as a nested table.
# [stix.types]
# ipv4-addr = "stix/ips"
# ipv6-addr = "stix/ips"
# domain-name = "stix/domains"
# url = "stix/urls"
# "file.MD5" = "stix/hashes"
# "file.SHA-1" = "stix/hashes"
# "file.SHA-256" = "stix/hashes"