Rename to something more sensible, implement boilerplate artifact upload endpoint
This commit is contained in:
parent
f3ab51d639
commit
e7d9e56d24
4
.gitignore
vendored
4
.gitignore
vendored
@ -5,4 +5,6 @@ appsettings.Local.json
|
||||
|
||||
/node_modules
|
||||
/package.json
|
||||
/package-lock.json
|
||||
/package-lock.json
|
||||
|
||||
/*.db
|
@ -1,9 +1,9 @@
|
||||
namespace Lunar.Exchange.Pigeonhole.Auth;
|
||||
namespace Lunar.Exchange.Amalgam.Auth;
|
||||
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Encodings.Web;
|
||||
using Lunar.Exchange.Pigeonhole.Models;
|
||||
using Lunar.Exchange.Amalgam.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
102
Controllers/DeploymentController.cs
Normal file
102
Controllers/DeploymentController.cs
Normal file
@ -0,0 +1,102 @@
|
||||
namespace Lunar.Exchange.Amalgam.Controllers;
|
||||
|
||||
using Models;
|
||||
using Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Text.Json;
|
||||
|
||||
[ApiController]
|
||||
[Route("deployment")]
|
||||
public class DeploymentController : ControllerBase
|
||||
{
|
||||
public static readonly int TIME_INTERVAL = 120; // in seconds
|
||||
public static readonly int MAX_SIZE = 50 * 1024 * 1024; // 50 MB
|
||||
private readonly ILogger<DeploymentController> Logger;
|
||||
private readonly AmalgamOptions Options;
|
||||
|
||||
public DeploymentController(ILogger<DeploymentController> logger, IOptions<AmalgamOptions> options)
|
||||
{
|
||||
Logger = logger;
|
||||
Options = options.Value;
|
||||
}
|
||||
|
||||
[HttpPost("push")]
|
||||
// TODO: authorize properly- move that into a filter/etc?
|
||||
public async Task<IActionResult> PushArtifact([FromHeader(Name = "X-Amalgam-Header")] string clientHeader, IFormFile artifact)
|
||||
{
|
||||
var headerBytes = Convert.FromBase64String(clientHeader);
|
||||
var plaintextHeaderBytes = new byte[headerBytes.Length - 28];
|
||||
|
||||
var key = Convert.FromBase64String(Options.SharedSecret);
|
||||
|
||||
using (var chacha = new ChaCha20Poly1305(key)) {
|
||||
try {
|
||||
chacha.Decrypt(
|
||||
nonce: headerBytes[..12],
|
||||
tag: headerBytes[12..28],
|
||||
ciphertext: headerBytes[28..],
|
||||
plaintext :plaintextHeaderBytes
|
||||
);
|
||||
}
|
||||
catch (CryptographicException e)
|
||||
{
|
||||
Logger.LogDebug("Cryptographic error when processing a push request: {}", e.Message);
|
||||
return Unauthorized(new { Error = "Invalid header" });
|
||||
}
|
||||
};
|
||||
|
||||
var header = JsonSerializer.Deserialize<ClientHeaderModel>(plaintextHeaderBytes);
|
||||
|
||||
if (header is null)
|
||||
throw new NullReferenceException("Decrypted proof is null");
|
||||
|
||||
var timespan = DateTime.UtcNow.Subtract(header.Timestamp);
|
||||
if (timespan.TotalSeconds > TIME_INTERVAL)
|
||||
return Unauthorized(new { Error = "Expired proof" });
|
||||
|
||||
Logger.LogInformation("Received a valid file push from {}", "TODO");
|
||||
|
||||
if (header.FileSize > MAX_SIZE)
|
||||
return StatusCode(
|
||||
StatusCodes.Status413PayloadTooLarge,
|
||||
new { Error = $"File size exceeds limit of {MAX_SIZE} bytes" }
|
||||
);
|
||||
|
||||
if (artifact.Length <= 0)
|
||||
return BadRequest(new { Error = "Received an empty file" });
|
||||
|
||||
// Processing the actual file now
|
||||
using var memoryStream = new MemoryStream();
|
||||
await artifact.CopyToAsync(memoryStream);
|
||||
|
||||
if (memoryStream.Length != header.FileSize)
|
||||
return BadRequest(new { Error = "File length mismatch" });
|
||||
|
||||
var encrypted = memoryStream.ToArray();
|
||||
await memoryStream.DisposeAsync();
|
||||
|
||||
var plaintextBytes = new byte[encrypted.Length - 28];
|
||||
|
||||
using (var chacha = new ChaCha20Poly1305(Convert.FromBase64String(Options.SharedSecret))) {
|
||||
chacha.Decrypt(
|
||||
nonce: encrypted[..12],
|
||||
tag: encrypted[12..28],
|
||||
ciphertext: encrypted[28..],
|
||||
plaintext: plaintextBytes
|
||||
);
|
||||
};
|
||||
|
||||
var filePath = Path.GetTempFileName();
|
||||
|
||||
Logger.LogInformation("Writing received file to {} (orig: {})", filePath, artifact.FileName);
|
||||
using var stream = System.IO.File.Create(filePath);
|
||||
await stream.WriteAsync(plaintextBytes);
|
||||
|
||||
// Process uploaded files
|
||||
// Don't rely on or trust the FileName property without validation.
|
||||
|
||||
return Ok(new { artifact.Length });
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
namespace Lunar.Exchange.Pigeonhole.Controllers;
|
||||
namespace Lunar.Exchange.Amalgam.Controllers;
|
||||
|
||||
using Models;
|
||||
using Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Lunar.Exchange.Pigeonhole.Auth;
|
||||
using Lunar.Exchange.Amalgam.Auth;
|
||||
|
||||
[ApiController]
|
||||
[GithubHookAuthorize]
|
||||
|
18
Data/AmalgamContext.cs
Normal file
18
Data/AmalgamContext.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Lunar.Exchange.Amalgam.Data;
|
||||
|
||||
using System.Reflection;
|
||||
using Lunar.Exchange.Amalgam.Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class AmalgamContext : DbContext
|
||||
{
|
||||
public DbSet<Project> Projects { get; set; } = null!;
|
||||
|
||||
public AmalgamContext(DbContextOptions<AmalgamContext> options) : base(options) {}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
|
||||
}
|
||||
}
|
34
Data/Migrations/20220424202349_Initial.Designer.cs
generated
Normal file
34
Data/Migrations/20220424202349_Initial.Designer.cs
generated
Normal file
@ -0,0 +1,34 @@
|
||||
// <auto-generated />
|
||||
using Lunar.Exchange.Amalgam.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Lunar.Exchange.Amalgam.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(AmalgamContext))]
|
||||
[Migration("20220424202349_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.4");
|
||||
|
||||
modelBuilder.Entity("Lunar.Exchange.Amalgam.Data.Models.Project", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Projects");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
30
Data/Migrations/20220424202349_Initial.cs
Normal file
30
Data/Migrations/20220424202349_Initial.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Lunar.Exchange.Amalgam.Data.Migrations
|
||||
{
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Projects",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Projects", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Projects");
|
||||
}
|
||||
}
|
||||
}
|
32
Data/Migrations/AmalgamContextModelSnapshot.cs
Normal file
32
Data/Migrations/AmalgamContextModelSnapshot.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// <auto-generated />
|
||||
using Lunar.Exchange.Amalgam.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Lunar.Exchange.Amalgam.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(AmalgamContext))]
|
||||
partial class AmalgamContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.4");
|
||||
|
||||
modelBuilder.Entity("Lunar.Exchange.Amalgam.Data.Models.Project", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Projects");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
6
Data/Models/Project.cs
Normal file
6
Data/Models/Project.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Lunar.Exchange.Amalgam.Data.Models;
|
||||
|
||||
public class Project
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
7
Models/AmalgamOptions.cs
Normal file
7
Models/AmalgamOptions.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Lunar.Exchange.Amalgam.Models;
|
||||
|
||||
public class AmalgamOptions
|
||||
{
|
||||
public const string Section = "Amalgam";
|
||||
public string SharedSecret { get; set; } = null!;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace Lunar.Exchange.Pigeonhole.Models;
|
||||
namespace Lunar.Exchange.Amalgam.Models;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using Utilities;
|
||||
|
7
Models/ProofModel.cs
Normal file
7
Models/ProofModel.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Lunar.Exchange.Amalgam.Models;
|
||||
|
||||
public class ClientHeaderModel
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public long FileSize { get; set; }
|
||||
}
|
41
Program.cs
41
Program.cs
@ -1,23 +1,20 @@
|
||||
using Lunar.Exchange.Pigeonhole.Auth;
|
||||
using Lunar.Exchange.Pigeonhole.Models;
|
||||
using Lunar.Exchange.Amalgam.Data;
|
||||
using Lunar.Exchange.Amalgam.Auth;
|
||||
using Lunar.Exchange.Amalgam.Models;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Services.Configure<GithubHookOptions>(
|
||||
builder.Configuration.GetSection(GithubHookOptions.Section)
|
||||
builder.Services.AddDbContext<AmalgamContext>(
|
||||
options => options.UseSqlite(
|
||||
builder.Configuration.GetConnectionString("AmalgamContext")
|
||||
)
|
||||
);
|
||||
|
||||
builder.Services.AddSingleton<IAuthorizationHandler, GithubHookAuthorizationHandler>();
|
||||
|
||||
builder.Services
|
||||
.AddAuthentication()
|
||||
.AddScheme<AuthenticationSchemeOptions, GithubHookAuthenticationHandler>(
|
||||
@ -26,6 +23,20 @@ builder.Services
|
||||
options => {}
|
||||
);
|
||||
|
||||
builder.Services.AddControllers();
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Services.Configure<GithubHookOptions>(
|
||||
builder.Configuration.GetSection(GithubHookOptions.Section)
|
||||
);
|
||||
builder.Services.Configure<AmalgamOptions>(
|
||||
builder.Configuration.GetSection(AmalgamOptions.Section)
|
||||
);
|
||||
|
||||
builder.Services.AddSingleton<IAuthorizationHandler, GithubHookAuthorizationHandler>();
|
||||
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy(GithubHookAuthorizationHandler.POLICY_NAME, policy =>
|
||||
@ -35,6 +46,14 @@ builder.Services.AddAuthorization(options =>
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
||||
{
|
||||
using var scope = app.Services.CreateScope();
|
||||
using var context = scope.ServiceProvider.GetRequiredService<AmalgamContext>();
|
||||
await context.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
|
@ -9,7 +9,7 @@
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"pigeonhole": {
|
||||
"amalgam": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace Lunar.Exchange.Pigeonhole.Utilities;
|
||||
namespace Lunar.Exchange.Amalgam.Utilities;
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
|
@ -4,12 +4,17 @@
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>Lunar.Exchange.Pigeonhole</RootNamespace>
|
||||
<RootNamespace>Lunar.Exchange.Amalgam</RootNamespace>
|
||||
<UserSecretsId>90ff12a5-9b1c-4fc4-8080-f5430e34c681</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="SetSourceRevisionId" BeforeTargets="InitializeSourceControlInformation">
|
@ -5,5 +5,8 @@
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"AmalgamContext": "Data Source=amalgam-server.db"
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user