2017-06-14 18:40:12 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2019-09-14 12:33:03 +00:00
|
|
|
"math"
|
2017-06-14 18:40:12 +00:00
|
|
|
"path"
|
|
|
|
"sort"
|
2019-09-14 11:31:45 +00:00
|
|
|
"strconv"
|
2017-06-14 18:40:12 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/vault/api"
|
2018-08-30 17:45:08 +00:00
|
|
|
"github.com/pquerna/otp"
|
2017-06-14 18:40:12 +00:00
|
|
|
"github.com/pquerna/otp/totp"
|
2019-09-09 17:11:31 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2017-06-14 18:40:12 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type token struct {
|
2017-06-14 20:21:31 +00:00
|
|
|
Code string `json:"code"`
|
|
|
|
Icon string `json:"icon"`
|
2017-06-14 18:40:12 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Secret string `json:"-"`
|
2019-09-14 12:33:03 +00:00
|
|
|
Digits int `json:"digits"`
|
|
|
|
Period int `json:"period"`
|
2017-06-14 18:40:12 +00:00
|
|
|
}
|
|
|
|
|
2019-09-14 12:33:03 +00:00
|
|
|
func (t *token) GenerateCode(next bool) error {
|
2017-06-14 18:40:12 +00:00
|
|
|
secret := t.Secret
|
|
|
|
|
|
|
|
if n := len(secret) % 8; n != 0 {
|
|
|
|
secret = secret + strings.Repeat("=", 8-n)
|
|
|
|
}
|
|
|
|
|
2018-08-30 17:45:08 +00:00
|
|
|
opts := totp.ValidateOpts{
|
|
|
|
Period: 30,
|
|
|
|
Skew: 1,
|
|
|
|
Digits: otp.DigitsSix,
|
|
|
|
Algorithm: otp.AlgorithmSHA1,
|
|
|
|
}
|
|
|
|
|
2019-09-14 12:33:03 +00:00
|
|
|
if t.Digits != 0 {
|
|
|
|
opts.Digits = otp.Digits(t.Digits)
|
2018-08-30 17:45:08 +00:00
|
|
|
}
|
|
|
|
|
2019-09-14 12:33:03 +00:00
|
|
|
if t.Period != 0 {
|
|
|
|
opts.Period = uint(t.Period)
|
|
|
|
}
|
|
|
|
|
|
|
|
var pointOfTime = time.Now()
|
|
|
|
if next {
|
|
|
|
pointOfTime = pointOfTime.Add(time.Duration(t.Period) * time.Second)
|
2019-09-14 11:45:52 +00:00
|
|
|
}
|
|
|
|
|
2017-06-14 18:40:12 +00:00
|
|
|
var err error
|
2019-09-14 12:33:03 +00:00
|
|
|
t.Code, err = totp.GenerateCodeCustom(strings.ToUpper(secret), pointOfTime, opts)
|
2017-06-14 18:40:12 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sorter interface
|
|
|
|
|
|
|
|
type tokenList []*token
|
|
|
|
|
|
|
|
func (t tokenList) Len() int { return len(t) }
|
|
|
|
func (t tokenList) Less(i, j int) bool { return strings.ToLower(t[i].Name) < strings.ToLower(t[j].Name) }
|
|
|
|
func (t tokenList) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
|
|
|
|
|
|
|
func (t tokenList) LongestName() (l int) {
|
|
|
|
for _, s := range t {
|
|
|
|
if ll := len(s.Name); ll > l {
|
|
|
|
l = ll
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-09-14 12:33:03 +00:00
|
|
|
func (t tokenList) MinPeriod() int {
|
|
|
|
var m int = math.MaxInt32
|
|
|
|
|
|
|
|
for _, tok := range t {
|
2019-09-14 12:36:05 +00:00
|
|
|
if tok.Period != 0 && tok.Period < m {
|
2019-09-14 12:33:03 +00:00
|
|
|
m = tok.Period
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-14 12:36:05 +00:00
|
|
|
if m == math.MaxInt32 {
|
|
|
|
// Fallback: Everything uses the default value
|
|
|
|
m = 30
|
|
|
|
}
|
|
|
|
|
2019-09-14 12:33:03 +00:00
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
2017-06-19 17:33:55 +00:00
|
|
|
func useOrRenewToken(tok, accessToken string) (string, error) {
|
2017-06-14 18:40:12 +00:00
|
|
|
client, err := api.NewClient(&api.Config{
|
|
|
|
Address: cfg.Vault.Address,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2017-06-19 17:33:55 +00:00
|
|
|
return "", fmt.Errorf("Unable to create client: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if tok != "" {
|
|
|
|
client.SetToken(tok)
|
|
|
|
if s, err := client.Auth().Token().LookupSelf(); err == nil && s.Data != nil {
|
|
|
|
log.WithFields(log.Fields{"token": hashSecret(tok)}).Debugf("Token is valid for another %vs", s.Data["ttl"])
|
|
|
|
return tok, nil
|
|
|
|
} else {
|
|
|
|
log.WithFields(log.Fields{"token": hashSecret(tok)}).Debugf("Token did not met requirements: err = %s", err)
|
|
|
|
if s != nil {
|
|
|
|
log.WithFields(log.Fields{"token": hashSecret(tok)}).Debugf("Token did not met requirements: data = %v", s.Data)
|
|
|
|
}
|
|
|
|
}
|
2017-06-14 18:40:12 +00:00
|
|
|
}
|
|
|
|
|
2017-06-14 19:24:38 +00:00
|
|
|
if s, err := client.Logical().Write("auth/github/login", map[string]interface{}{"token": accessToken}); err != nil || s.Auth == nil {
|
2017-06-19 17:33:55 +00:00
|
|
|
return "", fmt.Errorf("Login did not work: Error = %s", err)
|
2017-06-14 19:24:38 +00:00
|
|
|
} else {
|
2017-06-19 17:33:55 +00:00
|
|
|
return s.Auth.ClientToken, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-14 12:33:03 +00:00
|
|
|
func getSecretsFromVault(tok string, next bool) ([]*token, error) {
|
2017-06-19 17:33:55 +00:00
|
|
|
client, err := api.NewClient(&api.Config{
|
|
|
|
Address: cfg.Vault.Address,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Unable to create client: %s", err)
|
2017-06-14 18:40:12 +00:00
|
|
|
}
|
|
|
|
|
2017-06-19 17:33:55 +00:00
|
|
|
client.SetToken(tok)
|
|
|
|
|
2017-06-14 18:40:12 +00:00
|
|
|
key := cfg.Vault.Prefix
|
|
|
|
|
|
|
|
resp := []*token{}
|
|
|
|
respChan := make(chan *token, 100)
|
|
|
|
|
|
|
|
keyPoolChan := make(chan string, 100)
|
|
|
|
|
|
|
|
scanPool := make(chan string, 100)
|
|
|
|
scanPool <- strings.TrimRight(key, "*")
|
|
|
|
|
|
|
|
done := make(chan struct{})
|
|
|
|
defer func() { done <- struct{}{} }()
|
|
|
|
|
|
|
|
wg := new(sync.WaitGroup)
|
|
|
|
wg.Add(1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case key := <-scanPool:
|
|
|
|
go scanKeyForSubKeys(client, key, scanPool, keyPoolChan, wg)
|
|
|
|
case key := <-keyPoolChan:
|
2019-09-14 12:33:03 +00:00
|
|
|
go fetchTokenFromKey(client, key, respChan, wg, next)
|
2017-06-14 18:40:12 +00:00
|
|
|
case t := <-respChan:
|
|
|
|
resp = append(resp, t)
|
2018-03-23 17:45:21 +00:00
|
|
|
wg.Done()
|
2017-06-14 18:40:12 +00:00
|
|
|
case <-done:
|
|
|
|
close(scanPool)
|
|
|
|
close(keyPoolChan)
|
|
|
|
close(respChan)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
sort.Sort(tokenList(resp))
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func scanKeyForSubKeys(client *api.Client, key string, subKeyChan, tokenKeyChan chan string, wg *sync.WaitGroup) {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
s, err := client.Logical().List(key)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Unable to list keys %q: %s", key, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if s == nil {
|
|
|
|
log.Errorf("There is no key %q", key)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.Data["keys"] != nil {
|
|
|
|
for _, sk := range s.Data["keys"].([]interface{}) {
|
|
|
|
sks := sk.(string)
|
|
|
|
if strings.HasSuffix(sks, "/") {
|
|
|
|
wg.Add(1)
|
|
|
|
subKeyChan <- path.Join(key, sks)
|
|
|
|
} else {
|
|
|
|
wg.Add(1)
|
|
|
|
tokenKeyChan <- path.Join(key, sks)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-14 12:33:03 +00:00
|
|
|
func fetchTokenFromKey(client *api.Client, k string, respChan chan *token, wg *sync.WaitGroup, next bool) {
|
2017-06-14 18:40:12 +00:00
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
data, err := client.Logical().Read(k)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Unable to read from key %q: %s", k, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-14 20:21:31 +00:00
|
|
|
if data.Data == nil {
|
|
|
|
// Key without any data? Weird.
|
2017-06-14 18:40:12 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-14 20:21:31 +00:00
|
|
|
tok := &token{
|
|
|
|
Icon: "key",
|
|
|
|
Name: k,
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range data.Data {
|
|
|
|
switch k {
|
|
|
|
case cfg.Vault.SecretField:
|
|
|
|
tok.Secret = v.(string)
|
|
|
|
case "code":
|
|
|
|
tok.Code = v.(string)
|
|
|
|
case "name":
|
|
|
|
tok.Name = v.(string)
|
|
|
|
case "account_name":
|
|
|
|
tok.Name = v.(string)
|
|
|
|
case "icon":
|
|
|
|
tok.Icon = v.(string)
|
2018-08-30 17:45:08 +00:00
|
|
|
case "digits":
|
2019-09-14 12:33:03 +00:00
|
|
|
tok.Digits, err = strconv.Atoi(v.(string))
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("Unable to parse digits")
|
|
|
|
}
|
2019-09-14 11:45:52 +00:00
|
|
|
case "period":
|
2019-09-14 12:33:03 +00:00
|
|
|
tok.Period, err = strconv.Atoi(v.(string))
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("Unable to parse digits")
|
|
|
|
}
|
2017-06-14 20:21:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-14 12:33:03 +00:00
|
|
|
if err = tok.GenerateCode(next); err != nil {
|
2019-09-14 11:37:56 +00:00
|
|
|
log.WithError(err).WithField("name", tok.Name).Error("Unable to generate code")
|
|
|
|
return
|
|
|
|
}
|
2018-08-30 17:45:08 +00:00
|
|
|
|
2017-06-14 20:21:31 +00:00
|
|
|
if tok.Code == "" {
|
|
|
|
// Nothing ended in us having a code, does not seem to be something for us
|
|
|
|
return
|
2017-06-14 18:40:12 +00:00
|
|
|
}
|
|
|
|
|
2018-03-23 17:45:21 +00:00
|
|
|
wg.Add(1)
|
2017-06-14 18:40:12 +00:00
|
|
|
respChan <- tok
|
|
|
|
}
|