Skip to content

Commit

Permalink
fix: format session key for filename compatibility
Browse files Browse the repository at this point in the history
The session key has to be compatible with all Keyring backend stores,
which includes a File store that uses the key as a filename.

Colon is a disallowed filename character on Windows, so the separator
was changed to a comma.

KeyringSession fields have been updated to reflect the structured data
encoded in the session key, and the parser now only returns a populated
KeyringSession if the key was in the expected format.
  • Loading branch information
jstewmon committed Oct 29, 2019
1 parent 36c2635 commit ad2e94a
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 40 deletions.
10 changes: 7 additions & 3 deletions cli/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,19 @@ func LsCommand(app *kingpin.Application, input LsCommandInput) {
fmt.Fprintf(w, "-\t")
}

var sessionIDs []string
var sessionLabels []string
for _, sess := range sessions {
if profile.Name == sess.Profile.Name {
sessionIDs = append(sessionIDs, sess.SessionID)
label := fmt.Sprintf("%d", sess.Expiration.Unix())
if sess.MfaSerial != "" {
label += " (mfa)"
}
sessionLabels = append(sessionLabels, label)
}
}

if len(sessions) > 0 {
fmt.Fprintf(w, "%s\t\n", strings.Join(sessionIDs, ", "))
fmt.Fprintf(w, "%s\t\n", strings.Join(sessionLabels, ", "))
} else {
fmt.Fprintf(w, "-\t\n")
}
Expand Down
75 changes: 40 additions & 35 deletions vault/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import (
"github.com/aws/aws-sdk-go/service/sts"
)

var sessionKeyPattern = regexp.MustCompile(`^session:(?P<profile>[^:]+):(?P<mfaSerial>[^:]*):(?P<expiration>[^:]+)$`)
var sessionKeyPattern = regexp.MustCompile(`^session,(?P<profile>[^,]+),(?P<mfaSerial>[^,]*),(?P<expiration>[^:]+)$`)
var oldSessionKeyPatterns = []*regexp.Regexp{
regexp.MustCompile(`^session:(?P<profile>[^ ]+):(?P<mfaSerial>[^ ]*):(?P<expiration>[^:]+)$`),
regexp.MustCompile(`^(.+?) session \((\d+)\)$`),
}
var base64Encoding = base64.URLEncoding.WithPadding(base64.NoPadding)
Expand All @@ -32,42 +33,51 @@ func IsSessionKey(s string) bool {
return false
}

func parseKeyringSession(s string, conf *Config) (KeyringSession, error) {
matches := sessionKeyPattern.FindStringSubmatch(s)
func parseSessionKey(key string, conf *Config) (KeyringSession, error) {
matches := sessionKeyPattern.FindStringSubmatch(key)
if len(matches) == 0 {
return KeyringSession{}, errors.New("failed to parse session name")
}
profileName, _ := base64Encoding.DecodeString(matches[1])
mfaSerial, _ := base64Encoding.DecodeString(matches[2])
sessionId := matches[3]
profileName, err := base64Encoding.DecodeString(matches[1])
if err != nil {
return KeyringSession{}, err
}
mfaSerial, err := base64Encoding.DecodeString(matches[2])
if err != nil {
return KeyringSession{}, err
}
tsInt, err := strconv.ParseInt(matches[3], 10, 64)
if err != nil {
return KeyringSession{}, err
}
profile, _ := conf.Profile(string(profileName))
return KeyringSession{
Profile: profile,
Name: s,
SessionID: sessionId,
MfaSerial: string(mfaSerial),
Profile: profile,
Key: key,
Expiration: time.Unix(tsInt, 0),
MfaSerial: string(mfaSerial),
}, nil
}

func formatSessionKey(profile string, mfaSerial string, expiration *time.Time) string {
return fmt.Sprintf(
"session,%s,%s,%d",
base64Encoding.EncodeToString([]byte(profile)),
base64Encoding.EncodeToString([]byte(mfaSerial)),
expiration.Unix(),
)
}

type KeyringSession struct {
Profile
Name string
SessionID string
MfaSerial string
Key string
Expiration time.Time
MfaSerial string
}

func (ks KeyringSession) IsExpired() bool {
// Older sessions were 20 characters long and opaque identifiers
if len(ks.SessionID) == 20 {
return true
}
// Now our session id's are timestamps
tsInt, err := strconv.ParseInt(ks.SessionID, 10, 64)
if err != nil {
return true
}
log.Printf("Session %q expires in %v", ks.Name, time.Unix(tsInt, 0).Sub(time.Now()).String())
return time.Now().After(time.Unix(tsInt, 0))
log.Printf("Session %q expires in %v", ks.Key, ks.Expiration.Sub(time.Now()).String())
return time.Now().After(ks.Expiration)
}

type KeyringSessions struct {
Expand All @@ -93,7 +103,7 @@ func (s *KeyringSessions) Sessions() ([]KeyringSession, error) {

for _, k := range keys {
if IsSessionKey(k) {
ks, err := parseKeyringSession(k, s.Config)
ks, err := parseSessionKey(k, s.Config)
if err != nil || ks.IsExpired() {
log.Printf("Session %s is obsolete, attempting deleting", k)
if err := s.Keyring.Remove(k); err != nil {
Expand All @@ -119,7 +129,7 @@ func (s *KeyringSessions) Retrieve(profile string, mfaSerial string) (creds sts.

for _, session := range sessions {
if session.Profile.Name == profile && session.MfaSerial == mfaSerial {
item, err := s.Keyring.Get(session.Name)
item, err := s.Keyring.Get(session.Key)
if err != nil {
return creds, err
}
Expand All @@ -130,7 +140,7 @@ func (s *KeyringSessions) Retrieve(profile string, mfaSerial string) (creds sts.

// double check the actual expiry time
if creds.Expiration.Before(time.Now()) {
log.Printf("Session %q is expired, deleting", session.Name)
log.Printf("Session %q is expired, deleting", session.Key)
if err = s.Keyring.Remove(session.Profile.Name); err != nil {
return creds, err
}
Expand All @@ -151,12 +161,7 @@ func (s *KeyringSessions) Store(profile string, mfaSerial string, session sts.Cr
return err
}

key := fmt.Sprintf(
"session:%s:%s:%d",
base64Encoding.EncodeToString([]byte(profile)),
base64Encoding.EncodeToString([]byte(mfaSerial)),
session.Expiration.Unix(),
)
key := formatSessionKey(profile, mfaSerial, session.Expiration)
log.Printf("Writing session for %s to keyring: %q", profile, key)

return s.Keyring.Set(keyring.Item{
Expand All @@ -180,8 +185,8 @@ func (s *KeyringSessions) Delete(profile string) (n int, err error) {

for _, session := range sessions {
if session.Profile.Name == profile {
log.Printf("Session %q matches profile %q", session.Name, profile)
if err = s.Keyring.Remove(session.Name); err != nil {
log.Printf("Session %q matches profile %q", session.Key, profile)
if err = s.Keyring.Remove(session.Key); err != nil {
return n, err
}
n++
Expand Down
4 changes: 2 additions & 2 deletions vault/sessions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ func TestIsSessionKey(t *testing.T) {
{"blah", false},
{"blah session (61633665646639303539)", true},
{"blah-iam session (32383863333237616430)", true},
{"session:c2Vzc2lvbg::1572281751", true},
{"session:c2Vzc2lvbg:YXJuOmF3czppYW06OjEyMzQ1Njc4OTA6bWZhL2pzdGV3bW9u:1572281751", true},
{"session,c2Vzc2lvbg,,1572281751", true},
{"session,c2Vzc2lvbg,YXJuOmF3czppYW06OjEyMzQ1Njc4OTA6bWZhL2pzdGV3bW9u,1572281751", true},
}

for _, tc := range testCases {
Expand Down

0 comments on commit ad2e94a

Please sign in to comment.