feat(scep): rework Get-InfisicalScepMdmProfile into FromEnrollment/FromProfile/Manual parameter sets
FromEnrollment (new default) consumes an InfisicalCertificateApplicationEnrollment and auto-fills ServerUrl from scep.scepEndpointUrl, CAThumbprint from the RA certificate thumbprint, and mints a fresh dynamic challenge automatically when challengeType=dynamic and -Challenge is not supplied. FromProfile preserves the legacy projection from an InfisicalCertificateProfile but now requires -ApplicationId so the server URL is built against /scep/applications/{appId}/profiles/{profileId}/pkiclient.exe. Manual requires explicit -ServerUrl, -Challenge, and -UniqueId. Module manifest, help XML, and build.ps1 expectedCmds list updated to register the three new cmdlets. CHANGELOG updated.
This commit is contained in:
@@ -6,24 +6,39 @@ using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Pki;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalScepMdmProfile")]
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalScepMdmProfile", DefaultParameterSetName = "FromEnrollment")]
|
||||
[OutputType(typeof(InfisicalScepMdmProfile))]
|
||||
public sealed class GetInfisicalScepMdmProfileCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
private const string Component = "GetInfisicalScepMdmProfileCmdlet";
|
||||
|
||||
[Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
|
||||
[Parameter(ParameterSetName = "FromEnrollment", Mandatory = true, ValueFromPipeline = true, Position = 0)]
|
||||
[Alias("Enrollment")]
|
||||
public InfisicalCertificateApplicationEnrollment EnrollmentObject { get; set; }
|
||||
|
||||
[Parameter(ParameterSetName = "FromProfile", Mandatory = true, ValueFromPipeline = true, Position = 0)]
|
||||
[Alias("Profile", "CertificateProfile")]
|
||||
public InfisicalCertificateProfile InputObject { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true)]
|
||||
[Parameter(ParameterSetName = "FromProfile", Mandatory = true)]
|
||||
[Alias("AppId")]
|
||||
public string ApplicationId { get; set; }
|
||||
|
||||
[Parameter(ParameterSetName = "FromEnrollment")]
|
||||
[Parameter(ParameterSetName = "FromProfile")]
|
||||
[Parameter(ParameterSetName = "Manual", Mandatory = true)]
|
||||
public SecureString Challenge { get; set; }
|
||||
|
||||
[Parameter(ParameterSetName = "Manual", Mandatory = true)]
|
||||
[Parameter(ParameterSetName = "FromProfile")]
|
||||
[Parameter(ParameterSetName = "FromEnrollment")]
|
||||
public string ServerUrl { get; set; }
|
||||
|
||||
[Parameter] public string UniqueId { get; set; }
|
||||
[Parameter] public string ServerUrl { get; set; }
|
||||
|
||||
[Parameter]
|
||||
[ValidateSet("Device", "User")]
|
||||
@@ -53,45 +68,21 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
try
|
||||
{
|
||||
if (InputObject == null) { throw new InvalidOperationException("InputObject is required."); }
|
||||
if (string.IsNullOrEmpty(InputObject.Id)) { throw new InvalidOperationException("InputObject.Id is required."); }
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedServerUrl = !string.IsNullOrEmpty(ServerUrl) ? ServerUrl : BuildDefaultServerUrl(connection, InputObject.Id);
|
||||
string resolvedUniqueId = !string.IsNullOrEmpty(UniqueId) ? UniqueId : SanitizeForCspId(!string.IsNullOrEmpty(InputObject.Slug) ? InputObject.Slug : InputObject.Id);
|
||||
|
||||
InfisicalCertificateProfileDefaults defaults = InputObject.Defaults;
|
||||
string resolvedKeyAlgorithm = !string.IsNullOrEmpty(KeyAlgorithm) ? KeyAlgorithm : MapKeyAlgorithm(defaults != null ? defaults.KeyAlgorithm : null);
|
||||
string resolvedEku = !string.IsNullOrEmpty(EkuMapping) ? EkuMapping : JoinEkuOids(defaults != null ? defaults.ExtendedKeyUsages : null);
|
||||
|
||||
InfisicalScepMdmProfile result = new InfisicalScepMdmProfile
|
||||
if (string.Equals(ParameterSetName, "FromEnrollment", StringComparison.Ordinal))
|
||||
{
|
||||
UniqueId = resolvedUniqueId,
|
||||
Scope = Scope,
|
||||
ServerUrl = resolvedServerUrl,
|
||||
Challenge = SecureStringToPlainText(Challenge),
|
||||
SubjectName = SubjectName,
|
||||
SubjectAlternativeNames = SubjectAlternativeNames,
|
||||
EkuMapping = resolvedEku,
|
||||
KeyUsage = KeyUsage,
|
||||
KeyLength = KeyLength,
|
||||
KeyAlgorithm = resolvedKeyAlgorithm,
|
||||
HashAlgorithm = HashAlgorithm,
|
||||
KeyProtection = KeyProtection,
|
||||
ContainerName = ContainerName,
|
||||
ValidPeriod = ValidPeriod,
|
||||
ValidPeriodUnits = ValidPeriodUnits,
|
||||
RetryCount = RetryCount,
|
||||
RetryDelay = RetryDelay,
|
||||
TemplateName = TemplateName,
|
||||
CAThumbprint = CAThumbprint,
|
||||
CustomTextToShowInPrompt = CustomTextToShowInPrompt,
|
||||
SourceProfileId = InputObject.Id,
|
||||
SourceProfileSlug = InputObject.Slug
|
||||
};
|
||||
WriteObject(BuildFromEnrollment(connection));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Verbose(Component, string.Concat("Built SCEP MDM profile for source profile '", InputObject.Slug ?? InputObject.Id, "' targeting ", result.ServerUrl, " (UniqueId=", result.UniqueId, ", Scope=", result.Scope, ")."));
|
||||
WriteObject(result);
|
||||
if (string.Equals(ParameterSetName, "FromProfile", StringComparison.Ordinal))
|
||||
{
|
||||
WriteObject(BuildFromProfile(connection));
|
||||
return;
|
||||
}
|
||||
|
||||
WriteObject(BuildManual(connection));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -99,11 +90,109 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildDefaultServerUrl(InfisicalConnection connection, string profileId)
|
||||
private InfisicalScepMdmProfile BuildFromEnrollment(InfisicalConnection connection)
|
||||
{
|
||||
if (EnrollmentObject == null) { throw new InvalidOperationException("EnrollmentObject is required."); }
|
||||
if (string.IsNullOrEmpty(EnrollmentObject.ApplicationId)) { throw new InvalidOperationException("EnrollmentObject.ApplicationId is required."); }
|
||||
if (string.IsNullOrEmpty(EnrollmentObject.ProfileId)) { throw new InvalidOperationException("EnrollmentObject.ProfileId is required."); }
|
||||
|
||||
InfisicalCertificateApplicationScepEnrollment scep = EnrollmentObject.Scep;
|
||||
if (scep == null) { throw new InvalidOperationException("Enrollment does not have SCEP configured."); }
|
||||
|
||||
string resolvedServerUrl = FirstNonEmpty(ServerUrl, scep.ScepEndpointUrl, BuildDefaultServerUrl(connection, EnrollmentObject.ApplicationId, EnrollmentObject.ProfileId));
|
||||
string resolvedUniqueId = !string.IsNullOrEmpty(UniqueId) ? UniqueId : SanitizeForCspId(EnrollmentObject.ProfileId);
|
||||
string resolvedThumbprint = !string.IsNullOrEmpty(CAThumbprint) ? CAThumbprint : scep.RaCertificateThumbprint;
|
||||
string resolvedChallenge = ResolveChallengeFromEnrollment(connection, scep);
|
||||
|
||||
InfisicalScepMdmProfile result = NewProfileShell(resolvedUniqueId, resolvedServerUrl, resolvedChallenge, resolvedThumbprint, null, null);
|
||||
result.SourceProfileId = EnrollmentObject.ProfileId;
|
||||
Logger.Verbose(Component, string.Concat("Built SCEP MDM profile from enrollment for application '", EnrollmentObject.ApplicationId, "' / profile '", EnrollmentObject.ProfileId, "' targeting ", result.ServerUrl, " (UniqueId=", result.UniqueId, ", Scope=", result.Scope, ", ChallengeType=", scep.ChallengeType ?? "<unknown>", ")."));
|
||||
return result;
|
||||
}
|
||||
|
||||
private InfisicalScepMdmProfile BuildFromProfile(InfisicalConnection connection)
|
||||
{
|
||||
if (InputObject == null) { throw new InvalidOperationException("InputObject is required."); }
|
||||
if (string.IsNullOrEmpty(InputObject.Id)) { throw new InvalidOperationException("InputObject.Id is required."); }
|
||||
if (string.IsNullOrEmpty(ApplicationId)) { throw new InvalidOperationException("ApplicationId is required when binding by certificate profile."); }
|
||||
if (Challenge == null) { throw new InvalidOperationException("Challenge is required when building from a certificate profile."); }
|
||||
|
||||
string resolvedServerUrl = !string.IsNullOrEmpty(ServerUrl) ? ServerUrl : BuildDefaultServerUrl(connection, ApplicationId, InputObject.Id);
|
||||
string resolvedUniqueId = !string.IsNullOrEmpty(UniqueId) ? UniqueId : SanitizeForCspId(!string.IsNullOrEmpty(InputObject.Slug) ? InputObject.Slug : InputObject.Id);
|
||||
InfisicalCertificateProfileDefaults defaults = InputObject.Defaults;
|
||||
string resolvedKeyAlgorithm = !string.IsNullOrEmpty(KeyAlgorithm) ? KeyAlgorithm : MapKeyAlgorithm(defaults != null ? defaults.KeyAlgorithm : null);
|
||||
string resolvedEku = !string.IsNullOrEmpty(EkuMapping) ? EkuMapping : JoinEkuOids(defaults != null ? defaults.ExtendedKeyUsages : null);
|
||||
|
||||
InfisicalScepMdmProfile result = NewProfileShell(resolvedUniqueId, resolvedServerUrl, SecureStringToPlainText(Challenge), CAThumbprint, resolvedKeyAlgorithm, resolvedEku);
|
||||
result.SourceProfileId = InputObject.Id;
|
||||
result.SourceProfileSlug = InputObject.Slug;
|
||||
Logger.Verbose(Component, string.Concat("Built SCEP MDM profile for source profile '", InputObject.Slug ?? InputObject.Id, "' targeting ", result.ServerUrl, " (UniqueId=", result.UniqueId, ", Scope=", result.Scope, ")."));
|
||||
return result;
|
||||
}
|
||||
|
||||
private InfisicalScepMdmProfile BuildManual(InfisicalConnection connection)
|
||||
{
|
||||
if (string.IsNullOrEmpty(UniqueId)) { throw new InvalidOperationException("UniqueId is required in Manual mode."); }
|
||||
string resolvedChallenge = SecureStringToPlainText(Challenge);
|
||||
InfisicalScepMdmProfile result = NewProfileShell(UniqueId, ServerUrl, resolvedChallenge, CAThumbprint, KeyAlgorithm, EkuMapping);
|
||||
Logger.Verbose(Component, string.Concat("Built SCEP MDM profile in Manual mode targeting ", result.ServerUrl, " (UniqueId=", result.UniqueId, ", Scope=", result.Scope, ")."));
|
||||
return result;
|
||||
}
|
||||
|
||||
private InfisicalScepMdmProfile NewProfileShell(string uniqueId, string serverUrl, string challenge, string thumbprint, string keyAlgorithm, string ekuMapping)
|
||||
{
|
||||
return new InfisicalScepMdmProfile
|
||||
{
|
||||
UniqueId = uniqueId,
|
||||
Scope = Scope,
|
||||
ServerUrl = serverUrl,
|
||||
Challenge = challenge,
|
||||
SubjectName = SubjectName,
|
||||
SubjectAlternativeNames = SubjectAlternativeNames,
|
||||
EkuMapping = ekuMapping,
|
||||
KeyUsage = KeyUsage,
|
||||
KeyLength = KeyLength,
|
||||
KeyAlgorithm = keyAlgorithm,
|
||||
HashAlgorithm = HashAlgorithm,
|
||||
KeyProtection = KeyProtection,
|
||||
ContainerName = ContainerName,
|
||||
ValidPeriod = ValidPeriod,
|
||||
ValidPeriodUnits = ValidPeriodUnits,
|
||||
RetryCount = RetryCount,
|
||||
RetryDelay = RetryDelay,
|
||||
TemplateName = TemplateName,
|
||||
CAThumbprint = thumbprint,
|
||||
CustomTextToShowInPrompt = CustomTextToShowInPrompt
|
||||
};
|
||||
}
|
||||
|
||||
private string ResolveChallengeFromEnrollment(InfisicalConnection connection, InfisicalCertificateApplicationScepEnrollment scep)
|
||||
{
|
||||
if (Challenge != null) { return SecureStringToPlainText(Challenge); }
|
||||
|
||||
string challengeType = scep.ChallengeType ?? string.Empty;
|
||||
if (string.Equals(challengeType, "dynamic", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
|
||||
Logger.Verbose(Component, "Minting SCEP dynamic challenge for enrollment.");
|
||||
return client.GenerateScepDynamicChallenge(connection, EnrollmentObject.ApplicationId, EnrollmentObject.ProfileId);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(string.Concat("Enrollment uses challengeType '", challengeType, "'. Supply -Challenge with the configured static challenge password."));
|
||||
}
|
||||
|
||||
private static string BuildDefaultServerUrl(InfisicalConnection connection, string applicationId, string profileId)
|
||||
{
|
||||
if (connection == null || connection.BaseUri == null) { throw new InvalidOperationException("Active Infisical connection is required to derive ServerUrl."); }
|
||||
string baseUrl = connection.BaseUri.GetLeftPart(UriPartial.Authority);
|
||||
return string.Concat(baseUrl, "/scep/", profileId, "/pkiclient.exe");
|
||||
return string.Concat(baseUrl, "/scep/applications/", applicationId, "/profiles/", profileId, "/pkiclient.exe");
|
||||
}
|
||||
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
{
|
||||
if (values == null) { return null; }
|
||||
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string SanitizeForCspId(string input)
|
||||
|
||||
Reference in New Issue
Block a user