1414package credentialrefresher
1515
1616import (
17+ "bufio"
1718 "context"
1819 "fmt"
20+ "io"
1921 "math"
2022 "math/rand"
2123 "net/http"
24+ "os"
25+ "path/filepath"
2226 "runtime/debug"
27+ "strings"
2328 "sync"
2429 "time"
2530
2631 "github.com/aws/amazon-ssm-agent/agent/appconfig"
2732 "github.com/aws/amazon-ssm-agent/agent/backoffconfig"
33+ "github.com/aws/amazon-ssm-agent/agent/fileutil"
2834 "github.com/aws/amazon-ssm-agent/agent/log"
35+ "github.com/aws/amazon-ssm-agent/agent/log/logger"
2936 "github.com/aws/amazon-ssm-agent/agent/managedInstances/sharedCredentials"
37+ "github.com/aws/amazon-ssm-agent/agent/versionutil"
3038 "github.com/aws/amazon-ssm-agent/common/identity"
3139 "github.com/aws/amazon-ssm-agent/common/identity/credentialproviders"
40+ "github.com/aws/amazon-ssm-agent/common/identity/credentialproviders/ec2roleprovider"
3241 "github.com/aws/amazon-ssm-agent/common/identity/endpoint"
3342 identity2 "github.com/aws/amazon-ssm-agent/common/identity/identity"
3443 "github.com/aws/amazon-ssm-agent/common/runtimeconfig"
3544 agentctx "github.com/aws/amazon-ssm-agent/core/app/context"
36-
45+ "github.com/aws/amazon-ssm-agent/core/executor"
3746 "github.com/aws/aws-sdk-go/aws/awserr"
3847 "github.com/aws/aws-sdk-go/aws/credentials"
3948 "github.com/aws/aws-sdk-go/service/ssm"
40-
4149 "github.com/cenkalti/backoff/v4"
4250)
4351
4452const credentialSourceEC2 = "EC2"
4553
46- var storeSharedCredentials = sharedCredentials .Store
47- var purgeSharedCredentials = sharedCredentials .Purge
48- var backoffRetry = backoff .Retry
49- var newSharedCredentials = credentials .NewSharedCredentials
54+ var (
55+ storeSharedCredentials = sharedCredentials .Store
56+ purgeSharedCredentials = sharedCredentials .Purge
57+ backoffRetry = backoff .Retry
58+ newSharedCredentials = credentials .NewSharedCredentials
5059
51- // Indicates the retrier should retry call for systems manager provided credentials
52- var shouldRetry = true
60+ // Indicates the retrier should retry call for systems manager provided credentials
61+ shouldRetry = true
62+
63+ fileExists = fileutil .Exists
64+ getFileNames = fileutil .GetFileNames
65+ newProcessExecutor = executor .NewProcessExecutor
66+ osOpen = os .Open
67+ isCredSaveDefaultSSMAgentVersionPresentUsingReader = isCredSaveDefaultSSMAgentVersionPresentUsingIoReader
68+ )
5369
5470// Map of known http responses when requesting credentials from systems manager
5571var statusCodes = map [int ]bool {
@@ -61,6 +77,12 @@ var statusCodes = map[int]bool{
6177 http .StatusMethodNotAllowed : ! shouldRetry ,
6278}
6379
80+ const (
81+ // last version in 3.2 that share cred file between workers for EC2
82+ defaultSSMEC2SharedFileUsageLastVersion = "3.2.1241.0"
83+ defaultSSMStartVersion = "3.2.0.0"
84+ )
85+
6486type ICredentialRefresher interface {
6587 Start () error
6688 Stop ()
@@ -305,10 +327,45 @@ func (c *credentialsRefresher) credentialRefresherRoutine() {
305327 c .log .Flush ()
306328 return
307329 }
330+ credentialSource := c .provider .CredentialSource ()
331+ isEC2CredentialSource := credentialSource == ec2roleprovider .CredentialSourceEC2
332+ isEc2CredFilePresent := fileExists (appconfig .DefaultEC2SharedCredentialsFilePath )
333+
334+ c .log .Tracef ("Credential source %v" , isEC2CredentialSource )
335+ c .log .Tracef ("Cred file present %v" , isEc2CredFilePresent )
336+
337+ isCredFilePurged := false
338+
339+ if isEC2CredentialSource && isEc2CredFilePresent {
340+ documentSessionWorkerRunning := c .isDocumentSessionWorkerProcessRunning ()
341+ credSaveDefaultSSMAgentPresent := c .credentialFileConsumerPresent ()
342+ c .log .Tracef ("Document/session worker source %v" , documentSessionWorkerRunning )
343+ c .log .Tracef ("Cred save default ssm agent %v" , credSaveDefaultSSMAgentPresent )
344+ if ! (documentSessionWorkerRunning && credSaveDefaultSSMAgentPresent ) {
345+ c .log .Info ("Starting credential purging" )
346+ err = backoffRetry (func () error {
347+ return purgeSharedCredentials (appconfig .DefaultEC2SharedCredentialsFilePath )
348+ }, c .backoffConfig )
349+ if err != nil {
350+ c .log .Warnf ("error while purging cred file: %v" , err )
351+ } else {
352+ isCredFilePurged = true
353+ }
354+ }
355+ }
308356
309357 // ShareFile may be updated after retrieveCredsWithRetry()
310358 newShareFile := c .provider .ShareFile ()
311- if newShareFile != "" {
359+ if isCredFilePurged {
360+ c .log .Info ("Credential file purged" )
361+ } else {
362+ // when ShouldPurgeInstanceProfileRoleCreds config is used,
363+ // the credential file created in 3.2 for EC2 will be deleted irrespective of whether doc/session worker is running or not
364+ c .tryPurgeCreds (newShareFile )
365+ }
366+
367+ // skip saving when the credential source is EC2
368+ if ! isEC2CredentialSource && newShareFile != "" {
312369 err = backoffRetry (func () error {
313370 return storeSharedCredentials (c .log , creds .AccessKeyID , creds .SecretAccessKey , creds .SessionToken ,
314371 newShareFile , c .identityRuntimeConfig .ShareProfile , false )
@@ -335,7 +392,7 @@ func (c *credentialsRefresher) credentialRefresherRoutine() {
335392 configCopy .CredentialsRetrievedAt = credentialsRetrievedAt
336393 configCopy .CredentialsExpiresAt = c .provider .RemoteExpiresAt ()
337394 configCopy .ShareFile = newShareFile
338- configCopy .CredentialSource = c . provider . CredentialSource ()
395+ configCopy .CredentialSource = credentialSource
339396 err = backoffRetry (func () error {
340397 return c .runtimeConfigClient .SaveConfig (configCopy )
341398 }, c .backoffConfig )
@@ -344,17 +401,16 @@ func (c *credentialsRefresher) credentialRefresherRoutine() {
344401 continue
345402 }
346403
347- c .tryPurgeCreds (& configCopy )
348404 c .identityRuntimeConfig = configCopy
349405 c .sendCredentialsReadyMessage ()
350406 }
351407 }
352408}
353409
354- func (c * credentialsRefresher ) tryPurgeCreds (configCopy * runtimeconfig. IdentityRuntimeConfig ) {
410+ func (c * credentialsRefresher ) tryPurgeCreds (newShareFile string ) {
355411 // Credentials are not purged until agent versions where EC2 agent workers
356412 // only consume shared credentials are fully deprecated
357- shouldPurgeCreds := configCopy . ShareFile != c .identityRuntimeConfig .ShareFile && c .appConfig .Agent .ShouldPurgeInstanceProfileRoleCreds
413+ shouldPurgeCreds := newShareFile != c .identityRuntimeConfig .ShareFile && c .appConfig .Agent .ShouldPurgeInstanceProfileRoleCreds
358414 purgeFileLocation := c .identityRuntimeConfig .ShareFile
359415
360416 if shouldPurgeCreds && purgeFileLocation != "" {
@@ -365,6 +421,114 @@ func (c *credentialsRefresher) tryPurgeCreds(configCopy *runtimeconfig.IdentityR
365421 c .log .Warn ("Skipping purge of default aws shared credentials path" )
366422 } else if err = purgeSharedCredentials (purgeFileLocation ); err != nil {
367423 c .log .Warnf ("Failed to purge old credentials. Err: %v" , err )
424+ } else {
425+ c .log .Info ("Credential file purged" )
426+ }
427+ }
428+ }
429+
430+ // isDocumentSessionWorkerProcessRunning checks whether document and session worker is running or not
431+ func (c * credentialsRefresher ) isDocumentSessionWorkerProcessRunning () bool {
432+ var isDocumentSessionWorkerFound = false
433+ var allProcesses []executor.OsProcess
434+ var err error
435+ processExecutor := newProcessExecutor (c .log )
436+ if allProcesses , err = processExecutor .Processes (); err != nil {
437+ c .log .Warnf ("error while getting process list: %v" , err )
438+ return isDocumentSessionWorkerFound
439+ }
440+ for _ , process := range allProcesses {
441+ executableName := strings .ToLower (process .Executable )
442+ if strings .Contains (executableName , appconfig .SSMDocumentWorkerName ) {
443+ c .log .Infof ("document worker with pid <%v> running" , process .Pid )
444+ isDocumentSessionWorkerFound = true
445+ break
446+ }
447+ if strings .Contains (executableName , appconfig .SSMSessionWorkerName ) {
448+ c .log .Infof ("session worker with pid <%v> running" , process .Pid )
449+ isDocumentSessionWorkerFound = true
450+ break
451+ }
452+ }
453+ return isDocumentSessionWorkerFound
454+ }
455+
456+ // credentialFileConsumerPresent checks whether default SSM agent which saves credential file was installed
457+ // during the last 72 hours by looking into audit file
458+ func (c * credentialsRefresher ) credentialFileConsumerPresent () bool {
459+ credSaveDefaultSSMAgentVersionPresent := false
460+ auditFileDateTimeFormat := "2006-01-02"
461+ auditFolderPath := filepath .Join (logger .DefaultLogDir , logger .AuditFolderName )
462+ auditFileNames , err := getFileNames (auditFolderPath )
463+ if err != nil {
464+ c .log .Warnf ("error while getting file names: %v" , err )
465+ return credSaveDefaultSSMAgentVersionPresent
466+ }
467+ currentTimeStamp := time .Now ()
468+ threeDaysBeforeDate := time .Date (currentTimeStamp .Year (), currentTimeStamp .Month (), currentTimeStamp .Day (), 0 , 0 , 0 , 0 , time .UTC ).AddDate (0 , 0 , - 3 )
469+
470+ for _ , fileName := range auditFileNames {
471+ // get date time from audit file name considering datetime format will be in "2006-01-02"
472+ dateFromAuditFileName := fileName [len (fileName )- len (auditFileDateTimeFormat ):]
473+
474+ // checks whether datetime in file name matches with format "2006-01-02"
475+ eventFileDateStamp , err := time .Parse (auditFileDateTimeFormat , dateFromAuditFileName )
476+ if err != nil {
477+ c .log .Warnf ("error while parsing audit file name for date stamp: %v" , err )
478+ credSaveDefaultSSMAgentVersionPresent = false
479+ }
480+
481+ eventFileDateStampUTC := eventFileDateStamp .UTC ()
482+ if err == nil && eventFileDateStampUTC .After (threeDaysBeforeDate ) {
483+ func () {
484+ file , err := osOpen (filepath .Join (auditFolderPath , fileName ))
485+ if err != nil {
486+ c .log .Warnf ("error while reading audit file: %v" , err )
487+ return
488+ }
489+ defer file .Close ()
490+
491+ if isCredSaveDefaultSSMAgentVersionPresentUsingReader (file ) {
492+ credSaveDefaultSSMAgentVersionPresent = true
493+ }
494+ }()
495+ }
496+
497+ if credSaveDefaultSSMAgentVersionPresent == true {
498+ c .log .Infof ("audit file with cred save default SSM Agent present" )
499+ break
500+ }
501+ }
502+ return credSaveDefaultSSMAgentVersionPresent
503+ }
504+
505+ // isCredSaveDefaultSSMAgentVersionPresentUsingIoReader checks whether default SSM agent which saves
506+ // credential file is present in reader or not
507+ func isCredSaveDefaultSSMAgentVersionPresentUsingIoReader (reader io.Reader ) bool {
508+ credSaveDefaultSSMAgentVersionPresent := false
509+ // Create a new scanner for the file.
510+ scanner := bufio .NewScanner (reader )
511+ // Loop over the lines in the file.
512+ for scanner .Scan () {
513+ splitVal := strings .Split (scanner .Text (), " " )
514+ // agent_telemetry event type should have only four fields
515+ if len (splitVal ) != 4 {
516+ continue
517+ }
518+ // not an agent telemetry event type
519+ if splitVal [0 ] != logger .AgentTelemetryMessage {
520+ continue
521+ }
522+ versionCompare , err := versionutil .VersionCompare (splitVal [2 ], defaultSSMEC2SharedFileUsageLastVersion )
523+ if err != nil || versionCompare > 0 {
524+ continue
525+ }
526+ versionCompare , err = versionutil .VersionCompare (splitVal [2 ], defaultSSMStartVersion )
527+ if err != nil || versionCompare < 0 {
528+ continue
368529 }
530+ credSaveDefaultSSMAgentVersionPresent = true
531+ break
369532 }
533+ return credSaveDefaultSSMAgentVersionPresent
370534}
0 commit comments