File: Secrets\RadiusSecuritySecretKinds.cs
Web Access
Project: src\src\Aspire.Hosting.Radius\Aspire.Hosting.Radius.csproj (Aspire.Hosting.Radius)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
namespace Aspire.Hosting.Radius;
 
/// <summary>
/// The <c>kind</c> vocabulary of the <c>Radius.Security/secrets</c> resource type, and the data
/// keys each kind's reference recipes require.
/// </summary>
/// <remarks>
/// <para>
/// This is deliberately separate from <see cref="RadiusSecretStoreType"/>, which describes the
/// legacy <c>Applications.Core/secretStores</c> <c>properties.type</c>. The two vocabularies
/// overlap but are not the same: the legacy type spells the certificate case <c>certificate</c>,
/// while the replacement type splits it into <c>certificate-pem</c> and <c>certificate-pkcs12</c>
/// and does not accept the bare spelling at all. Sharing one table would both miss
/// <c>certificate-pem</c> and treat the invalid <c>certificate</c> as recognized.
/// </para>
/// <para>
/// The kind enum is declared by the built-in resource-type manifest, and the per-kind required
/// keys are enforced by the reference recipes rather than by the control plane:
/// <see href="https://github.com/radius-project/radius/blob/v0.60.0/deploy/manifest/built-in-providers/self-hosted/secrets.yaml"/>
/// and
/// <see href="https://github.com/radius-project/resource-types-contrib/blob/main/Security/secrets/README.md"/>.
/// </para>
/// </remarks>
internal static class RadiusSecuritySecretKinds
{
    /// <summary>The default <c>kind</c> when the property is omitted.</summary>
    internal const string Generic = "generic";
 
    /// <summary>
    /// The bare <c>certificate</c> spelling, which is valid on the <em>legacy</em>
    /// <c>Applications.Core/secretStores</c> type but is not a member of this type's enum. Carrying
    /// it over is the most likely migration mistake, so it is named here to be rejected with an
    /// actionable message rather than passed through as an unrecognized kind.
    /// </summary>
    internal const string LegacyCertificate = "certificate";
 
    /// <summary>The replacement for the legacy bare <c>certificate</c> kind.</summary>
    internal const string CertificatePem = "certificate-pem";
 
    /// <summary>
    /// The legacy <c>raw</c> encoding, valid on <c>Applications.Core/secretStores</c> and absent
    /// from this type's <c>encoding</c> enum, which is <c>string</c>/<c>base64</c>. Named here for
    /// the same reason as <see cref="LegacyCertificate"/>: it is a migration mistake rather than a
    /// value a newer control plane might introduce.
    /// </summary>
    internal const string LegacyRawEncoding = "raw";
 
    /// <summary>
    /// The complete <c>kind</c> enum of the <c>Radius.Security/secrets</c> type as pinned by
    /// <see cref="RadiusBicepExtension.Version"/>.
    /// </summary>
    /// <remarks>
    /// The enum is closed, and it is enforced by the <em>Bicep compiler</em> rather than only by the
    /// control plane: the generated <c>bicepconfig.json</c> resolves <c>extension radius</c> to
    /// <c>br:biceptypes.azurecr.io/radius:0.60</c>, whose <c>types.json</c> models <c>kind</c> as a
    /// union of string literals with no bare string element. A value outside the union therefore
    /// fails <c>bicep build</c> during <c>rad deploy</c>, and a newer control plane cannot make it
    /// valid, because the compile step reads the pinned type definitions. Publishing an unknown
    /// literal would only produce an artifact that cannot deploy, so it is rejected here where the
    /// message can name the offending resource.
    /// <see href="https://github.com/radius-project/radius/blob/v0.60.0/hack/bicep-types-radius/generated/radius/radius.security/2025-08-01-preview/types.json"/>
    /// </remarks>
    internal static IReadOnlySet<string> All { get; } = new HashSet<string>(StringComparer.Ordinal)
    {
        Generic,
        CertificatePem,
        "certificate-pkcs12",
        "basicAuthentication",
        "awsIRSA",
        "azureWorkloadIdentity",
    };
 
    /// <summary>
    /// The complete <c>encoding</c> enum of a <c>Radius.Security/secrets</c> data entry, closed for
    /// the same reason and by the same mechanism as <see cref="All"/>.
    /// </summary>
    internal static IReadOnlySet<string> Encodings { get; } = new HashSet<string>(StringComparer.Ordinal)
    {
        "string",
        "base64",
    };
 
    /// <summary>
    /// Returns the data keys the reference recipes require for <paramref name="kind"/>.
    /// </summary>
    /// <remarks>
    /// Returns <see langword="false"/> for a kind with no documented required keys — <c>generic</c>
    /// and <c>certificate-pkcs12</c> (the reference recipe documents no per-key contract for it).
    /// Membership in the enum itself is checked separately against <see cref="All"/>.
    /// </remarks>
    internal static bool TryGetRequiredKeys(string? kind, out IReadOnlyList<string> requiredKeys)
    {
        requiredKeys = kind switch
        {
            CertificatePem => ["tls.crt", "tls.key"],
            "basicAuthentication" => ["username", "password"],
            "azureWorkloadIdentity" => ["clientId", "tenantId"],
            "awsIRSA" => ["roleARN"],
            _ => [],
        };
 
        return requiredKeys.Count > 0;
    }
}