Per-endpoint RunAs: Service / InteractiveUser / SpecificUser
Native per-endpoint identity instead of the schtasks bridge: - Service (default) keeps the existing path - hooks inherit the service account (SYSTEM by default, or whatever you installed under). - SpecificUser binds ProcessStartInfo.UserName / Password / Domain so the hook runs in a batch logon session as the named account. Useful for AD-write hooks that should NOT run as SYSTEM. - InteractiveUser uses WTSQueryUserToken(WTSGetActiveConsoleSessionId) + DuplicateTokenEx + CreateProcessAsUser to drop the child into the logged-in user's session with their environment block. This is the real fix for "calc.exe should pop up on my desktop" - no Task Scheduler bridge required. Stdio is captured via inheritable anonymous pipes so the hook still returns stdout/stderr to the caller normally. Implementation: - New RunAsMode enum + RunAsConfig model on EndpointConfig - ConfigStore round-trips RunAs.Password through DPAPI alongside bearer/HMAC/PFX secrets - AdminPipeServer's secret-merge logic preserves the encrypted blob when the GUI saves an endpoint without re-typing the password - New WebhookServer.Core.Execution.Native namespace with NativeMethods (P/Invoke) and InteractiveProcessLauncher (token-based launcher) - ProcessExecutor branches on RunAs.Mode; the Service/SpecificUser paths share .NET's Process; InteractiveUser uses the launcher - GUI editor gets a "Run as" section: dropdown + conditional username/password/load-profile fields under SpecificUser Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,8 @@ public sealed partial class EndpointEditorViewModel : ObservableObject
|
||||
Endpoint = JsonSerializer.Deserialize<EndpointConfig>(json, ConfigJson.Compact)!;
|
||||
Endpoint.Bearer ??= new BearerOptions();
|
||||
Endpoint.Hmac ??= new HmacOptions();
|
||||
Endpoint.RunAs ??= new RunAsConfig();
|
||||
Endpoint.RunAs.Password ??= new ProtectedString();
|
||||
IsNew = isNew;
|
||||
}
|
||||
|
||||
@@ -53,6 +55,58 @@ public sealed partial class EndpointEditorViewModel : ObservableObject
|
||||
public Visibility HmacVisible =>
|
||||
Endpoint.AuthMode == AuthMode.Hmac ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public Array RunAsModes { get; } = Enum.GetValues(typeof(RunAsMode));
|
||||
|
||||
public RunAsMode SelectedRunAsMode
|
||||
{
|
||||
get => Endpoint.RunAs?.Mode ?? RunAsMode.Service;
|
||||
set
|
||||
{
|
||||
Endpoint.RunAs ??= new RunAsConfig();
|
||||
if (Endpoint.RunAs.Mode == value) return;
|
||||
Endpoint.RunAs.Mode = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SpecificUserVisible));
|
||||
}
|
||||
}
|
||||
|
||||
public Visibility SpecificUserVisible =>
|
||||
SelectedRunAsMode == RunAsMode.SpecificUser ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public string RunAsUsername
|
||||
{
|
||||
get => Endpoint.RunAs?.Username ?? "";
|
||||
set
|
||||
{
|
||||
Endpoint.RunAs ??= new RunAsConfig();
|
||||
Endpoint.RunAs.Username = string.IsNullOrEmpty(value) ? null : value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string RunAsPassword
|
||||
{
|
||||
get => Endpoint.RunAs?.Password?.Plaintext ?? "";
|
||||
set
|
||||
{
|
||||
Endpoint.RunAs ??= new RunAsConfig();
|
||||
Endpoint.RunAs.Password ??= new ProtectedString();
|
||||
Endpoint.RunAs.Password.Plaintext = string.IsNullOrEmpty(value) ? null : value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool RunAsLoadProfile
|
||||
{
|
||||
get => Endpoint.RunAs?.LoadProfile ?? false;
|
||||
set
|
||||
{
|
||||
Endpoint.RunAs ??= new RunAsConfig();
|
||||
Endpoint.RunAs.LoadProfile = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string AllowedClientsText
|
||||
{
|
||||
get => string.Join(Environment.NewLine, Endpoint.AllowedClients);
|
||||
|
||||
@@ -130,6 +130,50 @@
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="Run as" Padding="6" Margin="0,8,0,0">
|
||||
<StackPanel>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Identity" VerticalAlignment="Center"/>
|
||||
<ComboBox Grid.Column="1" ItemsSource="{Binding RunAsModes}" SelectedItem="{Binding SelectedRunAsMode, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
<TextBlock Foreground="Gray" FontStyle="Italic" FontSize="11" Margin="120,2,0,0"
|
||||
Text="Service = run as the service account (default). InteractiveUser = the logged-in user's desktop session, lets hooks pop UI. SpecificUser = a named account with password."/>
|
||||
<StackPanel Visibility="{Binding SpecificUserVisible}">
|
||||
<Grid Margin="0,6,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Username" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding RunAsUsername, UpdateSourceTrigger=PropertyChanged}" FontFamily="Consolas"/>
|
||||
</Grid>
|
||||
<TextBlock Foreground="Gray" FontStyle="Italic" FontSize="11" Margin="120,2,0,0"
|
||||
Text="Examples: DOMAIN\justin .\local-user user@contoso.com"/>
|
||||
<Grid Margin="0,4,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Password" VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding RunAsPassword, UpdateSourceTrigger=PropertyChanged}" FontFamily="Consolas"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,4,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Load profile" VerticalAlignment="Center"/>
|
||||
<CheckBox Grid.Column="1" IsChecked="{Binding RunAsLoadProfile}" VerticalAlignment="Center"
|
||||
Content="Load the user's HKCU + AppData (slower; only needed if the script reads user-scoped state)"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<GroupBox Header="Response" Padding="6" Margin="0,8,0,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
|
||||
Reference in New Issue
Block a user