Re: Creating MSI for installing .NET security policies



Only MSI packages created in VS.NET have native support for Installer
classes via custom actions, and that is actually implemented via a hackfest
shim that the VS team put into their code base. Installer classes are
generally regarded as "the devil" by professional setup authors, as they
violate some key MSI principles and have really poor integration with MSI in
general. They are regarded as "toys for devs", but not for serious setup
authors.

The fact that the VS team continues to push Installer classes as the way to
do things is a source of tension with the Windows Installer guys at MS.

I know all of this stuff as I've been dragged into the world of doing more
"pro" setup development for a project after hitting the wall with the
capabilities of VS.NET deployment projects and found out about the dark side
of Installer classes.

Joe K.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
"Dominick Baier" <dbaier@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx> wrote in
message news:4580be63196508c8bc36e25f27d0@xxxxxxxxxxxxxxxxxxxxx
just create a installer project - add a class that derives from
Installer - override Install/Uninstall - where's the hack??

[RunInstaller(true)]
public class PolicyInstaller : Installer
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

string permissionSetName = "Acme Permissions";
string permissionSetDesc = "This is the set of permissions needed by
.NET applications for Acme corporation";
string codeGroupName = "Acme Code Group";
string codeGroupDesc = "Grants a few extra permissions to any assembly
signed with the Acme strong name";

public PolicyInstaller()
{
// This call is required by the Designer.
InitializeComponent();
}

#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion

// this code will run when the MSI file is installed
public override void Install(IDictionary stateSaver)
{

// first need to find the machine policy,
// which is where we'll make our changes
PolicyLevel machinePolicy =
_findPolicyLevel(PolicyLevelType.Machine);

if (null == machinePolicy)
{
// sanity check - this should never happen
throw new ApplicationException("Failed to find the machine
policy in the PolicyHierarchy");
}

// we need to add a named permission set
// that includes whatever permissions we're granting
NamedPermissionSet nps = new NamedPermissionSet(permissionSetName,
PermissionState.None);
nps.Description = permissionSetDesc;

// TODO: add some permissions
nps.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read,
@"c:\acme\expenses"));
nps.AddPermission(new
EnvironmentPermission(EnvironmentPermissionAccess.Read, "EXPENSE"));
nps.AddPermission(new
SqlClientPermission(PermissionState.Unrestricted));
nps.AddPermission(new
DataProtectionPermission(PermissionState.Unrestricted));

// add our named permission set to the machine policy level
// note that nothing is saved yet (we'll save at the end)
try
{
machinePolicy.AddNamedPermissionSet(nps);
}
catch
{
// duplicate name - update the existing one with the same name
machinePolicy.ChangeNamedPermissionSet(nps.Name, nps);
}

// create code group
CodeGroup cg = new UnionCodeGroup(
new StrongNameMembershipCondition(
new StrongNamePublicKeyBlob(acmePublicKey),
null, // match regardless of assembly's simple name
null), // match regardless of assembly's version
new PolicyStatement(nps,
PolicyStatementAttribute.Nothing) // no LevelFinal or
Exclusive attribute on this code group
);
cg.Name = codeGroupName;
cg.Description = codeGroupDesc;

// code groups with duplicate names are legal, but messy and
confusing,
// so we make sure to first remove any existing code groups with
our name
_removeCodeGroupsByName(machinePolicy.RootCodeGroup, cg.Name);

// add our new code group (note we've not saved yet).
machinePolicy.RootCodeGroup.AddChild(cg);

// finally, save all changes atomically.
SecurityManager.SavePolicyLevel(machinePolicy);
}

// this code will run when our MSI is uninstalled
public override void Uninstall(IDictionary savedState)
{
// first need to find the machine policy,
// which is where we'll make our changes
PolicyLevel machinePolicy =
_findPolicyLevel(PolicyLevelType.Machine);

if (null == machinePolicy)
{
// sanity check - this should never happen
throw new ApplicationException("Failed to find the machine
policy in the PolicyHierarchy");
}

// remove our code group
_removeCodeGroupsByName(machinePolicy.RootCodeGroup,
codeGroupName);

// remove our named permission set
try
{
machinePolicy.RemoveNamedPermissionSet(permissionSetName);
}
catch
{
// if it's not there, no biggie - just doing our best to clean
up.
}

// finally, save all changes atomically.
SecurityManager.SavePolicyLevel(machinePolicy);
}

PolicyLevel _findPolicyLevel(PolicyLevelType policyLevel)
{
IEnumerator policyLevelEnumerator =
SecurityManager.PolicyHierarchy();
PolicyLevel found = null;
while (policyLevelEnumerator.MoveNext())
{
PolicyLevel lvl = (PolicyLevel)policyLevelEnumerator.Current;
if (lvl.Type == policyLevel)
{
found = lvl;
}
}
return found;
}

void _removeCodeGroupsByName(CodeGroup parent, string childName)
{
// the implementation of CodeGroup.Children (viewed with Anakrino)
// gives a deep copy of the children, so technically this is
overkill,
// but the documentation doesn't guarantee this, so we enumerate
// then delete in two separate steps as you would with any dynamic
list
ArrayList codeGroupsToRemove = new ArrayList();
foreach (CodeGroup existingCodeGroup in parent.Children)
{
if (childName == existingCodeGroup.Name)
{
codeGroupsToRemove.Add(existingCodeGroup);
}
}
foreach (CodeGroup cg in codeGroupsToRemove)
{
parent.RemoveChild(cg);
}
}

// public key from AcmeExpense.exe using the secutil tool
byte[] acmePublicKey = new byte[]{ 0, 36, 0, 0, 4, 128, 0, 0, 148, 0,
0, 0,
6, 2, 0, 0, 0, 36, 0, 0, 82, 83,
65, 49, 0, 4, 0, 0, 1, 0, 1, 0, 85, 73, 181, 56, 224, 255, 198, 167, 151,
206, 111, 135,
63, 32, 172, 222, 77, 52, 224,
240, 213,
218, 202, 79, 18, 100, 175, 171,
17, 242,
83, 200, 243, 42, 105, 75, 231, 4,
81, 164, 11, 105, 39, 193, 38, 32, 161, 246,
131, 55, 86, 216, 252, 116, 204,
12, 60,
91, 125, 46, 230, 95, 108, 13,
161, 34, 69, 98, 191, 236, 147, 208, 59, 80, 172,
98, 1, 213, 167, 105, 180, 135,
22, 231,
69, 11, 55, 252, 224, 212, 219,
86, 112,
32, 92, 118, 40, 250, 51, 173, 23,
33, 77, 179, 133, 49, 197, 137, 174, 236, 57, 143, 37, 244, 251, 98, 178,
255, 175,
135, 107, 5, 168, 106, 175, 184,
116, 206 };
}


---
Dominick Baier, DevelopMentor
http://www.leastprivilege.com

The poblem with that is that doing .NET code inside an MSI via a
custom action is a little painful as of now. MSI doesn't have native
support for .NET custom actions, only script and native DLLs (or exe
files). There are many hacks to support this, but they are all hacks
for now and have a variety of shortcomings.

I agree that the .NET classes are better in theory.

Joe K.






.



Relevant Pages

  • Re: assign permission set
    ... policy it does so at some risk. ... If an msi install will work for you, ... The .NET Framework Configuration tool generates a Microsoft installer ... >> code shows how to programmatically replace the trusted zone code group. ...
    (microsoft.public.dotnet.security)
  • Re: Creating code groups | Setup project
    ... add a class lib to the installer project - and include its primary output ... throw new ApplicationException("Failed to find the machine policy ... // add our named permission set to the machine policy level ... // now we need to create a code group that matches all assemblies ...
    (microsoft.public.dotnet.security)
  • Re: Creating code groups | Setup project
    ... add a class lib to the installer project - and include its primary output ... throw new ApplicationException("Failed to find the machine policy ... // add our named permission set to the machine policy level ... // now we need to create a code group that matches all assemblies ...
    (microsoft.public.dotnet.security)
  • Re: Any good examples of modifying the CAS during install?
    ... custom actions of the windows installer and the folowing code template ... 3./ Override it's methods Rollback and Uninstall and call Remove CodeGroup ... static private PolicyLevel GetLevel ... PolicyLevel machinePolicyLevel = ...
    (microsoft.public.dotnet.security)
  • Re: Creating MSI for installing .NET security policies
    ... PolicyLevel machinePolicy = _findPolicyLevel; ... throw new ApplicationException("Failed to find the machine policy in the PolicyHierarchy"); ... CodeGroup cg = new UnionCodeGroup( ... PolicyStatementAttribute.Nothing) // no LevelFinal or Exclusive attribute on this code group ...
    (microsoft.public.dotnet.security)