diff --git a/cmd/cli/add.go b/cmd/cli/add.go index d922844..cba5fa4 100644 --- a/cmd/cli/add.go +++ b/cmd/cli/add.go @@ -9,12 +9,18 @@ import ( ) // Add prompts for the required information and creates a new peer -func Add(hostname, owner, description string, confirm bool) { - // TODO accept existing pubkey +func Add(hostname string, privKey, pubKey bool, owner, description string, confirm bool) { config, err := LoadConfigFile() check(err, "failed to load configuration file") server := GetServer(config) + var private, public string + if privKey { + private = MustPromptString("private key", true) + } + if pubKey { + public = MustPromptString("public key", true) + } if owner == "" { owner = MustPromptString("owner", true) } @@ -30,7 +36,7 @@ func Add(hostname, owner, description string, confirm bool) { // newline (not on stdout) to separate config fmt.Fprintln(os.Stderr) - peer, err := lib.NewPeer(server, owner, hostname, description) + peer, err := lib.NewPeer(server, private, public, owner, hostname, description) check(err, "failed to get new peer") // TODO Some kind of recovery here would be nice, to avoid diff --git a/cmd/root.go b/cmd/root.go index b9ed54b..947110b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -67,8 +67,8 @@ var ( } addCmd = &cobra.Command{ - Use: "add [hostname]", - Short: "Add a new peer + sync", + Use: "add ", + Short: "Add a new peer + sync, optionally using a provided WireGuard private key", PreRunE: func(cmd *cobra.Command, args []string) error { // Make sure we have the hostname if len(args) != 1 { @@ -77,7 +77,12 @@ var ( return nil }, Run: func(cmd *cobra.Command, args []string) { - cli.Add(args[0], owner, description, confirm) + privKey, err := cmd.PersistentFlags().GetBool("private-key") + pubKey, err := cmd.PersistentFlags().GetBool("public-key") + if err != nil { + cli.ExitFail("%w - error processing key flag", err) + } + cli.Add(args[0], privKey, pubKey, owner, description, confirm) }, } @@ -142,6 +147,8 @@ func init() { addCmd.Flags().StringVar(&owner, "owner", "", "owner of the new peer") addCmd.Flags().StringVar(&description, "description", "", "description of the new peer") addCmd.Flags().BoolVar(&confirm, "confirm", false, "confirm") + addCmd.PersistentFlags().BoolP("private-key", "r", false, "Accept user-supplied private key. If supplied, dsnet will generate a public key.") + addCmd.PersistentFlags().BoolP("public-key", "u", false, "Accept user-supplied public key. If supplied, the user must add the private key to the generated config (or provide it with --private-key).") removeCmd.Flags().BoolVar(&confirm, "confirm", false, "confirm") regenerateCmd.Flags().BoolVar(&confirm, "confirm", false, "confirm") diff --git a/lib/peer.go b/lib/peer.go index e522416..8935a1d 100644 --- a/lib/peer.go +++ b/lib/peer.go @@ -4,7 +4,10 @@ import ( "errors" "fmt" "net" + "strings" "time" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) // PeerType is what configuration to use when generating @@ -37,7 +40,15 @@ type Peer struct { PersistentKeepalive int } -func NewPeer(server *Server, owner string, hostname string, description string) (Peer, error) { +// NewPeer generates a peer from the supplied arguments and generates keys if needed. +// - server is required and provides network information +// - private is a base64-encoded private key; if the empty string, a new key will be generated +// - public is a base64-encoded public key. If empty, it will be generated from the private key. +// If **not** empty, the private key will be included IFF a private key was provided. +// - owner is the owner name (required) +// - hostname is the name of the peer (required) +// - description is the annotation for the peer +func NewPeer(server *Server, private, public, owner, hostname, description string) (Peer, error) { if owner == "" { return Peer{}, errors.New("missing owner") } @@ -45,11 +56,39 @@ func NewPeer(server *Server, owner string, hostname string, description string) return Peer{}, errors.New("missing hostname") } - privateKey, err := GenerateJSONPrivateKey() - if err != nil { - return Peer{}, fmt.Errorf("failed to generate private key: %s", err) + var privateKey JSONKey + if private != "" { + userKey := &JSONKey{} + userKey.UnmarshalJSON([]byte(private)) + privateKey = *userKey + } else { + var err error + privateKey, err = GenerateJSONPrivateKey() + if err != nil { + return Peer{}, fmt.Errorf("failed to generate private key: %s", err) + } + } + + var publicKey JSONKey + if public != "" { + b64Key := strings.Trim(string(public), "\"") + key, err := wgtypes.ParseKey(b64Key) + if err != nil { + return Peer{}, err + } + publicKey = JSONKey{Key: key} + if private == "" { + privateKey = JSONKey{Key: wgtypes.Key([wgtypes.KeyLen]byte{})} + } else { + pubK := privateKey.PublicKey() + ascK := pubK.Key.String() + if ascK != public { + return Peer{}, fmt.Errorf("user-supplied private and public keys are not related") + } + } + } else { + publicKey = privateKey.PublicKey() } - publicKey := privateKey.PublicKey() presharedKey, err := GenerateJSONKey() if err != nil {