mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2026-05-16 04:48:12 +02:00
25 KiB
25 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[3.4.1] - 2026-04-30
Breaking
- Engine query ports (Vault-style):
IUserQueryService,IApiKeyQueryService, andIApiKeyEntityScopeQueryServiceno longer expose async pagedSearch…Asyncwith string filters. They now use synchronousSearch/Countwith optionalExpression<Func<TDto, bool>>?predicates (Linq2Db-translatable),skip/limit, andResulttypes—matching the thin-search wiring inIdentityService/ApiKeyService. Custom Engine hosts must update call sites and registrations. - ACME session persistence:
IAcmeSessionStore,AcmePostgresSessionStore,AcmeSessionSnapshot, andAcmeSessionJsonSerializerare removed.ILetsEncryptServicenow depends onIAcmeSessionPersistenceService(AcmeSessionPersistenceServiceLinq2Db) foracme_sessionsJSON load/save. ICertsFlowDomainService: Constructor takesIRegistrationCacheDomainServiceinstead ofIRegistrationCachePersistenceService(registration cache orchestration moved behindRegistrationCacheDomainService).
Added
ExpressionCompose(QueryServices/ExpressionCompose.cs) for composing nested Linq2Db predicates (Vault parity).IRegistrationCacheDomainService/RegistrationCacheDomainService,RegistrationCachePayloadDocument, andRegistrationCachePayloadJsonTests(Engine unit tests) for registration-cache JSON handling;RegistrationCacheDtonow extendsDtoDocumentBase<Guid>withAccountIdas an alias ofId; persistence and mapping updates inRegistrationCachePersistenceServiceLinq2Db/CertsLinq2DbMapping.IAcmeSessionPersistenceService,AcmeSessionPersistenceServiceLinq2Db, andAcmeSessionPayloadMapperfor PostgreSQL-backed ACMEStatepersistence.ApiKeyEntityScopeDtoandApiKeyEntityScopeQueryServiceStubadjustments for entity-scope search parity.- Docs:
assets/docs/ARCHITECTURE_LAYERING.md(layering, spine flows, Pattern A/B); CONTRIBUTING.md links to it and documentsdotnet testforMaksIT.CertsUI.Engine.Tests/MaksIT.CertsUI.Tests.
Changed
LetsEncryptService: UsesIAcmeSessionPersistenceService; helper updates inLetsEncryptService.Helpers.cs.CertsFlowDomainService:PurgeStaleHttpChallengesAsync(HTTP-01 cleanup);AutoRenewalcalls it before renewal work.CacheService: Thin façade overIRegistrationCacheDomainService(host API unchanged for callers).IdentityService/ApiKeyService: Build predicates and callCount+SearchonIUserQueryService/IApiKeyQueryService/IApiKeyEntityScopeQueryService.- Engine: Dropped
Newtonsoft.Jsonpackage reference fromMaksIT.CertsUI.Engine(STJ-only JSON paths). - Web UI:
axiosConfiggetData/postData(and related helpers) return{ payload, status, ok }so callers can distinguish HTTP status; forms and slices updated (SearchUser,SearchApiKey,Utilities,EditUser,Home,FileUploadComponent,identitySlice, etc.). - Integration tests:
InMemoryUserStore,CacheServiceTests,CertsFlowServiceTests,ApiKeyQueryServiceIntegrationTests,AccountServicePatchAccountIntegrationTestsaligned with the new ports.
[3.4.0] - 2026-04-27
Breaking
- HA / interactive ACME:
CertsFlowDomainServiceno longer checksIPrimaryReplicaWorkload.IsPrimary. All replicas may run configure-client, init, orders, challenge completion, certificate download, apply, and revoke. The API no longer returns HTTP 503 withProblemDetails.typeurn:maksit:certs-ui:primary-replica-requiredfor those flows. Clients that retried on that signal (for example the SPA) should treat normal error semantics only. - HTTP-01 challenge:
AcmeChallengeAsyncno longer writes tokens underAcmeFolderor reads a legacy on-disk file. Challenge text is served from PostgreSQL only; ingress must reachGET /.well-known/acme-challenge/{token}on this app (or equivalent) rather than a shared volume of token files. - Startup: Removed the shared
initmarker file underDataFolder. Followers wait until the database reports at least one user (same readiness signal, without filesystem coupling). - HA / process model: Removed
IPrimaryReplicaWorkload,PrimaryReplicaGate, andPrimaryReplicaShutdownHostedService. There is no long-lived “primary replica” or lease renewal loop in the API process.
Changed
- ACME sessions: Let's Encrypt client
Stateis persisted in PostgreSQL tableacme_sessions(session_id, payload JSON, timestamps) so any replica can continue the same ACME session after load balancing. - LetsEncrypt /
HttpClient:ConfigureClientfetches the ACME directory using an absolute URL derived from staging/production configuration instead of assigningBaseAddresson the sharedHttpClient. InitializationHostedService: Dropped unusedIOptions<Configuration>from the constructor (DI callers unchanged except the removed parameter).- Bootstrap:
InitializationHostedServiceacquirescerts-ui-bootstrap(RuntimeLeaseNames.BootstrapCoordinator), runsCoordinationTableProvisioner+ optional default admin, releases the lease, and exits. Other pods wait untilusersexist. - Renewal:
AutoRenewalacquirescerts-ui-renewal-sweep(RuntimeLeaseNames.RenewalSweep) for each sweep, runs work, releases, then sleeps. Any pod may win the next sweep. - Lease names: Replaced
certs-ui-primarywithBootstrapCoordinatorandRenewalSweepconstants (seeRuntimeLeaseNames). - Helm (cloud-native defaults):
components.server.service.sessionAffinity.enableddefaults tofalseso the serverServiceuses stateless load balancing (noClientIPstickiness). Enable explicitly only when needed. - Helm:
certsClientRuntime.apiUrldefault is/apiso the Web UI calls the API on the same browser origin (typical single-ingress / reverse-proxy setup). Override with a full URL when UI and API are on different hosts.
Removed
CertsFlowPrimaryReplica,PrimaryReplicaRequiredObjectResult, andCertsFlowResultExtensions/ToCertsFlowActionResult;CertsFlowControllerusesToActionResult()like other API controllers.- Web UI: Primary-replica 503 auto-retry logic in
axiosConfig.ts. - Configuration / Helm:
AcmeFolderandDataFoldersettings and the default server acme/data PVC mounts (cloud-native: no app-local disk for ACME or bootstrap markers).AddMemoryCache()host registration removed (unused).
Upgrade notes
- Migrations: Apply FluentMigrator through
3.4.0(includesacme_sessionsand related coordination entries) before relying on cross-replica ACME sessions. - Compose / secrets: Remove
acmeanddatabind mounts fromdocker-compose.override.ymlif you still have them; they are no longer read by the application. - Operations: If you alert or filter on lease name
certs-ui-primary, retarget tocerts-ui-bootstrapandcerts-ui-renewal-sweep.
[3.3.22] - 2026-04-27
Changed
- Release tooling / frontend image versioning:
DockerPushnow supports per-imageversionEnvFilesand temporarily rewritesVITE_APP_VERSIONinsrc/MaksIT.WebUI/.envandsrc/MaksIT.WebUI/.env.prodto the release semver from<Version>(for example3.3.22) during docker build/push, then restores original files so placeholders remain unchanged in git. - HA / Terms of Service PDF cache: Replaced pod-filesystem Terms of Service PDF caching with shared PostgreSQL cache table
terms_of_service_cache(url,url_hash_hex,etag,last_modified_utc,content_type,content_bytes,fetched_at_utc,expires_at_utc). ToS retrieval now uses cache TTL/HTTP validators and no longer relies on local files. - Terms of Service API: Uses only session-based endpoint
GET /api/certs/{sessionId}/terms-of-servicefor interactive ACME flows (statelessisStagingvariant removed).
[3.3.21] - 2026-04-26
Changed
- FluentMigrator: Restored FluentMigrator defaults for the migration history table (
VersionInfo, columnsVersion,AppliedOn,Description). RemovedCertsFluentMigratorVersionTableMetaData, customIVersionTableMetaDataAccessorregistration, and post-migrate verification againstversion_info.
[3.3.20] - 2026-04-26
Fixed
- FluentMigrator DI: Attempted to force
version_infoby replacingIVersionTableMetaDataAccessorregistrations. Superseded in 3.3.21: reverted to defaultVersionInfotable and standard column names (see 3.3.21).
[3.3.19] - 2026-04-26
Removed
- Startup migrations: Removed legacy compatibility paths (EF-era baseline that seeded the version table when
usersalready existed,VersionInfo→version_inforename, PascalCase → snake_case column repair, andRunMigrationsService.BaselineVersion).
Upgrade notes
- Breaking: Recreate the Certs engine database (or use a new empty database) if you still relied on those removed startup paths;
MigrateUpexpects a schema managed only by FluentMigrator migrations in-process.
[3.3.18] - 2026-04-26
Changed
- Docker Compose:
docker-compose.override.ymlsets the sameReverseProxy__Clusters__*__Destinations__d1__Addressenvironment variables as Kubernetes (http://server:5000//http://client:5173/on the Compose network), so YARP behavior does not depend only on baked-inappsettings.json. - Helm: Optional
components.reverseproxy.kubernetesUpstreamHosts(defaulttrue) toggles injection of in-cluster upstream URLs; setfalseonly for custom Service naming. Single-replica and HA clusters use the same DNS pattern.
Fixed
- Helm / reverseproxy: YARP upstreams defaulted to Compose hostnames
server/client, which do not resolve in Kubernetes. The chart setsReverseProxy__Clusters__*__Destinations__d1__Addresstohttp://<release-fullname>-server:<port>/andhttp://<release-fullname>-client:<port>/whenkubernetesUpstreamHostsis enabled (ports fromcomponents.server.service.portandcomponents.client.service.port).
[3.3.17] - 2026-04-26
Changed
- HA / API: Non-primary replicas return
Result.ServiceUnavailablewith stable markerurn:maksit:certs-ui:primary-replica-requiredfor ACME orchestration; the host maps that to HTTP 503,Retry-After, and RFC 7807ProblemDetails(replacing ad-hoc 429-style overload semantics for this case). - Helm: Default
components.server.service.sessionAffinity(ClientIP, configurable timeout),terminationGracePeriodSeconds, and a shortpreStopsleep so rolling updates drain connections before the primary lease TTL window. Disable or tune undercomponents.serverif your ingress already pins API traffic.
[3.3.16] - 2026-04-26
Changed
- HA / primary replica: A single elected instance holds Postgres lease
certs-ui-primary(RuntimeLeaseNames.PrimaryReplica), renews it periodically, and is the only instance withIPrimaryReplicaWorkload.IsPrimaryafter startup. It runs coordination DDL, identity bootstrap, all ACME domain flows (CertsFlowDomainService), andAutoRenewal. Other replicas serve HTTP (identity, health, etc.) andAcmeChallengeAsync(HTTP-01 token materialization for ingress). Followers reject ACME orchestration at the domain layer until they become primary after failover. - Startup: Removed separate
certs-ui-bootstraplease; primary lease serializes first-time admin creation.PrimaryReplicaShutdownHostedService(registered last) releases the primary lease on shutdown.
[3.3.15] - 2026-04-26
Fixed
- Startup / HA:
InitializationHostedServiceno longer takes the bootstrap lease when PostgreSQL already has users. Only the empty-database path waits on the lease (single-writer default admin). Extra replicas used to block on the lease until Kubernetes canceledStartAsync, surfacing asTaskCanceledExceptionat startup while the first replica held the lease. - Startup: Retry backoff treats
OperationCanceledExceptionwhen the host is stopping as shutdown (no misleading “initialization failed” loop); cooperative cancel still ends startup.
[3.3.14] - 2026-04-26
Fixed
- Identity / PostgreSQL: Removed redundant
users.JwtTokensJson(historical JSON blob of sessions on the user row). Server-side session allowlist semantics are unchanged: issued sessions remain rows injwt_tokensand are validated the same way as in Vault’s persistedJwtTokenmodel—only the duplicate JSON encoding was dropped. Newusersinserts no longer hit23502on that column. FluentMigratorDropUsersJwtTokensJson(20260426140000) drops the column when present; the baseline no longer creates it;JwtTokensTableMigrateFromJsoncopies from JSON only if that column still exists (upgrades from older DBs).
Changed
- FluentMigrator:
RestoreUsersJwtTokensJsonIfDropped(20260426120000) is now a no-op (revision kept for databases that already applied it). Session material is stored only injwt_tokens, not duplicated as JSON onusers.
[3.3.13] - 2026-04-26
Fixed
- HA lease /
42P01: AddedCoordinationTableProvisionerwith explicitpublic.*DDL;InitializationHostedServicecalls it immediately before bootstrap lease acquire (idempotent, same as post-migrate repair).RuntimeLeaseServiceNpgsqlnow usespublic.app_runtime_leasesin SQL so a non-defaultsearch_pathcannot miss the table. Post-migrate verification requirespublic.app_runtime_leasesplususersor"VersionInfo".
Upgrade notes (Kubernetes / Helm)
- Pin container tags to the app semver (e.g.
3.3.13for server, client, reverseproxy) viaglobal.image.tagand/orcomponents.*.image.tag. The chart resolves the effective tag withglobal.image.tagwhen set (seesrc/helm/templates/_helpers.tpl). - Do not rely on
latest+imagePullPolicy: IfNotPresentalone — nodes keep the first pulled digest, so you can run an old server binary while the OCI chart is already3.3.13. Use an explicit semver tag and/orpullPolicy: Always(or bumpglobal.rolloutNonce/global.rollmeper chart NOTES) when upgrading. - Push all three images for the tag you pin (
certs-ui/server,certs-ui/client,certs-ui/reverseproxy) so every deployment can pull successfully.
[3.3.12] - 2026-04-26
Fixed
- FluentMigrator: Use
.ScanIn(…).For.All()instead of.For.Migrations()so in-process discovery matches FluentMigrator guidance (avoids “no migrations” / incomplete runner behavior in some versions). - FluentMigrator: Throw if the engine connection string is empty when registering the runner — a null/empty
WithGlobalConnectionStringputs the processor in connectionless/preview mode (SQL logged, nothing committed), which matches reports of empty databases with no errors. - Migrations: Log host/database (no password) and count of
[Migration]types beforeMigrateUp; after coordination DDL, verifypublic.usersorpublic."VersionInfo"exists or fail with an actionable error (wrongDatabase=, permissions, or preview mode). - Database bootstrap: If the role cannot open a maintenance connection to database
postgres(common for locked-down app users), log a warning and skip automaticCREATE DATABASEinstead of failing the whole migration step.
[3.3.11] - 2026-04-26
Added
- Database: FluentMigrator
RestoreUsersJwtTokensJsonIfDropped(20260426120000) initially re-addedusers.JwtTokensJsonwithADD COLUMN IF NOT EXISTSfor databases that had dropped it under an olderJwtTokensTableMigrateFromJsonrevision. Superseded in 3.3.14: that revision is a no-op andDropUsersJwtTokensJsondrops the column; tokens stay injwt_tokensonly. - Helm / config:
certsServerConfig.configuration.certsEngineConfiguration.autoSyncSchema(defaulttrue) is rendered into serverappsettings.jsonso add-only schema sync runs on every startup unless explicitly disabled.
Changed
- Startup schema policy: Documented expand-only expectations — FluentMigrator
Up()should add tables/columns; avoid dropping renamed columns in routineUp()without an explicit follow-up plan.JwtTokensTableMigrateFromJsonno longer dropsJwtTokensJsonin that revision’sUp()(tokens are normalized intojwt_tokens). 3.3.14 removesJwtTokensJsonfrom the live schema viaDropUsersJwtTokensJson. - Schema sync:
AutoSyncSchemadefaults to true in repoappsettings.json;SchemaSyncServicedesired map includesusers.IsActiveandTwoFactorSharedKey. 3.3.14 stops treatingJwtTokensJsonas a desired column. Still ADD COLUMN IF NOT EXISTS only (no DROP in sync). - ICertsEngineConfiguration / ISchemaSyncService: Clarified that add-only sync is recommended and describes the no-DROP guarantee.
[3.3.10] - 2026-04-26
Fixed
- Database: After FluentMigrator
MigrateUp,RunMigrationsServiceapplies idempotentCREATE TABLE IF NOT EXISTS/CREATE INDEX IF NOT EXISTSforacme_http_challengesandapp_runtime_leases. IfVersionInfoalready records the migration but tables are missing (restore drift, partial apply, manual DB edits), FluentMigrator would skipUp()and the bootstrap lease would fail with42P01; this repair aligns schema with runtime needs.
[3.3.9] - 2026-04-26
Fixed
- Startup / database: FluentMigrator (
EnsureCertsEngineMigratedAsync) now runs inProgram.csimmediately afterWebApplication.Build()and beforeRunAsync, so schema (includingapp_runtime_leases) exists before anyIHostedServicestarts.InitializationHostedServiceonly performs bootstrap lease + identity init.
[3.3.8] - 2026-04-26
Fixed
- Startup / database:
InitializationHostedServicenow runs FluentMigrator (EnsureCertsEngineMigratedAsync) before acquiring the bootstrap PostgreSQL lease, soapp_runtime_leasesexists on an empty database (same ordering idea as Vault: migrate first, then coordination). - Startup: While waiting for the bootstrap lease, migrations are not re-run on every poll interval (
migrationsAppliedguard).
Changed
- Container image:
MaksIT.CertsUIDockerfile installslibgssapi-krb5-2so Npgsql can load GSS/Kerberos support without missing-library warnings on slimaspnetimages.
[3.3.7] - 2026-04-25
Added
- HA runtime coordination: Added DB-backed HTTP-01 challenge persistence and runtime lease infrastructure (
acme_http_challenges,app_runtime_leases) plus coordinated startup/renewal execution. - Kubernetes readiness model: Added per-component Helm
replicaCount+ PodDisruptionBudget support and health endpoints (/health/live,/health/ready) for probes. - New backend host: Added
MaksIT.CertsUIWebAPI host with controllers, authorization filters (JWT and JWT-or-API-key), hosted services, and mapping/configuration abstractions. - Engine platform expansion: Added a domain-oriented
MaksIT.CertsUI.Enginestructure (Domain,Dto,DomainServices,Persistence,QueryServices,Infrastructure,FluentMigrations) with linq2db mappings and migration services. - Frontend identity/api-key UX: Added Users/API Keys pages and forms (
CreateUser,EditUser,SearchUser,CreateApiKey) with reusable list/filter/paging components. - Test suite: Added
MaksIT.CertsUI.Testswith service and integration coverage plus shared Postgres/WebAPI fixtures.
Changed
- Namespace and solution layout: Standardized around
MaksIT.CertsUI*and moved responsibilities into clearer host/engine layers. - Engine model organization: Reorganized ACME and related contracts from legacy top-level
Entities/ModelsintoDomainandDto. - Helm/runtime behavior: Updated deployment templates to support
env.valueFrom, pod-name-based holder identity, and probe wiring for live/ready endpoints. - Documentation: Updated README architecture references and linked HA architecture guidance.
- WebUI contracts: Aligned identity/API-key request/response and paged-search models with updated backend endpoints.
Removed
- Deprecated host: Removed legacy
MaksIT.Webapiproject and its old controllers/services/background services. - Legacy engine layout: Removed obsolete top-level engine files (
Entities,Models, previous ACME helper locations, old project.vscodefiles). - Old test project: Removed
MaksIT.Webapi.Testsin favor ofMaksIT.CertsUI.Tests.
[3.3.6] - 2026-04-13
Added
- LetsEncrypt: Per-host ACME rate-limit cooldown on
RegistrationCache(AcmeRenewalNotBeforeUtcByHostname), with HTTPRetry-Afterand problem-detail parsing (AcmeRetryAfterParser), structured logging, andResult.TooManyRequestswhen the CA returnsrateLimited. - LetsEncrypt:
AcmeProblemKindas anEnumeration(RFC 8555 problemtypeURIs) instead of ad hoc strings;LetsEncrytExceptionexposesProblemKind,RetryAfterUtc, and optional rate-limit hostname. - LetsEncrypt:
AcmeSessionStorefor per-sessionStatein memory;LetsEncryptServicesplit into partial files (LetsEncryptService.Helpers.cs) for HTTP/JWS/error helpers. - LetsEncrypt:
State.TryGetAccountKeyfor a single place to validate account key material afterInit. - LetsEncrypt.Tests: Unit tests for retry parsing, problem-kind resolution, and cooldown JSON round-trip.
Changed
- AutoRenewal: Skips hostnames that are still in an ACME cooldown window (with debug logs for skipped hosts).
- Certs flow: Persists registration cache after failed full certificate flows when a session exists so cooldown metadata is saved.
- LetsEncrypt: Broader nullable reference annotations on ACME DTOs (
Problem,AcmeDirectory,AuthorizationChallengeError, etc.) and explicit null guards inLetsEncryptService.
Fixed
- LetsEncrypt: Certificate PEM loading uses
X509Certificate2.CreateFromPeminstead of the obsoleteX509Certificate2(byte[])constructor (SYSLIB0057). - LetsEncrypt:
RevokeCertificatenow fails correctly on non-success responses (missingreturn), uses the same problem-document handling as other ACME calls, and disposes the HTTP response on successful revoke. - LetsEncrypt:
NewOrderauthorization error log line now logs the authorization status, not the order status.
[3.3.5] - 2026-04-12
Changed
CachedHostnamenow uses a C# 12 primary constructor (same public construction as before).
Fixed
RegistrationCacheloads cached PEM certificates viaX509CertificateLoader.LoadCertificateand disposes them withusingwhere certificates are parsed for expiry and host listing.RegistrationCache.TryGetCachedCertificatereturnsfalsewhen the cached entry has no private key blob, avoiding a null argument when importing key material.
[3.3.4] - 2026-04-01
Added
MaksIT.Webapi.Tests: service-level unit tests (settings, cache, identity, agent, account, certs flow) and domain tests forSettings.- Postman collections under
src/Postmanupdated to match currentMaksIT.Webapiroutes, JWT flow, and cache endpoints.
Fixed
- WebUI Terms of Service (Let's Encrypt): PDF viewer loads
pdfjs-distworker from a Vite-bundled asset (pdf.worker.min.mjs?url) so rendering works in dev and production instead of failing on missing or wrong worker URLs. AccountService.PatchAccountAsyncreturns the account built from the cache after reload, not a stale in-memory instance.
[3.3.3] - 2025-12-20
Changed
- Relicensed project from GPL-3.0 to Apache-2.0.
[3.3.2] - 2025-12-20
Changed
- Minimal Helm chart and documentation improvements.
[3.3.1] - 2025-11-22
Changed
- Public release following the v3.3.0 pre-release.
[3.3.0] - 2025-11-15
Changed
- Pre-release of the v3.3.x line.
[3.2.0] - 2025-09-11
Added
- New WebUI with authentication.
[3.1.0] - 2024-08-11
Changed
- Stabilized release following v3.0.0.
[3.0.0] - 2024-05-31
Added
- WebAPI and containerization.
[2.0.0] - 2019-11-01
Changed
- Dependency injection pattern implementation.
[1.0.0] - 2019-06-29
Added
- Initial release.