diff --git a/src/Deploy-Helm.ps1 b/src/Deploy-Helm.ps1 index 18f98b0..0b2e12e 100644 --- a/src/Deploy-Helm.ps1 +++ b/src/Deploy-Helm.ps1 @@ -50,11 +50,15 @@ helm lint $chartPath Write-Output "Rendering Helm chart for validation..." helm template $projectName $chartPath -n $namespace | Out-Null +# Generate a unique rollout value (current Unix timestamp) +$rollme = [int][double]::Parse((Get-Date -UFormat %s)) + # Deploy Helm release Write-Output "Deploying Helm release '$projectName'..." helm upgrade --install $projectName $chartPath -n $namespace ` --set imagePullSecret.create=false ` --set imagePullSecrets[0].name=cr-maksit-pull ` + --set-string "rollme=$rollme" # Check deployment status Write-Output "Waiting for deployment rollout..." diff --git a/src/LetsEncryptServer/Configuration.cs b/src/LetsEncryptServer/Configuration.cs index 951f075..be26176 100644 --- a/src/LetsEncryptServer/Configuration.cs +++ b/src/LetsEncryptServer/Configuration.cs @@ -3,82 +3,18 @@ namespace MaksIT.LetsEncryptServer { public class Agent { - - private string? _agentHostname; - public string AgentHostname { - get { - var env = Environment.GetEnvironmentVariable("MAKS-IT_AGENT_HOSTNAME"); - return env ?? _agentHostname ?? string.Empty; - } - set { - _agentHostname = value; - } - } - - private int? _agentPort; - public int AgentPort { - get { - var env = Environment.GetEnvironmentVariable("MAKS-IT_AGENT_PORT"); - return env != null ? int.Parse(env) : _agentPort ?? 0; - } - set { - _agentPort = value; - } - - } - - private string? _agentKey; - public string AgentKey { - get { - var env = Environment.GetEnvironmentVariable("MAKS-IT_AGENT_KEY"); - return env ?? _agentKey ?? string.Empty; - } - set { - _agentKey = value; - } - } - - private string? _serviceToReload; - public string ServiceToReload { - get { - var env = Environment.GetEnvironmentVariable("MAKS-IT_AGENT_SERVICE"); - return env ?? _serviceToReload ?? string.Empty; - } - set { - _serviceToReload = value; - } - } + public required string AgentHostname { get; set; } + public required int AgentPort { get; set; } + public required string AgentKey { get; set; } + public required string ServiceToReload { get; set; } } - - - - - public class Configuration : ILetsEncryptConfiguration { + public required string Production { get; set; } + public required string Staging { get; set; } - private string? _production; - public string Production { - get { - var env = Environment.GetEnvironmentVariable("LETSENCRYPT_SERVER_PRODUCTION"); - return env ?? _production ?? string.Empty; - } - set { - _production = value; - } - - } - - private string? _staging; - public string Staging { - get { - var env = Environment.GetEnvironmentVariable("LETSENCRYPT_SERVER_STAGING"); - return env ?? _staging ?? string.Empty; - } - set { - _staging = value; - } - } + public required string CacheFolder { get; set; } + public required string AcmeFolder { get; set; } public required Agent Agent { get; set; } } diff --git a/src/LetsEncryptServer/Controllers/WellKnownController.cs b/src/LetsEncryptServer/Controllers/WellKnownController.cs index 407ffb7..dc33ea5 100644 --- a/src/LetsEncryptServer/Controllers/WellKnownController.cs +++ b/src/LetsEncryptServer/Controllers/WellKnownController.cs @@ -10,20 +10,13 @@ namespace MaksIT.LetsEncryptServer.Controllers; [Route(".well-known")] public class WellKnownController : ControllerBase { - private readonly Configuration _appSettings; private readonly ICertsRestChallengeService _certsFlowService; - private readonly string _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme"); - public WellKnownController( IOptions appSettings, ICertsFlowService certsFlowService ) { - _appSettings = appSettings.Value; _certsFlowService = certsFlowService; - - if (!Directory.Exists(_acmePath)) - Directory.CreateDirectory(_acmePath); } diff --git a/src/LetsEncryptServer/Program.cs b/src/LetsEncryptServer/Program.cs index 3453c8b..b1b43fc 100644 --- a/src/LetsEncryptServer/Program.cs +++ b/src/LetsEncryptServer/Program.cs @@ -10,6 +10,16 @@ var builder = WebApplication.CreateBuilder(args); // Extract configuration var configuration = builder.Configuration; +var configMapPath = Path.Combine(Path.DirectorySeparatorChar.ToString(), "configMap", "appsettings.json"); +if (File.Exists(configMapPath)) { + configuration.AddJsonFile(configMapPath, optional: false, reloadOnChange: true); +} + +var secretsPath = Path.Combine(Path.DirectorySeparatorChar.ToString(), "secrets", "appsecrets.json"); +if (File.Exists(secretsPath)) { + configuration.AddJsonFile(secretsPath, optional: false, reloadOnChange: true); +} + // Configure strongly typed settings objects var configurationSection = configuration.GetSection("Configuration"); var appSettings = configurationSection.Get() ?? throw new ArgumentNullException(); diff --git a/src/LetsEncryptServer/Services/CacheService.cs b/src/LetsEncryptServer/Services/CacheService.cs index 8b7733b..e478adb 100644 --- a/src/LetsEncryptServer/Services/CacheService.cs +++ b/src/LetsEncryptServer/Services/CacheService.cs @@ -4,6 +4,7 @@ using MaksIT.Core.Extensions; using MaksIT.LetsEncrypt.Entities; using MaksIT.Results; +using Microsoft.Extensions.Options; namespace MaksIT.LetsEncryptServer.Services; @@ -19,14 +20,13 @@ public class CacheService : ICacheService, IDisposable { private readonly string _cacheDirectory; private readonly LockManager _lockManager; - public CacheService(ILogger logger) { + public CacheService( + ILogger logger, + IOptions appsettings + ) { _logger = logger; - _cacheDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache"); + _cacheDirectory = appsettings.Value.CacheFolder; _lockManager = new LockManager(); - - if (!Directory.Exists(_cacheDirectory)) { - Directory.CreateDirectory(_cacheDirectory); - } } /// diff --git a/src/LetsEncryptServer/Services/CertsFlowService.cs b/src/LetsEncryptServer/Services/CertsFlowService.cs index 4fa27e7..344e4b8 100644 --- a/src/LetsEncryptServer/Services/CertsFlowService.cs +++ b/src/LetsEncryptServer/Services/CertsFlowService.cs @@ -64,9 +64,7 @@ public class CertsFlowService : ICertsFlowService { _letsEncryptService = letsEncryptService; _cacheService = cashService; _agentService = agentService; - _acmePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "acme"); - if (!Directory.Exists(_acmePath)) - Directory.CreateDirectory(_acmePath); + _acmePath = _appSettings.AcmeFolder; } #region Common methods diff --git a/src/LetsEncryptServer/appsettings.json b/src/LetsEncryptServer/appsettings.json index 09dcd73..7ffa251 100644 --- a/src/LetsEncryptServer/appsettings.json +++ b/src/LetsEncryptServer/appsettings.json @@ -11,12 +11,15 @@ "Production": "https://acme-v02.api.letsencrypt.org/directory", "Staging": "https://acme-staging-v02.api.letsencrypt.org/directory", - "Agent": { - "AgentHostname": "http://websrv0001.corp.maks-it.com", - "AgentPort": 9000, - "AgentKey": "UGnCaElLLJClHgUeet/yr7vNvPf13b1WkDJQMfsiP6I=", + "CacheFolder": "/cache", + "AcmeFolder": "/acme", - "ServiceToReload": "haproxy" + "Agent": { + "AgentHostname": "", + "AgentPort": 9000, + "AgentKey": "", + + "ServiceToReload": "haproxy" } } } diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 3832182..dbc4732 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -22,15 +22,11 @@ services: environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_HTTP_PORTS=5000 - - LETSENCRYPT_SERVER_PRODUCTION=https://acme-v02.api.letsencrypt.org/directory - - LETSENCRYPT_SERVER_STAGING=https://acme-staging-v02.api.letsencrypt.org/directory - - MAKS_IT_AGENT_HOSTNAME=http://websrv0001.corp.maks-it.com - - MAKS-IT_AGENT_PORT=5000 - - MAKS-IT_AGENT_KEY=UGnCaElLLJClHgUeet/yr7vNvPf13b1WkDJQMfsiP6I= - - MAKS-IT_AGENT_SERVICE=haproxy volumes: - - D:\Compose\MaksIT.CertsUI\acme:/app/bin/Debug/net8.0/acme - - D:\Compose\MaksIT.CertsUI\cache:/app/bin/Debug/net8.0/cache + - D:\Compose\MaksIT.CertsUI\acme:/acme + - D:\Compose\MaksIT.CertsUI\cache:/cache + - D:\Compose\MaksIT.CertsUI\configMap\appsettings.json:/configMap/appsettings.json:ro + - D:\Compose\MaksIT.CertsUI\secrets\appsecrets.json:/secrets/appsecrets.json:ro networks: - maks-it diff --git a/src/helm/templates/NOTES.txt b/src/helm/templates/NOTES.txt new file mode 100644 index 0000000..739f4e2 --- /dev/null +++ b/src/helm/templates/NOTES.txt @@ -0,0 +1,39 @@ +Thank you for installing **{{ .Chart.Name }}**! + +This chart deploys the MaksIT CertsUI tool for automated Let's Encrypt HTTPS certificate renewal. + +------------------------------------------------------------ +## Components + +- **Server**: Handles certificate requests and renewal logic. +- **Client**: Web UI for managing and viewing certificate status. +- **Reverse Proxy**: Exposes the UI and API endpoints. + +------------------------------------------------------------ +## Configuration + +- **Secrets**: + The server uses a Kubernetes Secret (`appsecrets.json`) for sensitive data. + +- **ConfigMap**: + The server uses a ConfigMap (`appsettings.json`) for application settings. + +- **Persistence**: + PVCs are created for `/acme` and `/cache` directories. + +------------------------------------------------------------ +## Uninstall + +To remove all resources created by this chart: +``` +helm uninstall {{ .Release.Name }} +``` + +------------------------------------------------------------ +## Notes + +- Certificates are renewed automatically using Let's Encrypt. +- You can customize settings in `values.yaml` before installation. +- For advanced configuration, see the chart documentation and templates. + +------------------------------------------------------------ \ No newline at end of file diff --git a/src/helm/templates/_helpers.tpl b/src/helm/templates/_helpers.tpl index 9a5449a..3e0f862 100644 --- a/src/helm/templates/_helpers.tpl +++ b/src/helm/templates/_helpers.tpl @@ -3,13 +3,17 @@ {{- end }} {{- define "certs-ui.fullname" -}} -{{- $name := .Chart.Name -}} -{{- $rel := .Release.Name -}} -{{- if or (hasPrefix (printf "%s-" $name) $rel) (eq $rel $name) -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- $rel := .Release.Name -}} +{{- if contains $name $rel -}} {{- $rel | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" $rel $name | trunc 63 | trimSuffix "-" -}} {{- end -}} +{{- end -}} {{- end }} {{- define "certs-ui.chart" -}} diff --git a/src/helm/templates/configmap-appsettings.yaml b/src/helm/templates/configmap-appsettings.yaml new file mode 100644 index 0000000..59ecf8c --- /dev/null +++ b/src/helm/templates/configmap-appsettings.yaml @@ -0,0 +1,27 @@ +{{- $root := . -}} +{{- range $compName, $comp := .Values.components }} +{{- if $comp.configMapFile }} +{{- $cf := $comp.configMapFile -}} +{{- $cmName := printf "%s-%s-configmap" (include "certs-ui.fullname" $root) $compName -}} +{{- $existing := lookup "v1" "ConfigMap" $root.Release.Namespace $cmName -}} +{{- if and $cf.keep $existing }} +{{/* keep=true and ConfigMap exists -> render nothing */}} +{{- else }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ $cmName }} + labels: + {{- include "certs-ui.labels" $root | nindent 4 }} + app.kubernetes.io/component: {{ $compName }} + {{- if $cf.keep }} + annotations: + "helm.sh/resource-policy": keep + {{- end }} +data: + {{ $cf.key }}: | +{{ $cf.content | indent 4 }} +{{- end }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/src/helm/templates/deployments.yaml b/src/helm/templates/deployments.yaml index 5a74f36..3ceee19 100644 --- a/src/helm/templates/deployments.yaml +++ b/src/helm/templates/deployments.yaml @@ -1,3 +1,5 @@ +{{- $roll := ((.Values.rollme | default (now | unixEpoch)) | toString) -}} + {{- $root := . -}} {{- range $compName, $comp := .Values.components }} --- @@ -22,7 +24,7 @@ spec: app.kubernetes.io/component: {{ $compName }} {{- if and $comp.secretsFile $comp.secretsFile.forceUpdate }} annotations: - "checksum/secrets-file": {{ (default "" $comp.secretsFile.content) | toString | sha256sum | quote }} + rollme: "{{$roll}}" {{- end }} spec: {{- include "certs-ui.imagePullSecrets" $root | nindent 6 }} @@ -37,6 +39,8 @@ spec: containerPort: {{ $tgt }} {{- if $comp.env }} env: + - name: ROLLOUT_TOKEN + value: "{{$roll}}" {{- range $comp.env }} - name: {{ .name }} value: {{ .value | quote }} @@ -57,6 +61,14 @@ spec: mountPath: {{ $comp.secretsFile.mountPath }} subPath: {{ base $comp.secretsFile.mountPath }} {{- end }} + {{- if $comp.configMapFile }} + - name: {{ $compName }}-configmap + configMap: + name: {{ include "certs-ui.fullname" $root }}-{{ $compName }}-configmap + items: + - key: {{ $comp.configMapFile.key }} + path: {{ base $comp.configMapFile.mountPath }} + {{- end }} {{- end }} {{- if or $hasVols $hasSecret }} volumes: @@ -79,5 +91,13 @@ spec: - key: {{ $comp.secretsFile.key }} path: {{ base $comp.secretsFile.mountPath }} {{- end }} + {{- if $comp.configMapFile }} + - name: {{ $compName }}-configmap + configMap: + name: {{ include "certs-ui.fullname" $root }}-{{ $compName }}-configmap + items: + - key: {{ $comp.configMapFile.key }} + path: {{ base $comp.configMapFile.mountPath }} + {{- end }} {{- end }} {{- end }} diff --git a/src/helm/values.yaml b/src/helm/values.yaml index 89a4251..0c08a51 100644 --- a/src/helm/values.yaml +++ b/src/helm/values.yaml @@ -49,6 +49,34 @@ components: keep: true forceUpdate: false + configMapFile: + key: appsettings.json + mountPath: /configMap/appsettings.json + content: | + { + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Configuration": { + "Production": "https://acme-v02.api.letsencrypt.org/directory", + "Staging": "https://acme-staging-v02.api.letsencrypt.org/directory", + + "CacheFolder": "/cache", + "AcmeFolder": "/acme", + + "Agent": { + "AgentHostname": "http://websrv0001.corp.maks-it.com", + "AgentPort": 9000, + "ServiceToReload": "haproxy" + } + } + } + keep: true + forceUpdate: false + client: replicas: 1 image: