Add logging, including the json one

get infos from I've been pwned and the API on install.dm.gg/vpn-log.php

and send mail if there is anything strange
This commit is contained in:
Xavier Henner 2019-07-10 17:47:43 +02:00
parent 44cfdea6ed
commit 68de442333
14 changed files with 612 additions and 55 deletions

2
go.mod
View File

@ -1,6 +1,8 @@
module gitlab.dm.gg/vwf/openvpn-dm-mgt-server module gitlab.dm.gg/vwf/openvpn-dm-mgt-server
require ( require (
github.com/mattevans/pwned-passwords v0.0.0-20190611210716-1da592be4a34
github.com/onsi/gomega v1.5.0 // indirect
github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9 github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/ldap.v2 v2.5.1 gopkg.in/ldap.v2 v2.5.1

28
go.sum
View File

@ -1,6 +1,34 @@
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/mattevans/pwned-passwords v0.0.0-20190611210716-1da592be4a34 h1:cl/axA6OJFqTmZ573VUw9TTQ6/vbb+DIBFNFNPRFNaw=
github.com/mattevans/pwned-passwords v0.0.0-20190611210716-1da592be4a34/go.mod h1:lTBNMS5Uc86U2A2Cps0gWoo091FA0YH5FMXhTvL0ZPI=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9 h1:H1vjQ+Mfc8dFAOTuF541/tScdKoynzll9iKuWgaLLxM= github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9 h1:H1vjQ+Mfc8dFAOTuF541/tScdKoynzll9iKuWgaLLxM=
github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9/go.mod h1:0XGrzgrEp0fa/+JSV8XZePUwyjnU6C3bMc7Xz2bHHKI= github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9/go.mod h1:0XGrzgrEp0fa/+JSV8XZePUwyjnU6C3bMc7Xz2bHHKI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU= gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk= gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

11
ldap.go
View File

@ -111,8 +111,7 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
return err, false, false, nil return err, false, false, nil
} }
if len(sr.Entries) != 1 { if len(sr.Entries) != 1 {
log.Println("User does not exist or too many entries returned") return errors.New("User does not exist or too many entries returned"), false, false, nil
return nil, false, false, nil
} }
// check the attributes requested in the search // check the attributes requested in the search
@ -128,12 +127,12 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
// user must have both primary and secondary attributes // user must have both primary and secondary attributes
if len(primary) == 0 { if len(primary) == 0 {
log.Printf("User has no %s attribute", conf.primaryAttribute) log.Printf("User %s has no %s attribute", logins[0], conf.primaryAttribute)
return nil, false, false, nil return nil, false, false, nil
} }
if len(secondary) == 0 { if len(secondary) == 0 {
log.Printf("User has no %s attribute", conf.secondaryAttribute) log.Printf("User %s has no %s attribute", logins[0], conf.secondaryAttribute)
return nil, false, false, nil return nil, false, false, nil
} }
@ -150,7 +149,7 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
attributes = secondary attributes = secondary
} }
log.Printf("User has a valid account on %s", s) log.Printf("User %s has a valid account on %s", logins[0], s)
userdn := sr.Entries[0].DN userdn := sr.Entries[0].DN
@ -165,7 +164,7 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
} }
// everything is fine, // everything is fine,
log.Printf("User has a valid password on %s", s) log.Printf("User %s has a valid password on %s", logins[0], s)
return nil, true, true, attributes return nil, true, true, attributes
} }
// if we are here, no server is responding, rejectif auth // if we are here, no server is responding, rejectif auth

144
logs.go
View File

@ -1,11 +1,149 @@
package main package main
import ( import (
"bytes"
"encoding/json"
"io/ioutil"
"log" "log"
"net/http"
"net/smtp"
"text/template"
"time"
) )
func (c *vpnSession) Log() error { func (c *vpnSession) LogPrintln(v ...interface{}) {
//TODO get asname & shit log.Println(c.Login, c.IP, v)
log.Println(c) }
func (s *OpenVpnMgt) Log(c *vpnSession) error {
if s.vpnlogUrl != "" {
if err := c.getASInfos(s.vpnlogUrl); err != nil {
log.Println(err)
}
}
jsonStr, err := json.Marshal(c)
if err != nil {
return err
}
log.Println(string(jsonStr))
if err := s.SendMail(c); err != nil {
log.Println(err)
}
return nil
}
func (c *vpnSession) getASInfos(vpnlogUrl string) error {
jsonStr, err := json.Marshal(c)
if err != nil {
return err
}
req, err := http.NewRequest("POST", vpnlogUrl, bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
timeout := time.Duration(3 * time.Second)
client := http.Client{
Timeout: timeout,
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(body, c)
if err != nil {
return err
}
return nil
}
func (s *OpenVpnMgt) MailTemplate(c *vpnSession) error {
var buf1 bytes.Buffer
var buf2 bytes.Buffer
tmpl, err := template.New("pwnTemplate").Parse(s.pwnTemplate)
if err != nil {
return err
}
if err := tmpl.Execute(&buf1, c); err != nil {
return err
}
c.pwnMail = buf1.String()
tmpl, err = template.New("newAsTemplate").Parse(s.newAsTemplate)
if err != nil {
return err
}
if err := tmpl.Execute(&buf2, c); err != nil {
return err
}
c.newAsMail = buf2.String()
return nil
}
func (s *OpenVpnMgt) SendMail(c *vpnSession) error {
if c.Mail == "" {
return nil
}
if (s.newAsTemplate == "" || !c.NewAS) &&
(s.pwnTemplate == "" || !c.PwnedPasswd) {
// can not send mail without template or cause
return nil
}
// needed for the templating
c.MailFrom = s.MailFrom
c.CcPwnPassword = s.CcPwnPassword
// complete the templates
if err := s.MailTemplate(c); err != nil {
return err
}
mail, err := smtp.Dial(s.mailRelay)
if err != nil {
return err
}
defer mail.Close()
if c.PwnedPasswd {
mail.Mail(s.MailFrom)
mail.Rcpt(c.Mail)
if c.TooMuchPwn && s.CcPwnPassword != "" {
mail.Rcpt(s.CcPwnPassword)
}
wc, err := mail.Data()
if err != nil {
return nil
}
defer wc.Close()
buf := bytes.NewBufferString(c.pwnMail)
if _, err = buf.WriteTo(wc); err != nil {
return err
}
wc.Close()
}
if c.NewAS {
mail.Mail(s.MailFrom)
mail.Rcpt(c.Mail)
wc, err := mail.Data()
if err != nil {
return nil
}
defer wc.Close()
buf := bytes.NewBufferString(c.newAsMail)
if _, err = buf.WriteTo(wc); err != nil {
return err
}
wc.Close()
}
return nil return nil
} }

View File

@ -42,7 +42,7 @@ func main() {
if *logToSyslog { if *logToSyslog {
log.SetFlags(0) log.SetFlags(0)
server.syslog = true server.syslog = true
logWriter, e := syslog.New(syslog.LOG_NOTICE, "") logWriter, e := syslog.New(syslog.LOG_NOTICE, "vpnauth")
if e == nil { if e == nil {
log.SetOutput(logWriter) log.SetOutput(logWriter)
defer logWriter.Close() defer logWriter.Close()

View File

@ -0,0 +1,5 @@
sudo: false
language: go
go:
- 1.11.x
- tip

19
vendor/github.com/mattevans/pwned-passwords/LICENSE generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (C) 2018 by Matt Evans
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

62
vendor/github.com/mattevans/pwned-passwords/README.md generated vendored Normal file
View File

@ -0,0 +1,62 @@
# pwned-passwords
[![GoDoc](https://godoc.org/github.com/mattevans/pwned-passwords?status.svg)](https://godoc.org/github.com/mattevans/pwned-passwords)
[![Build Status](https://travis-ci.org/mattevans/pwned-passwords.svg?branch=master)](https://travis-ci.org/mattevans/pwned-passwords)
[![Go Report Card](https://goreportcard.com/badge/github.com/mattevans/pwned-passwords)](https://goreportcard.com/report/github.com/mattevans/pwned-passwords)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/mattevans/pwned-passwords/blob/master/LICENSE)
A simple [Go](http://golang.org) client library for checking compromised passwords against [HIBP Pwned Passwords](https://haveibeenpwned.com/Passwords).
Upon request, results will be cached (in-memory), keyed by hash. With a two hour expiry window, subsequent requests will use cached data or fetch fresh data accordingly.
Installation
-----------------
`go get -u github.com/mattevans/pwned-passwords`
Usage
-----------------
```go
package main
import (
"fmt"
hibp "github.com/mattevans/pwned-passwords"
"os"
)
func main() {
// Init a client.
client := hibp.NewClient()
// Check to see if your given string is compromised.
pwned, err := client.Pwned.Compromised("string to check")
if err != nil {
fmt.Println("Pwned failed")
os.Exit(1)
}
if pwned {
// Oh dear!
// You should avoid using that password
} else {
// Woo!
// All clear!
}
}
```
**Expire in-memory cache**
```go
client.Cache.Expire(HASHED_VALUE)
```
```go
client.Cache.ExpireAll()
```
Contributing
-----------------
If you've found a bug or would like to contribute, please create an issue here on GitHub, or better yet fork the project and submit a pull request!

65
vendor/github.com/mattevans/pwned-passwords/cache.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package hibp
import "time"
// cache holds our our cached hash/compromised pairs results.
var cache map[string]*PwnedStore
// cacheTTL stores the time to live of our cache (2 hours).
var cacheTTL = 2 * time.Hour
// CacheService handles in-memory caching of our hash/compromised pairs.
type CacheService service
// Get will return our stored in-memory hash/compromised pairs, if we have them.
func (s *CacheService) Get(hash string) *PwnedStore {
// Is our cache expired?
if s.IsExpired(hash) {
return nil
}
// Use stored results.
return cache[hash]
}
// Store will save our hash/compromised pairs to a PwnedStore.
func (s *CacheService) Store(hash string, compromised bool) {
// No cache? Initialize it.
if cache == nil {
cache = map[string]*PwnedStore{}
}
// Store
tn := time.Now()
cache[hash] = &PwnedStore{
Hash: hash,
Compromised: compromised,
UpdatedAt: &tn,
}
}
// IsExpired checks if we have cached hash and that it isn't expired.
func (s *CacheService) IsExpired(hash string) bool {
// No cache? bail.
if cache[hash] == nil {
return true
}
// Expired cache? bail.
lastUpdated := cache[hash].UpdatedAt
if lastUpdated != nil && lastUpdated.Add(cacheTTL).Before(time.Now()) {
return true
}
return false
}
// Expire will expire the cache for a given hash.
func (s *CacheService) Expire(hash string) {
cache[hash] = nil
}
// ExpireAll will expire all cache.
func (s *CacheService) ExpireAll() {
cache = nil
}

115
vendor/github.com/mattevans/pwned-passwords/hibp.go generated vendored Normal file
View File

@ -0,0 +1,115 @@
package hibp
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
const (
packageVersion = "0.0.1"
backendURL = "https://api.pwnedpasswords.com"
userAgent = "pwned-passwords-golang/" + packageVersion
)
// Client holds a connection to the HIBP API.
type Client struct {
client *http.Client
AppID string
UserAgent string
BackendURL *url.URL
// Services used for communicating with the API.
Pwned *PwnedService
Cache *CacheService
}
type service struct {
client *Client
}
// NewClient creates a new Client with the appropriate connection details and
// services used for communicating with the API.
func NewClient() *Client {
// Init new http.Client.
httpClient := http.DefaultClient
// Parse BE URL.
baseURL, _ := url.Parse(backendURL)
c := &Client{
client: httpClient,
BackendURL: baseURL,
UserAgent: userAgent,
}
c.Pwned = &PwnedService{client: c}
c.Cache = &CacheService{client: c}
return c
}
// NewRequest creates an API request. A relative URL can be provided in urlPath,
// which will be resolved to the BackendURL of the Client.
func (c *Client) NewRequest(method, urlPath string, body interface{}) (*http.Request, error) {
// Parse our URL.
rel, err := url.Parse(urlPath)
if err != nil {
return nil, err
}
// Resolve to absolute URI.
u := c.BackendURL.ResolveReference(rel)
buf := new(bytes.Buffer)
if body != nil {
err = json.NewEncoder(buf).Encode(body)
if err != nil {
return nil, err
}
}
// Create the request.
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, err
}
// Add our packages UA.
req.Header.Add("User-Agent", c.UserAgent)
return req, nil
}
// Do sends an API request and returns the API response.
func (c *Client) Do(req *http.Request) ([]string, error) {
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer func() {
if rerr := resp.Body.Close(); err == nil {
err = rerr
}
}()
// Error if anything else but 200.
// The API should always return a 200 (unless something is wrong) as per
// https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Unexpected API response status: %v", resp.StatusCode)
}
// Parse our resp.Body.
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// Response is returned as new-line'd string, split and return.
return strings.Split(string(body), "\r\n"), err
}

89
vendor/github.com/mattevans/pwned-passwords/pwned.go generated vendored Normal file
View File

@ -0,0 +1,89 @@
package hibp
import (
"crypto/sha1"
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"
"time"
)
// PwnedService handles retrieving pwned hashes from in-memory cache or
// by fetching fresh results.
type PwnedService service
// PwnedStore holds our pwned password hashes and compromised status.
type PwnedStore struct {
Hash string `json:"hash"`
Compromised bool `json:"compromised"`
UpdatedAt *time.Time `json:"updated_at"`
}
// Compromised will build and execute a request to HIBP to check to see
// if the passed value is compromised or not.
func (s *PwnedService) Compromised(value string) (bool, error) {
var err error
// Our value being checked is empty, we don't want that.
if value == "" {
return false, errors.New("Value for compromised check cannot be empty")
}
// SHA-1 hash our input value.
hashedStr := _hashString(value)
// If we have cached results, use them.
cache := s.client.Cache.Get(hashedStr)
if cache != nil {
hashedStr = cache.Hash
return cache.Compromised, err
}
// Pop our prefix and suffix.
prefix := strings.ToUpper(hashedStr[:5])
suffix := strings.ToUpper(hashedStr[5:])
// Build request.
request, err := s.client.NewRequest("GET", fmt.Sprintf("range/%s", prefix), nil)
if err != nil {
return false, err
}
// Make request.
response, err := s.client.Do(request)
if err != nil {
return false, err
}
// Range our response ([]string).
for _, target := range response {
// If our target, minus the compromised count matches our suffix.
if string(target[:35]) == suffix {
_, err = strconv.ParseInt(target[36:], 10, 64)
if err != nil {
return false, err
}
// Store in cache as compromised.
s.client.Cache.Store(hashedStr, true)
// Return.
return true, err
}
}
// Store in cache as non-compromised.
s.client.Cache.Store(hashedStr, false)
// Return.
return false, err
}
// _hashString will return a sha1 hash of the given value.
func _hashString(value string) string {
alg := sha1.New()
alg.Write([]byte(value))
return strings.ToUpper(hex.EncodeToString(alg.Sum(nil)))
}

2
vendor/modules.txt vendored
View File

@ -1,3 +1,5 @@
# github.com/mattevans/pwned-passwords v0.0.0-20190611210716-1da592be4a34
github.com/mattevans/pwned-passwords
# github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9 # github.com/pyke369/golang-support v0.0.0-20190703174728-34ca97aa79e9
github.com/pyke369/golang-support/uconfig github.com/pyke369/golang-support/uconfig
github.com/pyke369/golang-support/rcache github.com/pyke369/golang-support/rcache

View File

@ -71,12 +71,12 @@ func (s *OpenVpnMgt) Run() {
} }
} }
// send a command to the server. Set the channel to receive the response
func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string) { func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string) {
if len(s.buf) == 0 { if len(s.buf) == 0 {
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[remote].WriteString(line + "\r\n"); err != nil { if _, err := s.buf[remote].WriteString(line + "\r\n"); err != nil {
return err, nil return err, nil
} }
@ -91,6 +91,7 @@ func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string)
return nil, ret return nil, ret
} }
// send the help command on all vpn servers. Kind of useless
func (s *OpenVpnMgt) Help() (error, map[string]map[string]string) { func (s *OpenVpnMgt) Help() (error, map[string]map[string]string) {
ret := make(map[string]map[string]string) ret := make(map[string]map[string]string)
re := regexp.MustCompile("^(.*[^ ]) *: (.*)$") re := regexp.MustCompile("^(.*[^ ]) *: (.*)$")
@ -112,6 +113,7 @@ func (s *OpenVpnMgt) Help() (error, map[string]map[string]string) {
return nil, ret return nil, ret
} }
// send the verson command on all vpn servers. Kind of useless
func (s *OpenVpnMgt) Version() (error, map[string][]string) { func (s *OpenVpnMgt) Version() (error, map[string][]string) {
ret := make(map[string][]string) ret := make(map[string][]string)
for remote := range s.buf { for remote := range s.buf {
@ -124,20 +126,34 @@ func (s *OpenVpnMgt) Version() (error, map[string][]string) {
return nil, ret return nil, ret
} }
// internal DHCP
func (s *OpenVpnMgt) getIP(c *vpnSession) (string, error) {
// TODO implement
ip := s.ldap[c.Profile].ipMin
return ip.String(), nil
}
// called after a client is confirmed connected and authenticated
func (s *OpenVpnMgt) ClientValidated(line, remote string) { func (s *OpenVpnMgt) ClientValidated(line, remote string) {
err, c := s.getClient(line, remote) err, c := s.getClient(line, remote)
if err != nil { if err != nil {
log.Println(err, line) log.Println(err, line)
return return
} }
<-s.ret
c.Status = "success" c.Status = "success"
infos := <-s.ret
log.Println(c) if err := c.ParseEnv(&infos); err != nil {
log.Println(err)
} }
s.Log(c)
}
// called after a client is disconnected, including for auth issues
func (s *OpenVpnMgt) ClientDisconnect(line, remote string) { func (s *OpenVpnMgt) ClientDisconnect(line, remote string) {
//TODO free the IP
err, c := s.getClient(line, remote) err, c := s.getClient(line, remote)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -153,31 +169,25 @@ func (s *OpenVpnMgt) ClientDisconnect(line, remote string) {
// Don't log the initial auth failure due to absence of OTP code // Don't log the initial auth failure due to absence of OTP code
if c.Status != "Need OTP Code" { if c.Status != "Need OTP Code" {
c.Log() s.Log(c)
} }
defer delete(s.clients[remote], c.cID) defer delete(s.clients[remote], c.cID)
} }
func (s *OpenVpnMgt) getIP(c *vpnSession) (string, error) { // called at the initial connexion
// TODO implement
ip := s.ldap[c.Profile].ipMin
return ip.String(), nil
}
func (s *OpenVpnMgt) ClientConnect(line, remote string) { func (s *OpenVpnMgt) ClientConnect(line, remote string) {
client := NewVPNSession("log in") c := NewVPNSession("log in")
client.vpnserver = remote c.vpnserver = remote
client.ParseSessionId(line) c.ParseSessionId(line)
s.clients[remote][client.cID] = client s.clients[remote][c.cID] = c
infos := <-s.ret infos := <-s.ret
if err := client.ParseEnv(&infos); err != nil { if err := c.ParseEnv(&infos); err != nil {
log.Println(err) log.Println(err)
return return
} }
client.Auth(s) c.Auth(s)
} }
// find a client among all registered sessions // find a client among all registered sessions
@ -200,6 +210,7 @@ func (s *OpenVpnMgt) getClient(line, remote string) (error, *vpnSession) {
return errors.New("unknown vpn client"), nil return errors.New("unknown vpn client"), nil
} }
// main loop for a given openvpn server
func (s *OpenVpnMgt) handleConn(conn net.Conn) { func (s *OpenVpnMgt) handleConn(conn net.Conn) {
remote := conn.RemoteAddr().String() remote := conn.RemoteAddr().String()
@ -207,6 +218,8 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
defer delete(s.buf, remote) defer delete(s.buf, remote)
defer delete(s.clients, remote) defer delete(s.clients, remote)
// TODO : free all IPs if disconnected
// 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[remote] = 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) s.clients[remote] = make(map[int]*vpnSession)
@ -240,7 +253,7 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
return return
} }
log.Println("Valid openvpn connected from %s", remote) log.Printf("Valid openvpn connected from %s\n", remote)
for { for {
line, err := s.buf[remote].ReadString('\n') line, err := s.buf[remote].ReadString('\n')
@ -299,7 +312,7 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
response = append(response, line) response = append(response, line)
} }
// TODO remove this // TODO remove this
if strings.Index(line, "password") == -1 { if false && strings.Index(line, "password") == -1 {
log.Print(line) log.Print(line)
} }
} }

View File

@ -11,6 +11,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
hibp "github.com/mattevans/pwned-passwords"
) )
type vpnSession struct { type vpnSession struct {
@ -37,6 +39,10 @@ type vpnSession struct {
otpCode string `json:"-"` otpCode string `json:"-"`
localIP string `json:"-"` localIP string `json:"-"`
vpnserver string `json:"-"` vpnserver string `json:"-"`
pwnMail string `json:"-"`
newAsMail string `json:"-"`
MailFrom string `json:"-"`
CcPwnPassword string `json:"-"`
} }
func NewVPNSession(operation string) *vpnSession { func NewVPNSession(operation string) *vpnSession {
@ -73,6 +79,16 @@ func (c *vpnSession) ParseSessionId(line string) error {
return nil return nil
} }
func (c *vpnSession) CheckPwn(password string) error {
client := hibp.NewClient()
pwned, err := client.Pwned.Compromised(password)
if err != nil {
return err
}
c.PwnedPasswd = pwned
return nil
}
func (c *vpnSession) ParseEnv(infos *[]string) error { func (c *vpnSession) ParseEnv(infos *[]string) error {
var err error var err error
r := regexp.MustCompile("[^a-zA-Z0-9./_@-]") r := regexp.MustCompile("[^a-zA-Z0-9./_@-]")
@ -92,6 +108,8 @@ func (c *vpnSession) ParseEnv(infos *[]string) error {
c.IP = r.ReplaceAllString(p[1], "") c.IP = r.ReplaceAllString(p[1], "")
case "untrusted_ip": case "untrusted_ip":
c.IP = r.ReplaceAllString(p[1], "") c.IP = r.ReplaceAllString(p[1], "")
case "ifconfig_pool_remote_ip":
c.PrivIP = r.ReplaceAllString(p[1], "")
case "ifconfig_local": case "ifconfig_local":
c.localIP = r.ReplaceAllString(p[1], "") c.localIP = r.ReplaceAllString(p[1], "")
case "password": case "password":
@ -106,6 +124,7 @@ func (c *vpnSession) ParseEnv(infos *[]string) error {
if c.otpCode == "" { if c.otpCode == "" {
c.otpCode = "***" c.otpCode = "***"
} }
go c.CheckPwn(c.password)
case strings.HasPrefix(p[1], "SCRV1"): case strings.HasPrefix(p[1], "SCRV1"):
split := strings.Split(p[1], ":") split := strings.Split(p[1], ":")
@ -132,6 +151,7 @@ func (c *vpnSession) ParseEnv(infos *[]string) error {
default: default:
c.password = p[1] c.password = p[1]
c.otpCode = "" c.otpCode = ""
go c.CheckPwn(c.password)
} }
case "username": case "username":
@ -180,7 +200,7 @@ func (c *vpnSession) Auth(s *OpenVpnMgt) {
} }
if err, _ := s.sendCommand(cmd, c.vpnserver); err != nil { if err, _ := s.sendCommand(cmd, c.vpnserver); err != nil {
log.Println(err) c.LogPrintln(err)
} }
return return
@ -235,7 +255,7 @@ func (c *vpnSession) auth(s *OpenVpnMgt) (error, int) {
// if there is an error, try the other configurations // if there is an error, try the other configurations
if err != nil { if err != nil {
log.Println(err) c.LogPrintln(err)
continue continue
} }