Files
ionscale/internal/cmd/tailnet.go
T
2024-03-15 08:47:06 +01:00

460 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 networks 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 networks 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
}