Extracted version from other code

This commit is contained in:
Ask Bjørn Hansen 2023-02-13 19:30:01 -08:00
parent a16f4fe504
commit 57639813e0
5 changed files with 656 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright © 2012 NTP Pool Project
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.

31
go.mod Normal file
View File

@ -0,0 +1,31 @@
module go.ntppool.org/vault-token-manager
go 1.19
require github.com/hashicorp/vault/api v1.9.0
require (
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/fatih/color v1.14.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.4.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/stretchr/testify v1.7.2 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/net v0.6.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.3.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
)

90
go.sum Normal file
View File

@ -0,0 +1,90 @@
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I=
github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault/api v1.9.0 h1:ab7dI6W8DuCY7yCU8blo0UCYl2oHre/dloCmzMWg9w8=
github.com/hashicorp/vault/api v1.9.0/go.mod h1:lloELQP4EyhjnCQhF8agKvWIVTmxbpEJj70b98959sM=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

478
vault.go Normal file
View File

@ -0,0 +1,478 @@
package vaulttokenmanager
import (
"bytes"
"context"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"path"
"strconv"
"sync"
"time"
vaultapi "github.com/hashicorp/vault/api"
)
const tokenRefreshInterval = 10 * time.Hour
type notFoundError struct{}
func (m *notFoundError) Error() string {
return "token not found"
}
type token struct {
Secret string `json:"token"`
Created int64 `json:"token-ts"`
version int `json:"-"`
}
type TokenManager struct {
key string
basePath string
latest *token
versions map[int]*token
vault *vaultapi.Client
lock sync.RWMutex
}
func New(key, depEnv string) (*TokenManager, error) {
if len(depEnv) == 0 {
return nil, fmt.Errorf("invalid deployment mode parameter %q", depEnv)
}
var basePath = fmt.Sprintf("kv/data/ntppool/%s/", depEnv)
cl, err := vaultClient()
if err != nil {
return nil, err
}
tm := &TokenManager{
key: key,
basePath: basePath,
vault: cl,
versions: map[int]*token{},
}
err = tm.populate()
if err != nil {
return nil, err
}
// todo: pass context so it can be shutdown
go tm.rotateTokensBackground()
return tm, nil
}
func getSignatureVersion(sig []byte) (int, error) {
idx := bytes.IndexByte(sig, '-')
if idx < 1 {
return 0, fmt.Errorf("invalid signature")
}
versionb := sig[0:idx]
version, err := strconv.Atoi(string(versionb))
if err != nil || version == 0 {
return 0, fmt.Errorf("unknown signature version %d: %s", version, err)
}
return version, nil
}
func (tm *TokenManager) Validate(sig []byte, data ...[]byte) (bool, error) {
version, err := getSignatureVersion(sig)
if err != nil || version == 0 {
return false, err
}
token, err := tm.getTokenVersion(context.Background(), version)
if err != nil {
return false, err
}
expected, err := tm.signWith(token, data...)
if err != nil {
return false, err
}
if len(expected) > 0 && bytes.Equal(sig, expected) {
return true, nil
}
log.Printf("exp: %s", expected)
log.Printf("got: %s", sig)
return false, fmt.Errorf("could not validate signature")
}
func (tm *TokenManager) Sign(data ...[]byte) ([]byte, error) {
token, err := tm.getToken(context.Background())
if err != nil {
return nil, err
}
// log.Printf("got version: %d, token: %s", token.version, token.Secret)
return tm.signWith(token, data...)
}
func (tm *TokenManager) signWith(token *token, data ...[]byte) ([]byte, error) {
hm := hmac.New(sha256.New, []byte(token.Secret))
b := bytes.Join(data, []byte("|"))
p, err := hm.Write([]byte(b))
if err != nil || p != len(b) {
return nil, fmt.Errorf("hmac error: %s", err)
}
sha := hm.Sum(nil)
r := strconv.AppendInt([]byte{}, int64(token.version), 10)
r = append(r, []byte("-")...)
shaenc := make([]byte, base64.RawURLEncoding.EncodedLen(len(sha)))
base64.RawURLEncoding.Encode(shaenc, sha)
r = append(r, shaenc...)
return r, nil
}
func (tm *TokenManager) rotateTokensBackground() {
ctx := context.Background() // for when the app has context properly
l := log.New(os.Stderr, "rotateTokensBackground: ", 0)
ticker := time.NewTicker(tokenRefreshInterval / 5)
defer ticker.Stop()
for {
latest, err := tm.getToken(ctx)
if err != nil {
l.Printf("could not get token: %s", err)
}
latestTime := time.Unix(latest.Created, 0)
l.Printf("checking token age, latest is %s old (interval: %s)", time.Since(latestTime), tokenRefreshInterval.String())
if age := time.Since(latestTime); age > tokenRefreshInterval {
l.Printf("token age (%s) is more than %s, rotate it", age, tokenRefreshInterval)
tm.createNewToken(ctx, latest.version)
tm.lock.Lock()
tm.latest = nil
tm.lock.Unlock()
tm.getToken(ctx)
l.Printf("finished renewing token")
}
select {
case <-ctx.Done():
l.Printf("context done")
return
case <-ticker.C:
}
}
}
func (tm *TokenManager) createNewToken(ctx context.Context, cas int) error {
data := map[string]interface{}{
"data": makeToken(),
"metadata": map[string]interface{}{
"cas_required": true,
},
"options": map[string]interface{}{
"cas": cas,
},
}
_, err := tm.vault.Logical().WriteWithContext(ctx, tm.path(), data)
if err != nil {
return err
}
return nil
}
func (tm *TokenManager) path() string {
return path.Join(tm.basePath, tm.key)
}
func (tm *TokenManager) populate() error {
ctx := context.Background()
t, err := tm.getToken(ctx)
if err != nil {
if _, ok := err.(*notFoundError); !ok {
return err
}
}
if t == nil {
err := tm.createNewToken(ctx, 0)
if err != nil {
return fmt.Errorf("could not save token: %s", err)
}
t, err = tm.getToken(ctx)
if err != nil {
return err
}
if t == nil {
return fmt.Errorf("could not find token data")
}
}
if t != nil {
tm.latest = t
tm.versions[t.version] = t
}
return nil
}
func (tm *TokenManager) getTokenVersionCache(ctx context.Context, version int) (*token, error) {
tm.lock.RLock()
defer tm.lock.RUnlock()
if t, ok := tm.versions[version]; ok {
return t, nil
}
return nil, nil
}
func (tm *TokenManager) getTokenVersion(ctx context.Context, version int) (*token, error) {
token, err := tm.getTokenVersionCache(ctx, version)
if err != nil {
return nil, err
}
if token != nil {
return token, nil
}
latest, err := tm.getToken(ctx)
if err != nil {
return nil, err
}
if latest.version < version {
return nil, fmt.Errorf("invalid signature version")
}
tm.lock.Lock()
defer tm.lock.Unlock()
// in case it was set while we were waiting for a lock
if token, ok := tm.versions[version]; ok {
return token, nil
}
log.Printf("requesting token %q/%d", tm.key, version)
rv, err := tm.getKVVersion(ctx, tm.key, version)
if err != nil {
return nil, err
}
token, err = parseTokenVaultSecret(rv.Data)
if err != nil {
return nil, err
}
tm.versions[version] = token
return token, err
}
func (tm *TokenManager) getToken(ctx context.Context) (*token, error) {
tm.lock.RLock()
if token := tm.latest; token != nil {
tm.lock.RUnlock()
return token, nil
}
log.Printf("getToken didn't have latest token, getting from vault")
tm.lock.RUnlock()
tm.lock.Lock()
defer tm.lock.Unlock()
// in case it was set while we were waiting for a lock
if tm.latest != nil {
return tm.latest, nil
}
log.Printf("getToken calling getKV")
rv, err := tm.getKV(ctx, tm.key)
if err != nil {
return nil, err
}
if rv == nil {
return nil, &notFoundError{}
}
t, err := parseTokenVaultSecret(rv.Data)
if err != nil {
return nil, err
}
tm.latest = t
tm.versions[t.version] = t
log.Printf("getToken returning success")
return t, nil
}
func parseTokenVaultSecret(data map[string]interface{}) (*token, error) {
t := &token{}
var err error
if dataif, ok := data["data"]; ok {
data := dataif.(map[string]interface{})
if tokData, ok := data["token"]; ok {
if tokStr, ok := tokData.(string); ok {
t.Secret = tokStr
}
}
if tokData, ok := data["token-ts"]; ok {
if tokInt, ok := tokData.(json.Number); ok {
t.Created, err = tokInt.Int64()
if t.Created == 0 || err != nil {
log.Printf("could not parse Created from token secret (%T: %+v): %s", tokData, tokData, err)
}
}
}
}
if metaif, ok := data["metadata"]; ok {
meta := metaif.(map[string]interface{})
if version, ok := meta["version"]; ok {
if v, ok := version.(json.Number); ok {
v64, err := v.Int64()
if err != nil {
return nil, err
}
t.version = int(v64)
}
}
}
if t.version == 0 || len(t.Secret) == 0 {
return nil, fmt.Errorf("expected token data not found")
}
return t, nil
}
func makeToken() *token {
randomBytes := make([]byte, 16)
_, err := rand.Read(randomBytes)
if err != nil {
return nil
}
return &token{
Secret: base64.URLEncoding.EncodeToString(randomBytes),
Created: time.Now().Unix(),
}
}
var hasOutputVaultEnvMessage bool
func vaultClient() (*vaultapi.Client, error) {
c := vaultapi.DefaultConfig()
if c.Address == "https://127.0.0.1:8200" {
c.Address = "https://vault.ntppool.org"
}
cl, err := vaultapi.NewClient(c)
if err != nil {
return nil, err
}
// VAULT_TOKEN is read automatically from the environment if set
// so we just try the file here
token, err := os.ReadFile("/vault/secrets/token")
if err == nil {
cl.SetToken(string(token))
} else {
if !hasOutputVaultEnvMessage {
hasOutputVaultEnvMessage = true
log.Printf("could not read /vault/secrets/token (%s), using VAULT_TOKEN", err)
}
}
return cl, nil
}
func (tm *TokenManager) getKV(ctx context.Context, k string) (*vaultapi.Secret, error) {
cl, err := vaultClient()
if err != nil {
return nil, nil
}
rv, err := cl.Logical().ReadWithContext(ctx, tm.path())
if err != nil {
return nil, err
}
return rv, nil
}
func (tm *TokenManager) getKVVersion(ctx context.Context, k string, version int) (*vaultapi.Secret, error) {
cl, err := vaultClient()
if err != nil {
return nil, nil
}
data := map[string][]string{
"version": {strconv.Itoa(version)},
}
rv, err := cl.Logical().ReadWithDataWithContext(ctx, tm.path(), data)
if err != nil {
return nil, err
}
return rv, nil
}
func (tm *TokenManager) SetKV(ctx context.Context, k string, data *vaultapi.Secret) error {
p := tm.path()
cl, err := vaultClient()
if err != nil {
return nil
}
_, err = cl.Logical().WriteWithContext(ctx, p, data.Data)
if err != nil {
return err
}
return nil
}

36
vault_test.go Normal file
View File

@ -0,0 +1,36 @@
package vaulttokenmanager
import (
"testing"
)
func TestParseSignature(t *testing.T) {
v, err := getSignatureVersion([]byte("7-gjqkh34gq3i4gqf"))
if err != nil {
t.Log(err)
t.Fail()
}
if v != 7 {
t.Logf("expected 7, got %d", v)
t.Fail()
}
}
// func TestSignature(t *testing.T) {
// tm, err := New("monitor-token")
// if err != nil {
// t.Log(err)
// t.Fail()
// }
// batchID := []byte("0000000ABCEJKLFWEF")
// ipb := []byte{108, 61, 56, 35}
// sig, err := tm.Sign(1, batchID, ipb)
// if err != nil {
// t.Log(err)
// t.Fail()
// }
// t.Log(sig)
// }