114 lines
3.5 KiB
C#
114 lines
3.5 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using Microsoft.Extensions.Options;
|
|
using StellaOps.Registry.TokenService;
|
|
using StellaOps.Registry.TokenService.Observability;
|
|
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.Registry.TokenService.Tests;
|
|
|
|
public sealed class RegistryTokenIssuerTests : IDisposable
|
|
{
|
|
private readonly List<string> _tempFiles = new();
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void IssueToken_GeneratesJwtWithAccessClaim()
|
|
{
|
|
var pemPath = CreatePemKey();
|
|
var options = new RegistryTokenServiceOptions
|
|
{
|
|
Authority = new RegistryTokenServiceOptions.AuthorityOptions
|
|
{
|
|
Issuer = "https://authority.localhost",
|
|
RequireHttpsMetadata = false,
|
|
},
|
|
Signing = new RegistryTokenServiceOptions.SigningOptions
|
|
{
|
|
Issuer = "https://registry.localhost/token",
|
|
KeyPath = pemPath,
|
|
Lifetime = TimeSpan.FromMinutes(5)
|
|
},
|
|
Registry = new RegistryTokenServiceOptions.RegistryOptions
|
|
{
|
|
Realm = "https://registry.localhost/v2/token"
|
|
},
|
|
Plans =
|
|
{
|
|
new RegistryTokenServiceOptions.PlanRule
|
|
{
|
|
Name = "community",
|
|
Repositories =
|
|
{
|
|
new RegistryTokenServiceOptions.RepositoryRule
|
|
{
|
|
Pattern = "stella-ops/public/*",
|
|
Actions = new [] { "pull" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
options.Validate();
|
|
|
|
var issuer = new RegistryTokenIssuer(
|
|
Options.Create(options),
|
|
new PlanRegistry(options),
|
|
new RegistryTokenMetrics(),
|
|
TimeProvider.System);
|
|
|
|
var principal = new ClaimsPrincipal(new ClaimsIdentity(new[]
|
|
{
|
|
new Claim("sub", "client-1"),
|
|
new Claim("stellaops:plan", "community")
|
|
}, "test"));
|
|
|
|
var accessRequests = new[]
|
|
{
|
|
new RegistryAccessRequest("repository", "stella-ops/public/base", new [] { "pull" })
|
|
};
|
|
|
|
var response = issuer.IssueToken(principal, "registry.localhost", accessRequests);
|
|
|
|
Assert.NotEmpty(response.Token);
|
|
|
|
var handler = new JwtSecurityTokenHandler();
|
|
var jwt = handler.ReadJwtToken(response.Token);
|
|
|
|
Assert.Equal("https://registry.localhost/token", jwt.Issuer);
|
|
Assert.True(jwt.Payload.TryGetValue("access", out var access));
|
|
Assert.NotNull(access);
|
|
}
|
|
|
|
private string CreatePemKey()
|
|
{
|
|
using var rsa = RSA.Create(2048);
|
|
var builder = new StringWriter();
|
|
builder.WriteLine("-----BEGIN PRIVATE KEY-----");
|
|
builder.WriteLine(Convert.ToBase64String(rsa.ExportPkcs8PrivateKey(), Base64FormattingOptions.InsertLineBreaks));
|
|
builder.WriteLine("-----END PRIVATE KEY-----");
|
|
|
|
var path = Path.GetTempFileName();
|
|
File.WriteAllText(path, builder.ToString());
|
|
_tempFiles.Add(path);
|
|
return path;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (var file in _tempFiles)
|
|
{
|
|
try
|
|
{
|
|
File.Delete(file);
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
}
|