Files
git.stella-ops.org/docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md
master 5ce40d2eeb feat: Initialize Zastava Webhook service with TLS and Authority authentication
- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint.
- Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately.
- Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly.
- Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
2025-10-19 18:36:22 +03:00

16 KiB
Raw Blame History

Authority Plug-in Developer Guide

Status: Updated 2025-10-11 (AUTHPLUG-DOCS-01-001) with lifecycle + limiter diagrams and refreshed rate-limit guidance aligned to PLG6 acceptance criteria.

1. Overview

Authority plug-ins extend the StellaOps Authority service with custom identity providers, credential stores, and client-management logic. Unlike Concelier plug-ins (which ingest or export advisories), Authority plug-ins participate directly in authentication flows:

  • Use cases: integrate corporate directories (LDAP/AD)1 , delegate to external IDPs, enforce bespoke password/lockout policies, or add client provisioning automation.
  • Constraints: plug-ins load only during service start (no hot-reload), must function without outbound internet access, and must emit deterministic results for identical configuration input.
  • Ship targets: build against the hosts .NET 10 preview SDK, honour offline-first requirements, and surface actionable diagnostics so operators can triage issues from /ready.

2. Architecture Snapshot

Authority hosts follow a deterministic plug-in lifecycle. The exported diagram (docs/assets/authority/authority-plugin-lifecycle.svg) mirrors the steps below; regenerate it from the Mermaid source if you update the flow.

  1. Configuration load AuthorityPluginConfigurationLoader resolves YAML manifests under etc/authority.plugins/.
  2. Assembly discovery the shared PluginHost scans PluginBinaries/Authority for StellaOps.Authority.Plugin.*.dll assemblies.
  3. Registrar execution each assembly is searched for IAuthorityPluginRegistrar implementations. Registrars bind options, register services, and optionally queue bootstrap tasks.
  4. Runtime the host resolves IIdentityProviderPlugin instances, uses capability metadata to decide which OAuth grants to expose, and invokes health checks for readiness endpoints.

Authority plug-in lifecycle diagram

Source: docs/assets/authority/authority-plugin-lifecycle.mmd

Data persistence primer: the standard Mongo-backed plugin stores users in collections named authority_users_<pluginName> and lockout metadata in embedded documents. Additional plugins must document their storage layout and provide deterministic collection naming to honour the Offline Kit replication process.

3. Capability Metadata

Capability flags let the host reason about what your plug-in supports:

  • Declare capabilities in your descriptor using the string constants from AuthorityPluginCapabilities (password, mfa, clientProvisioning, bootstrap). The configuration loader now validates these tokens and rejects unknown values at startup.
  • AuthorityIdentityProviderCapabilities.FromCapabilities projects those strings into strongly typed booleans (SupportsPassword, etc.). Authority Core will use these flags when wiring flows such as the password grant. Built-in plugins (e.g., Standard) will fail fast or force-enable required capabilities if the descriptor is misconfigured, so keep manifests accurate.
  • Typical configuration (etc/authority.plugins/standard.yaml):
    plugins:
      descriptors:
        standard:
          assemblyName: "StellaOps.Authority.Plugin.Standard"
          capabilities:
            - password
            - bootstrap
    
  • Only declare a capability if the plug-in genuinely implements it. For example, if SupportsClientProvisioning is true, the plug-in must supply a working IClientProvisioningStore.

Operational reminder: the Authority host surfaces capability summaries during startup (see AuthorityIdentityProviderRegistry log lines). Use those logs during smoke tests to ensure manifests align with expectations.

Configuration path normalisation: Manifest-relative paths (e.g., tokenSigning.keyDirectory: "../keys") are resolved against the YAML file location and environment variables are expanded before validation. Plug-ins should expect to receive an absolute, canonical path when options are injected.

Password policy guardrails: The Standard registrar logs a warning when a plug-in weakens the default password policy (minimum length or required character classes). Keep overrides at least as strong as the compiled defaults—operators treat the warning as an actionable security deviation.

4. Project Scaffold

  • Target .NET 10 preview, enable nullable, treat warnings as errors, and mark Authority plug-ins with <IsAuthorityPlugin>true</IsAuthorityPlugin>.
  • Minimum references:
    • StellaOps.Authority.Plugins.Abstractions (contracts & capability helpers)
    • StellaOps.Plugin (hosting/DI helpers)
    • StellaOps.Auth.* libraries as needed for shared token utilities (optional today).
  • Example .csproj (trimmed from StellaOps.Authority.Plugin.Standard):
    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net10.0</TargetFramework>
        <Nullable>enable</Nullable>
        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
        <IsAuthorityPlugin>true</IsAuthorityPlugin>
      </PropertyGroup>
      <ItemGroup>
        <ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" />
        <ProjectReference Include="..\..\StellaOps.Plugin\StellaOps.Plugin.csproj" />
      </ItemGroup>
    </Project>
    
    (Add other references—e.g., MongoDB driver, shared auth libraries—according to your implementation.)

5. Implementing IAuthorityPluginRegistrar

  • Create a parameterless registrar class that returns your plug-in type name via PluginType.
  • Use AuthorityPluginRegistrationContext to:
    • Bind options (AddOptions<T>(pluginName).Bind(...)).
    • Register singletons for stores/enrichers using manifest metadata.
    • Register any hosted bootstrap tasks (e.g., seed admin users).
  • Always validate configuration inside PostConfigure and throw meaningful InvalidOperationException to fail fast during startup.
  • Use the provided ILoggerFactory from DI; avoid static loggers or console writes.
  • Example skeleton:
    internal sealed class MyPluginRegistrar : IAuthorityPluginRegistrar
    {
        public string PluginType => "my-custom";
    
        public void Register(AuthorityPluginRegistrationContext context)
        {
            var name = context.Plugin.Manifest.Name;
    
            context.Services.AddOptions<MyPluginOptions>(name)
                .Bind(context.Plugin.Configuration)
                .PostConfigure(opts => opts.Validate(name));
    
            context.Services.AddSingleton<IIdentityProviderPlugin>(sp =>
                new MyIdentityProvider(context.Plugin, sp.GetRequiredService<MyCredentialStore>(),
                                       sp.GetRequiredService<MyClaimsEnricher>(),
                                       sp.GetRequiredService<ILogger<MyIdentityProvider>>()));
        }
    }
    

6. Identity Provider Surface

  • Implement IIdentityProviderPlugin to expose:
    • IUserCredentialStore for password validation and user CRUD.
    • IClaimsEnricher to append roles/attributes onto issued principals.
    • Optional IClientProvisioningStore for machine-to-machine clients.
    • AuthorityIdentityProviderCapabilities to advertise supported flows.
  • Password guidance:
    • Standard plug-in hashes via ICryptoProvider using Argon2id by default and emits PHC-compliant strings. Successful PBKDF2 logins trigger automatic rehashes so migrations complete gradually. See docs/security/password-hashing.md for tuning advice.
    • Enforce password policies before hashing to avoid storing weak credentials.
  • Health checks should probe backing stores (e.g., Mongo ping) and return AuthorityPluginHealthResult so /ready can surface issues.
  • When supporting additional factors (e.g., TOTP), implement SupportsMfa and document the enrolment flow for resource servers.

7. Configuration & Secrets

  • Authority looks for manifests under etc/authority.plugins/. Each YAML file maps directly to a plug-in name.
  • Support environment overrides using STELLAOPS_AUTHORITY_PLUGINS__DESCRIPTORS__<NAME>__....
  • Never store raw secrets in git: allow operators to supply them via .local.yaml, environment variables, or injected secret files. Document which keys are mandatory.
  • Validate configuration as soon as the registrar runs; use explicit error messages to guide operators. The Standard plug-in now enforces complete bootstrap credentials (username + password) and positive lockout windows via StandardPluginOptions.Validate.
  • Cross-reference bootstrap workflows with docs/ops/authority_bootstrap.md (to be published alongside CORE6) so operators can reuse the same payload formats for manual provisioning.
  • passwordHashing inherits defaults from authority.security.passwordHashing. Override only when hardware constraints differ per plug-in:
    passwordHashing:
      algorithm: Argon2id
      memorySizeInKib: 19456
      iterations: 2
      parallelism: 1
    
    Invalid values (≤0) fail fast during startup, and legacy PBKDF2 hashes rehash automatically once the new algorithm succeeds.

7.1 Token Persistence Contract

  • The host automatically persists every issued principal (access, refresh, device, authorization code) in authority_tokens. Plug-in code must not bypass this store; use the provided IAuthorityTokenStore helpers when implementing custom flows.
  • When a plug-in disables a subject or client outside the standard handlers, call IAuthorityTokenStore.UpdateStatusAsync(...) for each affected token so revocation bundles stay consistent.
  • Supply machine-friendly revokedReason codes (compromised, rotation, policy, lifecycle, etc.) and optional revokedMetadata entries when invalidating credentials. These flow straight into revocation-bundle.json and should remain deterministic.
  • Token scopes should be normalised (trimmed, unique, ordinal sort) before returning from plug-in verification paths. TokenPersistenceHandlers will keep that ordering for downstream consumers.

7.2 Claims & Enrichment Checklist

  • Authority always sets the OpenID Connect basics: sub, client_id, preferred_username, optional name, and role (for password flows). Plug-ins must use IClaimsEnricher to append additional claims in a deterministic order (sort arrays, normalise casing) so resource servers can rely on stable shapes.
  • Recommended enrichment keys:
    • stellaops.realm plug-in/tenant identifier so services can scope policies.
    • stellaops.subject.type values such as human, service, bootstrap.
    • groups / projects sorted arrays describing operator entitlements.
  • Claims visible in tokens should mirror what /token and /userinfo emit. Avoid injecting sensitive PII directly; mark values with ClassifiedString.Personal inside the plug-in so audit sinks can tag them appropriately.
  • For client-credential flows, remember to enrich both the client principal and the validation path (TokenValidationHandlers) so refresh flows keep the same metadata.

7.3 Revocation Bundles & Reasons

  • Use IAuthorityRevocationStore to record subject/client/token revocations when credentials are deleted or rotated. Stick to the standard categories (token, subject, client, key).
  • Include a deterministic reason string and optional reasonDescription so operators understand why a subject was revoked when inspecting bundles offline.
  • Plug-ins should populate metadata with stable keys (e.g., revokedBy, sourcePlugin, ticketId) to simplify SOC correlation. The keys must be lowercase, ASCII, and free of secrets—bundles are mirrored to air-gapped agents.

8. Rate Limiting & Lockout Interplay

Rate limiting and account lockouts are complementary controls. Plug-ins must surface both deterministically so operators can correlate limiter hits with credential rejections.

Baseline quotas (from docs/dev/authority-rate-limit-tuning-outline.md):

Endpoint Default policy Notes
/token 30 requests / 60s, queue 0 Drop to 10/60s for untrusted ranges; raise only with WAF + monitoring.
/authorize 60 requests / 60s, queue 10 Reduce carefully; interactive UX depends on headroom.
/internal/* Disabled by default; recommended 5/60s when enabled Keep queue 0 for bootstrap APIs.

Retry metadata: The middleware stamps Retry-After plus tags authority.client_id, authority.remote_ip, and authority.endpoint. Plug-ins should keep these tags intact when crafting responses or telemetry so dashboards remain consistent.

Lockout counters: Treat lockouts as subject-scoped decisions. When multiple instances update counters, reuse the deterministic tie-breakers documented in src/DEDUP_CONFLICTS_RESOLUTION_ALGO.md (freshness overrides, precedence, and stable hashes) to avoid divergent lockout states across replicas.

Alerting hooks: Emit structured logs/metrics when either the limiter or credential store rejects access. Suggested gauges include aspnetcore_rate_limiting_rejections_total{limiter="authority-token"} and any custom auth.plugins.<pluginName>.lockouts_total counter.

Authority rate limit and lockout flow

Source: docs/assets/authority/authority-rate-limit-flow.mmd

9. Logging, Metrics, and Diagnostics

  • Always log via the injected ILogger<T>; include pluginName and correlation IDs where available.
  • Activity/metric names should align with AuthorityTelemetry constants (service.name=stellaops-authority).
  • Expose additional diagnostics via structured logging rather than writing custom HTTP endpoints; the host will integrate these into /health and /ready.
  • Emit metrics with stable names (auth.plugins.<pluginName>.*) when introducing custom instrumentation; coordinate with the Observability guild to reserve prefixes.

10. Testing & Tooling

  • Unit tests: use Mongo2Go (or similar) to exercise credential stores without hitting production infrastructure (StandardUserCredentialStoreTests is a template).
  • Determinism: fix timestamps to UTC and sort outputs consistently; avoid random GUIDs unless stable.
  • Smoke tests: launch dotnet run --project src/StellaOps.Authority/StellaOps.Authority with your plug-in under PluginBinaries/Authority and verify /ready.
  • Example verification snippet:
    [Fact]
    public async Task VerifyPasswordAsync_ReturnsSuccess()
    {
        var store = CreateCredentialStore();
        await store.UpsertUserAsync(new AuthorityUserRegistration("alice", "Pa55!", null, null, false,
            Array.Empty<string>(), new Dictionary<string, string?>()), CancellationToken.None);
    
        var result = await store.VerifyPasswordAsync("alice", "Pa55!", CancellationToken.None);
        Assert.True(result.Succeeded);
        Assert.True(result.User?.Roles.Count == 0);
    }
    

11. Packaging & Delivery

  • Output assembly should follow StellaOps.Authority.Plugin.<Name>.dll so the hosts search pattern picks it up.
  • Place the compiled DLL plus dependencies under PluginBinaries/Authority for offline deployments; include hashes/signatures in release notes (Security Guild guidance forthcoming).
  • Document any external prerequisites (e.g., CA cert bundle) in your plug-in README.
  • Update etc/authority.plugins/<plugin>.yaml samples and include deterministic SHA256 hashes for optional bootstrap payloads when distributing Offline Kit artefacts.

12. Checklist & Handoff

  • Capabilities declared and validated in automated tests.
  • Bootstrap workflows documented (if bootstrap capability used) and repeatable.
  • Local smoke test + unit/integration suites green (dotnet test).
  • Operational docs updated: configuration keys, secrets guidance, troubleshooting.
  • Submit the developer guide update referencing PLG6/DOC4 and tag DevEx + Docs reviewers for sign-off.

Mermaid sources for the embedded diagrams live under docs/assets/authority/. Regenerate the SVG assets with your preferred renderer before committing future updates so the visuals stay in sync with the .mmd definitions.


  1. Lightweight Directory Access Protocol (LDAPv3) specification — RFC 4511. ↩︎