Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Bug 1941: Support reading S3 credentials from AWS CLI configuration
https://winscp.net/tracker/1941
(cherry picked from commit 1bcbb70)

# Conflicts:
#	source/core/SessionData.cpp

Source commit: 1eb78423984500d739fd67928595a89e562ca9e0
  • Loading branch information
martinprikryl committed Nov 23, 2021
1 parent 31b8a5e commit 0e894c3
Show file tree
Hide file tree
Showing 12 changed files with 333 additions and 61 deletions.
109 changes: 107 additions & 2 deletions source/core/S3FileSystem.cpp
Expand Up @@ -27,6 +27,13 @@
//---------------------------------------------------------------------------
#define FILE_OPERATION_LOOP_TERMINAL FTerminal
//---------------------------------------------------------------------------
#define AWS_ACCESS_KEY_ID L"AWS_ACCESS_KEY_ID"
#define AWS_SECRET_ACCESS_KEY L"AWS_SECRET_ACCESS_KEY"
#define AWS_SESSION_TOKEN L"AWS_SESSION_TOKEN"
#define AWS_CONFIG_FILE L"AWS_CONFIG_FILE"
#define AWS_PROFILE L"AWS_PROFILE"
#define AWS_PROFILE_DEFAULT L"default"
//---------------------------------------------------------------------------
static std::unique_ptr<TCriticalSection> LibS3Section(TraceInitPtr(new TCriticalSection()));
//---------------------------------------------------------------------------
UTF8String LibS3Delimiter(L"/");
Expand All @@ -46,6 +53,84 @@ UnicodeString __fastcall S3LibDefaultRegion()
return StrFromS3(S3_DEFAULT_REGION);
}
//---------------------------------------------------------------------------
bool S3ConfigFileTried = false;
std::unique_ptr<TCustomIniFile> S3ConfigFile;
UnicodeString S3Profile;
//---------------------------------------------------------------------------
UnicodeString GetS3ConfigValue(const UnicodeString & Name, UnicodeString * Source)
{
UnicodeString Result;
UnicodeString ASource;
TGuard Guard(LibS3Section.get());
try
{
Result = GetEnvironmentVariable(Name);
if (!Result.IsEmpty())
{
ASource = FORMAT(L"%%%s%%", (Name));
}
else
{
if (!S3ConfigFileTried)
{
S3ConfigFileTried = true;

S3Profile = GetEnvironmentVariable(AWS_PROFILE);
if (S3Profile.IsEmpty())
{
S3Profile = AWS_PROFILE_DEFAULT;
}

UnicodeString ConfigFileName = GetEnvironmentVariable(AWS_CONFIG_FILE);
if (Result.IsEmpty())
{
UnicodeString ProfilePath = GetShellFolderPath(CSIDL_PROFILE);
UnicodeString DefaultConfigFileName = IncludeTrailingBackslash(ProfilePath) + L".aws\\credentials";
if (FileExists(DefaultConfigFileName))
{
ConfigFileName = DefaultConfigFileName;
}
}

S3ConfigFile.reset(new TMemIniFile(ConfigFileName));
}

if (S3ConfigFile.get() != NULL)
{
Result = S3ConfigFile->ReadString(S3Profile, Name, UnicodeString());
if (!Result.IsEmpty())
{
ASource = FORMAT(L"%s/%s", (ExtractFileName(S3ConfigFile->FileName), S3Profile));
}
}
}
}
catch (Exception & E)
{
throw ExtException(&E, MainInstructions(LoadStr(S3_CONFIG_ERROR)));
}
if (Source != NULL)
{
*Source = ASource;
}
return Result;
}
//---------------------------------------------------------------------------
UnicodeString S3EnvUserName(UnicodeString * Source)
{
return GetS3ConfigValue(AWS_ACCESS_KEY_ID, Source);
}
//---------------------------------------------------------------------------
UnicodeString S3EnvPassword(UnicodeString * Source)
{
return GetS3ConfigValue(AWS_SECRET_ACCESS_KEY, Source);
}
//---------------------------------------------------------------------------
UnicodeString S3EnvSessionToken(UnicodeString * Source)
{
return GetS3ConfigValue(AWS_SESSION_TOKEN, Source);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
const int TS3FileSystem::S3MinMultiPartChunkSize = 5 * 1024 * 1024;
const int TS3FileSystem::S3MaxMultiPartChunks = 10000;
Expand Down Expand Up @@ -105,7 +190,17 @@ void __fastcall TS3FileSystem::Open()
FAccessKeyId.SetLength(S3_MAX_ACCESS_KEY_ID_LENGTH);
}

UnicodeString SecretAccessKey = UTF8String(NormalizeString(Data->Password));
UnicodeString Password = Data->Password;
if (Password.IsEmpty() && Data->S3CredentialsEnv)
{
UnicodeString PasswordSource;
Password = S3EnvPassword(&PasswordSource);
if (!Password.IsEmpty())
{
FTerminal->LogEvent(FORMAT(L"Password (secret access key) read from %s", (PasswordSource)));
}
}
UnicodeString SecretAccessKey = UTF8String(NormalizeString(Password));
if (SecretAccessKey.IsEmpty() && !FTerminal->SessionData->FingerprintScan)
{
if (!FTerminal->PromptUser(Data, pkPassword, LoadStr(S3_SECRET_ACCESS_KEY_TITLE), L"",
Expand All @@ -117,7 +212,17 @@ void __fastcall TS3FileSystem::Open()
}
FSecretAccessKey = UTF8String(SecretAccessKey);

FSecurityTokenBuf = UTF8String(Data->S3SessionToken);
UnicodeString SessionToken = Data->S3SessionToken;
if (SessionToken.IsEmpty() && Data->S3CredentialsEnv)
{
UnicodeString SessionTokenSource;
SessionToken = S3EnvSessionToken(&SessionTokenSource);
if (!SessionToken.IsEmpty())
{
FTerminal->LogEvent(FORMAT(L"Session token read from %s", (SessionTokenSource)));
}
}
FSecurityTokenBuf = UTF8String(SessionToken);
FSecurityToken = static_cast<const char *>(FSecurityTokenBuf.data());

FHostName = UTF8String(Data->HostNameExpanded);
Expand Down
3 changes: 3 additions & 0 deletions source/core/S3FileSystem.h
Expand Up @@ -184,5 +184,8 @@ class TS3FileSystem : public TCustomFileSystem
UnicodeString __fastcall S3LibVersion();
UnicodeString __fastcall S3LibDefaultHostName();
UnicodeString __fastcall S3LibDefaultRegion();
UnicodeString S3EnvUserName(UnicodeString * Source = NULL);
UnicodeString S3EnvPassword(UnicodeString * Source = NULL);
UnicodeString S3EnvSessionToken(UnicodeString * Source = NULL);
//------------------------------------------------------------------------------
#endif
38 changes: 35 additions & 3 deletions source/core/SessionData.cpp
Expand Up @@ -233,10 +233,12 @@ void __fastcall TSessionData::DefaultSettings()
SCPLsFullTime = asAuto;
NotUtf = asAuto;
// S3
S3DefaultRegion = L"";
S3SessionToken = L"";
S3UrlStyle = s3usVirtualHost;
S3MaxKeys = asAuto;
S3CredentialsEnv = false;
// SFTP
SftpServer = L"";
Expand Down Expand Up @@ -399,6 +401,7 @@ void __fastcall TSessionData::NonPersistant()
PROPERTY(S3SessionToken); \
PROPERTY(S3UrlStyle); \
PROPERTY(S3MaxKeys); \
PROPERTY(S3CredentialsEnv); \
\
PROPERTY(ProxyMethod); \
PROPERTY(ProxyHost); \
Expand Down Expand Up @@ -746,6 +749,7 @@ void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool PuttyI
S3SessionToken = Storage->ReadString(L"S3SessionToken", S3SessionToken);
S3UrlStyle = (TS3UrlStyle)Storage->ReadInteger(L"S3UrlStyle", S3UrlStyle);
S3MaxKeys = Storage->ReadEnum(L"S3MaxKeys", S3MaxKeys, AutoSwitchMapping);
S3CredentialsEnv = Storage->ReadBool(L"S3CredentialsEnv", S3CredentialsEnv);
// PuTTY defaults to TcpNoDelay, but the psftp/pscp ignores this preference, and always set this to off (what is our default too)
if (!PuttyImport)
Expand Down Expand Up @@ -1136,6 +1140,7 @@ void __fastcall TSessionData::DoSave(THierarchicalStorage * Storage,
WRITE_DATA(String, S3SessionToken);
WRITE_DATA(Integer, S3UrlStyle);
WRITE_DATA(Integer, S3MaxKeys);
WRITE_DATA(Bool, S3CredentialsEnv);
WRITE_DATA(Integer, SendBuf);
WRITE_DATA(String, SourceAddress);
WRITE_DATA(String, ProtocolFeatures);
Expand Down Expand Up @@ -2568,13 +2573,22 @@ void __fastcall TSessionData::SetUserName(UnicodeString value)
//---------------------------------------------------------------------
UnicodeString __fastcall TSessionData::GetUserNameExpanded()
{
return ::ExpandEnvironmentVariables(UserName);
UnicodeString Result = ::ExpandEnvironmentVariables(UserName);
if (Result.IsEmpty() && HasS3AutoCredentials())
{
Result = S3EnvUserName();
}
return Result;
}
//---------------------------------------------------------------------
UnicodeString TSessionData::GetUserNameSource()
{
UnicodeString Result;
if (UserName != UserNameExpanded)
if (UserName.IsEmpty() && HasS3AutoCredentials())
{
S3EnvUserName(&Result);
}
if (Result.IsEmpty() && (UserName != UserNameExpanded))
{
Result = UserName;
}
Expand Down Expand Up @@ -3276,7 +3290,10 @@ UnicodeString __fastcall TSessionData::GenerateSessionUrl(unsigned int Flags)

Url += GetProtocolUrl(FLAGSET(Flags, sufHttpForWebDAV));

if (FLAGSET(Flags, sufUserName) && !UserNameExpanded.IsEmpty())
// Add username only if it was somehow explicitly specified (so not with S3CredentialsEnv), but if it was, add it in the expanded form.
// For scripting, we might use unexpanded form (keeping the environment variables),
// but for consistency with code generation (where explicit expansion code would need to be added), we do not.
if (FLAGSET(Flags, sufUserName) && !UserName.IsEmpty())
{
Url += EncodeUrlString(UserNameExpanded);

Expand Down Expand Up @@ -4179,6 +4196,11 @@ void __fastcall TSessionData::SetS3MaxKeys(TAutoSwitch value)
SET_SESSION_PROPERTY(S3MaxKeys);
}
//---------------------------------------------------------------------
void __fastcall TSessionData::SetS3CredentialsEnv(bool value)
{
SET_SESSION_PROPERTY(S3CredentialsEnv);
}
//---------------------------------------------------------------------
void __fastcall TSessionData::SetIsWorkspace(bool value)
{
SET_SESSION_PROPERTY(IsWorkspace);
Expand Down Expand Up @@ -4312,6 +4334,16 @@ TStrings * TSessionData::GetAllOptionNames(bool PuttyExport)
std::unique_ptr<TSessionData> FactoryDefaults(new TSessionData(L""));
return FactoryDefaults->SaveToOptions(NULL, false, PuttyExport);
}
//---------------------------------------------------------------------
bool TSessionData::HasS3AutoCredentials()
{
return (FSProtocol == fsS3) && S3CredentialsEnv;
}
//---------------------------------------------------------------------
bool TSessionData::HasAutoCredentials()
{
return HasS3AutoCredentials();
}
//=== TStoredSessionList ----------------------------------------------
__fastcall TStoredSessionList::TStoredSessionList(bool aReadOnly):
TNamedObjectList(), FReadOnly(aReadOnly)
Expand Down
5 changes: 5 additions & 0 deletions source/core/SessionData.h
Expand Up @@ -228,6 +228,7 @@ friend class TStoredSessionList;
UnicodeString FS3SessionToken;
TS3UrlStyle FS3UrlStyle;
TAutoSwitch FS3MaxKeys;
bool FS3CredentialsEnv;
bool FIsWorkspace;
UnicodeString FLink;
UnicodeString FNameOverride;
Expand Down Expand Up @@ -414,6 +415,7 @@ friend class TStoredSessionList;
void __fastcall SetS3SessionToken(UnicodeString value);
void __fastcall SetS3UrlStyle(TS3UrlStyle value);
void __fastcall SetS3MaxKeys(TAutoSwitch value);
void __fastcall SetS3CredentialsEnv(bool value);
void __fastcall SetLogicalHostName(UnicodeString value);
void __fastcall SetIsWorkspace(bool value);
void __fastcall SetLink(UnicodeString value);
Expand Down Expand Up @@ -469,6 +471,7 @@ friend class TStoredSessionList;
const UnicodeString & Name, bool Value);
TStrings * __fastcall GetRawSettingsForUrl();
void __fastcall DoCopyData(TSessionData * SourceData, bool NoRecrypt);
bool HasS3AutoCredentials();
template<class AlgoT>
void __fastcall SetAlgoList(AlgoT * List, const AlgoT * DefaultList, const UnicodeString * Names,
int Count, AlgoT WarnAlgo, UnicodeString value);
Expand Down Expand Up @@ -518,6 +521,7 @@ friend class TStoredSessionList;
UnicodeString __fastcall GenerateSessionUrl(unsigned int Flags);
bool __fastcall HasRawSettingsForUrl();
bool __fastcall HasSessionName();
bool HasAutoCredentials();

UnicodeString __fastcall GenerateOpenCommandArgs(bool Rtf);
void __fastcall GenerateAssemblyCode(TAssemblyLanguage Language, UnicodeString & Head, UnicodeString & Tail, int & Indent);
Expand Down Expand Up @@ -684,6 +688,7 @@ friend class TStoredSessionList;
__property UnicodeString S3SessionToken = { read = FS3SessionToken, write = SetS3SessionToken };
__property TS3UrlStyle S3UrlStyle = { read = FS3UrlStyle, write = SetS3UrlStyle };
__property TAutoSwitch S3MaxKeys = { read = FS3MaxKeys, write = SetS3MaxKeys };
__property bool S3CredentialsEnv = { read = FS3CredentialsEnv, write = SetS3CredentialsEnv };
__property bool IsWorkspace = { read = FIsWorkspace, write = SetIsWorkspace };
__property UnicodeString Link = { read = FLink, write = SetLink };
__property UnicodeString NameOverride = { read = FNameOverride, write = SetNameOverride };
Expand Down

0 comments on commit 0e894c3

Please sign in to comment.