Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #274: Avoid premature vault token renewals #314

Merged
merged 6 commits into from
Jun 29, 2017

Conversation

pschultz
Copy link
Member

Don't attempt Vault token renewals if the token isn't renewable. If it
is, don't renew the token with each refresh; only do so if the token
would expire shortly. Increase the token's lifetime by its original TTL.

This now requires the "read" capability for the "auth/token/lookup-self" path in Vault. That is granted by default to all tokens. If the lookup-self request fails for any reason, fabio will assume that the token is not renewable.

No new configuration is required. Automatic renewal will work as long as the refresh interval is smaller than the token TTL, same as before.

Don't attempt Vault token renewals if the token isn't renewable. If it
is, don't renew the token with each refresh; only do so if the token
would expire shortly. Increase the token's lifetime by its original TTL.
@pschultz
Copy link
Member Author

The builds failed because of network issues between Travis and HashiCorp:

$ wget https://releases.hashicorp.com/consul/0.8.3/consul_0.8.3_linux_amd64.zip
--2017-06-28 14:01:50-- https://releases.hashicorp.com/consul/0.8.3/consul_0.8.3_linux_amd64.zip
Resolving releases.hashicorp.com (releases.hashicorp.com)... 151.101.89.183, 2a04:4e42:11::439
Connecting to releases.hashicorp.com (releases.hashicorp.com)|151.101.89.183|:443... failed: Connection timed out.
Connecting to releases.hashicorp.com (releases.hashicorp.com)|2a04:4e42:11::439|:443... failed: Network is unreachable.

I don't think I can retry them.

Copy link
Contributor

@magiconair magiconair left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some smaller things and one potential race.

return c, nil
}

func (s *VaultSource) setToken(c *api.Client) error {
func (s *VaultSource) setAuth(c *api.Client) error {
firstCall := true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks racy and should probably be inside the lock.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically not a race (all reads and writes are inside the lock), but I moved it down anyway to remove any doubts.

Copy link
Contributor

@magiconair magiconair Jun 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, since this is a local var but then this doesn't make sense. The defer function should then read and you can drop firstCall.

defer func() {
    c.SetToken(s.auth.token)
    if s.auth.token != "" {
        s.checkRenewal(c)
    }
    s.mu.Unlock()
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s.auth.token will never be empty when the deferred function executes (assuming there is no error). Apparently I made this is too confusing. I think we can use sync.Once to make it obvious. Let my try that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also suggest to remove the SetToken call from the defer method and put it where necessary. There is too much magic going on here which is hard to follow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See 49767dd

// checkRenewal checks if the Vault token can be renewed, and if so when it
// expires and how big the renewal increment should be.
func (s *VaultSource) checkRenewal(c *api.Client) {
response, err := c.Auth().Token().LookupSelf()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/response/resp/

return
}

if data.Renewable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls rewrite as switch statement. I try to avoid if/else cascades.

return nil
}

response, err := c.Auth().Token().RenewSelf(s.auth.renewIncrement)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/response/resp/

case ttl < 2*s.Refresh:
// Renew the token if it isn't valid for two more refresh intervals.
break
case ttl < 1*time.Minute:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls drop the 1* since it is redundant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is redundant, but it reads as "one minute" which I prefer. This is a constant expression that will be evaluated at compile time, so there is no runtime impact. If you insist I'll change it though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't insist but I'd prefer since it is redundant :)

auth struct {
token string // actual token
expireTime time.Time // zero value if the token isn't renewable
renewIncrement int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls add a comment and explain the unit. Maybe a shorter name, e.g. renewTTL

@pschultz
Copy link
Member Author

Please take another look. I will fixup into a single commit once you're good with the PR.

Copy link
Contributor

@magiconair magiconair left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small nit-picks

token string // actual token
expireTime time.Time // zero value if the token isn't renewable
renewIncrement int
token string // actual token
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls use the Go doc standard here:

// token is the actual token
token string

// expireTime is ...
expireTime time.Time
...


ttl := time.Until(data.ExpireTime)
ttl = ttl / time.Second * time.Second // truncate to seconds
log.Printf("[WARN] vault: Token is not renewable and will expire %s from now (at %s)",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logging is the default case, right?

pls change the log msg to Token is not renewable and expires in %s from now at %s

@magiconair
Copy link
Contributor

Don't bother. I can squash the merge myself.

@magiconair
Copy link
Contributor

@pschultz Cheeky side question: Would you mind me adding your logo to the README? :)

@pschultz
Copy link
Member Author

Sure, you can add the logo. We don't have an English site you can link to though, only the one in German.

@magiconair
Copy link
Contributor

magiconair commented Jun 29, 2017

I think this is more readable. What do you think?

func (s *VaultSource) setAuth(c *api.Client) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.auth.token != "" {
		return nil
	}

	if s.vaultToken == "" {
		return errors.New("vault: no token")
	}

	// did we get a wrapped token?
	resp, err := c.Logical().Unwrap(s.vaultToken)
	if err != nil {
		// unwrapping failed?
		if !strings.HasPrefix(err.Error(), "no value found at") {
			return err
		}
		s.auth.token = s.vaultToken
	} else {
		log.Printf("[INFO] vault: Unwrapped token %s", s.vaultToken)
		s.auth.token = resp.Auth.ClientToken
	}

	s.auth.once.Do(func() { s.checkRenewal(c) })
	c.SetToken(s.auth.token)
	return nil
}

@pschultz
Copy link
Member Author

Yeah, that's better. The last if/else statements can even be done with a nice switch. See f6e2463.

@magiconair
Copy link
Contributor

LGTM

@magiconair magiconair merged commit d6d0a52 into fabiolb:master Jun 29, 2017
@pschultz pschultz deleted the vault-token-renewal branch June 30, 2017 08:26
@magiconair magiconair added this to the 1.5.1 milestone Oct 10, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants