Jarl 0.1.0: manage BIND through a web UI, REST API and CLI
Jarl keeps a BIND 9.20 server's whole configuration (views, ACLs, TSIG keys, zones of every type, records, named.conf options) in SQLite and renders named.conf, keys.conf and zone files from it. Every change goes through one changeset pipeline: whole-configuration validation, named-checkconf -z and named-checkzone in a staging directory, atomic install, rndc reconfig/reload, serial verification with rndc zonestatus, and rollback if named does not load the change. Changes apply one at a time by default or are bundled and applied together. Applied changesets can be reverted. - jarld: the daemon with the REST API and an embedded web UI, plus import, adopt, check, resync, backup and local user recovery commands - jarl: the CLI, including ACME DNS-01 hooks for certbot and acme.sh - users with argon2id passwords and CSRF-protected sessions; hashed API tokens with granular grants and roles; an audit log - systemd unit, sysusers.d/tmpfiles.d files, Makefile - unit tests, integration tests against a real named, scripts/e2e.sh - docs/ guides, README and CHANGELOG Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
bd6b8778e4
122 files changed
+29217
No files matched your search
@@ -0,0 +1,135 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Changeset statuses.
|
||||
const (
|
||||
ChangesetOpen = "open"
|
||||
ChangesetApplied = "applied"
|
||||
ChangesetDiscarded = "discarded"
|
||||
)
|
||||
|
||||
// Op is one operation in a changeset, for example "record.create".
|
||||
type Op struct {
|
||||
Seq int `json:"seq"`
|
||||
Kind string `json:"op"`
|
||||
// ID is the object the operation acts on. For create operations it is
|
||||
// allocated when the operation is added, so later operations in the
|
||||
// same changeset can refer to the new object.
|
||||
ID int64 `json:"id,omitempty"`
|
||||
// BaseVersion is the version of the object when the operation was
|
||||
// staged. Applying fails with a conflict if the object has changed
|
||||
// since (0 skips the check).
|
||||
BaseVersion int `json:"base_version,omitempty"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
AddedAt time.Time `json:"added_at"`
|
||||
}
|
||||
|
||||
// Changeset is a group of operations applied together.
|
||||
type Changeset struct {
|
||||
ID int64 `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Via string `json:"via"` // web, api, cli, import, system
|
||||
Ops []Op `json:"ops"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
AppliedAt *time.Time `json:"applied_at,omitempty"`
|
||||
AppliedBy string `json:"applied_by,omitempty"`
|
||||
Result json.RawMessage `json:"result,omitempty"`
|
||||
Diff string `json:"diff,omitempty"`
|
||||
}
|
||||
|
||||
// Grant gives access to a kind of resource. Filters narrow it: empty
|
||||
// filters match everything.
|
||||
type Grant struct {
|
||||
Resource string `json:"resource"` // zones, records, views, acls, tsig, options, audit, *
|
||||
Access string `json:"access"` // read or write (write implies read)
|
||||
Zones []string `json:"zones,omitempty"`
|
||||
Views []string `json:"views,omitempty"`
|
||||
Names []string `json:"names,omitempty"`
|
||||
Types []string `json:"types,omitempty"`
|
||||
}
|
||||
|
||||
// User is a person or service account.
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
PasswordHash string `json:"-"`
|
||||
HasPassword bool `json:"has_password"`
|
||||
Admin bool `json:"admin"`
|
||||
Grants []Grant `json:"grants"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Comment string `json:"comment"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
||||
}
|
||||
|
||||
// Token is an API key. The secret is shown once at creation and only its
|
||||
// hash is stored.
|
||||
type Token struct {
|
||||
ID int64 `json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Name string `json:"name"`
|
||||
PublicID string `json:"public_id"`
|
||||
SecretHash string `json:"-"`
|
||||
// Grants restrict the token to a subset of its user's permissions;
|
||||
// nil means the token has all of the user's permissions.
|
||||
Grants []Grant `json:"grants"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
|
||||
LastUsedIP string `json:"last_used_ip,omitempty"`
|
||||
RevokedAt *time.Time `json:"revoked_at,omitempty"`
|
||||
}
|
||||
|
||||
// Session is a logged-in web session.
|
||||
type Session struct {
|
||||
IDHash string `json:"-"`
|
||||
UserID int64 `json:"user_id"`
|
||||
CSRFToken string `json:"-"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
LastSeenAt time.Time `json:"last_seen_at"`
|
||||
IP string `json:"ip"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
}
|
||||
|
||||
// AuditEvent records an action.
|
||||
type AuditEvent struct {
|
||||
ID int64 `json:"id"`
|
||||
Time time.Time `json:"time"`
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Auth string `json:"auth,omitempty"` // session, token, local
|
||||
TokenID int64 `json:"token_id,omitempty"`
|
||||
TokenName string `json:"token_name,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
UserAgent string `json:"user_agent,omitempty"`
|
||||
Action string `json:"action"`
|
||||
ObjectType string `json:"object_type,omitempty"`
|
||||
ObjectID int64 `json:"object_id,omitempty"`
|
||||
ObjectName string `json:"object_name,omitempty"`
|
||||
ChangesetID int64 `json:"changeset_id,omitempty"`
|
||||
Outcome string `json:"outcome"` // success, failure, denied
|
||||
Message string `json:"message,omitempty"`
|
||||
Details json.RawMessage `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// DeployedFile records a file Jarl wrote, for drift detection.
|
||||
type DeployedFile struct {
|
||||
Path string `json:"path"`
|
||||
SHA256 string `json:"sha256"`
|
||||
Size int64 `json:"size"`
|
||||
Kind string `json:"kind"`
|
||||
WrittenAt time.Time `json:"written_at"`
|
||||
ChangesetID int64 `json:"changeset_id"`
|
||||
}
|
||||
Reference in new issue
Block a user