Skip to content

Commit

Permalink
Prepare for release (#60)
Browse files Browse the repository at this point in the history
Last cleanup and retesting of everything before publishing an update.
  • Loading branch information
nabsul authored Oct 11, 2022
1 parent 24a3ce7 commit aafd15f
Show file tree
Hide file tree
Showing 36 changed files with 326 additions and 187 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ bin
obj
*.user
appsettings.local.json
.vscode
temp
9 changes: 1 addition & 8 deletions Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace KCert.Controllers;

[Host("*:8080")]
[LocalPortFilter(8080)]
[Route("")]
public class HomeController : Controller
{
Expand Down Expand Up @@ -93,11 +93,4 @@ public async Task<IActionResult> RenewAsync(string ns, string name)
await _kcert.StartRenewalProcessAsync(ns, name, hosts, CancellationToken.None);
return RedirectToAction("Home");
}

[Route("error")]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
4 changes: 2 additions & 2 deletions Controllers/HttpChallengeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

namespace KCert.Controllers;

[Host("*:80")]
[LocalPortFilter(80)]
[Route(".well-known/acme-challenge")]
public class HttpChallengeController : ControllerBase
public class HttpChallengeController : Controller
{
private readonly CertClient _cert;
private readonly ILogger<HttpChallengeController> _log;
Expand Down
26 changes: 26 additions & 0 deletions LocalPortFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace KCert;

public class LocalPortFilterAttribute : ActionFilterAttribute
{
private readonly int _port;

public LocalPortFilterAttribute(int port)
{
_port = port;
}

public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.HttpContext.Connection.LocalPort != _port)
{
context.Result = new NotFoundResult();
}
}

public override void OnActionExecuting(ActionExecutingContext context)
{
}
}
62 changes: 30 additions & 32 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using KCert;
using KCert.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

// command line option for manually generating an ECDSA key
if (args.Length > 0 && args[^1] == "generate-key")
{
Console.WriteLine("Generating ACME Key");
Expand All @@ -15,33 +17,29 @@
return;
}

var fallbacks = new Dictionary<string, string>
{
{ "Acme:Key", CertClient.GenerateNewKey() }
};

var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((ctx, cfg) =>
{
cfg.AddInMemoryCollection(fallbacks);
cfg.AddUserSecrets<Program>(optional: true);
cfg.AddEnvironmentVariables();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(opt =>
{
opt.ListenAnyIP(80);
opt.ListenAnyIP(8080);
});
webBuilder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddHostedService<RenewalService>();
services.AddHostedService<IngressMonitorService>();
services.AddHostedService<ConfigMonitorService>();
})
.Build();

host.Run();
var builder = WebApplication.CreateBuilder(args);

// Add all services marked with the [Service] attribute
Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.GetCustomAttribute<ServiceAttribute>() != null)
.ToList().ForEach(t => builder.Services.AddSingleton(t));

builder.Services.AddControllersWithViews();

builder.WebHost.ConfigureKestrel(opt => {
opt.ListenAnyIP(80);
opt.ListenAnyIP(8080);
});

// add background services
builder.Services.AddHostedService<RenewalService>();
builder.Services.AddHostedService<IngressMonitorService>();
builder.Services.AddHostedService<ConfigMonitorService>();

var app = builder.Build();

app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());

app.Run();
96 changes: 44 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,81 +18,73 @@ KCert is a simple alternative to [cert-manager](https://github.com/jetstack/cert

## Installing KCert

The following instructions assume that you will be using the included `deploy.yml` file as your template to install KCert.
If you are customizing your setup you will likely need to modify the instructions accordingly.
### Deploy with Helm

> Note: KCert has been tested with [Ingress NGINX Controller](https://kubernetes.github.io/ingress-nginx/).
> If you'd like to use it with a different controller and have trouble, there may be some hidden settings that need to be tweaked.
> Please [create an issue](https://github.com/nabsul/kcert/issues) and I'd be happy to help.
First, add the Helm repo with: `helm repo add nabsul https://nabsul.github.io/helm`.

Getting started with KCert is very straigh-forward.
Starting with the `deploy.yml` [template in this repo](https://raw.githubusercontent.com/nabsul/kcert/main/deploy.yml),
find the `env:` section.
Fill in all the required values (marked with `#` comments):
Then install with the following command (filling in your details):

```yaml
- name: ACME__DIRURL
value: # https://acme-staging-v02.api.letsencrypt.org/directory or https://acme-v02.api.letsencrypt.org/directory
- name: ACME__TERMSACCEPTED
value: # You must set this to "true" to indicate your acceptance of Let's Encrypt's terms of service (https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf)
- name: ACME__EMAIL
value: # Your email address for Let's Encrypt and email notifications
```sh
kubectl create ns kcert
helm install kcert nabsul/kcert -n kcert --debug --set acmeAcceptTerms=true,acmeEmail=[YOUR EMAIL]
```

If this is your first time using KCert you should probably start out with `https://acme-staging-v02.api.letsencrypt.org/directory`.
Experiment and make sure everything is working as expected, then switch over to `https://acme-v02.api.letsencrypt.org/directory`.
More information this topic can be found [here](https://letsencrypt.org/docs/staging-environment/).
Note: This defaults to running KCert against Let's Encrypt's staging environment.
After you've tested against staging, you can swicht to production with:

Once you've configured your settings, deploy KCert by running `kubectl apply -f ./deploy.yml`.
Congratulations, KCert should now be running!
```sh
helm install kcert nabsul/kcert -n kcert --debug --set acmeAcceptTerms=true,acmeEmail=[YOUR EMAIL],acmeDirUrl=https://acme-v02.api.letsencrypt.org/directory
```

To check that everything is running as expected:
For setting up SMTP email notifications and other parameters, please check the `charts/kcert/values.yaml` file.

- Run `kubectl -n kcert logs svc/kcert` and make sure there are no error messages
- Run `kubectl -n kcert port-forward svc/kcert 8080` and go to `http://localhost:8080` in your browser
### Creating a Certificate via Ingress

### Recommended: Email Notifications
KCert automatically looks for ingresses that reference a certicate.
If that certificate doesn't exist, it will create it (and renew it).
KCert only monitors ingresses with the `kcert.dev/ingress: "managed"` label.
You can either create your own ingress manually, or use the `kcert-ingress` chart:

KCert can auotmatically send you an email notification when it renews a certificate or fails to do so.
To configure email, you'll need to provide the following SMTP configuration details:
```sh
helm install myingress1 nabsul/kcert-ingress -n kcert --debug --set name=[INGRESS_NAME],host=[DOMAIN],service=[SERVICE_NAME],port=[SERVICE_PORT]
```

- The email address, username and password of the SMTP account
- The hostname and port of the SMTP server (SSL required)
### Creating a Certificate via ConfigMap

The password should be placed in a Kubernetes secret as follows:
If you want to create a certificate without creating an ingress, you can do so via a ConfigMap.
You can create one using the `kcert-configmap` chart as follows:

```sh
kubectl -n kcert create secret generic kcert-smtp --from-literal=password=[...]
helm install myingress1 nabsul/kcert-ingress -n kcert --debug --set name=[INGRESS_NAME],host=[DOMAIN],service=[SERVICE_NAME],port=[SERVICE_PORT]
```

You can then add the following to the `env:` section of your deployment:
## Other Advice

```yaml
- name: SMTP__EMAILFROM
value: [...]
- name: SMTP__HOST
value: [...]
- name: SMTP__PORT
value: "[...]" # Be sure to put the port number between quotes
- name: SMTP__USER
value: [...]
- name: SMTP__PASS
valueFrom:
secretKeyRef:
name: kcert-smtp
key: password
```
### Test in Staging First

If this is your first time using KCert you should probably start out with `https://acme-staging-v02.api.letsencrypt.org/directory`.
Experiment and make sure everything is working as expected, then switch over to `https://acme-v02.api.letsencrypt.org/directory`.
More information this topic can be found [here](https://letsencrypt.org/docs/staging-environment/).

### Diagnostics

To check that everything is running as expected:

- Run `kubectl -n kcert logs svc/kcert` and make sure there are no error messages
- Run `kubectl -n kcert port-forward svc/kcert 8080` and go to `http://localhost:8080` in your browser

### Testing SMTP Configuration

To test your email configuration you can connect to the KCert dasboard by running
`kubectl -n kcert port-forward svc/kcert 80` and opening `http://localhost` in your browser.
`kubectl -n kcert port-forward svc/kcert 8080` and opening `http://localhost:8080` in your browser.
From there, navigate to the configuration section.
Check that your settings are listed there, and then click "Send Test Email" to receive a test email.

### Optional: Configure a fixed ACME Key

By default KCert will generate a random secret key at startup.
For many use cases this should be fine.
If you would like to use a fixed key, you can provide it with an environment variable.
For many use cases this will be fine.
If you would like to use a fixed key, you can provide it as an environment variable.

You can generate your own random key with the following:

Expand All @@ -119,7 +111,7 @@ Finally, add this to your deployment's environment variables:
## Creating Certificates
KCert watches for changes to ingresses in cluster and reacts to them accordingly.
KCert will ignore an ingress unless it is labelled with `kubernetes.io/ingress.class=nginx`.
KCert will ignore an ingress unless it is labelled with `kcert.dev/ingress=managed`.
For example, you could configure an ingress as follows:

```yaml
Expand Down Expand Up @@ -238,7 +230,7 @@ It will behave as if it is running in the cluster and you will be able to explor

KCert does not create many resources,
and most of them are restricted to the kcert namespace.
Removing KCert from your cluster is as simple as executing `kubectl delete -f deploy.yml` or these three commands:
Removing KCert from your cluster is as simple as executing these three commands:

```sh
kubectl delete namespace kcert
Expand Down
2 changes: 1 addition & 1 deletion Services/ConfigMonitorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private async Task HandleCertificateRequestAsync(V1ConfigMap config, Cancellatio

if (!config.Data.TryGetValue("hosts", out var hostList))
{
_log.LogError("ConfigMap {ns}-{n} does not a hosts entry", ns, name);
_log.LogError("ConfigMap {ns}-{n} does not have a hosts entry", ns, name);
return;
}

Expand Down
1 change: 1 addition & 0 deletions Services/KCertClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ private async Task AddChallengeHostsAsync(IEnumerable<string> hosts)
},
Spec = new()
{
IngressClassName = _cfg.ChallengeIngressClassName,
Rules = hosts.Select(CreateRule).ToList()
}
};
Expand Down
7 changes: 5 additions & 2 deletions Services/KCertConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ namespace KCert.Services;
public class KCertConfig
{
private readonly IConfiguration _cfg;
private readonly string _key;

public KCertConfig(IConfiguration cfg)
{
_cfg = cfg;
_key = CertClient.GenerateNewKey();
}

public bool WatchIngresses => GetBool("KCert:WatchIngresses");
Expand All @@ -23,10 +25,11 @@ public KCertConfig(IConfiguration cfg)
public string KCertServiceName => GetString("KCert:ServiceName");
public string KCertIngressName => GetString("KCert:IngressName");
public int KCertServicePort => GetInt("KCert:ServicePort");
public bool ShowRenewButton => GetBool("KCert:ShowRenewButton");
public bool ShowRenewButton => GetBool("KCert:ShowRenewButton");
public int InitialSleepOnFailure => GetInt("KCert:InitialSleepOnFailure");

public Dictionary<string, string> ChallengeIngressAnnotations => GetDictionary("ChallengeIngress:Annotations");
public string ChallengeIngressClassName => GetString("ChallengeIngress:ClassName");

public Dictionary<string, string> ChallengeIngressLabels => GetDictionary("ChallengeIngress:Labels");

Expand All @@ -38,7 +41,7 @@ public KCertConfig(IConfiguration cfg)

public Uri AcmeDir => new(GetString("Acme:DirUrl"));
public string AcmeEmail => GetString("Acme:Email");
public string AcmeKey => GetString("Acme:Key");
public string AcmeKey => GetString("Acme:Key") ?? _key; // If no key is provided via configs, use generated key.
public bool AcmeAccepted => GetBool("Acme:TermsAccepted");

public string SmtpEmailFrom => GetString("Smtp:EmailFrom");
Expand Down
44 changes: 0 additions & 44 deletions Startup.cs

This file was deleted.

1 change: 1 addition & 0 deletions appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"AutoRenewal": true
},
"ChallengeIngress": {
"ClassName": "nginx",
"Annotations": {
"kubernetes.io/ingress.class": "nginx"
},
Expand Down
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
v1.1.0

- Helm chart added
- Reduce number of error emails with backoff logic
- Support creating a certificate without Ingress
Loading

0 comments on commit aafd15f

Please sign in to comment.