// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, // either express or implied. See the License for the specific language governing // permissions and limitations under the License. package runtimeconfig import ( "encoding/json" "fmt" "time" rch "github.com/aws/amazon-ssm-agent/common/runtimeconfig/runtimeconfighandler" "github.com/cenkalti/backoff/v4" ) const ( identityConfig = "identity_config.json" ) const ( runtimeConfigSchemaVersion = "1.1" ) type IdentityRuntimeConfig struct { SchemaVersion string InstanceId string IdentityType string ShareFile string ShareProfile string CredentialsExpiresAt time.Time CredentialsRetrievedAt time.Time CredentialSource string } func (i IdentityRuntimeConfig) Equal(config IdentityRuntimeConfig) bool { sameId := i.InstanceId == config.InstanceId sameProfile := i.ShareProfile == config.ShareProfile sameFile := i.ShareFile == config.ShareFile sameType := i.IdentityType == config.IdentityType return sameId && sameProfile && sameFile && sameType } func NewIdentityRuntimeConfigClient() IIdentityRuntimeConfigClient { return &identityRuntimeConfigClient{ configHandler: rch.NewRuntimeConfigHandler(identityConfig), } } type IIdentityRuntimeConfigClient interface { ConfigExists() (bool, error) GetConfig() (IdentityRuntimeConfig, error) GetConfigWithRetry() (IdentityRuntimeConfig, error) SaveConfig(IdentityRuntimeConfig) error } type identityRuntimeConfigClient struct { configHandler rch.IRuntimeConfigHandler } func (i *identityRuntimeConfigClient) ConfigExists() (bool, error) { return i.configHandler.ConfigExists() } func (i *identityRuntimeConfigClient) GetConfig() (IdentityRuntimeConfig, error) { var config IdentityRuntimeConfig bytesContent, err := i.configHandler.GetConfig() if err != nil { return config, err } err = json.Unmarshal(bytesContent, &config) if err != nil { return config, fmt.Errorf("error decoding identity runtime config: %v", err) } return config, nil } func (i *identityRuntimeConfigClient) GetConfigWithRetry() (out IdentityRuntimeConfig, err error) { backoffConfig := backoff.NewExponentialBackOff() // Attempts GetConfig up to 6 times with exponential backoff backoffConfig.MaxElapsedTime = time.Second * 4 err = backoff.Retry(func() error { if configExists, existsError := i.ConfigExists(); err != nil { return fmt.Errorf("failed to check whether config extists. Err: %w", existsError) } else if !configExists { return nil } out, err = i.GetConfig() return err }, backoffConfig) return } func (i *identityRuntimeConfigClient) SaveConfig(config IdentityRuntimeConfig) error { // update runtime config version config.SchemaVersion = runtimeConfigSchemaVersion bytesContent, err := json.Marshal(config) if err != nil { return fmt.Errorf("error encoding identity runtime config: %v", err) } err = i.configHandler.SaveConfig(bytesContent) if err != nil { return err } // Because of the importance of identityRuntimeConfig, we want to make sure the file is readable after writing savedConfig, err := i.GetConfig() if err != nil { return fmt.Errorf("failed to validate config is readable after writing: %v", err) } // verify saved config and config to be saved are equivalent if !savedConfig.Equal(config) { return fmt.Errorf("failed to verify config on disk is equivalent to the config that was saved") } return nil }