Artifactory Check Users Requests
These scripts grep the Artifactory access logs for user access, the generated output is then formatted via logstash and finally converted to a Excel spreadsheet, during which Active Directory is check to see if the users account is disabled or enabled.
The reason for this script was created is that security needed to ensure that no disabled users had been trying to access Artifactory from outside of the network after they had left the company.
Steps
- CheckUsersRequests.sh
- FormatLogs.sh (logstash is needed)
- CreateExcelDoc.ps1 (excel is needed)
#!/bin/bash
# This script greps the Artifactory logs for users list in argument file (e.g. "search_users.txt") and for each generates a "username" file.
#
# e.g.
# ./CheckUsersRequests.sh search_users.txt
#
# Cygwin
# ./CheckUsersRequests.sh search_users.txt /cygdrive/c/Users/john.newman/Desktop/Artifactory_Logs/
# ./CheckUsersRequests.sh search_users.txt /cygdrive/c/Users/john.newman/Desktop/Artifactory_Logs/ /cygdrive/c/temp/_per-user_logs
#
# As command "zgrep" is used a Linux box or Cygwin is required.
#
# NOTE: The output log files are used by script "CreateExcelDoc.ps1" with JSON files created for debugging.
#
usernames_file="$1"
: ${usernames_file:?"You MUST provide an file with usernames to search"}
log_file_path="$2"
: "/opt/artifactory/logs"
output_path="$3"
: "_per-user_logs"
if [ ! -d "$output_path" ]; then
mkdir "$output_path"
fi
while read username
do
echo "$username"
grep "$username" $log_file_path/access.log > "$output_path/$username.txt"
zgrep "$username" $log_file_path/access.*.log.zip >> "$output_path/$username.txt"
done < "$usernames_file"
input {
stdin {
type => "apache"
}
}
filter {
if [type] == "apache" {
kv {
trim => " "
}
grok {
match => [
"message", "%{DATESTAMP:datestamp}%{SPACE}\[%{GREEDYDATA:request_type}\]%{SPACE}%{NOTSPACE:repo}\:(?<package>[a-zA-Z\.]*([^\.][\d])*)\.(?<version>\d([0-9\.])+)\-%{NOTSPACE:prerelease}\.nupkg%{SPACE}for%{SPACE}%{NOTSPACE:user}\/%{IPORHOST:remote}\.",
"message", "%{DATESTAMP:datestamp}%{SPACE}\[%{GREEDYDATA:request_type}\]%{SPACE}%{NOTSPACE:repo}\:(?<package>[a-zA-Z\.]*([^\.][\d])*)\.(?<version>\d([0-9\.])+)\.nupkg%{SPACE}for%{SPACE}%{NOTSPACE:user}\/%{IPORHOST:remote}\.",
"message", "%{DATESTAMP:datestamp}%{SPACE}\[%{GREEDYDATA:request_type}\]%{SPACE}%{GREEDYDATA:msg}%{SPACE}for%{SPACE}%{NOTSPACE:user}\/%{IPORHOST:remote}\.",
"message", "%{DATESTAMP:datestamp}%{SPACE}\[%{GREEDYDATA:request_type}\]%{SPACE}for%{SPACE}%{NOTSPACE:user}\/%{IPORHOST:remote}\.",
"message", "%{DATESTAMP:datestamp}%{SPACE}\[%{GREEDYDATA:request_type}\]%{SPACE}for%{SPACE}%{NOTSPACE:user}\."
]
}
date {
match => [ "datestamp", "YYYY-MM-dd HH:mm:ss,SSS" ]
}
}
if ![repo] {
mutate {
add_field => { "repo" => "" }
}
}
if ![package] {
mutate {
add_field => { "package" => "" }
}
}
if ![version] {
mutate {
add_field => { "version" => "" }
}
}
if ![prerelease] {
mutate {
add_field => { "prerelease" => "" }
}
}
if ![msg] {
mutate {
add_field => { "msg" => "" }
}
}
if ![tags] {
mutate {
add_field => { "tags" => "" }
}
}
}
output {
if "_grokparsefailure" in [tags] {
stdout { }
} else {
file {
path => "output.json"
}
file {
path => "output.txt"
flush_interval => 0
message_format => "%{+dd/MM/YY} %{+HH:mm:ss} %{request_type} %{user} %{remote} %{repo} %{package} %{version} %{prerelease} %{msg} %{tags}"
}
}
}
#!/bin/bash
# This script uses logstash to format / normalized files that are generated by script "CheckUsersRequests.sh" and selects the files based on users listed in argument file (e.g. "search_users.txt").
#
# e.g.
# ./FormatLogs.sh search_users.txt
#
# Bash (Windows)
# ./FormatLogs.sh search_users.txt /c/temp/_per-user_logs /s/Dev/logstash/logstash-1.3.2-flatjar.jar /c/temp/_output_formatted /c/temp/_temp
#
# NOTE: Cygwin does not work with Java
#
usernames_file="$1"
: ${usernames_file:?"You MUST provide an file with usernames to search"}
input_path="$2"
: "_Output"
logstash_jar_file="$3"
: "/s/Dev/logstash/logstash-1.3.2-flatjar.jar"
output_path="$4"
: "_Output_Formatted"
temp_path="$5"
: "_Temp"
java_path="java"
logstash_config_file="logstash/logstash.conf"
if [ ! -d "$output_path" ]; then
mkdir "$output_path"
fi
if [ ! -d "$temp_path" ]; then
mkdir "$temp_path"
fi
echo "date time request_type user remote repo package version prerelease msg tags" > "header.txt"
while read username
do
if [ ! -e "$input_path/$username.txt" ]; then
echo "ERROR: File not found: $input_path/$username.txt"
else
echo "$username"
# Remove log path from file line
sed "s/^.*\/access.*.log.zip://" "$input_path/$username.txt" > "$temp_path/$username.txt"
${java_path} -Xmx512m -jar ${logstash_jar_file} agent -f "$logstash_config_file" < "$temp_path/$username.txt"
if [ -e "output.json" ]; then
mv "output.json" "$output_path/$username.json"
fi
if [ -e "output.txt" ]; then
cat "header.txt" "output.txt" > "output.log"
rm -f "output.txt"
mv "output.log" "$output_path/$username.log"
fi
fi
done < "$usernames_file"
if [ -e "header.txt" ]; then
rm -f "header.txt"
fi
[CmdletBinding()]
Param(
[parameter(Mandatory=$True,Position=0)]
[alias("I")]
$InputFiles,
[parameter(Mandatory=$False,Position=1)]
[alias("O")]
[String]$OutputFile = ""
)
# This scrip creates an Excel Spreadsheet from formatted "username" files that are generated by script "FormatLogs.sh"
# It checks Active Directiory for account actions, like "LastLogonDate".
#
# e.g.
# .\CreateExcelDoc.ps1 -InputFiles (Get-ChildItem -Path "C:\temp\_output_formatted" -Filter "*.log" | ?{ !$_.PSIsContainer }) -OutputFile "Artifactory_Access_Users.xlsx"
#
$script:Path = $(Split-Path -parent $MyInvocation.MyCommand.Definition)
$script:ModulePath = $(Join-Path -Path "$script:Path" -ChildPath ".\modules")
if (!([string]::IsNullOrEmpty($OutputFile))) {
if (!([System.IO.Path]::IsPathRooted("$OutputFile"))) {
$OutputFile = $(Join-Path -Path $(Get-Location) -ChildPath $([System.IO.Path]::GetFileName("$OutputFile")))
}
}
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) -gt 0)
}
function Insert-ADDetails(
[parameter(Mandatory=$True)]
[System.__ComObject]$worksheet,
[parameter(Mandatory=$True)]
[String]$identity)
{
$xlShiftDown = [Microsoft.Office.Interop.Excel.XlInsertShiftDirection]::xlShiftDown
$props = @("AccountExpirationDate", "Created", "LastBadPasswordAttempt", "LastLogonDate", "Modified", "PasswordLastSet", "whenChanged", "Enabled")
$userADProps = Get-ADUser -identity "$identity" -Properties $props
if ($userADProps -ne $null) {
foreach ($prop in $props) {
$propValue = $userADProps.("$prop")
$propDate = "{0:dd/MM/yyyy}" -f ($propValue)
$propTime = "{0:HH:mm:ss}" -f ($propValue)
$propAdAction = "AD ACTION"
if (!([string]::IsNullOrEmpty($propValue))) {
$eRow = $worksheet.cells.item(2,1).entireRow
$active = $eRow.activate()
$active = $eRow.insert($xlShiftDown)
$rowColor = 37
if ("$prop" -eq "Enabled") {
$propDate = " " # NOTE: Space is used so this row is placed at the top after sorting.
$propTime = " " # NOTE: Space is used so this row is placed at the top after sorting.
if ($propValue -eq $False) {
$rowColor = 3
$prop = "Disabled"
$worksheet.Tab.ColorIndex = 3
} else {
$rowColor = 4
}
}
$columnMax = ($worksheet.usedRange.columns).count
for($column = 1 ; $column -le $columnMax ; $column ++) {
$worksheet.cells.item(2,$column).Interior.ColorIndex = $rowColor
}
$worksheet.Cells.Item(2,1) = ($propDate)
$worksheet.Cells.Item(2,2) = ($propTime)
$worksheet.Cells.Item(2,3) = $propAdAction
$worksheet.Cells.Item(2,4) = "$prop"
}
}
}
}
function Sort-Feild(
[parameter(Mandatory=$True)]
[System.__ComObject]$worksheet)
{
$xlYes = 1
$xlNo = 2
$xlSortOnValues = $xlSortNormal = 0
$xlTopToBottom = $xlSummaryBelow = 1
$xlAscending = 1
$xlDescending = 2
$worksheet.sort.sortFields.clear()
$usedRange = $worksheet.UsedRange
[void]$worksheet.sort.sortFields.add($worksheet.Range("A1"), $xlSortOnValues, $xlDescending, $xlSortNormal)
$worksheet.sort.setRange($worksheet.UsedRange)
$worksheet.sort.header = $xlYes
$worksheet.sort.orientation = $xlTopToBottom
$worksheet.sort.apply()
[void]$worksheet.Range("A1").Select()
}
function Add-worksheet(
[parameter(Mandatory=$True)]
[System.__ComObject]$worksheets,
[parameter(Mandatory=$True)]
[String]$file)
{
$name = [System.IO.Path]::GetFileNameWithoutExtension("$file")
$worksheet = $worksheets.add([System.Reflection.Missing]::Value,$worksheets.Item($worksheets.count))
$worksheet.Name = "$name"
$TxtConnector = ("TEXT;$file")
$CellRef = $worksheet.Range("A1")
$Connector = $worksheet.QueryTables.add($TxtConnector,$CellRef)
$worksheet.QueryTables.item("$($Connector.name)").TextFileTabDelimiter = $True
$worksheet.QueryTables.item("$($Connector.name)").TextFileParseType = 1
[void]$worksheet.QueryTables.item("$($Connector.name)").Refresh()
$worksheet.QueryTables.item("$($Connector.name)").delete()
Insert-ADDetails -worksheet $worksheet `
-identity "$name"
$excel.Rows.Item(1).Font.Bold = $true
[void]$excel.Cells.EntireColumn.AutoFilter()
[void]$worksheet.UsedRange.EntireColumn.AutoFit()
$worksheet.Activate();
$worksheet.Application.ActiveWindow.SplitRow = 1;
$worksheet.Application.ActiveWindow.FreezePanes = $True;
Sort-Feild -worksheet $worksheet
$a = Release-Ref($worksheet)
}
function Main
{
$excel = New-Object -ComObject excel.application
$excel.visible = ([string]::IsNullOrEmpty($OutputFile))
$excel.DisplayAlerts = $False
$workbooks = $excel.Workbooks.Add()
$worksheets = $workbooks.worksheets
while ($workbooks.sheets.count -gt 1) {
$worksheets.Item($workbooks.sheets.count).delete()
}
foreach ($file in $InputFiles) {
Write-Host "$($file.FullName)"
Add-worksheet -worksheets $worksheets `
-file "$($file.FullName)"
}
$worksheets.Item(1).delete()
$worksheets.Item(1).Select()
if (!([string]::IsNullOrEmpty($OutputFile))) {
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlWorkbookDefault
$workbooks.SaveAs("$OutputFile", $xlFixedFormat)
$excel.Quit()
}
$a = Release-Ref($workbooks)
$a = Release-Ref($excel)
$excel = $null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
Main