Post-repair hardware validation pipeline for Proxmox cluster hosts. Go orchestrator + in-image agent + mkosi live image + bundled dnsmasq PXE + SQLite + HTMX/SSE UI + notify registry + janitor + full docs.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const cookieName = "vetting_session"
|
||||
|
||||
type Manager struct {
|
||||
PasswordHash string
|
||||
Secret []byte
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
func (m *Manager) VerifyPassword(password string) bool {
|
||||
if m.PasswordHash == "" {
|
||||
return false
|
||||
}
|
||||
return bcrypt.CompareHashAndPassword([]byte(m.PasswordHash), []byte(password)) == nil
|
||||
}
|
||||
|
||||
// Issue writes a signed session cookie valid for m.TTL.
|
||||
func (m *Manager) Issue(w http.ResponseWriter, r *http.Request) {
|
||||
expiry := time.Now().Add(m.TTL).Unix()
|
||||
payload := strconv.FormatInt(expiry, 10)
|
||||
sig := m.sign(payload)
|
||||
value := payload + "." + sig
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieName,
|
||||
Value: value,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: r.TLS != nil,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Expires: time.Unix(expiry, 0),
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Manager) Clear(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieName,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
MaxAge: -1,
|
||||
})
|
||||
}
|
||||
|
||||
var errInvalidSession = errors.New("invalid session")
|
||||
|
||||
// Validate returns nil if the request's cookie is present, signed, and not expired.
|
||||
func (m *Manager) Validate(r *http.Request) error {
|
||||
c, err := r.Cookie(cookieName)
|
||||
if err != nil {
|
||||
return errInvalidSession
|
||||
}
|
||||
parts := strings.SplitN(c.Value, ".", 2)
|
||||
if len(parts) != 2 {
|
||||
return errInvalidSession
|
||||
}
|
||||
payload, sig := parts[0], parts[1]
|
||||
expected := m.sign(payload)
|
||||
if !hmac.Equal([]byte(sig), []byte(expected)) {
|
||||
return errInvalidSession
|
||||
}
|
||||
expiry, err := strconv.ParseInt(payload, 10, 64)
|
||||
if err != nil {
|
||||
return errInvalidSession
|
||||
}
|
||||
if time.Now().Unix() >= expiry {
|
||||
return errInvalidSession
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) sign(payload string) string {
|
||||
mac := hmac.New(sha256.New, m.Secret)
|
||||
_, _ = mac.Write([]byte(payload))
|
||||
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
// BcryptHash is a helper used by the gen-admin-password tool.
|
||||
func BcryptHash(password string) (string, error) {
|
||||
b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("bcrypt: %w", err)
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
Reference in New Issue
Block a user