should work with multiple openvpn servers
the goal is to have an udp instance, and a tcp/443 one can handle connected and disconnected messages
This commit is contained in:
		
							parent
							
								
									274e824630
								
							
						
					
					
						commit
						44cfdea6ed
					
				
							
								
								
									
										11
									
								
								logs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								logs.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *vpnSession) Log() error {
 | 
				
			||||||
 | 
						//TODO get asname & shit
 | 
				
			||||||
 | 
						log.Println(c)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								otp.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								otp.go
									
									
									
									
									
								
							@ -13,6 +13,14 @@ func (s *OpenVpnMgt) GenerateOTP(user string) ([]string, error) {
 | 
				
			|||||||
// 	return s.GenerateOTPGeneric(user, 60, "sha256", 30, 8)
 | 
					// 	return s.GenerateOTPGeneric(user, 60, "sha256", 30, 8)
 | 
				
			||||||
// }
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *OpenVpnMgt) TokenPassword(c *vpnSession) (bool, string) {
 | 
				
			||||||
 | 
						//TODO implement that correcly
 | 
				
			||||||
 | 
						if c.password == "maith1wiePuw3ieb4heiNie5y" {
 | 
				
			||||||
 | 
							return true, "maith1wiePuw3ieb4heiNie5y"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false, "maith1wiePuw3ieb4heiNie5y"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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{}
 | 
				
			||||||
	now := time.Now()
 | 
						now := time.Now()
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										339
									
								
								vpnserver.go
									
									
									
									
									
								
							
							
						
						
									
										339
									
								
								vpnserver.go
									
									
									
									
									
								
							@ -3,11 +3,12 @@ package main
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bufio"
 | 
						"bufio"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -15,11 +16,11 @@ import (
 | 
				
			|||||||
// Server represents the server
 | 
					// Server represents the server
 | 
				
			||||||
type OpenVpnMgt struct {
 | 
					type OpenVpnMgt struct {
 | 
				
			||||||
	Port             string
 | 
						Port             string
 | 
				
			||||||
	buf              *bufio.ReadWriter
 | 
						buf              map[string]*bufio.ReadWriter
 | 
				
			||||||
	connected        bool
 | 
					 | 
				
			||||||
	m                sync.RWMutex
 | 
						m                sync.RWMutex
 | 
				
			||||||
	ret              chan []string
 | 
						ret              chan []string
 | 
				
			||||||
	ldap             map[string]ldapConfig
 | 
						ldap             map[string]ldapConfig
 | 
				
			||||||
 | 
						clients          map[string]map[int]*vpnSession
 | 
				
			||||||
	authCa           string
 | 
						authCa           string
 | 
				
			||||||
	vpnlogUrl        string
 | 
						vpnlogUrl        string
 | 
				
			||||||
	mailRelay        string
 | 
						mailRelay        string
 | 
				
			||||||
@ -35,9 +36,11 @@ type OpenVpnMgt struct {
 | 
				
			|||||||
// NewServer returns a pointer to a new server
 | 
					// NewServer returns a pointer to a new server
 | 
				
			||||||
func NewVPNServer(port string) *OpenVpnMgt {
 | 
					func NewVPNServer(port string) *OpenVpnMgt {
 | 
				
			||||||
	return &OpenVpnMgt{
 | 
						return &OpenVpnMgt{
 | 
				
			||||||
		Port: port,
 | 
							Port:    port,
 | 
				
			||||||
		ret:  make(chan []string),
 | 
							ret:     make(chan []string),
 | 
				
			||||||
		ldap: make(map[string]ldapConfig),
 | 
							ldap:    make(map[string]ldapConfig),
 | 
				
			||||||
 | 
							buf:     make(map[string]*bufio.ReadWriter),
 | 
				
			||||||
 | 
							clients: make(map[string]map[int]*vpnSession),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -68,129 +71,18 @@ func (s *OpenVpnMgt) Run() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *OpenVpnMgt) TokenPassword(c *vpnSession) (bool, string) {
 | 
					func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string) {
 | 
				
			||||||
	//TODO implement that correcly
 | 
						if len(s.buf) == 0 {
 | 
				
			||||||
	if c.password == "maith1wiePuw3ieb4heiNie5y" {
 | 
					 | 
				
			||||||
		return true, "maith1wiePuw3ieb4heiNie5y"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return false, "maith1wiePuw3ieb4heiNie5y"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 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
 | 
					 | 
				
			||||||
	if c.password == "" {
 | 
					 | 
				
			||||||
		return errors.New("Empty Password"), -1
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// check if the password is a valid token (see TOTP request)
 | 
					 | 
				
			||||||
	tokenPasswordOk, tokenPassword := s.TokenPassword(c)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// password is a token. We remove it from the session object to
 | 
					 | 
				
			||||||
	// avoid checking it against the ldap
 | 
					 | 
				
			||||||
	if tokenPasswordOk {
 | 
					 | 
				
			||||||
		c.password = ""
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// if the otp is not empty, we check it against the valid codes as soon as
 | 
					 | 
				
			||||||
	// possible
 | 
					 | 
				
			||||||
	otpvalidated := false
 | 
					 | 
				
			||||||
	if c.otpCode != "" {
 | 
					 | 
				
			||||||
		codes, err := s.GenerateOTP(c.Login)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err, -2
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		for _, possible := range codes {
 | 
					 | 
				
			||||||
			if possible == c.otpCode {
 | 
					 | 
				
			||||||
				otpvalidated = true
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	c.Profile = ""
 | 
					 | 
				
			||||||
	login := []string{c.Login}
 | 
					 | 
				
			||||||
	pass := c.password
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for {
 | 
					 | 
				
			||||||
		n := c.Profile
 | 
					 | 
				
			||||||
		for k, ldap := range s.ldap {
 | 
					 | 
				
			||||||
			if ldap.upgradeFrom != c.Profile {
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			err, userOk, passOk, secondary := ldap.Auth(login, pass)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// if there is an error, try the other configurations
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				log.Println(err)
 | 
					 | 
				
			||||||
				continue
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// we did find a valid User
 | 
					 | 
				
			||||||
			if userOk {
 | 
					 | 
				
			||||||
				// the login for the new auth level is given by the current one
 | 
					 | 
				
			||||||
				login = secondary
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if c.Mail == "" {
 | 
					 | 
				
			||||||
					c.Mail = secondary[0]
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if passOk && c.Profile != "" {
 | 
					 | 
				
			||||||
					// it's at least the second auth level, and we have a valid
 | 
					 | 
				
			||||||
					// password on 2 different auth system. It's a dupplicate
 | 
					 | 
				
			||||||
					// password, let's log it
 | 
					 | 
				
			||||||
					log.Printf("User %s has a dupplicate password\n", c.Login)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				// we have either a positive auth ok a previous valid one
 | 
					 | 
				
			||||||
				if passOk || c.Profile != "" || tokenPasswordOk {
 | 
					 | 
				
			||||||
					c.Profile = k
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// no profile update this turn, no need to continue
 | 
					 | 
				
			||||||
		if n == c.Profile {
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// no profile validated, we stop here
 | 
					 | 
				
			||||||
	if c.Profile == "" {
 | 
					 | 
				
			||||||
		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) {
 | 
					 | 
				
			||||||
	if !s.connected {
 | 
					 | 
				
			||||||
		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)
 | 
							log.Println(line)
 | 
				
			||||||
		if _, err := s.buf.WriteString(line + "\r\n"); err != nil {
 | 
							if _, err := s.buf[remote].WriteString(line + "\r\n"); err != nil {
 | 
				
			||||||
			return err, nil
 | 
								return err, nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := s.buf.Flush(); err != nil {
 | 
						if err := s.buf[remote].Flush(); err != nil {
 | 
				
			||||||
		return err, nil
 | 
							return err, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -199,30 +91,72 @@ func (s *OpenVpnMgt) sendCommand(msg []string) (error, []string) {
 | 
				
			|||||||
	return nil, ret
 | 
						return nil, ret
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *OpenVpnMgt) Help() (error, []string) {
 | 
					func (s *OpenVpnMgt) Help() (error, map[string]map[string]string) {
 | 
				
			||||||
	err, msg := s.sendCommand([]string{"help"})
 | 
						ret := make(map[string]map[string]string)
 | 
				
			||||||
	if err != nil {
 | 
						re := regexp.MustCompile("^(.*[^ ])  *: (.*)$")
 | 
				
			||||||
		return err, nil
 | 
						for remote := range s.buf {
 | 
				
			||||||
 | 
							help := make(map[string]string)
 | 
				
			||||||
 | 
							err, msg := s.sendCommand([]string{"help"}, remote)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err, ret
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, line := range msg {
 | 
				
			||||||
 | 
								match := re.FindStringSubmatch(line)
 | 
				
			||||||
 | 
								if len(match) == 0 {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								help[match[1]] = match[2]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ret[remote] = help
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil, msg
 | 
						return nil, ret
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *OpenVpnMgt) Version() (error, []string) {
 | 
					func (s *OpenVpnMgt) Version() (error, map[string][]string) {
 | 
				
			||||||
	err, msg := s.sendCommand([]string{"version"})
 | 
						ret := make(map[string][]string)
 | 
				
			||||||
	if err != nil {
 | 
						for remote := range s.buf {
 | 
				
			||||||
		return err, nil
 | 
							err, msg := s.sendCommand([]string{"version"}, remote)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err, ret
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ret[remote] = msg
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil, msg
 | 
						return nil, ret
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *OpenVpnMgt) ClientValidated(line string) {
 | 
					func (s *OpenVpnMgt) ClientValidated(line, remote string) {
 | 
				
			||||||
	//TODO manage that : find the client, log stuff
 | 
						err, c := s.getClient(line, remote)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println(err, line)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	<-s.ret
 | 
						<-s.ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Status = "success"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Println(c)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *OpenVpnMgt) ClientDisconnect(line string) {
 | 
					func (s *OpenVpnMgt) ClientDisconnect(line, remote string) {
 | 
				
			||||||
	//TODO manage that : find the client, log stuff
 | 
						err, c := s.getClient(line, remote)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<-s.ret
 | 
						<-s.ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if the disconnect is due to an auth failure, don't change the status
 | 
				
			||||||
 | 
						if c.Status == "success" {
 | 
				
			||||||
 | 
							c.Operation = "log out"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Don't log the initial auth failure due to absence of OTP code
 | 
				
			||||||
 | 
						if c.Status != "Need OTP Code" {
 | 
				
			||||||
 | 
							c.Log()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer delete(s.clients[remote], c.cID)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *OpenVpnMgt) getIP(c *vpnSession) (string, error) {
 | 
					func (s *OpenVpnMgt) getIP(c *vpnSession) (string, error) {
 | 
				
			||||||
@ -232,124 +166,141 @@ func (s *OpenVpnMgt) getIP(c *vpnSession) (string, error) {
 | 
				
			|||||||
	return ip.String(), nil
 | 
						return ip.String(), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *OpenVpnMgt) ClientConnect(line string) {
 | 
					func (s *OpenVpnMgt) ClientConnect(line, remote string) {
 | 
				
			||||||
	var cmd []string
 | 
					 | 
				
			||||||
	var ip string
 | 
					 | 
				
			||||||
	var errIP error
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	client := NewVPNSession("log in")
 | 
						client := NewVPNSession("log in")
 | 
				
			||||||
 | 
						client.vpnserver = remote
 | 
				
			||||||
	client.ParseSessionId(line)
 | 
						client.ParseSessionId(line)
 | 
				
			||||||
 | 
						s.clients[remote][client.cID] = client
 | 
				
			||||||
	infos := <-s.ret
 | 
						infos := <-s.ret
 | 
				
			||||||
	if err := client.ParseEnv(&infos); err != nil {
 | 
						if err := client.ParseEnv(&infos); err != nil {
 | 
				
			||||||
		log.Println(err)
 | 
							log.Println(err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err, ok := s.Auth(client)
 | 
						client.Auth(s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// if auth is ok, time to get an IP address
 | 
					// find a client among all registered sessions
 | 
				
			||||||
	if ok == 0 {
 | 
					func (s *OpenVpnMgt) getClient(line, remote string) (error, *vpnSession) {
 | 
				
			||||||
		ip, errIP = s.getIP(client)
 | 
						re := regexp.MustCompile("^[^0-9]*,([0-9]+)[^0-9]*")
 | 
				
			||||||
		if errIP != nil {
 | 
						match := re.FindStringSubmatch(line)
 | 
				
			||||||
			ok = -10
 | 
						if len(match) == 0 {
 | 
				
			||||||
			err = errIP
 | 
							return errors.New("invalid message"), nil
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						id, err := strconv.Atoi(match[1])
 | 
				
			||||||
	switch {
 | 
						if err != nil {
 | 
				
			||||||
	case ok == 0:
 | 
							return err, nil
 | 
				
			||||||
		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 _, ok := s.clients[remote]; !ok {
 | 
				
			||||||
	if err, _ := s.sendCommand(cmd); err != nil {
 | 
							return errors.New("unknown vpn server"), nil
 | 
				
			||||||
		log.Println(err)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if c, ok := s.clients[remote][id]; ok {
 | 
				
			||||||
	return
 | 
							return nil, c
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return errors.New("unknown vpn client"), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *OpenVpnMgt) handleConn(conn net.Conn) {
 | 
					func (s *OpenVpnMgt) handleConn(conn net.Conn) {
 | 
				
			||||||
	defer conn.Close()
 | 
						remote := conn.RemoteAddr().String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// we don't want multiple connexions, only one openvpn server at a time
 | 
						defer conn.Close()
 | 
				
			||||||
	s.m.Lock()
 | 
						defer delete(s.buf, remote)
 | 
				
			||||||
	if s.connected {
 | 
						defer delete(s.clients, remote)
 | 
				
			||||||
		conn.Write([]byte("Sorry, only one server allowed\n"))
 | 
					 | 
				
			||||||
		s.m.Unlock()
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	s.connected = true
 | 
					 | 
				
			||||||
	s.m.Unlock()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// we store the buffer pointer in the struct, to be accessed from other methods
 | 
						// we store the buffer pointer in the struct, to be accessed from other methods
 | 
				
			||||||
	s.buf = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
 | 
						s.buf[remote] = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
 | 
				
			||||||
 | 
						s.clients[remote] = make(map[int]*vpnSession)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// most response are multilined, use response to concatenate them
 | 
						// most response are multilined, use response to concatenate them
 | 
				
			||||||
	response := []string{}
 | 
						response := []string{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// remove bogus clients
 | 
				
			||||||
 | 
						line, err := s.buf[remote].ReadString('\n')
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if line != ">INFO:OpenVPN Management Interface Version 1 -- type 'help' for more info\r\n" {
 | 
				
			||||||
 | 
							log.Println("Bogus Client")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ask for statistics
 | 
				
			||||||
 | 
						if _, err := s.buf[remote].WriteString("bytecount 10\r\n"); err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := s.buf[remote].Flush(); err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if line, err := s.buf[remote].ReadString('\n'); err != nil ||
 | 
				
			||||||
 | 
							line != "SUCCESS: bytecount interval changed\r\n" {
 | 
				
			||||||
 | 
							log.Println("Bogus Client")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Println("Valid openvpn connected from %s", remote)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		line, err := s.buf.ReadString('\n')
 | 
							line, err := s.buf[remote].ReadString('\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// manage basic errors
 | 
							// manage basic errors
 | 
				
			||||||
		switch {
 | 
							switch {
 | 
				
			||||||
		case err == io.EOF:
 | 
							case err == io.EOF:
 | 
				
			||||||
			log.Println("Reached EOF - close this connection.\n")
 | 
								log.Println("Reached EOF - close this connection.\n")
 | 
				
			||||||
			s.connected = false
 | 
					 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		case err != nil:
 | 
							case err != nil:
 | 
				
			||||||
			log.Println("Error reading line. Got: '"+line+"'\n", err)
 | 
								log.Println("Error reading line. Got: '"+line+"'\n", err)
 | 
				
			||||||
			s.connected = false
 | 
					 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		line = strings.Trim(line, "\n\r ")
 | 
							line = strings.Trim(line, "\n\r ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// manage exit commands
 | 
				
			||||||
 | 
							for _, terminator := range []string{"quit", "exit"} {
 | 
				
			||||||
 | 
								if line == terminator || strings.HasPrefix(line, terminator+" ") {
 | 
				
			||||||
 | 
									log.Println("server disconnected")
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// manage all "terminator" lines
 | 
							// manage all "terminator" lines
 | 
				
			||||||
		for _, terminator := range []string{"END", ">CLIENT:ENV,END", "SUCCESS"} {
 | 
							for _, terminator := range []string{"END", ">CLIENT:ENV,END", "SUCCESS"} {
 | 
				
			||||||
			if strings.HasPrefix(line, terminator) {
 | 
								if strings.HasPrefix(line, terminator) {
 | 
				
			||||||
				s.ret <- response
 | 
									s.ret <- response
 | 
				
			||||||
				response = nil
 | 
									response = nil
 | 
				
			||||||
 | 
									line = ""
 | 
				
			||||||
				break
 | 
									break
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch {
 | 
							switch {
 | 
				
			||||||
		// a new openvpn server is connected
 | 
							// command successfull, we can ignore
 | 
				
			||||||
		case strings.HasPrefix(line, ">INFO"):
 | 
					 | 
				
			||||||
		// command sucessfull, we can ignore
 | 
					 | 
				
			||||||
		case strings.HasPrefix(line, ">SUCCESS: client-deny command succeeded"):
 | 
							case strings.HasPrefix(line, ">SUCCESS: client-deny command succeeded"):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// trafic stats
 | 
				
			||||||
 | 
							case strings.HasPrefix(line, ">BYTECOUNT_CLI"):
 | 
				
			||||||
 | 
								//TODO use that
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// new bloc for a disconnect event.
 | 
							// new bloc for a disconnect 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:DISCONNECT"):
 | 
							case strings.HasPrefix(line, ">CLIENT:DISCONNECT"):
 | 
				
			||||||
			go s.ClientDisconnect(line)
 | 
								go s.ClientDisconnect(line, remote)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 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"):
 | 
							case strings.HasPrefix(line, ">CLIENT:ADDRESS"):
 | 
				
			||||||
			go s.ClientValidated(line)
 | 
								go s.ClientValidated(line, remote)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case strings.HasPrefix(line, ">CLIENT:CONNECT"):
 | 
							case strings.HasPrefix(line, ">CLIENT:CONNECT"):
 | 
				
			||||||
			go s.ClientConnect(line)
 | 
								go s.ClientConnect(line, remote)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		default:
 | 
							default:
 | 
				
			||||||
			response = append(response, line)
 | 
								response = append(response, line)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		log.Print(line)
 | 
							// TODO remove this
 | 
				
			||||||
 | 
							if strings.Index(line, "password") == -1 {
 | 
				
			||||||
 | 
								log.Print(line)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										180
									
								
								vpnsession.go
									
									
									
									
									
								
							
							
						
						
									
										180
									
								
								vpnsession.go
									
									
									
									
									
								
							@ -3,6 +3,9 @@ package main
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/base64"
 | 
						"encoding/base64"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
@ -33,6 +36,7 @@ type vpnSession struct {
 | 
				
			|||||||
	password    string    `json:"-"`
 | 
						password    string    `json:"-"`
 | 
				
			||||||
	otpCode     string    `json:"-"`
 | 
						otpCode     string    `json:"-"`
 | 
				
			||||||
	localIP     string    `json:"-"`
 | 
						localIP     string    `json:"-"`
 | 
				
			||||||
 | 
						vpnserver   string    `json:"-"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewVPNSession(operation string) *vpnSession {
 | 
					func NewVPNSession(operation string) *vpnSession {
 | 
				
			||||||
@ -46,6 +50,13 @@ func NewVPNSession(operation string) *vpnSession {
 | 
				
			|||||||
	return &v
 | 
						return &v
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *vpnSession) String() string {
 | 
				
			||||||
 | 
						if res, err := json.MarshalIndent(c, " ", "  "); err == nil {
 | 
				
			||||||
 | 
							return string(res)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *vpnSession) b64Login() string {
 | 
					func (c *vpnSession) b64Login() string {
 | 
				
			||||||
	return base64.StdEncoding.EncodeToString([]byte(c.Login))
 | 
						return base64.StdEncoding.EncodeToString([]byte(c.Login))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -92,6 +103,9 @@ func (c *vpnSession) ParseEnv(infos *[]string) error {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
				c.password = split[2]
 | 
									c.password = split[2]
 | 
				
			||||||
				c.otpCode = split[4]
 | 
									c.otpCode = split[4]
 | 
				
			||||||
 | 
									if c.otpCode == "" {
 | 
				
			||||||
 | 
										c.otpCode = "***"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			case strings.HasPrefix(p[1], "SCRV1"):
 | 
								case strings.HasPrefix(p[1], "SCRV1"):
 | 
				
			||||||
				split := strings.Split(p[1], ":")
 | 
									split := strings.Split(p[1], ":")
 | 
				
			||||||
@ -106,13 +120,18 @@ func (c *vpnSession) ParseEnv(infos *[]string) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				data, err = base64.StdEncoding.DecodeString(split[2])
 | 
									data, err = base64.StdEncoding.DecodeString(split[2])
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
 | 
										c.password = p[1]
 | 
				
			||||||
					break
 | 
										break
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				c.otpCode = string(data)
 | 
									c.otpCode = string(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if c.otpCode == "" {
 | 
				
			||||||
 | 
										c.otpCode = "***"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			default:
 | 
								default:
 | 
				
			||||||
				c.password = p[1]
 | 
									c.password = p[1]
 | 
				
			||||||
				c.otpCode = "***"
 | 
									c.otpCode = ""
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		case "username":
 | 
							case "username":
 | 
				
			||||||
@ -124,9 +143,160 @@ func (c *vpnSession) ParseEnv(infos *[]string) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *vpnSession) String() string {
 | 
					func (c *vpnSession) Auth(s *OpenVpnMgt) {
 | 
				
			||||||
	if res, err := json.MarshalIndent(c, " ", "  "); err == nil {
 | 
						var cmd []string
 | 
				
			||||||
		return string(res)
 | 
						var ip string
 | 
				
			||||||
 | 
						var errIP error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err, ok := c.auth(s)
 | 
				
			||||||
 | 
						// if auth is ok, time to get an IP address
 | 
				
			||||||
 | 
						if ok == 0 {
 | 
				
			||||||
 | 
							ip, errIP = s.getIP(c)
 | 
				
			||||||
 | 
							if errIP != nil {
 | 
				
			||||||
 | 
								ok = -10
 | 
				
			||||||
 | 
								err = errIP
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return ""
 | 
					
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case ok == 0:
 | 
				
			||||||
 | 
							cmd = []string{
 | 
				
			||||||
 | 
								fmt.Sprintf("client-auth %d %d", c.cID, c.kID),
 | 
				
			||||||
 | 
								fmt.Sprintf("ifconfig-push %s %s", ip, c.localIP),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, r := range s.ldap[c.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\"",
 | 
				
			||||||
 | 
								c.cID, c.kID, err, err)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case ok == 1:
 | 
				
			||||||
 | 
							cmd = []string{fmt.Sprintf(
 | 
				
			||||||
 | 
								"client-deny %d %d \"Need OTP\" \"CRV1:R,E:%s:%s:OTP Code \"",
 | 
				
			||||||
 | 
								c.cID, c.kID, c.password, c.b64Login())}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err, _ := s.sendCommand(cmd, c.vpnserver); err != nil {
 | 
				
			||||||
 | 
							log.Println(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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 (c *vpnSession) auth(s *OpenVpnMgt) (error, int) {
 | 
				
			||||||
 | 
						// an empty password is not good
 | 
				
			||||||
 | 
						if c.password == "" {
 | 
				
			||||||
 | 
							c.Status = "Empty Password"
 | 
				
			||||||
 | 
							return errors.New("Empty Password"), -1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check if the password is a valid token (see TOTP request)
 | 
				
			||||||
 | 
						tokenPasswordOk, tokenPassword := s.TokenPassword(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// password is a token. We remove it from the session object to
 | 
				
			||||||
 | 
						// avoid checking it against the ldap
 | 
				
			||||||
 | 
						if tokenPasswordOk {
 | 
				
			||||||
 | 
							c.password = ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if the otp is not empty, we check it against the valid codes as soon as
 | 
				
			||||||
 | 
						// possible
 | 
				
			||||||
 | 
						otpvalidated := false
 | 
				
			||||||
 | 
						if c.otpCode != "" {
 | 
				
			||||||
 | 
							codes, err := s.GenerateOTP(c.Login)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err, -2
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, possible := range codes {
 | 
				
			||||||
 | 
								if possible == c.otpCode {
 | 
				
			||||||
 | 
									otpvalidated = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Profile = ""
 | 
				
			||||||
 | 
						login := []string{c.Login}
 | 
				
			||||||
 | 
						pass := c.password
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							n := c.Profile
 | 
				
			||||||
 | 
							for k, ldap := range s.ldap {
 | 
				
			||||||
 | 
								if ldap.upgradeFrom != c.Profile {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								err, userOk, passOk, secondary := ldap.Auth(login, pass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// if there is an error, try the other configurations
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Println(err)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// we did find a valid User
 | 
				
			||||||
 | 
								if userOk {
 | 
				
			||||||
 | 
									// the login for the new auth level is given by the current one
 | 
				
			||||||
 | 
									login = secondary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if c.Mail == "" {
 | 
				
			||||||
 | 
										c.Mail = secondary[0]
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if passOk && c.Profile != "" {
 | 
				
			||||||
 | 
										// it's at least the second auth level, and we have a valid
 | 
				
			||||||
 | 
										// password on 2 different auth system. It's a dupplicate
 | 
				
			||||||
 | 
										// password, let's log it
 | 
				
			||||||
 | 
										log.Printf("User %s has a dupplicate password\n", c.Login)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// we have either a positive auth ok a previous valid one
 | 
				
			||||||
 | 
									if passOk || c.Profile != "" || tokenPasswordOk {
 | 
				
			||||||
 | 
										c.Profile = k
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// no profile update this turn, no need to continue
 | 
				
			||||||
 | 
							if n == c.Profile {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// no profile validated, we stop here
 | 
				
			||||||
 | 
						if c.Profile == "" {
 | 
				
			||||||
 | 
							c.Status = "fail (password)"
 | 
				
			||||||
 | 
							return errors.New("Authentication Failed"), -3
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check the MFA requested by the secured profile
 | 
				
			||||||
 | 
						c.TwoFA = true
 | 
				
			||||||
 | 
						switch s.ldap[c.Profile].mfaType {
 | 
				
			||||||
 | 
						case "internal":
 | 
				
			||||||
 | 
							if otpvalidated {
 | 
				
			||||||
 | 
								return nil, 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// log that the failure is due to the OTP
 | 
				
			||||||
 | 
							if c.otpCode == "" {
 | 
				
			||||||
 | 
								c.Status = "Need OTP Code"
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								c.Status = "fail (OTP) : "
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.password = tokenPassword
 | 
				
			||||||
 | 
							return errors.New("Need OTP Code"), 1
 | 
				
			||||||
 | 
						case "okta":
 | 
				
			||||||
 | 
							//TODO implement okta MFA
 | 
				
			||||||
 | 
							c.Status = "fail (Okta)"
 | 
				
			||||||
 | 
							return nil, -4
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							c.TwoFA = false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// no MFA requested, the login is valid
 | 
				
			||||||
 | 
						return nil, 0
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user