mirror of
https://github.com/juanfont/headscale.git
synced 2025-11-10 01:20:58 +01:00
wip
This commit is contained in:
parent
46477b8021
commit
47cc4d0f9c
@ -44,6 +44,7 @@ const (
|
|||||||
HeadscaleService_GetPolicy_FullMethodName = "/headscale.v1.HeadscaleService/GetPolicy"
|
HeadscaleService_GetPolicy_FullMethodName = "/headscale.v1.HeadscaleService/GetPolicy"
|
||||||
HeadscaleService_SetPolicy_FullMethodName = "/headscale.v1.HeadscaleService/SetPolicy"
|
HeadscaleService_SetPolicy_FullMethodName = "/headscale.v1.HeadscaleService/SetPolicy"
|
||||||
HeadscaleService_Health_FullMethodName = "/headscale.v1.HeadscaleService/Health"
|
HeadscaleService_Health_FullMethodName = "/headscale.v1.HeadscaleService/Health"
|
||||||
|
HeadscaleService_ListPendingRegistrations_FullMethodName = "/headscale.v1.HeadscaleService/ListPendingRegistrations"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeadscaleServiceClient is the client API for HeadscaleService service.
|
// HeadscaleServiceClient is the client API for HeadscaleService service.
|
||||||
@ -81,6 +82,8 @@ type HeadscaleServiceClient interface {
|
|||||||
SetPolicy(ctx context.Context, in *SetPolicyRequest, opts ...grpc.CallOption) (*SetPolicyResponse, error)
|
SetPolicy(ctx context.Context, in *SetPolicyRequest, opts ...grpc.CallOption) (*SetPolicyResponse, error)
|
||||||
// --- Health start ---
|
// --- Health start ---
|
||||||
Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error)
|
Health(ctx context.Context, in *HealthRequest, opts ...grpc.CallOption) (*HealthResponse, error)
|
||||||
|
// --- Pending registrations ---
|
||||||
|
ListPendingRegistrations(ctx context.Context, in *ListPendingRegistrationsRequest, opts ...grpc.CallOption) (*ListPendingRegistrationsResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type headscaleServiceClient struct {
|
type headscaleServiceClient struct {
|
||||||
@ -341,6 +344,16 @@ func (c *headscaleServiceClient) Health(ctx context.Context, in *HealthRequest,
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *headscaleServiceClient) ListPendingRegistrations(ctx context.Context, in *ListPendingRegistrationsRequest, opts ...grpc.CallOption) (*ListPendingRegistrationsResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(ListPendingRegistrationsResponse)
|
||||||
|
err := c.cc.Invoke(ctx, HeadscaleService_ListPendingRegistrations_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// HeadscaleServiceServer is the server API for HeadscaleService service.
|
// HeadscaleServiceServer is the server API for HeadscaleService service.
|
||||||
// All implementations must embed UnimplementedHeadscaleServiceServer
|
// All implementations must embed UnimplementedHeadscaleServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@ -376,6 +389,8 @@ type HeadscaleServiceServer interface {
|
|||||||
SetPolicy(context.Context, *SetPolicyRequest) (*SetPolicyResponse, error)
|
SetPolicy(context.Context, *SetPolicyRequest) (*SetPolicyResponse, error)
|
||||||
// --- Health start ---
|
// --- Health start ---
|
||||||
Health(context.Context, *HealthRequest) (*HealthResponse, error)
|
Health(context.Context, *HealthRequest) (*HealthResponse, error)
|
||||||
|
// --- Pending registrations ---
|
||||||
|
ListPendingRegistrations(context.Context, *ListPendingRegistrationsRequest) (*ListPendingRegistrationsResponse, error)
|
||||||
mustEmbedUnimplementedHeadscaleServiceServer()
|
mustEmbedUnimplementedHeadscaleServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -461,6 +476,9 @@ func (UnimplementedHeadscaleServiceServer) SetPolicy(context.Context, *SetPolicy
|
|||||||
func (UnimplementedHeadscaleServiceServer) Health(context.Context, *HealthRequest) (*HealthResponse, error) {
|
func (UnimplementedHeadscaleServiceServer) Health(context.Context, *HealthRequest) (*HealthResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Health not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method Health not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedHeadscaleServiceServer) ListPendingRegistrations(context.Context, *ListPendingRegistrationsRequest) (*ListPendingRegistrationsResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method ListPendingRegistrations not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
|
func (UnimplementedHeadscaleServiceServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
|
||||||
func (UnimplementedHeadscaleServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedHeadscaleServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@ -932,6 +950,24 @@ func _HeadscaleService_Health_Handler(srv interface{}, ctx context.Context, dec
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _HeadscaleService_ListPendingRegistrations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(ListPendingRegistrationsRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(HeadscaleServiceServer).ListPendingRegistrations(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: HeadscaleService_ListPendingRegistrations_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(HeadscaleServiceServer).ListPendingRegistrations(ctx, req.(*ListPendingRegistrationsRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// HeadscaleService_ServiceDesc is the grpc.ServiceDesc for HeadscaleService service.
|
// HeadscaleService_ServiceDesc is the grpc.ServiceDesc for HeadscaleService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@ -1039,6 +1075,10 @@ var HeadscaleService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "Health",
|
MethodName: "Health",
|
||||||
Handler: _HeadscaleService_Health_Handler,
|
Handler: _HeadscaleService_Health_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ListPendingRegistrations",
|
||||||
|
Handler: _HeadscaleService_ListPendingRegistrations_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "headscale/v1/headscale.proto",
|
Metadata: "headscale/v1/headscale.proto",
|
||||||
|
|||||||
26
gen/go/headscale/v1/pending.pb.go
Normal file
26
gen/go/headscale/v1/pending.pb.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Code generated manually to provide types for PendingRegistrations RPC until buf generate is run.
|
||||||
|
// This file defines protobuf-compatible Go structs without full reflection metadata.
|
||||||
|
// It is sufficient for compiling server code that references these types.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PendingRegistration is a lightweight representation of a pending registration.
|
||||||
|
type PendingRegistration struct {
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"`
|
||||||
|
MachineKey string `protobuf:"bytes,3,opt,name=machine_key,json=machineKey,proto3" json:"machine_key,omitempty"`
|
||||||
|
NodeKey string `protobuf:"bytes,4,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"`
|
||||||
|
Expiry *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=expiry,proto3" json:"expiry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPendingRegistrationsRequest is the empty request message.
|
||||||
|
type ListPendingRegistrationsRequest struct{}
|
||||||
|
|
||||||
|
// ListPendingRegistrationsResponse contains the current pending registrations.
|
||||||
|
type ListPendingRegistrationsResponse struct {
|
||||||
|
Registrations []*PendingRegistration `protobuf:"bytes,1,rep,name=registrations,proto3" json:"registrations,omitempty"`
|
||||||
|
}
|
||||||
@ -793,4 +793,26 @@ func (api headscaleV1APIServer) Health(
|
|||||||
return response, healthErr
|
return response, healthErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api headscaleV1APIServer) ListPendingRegistrations(
|
||||||
|
ctx context.Context,
|
||||||
|
_ *v1.ListPendingRegistrationsRequest,
|
||||||
|
) (*v1.ListPendingRegistrationsResponse, error) {
|
||||||
|
regs := api.h.state.ListPendingRegistrations()
|
||||||
|
resp := &v1.ListPendingRegistrationsResponse{}
|
||||||
|
for _, r := range regs {
|
||||||
|
var ts *timestamppb.Timestamp
|
||||||
|
if r.Expiry != nil {
|
||||||
|
ts = timestamppb.New(*r.Expiry)
|
||||||
|
}
|
||||||
|
resp.Registrations = append(resp.Registrations, &v1.PendingRegistration{
|
||||||
|
Id: r.ID.String(),
|
||||||
|
Hostname: r.Hostname,
|
||||||
|
MachineKey: r.MachineKey,
|
||||||
|
NodeKey: r.NodeKey,
|
||||||
|
Expiry: ts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
|
func (api headscaleV1APIServer) mustEmbedUnimplementedHeadscaleServiceServer() {}
|
||||||
|
|||||||
@ -69,6 +69,17 @@ type State struct {
|
|||||||
primaryRoutes *routes.PrimaryRoutes
|
primaryRoutes *routes.PrimaryRoutes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PendingRegistration represents a pending node registration entry in memory.
|
||||||
|
// It is populated from the registrationCache and used by API/gRPC layers.
|
||||||
|
// Note: This is an in-memory view only; entries expire automatically from the cache.
|
||||||
|
type PendingRegistration struct {
|
||||||
|
ID types.RegistrationID
|
||||||
|
Hostname string
|
||||||
|
MachineKey string
|
||||||
|
NodeKey string
|
||||||
|
Expiry *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// NewState creates and initializes a new State instance, setting up the database,
|
// NewState creates and initializes a new State instance, setting up the database,
|
||||||
// IP allocator, DERP map, policy manager, and loading existing users and nodes.
|
// IP allocator, DERP map, policy manager, and loading existing users and nodes.
|
||||||
func NewState(cfg *types.Config) (*State, error) {
|
func NewState(cfg *types.Config) (*State, error) {
|
||||||
@ -968,6 +979,36 @@ func (s *State) SetRegistrationCacheEntry(id types.RegistrationID, entry types.R
|
|||||||
s.registrationCache.Set(id, entry)
|
s.registrationCache.Set(id, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListPendingRegistrations returns a snapshot of current pending registrations.
|
||||||
|
// It iterates the registrationCache and extracts minimal identifying details.
|
||||||
|
func (s *State) ListPendingRegistrations() []PendingRegistration {
|
||||||
|
if s.registrationCache == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var regs []PendingRegistration
|
||||||
|
|
||||||
|
// zcache/v2 supports Keys iteration; use Range if available for efficiency.
|
||||||
|
// We use Keys here and then look up to read values.
|
||||||
|
for _, id := range s.registrationCache.Keys() {
|
||||||
|
if rn, ok := s.registrationCache.Get(id); ok {
|
||||||
|
var exp *time.Time
|
||||||
|
if rn.Node.Expiry != nil {
|
||||||
|
exp = rn.Node.Expiry
|
||||||
|
}
|
||||||
|
regs = append(regs, PendingRegistration{
|
||||||
|
ID: id,
|
||||||
|
Hostname: rn.Node.Hostname,
|
||||||
|
MachineKey: rn.Node.MachineKey.String(),
|
||||||
|
NodeKey: rn.Node.NodeKey.String(),
|
||||||
|
Expiry: exp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return regs
|
||||||
|
}
|
||||||
|
|
||||||
// logHostinfoValidation logs warnings when hostinfo is nil or has empty hostname.
|
// logHostinfoValidation logs warnings when hostinfo is nil or has empty hostname.
|
||||||
func logHostinfoValidation(machineKey, nodeKey, username, hostname string, hostinfo *tailcfg.Hostinfo) {
|
func logHostinfoValidation(machineKey, nodeKey, username, hostname string, hostinfo *tailcfg.Hostinfo) {
|
||||||
if hostinfo == nil {
|
if hostinfo == nil {
|
||||||
|
|||||||
48
hscontrol/types/pending_registration.go
Normal file
48
hscontrol/types/pending_registration.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PendingRegistrationProto converts a state.PendingRegistration-like struct to protobuf.
|
||||||
|
// Kept in types to avoid circular imports from hscontrol/state.
|
||||||
|
// Input is the plain fields to serialize.
|
||||||
|
type PendingRegistrationProto struct {
|
||||||
|
ID string
|
||||||
|
Hostname string
|
||||||
|
MachineKey string
|
||||||
|
NodeKey string
|
||||||
|
// Expiry may be nil
|
||||||
|
ExpiryUnixMilli int64 // not used; we will pass a pointer time via Timestamppb in helpers if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildPendingRegistration constructs a v1.PendingRegistration.
|
||||||
|
func BuildPendingRegistration(id, hostname, machineKey, nodeKey string, expiry *int64) *v1.PendingRegistration {
|
||||||
|
pr := &v1.PendingRegistration{
|
||||||
|
Id: id,
|
||||||
|
Hostname: hostname,
|
||||||
|
MachineKey: machineKey,
|
||||||
|
NodeKey: nodeKey,
|
||||||
|
}
|
||||||
|
if expiry != nil {
|
||||||
|
// We cannot convert int64 millis without time. Keep the field for potential future.
|
||||||
|
// Callers should prefer using BuildPendingRegistrationWithTimestamp.
|
||||||
|
_ = expiry
|
||||||
|
}
|
||||||
|
return pr
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildPendingRegistrationWithTimestamp sets a proper protobuf timestamp if provided.
|
||||||
|
func BuildPendingRegistrationWithTimestamp(id, hostname, machineKey, nodeKey string, ts *timestamppb.Timestamp) *v1.PendingRegistration {
|
||||||
|
pr := &v1.PendingRegistration{
|
||||||
|
Id: id,
|
||||||
|
Hostname: hostname,
|
||||||
|
MachineKey: machineKey,
|
||||||
|
NodeKey: nodeKey,
|
||||||
|
}
|
||||||
|
if ts != nil {
|
||||||
|
pr.Expiry = ts
|
||||||
|
}
|
||||||
|
return pr
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ package headscale.v1;
|
|||||||
option go_package = "github.com/juanfont/headscale/gen/go/v1";
|
option go_package = "github.com/juanfont/headscale/gen/go/v1";
|
||||||
|
|
||||||
import "google/api/annotations.proto";
|
import "google/api/annotations.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
import "headscale/v1/user.proto";
|
import "headscale/v1/user.proto";
|
||||||
import "headscale/v1/preauthkey.proto";
|
import "headscale/v1/preauthkey.proto";
|
||||||
@ -190,6 +191,14 @@ service HeadscaleService {
|
|||||||
}
|
}
|
||||||
// --- Health end ---
|
// --- Health end ---
|
||||||
|
|
||||||
|
// --- Pending registrations ---
|
||||||
|
rpc ListPendingRegistrations(ListPendingRegistrationsRequest) returns (ListPendingRegistrationsResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
get : "/api/v1/pending-registrations"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// --- Pending registrations end ---
|
||||||
|
|
||||||
// Implement Tailscale API
|
// Implement Tailscale API
|
||||||
// rpc GetDevice(GetDeviceRequest) returns(GetDeviceResponse) {
|
// rpc GetDevice(GetDeviceRequest) returns(GetDeviceResponse) {
|
||||||
// option(google.api.http) = {
|
// option(google.api.http) = {
|
||||||
@ -223,3 +232,17 @@ message HealthRequest {}
|
|||||||
message HealthResponse {
|
message HealthResponse {
|
||||||
bool database_connectivity = 1;
|
bool database_connectivity = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message PendingRegistration {
|
||||||
|
string id = 1;
|
||||||
|
string hostname = 2;
|
||||||
|
string machine_key = 3;
|
||||||
|
string node_key = 4;
|
||||||
|
google.protobuf.Timestamp expiry = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListPendingRegistrationsRequest {}
|
||||||
|
|
||||||
|
message ListPendingRegistrationsResponse {
|
||||||
|
repeated PendingRegistration registrations = 1;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user