in src/Agent.Listener/Configuration/ConfigurationManager.cs [75:515]
public async Task ConfigureAsync(CommandSettings command)
{
ArgUtil.NotNull(command, nameof(command));
if (PlatformUtil.RunningOnWindows)
{
CheckAgentRootDirectorySecure();
}
Trace.Info(nameof(ConfigureAsync));
if (IsConfigured())
{
throw new InvalidOperationException(StringUtil.Loc("AlreadyConfiguredError"));
}
// Populate proxy setting from commandline args
var vstsProxy = HostContext.GetService<IVstsAgentWebProxy>();
bool saveProxySetting = false;
string proxyUrl = command.GetProxyUrl();
if (!string.IsNullOrEmpty(proxyUrl))
{
if (!Uri.IsWellFormedUriString(proxyUrl, UriKind.Absolute))
{
throw new ArgumentOutOfRangeException(nameof(proxyUrl));
}
Trace.Info("Reset proxy base on commandline args.");
string proxyUserName = command.GetProxyUserName();
string proxyPassword = command.GetProxyPassword();
(vstsProxy as VstsAgentWebProxy).SetupProxy(proxyUrl, proxyUserName, proxyPassword);
saveProxySetting = true;
}
// Populate cert setting from commandline args
var agentCertManager = HostContext.GetService<IAgentCertificateManager>();
bool saveCertSetting = false;
bool skipCertValidation = command.GetSkipCertificateValidation();
string caCert = command.GetCACertificate();
string clientCert = command.GetClientCertificate();
string clientCertKey = command.GetClientCertificatePrivateKey();
string clientCertArchive = command.GetClientCertificateArchrive();
string clientCertPassword = command.GetClientCertificatePassword();
// We require all Certificate files are under agent root.
// So we can set ACL correctly when configure as service
if (!string.IsNullOrEmpty(caCert))
{
caCert = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), caCert);
ArgUtil.File(caCert, nameof(caCert));
}
if (!string.IsNullOrEmpty(clientCert) &&
!string.IsNullOrEmpty(clientCertKey) &&
!string.IsNullOrEmpty(clientCertArchive))
{
// Ensure all client cert pieces are there.
clientCert = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCert);
clientCertKey = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCertKey);
clientCertArchive = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCertArchive);
ArgUtil.File(clientCert, nameof(clientCert));
ArgUtil.File(clientCertKey, nameof(clientCertKey));
ArgUtil.File(clientCertArchive, nameof(clientCertArchive));
}
else if (!string.IsNullOrEmpty(clientCert) ||
!string.IsNullOrEmpty(clientCertKey) ||
!string.IsNullOrEmpty(clientCertArchive))
{
// Print out which args are missing.
ArgUtil.NotNullOrEmpty(Constants.Agent.CommandLine.Args.SslClientCert, Constants.Agent.CommandLine.Args.SslClientCert);
ArgUtil.NotNullOrEmpty(Constants.Agent.CommandLine.Args.SslClientCertKey, Constants.Agent.CommandLine.Args.SslClientCertKey);
ArgUtil.NotNullOrEmpty(Constants.Agent.CommandLine.Args.SslClientCertArchive, Constants.Agent.CommandLine.Args.SslClientCertArchive);
}
if (skipCertValidation || !string.IsNullOrEmpty(caCert) || !string.IsNullOrEmpty(clientCert))
{
Trace.Info("Reset agent cert setting base on commandline args.");
(agentCertManager as AgentCertificateManager).SetupCertificate(skipCertValidation, caCert, clientCert, clientCertKey, clientCertArchive, clientCertPassword);
saveCertSetting = true;
}
AgentSettings agentSettings = new AgentSettings();
// TEE EULA
agentSettings.AcceptTeeEula = false;
switch (PlatformUtil.HostOS)
{
case PlatformUtil.OS.OSX:
case PlatformUtil.OS.Linux:
// Write the section header.
WriteSection(StringUtil.Loc("EulasSectionHeader"));
// Verify the EULA exists on disk in the expected location.
string eulaFile = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), "license.html");
ArgUtil.File(eulaFile, nameof(eulaFile));
// Write elaborate verbiage about the TEE EULA.
_term.WriteLine(StringUtil.Loc("TeeEula", eulaFile));
_term.WriteLine();
// Prompt to acccept the TEE EULA.
agentSettings.AcceptTeeEula = command.GetAcceptTeeEula();
break;
case PlatformUtil.OS.Windows:
// Warn and continue if .NET 4.6 is not installed.
if (!NetFrameworkUtil.Test(new Version(4, 6), Trace))
{
WriteSection(StringUtil.Loc("PrerequisitesSectionHeader")); // Section header.
_term.WriteLine(StringUtil.Loc("MinimumNetFrameworkTfvc")); // Warning.
}
break;
default:
throw new NotSupportedException();
}
// Create the configuration provider as per agent type.
string agentType;
if (command.GetDeploymentOrMachineGroup())
{
agentType = Constants.Agent.AgentConfigurationProvider.DeploymentAgentConfiguration;
}
else if (command.GetDeploymentPool())
{
agentType = Constants.Agent.AgentConfigurationProvider.SharedDeploymentAgentConfiguration;
}
else if (command.GetEnvironmentVMResource())
{
agentType = Constants.Agent.AgentConfigurationProvider.EnvironmentVMResourceConfiguration;
}
else
{
agentType = Constants.Agent.AgentConfigurationProvider.BuildReleasesAgentConfiguration;
}
var extensionManager = HostContext.GetService<IExtensionManager>();
IConfigurationProvider agentProvider =
(extensionManager.GetExtensions<IConfigurationProvider>())
.FirstOrDefault(x => x.ConfigurationProviderType == agentType);
ArgUtil.NotNull(agentProvider, agentType);
bool isHostedServer = false;
// Loop getting url and creds until you can connect
ICredentialProvider credProvider = null;
VssCredentials creds = null;
WriteSection(StringUtil.Loc("ConnectSectionHeader"));
while (true)
{
// Get the URL
agentProvider.GetServerUrl(agentSettings, command);
// Get the credentials
credProvider = GetCredentialProvider(command, agentSettings.ServerUrl);
creds = credProvider.GetVssCredentials(HostContext);
Trace.Info("cred retrieved");
try
{
// Determine the service deployment type based on connection data. (Hosted/OnPremises)
await _serverUtil.DetermineDeploymentType(agentSettings.ServerUrl, creds, _locationServer);
if (!_serverUtil.TryGetDeploymentType(out isHostedServer))
{
Trace.Warning(@"Deployment type determination has been failed;
assume it is OnPremises and the deployment type determination was not implemented for this server version.");
}
// Get the collection name for deployment group
agentProvider.GetCollectionName(agentSettings, command, isHostedServer);
// Validate can connect.
await agentProvider.TestConnectionAsync(agentSettings, creds, isHostedServer);
Trace.Info("Test Connection complete.");
break;
}
catch (SocketException e)
{
ExceptionsUtil.HandleSocketException(e, agentSettings.ServerUrl, _term.WriteError);
}
catch (Exception e) when (!command.Unattended())
{
_term.WriteError(e);
_term.WriteError(StringUtil.Loc("FailedToConnect"));
}
}
// We want to use the native CSP of the platform for storage, so we use the RSACSP directly
RSAParameters publicKey;
var keyManager = HostContext.GetService<IRSAKeyManager>();
using (var rsa = keyManager.CreateKey())
{
publicKey = rsa.ExportParameters(false);
}
// Loop getting agent name and pool name
WriteSection(StringUtil.Loc("RegisterAgentSectionHeader"));
while (true)
{
try
{
await agentProvider.GetPoolIdAndName(agentSettings, command);
break;
}
catch (Exception e) when (!command.Unattended())
{
_term.WriteError(e);
_term.WriteError(agentProvider.GetFailedToFindPoolErrorString());
}
}
TaskAgent agent;
while (true)
{
agentSettings.AgentName = command.GetAgentName();
// Get the system capabilities.
// TODO: Hook up to ctrl+c cancellation token.
_term.WriteLine(StringUtil.Loc("ScanToolCapabilities"));
Dictionary<string, string> systemCapabilities = await HostContext.GetService<ICapabilitiesManager>().GetCapabilitiesAsync(agentSettings, CancellationToken.None);
_term.WriteLine(StringUtil.Loc("ConnectToServer"));
agent = await agentProvider.GetAgentAsync(agentSettings);
if (agent != null)
{
if (command.GetReplace())
{
// Update existing agent with new PublicKey, agent version and SystemCapabilities.
agent = UpdateExistingAgent(agent, publicKey, systemCapabilities);
try
{
agent = await agentProvider.UpdateAgentAsync(agentSettings, agent, command);
_term.WriteLine(StringUtil.Loc("AgentReplaced"));
break;
}
catch (Exception e) when (!command.Unattended())
{
_term.WriteError(e);
_term.WriteError(StringUtil.Loc("FailedToReplaceAgent"));
}
}
else if (command.Unattended())
{
// if not replace and it is unattended config.
agentProvider.ThrowTaskAgentExistException(agentSettings);
}
}
else
{
// Create a new agent.
agent = CreateNewAgent(agentSettings.AgentName, publicKey, systemCapabilities);
try
{
agent = await agentProvider.AddAgentAsync(agentSettings, agent, command);
_term.WriteLine(StringUtil.Loc("AgentAddedSuccessfully"));
break;
}
catch (Exception e) when (!command.Unattended())
{
_term.WriteError(e);
_term.WriteError(StringUtil.Loc("AddAgentFailed"));
}
}
}
// Add Agent Id to settings
agentSettings.AgentId = agent.Id;
// respect the serverUrl resolve by server.
// in case of agent configured using collection url instead of account url.
string agentServerUrl;
if (agent.Properties.TryGetValidatedValue<string>("ServerUrl", out agentServerUrl) &&
!string.IsNullOrEmpty(agentServerUrl))
{
Trace.Info($"Agent server url resolve by server: '{agentServerUrl}'.");
// we need make sure the Schema/Host/Port component of the url remain the same.
UriBuilder inputServerUrl = new UriBuilder(agentSettings.ServerUrl);
UriBuilder serverReturnedServerUrl = new UriBuilder(agentServerUrl);
if (Uri.Compare(inputServerUrl.Uri, serverReturnedServerUrl.Uri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0)
{
inputServerUrl.Path = serverReturnedServerUrl.Path;
Trace.Info($"Replace server returned url's scheme://host:port component with user input server url's scheme://host:port: '{inputServerUrl.Uri.AbsoluteUri}'.");
agentSettings.ServerUrl = inputServerUrl.Uri.AbsoluteUri;
}
else
{
agentSettings.ServerUrl = agentServerUrl;
}
}
// See if the server supports our OAuth key exchange for credentials
if (agent.Authorization != null &&
agent.Authorization.ClientId != Guid.Empty &&
agent.Authorization.AuthorizationUrl != null)
{
// We use authorizationUrl as the oauth endpoint url by default.
// For TFS, we need make sure the Schema/Host/Port component of the oauth endpoint url also match configuration url. (Incase of customer's agent configure URL and TFS server public URL are different)
// Which means, we will keep use the original authorizationUrl in the VssOAuthJwtBearerClientCredential (authorizationUrl is the audience),
// But might have different Url in VssOAuthCredential (connection url)
// We can't do this for VSTS, since its SPS/TFS urls are different.
UriBuilder configServerUrl = new UriBuilder(agentSettings.ServerUrl);
UriBuilder oauthEndpointUrlBuilder = new UriBuilder(agent.Authorization.AuthorizationUrl);
if (!isHostedServer && Uri.Compare(configServerUrl.Uri, oauthEndpointUrlBuilder.Uri, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) != 0)
{
oauthEndpointUrlBuilder.Scheme = configServerUrl.Scheme;
oauthEndpointUrlBuilder.Host = configServerUrl.Host;
oauthEndpointUrlBuilder.Port = configServerUrl.Port;
Trace.Info($"Set oauth endpoint url's scheme://host:port component to match agent configure url's scheme://host:port: '{oauthEndpointUrlBuilder.Uri.AbsoluteUri}'.");
}
var credentialData = new CredentialData
{
Scheme = Constants.Configuration.OAuth,
Data =
{
{ "clientId", agent.Authorization.ClientId.ToString("D") },
{ "authorizationUrl", agent.Authorization.AuthorizationUrl.AbsoluteUri },
{ "oauthEndpointUrl", oauthEndpointUrlBuilder.Uri.AbsoluteUri },
},
};
// Save the negotiated OAuth credential data
_store.SaveCredential(credentialData);
}
else
{
switch (PlatformUtil.HostOS)
{
case PlatformUtil.OS.OSX:
case PlatformUtil.OS.Linux:
// Save the provided admin cred for compat with previous agent.
_store.SaveCredential(credProvider.CredentialData);
break;
case PlatformUtil.OS.Windows:
// Not supported against TFS 2015.
_term.WriteError(StringUtil.Loc("Tfs2015NotSupported"));
return;
default:
throw new NotSupportedException();
}
}
// Testing agent connection, detect any protential connection issue, like local clock skew that cause OAuth token expired.
_term.WriteLine(StringUtil.Loc("TestAgentConnection"));
var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials credential = credMgr.LoadCredentials();
var agentSvr = HostContext.GetService<IAgentServer>();
try
{
await agentSvr.ConnectAsync(new Uri(agentSettings.ServerUrl), credential);
}
catch (VssOAuthTokenRequestException ex) when (ex.Message.Contains("Current server time is"))
{
// there are two exception messages server send that indicate clock skew.
// 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}.
// 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}.
Trace.Error("Catch exception during test agent connection.");
Trace.Error(ex);
throw new InvalidOperationException(StringUtil.Loc("LocalClockSkewed"));
}
catch (SocketException ex)
{
ExceptionsUtil.HandleSocketException(ex, agentSettings.ServerUrl, Trace.Error);
throw;
}
// We will Combine() what's stored with root. Defaults to string a relative path
agentSettings.WorkFolder = command.GetWork();
// notificationPipeName for Hosted agent provisioner.
agentSettings.NotificationPipeName = command.GetNotificationPipeName();
agentSettings.MonitorSocketAddress = command.GetMonitorSocketAddress();
agentSettings.NotificationSocketAddress = command.GetNotificationSocketAddress();
agentSettings.DisableLogUploads = command.GetDisableLogUploads();
agentSettings.AlwaysExtractTask = command.GetAlwaysExtractTask();
_store.SaveSettings(agentSettings);
if (saveProxySetting)
{
Trace.Info("Save proxy setting to disk.");
(vstsProxy as VstsAgentWebProxy).SaveProxySetting();
}
if (saveCertSetting)
{
Trace.Info("Save agent cert setting to disk.");
(agentCertManager as AgentCertificateManager).SaveCertificateSetting();
}
_term.WriteLine(StringUtil.Loc("SavedSettings", DateTime.UtcNow));
bool saveRuntimeOptions = false;
var runtimeOptions = new AgentRuntimeOptions();
if (PlatformUtil.RunningOnWindows && command.GetGitUseSChannel())
{
saveRuntimeOptions = true;
runtimeOptions.GitUseSecureChannel = true;
}
if (saveRuntimeOptions)
{
Trace.Info("Save agent runtime options to disk.");
_store.SaveAgentRuntimeOptions(runtimeOptions);
}
if (PlatformUtil.RunningOnWindows)
{
// config windows service
bool runAsService = command.GetRunAsService();
if (runAsService)
{
Trace.Info("Configuring to run the agent as service");
var serviceControlManager = HostContext.GetService<IWindowsServiceControlManager>();
serviceControlManager.ConfigureService(agentSettings, command);
}
// config auto logon
else if (command.GetRunAsAutoLogon())
{
Trace.Info("Agent is going to run as process setting up the 'AutoLogon' capability for the agent.");
var autoLogonConfigManager = HostContext.GetService<IAutoLogonManager>();
await autoLogonConfigManager.ConfigureAsync(command);
//Important: The machine may restart if the autologon user is not same as the current user
//if you are adding code after this, keep that in mind
}
}
else if (PlatformUtil.RunningOnLinux)
{
// generate service config script for Linux
var serviceControlManager = HostContext.GetService<ILinuxServiceControlManager>();
serviceControlManager.GenerateScripts(agentSettings);
}
else if (PlatformUtil.RunningOnMacOS)
{
// generate service config script for macOS
var serviceControlManager = HostContext.GetService<IMacOSServiceControlManager>();
serviceControlManager.GenerateScripts(agentSettings);
}
}