Authorization problem with DirectoryEntry and ActiveDirectorySecurity - c#

I am writing a class to set or remove access rules to protect or unprotect objects from accidental removal, using ActiveDirectorySecurity.AddAccessRule and ActiveDirectorySecurity.RemoveAccessRule.
My class works fine if I provide the username and password of an admin account to the DirectoryEntry (which I don't want to do), but if I don't provide username and password it throws an error
System.DirectoryServices.DirectoryServicesCOMException: A constraint violation occurred
My default account however can change the protect object flag using the Active Directory Users and Computers app without any problems.
Any ideas on why this could be happening?
using System;
using System.DirectoryServices;
using System.Security.AccessControl;
using System.Security.Principal;
namespace test123
{
internal class Test124
{
internal static void RemoveAccidentialProtection()
{
string dn = "CN=XXX,OU=XXX,OU=XXX,OU=XXX,OU=XXX,DC=XXX,DC=XXX";
using (DirectoryEntry ent = new DirectoryEntry("LDAP://XXX:nnn/" + dn))
{
IdentityReference everyOneAccount = new NTAccount("Everyone").Translate(typeof(SecurityIdentifier)); //S - 1 - 1 - 0
ActiveDirectoryAccessRule objAce = new ActiveDirectoryAccessRule(everyOneAccount, ActiveDirectoryRights.Delete | ActiveDirectoryRights.DeleteTree, AccessControlType.Deny);
ent.ObjectSecurity.RemoveAccessRule(objAce);
ent.CommitChanges();
}
}
}
}

I found a post that says DirectoryEntry.CommitChanges defaults to setting ownership information which isn't allowed for regular users, so you need to set DirectoryEntry to only write the ACL changes. Put the following line just before the call to CommitChanges:
ent.Options.SecurityMasks = SecurityMasks.Dacl;

Related

Adding a DBContext to AWS Lambda causes it to hang

I am new to C# and Serverless development. I am trying to create an AWS Lambda that has an API Gateway trigger. When the Lambda triggers I want to write a User record to the database.
Lambda:
namespace CreateProfile;
using System.Net;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
using Database;
using Users.Models;
public class Function
{
/// <summary>
/// This Lambda function is for creating a user profile
/// </summary>
public APIGatewayHttpApiV2ProxyResponse FunctionHandler(User user, ILambdaContext context)
{
LambdaLogger.Log($"Calling function name: {context.FunctionName}\n");
LambdaLogger.Log($"Payload Received: {user}");
// 1. Populate the relevant table(s) from our input
using myDbContext ctx = new();
ctx.Users.Add(user);
ctx.SaveChanges();
APIGatewayHttpApiV2ProxyResponse response = new ()
{
StatusCode = (int)HttpStatusCode.OK,
Body = "Great Scott...it worked!",
Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } }
};
return response;
}
}
I am using the following Assembly:
using Amazon.Lambda.Core;
using Amazon.Lambda.Serialization.SystemTextJson;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(
DefaultLambdaJsonSerializer
))]
I am using the following as my database context:
namespace Database;
using Users.Models;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using Npgsql;
[DbConfigurationType(typeof(Config))]
public class MyDbContext: DbContext
{
public myDbContext(): base(MakeConnString()) {}
private static string MakeConnString()
{
// Will be moving these to a common location
string OptEnv(string key, string default_) =>
Environment.GetEnvironmentVariable(key) ?? default_;
string Env(string key) =>
Environment.GetEnvironmentVariable(key) ?? throw new MissingFieldException(key);
NpgsqlConnectionStringBuilder builder = new()
{
Host = Env("PGHOST"),
Port = int.Parse(OptEnv("PGPORT", "5432")),
SslMode = Enum.Parse<SslMode>(OptEnv("PGSSLMODE", "Require")),
TrustServerCertificate = true,
Database = OptEnv("PGDATABASE", "postgres"),
Username = OptEnv("PGUSER", "postgres"),
Password = Env("PGPASSWORD")
};
return builder.ConnectionString;
}
public DbSet<User> Users { get; set; }
}
When running this my Lambda hangs and I can't figure out why.
I'm assuming the database you are trying to interact with is an RDS instance or running on an EC2 instance in the same account, right?
If so, are your Lambda function deployed into your VPC? If not, the Lambda needs to be in order to talk to a VPC resource. The default is the Lambdas are NOT deployed in your VPC.
If you are using Serverless Framework then you need to add the following config to the provider section of your serverless.yml [https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml]
provider:
# Optional VPC settings
# If you use VPC then both securityGroupIds and subnetIds are required
vpc:
securityGroupIds:
- securityGroupId1
subnetIds:
- subnetId1
- subnetId2
The subnets you reference need to have a route to the subnets your database is provisioned into. They can be in the same subnets as the ones your RDS instance or EC2 is running the in DB.
Lastly, you need to ensure that the Security Group allows outbound traffic on the correct port for your Lambda Security Group, as well as, ensure that the Security Group on your database (EC2 or RDS) allows traffic from either the Lambda SG or the CIDR/IP ranges of the subnets you are deploying the Lambdas into on the correct port #.
The hanging is typically the request not making it to the DB - if you are already set up with your Lambda deployed in your VPC, then you should check the routing and Security Groups mentioned.

Generate GPO Report From Untrusted Domain

I'm calling LogonUser with LOGON_TYPE_NEW_CREDENTIALS and LOGON32_PROVIDER_WINNT50 to get my thread to impersonate a user in the other domain. I'm able to connect to remote file shares and everything else just fine into the untrusted domain.
The problem I'm running into now is when I use GPMGMTLib to generate a GPO report I keep getting exception "HRESULT: 0x80072020" when it calls GenerateReport().
using GPMGMTLib;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace CrossDomainWork
{
class Program
{
static void Main(string[] args)
{
ImpersonationContext context = new ImpersonationContext("ourdmzdomain.com", "dmzuser", "dmzpassword");
context.Start();
GPM gpm = new GPM();
var constants = gpm.GetConstants();
var domain = gpm.GetDomain("ourdmzdomain.com", "", constants.UseAnyDC);
var gpo = domain.GetGPO("{31B2F340-016D-11D2-945F-00C04FB984F9}");
object missing = Type.Missing;
var result = gpo.GenerateReport(GPMReportType.repHTML, ref missing, out missing).Result;
context.Stop();
}
}
}
I have no experience here, so this is just a guess.
Looking at the documentation for GenerateReport, the last two parameters are pvarGPMProgress (for reporting progress), and pvarGPMCancel (some kind of cancellation token).
You are passing the same object for both. I wonder if that's what's making it choke. You can try creating a second object.
Maybe it's also possible that it doesn't like getting Type.Missing as the value. You can try just setting them to null.
Also, does the group policy have any special permissions on it?
What namespace is that ImpersonationContext in that you're using? I can't find it. We do have an untrusted domain at work that I can test with, if I can get your code to compile.
Edit:
If you have SetLastError = true in your DllImport statements, then you can use Marshal.GetLastWin32Error() to get some additional details. For example:
try {
result = gpo.GenerateReport(GPMReportType.repHTML, ref missing, out missing).Result;
} catch {
var win32 = new Win32Exception(Marshal.GetLastWin32Error());
Console.Write(win32.Message);
}
For me, it tells me
An attempt was made to reference a token that does not exist
Which doesn't solve the puzzle, but it's another piece to the puzzle.

Console Application to mount azure cloud drive

I want to mount vhd from my private container. After google I get that it is only possible through .net. I am more of a JAVA person. I need a batch script or code in c# (So that I can get an exe file) which can automatically run at startup and mount vhd. So I decided to create a console app in order to get exe file.(I have very less knowledge c#/Visual studio) I am using following C# console application to do this.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using Microsoft.WindowsAzure.Internal;
namespace WorkerRole1
{
public class WorkerRole : RoleEntryPoint
{
public override void Run()
{
// This is a sample worker implementation. Replace with your logic.
Trace.WriteLine("WorkerRole1 entry point called", "Starting");
MountDrive();
//while (true)
//{
// Thread.Sleep(10000);
// Trace.WriteLine("Working", "Information");
//}
}
public override bool OnStart()
{
// Set the maximum number of concurrent connections
ServicePointManager.DefaultConnectionLimit = 12;
// For information on handling configuration changes
// see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
return base.OnStart();
}
public void MountDrive()
{
string connectionStringSettingName = "DefaultEndpointsProtocol=http;AccountName=abc;AccountKey=xyz";
string azureContainerName = "vhds";
string vhdName = "myvhd.vhd";
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionStringSettingName);
//CloudStorageAccount storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
LocalResource localCache = RoleEnvironment.GetLocalResource("MyAzureDriveCache");
CloudDrive.InitializeCache(localCache.RootPath, localCache.MaximumSizeInMegabytes);
Trace.WriteLine("RootPath =====" + localCache.RootPath);
Trace.WriteLine("MaximumSizeInMegabytes =====" + localCache.MaximumSizeInMegabytes);
// Just checking: make sure the container exists
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
blobClient.GetContainerReference(azureContainerName).CreateIfNotExist();
// Create cloud drive
CloudDrive myCloudDrive = storageAccount.CreateCloudDrive(
blobClient
.GetContainerReference(azureContainerName)
.GetPageBlobReference(vhdName)
.Uri.ToString()
);
Trace.WriteLine("Uri =====" + blobClient
.GetContainerReference(azureContainerName)
.GetPageBlobReference(vhdName)
.Uri.ToString());
try
{
myCloudDrive.Create(1024);
}
catch (CloudDriveException ex)
{
// handle exception here
// exception is also thrown if all is well but the drive already exists
}
string driveLetter = myCloudDrive.Mount(50, DriveMountOptions.Force);//Here It throws a Exception
Trace.WriteLine("Drive =====" + driveLetter);
for (int i = 0; i < 10; i++)
{
System.IO.File.WriteAllText(driveLetter + "\\" + i.ToString() + ".txt", "Test");
}
}
}
}
But I keep getting exception ERROR_DEVFABRIC_LOCAL_MOUNT_ONLY at
string driveLetter = myCloudDrive.Mount(50, DriveMountOptions.Force);
Please tell me where am I going wrong?
When RoleEnvironment.IsAvailable is set to false, this means you are not running in a Windows Azure Web/Worker/VM Role. The Windows Azure Drive only works when mounted in these roles (since it depends on RoleEnvironment).
More information can be found in the whitepaper.
ERROR_DEVFABRIC_LOCAL_MOUNT_ONLY means that when you are running locally you must mount the drive form development storage.
change the following line:
string connectionStringSettingName = "UseDevelopmentStorage=true";
or even better use RoleEnvironment.GetConfigurationSettingValue, like:
string connectionStringSettingName = RoleEnvironment.GetConfigurationSettingValue("DriveConnectionString");
and set the Setting in the service configuration (files)

DirectoryEntry to change password: Different behavior between Vista/Server2008

On a Vista dev machine I used this code successfully to change user "Administrator" password:
directoryEntry.Invoke("SetPassword", "new");
When I moved it over to my Server 2008 dev machine that code did not work, and I was forced to use the following code:
directoryEntry.Invoke("ChangePassword", new object[] { "old", "new" });
My question is, why?
For both cases, I created my DirectoryEntry object as such:
DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("WinNT://{0}/{1}", computerName, username));
Thanks! 8)
In case you guys find it helpful, heres the actual code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.DirectoryServices;
using System.Security.Principal;
namespace AccountMod
{
class Program
{
static void Main()
{
Console.WriteLine("Attempting reset...\n");
try
{
String machineNameAndUser = WindowsIdentity.GetCurrent().Name.ToString();
String machineName = WindowsIdentity.GetCurrent().Name.ToString().Substring(0, machineNameAndUser.IndexOf('\\'));
Console.WriteLine("Computer's name: " + machineName);
ResetPassword(machineName, "Administrator", "new");
//ChangePassword("Administrator", "current", "new"); Console.WriteLine("Finished...");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
Console.WriteLine(e.InnerException);
}
Console.ReadKey();
}
public static void ResetPassword(string computerName, string username, string newPassword)
{
DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("WinNT://{0}/{1}", computerName, username));
directoryEntry.Invoke("SetPassword", newPassword);
//directoryEntry.Invoke("ChangePassword", new object[] { "current", "new" });
}
}
}
Are you (or could you upgrade to) .NET 3.5? The AD integration for users, groups, computers has been massively improved in .NET 3.5 - check out the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 for details.
In your case, you could do something like:
// establish context for local machine
PrincipalContext ctx = new PrincipalContext(ContextType.Machine);
// find the "Administrator" account
UserPrincipal admin = UserPrincipal.FindByIdentity(ctx, "Administrator");
// set the password to a new value
admin.SetPassword("new-top-secret-password");
admin.Save();
and you're done! The WinNT: provider is very limited in what it can do and should be avoided if ever possible.
Check user properties for which you want to set password, if
user cannot change password
property is checked then u cant set password by using directoryEntry.Invoke("SetPassword", "new");Use admin credential while creating DirectoryEntry object or uncheck " user cannot change password" property

C#/.NET Server Path to default/index page

In my attempt to further future-proof a project I am trying to find the best way to retrieve the full path and filename of the index/default page in a web directory using C# and without knowing the web server's list of filename possibilities.
'Server.MapPath("/test/")' gives me 'C:\www\test\'
...so does: 'Server.MapPath(Page.ResolveUrl("/test/"))'
...but I need 'C:\www\test\index.html'.
Does anyone know of an existing method of retrieving the filename that the webserver will serve up when someone browses to that directory - be it default.aspx, or index.html, or whatever?
Thanks for any help,
fodder
ASP.NET has no knowledge of this. You would need to query IIS for the default document list.
The reason for this is that IIS will look in your web folder for the first matching file in the IIS default document list then hand off to the matching ISAPI extension for that file type (by extension) in the script mappings.
To obtain the default document list you can do the following (using the Default Website as an example where the IIS Number = 1):
using System;
using System.DirectoryServices;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
using (DirectoryEntry w3svc =
new DirectoryEntry("IIS://Localhost/W3SVC/1/root"))
{
string[] defaultDocs =
w3svc.Properties["DefaultDoc"].Value.ToString().Split(',');
}
}
}
}
It would then be a case of iterating the defaultDocs array to see which file exists in the folder, the first match is the default document. For example:
// Call me using: string doc = GetDefaultDocument("/");
public string GetDefaultDocument(string serverPath)
{
using (DirectoryEntry w3svc =
new DirectoryEntry("IIS://Localhost/W3SVC/1/root"))
{
string[] defaultDocs =
w3svc.Properties["DefaultDoc"].Value.ToString().Split(',');
string path = Server.MapPath(serverPath);
foreach (string docName in defaultDocs)
{
if(File.Exists(Path.Combine(path, docName)))
{
Console.WriteLine("Default Doc is: " + docName);
return docName;
}
}
// No matching default document found
return null;
}
}
Sadly this won't work if you're in a partial trust ASP.NET environment (for example shared hosting).

Categories

Resources