Record project continuation changes

This commit is contained in:
2026-05-12 21:02:29 +03:00
parent 3059d1d7a3
commit 8f69d53193
339 changed files with 101111 additions and 1769 deletions
+15 -7
View File
@@ -14,12 +14,13 @@ const (
)
type User struct {
ID string
Email string
PasswordHash string
MFAEnabled bool
CreatedAt time.Time
UpdatedAt time.Time
ID string `json:"id"`
Email string `json:"email"`
PasswordHash string `json:"-"`
MFAEnabled bool `json:"mfa_enabled"`
PlatformRole string `json:"platform_role"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Device struct {
@@ -40,7 +41,7 @@ type AuthSession struct {
ID string
UserID string
DeviceID string
RefreshTokenHash string
RefreshTokenHash string `json:"-"`
RefreshExpiresAt time.Time
LastSeenAt *time.Time
LastRotatedAt *time.Time
@@ -69,6 +70,13 @@ type BootstrapOwnerCommand struct {
ActivationSignature string `json:"activation_signature"`
}
type CreateUserCommand struct {
ActorUserID string `json:"actor_user_id"`
Email string `json:"email"`
Password string `json:"password"`
PlatformRole string `json:"platform_role"`
}
type RevokeAuthSessionCommand struct {
UserID string `json:"user_id"`
AuthSessionID string `json:"auth_session_id"`
+30
View File
@@ -34,6 +34,10 @@ func (m *Module) RegisterRoutes(router chi.Router) {
r.Get("/devices", m.handleTrustedDevices)
r.Post("/devices/{deviceID}/revoke", m.handleRevokeTrustedDevice)
})
router.Route("/users", func(r chi.Router) {
r.Get("/", m.handleListUsers)
r.Post("/", m.handleCreateUser)
})
}
func (m *Module) handleInstallationStatus(w http.ResponseWriter, r *http.Request) {
@@ -78,6 +82,32 @@ func (m *Module) handleLogin(w http.ResponseWriter, r *http.Request) {
httpx.WriteJSON(w, http.StatusOK, result)
}
func (m *Module) handleListUsers(w http.ResponseWriter, r *http.Request) {
actorUserID := r.URL.Query().Get("actor_user_id")
users, err := m.service.ListUsers(r.Context(), actorUserID)
if err != nil {
status, message := m.service.MapError(err)
httpx.WriteError(w, status, message)
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"users": users})
}
func (m *Module) handleCreateUser(w http.ResponseWriter, r *http.Request) {
var cmd CreateUserCommand
if err := json.NewDecoder(r.Body).Decode(&cmd); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid user payload")
return
}
user, err := m.service.CreateUser(r.Context(), cmd)
if err != nil {
status, message := m.service.MapError(err)
httpx.WriteError(w, status, message)
return
}
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"user": user})
}
func (m *Module) handleRefresh(w http.ResponseWriter, r *http.Request) {
var cmd RefreshCommand
if err := json.NewDecoder(r.Body).Decode(&cmd); err != nil {
@@ -70,7 +70,7 @@ type postgresInstallationRepository struct {
func (r *postgresUserRepository) GetByEmail(ctx context.Context, email string) (*User, error) {
const query = `
SELECT id::text, email, password_hash, mfa_enabled, created_at, updated_at
SELECT id::text, email, password_hash, mfa_enabled, platform_role, created_at, updated_at
FROM users
WHERE email = $1
`
@@ -79,13 +79,53 @@ WHERE email = $1
func (r *postgresUserRepository) GetByID(ctx context.Context, userID string) (*User, error) {
const query = `
SELECT id::text, email, password_hash, mfa_enabled, created_at, updated_at
SELECT id::text, email, password_hash, mfa_enabled, platform_role, created_at, updated_at
FROM users
WHERE id = $1::uuid
`
return scanOptionalUser(r.db.QueryRow(ctx, query, userID))
}
func (r *postgresUserRepository) List(ctx context.Context) ([]User, error) {
const query = `
SELECT id::text, email, password_hash, mfa_enabled, platform_role, created_at, updated_at
FROM users
ORDER BY created_at DESC
`
rows, err := r.db.Query(ctx, query)
if err != nil {
return nil, fmt.Errorf("query users: %w", err)
}
defer rows.Close()
var users []User
for rows.Next() {
user, err := scanOptionalUser(rows)
if err != nil {
return nil, err
}
if user != nil {
users = append(users, *user)
}
}
return users, rows.Err()
}
func (r *postgresUserRepository) Create(ctx context.Context, user User) (*User, error) {
const query = `
INSERT INTO users (email, password_hash, mfa_enabled, platform_role, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id::text, email, password_hash, mfa_enabled, platform_role, created_at, updated_at
`
return scanOptionalUser(r.db.QueryRow(ctx, query,
user.Email,
user.PasswordHash,
user.MFAEnabled,
user.PlatformRole,
user.CreatedAt,
user.UpdatedAt,
))
}
func (r *postgresDeviceRepository) Upsert(ctx context.Context, params UpsertDeviceParams) (*Device, error) {
const query = `
INSERT INTO devices (
@@ -348,7 +388,7 @@ ON CONFLICT (email) DO UPDATE SET
password_hash = EXCLUDED.password_hash,
platform_role = EXCLUDED.platform_role,
updated_at = EXCLUDED.updated_at
RETURNING id::text, email, password_hash, mfa_enabled, created_at, updated_at
RETURNING id::text, email, password_hash, mfa_enabled, platform_role, created_at, updated_at
`, email, params.PasswordHash, params.Role, now))
if err != nil {
return nil, fmt.Errorf("upsert bootstrap owner: %w", err)
@@ -461,6 +501,7 @@ func scanOptionalUser(row scanner) (*User, error) {
&user.Email,
&user.PasswordHash,
&user.MFAEnabled,
&user.PlatformRole,
&user.CreatedAt,
&user.UpdatedAt,
); err != nil {
@@ -7,8 +7,10 @@ import (
)
type UserRepository interface {
List(ctx context.Context) ([]User, error)
GetByEmail(ctx context.Context, email string) (*User, error)
GetByID(ctx context.Context, userID string) (*User, error)
Create(ctx context.Context, user User) (*User, error)
}
type DeviceRepository interface {
+67 -1
View File
@@ -13,11 +13,13 @@ import (
"github.com/example/remote-access-platform/backend/internal/platform/authority"
"github.com/example/remote-access-platform/backend/internal/platform/module"
postgresplatform "github.com/example/remote-access-platform/backend/internal/platform/postgres"
)
type Service struct {
cfg module.Config
store Store
db postgresplatform.DBTX
transactor Transactor
tokenManager *TokenManager
authority *authority.Verifier
@@ -31,7 +33,7 @@ func NewService(deps module.Dependencies, store Store, transactor Transactor, ve
} else if verifier, err := authority.NewVerifier(deps.Config.Installation); err == nil {
authorityVerifier = verifier
}
return &Service{
service := &Service{
cfg: deps.Config,
store: store,
transactor: transactor,
@@ -45,6 +47,10 @@ func NewService(deps module.Dependencies, store Store, transactor Transactor, ve
authority: authorityVerifier,
now: time.Now,
}
if postgresStore, ok := store.(*postgresStore); ok {
service.db = postgresStore.db
}
return service
}
func (s *Service) Login(ctx context.Context, cmd LoginCommand) (*AuthResult, error) {
@@ -120,6 +126,44 @@ func (s *Service) Login(ctx context.Context, cmd LoginCommand) (*AuthResult, err
return &result, nil
}
func (s *Service) ListUsers(ctx context.Context, actorUserID string) ([]User, error) {
if err := s.ensurePlatformAdmin(ctx, actorUserID); err != nil {
return nil, err
}
return s.store.Users().List(ctx)
}
func (s *Service) CreateUser(ctx context.Context, cmd CreateUserCommand) (*User, error) {
if err := s.ensurePlatformAdmin(ctx, cmd.ActorUserID); err != nil {
return nil, err
}
email := strings.ToLower(strings.TrimSpace(cmd.Email))
password := strings.TrimSpace(cmd.Password)
role := strings.TrimSpace(cmd.PlatformRole)
if role == "" {
role = "user"
}
if email == "" || !strings.Contains(email, "@") || len(password) < 8 {
return nil, ErrInvalidBootstrapOwner
}
if role != "user" && role != authority.PlatformRoleAdmin && role != authority.PlatformRoleRecoveryAdmin {
return nil, ErrInvalidBootstrapOwner
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("hash user password: %w", err)
}
now := s.now().UTC()
return s.store.Users().Create(ctx, User{
Email: email,
PasswordHash: string(passwordHash),
MFAEnabled: false,
PlatformRole: role,
CreatedAt: now,
UpdatedAt: now,
})
}
func (s *Service) Refresh(ctx context.Context, cmd RefreshCommand) (*AuthResult, error) {
authSessionID, err := s.tokenManager.ParseRefreshToken(cmd.RefreshToken)
if err != nil {
@@ -438,3 +482,25 @@ func (s *Service) installationStatusFromRecord(record *InstallationAuthorityStat
func (s *Service) strictAuthority() bool {
return s.authority != nil && s.authority.Strict()
}
func (s *Service) ensurePlatformAdmin(ctx context.Context, actorUserID string) error {
if actorUserID == "" {
return ErrInvalidCredentials
}
role := authority.PlatformRoleUser
if s.db != nil {
effectiveRole, err := authority.EffectivePlatformRole(ctx, s.db, s.authority, actorUserID)
if err != nil {
return err
}
role = effectiveRole
} else if user, err := s.store.Users().GetByID(ctx, actorUserID); err != nil {
return err
} else if user != nil && user.PlatformRole != "" {
role = user.PlatformRole
}
if role != authority.PlatformRoleAdmin && role != authority.PlatformRoleRecoveryAdmin {
return ErrDeviceRevoked
}
return nil
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,216 @@
package cluster
import (
"context"
"testing"
"time"
)
func TestVPNPacketHubPopBatchAndStatsKeys(t *testing.T) {
hub := newVPNPacketHub()
key := vpnPacketKey{
ClusterID: "cluster-1",
VPNConnectionID: "vpn-1",
Direction: vpnDirectionClientToGateway,
}
packetA := []byte{
0x45, 0x00, 0x00, 20,
0x00, 0x01, 0x00, 0x00,
64, 17, 0, 0,
192, 168, 0, 1,
192, 168, 0, 2,
0x00, 0x50, 0x01, 0xBB,
}
packetB := make([]byte, len(packetA))
copy(packetB, packetA)
packetB[19] = 0xBA
hub.Push(key, packetA)
hub.Push(key, packetB)
packets := hub.PopBatch(context.Background(), key, 0, vpnPacketBatchMaxPackets, vpnPacketBatchMaxBytes)
if len(packets) != 2 {
t.Fatalf("expected 2 packets in batch, got %d", len(packets))
}
statsAny := hub.Snapshot("cluster-1", "vpn-1")[vpnDirectionClientToGateway]
stats, ok := statsAny.(map[string]any)
if !ok {
t.Fatalf("unexpected stats payload type: %T", statsAny)
}
for _, keyName := range []string{
"pushed",
"pushed_bytes",
"popped",
"popped_bytes",
"window_push_rate_pps",
"window_pop_rate_pps",
"window_push_rate_mbps",
"window_pop_rate_mbps",
"window_push_packets",
"window_pop_packets",
"queue_depth",
"queue_depths",
"queue_depth_max",
"queue_depth_high_watermark",
"queue_depth_high_at",
"shard_depth_high_watermark",
"shard_depth_high_at",
"queue_capacity",
"queue_shard_capacity",
"queue_full_drops",
"requeue_drops",
"cleared_stale_packets",
"flow_shard_count",
"flow_isolation",
} {
if _, found := stats[keyName]; !found {
t.Fatalf("missing vpn packet stat key %s", keyName)
}
}
if got, ok := stats["popped"].(uint64); !ok || got != 2 {
t.Fatalf("expected popped=2, got %v (ok=%v)", stats["popped"], ok)
}
if got, ok := stats["pushed"].(uint64); !ok || got != 2 {
t.Fatalf("expected pushed=2, got %v (ok=%v)", stats["pushed"], ok)
}
if got, ok := stats["queue_depth_high_watermark"].(int); !ok || got < 1 {
t.Fatalf("expected queue depth high watermark, got %v (ok=%v)", stats["queue_depth_high_watermark"], ok)
}
}
func TestVPNPacketHubGatherBehavior(t *testing.T) {
hub := newVPNPacketHub()
key := vpnPacketKey{
ClusterID: "cluster-1",
VPNConnectionID: "vpn-1",
Direction: vpnDirectionGatewayToClient,
}
packet := []byte{
0x45, 0x00, 0x00, 20,
0x00, 0x01, 0x00, 0x00,
64, 6, 0, 0,
10, 0, 0, 1,
10, 0, 0, 2,
0x12, 0x34, 0x56, 0x78,
}
hub.Push(key, packet)
hub.Push(key, packet)
hub.Push(key, packet)
first, ok := hub.Pop(context.Background(), key, 0)
if !ok {
t.Fatal("expected packet from queue")
}
batch := hub.PopBatch(context.Background(), key, 0, 1, 1024)
if len(batch) != 1 {
t.Fatalf("expected 1 packet because batch limit 1, got %d", len(batch))
}
_ = first
}
func TestVPNPacketHubFlowShardsReportDepths(t *testing.T) {
hub := newVPNPacketHub()
key := vpnPacketKey{
ClusterID: "cluster-1",
VPNConnectionID: "vpn-1",
Direction: vpnDirectionGatewayToClient,
}
for i := byte(1); i <= 8; i++ {
packet := []byte{
0x45, 0x00, 0x00, 24,
0x00, i, 0x00, 0x00,
64, 6, 0, 0,
10, 0, 0, i,
192, 168, 200, i,
0x12, i, 0x56, i,
}
if err := hub.Push(key, packet); err != nil {
t.Fatalf("push packet %d: %v", i, err)
}
}
statsAny := hub.Snapshot("cluster-1", "vpn-1")[vpnDirectionGatewayToClient]
stats, ok := statsAny.(map[string]any)
if !ok {
t.Fatalf("unexpected stats payload type: %T", statsAny)
}
if got, ok := stats["queue_depth"].(int); !ok || got != 8 {
t.Fatalf("expected queue_depth=8, got %v (ok=%v)", stats["queue_depth"], ok)
}
depths, ok := stats["queue_depths"].([]int)
if !ok {
t.Fatalf("unexpected queue_depths payload type: %T", stats["queue_depths"])
}
if len(depths) != vpnPacketFlowShardCount {
t.Fatalf("expected %d queue shards, got %d", vpnPacketFlowShardCount, len(depths))
}
nonEmpty := 0
for _, depth := range depths {
if depth > 0 {
nonEmpty++
}
}
if nonEmpty < 2 {
t.Fatalf("expected packets to be distributed across at least 2 shards, got depths=%v", depths)
}
}
func TestVPNPacketHubClearDoesNotCountAsDrop(t *testing.T) {
hub := newVPNPacketHub()
key := vpnPacketKey{
ClusterID: "cluster-1",
VPNConnectionID: "vpn-1",
Direction: vpnDirectionClientToGateway,
}
packet := []byte{
0x45, 0x00, 0x00, 20,
0x00, 0x01, 0x00, 0x00,
64, 6, 0, 0,
10, 0, 0, 1,
10, 0, 0, 2,
0x12, 0x34, 0x56, 0x78,
}
if err := hub.Push(key, packet); err != nil {
t.Fatalf("push packet: %v", err)
}
if cleared := hub.Clear(key); cleared != 1 {
t.Fatalf("expected cleared=1, got %d", cleared)
}
statsAny := hub.Snapshot("cluster-1", "vpn-1")[vpnDirectionClientToGateway]
stats, ok := statsAny.(map[string]any)
if !ok {
t.Fatalf("unexpected stats payload type: %T", statsAny)
}
if got, ok := stats["dropped"].(uint64); !ok || got != 0 {
t.Fatalf("expected dropped=0 for stale clear, got %v (ok=%v)", stats["dropped"], ok)
}
if got, ok := stats["cleared_stale_packets"].(uint64); !ok || got != 1 {
t.Fatalf("expected cleared_stale_packets=1, got %v (ok=%v)", stats["cleared_stale_packets"], ok)
}
}
func TestVPNClientDiagnosticStopCommandDrainsPendingWork(t *testing.T) {
hub := newVPNClientDiagnosticHub()
hub.Enqueue("cluster-1", "device-1", map[string]any{"type": "vpn_page_probe", "url": "https://speedtest.rt.ru/"})
hub.Enqueue("cluster-1", "device-1", map[string]any{"type": "vpn_tcp_connect", "host": "192.168.200.95"})
hub.Enqueue("cluster-1", "device-1", map[string]any{"type": "stop_vpn"})
item, ok := hub.Pop(context.Background(), "cluster-1", "device-1", time.Millisecond)
if !ok {
t.Fatal("expected priority stop command")
}
if got, _ := item.Payload["type"].(string); got != "stop_vpn" {
t.Fatalf("first command = %q, want stop_vpn", got)
}
if item, ok := hub.Pop(context.Background(), "cluster-1", "device-1", 0); ok {
t.Fatalf("expected old commands to be drained, got %#v", item.Payload)
}
}
File diff suppressed because it is too large Load Diff
@@ -32,3 +32,136 @@ func TestMeshLatestObservationKeyDefaults(t *testing.T) {
t.Fatalf("key = %q", key)
}
}
func TestEnrichVPNClientFabricRoutePrefersPlacementEntryAndActiveExit(t *testing.T) {
item := VPNClientConnection{
AllowedNodeIDs: []string{"node-a", "node-b", "node-b"},
EntryNodeIDs: []string{"entry-1", "entry-2"},
ExitNodeID: "exit-policy",
ActiveLease: &NodeVPNAssignmentLease{
OwnerNodeID: "exit-active",
},
ClientConfig: json.RawMessage(`{"routes":["0.0.0.0/0"]}`),
}
var cfg map[string]any
if err := json.Unmarshal(enrichVPNClientFabricRoute(item, "entry-2", ""), &cfg); err != nil {
t.Fatalf("unmarshal enriched config: %v", err)
}
route, ok := cfg["vpn_fabric_route"].(map[string]any)
if !ok {
t.Fatalf("missing vpn_fabric_route in %#v", cfg)
}
if route["preferred_data_plane"] != "fabric_mesh" || route["fallback_data_plane"] != "backend_relay" {
t.Fatalf("unexpected data-plane route contract: %#v", route)
}
if route["selected_entry_node_id"] != "entry-2" || route["selected_exit_node_id"] != "exit-active" {
t.Fatalf("unexpected selected route endpoints: %#v", route)
}
if route["route_candidate_count"].(float64) != 8 {
t.Fatalf("route candidate count = %#v", route["route_candidate_count"])
}
candidates := route["route_candidates"].([]any)
firstCandidate := candidates[0].(map[string]any)
if firstCandidate["role"] != "preferred" || firstCandidate["entry_node_id"] != "entry-2" || firstCandidate["exit_node_id"] != "exit-active" {
t.Fatalf("preferred route candidate = %#v", firstCandidate)
}
entryPool := route["entry_pool_node_ids"].([]any)
exitPool := route["exit_pool_node_ids"].([]any)
if len(entryPool) != 2 || entryPool[0] != "entry-1" || entryPool[1] != "entry-2" {
t.Fatalf("entry pool = %#v", entryPool)
}
if len(exitPool) != 4 || exitPool[0] != "exit-policy" || exitPool[1] != "exit-active" || exitPool[2] != "node-a" || exitPool[3] != "node-b" {
t.Fatalf("exit pool = %#v", exitPool)
}
contract, ok := cfg["vpn_dataplane_contract"].(map[string]any)
if !ok {
t.Fatalf("missing vpn_dataplane_contract in %#v", cfg)
}
if contract["tunnel_type"] != "universal_ip_packet" || contract["application_protocol_agnostic"] != true {
t.Fatalf("unexpected dataplane contract: %#v", contract)
}
failover := contract["failover"].(map[string]any)
if failover["enabled"] != true || failover["alternate_route_count"].(float64) != 7 {
t.Fatalf("unexpected failover contract: %#v", failover)
}
}
func TestEnrichVPNClientFabricRoutePrefersExplicitExit(t *testing.T) {
item := VPNClientConnection{
AllowedNodeIDs: []string{"node-a", "node-b", "node-c"},
EntryNodeIDs: []string{"entry-1", "entry-2"},
ExitNodeID: "exit-policy-a",
ActiveLease: &NodeVPNAssignmentLease{
OwnerNodeID: "",
},
ClientConfig: json.RawMessage(`{"routes":["0.0.0.0/0"]}`),
}
var cfg map[string]any
if err := json.Unmarshal(enrichVPNClientFabricRoute(item, "entry-1", "node-c"), &cfg); err != nil {
t.Fatalf("unmarshal enriched config: %v", err)
}
route, ok := cfg["vpn_fabric_route"].(map[string]any)
if !ok {
t.Fatalf("missing vpn_fabric_route in %#v", cfg)
}
if route["selected_entry_node_id"] != "entry-1" {
t.Fatalf("unexpected selected entry: %#v", route["selected_entry_node_id"])
}
if route["selected_exit_node_id"] != "node-c" {
t.Fatalf("unexpected selected exit: %#v", route["selected_exit_node_id"])
}
}
func TestEnrichVPNClientEntryEndpointCandidatesAddsReportedEntryAPI(t *testing.T) {
item := VPNClientConnection{
EntryNodeIDs: []string{"entry-1"},
ClientConfig: json.RawMessage(`{
"vpn_fabric_route": {
"status": "planned",
"selected_entry_node_id": "entry-1",
"selected_exit_node_id": "exit-1"
}
}`),
}
heartbeatMetadata := json.RawMessage(`{
"mesh_endpoint_report": {
"transport": "direct_http",
"connectivity_mode": "direct",
"nat_type": "none",
"region": "test",
"peer_endpoint": "http://entry.example.test:19131",
"endpoint_candidates": [{
"endpoint_id": "public-http",
"node_id": "entry-1",
"transport": "direct_http",
"address": "http://entry.example.test:19131",
"reachability": "public",
"priority": 0
}]
}
}`)
endpoints := map[string][]map[string]any{
"entry-1": vpnEntryEndpointCandidatesFromHeartbeat("entry-1", json.RawMessage(`{"vpn_local_gateway_shortcut":true}`), heartbeatMetadata),
}
var cfg map[string]any
if err := json.Unmarshal(enrichVPNClientEntryEndpointCandidates(item, endpoints), &cfg); err != nil {
t.Fatalf("unmarshal enriched config: %v", err)
}
if cfg["vpn_entry_endpoint_candidate_count"].(float64) != 1 {
t.Fatalf("candidate count = %#v", cfg["vpn_entry_endpoint_candidate_count"])
}
candidates := cfg["vpn_entry_endpoint_candidates"].([]any)
candidate := candidates[0].(map[string]any)
if candidate["node_id"] != "entry-1" || candidate["api_base_url"] != "http://entry.example.test:19131/api/v1" {
t.Fatalf("unexpected endpoint candidate: %#v", candidate)
}
if candidate["local_gateway_shortcut"] != true {
t.Fatalf("local gateway shortcut missing: %#v", candidate)
}
if candidate["selected_entry"] != true || candidate["source"] != "node_latest_heartbeat.mesh_endpoint_report.endpoint_candidates" {
t.Fatalf("unexpected endpoint metadata: %#v", candidate)
}
}
+28 -1
View File
@@ -22,6 +22,7 @@ type Repository interface {
AssignNodeToGroup(ctx context.Context, input AssignNodeGroupInput) (ClusterNode, error)
CreateJoinToken(ctx context.Context, input CreateJoinTokenInput, tokenHash string) (NodeJoinToken, error)
ListJoinTokens(ctx context.Context, clusterID string) ([]NodeJoinToken, error)
SetJoinTokenAuthority(ctx context.Context, clusterID, tokenID string, payload json.RawMessage, signature ClusterSignature) (NodeJoinToken, error)
GetValidJoinTokenByHash(ctx context.Context, clusterID, tokenHash string) (NodeJoinToken, error)
RevokeJoinToken(ctx context.Context, input RevokeJoinTokenInput) (NodeJoinToken, error)
@@ -40,8 +41,16 @@ type Repository interface {
RecordHeartbeat(ctx context.Context, input RecordHeartbeatInput) (NodeHeartbeat, error)
ListNodeHeartbeats(ctx context.Context, clusterID, nodeID string, limit int) ([]NodeHeartbeat, error)
CreateReleaseVersion(ctx context.Context, input CreateReleaseVersionInput) (ReleaseVersion, error)
ListReleaseVersions(ctx context.Context, clusterID, product, channel string) ([]ReleaseVersion, error)
ListNodeUpdateServiceCandidates(ctx context.Context, clusterID string) ([]NodeUpdateServiceCandidate, error)
UpsertNodeUpdatePolicy(ctx context.Context, input UpsertNodeUpdatePolicyInput) (NodeUpdatePolicy, error)
GetNodeUpdatePolicy(ctx context.Context, clusterID, nodeID, product string) (NodeUpdatePolicy, error)
ReportNodeUpdateStatus(ctx context.Context, input ReportNodeUpdateStatusInput) (NodeUpdateStatus, error)
ListNodeUpdateStatuses(ctx context.Context, clusterID, nodeID string, limit int) ([]NodeUpdateStatus, error)
RevokeNodeIdentity(ctx context.Context, input RevokeNodeIdentityInput) error
DisableClusterMembership(ctx context.Context, input DisableMembershipInput) error
DeleteClusterNode(ctx context.Context, input DeleteClusterNodeInput) error
UpsertFabricTestingFlag(ctx context.Context, input UpsertFabricTestingFlagInput) (FabricTestingFlag, error)
ListFabricTestingFlags(ctx context.Context) ([]FabricTestingFlag, error)
GetEffectiveNodeTestingFlags(ctx context.Context, clusterID, nodeID string) (EffectiveNodeTestingFlags, error)
@@ -55,6 +64,22 @@ type Repository interface {
ListMeshLinks(ctx context.Context, clusterID string) ([]MeshLinkObservation, error)
CreateRouteIntent(ctx context.Context, input CreateRouteIntentInput) (MeshRouteIntent, error)
ListRouteIntents(ctx context.Context, clusterID string) ([]MeshRouteIntent, error)
ExpireRouteIntent(ctx context.Context, input RouteIntentLifecycleInput, expiresAt time.Time) (MeshRouteIntent, error)
DisableRouteIntent(ctx context.Context, input RouteIntentLifecycleInput) (MeshRouteIntent, error)
RecordFabricServiceChannelRouteFeedback(ctx context.Context, input RecordFabricServiceChannelRouteFeedbackInput) (FabricServiceChannelRouteFeedbackObservation, error)
ListFabricServiceChannelRouteFeedback(ctx context.Context, input ListFabricServiceChannelRouteFeedbackInput) ([]FabricServiceChannelRouteFeedbackObservation, error)
ExpireFabricServiceChannelRouteFeedback(ctx context.Context, input ExpireFabricServiceChannelRouteFeedbackInput) (ExpireFabricServiceChannelRouteFeedbackResult, error)
StoreFabricServiceChannelLease(ctx context.Context, input StoreFabricServiceChannelLeaseInput) (FabricServiceChannelLeaseRecord, error)
GetFabricServiceChannelLease(ctx context.Context, clusterID, channelID string) (FabricServiceChannelLeaseRecord, error)
ListFabricServiceChannelLeases(ctx context.Context, input ListFabricServiceChannelLeasesInput) ([]FabricServiceChannelLeaseRecord, error)
CleanupExpiredFabricServiceChannelLeases(ctx context.Context, clusterID string, now time.Time, limit int) (int, error)
RecordFabricServiceChannelRouteRebuildAttempt(ctx context.Context, input RecordFabricServiceChannelRouteRebuildAttemptInput) (FabricServiceChannelRouteRebuildAttempt, error)
ListFabricServiceChannelRouteRebuildAttempts(ctx context.Context, input ListFabricServiceChannelRouteRebuildAttemptsInput) ([]FabricServiceChannelRouteRebuildAttempt, error)
UpdateFabricServiceChannelRouteRebuildCorrelationSnapshot(ctx context.Context, input UpdateFabricServiceChannelRouteRebuildCorrelationSnapshotInput) error
GetFabricServiceChannelSchemaStatus(ctx context.Context, input GetFabricServiceChannelSchemaStatusInput) (FabricServiceChannelSchemaStatus, error)
UpsertFabricServiceChannelRouteRebuildAlertSilence(ctx context.Context, input SilenceFabricServiceChannelRouteRebuildAlertInput, expiresAt time.Time) (FabricServiceChannelRouteRebuildAlertSilence, error)
ListFabricServiceChannelRouteRebuildAlertSilences(ctx context.Context, clusterID string, now time.Time) ([]FabricServiceChannelRouteRebuildAlertSilence, error)
DeleteFabricServiceChannelRouteRebuildAlertSilence(ctx context.Context, input UnsilenceFabricServiceChannelRouteRebuildAlertInput) (FabricServiceChannelRouteRebuildAlertSilence, error)
ListQoSPolicies(ctx context.Context, clusterID string) ([]MeshQoSPolicy, error)
ListFabricEntryPoints(ctx context.Context, clusterID string) ([]FabricEntryPoint, error)
CreateFabricEntryPoint(ctx context.Context, input CreateFabricEntryPointInput) (FabricEntryPoint, error)
@@ -78,6 +103,7 @@ type Repository interface {
ListVPNConnectionAllowedNodes(ctx context.Context, clusterID, vpnConnectionID string) ([]VPNConnectionAllowedNode, error)
AcquireVPNConnectionLease(ctx context.Context, input AcquireVPNConnectionLeaseInput, expiresAt time.Time, fencingToken string) (VPNConnectionLease, error)
RenewVPNConnectionLease(ctx context.Context, input RenewVPNConnectionLeaseInput, expiresAt time.Time) (VPNConnectionLease, error)
RenewNodeVPNAssignmentLease(ctx context.Context, input RenewNodeVPNAssignmentLeaseInput, expiresAt time.Time) (VPNConnectionLease, error)
ReleaseVPNConnectionLease(ctx context.Context, input ReleaseVPNConnectionLeaseInput) (VPNConnectionLease, error)
FenceVPNConnectionLease(ctx context.Context, input FenceVPNConnectionLeaseInput) (VPNConnectionLease, error)
GetActiveVPNConnectionLease(ctx context.Context, clusterID, vpnConnectionID string) (VPNConnectionLease, error)
@@ -85,7 +111,8 @@ type Repository interface {
ExpireStaleVPNConnectionLeases(ctx context.Context, clusterID string, now time.Time) ([]VPNConnectionLease, error)
ListNodeVPNAssignments(ctx context.Context, clusterID, nodeID string) ([]NodeVPNAssignment, error)
ReportNodeVPNAssignmentStatus(ctx context.Context, input ReportNodeVPNAssignmentStatusInput) (NodeVPNAssignmentStatus, error)
GetVPNClientProfile(ctx context.Context, clusterID, organizationID, userID, preferredEntryNodeID, preferredExitNodeID string, generatedAt time.Time) (VPNClientProfile, error)
RecordAudit(ctx context.Context, event ClusterAuditEvent) error
ListAuditEvents(ctx context.Context, clusterID string, limit int) ([]ClusterAuditEvent, error)
ListAuditEvents(ctx context.Context, input ListAuditEventsInput) ([]ClusterAuditEvent, error)
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -40,6 +40,9 @@ func (m *Module) Name() string {
func (m *Module) RegisterRoutes(router chi.Router) {
router.Route("/node-agents", func(r chi.Router) {
r.Post("/docker-install-profile", m.dockerInstallProfile)
r.Post("/windows-install-profile", m.windowsInstallProfile)
r.Post("/linux-install-profile", m.linuxInstallProfile)
r.Post("/enroll", m.enrollAgent)
r.Post("/enrollments/{requestID}/bootstrap", m.bootstrapEnrollment)
r.Post("/register", m.registerAgent)
@@ -53,6 +56,48 @@ func (m *Module) RegisterRoutes(router chi.Router) {
})
}
func (m *Module) linuxInstallProfile(w http.ResponseWriter, r *http.Request) {
var payload clustermodule.DockerInstallProfileRequest
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid linux install profile payload")
return
}
profile, err := m.cluster.GetLinuxInstallProfile(r.Context(), payload)
if err != nil {
httpx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"linux_install_profile": profile})
}
func (m *Module) windowsInstallProfile(w http.ResponseWriter, r *http.Request) {
var payload clustermodule.DockerInstallProfileRequest
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid windows install profile payload")
return
}
profile, err := m.cluster.GetWindowsInstallProfile(r.Context(), payload)
if err != nil {
httpx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"windows_install_profile": profile})
}
func (m *Module) dockerInstallProfile(w http.ResponseWriter, r *http.Request) {
var payload clustermodule.DockerInstallProfileRequest
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
httpx.WriteError(w, http.StatusBadRequest, "invalid docker install profile payload")
return
}
profile, err := m.cluster.GetDockerInstallProfile(r.Context(), payload)
if err != nil {
httpx.WriteError(w, http.StatusBadRequest, err.Error())
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{"docker_install_profile": profile})
}
func (m *Module) enrollAgent(w http.ResponseWriter, r *http.Request) {
var payload struct {
ClusterID string `json:"cluster_id"`
@@ -242,6 +242,37 @@ func (m *Module) loadAdminSummary(ctx context.Context, orgID string) (AdminSumma
return AdminSummary{}, err
}
var vpnConnectionCount int64
var vpnActiveLeaseCount int64
var vpnForwardingCount int64
if err := m.db.QueryRow(ctx, `
SELECT COUNT(*)
FROM vpn_connections
WHERE organization_id = $1::uuid
AND desired_state = 'enabled'
`, orgID).Scan(&vpnConnectionCount); err != nil {
return AdminSummary{}, err
}
if err := m.db.QueryRow(ctx, `
SELECT COUNT(*)
FROM vpn_connection_leases l
INNER JOIN vpn_connections vc ON vc.id = l.vpn_connection_id
WHERE vc.organization_id = $1::uuid
AND l.status = 'active'
AND l.expires_at > NOW()
`, orgID).Scan(&vpnActiveLeaseCount); err != nil {
return AdminSummary{}, err
}
if err := m.db.QueryRow(ctx, `
SELECT COUNT(*)
FROM vpn_connection_assignment_latest_statuses s
INNER JOIN vpn_connections vc ON vc.id = s.vpn_connection_id
WHERE vc.organization_id = $1::uuid
AND COALESCE((s.status_payload->>'packet_forwarding')::boolean, false)
`, orgID).Scan(&vpnForwardingCount); err != nil {
return AdminSummary{}, err
}
auditRows, err := m.db.Query(ctx, `
SELECT ae.id::text, ae.event_type, ae.target_type, ae.target_id, ae.payload, ae.created_at
FROM audit_events ae
@@ -265,6 +296,12 @@ func (m *Module) loadAdminSummary(ctx context.Context, orgID string) (AdminSumma
if err := auditRows.Err(); err != nil {
return AdminSummary{}, err
}
if services == nil {
services = []ServiceSummary{}
}
if audit == nil {
audit = []OrgAuditEvent{}
}
return AdminSummary{
OrganizationID: orgID,
@@ -272,14 +309,34 @@ func (m *Module) loadAdminSummary(ctx context.Context, orgID string) (AdminSumma
ActiveSessionCount: activeSessionCount,
ServiceEndpoints: services,
ConnectorStatus: map[string]any{
"vpn": "not_implemented",
"connector": "not_implemented",
"vpn": map[string]any{
"enabled_connections": vpnConnectionCount,
"active_leases": vpnActiveLeaseCount,
"packet_forwarding": vpnForwardingCount,
"status": connectorStatus(vpnConnectionCount, vpnActiveLeaseCount, vpnForwardingCount),
},
"rdp": map[string]any{
"status": "resource_catalog_ready",
},
},
RecentAudit: audit,
TopologyExposure: tenantSafeTopologyExposure(),
}, nil
}
func connectorStatus(enabledConnections, activeLeases, forwarding int64) string {
if enabledConnections == 0 {
return "not_configured"
}
if forwarding > 0 {
return "active"
}
if activeLeases > 0 {
return "gateway_blocked"
}
return "waiting_for_gateway"
}
func tenantSafeTopologyExposure() string {
return "tenant_safe_no_core_mesh_topology"
}
@@ -49,6 +49,7 @@ type Resource struct {
Address string `json:"address"`
Protocol string `json:"protocol"`
SecretRef *string `json:"secret_ref,omitempty"`
HasSecret bool `json:"has_secret"`
CertificateVerificationMode string `json:"certificate_verification_mode"`
RenderQualityProfile string `json:"render_quality_profile"`
ClipboardMode string `json:"clipboard_mode"`
@@ -116,6 +117,7 @@ func (m *Module) listResources(w http.ResponseWriter, r *http.Request) {
query := `
SELECT r.id, r.organization_id, r.name, r.address, r.protocol, r.secret_ref,
r.certificate_verification_mode, r.metadata, r.created_at, r.updated_at,
EXISTS (SELECT 1 FROM resource_secrets sec WHERE sec.resource_id = r.id) AS has_secret,
COALESCE(rp.clipboard_mode, 'disabled') AS clipboard_mode,
COALESCE(rp.file_transfer_mode, 'disabled') AS file_transfer_mode
FROM resources r
@@ -500,6 +502,7 @@ func (m *Module) getByID(ctx context.Context, resourceID string) (Resource, erro
row := m.db.QueryRow(ctx, `
SELECT r.id, r.organization_id, r.name, r.address, r.protocol, r.secret_ref,
r.certificate_verification_mode, r.metadata, r.created_at, r.updated_at,
EXISTS (SELECT 1 FROM resource_secrets sec WHERE sec.resource_id = r.id) AS has_secret,
COALESCE(rp.clipboard_mode, 'disabled') AS clipboard_mode,
COALESCE(rp.file_transfer_mode, 'disabled') AS file_transfer_mode
FROM resources r
@@ -555,6 +558,7 @@ func scanResource(row rowScanner) (Resource, error) {
&resource.Metadata,
&resource.CreatedAt,
&resource.UpdatedAt,
&resource.HasSecret,
&resource.ClipboardMode,
&resource.FileTransferMode,
); err != nil {