mirror of
https://github.com/Luzifer/vault-user-token.git
synced 2024-12-05 14:14:07 +00:00
165 lines
4.1 KiB
Go
165 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/api"
|
|
homedir "github.com/mitchellh/go-homedir"
|
|
"github.com/pkg/errors"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/Luzifer/rconfig/v2"
|
|
)
|
|
|
|
const (
|
|
tokenRenewValidity = 900 // Seconds
|
|
tokenRenewEarly = 30 * time.Second
|
|
vaultTokenFilePerms = 0o600
|
|
)
|
|
|
|
var (
|
|
cfg = struct {
|
|
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warning, error)"`
|
|
|
|
UseFullHostname bool `flag:"full-hostname" default:"true" description:"Use the full reported hostname (true) or only the first part (false)"`
|
|
|
|
VaultAddress string `flag:"vault-addr" env:"VAULT_ADDR" default:"https://127.0.0.1:8200" description:"Vault API address"`
|
|
VaultRoleID string `flag:"vault-role-id" env:"VAULT_ROLE_ID" default:"" description:"ID of the role to use"`
|
|
|
|
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
|
}{}
|
|
|
|
version = "dev"
|
|
|
|
client *api.Client
|
|
)
|
|
|
|
func init() {
|
|
rconfig.AutoEnv(true)
|
|
if err := rconfig.Parse(&cfg); err != nil {
|
|
log.WithError(err).Fatal("Unable to parse commandline options")
|
|
}
|
|
|
|
if cfg.VersionAndExit {
|
|
fmt.Printf("vault-user-token %s\n", version) //revive:disable:unhandled-error // printing to stdout is not expected to err
|
|
os.Exit(0)
|
|
}
|
|
|
|
if cfg.VaultRoleID == "" {
|
|
log.Fatal("You need to supply a role id for this to work")
|
|
}
|
|
|
|
if logLevel, err := log.ParseLevel(cfg.LogLevel); err == nil {
|
|
log.SetLevel(logLevel)
|
|
} else {
|
|
log.WithError(err).Fatal("Unable to parse log level")
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
roleSecret, err := getVaultRoleSecret()
|
|
if err != nil {
|
|
log.WithError(err).Fatal("getting vault role secret")
|
|
}
|
|
|
|
client, err = api.NewClient(&api.Config{
|
|
Address: cfg.VaultAddress,
|
|
})
|
|
|
|
if err != nil {
|
|
log.WithError(err).Fatal("Unable to create new vault client")
|
|
}
|
|
|
|
for {
|
|
if err = authenticateVault(roleSecret); err != nil {
|
|
log.WithError(err).Fatal("Unable to authenticate vault")
|
|
}
|
|
|
|
if err = keepRenewingToken(); err != nil {
|
|
log.WithError(err).Error("Unale to renew token")
|
|
}
|
|
}
|
|
}
|
|
|
|
func getVaultRoleSecret() (string, error) {
|
|
var secret string
|
|
|
|
secretOverrideFile, err := homedir.Expand("~/.config/vault-user-token.secret")
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "expanding location of secret override")
|
|
}
|
|
|
|
sofInfo, err := os.Stat(secretOverrideFile)
|
|
if err == nil {
|
|
if hasInsecurePermission(sofInfo.Mode()) {
|
|
return "", errors.New("secret override file has insecure permissions")
|
|
}
|
|
|
|
//#nosec:G304 // File is only read as string, not sec-relevant
|
|
if secretOverride, err := os.ReadFile(secretOverrideFile); err == nil {
|
|
return strings.TrimSpace(string(secretOverride)), nil
|
|
}
|
|
}
|
|
|
|
if secret, err = os.Hostname(); err != nil {
|
|
return "", errors.Wrap(err, "resolving hostname")
|
|
}
|
|
|
|
if parts := strings.Split(secret, "."); !cfg.UseFullHostname && len(parts) > 1 {
|
|
secret = parts[0]
|
|
}
|
|
|
|
return secret, nil
|
|
}
|
|
|
|
func hasInsecurePermission(filePerm os.FileMode) bool {
|
|
for _, insecMode := range []os.FileMode{0o040, 0o020, 0o004, 0o002} {
|
|
if filePerm&insecMode == insecMode {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func keepRenewingToken() error {
|
|
for {
|
|
var (
|
|
lease *api.Secret
|
|
err error
|
|
)
|
|
if lease, err = client.Auth().Token().RenewSelf(tokenRenewValidity); err != nil {
|
|
return errors.Wrap(err, "renewing token")
|
|
}
|
|
|
|
log.Debugf("Token renewed for another %d seconds.", lease.Auth.LeaseDuration)
|
|
|
|
<-time.After(time.Duration(lease.Auth.LeaseDuration)*time.Second - tokenRenewEarly)
|
|
}
|
|
}
|
|
|
|
func authenticateVault(roleSecret string) error {
|
|
data := map[string]interface{}{
|
|
"role_id": cfg.VaultRoleID,
|
|
"secret_id": roleSecret,
|
|
}
|
|
|
|
loginSecret, lserr := client.Logical().Write("auth/approle/login", data)
|
|
if lserr != nil || loginSecret.Auth == nil {
|
|
return errors.Wrap(lserr, "logging in using approle")
|
|
}
|
|
|
|
client.SetToken(loginSecret.Auth.ClientToken)
|
|
|
|
tokenFile, err := homedir.Expand("~/.vault-token")
|
|
if err != nil {
|
|
return errors.Wrap(err, "expanding token file path")
|
|
}
|
|
|
|
return errors.Wrap(
|
|
os.WriteFile(tokenFile, []byte(loginSecret.Auth.ClientToken), vaultTokenFilePerms),
|
|
"writing vault token file",
|
|
)
|
|
}
|