Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid constructing NetworkCredential objects on macOS machines #5058

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/Agent.Sdk/AgentWebProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class AgentWebProxy : IWebProxy

public ICredentials Credentials { get; set; }

// This was added to work around malloc-related crashes on MacOS
private static readonly NetworkCredential _neverDisposedCredentialObject = new();

public AgentWebProxy()
{
}
Expand All @@ -48,7 +51,21 @@ public void Update(string proxyAddress, string proxyUsername, string proxyPasswo
}
else
{
Credentials = new NetworkCredential(proxyUsername, proxyPassword);
bool avoidConstructingNetCredential =
PlatformUtil.HostOS == PlatformUtil.OS.OSX
&& Boolean.TryParse(Environment.GetEnvironmentVariable("AVOID_NET_CREDENTIAL_OBJECTS_ON_MAC"), out bool ffEnabled)
&& ffEnabled;

if (avoidConstructingNetCredential)
{
_neverDisposedCredentialObject.UserName = proxyUsername;
_neverDisposedCredentialObject.Password = proxyPassword;
Credentials = _neverDisposedCredentialObject;
}
else
{
Credentials = new NetworkCredential(proxyUsername, proxyPassword);
}
}

if (proxyBypassList != null)
Expand Down
6 changes: 6 additions & 0 deletions src/Agent.Sdk/Knob/AgentKnobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -779,5 +779,11 @@ public class AgentKnobs
"If true, agent will use sparse checkout in checkout task.",
new RuntimeKnobSource("AGENT_USE_SPARSE_CHECKOUT_IN_CHECKOUT_TASK"),
new BuiltInDefaultKnobSource("false"));

public static readonly Knob AvoidNetCredentialObjectsOnMac = new Knob(
nameof(AvoidNetCredentialObjectsOnMac),
"Eliminates construction of NetwokCredential objects on macOS, which internally suffer from malloc-related crashes",
new EnvironmentKnobSource("AVOID_NET_CREDENTIAL_OBJECTS_ON_MAC"),
new BuiltInDefaultKnobSource("false"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.VisualStudio.Services.WebApi;
using Agent.Sdk;
using Agent.Sdk.Util;
using Agent.Sdk.Knob;

namespace Microsoft.VisualStudio.Services.Agent
{
Expand Down Expand Up @@ -120,6 +121,7 @@ public void SaveCertificateSetting()
Trace.Info($"Store client cert private key password with lookup key {lookupKey}");

var credStore = HostContext.GetService<IAgentCredentialStore>();
var avoidNetCredentialFF = AgentKnobs.AvoidNetCredentialObjectsOnMac.GetValue(HostContext).AsBoolean();
credStore.Write($"VSTS_AGENT_CLIENT_CERT_PASSWORD_{lookupKey}", "VSTS", ClientCertificatePassword);

setting.ClientCertPasswordLookupKey = lookupKey;
Expand Down Expand Up @@ -198,7 +200,9 @@ public void LoadCertificateSettings()
if (!string.IsNullOrEmpty(certSetting.ClientCertPasswordLookupKey))
{
var cerdStore = HostContext.GetService<IAgentCredentialStore>();
ClientCertificatePassword = cerdStore.Read($"VSTS_AGENT_CLIENT_CERT_PASSWORD_{certSetting.ClientCertPasswordLookupKey}").Password;
var avoidNetCredentialFF = AgentKnobs.AvoidNetCredentialObjectsOnMac.GetValue(HostContext).AsBoolean();
var target = $"VSTS_AGENT_CLIENT_CERT_PASSWORD_{certSetting.ClientCertPasswordLookupKey}";
ClientCertificatePassword = avoidNetCredentialFF ? cerdStore.Read2(target).Password : cerdStore.Read(target).Password;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think FF check can be removed here and directly Read2 can be used?

HostContext.SecretMasker.AddValue(ClientCertificatePassword, WellKnownSecretAliases.ClientCertificatePassword);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public override void Initialize(IHostContext hostContext)
_symmetricKey = keyBuilder.ToArray();
}

public NetworkCredential Write(string target, string username, string password)
public void Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
Expand All @@ -79,7 +79,6 @@ public NetworkCredential Write(string target, string username, string password)
Credential cred = new Credential(username, Encrypt(password));
_credStore[target] = cred;
SyncCredentialStoreFile();
return new NetworkCredential(username, password);
}

public NetworkCredential Read(string target)
Expand All @@ -100,6 +99,13 @@ public NetworkCredential Read(string target)
throw new KeyNotFoundException(target);
}

public (string UserName, string Password) Read2(string target)
{
// NetworkCredential objects cause crashes only on macOS => we can invoke the original implemantation here
var ret = Read(target);
return (ret.UserName, ret.Password);
}

public void Delete(string target)
{
Trace.Entering();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public override void Initialize(IHostContext hostContext)
}
}

public NetworkCredential Write(string target, string username, string password)
public void Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
Expand Down Expand Up @@ -161,8 +161,6 @@ public NetworkCredential Write(string target, string username, string password)
throw new InvalidOperationException($"'security add-generic-password' failed with exit code {exitCode}.");
}
}

return new NetworkCredential(username, password);
}
finally
{
Expand All @@ -171,6 +169,17 @@ public NetworkCredential Write(string target, string username, string password)
}

public NetworkCredential Read(string target)
{
var cred = ReadCredentialFromKeychain(target);
return new NetworkCredential(cred.UserName, cred.Password);
}

public (string UserName, string Password) Read2(string target)
{
return ReadCredentialFromKeychain(target);
}

public (string UserName, string Password) ReadCredentialFromKeychain(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
Expand Down Expand Up @@ -223,7 +232,7 @@ public NetworkCredential Read(string target)
Trace.Info($"Successfully find-generic-password for {target} (VSTSAGENT)");
username = Encoding.UTF8.GetString(Convert.FromBase64String(secrets[0]));
password = Encoding.UTF8.GetString(Convert.FromBase64String(secrets[1]));
return new NetworkCredential(username, password);
return (username, password);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@ public override void Initialize(IHostContext hostContext)
base.Initialize(hostContext);
}

public NetworkCredential Write(string target, string username, string password)
public void Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
ArgUtil.NotNullOrEmpty(username, nameof(username));
ArgUtil.NotNullOrEmpty(password, nameof(password));

Trace.Info($"Attempt to store credential for '{target}' to cred store.");
return new NetworkCredential(username, password);
}

public NetworkCredential Read(string target)
Expand All @@ -34,6 +33,12 @@ public NetworkCredential Read(string target)
throw new KeyNotFoundException(target);
}

public (string UserName, string Password) Read2(string target)
{
var ret = Read(target);
return (ret.UserName, ret.Password);
}

public void Delete(string target)
{
Trace.Entering();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public override void Initialize(IHostContext hostContext)
}
}

public NetworkCredential Write(string target, string username, string password)
public void Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
Expand All @@ -67,7 +67,7 @@ public NetworkCredential Write(string target, string username, string password)
SyncCredentialStoreFile();

// save to Windows Credential Store
return WriteInternal(target, username, password);
WriteInternal(target, username, password);
}

public NetworkCredential Read(string target)
Expand Down Expand Up @@ -140,6 +140,13 @@ public NetworkCredential Read(string target)
}
}

public (string UserName, string Password) Read2(string target)
{
// NetworkCredential objects cause crashes only on macOS => we can invoke the original implemantation here
var ret = Read(target);
return (ret.UserName, ret.Password);
}

public void Delete(string target)
{
Trace.Entering();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ namespace Microsoft.VisualStudio.Services.Agent
)]
public interface IAgentCredentialStore : IAgentService
{
NetworkCredential Write(string target, string username, string password);
void Write(string target, string username, string password);

// throw exception when target not found from cred store
NetworkCredential Read(string target);


// variant that does not return NetworkCredential class, which suffers from OS-level crashes on macOS
(string UserName, string Password) Read2(string target);

// throw exception when target not found from cred store
void Delete(string target);
}
Expand Down
19 changes: 16 additions & 3 deletions src/Microsoft.VisualStudio.Services.Agent/VstsAgentWebProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,22 @@ private void LoadProxySetting()
if (!string.IsNullOrEmpty(lookupKey))
{
var credStore = HostContext.GetService<IAgentCredentialStore>();
var proxyCred = credStore.Read($"VSTS_AGENT_PROXY_{lookupKey}");
ProxyUsername = proxyCred.UserName;
ProxyPassword = proxyCred.Password;
var avoidNetCredentialFF = AgentKnobs.AvoidNetCredentialObjectsOnMac.GetValue(HostContext).AsBoolean();
var target = $"VSTS_AGENT_PROXY_{lookupKey}";

if (avoidNetCredentialFF)
{
var proxyCred = credStore.Read2(target);
ProxyUsername = proxyCred.UserName;
ProxyPassword = proxyCred.Password;
}
else
{
NetworkCredential proxyCred = credStore.Read(target);
ProxyUsername = proxyCred.UserName;
ProxyPassword = proxyCred.Password;
}

}
}

Expand Down
Loading