From 084a5ca7fc357265bab53f70d2117d949bc2e904 Mon Sep 17 00:00:00 2001 From: Denis Levin Date: Mon, 22 Apr 2019 18:31:01 -0700 Subject: [PATCH 01/26] Added support for storing PAT tokens to Azure KeyVault --- .../AzureDevOps.Authentication.Proxy.csproj | 13 +- .../Proxy/packages.config | 2 +- .../Src/AzureDevOps.Authentication.csproj | 7 +- .../Bitbucket.Authentication.Proxy.csproj | 9 +- .../Src/Bitbucket.Authentication.csproj | 3 +- .../Test/Bitbucket.Authentication.Test.csproj | 5 +- Cli/Manager/Cli-Manager.csproj | 23 +- Cli/Manager/app.config | 21 +- Cli/Manager/packages.config | 2 + Cli/Test/ProgramTests.cs | 4 +- Cli/Test/app.config | 12 + .../Proxy/GitHub.Authentication.Proxy.csproj | 4 +- .../Src/GitHub.Authentication.csproj | 2 +- .../Test/AuthenticationTests.cs | 4 +- .../Microsoft.Alm.Authentication.Proxy.csproj | 4 +- .../Src/Credential.cs | 22 +- .../Src/KeyVaultHelper.cs | 256 ++++++++++++++++++ .../KeyVaultHelperConfigurationException.cs | 29 ++ .../Src/KeyVaultSecretStore.cs | 220 +++++++++++++++ .../Src/Microsoft.Alm.Authentication.csproj | 54 +++- Microsoft.Alm.Authentication/Src/app.config | 8 +- .../Src/packages.config | 9 + Microsoft.Alm.Authentication/Test/app.config | 4 +- Shared/Cli/Functions/Common.cs | 117 +++++--- Shared/Cli/OperationArguments.cs | 49 +++- Shared/Cli/Program.cs | 14 +- 26 files changed, 798 insertions(+), 99 deletions(-) create mode 100644 Microsoft.Alm.Authentication/Src/KeyVaultHelper.cs create mode 100644 Microsoft.Alm.Authentication/Src/KeyVaultHelperConfigurationException.cs create mode 100644 Microsoft.Alm.Authentication/Src/KeyVaultSecretStore.cs diff --git a/AzureDevOps.Authentication/Proxy/AzureDevOps.Authentication.Proxy.csproj b/AzureDevOps.Authentication/Proxy/AzureDevOps.Authentication.Proxy.csproj index a5a137283..602df84d1 100644 --- a/AzureDevOps.Authentication/Proxy/AzureDevOps.Authentication.Proxy.csproj +++ b/AzureDevOps.Authentication/Proxy/AzureDevOps.Authentication.Proxy.csproj @@ -7,7 +7,10 @@ Properties AzureDevOps.Authentication.Test AzureDevOps.Authentication.Proxy - + + + v4.5.2 + @@ -31,8 +34,8 @@ ..\..\packages\Moq.4.8.2\lib\net45\Moq.dll - - ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll @@ -56,7 +59,9 @@ - + + Designer + diff --git a/AzureDevOps.Authentication/Proxy/packages.config b/AzureDevOps.Authentication/Proxy/packages.config index 4756c7bb1..bb78bac8d 100644 --- a/AzureDevOps.Authentication/Proxy/packages.config +++ b/AzureDevOps.Authentication/Proxy/packages.config @@ -3,7 +3,7 @@ - + \ No newline at end of file diff --git a/AzureDevOps.Authentication/Src/AzureDevOps.Authentication.csproj b/AzureDevOps.Authentication/Src/AzureDevOps.Authentication.csproj index 7d81070b4..12803fd77 100644 --- a/AzureDevOps.Authentication/Src/AzureDevOps.Authentication.csproj +++ b/AzureDevOps.Authentication/Src/AzureDevOps.Authentication.csproj @@ -10,7 +10,8 @@ {19770407-D7D8-4A37-914C-F552FF4B90D4} AzureDevOps.Authentication AzureDevOps.Authentication - v4.5.1 + v4.5.2 + @@ -18,8 +19,8 @@ $(ProjectDir)$(OutputPath) - - ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.19.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.4.5.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.19.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll diff --git a/Bitbucket.Authentication/Proxy/Bitbucket.Authentication.Proxy.csproj b/Bitbucket.Authentication/Proxy/Bitbucket.Authentication.Proxy.csproj index a5f75d085..ae4e06f44 100644 --- a/Bitbucket.Authentication/Proxy/Bitbucket.Authentication.Proxy.csproj +++ b/Bitbucket.Authentication/Proxy/Bitbucket.Authentication.Proxy.csproj @@ -7,7 +7,10 @@ Properties Bitbucket.Authentication.Test Bitbucket.Authentication.Proxy - + + + v4.5.2 + @@ -31,8 +34,8 @@ ..\..\packages\Moq.4.8.2\lib\net45\Moq.dll - - ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll diff --git a/Bitbucket.Authentication/Src/Bitbucket.Authentication.csproj b/Bitbucket.Authentication/Src/Bitbucket.Authentication.csproj index 3f52f585e..bfefa0b41 100644 --- a/Bitbucket.Authentication/Src/Bitbucket.Authentication.csproj +++ b/Bitbucket.Authentication/Src/Bitbucket.Authentication.csproj @@ -10,7 +10,8 @@ {EE663736-5BAD-4CA6-A4F8-99978925AD8A} Bitbucket.Authentication Atlassian.Bitbucket.Authentication - v4.5.1 + v4.5.2 + diff --git a/Bitbucket.Authentication/Test/Bitbucket.Authentication.Test.csproj b/Bitbucket.Authentication/Test/Bitbucket.Authentication.Test.csproj index 53c57081d..06ffc35fa 100644 --- a/Bitbucket.Authentication/Test/Bitbucket.Authentication.Test.csproj +++ b/Bitbucket.Authentication/Test/Bitbucket.Authentication.Test.csproj @@ -14,7 +14,10 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - + + + v4.5.2 + diff --git a/Cli/Manager/Cli-Manager.csproj b/Cli/Manager/Cli-Manager.csproj index 944e771d7..76dec81b6 100644 --- a/Cli/Manager/Cli-Manager.csproj +++ b/Cli/Manager/Cli-Manager.csproj @@ -6,7 +6,8 @@ Properties git-credential-manager true - + + Exe {19770407-63D4-40A8-A9DF-F1C4B473308A} Cli-Manager @@ -24,10 +25,25 @@ get + + ..\..\packages\Microsoft.Azure.Services.AppAuthentication.1.0.3\lib\net452\Microsoft.Azure.Services.AppAuthentication.dll + + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.4.5.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + + + + + + + + + @@ -57,6 +73,9 @@ false + + Designer + @@ -67,5 +86,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + \ No newline at end of file diff --git a/Cli/Manager/app.config b/Cli/Manager/app.config index ff9950103..be876fec2 100644 --- a/Cli/Manager/app.config +++ b/Cli/Manager/app.config @@ -1,3 +1,20 @@ - + - + + + + + + + + + + + + + + + + + + diff --git a/Cli/Manager/packages.config b/Cli/Manager/packages.config index 644ec0093..e2e3ccbae 100644 --- a/Cli/Manager/packages.config +++ b/Cli/Manager/packages.config @@ -1,5 +1,7 @@  + + diff --git a/Cli/Test/ProgramTests.cs b/Cli/Test/ProgramTests.cs index d76616ef6..b9d441d32 100644 --- a/Cli/Test/ProgramTests.cs +++ b/Cli/Test/ProgramTests.cs @@ -29,7 +29,7 @@ using Microsoft.Alm.Authentication; using Moq; using Xunit; -using Azure = AzureDevOps.Authentication; +using AzureDev = AzureDevOps.Authentication; using Git = Microsoft.Alm.Authentication.Git; namespace Microsoft.Alm.Cli.Test @@ -108,7 +108,7 @@ public async Task LoadOperationArgumentsTest() Assert.NotNull(opargs.DevOpsTokenScope); - var expectedScope = Azure.TokenScope.BuildAccess | Azure.TokenScope.CodeWrite; + var expectedScope = AzureDev.TokenScope.BuildAccess | AzureDev.TokenScope.CodeWrite; Assert.Equal(expectedScope, opargs.DevOpsTokenScope); } diff --git a/Cli/Test/app.config b/Cli/Test/app.config index b546f120b..a7de87bc0 100644 --- a/Cli/Test/app.config +++ b/Cli/Test/app.config @@ -10,6 +10,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/GitHub.Authentication/Proxy/GitHub.Authentication.Proxy.csproj b/GitHub.Authentication/Proxy/GitHub.Authentication.Proxy.csproj index 5a487378e..3b4357bbc 100644 --- a/GitHub.Authentication/Proxy/GitHub.Authentication.Proxy.csproj +++ b/GitHub.Authentication/Proxy/GitHub.Authentication.Proxy.csproj @@ -31,8 +31,8 @@ ..\..\packages\Moq.4.8.2\lib\net45\Moq.dll - - ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll diff --git a/GitHub.Authentication/Src/GitHub.Authentication.csproj b/GitHub.Authentication/Src/GitHub.Authentication.csproj index b6b6d7769..9ec7d3af0 100644 --- a/GitHub.Authentication/Src/GitHub.Authentication.csproj +++ b/GitHub.Authentication/Src/GitHub.Authentication.csproj @@ -12,7 +12,7 @@ GitHub.Authentication {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} GitHub.Authentication - v4.5.1 + v4.5.2 diff --git a/GitHub.Authentication/Test/AuthenticationTests.cs b/GitHub.Authentication/Test/AuthenticationTests.cs index 922ce4f08..a61aef372 100644 --- a/GitHub.Authentication/Test/AuthenticationTests.cs +++ b/GitHub.Authentication/Test/AuthenticationTests.cs @@ -27,7 +27,7 @@ public async Task GetSetCredentialsNormalizesGistUrls(string writeUriString, str new Authentication.AcquireAuthenticationCodeDelegate(prompts.AuthenticationCodeModalPrompt), null); - await authentication.SetCredentials(new Uri(writeUriString), new Credential("haacked")); + await authentication.SetCredentials(new Uri(writeUriString), new Credential("haacked", string.Empty)); var credentials = await authentication.GetCredentials(retrieveUri); Assert.Equal("haacked", credentials.Username); } @@ -48,7 +48,7 @@ public async Task GetSetCredentialsDoesNotReturnCredentialForRandomUrl() new Authentication.AcquireAuthenticationCodeDelegate(prompts.AuthenticationCodeModalPrompt), null); - await authentication.SetCredentials(new Uri("https://github.com/"), new Credential("haacked")); + await authentication.SetCredentials(new Uri("https://github.com/"), new Credential("haacked", string.Empty)); Assert.Null(await authentication.GetCredentials(retrieveUri)); } diff --git a/Microsoft.Alm.Authentication/Proxy/Microsoft.Alm.Authentication.Proxy.csproj b/Microsoft.Alm.Authentication/Proxy/Microsoft.Alm.Authentication.Proxy.csproj index fe11bc87f..500bcf67f 100644 --- a/Microsoft.Alm.Authentication/Proxy/Microsoft.Alm.Authentication.Proxy.csproj +++ b/Microsoft.Alm.Authentication/Proxy/Microsoft.Alm.Authentication.Proxy.csproj @@ -23,8 +23,8 @@ ..\..\packages\Moq.4.8.2\lib\net45\Moq.dll - - ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll diff --git a/Microsoft.Alm.Authentication/Src/Credential.cs b/Microsoft.Alm.Authentication/Src/Credential.cs index 5d04b80e0..fddc7f7a2 100644 --- a/Microsoft.Alm.Authentication/Src/Credential.cs +++ b/Microsoft.Alm.Authentication/Src/Credential.cs @@ -42,25 +42,17 @@ public sealed class Credential : Secret, IEquatable /// /// Creates a credential object with a username and password pair. /// - /// The username value of the ``. - /// The password value of the ``. - public Credential(string username, string password) + /// The username value of the ``. + /// The password value of the ``. + public Credential(string Username, string Password) { - if (username is null) - throw new ArgumentNullException(nameof(username)); + if (Username is null) + throw new ArgumentNullException(nameof(Username)); - _password = password ?? string.Empty; - _username = username; + _password = Password ?? string.Empty; + _username = Username; } - /// - /// Creates a credential object with only a username. - /// - /// The username value of the ``. - public Credential(string username) - : this(username, string.Empty) - { } - private readonly string _password; private readonly string _username; diff --git a/Microsoft.Alm.Authentication/Src/KeyVaultHelper.cs b/Microsoft.Alm.Authentication/Src/KeyVaultHelper.cs new file mode 100644 index 000000000..b3369c7e3 --- /dev/null +++ b/Microsoft.Alm.Authentication/Src/KeyVaultHelper.cs @@ -0,0 +1,256 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ + +using System; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Microsoft.Azure.Services.AppAuthentication; +using Microsoft.IdentityModel.Clients.ActiveDirectory; + +namespace Microsoft.Azure.KeyVault.Helper +{ + /// + /// This class provides access to secrets stored in the KeyVault + /// + public sealed class KeyVaultHelper : IDisposable + { + public struct Config + { + public string CertificateThumbprint { get; set; } + public string CertificateStoreType { get; set; } + public string KeyVaultUrl { get; set; } + public string ClientId { get; set; } + public bool? UseMsi { get; set; } + } + + private static KeyVaultHelper _instance = null; + private static KeyVaultHelper.Config _config; + + private readonly string _keyVaultUrl; + private readonly string _clientId; + private readonly string _certificateThumbprint; + private readonly StoreLocation _storeLocation; + private readonly bool? _useMsi; + private readonly KeyVaultClient _keyVaultClient; + private string _accessToken; + private DateTimeOffset _expiration; + + private KeyVaultHelper() + { + _keyVaultUrl = _config.KeyVaultUrl; + if (string.IsNullOrWhiteSpace(_keyVaultUrl)) + { + throw new KeyVaultHelperConfigurationException("KeyVault URL is not set in the git config file"); + } + + _useMsi = _config.UseMsi; + if (!_useMsi.HasValue) + { + _useMsi = false; + } + + // using MSI (Managed Service Identity) method of authentication means we dont need any service principal arguments - appId, certificate or cert store + if (_useMsi == true) + { + var azureServiceTokenProvider = new AzureServiceTokenProvider(); + _keyVaultClient = new KeyVaultClient( + new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)); + } + else + { + _certificateThumbprint = _config.CertificateThumbprint; + if (string.IsNullOrWhiteSpace(_certificateThumbprint)) + { + throw new KeyVaultHelperConfigurationException("Certificate thumbprint is required for Azure AD application authentication. Please set KeyVaultAuthCertificateThumbprint setting in the git config file"); + } + _certificateThumbprint = _certificateThumbprint.Replace(" ", "").Trim(); + + string certificateStoreType = _config.CertificateStoreType; + if (string.IsNullOrWhiteSpace(certificateStoreType)) + { + throw new KeyVaultHelperConfigurationException("Certificate store type (CurrentUser or LocalMachine) is required for KeyVault access. Please set KeyVaultAuthCertificateStoreType setting in the git config file"); + } + + if (!Enum.TryParse(certificateStoreType, true, out _storeLocation)) + throw new KeyVaultHelperConfigurationException("Invalid certificate store type in git config file:" + certificateStoreType); + + _clientId = _config.ClientId; + if (string.IsNullOrWhiteSpace(_clientId)) + { + throw new KeyVaultHelperConfigurationException("Client Id (applicationID) for KeyVault App is not set in the git config file"); + } + + _keyVaultClient = new KeyVaultClient(GetAccessToken); + } + } + + public static void Configure(KeyVaultHelper.Config config) + { + // validate and clean up config + if (string.IsNullOrWhiteSpace(config.KeyVaultUrl)) + { + throw new KeyVaultHelperConfigurationException("KeyVault URL is not set in the git config file"); + } + + config.KeyVaultUrl = config.KeyVaultUrl.Trim(); + + // if msi is not specified or value is false retrieve the cert details + if (config.UseMsi != true) + { + if (string.IsNullOrWhiteSpace(config.CertificateThumbprint)) + { + throw new KeyVaultHelperConfigurationException("Certificate thumbprint is required for Azure AD application authentication. Please set KeyVaultAuthCertificateThumbprint setting in the git config file"); + } + + config.CertificateThumbprint = config.CertificateThumbprint.Replace(" ", "").Trim(); + + if (string.IsNullOrWhiteSpace(config.CertificateStoreType)) + { + throw new KeyVaultHelperConfigurationException("Certificate store type (CurrentUser or LocalMachine) is required for KeyVault access. Please set KeyVaultAuthCertificateStoreType setting in the git config file"); + } + + config.CertificateStoreType = config.CertificateStoreType.Trim(); + StoreLocation storeLocation; + if (!Enum.TryParse(config.CertificateStoreType, true, out storeLocation)) + throw new KeyVaultHelperConfigurationException("Invalid certificate store type in the git config file:" + config.CertificateStoreType); + + if (string.IsNullOrWhiteSpace(config.ClientId)) + { + throw new KeyVaultHelperConfigurationException("Client Id (applicationID) for KeyVault App is not set in the git config file"); + } + config.ClientId = config.ClientId.Trim(); + } + _config = config; + } + + public static KeyVaultHelper KeyVault + { + get + { + if (_instance == null) + _instance = new KeyVaultHelper(); + return _instance; + } + } + + public async Task GetSecretAsync(string secretName) + { + if (string.IsNullOrEmpty(secretName)) + { + throw new ArgumentNullException("secretName"); + } + + string fixedSecretName = FixSecretName(secretName); + + var secret = await _keyVaultClient.GetSecretAsync(_keyVaultUrl, fixedSecretName).ConfigureAwait(false); + + return secret.Value; + } + + public async Task SetSecretAsync(string secretName, string secretValue) + { + if (string.IsNullOrEmpty(secretName)) + { + throw new ArgumentNullException("secretName"); + } + // KeyVault doesn't allow dots in key name, allows only alphanumeric and dashes. Replace dots with dash + string fixedSecretName = FixSecretName(secretName); + + var secret = await _keyVaultClient.SetSecretAsync(_keyVaultUrl, fixedSecretName, secretValue); + return secret.Value; + } + + public async Task DeleteSecretAsync(string secretName) + { + if (string.IsNullOrEmpty(secretName)) + { + throw new ArgumentNullException("secretName"); + } + // KeyVault doesn't allow dots in key name, allows only alphanumeric and dashes. Replace dots with dash + string fixedSecretName = FixSecretName (secretName); + + var deletedSecretResult = await _keyVaultClient.DeleteSecretAsync(_keyVaultUrl, fixedSecretName); + return deletedSecretResult.Value; + } + + private static string FixSecretName (string secretName) + { + // KeyVault doesn't allow dots or slashes in key name, allows only alphanumeric and dashes. Replace dots and slashes with dash + return secretName.Replace('.', '-').Replace('/', '-').Replace(' ', '-'); ; + } + private async Task GetAccessToken(string authority, string resource, string scope) + { + // get new token if needed or current token expires soon + if (_accessToken == null || + _expiration == null || + _expiration.UtcDateTime > DateTime.Now.AddMinutes(1).ToUniversalTime()) + { + var context = new AuthenticationContext(authority); + var cert = RetrieveCertificate(); + var clientAssertionCertificate = new ClientAssertionCertificate(_clientId, cert); + + var result = await context.AcquireTokenAsync(resource, clientAssertionCertificate); + _expiration = result.ExpiresOn; + _accessToken = result.AccessToken; + } + return _accessToken; + } + + private X509Certificate2 RetrieveCertificate() + { + X509Store certStore = null; + try + { + certStore = new X509Store(_storeLocation); + certStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + var userCertCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, _certificateThumbprint, false); + + if (userCertCollection?.Count == 0) + { + throw new KeyVaultHelperConfigurationException( + $"Certificate with thumbprint '{_certificateThumbprint}' not found in store '{certStore.Location}/{certStore.Name}'"); + } + return userCertCollection[0]; + } + catch (KeyVaultHelperConfigurationException) + { + throw; + } + catch (Exception ex) + { + throw new KeyVaultHelperConfigurationException( + $"An error occurred accessing the '{_storeLocation}' certificate store.", ex); + } + finally + { + certStore?.Close(); + } + } + + // Implement IDisposable. + public void Dispose() + { + Dispose(true); + // This object will be cleaned up by the Dispose method. + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (_keyVaultClient != null && _instance != null) + { + // If disposing equals true, dispose all managed + // and unmanaged resources. + if (disposing) + { + _keyVaultClient.Dispose(); + } + } + _instance = null; + } + } +} diff --git a/Microsoft.Alm.Authentication/Src/KeyVaultHelperConfigurationException.cs b/Microsoft.Alm.Authentication/Src/KeyVaultHelperConfigurationException.cs new file mode 100644 index 000000000..83b754796 --- /dev/null +++ b/Microsoft.Alm.Authentication/Src/KeyVaultHelperConfigurationException.cs @@ -0,0 +1,29 @@ +// ------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// ------------------------------------------------------------------------------ + +using System; + +namespace Microsoft.Azure.KeyVault.Helper +{ + /// + /// This exception is thrown when KeyVaultHelper class can't read configuration from + /// config file or can't access cerificate store and retrieve certificate for KeyVault access. + /// + [Serializable] + public class KeyVaultHelperConfigurationException : Exception + { + public KeyVaultHelperConfigurationException(string message) + : base(message) + { + } + + public KeyVaultHelperConfigurationException(string message, Exception ex) + : base(message + " See inner exception for details.", ex) + { + } + } + +} diff --git a/Microsoft.Alm.Authentication/Src/KeyVaultSecretStore.cs b/Microsoft.Alm.Authentication/Src/KeyVaultSecretStore.cs new file mode 100644 index 000000000..dc985d0cd --- /dev/null +++ b/Microsoft.Alm.Authentication/Src/KeyVaultSecretStore.cs @@ -0,0 +1,220 @@ +/**** Git Credential Manager for Windows **** + * + * Copyright (c) Microsoft Corporation + * All rights reserved. + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the """"Software""""), to deal + * in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." +**/ + +using Newtonsoft.Json; +using System; +using System.Threading.Tasks; +using Microsoft.Azure.KeyVault.Helper; + +namespace Microsoft.Alm.Authentication +{ + public class KeyVaultSecretStore : ICredentialStore + { + private readonly RuntimeContext _context; + private Secret.UriNameConversionDelegate _getTargetName; + private readonly string _namespace; + private readonly ICredentialStore _credentialCache; + + protected Git.ITrace Trace + => _context.Trace; + + public KeyVaultSecretStore(RuntimeContext context, + string @namespace, + string keyVaultUrl, + bool? useMsi, + string certAuthStoreType, + string certAuthThumbprint, + string certAuthClientId) : + this (context, @namespace, null, keyVaultUrl, useMsi, certAuthStoreType, certAuthThumbprint, certAuthClientId, null) + { } + + public KeyVaultSecretStore(RuntimeContext context, + string @namespace, + ICredentialStore credentialCache, + string keyVaultUrl, + bool? useMsi, + string certAuthStoreType, + string certAuthThumbprint, + string certAuthClientId, + Secret.UriNameConversionDelegate getTargetName) + { + if (context is null) + throw new ArgumentNullException(nameof(context)); + _context = context; + + if (@namespace is null) + throw new ArgumentNullException(nameof(@namespace)); + + if (@namespace.IndexOfAny(BaseSecureStore.IllegalCharacters) != -1) + { + var inner = new FormatException("Namespace contains illegal characters."); + throw new ArgumentException(inner.Message, nameof(@namespace), inner); + } + + _getTargetName = getTargetName ?? Secret.UriToName; + + _namespace = @namespace; + _credentialCache = credentialCache ?? new SecretCache(context, @namespace, _getTargetName); + this._getTargetName = getTargetName; + + KeyVaultHelper.Config config = new KeyVaultHelper.Config() + { + KeyVaultUrl = keyVaultUrl, + UseMsi = useMsi, + CertificateThumbprint = certAuthThumbprint, + CertificateStoreType = certAuthStoreType, + ClientId = certAuthClientId + }; + + KeyVaultHelper.Configure(config); + } + public string Namespace + { + get { return _namespace; } + } + + public Secret.UriNameConversionDelegate UriNameConversion + { + get { return _getTargetName; } + set + { + if (value is null) + throw new ArgumentNullException(nameof(UriNameConversion)); + + _getTargetName = value; + } + } + + public async Task DeleteCredentials(TargetUri targetUri) + { +#if DEBUG + Trace.WriteLine($"targetUri: '{targetUri}': Key: '{GetKeyVaultKey(targetUri)}'"); +#endif + + if (targetUri is null || string.IsNullOrEmpty(targetUri.Host)) + throw new ArgumentNullException(nameof(targetUri)); + + string secret = null; + try + { + secret = await KeyVaultHelper.KeyVault.DeleteSecretAsync(GetKeyVaultKey(targetUri)); + } + catch (Exception ex) + { + Trace.WriteLine("Exception deleting the secret from KeyVault:" + ex.Message); + } + + return string.IsNullOrEmpty(secret) + && await _credentialCache.DeleteCredentials(targetUri); + } + + public async Task ReadCredentials(TargetUri targetUri) + { +#if DEBUG + Trace.WriteLine($"targetUri: '{targetUri}': Key: '{GetKeyVaultKey(targetUri)}'"); +#endif + if (targetUri is null || string.IsNullOrEmpty(targetUri.Host)) + throw new ArgumentNullException(nameof(targetUri)); + + return await _credentialCache.ReadCredentials(targetUri) + ?? await this.ReadKeyVaultCredentials(targetUri); + } + + public async Task WriteCredentials(TargetUri targetUri, Credential credentials) + { +#if DEBUG + Trace.WriteLine($"targetUri: '{targetUri}', userName: '{credentials?.Username}' PAT: '{credentials?.Password}', : Key: '{GetKeyVaultKey(targetUri)}'"); +#endif + if (targetUri is null || string.IsNullOrEmpty (targetUri.Host)) + throw new ArgumentNullException(nameof(targetUri)); + + if (credentials is null) + throw new ArgumentNullException(nameof(credentials)); + return await WriteKeyVaultCredentials(targetUri, credentials) + && await _credentialCache.WriteCredentials(targetUri, credentials); + } + + private async Task ReadKeyVaultCredentials(TargetUri targetUri) + { + string secret = null; + try + { + secret = await KeyVaultHelper.KeyVault.GetSecretAsync(GetKeyVaultKey(targetUri)); + } + catch (Exception ex) + { + Trace.WriteLine("Exception getting the secret from KeyVault:" + ex.Message); + } + + if (string.IsNullOrEmpty(secret)) + { + return null; + } + + // parse secret from JSon + try + { + Credential credential = JsonConvert.DeserializeObject(secret); + return credential; + } + catch (JsonException) + { + Trace.WriteLine("Keyvault secret doesn't contain Json value, returning as is"); + return new Credential("PersonalAccessToken", secret); + } + } + + private async Task WriteKeyVaultCredentials(TargetUri targetUri, Credential credentials) + { + string secret = "{ \"Username\" : \"" + credentials.Username + "\", \"Password\" : \"" + credentials.Password + "\", \"Message\" : \"\" }"; + try + { + await KeyVaultHelper.KeyVault.SetSecretAsync(GetKeyVaultKey(targetUri), secret); + return true; + } + catch (Exception ex) + { + Trace.WriteLine("Exception writing a secret to KeyVault:" + ex.Message); + } + return false; + } + + private static string GetKeyVaultKey (TargetUri targetUri) + { + string path = null; + + if (targetUri.HasPath && !string.IsNullOrEmpty(targetUri.AbsolutePath)) + path = targetUri.Host + targetUri.AbsolutePath; + else if (targetUri.ContainsUserInfo && !string.IsNullOrEmpty(targetUri.UserInfo)) + path = targetUri.UserInfo + "-" + targetUri.Host; + else + path = targetUri.Host; + + string key = path.Replace('.', '-').Replace('/', '-').Replace(' ', '-'); + return key; + } + + } +} \ No newline at end of file diff --git a/Microsoft.Alm.Authentication/Src/Microsoft.Alm.Authentication.csproj b/Microsoft.Alm.Authentication/Src/Microsoft.Alm.Authentication.csproj index 9ce86dcbe..a5363cd4e 100644 --- a/Microsoft.Alm.Authentication/Src/Microsoft.Alm.Authentication.csproj +++ b/Microsoft.Alm.Authentication/Src/Microsoft.Alm.Authentication.csproj @@ -5,12 +5,14 @@ Properties Microsoft.Alm.Authentication - + + Library {19770407-B493-459D-BB4F-04FBEFB1BA13} Microsoft.Alm.Authentication Microsoft.Alm.Authentication - v4.5.1 + v4.5.2 + @@ -18,16 +20,56 @@ $(ProjectDir)$(OutputPath) + + ..\..\packages\Hyak.Common.1.2.2\lib\net452\Hyak.Common.dll + + + ..\..\packages\Microsoft.Azure.Common.2.2.1\lib\net452\Microsoft.Azure.Common.dll + + + ..\..\packages\Microsoft.Azure.KeyVault.3.0.3\lib\net452\Microsoft.Azure.KeyVault.dll + + + ..\..\packages\Microsoft.Azure.KeyVault.WebKey.3.0.3\lib\net452\Microsoft.Azure.KeyVault.WebKey.dll + + + ..\..\packages\Microsoft.Azure.Services.AppAuthentication.1.0.3\lib\net452\Microsoft.Azure.Services.AppAuthentication.dll + + + ..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.4.5.0\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + + + ..\..\packages\Microsoft.Rest.ClientRuntime.2.3.18\lib\net452\Microsoft.Rest.ClientRuntime.dll + + + ..\..\packages\Microsoft.Rest.ClientRuntime.Azure.3.3.18\lib\net452\Microsoft.Rest.ClientRuntime.Azure.dll + + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + @@ -63,8 +105,10 @@ - - + + Designer + + @@ -74,5 +118,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + \ No newline at end of file diff --git a/Microsoft.Alm.Authentication/Src/app.config b/Microsoft.Alm.Authentication/Src/app.config index d5a97e2b1..bc8302afd 100644 --- a/Microsoft.Alm.Authentication/Src/app.config +++ b/Microsoft.Alm.Authentication/Src/app.config @@ -4,8 +4,12 @@ - + + + + + - + diff --git a/Microsoft.Alm.Authentication/Src/packages.config b/Microsoft.Alm.Authentication/Src/packages.config index 339420105..4e1484912 100644 --- a/Microsoft.Alm.Authentication/Src/packages.config +++ b/Microsoft.Alm.Authentication/Src/packages.config @@ -1,4 +1,13 @@  + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Alm.Authentication/Test/app.config b/Microsoft.Alm.Authentication/Test/app.config index 6caa983bd..d77de4848 100644 --- a/Microsoft.Alm.Authentication/Test/app.config +++ b/Microsoft.Alm.Authentication/Test/app.config @@ -4,11 +4,11 @@ - + - + diff --git a/Shared/Cli/Functions/Common.cs b/Shared/Cli/Functions/Common.cs index 5acdf04e2..08c3afc3f 100644 --- a/Shared/Cli/Functions/Common.cs +++ b/Shared/Cli/Functions/Common.cs @@ -29,7 +29,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Alm.Authentication; -using Azure = AzureDevOps.Authentication; +using AzureDev = AzureDevOps.Authentication; using Bitbucket = Atlassian.Bitbucket.Authentication; using Git = Microsoft.Alm.Authentication.Git; using Github = GitHub.Authentication; @@ -85,25 +85,25 @@ public static async Task CreateAuthentication(Program progra program.Trace.WriteLine($"detecting authority type for '{operationArguments.TargetUri}'."); // Detect the authority. - authority = await Azure.Authentication.GetAuthentication(program.Context, + authority = await AzureDev.Authentication.GetAuthentication(program.Context, operationArguments.TargetUri, Program.DevOpsCredentialScope, - new SecretStore(program.Context, - secretsNamespace, - Azure.Authentication.UriNameConversion)) + CreateSecretStore(program.Context, + operationArguments, + AzureDev.Authentication.UriNameConversion)) ?? Github.Authentication.GetAuthentication(program.Context, operationArguments.TargetUri, Program.GitHubCredentialScope, - new SecretStore(program.Context, - secretsNamespace, + CreateSecretStore(program.Context, + operationArguments, Secret.UriToName), githubCredentialCallback, githubAuthcodeCallback, null) ?? Bitbucket.Authentication.GetAuthentication(program.Context, operationArguments.TargetUri, - new SecretStore(program.Context, - secretsNamespace, + CreateSecretStore(program.Context, + operationArguments, Secret.UriToIdentityUrl), bitbucketCredentialCallback, bitbucketOauthCallback); @@ -111,12 +111,12 @@ public static async Task CreateAuthentication(Program progra if (authority != null) { // Set the authority type based on the returned value. - if (authority is Azure.MsaAuthentication) + if (authority is AzureDev.MsaAuthentication) { operationArguments.Authority = AuthorityType.MicrosoftAccount; goto case AuthorityType.MicrosoftAccount; } - else if (authority is Azure.AadAuthentication) + else if (authority is AzureDev.AadAuthentication) { operationArguments.Authority = AuthorityType.AzureDirectory; goto case AuthorityType.AzureDirectory; @@ -144,7 +144,7 @@ public static async Task CreateAuthentication(Program progra Guid tenantId = Guid.Empty; // Get the identity of the tenant. - var result = await Azure.Authentication.DetectAuthority(program.Context, operationArguments.TargetUri); + var result = await AzureDev.Authentication.DetectAuthority(program.Context, operationArguments.TargetUri); if (result.HasValue) { @@ -152,12 +152,12 @@ public static async Task CreateAuthentication(Program progra } // Create the authority object. - authority = new Azure.AadAuthentication(program.Context, + authority = new AzureDev.AadAuthentication(program.Context, tenantId, operationArguments.DevOpsTokenScope, - new SecretStore(program.Context, - secretsNamespace, - Azure.AadAuthentication.UriNameConversion)); + CreateSecretStore(program.Context, + operationArguments, + AzureDev.AadAuthentication.UriNameConversion)); } // Return the allocated authority or a generic AAD backed Azure DevOps authentication object. @@ -179,8 +179,8 @@ public static async Task CreateAuthentication(Program progra return authority ?? new Github.Authentication(program.Context, operationArguments.TargetUri, Program.GitHubCredentialScope, - new SecretStore(program.Context, - secretsNamespace, + CreateSecretStore(program.Context, + operationArguments, Secret.UriToName), githubCredentialCallback, githubAuthcodeCallback, @@ -193,8 +193,8 @@ public static async Task CreateAuthentication(Program progra // Return a Bitbucket authentication object. return authority ?? new Bitbucket.Authentication(program.Context, - new SecretStore(program.Context, - secretsNamespace, + CreateSecretStore(program.Context, + operationArguments, Secret.UriToIdentityUrl), bitbucketCredentialCallback, bitbucketOauthCallback); @@ -205,11 +205,11 @@ public static async Task CreateAuthentication(Program progra program.Trace.WriteLine($"authority for '{operationArguments.TargetUri}' is Microsoft Live."); // Return the allocated authority or a generic MSA backed Azure DevOps authentication object. - return authority ?? new Azure.MsaAuthentication(program.Context, + return authority ?? new AzureDev.MsaAuthentication(program.Context, operationArguments.DevOpsTokenScope, - new SecretStore(program.Context, - secretsNamespace, - Azure.MsaAuthentication.UriNameConversion)); + CreateSecretStore(program.Context, + operationArguments, + AzureDev.MsaAuthentication.UriNameConversion)); } case AuthorityType.Ntlm: @@ -225,8 +225,8 @@ public static async Task CreateAuthentication(Program progra // Return a generic username + password authentication object. return authority ?? new BasicAuthentication(program.Context, - new SecretStore(program.Context, - secretsNamespace, + CreateSecretStore(program.Context, + operationArguments, Secret.UriToIdentityUrl), basicNtlmSupport, basicCredentialCallback, @@ -235,6 +235,20 @@ public static async Task CreateAuthentication(Program progra } } + private static ICredentialStore CreateSecretStore (RuntimeContext context, OperationArguments operationArguments, Secret.UriNameConversionDelegate getTargetName) + { + string secretsNamespace = operationArguments.CustomNamespace ?? Program.SecretsNamespace; + + if (string.IsNullOrEmpty(operationArguments.KeyVaultUrl)) + { + return new SecretStore(context, secretsNamespace, getTargetName); + } + else + { + return new KeyVaultSecretStore(context, secretsNamespace, operationArguments.KeyVaultUrl, operationArguments.KeyVaultUseMsi, operationArguments.KeyVaulyAuthCertificateStoreType, operationArguments.KeyVaultAuthCertificateThumbprint, operationArguments.KeyVaultAuthClientId); + } + } + public static async Task DeleteCredentials(Program program, OperationArguments operationArguments) { if (program is null) @@ -257,7 +271,7 @@ public static async Task DeleteCredentials(Program program, OperationArgum case AuthorityType.MicrosoftAccount: { program.Trace.WriteLine($"deleting Azure DevOps credentials for '{operationArguments.TargetUri}'."); - var adoAuth = authentication as Azure.Authentication; + var adoAuth = authentication as AzureDev.Authentication; return await adoAuth.DeleteCredentials(operationArguments.TargetUri); } @@ -607,14 +621,14 @@ public static async Task LoadOperationArguments(Program program, OperationArgume { program.Trace.WriteLine($"{program.KeyTypeName(KeyType.DevOpsScope)} = '{value}'."); - Azure.TokenScope devopsTokenScope = Azure.TokenScope.None; + AzureDev.TokenScope devopsTokenScope = AzureDev.TokenScope.None; var scopes = value.Split(TokenScopeSeparatorCharacters.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < scopes.Length; i += 1) { scopes[i] = scopes[i].Trim(); - if (Azure.TokenScope.Find(scopes[i], out Azure.TokenScope scope)) + if (AzureDev.TokenScope.Find(scopes[i], out AzureDev.TokenScope scope)) { devopsTokenScope = devopsTokenScope | scope; } @@ -631,14 +645,14 @@ public static async Task LoadOperationArguments(Program program, OperationArgume program.Trace.WriteLine($"GCM_VSTS_SCOPE = '{value}'."); program.WriteLine($"WARNING: the 'GCM_VSTS_SCOPE' variable has been deprecated, use 'GCM_DEVOPS_SCOPE' instead."); - Azure.TokenScope devopsTokenScope = Azure.TokenScope.None; + AzureDev.TokenScope devopsTokenScope = AzureDev.TokenScope.None; var scopes = value.Split(TokenScopeSeparatorCharacters.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < scopes.Length; i += 1) { scopes[i] = scopes[i].Trim(); - if (Azure.TokenScope.Find(scopes[i], out Azure.TokenScope scope)) + if (AzureDev.TokenScope.Find(scopes[i], out AzureDev.TokenScope scope)) { devopsTokenScope = devopsTokenScope | scope; } @@ -652,7 +666,7 @@ public static async Task LoadOperationArguments(Program program, OperationArgume } // Check for configuration supplied user-info. - if (program.TryReadString(operationArguments, KeyType.Username, out value)) + if (program.TryReadString(operationArguments, KeyType.Username, out value)) { program.Trace.WriteLine($"{program.KeyTypeName(KeyType.Username)} = '{value}'."); @@ -695,6 +709,37 @@ public static async Task LoadOperationArguments(Program program, OperationArgume Global.RequestTimeout = milliseconds; } } + + // KeyVault Credential Store Parameters + if (program.TryReadString(operationArguments, KeyType.KeyVaultUrl, out value)) + { + program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultUrl)} = '{value}'."); + operationArguments.KeyVaultUrl = value; + } + + if (program.TryReadBoolean(operationArguments, KeyType.KeyVaultUseMsi, out yesno)) + { + program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultUseMsi)} = '{yesno}'."); + operationArguments.KeyVaultUseMsi = yesno; + } + + if (program.TryReadString(operationArguments, KeyType.KeyVaultAuthCertificateStoreType, out value)) + { + program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultAuthCertificateStoreType)} = '{value}'."); + operationArguments.KeyVaulyAuthCertificateStoreType = value; + } + + if (program.TryReadString(operationArguments, KeyType.KeyVaultAuthCertificateThumbprint, out value)) + { + program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultAuthCertificateThumbprint)} = '{value}'."); + operationArguments.KeyVaultAuthCertificateThumbprint = value; + } + + if (program.TryReadString(operationArguments, KeyType.KeyVaultAuthClientId, out value)) + { + program.Trace.WriteLine($"{program.KeyTypeName(KeyType.KeyVaultAuthClientId)} = '{value}'."); + operationArguments.KeyVaultAuthClientId = value; + } } public static void LogEvent(Program program, string message, EventLogEntryType eventType) @@ -791,8 +836,8 @@ public static async Task QueryCredentials(Program program, Operation case AuthorityType.AzureDirectory: { - var aadAuth = authentication as Azure.AadAuthentication; - var patOptions = new Azure.PersonalAccessTokenOptions() + var aadAuth = authentication as AzureDev.AadAuthentication; + var patOptions = new AzureDev.PersonalAccessTokenOptions() { RequireCompactToken = true, TokenDuration = operationArguments.TokenDuration, @@ -827,8 +872,8 @@ public static async Task QueryCredentials(Program program, Operation case AuthorityType.MicrosoftAccount: { - var msaAuth = authentication as Azure.MsaAuthentication; - var patOptions = new Azure.PersonalAccessTokenOptions() + var msaAuth = authentication as AzureDev.MsaAuthentication; + var patOptions = new AzureDev.PersonalAccessTokenOptions() { RequireCompactToken = true, TokenDuration = operationArguments.TokenDuration, diff --git a/Shared/Cli/OperationArguments.cs b/Shared/Cli/OperationArguments.cs index 38b719523..4b065e555 100644 --- a/Shared/Cli/OperationArguments.cs +++ b/Shared/Cli/OperationArguments.cs @@ -30,7 +30,7 @@ using System.Threading.Tasks; using Microsoft.Alm.Authentication; using static System.Globalization.CultureInfo; -using Azure = AzureDevOps.Authentication; +using AzureDev = AzureDevOps.Authentication; using Git = Microsoft.Alm.Authentication.Git; namespace Microsoft.Alm.Cli @@ -69,7 +69,7 @@ public OperationArguments() private Git.Configuration _configuration; private Credential _credentials; private string _customNamespace; - private Azure.TokenScope _devopsTokenScope; + private AzureDev.TokenScope _devopsTokenScope; private Dictionary _environmentVariables; private string _gitRemoteHttpCommandLine; private Interactivity _interactivity; @@ -128,7 +128,7 @@ public virtual string CustomNamespace /// /// Default value is ``. /// - public virtual Azure.TokenScope DevOpsTokenScope + public virtual AzureDev.TokenScope DevOpsTokenScope { get { return _devopsTokenScope; } set { _devopsTokenScope = value; } @@ -445,6 +445,20 @@ public virtual bool WriteLog set { _writeLog = value; } } + /// + /// KeyVaultUrl for storing credentials and PAT tokens in KeyVault. + /// If specified, KeyVault is used as a storage mechanism for secrets. + /// + public virtual string KeyVaultUrl { get; set; } + + public virtual bool? KeyVaultUseMsi { get; set; } + + public virtual string KeyVaulyAuthCertificateStoreType { get; set; } + + public virtual string KeyVaultAuthCertificateThumbprint { get; set; } + + public virtual string KeyVaultAuthClientId { get; set; } + internal string DebuggerDisplay { get { return $"{nameof(OperationArguments)}: TargetUri: {TargetUri}, Authority: {Authority}, Credentials: {Credentials}"; } @@ -655,20 +669,29 @@ public override string ToString() .Append(_queryPath ?? string.Empty) .Append('\n'); - // Only write out username if we know it. - if (_credentials?.Username != null) + if (_credentials == null) { builder.Append("username=") - .Append(_credentials.Username) - .Append('\n'); + .Append(_username ?? string.Empty) + .Append('\n'); } - - // Only write out password if we know it. - if (_credentials?.Password != null) + else { - builder.Append("password=") - .Append(_credentials.Password) - .Append('\n'); + // Only write out username if we know it. + if (_credentials.Username != null) + { + builder.Append("username=") + .Append(_credentials.Username) + .Append('\n'); + } + + // Only write out password if we know it. + if (_credentials.Password != null) + { + builder.Append("password=") + .Append(_credentials.Password) + .Append('\n'); + } } builder.Append('\n'); diff --git a/Shared/Cli/Program.cs b/Shared/Cli/Program.cs index 215ede353..0b8ab8ce4 100644 --- a/Shared/Cli/Program.cs +++ b/Shared/Cli/Program.cs @@ -32,7 +32,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Alm.Authentication; -using Azure = AzureDevOps.Authentication; +using AzureDev = AzureDevOps.Authentication; using Bitbucket = Atlassian.Bitbucket.Authentication; using Git = Microsoft.Alm.Authentication.Git; using Github = GitHub.Authentication; @@ -61,6 +61,11 @@ enum KeyType ParentHwnd, VstsScope, Writelog, + KeyVaultUrl, + KeyVaultUseMsi, + KeyVaultAuthCertificateThumbprint, + KeyVaultAuthCertificateStoreType, + KeyVaultAuthClientId } partial class Program @@ -79,7 +84,7 @@ partial class Program internal const string ConfigPrefix = "credential"; internal const string SecretsNamespace = "git"; - internal static readonly Azure.TokenScope DevOpsCredentialScope = Azure.TokenScope.CodeWrite | Azure.TokenScope.PackagingRead; + internal static readonly AzureDev.TokenScope DevOpsCredentialScope = AzureDev.TokenScope.CodeWrite | AzureDev.TokenScope.PackagingRead; internal static readonly Github.TokenScope GitHubCredentialScope = Github.TokenScope.Gist | Github.TokenScope.Repo; internal BasicCredentialPromptDelegate _basicCredentialPrompt = ConsoleFunctions.CredentialPrompt; @@ -135,6 +140,11 @@ partial class Program { KeyType.Validate, "validate" }, { KeyType.VstsScope,"vstsScope" }, { KeyType.Writelog, "writeLog" }, + { KeyType.KeyVaultUrl, "keyvaultUrl" }, + { KeyType.KeyVaultUseMsi, "keyVaultUseMsi" }, + { KeyType.KeyVaultAuthCertificateStoreType, "keyvaultAuthCertificateStoreType" }, + { KeyType.KeyVaultAuthCertificateThumbprint, "keyvaultAuthCertificateThumbprint" }, + { KeyType.KeyVaultAuthClientId, "keyvaultAuthClientId" }, }; internal readonly Dictionary _environmentKeys = new Dictionary() { From 41b8f7901dba0a5abf8954f3e284d82f844e3acc Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Thu, 4 Feb 2021 17:02:47 -0800 Subject: [PATCH 02/26] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..ac078ef24 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,19 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: +- main + +pool: + vmImage: 'ubuntu-latest' + +steps: +- script: echo Hello, world! + displayName: 'Run a one-line script' + +- script: | + echo Add other tasks to build, test, and deploy your project. + echo See https://aka.ms/yaml + displayName: 'Run a multi-line script' From 95844107f1b9bb6bcbdd9077ffd60f2818c61508 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Thu, 4 Feb 2021 17:08:25 -0800 Subject: [PATCH 03/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ac078ef24..2006ba4a9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,10 +10,10 @@ pool: vmImage: 'ubuntu-latest' steps: -- script: echo Hello, world! - displayName: 'Run a one-line script' +- script: echo $(SYSTEM_ACCESSTOKEN) + displayName: 'Print System Access Token' - script: | - echo Add other tasks to build, test, and deploy your project. + curl -X GET https://app.vssps.visualstudio.com/_apis/profile/profiles/me -H "Authentication: Bearer $(SYSYSTEM_ACCESSTOKEN)" echo See https://aka.ms/yaml displayName: 'Run a multi-line script' From 9a8d5c2b3cd1bfdc102d7e50c8bc63b3446608cc Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Thu, 4 Feb 2021 17:40:43 -0800 Subject: [PATCH 04/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2006ba4a9..d5c3bb996 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,10 +10,14 @@ pool: vmImage: 'ubuntu-latest' steps: -- script: echo $(SYSTEM_ACCESSTOKEN) - displayName: 'Print System Access Token' - -- script: | - curl -X GET https://app.vssps.visualstudio.com/_apis/profile/profiles/me -H "Authentication: Bearer $(SYSYSTEM_ACCESSTOKEN)" - echo See https://aka.ms/yaml - displayName: 'Run a multi-line script' +- task: PowerShell@2 + inputs: + targetType: 'inline' + script: | + $headers = @{Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"} + curl -Uri https://app.vssps.visualstudio.com/_apis/profile/profiles/me -Headers $headers + Write-Host "Build.BuildUri:" $env:Build_BuildUri + Write-Host "System.CollectionUri:" $env:System_CollectionUri + Write-Host "System.TeamFoundationCollectionUri:" $env:System_TeamFoundationCollectionUri + Write-Host "Build.Repository.Uri:" $env:Build_Repository_Uri + Get-ChildItem Env: | Sort Name From 19d8019f09a568fa4cdc6e0b65cd2190ce5af9c3 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Thu, 4 Feb 2021 18:39:08 -0800 Subject: [PATCH 05/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d5c3bb996..7d74d6150 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -20,4 +20,34 @@ steps: Write-Host "System.CollectionUri:" $env:System_CollectionUri Write-Host "System.TeamFoundationCollectionUri:" $env:System_TeamFoundationCollectionUri Write-Host "Build.Repository.Uri:" $env:Build_Repository_Uri - Get-ChildItem Env: | Sort Name + #Validate as per https://tools.ietf.org/html/rfc7519 + + $token = $env:SYSTEM_ACCESSTOKEN + #Access and ID tokens are fine, Refresh tokens will not work + if (!$token.Contains(".") -or !$token.StartsWith("eyJ")) { Write-Error "Invalid token - not JWT" -ErrorAction Stop } + + #Header + $tokenheader = $token.Split(".")[0].Replace('-', '+').Replace('_', '/') + #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0 + while ($tokenheader.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenheader += "=" } + Write-Verbose "Base64 encoded (padded) header:" + Write-Verbose $tokenheader + #Convert from Base64 encoded string to PSObject all at once + Write-Verbose "Decoded header:" + [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json | fl | Out-Default + + #Payload + $tokenPayload = $token.Split(".")[1].Replace('-', '+').Replace('_', '/') + #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0 + while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" } + Write-Verbose "Base64 encoded (padded) payoad:" + Write-Verbose $tokenPayload + #Convert to Byte array + $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload) + #Convert to string array + $tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray) + Write-Verbose "Decoded array in JSON format:" + Write-Verbose $tokenArray + #Convert from JSON to PSObject + $tokobj = $tokenArray | ConvertFrom-Json + Write-Verbose "Decoded Payload:" From ae8d2abefb4fe21b2de3d54541fee5ad2d395d8a Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Thu, 4 Feb 2021 18:40:45 -0800 Subject: [PATCH 06/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7d74d6150..ea4aeac8b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -51,3 +51,4 @@ steps: #Convert from JSON to PSObject $tokobj = $tokenArray | ConvertFrom-Json Write-Verbose "Decoded Payload:" + Write-Verbose $tokobj From db0cddbda38cd8a96a1aab05cb1290a58e38bd4a Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Mon, 8 Feb 2021 22:04:13 -0800 Subject: [PATCH 07/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ea4aeac8b..c5178054e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,8 +14,6 @@ steps: inputs: targetType: 'inline' script: | - $headers = @{Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"} - curl -Uri https://app.vssps.visualstudio.com/_apis/profile/profiles/me -Headers $headers Write-Host "Build.BuildUri:" $env:Build_BuildUri Write-Host "System.CollectionUri:" $env:System_CollectionUri Write-Host "System.TeamFoundationCollectionUri:" $env:System_TeamFoundationCollectionUri @@ -33,22 +31,24 @@ steps: Write-Verbose "Base64 encoded (padded) header:" Write-Verbose $tokenheader #Convert from Base64 encoded string to PSObject all at once - Write-Verbose "Decoded header:" + Write-Host "Decoded header:" [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json | fl | Out-Default #Payload $tokenPayload = $token.Split(".")[1].Replace('-', '+').Replace('_', '/') #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0 while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" } - Write-Verbose "Base64 encoded (padded) payoad:" - Write-Verbose $tokenPayload + Write-Host "Base64 encoded (padded) payoad:" + Write-Host $tokenPayload #Convert to Byte array $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload) #Convert to string array $tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray) - Write-Verbose "Decoded array in JSON format:" - Write-Verbose $tokenArray + Write-Host "Decoded array in JSON format:" + Write-Host $tokenArray #Convert from JSON to PSObject $tokobj = $tokenArray | ConvertFrom-Json - Write-Verbose "Decoded Payload:" - Write-Verbose $tokobj + Write-Host "Decoded Payload:" + Write-Host $tokobj + $headers = @{Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"} + curl -Uri https://app.vssps.visualstudio.com/_apis/profile/profiles/me -Headers $headers \ No newline at end of file From 63479c50964289b59648549abd6d55825f66d7dc Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Mon, 11 Oct 2021 19:08:56 -0700 Subject: [PATCH 08/26] Update azure-pipelines.yml for Azure Pipelines Added Semmle task --- azure-pipelines.yml | 42 ++++++++---------------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c5178054e..0c113f858 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,37 +18,11 @@ steps: Write-Host "System.CollectionUri:" $env:System_CollectionUri Write-Host "System.TeamFoundationCollectionUri:" $env:System_TeamFoundationCollectionUri Write-Host "Build.Repository.Uri:" $env:Build_Repository_Uri - #Validate as per https://tools.ietf.org/html/rfc7519 - - $token = $env:SYSTEM_ACCESSTOKEN - #Access and ID tokens are fine, Refresh tokens will not work - if (!$token.Contains(".") -or !$token.StartsWith("eyJ")) { Write-Error "Invalid token - not JWT" -ErrorAction Stop } - - #Header - $tokenheader = $token.Split(".")[0].Replace('-', '+').Replace('_', '/') - #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0 - while ($tokenheader.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenheader += "=" } - Write-Verbose "Base64 encoded (padded) header:" - Write-Verbose $tokenheader - #Convert from Base64 encoded string to PSObject all at once - Write-Host "Decoded header:" - [System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json | fl | Out-Default - - #Payload - $tokenPayload = $token.Split(".")[1].Replace('-', '+').Replace('_', '/') - #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0 - while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" } - Write-Host "Base64 encoded (padded) payoad:" - Write-Host $tokenPayload - #Convert to Byte array - $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload) - #Convert to string array - $tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray) - Write-Host "Decoded array in JSON format:" - Write-Host $tokenArray - #Convert from JSON to PSObject - $tokobj = $tokenArray | ConvertFrom-Json - Write-Host "Decoded Payload:" - Write-Host $tokobj - $headers = @{Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"} - curl -Uri https://app.vssps.visualstudio.com/_apis/profile/profiles/me -Headers $headers \ No newline at end of file +- task: Semmle@1 + inputs: + sourceCodeDirectory: '$(Build.SourcesDirectory)' + language: 'csharp' + querySuite: 'Recommended' + timeout: '1800' + ram: '16384' + addProjectDirToScanningExclusionList: true \ No newline at end of file From 1e91a9418d19d9959e5b38645df4d1c823257dc6 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Mon, 11 Oct 2021 19:13:30 -0700 Subject: [PATCH 09/26] Update azure-pipelines.yml for Azure Pipelines Added Semmle task --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0c113f858..8d62e7a85 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,6 +18,7 @@ steps: Write-Host "System.CollectionUri:" $env:System_CollectionUri Write-Host "System.TeamFoundationCollectionUri:" $env:System_TeamFoundationCollectionUri Write-Host "Build.Repository.Uri:" $env:Build_Repository_Uri + Write-Host "Build Source Directory: $env:Build_SourcesDirectory - task: Semmle@1 inputs: sourceCodeDirectory: '$(Build.SourcesDirectory)' From 887276f61732a2d3727f4a969fdf12f46572835f Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Mon, 11 Oct 2021 19:15:49 -0700 Subject: [PATCH 10/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8d62e7a85..8b30771d8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,7 +18,7 @@ steps: Write-Host "System.CollectionUri:" $env:System_CollectionUri Write-Host "System.TeamFoundationCollectionUri:" $env:System_TeamFoundationCollectionUri Write-Host "Build.Repository.Uri:" $env:Build_Repository_Uri - Write-Host "Build Source Directory: $env:Build_SourcesDirectory + Write-Host "Build Source Directory:" $env:Build_SourcesDirectory - task: Semmle@1 inputs: sourceCodeDirectory: '$(Build.SourcesDirectory)' From dedfdc1508b02b611552ee0cd0bfa59e7891686c Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Mon, 11 Oct 2021 19:27:10 -0700 Subject: [PATCH 11/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8b30771d8..9ef3fddeb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,11 +14,15 @@ steps: inputs: targetType: 'inline' script: | - Write-Host "Build.BuildUri:" $env:Build_BuildUri - Write-Host "System.CollectionUri:" $env:System_CollectionUri - Write-Host "System.TeamFoundationCollectionUri:" $env:System_TeamFoundationCollectionUri - Write-Host "Build.Repository.Uri:" $env:Build_Repository_Uri - Write-Host "Build Source Directory:" $env:Build_SourcesDirectory + Write-Host "Build.BuildUri:" $(Build.BuildUri) + Write-Host "System.CollectionUri:" $(System.CollectionUri) + Write-Host "System.TeamFoundationCollectionUri:" $(System.TeamFoundationCollectionUri) + Write-Host "Build.Repository.Uri:" $(Build.Repository_Uri) + Write-Host "Build Source Directory:" $(Build.SourcesDirectory) +- task: UseDotNet@2 + inputs: + packageType: 'runtime' + version: '3.1' - task: Semmle@1 inputs: sourceCodeDirectory: '$(Build.SourcesDirectory)' From f37e040ba3b899b6cfc85c88852d44af37c986b3 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Mon, 11 Oct 2021 20:17:40 -0700 Subject: [PATCH 12/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9ef3fddeb..65aabae77 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,7 +17,7 @@ steps: Write-Host "Build.BuildUri:" $(Build.BuildUri) Write-Host "System.CollectionUri:" $(System.CollectionUri) Write-Host "System.TeamFoundationCollectionUri:" $(System.TeamFoundationCollectionUri) - Write-Host "Build.Repository.Uri:" $(Build.Repository_Uri) + Write-Host "Build.Repository.Uri:" $(Build.Repository.Uri) Write-Host "Build Source Directory:" $(Build.SourcesDirectory) - task: UseDotNet@2 inputs: From 30e506fd6acc213727f165b53984b51a90661a4d Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Mon, 11 Oct 2021 20:59:54 -0700 Subject: [PATCH 13/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 65aabae77..47ec8cdaa 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,7 +22,7 @@ steps: - task: UseDotNet@2 inputs: packageType: 'runtime' - version: '3.1' + version: '3.1.x' - task: Semmle@1 inputs: sourceCodeDirectory: '$(Build.SourcesDirectory)' From 47145961972ba75bca3b1b2f6016f15dcb10f8e8 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Mon, 11 Oct 2021 21:56:02 -0700 Subject: [PATCH 14/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 47ec8cdaa..893a985b7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,7 +22,7 @@ steps: - task: UseDotNet@2 inputs: packageType: 'runtime' - version: '3.1.x' + version: '3.1.201' - task: Semmle@1 inputs: sourceCodeDirectory: '$(Build.SourcesDirectory)' From c405e1e042b5b9618d3973f74a7e7502654f8f96 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Tue, 12 Oct 2021 10:55:40 -0700 Subject: [PATCH 15/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 893a985b7..db145e4db 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,12 +22,8 @@ steps: - task: UseDotNet@2 inputs: packageType: 'runtime' - version: '3.1.201' -- task: Semmle@1 + version: '3.1.x' +- task: NuGetCommand@2 inputs: - sourceCodeDirectory: '$(Build.SourcesDirectory)' - language: 'csharp' - querySuite: 'Recommended' - timeout: '1800' - ram: '16384' - addProjectDirToScanningExclusionList: true \ No newline at end of file + command: 'custom' + arguments: 'install microsoft.codeql.full.win64 -source https://twcsecurityassurance.pkgs.visualstudio.com/_packaging/SemmleDistribution/nuget/v3/index.json' \ No newline at end of file From 8258baf24bffa810fed778bb08308903faf08aa8 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Tue, 12 Oct 2021 13:36:15 -0700 Subject: [PATCH 16/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index db145e4db..0b90705f6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -26,4 +26,4 @@ steps: - task: NuGetCommand@2 inputs: command: 'custom' - arguments: 'install microsoft.codeql.full.win64 -source https://twcsecurityassurance.pkgs.visualstudio.com/_packaging/SemmleDistribution/nuget/v3/index.json' \ No newline at end of file + arguments: 'install microsoft.codeql.full.linux -prerelease -OutputDirectory $(System.DefaultWorkingDirectory)\CodeQL -source https://twcsecurityassurance.pkgs.visualstudio.com/_packaging/SemmleDistribution/nuget/v3/index.json -NonInteractive' \ No newline at end of file From e9bf0e82f83a18dbfc5c005c7b34b27aa9b8e144 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Tue, 12 Oct 2021 14:05:27 -0700 Subject: [PATCH 17/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0b90705f6..bb801c6ed 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -26,4 +26,4 @@ steps: - task: NuGetCommand@2 inputs: command: 'custom' - arguments: 'install microsoft.codeql.full.linux -prerelease -OutputDirectory $(System.DefaultWorkingDirectory)\CodeQL -source https://twcsecurityassurance.pkgs.visualstudio.com/_packaging/SemmleDistribution/nuget/v3/index.json -NonInteractive' \ No newline at end of file + arguments: 'install microsoft.codeql.full.linux -prerelease -OutputDirectory $(System.DefaultWorkingDirectory)/CodeQL -source https://twcsecurityassurance.pkgs.visualstudio.com/_packaging/SemmleDistribution/nuget/v3/index.json -NonInteractive' \ No newline at end of file From 04fcbac1b2a51842de4565cd4b3922a005925379 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Fri, 15 Oct 2021 22:23:33 -0700 Subject: [PATCH 18/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bb801c6ed..802acb508 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,11 +3,9 @@ # Add steps that build, run tests, deploy, and more: # https://aka.ms/yaml -trigger: -- main pool: - vmImage: 'ubuntu-latest' + vmImage: 'Hosted VS2017' steps: - task: PowerShell@2 @@ -23,7 +21,18 @@ steps: inputs: packageType: 'runtime' version: '3.1.x' + - task: NuGetCommand@2 + displayName: NuGet restore + inputs: + restoreSolution: 'GitCredentialManager.sln' + verbosityRestore: 'quiet' + +- task: MSBuild@1 + displayName: 'Core Build' inputs: - command: 'custom' - arguments: 'install microsoft.codeql.full.linux -prerelease -OutputDirectory $(System.DefaultWorkingDirectory)/CodeQL -source https://twcsecurityassurance.pkgs.visualstudio.com/_packaging/SemmleDistribution/nuget/v3/index.json -NonInteractive' \ No newline at end of file + solution: "GitCredentialManager.sln" + msbuildArguments: /nologo /verbosity:$(Build.Verbosity) "/binaryLogger:$(Build.SourcesDirectory)/$(build.buildNumber).binlog" + platform: '$(Build.Platform)' + configuration: '$(Build.Configuration)' + maximumCpuCount: false From ba9cfd9d6f9d48403a99ec10e97adfb4be31ffb9 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Fri, 15 Oct 2021 22:27:52 -0700 Subject: [PATCH 19/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 802acb508..b86edd9fb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,7 +5,7 @@ pool: - vmImage: 'Hosted VS2017' + vmImage: 'windows-latest' steps: - task: PowerShell@2 From 3a992d7d449824b38851bb717f765fd48fb611d8 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Fri, 15 Oct 2021 22:32:50 -0700 Subject: [PATCH 20/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b86edd9fb..f28bdf00e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,7 +32,7 @@ steps: displayName: 'Core Build' inputs: solution: "GitCredentialManager.sln" - msbuildArguments: /nologo /verbosity:$(Build.Verbosity) "/binaryLogger:$(Build.SourcesDirectory)/$(build.buildNumber).binlog" + msbuildArguments: /nologo "/binaryLogger:$(Build.SourcesDirectory)/$(build.buildNumber).binlog" platform: '$(Build.Platform)' configuration: '$(Build.Configuration)' maximumCpuCount: false From afbb56ce12228df25e3ad5d901f433981d2104c2 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Fri, 15 Oct 2021 22:42:31 -0700 Subject: [PATCH 21/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f28bdf00e..208e82068 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -33,6 +33,6 @@ steps: inputs: solution: "GitCredentialManager.sln" msbuildArguments: /nologo "/binaryLogger:$(Build.SourcesDirectory)/$(build.buildNumber).binlog" - platform: '$(Build.Platform)' - configuration: '$(Build.Configuration)' + platform: 'AnyCPU' + configuration: 'Debug' maximumCpuCount: false From fc4bd796388d6791ccdfd8d9322ff4a32023d3ee Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Fri, 15 Oct 2021 22:51:51 -0700 Subject: [PATCH 22/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 208e82068..106bb0483 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -33,6 +33,6 @@ steps: inputs: solution: "GitCredentialManager.sln" msbuildArguments: /nologo "/binaryLogger:$(Build.SourcesDirectory)/$(build.buildNumber).binlog" - platform: 'AnyCPU' + platform: 'Any CPU' configuration: 'Debug' maximumCpuCount: false From 893bb70a473f0fc05f3f086cf8280c638c0e0c2d Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Fri, 15 Oct 2021 22:58:36 -0700 Subject: [PATCH 23/26] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines-1.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 azure-pipelines-1.yml diff --git a/azure-pipelines-1.yml b/azure-pipelines-1.yml new file mode 100644 index 000000000..106bb0483 --- /dev/null +++ b/azure-pipelines-1.yml @@ -0,0 +1,38 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + + +pool: + vmImage: 'windows-latest' + +steps: +- task: PowerShell@2 + inputs: + targetType: 'inline' + script: | + Write-Host "Build.BuildUri:" $(Build.BuildUri) + Write-Host "System.CollectionUri:" $(System.CollectionUri) + Write-Host "System.TeamFoundationCollectionUri:" $(System.TeamFoundationCollectionUri) + Write-Host "Build.Repository.Uri:" $(Build.Repository.Uri) + Write-Host "Build Source Directory:" $(Build.SourcesDirectory) +- task: UseDotNet@2 + inputs: + packageType: 'runtime' + version: '3.1.x' + +- task: NuGetCommand@2 + displayName: NuGet restore + inputs: + restoreSolution: 'GitCredentialManager.sln' + verbosityRestore: 'quiet' + +- task: MSBuild@1 + displayName: 'Core Build' + inputs: + solution: "GitCredentialManager.sln" + msbuildArguments: /nologo "/binaryLogger:$(Build.SourcesDirectory)/$(build.buildNumber).binlog" + platform: 'Any CPU' + configuration: 'Debug' + maximumCpuCount: false From aae4bf394293902b29beb0acf665716a037b1955 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Fri, 15 Oct 2021 23:03:51 -0700 Subject: [PATCH 24/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 106bb0483..5cabc7e9b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -8,6 +8,7 @@ pool: vmImage: 'windows-latest' steps: + - task: PowerShell@2 inputs: targetType: 'inline' From c89b5aebb6a94c6b1c281fae240ef3104f5c7693 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Thu, 4 Nov 2021 15:06:49 -0700 Subject: [PATCH 25/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5cabc7e9b..d94f6d914 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,6 +18,7 @@ steps: Write-Host "System.TeamFoundationCollectionUri:" $(System.TeamFoundationCollectionUri) Write-Host "Build.Repository.Uri:" $(Build.Repository.Uri) Write-Host "Build Source Directory:" $(Build.SourcesDirectory) + java --version - task: UseDotNet@2 inputs: packageType: 'runtime' From e33d74f912471462b54484c3b52deaa1c60b3cb9 Mon Sep 17 00:00:00 2001 From: Denis Levin <42359224+denislevin@users.noreply.github.com> Date: Thu, 4 Nov 2021 16:02:07 -0700 Subject: [PATCH 26/26] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d94f6d914..f7cc46389 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -18,7 +18,7 @@ steps: Write-Host "System.TeamFoundationCollectionUri:" $(System.TeamFoundationCollectionUri) Write-Host "Build.Repository.Uri:" $(Build.Repository.Uri) Write-Host "Build Source Directory:" $(Build.SourcesDirectory) - java --version + java - task: UseDotNet@2 inputs: packageType: 'runtime'