Browse Source

feat(cli): implement network cli

Mustafa Arici 8 years ago
parent
commit
36ee539bee
8 changed files with 574 additions and 409 deletions
  1. 5 5
      bindata/bindata.go
  2. 17 401
      cmd/ovpm/main.go
  3. 118 0
      cmd/ovpm/net.go
  4. 326 0
      cmd/ovpm/user.go
  5. 0 1
      cmd/ovpm/utils.go
  6. 105 0
      cmd/ovpm/vpn.go
  7. 1 0
      cmd/ovpmd/main.go
  8. 2 2
      net.go

+ 5 - 5
bindata/bindata.go

@@ -87,7 +87,7 @@ func templateCcdFileTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/ccd.file.tmpl", size: 74, mode: os.FileMode(420), modTime: time.Unix(1502998813, 0)}
+	info := bindataFileInfo{name: "template/ccd.file.tmpl", size: 74, mode: os.FileMode(420), modTime: time.Unix(1503322186, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
@@ -107,7 +107,7 @@ func templateClientOvpnTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/client.ovpn.tmpl", size: 365, mode: os.FileMode(420), modTime: time.Unix(1503411871, 0)}
+	info := bindataFileInfo{name: "template/client.ovpn.tmpl", size: 365, mode: os.FileMode(420), modTime: time.Unix(1503410118, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
@@ -127,7 +127,7 @@ func templateDh4096PemTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/dh4096.pem.tmpl", size: 1468, mode: os.FileMode(420), modTime: time.Unix(1502796579, 0)}
+	info := bindataFileInfo{name: "template/dh4096.pem.tmpl", size: 1468, mode: os.FileMode(420), modTime: time.Unix(1503322186, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
@@ -147,7 +147,7 @@ func templateIptablesTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/iptables.tmpl", size: 0, mode: os.FileMode(420), modTime: time.Unix(1502796579, 0)}
+	info := bindataFileInfo{name: "template/iptables.tmpl", size: 0, mode: os.FileMode(420), modTime: time.Unix(1503322186, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }
@@ -167,7 +167,7 @@ func templateServerConfTmpl() (*asset, error) {
 		return nil, err
 	}
 
-	info := bindataFileInfo{name: "template/server.conf.tmpl", size: 9585, mode: os.FileMode(420), modTime: time.Unix(1502796579, 0)}
+	info := bindataFileInfo{name: "template/server.conf.tmpl", size: 9585, mode: os.FileMode(420), modTime: time.Unix(1503322186, 0)}
 	a := &asset{bytes: bytes, info: info}
 	return a, nil
 }

+ 17 - 401
cmd/ovpm/main.go

@@ -1,15 +1,10 @@
 package main
 
 import (
-	"context"
-	"fmt"
-	"net"
 	"os"
 
 	"github.com/Sirupsen/logrus"
 	"github.com/cad/ovpm"
-	"github.com/cad/ovpm/pb"
-	"github.com/olekukonko/tablewriter"
 	"github.com/urfave/cli"
 )
 
@@ -43,408 +38,29 @@ func main() {
 			Name:  "user",
 			Usage: "User Operations",
 			Subcommands: []cli.Command{
-				{
-					Name:  "list",
-					Usage: "List VPN users.",
-					Action: func(c *cli.Context) error {
-						action = "user:list"
-						conn := getConn(c.GlobalString("daemon-port"))
-						defer conn.Close()
-						userSvc := pb.NewUserServiceClient(conn)
-						vpnSvc := pb.NewVPNServiceClient(conn)
-
-						server, err := vpnSvc.Status(context.Background(), &pb.VPNStatusRequest{})
-						if err != nil {
-							logrus.Errorf("can not get server status: %v", err)
-							os.Exit(1)
-							return err
-						}
-
-						resp, err := userSvc.List(context.Background(), &pb.UserListRequest{})
-						if err != nil {
-							logrus.Errorf("users can not be fetched: %v", err)
-							os.Exit(1)
-							return err
-						}
-						table := tablewriter.NewWriter(os.Stdout)
-						table.SetHeader([]string{"#", "username", "ip", "created at", "valid crt", "no gw"})
-						//table.SetBorder(false)
-						for i, user := range resp.Users {
-							static := ""
-							if user.HostID != 0 {
-								static = "s"
-							}
-							data := []string{fmt.Sprintf("%v", i+1), user.Username, fmt.Sprintf("%s %s", user.IPNet, static), user.CreatedAt, fmt.Sprintf("%t", user.ServerSerialNumber == server.SerialNumber), fmt.Sprintf("%t", user.NoGW)}
-							table.Append(data)
-						}
-						table.Render()
-
-						return nil
-					},
-				},
-				{
-					Name:  "create",
-					Usage: "Create a VPN user.",
-					Flags: []cli.Flag{
-						cli.StringFlag{
-							Name:  "username, u",
-							Usage: "username for the vpn user",
-						},
-						cli.StringFlag{
-							Name:  "password, p",
-							Usage: "password for the vpn user",
-						},
-						cli.BoolFlag{
-							Name:  "no-gw",
-							Usage: "don't push vpn server as default gateway for this user",
-						},
-						cli.StringFlag{
-							Name:  "static",
-							Usage: "ip address for the vpn user",
-						},
-					},
-					Action: func(c *cli.Context) error {
-						action = "user:create"
-						username := c.String("username")
-						password := c.String("password")
-						noGW := c.Bool("no-gw")
-						static := c.String("static")
-
-						if username == "" || password == "" {
-							fmt.Println(cli.ShowSubcommandHelp(c))
-							os.Exit(1)
-						}
-
-						var hostid uint32
-						if static != "" {
-							h := ovpm.IP2HostID(net.ParseIP(static).To4())
-							if h == 0 {
-								fmt.Println("--static flag takes a valid ipv4 address")
-								fmt.Println()
-								fmt.Println(cli.ShowSubcommandHelp(c))
-								os.Exit(1)
-							}
-
-							hostid = h
-						}
-
-						//conn := getConn(c.String("port"))
-						conn := getConn(c.GlobalString("daemon-port"))
-						defer conn.Close()
-						userSvc := pb.NewUserServiceClient(conn)
-
-						response, err := userSvc.Create(context.Background(), &pb.UserCreateRequest{Username: username, Password: password, NoGW: noGW, HostID: hostid})
-						if err != nil {
-							logrus.Errorf("user can not be created '%s': %v", username, err)
-							os.Exit(1)
-							return err
-						}
-						logrus.Infof("user created: %s", response.Users[0].Username)
-						return nil
-					},
-				},
-				{
-					Name:  "update",
-					Usage: "Update a VPN user.",
-					Flags: []cli.Flag{
-						cli.StringFlag{
-							Name:  "username, u",
-							Usage: "username of the vpn user to update",
-						},
-						cli.StringFlag{
-							Name:  "password, p",
-							Usage: "new password for the vpn user",
-						},
-						cli.BoolFlag{
-							Name:  "no-gw",
-							Usage: "don't push vpn server as default gateway for this user",
-						},
-						cli.BoolFlag{
-							Name:  "gw",
-							Usage: "push vpn server as default gateway for this user",
-						},
-						cli.StringFlag{
-							Name:  "static",
-							Usage: "ip address for the vpn user",
-						},
-					},
-					Action: func(c *cli.Context) error {
-						action = "user:update"
-						username := c.String("username")
-						password := c.String("password")
-						nogw := c.Bool("no-gw")
-						gw := c.Bool("gw")
-						static := c.String("static")
-
-						if username == "" {
-							fmt.Println(cli.ShowSubcommandHelp(c))
-							os.Exit(1)
-						}
-
-						if !(password != "" || gw || nogw) {
-							fmt.Println("nothing is updated!")
-							fmt.Println()
-							fmt.Println(cli.ShowSubcommandHelp(c))
-							os.Exit(1)
-						}
-
-						var hostid uint32
-						if static != "" {
-							h := ovpm.IP2HostID(net.ParseIP(static).To4())
-							if h == 0 {
-								fmt.Println("--static flag takes a valid ipv4 address")
-								fmt.Println()
-								fmt.Println(cli.ShowSubcommandHelp(c))
-								os.Exit(1)
-							}
-
-							hostid = h
-						}
-
-						var gwPref pb.UserUpdateRequest_GWPref
-
-						switch {
-						case gw && !nogw:
-							gwPref = pb.UserUpdateRequest_GW
-						case !gw && nogw:
-							gwPref = pb.UserUpdateRequest_NOGW
-						case gw && nogw:
-							// Ambigius.
-							fmt.Println("you can't use --gw together with --no-gw")
-							fmt.Println()
-							fmt.Println(cli.ShowSubcommandHelp(c))
-							os.Exit(1)
-						default:
-							gwPref = pb.UserUpdateRequest_NOPREF
-
-						}
-
-						//conn := getConn(c.String("port"))
-						conn := getConn(c.GlobalString("daemon-port"))
-						defer conn.Close()
-						userSvc := pb.NewUserServiceClient(conn)
-
-						response, err := userSvc.Update(context.Background(), &pb.UserUpdateRequest{
-							Username: username,
-							Password: password,
-							Gwpref:   gwPref,
-							HostID:   hostid,
-						})
-
-						if err != nil {
-							logrus.Errorf("user can not be updated '%s': %v", username, err)
-							os.Exit(1)
-							return err
-						}
-						logrus.Infof("user updated: %s", response.Users[0].Username)
-						return nil
-					},
-				},
-				{
-					Name:  "delete",
-					Usage: "Delete a VPN user.",
-					Flags: []cli.Flag{
-						cli.StringFlag{
-							Name:  "user, u",
-							Usage: "username of the vpn user",
-						},
-					},
-					Action: func(c *cli.Context) error {
-						action = "user:delete"
-						username := c.String("user")
-
-						if username == "" {
-							fmt.Println(cli.ShowSubcommandHelp(c))
-							os.Exit(1)
-						}
-
-						//conn := getConn(c.String("port"))
-						conn := getConn(c.GlobalString("daemon-port"))
-						defer conn.Close()
-						userSvc := pb.NewUserServiceClient(conn)
-
-						_, err := userSvc.Delete(context.Background(), &pb.UserDeleteRequest{Username: username})
-						if err != nil {
-							logrus.Errorf("user can not be deleted '%s': %v", username, err)
-							os.Exit(1)
-							return err
-						}
-						logrus.Infof("user deleted: %s", username)
-						return nil
-					},
-				},
-				{
-					Name:  "renew",
-					Usage: "Renew VPN user certificates.",
-					Flags: []cli.Flag{
-						cli.StringFlag{
-							Name:  "user, u",
-							Usage: "username of the vpn user",
-						},
-					},
-					Action: func(c *cli.Context) error {
-						action = "user:renew"
-						username := c.String("user")
-
-						if username == "" {
-							fmt.Println(cli.ShowSubcommandHelp(c))
-							os.Exit(1)
-						}
-
-						//conn := getConn(c.String("port"))
-						conn := getConn(c.GlobalString("daemon-port"))
-						defer conn.Close()
-						userSvc := pb.NewUserServiceClient(conn)
-						pb.NewVPNServiceClient(conn)
-
-						_, err := userSvc.Renew(context.Background(), &pb.UserRenewRequest{Username: username})
-						if err != nil {
-							logrus.Errorf("can't renew user cert '%s': %v", username, err)
-							os.Exit(1)
-							return err
-						}
-						logrus.Infof("user cert renewed: '%s'", username)
-						return nil
-					},
-				},
-				{
-					Name:  "genconfig",
-					Usage: "Generate client config for the user. (.ovpn file)",
-					Flags: []cli.Flag{
-						cli.StringFlag{
-							Name:  "user, u",
-							Usage: "username of the vpn user",
-						},
-						cli.StringFlag{
-							Name:  "out, o",
-							Usage: ".ovpn file output path",
-						},
-					},
-					Action: func(c *cli.Context) error {
-						action = "user:export-config"
-						username := c.String("user")
-						output := c.String("out")
-
-						if username == "" {
-							fmt.Println(cli.ShowSubcommandHelp(c))
-							os.Exit(1)
-						}
-						if output == "" {
-							output = username + ".ovpn"
-						}
-
-						//conn := getConn(c.String("port"))
-						conn := getConn(c.GlobalString("daemon-port"))
-						defer conn.Close()
-						userSvc := pb.NewUserServiceClient(conn)
-						pb.NewVPNServiceClient(conn)
-
-						res, err := userSvc.GenConfig(context.Background(), &pb.UserGenConfigRequest{Username: username})
-						if err != nil {
-							logrus.Errorf("user config can not be exported %s: %v", username, err)
-							return err
-						}
-						emitToFile(output, res.ClientConfig, 0)
-						logrus.Infof("exported to %s", output)
-						return nil
-					},
-				},
+				userListCommand,
+				userCreateCommand,
+				userUpdateCommand,
+				userDeleteCommand,
+				userRenewCommand,
+				userGenconfigCommand,
 			},
 		},
 		{
 			Name:  "vpn",
 			Usage: "VPN Operations",
 			Subcommands: []cli.Command{
-				{
-					Name:  "status",
-					Usage: "Show VPN status.",
-					Action: func(c *cli.Context) error {
-						conn := getConn(c.GlobalString("daemon-port"))
-						defer conn.Close()
-						vpnSvc := pb.NewVPNServiceClient(conn)
-
-						res, err := vpnSvc.Status(context.Background(), &pb.VPNStatusRequest{})
-						if err != nil {
-							os.Exit(1)
-							return err
-						}
-
-						table := tablewriter.NewWriter(os.Stdout)
-						table.SetHeader([]string{"attribute", "value"})
-						table.Append([]string{"Name", res.Name})
-						table.Append([]string{"Hostname", res.Hostname})
-						table.Append([]string{"Port", res.Port})
-						table.Append([]string{"Network", res.Net})
-						table.Append([]string{"Netmask", res.Mask})
-						table.Append([]string{"Created At", res.CreatedAt})
-						table.Render()
-
-						return nil
-					},
-				},
-				{
-					Name:  "init",
-					Usage: "Initialize VPN server.",
-					Flags: []cli.Flag{
-						cli.StringFlag{
-							Name:  "hostname, s",
-							Usage: "ip address or FQDN of the vpn server",
-						},
-						cli.StringFlag{
-							Name:  "port, p",
-							Usage: "port number of the vpn server",
-							Value: ovpm.DefaultVPNPort,
-						},
-					},
-					Action: func(c *cli.Context) error {
-						action = "vpn:init"
-						hostname := c.String("hostname")
-						if hostname == "" {
-							logrus.Errorf("'hostname' is needed")
-							fmt.Println(cli.ShowSubcommandHelp(c))
-							os.Exit(1)
-
-						}
-
-						port := c.String("port")
-						if port == "" {
-							port = ovpm.DefaultVPNPort
-						}
-
-						conn := getConn(c.GlobalString("daemon-port"))
-						defer conn.Close()
-						vpnSvc := pb.NewVPNServiceClient(conn)
-
-						var response string
-						for {
-							fmt.Println("This operation will cause invalidation of existing user certificates.")
-							fmt.Println("After this opeartion, new client config files (.ovpn) should be generated for each existing user.")
-							fmt.Println()
-							fmt.Println("Are you sure ? (y/N)")
-							_, err := fmt.Scanln(&response)
-							if err != nil {
-								logrus.Fatal(err)
-								os.Exit(1)
-								return err
-							}
-							okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
-							nokayResponses := []string{"n", "N", "no", "No", "NO"}
-							if stringInSlice(response, okayResponses) {
-								if _, err := vpnSvc.Init(context.Background(), &pb.VPNInitRequest{Hostname: hostname, Port: port}); err != nil {
-									logrus.Errorf("server can not be initialized: %v", err)
-									os.Exit(1)
-									return err
-								}
-								logrus.Info("ovpm server initialized")
-								break
-							} else if stringInSlice(response, nokayResponses) {
-								return fmt.Errorf("user decided to cancel")
-							}
-						}
-
-						return nil
-					},
-				},
+				vpnStatusCommand,
+				vpnInitCommand,
+			},
+		},
+		{
+			Name:  "net",
+			Usage: "Network Operations",
+			Subcommands: []cli.Command{
+				netListCommand,
+				netDefineCommand,
+				netUndefineCommand,
 			},
 		},
 	}

+ 118 - 0
cmd/ovpm/net.go

@@ -0,0 +1,118 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/cad/ovpm/pb"
+	"github.com/olekukonko/tablewriter"
+	"github.com/urfave/cli"
+)
+
+var netDefineCommand = cli.Command{
+	Name:  "define",
+	Usage: "Define a network.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "cidr, c",
+			Usage: "CIDR of the network",
+		},
+		cli.StringFlag{
+			Name:  "name, n",
+			Usage: "name of the network",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "net:create"
+		name := c.String("name")
+		cidr := c.String("cidr")
+
+		if name == "" || cidr == "" {
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		netSvc := pb.NewNetworkServiceClient(conn)
+
+		response, err := netSvc.Create(context.Background(), &pb.NetworkCreateRequest{Name: name, CIDR: cidr})
+		if err != nil {
+			logrus.Errorf("network can not be created '%s': %v", name, err)
+			os.Exit(1)
+			return err
+		}
+		logrus.Infof("network created: %s (%s)", response.Network.Name, response.Network.CIDR)
+		return nil
+	},
+}
+
+var netListCommand = cli.Command{
+	Name:  "list",
+	Usage: "List network definitions.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "cidr, c",
+			Usage: "CIDR of the network",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "net:list"
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		netSvc := pb.NewNetworkServiceClient(conn)
+
+		resp, err := netSvc.List(context.Background(), &pb.NetworkListRequest{})
+		if err != nil {
+			logrus.Errorf("networks can not be fetched: %v", err)
+			os.Exit(1)
+			return err
+		}
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"#", "name", "cidr", "created at"})
+		//table.SetBorder(false)
+		for i, network := range resp.Networks {
+			data := []string{fmt.Sprintf("%v", i+1), network.Name, network.CIDR, network.CreatedAt}
+			table.Append(data)
+		}
+		table.Render()
+
+		return nil
+	},
+}
+
+var netUndefineCommand = cli.Command{
+	Name:  "undefine",
+	Usage: "Undefine an existing network.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "name, n",
+			Usage: "name of the network",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "net:delete"
+		name := c.String("name")
+
+		if name == "" {
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		netSvc := pb.NewNetworkServiceClient(conn)
+
+		resp, err := netSvc.Delete(context.Background(), &pb.NetworkDeleteRequest{Name: name})
+		if err != nil {
+			logrus.Errorf("networks can not be deleted: %v", err)
+			os.Exit(1)
+			return err
+		}
+		logrus.Infof("network deleted: %s (%s)", resp.Network.Name, resp.Network.CIDR)
+
+		return nil
+	},
+}

+ 326 - 0
cmd/ovpm/user.go

@@ -0,0 +1,326 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"os"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/cad/ovpm"
+	"github.com/cad/ovpm/pb"
+	"github.com/olekukonko/tablewriter"
+	"github.com/urfave/cli"
+)
+
+var userGenconfigCommand = cli.Command{
+	Name:  "genconfig",
+	Usage: "Generate client config for the user. (.ovpn file)",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "user, u",
+			Usage: "username of the vpn user",
+		},
+		cli.StringFlag{
+			Name:  "out, o",
+			Usage: ".ovpn file output path",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "user:export-config"
+		username := c.String("user")
+		output := c.String("out")
+
+		if username == "" {
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+		if output == "" {
+			output = username + ".ovpn"
+		}
+
+		//conn := getConn(c.String("port"))
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		userSvc := pb.NewUserServiceClient(conn)
+		pb.NewVPNServiceClient(conn)
+
+		res, err := userSvc.GenConfig(context.Background(), &pb.UserGenConfigRequest{Username: username})
+		if err != nil {
+			logrus.Errorf("user config can not be exported %s: %v", username, err)
+			return err
+		}
+		emitToFile(output, res.ClientConfig, 0)
+		logrus.Infof("exported to %s", output)
+		return nil
+	},
+}
+
+var userRenewCommand = cli.Command{
+	Name:  "renew",
+	Usage: "Renew VPN user certificates.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "user, u",
+			Usage: "username of the vpn user",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "user:renew"
+		username := c.String("user")
+
+		if username == "" {
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+
+		//conn := getConn(c.String("port"))
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		userSvc := pb.NewUserServiceClient(conn)
+		pb.NewVPNServiceClient(conn)
+
+		_, err := userSvc.Renew(context.Background(), &pb.UserRenewRequest{Username: username})
+		if err != nil {
+			logrus.Errorf("can't renew user cert '%s': %v", username, err)
+			os.Exit(1)
+			return err
+		}
+		logrus.Infof("user cert renewed: '%s'", username)
+		return nil
+	},
+}
+
+var userDeleteCommand = cli.Command{
+	Name:  "delete",
+	Usage: "Delete a VPN user.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "user, u",
+			Usage: "username of the vpn user",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "user:delete"
+		username := c.String("user")
+
+		if username == "" {
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+
+		//conn := getConn(c.String("port"))
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		userSvc := pb.NewUserServiceClient(conn)
+
+		_, err := userSvc.Delete(context.Background(), &pb.UserDeleteRequest{Username: username})
+		if err != nil {
+			logrus.Errorf("user can not be deleted '%s': %v", username, err)
+			os.Exit(1)
+			return err
+		}
+		logrus.Infof("user deleted: %s", username)
+		return nil
+	},
+}
+
+var userUpdateCommand = cli.Command{
+	Name:  "update",
+	Usage: "Update a VPN user.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "username, u",
+			Usage: "username of the vpn user to update",
+		},
+		cli.StringFlag{
+			Name:  "password, p",
+			Usage: "new password for the vpn user",
+		},
+		cli.BoolFlag{
+			Name:  "no-gw",
+			Usage: "don't push vpn server as default gateway for this user",
+		},
+		cli.BoolFlag{
+			Name:  "gw",
+			Usage: "push vpn server as default gateway for this user",
+		},
+		cli.StringFlag{
+			Name:  "static",
+			Usage: "ip address for the vpn user",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "user:update"
+		username := c.String("username")
+		password := c.String("password")
+		nogw := c.Bool("no-gw")
+		gw := c.Bool("gw")
+		static := c.String("static")
+
+		if username == "" {
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+
+		if !(password != "" || gw || nogw) {
+			fmt.Println("nothing is updated!")
+			fmt.Println()
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+
+		var hostid uint32
+		if static != "" {
+			h := ovpm.IP2HostID(net.ParseIP(static).To4())
+			if h == 0 {
+				fmt.Println("--static flag takes a valid ipv4 address")
+				fmt.Println()
+				fmt.Println(cli.ShowSubcommandHelp(c))
+				os.Exit(1)
+			}
+
+			hostid = h
+		}
+
+		var gwPref pb.UserUpdateRequest_GWPref
+
+		switch {
+		case gw && !nogw:
+			gwPref = pb.UserUpdateRequest_GW
+		case !gw && nogw:
+			gwPref = pb.UserUpdateRequest_NOGW
+		case gw && nogw:
+			// Ambigius.
+			fmt.Println("you can't use --gw together with --no-gw")
+			fmt.Println()
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		default:
+			gwPref = pb.UserUpdateRequest_NOPREF
+
+		}
+
+		//conn := getConn(c.String("port"))
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		userSvc := pb.NewUserServiceClient(conn)
+
+		response, err := userSvc.Update(context.Background(), &pb.UserUpdateRequest{
+			Username: username,
+			Password: password,
+			Gwpref:   gwPref,
+			HostID:   hostid,
+		})
+
+		if err != nil {
+			logrus.Errorf("user can not be updated '%s': %v", username, err)
+			os.Exit(1)
+			return err
+		}
+		logrus.Infof("user updated: %s", response.Users[0].Username)
+		return nil
+	},
+}
+
+var userCreateCommand = cli.Command{
+	Name:  "create",
+	Usage: "Create a VPN user.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "username, u",
+			Usage: "username for the vpn user",
+		},
+		cli.StringFlag{
+			Name:  "password, p",
+			Usage: "password for the vpn user",
+		},
+		cli.BoolFlag{
+			Name:  "no-gw",
+			Usage: "don't push vpn server as default gateway for this user",
+		},
+		cli.StringFlag{
+			Name:  "static",
+			Usage: "ip address for the vpn user",
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "user:create"
+		username := c.String("username")
+		password := c.String("password")
+		noGW := c.Bool("no-gw")
+		static := c.String("static")
+
+		if username == "" || password == "" {
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+		}
+
+		var hostid uint32
+		if static != "" {
+			h := ovpm.IP2HostID(net.ParseIP(static).To4())
+			if h == 0 {
+				fmt.Println("--static flag takes a valid ipv4 address")
+				fmt.Println()
+				fmt.Println(cli.ShowSubcommandHelp(c))
+				os.Exit(1)
+			}
+
+			hostid = h
+		}
+
+		//conn := getConn(c.String("port"))
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		userSvc := pb.NewUserServiceClient(conn)
+
+		response, err := userSvc.Create(context.Background(), &pb.UserCreateRequest{Username: username, Password: password, NoGW: noGW, HostID: hostid})
+		if err != nil {
+			logrus.Errorf("user can not be created '%s': %v", username, err)
+			os.Exit(1)
+			return err
+		}
+		logrus.Infof("user created: %s", response.Users[0].Username)
+		return nil
+	},
+}
+
+var userListCommand = cli.Command{
+	Name:  "list",
+	Usage: "List VPN users.",
+	Action: func(c *cli.Context) error {
+		action = "user:list"
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		userSvc := pb.NewUserServiceClient(conn)
+		vpnSvc := pb.NewVPNServiceClient(conn)
+
+		server, err := vpnSvc.Status(context.Background(), &pb.VPNStatusRequest{})
+		if err != nil {
+			logrus.Errorf("can not get server status: %v", err)
+			os.Exit(1)
+			return err
+		}
+
+		resp, err := userSvc.List(context.Background(), &pb.UserListRequest{})
+		if err != nil {
+			logrus.Errorf("users can not be fetched: %v", err)
+			os.Exit(1)
+			return err
+		}
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"#", "username", "ip", "created at", "valid crt", "no gw"})
+		//table.SetBorder(false)
+		for i, user := range resp.Users {
+			static := ""
+			if user.HostID != 0 {
+				static = "s"
+			}
+			data := []string{fmt.Sprintf("%v", i+1), user.Username, fmt.Sprintf("%s %s", user.IPNet, static), user.CreatedAt, fmt.Sprintf("%t", user.ServerSerialNumber == server.SerialNumber), fmt.Sprintf("%t", user.NoGW)}
+			table.Append(data)
+		}
+		table.Render()
+
+		return nil
+	},
+}

+ 0 - 1
cmd/ovpm/utils.go

@@ -33,5 +33,4 @@ func getConn(port string) *grpc.ClientConn {
 		logrus.Fatalf("fail to dial: %v", err)
 	}
 	return conn
-
 }

+ 105 - 0
cmd/ovpm/vpn.go

@@ -0,0 +1,105 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/cad/ovpm"
+	"github.com/cad/ovpm/pb"
+	"github.com/olekukonko/tablewriter"
+	"github.com/urfave/cli"
+)
+
+var vpnStatusCommand = cli.Command{
+	Name:  "status",
+	Usage: "Show VPN status.",
+	Action: func(c *cli.Context) error {
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		vpnSvc := pb.NewVPNServiceClient(conn)
+
+		res, err := vpnSvc.Status(context.Background(), &pb.VPNStatusRequest{})
+		if err != nil {
+			os.Exit(1)
+			return err
+		}
+
+		table := tablewriter.NewWriter(os.Stdout)
+		table.SetHeader([]string{"attribute", "value"})
+		table.Append([]string{"Name", res.Name})
+		table.Append([]string{"Hostname", res.Hostname})
+		table.Append([]string{"Port", res.Port})
+		table.Append([]string{"Network", res.Net})
+		table.Append([]string{"Netmask", res.Mask})
+		table.Append([]string{"Created At", res.CreatedAt})
+		table.Render()
+
+		return nil
+	},
+}
+
+var vpnInitCommand = cli.Command{
+	Name:  "init",
+	Usage: "Initialize VPN server.",
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "hostname, s",
+			Usage: "ip address or FQDN of the vpn server",
+		},
+		cli.StringFlag{
+			Name:  "port, p",
+			Usage: "port number of the vpn server",
+			Value: ovpm.DefaultVPNPort,
+		},
+	},
+	Action: func(c *cli.Context) error {
+		action = "vpn:init"
+		hostname := c.String("hostname")
+		if hostname == "" {
+			logrus.Errorf("'hostname' is needed")
+			fmt.Println(cli.ShowSubcommandHelp(c))
+			os.Exit(1)
+
+		}
+
+		port := c.String("port")
+		if port == "" {
+			port = ovpm.DefaultVPNPort
+		}
+
+		conn := getConn(c.GlobalString("daemon-port"))
+		defer conn.Close()
+		vpnSvc := pb.NewVPNServiceClient(conn)
+
+		var response string
+		for {
+			fmt.Println("This operation will cause invalidation of existing user certificates.")
+			fmt.Println("After this opeartion, new client config files (.ovpn) should be generated for each existing user.")
+			fmt.Println()
+			fmt.Println("Are you sure ? (y/N)")
+			_, err := fmt.Scanln(&response)
+			if err != nil {
+				logrus.Fatal(err)
+				os.Exit(1)
+				return err
+			}
+			okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
+			nokayResponses := []string{"n", "N", "no", "No", "NO"}
+			if stringInSlice(response, okayResponses) {
+				if _, err := vpnSvc.Init(context.Background(), &pb.VPNInitRequest{Hostname: hostname, Port: port}); err != nil {
+					logrus.Errorf("server can not be initialized: %v", err)
+					os.Exit(1)
+					return err
+				}
+				logrus.Info("ovpm server initialized")
+				break
+			} else if stringInSlice(response, nokayResponses) {
+				return fmt.Errorf("user decided to cancel")
+			}
+		}
+
+		return nil
+	},
+}

+ 1 - 0
cmd/ovpmd/main.go

@@ -91,6 +91,7 @@ func newServer(port string) *server {
 	s := grpc.NewServer()
 	pb.RegisterUserServiceServer(s, &api.UserService{})
 	pb.RegisterVPNServiceServer(s, &api.VPNService{})
+	pb.RegisterNetworkServiceServer(s, &api.NetworkService{})
 	return &server{lis: lis, grpcServer: s, signal: sigs, done: done, port: port}
 }
 

+ 2 - 2
net.go

@@ -19,7 +19,7 @@ type DBNetwork struct {
 	ServerID uint
 	Server   DBServer
 
-	Name string
+	Name string `gorm:"unique_index"`
 	CIDR string
 }
 
@@ -109,7 +109,7 @@ func (n *DBNetwork) GetName() string {
 
 // GetCIDR returns network's CIDR.
 func (n *DBNetwork) GetCIDR() string {
-	return n.Name
+	return n.CIDR
 }
 
 // GetCreatedAt returns network's name.