1
0
Эх сурвалжийг харах

commit 8360e6265d3b4ad6f1cbf0f176a47aaf40322ff0
Author: Mustafa Arici <mustafa@arici.io>
Date: Wed Jul 26 05:43:51 2017 +0300

feat: remote control protocol

In order to seperate cli with the core of the system we had to come up
with a remote control system. gRPC is used because of its performance
and generic approach.

Mustafa Arici 8 жил өмнө
parent
commit
333665db60
16 өөрчлөгдсөн 1471 нэмэгдсэн , 188 устгасан
  1. 5 5
      bindata.go
  2. 103 49
      cmd/ovpm/main.go
  3. 36 0
      cmd/ovpm/utils.go
  4. 68 0
      cmd/ovpmd/main.go
  5. 1 0
      conf.go
  6. 7 53
      db.go
  7. 441 0
      pb/user.pb.go
  8. 47 0
      pb/user.proto
  9. 327 0
      pb/vpn.pb.go
  10. 30 0
      pb/vpn.proto
  11. 45 3
      pki.go
  12. 143 0
      rpcapi.go
  13. 0 2
      template/client.ovpn.tmpl
  14. 1 0
      template/server.conf.tmpl
  15. 99 43
      user.go
  16. 118 33
      vpn.go

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 5 - 5
bindata.go


+ 103 - 49
cmd/ovpm/main.go

@@ -2,13 +2,15 @@
 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"
-	"os"
-	"time"
 )
 
 var action string
@@ -23,6 +25,10 @@ func main() {
 			Name:  "verbose",
 			Usage: "verbose output",
 		},
+		cli.StringFlag{
+			Name:  "daemon-port",
+			Usage: "port number for OVPM daemon to call",
+		},
 	}
 	app.Before = func(c *cli.Context) error {
 		logrus.SetLevel(logrus.WarnLevel)
@@ -30,6 +36,7 @@ func main() {
 			logrus.SetLevel(logrus.DebugLevel)
 		}
 		return nil
+
 	}
 	app.Commands = []cli.Command{
 		{
@@ -41,12 +48,20 @@ func main() {
 					Usage: "List VPN users.",
 					Action: func(c *cli.Context) error {
 						action = "user:list"
-						server, err := ovpm.GetServerInstance()
+						//conn := getConn(c.String("port"))
+						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
 						}
-						users, err := ovpm.GetAllUsers()
+
+						resp, err := userSvc.List(context.Background(), &pb.UserListRequest{})
 						if err != nil {
 							logrus.Errorf("users can not be fetched: %v", err)
 							os.Exit(1)
@@ -55,8 +70,8 @@ func main() {
 						table := tablewriter.NewWriter(os.Stdout)
 						table.SetHeader([]string{"#", "username", "created at", "valid crt"})
 						//table.SetBorder(false)
-						for i, user := range users {
-							data := []string{fmt.Sprintf("%v", i+1), user.Username, user.CreatedAt.Format(time.UnixDate), fmt.Sprintf("%t", server.CheckSerial(user.ServerSerialNumber))}
+						for i, user := range resp.Users {
+							data := []string{fmt.Sprintf("%v", i+1), user.Username, user.CreatedAt, fmt.Sprintf("%t", user.ServerSerialNumber == server.SerialNumber)}
 							table.Append(data)
 						}
 						table.Render()
@@ -86,13 +101,19 @@ func main() {
 							fmt.Println(cli.ShowSubcommandHelp(c))
 							os.Exit(1)
 						}
-						user, err := ovpm.CreateUser(username, password)
+
+						//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})
 						if err != nil {
 							logrus.Errorf("user can not be created '%s': %v", username, err)
 							os.Exit(1)
 							return err
 						}
-						logrus.Infof("user created: %s", user.Username)
+						logrus.Infof("user created: %s", response.Users[0].Username)
 						return nil
 					},
 				},
@@ -113,7 +134,13 @@ func main() {
 							fmt.Println(cli.ShowSubcommandHelp(c))
 							os.Exit(1)
 						}
-						err := ovpm.DeleteUser(username)
+
+						//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)
@@ -140,7 +167,14 @@ func main() {
 							fmt.Println(cli.ShowSubcommandHelp(c))
 							os.Exit(1)
 						}
-						err := ovpm.SignUser(username)
+
+						//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)
@@ -175,7 +209,16 @@ func main() {
 						if output == "" {
 							output = username + ".ovpn"
 						}
-						err := ovpm.DumpUserOVPNConf(username, output)
+
+						//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})
+						emitToFile(output, res.ClientConfig, 0)
+
 						if err != nil {
 							logrus.Errorf("user config can not be exported %s: %v", username, err)
 							return err
@@ -208,7 +251,11 @@ func main() {
 					Name:  "status",
 					Usage: "Show VPN status.",
 					Action: func(c *cli.Context) error {
-						server, err := ovpm.GetServerInstance()
+						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
@@ -216,12 +263,12 @@ func main() {
 
 						table := tablewriter.NewWriter(os.Stdout)
 						table.SetHeader([]string{"attribute", "value"})
-						table.Append([]string{"Name", server.Name})
-						table.Append([]string{"Hostname", server.Hostname})
-						table.Append([]string{"Port", server.Port})
-						table.Append([]string{"Network", server.Net})
-						table.Append([]string{"Netmask", server.Mask})
-						table.Append([]string{"Created At", server.CreatedAt.Format(time.UnixDate)})
+						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
@@ -243,46 +290,48 @@ func main() {
 					},
 					Action: func(c *cli.Context) error {
 						action = "vpn:init"
-						if c.String("hostname") == "" {
+						hostname := c.String("hostname")
+						if hostname == "" {
 							logrus.Errorf("'hostname' is needed")
 							fmt.Println(cli.ShowSubcommandHelp(c))
 							os.Exit(1)
 
 						}
 
-						if ovpm.CheckBootstrapped() {
-							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)
+						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
 								}
-								okayResponses := []string{"y", "Y", "yes", "Yes", "YES"}
-								nokayResponses := []string{"n", "N", "no", "No", "NO"}
-								if stringInSlice(response, okayResponses) {
-									if err := ovpm.DeleteServer("default"); err != nil {
-										logrus.Errorf("server can not be deleted: %v", err)
-										os.Exit(1)
-										return err
-									}
-
-									break
-								} else if stringInSlice(response, nokayResponses) {
-									return fmt.Errorf("user decided to cancel")
-								}
-							}
 
-						}
-						if err := ovpm.CreateServer("default", c.String("hostname"), c.String("port")); err != nil {
-							logrus.Errorf("server can not be created: %v", err)
-							fmt.Println(cli.ShowSubcommandHelp(c))
-							os.Exit(1)
+								break
+							} else if stringInSlice(response, nokayResponses) {
+								return fmt.Errorf("user decided to cancel")
+							}
 						}
 
 						return nil
@@ -293,7 +342,12 @@ func main() {
 					Usage: "Apply pending changes.",
 					Action: func(c *cli.Context) error {
 						action = "apply"
-						if err := ovpm.Emit(); err != nil {
+
+						conn := getConn(c.GlobalString("daemon-port"))
+						defer conn.Close()
+						vpnSvc := pb.NewVPNServiceClient(conn)
+
+						if _, err := vpnSvc.Apply(context.Background(), &pb.VPNApplyRequest{}); err != nil {
 							logrus.Errorf("can not apply configuration: %v", err)
 							os.Exit(1)
 							return err

+ 36 - 0
cmd/ovpm/utils.go

@@ -0,0 +1,36 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"os"
+
+	"google.golang.org/grpc"
+)
+
+func emitToFile(filePath, content string, mode uint) error {
+	file, err := os.Create(filePath)
+	if err != nil {
+		return fmt.Errorf("Cannot create file %s: %v", filePath, err)
+
+	}
+	if mode != 0 {
+		file.Chmod(os.FileMode(mode))
+	}
+	defer file.Close()
+	fmt.Fprintf(file, content)
+	return nil
+}
+
+func getConn(port string) *grpc.ClientConn {
+	if port == "" {
+		port = "9090"
+	}
+
+	conn, err := grpc.Dial(fmt.Sprintf(":%s", port), grpc.WithInsecure())
+	if err != nil {
+		log.Fatalf("fail to dial: %v", err)
+	}
+	return conn
+
+}

+ 68 - 0
cmd/ovpmd/main.go

@@ -0,0 +1,68 @@
+//go:generate go-bindata template/
+package main
+
+import (
+	"fmt"
+	"net"
+	"os"
+
+	"google.golang.org/grpc"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/cad/ovpm"
+	"github.com/cad/ovpm/pb"
+	"github.com/urfave/cli"
+)
+
+var action string
+
+func main() {
+	app := cli.NewApp()
+	app.Name = "ovpmd"
+	app.Usage = "OpenVPN Manager Daemon"
+	app.Version = ovpm.Version
+	app.Flags = []cli.Flag{
+		cli.BoolFlag{
+			Name:  "verbose",
+			Usage: "verbose output",
+		},
+		cli.StringFlag{
+			Name:  "port",
+			Usage: "port number for daemon to listen on",
+		},
+	}
+	app.Before = func(c *cli.Context) error {
+		logrus.SetLevel(logrus.InfoLevel)
+		if c.GlobalBool("verbose") {
+			logrus.SetLevel(logrus.DebugLevel)
+		}
+		return nil
+	}
+	app.Action = func(c *cli.Context) error {
+		port := c.String("port")
+		if port == "" {
+			port = "9090"
+		}
+		lis, err := net.Listen("tcp", fmt.Sprintf(":%s", port))
+		if err != nil {
+			logrus.Fatalf("could not listen to port %s: %v", port, err)
+		}
+		s := grpc.NewServer()
+		pb.RegisterUserServiceServer(s, &ovpm.UserSvc{})
+		pb.RegisterVPNServiceServer(s, &ovpm.VPNSvc{})
+		logrus.Infof("OVPM is running :%s ...", port)
+		s.Serve(lis)
+		return nil
+	}
+	app.Run(os.Args)
+	ovpm.CloseDB()
+}
+
+func stringInSlice(a string, list []string) bool {
+	for _, b := range list {
+		if b == a {
+			return true
+		}
+	}
+	return false
+}

+ 1 - 0
conf.go

@@ -16,6 +16,7 @@ const (
 	DefaultCACertPath   = varBasePath + "ca.crt"
 	DefaultCAKeyPath    = varBasePath + "ca.key"
 	DefaultDHParamsPath = varBasePath + "dh4096.pem"
+	DefaultCRLPath      = varBasePath + "crl.pem"
 
 	CrtExpireYears = 10
 	CrtKeyLength   = 2024

+ 7 - 53
db.go

@@ -3,61 +3,13 @@ package ovpm
 import (
 	"github.com/Sirupsen/logrus"
 	"github.com/jinzhu/gorm"
+
+	// We blank import sqlite here because gorm needs it.
 	_ "github.com/jinzhu/gorm/dialects/sqlite"
 )
 
 var db *gorm.DB
 
-// User is database model for VPN users.
-type User struct {
-	gorm.Model
-	ServerID           uint
-	Server             Server
-	ServerSerialNumber string
-
-	Username string `gorm:"unique_index"`
-	Password string
-	Cert     string
-	Key      string
-}
-
-func (u *User) setPassword(newPassword string) error {
-	// TODO(cad): Use a proper password hashing algorithm here.
-	u.Password = newPassword
-	return nil
-}
-
-// Network is database model for external networks on the VPN server.
-type Network struct {
-	gorm.Model
-	ServerID uint
-	Server   Server
-
-	Name        string
-	NetworkCIDR string
-}
-
-// Server is database model for storing VPN server related stuff.
-type Server struct {
-	gorm.Model
-	Name         string `gorm:"unique_index"` // Server name.
-	SerialNumber string
-
-	Hostname string // Server's ip address or FQDN
-	Port     string // Server's listening port
-	Cert     string // Server RSA certificate.
-	Key      string // Server RSA private key.
-	CACert   string // Root CA RSA certificate.
-	CAKey    string // Root CA RSA key.
-	Net      string // VPN network.
-	Mask     string // VPN network mask.
-}
-
-// CheckSerial takes a serial number and checks it against the current server's serial number.
-func (s *Server) CheckSerial(serialNo string) bool {
-	return serialNo == s.SerialNumber
-}
-
 // CloseDB closes the database.
 func CloseDB() {
 	db.Close()
@@ -70,7 +22,9 @@ func init() {
 		logrus.Fatalf("couldn't open sqlite database %s: %v", DefaultDBPath, err)
 	}
 
-	db.AutoMigrate(&User{})
-	db.AutoMigrate(&Network{})
-	db.AutoMigrate(&Server{})
+	db.AutoMigrate(&DBUser{})
+	db.AutoMigrate(&DBNetwork{})
+	db.AutoMigrate(&DBServer{})
+	db.AutoMigrate(&DBRevoked{})
+
 }

+ 441 - 0
pb/user.pb.go

@@ -0,0 +1,441 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: user.proto
+
+/*
+Package pb is a generated protocol buffer package.
+
+It is generated from these files:
+	user.proto
+	vpn.proto
+
+It has these top-level messages:
+	UserListRequest
+	UserCreateRequest
+	UserDeleteRequest
+	UserRenewRequest
+	UserGenConfigRequest
+	UserResponse
+	UserGenConfigResponse
+	VPNStatusRequest
+	VPNInitRequest
+	VPNApplyRequest
+	VPNStatusResponse
+	VPNInitResponse
+	VPNApplyResponse
+*/
+package pb
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+import (
+	context "golang.org/x/net/context"
+	grpc "google.golang.org/grpc"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type UserListRequest struct {
+}
+
+func (m *UserListRequest) Reset()                    { *m = UserListRequest{} }
+func (m *UserListRequest) String() string            { return proto.CompactTextString(m) }
+func (*UserListRequest) ProtoMessage()               {}
+func (*UserListRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+type UserCreateRequest struct {
+	Username string `protobuf:"bytes,1,opt,name=Username" json:"Username,omitempty"`
+	Password string `protobuf:"bytes,2,opt,name=Password" json:"Password,omitempty"`
+}
+
+func (m *UserCreateRequest) Reset()                    { *m = UserCreateRequest{} }
+func (m *UserCreateRequest) String() string            { return proto.CompactTextString(m) }
+func (*UserCreateRequest) ProtoMessage()               {}
+func (*UserCreateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+func (m *UserCreateRequest) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+func (m *UserCreateRequest) GetPassword() string {
+	if m != nil {
+		return m.Password
+	}
+	return ""
+}
+
+type UserDeleteRequest struct {
+	Username string `protobuf:"bytes,1,opt,name=Username" json:"Username,omitempty"`
+}
+
+func (m *UserDeleteRequest) Reset()                    { *m = UserDeleteRequest{} }
+func (m *UserDeleteRequest) String() string            { return proto.CompactTextString(m) }
+func (*UserDeleteRequest) ProtoMessage()               {}
+func (*UserDeleteRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
+
+func (m *UserDeleteRequest) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+type UserRenewRequest struct {
+	Username string `protobuf:"bytes,1,opt,name=Username" json:"Username,omitempty"`
+}
+
+func (m *UserRenewRequest) Reset()                    { *m = UserRenewRequest{} }
+func (m *UserRenewRequest) String() string            { return proto.CompactTextString(m) }
+func (*UserRenewRequest) ProtoMessage()               {}
+func (*UserRenewRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
+
+func (m *UserRenewRequest) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+type UserGenConfigRequest struct {
+	Username string `protobuf:"bytes,1,opt,name=Username" json:"Username,omitempty"`
+}
+
+func (m *UserGenConfigRequest) Reset()                    { *m = UserGenConfigRequest{} }
+func (m *UserGenConfigRequest) String() string            { return proto.CompactTextString(m) }
+func (*UserGenConfigRequest) ProtoMessage()               {}
+func (*UserGenConfigRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
+
+func (m *UserGenConfigRequest) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+type UserResponse struct {
+	Users []*UserResponse_User `protobuf:"bytes,1,rep,name=users" json:"users,omitempty"`
+}
+
+func (m *UserResponse) Reset()                    { *m = UserResponse{} }
+func (m *UserResponse) String() string            { return proto.CompactTextString(m) }
+func (*UserResponse) ProtoMessage()               {}
+func (*UserResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
+
+func (m *UserResponse) GetUsers() []*UserResponse_User {
+	if m != nil {
+		return m.Users
+	}
+	return nil
+}
+
+type UserResponse_User struct {
+	Username           string `protobuf:"bytes,1,opt,name=Username" json:"Username,omitempty"`
+	ServerSerialNumber string `protobuf:"bytes,2,opt,name=ServerSerialNumber" json:"ServerSerialNumber,omitempty"`
+	Cert               string `protobuf:"bytes,3,opt,name=Cert" json:"Cert,omitempty"`
+	CreatedAt          string `protobuf:"bytes,4,opt,name=CreatedAt" json:"CreatedAt,omitempty"`
+}
+
+func (m *UserResponse_User) Reset()                    { *m = UserResponse_User{} }
+func (m *UserResponse_User) String() string            { return proto.CompactTextString(m) }
+func (*UserResponse_User) ProtoMessage()               {}
+func (*UserResponse_User) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5, 0} }
+
+func (m *UserResponse_User) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+func (m *UserResponse_User) GetServerSerialNumber() string {
+	if m != nil {
+		return m.ServerSerialNumber
+	}
+	return ""
+}
+
+func (m *UserResponse_User) GetCert() string {
+	if m != nil {
+		return m.Cert
+	}
+	return ""
+}
+
+func (m *UserResponse_User) GetCreatedAt() string {
+	if m != nil {
+		return m.CreatedAt
+	}
+	return ""
+}
+
+type UserGenConfigResponse struct {
+	ClientConfig string `protobuf:"bytes,1,opt,name=ClientConfig" json:"ClientConfig,omitempty"`
+}
+
+func (m *UserGenConfigResponse) Reset()                    { *m = UserGenConfigResponse{} }
+func (m *UserGenConfigResponse) String() string            { return proto.CompactTextString(m) }
+func (*UserGenConfigResponse) ProtoMessage()               {}
+func (*UserGenConfigResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
+
+func (m *UserGenConfigResponse) GetClientConfig() string {
+	if m != nil {
+		return m.ClientConfig
+	}
+	return ""
+}
+
+func init() {
+	proto.RegisterType((*UserListRequest)(nil), "pb.UserListRequest")
+	proto.RegisterType((*UserCreateRequest)(nil), "pb.UserCreateRequest")
+	proto.RegisterType((*UserDeleteRequest)(nil), "pb.UserDeleteRequest")
+	proto.RegisterType((*UserRenewRequest)(nil), "pb.UserRenewRequest")
+	proto.RegisterType((*UserGenConfigRequest)(nil), "pb.UserGenConfigRequest")
+	proto.RegisterType((*UserResponse)(nil), "pb.UserResponse")
+	proto.RegisterType((*UserResponse_User)(nil), "pb.UserResponse.User")
+	proto.RegisterType((*UserGenConfigResponse)(nil), "pb.UserGenConfigResponse")
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// Client API for UserService service
+
+type UserServiceClient interface {
+	List(ctx context.Context, in *UserListRequest, opts ...grpc.CallOption) (*UserResponse, error)
+	Create(ctx context.Context, in *UserCreateRequest, opts ...grpc.CallOption) (*UserResponse, error)
+	Delete(ctx context.Context, in *UserDeleteRequest, opts ...grpc.CallOption) (*UserResponse, error)
+	Renew(ctx context.Context, in *UserRenewRequest, opts ...grpc.CallOption) (*UserResponse, error)
+	GenConfig(ctx context.Context, in *UserGenConfigRequest, opts ...grpc.CallOption) (*UserGenConfigResponse, error)
+}
+
+type userServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewUserServiceClient(cc *grpc.ClientConn) UserServiceClient {
+	return &userServiceClient{cc}
+}
+
+func (c *userServiceClient) List(ctx context.Context, in *UserListRequest, opts ...grpc.CallOption) (*UserResponse, error) {
+	out := new(UserResponse)
+	err := grpc.Invoke(ctx, "/pb.UserService/List", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *userServiceClient) Create(ctx context.Context, in *UserCreateRequest, opts ...grpc.CallOption) (*UserResponse, error) {
+	out := new(UserResponse)
+	err := grpc.Invoke(ctx, "/pb.UserService/Create", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *userServiceClient) Delete(ctx context.Context, in *UserDeleteRequest, opts ...grpc.CallOption) (*UserResponse, error) {
+	out := new(UserResponse)
+	err := grpc.Invoke(ctx, "/pb.UserService/Delete", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *userServiceClient) Renew(ctx context.Context, in *UserRenewRequest, opts ...grpc.CallOption) (*UserResponse, error) {
+	out := new(UserResponse)
+	err := grpc.Invoke(ctx, "/pb.UserService/Renew", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *userServiceClient) GenConfig(ctx context.Context, in *UserGenConfigRequest, opts ...grpc.CallOption) (*UserGenConfigResponse, error) {
+	out := new(UserGenConfigResponse)
+	err := grpc.Invoke(ctx, "/pb.UserService/GenConfig", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// Server API for UserService service
+
+type UserServiceServer interface {
+	List(context.Context, *UserListRequest) (*UserResponse, error)
+	Create(context.Context, *UserCreateRequest) (*UserResponse, error)
+	Delete(context.Context, *UserDeleteRequest) (*UserResponse, error)
+	Renew(context.Context, *UserRenewRequest) (*UserResponse, error)
+	GenConfig(context.Context, *UserGenConfigRequest) (*UserGenConfigResponse, error)
+}
+
+func RegisterUserServiceServer(s *grpc.Server, srv UserServiceServer) {
+	s.RegisterService(&_UserService_serviceDesc, srv)
+}
+
+func _UserService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(UserListRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(UserServiceServer).List(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.UserService/List",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(UserServiceServer).List(ctx, req.(*UserListRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _UserService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(UserCreateRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(UserServiceServer).Create(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.UserService/Create",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(UserServiceServer).Create(ctx, req.(*UserCreateRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _UserService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(UserDeleteRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(UserServiceServer).Delete(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.UserService/Delete",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(UserServiceServer).Delete(ctx, req.(*UserDeleteRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _UserService_Renew_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(UserRenewRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(UserServiceServer).Renew(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.UserService/Renew",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(UserServiceServer).Renew(ctx, req.(*UserRenewRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _UserService_GenConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(UserGenConfigRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(UserServiceServer).GenConfig(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.UserService/GenConfig",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(UserServiceServer).GenConfig(ctx, req.(*UserGenConfigRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _UserService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "pb.UserService",
+	HandlerType: (*UserServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "List",
+			Handler:    _UserService_List_Handler,
+		},
+		{
+			MethodName: "Create",
+			Handler:    _UserService_Create_Handler,
+		},
+		{
+			MethodName: "Delete",
+			Handler:    _UserService_Delete_Handler,
+		},
+		{
+			MethodName: "Renew",
+			Handler:    _UserService_Renew_Handler,
+		},
+		{
+			MethodName: "GenConfig",
+			Handler:    _UserService_GenConfig_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "user.proto",
+}
+
+func init() { proto.RegisterFile("user.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+	// 351 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0xd1, 0x4e, 0xea, 0x40,
+	0x10, 0xbd, 0x85, 0x42, 0x2e, 0x03, 0x89, 0x30, 0x42, 0xb2, 0x36, 0x3e, 0x90, 0x7d, 0x22, 0x31,
+	0x29, 0x11, 0x1e, 0x7d, 0xd2, 0x9a, 0xf8, 0xa0, 0x31, 0x06, 0xe2, 0x07, 0xb4, 0x32, 0x9a, 0x26,
+	0xb0, 0xad, 0xbb, 0x8b, 0xfc, 0x80, 0xff, 0xe1, 0xbf, 0xf8, 0x65, 0x66, 0xbb, 0x2d, 0x05, 0x52,
+	0x0d, 0x6f, 0x3b, 0xe7, 0xcc, 0x19, 0x66, 0x0e, 0xa7, 0x00, 0x6b, 0x45, 0xd2, 0x4f, 0x65, 0xa2,
+	0x13, 0xac, 0xa5, 0x11, 0xef, 0xc1, 0xc9, 0xb3, 0x22, 0xf9, 0x10, 0x2b, 0x3d, 0xa3, 0xf7, 0x35,
+	0x29, 0xcd, 0xef, 0xa1, 0x67, 0xa0, 0x40, 0x52, 0xa8, 0x29, 0x07, 0xd1, 0x83, 0xff, 0x06, 0x14,
+	0xe1, 0x8a, 0x98, 0x33, 0x74, 0x46, 0xad, 0xd9, 0xb6, 0x36, 0xdc, 0x53, 0xa8, 0xd4, 0x26, 0x91,
+	0x0b, 0x56, 0xb3, 0x5c, 0x51, 0xf3, 0xb1, 0x1d, 0x76, 0x4b, 0x4b, 0x3a, 0x6a, 0x18, 0xf7, 0xa1,
+	0x6b, 0xde, 0x33, 0x12, 0xb4, 0x39, 0xa6, 0x7f, 0x02, 0x7d, 0xf3, 0xbe, 0x23, 0x11, 0x24, 0xe2,
+	0x35, 0x7e, 0x3b, 0x46, 0xf3, 0xed, 0x40, 0xc7, 0xfe, 0x88, 0x4a, 0x13, 0xa1, 0x08, 0x2f, 0xa0,
+	0x61, 0x7c, 0x51, 0xcc, 0x19, 0xd6, 0x47, 0xed, 0xc9, 0xc0, 0x4f, 0x23, 0x7f, 0xb7, 0xc1, 0x16,
+	0xb6, 0xc7, 0xfb, 0x74, 0xc0, 0x35, 0xf5, 0x9f, 0x9e, 0xf8, 0x80, 0x73, 0x92, 0x1f, 0x24, 0xe7,
+	0x24, 0xe3, 0x70, 0xf9, 0xb8, 0x5e, 0x45, 0x24, 0x73, 0x77, 0x2a, 0x18, 0x44, 0x70, 0x03, 0x92,
+	0x9a, 0xd5, 0xb3, 0x8e, 0xec, 0x8d, 0xe7, 0xd0, 0xb2, 0x7f, 0xc2, 0xe2, 0x5a, 0x33, 0x37, 0x23,
+	0x4a, 0x80, 0x5f, 0xc1, 0xe0, 0xe0, 0xf0, 0xfc, 0x18, 0x0e, 0x9d, 0x60, 0x19, 0x93, 0xd0, 0x16,
+	0xcf, 0x57, 0xdb, 0xc3, 0x26, 0x5f, 0x35, 0x68, 0x1b, 0xb5, 0xd9, 0x24, 0x7e, 0x21, 0x1c, 0x83,
+	0x6b, 0x22, 0x80, 0xa7, 0xc5, 0xe5, 0x3b, 0x81, 0xf0, 0xba, 0x87, 0x76, 0xf0, 0x7f, 0x38, 0x85,
+	0xa6, 0x5d, 0x05, 0xb7, 0x66, 0xed, 0x05, 0xe6, 0x37, 0x91, 0x0d, 0x42, 0x29, 0xda, 0x0b, 0x46,
+	0xa5, 0xe8, 0x12, 0x1a, 0x59, 0x18, 0xb0, 0x5f, 0x92, 0x65, 0x36, 0x2a, 0x25, 0x37, 0xd0, 0xda,
+	0xda, 0x82, 0xac, 0x68, 0x38, 0x8c, 0x88, 0x77, 0x56, 0xc1, 0x14, 0x33, 0xa2, 0x66, 0xf6, 0x8d,
+	0x4c, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x31, 0xff, 0x3d, 0x2c, 0x31, 0x03, 0x00, 0x00,
+}

+ 47 - 0
pb/user.proto

@@ -0,0 +1,47 @@
+syntax = "proto3";
+
+package pb;
+
+message UserListRequest {
+
+}
+
+message UserCreateRequest {
+  string Username = 1;
+  string Password = 2;
+}
+
+message UserDeleteRequest {
+  string Username = 1;
+}
+
+message UserRenewRequest {
+  string Username = 1;
+}
+
+message UserGenConfigRequest {
+  string Username = 1;
+}
+
+service UserService {
+  rpc List (UserListRequest) returns (UserResponse) {}
+  rpc Create (UserCreateRequest) returns (UserResponse) {}
+  rpc Delete (UserDeleteRequest) returns (UserResponse) {}
+  rpc Renew (UserRenewRequest) returns (UserResponse) {}
+  rpc GenConfig (UserGenConfigRequest) returns (UserGenConfigResponse) {}
+}
+
+message UserResponse {
+  message User {
+    string Username = 1;
+    string ServerSerialNumber = 2;
+    string Cert = 3;
+    string CreatedAt = 4;
+  }
+
+  repeated User users = 1;
+}
+
+message UserGenConfigResponse {
+  string ClientConfig = 1;
+}

+ 327 - 0
pb/vpn.pb.go

@@ -0,0 +1,327 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: vpn.proto
+
+package pb
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+import (
+	context "golang.org/x/net/context"
+	grpc "google.golang.org/grpc"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+type VPNStatusRequest struct {
+}
+
+func (m *VPNStatusRequest) Reset()                    { *m = VPNStatusRequest{} }
+func (m *VPNStatusRequest) String() string            { return proto.CompactTextString(m) }
+func (*VPNStatusRequest) ProtoMessage()               {}
+func (*VPNStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
+
+type VPNInitRequest struct {
+	Hostname string `protobuf:"bytes,1,opt,name=Hostname" json:"Hostname,omitempty"`
+	Port     string `protobuf:"bytes,2,opt,name=Port" json:"Port,omitempty"`
+}
+
+func (m *VPNInitRequest) Reset()                    { *m = VPNInitRequest{} }
+func (m *VPNInitRequest) String() string            { return proto.CompactTextString(m) }
+func (*VPNInitRequest) ProtoMessage()               {}
+func (*VPNInitRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} }
+
+func (m *VPNInitRequest) GetHostname() string {
+	if m != nil {
+		return m.Hostname
+	}
+	return ""
+}
+
+func (m *VPNInitRequest) GetPort() string {
+	if m != nil {
+		return m.Port
+	}
+	return ""
+}
+
+type VPNApplyRequest struct {
+}
+
+func (m *VPNApplyRequest) Reset()                    { *m = VPNApplyRequest{} }
+func (m *VPNApplyRequest) String() string            { return proto.CompactTextString(m) }
+func (*VPNApplyRequest) ProtoMessage()               {}
+func (*VPNApplyRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{2} }
+
+type VPNStatusResponse struct {
+	Name         string `protobuf:"bytes,1,opt,name=Name" json:"Name,omitempty"`
+	SerialNumber string `protobuf:"bytes,2,opt,name=SerialNumber" json:"SerialNumber,omitempty"`
+	Hostname     string `protobuf:"bytes,3,opt,name=Hostname" json:"Hostname,omitempty"`
+	Port         string `protobuf:"bytes,4,opt,name=Port" json:"Port,omitempty"`
+	Cert         string `protobuf:"bytes,5,opt,name=Cert" json:"Cert,omitempty"`
+	CACert       string `protobuf:"bytes,6,opt,name=CACert" json:"CACert,omitempty"`
+	Net          string `protobuf:"bytes,7,opt,name=Net" json:"Net,omitempty"`
+	Mask         string `protobuf:"bytes,8,opt,name=Mask" json:"Mask,omitempty"`
+	CreatedAt    string `protobuf:"bytes,9,opt,name=CreatedAt" json:"CreatedAt,omitempty"`
+}
+
+func (m *VPNStatusResponse) Reset()                    { *m = VPNStatusResponse{} }
+func (m *VPNStatusResponse) String() string            { return proto.CompactTextString(m) }
+func (*VPNStatusResponse) ProtoMessage()               {}
+func (*VPNStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{3} }
+
+func (m *VPNStatusResponse) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *VPNStatusResponse) GetSerialNumber() string {
+	if m != nil {
+		return m.SerialNumber
+	}
+	return ""
+}
+
+func (m *VPNStatusResponse) GetHostname() string {
+	if m != nil {
+		return m.Hostname
+	}
+	return ""
+}
+
+func (m *VPNStatusResponse) GetPort() string {
+	if m != nil {
+		return m.Port
+	}
+	return ""
+}
+
+func (m *VPNStatusResponse) GetCert() string {
+	if m != nil {
+		return m.Cert
+	}
+	return ""
+}
+
+func (m *VPNStatusResponse) GetCACert() string {
+	if m != nil {
+		return m.CACert
+	}
+	return ""
+}
+
+func (m *VPNStatusResponse) GetNet() string {
+	if m != nil {
+		return m.Net
+	}
+	return ""
+}
+
+func (m *VPNStatusResponse) GetMask() string {
+	if m != nil {
+		return m.Mask
+	}
+	return ""
+}
+
+func (m *VPNStatusResponse) GetCreatedAt() string {
+	if m != nil {
+		return m.CreatedAt
+	}
+	return ""
+}
+
+type VPNInitResponse struct {
+}
+
+func (m *VPNInitResponse) Reset()                    { *m = VPNInitResponse{} }
+func (m *VPNInitResponse) String() string            { return proto.CompactTextString(m) }
+func (*VPNInitResponse) ProtoMessage()               {}
+func (*VPNInitResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{4} }
+
+type VPNApplyResponse struct {
+}
+
+func (m *VPNApplyResponse) Reset()                    { *m = VPNApplyResponse{} }
+func (m *VPNApplyResponse) String() string            { return proto.CompactTextString(m) }
+func (*VPNApplyResponse) ProtoMessage()               {}
+func (*VPNApplyResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{5} }
+
+func init() {
+	proto.RegisterType((*VPNStatusRequest)(nil), "pb.VPNStatusRequest")
+	proto.RegisterType((*VPNInitRequest)(nil), "pb.VPNInitRequest")
+	proto.RegisterType((*VPNApplyRequest)(nil), "pb.VPNApplyRequest")
+	proto.RegisterType((*VPNStatusResponse)(nil), "pb.VPNStatusResponse")
+	proto.RegisterType((*VPNInitResponse)(nil), "pb.VPNInitResponse")
+	proto.RegisterType((*VPNApplyResponse)(nil), "pb.VPNApplyResponse")
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// Client API for VPNService service
+
+type VPNServiceClient interface {
+	Status(ctx context.Context, in *VPNStatusRequest, opts ...grpc.CallOption) (*VPNStatusResponse, error)
+	Init(ctx context.Context, in *VPNInitRequest, opts ...grpc.CallOption) (*VPNInitResponse, error)
+	Apply(ctx context.Context, in *VPNApplyRequest, opts ...grpc.CallOption) (*VPNApplyResponse, error)
+}
+
+type vPNServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewVPNServiceClient(cc *grpc.ClientConn) VPNServiceClient {
+	return &vPNServiceClient{cc}
+}
+
+func (c *vPNServiceClient) Status(ctx context.Context, in *VPNStatusRequest, opts ...grpc.CallOption) (*VPNStatusResponse, error) {
+	out := new(VPNStatusResponse)
+	err := grpc.Invoke(ctx, "/pb.VPNService/Status", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *vPNServiceClient) Init(ctx context.Context, in *VPNInitRequest, opts ...grpc.CallOption) (*VPNInitResponse, error) {
+	out := new(VPNInitResponse)
+	err := grpc.Invoke(ctx, "/pb.VPNService/Init", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *vPNServiceClient) Apply(ctx context.Context, in *VPNApplyRequest, opts ...grpc.CallOption) (*VPNApplyResponse, error) {
+	out := new(VPNApplyResponse)
+	err := grpc.Invoke(ctx, "/pb.VPNService/Apply", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// Server API for VPNService service
+
+type VPNServiceServer interface {
+	Status(context.Context, *VPNStatusRequest) (*VPNStatusResponse, error)
+	Init(context.Context, *VPNInitRequest) (*VPNInitResponse, error)
+	Apply(context.Context, *VPNApplyRequest) (*VPNApplyResponse, error)
+}
+
+func RegisterVPNServiceServer(s *grpc.Server, srv VPNServiceServer) {
+	s.RegisterService(&_VPNService_serviceDesc, srv)
+}
+
+func _VPNService_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(VPNStatusRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(VPNServiceServer).Status(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.VPNService/Status",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(VPNServiceServer).Status(ctx, req.(*VPNStatusRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _VPNService_Init_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(VPNInitRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(VPNServiceServer).Init(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.VPNService/Init",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(VPNServiceServer).Init(ctx, req.(*VPNInitRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _VPNService_Apply_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(VPNApplyRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(VPNServiceServer).Apply(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/pb.VPNService/Apply",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(VPNServiceServer).Apply(ctx, req.(*VPNApplyRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _VPNService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "pb.VPNService",
+	HandlerType: (*VPNServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Status",
+			Handler:    _VPNService_Status_Handler,
+		},
+		{
+			MethodName: "Init",
+			Handler:    _VPNService_Init_Handler,
+		},
+		{
+			MethodName: "Apply",
+			Handler:    _VPNService_Apply_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "vpn.proto",
+}
+
+func init() { proto.RegisterFile("vpn.proto", fileDescriptor1) }
+
+var fileDescriptor1 = []byte{
+	// 312 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0xcb, 0x4e, 0xf3, 0x30,
+	0x10, 0x85, 0xff, 0xf4, 0x92, 0xbf, 0x19, 0x21, 0x68, 0x87, 0x82, 0xac, 0x88, 0x05, 0xca, 0x8a,
+	0x55, 0x25, 0x2e, 0x12, 0x5b, 0xa2, 0x6c, 0x60, 0x81, 0x15, 0xb5, 0x52, 0xf6, 0x09, 0x78, 0x11,
+	0xd1, 0x26, 0xc6, 0x76, 0x2a, 0xf1, 0x52, 0xbc, 0x18, 0x2f, 0x81, 0x7c, 0x49, 0x93, 0x54, 0x62,
+	0x77, 0xfc, 0x39, 0x67, 0x66, 0x72, 0xc6, 0x10, 0xec, 0x79, 0xb5, 0xe2, 0xa2, 0x56, 0x35, 0x8e,
+	0x78, 0x11, 0x21, 0xcc, 0xb3, 0x94, 0x6e, 0x54, 0xae, 0x1a, 0xb9, 0x66, 0x9f, 0x0d, 0x93, 0x2a,
+	0x7a, 0x82, 0xd3, 0x2c, 0xa5, 0x2f, 0x55, 0xa9, 0x1c, 0xc1, 0x10, 0x66, 0xcf, 0xb5, 0x54, 0x55,
+	0xbe, 0x63, 0xc4, 0xbb, 0xf6, 0x6e, 0x82, 0xf5, 0xe1, 0x8c, 0x08, 0x93, 0xb4, 0x16, 0x8a, 0x8c,
+	0x0c, 0x37, 0x3a, 0x5a, 0xc0, 0x59, 0x96, 0xd2, 0x98, 0xf3, 0xed, 0x57, 0x5b, 0xf4, 0xc7, 0x83,
+	0x45, 0xaf, 0x93, 0xe4, 0x75, 0x25, 0x8d, 0x99, 0x76, 0x45, 0x8d, 0xc6, 0x08, 0x4e, 0x36, 0x4c,
+	0x94, 0xf9, 0x96, 0x36, 0xbb, 0x82, 0x09, 0x57, 0x78, 0xc0, 0x06, 0x03, 0x8d, 0xff, 0x18, 0x68,
+	0xd2, 0x0d, 0xa4, 0x59, 0xc2, 0x84, 0x22, 0x53, 0xcb, 0xb4, 0xc6, 0x4b, 0xf0, 0x93, 0xd8, 0x50,
+	0xdf, 0x50, 0x77, 0xc2, 0x39, 0x8c, 0x29, 0x53, 0xe4, 0xbf, 0x81, 0x5a, 0x6a, 0xf7, 0x6b, 0x2e,
+	0x3f, 0xc8, 0xcc, 0xba, 0xb5, 0xc6, 0x2b, 0x08, 0x12, 0xc1, 0x72, 0xc5, 0xde, 0x63, 0x45, 0x02,
+	0x73, 0xd1, 0x01, 0x17, 0x80, 0x8d, 0xd0, 0xfe, 0xaa, 0x4b, 0xda, 0x65, 0x62, 0xd9, 0xdd, 0xb7,
+	0x07, 0xa0, 0x43, 0x61, 0x62, 0x5f, 0xbe, 0x31, 0x7c, 0x04, 0xdf, 0xe6, 0x83, 0xcb, 0x15, 0x2f,
+	0x56, 0xc7, 0x8b, 0x09, 0x2f, 0x8e, 0xa8, 0xab, 0xfc, 0x0f, 0x6f, 0x61, 0xa2, 0x7b, 0x21, 0xba,
+	0x0f, 0x7a, 0xbb, 0x0b, 0xcf, 0x07, 0xec, 0x60, 0x79, 0x80, 0xa9, 0x99, 0x05, 0xdb, 0xfb, 0xfe,
+	0xb6, 0xc2, 0xe5, 0x10, 0xb6, 0xae, 0xc2, 0x37, 0x2f, 0xe7, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff,
+	0x10, 0x88, 0xf5, 0x13, 0x46, 0x02, 0x00, 0x00,
+}

+ 30 - 0
pb/vpn.proto

@@ -0,0 +1,30 @@
+syntax = "proto3";
+
+package pb;
+
+message VPNStatusRequest {}
+message VPNInitRequest {
+  string Hostname = 1;
+  string Port = 2;
+}
+message VPNApplyRequest {}
+
+service VPNService {
+  rpc Status (VPNStatusRequest) returns (VPNStatusResponse) {}
+  rpc Init (VPNInitRequest) returns (VPNInitResponse) {}
+  rpc Apply (VPNApplyRequest) returns (VPNApplyResponse) {}
+}
+
+message VPNStatusResponse {
+  string Name = 1;
+  string SerialNumber = 2;
+  string Hostname = 3;
+  string Port = 4;
+  string Cert = 5;
+  string CACert = 6;
+  string Net = 7;
+  string Mask = 8;
+  string CreatedAt = 9;
+}
+message VPNInitResponse {}
+message VPNApplyResponse {}

+ 45 - 3
pki.go

@@ -76,8 +76,8 @@ func CreateCA() (*CA, error) {
 		NotAfter:              now.Add(time.Duration(24*365) * time.Hour).UTC(),
 		BasicConstraintsValid: true,
 		IsCA:     true,
-		KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
-		//ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
+		KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
+		//ExtKeyUsage: []x509.ExtKeyUsage{x509.KeyUsageCertSign, x509.ExtKeyUsageClientAuth},
 	}
 
 	// Sign the certificate authority
@@ -120,6 +120,48 @@ func getCertFromPEM(pemCert string) (*x509.Certificate, error) {
 	return cert, nil
 }
 
+func MakeCRL(revokedCertificateSerials []*big.Int) (string, error) {
+	ca, err := getCA()
+	if err != nil {
+		return "", err
+	}
+
+	caCrt, err := getCertFromPEM(ca.Cert)
+	if err != nil {
+		return "", err
+	}
+
+	block, _ := pem.Decode([]byte(ca.Key))
+	if block == nil {
+		return "", fmt.Errorf("failed to parse ca private key")
+	}
+
+	priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+	if err != nil {
+		return "", fmt.Errorf("failed to parse ca private key: %s", err)
+	}
+	var revokedCertList []pkix.RevokedCertificate
+	for _, serial := range revokedCertificateSerials {
+		revokedCert := pkix.RevokedCertificate{
+			SerialNumber:   serial,
+			RevocationTime: time.Now().UTC(),
+		}
+		revokedCertList = append(revokedCertList, revokedCert)
+	}
+	crl, err := caCrt.CreateCRL(rand.Reader, priv, revokedCertList, time.Now().UTC(), time.Now().Add(365*24*60*time.Minute).UTC())
+	if err != nil {
+		return "", err
+	}
+
+	crlPem := pem.EncodeToMemory(&pem.Block{
+		Type:  "X509 CRL",
+		Bytes: crl,
+	})
+
+	return string(crlPem[:]), nil
+
+}
+
 func createCert(commonName string, ca *CA, server bool) (*Cert, error) {
 	// Get CA private key
 	block, _ := pem.Decode([]byte(ca.Key))
@@ -192,7 +234,7 @@ type basicConstraints struct {
 }
 
 func getCA() (*CA, error) {
-	server := Server{}
+	server := DBServer{}
 	db.First(&server)
 	if db.NewRecord(&server) {
 		return nil, fmt.Errorf("can not retrieve server from db")

+ 143 - 0
rpcapi.go

@@ -0,0 +1,143 @@
+//go:generate protoc -I pb/ pb/user.proto pb/vpn.proto --go_out=plugins=grpc:pb
+
+package ovpm
+
+import (
+	"os"
+	"time"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/cad/ovpm/pb"
+	"golang.org/x/net/context"
+)
+
+type UserSvc struct{}
+
+func (s *UserSvc) List(ctx context.Context, req *pb.UserListRequest) (*pb.UserResponse, error) {
+	var ut []*pb.UserResponse_User
+
+	users, err := GetAllUsers()
+	if err != nil {
+		logrus.Errorf("users can not be fetched: %v", err)
+		os.Exit(1)
+		return nil, err
+	}
+	for _, user := range users {
+		ut = append(ut, &pb.UserResponse_User{
+			ServerSerialNumber: user.GetServerSerialNumber(),
+			Username:           user.GetUsername(),
+			CreatedAt:          user.GetCreatedAt(),
+		})
+	}
+
+	return &pb.UserResponse{Users: ut}, nil
+}
+
+func (s *UserSvc) Create(ctx context.Context, req *pb.UserCreateRequest) (*pb.UserResponse, error) {
+	var ut []*pb.UserResponse_User
+	user, err := CreateNewUser(req.Username, req.Password)
+	if err != nil {
+		return nil, err
+	}
+
+	pbUser := pb.UserResponse_User{
+		Username:           user.GetUsername(),
+		ServerSerialNumber: user.GetServerSerialNumber(),
+	}
+	ut = append(ut, &pbUser)
+
+	return &pb.UserResponse{Users: ut}, nil
+}
+
+func (s *UserSvc) Delete(ctx context.Context, req *pb.UserDeleteRequest) (*pb.UserResponse, error) {
+	var ut []*pb.UserResponse_User
+	user, err := GetUser(req.Username)
+	if err != nil {
+		return nil, err
+	}
+
+	pbUser := pb.UserResponse_User{
+		Username:           user.GetUsername(),
+		ServerSerialNumber: user.GetServerSerialNumber(),
+	}
+	ut = append(ut, &pbUser)
+
+	err = user.Delete()
+	if err != nil {
+		return nil, err
+	}
+
+	return &pb.UserResponse{Users: ut}, nil
+}
+
+func (s *UserSvc) Renew(ctx context.Context, req *pb.UserRenewRequest) (*pb.UserResponse, error) {
+	var ut []*pb.UserResponse_User
+	user, err := GetUser(req.Username)
+	if err != nil {
+		return nil, err
+	}
+
+	pbUser := pb.UserResponse_User{
+		Username:           user.GetUsername(),
+		ServerSerialNumber: user.GetServerSerialNumber(),
+	}
+	ut = append(ut, &pbUser)
+
+	err = user.Sign()
+	if err != nil {
+		return nil, err
+	}
+
+	return &pb.UserResponse{Users: ut}, nil
+}
+
+func (s *UserSvc) GenConfig(ctx context.Context, req *pb.UserGenConfigRequest) (*pb.UserGenConfigResponse, error) {
+	user, err := GetUser(req.Username)
+	if err != nil {
+		return nil, err
+	}
+	configBlob, err := sDumpUserOVPNConf(user.GetUsername())
+	if err != nil {
+		return nil, err
+	}
+
+	return &pb.UserGenConfigResponse{ClientConfig: configBlob}, nil
+}
+
+type VPNSvc struct{}
+
+func (s *VPNSvc) Status(ctx context.Context, req *pb.VPNStatusRequest) (*pb.VPNStatusResponse, error) {
+	server, err := GetServerInstance()
+	if err != nil {
+		return nil, err
+	}
+
+	response := pb.VPNStatusResponse{
+		Name:         server.Name,
+		SerialNumber: server.SerialNumber,
+		Hostname:     server.Hostname,
+		Port:         server.Port,
+		Cert:         server.Cert,
+		CACert:       server.CACert,
+		Net:          server.Net,
+		Mask:         server.Mask,
+		CreatedAt:    server.CreatedAt.Format(time.UnixDate),
+	}
+	return &response, nil
+}
+
+func (s *VPNSvc) Init(ctx context.Context, req *pb.VPNInitRequest) (*pb.VPNInitResponse, error) {
+	if err := InitServer("default", req.Hostname, req.Port); err != nil {
+		logrus.Errorf("server can not be created: %v", err)
+	}
+	return &pb.VPNInitResponse{}, nil
+}
+
+func (s *VPNSvc) Apply(ctx context.Context, req *pb.VPNApplyRequest) (*pb.VPNApplyResponse, error) {
+	if err := Emit(); err != nil {
+		logrus.Errorf("can not apply configuration: %v", err)
+		return nil, err
+	}
+	logrus.Info("changes applied")
+	return &pb.VPNApplyResponse{}, nil
+}

+ 0 - 2
template/client.ovpn.tmpl

@@ -13,9 +13,7 @@ verb 3
 
 <ca>
 {{ .CA }}</ca>
-
 <cert>
 {{ .Cert }}</cert>
-
 <key>
 {{ .Key }}</key>

+ 1 - 0
template/server.conf.tmpl

@@ -151,6 +151,7 @@ client-config-dir {{ .CCDPath }}
 # Then add this line to ccd/Thelonious:
 #   ifconfig-push 10.9.0.1 10.9.0.2
 
+crl-verify {{ .CRLPath }}
 # Suppose that you want to enable different
 # firewall access policies for different groups
 # of clients.  There are two methods:

+ 99 - 43
user.go

@@ -2,14 +2,49 @@ package ovpm
 
 import (
 	"fmt"
+	"time"
+
 	"github.com/Sirupsen/logrus"
 	"github.com/asaskevich/govalidator"
+	"github.com/jinzhu/gorm"
 )
 
+// User represents the interface that is being used within the public api.
+type User interface {
+	GetUsername() string
+	GetServerSerialNumber() string
+	GetCert() string
+}
+
+// DBUser is database model for VPN users.
+type DBUser struct {
+	gorm.Model
+	ServerID uint
+	Server   DBServer
+
+	Username           string `gorm:"unique_index"`
+	Cert               string
+	ServerSerialNumber string
+	Password           string
+	Key                string
+}
+
+// DBRevoked is a database model for revoked VPN users.
+type DBRevoked struct {
+	gorm.Model
+	SerialNumber string
+}
+
+func (u *DBUser) setPassword(newPassword string) error {
+	// TODO(cad): Use a proper password hashing algorithm here.
+	u.Password = newPassword
+	return nil
+}
+
 // GetUser finds and returns the user with the given username from database.
-func GetUser(username string) (*User, error) {
-	user := User{}
-	db.Where(&User{Username: username}).First(&user)
+func GetUser(username string) (*DBUser, error) {
+	user := DBUser{}
+	db.Where(&DBUser{Username: username}).First(&user)
 	if db.NewRecord(&user) {
 		// user is not found
 		return nil, fmt.Errorf("user not found: %s", username)
@@ -18,18 +53,18 @@ func GetUser(username string) (*User, error) {
 }
 
 // GetAllUsers returns all recorded users in the database.
-func GetAllUsers() ([]*User, error) {
-	var users []*User
+func GetAllUsers() ([]*DBUser, error) {
+	var users []*DBUser
 	db.Find(&users)
 
 	return users, nil
 
 }
 
-// CreateUser creates a new user with the given username and password in the database.
+// CreateNewUser creates a new user with the given username and password in the database.
 // It also generates the necessary client keys and signs certificates with the current
 // server's CA.
-func CreateUser(username, password string) (*User, error) {
+func CreateNewUser(username, password string) (*DBUser, error) {
 	if !CheckBootstrapped() {
 		return nil, fmt.Errorf("you first need to create server")
 	}
@@ -49,12 +84,17 @@ func CreateUser(username, password string) (*User, error) {
 	if err != nil {
 		return nil, fmt.Errorf("can not create client cert %s: %v", username, err)
 	}
+	server, err := GetServerInstance()
+	if err != nil {
+		return nil, fmt.Errorf("can not get server: %v", err)
+	}
+	user := DBUser{
 
-	user := User{
-		Username: username,
-		Password: password,
-		Cert:     clientCert.Cert,
-		Key:      clientCert.Key,
+		Username:           username,
+		Password:           password,
+		Cert:               clientCert.Cert,
+		Key:                clientCert.Key,
+		ServerSerialNumber: server.SerialNumber,
 	}
 
 	db.Create(&user)
@@ -72,60 +112,56 @@ func CreateUser(username, password string) (*User, error) {
 	return &user, nil
 }
 
-// DeleteUser deletes a user by the given username from the database.
-func DeleteUser(username string) error {
-	user := User{}
-	db.Unscoped().Where(&User{Username: username}).First(&user)
-	if db.NewRecord(&user) {
+// Delete deletes a user by the given username from the database.
+func (u *DBUser) Delete() error {
+	if db.NewRecord(&u) {
 		// user is not found
-		return fmt.Errorf("user not found: %s", username)
+		return fmt.Errorf("user is not initialized: %s", u.Username)
+	}
+	crt, err := getCertFromPEM(u.Cert)
+	if err != nil {
+		return fmt.Errorf("can not get user's certificate: %v", err)
 	}
-	db.Unscoped().Delete(&user)
+	db.Create(&DBRevoked{
+		SerialNumber: crt.SerialNumber.Text(16),
+	})
+	db.Unscoped().Delete(&u)
 
-	err := Emit()
+	err = Emit()
 	if err != nil {
 		return err
 	}
+	u = nil // delete the existing user struct
 	return nil
 }
 
-// ResetUserPassword resets the users password into the provided password.
-func ResetUserPassword(username, newPassword string) error {
-	user := User{}
-	db.Where(&User{Username: username}).First(&user)
-	if db.NewRecord(&user) {
-		// user is not found
-		return fmt.Errorf("user not found: %s", username)
-	}
-
-	err := user.setPassword(newPassword)
+// ResetPassword resets the users password into the provided password.
+func (u *DBUser) ResetPassword(newPassword string) error {
+	err := u.setPassword(newPassword)
 	if err != nil {
 		// user password can not be updated
-		return fmt.Errorf("user password can not be updated %s: %v", username, err)
+		return fmt.Errorf("user password can not be updated %s: %v", u.Username, err)
 	}
+	db.Save(u)
 	return nil
 }
 
-// SignUser create a key and a ceritificate signed by the current server's CA.
+// Sign creates a key and a ceritificate signed by the current server's CA.
 //
 // This is often used to sign users when the current CA is changed while there are
 // still  existing users in the database.
-func SignUser(username string) error {
+func (u *DBUser) Sign() error {
 	if !CheckBootstrapped() {
 		return fmt.Errorf("you first need to create server")
 	}
-	user, err := GetUser(username)
-	if err != nil {
-		return fmt.Errorf("user not found %s: %v", username, err)
-	}
 	ca, err := getCA()
 	if err != nil {
 		return err
 	}
 
-	clientCert, err := CreateClientCert(username, ca)
+	clientCert, err := CreateClientCert(u.Username, ca)
 	if err != nil {
-		return fmt.Errorf("can not create client cert %s: %v", username, err)
+		return fmt.Errorf("can not create client cert %s: %v", u.Username, err)
 	}
 
 	server, err := GetServerInstance()
@@ -133,10 +169,30 @@ func SignUser(username string) error {
 		return err
 	}
 
-	user.Cert = clientCert.Cert
-	user.Key = clientCert.Key
-	user.ServerSerialNumber = server.SerialNumber
+	u.Cert = clientCert.Cert
+	u.Key = clientCert.Key
+	u.ServerSerialNumber = server.SerialNumber
 
-	db.Save(&user)
+	db.Save(&u)
 	return nil
 }
+
+// GetUsername returns user's username.
+func (u *DBUser) GetUsername() string {
+	return u.Username
+}
+
+// GetCert returns user's public certificate.
+func (u *DBUser) GetCert() string {
+	return u.Cert
+}
+
+// GetServerSerialNumber returns user's server serial number.
+func (u *DBUser) GetServerSerialNumber() string {
+	return u.ServerSerialNumber
+}
+
+// GetCreatedAt returns user's creation time.
+func (u *DBUser) GetCreatedAt() string {
+	return u.CreatedAt.Format(time.UnixDate)
+}

+ 118 - 33
vpn.go

@@ -5,33 +5,73 @@ package ovpm
 import (
 	"bytes"
 	"fmt"
-	"github.com/Sirupsen/logrus"
-	"github.com/asaskevich/govalidator"
-	"github.com/google/uuid"
+	"math/big"
 	"net"
 	"os"
 	"os/exec"
 	"strings"
 	"text/template"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/asaskevich/govalidator"
+	"github.com/google/uuid"
+	"github.com/jinzhu/gorm"
 )
 
+// DBNetwork is database model for external networks on the VPN server.
+type DBNetwork struct {
+	gorm.Model
+	ServerID uint
+	Server   DBServer
+
+	Name        string
+	NetworkCIDR string
+}
+
+// DBServer is database model for storing VPN server related stuff.
+type DBServer struct {
+	gorm.Model
+	Name         string `gorm:"unique_index"` // Server name.
+	SerialNumber string
+
+	Hostname string // Server's ip address or FQDN
+	Port     string // Server's listening port
+	Cert     string // Server RSA certificate.
+	Key      string // Server RSA private key.
+	CACert   string // Root CA RSA certificate.
+	CAKey    string // Root CA RSA key.
+	Net      string // VPN network.
+	Mask     string // VPN network mask.
+	CRL      string // Certificate Revocation List
+}
+
+// CheckSerial takes a serial number and checks it against the current server's serial number.
+func (s *DBServer) CheckSerial(serialNo string) bool {
+	return serialNo == s.SerialNumber
+}
+
 type _VPNServerConfig struct {
 	CertPath     string
 	KeyPath      string
 	CACertPath   string
 	CAKeyPath    string
 	CCDPath      string
+	CRLPath      string
 	DHParamsPath string
 	Net          string
 	Mask         string
 	Port         string
 }
 
-// CreateServer generates keys and certs for a Root CA, and saves them in the database.
-func CreateServer(serverName string, hostname string, port string) error {
+// InitServer regenerates keys and certs for a Root CA, and saves them in the database.
+func InitServer(serverName string, hostname string, port string) error {
 	if CheckBootstrapped() {
-		return fmt.Errorf("server is already created")
+		if err := DeleteServer("default"); err != nil {
+			logrus.Errorf("server can not be deleted: %v", err)
+			return err
+		}
 	}
+
 	if !govalidator.IsHost(hostname) {
 		return fmt.Errorf("validation error: hostname:`%s` should be either an ip address or a FQDN", hostname)
 	}
@@ -47,7 +87,12 @@ func CreateServer(serverName string, hostname string, port string) error {
 	}
 	serialNumber := uuid.New().String()
 
-	serverInstance := Server{
+	// crl, err := MakeCRL([]*big.Int{})
+	// if err != nil {
+	// 	return fmt.Errorf("can not create server crl: %v", err)
+	// }
+
+	serverInstance := DBServer{
 		Name: serverName,
 
 		SerialNumber: serialNumber,
@@ -73,7 +118,7 @@ func CreateServer(serverName string, hostname string, port string) error {
 	}
 	// Sign all users in the db with the new server
 	for _, user := range users {
-		err := SignUser(user.Username)
+		err := user.Sign()
 		logrus.Infof("user certificate changed for %s, you should run: $ ovpm user export-config --user %s", user.Username, user.Username)
 		if err != nil {
 			logrus.Errorf("can not sign user %s: %v", user.Username, err)
@@ -89,21 +134,21 @@ func DeleteServer(serverName string) error {
 		return fmt.Errorf("server not found")
 	}
 
-	db.Unscoped().Delete(&Server{})
+	db.Unscoped().Delete(&DBServer{})
+	db.Unscoped().Delete(&DBRevoked{})
 	return nil
 }
 
-// DumpUserOVPNConf combines a specially generated config for the client with CA's and Client's certs and Clients key then dumps them to the specified path.
-func DumpUserOVPNConf(username, outPath string) error {
+func sDumpUserOVPNConf(username string) (string, error) {
 	var result bytes.Buffer
 	user, err := GetUser(username)
 	if err != nil {
-		return err
+		return "", err
 	}
 
 	server, err := GetServerInstance()
 	if err != nil {
-		return err
+		return "", err
 	}
 
 	params := struct {
@@ -121,38 +166,47 @@ func DumpUserOVPNConf(username, outPath string) error {
 	}
 	data, err := Asset("template/client.ovpn.tmpl")
 	if err != nil {
-		return err
+		return "", err
 	}
 
 	t, err := template.New("client.ovpn").Parse(string(data))
 	if err != nil {
-		return fmt.Errorf("can not parse client.ovpn.tmpl template: %s", err)
+		return "", fmt.Errorf("can not parse client.ovpn.tmpl template: %s", err)
 	}
 
 	err = t.Execute(&result, params)
 	if err != nil {
-		return fmt.Errorf("can not render client.ovpn: %s", err)
+		return "", fmt.Errorf("can not render client.ovpn: %s", err)
 	}
 
+	return result.String(), nil
+}
+
+// DumpUserOVPNConf combines a specially generated config for the client with CA's and Client's certs and Clients key then dumps them to the specified path.
+func DumpUserOVPNConf(username, outPath string) error {
+	result, err := sDumpUserOVPNConf(username)
+	if err != nil {
+		return err
+	}
 	// Wite rendered content into openvpn server conf.
-	return emitToFile(outPath, result.String(), 0)
+	return emitToFile(outPath, result, 0)
 
 }
 
 // Emit generates all needed files for the OpenVPN server and dumps them to their corresponding paths defined in the config.
 func Emit() error {
 	// Check dependencies
-	if !checkOpenVPNBinary() {
-		return fmt.Errorf("openvpn binary can not be found! you should install OpenVPN on this machine")
+	if !checkOpenVPNExecutable() {
+		return fmt.Errorf("openvpn executable can not be found! you should install OpenVPN on this machine")
 	}
 
-	if !checkOpenSSLBinary() {
-		return fmt.Errorf("openssl binary can not be found! you should install openssl on this machine")
+	if !checkOpenSSLExecutable() {
+		return fmt.Errorf("openssl executable can not be found! you should install openssl on this machine")
 
 	}
 
-	if !checkIptablesBinary() {
-		return fmt.Errorf("iptables binary can not be found")
+	if !checkIptablesExecutable() {
+		return fmt.Errorf("iptables executable can not be found")
 	}
 
 	if !CheckBootstrapped() {
@@ -191,6 +245,10 @@ func Emit() error {
 		return fmt.Errorf("can not emit iptables conf: %s", err)
 	}
 
+	if err := emitCRL(); err != nil {
+		return fmt.Errorf("can not emit crl: %s", err)
+	}
+
 	logrus.Info("changes are applied to the filesystem")
 
 	return nil
@@ -211,6 +269,15 @@ func emitToFile(filePath, content string, mode uint) error {
 }
 
 func emitServerConf() error {
+	serverInstance, err := GetServerInstance()
+	if err != nil {
+		return fmt.Errorf("can not retrieve server: %v", err)
+	}
+	port := DefaultVPNPort
+	if serverInstance.Port != "" {
+		port = serverInstance.Port
+	}
+
 	var result bytes.Buffer
 
 	server := _VPNServerConfig{
@@ -219,10 +286,11 @@ func emitServerConf() error {
 		CACertPath:   DefaultCACertPath,
 		CAKeyPath:    DefaultCAKeyPath,
 		CCDPath:      DefaultVPNCCDPath,
+		CRLPath:      DefaultCRLPath,
 		DHParamsPath: DefaultDHParamsPath,
 		Net:          DefaultServerNetwork,
 		Mask:         DefaultServerNetMask,
-		Port:         DefaultVPNPort,
+		Port:         port,
 	}
 	data, err := Asset("template/server.conf.tmpl")
 	if err != nil {
@@ -244,8 +312,8 @@ func emitServerConf() error {
 }
 
 // GetServerInstance returns the default server from the database.
-func GetServerInstance() (*Server, error) {
-	var server Server
+func GetServerInstance() (*DBServer, error) {
+	var server DBServer
 	db.First(&server)
 	if db.NewRecord(server) {
 		return nil, fmt.Errorf("can not retrieve server from db")
@@ -255,7 +323,7 @@ func GetServerInstance() (*Server, error) {
 
 // CheckBootstrapped checks if there is a default server in the database or not.
 func CheckBootstrapped() bool {
-	var server Server
+	var server DBServer
 	db.First(&server)
 	if db.NewRecord(server) {
 		return false
@@ -283,6 +351,23 @@ func emitServerCert() error {
 	return emitToFile(DefaultCertPath, server.Cert, 0)
 }
 
+func emitCRL() error {
+	var revokedDBItems []*DBRevoked
+	db.Find(&revokedDBItems)
+	var revokedCertSerials []*big.Int
+	for _, item := range revokedDBItems {
+		bi := big.NewInt(0)
+		bi.SetString(item.SerialNumber, 16)
+		revokedCertSerials = append(revokedCertSerials, bi)
+	}
+	crl, err := MakeCRL(revokedCertSerials)
+	if err != nil {
+		return fmt.Errorf("can not emit crl: %v", err)
+	}
+
+	return emitToFile(DefaultCRLPath, crl, 0)
+}
+
 func emitCACert() error {
 	server, err := GetServerInstance()
 	if err != nil {
@@ -375,35 +460,35 @@ func emitIptables() error {
 	return nil
 }
 
-func checkOpenVPNBinary() bool {
+func checkOpenVPNExecutable() bool {
 	cmd := exec.Command("which", "openvpn")
 	output, err := cmd.Output()
 	if err != nil {
 		logrus.Errorf("openvpn is not installed: %s  ✘", err)
 		return false
 	}
-	logrus.Infof("openvpn binary detected: %s  ✔", strings.TrimSpace(string(output[:])))
+	logrus.Infof("openvpn executable detected: %s  ✔", strings.TrimSpace(string(output[:])))
 	return true
 }
 
-func checkOpenSSLBinary() bool {
+func checkOpenSSLExecutable() bool {
 	cmd := exec.Command("which", "openssl")
 	output, err := cmd.Output()
 	if err != nil {
 		logrus.Errorf("openssl is not installed: %s  ✘", err)
 		return false
 	}
-	logrus.Infof("openssl binary detected: %s  ✔", strings.TrimSpace(string(output[:])))
+	logrus.Infof("openssl executable detected: %s  ✔", strings.TrimSpace(string(output[:])))
 	return true
 }
 
-func checkIptablesBinary() bool {
+func checkIptablesExecutable() bool {
 	cmd := exec.Command("which", "iptables")
 	output, err := cmd.Output()
 	if err != nil {
 		logrus.Errorf("iptables is not installed: %s  ✘", err)
 		return false
 	}
-	logrus.Infof("iptables binary detected: %s  ✔", strings.TrimSpace(string(output[:])))
+	logrus.Infof("iptables executable detected: %s  ✔", strings.TrimSpace(string(output[:])))
 	return true
 }

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно