mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-03-31 15:07:49 +01:00
feat: add command to set name of a machine
This commit is contained in:
@@ -33,6 +33,7 @@ func machineCommands() *cobra.Command {
|
||||
command.AddCommand(disableExitNodeCommand())
|
||||
command.AddCommand(disableMachineKeyExpiryCommand())
|
||||
command.AddCommand(authorizeMachineCommand())
|
||||
command.AddCommand(setMachineNameCommand())
|
||||
|
||||
return command
|
||||
}
|
||||
@@ -168,6 +169,40 @@ func deleteMachineCommand() *cobra.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
func setMachineNameCommand() *cobra.Command {
|
||||
command, tc := prepareCommand(false, &cobra.Command{
|
||||
Use: "set-name",
|
||||
Short: "Set the name of a given machine",
|
||||
SilenceUsage: true,
|
||||
})
|
||||
|
||||
var machineID uint64
|
||||
var useOSHostname bool
|
||||
var name string
|
||||
command.Flags().Uint64Var(&machineID, "machine-id", 0, "Machine ID")
|
||||
command.Flags().StringVar(&name, "name", "", "New name for the machine")
|
||||
command.Flags().BoolVar(&useOSHostname, "use-os-hostname", false, "Auto-generate from the machine OS hostname")
|
||||
|
||||
_ = command.MarkFlagRequired("machine-id")
|
||||
|
||||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
if !useOSHostname && name == "" {
|
||||
return fmt.Errorf("name is required when not using os hostname")
|
||||
}
|
||||
|
||||
req := api.SetMachineNameRequest{MachineId: machineID, Name: name, UseOsHostname: useOSHostname}
|
||||
if _, err := tc.Client().SetMachineName(cmd.Context(), connect.NewRequest(&req)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Machine name set.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
func expireMachineCommand() *cobra.Command {
|
||||
command, tc := prepareCommand(false, &cobra.Command{
|
||||
Use: "expire",
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func m202502150830_use_hostname() *gormigrate.Migration {
|
||||
return &gormigrate.Migration{
|
||||
ID: "202502150830",
|
||||
Migrate: func(db *gorm.DB) error {
|
||||
type Machine struct {
|
||||
UseOSHostname bool `gorm:"default:true"`
|
||||
}
|
||||
|
||||
if err := db.Migrator().AddColumn(&Machine{}, "UseOSHostname"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Rollback: nil,
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ func Migrations() []*gormigrate.Migration {
|
||||
m202401061400_machine_indeces(),
|
||||
m202402120800_user_last_authenticated(),
|
||||
m202403130830_json_to_text(),
|
||||
m202502150830_use_hostname(),
|
||||
}
|
||||
return migrations
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ type Machine struct {
|
||||
Tags Tags
|
||||
KeyExpiryDisabled bool
|
||||
Authorized bool
|
||||
UseOSHostname bool `gorm:"default:true"`
|
||||
|
||||
HostInfo HostInfo
|
||||
Endpoints Endpoints
|
||||
@@ -124,7 +125,7 @@ func (m *Machine) IsAllowedExitNode() bool {
|
||||
}
|
||||
|
||||
func (m *Machine) AdvertisedPrefixes() []string {
|
||||
result := []string{}
|
||||
var result []string
|
||||
for _, r := range m.HostInfo.RoutableIPs {
|
||||
if r.Bits() != 0 {
|
||||
result = append(result, r.String())
|
||||
|
||||
@@ -482,6 +482,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, form
|
||||
ID: util.NextID(),
|
||||
Name: sanitizeHostname,
|
||||
NameIdx: nameIdx,
|
||||
UseOSHostname: true,
|
||||
MachineKey: machineKey,
|
||||
NodeKey: nodeKey,
|
||||
Ephemeral: ephemeral || req.Ephemeral,
|
||||
@@ -511,7 +512,7 @@ func (h *AuthenticationHandlers) endMachineRegistrationFlow(c echo.Context, form
|
||||
tags := append(registeredTags, advertisedTags...)
|
||||
|
||||
sanitizeHostname := dnsname.SanitizeHostname(req.Hostinfo.Hostname)
|
||||
if m.Name != sanitizeHostname {
|
||||
if m.UseOSHostname && m.Name != sanitizeHostname {
|
||||
nameIdx, err := h.repository.GetNextMachineNameIndex(ctx, tailnet.ID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return logError(err)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/jsiebens/ionscale/internal/config"
|
||||
"github.com/jsiebens/ionscale/internal/core"
|
||||
"github.com/jsiebens/ionscale/internal/domain"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"tailscale.com/smallzstd"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/util/dnsname"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -80,12 +82,27 @@ func (h *PollNetMapHandler) handlePollNetMap(c echo.Context, m *domain.Machine,
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
fmt.Println("======================================================")
|
||||
fmt.Println(mapRequest.Hostinfo.Hostname)
|
||||
fmt.Println(mapRequest.Stream)
|
||||
fmt.Println("======================================================")
|
||||
|
||||
if !mapRequest.Stream {
|
||||
m.HostInfo = domain.HostInfo(*mapRequest.Hostinfo)
|
||||
m.DiscoKey = mapRequest.DiscoKey.String()
|
||||
m.Endpoints = mapRequest.Endpoints
|
||||
m.LastSeen = &now
|
||||
|
||||
sanitizeHostname := dnsname.SanitizeHostname(m.HostInfo.Hostname)
|
||||
if m.UseOSHostname && m.Name != sanitizeHostname {
|
||||
nameIdx, err := h.repository.GetNextMachineNameIndex(ctx, m.TailnetID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return logError(err)
|
||||
}
|
||||
m.Name = sanitizeHostname
|
||||
m.NameIdx = nameIdx
|
||||
}
|
||||
|
||||
if err := h.repository.SaveMachine(ctx, m); err != nil {
|
||||
return logError(err)
|
||||
}
|
||||
|
||||
@@ -86,14 +86,13 @@ func (h *RegistrationHandlers) Register(c echo.Context) error {
|
||||
}
|
||||
|
||||
sanitizeHostname := dnsname.SanitizeHostname(req.Hostinfo.Hostname)
|
||||
if m.Name != sanitizeHostname {
|
||||
if m.UseOSHostname && m.Name != sanitizeHostname {
|
||||
nameIdx, err := h.repository.GetNextMachineNameIndex(ctx, m.TailnetID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return logError(err)
|
||||
}
|
||||
m.Name = sanitizeHostname
|
||||
m.NameIdx = nameIdx
|
||||
|
||||
}
|
||||
|
||||
advertisedTags := domain.SanitizeTags(req.Hostinfo.RequestTags)
|
||||
@@ -196,6 +195,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, ma
|
||||
ID: util.NextID(),
|
||||
Name: sanitizeHostname,
|
||||
NameIdx: nameIdx,
|
||||
UseOSHostname: true,
|
||||
MachineKey: machineKey,
|
||||
NodeKey: nodeKey,
|
||||
Ephemeral: authKey.Ephemeral || req.Ephemeral,
|
||||
@@ -225,7 +225,7 @@ func (h *RegistrationHandlers) authenticateMachineWithAuthKey(c echo.Context, ma
|
||||
m.IPv6 = domain.IP{Addr: ipv6}
|
||||
} else {
|
||||
sanitizeHostname := dnsname.SanitizeHostname(req.Hostinfo.Hostname)
|
||||
if m.Name != sanitizeHostname {
|
||||
if m.UseOSHostname && m.Name != sanitizeHostname {
|
||||
nameIdx, err := h.repository.GetNextMachineNameIndex(ctx, tailnet.ID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return logError(err)
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"tailscale.com/util/dnsname"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -162,6 +164,72 @@ func (s *Service) ExpireMachine(ctx context.Context, req *connect.Request[api.Ex
|
||||
return connect.NewResponse(&api.ExpireMachineResponse{}), nil
|
||||
}
|
||||
|
||||
func (s *Service) SetMachineName(ctx context.Context, req *connect.Request[api.SetMachineNameRequest]) (*connect.Response[api.SetMachineNameResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
|
||||
m, err := s.repository.GetMachine(ctx, req.Msg.MachineId)
|
||||
if err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if m == nil {
|
||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("machine not found"))
|
||||
}
|
||||
|
||||
if !principal.IsSystemAdmin() && !principal.IsTailnetAdmin(m.TailnetID) {
|
||||
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied"))
|
||||
}
|
||||
|
||||
if req.Msg.UseOsHostname {
|
||||
sanitizeHostname := dnsname.SanitizeHostname(m.HostInfo.Hostname)
|
||||
nameIdx, err := s.repository.GetNextMachineNameIndex(ctx, m.TailnetID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
m.Name = sanitizeHostname
|
||||
m.NameIdx = nameIdx
|
||||
m.UseOSHostname = true
|
||||
if err := s.repository.SaveMachine(ctx, m); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(m.TailnetID)
|
||||
|
||||
return connect.NewResponse(&api.SetMachineNameResponse{}), nil
|
||||
}
|
||||
|
||||
if strings.TrimSpace(req.Msg.Name) == "" {
|
||||
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("machine name is required when not using os hostname"))
|
||||
}
|
||||
|
||||
sanitizeHostname := dnsname.SanitizeHostname(req.Msg.Name)
|
||||
|
||||
if sanitizeHostname == m.Name {
|
||||
return connect.NewResponse(&api.SetMachineNameResponse{}), nil
|
||||
}
|
||||
|
||||
nameIdx, err := s.repository.GetNextMachineNameIndex(ctx, m.TailnetID, sanitizeHostname)
|
||||
if err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
if nameIdx > 0 {
|
||||
return nil, connect.NewError(connect.CodeAlreadyExists, fmt.Errorf("machine name already in use"))
|
||||
}
|
||||
|
||||
m.Name = sanitizeHostname
|
||||
m.NameIdx = 0
|
||||
m.UseOSHostname = false
|
||||
if err := s.repository.SaveMachine(ctx, m); err != nil {
|
||||
return nil, logError(err)
|
||||
}
|
||||
|
||||
s.sessionManager.NotifyAll(m.TailnetID)
|
||||
|
||||
return connect.NewResponse(&api.SetMachineNameResponse{}), nil
|
||||
}
|
||||
|
||||
func (s *Service) AuthorizeMachine(ctx context.Context, req *connect.Request[api.AuthorizeMachineRequest]) (*connect.Response[api.AuthorizeMachineResponse], error) {
|
||||
principal := CurrentPrincipal(ctx)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user