diff --git a/source/Calamari.Tests/ArgoCD/Contracts/ArgoCDCustomPropertiesDtoSerializationTests.cs b/source/Calamari.Tests/ArgoCD/Contracts/ArgoCDCustomPropertiesDtoSerializationTests.cs index 089b7d689..482bc4b90 100644 --- a/source/Calamari.Tests/ArgoCD/Contracts/ArgoCDCustomPropertiesDtoSerializationTests.cs +++ b/source/Calamari.Tests/ArgoCD/Contracts/ArgoCDCustomPropertiesDtoSerializationTests.cs @@ -10,8 +10,8 @@ namespace Calamari.Tests.ArgoCD.Contracts; /// /// Deserialisation tests for with a focus on /// the polymorphic array. The converter discriminates on -/// the Type field emitted by Octopus Server (the concrete type name); a missing -/// Type defaults to for backwards compatibility. +/// the Type field emitted by Octopus Server; a missing Type defaults to +/// for backwards compatibility. /// [TestFixture] public class ArgoCDCustomPropertiesDtoSerializationTests @@ -27,7 +27,7 @@ static T DeserializeRaw(string json) => JsonConvert.DeserializeObject(json, Settings)!; [Test] - public void TypeGitCredentialDto_DeserializesAsHttpsCredential() + public void TypeUsernamePassword_DeserializesAsHttpsCredential() { const string json = """ { @@ -52,6 +52,62 @@ public void TypeGitCredentialDto_DeserializesAsHttpsCredential() credential.Password.Should().Be("pass"); } + [Test] + public void TypeSshKey_DeserializesAsSshCredential() + { + const string json = """ + { + "Gateways": [], "Applications": [], + "Credentials": [ + { + "Type": "SshKey", + "Url": "git@github.com:org/repo.git", + "Username": "git", + "PrivateKey": "-----BEGIN OPENSSH PRIVATE KEY-----" + } + ] + } + """; + + var result = DeserializeRaw(json); + + result.Credentials.Should().HaveCount(1); + var credential = result.Credentials[0].Should().BeOfType().Subject; + credential.Url.Should().Be("git@github.com:org/repo.git"); + credential.Username.Should().Be("git"); + credential.PrivateKey.Should().Be("-----BEGIN OPENSSH PRIVATE KEY-----"); + } + + [Test] + public void MixedArray_BothTypesPreserved() + { + const string json = """ + { + "Gateways": [], "Applications": [], + "Credentials": [ + { + "Type": "UsernamePassword", + "Url": "https://github.com/org/repo.git", + "Username": "user", + "Password": "pass" + }, + { + "Type": "SshKey", + "Url": "git@github.com:org/other.git", + "Username": "git", + "PrivateKey": "-----BEGIN OPENSSH PRIVATE KEY-----" + } + ] + } + """; + + var result = DeserializeRaw(json); + + result.Credentials.Should().HaveCount(2); + result.Credentials[0].Should().BeOfType(); + result.Credentials[1].Should().BeOfType(); + } + [Test] public void MissingType_DefaultsToHttpsCredential() { diff --git a/source/Calamari.Tests/ArgoCD/Git/AuthenticatingRepositoryFactoryTests.cs b/source/Calamari.Tests/ArgoCD/Git/AuthenticatingRepositoryFactoryTests.cs index 520ac2305..e471ca16b 100644 --- a/source/Calamari.Tests/ArgoCD/Git/AuthenticatingRepositoryFactoryTests.cs +++ b/source/Calamari.Tests/ArgoCD/Git/AuthenticatingRepositoryFactoryTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using Calamari.ArgoCD.Git; using Calamari.ArgoCD.Git.PullRequests; @@ -8,6 +7,7 @@ using Calamari.Testing.Helpers; using Calamari.Tests.Fixtures.Integration.FileSystem; using FluentAssertions; +using NSubstitute; using NUnit.Framework; using Octopus.Calamari.Contracts.ArgoCD; @@ -54,10 +54,7 @@ public void HttpsCredentialIsSelectedWhenUrlMatchesHttpsCredential() { var httpsUrl = RepositoryHelpers.ToFileUri(OriginPath); var factory = new AuthenticatingRepositoryFactory( - new Dictionary - { - [httpsUrl] = new GitCredentialDto(httpsUrl, "", "") - }, + [new GitCredentialDto(httpsUrl, "", "")], repositoryFactory, log); @@ -70,7 +67,7 @@ public void AnonymousCloneWhenNoCredentialsMatch() { var originUrl = RepositoryHelpers.ToFileUri(OriginPath); var factory = new AuthenticatingRepositoryFactory( - new Dictionary(), + [], repositoryFactory, log); @@ -80,6 +77,37 @@ public void AnonymousCloneWhenNoCredentialsMatch() } } + [TestFixture] + public class SshUrlTests : AuthenticatingRepositoryFactoryTestBase + { + [Test] + public void SshCredentialBranch_IsSelectedAndDispatchesSshKeyGitConnection() + { + // Use an ssh:// URL so the new strict validation allows it, and mock the factory + // so no real SSH connection is attempted. + const string sshUrl = "ssh://git@github.com/org/repo.git"; + var mockRepoFactory = Substitute.For(); + + var factory = new AuthenticatingRepositoryFactory( + [new SshKeyGitCredentialDto(sshUrl, "git", "private-key", [])], + mockRepoFactory, + log); + + factory.CloneRepository(sshUrl, branchName.ToFriendlyName()); + + mockRepoFactory.Received() + .CloneRepository( + Arg.Any(), + Arg.Is(c => c is SshKeyGitConnection)); + } + + [Test] + public void HttpsCredentialTakesPriorityOverSshWhenBothMatchAnSshUrl() + { + AssertHttpsCredentialTakesPriorityOverSsh("ssh://git@github.com/org/repo.git"); + } + } + [TestFixture] public class ScpStyleUrlTests : AuthenticatingRepositoryFactoryTestBase { @@ -91,10 +119,7 @@ public void ScpStyleUrlDoesNotMatchHttpsCredential() var httpsUrl = "https://github.com/org/repo.git"; var factory = new AuthenticatingRepositoryFactory( - new Dictionary - { - [httpsUrl] = new GitCredentialDto(httpsUrl, "user", "pass") - }, + [new GitCredentialDto(httpsUrl, "user", "pass")], repositoryFactory, log); @@ -104,5 +129,32 @@ public void ScpStyleUrlDoesNotMatchHttpsCredential() act.Should().Throw(); // clone failure expected log.Messages.Should().Contain(m => m.FormattedMessage.Contains("No Git credentials found")); } + + [Test] + public void HttpsCredentialTakesPriorityOverSshWhenBothMatchAnScpUrl() + { + AssertHttpsCredentialTakesPriorityOverSsh("git@github.com:org/repo.git"); + } + } + + protected void AssertHttpsCredentialTakesPriorityOverSsh(string url) + { + var mockRepoFactory = Substitute.For(); + + // If there are HTTPS and SSH credentials for the same URL, HTTPS wins so API functionality works. + IGitCredentialDto[] rawCredentials = + [ + new GitCredentialDto(url, "https-user", "https-pass"), + new SshKeyGitCredentialDto(url, "ssh-user", "private-key", []) + ]; + + var factory = new AuthenticatingRepositoryFactory(rawCredentials, mockRepoFactory, log); + + factory.CloneRepository(url, "main"); + + mockRepoFactory.Received() + .CloneRepository( + Arg.Any(), + Arg.Is(c => c is HttpsGitConnection)); } -} +} \ No newline at end of file diff --git a/source/Calamari.Tests/ArgoCD/Git/PullRequests/GitVendorApiAdapter_PullRequestTests.cs b/source/Calamari.Tests/ArgoCD/Git/PullRequests/GitVendorApiAdapter_PullRequestTests.cs index de0acc73f..1a170168c 100644 --- a/source/Calamari.Tests/ArgoCD/Git/PullRequests/GitVendorApiAdapter_PullRequestTests.cs +++ b/source/Calamari.Tests/ArgoCD/Git/PullRequests/GitVendorApiAdapter_PullRequestTests.cs @@ -104,6 +104,7 @@ async Task TestPullRequest(string repositoryUrl, string defaultBranch, string cl using var temporaryFolder = TemporaryDirectory.Create(); CredentialsHandler credentialsHandler = (url, usernameFromUrl, types) => new UsernamePasswordCredentials { Username = cloneUsername, Password = clonePassword}; + LibGit2SharpTransportRegistration.EnsureRegistered(); var repositoryPath = Repository.Clone(repositoryUrl, temporaryFolder.DirectoryPath, new CloneOptions() { FetchOptions = diff --git a/source/Calamari.Tests/ArgoCD/Git/RepositoryFactoryTests.cs b/source/Calamari.Tests/ArgoCD/Git/RepositoryFactoryTests.cs index 7daa4ea70..c624b86c8 100644 --- a/source/Calamari.Tests/ArgoCD/Git/RepositoryFactoryTests.cs +++ b/source/Calamari.Tests/ArgoCD/Git/RepositoryFactoryTests.cs @@ -5,6 +5,7 @@ using Calamari.ArgoCD.Git.PullRequests; using Calamari.Common.Commands; using Calamari.Common.Plumbing.FileSystem; +using Calamari.Common.Plumbing.Logging; using Calamari.Integration.Time; using Calamari.Testing.Helpers; using Calamari.Tests.Fixtures.Integration.FileSystem; @@ -94,6 +95,33 @@ public void CanCloneAnExistingRepositoryAtHEADAndAssociatedFiles() fileContent.Should().Be(originalContent); } + [Test] + public void CloningSshKeyGitConnectionDoesNotResolveAPullRequestClientAndLogsVerboseMessage() + { + // Arrange + var filename = "sshTest.txt"; + var content = "ssh test content"; + CreateCommitOnOrigin(branchName, filename, content); + + var mockResolver = Substitute.For(); + var factoryWithMockedResolver = new RepositoryFactory(log, fileSystem, tempDirectory, mockResolver, new SystemClock()); + + var sshConnection = new SshKeyGitConnection( + username: "git", + privateKey: "private-key", + url: OriginPath, + gitReference: branchName); + + // libgit2 skips credential callbacks for local file paths, so this test validates only pull-request-client resolution and verbose logging — not SSH credential validity. + // Act + factoryWithMockedResolver.CloneRepository("Clone_WithSshConnection", sshConnection); + + mockResolver.DidNotReceive().TryResolve(Arg.Any(), Arg.Any(), Arg.Any()); + + log.MessagesVerboseFormatted + .Should().Contain(s => s.Contains("SSH authentication") && s.Contains("Git vendor functionality will not be available")); + } + void CreateCommitOnOrigin(GitBranchName branchName, string fileName, string content) { var message = $"Commit: Message"; diff --git a/source/Calamari.Tests/ArgoCD/Git/RepositoryHelpers.cs b/source/Calamari.Tests/ArgoCD/Git/RepositoryHelpers.cs index ef78e21a2..8e321e6cd 100644 --- a/source/Calamari.Tests/ArgoCD/Git/RepositoryHelpers.cs +++ b/source/Calamari.Tests/ArgoCD/Git/RepositoryHelpers.cs @@ -10,6 +10,8 @@ public static class RepositoryHelpers { public static Repository CreateBareRepository(string repositoryPath) { + LibGit2SharpTransportRegistration.EnsureRegistered(); + Directory.CreateDirectory(repositoryPath); Repository.Init(repositoryPath, isBare: true); return new Repository(repositoryPath); @@ -19,6 +21,8 @@ public static Repository CreateBareRepository(string repositoryPath) public static void CreateBranchIn(GitBranchName branchName, string originPath) { + LibGit2SharpTransportRegistration.EnsureRegistered(); + var signature = new Signature("Your Name", "your.email@example.com", DateTimeOffset.Now); var repository = new Repository(originPath); @@ -47,6 +51,8 @@ public static void CreateBranchIn(GitBranchName branchName, string originPath) public static string CloneOrigin(string tempDirectory, string originPath, GitBranchName branchName) { + LibGit2SharpTransportRegistration.EnsureRegistered(); + var subPath = Guid.NewGuid().ToString(); var resultPath = Path.Combine(tempDirectory, subPath); Repository.Clone(originPath, resultPath); diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs index 9e2862835..04fcc55a4 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDAppImagesInstallConvention.cs @@ -62,11 +62,7 @@ public void Install(RunningDeployment deployment) var argoProperties = customPropertiesLoader.Load(); - // Takes the first git credential per URL, with a preference for username/password credentials (they are more broadly useful) - var gitCredentials = argoProperties.Credentials - .GroupBy(c => c.Url) - .ToDictionary(g => g.Key, g => g.OfType().FirstOrDefault() ?? g.First()); - var authenticatingRepositoryFactory = new AuthenticatingRepositoryFactory(gitCredentials, repositoryFactory, log); + var authenticatingRepositoryFactory = new AuthenticatingRepositoryFactory(argoProperties.Credentials, repositoryFactory, log); var deploymentScope = deployment.Variables.GetDeploymentScope(); log.LogApplicationCounts(deploymentScope, argoProperties.Applications); diff --git a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs index f51fb53ed..00de91420 100644 --- a/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs +++ b/source/Calamari/ArgoCD/Conventions/UpdateArgoCDApplicationManifestsInstallConvention.cs @@ -69,11 +69,7 @@ public void Install(RunningDeployment deployment) var argoProperties = customPropertiesLoader.Load(); - // Takes the first git credential per URL, with a preference for username/password credentials (they are more broadly useful) - var gitCredentials = argoProperties.Credentials - .GroupBy(c => c.Url) - .ToDictionary(g => g.Key, g => g.OfType().FirstOrDefault() ?? g.First()); - var authenticatingRepositoryFactory = new AuthenticatingRepositoryFactory(gitCredentials, repositoryFactory, log); + var authenticatingRepositoryFactory = new AuthenticatingRepositoryFactory(argoProperties.Credentials, repositoryFactory, log); var deploymentScope = deployment.Variables.GetDeploymentScope(); log.LogApplicationCounts(deploymentScope, argoProperties.Applications); diff --git a/source/Calamari/ArgoCD/Git/AuthenticatingRepositoryFactory.cs b/source/Calamari/ArgoCD/Git/AuthenticatingRepositoryFactory.cs index 716be8a57..4fab8845a 100644 --- a/source/Calamari/ArgoCD/Git/AuthenticatingRepositoryFactory.cs +++ b/source/Calamari/ArgoCD/Git/AuthenticatingRepositoryFactory.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Linq; using Calamari.Common.Plumbing.Logging; using Octopus.Calamari.Contracts.ArgoCD; @@ -11,11 +13,15 @@ public class AuthenticatingRepositoryFactory readonly ILog log; public AuthenticatingRepositoryFactory( - Dictionary gitCredentials, + IReadOnlyCollection gitCredentials, IRepositoryFactory repositoryFactory, ILog log) { - this.gitCredentials = gitCredentials; + // Takes the first git credential per URL, with a preference for username/password credentials (they are more broadly useful as they can be used for PR creation) + this.gitCredentials = gitCredentials + .GroupBy(c => c.Url) + .ToDictionary(g => g.Key, g => g.OfType().FirstOrDefault() ?? g.First()); + this.repositoryFactory = repositoryFactory; this.log = log; } @@ -23,13 +29,34 @@ public AuthenticatingRepositoryFactory( public RepositoryWrapper CloneRepository(string requestedUrl, string targetRevision) { var gitCredential = gitCredentials.GetValueOrDefault(requestedUrl); - if (gitCredential is GitCredentialDto passwordCredential) + switch (gitCredential) { - var gitConnection = new HttpsGitConnection(passwordCredential.Username, passwordCredential.Password, GitCloneSafeUrl.ConvertToUriString(requestedUrl), GitReference.CreateFromString(targetRevision)); - return repositoryFactory.CloneRepository(UniqueRepoNameGenerator.Generate(), gitConnection); + case GitCredentialDto passwordCredential: + { + var gitConnection = new HttpsGitConnection(passwordCredential.Username, passwordCredential.Password, GitCloneSafeUrl.ConvertToUriString(requestedUrl), GitReference.CreateFromString(targetRevision)); + return repositoryFactory.CloneRepository(UniqueRepoNameGenerator.Generate(), gitConnection); + } + case SshKeyGitCredentialDto sshCredential: + { + var sshConnection = new SshKeyGitConnection( + sshCredential.Username, + sshCredential.PrivateKey, + requestedUrl, + GitReference.CreateFromString(targetRevision)); + return repositoryFactory.CloneRepository(UniqueRepoNameGenerator.Generate(), sshConnection); + } + case null: + { + log.Info($"No Git credentials found for: '{requestedUrl}', will attempt to clone repository anonymously."); + break; + } + default: + { + log.Warn($"An unrecognised credential type '{gitCredential.GetType().Name}' was found for '{requestedUrl}'. Ignoring the credentials and attempting an anonymous clone."); + break; + } } - log.Info($"No Git credentials found for: '{requestedUrl}', will attempt to clone repository anonymously."); var anonGitConnection = new HttpsGitConnection(null, null, GitCloneSafeUrl.ConvertToUriString(requestedUrl), GitReference.CreateFromString(targetRevision)); return repositoryFactory.CloneRepository(UniqueRepoNameGenerator.Generate(), anonGitConnection); } diff --git a/source/Calamari/ArgoCD/Git/GitConnection.cs b/source/Calamari/ArgoCD/Git/GitConnection.cs index 6c1b1ac68..5c8511274 100644 --- a/source/Calamari/ArgoCD/Git/GitConnection.cs +++ b/source/Calamari/ArgoCD/Git/GitConnection.cs @@ -56,4 +56,24 @@ static Uri ParseAsHttpsUri(string repositoryUrl) return uri; } } + + public class SshKeyGitConnection : IGitConnection + { + public SshKeyGitConnection( + string? username, + string privateKey, + string url, + GitReference gitReference) + { + Username = username; + PrivateKey = privateKey; + Url = url; + GitReference = gitReference; + } + + public string? Username { get; } + public string PrivateKey { get; } + public string Url { get; } + public GitReference GitReference { get; } + } } \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Git/IGitCredentialDtoJsonConverter.cs b/source/Calamari/ArgoCD/Git/IGitCredentialDtoJsonConverter.cs index b26babda6..735af842a 100644 --- a/source/Calamari/ArgoCD/Git/IGitCredentialDtoJsonConverter.cs +++ b/source/Calamari/ArgoCD/Git/IGitCredentialDtoJsonConverter.cs @@ -32,6 +32,7 @@ public override IGitCredentialDto ReadJson( return type switch { null or GitCredentialDto.DiscriminatorValue => obj.ToObject(ConcreteSerializer)!, + SshKeyGitCredentialDto.DiscriminatorValue => obj.ToObject(ConcreteSerializer)!, _ => throw new JsonSerializationException($"Unrecognised credential Type '{type}'.") }; } diff --git a/source/Calamari/ArgoCD/Git/LibGit2SharpCredentialsHandlerExtensionMethods.cs b/source/Calamari/ArgoCD/Git/LibGit2SharpCredentialsHandlerExtensionMethods.cs new file mode 100644 index 000000000..3296b5435 --- /dev/null +++ b/source/Calamari/ArgoCD/Git/LibGit2SharpCredentialsHandlerExtensionMethods.cs @@ -0,0 +1,70 @@ +using System; +using System.Net; +using LibGit2Sharp; +using LibGit2Sharp.Handlers; + +namespace Calamari.ArgoCD.Git; + +public static class LibGit2SharpCredentialsHandlerExtensionMethods +{ + public static CredentialsHandler ToLibGit2SharpCredentialHandler(this IGitConnection? connection) + { + return connection switch + { + HttpsGitConnection { Username: null, Password: null } => Anonymous(), + HttpsGitConnection https => UsernamePassword(https), + SshKeyGitConnection sshKey => SshKey(sshKey), + null => Anonymous(), + _ => throw new NotSupportedException(), + }; + } + + public static CertificateCheckHandler? ToLibGit2SharpCertificateCheckHandler(this IGitConnection? connection) + { + return connection switch + { + SshKeyGitConnection sshKey => SshHostKeyVerificationBypass.AcceptAll, + _ => null + }; + } + + static CredentialsHandler Anonymous() + { + return null!; // A null CredentialsHandler is valid for LibGit2Sharp + } + + static CredentialsHandler SshKey(SshKeyGitConnection connection) + { + return (_, userFromUrl, types) => + { + if (!types.HasFlag(SupportedCredentialTypes.SshMemory)) + { + throw new InvalidOperationException("SSH key credentials provided but are not supported by this endpoint."); + } + + return new SshKeyMemoryCredentials + { + Username = connection.Username ?? userFromUrl, + PrivateKey = connection.PrivateKey, + }; + }; + } + + static CredentialsHandler UsernamePassword(HttpsGitConnection connection) + { + return (_, _, types) => + { + if (!types.HasFlag(SupportedCredentialTypes.UsernamePassword)) + { + throw new InvalidOperationException("Username/password credentials provided but are not supported by this endpoint."); + } + + var securePassword = new NetworkCredential(string.Empty, connection.Password).SecurePassword; + return new SecureUsernamePasswordCredentials + { + Username = connection.Username, + Password = securePassword, + }; + }; + } +} \ No newline at end of file diff --git a/source/Calamari/ArgoCD/Git/RepositoryFactory.cs b/source/Calamari/ArgoCD/Git/RepositoryFactory.cs index 8a1d72b38..382d5a3f9 100644 --- a/source/Calamari/ArgoCD/Git/RepositoryFactory.cs +++ b/source/Calamari/ArgoCD/Git/RepositoryFactory.cs @@ -66,14 +66,8 @@ RepositoryWrapper CheckoutGitRepository(IGitConnection gitConnection, string che BranchName = (gitConnection.GitReference as GitBranchName)?.ToFriendlyName() }; - if (gitConnection is HttpsGitConnection { Username: not null, Password: not null } https) - { - options.FetchOptions.CredentialsProvider = (_, _, _) => new UsernamePasswordCredentials - { - Username = https.Username, - Password = https.Password - }; - } + options.FetchOptions.CredentialsProvider = gitConnection.ToLibGit2SharpCredentialHandler(); + options.FetchOptions.CertificateCheck = gitConnection.ToLibGit2SharpCertificateCheckHandler(); string repoPath; log.InfoFormat("Cloning repository {0}", log.FormatLink(gitConnection.Url)); @@ -122,6 +116,11 @@ RepositoryWrapper CheckoutGitRepository(IGitConnection gitConnection, string che ? gitVendorPullRequestClientResolver.TryResolve(httpsGitConnection, log, CancellationToken.None).Result : null; + if (gitConnection is SshKeyGitConnection) + { + log.Verbose("Git is using SSH authentication, Git vendor functionality will not be available"); + } + return new RepositoryWrapper(repo, fileSystem, checkoutPath, diff --git a/source/Calamari/ArgoCD/Git/RepositoryWrapper.cs b/source/Calamari/ArgoCD/Git/RepositoryWrapper.cs index 88d870b82..7e1c0c577 100644 --- a/source/Calamari/ArgoCD/Git/RepositoryWrapper.cs +++ b/source/Calamari/ArgoCD/Git/RepositoryWrapper.cs @@ -38,12 +38,6 @@ public class RepositoryWrapper( public string WorkingDirectory => repository.Info.WorkingDirectory; - Credentials RepositoryCredentials => connection switch - { - HttpsGitConnection https => new UsernamePasswordCredentials { Username = https.Username, Password = https.Password }, - _ => null - }; - // returns true if changes were made to the repository public bool CommitChanges(string summary, string description) { @@ -197,8 +191,9 @@ public void PushChanges(GitBranchName branchName) PushStatusError? errorsDetected = null; var pushOptions = new PushOptions { - CredentialsProvider = (url, usernameFromUrl, types) => RepositoryCredentials, - OnPushStatusError = errors => errorsDetected = errors + CredentialsProvider = connection.ToLibGit2SharpCredentialHandler(), + OnPushStatusError = errors => errorsDetected = errors, + CertificateCheck = connection.ToLibGit2SharpCertificateCheckHandler() }; repository.Network.Push(repository.Head, pushOptions); @@ -214,7 +209,8 @@ void FetchAndRebase(GitBranchName branchName) var refSpecs = remote.FetchRefSpecs.Select(x => x.Specification).ToList(); var fetchOptions = new FetchOptions { - CredentialsProvider = (url, usernameFromUrl, types) => RepositoryCredentials + CredentialsProvider = connection.ToLibGit2SharpCredentialHandler(), + CertificateCheck = connection.ToLibGit2SharpCertificateCheckHandler() }; try diff --git a/source/Calamari/ArgoCD/Git/SshHostKeyVerificationBypass.cs b/source/Calamari/ArgoCD/Git/SshHostKeyVerificationBypass.cs new file mode 100644 index 000000000..3723cc778 --- /dev/null +++ b/source/Calamari/ArgoCD/Git/SshHostKeyVerificationBypass.cs @@ -0,0 +1,7 @@ +namespace Calamari.ArgoCD.Git; + +static class SshHostKeyVerificationBypass +{ + // TODO(eddy): Implement proper host key verification + public static readonly LibGit2Sharp.Handlers.CertificateCheckHandler AcceptAll = (cert, valid, host) => true; +} diff --git a/source/Calamari/CommitToGit/CommitToGitConfigFactory.cs b/source/Calamari/CommitToGit/CommitToGitConfigFactory.cs index 6b437fe33..30b3f2da9 100644 --- a/source/Calamari/CommitToGit/CommitToGitConfigFactory.cs +++ b/source/Calamari/CommitToGit/CommitToGitConfigFactory.cs @@ -36,11 +36,12 @@ public CommitToGitRepositorySettings CreateRepositoryConfig(RunningDeployment de var properties = customPropertiesLoader.Load(); - var connection = properties.GitCredential switch - { - UsernamePasswordGitCredentialDto usernamePassword => new HttpsGitConnection(usernamePassword.Username, usernamePassword.Password, uriAsString, GitReference.CreateFromString(gitReferenceAsString)), - _ => throw new NotSupportedException("Commit-To-Git only supports the use of username/password. Please select a username/password based credential in your step configuration."), - }; + IGitConnection connection = properties.GitCredential switch + { + UsernamePasswordGitCredentialDto usernamePassword => new HttpsGitConnection(usernamePassword.Username, usernamePassword.Password, uriAsString, GitReference.CreateFromString(gitReferenceAsString)), + SshKeyGitCredentialDto ssh => new SshKeyGitConnection(ssh.Username, ssh.PrivateKey, uriAsString, GitReference.CreateFromString(gitReferenceAsString)), + _ => throw new NotSupportedException($"An unrecognised credential type '{properties.GitCredential.GetType().Name}' was found for '{uriAsString}'"), + }; return new CommitToGitRepositorySettings(connection, commitParameters,