policy/v2: align SSH check action with SaaS wire format
SSH check rules now emit CheckPeriod in seconds (matching Tailscale SaaS) instead of nanoseconds. Adds golden compat tests covering accept/check modes. Updates #3157
This commit is contained in:
parent
ded51a4d30
commit
affaa1a31d
@ -169,7 +169,7 @@ func (h *Headscale) NoiseUpgradeHandler(
|
|||||||
r.Post("/map", ns.PollNetMapHandler)
|
r.Post("/map", ns.PollNetMapHandler)
|
||||||
|
|
||||||
// SSH Check mode endpoint, consulted to validate if a given SSH connection should be accepted or rejected.
|
// SSH Check mode endpoint, consulted to validate if a given SSH connection should be accepted or rejected.
|
||||||
r.Get("/ssh/action/from/{src_node_id}/to/{dst_node_id}", ns.SSHActionHandler)
|
r.Get("/ssh/action/{src_node_id}/to/{dst_node_id}", ns.SSHActionHandler)
|
||||||
|
|
||||||
// Not implemented yet
|
// Not implemented yet
|
||||||
//
|
//
|
||||||
@ -414,7 +414,6 @@ func (ns *noiseServer) SSHActionHandler(
|
|||||||
reqLog := log.With().
|
reqLog := log.With().
|
||||||
Uint64("src_node_id", srcNodeID.Uint64()).
|
Uint64("src_node_id", srcNodeID.Uint64()).
|
||||||
Uint64("dst_node_id", dstNodeID.Uint64()).
|
Uint64("dst_node_id", dstNodeID.Uint64()).
|
||||||
Str("ssh_user", req.URL.Query().Get("ssh_user")).
|
|
||||||
Str("local_user", req.URL.Query().Get("local_user")).
|
Str("local_user", req.URL.Query().Get("local_user")).
|
||||||
Logger()
|
Logger()
|
||||||
|
|
||||||
@ -515,8 +514,8 @@ func (ns *noiseServer) sshActionHoldAndDelegate(
|
|||||||
) (*tailcfg.SSHAction, error) {
|
) (*tailcfg.SSHAction, error) {
|
||||||
holdURL, err := url.Parse(
|
holdURL, err := url.Parse(
|
||||||
ns.headscale.cfg.ServerURL +
|
ns.headscale.cfg.ServerURL +
|
||||||
"/machine/ssh/action/from/$SRC_NODE_ID/to/$DST_NODE_ID" +
|
"/machine/ssh/action/$SRC_NODE_ID/to/$DST_NODE_ID" +
|
||||||
"?ssh_user=$SSH_USER&local_user=$LOCAL_USER",
|
"?local_user=$LOCAL_USER",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewHTTPError(
|
return nil, NewHTTPError(
|
||||||
|
|||||||
@ -198,13 +198,50 @@ func TestRegistrationHandler_OversizedBody(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusBadRequest, rec.Code)
|
assert.Equal(t, http.StatusBadRequest, rec.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSSHActionRoute_OldPathReturns404 pins the wire-format shape of the
|
||||||
|
// SSH check-action endpoint. Pre-alignment headscale served
|
||||||
|
// /machine/ssh/action/from/{src}/to/{dst}?ssh_user=...; the current
|
||||||
|
// endpoint is /machine/ssh/action/{src}/to/{dst}?local_user=.... If
|
||||||
|
// someone re-adds the old route shape, this fails.
|
||||||
|
func TestSSHActionRoute_OldPathReturns404(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Route("/machine", func(r chi.Router) {
|
||||||
|
r.Get("/ssh/action/{src_node_id}/to/{dst_node_id}", func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{"new", "/machine/ssh/action/1/to/2", http.StatusOK},
|
||||||
|
{"old-with-from", "/machine/ssh/action/from/1/to/2", http.StatusNotFound},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, tc.path, nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.want, rec.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// newSSHActionRequest builds an httptest request with the chi URL params
|
// newSSHActionRequest builds an httptest request with the chi URL params
|
||||||
// SSHActionHandler reads (src_node_id and dst_node_id), so the handler
|
// SSHActionHandler reads (src_node_id and dst_node_id), so the handler
|
||||||
// can be exercised directly without going through the chi router.
|
// can be exercised directly without going through the chi router.
|
||||||
func newSSHActionRequest(t *testing.T, src, dst types.NodeID) *http.Request {
|
func newSSHActionRequest(t *testing.T, src, dst types.NodeID) *http.Request {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
url := fmt.Sprintf("/machine/ssh/action/from/%d/to/%d", src.Uint64(), dst.Uint64())
|
url := fmt.Sprintf("/machine/ssh/action/%d/to/%d", src.Uint64(), dst.Uint64())
|
||||||
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||||
|
|
||||||
rctx := chi.NewRouteContext()
|
rctx := chi.NewRouteContext()
|
||||||
@ -315,7 +352,7 @@ func TestSSHActionFollowUp_RejectsBindingMismatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf(
|
url := fmt.Sprintf(
|
||||||
"/machine/ssh/action/from/%d/to/%d?auth_id=%s",
|
"/machine/ssh/action/%d/to/%d?auth_id=%s",
|
||||||
srcOther.ID.Uint64(), dstOther.ID.Uint64(), authID.String(),
|
srcOther.ID.Uint64(), dstOther.ID.Uint64(), authID.String(),
|
||||||
)
|
)
|
||||||
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user