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...) }