Merge pull request #70 from ebsco/LogonAsAService

Adding logon as a service right to user account specified in configuration
pull/75/head
Oleg Nenashev 2015-01-30 23:16:39 +03:00
commit 0105fe5214
6 changed files with 312 additions and 2 deletions

View File

@ -81,6 +81,162 @@ namespace winsw
} }
} }
static class LogonAsAService
{
public static void AddLogonAsAServiceRight(string Username)
{
//Needs to be at least XP or 2003 server
//https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx
System.OperatingSystem osInfo = System.Environment.OSVersion;
if (osInfo.Version.Major >= 5 && osInfo.Version.Minor >= 1)
{
var newuser = GetLocalAccountIfLocalAccount(Username);
//Trace.WriteLine("Username for Logon as A Service: " + newuser);
long rightexitcode = SetRight(newuser, PrivlegeRights.SeServiceLogonRight.ToString());
if (rightexitcode != 0)
{
Console.WriteLine("Failed to set logon as a service right");
Environment.Exit(1);
}
}
else
{
Console.WriteLine("Cannot set Logon as a Service right. Unsupported operating system detected");
}
}
private static string GetDomain(string s)
{
int stop = s.IndexOf("\\");
if (stop >= 0)
return s.Substring(0, stop);
else
return null;
}
private static string GetLogin(string s)
{
int stop = s.IndexOf("\\");
return (stop > -1) ? s.Substring(stop + 1, s.Length - stop - 1) : s;
}
private static string GetLocalAccountIfLocalAccount(string username)
{
var machinename = Environment.MachineName;
string domain = GetDomain(username);
if (domain == null || domain.ToLower() == machinename.ToLower() || domain == ".")
{
return GetLogin(username);
}
return username;
}
/// <summary>Adds a privilege to an account</summary>
/// <param name="accountName">Name of an account - "domain\account" or only "account"</param>
/// <param name="privilegeName">Name ofthe privilege</param>
/// <returns>The windows error code returned by LsaAddAccountRights</returns>
private static long SetRight(String accountName, String privilegeName)
{
long winErrorCode = 0; //contains the last error
//pointer an size for the SID
IntPtr sid = IntPtr.Zero;
int sidSize = 0;
//StringBuilder and size for the domain name
StringBuilder domainName = new StringBuilder();
int nameSize = 0;
//account-type variable for lookup
int accountType = 0;
//get required buffer size
Advapi32.LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);
//allocate buffers
domainName = new StringBuilder(nameSize);
sid = Marshal.AllocHGlobal(sidSize);
//lookup the SID for the account
bool result = Advapi32.LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize,
ref accountType);
//say what you're doing
//Console.WriteLine("LookupAccountName result = " + result);
//Console.WriteLine("IsValidSid: " + Advapi32.IsValidSid(sid));
//Console.WriteLine("LookupAccountName domainName: " + domainName.ToString());
if (!result)
{
winErrorCode = Kernel32.GetLastError();
Console.WriteLine("LookupAccountName failed: " + winErrorCode);
}
else
{
//initialize an empty unicode-string
LSA_UNICODE_STRING systemName = new LSA_UNICODE_STRING();
//combine all policies
int access = (int)(
LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN |
LSA_AccessPolicy.POLICY_CREATE_ACCOUNT |
LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE |
LSA_AccessPolicy.POLICY_CREATE_SECRET |
LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION |
LSA_AccessPolicy.POLICY_LOOKUP_NAMES |
LSA_AccessPolicy.POLICY_NOTIFICATION |
LSA_AccessPolicy.POLICY_SERVER_ADMIN |
LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS |
LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS |
LSA_AccessPolicy.POLICY_TRUST_ADMIN |
LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION |
LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION
);
//initialize a pointer for the policy handle
IntPtr policyHandle = IntPtr.Zero;
//these attributes are not used, but LsaOpenPolicy wants them to exists
LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
ObjectAttributes.Length = 0;
ObjectAttributes.RootDirectory = IntPtr.Zero;
ObjectAttributes.Attributes = 0;
ObjectAttributes.SecurityDescriptor = IntPtr.Zero;
ObjectAttributes.SecurityQualityOfService = IntPtr.Zero;
//get a policy handle
uint resultPolicy = Advapi32.LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle);
winErrorCode = Advapi32.LsaNtStatusToWinError(resultPolicy);
if (winErrorCode != 0)
{
Console.WriteLine("OpenPolicy failed: " + winErrorCode);
}
else
{
//Now that we have the SID an the policy,
//we can add rights to the account.
//initialize an unicode-string for the privilege name
LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1];
userRights[0] = new LSA_UNICODE_STRING();
userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName);
userRights[0].Length = (UInt16)(privilegeName.Length * UnicodeEncoding.CharSize);
userRights[0].MaximumLength = (UInt16)((privilegeName.Length + 1) * UnicodeEncoding.CharSize);
//add the right to the account
uint res = Advapi32.LsaAddAccountRights(policyHandle, sid, userRights, 1);
winErrorCode = Advapi32.LsaNtStatusToWinError(res);
if (winErrorCode != 0)
{
Console.WriteLine("LsaAddAccountRights failed: " + winErrorCode);
}
Advapi32.LsaClose(policyHandle);
}
Advapi32.FreeSid(sid);
}
return winErrorCode;
}
}
/// <summary> /// <summary>
/// Advapi32.dll wrapper for performing additional service related operations that are not /// Advapi32.dll wrapper for performing additional service related operations that are not
/// available in WMI. /// available in WMI.
@ -105,10 +261,85 @@ namespace winsw
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseServiceHandle(IntPtr hSCObject); internal static extern bool CloseServiceHandle(IntPtr hSCObject);
[DllImport("ADVAPI32.DLL")] [DllImport("advapi32.DLL")]
internal static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus); internal static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus);
[DllImport("advapi32.dll", PreserveSig = true)]
internal static extern UInt32 LsaOpenPolicy(ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, Int32 DesiredAccess,
out IntPtr PolicyHandle);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
internal static extern uint LsaAddAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, LSA_UNICODE_STRING[] UserRights, uint CountOfRights);
[DllImport("advapi32")]
internal static extern void FreeSid(IntPtr pSid);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, PreserveSig = true)]
internal static extern bool LookupAccountName(string lpSystemName, string lpAccountName, IntPtr psid, ref int cbsid, StringBuilder domainName,
ref int cbdomainLength, ref int use);
[DllImport("advapi32.dll")]
internal static extern bool IsValidSid(IntPtr pSid);
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern uint LsaClose(IntPtr ObjectHandle);
[DllImport("advapi32.dll", SetLastError = false)]
internal static extern uint LsaNtStatusToWinError(uint status);
} }
//http://msdn.microsoft.com/en-us/library/windows/desktop/bb545671(v=vs.85).aspx
internal enum PrivlegeRights
{
SeServiceLogonRight, //Required for an account to log on using the service logon type.
SeRemoteInteractiveLogonRight, //Required for an account to log on remotely using the interactive logon type.
SeNetworkLogonRight, //Required for an account to log on using the network logon type.
SeInteractiveLogonRight, //Required for an account to log on using the interactive logon type.
SeDenyServiceLogonRight, //Explicitly denies an account the right to log on using the service logon type.
SeDenyRemoteInteractiveLogonRight, //Explicitly denies an account the right to log on remotely using the interactive logon type.
SeDenyNetworkLogonRight, //Explicitly denies an account the right to log on using the network logon type.
SeDenyInteractiveLogonRight, //Explicitly denies an account the right to log on using the interactive logon type.
SeDenyBatchLogonRight, //Explicitly denies an account the right to log on using the batch logon type.
SeBatchLogonRight //Required for an account to log on using the batch logon type.
}
[StructLayout(LayoutKind.Sequential)]
struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING ObjectName;
public UInt32 Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
// enum all policies
enum LSA_AccessPolicy : long
{
POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
POLICY_TRUST_ADMIN = 0x00000008L,
POLICY_CREATE_ACCOUNT = 0x00000010L,
POLICY_CREATE_SECRET = 0x00000020L,
POLICY_CREATE_PRIVILEGE = 0x00000040L,
POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
POLICY_SERVER_ADMIN = 0x00000400L,
POLICY_LOOKUP_NAMES = 0x00000800L,
POLICY_NOTIFICATION = 0x00001000L
}
internal enum SCM_ACCESS : uint internal enum SCM_ACCESS : uint
{ {

View File

@ -21,6 +21,8 @@ namespace winsw
[In] ref STARTUPINFO lpStartupInfo, [In] ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation); out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
internal static extern int GetLastError();
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]

16
Main.cs
View File

@ -549,6 +549,7 @@ namespace winsw
if (args[0] == "install") if (args[0] == "install")
{ {
string username=null, password=null; string username=null, password=null;
bool setallowlogonasaserviceright = false;
if (args.Count > 1 && args[1] == "/p") if (args.Count > 1 && args[1] == "/p")
{ {
// we expected username/password on stdin // we expected username/password on stdin
@ -556,6 +557,14 @@ namespace winsw
username = Console.ReadLine(); username = Console.ReadLine();
Console.Write("Password: "); Console.Write("Password: ");
password = ReadPassword(); password = ReadPassword();
Console.WriteLine();
Console.Write("Set Account rights to allow log on as a service (y/n)?: ");
var keypressed = Console.ReadKey();
Console.WriteLine();
if (keypressed.Key == ConsoleKey.Y)
{
setallowlogonasaserviceright = true;
}
} }
else else
{ {
@ -563,9 +572,15 @@ namespace winsw
{ {
username = d.ServiceAccountUser; username = d.ServiceAccountUser;
password = d.ServiceAccountPassword; password = d.ServiceAccountPassword;
setallowlogonasaserviceright = d.AllowServiceAcountLogonRight;
} }
} }
if (setallowlogonasaserviceright)
{
LogonAsAService.AddLogonAsAServiceRight(username);
}
svc.Create ( svc.Create (
d.Id, d.Id,
d.Caption, d.Caption,
@ -699,6 +714,5 @@ namespace winsw
} }
} }
} }
} }
} }

View File

@ -278,8 +278,11 @@ It is possible to specify the useraccount (and password) that the service will r
<domain>YOURDOMAIN</domain> <domain>YOURDOMAIN</domain>
<user>useraccount</user> <user>useraccount</user>
<password>Pa55w0rd</password> <password>Pa55w0rd</password>
<allowservicelogon>true</allowservicelogon>
</serviceaccount> </serviceaccount>
The <allowservicelogon> is optional. If set to true, will automatically set the "Allow Log On As A Service" right to the listed account.
### Working directory ### Working directory
Some services need to run with a working directory specified. To do this, specify a `<workingdirectory>` element like this: Some services need to run with a working directory specified. To do this, specify a `<workingdirectory>` element like this:

View File

@ -540,6 +540,14 @@ namespace winsw
} }
protected string AllowServiceLogon
{
get
{
return GetServiceAccountPart("allowservicelogon");
}
}
protected string serviceAccountDomain protected string serviceAccountDomain
{ {
get{ get{
@ -573,6 +581,22 @@ namespace winsw
return !string.IsNullOrEmpty(serviceAccountDomain) && !string.IsNullOrEmpty(serviceAccountName); return !string.IsNullOrEmpty(serviceAccountDomain) && !string.IsNullOrEmpty(serviceAccountName);
} }
public bool AllowServiceAcountLogonRight
{
get
{
if (AllowServiceLogon != null)
{
bool parsedvalue = false;
if (Boolean.TryParse(AllowServiceLogon, out parsedvalue))
{
return parsedvalue;
}
}
return false;
}
}
/// <summary> /// <summary>
/// Time to wait for the service to gracefully shutdown before we forcibly kill it /// Time to wait for the service to gracefully shutdown before we forcibly kill it
/// </summary> /// </summary>

View File

@ -17,6 +17,7 @@ namespace winswTests
private const string Username = "User"; private const string Username = "User";
private const string Password = "Password"; private const string Password = "Password";
private const string Domain = "Domain"; private const string Domain = "Domain";
private const string AllowServiceAccountLogonRight = "true";
[SetUp] [SetUp]
public void SetUp() public void SetUp()
@ -32,6 +33,7 @@ namespace winswTests
+ "<domain>" + Domain + "</domain>" + "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>" + "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>" + "<password>" + Password + "</password>"
+ "<allowservicelogon>" + AllowServiceAccountLogonRight + "</allowservicelogon>"
+ "</serviceaccount>" + "</serviceaccount>"
+ "<workingdirectory>" + "<workingdirectory>"
+ ExpectedWorkingDirectory + ExpectedWorkingDirectory
@ -48,6 +50,12 @@ namespace winswTests
Assert.That(extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory)); Assert.That(extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory));
} }
[Test]
public void VerifyServiceLogonRight()
{
Assert.That(extendedServiceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(true));
}
[Test] [Test]
public void VerifyUsername() public void VerifyUsername()
{ {
@ -153,5 +161,33 @@ namespace winswTests
Assert.That(logHandler.Period, Is.EqualTo(7)); Assert.That(logHandler.Period, Is.EqualTo(7));
Assert.That(logHandler.Pattern, Is.EqualTo("log pattern")); Assert.That(logHandler.Pattern, Is.EqualTo("log pattern"));
} }
[Test]
public void VerifyServiceLogonRightGraceful()
{
const string SeedXml="<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "<allowservicelogon>true1</allowservicelogon>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(SeedXml);
Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(false));
}
[Test]
public void VerifyServiceLogonRightOmitted()
{
const string SeedXml = "<service>"
+ "<serviceaccount>"
+ "<domain>" + Domain + "</domain>"
+ "<user>" + Username + "</user>"
+ "<password>" + Password + "</password>"
+ "</serviceaccount>"
+ "</service>";
var serviceDescriptor = ServiceDescriptor.FromXML(SeedXml);
Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(false));
}
} }
} }