Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve configuration self elevation flow #4844

Merged
merged 9 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -107,6 +107,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
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
Loading