software steht so weit.
jetzt build + docker build und testen
This commit is contained in:
1 parent
42f0c0accb
commit
ca6cdcca63
90 files changed
+10298
-480
No files matched your search
+89
-12
@@ -5,49 +5,80 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TokenPrefix kennzeichnet Enrollment-Tokens.
|
||||
const TokenPrefix = "sltn_enroll_"
|
||||
|
||||
// ErrInvalidToken: Der Token-String hat nicht das Format
|
||||
// sltn_enroll_<secret>.<fingerprint>.
|
||||
var ErrInvalidToken = errors.New("crypto: ungültiger Enrollment-Token")
|
||||
var (
|
||||
// ErrInvalidToken: Der Token-String hat nicht das Format
|
||||
// sltn_enroll_<secret>.<fingerprint>[.<server-url>].
|
||||
ErrInvalidToken = errors.New("crypto: ungültiger Enrollment-Token")
|
||||
// ErrInvalidServerURL: keine reine https://host[:port]-Adresse.
|
||||
ErrInvalidServerURL = errors.New("crypto: ungültige Server-URL")
|
||||
)
|
||||
|
||||
const maxServerURLLen = 255
|
||||
|
||||
// EnrollmentToken ist die Einladung für einen neuen Agenten.
|
||||
//
|
||||
// Secret (inkl. Präfix) ist der geheime Teil: Der Server speichert davon nur
|
||||
// den Hash (store.AddEnrollmentToken), der Agent schickt es beim Enrollment.
|
||||
// Fingerprint ist nicht geheim; er sagt dem Agenten, welches
|
||||
// Server-Zertifikat er beim Erstkontakt erwarten muss.
|
||||
// Server-Zertifikat er erwarten muss. ServerURL (optional, nicht geheim) ist
|
||||
// die Adresse, unter der der Agent den Server erreicht – eine falsche
|
||||
// Adresse führt dank Fingerprint-Prüfung höchstens zu einem
|
||||
// Verbindungsfehler, nie zu einem falschen Server.
|
||||
type EnrollmentToken struct {
|
||||
Secret string
|
||||
Fingerprint Fingerprint
|
||||
ServerURL string
|
||||
}
|
||||
|
||||
// String liefert die Form, die der Admin an den Agenten weitergibt.
|
||||
func (t EnrollmentToken) String() string { return t.Secret + "." + string(t.Fingerprint) }
|
||||
// String liefert die Form, die der Admin an den Agenten weitergibt. Alle
|
||||
// Teile sind Base64url – der Token ist ohne Quoting shell-sicher.
|
||||
func (t EnrollmentToken) String() string {
|
||||
s := t.Secret + "." + string(t.Fingerprint)
|
||||
if t.ServerURL != "" {
|
||||
s += "." + base64.RawURLEncoding.EncodeToString([]byte(t.ServerURL))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewEnrollmentToken erzeugt einen Token mit 256 Bit Zufall für den Server
|
||||
// mit Fingerprint fp.
|
||||
func NewEnrollmentToken(fp Fingerprint) (EnrollmentToken, error) {
|
||||
// mit Fingerprint fp; serverURL ist optional ("" = nicht einbetten).
|
||||
func NewEnrollmentToken(fp Fingerprint, serverURL string) (EnrollmentToken, error) {
|
||||
if _, err := ParseFingerprint(string(fp)); err != nil {
|
||||
return EnrollmentToken{}, err
|
||||
}
|
||||
if serverURL != "" {
|
||||
u, err := ValidateServerURL(serverURL)
|
||||
if err != nil {
|
||||
return EnrollmentToken{}, err
|
||||
}
|
||||
serverURL = u
|
||||
}
|
||||
var b [32]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
return EnrollmentToken{}, err
|
||||
}
|
||||
return EnrollmentToken{Secret: TokenPrefix + base64.RawURLEncoding.EncodeToString(b[:]), Fingerprint: fp}, nil
|
||||
return EnrollmentToken{
|
||||
Secret: TokenPrefix + base64.RawURLEncoding.EncodeToString(b[:]),
|
||||
Fingerprint: fp,
|
||||
ServerURL: serverURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseEnrollmentToken zerlegt einen Token-String. Leerraum am Rand (beim
|
||||
// Kopieren) wird ignoriert.
|
||||
func ParseEnrollmentToken(s string) (EnrollmentToken, error) {
|
||||
parts := strings.Split(strings.TrimSpace(s), ".")
|
||||
if len(parts) != 2 {
|
||||
return EnrollmentToken{}, fmt.Errorf("%w: erwartet %s<secret>.<fingerprint>", ErrInvalidToken, TokenPrefix)
|
||||
if len(parts) != 2 && len(parts) != 3 {
|
||||
return EnrollmentToken{}, fmt.Errorf("%w: erwartet %s<secret>.<fingerprint>[.<server-url>]", ErrInvalidToken, TokenPrefix)
|
||||
}
|
||||
secret, rawFP := parts[0], parts[1]
|
||||
body, ok := strings.CutPrefix(secret, TokenPrefix)
|
||||
@@ -61,5 +92,51 @@ func ParseEnrollmentToken(s string) (EnrollmentToken, error) {
|
||||
if err != nil {
|
||||
return EnrollmentToken{}, fmt.Errorf("%w: %w", ErrInvalidToken, err)
|
||||
}
|
||||
return EnrollmentToken{Secret: secret, Fingerprint: fp}, nil
|
||||
tok := EnrollmentToken{Secret: secret, Fingerprint: fp}
|
||||
if len(parts) == 3 {
|
||||
raw, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||
if err != nil || len(raw) == 0 {
|
||||
return EnrollmentToken{}, fmt.Errorf("%w: Server-URL beschädigt", ErrInvalidToken)
|
||||
}
|
||||
if tok.ServerURL, err = ValidateServerURL(string(raw)); err != nil {
|
||||
return EnrollmentToken{}, fmt.Errorf("%w: %w", ErrInvalidToken, err)
|
||||
}
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// ValidateServerURL prüft eine Server-Adresse und normalisiert sie: nur
|
||||
// https, nur Host und optional Port (kein Benutzer, Pfad, Query, Fragment),
|
||||
// Host klein, ohne Schrägstrich am Ende.
|
||||
func ValidateServerURL(raw string) (string, error) {
|
||||
bad := func(why string) (string, error) {
|
||||
return "", fmt.Errorf("%w: %q – %s (erwartet https://host[:port])", ErrInvalidServerURL, raw, why)
|
||||
}
|
||||
if len(raw) > maxServerURLLen {
|
||||
return bad("zu lang")
|
||||
}
|
||||
u, err := url.Parse(strings.TrimSpace(raw))
|
||||
switch {
|
||||
case err != nil:
|
||||
return bad("nicht lesbar")
|
||||
case u.Scheme != "https":
|
||||
return bad("nur https")
|
||||
case u.Hostname() == "":
|
||||
return bad("Host fehlt")
|
||||
case u.User != nil:
|
||||
return bad("keine Zugangsdaten in der URL")
|
||||
case u.Opaque != "" || (u.Path != "" && u.Path != "/") || u.RawQuery != "" || u.ForceQuery || u.Fragment != "":
|
||||
return bad("kein Pfad, keine Query")
|
||||
}
|
||||
host := strings.ToLower(u.Hostname())
|
||||
if port := u.Port(); port != "" {
|
||||
if n, err := strconv.Atoi(port); err != nil || n < 1 || n > 65535 {
|
||||
return bad("ungültiger Port")
|
||||
}
|
||||
return "https://" + net.JoinHostPort(host, port), nil
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
host = "[" + host + "]"
|
||||
}
|
||||
return "https://" + host, nil
|
||||
}
|
||||
Reference in new issue
Block a user