Skip to content

Commit 2dfe3b9

Browse files
VishnuKarthikRavindrangianniLesl
authored andcommitted
Add support for purging EC2 credential file with delay
cr: https://code.amazon.com/reviews/CR-93134477
1 parent dd72377 commit 2dfe3b9

File tree

6 files changed

+543
-25
lines changed

6 files changed

+543
-25
lines changed

agent/log/logger/eventlog.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// either express or implied. See the License for the specific language governing
1212
// permissions and limitations under the License.
1313

14-
// Package log is used to initialize the logger(main logger and event logger). This package should be imported once, usually from main, then call GetLogger.
14+
// Package logger is used to initialize the logger(main logger and event logger). This package should be imported once, usually from main, then call GetLogger.
1515
package logger
1616

1717
import (
@@ -32,6 +32,7 @@ import (
3232
var (
3333
eventLogInst *EventLog
3434
singleSpacePattern = regexp.MustCompile(`\s+`)
35+
AuditFolderName = "audits"
3536
)
3637

3738
// GetEventLog returns the Event log instance and is called by the SSM Logger during app startup
@@ -48,7 +49,7 @@ func GetEventLog(logFilePath string, logFileName string) (eventLog *EventLog) {
4849
eventChannel: make(chan string, 2),
4950
noOfHistoryFiles: maxRollsDay,
5051
schemaVersion: "1",
51-
eventLogPath: filepath.Join(logFilePath, "audits"),
52+
eventLogPath: filepath.Join(logFilePath, AuditFolderName),
5253
eventLogName: logFileName,
5354
datePattern: "2006-01-02",
5455
fileSystem: filesystem.NewFileSystem(),

common/runtimeconfig/identity_runtimeconfig.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ const (
2626
identityConfig = "identity_config.json"
2727
)
2828

29+
const (
30+
runtimeConfigSchemaVersion = "1.1"
31+
)
32+
2933
type IdentityRuntimeConfig struct {
34+
SchemaVersion string
3035
InstanceId string
3136
IdentityType string
3237
ShareFile string
@@ -101,13 +106,15 @@ func (i *identityRuntimeConfigClient) GetConfigWithRetry() (out IdentityRuntimeC
101106
}
102107

103108
func (i *identityRuntimeConfigClient) SaveConfig(config IdentityRuntimeConfig) error {
109+
110+
// update runtime config version
111+
config.SchemaVersion = runtimeConfigSchemaVersion
112+
104113
bytesContent, err := json.Marshal(config)
105114
if err != nil {
106115
return fmt.Errorf("error encoding identity runtime config: %v", err)
107116
}
108-
109117
err = i.configHandler.SaveConfig(bytesContent)
110-
111118
if err != nil {
112119
return err
113120
}

common/runtimeconfig/identity_runtimeconfig_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func Test_identityRuntimeConfigClient_ConfigExists(t *testing.T) {
2929
func Test_identityRuntimeConfigClient_GetConfig(t *testing.T) {
3030
var emptyConfig IdentityRuntimeConfig
3131
parsedConfig := IdentityRuntimeConfig{
32+
"1.1",
3233
"InstanceId",
3334
"IdentityType",
3435
"ShareFile",
@@ -100,6 +101,7 @@ func Test_identityRuntimeConfigClient_GetConfig(t *testing.T) {
100101

101102
func Test_identityRuntimeConfigClient_SaveConfig(t *testing.T) {
102103
successConfig := IdentityRuntimeConfig{
104+
"1.1",
103105
"InstanceId",
104106
"IdentityType",
105107
"ShareFile",
@@ -109,7 +111,7 @@ func Test_identityRuntimeConfigClient_SaveConfig(t *testing.T) {
109111
"SSM",
110112
}
111113
successContent, _ := json.Marshal(successConfig)
112-
failContent, _ := json.Marshal(IdentityRuntimeConfig{})
114+
failContent, _ := json.Marshal(IdentityRuntimeConfig{SchemaVersion: "1.1"})
113115

114116
handlerMock := &mocks.IRuntimeConfigHandler{}
115117
handlerMock.On("SaveConfig", successContent).Return(nil)
@@ -163,6 +165,7 @@ func Test_identityRuntimeConfigClient_SaveConfig(t *testing.T) {
163165

164166
func Test_identityRuntimeConfigClient_SaveConfig_VerifyFailGetConfig(t *testing.T) {
165167
config := IdentityRuntimeConfig{
168+
"1.1",
166169
"InstanceId",
167170
"IdentityType",
168171
"ShareFile",
@@ -189,6 +192,7 @@ func Test_identityRuntimeConfigClient_SaveConfig_VerifyFailGetConfig(t *testing.
189192

190193
func Test_identityRuntimeConfigClient_SaveConfig_VerifyFailConfigEquals(t *testing.T) {
191194
correctConfig := IdentityRuntimeConfig{
195+
"1.1",
192196
"InstanceId",
193197
"IdentityType",
194198
"ShareFile",
@@ -199,6 +203,7 @@ func Test_identityRuntimeConfigClient_SaveConfig_VerifyFailConfigEquals(t *testi
199203
}
200204

201205
wrongConfig := IdentityRuntimeConfig{
206+
"1.1",
202207
"InstanceId",
203208
"SomeOtherIdentityType",
204209
"ShareFile",
@@ -239,6 +244,7 @@ func TestIdentityRuntimeConfig_Equal(t *testing.T) {
239244

240245
baselineArg := args{
241246
IdentityRuntimeConfig{
247+
"1.1",
242248
"InstanceId",
243249
"IdentityType",
244250
"ShareFile",

core/app/credentialrefresher/credentialrefresher.go

Lines changed: 177 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,58 @@
1414
package credentialrefresher
1515

1616
import (
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

4452
const 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
5571
var 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+
6486
type 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

Comments
 (0)