Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Add certificates to Operator.Web #756

Merged
merged 11 commits into from
May 15, 2024
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());
ian-buse marked this conversation as resolved.
Show resolved Hide resolved

/// <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
Loading