oidc: add confirmation page for node registration
Render an interstitial showing device hostname, OS, and machine-key
fingerprint before finalising OIDC registration. The user must POST
to /register/confirm/{auth_id} with a CSRF double-submit cookie.
Removes the TODO at oidc.go:201.
This commit is contained in:
parent
d5a4e6e36a
commit
d66d3a4269
7 changed files with 575 additions and 23 deletions
101
hscontrol/templates/register_confirm.go
Normal file
101
hscontrol/templates/register_confirm.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"github.com/chasefleming/elem-go"
|
||||
"github.com/chasefleming/elem-go/attrs"
|
||||
)
|
||||
|
||||
// RegisterConfirmInfo carries the human-readable information shown on
|
||||
// the registration confirmation interstitial that an OIDC-authenticated
|
||||
// user must explicitly accept before a pending node is registered to
|
||||
// their identity. The fields here intentionally include enough device
|
||||
// detail (hostname, OS, machine-key fingerprint) for the user to
|
||||
// recognise whether the device they are about to claim is in fact
|
||||
// theirs.
|
||||
type RegisterConfirmInfo struct {
|
||||
// FormAction is the absolute or relative URL the confirm form
|
||||
// POSTs to. Typically /register/confirm/{auth_id}.
|
||||
FormAction string
|
||||
|
||||
// CSRFTokenName is the name of the hidden form field carrying the
|
||||
// CSRF token. The corresponding cookie shares this name.
|
||||
CSRFTokenName string
|
||||
|
||||
// CSRFToken is the per-session token that must match the value of
|
||||
// the cookie set by the OIDC callback before the POST is honoured.
|
||||
CSRFToken string
|
||||
|
||||
// User is the OIDC-authenticated identity the device will be
|
||||
// registered to if the user confirms.
|
||||
User string
|
||||
|
||||
// Hostname is the hostname the registering tailscaled instance
|
||||
// reported in its RegisterRequest.
|
||||
Hostname string
|
||||
|
||||
// OS is the operating system the registering tailscaled reported.
|
||||
// May be the empty string when the client did not send Hostinfo.
|
||||
OS string
|
||||
|
||||
// MachineKey is the short fingerprint of the registering machine
|
||||
// key. The full key is intentionally not shown.
|
||||
MachineKey string
|
||||
}
|
||||
|
||||
// RegisterConfirm renders an interstitial page that asks the
|
||||
// OIDC-authenticated user to explicitly confirm that they want to
|
||||
// register the named device under their account. Without this
|
||||
// confirmation step a single GET to /register/{auth_id} could
|
||||
// silently complete a phishing-style registration when the victim's
|
||||
// IdP allows silent SSO.
|
||||
func RegisterConfirm(info RegisterConfirmInfo) *elem.Element {
|
||||
deviceList := elem.Ul(nil,
|
||||
elem.Li(nil, elem.Strong(nil, elem.Text("Hostname: ")), elem.Text(info.Hostname)),
|
||||
elem.Li(nil, elem.Strong(nil, elem.Text("OS: ")), elem.Text(displayOrUnknown(info.OS))),
|
||||
elem.Li(nil, elem.Strong(nil, elem.Text("Machine key: ")), Code(elem.Text(info.MachineKey))),
|
||||
elem.Li(nil, elem.Strong(nil, elem.Text("Will be registered to: ")), elem.Text(info.User)),
|
||||
)
|
||||
|
||||
form := elem.Form(
|
||||
attrs.Props{
|
||||
attrs.Method: "POST",
|
||||
attrs.Action: info.FormAction,
|
||||
},
|
||||
elem.Input(attrs.Props{
|
||||
attrs.Type: "hidden",
|
||||
attrs.Name: info.CSRFTokenName,
|
||||
attrs.Value: info.CSRFToken,
|
||||
}),
|
||||
elem.Button(
|
||||
attrs.Props{attrs.Type: "submit"},
|
||||
elem.Text("Confirm registration"),
|
||||
),
|
||||
)
|
||||
|
||||
return HtmlStructure(
|
||||
elem.Title(nil, elem.Text("Headscale - Confirm node registration")),
|
||||
mdTypesetBody(
|
||||
headscaleLogo(),
|
||||
H2(elem.Text("Confirm node registration")),
|
||||
P(elem.Text(
|
||||
"A device is asking to be added to your tailnet. "+
|
||||
"Please review the details below and confirm that this device is yours.",
|
||||
)),
|
||||
deviceList,
|
||||
form,
|
||||
P(elem.Text(
|
||||
"If you do not recognise this device, close this window. "+
|
||||
"The registration request will expire automatically.",
|
||||
)),
|
||||
pageFooter(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func displayOrUnknown(s string) string {
|
||||
if s == "" {
|
||||
return "(unknown)"
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue