Extracted version from other code
This commit is contained in:
parent
a16f4fe504
commit
57639813e0
21
LICENSE
Normal file
21
LICENSE
Normal 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
31
go.mod
Normal 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
90
go.sum
Normal 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
478
vault.go
Normal 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, ¬FoundError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
36
vault_test.go
Normal 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)
|
||||||
|
// }
|
Loading…
Reference in New Issue
Block a user