summaryrefslogtreecommitdiff
path: root/src/marlin.core/functional/SshKeyGitCredential.cs
blob: 9183a66e3cd27850768fc0a3e37895eb6e009ab3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
namespace marlin.core.functional;

using System.Diagnostics;

/// <summary>
/// SSH auth via a private key. The key is written to a per-invocation temp file
/// (owner-read/write only) and deleted as soon as the git process that used it exits —
/// ssh has no clean way to take key material via stdin/env, so a short-lived file is
/// the least-bad option.
/// </summary>
public sealed class SshKeyGitCredential : IGitCredential
{
    private readonly string _privateKeyPem;

    public SshKeyGitCredential(string privateKeyPem)
    {
        this._privateKeyPem = privateKeyPem;
    }

    public async Task<IAsyncDisposable> ApplyAsync(ProcessStartInfo psi)
    {
        string keyPath = Path.Combine(Path.GetTempPath(), $"marlin_{Guid.NewGuid():N}.key");
        await File.WriteAllTextAsync(keyPath, this._privateKeyPem);

        if (!OperatingSystem.IsWindows())
            File.SetUnixFileMode(keyPath, UnixFileMode.UserRead | UnixFileMode.UserWrite);

        psi.Environment["GIT_SSH_COMMAND"] =
            $"ssh -i \"{keyPath}\" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new";

        return new TempKeyFile(keyPath);
    }

    private sealed class TempKeyFile : IAsyncDisposable
    {
        private readonly string _path;

        public TempKeyFile(string path)
        {
            this._path = path;
        }

        public ValueTask DisposeAsync()
        {
            try
            {
                File.Delete(this._path);
            }
            catch (IOException)
            {
                // best-effort cleanup; a leftover temp file is not worth failing the caller over
            }

            return ValueTask.CompletedTask;
        }
    }
}