Each view's match-clients starts with "key jarl-view-NAME" (after
"!key" for the views listed after it), so a query signed with a view's
key lands in that view from any address. The secrets are derived from a
random seed in Jarl's database. The delegation check signs its queries
to this server's own addresses with the key of the zone's view: before,
they got whichever view matched the server's address, usually internal.
jarl view key / GET /views/{id}/key show a key for dig -k (tsig:write,
audited). jarl-view- key names are reserved; the importer drops view
keys from a configuration Jarl wrote.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
3.0 KiB
Go
85 lines
3.0 KiB
Go
package render
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"jarl/internal/model"
|
|
"jarl/internal/namedconf"
|
|
)
|
|
|
|
// View keys let Jarl choose a view regardless of where a query comes from:
|
|
// each view gets a TSIG key, and its match-clients starts with that key
|
|
// (after excluding the keys of the views listed after it, so no earlier
|
|
// view takes their queries). Queries signed with a view's key always land
|
|
// in that view. Jarl signs its own checks of this server's addresses with
|
|
// them (a query from this host would otherwise land in whichever view
|
|
// matches this host's address), and you can too: dig -k.
|
|
//
|
|
// The secrets are derived from a random seed kept in Jarl's database
|
|
// (Extras.ViewKeySeed), so they stay the same from one apply to the next
|
|
// and need no storage of their own. They are in keys.conf with the other
|
|
// keys.
|
|
|
|
// ViewKeyAlgorithm is the view keys' algorithm.
|
|
const ViewKeyAlgorithm = "hmac-sha256"
|
|
|
|
// ViewKeyName returns the name of a view's key.
|
|
func ViewKeyName(view string) string { return model.ViewKeyName(view) }
|
|
|
|
// IsViewKey reports whether a key name is a view key's.
|
|
func IsViewKey(name string) bool {
|
|
return strings.HasPrefix(strings.ToLower(strings.TrimSuffix(name, ".")), model.ViewKeyPrefix)
|
|
}
|
|
|
|
// ViewKeySecret returns the secret of the key of the view with this ID.
|
|
func ViewKeySecret(seed []byte, viewID int64) string {
|
|
m := hmac.New(sha256.New, seed)
|
|
m.Write([]byte("jarl view key " + strconv.FormatInt(viewID, 10)))
|
|
return base64.StdEncoding.EncodeToString(m.Sum(nil))
|
|
}
|
|
|
|
// viewKeysOn reports whether view keys are rendered.
|
|
func viewKeysOn(s *model.State, x Extras) bool { return len(x.ViewKeySeed) > 0 && len(s.Views) > 0 }
|
|
|
|
// renderViewKeys returns the key statements of the views' keys.
|
|
func renderViewKeys(s *model.State, x Extras) string {
|
|
if !viewKeysOn(s, x) {
|
|
return ""
|
|
}
|
|
var sb strings.Builder
|
|
sb.WriteString("\n" + commentBlock("//", "View keys: a query signed with one of these always lands in its view.\nJarl signs its own checks of this server with them; dig -k works too."))
|
|
for _, v := range s.OrderedViews() {
|
|
sb.WriteString("key " + namedconf.QuoteString(ViewKeyName(v.Name)) + " {\n\talgorithm " + ViewKeyAlgorithm + ";\n\tsecret " + namedconf.QuoteString(ViewKeySecret(x.ViewKeySeed, v.ID)) + ";\n};\n")
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// viewMatchClients returns a view's match-clients with the view keys in
|
|
// front: "!key" for the keys of the views after it, then its own key, then
|
|
// the view's own list ("any" when it has none, which is what an absent
|
|
// match-clients means).
|
|
func viewMatchClients(s *model.State, x Extras, v *model.View) []string {
|
|
if !viewKeysOn(s, x) {
|
|
return v.MatchClients
|
|
}
|
|
var out []string
|
|
after := false
|
|
for _, o := range s.OrderedViews() {
|
|
if after {
|
|
out = append(out, "!key "+namedconf.QuoteString(ViewKeyName(o.Name)))
|
|
}
|
|
if o.ID == v.ID {
|
|
after = true
|
|
}
|
|
}
|
|
out = append(out, "key "+namedconf.QuoteString(ViewKeyName(v.Name)))
|
|
if len(v.MatchClients) == 0 {
|
|
return append(out, "any")
|
|
}
|
|
return append(out, v.MatchClients...)
|
|
}
|