Repository navigation
Add dotnetup <> install under admin mode MVP
#56664
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
Open
nagilson
wants to merge
50
commits into
dotnet:release/dnup
Choose a base branch
from
nagilson:nagilson-dotnetup-admin-mvp
base: release/dnup
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+10,129
−626
Open
Changes from 12 commits
Commits
Show all changes
50 commits
Select commit
Hold shift + click to select a range
476a16d
add initial admin mode .md file
nagilson a11f9e8
WIP - initial admin spec; hand-written.
nagilson d82c305
cleanup and sync with meeting feedback
nagilson 6b8be92
Futher expansion upon dotnetup admin edge cases or potential issues
nagilson 0467f68
block unsigned dailies in admin mode by default
nagilson 815bed7
consider muxer replacement option
nagilson 6b1a39f
reg key should enforce admin mode rather than user local config file
nagilson 2ca2542
fix admin mode to be able to revert it with uac
nagilson d393b2a
admin mode shuold set block unsigned by default - ugh claude made thi…
nagilson 7070a6b
copilot fb
nagilson 014af5c
add learnings from meeting with @joeloff
nagilson 1cad672
dotnetup: add admin access mode contracts
nagilson 63b4199
dotnetup: tag telemetry with access mode
nagilson b20d2f2
dotnetup: block unsupported commands in admin mode
nagilson c41c672
dotnetup: switch admin mode with init --mode and env set
nagilson f0b5e27
dotnetup: install Windows installer bundles in admin mode
nagilson 2d1ac81
dotnetup: check admin installs without loading hostfxr in process
nagilson eea1f45
dotnetup: harden admin installer verification and cancellation
nagilson 3aad05c
dotnetup: keep self update and setup working across admin mode changes
nagilson 16ee132
dotnetup: document admin mode MVP
nagilson fc43e90
env script and legacy print env script shouldn't cause shell failure
nagilson 604edaa
dotnetup: localize admin mode guidance and simplify gate diagnostics
nagilson dee539d
dotnetup: hide migration choices while admin mode is selected
nagilson 0edd196
dotnetup: preserve the explicit untracked install option
nagilson 4a0d2a6
dotnetup: remove unused legacy framework branches from policy store
nagilson 887dc28
dotnetup: ignore user data overrides for installer bundle caches
nagilson 00fadfc
dotnetup: consolidate admin test fixtures and command coverage
nagilson 86fea34
Dont use user controllable directory for admin installs if dotnetup i…
nagilson fa84796
dotnetup: annotate the Authenticode verifier with its supported platform
nagilson 3db2c88
dotnetup: report a specific error for non-.exe installer downloads
nagilson 3e78ae2
dotnetup: check only host/fxr version directories for hostfxr
nagilson 1535a03
dotnetup: localize installer orchestration messages
nagilson 7852f07
dotnetup: clarify how env set leaves admin mode
nagilson a16c9c4
dotnetup: remove admin installer details from the signature verificat…
nagilson 3754e0a
dotnetup: document the architecture assumption in admin root resolution
nagilson 16a4bcf
dotnetup: share host-reported install checks between validators
nagilson e1effff
dotnetup: keep the admin-mode --untracked check in the install command
nagilson 05fe8bc
fix style gates
nagilson 44a57ec
Merge branch 'release/dnup' into nagilson-dotnetup-admin-mvp
nagilson 1a94289
dotnetup: inject the machine policy instead of static test hooks
nagilson 415fad1
dotnetup: complete policy injection coverage
nagilson 4bc05bf
dotnetup: remove redundant bundle tracking assertion
nagilson cb30dcc
Fix dynamic dotnetup test assets after net12 transition
nagilson a35b6ab
Localize Windows installer bundle messages
nagilson e2fd7cd
Use native hostfxr invocation without parsing --list-runtime but with…
nagilson 89259ef
Fix CI to not require sdk to build dotnetup to get sdks
nagilson 3bd97cd
Simplify output instructions for admin mode
nagilson 9e93fe3
Cancellation message doesnt display if its on stderr
nagilson 6b9329d
Shorten ctrl + c interrupt + allow switching out with init --mode
nagilson c167eb9
Isolate installer cancellation rendering tests from CI enrichment
nagilson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
274 changes: 274 additions & 0 deletions
274
documentation/general/dotnetup/designs/dotnetup-admin-mode.md
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
src/Installer/Microsoft.Dotnet.Installation/Internal/DotnetupPolicy.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Runtime.Versioning; | ||
| #if NET | ||
| using Microsoft.Win32; | ||
| #endif | ||
|
|
||
| namespace Microsoft.Dotnet.Installation.Internal; | ||
|
|
||
| /// <summary> | ||
| /// Reads and writes the machine-wide dotnetup policy flags. | ||
| /// </summary> | ||
| internal interface IDotnetupPolicyStore | ||
| { | ||
| /// <summary> | ||
| /// Returns true when the named flag is set to a nonzero value. Returns false when the value is | ||
| /// missing, unreadable, or not a DWORD. | ||
| /// </summary> | ||
| bool ReadFlag(string valueName); | ||
|
|
||
| /// <summary> | ||
| /// Writes each flag as a DWORD 1 (true) or 0 (false), creating the policy key if needed. | ||
| /// Throws when the values cannot be written. | ||
| /// </summary> | ||
| void WriteFlags(IReadOnlyDictionary<string, bool> values); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Machine-wide dotnetup policy stored as independent <c>REG_DWORD</c> values under | ||
| /// <c>HKLM\SOFTWARE\Policies\Microsoft\dotnet\Dotnetup</c>. Admin mode is Windows-only and is only | ||
| /// ever read from this policy store, never from the per-user dotnetup configuration. | ||
| /// </summary> | ||
| internal static class DotnetupPolicy | ||
| { | ||
| internal const string WindowsPolicyKey = @"SOFTWARE\Policies\Microsoft\dotnet\Dotnetup"; | ||
| internal const string AdminModeValueName = "AdminMode"; | ||
| internal const string BlockUnsignedDownloadsValueName = "BlockUnsignedDownloads"; | ||
|
|
||
| /// <summary> | ||
| /// The store backing the policy. Defaults to the Windows registry; tests may replace it and must | ||
| /// call <see cref="ResetStoreForTesting"/> afterwards. | ||
| /// </summary> | ||
| internal static IDotnetupPolicyStore Store { get; set; } = CreateDefaultStore(); | ||
|
|
||
| internal static void ResetStoreForTesting() => Store = CreateDefaultStore(); | ||
|
nagilson marked this conversation as resolved.
Outdated
|
||
|
|
||
| /// <summary>Returns true when the machine policy enables admin mode. Always false off Windows.</summary> | ||
| public static bool IsAdminModeEnabled() => | ||
| OperatingSystem.IsWindows() && Store.ReadFlag(AdminModeValueName); | ||
|
|
||
| /// <summary>Returns true when the Windows registry policy blocks unsigned downloads.</summary> | ||
| public static bool IsUnsignedDownloadBlockedByRegistry() => | ||
| Store.ReadFlag(BlockUnsignedDownloadsValueName); | ||
|
|
||
| /// <summary>Enables admin mode, which also blocks unsigned downloads.</summary> | ||
| public static void EnableAdminMode() => | ||
| Store.WriteFlags(new Dictionary<string, bool> | ||
| { | ||
| [AdminModeValueName] = true, | ||
| [BlockUnsignedDownloadsValueName] = true, | ||
| }); | ||
|
|
||
| /// <summary> | ||
| /// Disables admin mode. The unsigned download policy is left unchanged because IT may manage it | ||
| /// independently. | ||
| /// </summary> | ||
| public static void DisableAdminMode() => | ||
| Store.WriteFlags(new Dictionary<string, bool> | ||
| { | ||
| [AdminModeValueName] = false, | ||
| }); | ||
|
|
||
| private static IDotnetupPolicyStore CreateDefaultStore() => | ||
| OperatingSystem.IsWindows() ? new WindowsRegistryPolicyStore() : new UnsupportedPlatformPolicyStore(); | ||
|
|
||
| [SupportedOSPlatform("windows")] | ||
| private sealed class WindowsRegistryPolicyStore : IDotnetupPolicyStore | ||
|
nagilson marked this conversation as resolved.
Outdated
|
||
| { | ||
| public bool ReadFlag(string valueName) | ||
| { | ||
| #if NET | ||
| try | ||
| { | ||
| using RegistryKey baseKey = OpenLocalMachine(); | ||
| using RegistryKey? key = baseKey.OpenSubKey(WindowsPolicyKey); | ||
| return key?.GetValue(valueName) is int value && value != 0; | ||
| } | ||
| catch | ||
| { | ||
| return false; | ||
| } | ||
| #else | ||
| return false; | ||
| #endif | ||
| } | ||
|
|
||
| public void WriteFlags(IReadOnlyDictionary<string, bool> values) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(values); | ||
| #if NET | ||
|
nagilson marked this conversation as resolved.
Outdated
|
||
| using RegistryKey baseKey = OpenLocalMachine(); | ||
| using RegistryKey key = baseKey.CreateSubKey(WindowsPolicyKey, writable: true) | ||
| ?? throw new InvalidOperationException($@"Unable to open HKLM\{WindowsPolicyKey} for writing."); | ||
| foreach (var (valueName, enabled) in values) | ||
| { | ||
| key.SetValue(valueName, enabled ? 1 : 0, RegistryValueKind.DWord); | ||
| } | ||
| #else | ||
| throw new PlatformNotSupportedException(); | ||
| #endif | ||
| } | ||
|
|
||
| #if NET | ||
| private static RegistryKey OpenLocalMachine() => | ||
| RegistryKey.OpenBaseKey( | ||
| RegistryHive.LocalMachine, | ||
| Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32); | ||
| #endif | ||
| } | ||
|
|
||
| private sealed class UnsupportedPlatformPolicyStore : IDotnetupPolicyStore | ||
| { | ||
| public bool ReadFlag(string valueName) => false; | ||
|
|
||
| public void WriteFlags(IReadOnlyDictionary<string, bool> values) => | ||
| throw new PlatformNotSupportedException("dotnetup machine policy is only supported on Windows."); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Microsoft.Dotnet.Installation.Internal; | ||
|
|
||
| namespace Microsoft.DotNet.Tools.Bootstrapper; | ||
|
|
||
| /// <summary>Where the effective access mode came from.</summary> | ||
| internal enum AccessModeSource | ||
| { | ||
| /// <summary>No access mode is configured.</summary> | ||
| Unset, | ||
|
|
||
| /// <summary>The access mode recorded in the per-user dotnetup config.</summary> | ||
| UserConfig, | ||
|
|
||
| /// <summary>The machine policy (admin mode).</summary> | ||
| Policy, | ||
| } | ||
|
|
||
| /// <summary>The access mode dotnetup operates under, and its source.</summary> | ||
| internal sealed record EffectiveAccessMode(DotnetAccessMode? Mode, AccessModeSource Source) | ||
| { | ||
| public bool IsAdmin => Mode == DotnetAccessMode.Admin; | ||
|
|
||
| public static EffectiveAccessMode Unset { get; } = new(null, AccessModeSource.Unset); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Resolves the effective access mode. The Windows machine policy enables admin mode and takes | ||
| /// precedence over the per-user config, which can never select admin mode. | ||
| /// </summary> | ||
| internal static class AccessModeResolver | ||
| { | ||
| /// <summary>When non-null, replaces resolution. Tests must reset it to <c>null</c>.</summary> | ||
| internal static Func<EffectiveAccessMode>? OverrideForTesting { get; set; } | ||
|
nagilson marked this conversation as resolved.
Outdated
|
||
|
|
||
| /// <summary>Resolves the effective access mode. Never throws and never writes output.</summary> | ||
| public static EffectiveAccessMode Resolve() | ||
| { | ||
| var overrideFn = OverrideForTesting; | ||
| if (overrideFn is not null) | ||
| { | ||
| return overrideFn(); | ||
| } | ||
|
|
||
| if (OperatingSystem.IsWindows() && DotnetupPolicy.IsAdminModeEnabled()) | ||
| { | ||
| return new EffectiveAccessMode(DotnetAccessMode.Admin, AccessModeSource.Policy); | ||
| } | ||
|
|
||
| DotnetAccessMode? configured = TryReadUserConfigAccessMode(); | ||
| return configured is DotnetAccessMode mode && mode != DotnetAccessMode.Admin | ||
| ? new EffectiveAccessMode(mode, AccessModeSource.UserConfig) | ||
| : EffectiveAccessMode.Unset; | ||
| } | ||
|
|
||
| private static DotnetAccessMode? TryReadUserConfigAccessMode() | ||
| { | ||
| try | ||
| { | ||
| return DotnetupConfig.ReadAccessModeSilently(); | ||
| } | ||
| catch | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.CommandLine; | ||
|
|
||
| namespace Microsoft.DotNet.Tools.Bootstrapper; | ||
|
|
||
| /// <summary> | ||
| /// Describes whether a command can run in admin mode and which of its options and arguments are | ||
| /// permitted there. Commands are unsupported unless they opt in (default deny). | ||
| /// </summary> | ||
| internal sealed class AdminModeSupport | ||
| { | ||
| private AdminModeSupport(bool isSupported, bool allowsAnySymbol, IReadOnlySet<Symbol> allowedSymbols) | ||
| { | ||
| IsSupported = isSupported; | ||
| AllowsAnySymbol = allowsAnySymbol; | ||
| AllowedSymbols = allowedSymbols; | ||
| } | ||
|
|
||
| /// <summary>The command cannot run in admin mode.</summary> | ||
| public static AdminModeSupport Unsupported { get; } = new(false, false, CreateSet([])); | ||
|
|
||
| /// <summary>The command runs in admin mode with any of its options and arguments.</summary> | ||
| public static AdminModeSupport AnySymbol { get; } = new(true, true, CreateSet([])); | ||
|
|
||
| /// <summary>The command runs in admin mode, but only with the given options and arguments.</summary> | ||
| public static AdminModeSupport Allow(params Symbol[] symbols) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(symbols); | ||
| return new(true, false, CreateSet(symbols)); | ||
| } | ||
|
|
||
| public bool IsSupported { get; } | ||
|
|
||
| public bool AllowsAnySymbol { get; } | ||
|
|
||
| /// <summary>The permitted options and arguments, compared by reference.</summary> | ||
| public IReadOnlySet<Symbol> AllowedSymbols { get; } | ||
|
|
||
| /// <summary>Returns true when <paramref name="symbol"/> may be used in admin mode.</summary> | ||
| public bool Allows(Symbol symbol) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(symbol); | ||
| return IsSupported && (AllowsAnySymbol || AllowedSymbols.Contains(symbol)); | ||
| } | ||
|
|
||
| private static HashSet<Symbol> CreateSet(Symbol[] symbols) | ||
| { | ||
| var set = new HashSet<Symbol>(ReferenceEqualityComparer.Instance); | ||
| foreach (var symbol in symbols) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(symbol, nameof(symbols)); | ||
| set.Add(symbol); | ||
| } | ||
|
|
||
| return set; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.