Rename to something more sensible, implement boilerplate artifact upload endpoint

This commit is contained in:
Saphire 2022-04-25 15:02:13 +07:00
parent f3ab51d639
commit e7d9e56d24
Signed by: Saphire
GPG Key ID: B26EB7A1F07044C4
17 changed files with 285 additions and 20 deletions

4
.gitignore vendored
View File

@ -5,4 +5,6 @@ appsettings.Local.json
/node_modules
/package.json
/package-lock.json
/package-lock.json
/*.db

View File

@ -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;

View 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 });
}
}

View File

@ -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
View 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());
}
}

View 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
}
}
}

View 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");
}
}
}

View 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
View 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
View File

@ -0,0 +1,7 @@
namespace Lunar.Exchange.Amalgam.Models;
public class AmalgamOptions
{
public const string Section = "Amalgam";
public string SharedSecret { get; set; } = null!;
}

View File

@ -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
View File

@ -0,0 +1,7 @@
namespace Lunar.Exchange.Amalgam.Models;
public class ClientHeaderModel
{
public DateTime Timestamp { get; set; }
public long FileSize { get; set; }
}

View File

@ -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())
{

View File

@ -9,7 +9,7 @@
}
},
"profiles": {
"pigeonhole": {
"amalgam": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,

View File

@ -1,4 +1,4 @@
namespace Lunar.Exchange.Pigeonhole.Utilities;
namespace Lunar.Exchange.Amalgam.Utilities;
using System;
using Microsoft.AspNetCore.Mvc.ActionConstraints;

View File

@ -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">

View File

@ -5,5 +5,8 @@
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"AmalgamContext": "Data Source=amalgam-server.db"
},
"AllowedHosts": "*"
}