269 lines
8.7 KiB
Go
269 lines
8.7 KiB
Go
// Package models holds the plain data types shared between the storage layer,
|
|
// the DNS engine and the HTTP layer. Nothing here depends on a database or on
|
|
// the DNS library, which keeps the type definitions usable from every package.
|
|
package models
|
|
|
|
import "time"
|
|
|
|
// ZoneKind distinguishes forward lookup zones from the two reverse families.
|
|
type ZoneKind string
|
|
|
|
const (
|
|
ZoneForward ZoneKind = "forward"
|
|
ZoneReverse4 ZoneKind = "reverse4"
|
|
ZoneReverse6 ZoneKind = "reverse6"
|
|
)
|
|
|
|
// Valid reports whether k is a kind the application understands.
|
|
func (k ZoneKind) Valid() bool {
|
|
switch k {
|
|
case ZoneForward, ZoneReverse4, ZoneReverse6:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Label returns a human readable name for the zone kind.
|
|
func (k ZoneKind) Label() string {
|
|
switch k {
|
|
case ZoneReverse4:
|
|
return "Reverse IPv4"
|
|
case ZoneReverse6:
|
|
return "Reverse IPv6"
|
|
default:
|
|
return "Forward"
|
|
}
|
|
}
|
|
|
|
// Zone is an authoritative DNS zone.
|
|
type Zone struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"` // normalised FQDN, always ends in "."
|
|
Kind ZoneKind `json:"kind"`
|
|
Description string `json:"description"`
|
|
Enabled bool `json:"enabled"`
|
|
DefaultTTL uint32 `json:"default_ttl"`
|
|
PrimaryNS string `json:"primary_ns"`
|
|
AdminEmail string `json:"admin_email"`
|
|
Serial uint32 `json:"serial"`
|
|
Refresh uint32 `json:"refresh"`
|
|
Retry uint32 `json:"retry"`
|
|
Expire uint32 `json:"expire"`
|
|
Minimum uint32 `json:"minimum"`
|
|
AutoSerial bool `json:"auto_serial"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
// RecordCount is populated by list queries; it is not stored.
|
|
RecordCount int `json:"record_count,omitempty"`
|
|
}
|
|
|
|
// Record is a single resource record inside a zone. Name is stored relative to
|
|
// the zone apex ("@" for the apex itself) and Data holds the rdata in the usual
|
|
// zone-file presentation format.
|
|
type Record struct {
|
|
ID int64 `json:"id"`
|
|
ZoneID int64 `json:"zone_id"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Data string `json:"data"`
|
|
TTL *uint32 `json:"ttl"` // nil means "inherit the zone default"
|
|
Enabled bool `json:"enabled"`
|
|
Comment string `json:"comment"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
// ZoneName is filled in by cross-zone queries for display purposes.
|
|
ZoneName string `json:"zone_name,omitempty"`
|
|
}
|
|
|
|
// EffectiveTTL resolves the record TTL against the zone default.
|
|
func (r Record) EffectiveTTL(zoneDefault uint32) uint32 {
|
|
if r.TTL != nil {
|
|
return *r.TTL
|
|
}
|
|
return zoneDefault
|
|
}
|
|
|
|
// BlockAction is what a policy does with a query that matched a blacklist.
|
|
type BlockAction string
|
|
|
|
const (
|
|
BlockNXDOMAIN BlockAction = "nxdomain"
|
|
BlockRefused BlockAction = "refused"
|
|
BlockSinkhole BlockAction = "sinkhole"
|
|
)
|
|
|
|
// Valid reports whether a is a supported block action.
|
|
func (a BlockAction) Valid() bool {
|
|
switch a {
|
|
case BlockNXDOMAIN, BlockRefused, BlockSinkhole:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Label returns a display name for the block action.
|
|
func (a BlockAction) Label() string {
|
|
switch a {
|
|
case BlockRefused:
|
|
return "REFUSED"
|
|
case BlockSinkhole:
|
|
return "Sinkhole"
|
|
default:
|
|
return "NXDOMAIN"
|
|
}
|
|
}
|
|
|
|
// Network is a client subnet that policies can be attached to.
|
|
type Network struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
CIDR string `json:"cidr"`
|
|
Description string `json:"description"`
|
|
Enabled bool `json:"enabled"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
Policies []Policy `json:"policies,omitempty"`
|
|
}
|
|
|
|
// Policy groups a set of blacklists and allowlists with a block action.
|
|
type Policy struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Enabled bool `json:"enabled"`
|
|
BlockAction BlockAction `json:"block_action"`
|
|
SinkholeIPv4 string `json:"sinkhole_ipv4"`
|
|
SinkholeIPv6 string `json:"sinkhole_ipv6"`
|
|
BlockTTL uint32 `json:"block_ttl"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
BlacklistIDs []int64 `json:"blacklist_ids,omitempty"`
|
|
AllowlistIDs []int64 `json:"allowlist_ids,omitempty"`
|
|
NetworkCount int `json:"network_count,omitempty"`
|
|
BlacklistName []string `json:"blacklist_names,omitempty"`
|
|
AllowlistName []string `json:"allowlist_names,omitempty"`
|
|
}
|
|
|
|
// DomainList is either a blacklist or an allowlist; the Kind field says which.
|
|
type DomainList struct {
|
|
ID int64 `json:"id"`
|
|
Kind string `json:"kind"` // "blacklist" or "allowlist"
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Enabled bool `json:"enabled"`
|
|
SourceURL string `json:"source_url"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
|
|
DomainCount int `json:"domain_count"`
|
|
UsedBy []string `json:"used_by,omitempty"`
|
|
}
|
|
|
|
// List kinds.
|
|
const (
|
|
KindBlacklist = "blacklist"
|
|
KindAllowlist = "allowlist"
|
|
)
|
|
|
|
// DomainEntry is one domain inside a DomainList.
|
|
type DomainEntry struct {
|
|
ID int64 `json:"id"`
|
|
ListID int64 `json:"list_id"`
|
|
Domain string `json:"domain"`
|
|
MatchSubdomains bool `json:"match_subdomains"`
|
|
Enabled bool `json:"enabled"`
|
|
Comment string `json:"comment"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// APIToken is a bearer credential for automation. The secret itself is never
|
|
// stored: only a prefix (for lookup) and an Argon2id hash.
|
|
type APIToken struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Prefix string `json:"prefix"`
|
|
Enabled bool `json:"enabled"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
LastUsedAt *time.Time `json:"last_used_at"`
|
|
|
|
// Secret is populated only in the response that creates the token.
|
|
Secret string `json:"secret,omitempty"`
|
|
}
|
|
|
|
// Admin is the single administrator account.
|
|
type Admin struct {
|
|
Username string `json:"username"`
|
|
PasswordHash string `json:"-"`
|
|
MustChangePassword bool `json:"must_change_password"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
LastLoginAt *time.Time `json:"last_login_at"`
|
|
}
|
|
|
|
// QueryLogEntry is one logged DNS query.
|
|
type QueryLogEntry struct {
|
|
ID int64 `json:"id"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
ClientIP string `json:"client_ip"`
|
|
NetworkID *int64 `json:"network_id"`
|
|
NetworkName string `json:"network_name"`
|
|
QName string `json:"qname"`
|
|
QType string `json:"qtype"`
|
|
Rcode string `json:"rcode"`
|
|
Source string `json:"source"`
|
|
CacheHit bool `json:"cache_hit"`
|
|
Blocked bool `json:"blocked"`
|
|
PolicyID *int64 `json:"policy_id"`
|
|
PolicyName string `json:"policy_name"`
|
|
BlacklistID *int64 `json:"blacklist_id"`
|
|
BlacklistName string `json:"blacklist_name"`
|
|
MatchedRule string `json:"matched_rule"`
|
|
Protocol string `json:"protocol"`
|
|
DurationUS int64 `json:"duration_us"`
|
|
AnswerCount int `json:"answer_count"`
|
|
}
|
|
|
|
// DurationMS renders the query duration in milliseconds for the UI.
|
|
func (q QueryLogEntry) DurationMS() float64 { return float64(q.DurationUS) / 1000.0 }
|
|
|
|
// Answer source values recorded in the query log and exported in metrics.
|
|
const (
|
|
SourceAuthoritative = "authoritative"
|
|
SourceCache = "cache"
|
|
SourceStale = "stale"
|
|
SourceRecursive = "recursive"
|
|
SourceBlocked = "blocked"
|
|
SourceRefused = "refused"
|
|
SourceRateLimited = "ratelimited"
|
|
SourceError = "error"
|
|
SourceLocal = "local"
|
|
)
|
|
|
|
// AuditEntry records one administrative change.
|
|
type AuditEntry struct {
|
|
ID int64 `json:"id"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Actor string `json:"actor"`
|
|
Source string `json:"source"` // web | api | cli | system
|
|
ClientIP string `json:"client_ip"`
|
|
Action string `json:"action"`
|
|
ObjectType string `json:"object_type"`
|
|
ObjectID string `json:"object_id"`
|
|
ObjectName string `json:"object_name"`
|
|
Details string `json:"details"`
|
|
}
|
|
|
|
// ImportSummary reports the outcome of a bulk domain import.
|
|
type ImportSummary struct {
|
|
LinesProcessed int `json:"lines_processed"`
|
|
Imported int `json:"imported"`
|
|
Duplicates int `json:"duplicates"`
|
|
Invalid int `json:"invalid"`
|
|
Ignored int `json:"ignored"` // comments and blank lines
|
|
InvalidSamples []string `json:"invalid_samples,omitempty"`
|
|
}
|