work with authentik

This commit is contained in:
Matt Batchelder
2026-02-27 17:48:21 -05:00
parent 90eb649940
commit 2aaa0442b2
13 changed files with 699 additions and 2 deletions

View File

@@ -2,6 +2,7 @@ using System.Security.Cryptography;
using System.Text.RegularExpressions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OTSSignsOrchestrator.Core.Configuration;
namespace OTSSignsOrchestrator.Core.Services;
@@ -106,6 +107,9 @@ public class PostInstanceInitService
_logger.LogInformation("[PostInit] Setting Xibo theme to 'otssigns'");
await xibo.SetThemeAsync(instanceUrl, accessToken, "otssigns");
// ── 6a. Deploy SAML configuration ─────────────────────────────────
await DeploySamlConfigurationAsync(abbrev, instanceUrl, settings, ct);
// ── 7. Store credentials in Bitwarden ─────────────────────────────
_logger.LogInformation("[PostInit] Storing credentials in Bitwarden");
@@ -196,6 +200,9 @@ public class PostInstanceInitService
_logger.LogInformation("[PostInit] Setting theme to 'otssigns'");
await xibo.SetThemeAsync(instanceUrl, accessToken, "otssigns");
// ── 5a. Deploy SAML configuration ─────────────────────────────────
await DeploySamlConfigurationAsync(abbrev, instanceUrl, settings, ct);
// ── 6. Store admin password in Bitwarden ──────────────────────────
_logger.LogInformation("[PostInit] Storing credentials in Bitwarden");
var adminSecretId = await bws.CreateInstanceSecretAsync(
@@ -335,6 +342,90 @@ public class PostInstanceInitService
};
}
// ─────────────────────────────────────────────────────────────────────────
// SAML configuration deployment
// ─────────────────────────────────────────────────────────────────────────
/// <summary>
/// Provisions a SAML application in Authentik, renders the settings-custom.php template,
/// and writes the rendered file to the instance's NFS-backed cms-custom volume.
/// Errors are logged but do not fail the overall post-init process.
/// </summary>
private async Task DeploySamlConfigurationAsync(
string abbrev,
string instanceUrl,
SettingsService settings,
CancellationToken ct)
{
try
{
_logger.LogInformation("[PostInit] Deploying SAML settings-custom.php for {Abbrev}", abbrev);
using var scope = _services.CreateScope();
var authentik = scope.ServiceProvider.GetRequiredService<IAuthentikService>();
var git = scope.ServiceProvider.GetRequiredService<GitTemplateService>();
var docker = scope.ServiceProvider.GetRequiredService<IDockerCliService>();
// ── 1. Fetch template from git repo ───────────────────────────────
var repoUrl = await settings.GetAsync(SettingsService.GitRepoUrl);
var repoPat = await settings.GetAsync(SettingsService.GitRepoPat);
if (string.IsNullOrWhiteSpace(repoUrl))
throw new InvalidOperationException("Git repository URL is not configured.");
var templateConfig = await git.FetchAsync(repoUrl, repoPat);
var templatePath = Path.Combine(templateConfig.CacheDir, "settings-custom.php.template");
if (!File.Exists(templatePath))
{
_logger.LogWarning(
"[PostInit] settings-custom.php.template not found in git repo — skipping SAML deployment");
return;
}
var templateContent = await File.ReadAllTextAsync(templatePath, ct);
// ── 2. Provision Authentik SAML application ───────────────────────
var samlBaseUrl = instanceUrl.TrimEnd('/') + "/saml";
var samlConfig = await authentik.ProvisionSamlAsync(abbrev, instanceUrl, ct);
// ── 3. Render template ────────────────────────────────────────────
var rendered = templateContent
.Replace("{{SAML_BASE_URL}}", samlBaseUrl)
.Replace("{{SAML_SP_ENTITY_ID}}", $"{samlBaseUrl}/metadata")
.Replace("{{AUTHENTIK_IDP_ENTITY_ID}}", samlConfig.IdpEntityId)
.Replace("{{AUTHENTIK_SSO_URL}}", samlConfig.SsoUrlRedirect)
.Replace("{{AUTHENTIK_SLO_URL}}", samlConfig.SloUrlRedirect)
.Replace("{{AUTHENTIK_IDP_X509_CERT}}", samlConfig.IdpX509Cert);
// ── 4. Write rendered file to NFS volume ──────────────────────────
var nfsServer = await settings.GetAsync(SettingsService.NfsServer);
var nfsExport = await settings.GetAsync(SettingsService.NfsExport);
var nfsExportFolder = await settings.GetAsync(SettingsService.NfsExportFolder);
if (string.IsNullOrWhiteSpace(nfsServer) || string.IsNullOrWhiteSpace(nfsExport))
throw new InvalidOperationException("NFS settings are not configured — cannot write SAML config to volume.");
// Path within the NFS export: {abbrev}/cms-custom/settings-custom.php
var nfsRelativePath = $"{abbrev}/cms-custom/settings-custom.php";
var (success, error) = await docker.WriteFileToNfsAsync(
nfsServer, nfsExport, nfsRelativePath, rendered, nfsExportFolder);
if (!success)
throw new InvalidOperationException($"Failed to write settings-custom.php to NFS: {error}");
_logger.LogInformation(
"[PostInit] SAML configuration deployed for {Abbrev} (Authentik provider={ProviderId})",
abbrev, samlConfig.ProviderId);
}
catch (Exception ex)
{
_logger.LogError(ex, "[PostInit] SAML deployment failed for {Abbrev}: {Message}. " +
"Instance will continue without SAML — configure manually if needed.", abbrev, ex.Message);
// Don't rethrow — SAML failure should not block the rest of post-init
}
}
// ─────────────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────────────