Wednesday, April 18, 2018

Disable Local Users on Server 2012 with Powershell

At work, we had to implement a control for one of our servers that was not tied to Active Directory and had about 50 logins for various departments to access one of the environments. For PCI compliance, we needed a way to disable those accounts that have not logged in for 90 days. This proved to be a bit of a challenge because there was no central management. Seeing that it is only one server made me feel like the more it could be done.

Using Powershell and the ADSI functions of this opened the doors to the completion of the task. 

$users = $([ADSI]"WinNT://$env:COMPUTERNAME").Children | where {$_.SchemaClassName -eq 'user'}

The above command would output all the local users of the server, and then adding an array to remove certain service accounts was the next part because we still needed a backup account (the local administrator) and the services for monitoring and antivirus

$svc = @("Administrator","_svc-monitoring","SophosSAUWIN")

It was easy to omit those from the list using a simple ForEach loop

foreach ($s in $svc) {
    $users = $users | where {$_.Name -ne "$s"}
}

In the early testing, it would error out if there was a user account that was setup, and had never logged in. For this, I broke it up into two arrays; an array of active users (those who had logged in) and an array of inactive users (those who had never logged in). Fairly simple stuff ...

    $active = @()
        foreach ($u in $users){
            $activeUsers = @{Name = $u.Name} | where {$u.Properties.LastLogin.Value -ne $null}
            $activeObj = New-Object PSObject –Property $activeUsers
            $active += $activeUsers
        }

    $inactive = @()
        foreach ($u in $users){
            $inactiveUsers = @{Name = $u.Name} | where {$u.Properties.LastLogin.Value -eq $null}
            $inactiveObj = New-Object PSObject –Property $inactiveUsers
            $inactive += $inactiveUsers
        }

Next step was to set some disable parameters. The first two are straight forward in getting the date the script runs, used later for the user description to show when the account was disabled, and the other to subtract 90 days and convert to a large integer

$today = Get-Date
$nintyDays = ((Get-Date).AddDays(-90)).TofileTime()

This one was found in a TechNet article stating all the user flags, basically it takes whatever the userflag is and adds two. Simple enough. So if the default user that is setup has a flag of 513, than 515 would be disabled.

$ADS_UF_ACCOUNTDISABLE = 0x0002

The brunt of the work happens in the following two ForEach loops. The first looping thorough accounts that have actually logged in, and the second looping through accounts that have not logged in. This actually works out better, because I figure if someone requests an account and doesn't log in than why even create the account?

In the arrays created above, all I am collecting there are the usernames to pass in as a variable into the ADSI string saved as the $objUser variable which then grabs all the information that is tied to the user account. The if statement grabs the user flags and adds the disable account flag. This is because if the account was disabled a week ago, I wanted to maintain the account description on the date that it was disabled. Without that it would keep overwriting the description with the date that the script ran. I put the Write-Host in there for the purposes of testing, but the else statement will disable the account and change the description.

foreach ($a in $active) {
    $username = $a.Name
    $objUser = [ADSI]"WinNT://WORKGROUP/$env:COMPUTERNAME/$username"

    if ($objUser.psbase.Properties.item("userflags").value -band $ADS_UF_ACCOUNTDISABLE) {
                Write-Host "$username already disabled"}
    else {
        $logon = ($objUser.LastLogin).ToFileTime()
        if ($logon -le $nintyDays){
                $objUser.description = "Account Disabled $today"
                $objUser.userflags = $disableUser
                $objUser.setinfo()}
    }
}

foreach ($i in $inactive) {
    $username = $i.Name
    $objUser = [ADSI]"WinNT://WORKGROUP/$env:COMPUTERNAME/$username"
    
    if ($objUser.psbase.properties.item("userflags").value -band $ADS_UF_ACCOUNTDISABLE) {
                Write-Host "$username already disabled"}
    else {
        $objUser.description = "Account Disabled $today"
        $objUser.userflags = $disableUser
        $objUser.setinfo()
    }
}

Still with me? That was pretty easy. The next step was to make it run every morning at 1:30am with a scheduled task. The only way I could figure that out was to have a batch script, because the script needs to run as admin. The bat file is listed below.

@echo off

set scriptFileName=disableUser
set scriptFolderPath=c:\tools\scripts
set powershellScriptFileName=%scriptFileName%.ps1

powershell -Command "Start-Process powershell \"-ExecutionPolicy Bypass -NoProfile -NoExit -Command `\"cd \`\"%scriptFolderPath%\`\"; & \`\".\%powershellScriptFileName%\`\"`\"\" -Verb RunAs"

Here's the xml of the scheduled task for reference, but is probably the easiest part of this task to setup, but could be imported fairly quickly if needed.

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2018-04-18T12:34:56.0000000</Date>
    <Author>WIN-JUMPPY\Al.Fredo</Author>
  </RegistrationInfo>
  <Triggers>
    <CalendarTrigger>
      <StartBoundary>2018-04-18T01:30:00</StartBoundary>
      <Enabled>false</Enabled>
      <ScheduleByDay>
        <DaysInterval>1</DaysInterval>
      </ScheduleByDay>
    </CalendarTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>WIN-JUMPPY\Al.Fredo</UserId>
      <LogonType>Password</LogonType>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>P3D</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>C:\Tools\scripts\disableUser.bat</Command>
      <WorkingDirectory>C:\Tools\scripts</WorkingDirectory>
    </Exec>
  </Actions>
</Task> 

That's mostly it. If there are any questions, feel free to let me know! Just a side note though, in Server 2016 there is a Powershell module that can handle this. Thanks for reading.

Monday, April 9, 2018

Cockpit on Raspbian Stretch

Getting the Cockpit Project to run on a Raspberry Pi is quite easy, and requires a simple build from the source code that is available as a packaged tar file on GitHub. At the time of writing this it was version 165.

To start, node is a prerequisite and let's install that on Raspbian from source as well.

wget https://nodejs.org/dist/v9.9.0/node-v9.9.0.tar.gz 
tar -xzvf node-v9.9.0.tar.gz 
cd node-v9.9.0.tar.gz
./configure && make && sudo make install. 

This will take quite some time, so I would suggest running it with screen. After the install of node completes, there are some build prerequistes needed for Cockpit

sudo apt-get install autoconf intltool libglib2.0-dev libsystemd-dev \
libjson-glib-dev libpolkit-agent-1-dev libkrb5-dev libssh-dev \
libpam-dev libkeyutils-dev glib-networking

Download the package from GitHub, and untar it:

wget https://github.com/cockpit-project/cockpit/releases/download/165/cockpit-165.tar.xz

tar xf cockpit-165.tar.xz

You can follow these steps to build Cockpit, and from the original post I found here with --disable-pcp, I am not sure if it is still needed because the build process is a bit different from that and the ./autogen.sh failed stating it should be built from a git checkout or tar file and these worked on my install (Note that it is using the .xz archive and not the .gz archive)

cd cockpit-165
./configure --disable-pcp --disable-doc
make 
sudo make install
sudo cp ../src/bridge/cockpit.pam.insecure /etc/pam.d/cockpit

If the build is successful and there are no errors,  start cockpit:

sudo systemctl start cockpit.socket 

The status should show running, and it can be enabled to run on boot:

sudo systemctl enable cockpit.socket

To access cockpit use https://pi-address:9090 and you may see some informational messages, but they can be safely ignored.

Wednesday, April 4, 2018

Cockpit on CentOS 7

A while back I wrote a post on installing webmin on CentOS 7. I don't have anything against Webmin, it is a powerful tool for administering headless Linux servers, but I am always looking for something new and something different to manage servers. Recently I found a project called Cockpit which is another web manager for GNU/Linux servers. This is yet another powerful tool for a server administrator, and extremely simple to install.

The repository for Cockpit is already a part of CentOS 7. To install, follow these steps from the official project page

sudo yum install cockpit

sudo systemctl enable --now cockpit.socket

The last part is optional, and only required if the firewalld is enabled. (I used to disable this by default, however I am now changing the way use services on CentOS and Linux in general and leaving the firewall enabled and opening the ports / services needed.

sudo firewall-cmd --permanent --zone=public --add-service=cockpit
sudo firewall-cmd --reload



With the install complete, in a web browser go to https://ip-address-of-machine:9090 and use your system user account and password to log in. If your account has sudo access, you can save the password in the cache for privileged actions such as restarting services. Navigate around and see what this tool is capable of, also check out the official documentation for more useful tips or how to contribute.