Groups | Blog | Home
all groups > dotnet security > october 2006 >

dotnet security : Creating MSI for installing .NET security policies


David++
10/12/2006 7:38:02 AM
Hi folks,

I'm deploying my app to a network share and I need the security permissions
installed on the client. AFAICT I cannot use ClickOnce because ClickOnce
deployment doesnt allow user to specify install location i.e. Network share.
So I have gone down the route of creating a Strong Name key and signing my
assemblies with that key, creating a new Code Group in the Microsoft .NET
configuration Tool which assigns full trust to my Strong Name key, and then
finally creating a deployment package MSI to allow users to install the
Security Policy easily.

Now, this all works ok, but I find it a bit sas that the security policies
are over written in the Code Groups with the new settings i.e. they are not
merged with the existing groups. Luckily my clients have a clean slate of
policies anyway so I wasnt overwriting anyone elses, but what if some other
vendor had written an app for the client and had used the same approach, I
would scrub there policies, or vice versa someone could quite easily scrub
mine. Is this correct?

Of course policies could be manually added in, but this might be a pain if
there were a lot of users.

Or is there another way I have missed?

Thanks again,
David++
10/12/2006 8:15:01 AM
Hi Joe,

Thanks for the reply. That sounds like a good way of doing it. I'll give it
a shot.

Cheers very much,
David

[quoted text, click to view]
David++
10/12/2006 8:20:02 AM
Hi Dominick,

Sounds good, any code snippets you can summon would be very useful, I will
also turn to the mighty google for this one also.

Best,
David

[quoted text, click to view]
Joe Kaplan
10/12/2006 9:56:29 AM
Probably the most reliable method would be to create a custom action that
calls caspol.exe with the proper arguments to add your policy to the current
policy. You'll probably want to use a search of some sort to find the
appropriate .NET framework directory to use to call caspol so that you
configure for the correct framework version.

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
--
[quoted text, click to view]

Joe Kaplan
10/12/2006 10:50:19 AM
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.

--
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@pleasepleasenospam_leastprivilege.com> wrote in
message news:4580be63196448c8bc30df997b80@news.microsoft.com...
[quoted text, click to view]

Joe Kaplan
10/12/2006 12:13:54 PM
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@pleasepleasenospam_leastprivilege.com> wrote in
message news:4580be63196508c8bc36e25f27d0@news.microsoft.com...
[quoted text, click to view]
Dominick Baier
10/12/2006 2:11:05 PM
Hi,

no thats the default behavior. You should write an MSI installer with custom
install/uninstall actions to programmatically create the stuff for you.

System.Security.Policy has all the classes you need.

I can dig out some code if you need it.

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

[quoted text, click to view]

Dominick Baier
10/12/2006 2:16:14 PM
ouch...

use the API. Thats the most robust way.

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

[quoted text, click to view]

Dominick Baier
10/12/2006 2:59:15 PM
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,
Joe Kaplan
10/12/2006 3:31:52 PM
Yep, it is really all about the audience in this case. They are great for
devs and not so great for people doing professional deployment packaging.

Ideally, MS would fix this mess and create a solution that worked well for
both. The current implementation is not well-conceived and leads to this
tension. It could be a much better for both audiences.

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@pleasepleasenospam_leastprivilege.com> wrote in
message news:4580be63196598c8bc4646b1f770@news.microsoft.com...
[quoted text, click to view]

Dominick Baier
10/12/2006 4:49:26 PM
OK :)

but for the purpose of having a robust way to install/uninstall policies
they are perfect (for me).

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

[quoted text, click to view]

AddThis Social Bookmark Button