-
Notifications
You must be signed in to change notification settings - Fork 7.1k
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
Rewrite convertfrom sddlstring in csharp #3936
Rewrite convertfrom sddlstring in csharp #3936
Conversation
@@ -25,6 +25,7 @@ | |||
<PackageReference Include="System.Security.AccessControl" Version="4.4.0-preview1-25302-01" /> | |||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="4.4.0-preview1-25302-01" /> | |||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0-preview1-25302-01" /> | |||
<PackageReference Include="System.Threading.AccessControl" Version="4.4.0-preview1-25302-01" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is required by types moved from System.Security.AccessControl to System.Threading.AccessControl (System.Security.AccessControl.MutexRights), am I allowed to add new references here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks to me the package System.Threading.AccessControl
is required by Microsoft.PowerShell.Commands.Utility
instead of System.Management.Automation
. If that's the case, this PackageReference
entry should be added to Microosft.PowerShell.Commands.Utility
instead. And please remove the same entry from Microsoft.PowerShell.SDK.csproj
.
@iSazonov if you have time, could you please review this PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave a comment
[ValidateSet( | ||
"FileSystemRights", "RegistryRights", "ActiveDirectoryRights", | ||
"MutexRights", "SemaphoreRights", "CryptoKeyRights", | ||
"EventWaitHandleRights")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please create C# constants for this strings because they're using in other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
used custom enum instead
protected override void BeginProcessing() | ||
{ | ||
#if CORECLR | ||
if (Type == "CryptoKeyRights" || Type == "ActiveDirectoryRights") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now ActiveDirectoryRights is in .Net Core 2.0 https://apisof.net/catalog/System.DirectoryServices.ActiveDirectoryRights
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, done
{ | ||
return sid.Translate(typeof(NTAccount)).ToString(); | ||
} | ||
catch {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we should write an error for user.
{ "FileSystemRights", typeof(System.Security.AccessControl.FileSystemRights) }, | ||
{ "RegistryRights", typeof(System.Security.AccessControl.RegistryRights) }, | ||
#if !CORECLR | ||
{ "ActiveDirectoryRights", typeof(System.DirectoryServices.ActiveDirectoryRights) }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment above about "ActiveDirectoryRights".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
/// </summary> | ||
private List<string> GetAccessRights(long accessMask, string type) | ||
{ | ||
IDictionary<string, Type> rightTypes = new Dictionary<string, Type> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use OrderedDictionary.
rightTypes
is good candidate for static cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added static cache field
{ | ||
foreach (var accessFlag in Enum.GetNames(rightType)) | ||
{ | ||
long longKeyValue = (long)Enum.Parse(rightType, accessFlag); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, we re-evaluate Enum.GetNames and Enum.Parse again and again. We should refactor "GetAccessRights" to use cache(s).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could write a script to generate C# and avoid reflection entirely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lzybkr what kind of code generation script do you mean? t4 template?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I just meant a PowerShell script that generates switch statements or something like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added ps1 generator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lzybkr Will this require recompilation if there will are changes in CoreFX? We could initilize this in static cache on Begin step.
It seems so rare that the use of generation seems to be an unnecessary complication.
|
||
if (ace is QualifiedAce qualifiedAce) | ||
{ | ||
aceString += qualifiedAce.AceQualifier; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use StringBuilder because aceString
is changed multiple times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
return aceStrings; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add Newline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
/// <summary> | ||
/// '{0}' is not supported in this system. | ||
/// </summary> | ||
public static string TypeNotSupported { get { return UtilityCommonStrings.TypeNotSupported; } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we can directly use UtilityCommonStrings.TypeNotSupported
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed obsolete property
|
||
// Go through the entries in the different right types, and see if they apply to the | ||
// provided access mask. If they do, then add that to the result. | ||
foreach (var rightType in typesToExamine) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SteveL-MSFT @daxian-dbw We should make a conclusion about the algorithm. SDDL is always "object-specific" but we go through all "types". I believe that's a mistake. An user always know the object type (where he did get the SDDL), should set it explicit and the cmdlet must decode only against this type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this domain enough to be confident here - but in principle I fully agree with @iSazonov - the current algorithm is strange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lzybkr Could you please ping an expert in this domain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation for SDDL looks pretty good, I think if we have concerns about this algorithm, we can answer them ourselves, but I don't have time for that now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I re-read the document and made any tests - My conclusion is full discard the algorithm:
- From the docs:
Each type of securable object defines its own set of specific access rights and its own mapping of generic access rights. For information about the specific and generic access rights for each type of securable object, see the overview for that type of object.
- the conclusion is an SDDL has no meaning without object entity - the cmdlet should get the object type as argument.
- The cmdlet calls [Security.AccessControl.CommonSecurityDescriptor]::new($false,$false,$Sddl)
The constructor in Core FX
I tested the cmdlet with (1) [Security.AccessControl.CommonSecurityDescriptor]::new($false,$false,$Sddl) and (2) [Security.AccessControl.CommonSecurityDescriptor]::new($true,$false,$Sddl) for path "c:\test" (it is container) and got different results.
- the conclusion is the cmdlet should take into account
isContainer
(folders/containers) andisDS
(directory objects and containers).
-
CoreFX can throw exceptions (ArgumentException) - maybe the cmdlet should catch them and re-throw (put CoreFX exception in InnerException)
-
The cmdlet should have strongly typed output. In current PSCustomObject we should add "Type".
-
"Type" - we can define it as Enum ( remove unsupported member by #if !Unix ) and exclude ValidateSet at all.
-
We should add tests for all object types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@iSazonov I also have doubts about current method of converting ACE AccessMask into access flag enum based strings. There are situations when flag cannot be converted without additional logic i.e. FileSystemRights contains entries with duplicate flag values - some applies only for files, other only for containers. I guess that this won't be solved by requiring Type parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zacateras Please see (2) point from my conclusion.
•the conclusion is the cmdlet should take into account isContainer (folders/containers) and isDS (directory objects and containers).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@iSazonov Apart from fixing CommonSecurityDescriptor constructor call (by requiring isContainer and isDS flags) we should also take care about correct parsing of AccessMasks to enum names. FileSystemRights and ActiveDirectoryRights contain several names that use the same value, eg. FileSystemRights contain AppendData & CreateDirectories which have the same value (4). In consequence Directories with access flag (4) can get AppendData right, what is incorrect.
As a solution we can add custom enums (or at least custom mapping dictionaries) containing distinct subsets of allowed values - one for containers (ContainerSystemRights) and one for other objects (FileSystemRights). Alternatively we can discard CommonSecurityDescriptor/Right-Enum based parsing algorithm entirely and try to implement text parser using sddl docs. What do you think ?
@zacateras Thanks for your work! I have question about the algorithm that I addressed to PowerShell team - please wait the conclusion before continue. Also we haven't tests for the cmdlet - you should add its and pass CI before we can merge. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the contribution - I'll be very happy to see this one merged.
@@ -1,174 +0,0 @@ | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can git rm
this file now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this file name is referenced 3 times:
/src/Modules/map.json
(remove entry?)/src/System.Management.Automation/engine/InitialSessionState.cs
(replace with some other build result file?)/src/System.Management.Automation/security/Authenticode.cs
(replace with some other build result file?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching that.
Remove the entry in map.json
- that's perfectly safe.
I think you can change the other two references to Microsoft.PowerShell.Utility.psd1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
protected override void BeginProcessing() | ||
{ | ||
#if CORECLR | ||
if (Type == "CryptoKeyRights" || Type == "ActiveDirectoryRights") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ValidateSet
is case insensitive, so we would need a case insensitive comparison here too.
That said, maybe it's better to change the parameter type to an enum
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should add - we don't need this code at all - the ValidateSet
could simply be different under CORECLR
.
The PowerShell version of this command necessarily had runtime code to cover this case, but when we are building different binaries, we can do better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed ValidateSet, used custom enum instead
|
||
PSObject result = new PSObject(); | ||
|
||
result.Properties.Add(new PSNoteProperty("Owner", owner)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want strongly typed output instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following up on my own question and after playing around with this cmdlet a little, we definitely want strongly typed output.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, done
{ | ||
foreach (var accessFlag in Enum.GetNames(rightType)) | ||
{ | ||
long longKeyValue = (long)Enum.Parse(rightType, accessFlag); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could write a script to generate C# and avoid reflection entirely.
|
||
// Go through the entries in the different right types, and see if they apply to the | ||
// provided access mask. If they do, then add that to the result. | ||
foreach (var rightType in typesToExamine) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this domain enough to be confident here - but in principle I fully agree with @iSazonov - the current algorithm is strange.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add some tests? I just cloned this branch and a basic attempt to use this cmdlet failed:
PS C:\users\slee\repos\zacateras> $sddl = "O:BAG:BAD:(A;CI;CCDCLCSWRPWPRCWD;;;BA)(A;CI;CCDCRP;;;NS)(A;CI;CCDCRP;;;LS)(A;
CI;CCDCRP;;;AU)"
PS C:\users\slee\repos\zacateras> ConvertFrom-SddlString -Sddl $sddl
ConvertFrom-SddlString : Unable to cast object of type 'System.Security.AccessControl.FileSystemRights' to type
'System.Int64'.
At line:1 char:1
+ ConvertFrom-SddlString -Sddl $sddl
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [ConvertFrom-SddlString], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.ConvertFromSddlStringCommand
Which worked with the old script cmdlet:
PS C:\users\slee\repos\PowerShell> $sddl = "O:BAG:BAD:(A;CI;CCDCLCSWRPWPRCWD;;;BA)(A;CI;CCDCRP;;;NS)(A;CI;CCDCRP;;;LS)(A
;CI;CCDCRP;;;AU)"
PS C:\users\slee\repos\PowerShell> ConvertFrom-SddlString -Sddl $sddl
Owner : BUILTIN\Administrators
Group : BUILTIN\Administrators
DiscretionaryAcl : {NT AUTHORITY\Authenticated Users: AccessAllowed (ListDirectory, WriteData,
WriteExtendedAttributes), NT AUTHORITY\LOCAL SERVICE: AccessAllowed (ListDirectory, WriteData,
WriteExtendedAttributes), NT AUTHORITY\NETWORK SERVICE: AccessAllowed (ListDirectory, WriteData,
WriteExtendedAttributes), BUILTIN\Administrators: AccessAllowed (ChangePermissions,
CreateDirectories, ExecuteKey, ListDirectory, ReadExtendedAttributes, ReadPermissions, Traverse,
WriteData, WriteExtendedAttributes, WriteKey)}
SystemAcl : {}
RawDescriptor : System.Security.AccessControl.CommonSecurityDescriptor
76608a2
to
51f3466
Compare
@SteveL-MSFT The algorithm is broken! Please see my comment above #3936 (comment) I believe we cannot merge the PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewing algorithm
@daxian-dbw Can we use System.DirectoryService or before we should move to 4.5.0 version ? |
@zacateras Do you plan to continue? |
cbb1c14
to
0fb99ce
Compare
0fb99ce
to
3a8d3de
Compare
@iSazonov yep. I thought that we are waiting for some external review. |
Now that Beta.5 is out, I'll spend time on this tomorrow |
We should follow MSDN docs - see my comment above. |
@SteveL-MSFT Could you please continue? |
@@ -0,0 +1,119 @@ | |||
$RightTypes = @( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this file meant to be checked in? It's not part of the build to run and the resulting source doesn't indicate it shouldn't be edited.
/// </summary> | ||
SemaphoreRights, | ||
|
||
#if !CORECLR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we're officially removing fullclr support, we should not include this
{ 2031619, "FullControl" }, | ||
} | ||
}, | ||
#if !CORECLR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we're officially removing fullclr support, we should not include this
@@ -0,0 +1,21 @@ | |||
Describe "ConvertFrom-SddlString" -Tags "CI" { | |||
It "Should convert without type" -Skip:($IsLinux -Or $IsOSX) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this cmdlet is not supported on non-Windows, you should skip them in bulk using this pattern https://github.com/PowerShell/PowerShell/blob/master/docs/testing-guidelines/WritingPesterTests.md#skipping-tests-in-bulk (that way if additional tests get added they get auto-skipped)
$SddlStringInfo.Group | Should Be "BUILTIN\Administrators" | ||
$SddlStringInfo.DiscretionaryAcl.Length | Should BeGreaterThan 0 | ||
$SddlStringInfo.SystemAcl.Length | Should Be $null | ||
$SddlStringInfo.RawDescriptor | Should BeOfType [System.Security.AccessControl.CommonSecurityDescriptor] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be some tests for -Type
parameter
@@ -80,6 +80,8 @@ | |||
|
|||
<ItemGroup> | |||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.2.0" /> | |||
<PackageReference Include="System.DirectoryServices" Version="4.0.0-preview3-25423-02" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't appear to be a supported package, perhaps we should not support AD rights for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Official package is System.DirectoryService. It is 4.5.0 version. @daxian-dbw said that we should review the API before use.
Sorry this took so long, got randomized by other things. I also compared the result with ConvertFrom-SddlString "D:(A;;FA;;;SY)(A;;FA;;;BA)" # old script version
DiscretionaryAcl : {NT AUTHORITY\SYSTEM: AccessAllowed (ChangePermissions, CreateDirectories, Delete, DeleteSubdirectoriesAndFiles, ExecuteKey, FullControl,
FullControl, FullControl, FullControl, ListDirectory, Modify, Read, ReadAndExecute, ReadAttributes, ReadExtendedAttributes,
ReadPermissions, Synchronize, TakeOwnership, Traverse, Write, WriteAttributes, WriteData, WriteExtendedAttributes, WriteKey),
BUILTIN\Administrators: AccessAllowed (ChangePermissions, CreateDirectories, Delete, DeleteSubdirectoriesAndFiles, ExecuteKey, FullControl,
FullControl, FullControl, FullControl, ListDirectory, Modify, Read, ReadAndExecute, ReadAttributes, ReadExtendedAttributes,
ReadPermissions, Synchronize, TakeOwnership, Traverse, Write, WriteAttributes, WriteData, WriteExtendedAttributes, WriteKey)} # c# version
DiscretionaryAcl : {NT AUTHORITY\SYSTEM: AccessAllowed (AppendData, ChangePermissions, CreateFiles, Delete, DeleteSubdirectoriesAndFiles, ExecuteFile,
FullControl, FullControl, FullControl, FullControl, Modify, Read, ReadAndExecute, ReadAttributes, ReadData, ReadExtendedAttributes,
ReadKey, ReadPermissions, Synchronize, TakeOwnership, Write, WriteAttributes, WriteExtendedAttributes, WriteKey), BUILTIN\Administrators:
AccessAllowed (AppendData, ChangePermissions, CreateFiles, Delete, DeleteSubdirectoriesAndFiles, ExecuteFile, FullControl, FullControl,
FullControl, FullControl, Modify, Read, ReadAndExecute, ReadAttributes, ReadData, ReadExtendedAttributes, ReadKey, ReadPermissions,
Synchronize, TakeOwnership, Write, WriteAttributes, WriteExtendedAttributes, WriteKey)} And the newer version has more permissions (although not sure if the old one was wrong). |
@SteveL-MSFT Did you look my comment above? Main is "SDDL has no meaning without object entity - the cmdlet should get the object type as argument." |
@iSazonov Seems like if you have the object, you would be using |
@SteveL-MSFT My idea is that we should make |
@iSazonov If AccessMask is presented using well known aliases then it can be parsed entirely without the knowledge of securable object type (Type parameter). In case of numerical AccessMasks (eg. 0x02142512) we can translate only higher 16-32bits if the Type is not provided (lower bits can remain unparsed as a reminder). If the Type parameter is present then specific rights scheme could be used - according to constant definitions in winnt.h. Also more securable object types can be considered - according to the list in docs. I started an implementation of SDDL parser using the documentation. The code can be found here (with examples here). If we want to use parts of the implemented approach then I can integrate its simplified form into the command. What do you think about such solution? |
@zacateras I still don't understand the need for this heuristic. For which scenarios is it addressed? As for me it looks like a trasformation enum1 -> int -> enum2 - what's confusing. |
This PR has been left unattended for a long time. I will close this PR for now. |
#3212