working prototype

can push OTP request
can push routes
This commit is contained in:
Xavier Henner 2019-07-09 23:37:37 +02:00
parent f975a19f65
commit 274e824630
8 changed files with 205 additions and 96 deletions

10
ldap.go
View File

@ -21,11 +21,12 @@ type ldapConfig struct {
primaryAttribute string primaryAttribute string
secondaryAttribute string secondaryAttribute string
validGroups []string validGroups []string
otpType string mfaType string
certAuth string certAuth string
ipMin net.IP ipMin net.IP
ipMax net.IP ipMax net.IP
upgradeFrom string upgradeFrom string
routes []string
} }
func (l *ldapConfig) addIPRange(s string) error { func (l *ldapConfig) addIPRange(s string) error {
@ -69,11 +70,12 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
} }
} }
if len(logins) != 1 { // no server ldap or multiple login should not happen here
return errors.New("invalid login"), false, false, nil if len(logins) != 1 || len(conf.servers) == 0 {
return nil, false, false, nil
} }
attributes = logins
attributes = logins
for _, s := range conf.servers { for _, s := range conf.servers {
// we force ldaps because we can // we force ldaps because we can
l, err := myDialTLS("tcp", s+":636", &tls.Config{ServerName: s}) l, err := myDialTLS("tcp", s+":636", &tls.Config{ServerName: s})

View File

@ -31,8 +31,6 @@ func main() {
server.CcPwnPassword = config.GetString("config.ccPwnPassword", "") server.CcPwnPassword = config.GetString("config.ccPwnPassword", "")
server.pwnTemplate = config.GetString("config.pwnTemplate", "") server.pwnTemplate = config.GetString("config.pwnTemplate", "")
server.newAsTemplate = config.GetString("config.newAsTemplate", "") server.newAsTemplate = config.GetString("config.newAsTemplate", "")
server.slackTemplate = config.GetString("config.slackTemplate", "")
server.slackTemplate2 = config.GetString("config.slackTemplate2", "")
server.cacheDir = config.GetString("config.cacheDir", "") server.cacheDir = config.GetString("config.cacheDir", "")
server.authCa = config.GetString("config.authCa", "") server.authCa = config.GetString("config.authCa", "")
server.otpMasterSecrets = parseConfigArray(config, "config.masterSecrets") server.otpMasterSecrets = parseConfigArray(config, "config.masterSecrets")
@ -62,7 +60,8 @@ func main() {
primaryAttribute: config.GetString(profile+".primaryAttribute", ""), primaryAttribute: config.GetString(profile+".primaryAttribute", ""),
secondaryAttribute: config.GetString(profile+".secondaryAttribute", ""), secondaryAttribute: config.GetString(profile+".secondaryAttribute", ""),
validGroups: parseConfigArray(config, profile+".validGroups"), validGroups: parseConfigArray(config, profile+".validGroups"),
otpType: config.GetString(profile+".otp", ""), routes: parseConfigArray(config, profile+".routes"),
mfaType: config.GetString(profile+".mfa", ""),
certAuth: config.GetString(profile+".cert", "optionnal"), certAuth: config.GetString(profile+".cert", "optionnal"),
upgradeFrom: config.GetString(profile+".upgradeFrom", ""), upgradeFrom: config.GetString(profile+".upgradeFrom", ""),
} }

View File

@ -1,11 +0,0 @@
client-deny 0 0 "Need OTP" "CRV1:R:blabla:ZXVjbGlkZQ==:OTP Code "
client-auth 1 0
ifconfig-push 10.8.66.3 10.8.66.1
push "dhcp-option DNS 10.190.32.2"
push "dhcp-option DNS 10.190.32.20"
END

View File

@ -15,9 +15,9 @@ config
[ [
"CN=SEC_VPN_Users_External,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily", "CN=SEC_VPN_Users_External,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily",
] ]
otp: "okta" mfa: "okta"
cert: "ignore" cert: "ignore"
ip_range: "192.168.207.1 - 192.168.207.254", IPRange: "192.168.207.1 - 192.168.207.254",
routes: routes:
[ [
"10.189.10.9 255.255.255.255", "10.189.10.9 255.255.255.255",
@ -45,10 +45,9 @@ config
[ [
"CN=SEC_VPN,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily", "CN=SEC_VPN,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily",
] ]
otp: "okta" mfa: "okta"
cert: "optionnal" cert: "optionnal"
upgrade-to: "DEV" IPRange: "192.168.201.1-192.168.203.254"
ip_range: "192.168.201.1 - 192.168.203.254"
} }
DEV: DEV:
{ {
@ -59,37 +58,41 @@ config
searchFilter: "(&(mail=%s))" searchFilter: "(&(mail=%s))"
primaryAttribute: "description" primaryAttribute: "description"
secondaryAttribute: "sshPublicKey" secondaryAttribute: "sshPublicKey"
upgrade-from: "CORP" upgradeFrom: "CORP"
upgrade-to: "ADMINS" mfa: ""
otp: "okta"
cert: "optionnal" cert: "optionnal"
ip_range: "192.168.204.1 - 192.168.206.254" IPRange: "192.168.204.1-192.168.206.254"
routes:
[
"10.190.32.51 255.255.255.255",
]
} }
ADMINS: ADMINS:
{ {
validGroups: validGroups:
[ [
"infra", "infra2",
"net", "net",
"datacenter", "datacenter",
] ]
upgrade-from: "DEV" upgradeFrom: "DEV"
otp: [ "internal", "slack" ] mfa: "internal"
cert: "mandatory" cert: "mandatory"
ip_range: "192.168.200.2 - 192.168.200.254" IPRange: "192.168.200.2-192.168.200.254"
} }
} }
openvpnPort: "127.0.0.1:4000"
httpPort: ":8443"
httpCa: "/usr/local/share/ca-certificates/Dailymotion.crt"
httpKey: "/etc/ssl/private/server-key.pem"
httpCert: "/etc/ssl/certs/server-bundle.pem"
cacheDir: "/var/run/openvpn/" cacheDir: "/var/run/openvpn/"
masterSecrets: [ "*******************************J" ] authCa: "/usr/local/share/ca-certificates/Dailymotion.crt"
masterSecrets: [ "********************************"]
vpnLogUrl: "https://install.dm.gg/vpn-log.php" vpnLogUrl: "https://install.dm.gg/vpn-log.php"
slackToken: "*************************************************************************"
slackChannels: [ "#squad-it-office" ]
configParser: "/etc/openvpn/roadwarrior_([a-zA-Z0-9]*).conf"
mailRelay: "mailrelay.dailymotion.com:25" mailRelay: "mailrelay.dailymotion.com:25"
mailFrom: "engineering-infra@dailymotion.com" mailFrom: "engineering-infra@dailymotion.com"
ccPwnPassword: "security-incident-report@dailymotion.com" ccPwnPassword: "security-incident-report@dailymotion.com"
pwnTemplate: "Mime-Version: 1.0;\nContent-Type: text/html; charset=\"ISO-8859-1\";\nContent-Transfer-Encoding: 7bit;\nFrom: {{.MailFrom}}\nSubject: [Dailymotion] Your current okta password is compromised\nTo: {{.Mail}}\nCc: {{.CcPwnPassword}}\n\n<html><body>Hello<br>\n<br>\nWe have detected that you recently connected to the dailymotion's corporate VPN with login {{.Login}} and a password which was part a password-related breach - possibly related to your own account on a third party website - and which is now widely known to hackers.<br>\n<br>\nPlease contact the security team and go to the Okta homepage to change your password immediately : <a href=\"https://dailymotion.okta.com\">https://dailymotion.okta.com</a>/<br>\n<br>\nIf you were using the same unsafe password anywhere else, you should change it everywhere and make sure you use a unique password for every service (password managers make this feasible).<br>\n<br>\nWe remind you that you should always keep your passwords strong and strictly unique, especially when it comes to your dailymotion accounts. A robust password can, for example, be generated using an easily remembered phrase and retaining certain letters: for example, the phrase \"a bird in the hand is worth two in the bush\" would give the password \"1bitH=2itB\" (this example must not be used as a password).<br>\n<br>\nRegards,<br>\n<br>\n--<br>\nThe Dailymotion Security Team</body></html>" pwnTemplate: "Mime-Version: 1.0;\nContent-Type: text/html; charset=\"ISO-8859-1\";\nContent-Transfer-Encoding: 7bit;\nFrom: {{.MailFrom}}\nSubject: [Dailymotion] Your current okta password is compromised\nTo: {{.Mail}}\nCc: {{.CcPwnPassword}}\n\n<html><body>Hello<br>\n<br>\nWe have detected that you recently connected to the dailymotion's corporate VPN with login {{.Login}} and a password which was part a password-related breach - possibly related to your own account on a third party website - and which is now widely known to hackers.<br>\n<br>\nPlease contact the security team and go to the Okta homepage to change your password immediately : <a href=\"https://dailymotion.okta.com\">https://dailymotion.okta.com</a>/<br>\n<br>\nIf you were using the same unsafe password anywhere else, you should change it everywhere and make sure you use a unique password for every service (password managers make this feasible).<br>\n<br>\nWe remind you that you should always keep your passwords strong and strictly unique, especially when it comes to your dailymotion accounts. A robust password can, for example, be generated using an easily remembered phrase and retaining certain letters: for example, the phrase \"a bird in the hand is worth two in the bush\" would give the password \"1bitH=2itB\" (this example must not be used as a password).<br>\n<br>\nRegards,<br>\n<br>\n--<br>\nThe Dailymotion Security Team</body></html>"
newAsTemplate: "From: {{.MailFrom}}\nSubject: A new connection from you to the Dailymotion VPN\nTo: {{.Mail}}\n\nHello\n\nWe have detected a new connection to the vpn from {{.Login}}.\nIt was detected the {{.Time}} coming from the ip {{.IP}} ({{.AsName}}).\n\nIt's not the usual internet provider you connect to the Dailymotion VPN from.\nOr maybe it's the first time you use the VPN from this location.\n\nIf you think there is something suspicious, please contact {{.CcPwnPassword}}\nIf it's you who connected to the VPN, we are sorry for the spam. You won't receive another mail if you connect from this location.\n\nRegards,\n\n--\nThe Dailymotion Infra and Security Teams" newAsTemplate: "From: {{.MailFrom}}\nSubject: A new connection from you to the Dailymotion VPN\nTo: {{.Mail}}\n\nHello\n\nWe have detected a new connection to the vpn from {{.Login}}.\nIt was detected the {{.Time}} coming from the ip {{.IP}} ({{.AsName}}).\n\nIt's not the usual internet provider you connect to the Dailymotion VPN from.\nOr maybe it's the first time you use the VPN from this location.\n\nIf you think there is something suspicious, please contact {{.CcPwnPassword}}\nIf it's you who connected to the VPN, we are sorry for the spam. You won't receive another mail if you connect from this location.\n\nRegards,\n\n--\nThe Dailymotion Infra and Security Teams"
slackTemplate: "Hello.\nYou tried to connect to the VPN without an OTP code from your phone app.\nIf you have a Mac, use `{{.Login}}@{{.OTP}}` as your login.\nIf you have a PC with windows or linux, the VPN application should ask you for an OTP code after your login and password.\nThe OTP code is `{{.OTP}}`\n\nPS : you can check <https://wiki.dailymotion.com/display/officeit/OpenVPN#OpenVPN-OneTimePassword|the documentation> to learn how to use a proper OTP application and get rid of these messages"
slackTemplate2: "User *{{.Login}}* required the slack OTP token"
} }

7
otp.go
View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"log"
"time" "time"
) )
@ -10,9 +9,9 @@ func (s *OpenVpnMgt) GenerateOTP(user string) ([]string, error) {
} }
// alternative OTP generator, not used at the moment // alternative OTP generator, not used at the moment
func (s *OpenVpnMgt) GenerateSlackOTP(user string) ([]string, error) { // func (s *OpenVpnMgt) GenerateSlackOTP(user string) ([]string, error) {
return s.GenerateOTPGeneric(user, 60, "sha256", 30, 8) // return s.GenerateOTPGeneric(user, 60, "sha256", 30, 8)
} // }
func (s *OpenVpnMgt) GenerateOTPGeneric(user string, period int, algo string, secretLen int, digits int) ([]string, error) { func (s *OpenVpnMgt) GenerateOTPGeneric(user string, period int, algo string, secretLen int, digits int) ([]string, error) {
codes := []string{} codes := []string{}

33
roadwarrior.conf Normal file
View File

@ -0,0 +1,33 @@
#script-security 3
auth-user-pass-optional
ca /usr/local/share/ca-certificates/Dailymotion.crt
cert /etc/ssl/certs/vpn.dailymotion.com-cert.pem
user openvpn
cipher aes-128-cbc
dev vpnroadwarrior
dev-type tun
dh dh2048.pem
ifconfig 192.168.200.0 192.168.207.255
ifconfig-nowarn
keepalive 10 120
key /etc/ssl/private/vpn.dailymotion.com-key.pem
management 127.0.0.1 4000
management-client
management-client-auth
mode server
group openvpn
persist-key
persist-remote-ip
persist-tun
port 41690
proto tcp-server
push "dhcp-option DNS 10.190.32.2"
push "dhcp-option DNS 10.190.32.20"
push "topology p2p"
reneg-sec 43200
tls-auth tlsauth.key
tls-server
topology p2p
username-as-common-name
verb 4
client-cert-not-required

View File

@ -3,6 +3,7 @@ package main
import ( import (
"bufio" "bufio"
"errors" "errors"
"fmt"
"io" "io"
"log" "log"
"net" "net"
@ -26,8 +27,6 @@ type OpenVpnMgt struct {
CcPwnPassword string CcPwnPassword string
pwnTemplate string pwnTemplate string
newAsTemplate string newAsTemplate string
slackTemplate string
slackTemplate2 string
cacheDir string cacheDir string
syslog bool syslog bool
otpMasterSecrets []string otpMasterSecrets []string
@ -69,31 +68,40 @@ func (s *OpenVpnMgt) Run() {
} }
} }
func (s *OpenVpnMgt) TokenPassword(c *vpnSession) bool { func (s *OpenVpnMgt) TokenPassword(c *vpnSession) (bool, string) {
return false //TODO implement that correcly
if c.password == "maith1wiePuw3ieb4heiNie5y" {
return true, "maith1wiePuw3ieb4heiNie5y"
}
return false, "maith1wiePuw3ieb4heiNie5y"
} }
func (s *OpenVpnMgt) Auth(c *vpnSession) (error, bool) { // main authentication function.
// returns 0 if auth is valid
// returns 1 if an TOTP code is necessary
// returns a negative if auth is not valid
func (s *OpenVpnMgt) Auth(c *vpnSession) (error, int) {
// an empty password is not good // an empty password is not good
if c.password == "" { if c.password == "" {
return nil, false return errors.New("Empty Password"), -1
} }
// check if the password is a valid token validated for TOTP 2FA // check if the password is a valid token (see TOTP request)
tokenPassword := s.TokenPassword(c) tokenPasswordOk, tokenPassword := s.TokenPassword(c)
// If this is the case, empty the password to avoid checking it against the
// ldap server // password is a token. We remove it from the session object to
if tokenPassword { // avoid checking it against the ldap
if tokenPasswordOk {
c.password = "" c.password = ""
} }
// if the otp is indicated, we check it against the valid codes as soon as // if the otp is not empty, we check it against the valid codes as soon as
// possible // possible
otpvalidated := false otpvalidated := false
if c.otpCode != "" { if c.otpCode != "" {
codes, err := s.GenerateOTP(c.Login) codes, err := s.GenerateOTP(c.Login)
if err != nil { if err != nil {
return err, false return err, -2
} }
for _, possible := range codes { for _, possible := range codes {
if possible == c.otpCode { if possible == c.otpCode {
@ -102,8 +110,6 @@ func (s *OpenVpnMgt) Auth(c *vpnSession) (error, bool) {
} }
} }
log.Println(otpvalidated)
c.Profile = "" c.Profile = ""
login := []string{c.Login} login := []string{c.Login}
pass := c.password pass := c.password
@ -114,8 +120,6 @@ func (s *OpenVpnMgt) Auth(c *vpnSession) (error, bool) {
if ldap.upgradeFrom != c.Profile { if ldap.upgradeFrom != c.Profile {
continue continue
} }
log.Printf("try %s with login %s\n", k, login)
err, userOk, passOk, secondary := ldap.Auth(login, pass) err, userOk, passOk, secondary := ldap.Auth(login, pass)
// if there is an error, try the other configurations // if there is an error, try the other configurations
@ -141,20 +145,38 @@ func (s *OpenVpnMgt) Auth(c *vpnSession) (error, bool) {
} }
// we have either a positive auth ok a previous valid one // we have either a positive auth ok a previous valid one
if passOk || c.Profile != "" || tokenPassword { if passOk || c.Profile != "" || tokenPasswordOk {
c.Profile = k c.Profile = k
} }
} }
} }
// no profile update this turn, no need to continue
if n == c.Profile { if n == c.Profile {
break break
} }
} }
log.Println(c) // no profile validated, we stop here
log.Println(s.ldap[c.Profile]) if c.Profile == "" {
return nil, false return errors.New("Authentication Failed"), -3
}
// check the MFA requested by the secured profile
switch s.ldap[c.Profile].mfaType {
case "internal":
if otpvalidated {
return nil, 0
}
c.password = tokenPassword
return errors.New("Need OTP Code"), 1
case "okta":
//TODO implement okta MFA
return nil, -4
}
// no MFA requested, the login is valid
return nil, 0
} }
func (s *OpenVpnMgt) sendCommand(msg []string) (error, []string) { func (s *OpenVpnMgt) sendCommand(msg []string) (error, []string) {
@ -162,6 +184,7 @@ func (s *OpenVpnMgt) sendCommand(msg []string) (error, []string) {
return errors.New("No openvpn server present"), nil return errors.New("No openvpn server present"), nil
} }
for _, line := range msg { for _, line := range msg {
log.Println(line)
if _, err := s.buf.WriteString(line + "\r\n"); err != nil { if _, err := s.buf.WriteString(line + "\r\n"); err != nil {
return err, nil return err, nil
} }
@ -192,36 +215,73 @@ func (s *OpenVpnMgt) Version() (error, []string) {
return nil, msg return nil, msg
} }
func (s *OpenVpnMgt) ClientValidated(line string) {
//TODO manage that : find the client, log stuff
<-s.ret
}
func (s *OpenVpnMgt) ClientDisconnect(line string) { func (s *OpenVpnMgt) ClientDisconnect(line string) {
msg := <-s.ret //TODO manage that : find the client, log stuff
log.Println(msg) <-s.ret
}
func (s *OpenVpnMgt) getIP(c *vpnSession) (string, error) {
// TODO implement
ip := s.ldap[c.Profile].ipMin
return ip.String(), nil
} }
func (s *OpenVpnMgt) ClientConnect(line string) { func (s *OpenVpnMgt) ClientConnect(line string) {
var cmd []string
var ip string
var errIP error
client := NewVPNSession("log in") client := NewVPNSession("log in")
client.ParseSessionId(line) client.ParseSessionId(line)
infos := <-s.ret infos := <-s.ret
if err := client.ParseEnv(&infos); err != nil {
client.ParseEnv(&infos) log.Println(err)
return
}
err, ok := s.Auth(client) err, ok := s.Auth(client)
if err != nil { // if auth is ok, time to get an IP address
if ok == 0 {
ip, errIP = s.getIP(client)
if errIP != nil {
ok = -10
err = errIP
}
}
switch {
case ok == 0:
cmd = []string{
fmt.Sprintf("client-auth %d %d", client.cID, client.kID),
fmt.Sprintf("ifconfig-push %s %s", ip, client.localIP),
}
for _, r := range s.ldap[client.Profile].routes {
cmd = append(cmd, fmt.Sprintf("push \"route %s vpn_gateway\"", r))
}
cmd = append(cmd, "END")
case ok < 0:
cmd = []string{fmt.Sprintf("client-deny %d %d \"%s\" \"%s\"",
client.cID, client.kID, err, err)}
case ok == 1:
cmd = []string{fmt.Sprintf(
"client-deny %d %d \"Need OTP\" \"CRV1:R,E:%s:%s:OTP Code \"",
client.cID, client.kID, client.password, client.b64Login())}
}
if err, _ := s.sendCommand(cmd); err != nil {
log.Println(err) log.Println(err)
} }
if ok { return
log.Println("auth ok")
}
// err, msg := s.sendCommand([]string{fmt.Sprintf("client-deny %d %d \"Need OTP\" \"CRV1:R:blabla:eC5oZW5uZXI=:OTP Code \"", client.cID, client.kID)})
// if err != nil {
// return
// }
// log.Println(msg)
} }
func (s *OpenVpnMgt) handleConn(conn net.Conn) { func (s *OpenVpnMgt) handleConn(conn net.Conn) {
@ -259,6 +319,15 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
} }
line = strings.Trim(line, "\n\r ") line = strings.Trim(line, "\n\r ")
// manage all "terminator" lines
for _, terminator := range []string{"END", ">CLIENT:ENV,END", "SUCCESS"} {
if strings.HasPrefix(line, terminator) {
s.ret <- response
response = nil
break
}
}
switch { switch {
// a new openvpn server is connected // a new openvpn server is connected
case strings.HasPrefix(line, ">INFO"): case strings.HasPrefix(line, ">INFO"):
@ -272,16 +341,15 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
// new bloc for a connect event. // new bloc for a connect event.
// We start the receiving handler, which will wait for the Channel message // We start the receiving handler, which will wait for the Channel message
case strings.HasPrefix(line, ">CLIENT:ADDRESS"):
go s.ClientValidated(line)
case strings.HasPrefix(line, ">CLIENT:CONNECT"): case strings.HasPrefix(line, ">CLIENT:CONNECT"):
go s.ClientConnect(line) go s.ClientConnect(line)
// write the cumulated lines into the channel to the current handler
case strings.HasPrefix(line, "END") || strings.HasPrefix(line, ">CLIENT:ENV,END"):
s.ret <- response
response = nil
default: default:
response = append(response, line) response = append(response, line)
} }
//log.Print(line) log.Print(line)
} }
} }

View File

@ -4,6 +4,7 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"os" "os"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -31,6 +32,7 @@ type vpnSession struct {
dev string `json:"-"` dev string `json:"-"`
password string `json:"-"` password string `json:"-"`
otpCode string `json:"-"` otpCode string `json:"-"`
localIP string `json:"-"`
} }
func NewVPNSession(operation string) *vpnSession { func NewVPNSession(operation string) *vpnSession {
@ -44,6 +46,10 @@ func NewVPNSession(operation string) *vpnSession {
return &v return &v
} }
func (c *vpnSession) b64Login() string {
return base64.StdEncoding.EncodeToString([]byte(c.Login))
}
func (c *vpnSession) ParseSessionId(line string) error { func (c *vpnSession) ParseSessionId(line string) error {
var err error var err error
client_id := strings.Split(strings.Replace(line, ">CLIENT:CONNECT,", "", 1), ",") client_id := strings.Split(strings.Replace(line, ">CLIENT:CONNECT,", "", 1), ",")
@ -56,30 +62,39 @@ func (c *vpnSession) ParseSessionId(line string) error {
return nil return nil
} }
func (c *vpnSession) ParseEnv(infos *[]string) { func (c *vpnSession) ParseEnv(infos *[]string) error {
var err error
r := regexp.MustCompile("[^a-zA-Z0-9./_@-]")
for _, line := range *infos { for _, line := range *infos {
p := strings.Split(strings.Replace(line, ">CLIENT:ENV,", "", 1), "=") p := strings.Split(strings.Replace(line, ">CLIENT:ENV,", "", 1), "=")
switch p[0] { switch p[0] {
case "trusted_port": case "trusted_port":
c.port, _ = strconv.Atoi(p[1]) if c.port, err = strconv.Atoi(r.ReplaceAllString(p[1], "")); err != nil {
case "trusted_ip": return err
c.IP = p[1] }
case "untrusted_port": case "untrusted_port":
c.port, _ = strconv.Atoi(p[1]) if c.port, err = strconv.Atoi(r.ReplaceAllString(p[1], "")); err != nil {
return err
}
case "trusted_ip":
c.IP = r.ReplaceAllString(p[1], "")
case "untrusted_ip": case "untrusted_ip":
c.IP = p[1] c.IP = r.ReplaceAllString(p[1], "")
case "ifconfig_local":
c.localIP = r.ReplaceAllString(p[1], "")
case "password": case "password":
switch { switch {
case strings.HasPrefix(c.password, "CRV1"): case strings.HasPrefix(p[1], "CRV1"):
split := strings.Split(c.password, ":") split := strings.Split(p[1], ":")
if len(split) != 5 { if len(split) != 5 {
break break
} }
c.password = split[2] c.password = split[2]
c.otpCode = split[4] c.otpCode = split[4]
case strings.HasPrefix(c.password, "SCRV1"): case strings.HasPrefix(p[1], "SCRV1"):
split := strings.Split(c.password, ":") split := strings.Split(p[1], ":")
if len(split) != 3 { if len(split) != 3 {
break break
} }
@ -101,11 +116,12 @@ func (c *vpnSession) ParseEnv(infos *[]string) {
} }
case "username": case "username":
c.Login = p[1] c.Login = r.ReplaceAllString(p[1], "")
case "dev": case "dev":
c.dev = p[1] c.dev = r.ReplaceAllString(p[1], "")
} }
} }
return nil
} }
func (c *vpnSession) String() string { func (c *vpnSession) String() string {