-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Credential Caching for the process-creds command (#158)
* Initial thoughts on changed file structure * First working (returns valid looking result) version * add beeline to credProcessRun for timing check * remove some extraneous comments * Move creds process cache logic to pkg * change package name to match dir structure
- Loading branch information
1 parent
aca8b01
commit 9d9503a
Showing
3 changed files
with
273 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package creds_process | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/chanzuckerberg/aws-oidc/pkg/aws_config_client" | ||
"github.com/chanzuckerberg/go-misc/oidc_cli/storage" | ||
"github.com/chanzuckerberg/go-misc/pidlock" | ||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
type Cache struct { | ||
storage storage.Storage | ||
lock *pidlock.Lock | ||
|
||
updateCred func(context.Context, *aws_config_client.AWSOIDCConfiguration) (*ProcessedCred, error) | ||
} | ||
|
||
func NewCache( | ||
storage storage.Storage, | ||
credGetter func(context.Context, *aws_config_client.AWSOIDCConfiguration) (*ProcessedCred, error), | ||
lock *pidlock.Lock, | ||
) *Cache { | ||
return &Cache{ | ||
storage: storage, | ||
updateCred: credGetter, | ||
lock: lock, | ||
} | ||
} | ||
|
||
// Read will attempt to read a cred from the cache | ||
// if not present or expired, will refresh | ||
func (c *Cache) Read(ctx context.Context, config *aws_config_client.AWSOIDCConfiguration) (*ProcessedCred, error) { | ||
cachedCred, err := c.readFromStorage(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// if we have a valid cred, use it | ||
if cachedCred.IsFresh() { | ||
return cachedCred, nil | ||
} | ||
|
||
// otherwise, try refreshing | ||
return c.refresh(ctx, config) | ||
} | ||
|
||
func (c *Cache) refresh(ctx context.Context, config *aws_config_client.AWSOIDCConfiguration) (*ProcessedCred, error) { | ||
err := c.lock.Lock() | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer c.lock.Unlock() //nolint:errcheck | ||
|
||
// acquire lock, try reading from cache again just in case | ||
// someone else got here first | ||
cachedCred, err := c.readFromStorage(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// if we have a valid cred, use it | ||
if cachedCred.IsFresh() { | ||
return cachedCred, nil | ||
} | ||
|
||
// ok, at this point we have the lock and there are no good creds around | ||
// fetch a new one and save it | ||
cred, err := c.updateCred(ctx, config) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// check the new cred is good to use | ||
if !cred.IsFresh() { | ||
return nil, errors.New("invalid cred fetched") | ||
} | ||
|
||
strCred, err := cred.Marshal() | ||
|
||
if err != nil { | ||
return nil, errors.Wrap(err, "unable to marshall cred") | ||
} | ||
// save cred to storage | ||
err = c.storage.Set(ctx, strCred) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "Unable to cache the strCred") | ||
} | ||
|
||
return cred, nil | ||
} | ||
|
||
// reads cred from storage, potentially returning a nil/expired cred | ||
// users must call IsFresh to check cred validty | ||
func (c *Cache) readFromStorage(ctx context.Context) (*ProcessedCred, error) { | ||
cached, err := c.storage.Read(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
cachedCred, err := CredFromString(cached) | ||
if err != nil { | ||
logrus.WithError(err).Debug("error fetching stored cred") | ||
err = c.storage.Delete(ctx) // can't read it, so attempt to purge it | ||
if err != nil { | ||
logrus.WithError(err).Debug("error clearing cred from storage") | ||
} | ||
} | ||
return cachedCred, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package creds_process | ||
|
||
import ( | ||
"encoding/base64" | ||
"encoding/json" | ||
"time" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// Used to store the parsed results from running assumeRole | ||
type ProcessedCred struct { | ||
Version int `json:"Version"` | ||
AccessKeyID string `json:"AccessKeyId"` | ||
SecretAccessKey string `json:"SecretAccessKey"` | ||
SessionToken string `json:"SessionToken"` | ||
Expiration string `json:"Expiration"` | ||
CacheExpiry time.Time `json:"CacheExpiration"` | ||
} | ||
|
||
func (pc *ProcessedCred) IsFresh() bool { | ||
if pc == nil { | ||
return false | ||
} | ||
return pc.CacheExpiry.After(time.Now().Add(timeSkew)) | ||
} | ||
|
||
const ( | ||
timeSkew = 5 * time.Minute | ||
ProcessedCredVersion = 1 | ||
) | ||
|
||
func CredFromString(credString *string, opts ...MarshalOpts) (*ProcessedCred, error) { | ||
if credString == nil { | ||
logrus.Debug("nil cred string") | ||
return nil, nil | ||
} | ||
credBytes, err := base64.StdEncoding.DecodeString(*credString) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "error b64 decoding token") | ||
} | ||
pc := &ProcessedCred{ | ||
Version: ProcessedCredVersion, | ||
} | ||
err = json.Unmarshal(credBytes, pc) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "could not json unmarshal cred") | ||
} | ||
|
||
for _, opt := range opts { | ||
opt(pc) | ||
} | ||
return pc, nil | ||
} | ||
|
||
func (pc *ProcessedCred) Marshal(opts ...MarshalOpts) (string, error) { | ||
if pc == nil { | ||
return "", errors.New("error Marshalling nil token") | ||
} | ||
|
||
// apply any processing to the token | ||
for _, opt := range opts { | ||
opt(pc) | ||
} | ||
|
||
credBytes, err := json.Marshal(pc) | ||
if err != nil { | ||
return "", errors.Wrap(err, "could not marshal token") | ||
} | ||
|
||
b64 := base64.StdEncoding.EncodeToString(credBytes) | ||
return b64, nil | ||
} | ||
|
||
// MarshalOpts changes a token for marshaling | ||
type MarshalOpts func(*ProcessedCred) |