Skip to content

Commit

Permalink
Improve configuration self elevation flow (#4844)
Browse files Browse the repository at this point in the history
## Change
This change adds the ability to use schema 0.3 configuration files and
set the module path when using the self-elevating configuration feature.
This required implementing the 0.3 serializer as well.

It also introduces an error when secure parameters would be passed
across an integrity boundary.
  • Loading branch information
JohnMcPMS authored Oct 7, 2024
1 parent 27f9000 commit 1ca81fc
Show file tree
Hide file tree
Showing 34 changed files with 767 additions and 189 deletions.
2 changes: 2 additions & 0 deletions doc/windows/package-manager/winget/returnCodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ Installation failed. Restart your PC then try again. |
| 0x8A15C00F | -1978286065 | WINGET_CONFIG_ERROR_TEST_FAILED | Some of the configuration units failed while testing their state. |
| 0x8A15C010 | -1978286064 | WINGET_CONFIG_ERROR_TEST_NOT_RUN | Configuration state was not tested. |
| 0x8A15C011 | -1978286063 | WINGET_CONFIG_ERROR_GET_FAILED | The configuration unit failed getting its properties. |
| 0x8A15C012 | -1978286062 | WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND | The specified configuration could not be found. |
| 0x8A15C013 | -1978286061 | WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY | Parameter cannot be passed across integrity boundary. |

## Configuration Processor Errors

Expand Down
4 changes: 4 additions & 0 deletions src/AppInstallerCLICore/Commands/DebugCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ namespace AppInstaller::CLI
OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::TestConfigurationUnitResult>>(context);
OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::IApplyGroupMemberSettingsResult>>(context);
OutputProxyStubInterfaceRegistration<winrt::Windows::Foundation::Collections::IIterable<winrt::Microsoft::Management::Configuration::ITestSettingsResult>>(context);
OutputProxyStubInterfaceRegistration<winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessorDetails2>(context);
OutputProxyStubInterfaceRegistration<winrt::Microsoft::Management::Configuration::IGetAllSettingsConfigurationUnitProcessor>(context);
OutputProxyStubInterfaceRegistration<winrt::Microsoft::Management::Configuration::IConfigurationStatics2>(context);

// TODO: Fix the layering inversion created by the COM deployment API (probably in order to operate winget.exe against the COM server).
// Then this code can just have a CppWinRT reference to the deployment API and spit out the interface registrations just like for configuration.
Expand All @@ -127,6 +130,7 @@ namespace AppInstaller::CLI
void DumpInterestingIIDsCommand::ExecuteInternal(Execution::Context& context) const
{
OutputIIDMapping<winrt::Microsoft::Management::Configuration::IConfigurationStatics>(context);
OutputIIDMapping<winrt::Microsoft::Management::Configuration::IConfigurationStatics2>(context);
}

Resource::LocString DumpErrorResourceCommand::ShortDescription() const
Expand Down
108 changes: 104 additions & 4 deletions src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
// Licensed under the MIT License.
#include "pch.h"
#include "Public/ConfigurationSetProcessorFactoryRemoting.h"
#include <AppInstallerErrors.h>
#include <AppInstallerLanguageUtilities.h>
#include <AppInstallerStrings.h>
#include <winget/ILifetimeWatcher.h>
#include <winget/Security.h>
#include <winrt/Microsoft.Management.Configuration.SetProcessorFactory.h>

using namespace winrt::Windows::Foundation;
using namespace winrt::Microsoft::Management::Configuration;
Expand Down Expand Up @@ -40,7 +43,7 @@ namespace AppInstaller::CLI::ConfigurationRemoting
// have this implementation leverage that one with an event handler for the packaged specifics.
// TODO: Add SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties and pass values along to sets on creation
// In turn, any properties must only be set via the command line (or eventual UI requests to the user).
struct DynamicFactory : winrt::implements<DynamicFactory, IConfigurationSetProcessorFactory, winrt::cloaked<WinRT::ILifetimeWatcher>>, WinRT::LifetimeWatcherBase
struct DynamicFactory : winrt::implements<DynamicFactory, IConfigurationSetProcessorFactory, SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties, winrt::cloaked<WinRT::ILifetimeWatcher>>, WinRT::LifetimeWatcherBase
{
DynamicFactory();

Expand All @@ -58,12 +61,58 @@ namespace AppInstaller::CLI::ConfigurationRemoting

void SendDiagnostics(const IDiagnosticInformation& information);

Collections::IVectorView<winrt::hstring> AdditionalModulePaths() const
{
THROW_HR(E_NOTIMPL);
}

void AdditionalModulePaths(const Collections::IVectorView<winrt::hstring>&)
{
THROW_HR(E_NOTIMPL);
}

SetProcessorFactory::PwshConfigurationProcessorPolicy Policy() const
{
THROW_HR(E_NOTIMPL);
}

void Policy(SetProcessorFactory::PwshConfigurationProcessorPolicy)
{
THROW_HR(E_NOTIMPL);
}

SetProcessorFactory::PwshConfigurationProcessorLocation Location() const
{
return m_location;
}

void Location(SetProcessorFactory::PwshConfigurationProcessorLocation value)
{
auto pwshFactory = m_defaultRemoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>();
pwshFactory.Location(value);
m_location = value;
}

winrt::hstring CustomLocation() const
{
return m_customLocation;
}

void CustomLocation(winrt::hstring value)
{
auto pwshFactory = m_defaultRemoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>();
pwshFactory.CustomLocation(value);
m_customLocation = value;
}

private:
IConfigurationSetProcessorFactory m_defaultRemoteFactory;
winrt::event<EventHandler<IDiagnosticInformation>> m_diagnostics;
IConfigurationSetProcessorFactory::Diagnostics_revoker m_factoryDiagnosticsEventRevoker;
std::mutex m_diagnosticsMutex;
DiagnosticLevel m_minimumLevel = DiagnosticLevel::Informational;
SetProcessorFactory::PwshConfigurationProcessorLocation m_location = SetProcessorFactory::PwshConfigurationProcessorLocation::Default;
winrt::hstring m_customLocation;
};

struct DynamicProcessorInfo
Expand All @@ -90,6 +139,33 @@ namespace AppInstaller::CLI::ConfigurationRemoting
m_currentIntegrityLevel = Security::GetEffectiveIntegrityLevel();
#endif

// Check for multiple integrity level requirements
bool multipleIntegrityLevels = false;
bool higherIntegrityLevelsThanCurrent = false;
for (const auto& existingUnit : m_configurationSet.Units())
{
auto integrityLevel = GetIntegrityLevelForUnit(existingUnit);
if (integrityLevel != m_currentIntegrityLevel)
{
multipleIntegrityLevels = true;

if (ToIntegral(m_currentIntegrityLevel) < ToIntegral(integrityLevel))
{
higherIntegrityLevelsThanCurrent = true;
break;
}
}
}

// Prevent supplied parameters from crossing integrity levels
for (const auto& parameter : m_configurationSet.Parameters())
{
if (parameter.ProvidedValue() != nullptr)
{
THROW_HR_IF(WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY, higherIntegrityLevelsThanCurrent || (multipleIntegrityLevels && parameter.IsSecure()));
}
}

m_setProcessors.emplace(m_currentIntegrityLevel, DynamicProcessorInfo{ m_dynamicFactory->DefaultFactory(), defaultRemoteSetProcessor});
}

Expand Down Expand Up @@ -194,7 +270,30 @@ namespace AppInstaller::CLI::ConfigurationRemoting
std::string SerializeSetProperties()
{
Json::Value json{ Json::ValueType::objectValue };

json["path"] = winrt::to_string(m_configurationSet.Path());

std::string locationString;
switch (m_dynamicFactory->Location())
{
case SetProcessorFactory::PwshConfigurationProcessorLocation::AllUsers:
locationString = "AllUsers";
break;
case SetProcessorFactory::PwshConfigurationProcessorLocation::CurrentUser:
locationString = "CurrentUser";
break;
case SetProcessorFactory::PwshConfigurationProcessorLocation::Custom:
locationString = Utility::ConvertToUTF8(m_dynamicFactory->CustomLocation());
break;
case SetProcessorFactory::PwshConfigurationProcessorLocation::Default:
break;
}

if (!locationString.empty())
{
json["modulePath"] = locationString;
}

Json::StreamWriterBuilder writerBuilder;
writerBuilder.settings_["indentation"] = "\t";
return Json::writeString(writerBuilder, json);
Expand All @@ -207,9 +306,10 @@ namespace AppInstaller::CLI::ConfigurationRemoting
std::string SerializeHighIntegrityLevelSet()
{
ConfigurationSet highIntegritySet;

// TODO: Currently we only support schema version 0.2 for handling elevated integrity levels.
highIntegritySet.SchemaVersion(L"0.2");
highIntegritySet.SchemaVersion(m_configurationSet.SchemaVersion());
highIntegritySet.Metadata(m_configurationSet.Metadata());
highIntegritySet.Parameters(m_configurationSet.Parameters());
highIntegritySet.Variables(m_configurationSet.Variables());

std::vector<ConfigurationUnit> highIntegrityUnits;
auto units = m_configurationSet.Units();
Expand Down
3 changes: 1 addition & 2 deletions src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,13 @@ namespace AppInstaller::CLI::Workflow
if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::ConfigureSelfElevation) && !Runtime::IsRunningAsAdmin())
{
factory = ConfigurationRemoting::CreateDynamicRuntimeFactory();
// TODO: Implement SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties on dynamic factory
}
else
{
factory = ConfigurationRemoting::CreateOutOfProcessFactory();
Configuration::SetModulePath(context, factory);
}

Configuration::SetModulePath(context, factory);
return factory;
}

Expand Down
22 changes: 22 additions & 0 deletions src/AppInstallerCLIE2ETests/ConfigureCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class ConfigureCommand
[OneTimeSetUp]
public void OneTimeSetup()
{
WinGetSettingsHelper.ConfigureFeature("configuration03", true);
WinGetSettingsHelper.ConfigureFeature("configureSelfElevate", true);
this.DeleteTxtFiles();
}

Expand All @@ -32,6 +34,8 @@ public void OneTimeSetup()
[OneTimeTearDown]
public void OneTimeTeardown()
{
WinGetSettingsHelper.ConfigureFeature("configuration03", false);
WinGetSettingsHelper.ConfigureFeature("configureSelfElevate", false);
this.DeleteTxtFiles();
}

Expand Down Expand Up @@ -207,6 +211,24 @@ public void ConfigureFromHistory()
Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath));
}

/// <summary>
/// Specifies the module path to an "elevated" server.
/// </summary>
[Test]
public void SpecifyModulePathToHighIntegrityServer()
{
string configFile = TestCommon.GetTestDataFile("Configuration\\GetPSModulePath.yml");
string testDirectory = TestCommon.GetRandomTestDir();

var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, $"{configFile} --module-path \"{testDirectory}\"");
Assert.AreEqual(0, result.ExitCode);

string testFile = Path.Join(TestCommon.GetTestDataFile("Configuration"), "PSModulePath.txt");
Assert.True(File.Exists(testFile));
string testFileContents = File.ReadAllText(testFile);
Assert.True(testFileContents.StartsWith(testDirectory));
}

private void DeleteTxtFiles()
{
// Delete all .txt files in the test directory; they are placed there by the tests
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLIE2ETests/ConfigureShowCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ public void ShowDetailsFromLocal(TestCommon.TestModuleLocation location)
[Test]
public void ShowDetails_Schema0_3_Fails()
{
WinGetSettingsHelper.ConfigureFeature("configuration03", false);

var result = TestCommon.RunAICLICommand("configure show", TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo_0_3.yml"));
Assert.AreEqual(Constants.ErrorCode.ERROR_EXPERIMENTAL_FEATURE_DISABLED, result.ExitCode);
}
Expand Down
4 changes: 4 additions & 0 deletions src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public static void InitializeWingetSettings()

var settingsJson = new Hashtable()
{
{
"$schema",
"https://aka.ms/winget-settings.schema.json"
},
{
"experimentalFeatures",
experimentalFeatures
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json
metadata:
1e62d683-2999-44e7-81f7-6f8f35e8d731: true
resources:
- name: Name1
type: xE2ETestResource/E2ETestResourcePSModulePath
metadata:
repository: AppInstallerCLIE2ETestsRepo
securityContext: elevated
properties:
outputPath: ${WinGetConfigRoot}\PSModulePath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ DscResourcesToExport = @(
'E2ETestResourceTypes'
'E2ETestResourceCrash'
'E2ETestResourcePID'
'E2ETestResourcePSModulePath'
)
HelpInfoURI = 'https://www.contoso.com/help'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ class E2ETestResourceCrash
}
}

# This resource writes the current PID to the provided file path.
# This resource writes the current PID to the provided file path.
[DscResource()]
class E2ETestResourcePID
{
Expand Down Expand Up @@ -324,3 +324,34 @@ class E2ETestResourcePID
}
}
}

# This resource writes the current PSModulePath to the provided file path.
[DscResource()]
class E2ETestResourcePSModulePath
{
[DscProperty(Key)]
[string] $key

[DscProperty(Mandatory)]
[string] $outputPath

[E2ETestResourcePSModulePath] Get()
{
$result = @{
key = "E2ETestResourcePSModulePath"
outputPath = $this.outputPath
}

return $result
}

[bool] Test()
{
return $false
}

[void] Set()
{
Set-Content -Path $this.outputPath -Value $env:PSModulePath -Force
}
}
3 changes: 3 additions & 0 deletions src/AppInstallerCLIPackage/Package.appxmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@
<Interface Name="Windows.Foundation.Collections.IIterable`1&lt;Microsoft.Management.Configuration.TestConfigurationUnitResult&gt;" InterfaceId="73848262-86D4-5FFC-8353-8408C4E649DE" />
<Interface Name="Windows.Foundation.Collections.IIterable`1&lt;Microsoft.Management.Configuration.IApplyGroupMemberSettingsResult&gt;" InterfaceId="5086070C-F468-5B00-8352-50FB420BA8B0" />
<Interface Name="Windows.Foundation.Collections.IIterable`1&lt;Microsoft.Management.Configuration.ITestSettingsResult&gt;" InterfaceId="2D28E6AA-7036-5D78-9B58-9456F1E332FE" />
<Interface Name="Microsoft.Management.Configuration.IConfigurationUnitProcessorDetails2" InterfaceId="E89623ED-76E2-5145-B920-D09659554E35" />
<Interface Name="Microsoft.Management.Configuration.IGetAllSettingsConfigurationUnitProcessor" InterfaceId="72EB8304-D8D3-57D4-9940-7C1C4AD8C40C" />
<Interface Name="Microsoft.Management.Configuration.IConfigurationStatics2" InterfaceId="540BE073-F2EF-5375-83AA-8E23086B0669" />
</ProxyStub>
</Extension>
<!-- This entry forces the package registration to process the windows.activatableClass.proxyStub extension above. -->
Expand Down
5 changes: 4 additions & 1 deletion src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
Original file line number Diff line number Diff line change
Expand Up @@ -3130,4 +3130,7 @@ Please specify one of them using the --source option to proceed.</value>
<data name="APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN" xml:space="preserve">
<value>Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have required privilege.</value>
</data>
</root>
<data name="WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY" xml:space="preserve">
<value>Parameter cannot be passed across integrity boundary.</value>
</data>
</root>
1 change: 1 addition & 0 deletions src/AppInstallerSharedLib/Errors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ namespace AppInstaller
WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_TEST_NOT_RUN, "Configuration state was not tested."),
WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_GET_FAILED, "The configuration unit failed getting its properties."),
WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND, "The specified configuration could not be found."),
WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY, "Parameter cannot be passed across integrity boundary."),

// Configuration Processor Errors
WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED, "The configuration unit was not installed."),
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerSharedLib/Public/AppInstallerErrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@
#define WINGET_CONFIG_ERROR_TEST_NOT_RUN ((HRESULT)0x8A15C010)
#define WINGET_CONFIG_ERROR_GET_FAILED ((HRESULT)0x8A15C011)
#define WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND ((HRESULT)0x8A15C012)
#define WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY ((HRESULT)0x8A15C013)

// Configuration Processor Errors
#define WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED ((HRESULT)0x8A15C101)
Expand Down
17 changes: 17 additions & 0 deletions src/ConfigurationRemotingServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ static int Main(string[] args)
if (metadataJson != null)
{
limitationSet.Path = metadataJson.Path;

if (metadataJson.ModulePath != null)
{
PowerShellConfigurationProcessorLocation parsedLocation = PowerShellConfigurationProcessorLocation.Default;
if (Enum.TryParse<PowerShellConfigurationProcessorLocation>(metadataJson.ModulePath, out parsedLocation))
{
factory.Location = parsedLocation;
}
else
{
factory.Location = PowerShellConfigurationProcessorLocation.Custom;
factory.CustomLocation = metadataJson.ModulePath;
}
}
}

// Set the limitation set in factory.
Expand All @@ -168,6 +182,9 @@ private class LimitationSetMetadata
{
[JsonPropertyName("path")]
public string Path { get; set; } = string.Empty;

[JsonPropertyName("modulePath")]
public string? ModulePath { get; set; } = null;
}

private static string GetExternalModulesPath()
Expand Down
Loading

0 comments on commit 1ca81fc

Please sign in to comment.