depot/go/tokend/vaultissuer.go

107 lines
3.3 KiB
Go
Raw Normal View History

package main
import (
"context"
"flag"
"fmt"
"regexp"
"time"
log "github.com/golang/glog"
vapi "github.com/hashicorp/vault/api"
)
var (
vaultTokenTTL = flag.String("vault_token_ttl", "20m", "TTL at which token must be renewed.")
vaultTokenMaxTTL = flag.String("vault_token_max_ttl", "1d", "TTL at which token must be reissued.")
vaultTokenRenewable = flag.Bool("vault_token_renewable", true, "Whether the tokens are renewable or reissued every time.")
)
type vaultTokenInteractor struct {
v *vapi.Client
}
var _ tokenInteractor = ((*vaultTokenInteractor)(nil))
func (i *vaultTokenInteractor) Revoke(ctx context.Context, tokenSecret *TokenSecret) error {
// Use the token, since we have it, to revoke itself.
c, err := i.v.Clone()
if err != nil {
return fmt.Errorf("generating a new Vault client: %w", err)
}
token, err := tokenSecret.TokenID()
if err != nil {
return fmt.Errorf("getting token from secret: %w", err)
}
c.SetToken(token)
// Since we have the token, we could potentially use /revoke, or even /revoke-self.
if err := c.Auth().Token().RevokeSelf(""); err != nil {
return fmt.Errorf("revoking token: %w", err)
}
return nil
}
func computePolicies(selfPolicies []string, username string, isPlainUser bool) []string {
appMatchRE := regexp.MustCompile(fmt.Sprintf(`^(server/[^/]+/)?app/%s$`, regexp.QuoteMeta(username)))
userMatchRE := regexp.MustCompile(fmt.Sprintf(`^(server/[^/]+/user|server-user)(/%s)?$`, regexp.QuoteMeta(username)))
var outPolicies []string
for _, p := range selfPolicies {
if p == "default" || appMatchRE.MatchString(p) || (isPlainUser && userMatchRE.MatchString(p)) {
outPolicies = append(outPolicies, p)
}
}
return outPolicies
}
func policiesForToken(ts *TokenSecret) ([]string, error) {
var ps []string
psIntf, ok := ts.Data["policies"].([]interface{})
if !ok {
return nil, fmt.Errorf("policies not present or not expected []interface{} type (was %T)", ts.Data["policies"])
}
for _, p := range psIntf {
ps = append(ps, p.(string))
}
return ps, nil
}
func (i *vaultTokenInteractor) Issue(ctx context.Context, username string, isPlainUser bool) (*TokenSecret, error) {
// Look up our own token to work out what policies we might want to apply.
// TODO: maybe consider caching this so we don't need to do it every time?
self, err := i.v.Auth().Token().LookupSelf()
if err != nil {
return nil, fmt.Errorf("looking up server-wide token: %w", err)
}
selfPolicies, err := policiesForToken(self)
if err != nil {
return nil, fmt.Errorf("retrieving self policies: %w", err)
}
wantPolicies := computePolicies(selfPolicies, username, isPlainUser)
log.Infof("policies for %v: %v", username, wantPolicies)
return i.v.Auth().Token().Create(&vapi.TokenCreateRequest{
// TODO: extend rather than replace metadata?
Metadata: map[string]string{
"app": username,
},
TTL: *vaultTokenTTL,
ExplicitMaxTTL: *vaultTokenMaxTTL,
Renewable: vaultTokenRenewable,
Policies: wantPolicies,
})
}
func (i *vaultTokenInteractor) Renew(ctx context.Context, tokenSecret *TokenSecret) (*TokenSecret, error) {
token, err := tokenSecret.TokenID()
if err != nil {
return nil, fmt.Errorf("getting token from secret: %w", err)
}
return i.v.Auth().Token().RenewTokenAsSelf(token, int((20 * time.Minute).Seconds()))
}