Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

fix linux repro extensions #2415

Merged
merged 10 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/ApiService/ApiService/Functions/AgentEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,34 @@ private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
if (ev.State == NodeState.Free) {
if (node.ReimageRequested || node.DeleteRequested) {
_log.Info($"stopping free node with reset flags: {machineId}");
await _context.NodeOperations.Stop(node);
_ = await _context.NodeOperations.Stop(node);
return null;
}

if (await _context.NodeOperations.CouldShrinkScaleset(node)) {
_log.Info($"stopping free node to resize scaleset: {machineId}");
await _context.NodeOperations.SetHalt(node);
_ = await _context.NodeOperations.SetHalt(node);
return null;
}
}

if (ev.State == NodeState.Init) {
if (node.DeleteRequested) {
_log.Info($"stopping node (init and delete_requested): {machineId}");
await _context.NodeOperations.Stop(node);
_ = await _context.NodeOperations.Stop(node);
return null;
}

// Don’t check reimage_requested, as nodes only send 'init' state once. If
// they send 'init' with reimage_requested, it's because the node was reimaged
// successfully.
node = node with { ReimageRequested = false, InitializedAt = DateTimeOffset.UtcNow };
await _context.NodeOperations.SetState(node, ev.State);
_ = await _context.NodeOperations.SetState(node, ev.State);
return null;
}

_log.Info($"node state update: {machineId} from {node.State} to {ev.State}");
await _context.NodeOperations.SetState(node, ev.State);
node = await _context.NodeOperations.SetState(node, ev.State);

if (ev.State == NodeState.Free) {
_log.Info($"node now available for work: {machineId}");
Expand Down Expand Up @@ -193,7 +193,8 @@ private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
}

if (!node.State.ReadyForReset()) {
await _context.NodeOperations.SetState(node, NodeState.Busy);
_ = await _context.NodeOperations.SetState(node, NodeState.Busy);
// node unused after this point
}

var nodeTask = new NodeTasks(
Expand Down
19 changes: 14 additions & 5 deletions src/ApiService/ApiService/Functions/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,12 @@ private async Async.Task<HttpResponseData> Patch(HttpRequestData req) {
context: patch.MachineId.ToString());
}

await _context.NodeOperations.Stop(node, done: true);
node = await _context.NodeOperations.Stop(node, done: true);
if (node.DebugKeepNode) {
await _context.NodeOperations.Replace(node with { DebugKeepNode = false });
var r = await _context.NodeOperations.Replace(node with { DebugKeepNode = false });
if (!r.IsOk) {
_log.Error($"Failed to replace node {node.MachineId} due to {r.ErrorV}");
}
}

return await RequestHandling.Ok(req, true);
Expand Down Expand Up @@ -137,7 +140,10 @@ private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
node = node with { DebugKeepNode = value };
}

await _context.NodeOperations.Replace(node);
var r = await _context.NodeOperations.Replace(node);
if (!r.IsOk) {
_log.Error($"Failed to replace node {node.MachineId} due to {r.ErrorV}");
}
return await RequestHandling.Ok(req, true);
}

Expand Down Expand Up @@ -166,9 +172,12 @@ private async Async.Task<HttpResponseData> Delete(HttpRequestData req) {
context: delete.MachineId.ToString());
}

await _context.NodeOperations.SetHalt(node);
node = await _context.NodeOperations.SetHalt(node);
if (node.DebugKeepNode) {
await _context.NodeOperations.Replace(node with { DebugKeepNode = false });
var r = await _context.NodeOperations.Replace(node with { DebugKeepNode = false });
if (!r.IsOk) {
_log.Error($"Failed to replace node {node.MachineId} due to {r.ErrorV}");
}
}

return await RequestHandling.Ok(req, true);
Expand Down
7 changes: 5 additions & 2 deletions src/ApiService/ApiService/Functions/ReproVmss.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private async Async.Task<HttpResponseData> Delete(HttpRequestData req) {
context: "repro delete");
}

var vm = await _context.ReproOperations.SearchByPartitionKeys(new[] { $"request.OkV.VmId" }).FirstOrDefaultAsync();
var vm = await _context.ReproOperations.SearchByPartitionKeys(new[] { request.OkV.VmId.ToString()! }).FirstOrDefaultAsync();

if (vm == null) {
return await _context.RequestHandling.NotOk(
Expand All @@ -112,7 +112,10 @@ private async Async.Task<HttpResponseData> Delete(HttpRequestData req) {
}

var updatedRepro = vm with { State = VmState.Stopping };
await _context.ReproOperations.Replace(updatedRepro);
var r = await _context.ReproOperations.Replace(updatedRepro);
if (!r.IsOk) {
_log.Error($"Failed to replace repro {updatedRepro.VmId} due to {r.ErrorV}");
}

var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(updatedRepro);
Expand Down
2 changes: 1 addition & 1 deletion src/ApiService/ApiService/Functions/Scaleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private async Task<HttpResponseData> Post(HttpRequestData req) {
ScalesetId: Guid.NewGuid(),
State: ScalesetState.Init,
NeedsConfigUpdate: false,
Auth: Auth.BuildAuth(),
Auth: await Auth.BuildAuth(),
PoolName: create.PoolName,
VmSku: create.VmSku,
Image: create.Image,
Expand Down
78 changes: 49 additions & 29 deletions src/ApiService/ApiService/onefuzzlib/Auth.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,65 @@
namespace Microsoft.OneFuzz.Service;

using System.Buffers.Binary;
using System.Diagnostics;
using System.Security.Cryptography;
using System.IO;

public class Auth {

private static ReadOnlySpan<byte> SSHRSABytes => new byte[] { (byte)'s', (byte)'s', (byte)'h', (byte)'-', (byte)'r', (byte)'s', (byte)'a' };
private static ProcessStartInfo SshKeyGenProcConfig(string tempFile) {

private static byte[] BuildPublicKey(RSA rsa) {
static Span<byte> WriteLengthPrefixedBytes(ReadOnlySpan<byte> src, Span<byte> dest) {
BinaryPrimitives.WriteInt32BigEndian(dest, src.Length);
dest = dest[sizeof(int)..];
src.CopyTo(dest);
return dest[src.Length..];
string keyGen = "ssh-keygen";
var winAzureFunctionPath = "C:\\Program Files\\Git\\usr\\bin\\ssh-keygen.exe";
if (File.Exists(winAzureFunctionPath)) {
keyGen = winAzureFunctionPath;
}
var p = new ProcessStartInfo() {
FileName = keyGen,
CreateNoWindow = false,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
p.ArgumentList.Add("-t");
p.ArgumentList.Add("rsa");
p.ArgumentList.Add("-f");
p.ArgumentList.Add(tempFile);
p.ArgumentList.Add("-P");
p.ArgumentList.Add("");
p.ArgumentList.Add("-b");
p.ArgumentList.Add("2048");
stishkin marked this conversation as resolved.
Show resolved Hide resolved
return p;
}

var parameters = rsa.ExportParameters(includePrivateParameters: false);

// public key format is "ssh-rsa", exponent, modulus, all written
// as (big-endian) length-prefixed bytes
var result = new byte[sizeof(int) + SSHRSABytes.Length + sizeof(int) + parameters.Modulus!.Length + sizeof(int) + parameters.Exponent!.Length];
var spanResult = result.AsSpan();
spanResult = WriteLengthPrefixedBytes(SSHRSABytes, spanResult);
spanResult = WriteLengthPrefixedBytes(parameters.Exponent, spanResult);
spanResult = WriteLengthPrefixedBytes(parameters.Modulus, spanResult);
Debug.Assert(spanResult.Length == 0);
// This works both on Windows and Linux azure function hosts
private static async Async.Task<(string, string)> GenerateKeyValuePair() {
var tmpFile = Path.GetTempFileName();
File.Delete(tmpFile);
stishkin marked this conversation as resolved.
Show resolved Hide resolved
tmpFile = tmpFile + ".key";
var startInfo = SshKeyGenProcConfig(tmpFile);
var proc = new Process() { StartInfo = startInfo };
stishkin marked this conversation as resolved.
Show resolved Hide resolved
if (proc is null) {
throw new Exception($"ssh-keygen process could start (got null)");
}
stishkin marked this conversation as resolved.
Show resolved Hide resolved
if (proc.Start()) {
await proc.WaitForExitAsync();
if (proc.ExitCode != 0) {
throw new Exception($"ssh-keygen failed due to {await proc.StandardError.ReadToEndAsync()}");
stishkin marked this conversation as resolved.
Show resolved Hide resolved
}

return result;
var priv = File.ReadAllText(tmpFile);
var pub = File.ReadAllText(tmpFile + ".pub");
File.Delete(tmpFile);
return (priv, pub.Trim());
} else {
throw new Exception("failed to start new ssh-keygen");
}
}
public static Authentication BuildAuth() {
using var rsa = RSA.Create(2048);
var privateKey = rsa.ExportRSAPrivateKey();
var formattedPrivateKey = $"-----BEGIN RSA PRIVATE KEY-----\n{Convert.ToBase64String(privateKey)}\n-----END RSA PRIVATE KEY-----\n";

var publicKey = BuildPublicKey(rsa);
var formattedPublicKey = $"ssh-rsa {Convert.ToBase64String(publicKey)} onefuzz-generated-key";

public static async Async.Task<Authentication> BuildAuth() {
var (priv, pub) = await GenerateKeyValuePair();
return new Authentication(
Password: Guid.NewGuid().ToString(),
PublicKey: formattedPublicKey,
PrivateKey: formattedPrivateKey);
PublicKey: pub,
PrivateKey: priv);
}
}
4 changes: 2 additions & 2 deletions src/ApiService/ApiService/onefuzzlib/Extension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,10 @@ public async Async.Task<VMExtensionWrapper> MonitorExtension(AzureLocation regio
} else if (vmOs == Os.Linux) {
return new VMExtensionWrapper {
Location = region,
Name = "OMSExtension",
Name = "OmsAgentForLinux",
TypePropertiesType = "OmsAgentForLinux",
Publisher = "Microsoft.EnterpriseCloud.Monitoring",
TypeHandlerVersion = "1.12",
TypeHandlerVersion = "1.0",
AutoUpgradeMinorVersion = true,
Settings = new BinaryData(extensionSettings),
ProtectedSettings = new BinaryData(protectedExtensionSettings),
Expand Down
Loading