Skip to content
Merged
129 changes: 0 additions & 129 deletions src/KubeOps.Cli/Certificates/CertificateGenerator.cs

This file was deleted.

22 changes: 0 additions & 22 deletions src/KubeOps.Cli/Certificates/Extensions.cs

This file was deleted.

18 changes: 6 additions & 12 deletions src/KubeOps.Cli/Generators/CertificateGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
using KubeOps.Cli.Certificates;
using KubeOps.Cli.Output;
using KubeOps.Operator.Web.Certificates;

namespace KubeOps.Cli.Generators;

internal class CertificateGenerator(string serverName, string namespaceName) : IConfigGenerator
{
public void Generate(ResultOutput output)
{
var (caCert, caKey) = Certificates.CertificateGenerator.CreateCaCertificate();
using Operator.Web.CertificateGenerator generator = new(serverName, namespaceName);

output.Add("ca.pem", caCert.ToPem(), OutputFormat.Plain);
output.Add("ca-key.pem", caKey.ToPem(), OutputFormat.Plain);

var (srvCert, srvKey) = Certificates.CertificateGenerator.CreateServerCertificate(
(caCert, caKey),
serverName,
namespaceName);

output.Add("svc.pem", srvCert.ToPem(), OutputFormat.Plain);
output.Add("svc-key.pem", srvKey.ToPem(), OutputFormat.Plain);
output.Add("ca.pem", generator.Root.Certificate.EncodeToPem(), OutputFormat.Plain);
output.Add("ca-key.pem", generator.Root.Key.EncodeToPem(), OutputFormat.Plain);
output.Add("svc.pem", generator.Server.Certificate.EncodeToPem(), OutputFormat.Plain);
output.Add("svc-key.pem", generator.Server.Key.EncodeToPem(), OutputFormat.Plain);
}
}
5 changes: 2 additions & 3 deletions src/KubeOps.Cli/KubeOps.Cli.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -18,7 +18,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.3.0" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.7.1" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
Expand All @@ -34,7 +33,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\KubeOps.Abstractions\KubeOps.Abstractions.csproj"/>
<ProjectReference Include="..\KubeOps.Abstractions\KubeOps.Abstractions.csproj" />
<ProjectReference Include="..\KubeOps.Operator.Web\KubeOps.Operator.Web.csproj" />
<ProjectReference Include="..\KubeOps.Transpiler\KubeOps.Transpiler.csproj" />
</ItemGroup>
Expand Down
53 changes: 51 additions & 2 deletions src/KubeOps.Operator.Web/Builder/OperatorBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.Runtime.Versioning;

using KubeOps.Abstractions.Builder;
using KubeOps.Operator.Web.Certificates;
using KubeOps.Operator.Web.LocalTunnel;
using KubeOps.Operator.Web.Webhooks;

using Microsoft.Extensions.DependencyInjection;

Expand Down Expand Up @@ -46,9 +48,56 @@ public static IOperatorBuilder AddDevelopmentTunnel(
ushort port,
string hostname = "localhost")
{
builder.Services.AddHostedService<DevelopmentTunnelService>();
builder.Services.AddSingleton(new TunnelConfig(hostname, port));
builder.Services.AddHostedService<TunnelWebhookService>();
builder.Services.AddSingleton(new WebhookLoader(Assembly.GetEntryAssembly()!));
builder.Services.AddSingleton(new WebhookConfig(hostname, port));
builder.Services.AddSingleton<DevelopmentTunnel>();

return builder;
}

/// <summary>
/// Adds a hosted service to the system that uses the server certificate from an <see cref="ICertificateProvider"/>
/// implementation to configure development webhooks without tunnels. The webhooks will be configured to use the hostname and port.
/// </summary>
/// <param name="builder">The operator builder.</param>
/// <param name="port">The port that the webhooks will use to connect to the operator.</param>
/// <param name="hostname">The hostname, IP, or FQDN of the machine running the operator.</param>
/// <param name="certificateProvider">The <see cref="ICertificateProvider"/> the <see cref="CertificateWebhookService"/>
/// will use to generate the PEM-encoded server certificate for the webhooks.</param>
/// <returns>The builder for chaining.</returns>
/// <example>
/// Use the development webhooks.
/// <code>
/// var builder = WebApplication.CreateBuilder(args);
/// string ip = "192.168.1.100";
/// ushort port = 443;
///
/// using CertificateGenerator generator = new CertificateGenerator(ip);
/// using X509Certificate2 cert = generator.Server.CopyServerCertWithPrivateKey();
/// // Configure Kestrel to listen on IPv4, use port 443, and use the server certificate
/// builder.WebHost.ConfigureKestrel(serverOptions =>
/// {
/// serverOptions.Listen(System.Net.IPAddress.Any, port, async listenOptions =>
/// {
/// listenOptions.UseHttps(cert);
/// });
/// });
/// builder.Services
/// .AddKubernetesOperator()
/// // Create the development webhook service using the cert provider
/// .UseCertificateProvider(port, ip, generator)
/// // More code
///
/// </code>
/// </example>
public static IOperatorBuilder UseCertificateProvider(this IOperatorBuilder builder, ushort port, string hostname, ICertificateProvider certificateProvider)
{
builder.Services.AddHostedService<CertificateWebhookService>();
builder.Services.AddSingleton(new WebhookLoader(Assembly.GetEntryAssembly()!));
builder.Services.AddSingleton(new WebhookConfig(hostname, port));
builder.Services.AddSingleton(certificateProvider);

return builder;
}
}
57 changes: 57 additions & 0 deletions src/KubeOps.Operator.Web/Certificates/CertificateExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace KubeOps.Operator.Web.Certificates
{
public static class CertificateExtensions
{
/// <summary>
/// Encodes the certificate in PEM format for use in Kubernetes.
/// </summary>
/// <param name="certificate">The certificate to encode.</param>
/// <returns>The byte representation of the PEM-encoded certificate.</returns>
public static byte[] EncodeToPemBytes(this X509Certificate2 certificate) => Encoding.ASCII.GetBytes(certificate.EncodeToPem());

/// <summary>
/// Encodes the certificate in PEM format.
/// </summary>
/// <param name="certificate">The certificate to encode.</param>
/// <returns>The string representation of the PEM-encoded certificate.</returns>
public static string EncodeToPem(this X509Certificate2 certificate) => new(PemEncoding.Write("CERTIFICATE", certificate.RawData));

/// <summary>
/// Encodes the key in PEM format.
/// </summary>
/// <param name="key">The key to encode.</param>
/// <returns>The string representation of the PEM-encoded key.</returns>
public static string EncodeToPem(this AsymmetricAlgorithm key) => new(PemEncoding.Write("PRIVATE KEY", key.ExportPkcs8PrivateKey()));

/// <summary>
/// Generates a new server certificate with its private key attached, and sets <see cref="X509KeyStorageFlags.PersistKeySet"/>.
/// For example, this certificate can be used in development environments to configure <see cref="Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions"/>.
/// </summary>
/// <param name="server">The cert/key tuple to attach.</param>
/// <returns>An <see cref="X509Certificate2"/> with the private key attached.</returns>
/// <exception cref="NotImplementedException">The <see cref="AsymmetricAlgorithm"/> not have a CopyWithPrivateKey method, or the
/// method has not been implemented in this extension.</exception>
public static X509Certificate2 CopyServerCertWithPrivateKey(this (X509Certificate2 Certificate, AsymmetricAlgorithm Key) server)
{
const string? password = null;
using X509Certificate2 temp = server.Key switch
{
ECDsa ecdsa => server.Certificate.CopyWithPrivateKey(ecdsa),
RSA rsa => server.Certificate.CopyWithPrivateKey(rsa),
ECDiffieHellman ecdh => server.Certificate.CopyWithPrivateKey(ecdh),
DSA dsa => server.Certificate.CopyWithPrivateKey(dsa),
_ => throw new NotImplementedException($"{server.Key} is not implemented for {nameof(CopyServerCertWithPrivateKey)}"),
};

return new X509Certificate2(
temp.Export(X509ContentType.Pfx, password),
password,
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
}
}
}
Loading