128 lines
4.0 KiB
Go
128 lines
4.0 KiB
Go
// Package console is the editing and viewing half of fancy-dns, split from the
|
|
// resolver so that it can run somewhere else. It owns authentication and the
|
|
// web UI, writes ClientGroup, BlockList and DNSZone objects to the cluster, and
|
|
// forwards everything the resolver owns (statistics, the query log, the cache)
|
|
// to the resolver's admin API.
|
|
//
|
|
// The resolver never calls the console and the console is never on the DNS
|
|
// path: stopping it cannot affect a query.
|
|
package console
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.k8s-git-k6n.net/mats/fancy-dns/internal/api"
|
|
"git.k8s-git-k6n.net/mats/fancy-dns/internal/config"
|
|
"git.k8s-git-k6n.net/mats/fancy-dns/internal/kubeedit"
|
|
"git.k8s-git-k6n.net/mats/fancy-dns/internal/web"
|
|
)
|
|
|
|
// Options configure a Console.
|
|
type Options struct {
|
|
Config *config.Store
|
|
Runtime *Runtime
|
|
Editor *kubeedit.Editor
|
|
Logger *slog.Logger
|
|
Version string
|
|
}
|
|
|
|
// Console serves the UI and the edit API.
|
|
type Console struct {
|
|
opt Options
|
|
log *slog.Logger
|
|
api *api.Server
|
|
started time.Time
|
|
}
|
|
|
|
// New builds a Console.
|
|
func New(opt Options) (*Console, error) {
|
|
if opt.Config == nil || opt.Runtime == nil || opt.Editor == nil {
|
|
return nil, errors.New("console: config, runtime and editor are required")
|
|
}
|
|
if opt.Logger == nil {
|
|
opt.Logger = slog.Default()
|
|
}
|
|
c := &Console{opt: opt, log: opt.Logger.With("component", "console"), started: time.Now()}
|
|
srv, err := api.New(c.hooks())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.api = srv
|
|
return c, nil
|
|
}
|
|
|
|
// actor names who is making a request, for the object annotation.
|
|
func actor(ctx context.Context) string {
|
|
if p, ok := api.PrincipalFrom(ctx); ok && p.Name != "" {
|
|
return p.Name
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Handler is the whole HTTP surface: the console's own routes, the resolver's
|
|
// API behind the same authentication, and the single-page app.
|
|
func (c *Console) Handler() (http.Handler, error) {
|
|
cfg := c.opt.Config.Get()
|
|
base := strings.TrimSuffix(cfg.API.BasePath, "/")
|
|
own := c.api.Handler()
|
|
|
|
mux := http.NewServeMux()
|
|
// Handled by the shared API server: authentication, the identity of the
|
|
// caller, and the edits whose hooks write to the cluster.
|
|
for _, p := range []string{
|
|
base + "/auth/",
|
|
"GET " + base + "/whoami",
|
|
"GET " + base + "/links",
|
|
"GET " + base + "/healthz",
|
|
"PUT " + base + "/clients",
|
|
"POST " + base + "/zones/records",
|
|
"PUT " + base + "/zones/records",
|
|
"DELETE " + base + "/zones/records",
|
|
"POST " + base + "/zones/delegations",
|
|
"DELETE " + base + "/zones/delegations",
|
|
"DELETE " + base + "/zones",
|
|
"POST " + base + "/blocklists/toggle",
|
|
"GET " + base + "/address-overrides",
|
|
"PUT " + base + "/address-overrides",
|
|
"DELETE " + base + "/address-overrides/{domain}",
|
|
"POST " + base + "/domains",
|
|
"DELETE " + base + "/domains/{name...}",
|
|
} {
|
|
mux.Handle(p, own)
|
|
}
|
|
|
|
// Reads that combine the cluster's objects with the resolver's view.
|
|
mux.Handle("GET "+base+"/zones", c.api.Protect(http.HandlerFunc(c.handleZones), false))
|
|
mux.Handle("GET "+base+"/clients", c.api.Protect(http.HandlerFunc(c.handleClients), false))
|
|
mux.Handle("POST "+base+"/clients/validate", c.api.Protect(http.HandlerFunc(c.handleClientsValidate), false))
|
|
mux.Handle("GET "+base+"/blocklist-sources", c.api.Protect(http.HandlerFunc(c.handleListSources), false))
|
|
mux.Handle("PUT "+base+"/blocklist-sources/{name}", c.api.Protect(http.HandlerFunc(c.handleListSourcePut), true))
|
|
mux.Handle("DELETE "+base+"/blocklist-sources/{name}", c.api.Protect(http.HandlerFunc(c.handleListSourceDelete), true))
|
|
|
|
// Everything else under the API belongs to the resolver.
|
|
proxy := c.opt.Runtime.Proxy()
|
|
read, write := c.api.Protect(proxy, false), c.api.Protect(proxy, true)
|
|
mux.Handle(base+"/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodGet || r.Method == http.MethodHead {
|
|
read.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
write.ServeHTTP(w, r)
|
|
}))
|
|
|
|
if cfg.API.UI {
|
|
ui, err := web.Handler("")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("web console: %w", err)
|
|
}
|
|
mux.Handle("/", ui)
|
|
}
|
|
return mux, nil
|
|
}
|