|
|
|
@ -17,16 +17,23 @@ import ( |
|
|
|
|
// "encoding/binary"
|
|
|
|
|
"encoding/json" |
|
|
|
|
"net/http" |
|
|
|
|
"gopkg.in/alecthomas/kingpin.v2" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
listenHost = kingpin.Flag("listen.host","host for openvpn-admin").Default("127.0.0.1").String() |
|
|
|
|
listenPort = kingpin.Flag("listen.port","port for openvpn-admin").Default("8080").String() |
|
|
|
|
easyrsaPath = kingpin.Flag("easyrsa.path", "path to easyrsa dir").Default("/etc/openvpn/easyrsa").String() |
|
|
|
|
indexTxtPath = kingpin.Flag("easyrsa.index-path", "path to easyrsa index file.").Default("/etc/openvpn/easyrsa/pki/index.txt").String() |
|
|
|
|
ccdCustom = kingpin.Flag("ccd.custom", "enable or disable custom routes").Default("false").Bool() |
|
|
|
|
ccdDir = kingpin.Flag("ccd.path", "path to client-config-dir").Default("/etc/openvpn/ccd").String() |
|
|
|
|
staticPath = kingpin.Flag("static.path", "path to static dir").Default("./static").String() |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
const ( |
|
|
|
|
easyrsaPath = "easyrsa" |
|
|
|
|
indexTxtPath = easyrsaPath + "/pki/index.txt" |
|
|
|
|
usernameRegexp = `^([a-zA-Z0-9_.-])+$` |
|
|
|
|
openvpnServerHost = "127.0.0.1" |
|
|
|
|
openvpnServerPort = "7777" |
|
|
|
|
listenHost = "127.0.0.1" |
|
|
|
|
listenPort = "8080" |
|
|
|
|
mgmtListenHost = "127.0.0.1" |
|
|
|
|
mgmtListenPort = "7788" |
|
|
|
|
) |
|
|
|
@ -40,6 +47,16 @@ type openvpnClientConfig struct { |
|
|
|
|
TLS string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type ccdLine struct { |
|
|
|
|
addr string `json:"addr"` |
|
|
|
|
mask string `json:"mask"` |
|
|
|
|
desc string `json:"desc"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type ccdFile struct { |
|
|
|
|
lines []ccdLine |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type indexTxtLine struct { |
|
|
|
|
Flag string |
|
|
|
|
ExpirationDate string |
|
|
|
@ -63,7 +80,7 @@ type clientStatus struct { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func userListHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
userList, _ := json.Marshal(indexTxtParser(fRead(indexTxtPath))) |
|
|
|
|
userList, _ := json.Marshal(indexTxtParser(fRead(*indexTxtPath))) |
|
|
|
|
fmt.Fprintf(w, "%s", userList) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -88,21 +105,24 @@ func userShowConfigHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
fmt.Fprintf(w, "%s", renderClientConfig(r.FormValue("username"))) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func userShowCcdHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
r.ParseForm() |
|
|
|
|
fmt.Printf("username: %v\n%s\n", r.PostForm, r.FormValue("username")) |
|
|
|
|
fmt.Fprintf(w, "%s", renderCcdConfig(r.FormValue("username"))) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func userApplyCcdHandler(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
r.ParseForm() |
|
|
|
|
fmt.Printf("username: %v\n%s\n", r.PostForm, r.FormValue("username")) |
|
|
|
|
fmt.Fprintf(w, "%s", ccdFileModify(r.FormValue("username"),ccdFileParser(r.FormValue("ccd")))) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func main() { |
|
|
|
|
fmt.Println("Bind: http://" + listenHost + ":" + listenPort) |
|
|
|
|
// usersFromIndexTxt := indexTxtParser(fRead(indexTxtPath))
|
|
|
|
|
// fRead(indexTxtPath)
|
|
|
|
|
// fWrite("hello", "hi")
|
|
|
|
|
// renderIndexTxt(indexTxtParser(fRead(indexTxtPath)))
|
|
|
|
|
// renderClientConfig("asd")
|
|
|
|
|
// crlFix()
|
|
|
|
|
// fmt.Println(reflect.TypeOf(indexTxtConf))
|
|
|
|
|
// fmt.Print(userUnrevoke("asd"))
|
|
|
|
|
// renderIndexTxt(usersFromIndexTxt)
|
|
|
|
|
// x := getActiveClients()
|
|
|
|
|
// fmt.Printf("%#v", x)
|
|
|
|
|
// killUserConnection("x")
|
|
|
|
|
fs := http.FileServer(http.Dir("./frontend/static")) |
|
|
|
|
kingpin.Parse() |
|
|
|
|
|
|
|
|
|
fmt.Println("Bind: http://" + *listenHost + ":" + *listenPort) |
|
|
|
|
|
|
|
|
|
fs := http.FileServer(http.Dir(*staticPath)) |
|
|
|
|
|
|
|
|
|
http.Handle("/", fs) |
|
|
|
|
http.HandleFunc("/api/users/list", userListHandler) |
|
|
|
@ -110,7 +130,10 @@ func main() { |
|
|
|
|
http.HandleFunc("/api/user/revoke", userRevokeHandler) |
|
|
|
|
http.HandleFunc("/api/user/unrevoke", userUnrevokeHandler) |
|
|
|
|
http.HandleFunc("/api/user/showconfig", userShowConfigHandler) |
|
|
|
|
log.Fatal(http.ListenAndServe(listenHost+":"+listenPort, nil)) |
|
|
|
|
http.HandleFunc("/api/user/ccd/list", userShowCcdHandler) |
|
|
|
|
http.HandleFunc("/api/user/ccd/apply", userApplyCcdHandler) |
|
|
|
|
|
|
|
|
|
log.Fatal(http.ListenAndServe(*listenHost + ":" + *listenPort, nil)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func fRead(path string) string { |
|
|
|
@ -175,10 +198,10 @@ func renderClientConfig(username string) string { |
|
|
|
|
conf := openvpnClientConfig{} |
|
|
|
|
conf.Host = openvpnServerHost |
|
|
|
|
conf.Port = openvpnServerPort |
|
|
|
|
conf.CA = fRead(easyrsaPath + "/pki/ca.crt") |
|
|
|
|
conf.Cert = fRead(easyrsaPath + "/pki/issued/" + username + ".crt") |
|
|
|
|
conf.Key = fRead(easyrsaPath + "/pki/private/" + username + ".key") |
|
|
|
|
conf.TLS = fRead(easyrsaPath + "/pki/ta.key") |
|
|
|
|
conf.CA = fRead(*easyrsaPath + "/pki/ca.crt") |
|
|
|
|
conf.Cert = fRead(*easyrsaPath + "/pki/issued/" + username + ".crt") |
|
|
|
|
conf.Key = fRead(*easyrsaPath + "/pki/private/" + username + ".key") |
|
|
|
|
conf.TLS = fRead(*easyrsaPath + "/pki/ta.key") |
|
|
|
|
t, _ := template.ParseFiles("client.conf.tpl") |
|
|
|
|
var tmp bytes.Buffer |
|
|
|
|
t.Execute(&tmp, conf) |
|
|
|
@ -190,10 +213,47 @@ func renderClientConfig(username string) string { |
|
|
|
|
return (fmt.Sprintf("User \"%s\" not found", username)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func ccdFileParser(txt string) ccdFile { |
|
|
|
|
ccdFile := ccdFile{} |
|
|
|
|
|
|
|
|
|
txtLinesArray := strings.Split(txt, "\n") |
|
|
|
|
|
|
|
|
|
for _, v := range txtLinesArray { |
|
|
|
|
str := strings.Fields(v) |
|
|
|
|
if len(str) > 0 { |
|
|
|
|
switch { |
|
|
|
|
case strings.HasPrefix(str[0], "ifconfig-push"): |
|
|
|
|
ccdFile.lines = append(ccdFile.lines, ccdLine{addr: str[2], mask: str[3], desc: "Client Address"}) |
|
|
|
|
case strings.HasPrefix(str[0], "push"): |
|
|
|
|
ccdFile.lines = append(ccdFile.lines, ccdLine{addr: str[2], mask: str[3], desc: strings.Join(str[4:], "")}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ccdFile |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func renderCcdConfig(username string) string { |
|
|
|
|
if checkCcdExist(username) { |
|
|
|
|
ccdFileParser(fRead(*ccdDir + "/" + username)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fmt.Printf("ccd for user \"%s\" not found", username) |
|
|
|
|
return (fmt.Sprintf("ccd for user \"%s\" not found", username)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func ccdFileModify(username string, ccdFile ccdFile) bool { |
|
|
|
|
if checkCcdExist(username) { |
|
|
|
|
} |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// https://community.openvpn.net/openvpn/ticket/623
|
|
|
|
|
func crlFix() { |
|
|
|
|
os.Chmod(easyrsaPath+"/pki", 0755) |
|
|
|
|
err := os.Chmod(easyrsaPath+"/pki/crl.pem", 0640) |
|
|
|
|
os.Chmod(*easyrsaPath + "/pki", 0755) |
|
|
|
|
err := os.Chmod(*easyrsaPath + "/pki/crl.pem", 0640) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Println(err) |
|
|
|
|
} |
|
|
|
@ -215,7 +275,7 @@ func validateUsername(username string) bool { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func checkUserExist(username string) bool { |
|
|
|
|
for _, u := range indexTxtParser(fRead(indexTxtPath)) { |
|
|
|
|
for _, u := range indexTxtParser(fRead(*indexTxtPath)) { |
|
|
|
|
if u.DistinguishedName == ("/CN=" + username) { |
|
|
|
|
return (true) |
|
|
|
|
} |
|
|
|
@ -223,9 +283,26 @@ func checkUserExist(username string) bool { |
|
|
|
|
return (false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func checkCcdExist(username string) bool { |
|
|
|
|
if *ccdCustom { |
|
|
|
|
if _, err := os.Stat(*ccdDir + "/" + username); err == nil { |
|
|
|
|
return (true) |
|
|
|
|
} else if os.IsNotExist(err) { |
|
|
|
|
fmt.Printf("ccd for user \"%s\" not found", username) |
|
|
|
|
return (false) |
|
|
|
|
} else { |
|
|
|
|
fmt.Printf("Something goes wrong during checking ccd for user \"%s\"", username) |
|
|
|
|
fmt.Printf("err: %s", err) |
|
|
|
|
return (false) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return (false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func usersList() []string { |
|
|
|
|
users := []string{} |
|
|
|
|
for _, line := range indexTxtParser(fRead(indexTxtPath)) { |
|
|
|
|
for _, line := range indexTxtParser(fRead(*indexTxtPath)) { |
|
|
|
|
users = append(users, line.Identity) |
|
|
|
|
} |
|
|
|
|
return (users) |
|
|
|
@ -240,7 +317,7 @@ func userCreate(username string) string { |
|
|
|
|
fmt.Printf("User \"%s\" already exists\n", username) |
|
|
|
|
return (fmt.Sprintf("User \"%s\" already exists\n", username)) |
|
|
|
|
} |
|
|
|
|
o := runBash(fmt.Sprintf("date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && ./easyrsa build-client-full %s nopass", easyrsaPath, username)) |
|
|
|
|
o := runBash(fmt.Sprintf("date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && ./easyrsa build-client-full %s nopass", *easyrsaPath, username)) |
|
|
|
|
fmt.Println(o) |
|
|
|
|
return ("") |
|
|
|
|
} |
|
|
|
@ -248,7 +325,7 @@ func userCreate(username string) string { |
|
|
|
|
func userRevoke(username string) string { |
|
|
|
|
if checkUserExist(username) { |
|
|
|
|
// check certificate valid flag 'V'
|
|
|
|
|
o := runBash(fmt.Sprintf("date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && echo yes | ./easyrsa revoke %s && ./easyrsa gen-crl", easyrsaPath, username)) |
|
|
|
|
o := runBash(fmt.Sprintf("date +%%Y-%%m-%%d\\ %%H:%%M:%%S && cd %s && echo yes | ./easyrsa revoke %s && ./easyrsa gen-crl", *easyrsaPath, username)) |
|
|
|
|
crlFix() |
|
|
|
|
return (fmt.Sprintln(o)) |
|
|
|
|
} |
|
|
|
@ -259,7 +336,7 @@ func userRevoke(username string) string { |
|
|
|
|
func userUnrevoke(username string) string { |
|
|
|
|
if checkUserExist(username) { |
|
|
|
|
// check certificate revoked flag 'R'
|
|
|
|
|
usersFromIndexTxt := indexTxtParser(fRead(indexTxtPath)) |
|
|
|
|
usersFromIndexTxt := indexTxtParser(fRead(*indexTxtPath)) |
|
|
|
|
for i := range usersFromIndexTxt { |
|
|
|
|
if usersFromIndexTxt[i].DistinguishedName == ("/CN=" + username) { |
|
|
|
|
usersFromIndexTxt[i].Flag = "V" |
|
|
|
@ -267,7 +344,7 @@ func userUnrevoke(username string) string { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
fWrite(indexTxtPath, renderIndexTxt(usersFromIndexTxt)) |
|
|
|
|
fWrite(*indexTxtPath, renderIndexTxt(usersFromIndexTxt)) |
|
|
|
|
fmt.Print(renderIndexTxt(usersFromIndexTxt)) |
|
|
|
|
crlFix() |
|
|
|
|
return (fmt.Sprintf("{\"msg\":\"User %s successfully unrevoked\"}", username)) |
|
|
|
|