Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9587d11e21 |
@@ -239,8 +239,7 @@ func runServer() {
|
|||||||
meta.Register(api, mods)
|
meta.Register(api, mods)
|
||||||
meta.RegisterHealth(api, sessions)
|
meta.RegisterHealth(api, sessions)
|
||||||
meta.RegisterWhoami(api, sessions, roles, mods)
|
meta.RegisterWhoami(api, sessions, roles, mods)
|
||||||
meta.ConfigPath = configPath
|
meta.RegisterUpdate(api, configPath)
|
||||||
meta.RegisterUpdate(api)
|
|
||||||
|
|
||||||
auth.RegisterLogin(api, sessions, auditStore, cfg.SecureCookie())
|
auth.RegisterLogin(api, sessions, auditStore, cfg.SecureCookie())
|
||||||
auth.RegisterLogout(api, sessions, cfg.SecureCookie())
|
auth.RegisterLogout(api, sessions, cfg.SecureCookie())
|
||||||
|
|||||||
+3
-2
@@ -1,8 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
@@ -24,7 +25,7 @@ func serverCert(certPath, keyPath string) (tls.Certificate, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateSelfSignedCert() (tls.Certificate, error) {
|
func generateSelfSignedCert() (tls.Certificate, error) {
|
||||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, err
|
return tls.Certificate{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,13 +110,25 @@ func Load(path string) (*File, error) {
|
|||||||
return nil, fmt.Errorf("parse config %s: %w", path, err)
|
return nil, fmt.Errorf("parse config %s: %w", path, err)
|
||||||
}
|
}
|
||||||
// release_repo, when set, is downloaded over the wire and (for /api/update)
|
// release_repo, when set, is downloaded over the wire and (for /api/update)
|
||||||
// executed. Reject http:// at the boundary so /install.sh and the updater
|
// executed. Validate shape + scheme once here so /install.sh and the updater
|
||||||
// never have to re-check.
|
// can use the string directly. Trim any trailing slash so downstream string
|
||||||
|
// concatenation produces a clean URL.
|
||||||
if f.Server.ReleaseRepo != "" {
|
if f.Server.ReleaseRepo != "" {
|
||||||
|
f.Server.ReleaseRepo = strings.TrimRight(f.Server.ReleaseRepo, "/")
|
||||||
u, err := url.Parse(f.Server.ReleaseRepo)
|
u, err := url.Parse(f.Server.ReleaseRepo)
|
||||||
if err != nil || u.Scheme != "https" {
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("server.release_repo: %w", err)
|
||||||
|
}
|
||||||
|
if u.Scheme != "https" {
|
||||||
return nil, fmt.Errorf("server.release_repo must use https:// (got %q)", f.Server.ReleaseRepo)
|
return nil, fmt.Errorf("server.release_repo must use https:// (got %q)", f.Server.ReleaseRepo)
|
||||||
}
|
}
|
||||||
|
if u.Host == "" {
|
||||||
|
return nil, fmt.Errorf("server.release_repo missing host: %q", f.Server.ReleaseRepo)
|
||||||
|
}
|
||||||
|
parts := strings.Split(strings.Trim(u.Path, "/"), "/")
|
||||||
|
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||||
|
return nil, fmt.Errorf("server.release_repo must be https://host/owner/repo, got %q", f.Server.ReleaseRepo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &f, nil
|
return &f, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,18 @@ import (
|
|||||||
"github.com/danielgtaylor/huma/v2"
|
"github.com/danielgtaylor/huma/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigPath is set at startup so the update handler can re-load config and
|
// RegisterUpdate wires POST /api/update. It runs the equivalent of
|
||||||
// surface release_repo / parse errors to the caller instead of only stderr.
|
|
||||||
var ConfigPath string
|
|
||||||
|
|
||||||
// RegisterUpdate wires POST /api/meta/update. It runs the equivalent of
|
|
||||||
// `sudo nadir update` in a detached session and returns 202 immediately; the
|
// `sudo nadir update` in a detached session and returns 202 immediately; the
|
||||||
// systemctl restart that ends the updater drops in-flight connections, so the
|
// systemctl restart that ends the updater drops in-flight connections, so the
|
||||||
// caller should poll /api/health to confirm the new version is up.
|
// caller should poll /api/health to confirm the new version is up.
|
||||||
//
|
//
|
||||||
|
// configPath is re-read by the handler so a missing release_repo (or any other
|
||||||
|
// config error) surfaces as 4xx/5xx to the caller, not as stderr only.
|
||||||
|
//
|
||||||
// Authorization: requires (meta, root). Only roles with a wildcard grant
|
// Authorization: requires (meta, root). Only roles with a wildcard grant
|
||||||
// (the default admin role) match, since "meta" isn't a real module with a
|
// (the default admin role) match, since "meta" isn't a real module with a
|
||||||
// declared permission vocabulary.
|
// declared permission vocabulary.
|
||||||
func RegisterUpdate(api huma.API) {
|
func RegisterUpdate(api huma.API, configPath string) {
|
||||||
huma.Register(api, huma.Operation{
|
huma.Register(api, huma.Operation{
|
||||||
OperationID: "meta-update",
|
OperationID: "meta-update",
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
@@ -36,13 +35,13 @@ func RegisterUpdate(api huma.API) {
|
|||||||
Errors: []int{400, 401, 403, 500},
|
Errors: []int{400, 401, 403, 500},
|
||||||
DefaultStatus: 202,
|
DefaultStatus: 202,
|
||||||
}, func(ctx context.Context, _ *struct{}) (*oscmd.StatusOutput, error) {
|
}, func(ctx context.Context, _ *struct{}) (*oscmd.StatusOutput, error) {
|
||||||
if ConfigPath != "" {
|
if configPath != "" {
|
||||||
cfg, err := config.Load(ConfigPath)
|
cfg, err := config.Load(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, huma.Error500InternalServerError("config load failed", err)
|
return nil, huma.Error500InternalServerError("config load failed", err)
|
||||||
}
|
}
|
||||||
if cfg.Server.ReleaseRepo == "" {
|
if cfg.Server.ReleaseRepo == "" {
|
||||||
return nil, huma.Error400BadRequest("server.release_repo not set in " + ConfigPath)
|
return nil, huma.Error400BadRequest("server.release_repo not set in " + configPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
|
|||||||
@@ -160,10 +160,11 @@ func registerLocale(api huma.API) {
|
|||||||
Metadata: op("read"),
|
Metadata: op("read"),
|
||||||
Errors: readErrors,
|
Errors: readErrors,
|
||||||
}, func(ctx context.Context, _ *struct{}) (*KeymapsOutput, error) {
|
}, func(ctx context.Context, _ *struct{}) (*KeymapsOutput, error) {
|
||||||
keymaps, err := oscmd.RunLines("localectl", "list-keymaps")
|
// ponytail: minimal servers ship without kbd / /usr/share/keymaps, so
|
||||||
if err != nil {
|
// localectl errors instead of returning empty. Treat that as "no keymaps
|
||||||
return nil, huma.Error500InternalServerError("localectl failed", err)
|
// available" rather than a server fault — set-keymap stays unreachable
|
||||||
}
|
// because nothing will be in the allowlist.
|
||||||
|
keymaps, _ := oscmd.RunLines("localectl", "list-keymaps")
|
||||||
out := &KeymapsOutput{}
|
out := &KeymapsOutput{}
|
||||||
out.Body.Keymaps = keymaps
|
out.Body.Keymaps = keymaps
|
||||||
return out, nil
|
return out, nil
|
||||||
@@ -185,10 +186,9 @@ func registerLocale(api huma.API) {
|
|||||||
if km == "" {
|
if km == "" {
|
||||||
return nil, huma.Error400BadRequest("empty keymap")
|
return nil, huma.Error400BadRequest("empty keymap")
|
||||||
}
|
}
|
||||||
keymaps, err := oscmd.RunLines("localectl", "list-keymaps")
|
// list-keymaps failure means no keymap allowlist on this host (kbd absent);
|
||||||
if err != nil {
|
// fall through to unknown-keymap 400 instead of 500.
|
||||||
return nil, huma.Error500InternalServerError("localectl failed", err)
|
keymaps, _ := oscmd.RunLines("localectl", "list-keymaps")
|
||||||
}
|
|
||||||
if !slices.Contains(keymaps, km) {
|
if !slices.Contains(keymaps, km) {
|
||||||
return nil, huma.Error400BadRequest("unknown keymap: " + km)
|
return nil, huma.Error400BadRequest("unknown keymap: " + km)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user