mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-03-31 15:07:49 +01:00
460 lines
11 KiB
Go
460 lines
11 KiB
Go
package cmd
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"github.com/bufbuild/connect-go"
|
||
idomain "github.com/jsiebens/ionscale/internal/domain"
|
||
"github.com/jsiebens/ionscale/pkg/client/ionscale"
|
||
"github.com/jsiebens/ionscale/pkg/defaults"
|
||
api "github.com/jsiebens/ionscale/pkg/gen/ionscale/v1"
|
||
"github.com/rodaine/table"
|
||
"github.com/spf13/cobra"
|
||
"gopkg.in/yaml.v3"
|
||
"os"
|
||
"strings"
|
||
"tailscale.com/tailcfg"
|
||
)
|
||
|
||
func tailnetCommand() *cobra.Command {
|
||
command := &cobra.Command{
|
||
Use: "tailnets",
|
||
Aliases: []string{"tailnet"},
|
||
Short: "Manage ionscale tailnets",
|
||
}
|
||
|
||
command.AddCommand(listTailnetsCommand())
|
||
command.AddCommand(createTailnetsCommand())
|
||
command.AddCommand(deleteTailnetCommand())
|
||
command.AddCommand(getDNSConfigCommand())
|
||
command.AddCommand(setDNSConfigCommand())
|
||
command.AddCommand(getACLConfigCommand())
|
||
command.AddCommand(setACLConfigCommand())
|
||
command.AddCommand(editACLConfigCommand())
|
||
command.AddCommand(getIAMPolicyCommand())
|
||
command.AddCommand(setIAMPolicyCommand())
|
||
command.AddCommand(editIAMPolicyCommand())
|
||
command.AddCommand(enableServiceCollectionCommand())
|
||
command.AddCommand(disableServiceCollectionCommand())
|
||
command.AddCommand(enableFileSharingCommand())
|
||
command.AddCommand(disableFileSharingCommand())
|
||
command.AddCommand(enableSSHCommand())
|
||
command.AddCommand(disableSSHCommand())
|
||
command.AddCommand(enableMachineAuthorizationCommand())
|
||
command.AddCommand(disableMachineAuthorizationCommand())
|
||
command.AddCommand(getDERPMap())
|
||
command.AddCommand(setDERPMap())
|
||
command.AddCommand(resetDERPMap())
|
||
|
||
return command
|
||
}
|
||
|
||
func listTailnetsCommand() *cobra.Command {
|
||
command, tc := prepareCommand(false, &cobra.Command{
|
||
Use: "list",
|
||
Short: "List available Tailnets",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
resp, err := tc.Client().ListTailnets(cmd.Context(), connect.NewRequest(&api.ListTailnetsRequest{}))
|
||
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
tbl := table.New("ID", "NAME")
|
||
for _, tailnet := range resp.Msg.Tailnet {
|
||
tbl.AddRow(tailnet.Id, tailnet.Name)
|
||
}
|
||
tbl.Print()
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func createTailnetsCommand() *cobra.Command {
|
||
command, tc := prepareCommand(false, &cobra.Command{
|
||
Use: "create",
|
||
Short: "Create a new Tailnet",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
var name string
|
||
var domain string
|
||
var email string
|
||
|
||
command.Flags().StringVarP(&name, "name", "n", "", "")
|
||
command.Flags().StringVar(&domain, "domain", "", "")
|
||
command.Flags().StringVar(&email, "email", "", "")
|
||
|
||
command.PreRunE = func(cmd *cobra.Command, args []string) error {
|
||
if name == "" {
|
||
return fmt.Errorf("flag --name is required")
|
||
}
|
||
if domain != "" && email != "" {
|
||
return fmt.Errorf("flags --email and --domain are mutually exclusive")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
|
||
dnsConfig := defaults.DefaultDNSConfig()
|
||
aclPolicy := defaults.DefaultACLPolicy().Marshal()
|
||
iamPolicy := "{}"
|
||
|
||
if len(domain) != 0 {
|
||
domainToLower := strings.ToLower(domain)
|
||
m, err := json.MarshalIndent(&ionscale.IAMPolicy{
|
||
Filters: []string{fmt.Sprintf("domain == %s", domainToLower)},
|
||
}, "", " ")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
iamPolicy = string(m)
|
||
}
|
||
|
||
if len(email) != 0 {
|
||
emailToLower := strings.ToLower(email)
|
||
m, err := json.MarshalIndent(&ionscale.IAMPolicy{
|
||
Emails: []string{emailToLower},
|
||
Roles: map[string]string{
|
||
emailToLower: string(idomain.UserRoleAdmin),
|
||
},
|
||
}, "", " ")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
iamPolicy = string(m)
|
||
}
|
||
|
||
resp, err := tc.Client().CreateTailnet(cmd.Context(), connect.NewRequest(&api.CreateTailnetRequest{
|
||
Name: name,
|
||
IamPolicy: iamPolicy,
|
||
AclPolicy: aclPolicy,
|
||
DnsConfig: dnsConfig,
|
||
}))
|
||
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
tbl := table.New("ID", "NAME")
|
||
tbl.AddRow(resp.Msg.Tailnet.Id, resp.Msg.Tailnet.Name)
|
||
tbl.Print()
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func deleteTailnetCommand() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "delete",
|
||
Short: "Delete a tailnet",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
var force bool
|
||
|
||
command.Flags().BoolVar(&force, "force", false, "When enabled, force delete the specified Tailnet even when machines are still available.")
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
_, err := tc.Client().DeleteTailnet(cmd.Context(), connect.NewRequest(&api.DeleteTailnetRequest{TailnetId: tc.TailnetID(), Force: force}))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
fmt.Println("Tailnet deleted.")
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func getDERPMap() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "get-derp-map",
|
||
Short: "Get the DERP Map configuration",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
var asJson bool
|
||
|
||
command.Flags().BoolVar(&asJson, "json", false, "When enabled, render output as json otherwise yaml")
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
resp, err := tc.Client().GetDERPMap(cmd.Context(), connect.NewRequest(&api.GetDERPMapRequest{TailnetId: tc.TailnetID()}))
|
||
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
var derpMap struct {
|
||
Regions map[int]*tailcfg.DERPRegion
|
||
}
|
||
|
||
if err := json.Unmarshal(resp.Msg.Value, &derpMap); err != nil {
|
||
return err
|
||
}
|
||
|
||
if asJson {
|
||
marshal, err := json.MarshalIndent(derpMap, "", " ")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
fmt.Println(string(marshal))
|
||
} else {
|
||
marshal, err := yaml.Marshal(derpMap)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
fmt.Println(string(marshal))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func setDERPMap() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "set-derp-map",
|
||
Short: "Set the DERP Map configuration",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
var file string
|
||
|
||
command.Flags().StringVar(&file, "file", "", "Path to json file with the DERP Map configuration")
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
rawJson, err := os.ReadFile(file)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
resp, err := tc.Client().SetDERPMap(cmd.Context(), connect.NewRequest(&api.SetDERPMapRequest{TailnetId: tc.TailnetID(), Value: rawJson}))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
var derpMap tailcfg.DERPMap
|
||
if err := json.Unmarshal(resp.Msg.Value, &derpMap); err != nil {
|
||
return err
|
||
}
|
||
|
||
fmt.Println("DERP Map updated successfully")
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func resetDERPMap() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "reset-derp-map",
|
||
Short: "Reset the DERP Map to the default configuration",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
if _, err := tc.Client().ResetDERPMap(cmd.Context(), connect.NewRequest(&api.ResetDERPMapRequest{TailnetId: tc.TailnetID()})); err != nil {
|
||
return err
|
||
}
|
||
|
||
fmt.Println("DERP Map updated successfully")
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func enableFileSharingCommand() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "enable-file-sharing",
|
||
Aliases: []string{"enable-taildrop"},
|
||
Short: "Enable Taildrop, the file sharing feature",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
req := api.EnableFileSharingRequest{
|
||
TailnetId: tc.TailnetID(),
|
||
}
|
||
|
||
if _, err := tc.Client().EnableFileSharing(cmd.Context(), connect.NewRequest(&req)); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func disableFileSharingCommand() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "disable-file-sharing",
|
||
Aliases: []string{"disable-taildrop"},
|
||
Short: "Disable Taildrop, the file sharing feature",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
req := api.DisableFileSharingRequest{
|
||
TailnetId: tc.TailnetID(),
|
||
}
|
||
|
||
if _, err := tc.Client().DisableFileSharing(cmd.Context(), connect.NewRequest(&req)); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func enableServiceCollectionCommand() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "enable-service-collection",
|
||
Short: "Enable monitoring live services running on your network’s machines.",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
req := api.EnableServiceCollectionRequest{
|
||
TailnetId: tc.TailnetID(),
|
||
}
|
||
|
||
if _, err := tc.Client().EnableServiceCollection(cmd.Context(), connect.NewRequest(&req)); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func disableServiceCollectionCommand() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "disable-service-collection",
|
||
Short: "Disable monitoring live services running on your network’s machines.",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
req := api.DisableServiceCollectionRequest{
|
||
TailnetId: tc.TailnetID(),
|
||
}
|
||
|
||
if _, err := tc.Client().DisableServiceCollection(cmd.Context(), connect.NewRequest(&req)); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func enableSSHCommand() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "enable-ssh",
|
||
Short: "Enable ssh access using tailnet and ACLs.",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
req := api.EnableSSHRequest{
|
||
TailnetId: tc.TailnetID(),
|
||
}
|
||
|
||
if _, err := tc.Client().EnableSSH(cmd.Context(), connect.NewRequest(&req)); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func disableSSHCommand() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "disable-ssh",
|
||
Short: "Disable ssh access using tailnet and ACLs.",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
req := api.DisableSSHRequest{
|
||
TailnetId: tc.TailnetID(),
|
||
}
|
||
|
||
if _, err := tc.Client().DisableSSH(cmd.Context(), connect.NewRequest(&req)); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func enableMachineAuthorizationCommand() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "enable-machine-authorization",
|
||
Short: "Enable machine authorization.",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
req := api.EnableMachineAuthorizationRequest{
|
||
TailnetId: tc.TailnetID(),
|
||
}
|
||
|
||
if _, err := tc.Client().EnableMachineAuthorization(cmd.Context(), connect.NewRequest(&req)); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|
||
|
||
func disableMachineAuthorizationCommand() *cobra.Command {
|
||
command, tc := prepareCommand(true, &cobra.Command{
|
||
Use: "disable-machine-authorization",
|
||
Short: "Disable machine authorization.",
|
||
SilenceUsage: true,
|
||
})
|
||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error {
|
||
req := api.DisableMachineAuthorizationRequest{
|
||
TailnetId: tc.TailnetID(),
|
||
}
|
||
|
||
if _, err := tc.Client().DisableMachineAuthorization(cmd.Context(), connect.NewRequest(&req)); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
return command
|
||
}
|