ConfigurationDynamicRuntimeFactory.cpp (25361B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/ConfigurationSetProcessorFactoryRemoting.h" 5 #include <AppInstallerErrors.h> 6 #include <AppInstallerLanguageUtilities.h> 7 #include <AppInstallerLogging.h> 8 #include <AppInstallerStrings.h> 9 #include <winget/ILifetimeWatcher.h> 10 #include <winget/Security.h> 11 #include <winrt/Microsoft.Management.Configuration.SetProcessorFactory.h> 12 13 using namespace winrt::Windows::Foundation; 14 using namespace winrt::Microsoft::Management::Configuration; 15 using namespace winrt::Windows::Storage; 16 17 namespace AppInstaller::CLI::ConfigurationRemoting 18 { 19 namespace anonymous 20 { 21 #ifndef AICLI_DISABLE_TEST_HOOKS 22 constexpr std::wstring_view EnableTestModeTestGuid = L"1e62d683-2999-44e7-81f7-6f8f35e8d731"; 23 constexpr std::wstring_view ForceHighIntegrityLevelUnitsTestGuid = L"f698d20f-3584-4f28-bc75-28037e08e651"; 24 constexpr std::wstring_view EnableRestrictedIntegrityLevelTestGuid = L"5cae3226-185f-4289-815c-3c089d238dc6"; 25 26 // Checks the configuration set metadata for a specific test guid that controls the behavior flow. 27 bool GetConfigurationSetMetadataOverride(const ConfigurationSet& configurationSet, const std::wstring_view& testGuid) 28 { 29 auto metadataOverride = configurationSet.Metadata().TryLookup(testGuid); 30 if (metadataOverride) 31 { 32 auto metadataOverrideProperty = metadataOverride.try_as<IPropertyValue>(); 33 if (metadataOverrideProperty && metadataOverrideProperty.Type() == PropertyType::Boolean) 34 { 35 return metadataOverrideProperty.GetBoolean(); 36 } 37 } 38 39 return false; 40 } 41 #endif 42 43 // This is implemented completely in the packaged context for now, if we want to make it more configurable, we will probably want to move it to configuration and 44 // have this implementation leverage that one with an event handler for the packaged specifics. 45 // TODO: Add SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties and pass values along to sets on creation 46 // In turn, any properties must only be set via the command line (or eventual UI requests to the user). 47 struct DynamicFactory : winrt::implements<DynamicFactory, IConfigurationSetProcessorFactory, SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties, Collections::IMap<winrt::hstring, winrt::hstring>, winrt::cloaked<WinRT::ILifetimeWatcher>>, WinRT::LifetimeWatcherBase 48 { 49 DynamicFactory(ProcessorEngine processorEngine); 50 51 IConfigurationSetProcessor CreateSetProcessor(const ConfigurationSet& configurationSet); 52 53 winrt::event_token Diagnostics(const EventHandler<IDiagnosticInformation>& handler); 54 void Diagnostics(const winrt::event_token& token) noexcept; 55 56 DiagnosticLevel MinimumLevel(); 57 void MinimumLevel(DiagnosticLevel value); 58 59 HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); 60 61 IConfigurationSetProcessorFactory& DefaultFactory(); 62 63 void SendDiagnostics(const IDiagnosticInformation& information); 64 65 Collections::IVectorView<winrt::hstring> AdditionalModulePaths() const 66 { 67 THROW_HR(E_NOTIMPL); 68 } 69 70 void AdditionalModulePaths(const Collections::IVectorView<winrt::hstring>&) 71 { 72 THROW_HR(E_NOTIMPL); 73 } 74 75 SetProcessorFactory::PwshConfigurationProcessorPolicy Policy() const 76 { 77 THROW_HR(E_NOTIMPL); 78 } 79 80 void Policy(SetProcessorFactory::PwshConfigurationProcessorPolicy) 81 { 82 THROW_HR(E_NOTIMPL); 83 } 84 85 SetProcessorFactory::PwshConfigurationProcessorLocation Location() const 86 { 87 return m_location; 88 } 89 90 void Location(SetProcessorFactory::PwshConfigurationProcessorLocation value) 91 { 92 auto pwshFactory = m_defaultRemoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>(); 93 pwshFactory.Location(value); 94 m_location = value; 95 } 96 97 winrt::hstring CustomLocation() const 98 { 99 return m_customLocation; 100 } 101 102 void CustomLocation(winrt::hstring value) 103 { 104 auto pwshFactory = m_defaultRemoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>(); 105 pwshFactory.CustomLocation(value); 106 m_customLocation = value; 107 } 108 109 // Implement a subset of IMap to enable property bag semantics 110 uint32_t Size() { THROW_HR(E_NOTIMPL); } 111 void Clear() { THROW_HR(E_NOTIMPL); } 112 Collections::IMapView<winrt::hstring, winrt::hstring> GetView() { THROW_HR(E_NOTIMPL); } 113 bool HasKey(winrt::hstring) { THROW_HR(E_NOTIMPL); } 114 void Remove(winrt::hstring) { THROW_HR(E_NOTIMPL); } 115 116 bool Insert(winrt::hstring key, winrt::hstring value) 117 { 118 auto result = m_defaultRemoteFactory.as<Collections::IMap<winrt::hstring, winrt::hstring>>().Insert(key, value); 119 m_factoryMapValues[key] = value; 120 return result; 121 } 122 123 winrt::hstring Lookup(winrt::hstring key) 124 { 125 return m_defaultRemoteFactory.as<Collections::IMap<winrt::hstring, winrt::hstring>>().Lookup(key); 126 } 127 128 ProcessorEngine Engine() const 129 { 130 return m_processorEngine; 131 } 132 133 std::optional<winrt::hstring> GetFactoryMapValue(winrt::hstring key) 134 { 135 auto itr = m_factoryMapValues.find(key); 136 return itr != m_factoryMapValues.end() ? std::make_optional(itr->second) : std::nullopt; 137 } 138 139 private: 140 IConfigurationSetProcessorFactory m_defaultRemoteFactory; 141 winrt::event<EventHandler<IDiagnosticInformation>> m_diagnostics; 142 IConfigurationSetProcessorFactory::Diagnostics_revoker m_factoryDiagnosticsEventRevoker; 143 std::mutex m_diagnosticsMutex; 144 DiagnosticLevel m_minimumLevel = DiagnosticLevel::Informational; 145 SetProcessorFactory::PwshConfigurationProcessorLocation m_location = SetProcessorFactory::PwshConfigurationProcessorLocation::Default; 146 winrt::hstring m_customLocation; 147 ProcessorEngine m_processorEngine; 148 std::map<winrt::hstring, winrt::hstring> m_factoryMapValues; 149 }; 150 151 struct DynamicProcessorInfo 152 { 153 IConfigurationSetProcessorFactory Factory; 154 IConfigurationSetProcessor Processor; 155 IConfigurationSetProcessorFactory::Diagnostics_revoker DiagnosticsEventRevoker; 156 }; 157 158 struct DynamicSetProcessor : winrt::implements<DynamicSetProcessor, IConfigurationSetProcessor, IFindUnitProcessorsSetProcessor> 159 { 160 using ProcessorMap = std::map<Security::IntegrityLevel, DynamicProcessorInfo>; 161 162 DynamicSetProcessor(winrt::com_ptr<DynamicFactory> dynamicFactory, IConfigurationSetProcessor defaultRemoteSetProcessor, const ConfigurationSet& configurationSet) : 163 m_dynamicFactory(std::move(dynamicFactory)), m_configurationSet(configurationSet) 164 { 165 #ifndef AICLI_DISABLE_TEST_HOOKS 166 if (m_configurationSet) 167 { 168 m_enableTestMode = GetConfigurationSetMetadataOverride(m_configurationSet, EnableTestModeTestGuid); 169 m_enableRestrictedIntegrityLevel = GetConfigurationSetMetadataOverride(m_configurationSet, EnableRestrictedIntegrityLevelTestGuid); 170 m_forceHighIntegrityLevelUnits = GetConfigurationSetMetadataOverride(m_configurationSet, ForceHighIntegrityLevelUnitsTestGuid); 171 } 172 173 m_currentIntegrityLevel = m_enableTestMode ? Security::IntegrityLevel::Medium : Security::GetEffectiveIntegrityLevel(); 174 #else 175 m_currentIntegrityLevel = Security::GetEffectiveIntegrityLevel(); 176 #endif 177 178 m_setIntegrityLevel = m_currentIntegrityLevel; 179 180 if (m_configurationSet) 181 { 182 m_setIntegrityLevel = SecurityContextToIntegrityLevel(m_configurationSet.Environment().Context()); 183 184 // Check for multiple integrity level requirements 185 bool multipleIntegrityLevels = false; 186 bool higherIntegrityLevelsThanCurrent = false; 187 for (const auto& environment : m_configurationSet.GetUnitEnvironments()) 188 { 189 auto integrityLevel = SecurityContextToIntegrityLevel(environment.Context()); 190 if (integrityLevel != m_currentIntegrityLevel) 191 { 192 multipleIntegrityLevels = true; 193 194 if (ToIntegral(m_currentIntegrityLevel) < ToIntegral(integrityLevel)) 195 { 196 higherIntegrityLevelsThanCurrent = true; 197 break; 198 } 199 } 200 } 201 202 // Prevent supplied parameters from crossing integrity levels 203 for (const auto& parameter : m_configurationSet.Parameters()) 204 { 205 if (parameter.ProvidedValue() != nullptr) 206 { 207 THROW_HR_IF(WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY, higherIntegrityLevelsThanCurrent || (multipleIntegrityLevels && parameter.IsSecure())); 208 } 209 } 210 } 211 212 m_setProcessors.emplace(m_currentIntegrityLevel, DynamicProcessorInfo{ m_dynamicFactory->DefaultFactory(), defaultRemoteSetProcessor}); 213 } 214 215 IConfigurationUnitProcessorDetails GetUnitProcessorDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) 216 { 217 // Always get processor details from the current integrity level 218 return m_setProcessors[m_currentIntegrityLevel].Processor.GetUnitProcessorDetails(unit, detailFlags); 219 } 220 221 // Creates a configuration unit processor for the given unit. 222 IConfigurationUnitProcessor CreateUnitProcessor(const ConfigurationUnit& unit) 223 { 224 // Determine and create set processors for all required integrity levels. 225 // Doing this here avoids creating them if the only call is going to be for details (ex. `configure show`) 226 std::call_once(m_createUnitSetProcessorsOnce, 227 [&]() 228 { 229 if (m_configurationSet) 230 { 231 for (const auto& environment : m_configurationSet.GetUnitEnvironments()) 232 { 233 Security::IntegrityLevel requiredIntegrityLevel = SecurityContextToIntegrityLevel(environment.Context()); 234 235 if (m_setProcessors.find(requiredIntegrityLevel) == m_setProcessors.end()) 236 { 237 CreateSetProcessorForIntegrityLevel(requiredIntegrityLevel); 238 } 239 } 240 } 241 }); 242 243 // Create set and unit processor for current unit. 244 #ifndef AICLI_DISABLE_TEST_HOOKS 245 Security::IntegrityLevel requiredIntegrityLevel = m_forceHighIntegrityLevelUnits ? Security::IntegrityLevel::High : GetIntegrityLevelForUnit(unit); 246 #else 247 Security::IntegrityLevel requiredIntegrityLevel = GetIntegrityLevelForUnit(unit); 248 #endif 249 250 auto itr = m_setProcessors.find(requiredIntegrityLevel); 251 if (itr == m_setProcessors.end()) 252 { 253 THROW_WIN32_IF_MSG(ERROR_NOT_SUPPORTED, !m_configurationSet, "Using configuration unit integrity level other than current level without a configuration set is not supported."); 254 itr = CreateSetProcessorForIntegrityLevel(requiredIntegrityLevel); 255 } 256 257 return itr->second.Processor.CreateUnitProcessor(unit); 258 } 259 260 Collections::IVector<IConfigurationUnitProcessorDetails> FindUnitProcessors(const FindUnitProcessorsOptions& findOptions) 261 { 262 IFindUnitProcessorsSetProcessor findUnitProcessorsSetProcessor; 263 264 if (m_setProcessors[m_currentIntegrityLevel].Processor.try_as<IFindUnitProcessorsSetProcessor>(findUnitProcessorsSetProcessor)) 265 { 266 return findUnitProcessorsSetProcessor.FindUnitProcessors(findOptions); 267 } 268 else 269 { 270 AICLI_LOG(Config, Error, << "Set Processor does not support FindUnitProcessors operation"); 271 THROW_HR(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR); 272 } 273 } 274 275 private: 276 // Converts the string representation of SecurityContext to the target integrity level for this instance 277 Security::IntegrityLevel SecurityContextToIntegrityLevel(SecurityContext securityContext) 278 { 279 switch (securityContext) 280 { 281 case SecurityContext::Current: 282 return m_setIntegrityLevel; 283 case SecurityContext::Restricted: 284 #ifndef AICLI_DISABLE_TEST_HOOKS 285 if (m_enableRestrictedIntegrityLevel) 286 { 287 return Security::IntegrityLevel::Medium; 288 } 289 else 290 #endif 291 { 292 // Not supporting elevated callers downgrading at the moment. 293 THROW_WIN32(ERROR_NOT_SUPPORTED); 294 295 // Technically this means the default level of the user token, so if UAC is disabled it would be the only integrity level (aka current). 296 // return Security::IntegrityLevel::Medium; 297 } 298 case SecurityContext::Elevated: 299 return Security::IntegrityLevel::High; 300 default: 301 THROW_WIN32(ERROR_NOT_SUPPORTED); 302 } 303 } 304 305 // Gets the integrity level that the given unit should be run at 306 Security::IntegrityLevel GetIntegrityLevelForUnit(const ConfigurationUnit& unit) 307 { 308 return SecurityContextToIntegrityLevel(unit.Environment().Context()); 309 } 310 311 // Serializes the set properties to be sent to the remote server 312 std::string SerializeSetProperties() 313 { 314 Json::Value json{ Json::ValueType::objectValue }; 315 316 json["path"] = winrt::to_string(m_configurationSet.Path()); 317 318 std::string locationString; 319 switch (m_dynamicFactory->Location()) 320 { 321 case SetProcessorFactory::PwshConfigurationProcessorLocation::AllUsers: 322 locationString = "AllUsers"; 323 break; 324 case SetProcessorFactory::PwshConfigurationProcessorLocation::CurrentUser: 325 locationString = "CurrentUser"; 326 break; 327 case SetProcessorFactory::PwshConfigurationProcessorLocation::Custom: 328 locationString = Utility::ConvertToUTF8(m_dynamicFactory->CustomLocation()); 329 break; 330 case SetProcessorFactory::PwshConfigurationProcessorLocation::Default: 331 break; 332 } 333 334 if (!locationString.empty()) 335 { 336 json["modulePath"] = locationString; 337 } 338 339 // Ensure that we always pass a path to the executable 340 if (m_dynamicFactory->Engine() == ProcessorEngine::DSCv3) 341 { 342 winrt::hstring dscExecutablePathPropertyName = ToHString(PropertyName::DscExecutablePath); 343 std::optional<winrt::hstring> dscExecutablePath = m_dynamicFactory->GetFactoryMapValue(dscExecutablePathPropertyName); 344 345 if (!dscExecutablePath) 346 { 347 dscExecutablePath = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePath)); 348 } 349 350 if (dscExecutablePath->empty()) 351 { 352 // This is backstop to prevent a case where dsc.exe not found. 353 AICLI_LOG(Config, Error, << "Could not find dsc.exe, it must be provided by the user."); 354 THROW_WIN32(ERROR_FILE_NOT_FOUND); 355 } 356 357 json["processorPath"] = Utility::ConvertToUTF8(dscExecutablePath.value()); 358 } 359 360 Json::StreamWriterBuilder writerBuilder; 361 writerBuilder.settings_["indentation"] = "\t"; 362 return Json::writeString(writerBuilder, json); 363 } 364 365 /// <summary> 366 /// Creates a separate configuration set containing high integrity units and returns the serialized string value. 367 /// </summary> 368 /// <returns>Serialized string value.</returns> 369 std::string SerializeHighIntegrityLevelSet() 370 { 371 ConfigurationSet highIntegritySet; 372 highIntegritySet.SchemaVersion(m_configurationSet.SchemaVersion()); 373 highIntegritySet.Metadata(m_configurationSet.Metadata()); 374 highIntegritySet.Parameters(m_configurationSet.Parameters()); 375 highIntegritySet.Variables(m_configurationSet.Variables()); 376 377 std::vector<ConfigurationUnit> highIntegrityUnits; 378 auto units = m_configurationSet.Units(); 379 380 for (auto unit : units) 381 { 382 if (unit.IsActive() && GetIntegrityLevelForUnit(unit) == Security::IntegrityLevel::High) 383 { 384 highIntegrityUnits.emplace_back(unit); 385 } 386 } 387 388 highIntegritySet.Units(std::move(highIntegrityUnits)); 389 390 // Serialize high integrity set and return output string. 391 Streams::InMemoryRandomAccessStream memoryStream; 392 highIntegritySet.Serialize(memoryStream); 393 394 Streams::DataReader reader(memoryStream.GetInputStreamAt(0)); 395 THROW_HR_IF(E_UNEXPECTED, memoryStream.Size() > std::numeric_limits<uint32_t>::max()); 396 uint32_t streamSize = (uint32_t)memoryStream.Size(); 397 std::vector<uint8_t> bytes; 398 bytes.resize(streamSize); 399 reader.LoadAsync(streamSize); 400 reader.ReadBytes(bytes); 401 reader.DetachStream(); 402 memoryStream.Close(); 403 404 return { bytes.begin(), bytes.end() }; 405 } 406 407 ProcessorMap::iterator CreateSetProcessorForIntegrityLevel(Security::IntegrityLevel integrityLevel) 408 { 409 IConfigurationSetProcessorFactory factory; 410 IConfigurationSetProcessorFactory::Diagnostics_revoker factoryDiagnosticsEventRevoker; 411 412 // If we got here, the only option is that the current integrity level is not High. 413 if (integrityLevel == Security::IntegrityLevel::High) 414 { 415 bool useRunAs = true; 416 #ifndef AICLI_DISABLE_TEST_HOOKS 417 useRunAs = !m_enableTestMode; 418 #endif 419 420 factory = CreateOutOfProcessFactory(m_dynamicFactory->Engine(), useRunAs, SerializeSetProperties(), SerializeHighIntegrityLevelSet()); 421 } 422 else 423 { 424 THROW_WIN32(ERROR_NOT_SUPPORTED); 425 } 426 427 if (factory) 428 { 429 factory.MinimumLevel(m_dynamicFactory->MinimumLevel()); 430 factoryDiagnosticsEventRevoker = factory.Diagnostics(winrt::auto_revoke, 431 [weak_this{ get_weak() }](const IInspectable&, const IDiagnosticInformation& information) 432 { 433 if (auto strong_this{ weak_this.get() }) 434 { 435 strong_this->m_dynamicFactory->SendDiagnostics(information); 436 } 437 }); 438 439 winrt::hstring propertyName = ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceEnabled); 440 if (auto propertyValue = m_dynamicFactory->GetFactoryMapValue(propertyName)) 441 { 442 factory.as<Collections::IMap<winrt::hstring, winrt::hstring>>().Insert(propertyName, propertyValue.value()); 443 } 444 } 445 446 return m_setProcessors.emplace(integrityLevel, DynamicProcessorInfo{ factory, factory.CreateSetProcessor(m_configurationSet), std::move(factoryDiagnosticsEventRevoker) }).first; 447 } 448 449 winrt::com_ptr<DynamicFactory> m_dynamicFactory; 450 Security::IntegrityLevel m_currentIntegrityLevel; 451 Security::IntegrityLevel m_setIntegrityLevel; 452 ProcessorMap m_setProcessors; 453 ConfigurationSet m_configurationSet; 454 std::once_flag m_createUnitSetProcessorsOnce; 455 456 #ifndef AICLI_DISABLE_TEST_HOOKS 457 bool m_enableTestMode = false; 458 bool m_enableRestrictedIntegrityLevel = false; 459 bool m_forceHighIntegrityLevelUnits = false; 460 #endif 461 }; 462 463 DynamicFactory::DynamicFactory(ProcessorEngine processorEngine) 464 { 465 m_processorEngine = processorEngine; 466 m_defaultRemoteFactory = CreateOutOfProcessFactory(processorEngine); 467 468 if (m_defaultRemoteFactory) 469 { 470 m_factoryDiagnosticsEventRevoker = m_defaultRemoteFactory.Diagnostics(winrt::auto_revoke, 471 [weak_this{ get_weak() }](const IInspectable&, const IDiagnosticInformation& information) 472 { 473 if (auto strong_this{ weak_this.get() }) 474 { 475 strong_this->SendDiagnostics(information); 476 } 477 }); 478 } 479 } 480 481 IConfigurationSetProcessor DynamicFactory::CreateSetProcessor(const ConfigurationSet& configurationSet) 482 { 483 return winrt::make<DynamicSetProcessor>(get_strong(), m_defaultRemoteFactory.CreateSetProcessor(configurationSet), configurationSet); 484 } 485 486 winrt::event_token DynamicFactory::Diagnostics(const EventHandler<IDiagnosticInformation>& handler) 487 { 488 return m_diagnostics.add(handler); 489 } 490 491 void DynamicFactory::Diagnostics(const winrt::event_token& token) noexcept 492 { 493 m_diagnostics.remove(token); 494 } 495 496 DiagnosticLevel DynamicFactory::MinimumLevel() 497 { 498 return m_minimumLevel; 499 } 500 501 void DynamicFactory::MinimumLevel(DiagnosticLevel value) 502 { 503 m_minimumLevel = value; 504 505 if (m_defaultRemoteFactory) 506 { 507 m_defaultRemoteFactory.MinimumLevel(value); 508 } 509 } 510 511 HRESULT STDMETHODCALLTYPE DynamicFactory::SetLifetimeWatcher(IUnknown* watcher) 512 { 513 return WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); 514 } 515 516 IConfigurationSetProcessorFactory& DynamicFactory::DefaultFactory() 517 { 518 return m_defaultRemoteFactory; 519 } 520 521 void DynamicFactory::SendDiagnostics(const IDiagnosticInformation& information) try 522 { 523 if (information.Level() >= m_minimumLevel) 524 { 525 std::lock_guard<std::mutex> lock{ m_diagnosticsMutex }; 526 m_diagnostics(*this, information); 527 } 528 } 529 // While diagnostics can be important, a failure to send them should not cause additional issues. 530 catch (...) {} 531 } 532 533 winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateDynamicRuntimeFactory(ProcessorEngine processorEngine) 534 { 535 return winrt::make<anonymous::DynamicFactory>(processorEngine); 536 } 537 }