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())) }