mirror of
https://github.com/external-secrets/external-secrets.git
synced 2024-12-14 11:57:59 +00:00
fix(vault): Treat tokens expiring in <60s as expired (#3637)
* fix(vault): Treat tokens expiring in <60s as expired Without this, it's possible to hit a TOCTOU issue where checkToken() sees a valid token, but it expires before the actual operation is performed. This condition is only reachable when the experimental caching feature is enabled. 60 seconds was chosen as a sane (but arbitrary) value. It should be more than enough to cover the amount of time between checkToken() and the actual operation. Signed-off-by: Andrew Gunnerson <andrew.gunnerson@elastic.co> * ADOPTERS.md: Add Elastic Signed-off-by: Andrew Gunnerson <andrew.gunnerson@elastic.co> --------- Signed-off-by: Andrew Gunnerson <andrew.gunnerson@elastic.co>
This commit is contained in:
parent
504b5506f4
commit
2053df7b7c
3 changed files with 89 additions and 0 deletions
|
@ -7,6 +7,7 @@
|
||||||
- [Container Solutions](http://container-solutions.com/)
|
- [Container Solutions](http://container-solutions.com/)
|
||||||
- [DaangnPay](https://www.daangnpay.com/)
|
- [DaangnPay](https://www.daangnpay.com/)
|
||||||
- [Epidemic Sound](https://www.epidemicsound.com/)
|
- [Epidemic Sound](https://www.epidemicsound.com/)
|
||||||
|
- [Elastic](https://www.elastic.co/)
|
||||||
- [Fivetran](https://www.fivetran.com)
|
- [Fivetran](https://www.fivetran.com)
|
||||||
- [Form3](https://www.form3.tech/)
|
- [Form3](https://www.form3.tech/)
|
||||||
- [GoTo](https://www.goto.com/)
|
- [GoTo](https://www.goto.com/)
|
||||||
|
|
|
@ -16,6 +16,7 @@ package vault
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
@ -160,6 +161,24 @@ func checkToken(ctx context.Context, token util.Token) (bool, error) {
|
||||||
if tokenType == "batch" {
|
if tokenType == "batch" {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
ttl, ok := resp.Data["ttl"]
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("no TTL found in response")
|
||||||
|
}
|
||||||
|
ttlInt, err := ttl.(json.Number).Int64()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("invalid token TTL: %v: %w", ttl, err)
|
||||||
|
}
|
||||||
|
expireTime, ok := resp.Data["expire_time"]
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("no expiration time found in response")
|
||||||
|
}
|
||||||
|
if ttlInt < 60 && expireTime != nil {
|
||||||
|
// Treat expirable tokens that are about to expire as already expired.
|
||||||
|
// This ensures that the token won't expire in between this check and
|
||||||
|
// performing the actual operation.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ package vault
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -208,3 +209,71 @@ func TestCheckTokenErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckTokenTtl(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
message string
|
||||||
|
secret *vault.Secret
|
||||||
|
cache bool
|
||||||
|
}{
|
||||||
|
"LongTTLExpirable": {
|
||||||
|
message: "should cache if expirable token expires far into the future",
|
||||||
|
secret: &vault.Secret{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"expire_time": "2024-01-01T00:00:00.000000000Z",
|
||||||
|
"ttl": json.Number("3600"),
|
||||||
|
"type": "service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cache: true,
|
||||||
|
},
|
||||||
|
"ShortTTLExpirable": {
|
||||||
|
message: "should not cache if expirable token is about to expire",
|
||||||
|
secret: &vault.Secret{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"expire_time": "2024-01-01T00:00:00.000000000Z",
|
||||||
|
"ttl": json.Number("5"),
|
||||||
|
"type": "service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
},
|
||||||
|
"ZeroTTLExpirable": {
|
||||||
|
message: "should not cache if expirable token has TTL of 0",
|
||||||
|
secret: &vault.Secret{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"expire_time": "2024-01-01T00:00:00.000000000Z",
|
||||||
|
"ttl": json.Number("0"),
|
||||||
|
"type": "service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
},
|
||||||
|
"NonExpirable": {
|
||||||
|
message: "should cache if token is non-expirable",
|
||||||
|
secret: &vault.Secret{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"expire_time": nil,
|
||||||
|
"ttl": json.Number("0"),
|
||||||
|
"type": "service",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cache: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
token := fake.Token{
|
||||||
|
LookupSelfWithContextFn: func(ctx context.Context) (*vault.Secret, error) {
|
||||||
|
return tc.secret, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cached, err := checkToken(context.Background(), token)
|
||||||
|
if cached != tc.cache || err != nil {
|
||||||
|
t.Errorf("%v: err = %v", tc.message, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue