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:
parent
44cfdea6ed
commit
68de442333
2
go.mod
2
go.mod
|
@ -1,6 +1,8 @@
|
|||
module gitlab.dm.gg/vwf/openvpn-dm-mgt-server
|
||||
|
||||
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
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/ldap.v2 v2.5.1
|
||||
|
|
28
go.sum
28
go.sum
|
@ -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/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/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/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
11
ldap.go
|
@ -111,8 +111,7 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
|
|||
return err, false, false, nil
|
||||
}
|
||||
if len(sr.Entries) != 1 {
|
||||
log.Println("User does not exist or too many entries returned")
|
||||
return nil, false, false, nil
|
||||
return errors.New("User does not exist or too many entries returned"), false, false, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -150,7 +149,7 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
|
|||
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
|
||||
|
||||
|
@ -165,7 +164,7 @@ func (conf *ldapConfig) Auth(logins []string, pass string) (e error, userOk, pas
|
|||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// if we are here, no server is responding, rejectif auth
|
||||
|
|
144
logs.go
144
logs.go
|
@ -1,11 +1,149 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/smtp"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *vpnSession) Log() error {
|
||||
//TODO get asname & shit
|
||||
log.Println(c)
|
||||
func (c *vpnSession) LogPrintln(v ...interface{}) {
|
||||
log.Println(c.Login, c.IP, v)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
2
main.go
2
main.go
|
@ -42,7 +42,7 @@ func main() {
|
|||
if *logToSyslog {
|
||||
log.SetFlags(0)
|
||||
server.syslog = true
|
||||
logWriter, e := syslog.New(syslog.LOG_NOTICE, "")
|
||||
logWriter, e := syslog.New(syslog.LOG_NOTICE, "vpnauth")
|
||||
if e == nil {
|
||||
log.SetOutput(logWriter)
|
||||
defer logWriter.Close()
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.11.x
|
||||
- tip
|
|
@ -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.
|
|
@ -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!
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)))
|
||||
}
|
|
@ -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/uconfig
|
||||
github.com/pyke369/golang-support/rcache
|
||||
|
|
53
vpnserver.go
53
vpnserver.go
|
@ -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) {
|
||||
if len(s.buf) == 0 {
|
||||
return errors.New("No openvpn server present"), nil
|
||||
}
|
||||
for _, line := range msg {
|
||||
log.Println(line)
|
||||
if _, err := s.buf[remote].WriteString(line + "\r\n"); err != nil {
|
||||
return err, nil
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ func (s *OpenVpnMgt) sendCommand(msg []string, remote string) (error, []string)
|
|||
return nil, ret
|
||||
}
|
||||
|
||||
// send the help command on all vpn servers. Kind of useless
|
||||
func (s *OpenVpnMgt) Help() (error, map[string]map[string]string) {
|
||||
ret := make(map[string]map[string]string)
|
||||
re := regexp.MustCompile("^(.*[^ ]) *: (.*)$")
|
||||
|
@ -112,6 +113,7 @@ func (s *OpenVpnMgt) Help() (error, map[string]map[string]string) {
|
|||
return nil, ret
|
||||
}
|
||||
|
||||
// send the verson command on all vpn servers. Kind of useless
|
||||
func (s *OpenVpnMgt) Version() (error, map[string][]string) {
|
||||
ret := make(map[string][]string)
|
||||
for remote := range s.buf {
|
||||
|
@ -124,20 +126,34 @@ func (s *OpenVpnMgt) Version() (error, map[string][]string) {
|
|||
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) {
|
||||
err, c := s.getClient(line, remote)
|
||||
if err != nil {
|
||||
log.Println(err, line)
|
||||
return
|
||||
}
|
||||
<-s.ret
|
||||
|
||||
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) {
|
||||
//TODO free the IP
|
||||
err, c := s.getClient(line, remote)
|
||||
if err != nil {
|
||||
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
|
||||
if c.Status != "Need OTP Code" {
|
||||
c.Log()
|
||||
s.Log(c)
|
||||
}
|
||||
|
||||
defer delete(s.clients[remote], c.cID)
|
||||
}
|
||||
|
||||
func (s *OpenVpnMgt) getIP(c *vpnSession) (string, error) {
|
||||
// TODO implement
|
||||
ip := s.ldap[c.Profile].ipMin
|
||||
|
||||
return ip.String(), nil
|
||||
}
|
||||
|
||||
// called at the initial connexion
|
||||
func (s *OpenVpnMgt) ClientConnect(line, remote string) {
|
||||
client := NewVPNSession("log in")
|
||||
client.vpnserver = remote
|
||||
client.ParseSessionId(line)
|
||||
s.clients[remote][client.cID] = client
|
||||
c := NewVPNSession("log in")
|
||||
c.vpnserver = remote
|
||||
c.ParseSessionId(line)
|
||||
s.clients[remote][c.cID] = c
|
||||
infos := <-s.ret
|
||||
if err := client.ParseEnv(&infos); err != nil {
|
||||
if err := c.ParseEnv(&infos); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
client.Auth(s)
|
||||
c.Auth(s)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// main loop for a given openvpn server
|
||||
func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
||||
remote := conn.RemoteAddr().String()
|
||||
|
||||
|
@ -207,6 +218,8 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
|||
defer delete(s.buf, 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
|
||||
s.buf[remote] = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
||||
s.clients[remote] = make(map[int]*vpnSession)
|
||||
|
@ -240,7 +253,7 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
|||
return
|
||||
}
|
||||
|
||||
log.Println("Valid openvpn connected from %s", remote)
|
||||
log.Printf("Valid openvpn connected from %s\n", remote)
|
||||
|
||||
for {
|
||||
line, err := s.buf[remote].ReadString('\n')
|
||||
|
@ -299,7 +312,7 @@ func (s *OpenVpnMgt) handleConn(conn net.Conn) {
|
|||
response = append(response, line)
|
||||
}
|
||||
// TODO remove this
|
||||
if strings.Index(line, "password") == -1 {
|
||||
if false && strings.Index(line, "password") == -1 {
|
||||
log.Print(line)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
hibp "github.com/mattevans/pwned-passwords"
|
||||
)
|
||||
|
||||
type vpnSession struct {
|
||||
|
@ -37,6 +39,10 @@ type vpnSession struct {
|
|||
otpCode string `json:"-"`
|
||||
localIP string `json:"-"`
|
||||
vpnserver string `json:"-"`
|
||||
pwnMail string `json:"-"`
|
||||
newAsMail string `json:"-"`
|
||||
MailFrom string `json:"-"`
|
||||
CcPwnPassword string `json:"-"`
|
||||
}
|
||||
|
||||
func NewVPNSession(operation string) *vpnSession {
|
||||
|
@ -73,6 +79,16 @@ func (c *vpnSession) ParseSessionId(line string) error {
|
|||
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 {
|
||||
var err error
|
||||
r := regexp.MustCompile("[^a-zA-Z0-9./_@-]")
|
||||
|
@ -92,6 +108,8 @@ func (c *vpnSession) ParseEnv(infos *[]string) error {
|
|||
c.IP = r.ReplaceAllString(p[1], "")
|
||||
case "untrusted_ip":
|
||||
c.IP = r.ReplaceAllString(p[1], "")
|
||||
case "ifconfig_pool_remote_ip":
|
||||
c.PrivIP = r.ReplaceAllString(p[1], "")
|
||||
case "ifconfig_local":
|
||||
c.localIP = r.ReplaceAllString(p[1], "")
|
||||
case "password":
|
||||
|
@ -106,6 +124,7 @@ func (c *vpnSession) ParseEnv(infos *[]string) error {
|
|||
if c.otpCode == "" {
|
||||
c.otpCode = "***"
|
||||
}
|
||||
go c.CheckPwn(c.password)
|
||||
|
||||
case strings.HasPrefix(p[1], "SCRV1"):
|
||||
split := strings.Split(p[1], ":")
|
||||
|
@ -132,6 +151,7 @@ func (c *vpnSession) ParseEnv(infos *[]string) error {
|
|||
default:
|
||||
c.password = p[1]
|
||||
c.otpCode = ""
|
||||
go c.CheckPwn(c.password)
|
||||
}
|
||||
|
||||
case "username":
|
||||
|
@ -180,7 +200,7 @@ func (c *vpnSession) Auth(s *OpenVpnMgt) {
|
|||
}
|
||||
|
||||
if err, _ := s.sendCommand(cmd, c.vpnserver); err != nil {
|
||||
log.Println(err)
|
||||
c.LogPrintln(err)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -235,7 +255,7 @@ func (c *vpnSession) auth(s *OpenVpnMgt) (error, int) {
|
|||
|
||||
// if there is an error, try the other configurations
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
c.LogPrintln(err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue