diff --git a/bootstrapper_authkey.go b/bootstrapper_authkey.go index 9e20c54..01c07db 100644 --- a/bootstrapper_authkey.go +++ b/bootstrapper_authkey.go @@ -52,6 +52,8 @@ func (b *AuthKeyBootstrapper) Execute(config *Config) (*Config, error) { EnableMetrics: true, Interface: DefaultInterfaceName(), AdditionalAllowedIPs: nil, + Mtu: DefaultMTU, + PersistentKeepalive: DefaultPersistentKeepaliveInterval, Profile: b.Profile, ArcSession: &sim.ArcSession, } diff --git a/bootstrapper_cellular.go b/bootstrapper_cellular.go index 32815e4..5c7b3ab 100644 --- a/bootstrapper_cellular.go +++ b/bootstrapper_cellular.go @@ -21,6 +21,8 @@ func (b *CellularBootstrapper) Execute(config *Config) (*Config, error) { EnableMetrics: true, Interface: DefaultInterfaceName(), AdditionalAllowedIPs: nil, + Mtu: DefaultMTU, + PersistentKeepalive: DefaultPersistentKeepaliveInterval, Profile: nil, ArcSession: nil, } diff --git a/bootstrapper_sim_linux.go b/bootstrapper_sim_linux.go index d9defdf..06574b1 100644 --- a/bootstrapper_sim_linux.go +++ b/bootstrapper_sim_linux.go @@ -21,7 +21,9 @@ func (b *SimBootstrapper) Execute(config *Config) (*Config, error) { return nil, err } - fmt.Printf("Running %s %s\n", b.KryptonCliPath, b.Arguments) + if v := os.Getenv("SORACOM_VERBOSE"); v != "" { + fmt.Fprintf(os.Stderr, "Running %s %s\n", b.KryptonCliPath, b.Arguments) + } // if no config, create a blank, then replace keys and ArcSession with new if config == nil { @@ -33,6 +35,8 @@ func (b *SimBootstrapper) Execute(config *Config) (*Config, error) { EnableMetrics: true, Interface: DefaultInterfaceName(), AdditionalAllowedIPs: nil, + Mtu: DefaultMTU, + PersistentKeepalive: DefaultPersistentKeepaliveInterval, Profile: nil, ArcSession: nil, } @@ -61,7 +65,10 @@ func (b *SimBootstrapper) Execute(config *Config) (*Config, error) { if err != nil { return nil, fmt.Errorf("error while marshaling response from krypton-cli: %s", err) } - fmt.Printf("Got response from %s: %s\n", b.KryptonCliPath, t) + + if v := os.Getenv("SORACOM_VERBOSE"); v != "" { + fmt.Fprintf(os.Stderr, "Got response from %s: %s\n", b.KryptonCliPath, t) + } config.PrivateKey = arcSession.ArcClientPeerPrivateKey config.PublicKey = (Key)(arcSession.ArcClientPeerPrivateKey.AsWgKey().PublicKey()) diff --git a/client.go b/client.go index 3d8e395..4d1aa41 100644 --- a/client.go +++ b/client.go @@ -198,7 +198,7 @@ func (c *DefaultSoracomClient) callAPI(params *apiParams) (*http.Response, error if c.Verbose() { fmt.Fprintln(os.Stderr, "--- Request dump ---------------------------------") r, _ := httputil.DumpRequest(req, true) - fmt.Fprintf(os.Stderr, "%s\n", r) + fmt.Fprintln(os.Stderr, r) fmt.Fprintln(os.Stderr, "--- End of request dump --------------------------") } res, err := c.doRequest(req) @@ -239,7 +239,7 @@ func (c *DefaultSoracomClient) doRequest(req *http.Request) (*http.Response, err if c.Verbose() && res != nil { fmt.Fprintln(os.Stderr, "--- Response dump --------------------------------") r, _ := httputil.DumpResponse(res, true) - fmt.Fprintf(os.Stderr, "%s\n", r) + fmt.Fprintln(os.Stderr, r) fmt.Fprintln(os.Stderr, "--- End of response dump -------------------------") } diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index 9e6c159..025cc3e 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -35,31 +35,48 @@ sim current SIM SIM Authentication Compatible modem/SIM card return cmd } -func bootstrap(bootstrapper soratun.Bootstrapper) (*soratun.Config, error) { - // if current config exists, pass it to the bootstrapper to merge if applicable - currentConfig, _ := readConfig(configPath) +// bootstrap do bootstrap with specified bootstrapper. If persist is set to true, save it to the path specified with "--config" flag +func bootstrap(bootstrapper soratun.Bootstrapper) error { + var currentConfig *soratun.Config = nil + + if !dumpConfig { + // Won't check error from `readConfig` because: + // + // 1. In the very first run, which means no `arc.json` in the file system, the `readConfig` always fail. + // We should move bootstrapping process forward. + // 2. Also bootstrap process—creating a new virtual SIM—should be finished successfully regardless of error + // (read failure, invalid JSON format, etc.) Once failed (= `currentCOnfig` is `nil`), Bootstrapper#Execute + // will create a fresh `soratun.Config` (it will vary on each bootstrap method) and can move the process + // forward. Bootstrapper will update existing configuration. + currentConfig, _ = readConfig(configPath) + } + config, err := bootstrapper.Execute(currentConfig) if err != nil { - return nil, err + return err } b, err := json.MarshalIndent(config, "", " ") if err != nil { - return nil, err + return err } - err = writeConfigurationToFile(string(b)) - if err != nil { - return nil, err - } + if !dumpConfig { + err = writeConfigurationToFile(string(b)) + if err != nil { + return err + } - if config.SimId != "" { - fmt.Printf("Virtual subscriber SIM ID: %s\n", config.SimId) - } + if config.SimId != "" { + fmt.Printf("Virtual subscriber SIM ID: %s\n", config.SimId) + } - printConfigurationFilePath() + printConfigurationFilePath() + } else { + fmt.Println(string(b)) + } - return config, nil + return nil } func printConfigurationFilePath() { diff --git a/cmd/bootstrap_authkey.go b/cmd/bootstrap_authkey.go index a4c7243..f478b33 100644 --- a/cmd/bootstrap_authkey.go +++ b/cmd/bootstrap_authkey.go @@ -51,9 +51,7 @@ func bootstrapAuthKeyCmd() *cobra.Command { } } - _, err = bootstrap(&soratun.AuthKeyBootstrapper{ - Profile: profile, - }) + err = bootstrap(&soratun.AuthKeyBootstrapper{Profile: profile}) if err != nil { log.Fatalf("failed to bootstrap: %v", err) } diff --git a/cmd/bootstrap_cellular.go b/cmd/bootstrap_cellular.go index 28e69b9..127ecf7 100644 --- a/cmd/bootstrap_cellular.go +++ b/cmd/bootstrap_cellular.go @@ -16,11 +16,7 @@ func bootstrapCellularCmd() *cobra.Command { Long: "This command will create a new virtual SIM which is associated with current physical SIM, then create configuration for soratun. Need active SORACOM Air for Cellular connection.", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - var err error - - _, err = bootstrap(&soratun.CellularBootstrapper{ - Endpoint: kryptonCellularEndpoint, - }) + err := bootstrap(&soratun.CellularBootstrapper{Endpoint: kryptonCellularEndpoint}) if err != nil { log.Fatalf("failed to bootstrap: %v", err) } @@ -28,6 +24,7 @@ func bootstrapCellularCmd() *cobra.Command { } cmd.Flags().StringVar(&kryptonCellularEndpoint, "endpoint", "https://krypton.soracom.io:8036", "Specify SORACOM Krypton Provisioning API endpoint.") + cmd.Flags().BoolVar(&dumpConfig, "dump-config", false, "dump configuration to stdout, ignoring --config setting") return cmd } diff --git a/cmd/bootstrap_sim.go b/cmd/bootstrap_sim.go index 2a02d70..323a7e6 100644 --- a/cmd/bootstrap_sim.go +++ b/cmd/bootstrap_sim.go @@ -23,6 +23,7 @@ var ( disableKeyCache bool clearKeyCache bool kryptonCliPath string + dumpConfig bool ) func bootstrapSimCmd() *cobra.Command { @@ -32,7 +33,7 @@ func bootstrapSimCmd() *cobra.Command { Long: "This command will create a new virtual SIM which is associated with current physical SIM, then create configuration for soratun. You need working \"krypton-cli\". See https://github.com/soracom/krypton-client-go for how to install.", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - _, err := bootstrap(&soratun.SimBootstrapper{ + err := bootstrap(&soratun.SimBootstrapper{ KryptonCliPath: kryptonCliPath, Arguments: buildKryptonCliArguments(), }) @@ -55,6 +56,7 @@ func bootstrapSimCmd() *cobra.Command { cmd.Flags().BoolVar(&disableKeyCache, "disable-key-cache", false, "Do not store authentication result to the key cache") cmd.Flags().BoolVar(&clearKeyCache, "clear-key-cache", false, "Remove all items in the key cache") cmd.Flags().StringVar(&kryptonCliPath, "krypton-cli-path", "/usr/local/bin/krypton-cli", "Path to krypton-cli") + cmd.Flags().BoolVar(&dumpConfig, "dump-config", false, "dump configuration to stdout, ignoring --config setting") return cmd } diff --git a/cmd/dump_wireguard_config.go b/cmd/dump_wireguard_config.go index 9f3e732..00fe8ba 100644 --- a/cmd/dump_wireguard_config.go +++ b/cmd/dump_wireguard_config.go @@ -2,7 +2,9 @@ package cmd import ( "fmt" + "io" "net" + "os" "strings" "github.com/spf13/cobra" @@ -15,12 +17,12 @@ func dumpWireGuardConfigCmd() *cobra.Command { Args: cobra.NoArgs, PreRun: initSoratun, Run: func(cmd *cobra.Command, args []string) { - dumpWireGuardConfig(false) + dumpWireGuardConfig(false, os.Stdout) }, } } -func dumpWireGuardConfig(mask bool) { +func dumpWireGuardConfig(mask bool, w io.Writer) { var ips []string for _, ip := range Config.ArcSession.ArcAllowedIPs { ips = append(ips, (*net.IPNet)(ip).String()) @@ -50,7 +52,7 @@ func dumpWireGuardConfig(mask bool) { } } - fmt.Printf(`[Interface] + fmt.Fprintf(w, `[Interface] Address = %s/32 PrivateKey = %s MTU = %d diff --git a/cmd/up.go b/cmd/up.go index 967d89e..a32719c 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -1,33 +1,88 @@ package cmd import ( + "encoding/json" "fmt" + "io/ioutil" "log" + "net" "os" + "strings" "github.com/soracom/soratun" "github.com/spf13/cobra" ) +var ( + mtu int + persistentKeepalive int + additionalAllowedIPs string + readStdin bool +) + func upCmd() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "up", Aliases: []string{"u"}, Short: "Setup SORACOM Arc interface", Args: cobra.NoArgs, - PreRun: initSoratun, Run: func(cmd *cobra.Command, args []string) { + if readStdin { + b, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalf("Failed to read configuration from stdin: %v", err) + } + + var config soratun.Config + err = json.Unmarshal(b, &config) + if err != nil { + log.Fatalf("Failed to read configuration from stdin: %v", err) + } + Config = &config + } else { + initSoratun(cmd, args) + } + + // override only if the flag was explicitly set + if cmd.Flags().Changed("mtu") { + Config.Mtu = mtu + } + + if cmd.Flags().Changed("persistent-keepalive") { + Config.PersistentKeepalive = persistentKeepalive + } + if Config.ArcSession == nil { log.Fatal("Failed to determine connection information. Please bootstrap or create a new session from the user console.") } + if additionalAllowedIPs != "" { + for _, s := range strings.Split(additionalAllowedIPs, ",") { + _, ipnet, err := net.ParseCIDR(strings.TrimSpace(s)) + if err != nil { + log.Fatalf("Invalid CIDR is set for \"--additional-allowd-ips\": %v", err) + } + Config.ArcSession.ArcAllowedIPs = append(Config.ArcSession.ArcAllowedIPs, &soratun.IPNet{ + IP: ipnet.IP, + Mask: ipnet.Mask, + }) + } + } + if v := os.Getenv("SORACOM_VERBOSE"); v != "" { - fmt.Println("--- WireGuard configuration ----------------------") - dumpWireGuardConfig(true) - fmt.Println("--- End of WireGuard configuration ---------------") + fmt.Fprintln(os.Stderr, "--- WireGuard configuration ----------------------") + dumpWireGuardConfig(true, os.Stderr) + fmt.Fprintln(os.Stderr, "--- End of WireGuard configuration ---------------") } soratun.Up(ctx, Config) }, } + + cmd.Flags().IntVar(&mtu, "mtu", soratun.DefaultMTU, "MTU for the interface, which will override arc.json#mtu value") + cmd.Flags().IntVar(&persistentKeepalive, "persistent-keepalive", soratun.DefaultPersistentKeepaliveInterval, "WireGuard `PersistentKeepalive` for the SORACOM Arc server, which will override arc.json#persistentKeepalive value") + cmd.Flags().StringVar(&additionalAllowedIPs, "additional-allowed-ips", "", "Comma separated string of additional WireGuard allowed CIDRs, which will be added to arc.json#additionalAllowedIPs array") + cmd.Flags().BoolVar(&readStdin, "read-stdin", false, "read configuration from stdin, ignoring --config setting") + + return cmd } diff --git a/config.go b/config.go index b80fe4c..6bcccd0 100644 --- a/config.go +++ b/config.go @@ -37,7 +37,7 @@ type Config struct { SimId string `json:"simId"` // LogLevel specifies logging level, verbose, error, or silent. LogLevel int `json:"logLevel"` - // If EnableMetrics is true, metrics will be logged when log-level is verbose or error. + // If EnableMetrics is true, metrics will be logged when log-level is verbose. EnableMetrics bool `json:"enableMetrics"` // Interface is name for the tunnel interface. Interface string `json:"interface"` diff --git a/krypton_client.go b/krypton_client.go index e6fe556..5a6caba 100644 --- a/krypton_client.go +++ b/krypton_client.go @@ -96,7 +96,7 @@ func (c *DefaultSoracomKryptonClient) callAPI(params *apiParams) (*http.Response if c.Verbose() { fmt.Fprintln(os.Stderr, "--- Request dump ---------------------------------") r, _ := httputil.DumpRequest(req, true) - fmt.Fprintf(os.Stderr, "%s\n", r) + fmt.Fprintln(os.Stderr, r) fmt.Fprintln(os.Stderr, "--- End of request dump --------------------------") } res, err := c.doRequest(req) @@ -131,7 +131,7 @@ func (c *DefaultSoracomKryptonClient) doRequest(req *http.Request) (*http.Respon if c.Verbose() && res != nil { fmt.Fprintln(os.Stderr, "--- Response dump --------------------------------") r, _ := httputil.DumpResponse(res, true) - fmt.Fprintf(os.Stderr, "%s\n", r) + fmt.Fprintln(os.Stderr, r) fmt.Fprintln(os.Stderr, "--- End of response dump -------------------------") }