Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 4 additions & 0 deletions Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@
<Project Path="playground/AksDemo/AksDemo.ApiService/AksDemo.ApiService.csproj" />
<Project Path="playground/AksDemo/AksDemo.AppHost/AksDemo.AppHost.csproj" />
</Folder>
<Folder Name="/playground/CertManagerDemo/">
<Project Path="playground/CertManagerDemo/CertManagerDemo.ApiService/CertManagerDemo.ApiService.csproj" />
<Project Path="playground/CertManagerDemo/CertManagerDemo.AppHost/CertManagerDemo.AppHost.csproj" />
</Folder>
<Folder Name="/playground/AzureStorageEndToEnd/">
<Project Path="playground/AzureStorageEndToEnd/AzureStorageEndToEnd.ApiService/AzureStorageEndToEnd.ApiService.csproj" />
<Project Path="playground/AzureStorageEndToEnd/AzureStorageEndToEnd.AppHost/AzureStorageEndToEnd.AppHost.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Playground.ServiceDefaults\Playground.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
57 changes: 57 additions & 0 deletions playground/CertManagerDemo/CertManagerDemo.ApiService/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

var app = builder.Build();

app.MapDefaultEndpoints();

// Endpoints used to validate end-to-end TLS through AGC + cert-manager.
// / - identity, useful for confirming routing reached the right pod
// /api - target of the storefront-gw HTTPRoute (no path rewriting on the gateway)
// /admin - target of the admin-gw HTTPRoute
// /tls - reports whether the request reached us over HTTPS via X-Forwarded-Proto;
// handy for confirming the cert chain end-to-end once a Let's Encrypt cert
// has been issued and bound to the gateway listener.

static object BuildIdentity(string surface) => new
{
service = "CertManagerDemo.ApiService",
surface,
machineName = Environment.MachineName,
podIp = Environment.GetEnvironmentVariable("POD_IP"),
timestampUtc = DateTimeOffset.UtcNow
};

app.MapGet("/", () => Results.Ok(BuildIdentity("root")));
app.MapGet("/api", () => Results.Ok(BuildIdentity("storefront")));
app.MapGet("/admin", () => Results.Ok(BuildIdentity("admin")));

app.MapGet("/tls", (HttpContext ctx) => Results.Ok(new
{
// AGC terminates TLS and forwards the client scheme via X-Forwarded-Proto. If the
// gateway listener has a Let's Encrypt cert bound by cert-manager, requests to the
// public FQDN over https should land here with X-Forwarded-Proto=https.
forwardedProto = ctx.Request.Headers["X-Forwarded-Proto"].ToString(),
forwardedFor = ctx.Request.Headers["X-Forwarded-For"].ToString(),
host = ctx.Request.Host.Value,
scheme = ctx.Request.Scheme,
isHttps = ctx.Request.IsHttps
}));

app.MapGet("/info", () => Results.Ok(new
{
machineName = Environment.MachineName,
osVersion = Environment.OSVersion.ToString(),
processorCount = Environment.ProcessorCount,
dotnetVersion = Environment.Version.ToString(),
aspnetcoreEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"),
podIp = Environment.GetEnvironmentVariable("POD_IP"),
podName = Environment.GetEnvironmentVariable("POD_NAME"),
nodeName = Environment.GetEnvironmentVariable("NODE_NAME")
}));

app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5297",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
65 changes: 65 additions & 0 deletions playground/CertManagerDemo/CertManagerDemo.AppHost/AppHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#pragma warning disable ASPIREAZURE003 // AddSubnet / AzureSubnetResource are evaluation-only

var builder = DistributedApplication.CreateBuilder(args);

// VNet layout (mirrors AksDemo so the two playgrounds can be diffed cleanly):
// 10.100.0.0/16 - vnet (chosen to avoid the AKS default service CIDR 10.0.0.0/16)
// 10.100.0.0/22 - aks node pool subnet (1024 IPs - room for pods/nodes)
// 10.100.4.0/24 - public AGC frontend subnet (delegated to ServiceNetworking by AddLoadBalancer)
// 10.100.5.0/24 - admin AGC frontend subnet
var vnet = builder.AddAzureVirtualNetwork("vnet", "10.100.0.0/16");
var aksSubnet = vnet.AddSubnet("aks-nodes", "10.100.0.0/22");
var publicSubnet = vnet.AddSubnet("alb-public", "10.100.4.0/24");
var adminSubnet = vnet.AddSubnet("alb-admin", "10.100.5.0/24");

var aks = builder.AddAzureKubernetesEnvironment("aks")
.WithSubnet(aksSubnet)
.WithSystemNodePool("Standard_D2as_v5");

aks.AddNodePool("workload", "Standard_D2as_v5", minCount: 1, maxCount: 3);

var publicLb = aks.AddLoadBalancer("public", publicSubnet);
var adminLb = aks.AddLoadBalancer("admin", adminSubnet);

// Email used to register the ACME account with Let's Encrypt. Treat as a parameter so
// it can be supplied per-environment (`aspire deploy -p acme-email=...`) without burning
// it into source.
var acmeEmail = builder.AddParameter("acme-email");

// Install cert-manager via the typed API. This is the only difference from AksDemo:
// AksDemo wires the chart in by hand and pairs it with a manual cluster-issuer annotation,
// whereas here cert-manager and its ClusterIssuer are first-class resources in the model.
var certManager = aks.AddCertManager("cert-manager");

// A single Let's Encrypt production ClusterIssuer with an HTTP-01 solver. cert-manager
// satisfies the challenge by serving a token at /.well-known/acme-challenge/{token} on
// the same hostname being validated, which works for any AGC-assigned FQDN because port
// 80 is publicly reachable.
var letsEncrypt = certManager.AddIssuer("letsencrypt-prod")
.WithLetsEncryptProduction(acmeEmail)
.WithHttp01Solver();

var api = builder.AddProject<Projects.CertManagerDemo_ApiService>("api")
.WithExternalHttpEndpoints();

// Public gateway: serves /api -> the api service, attached to the public AGC ALB.
// WithTls(letsEncrypt) creates an HTTPS listener AND adds the
// `cert-manager.io/cluster-issuer: letsencrypt-prod` annotation in one call. Once AGC
// assigns the gateway its <random>.fz<n>.alb.azure.com FQDN, the tls-fqdn-discovery
// pipeline step patches it into the listener and cert-manager issues a real Let's Encrypt
// cert via HTTP-01 against that FQDN. No ClusterIssuer needs to be created out-of-band.
aks.AddGateway("storefront-gw")
.WithLoadBalancer(publicLb)
.WithRoute("/api", api.GetEndpoint("http"))
.WithTls(letsEncrypt);

// Admin gateway: same backend on a separate AGC ALB. No TLS here on purpose, so the diff
// between an HTTP-only and a cert-manager-managed HTTPS gateway is obvious side-by-side.
aks.AddGateway("admin-gw")
.WithLoadBalancer(adminLb)
.WithRoute("/admin", api.GetEndpoint("http"));

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>9b7a14f1-4e6f-4f2d-9d3a-1c0a7b4d2e88</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<AspireProjectOrPackageReference Include="Aspire.Hosting.AppHost" />
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure" />
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.Kubernetes" />
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.Network" />

<ProjectReference Include="..\CertManagerDemo.ApiService\CertManagerDemo.ApiService.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:16240;http://localhost:16241",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:17160",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:17160",
"ASPIRE_SHOW_DASHBOARD_RESOURCES": "true"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:16241",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:17161",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:17161",
"ASPIRE_SHOW_DASHBOARD_RESOURCES": "true",
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
}
},
"generate-manifest": {
"commandName": "Project",
"launchBrowser": true,
"dotnetRunMessages": true,
"commandLineArgs": "--publisher manifest --output-path aspire-manifest.json",
"applicationUrl": "http://localhost:16241",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:17161"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
5 changes: 5 additions & 0 deletions playground/CertManagerDemo/aspire.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"appHost": {
"path": "CertManagerDemo.AppHost/CertManagerDemo.AppHost.csproj"
}
}
42 changes: 42 additions & 0 deletions src/Aspire.Hosting.Azure.Kubernetes/AzureCertManagerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure.Kubernetes;
using Aspire.Hosting.Kubernetes;

namespace Aspire.Hosting;

/// <summary>
/// Provides AKS-specific overloads for installing cert-manager into an
/// <see cref="AzureKubernetesEnvironmentResource"/>.
/// </summary>
public static class AzureCertManagerExtensions
{
/// <summary>
/// Installs cert-manager into the AKS cluster's underlying Kubernetes environment and
/// returns a typed <see cref="CertManagerResource"/> that can host issuer resources.
/// </summary>
/// <param name="builder">The AKS environment resource builder.</param>
/// <param name="name">The Aspire resource name for the cert-manager installation.</param>
/// <param name="chartVersion">The cert-manager Helm chart version to install.
/// Defaults to a pinned version validated against this Aspire build.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{CertManagerResource}"/> for chaining.</returns>
/// <remarks>
/// Delegates to <see cref="CertManagerExtensions.AddCertManager(IResourceBuilder{KubernetesEnvironmentResource}, string, string?)"/>
/// against the underlying <see cref="AzureKubernetesEnvironmentResource.KubernetesEnvironment"/>,
/// mirroring the pattern used by <see cref="AzureKubernetesIngressExtensions.AddHelmChart"/>.
/// </remarks>
[AspireExport(Description = "Installs cert-manager into an AKS environment")]
public static IResourceBuilder<CertManagerResource> AddCertManager(
this IResourceBuilder<AzureKubernetesEnvironmentResource> builder,
[ResourceName] string name,
string? chartVersion = null)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);

var k8sEnvBuilder = builder.ApplicationBuilder.CreateResourceBuilder(builder.Resource.KubernetesEnvironment);
return k8sEnvBuilder.AddCertManager(name, chartVersion);
}
}
Loading
Loading