ניטור, דוחות ואוטומציה בסביבת הענן – מדריך טכני
במאמר הקודם הוסבר תהליך המאגד כמה רכיבי ענן שונים, לכדי תהליך שיכול לתת למנהלי מערכות משוב והתרעות. המאמר הסביר את הארכיטקטורה והשלבים של התהליך. במאמר הבא יוסבר בפרוטרוט התהליך הטכני שבו בונים בפועל את התהליך.
בכללותו, התהליך מתחלק לשני חלקים:
1. צד GCP שבו חיה התשתית והרכיבים שצריך לנטר, ושם גם רץ התהליך המנטר.
2. צד Azure (כשירותי SAAS) שדרכו שולחים במייל תוצאות ניטור והתרעות.
כאן אני מביא כדוגמה הליך שליחת מייל באמצעות Exchange Online. כל ארגון שמחזיק חשבונות מנוי של 365 וזהויות של Entra ID (Azure Active Directory), יכול להשתמש בתצורה זו עבור שליחת דואר. אבל אפשר להשתמש ברעיון דומה (אם כי בשינוי תבנית) על מנת לשלוח דואר באמצעות API של ספקיות דואר אחרות, כמו Gmail.
שליחת מייל אוטומטית דרך Exchange Online צריכה לעבור דרך API של מיקרוסופט. זה כרוך בהליכי אימות מאובטחים כמו Oauth2, ואז שליחה של המייל בתבנית מאוד ספציפית.
תחילה נכין את הרכיבים בצד Azure, כדי שיהיו מוכנים לקבל פניות והתממשקות בזמן שנגדיר ונפעיל את צד GCP.
Entra ID (Azure Active Directory):
הצעד הזה תלוי מאוד באופי הרישוי ותצורת העבודה מול המערכות של 365. זה יכול להיות תצורה היברידית שבה יש Active-Directory On-prem שמסתנכרן ל-Azure Active Directory. אולי יש שרתי דואר On-Prem, אולי זה היברידי ואולי אין כלום On-Prem והכל מתנהל אך ורק דרך 365.
המטרה הסופית היא שיהיה משתמש קיים ב-Entra ID עם תיבת דואר בשירות Exchange Online (לא תיבה On-prem בשירות היברידי).
למשתמש נקרא gcp-monitor-mail@example.com.
כמובן, שאת ה-Example.com צריך להחליף בשם הדומיין שבו משתמשים באמת.
Enterprise Application:
1. בלוח הניהול של Entra ID מוצאים את Enterprise Applications
2. לוחצים למעלה על סימן + New application
3. בדף שנפתח לוחצים שוב למעלה על סימן + Create your own application
4. בחלון הצד שיפתח, לתת את השם – GCP-Mail, ואז לסמן את האפשרות התחתונה – Integrate any other application you don't find in the gallery
5. ללחוץ Create
App registration:
1. בלוח הניהול של Entra ID לעבור ל-App registration
2. בדף שיפתח, לשים לב ולראות שישנן 3 לשוניות. אנחנו נהיה באמצע, בלשונית של Owned applications. צריך לעבור ללשונית השמאלית של All applications.
3. להכנס לאפליקציה GCP-Mail.
4. כשנכנסים, מגיעים לדף הראשוני של האפליקציה – Overview. נראה שם כמה שורות עם פרטי האפליקציה. צריך להעתיק 2 שדות ולהדביק בצד: Application (client) ID, Directory (tenant) ID.
5. לעבור ל-Blade של API permissions.
6. ללחוץ על + Add a permission.
7. בחלון הצד שיפתח, לבחור באפשרות העליונה – Microsoft Graph.
8. אז תפתחנה שתי אפשרויות. לבחור בימנית – Application permissions.
9. אז תופיע רשימה של קבוצות הרשאות. נכתוב בחיפוש mail.send. אז נראה רק את קבוצת ההרשאות "Mail". נפתח אותה ונסמן את ההרשאה Mail.Send, ונלחץ למטה "Add permissions".
10. כעת נראה שההרשאה מופיעה, אבל בעמודה Status מופיע סימן אזהרה. לכן נלחץ למעלה "Grant admin consent <Organization name>". זה מימין לסימון + Add a permission.
הנפקת Token:
1. עדיין ב-App registration, הולכים ל-Blade של Certificates & Secrets.
2. לוחצים על + New client secret.
3. אפשר לתת תיאור כמו "Secret for sending mail". לא הכרחי. בוחרים את זמן התפוגה של אותו Secret. עדיף 3-6 חודשים. לוחצים למטה Add.
4. ה-Secret יופיע ברשימה, ורק בפעם הראשונה הזו, מיד לאחר שיצרנו אותו, נוכל לראות את ה-Value שלו ולהעתיק אותו. להעתיק ולשמור בצד.
הקשחה:
כעת ניתן באמצעות אותם Client-ID (App ID) ו-Client-Secret לשלוח דואר בשם כל משתמש שיש לו תיבה ב-Exchange Online. מטעמי אבטחת מידע נרצה להגביל את האפשרות לשלוח דואר בתצורה הזו רק למשתמשים נבחרים.
את ההקשחה הזו נבצע באמצעות פקודות של Exchange Shell של Exchange Online.
דבר ראשון נתחבר לשם באמצעות הפקודה הבאה:
Connect-ExchangeOnline
מן הסתם נצטרך לעבור את הליך ההזדהות המאובטח, ולאחריו נוכל לבצע פעולות באמצעות פקודות. אז נוכל להזין את שתי הפקודות הבאות:
New-DistributionGroup -Name "Mail-Graph" -Type "Security" -HiddenFromAddressListsEnabled $true -Members @("gcp-monitor-mail@example.com")
New-ApplicationAccessPolicy -AppId <APP ID> -PolicyScopeGroupId Mail-Graph -AccessRight RestrictAccess
הפקודה הראשונה יוצרת קבוצה שאליה אנחנו מכניסים את המשתמש המיועד לשלוח מיילים בתצורה הזו, הפקודה השניה יוצרת חוק שרק חברי הקבוצה הנ"ל יוכלו להשתמש באפליקציה שיצרנו כדי לשלוח דואר. אפשר להוסיף את ה-Flage של Description לכל אחת מהפקודות האלה, למקרה שבעתיד מישהו יתקל בקבוצה או בחוק וינסה להבין מה עוללנו שם.
– כאן הסתיימה הכנת החלק של צד Azure, ועוברים לצד של GCP.
IAM:
1. ליצור חשבון שירות (Service account) בשם monitoring-jobs.
2. לתת לו ברמת הפרויקט את ה-Role של Cloud Run Service Agent.
3. להעתיק את השם המלא (תבנית מייל) של החשבון ולשים בצד.
בהמשך ניתן לו הרשאות פרטניות למשאבים אחרים.
4. ליצור חשבון שירות בשם custom-build.
5. לתת לו ברמת הפרוייקט את ההרשאה של Cloud Build Service Account.
6. להעתיק את השם המלא של החשבון ולשים בצד.
למיטיבי לכת, הייתי ממליץ להחליף את ההרשאה הכללית של Cloud Build Service Account ברשימת ההרשאות המקוצצת הבאה:
- cloudbuild.builds.create
- cloudbuild.builds.update
- cloudbuild.builds.list
- cloudbuild.builds.get
- cloudbuild.workerpools.use
- logging.logEntries.create
- logging.logEntries.list
- logging.views.access
- pubsub.topics.create
- pubsub.topics.publish
כאמור, זה למיטיבי לכת. יכול להיות שמחר או מחרתיים זה ישתנה, וכדי להריץ Build בסיסי תידרש הרשאה נוספת או אחרת. בנוסף, זה גם תלוי בתצורה שבה מפעילים את ה-Build. תמיד אפשר לתת הרשאה לחשבון על משאב ספציפי, במקום ברמת הפרוייקט. זה הרבה יותר מאובטח.
Secret Manager:
1. בלוח הניהול של GCP לוחצים על שלושת הפסים למעלה משמאל כדי לקבל תפריט. שם תחת Security ללחוץ על Secret Manager.
2. מי שמשתמש פעם ראשונה במוצר הזה באותו הפרויקט, יצטרך לנבל את הפה. כלומר, ללחוץ Enable API ולהמשיך.
3. לוחצים + CREATE SECRET.
4. ניתן לו שם – graph-app-secret.
5. בשדה של Secret value מדביקים את ה-Secret שיצרנו באפליקציה GCP-Mail ב-Enterprize Applications של Entra ID.
6. לסמן את התיבה של Manually manage locations for this secret, ובתפריט שקופץ לבחור באזור תל אביב.
7. אפשר לסמן ולקבוע תפוגה. מי שיסמן ויקבע תפוגה, יוכל ליצור סקריפט שירוץ כל שבוע ויזהיר אותו לפני תפוגת ה-Secret, כדי שינפיק חדש באפליקציה ב-Azure ויחליף ב-Secret Manager.
8. לוחצים למטה על CREATE SECRET.
הרשאות:
לאחר שיש לנו Secret, לתת לחשבון השירות שיצרנו קודם הרשאות לקרוא אותו. כך הסקריפטים שירוצו יוכלו להתחבר למשאבים אחרים בצורה מאובטחת, בלי לשמור סיסמאות בקוד.
- לאחר יצירת ה-Secret נעבור לדף שלו. אם לא, אפשר ללחוץ עליו ולהכנס.
- נעבור ללשונית PERMISSIONS.
- ללחוץ למעלה על GRANT ACCESS.
- תקפוץ חלונית מימין, שם בשדה של New principal נדביק את המייל של חשבון השירות שיצרנו.
- ללחוץ על השדה Select a role ובחלון שיקפוץ נכתוב בחיפוש "Accessor". זה יקפיץ לנו את ההרשאה שאנחנו צריכים – Secret Manager Secret Accessor. לבחור.
- ללחוץ SAVE.
Storage Bucket:
פעמים רבות נרצה לאחסן תוצאות של דוחות וניטור. נוכל לאחסן את הקבצים במקומות שונים בהתאם לסיווג. למשל – דלי עבור קבצי דו"חות על תשתית הענן של גוגל, דלי עבור קבצי דו"חות של סביבת Active-Directory מסוימת, דלי עבור משימות אחרות וכו'.
באמצעות חלוקה לדליים, ניתן לנהל את הדלי כולו באמצעות מערכת הרשאות אחת אחידה, ולאפשר גישה לדלי עבור חשבון שירות מסוים שמבצע את אותה הפעולה.
כך חשבון שירות בעל הרשאות ניטור (או אפילו שינוי הגדרות מסוימות) בסביבת GCP, אין לו הרשאות לסביבת Active-Directory או לקבצי התוצאות. חשבון שמריץ סקריפטים על Active-Directory חסר את ההרשאות למערכות GCP או לקבצי הדו"חות שמנפיקים עליה.
בצורה כזו, אם צריך סיסמה או משהו כזה לצרכי הסקריפט, מאחסנים ב-Secret נפרדים תחת Secret Manager.
רק לזכור שכל חשבונות השירות שמריצים תהליך שבסוף שולח מייל, כולם צריכים גישה ל-Secret של אפליקציית שליחת המיילים ב-EntraID.
אז ניצור דלי עבור קבצי דו"חות של סביבת GCP:
- בלוח הניהול של GCP לוחצים על שלושת הפסים למעלה משמאל כדי לפתוח את התפריט הכללי, שם לוחצים על Cloud Storage.
- בדף של Cloud Storage לוחצים למעלה על CREATE.
- לתת שם לדלי – צריך לזכור שכל דלי מקבל שם שהוא יחודי בכל סביבת GCP. אם מישהו אי שם בעולם כבר בחר את השם הזה לדלי שיצר, לא נוכל לבחור. אז לצורך ההדגמה אני נותן את השם – yostest-monitoring-gcp. ללחוץ CONTINUE.
- עכשיו צריך לבחור איפה הדלי מאוחסן. לבחור Region באזור תל אביב – me-west1. ללחוץ CONTINUE.
- Storage Class – פה נשאיר את זה רגיל. ללחוץ CONTINUE.
- כפי שהסברתי לעיל, ההעדפה היא לנהל מערכת הרשאות אחת לכל הדלי. ההפרדה בהרשאות תתבצע ברמת הדלי, וכל מה שצריך הרשאות נפרדות יתנהל בדלי נפרד. לכן בשלב הבא לבחור ב-Uniform Access control ואז ללחוץ CONTINUE.
- כעת לבחור אם אנחנו רוצים להגדיר דרכים להגן על הקבצים מפני מחיקות ושינויים. במקרה הזה נראה לי נכון להגדיר Retention של שנה ברמת הדלי. רוב הדוחות (לפחות איך שאני כתבתי אותם )מנפיקים קבצי CSV רזים, וברוב המקרים שם הקובץ נושא חותמת זמן. כך שדוח חדש לא ידרוס את הישן.
- ללחוץ CREATE. בחלונית שתקפוץ, לאשר אכיפה של public access prevention.
אחסון לפרק זמן קצוב:
קודם הגדרנו שכל קובץ שעולה לדלי, משוריין במשך שנה ולא ניתן למחוק אותו. עכשיו נרצה להגדיר שאחרי שנה הקובץ ימחק באופן אוטומטי.
נוכל גם לתת תיקיות שונות לסוגי קבצים שונים, ואת חלקם נמחוק לאחר שנה ואת חלקם לאחר שלוש שנים.
- כעת יפתח הדף של הדלי, ונלחץ על CREATE FOLDER. בחלונית שתקפוץ נכניס את השם – instance-report. לאשר וליצור.
- נעבור ללשונית LIFECYCLE. שם קובעים חוקים שמגדירים מה לעשות עם קבצים מסוימים אחרי פרק זמן מסוים.
- ללחוץ ADD A RULE.
- תחת Select an action לבחור Delete object. ללחוץ CONTINUE.
- בגלל שקודם הגדרנו Retention ברמת הדלי, לא נוכל למחוק קבצים לפני שתעבור שנה. אבל נוכל להחליט שחלק ישארו ליותר משנה. במקרה כזה, נסמן Rule Scope של Object name matches prefix. ואז נשים קבצים של כל דוח בתיקיה אחרת. שם התיקיה זה ה-Prefix שיקבע אחרי כמה זמן נמחק את האובייקטים שבה. כרגע לקבוע את ה-Scope לאובייקטים שהנתיב שלהם כולל את המחרוזת 'instance-report', שזה שם התיקיה שיצרנו קודם עבור הקבצים של אותו הדו"ח.
- למטה יותר יש את Set Conditions. שם נסמן Age, ואז נכתוב 370. הקבצים ימחקו לאחר 370 יום.
- ללחוץ למטה CREATE.
הרשאות:
- נעבור ללשונית PERMISSIONS.
- ללחוץ על GRANT ACCESS.
- לתת למשתמש monitoring-jobs שנוצר קודם, את ההרשאה Storage Object User.
- לאשר ולסגור.
סקריפטים לניטור:
ליבת התהליך היא סקריפטים שיודעים לאסוף נתונים על סביבת הענן, לעבד אותם ולעדכן אותנו על המצב. לכן לפני הכל אנחנו צריכים להגדיר מטרות. סקריפטים שניתן לכתוב לדוגמה:
- רשימת מכונות פעילות – פרוייקט, גודל מכונה, גודל דיסקים, כתובות פנימיות וחיצוניות וכו'.
- כל הדיסקים שאינם מחוברים לאף שרת. לא חבל לשלם סתם אחסון?
- רשימת כתובות IP שאינן מחוברות לאף מכונה.
- דליי אחסון הפתוחים לגישה ציבורית.
- משתמשים בעלי הרשאות ניהול בכל הפרוייקטים.
- נתוני אשכולות קוברנטיס.
- שינויים במכונות – כמו הפעלה, כיבוי, הוספת דיסק, הגדלת דיסק, הוספת והסרת כתובות IP וכו'.
רשימת האפשרויות ארוכה, וכאן אציג לדוגמה 3 סקריפטים. POC.
את הסקריפט הראשון אציג עכשיו, השניים האחרונים יופיעו בסוף כנספח א' ו-ב'.
את הסקריפט נשמור כקובץ PowerShell – InstanceReport.ps1.
סקריפט – רשימת מכונות פעילות:
$file = "InstanceReport-" + (Get-Date).ToString("dd-MM-yyyy") + ".csv"
$projects = gcloud projects list --format=json | ConvertFrom-Json
$list = @()
# Loop over all projects to get VM info of all VMs.
foreach ($x in $projects.projectId) {
$vms = gcloud compute instances list --format "json" --project=$x | ConvertFrom-Json
$disks = gcloud compute disks list --format "json" --project=$x | ConvertFrom-Json
# Set, add and modify attributes names with the desired format for the report.
$selected = $vms | select @{N='Name' ;E={$_.name}},
@{N='Project' ;E={$x}},
@{N='Zone' ;E={$_.zone.split("/")[-1]}},
Description,
@{N='Env' ;E={$_.labels.env}},
@{N='Product' ;E={$_.labels.product}},
@{N='Role' ;E={$_.labels.role}},
@{N='MachineType' ;E={$_.machineType.split("/")[-1]}},
@{N='NIC' ;E={$_.networkInterfaces.name -join ","}},
@{N='IP' ;E={$_.networkInterfaces.networkIP -join ","}},
DiskName, DiskSize, DiskType,
@{N='OS'; E={
# Assuming a simple metadata key or image naming convention for OS type. This is a placeholder and needs adjustment.
if ($_.metadata.items -match 'windows') { "Windows" } else { "Other" }
}},
selfLink
# Attached disks info which is not included in VM metadata.
foreach ($y in $selected) {
$y.DiskName = ($disks | ?{$_.users -eq $y.selfLink}).name -join ","
$y.DiskSize = ($disks | ?{$_.users -eq $y.selfLink}).sizeGb -join ","
$y.DiskType = (($disks | ?{$_.users -eq $y.selfLink}).type | %{$_.split("/")[-1]}) -join ","
}
$list += $selected | Sort-Object -Property Product
}
$list | select -Property * -ExcludeProperty selfLink | Export-Csv -NoTypeInformation -Encoding UTF8 -Path ("/tmp/" + $file)
gsutil cp ("/tmp/" + $file) gs://reports-bucket-org/VM-Changes
"<h2>Detailed list of all VM's in the organization</H2>" | Out-File /tmp/mail-body
# ====================================================================
# Configure mail attributes
$mailElements = '' | select To, Cc, Subject, Attachments
$mailElements.To = "recipiant@example.org"
$mailElements.Cc = "cc@example.org"
$mailElements.Subject = "Instance Report"
$mailElements.Attachments = $file
$mailElements | Export-Csv -NoTypeInformation -Encoding UTF8 -Path /tmp/mail-elements.csv
/infrastructure-jobs/Send-Mail.ps1
הסבר קצר על הסקריפט:
עד שורה 36, הסקריפט אוסף רשימה של כל הפרויקטים. לולאה עוברת אחד אחד ולוקחת משם רשימות של המכונות הוירטואליות. אז התהליך מעבד את המאפיינים שלהן לכדי טבלת נתונים בפורמט שאנחנו רוצים.
בשורות 38-39 הטבלה יוצאת כקובץ csv ומועתקת לדלי אחסון.
משם ישנה הכנה לשליחת מייל בתבנית מאוד מסוימת. טקסט בתבנית כלשהי עובר לקובץ שיכיל את גוף המייל. כתובות לנמענים, כותרת המייל ושדה עבור שם קובץ מצורף, נכנסים לתבנית csv.
אז מתבצעת קריאה לסקריפט נפרד שמבצע את שליחת המייל בהתאם לנתונים שהוגדרו בשני קבצים אלה.
שליחת המייל:
כאמור, בחרנו לשלוח את המיילים באמצעות Exchange Online. את המייל ניתן לשלוח בכמה תצורות. מבין האפשרויות, אנחנו שולחים את המייל בתור JSON. לצורך כך נצטרך לעבור תהליך שבונה את ה-JSON בהתאם לנתוני המייל ותוכנו, וכן מנהל את ההתחברות המאובטחת לאותו API שנקרא Microsoft Graph.
בניית תבנית JSON:
בתור התחלה ניצור תבנית כללית עבור אותו JSON:
@{
message = @{
subject = $mail.Subject
body = @{
contentType = "HTML"
content = $msg
}
toRecipients = @(
@{emailAddress = @{"address" = to}}
)
ccRecipients = @(
@{emailAddress = @{"address" = cc}}
)
from = @{emailAddress = @{"address" = "reporter@example.org"}}
attachments = @(
@{
"@odata.type" = "#microsoft.graph.fileAttachment"
name = filename
contentType = "text/csv"
contentBytes = $global:attachments[num]
}
)
}
}
את התבנית הזו נשמור כקובץ בשם msg-template.txt.
הסבר:
התבנית הזו מכילה את השדות העיקריים ב-JSON של המייל, ואנחנו צריכים לעדכן את השדות בהתאם למייל שאותו נרצה לשלוח.
בניה, עיבוד, התחברות ושליחה:
הסקריפט הבא מבצע את כל התהליך:
# Functions to insert variables to the mail templates
function Set-Mail-Addresses {
param ($addr)
for ($i = 0; $i -lt $addr.split(",").Count; $i++) {
$row = $template[8].Replace("to", ('"' + $addr.split(",")[$i] + '"'))
if ($i + 1 -lt $addr.split(",").Count) {$row = $row + ","}
$global:message += $row
}
}
function Set-Mail-Attachments {
param ($filename)
for ($i = 0; $i -lt $filename.split(",").Count; $i++) {
$location = ("/tmp/" + $filename.split(",")[$i])
$file = Get-Content -Path $location -AsByteStream -Raw
$global:attachments += [System.Convert]::ToBase64String($file)
$global:message += $template[15..16]
$global:message += $template[17].Replace("filename", ('"' + $filename.split(",")[$i] + '"'))
$global:message += $template[18]
$global:message += $template[19].Replace("num", $i)
$row = $template[20]
if ($i + 1 -lt $filename.split(",").Count) {$row = $row + ","}
$global:message += $row
}
}
# =========================================================================
# Get Token for Microsoft Graph authentication
$uri = "https://login.microsoftonline.com/<tenent ID>/oauth2/v2.0/token"
$body = @{
client_id = "APP ID"
scope = "https://graph.microsoft.com/.default"
client_secret = gcloud secrets versions access latest --secret graph-app-secret --project yositest
grant_type = "client_credentials"
}
$token = (Invoke-RestMethod -Uri $uri -Method Post -Body $body).access_token
$Headers = @{
'Content-Type' = "application\json"
'Authorization' = "Bearer $token" }
# =========================================================================
# Set mail elements
$mail = Import-Csv /tmp/mail-elements.csv
$msg = Get-Content /tmp/mail-body | Out-String
$template = Get-Content /infrastructure-jobs/msg-template.txt
$global:attachments = @()
$global:message = $template[0..7]
Set-Mail-Addresses -addr $mail.To
$global:message += $template[9..10]
if ($mail.Cc -ne '') {Set-Mail-Addresses -addr $mail.Cc}
$global:message += $template[12..13]
if ($mail.Attachments -notmatch "N/A") {
$global:fileb = @()
$global:message += $template[14]
Set-Mail-Attachments -filename $mail.Attachments
$global:message += $template[21]
}
$global:message += $template[22..23]
# Convert message string to JSON and send mail.
$msgjson = Invoke-Expression ($message | Out-String) | ConvertTo-Json -dept 100
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/reporter@example.org/sendMail" -Method Post -Headers $headers -Body $msgjson -ContentType "application/json"
הסבר קצר על הסקריפט:
עד שורה 26 – מופיעות שתי פונקציות שנועדו לבנות את תבנית ה-JSON בהתאם לנתוני השליחה שהתקבלו מהסקריפט הקודם.
שורות 32-45 – מבצעות הנפקה של Access Token מאת Microsoft Graph. איתו יהיה אפשר אחר כך לשלוח את המייל.
בשורה 32 – צריך להחליף את ה-tenent ID בערך שרשמנו קודם בצד.
בשורה 35 – צריך להחליף את ה-App ID בערך שרשמנו קודם בצד.
בשורות 51 – 69 – מתבצעת בניה של ה-JSON בהתאם לכל הנתונים שכתבנו קודם – גוף המייל, קבצים מצורפים, נמענים וכותרת. בשורה 74 כל המבנה הזה הופך מטקסט שטוח לאובייקט JSON.
בשורה 75 – מתבצעת שליחה של המייל עם ה-Token שיצרנו קודם למתן גישה.
– כדאי לשים לב לחלק האמצעי. זו הדגמה טובה של הדרך שבה ניגשים לקבל Access Token בפרוטוקול Oauth2. משם רואים איך מכינים את המפתח ב-Header כדי לגשת באמצעותו ל-API.
– חשוב לשים לב לנתיבי הקבצים שבקוד. זה לא לחינם, ויובהר בהמשך כשנראה איך מאחסנים את הקוד ואיך ניגשים אליו באמצע התהליך.
אז יש לנו כרגע סקריפט שמבצע עבודת ניטור מסוימת, ולאחר שהוא אוסף את נתוני הניטור, הוא קורא לסקריפט אחר ששולח את הנתונים במייל לנמענים שהכנסנו בסקריפט הראשון.
עכשיו אנחנו צריכים להריץ את הסקריפט לפי תזמון מסוים בצורה אוטומטית.
Secure Source Manager:
בזמן שבניתי את הפתרון הזה, השתמשתי בתצורה הזו של GCP ל-GIT עבור אחסון ספריות קוד. התצורה הזו לא כל כך מתממשקת עם דברים אחרים. בשלב שבו המאמר נכתב, המוצר אינו אפוי עד הסוף (עד כמה שידוע לי, השימוש בו עדיין לפי הזמנה). אבל זה עובד ויכול לשמש כדוגמה. באותה המידה ניתן להשתמש ב-Github או אפילו בדלי אחסון. אם כי שימוש ב-Github יאפשר התממשקות קלה יותר עם מוצרים אחרים כמו Cloud Build או אפילו Cloud Run.
בגלל שהמוצר עוד לא פתוח לגמרי לציבור הרחב, לא אתן הוראות הקמה, אלא הוראה כללית כיצד ניתן לעשות Build מתוך ספריית קוד מוכנה במוצר הזה, ב-Github או בדלי אחסון.
באופן כללי אומר שאת הסקריפטים ואת הקוד שנרצה להריץ במשימות שלנו, נעלה לאותה ספריית קוד, איפה שלא נאחסן אותה. את הקוד של משימות הניטור וההגדרה השונות, לחלק לתיקיות בהתאם לסיווג ולמטרות השונות. בנפרד לשמור כמה קבצים שמשמשים את כולם. לדוגמה:
1. קובץ התבנית של ה-JSON.
2. קובץ ובו הסקריפט ששולח מייל. שכל משימה שרוצה לשלוח מייל בסוף קוראת לו.
אז בנינו לנו את ספריית הקוד. ובהתאם למה שיש בסקריפטים, נניח שקראנו ל-Repository בשם " infrastructure-jobs", ושם הצבנו תיקיות וקבצים שונים
Artifact registry:
היות ואנו מתעתדים להריץ את התהליכים שלנו באמצעות Cloud Run, נצטרך להשתמש ב-Artifact Registry כדי לאחסן Container Image.
- בלוח הניהול של GCP, לוחצים על שלושת הפסים למעלה משמאל כדי לפתוח את התפריט הראשי. שם למטה ללחוץ על VIEW ALL PRODUCTS. שם בדף שנפתח נלך לקטגוריה של CI/CD ונבחר ב-Artifact Registry. לחילופין, פשוט נחפש Artifact Registry בשורת החיפוש בראש העמוד.
- מי שזו פעם ראשונה שהוא משתמש במוצר (או לפחות בפרויקט שבו הוא רוצה לשמור את ה-Container Image), צריך לאפשר את ה-API של המוצר. ללחוץ ENABLE.
- בדף שנפתח, ללחוץ למעלה + CREATE REPOSITORY.
- ניתן את השם – monitoring-jobs.
- לבחור באזור תל אביב (me-west1).
- ללחוץ על ADD CLEANUP POLICY.
- נסמן את האפשרות של Delete artifacts.
- ניתן שם את השם – last-3.
- נסמן את Keep most recent versions ומיד בשדה שמתחת (כתוב Keep count) נשים את הספרה 3.
- ללחוץ למטה על CREATE.
הרשאות:
ברגע שנוצרה ספריה, נצטרך לתת עליה הרשאות.
- בדף של Repositories לסמן את תיבת הסימון ליד monitoring-jobs.
- מיד נראה בצד ימין חלונית ובה PERMISSIONS. ללחוץ על ADD PRINCIPAL.
- בשדה של new principal להדביק את המייל של חשבון השירות montoring-jobs שיצרנו קודם.
- ללחוץ על השדה Select a role , ובחלון שקופץ לכתוב בחיפוש Artifact. שם בוחרים את ההרשאה – Artifact Registry Reader.
- בהמשך בודקים באיזה חשבון משתמשים ל-Build, ולו נותנים בדיוק באותה הצורה, את ההרשאה Artifact Registry Writer.
Cloud Build:
כדי להריץ את הסקריפט בסביבת הענן, נצטרך לבנות Container Image שידע להפעיל את הסקריפטים האלה.
כאן בונים קונטיינר שיודע להריץ PowerShell ואת חבילות שורת הפקודה של GCP – כמו gcloud, gsutil וכו'. לקונטיינר כבר נעתיק את קבצי הסקריפטים, כי זו ספריה קטנה במשקל.
אז לפני הכל אנחנו צריכים Dockerfile. וככה זה יראה:
FROM gcr.io/google.com/cloudsdktool/google-cloud-cli:slim
# Install powershell
RUN mv /etc/apt/trusted.gpg /etc/apt/trusted.gpg.d/
RUN apt-get install -y wget
RUN cd /tmp
RUN wget -q https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb
RUN dpkg -i packages-microsoft-prod.deb
RUN rm packages-microsoft-prod.deb
RUN apt-get update
RUN apt-get install -y powershell
# Install modules to allow remote powershell
RUN echo $' \n \
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 \n \
Install-Module PSWSMan -Scope AllUsers -Force \n \
Install-WSMan \n \
Install-Module -Name WindowsCompatibility -force ' >> /tmp/scr.ps1
RUN pwsh -c "/tmp/scr.ps1"
RUN apt install gss-ntlmssp -y
# Get scripts library
RUN mkdir infrastructure-jobs
ADD ./infrastructure-jobs /infrastructure-jobs
# Set local timezone
RUN rm -rf /etc/localtime
RUN ln -s /usr/share/zoneinfo/Asia/Jerusalem /etc/localtime
RUN echo "Asia/Jerusalem" > /etc/timezone
# Clean up
RUN rm /tmp/scr.ps1
SHELL ["powershell", "-Command"]
מבנה הקובץ:
FROM – חבילת הבסיס של הקונטיינר מתחילה מקונטיינר בנוי של גוגל שנועד להריץ את הפקודות של הענן שלהם.
עכשיו נוסיף לחבילה הזו פריטים נוספים.
שורות 4-11, התקנת PowerShell Core – PowerShell Core זו חבילה בסיסית של PowerShell שנועדה להתקנה ולהרצה על מערכות שאינן ווינדוס.
– התהליך מתקין את החבילה כפי שהיא מיועדת לגרסה 12 של Debian. במקור מיקרוסופט נתנו רצף פקודות שאמור בצורה גמישה להתקין לגרסה הקיימת האחרונה, אבל ראיתי שזה לא עובד כמו שצריך ועוד לא מצאתי את הזמן להכין את הדרך שבה זה יעבוד כמו שצריך. אז בינתיים נהיה מוקשחים לגרסה מסוימת.
שורות 13-21, התקנת מודולים שונים עבור PowerShell – בעצם כל הבלוק הזה יורה מחרוזת ארוכה שנקלטת לתוך קובץ. הקובץ נשמר במתכונת סקריפט של PowerShell, והסקריפט מתקין את המודולים. המודולים והחבילה שמותקנת בשורה 21, נועדו לאפשר התקשרות מרוחקת של הסקריפט עם שרתי ווינדוס – כמו Invoke-Command או Enter-PSSession.
– מי שרוצה להוסיף עוד מודולים – כמו מודולים של SCCM או Microsoft Graph או MSOL או כל מודול אחר של PowerShell, פשוט מוסיף שורות אחרי שורה 18. בכל שורה חדשה שמים פקודה להתקנת מודול חדש. כל עוד זה לא שורה אחרונה, בסוף השורה צריך לשים את הרצף "\n \" כדי שבסקריפט שיוצא מזה (scr.ps1) תופיע שורת פקודה חדשה.
שורות 24-25, הוספת ספריית הסקריפטים לקונטיינר.
שורות 28-30, הגדרת הקונטיינר שירוץ לפי שעון ישראל.
שורה 34, הגדרת הקונטיינר להפעיל את PowerShell מיד כשהוא עולה.
Build YAML:
לאחר שיש לנו Dockerfile שעובד, נצטרך YAML שמגדיר ל-Cloud Build איך לבנות את הקונטיינר.
קובץ YAML כזה סדור ומתחלק על פי צעדים, כאשר בכל צעד מבצעים פעולה אחרת.
הקובץ שאיתו נעבוד מכיל שני צעדים בלבד:
1. קונטיינר של git שתפקידו להעתיק את ספריות הקוד מה-Repositories שבו אנחנו מאחסנים אותן, לסביבה של ה-Build.
2. קונטיינר של Docker שיבנה את הקונטיינר ויעלה אותו ל-Artifact Registry באמצעות פקודות Docker.
הקוד:
#build :
#======
steps:
- name: gcr.io/cloud-builders/git
script: >
mkdir infrastructure-jobs
cd infrastructure-jobs
git clone
https://repository-hub/monitoring/infrastructure-jobs.git
.
mv Dockerfile.gcloud-pwsh ../Dockerfile
- name: gcr.io/cloud-builders/docker
script: >
ls -l
docker build -t
me-west1-docker.pkg.dev/yositest/monitoring-jobs/gcloud-pwsh .
docker push
me-west1-docker.pkg.dev/yositest/monitoring-jobs/gcloud-pwsh
הסבר:
ישנה תצורה שבה מבצעים פעולות שונות בתוך YAML של Build.
כדי לפשט את הדברים, פשוט נריץ את התהליך באמצעות סקריפטים של bash שיפעלו בכל שלב.
ה-Build עובד בתוך Context מסוים. זה אומר שישנה סביבה בה אפשר לשמור קבצים בצעד אחד, ולגשת אליהם אחר כך בצעדים הבאים.
צעד ראשון:
- בשורה 4 נפתח הצעד הראשון, שמריץ קונטיינר של git.
- שם אנחנו יוצרים תיקיה עבור ספריות הקוד, ונכנסים לשם. כאשר נעבור לצעד הבא, הקונטיינר שיעלה עבור הצעד הבא יטען את סביבת העבודה ונמצא שם את התיקיה הזו ונוכל לעבוד איתה.
- אז באמצעות פקודת git clone אנחנו מעתיקים את התוכן של ספריית הקוד לתיקיה שיצרנו.
- אותו Dockerfile שראינו קודם, גם הוא יושב בספריית הקוד, בתיקיה הכללית שעליה דיברנו קודם. אז מעבירים אותו מתת התיקיה הכללית שבה הוא קיים, אל התיקיה הראשית. זה חשוב, כי מהמקום שבו רץ אותו Dockerfile, הוא צריך גישה פשוטה מבחינת נתיב אל כל התיקיות שהוא ירצה להעתיק.
– אם שומרים את ספריות הקוד בתוך דלי אחסון, אפשר להשתמש בקונטיינר של gcloud ובפקודת gsutil כדי להעתיק את הספריות מן הדלי
צעד שני:
- בשורה 15 נפתח הצעד השני, שמריץ קונטיינר של Docker.
- שם מפעילים פקודת docker build שתבנה לנו Container Image על בסיס ה-Dockerfile שהעברנו קודם לתיקיה הראשית.
- לאחר מכן באה פקודת docker push שמעלה ל-Artifact Registry את ה-image שנבנה עכשיו.
– לשים לב שמלכתחילה ה-Image נוצר עם תגית שם שכוללת את ה-Instance הכללי של Artifact Registry של אזור תל אביב, לאחר מכן בא שם הפרויקט שבו הקמנו את הספריה, לאחר מכן שם הספריה, ולבסוף שם ה-Image שישב בתוך הספריה.
– התצורה הזו של השם, מאפשרת לפקודה docker push להעלות את ה-Image בדיוק לאן שאנחנו רוצים.
תהליך Build:
- בלוח הניהול של GCP, לוחצים על שלושת הפסים למעלה משמאל כדי לפתוח את התפריט הראשי. שם למטה ללחוץ על VIEW ALL PRODUCTS. שם בדף שנפתח נלך לקטגוריה של CI/CD ונבחר ב-Cloud Build. לחילופין, פשוט נחפש Cloud Build בשורת החיפוש בראש העמוד.
- מי שזו פעם ראשונה שהוא משתמש במוצר (או לפחות בפרויקט שבו הוא רוצה ליצור את ה-Build), צריך לאפשר את ה-API של המוצר. ללחוץ ENABLE.
- בתפריט השמאלי של Cloud Build, לבחור ב-Triggers.
- בדף שנפתח, ללחוץ למעלה + CREATE TRIGGER.
- ניתן את השם – gcloud-pwsh.
- כעת צריך לבחור את האירוע שיפעיל את ה-Trigger. זה מאוד משנה בהתאם למקום שבו מאחסנים את ספריות הקוד. אם זה במקום כמו Github, אפשר להתממשק ולקשר את ה-Repository ל-GCP באמצעות Repositories (שנמצא ממש מעל Triggers). אז יהיה אפשר להפעיל את ה-Trigger בתגובה לכל Push. אם זה במקום שאי אפשר לחבר דרך Repositories אבל זו עדיין ספריה מבוססת GIT, אפשר להגדיר בספריה Webhook. זה עדיין יפעיל את ה-Trigger בתגובה לכל Push לספריה. אם הספריה יושבת על דלי, אפשר להשתמש ב-Pub/Sub שמפעיל את ה-Trigger בכל פעם שעולה קובץ לדלי. קצרה היריעה מכדי שאפרט בצורה טכנית את הדרך לבצע את כל השיטות האלה. רק אומר שבמקרה שלי הגדרתי Webhook.
- כשנגלול למטה להגדרות ה-Build, נראה שאם בחרנו ב-Webhook, מתאפשרת לנו רק העבודה באמצעות YAML או JSON של Cloud Build, שנוכל להזין פה במערכת. תחת האפשרות של Inline, ללחוץ על OPEN EDITOR.
- מימין יקפוץ חלון ובו תיבת טקסט גדולה שאותה ננקה ונדביק שם את ה-YAML שהכנו וראינו לעיל.
- ללחוץ DONE.
- כמעט הכי למטה יש שדה של Service account. לבחור שם את חשבון השירות custom-build שיצרנו לעיל.
- ללחוץ Save.
הרשאות:
– צריך לתת הרשאות לחשבון שמריץ את ה-Build. הרשאת קריאה לספריות הקוד, והרשאת כתיבה לספריה שאליה נרצה להעלות את הקונטיינר ל-Artifact Registry.
כעת מה שנשאר זה לבצע Push לספריות הקוד או להריץ את ה-Build בצורה אחרת. בסיום, יהיה לנו Container Image מוכן לעבודה ב-Artifact Registry.
Cloud Run:
ועכשיו, לאחר כל ההכנות, הבישול, האפיה, הניקיון והסדר, ניתן להגיש לשולחן.
מה מריצים:
- ללחוץ על שלושת הפסים כדי לפתוח את התפריט הראשי, ושם ללחוץ ALL PRUDUCTS. שם בקטגוריה Serverless מוצאים את Cloud Run. או לחילופין, לכתוב Run בחיפוש למעלה.
- אם השירות עוד לא היה בשימוש באותו הפרוייקט, צריך ללחוץ Enable API.
- בדף של השירות ללחוץ למעלה CREATE JOB.
- נפתח דף הגדרת שירות. צריך לבחור כתובת של Container Image. ניתן את הכתובת שיצרנו ב-Build, יחד עם התגית latest. לאמור – בכל פעם שתופעל המשימה, Cloud Run ימשוך את הגרסה האחרונה של אותו Image. הכתובת במקרה שלנו: me-west1-docker.pkg.dev/yositest/monitoring-jobs/gcloud-pwsh
- לבחור שם למשימה. במקרה שלנו השם יהיה gcp-instance-report.
- מיקום שבו תרוץ המשימה – אזור תל אביב.
- ללחוץ על החץ שפותח את ההגדרות של Container, Variables & Secrets, Connections, Security וכו'
- בשדה של Container Command נכניס את pwsh.
- בשדה של Arguments נכניס את הנתיב לקובץ הסקריפט (בתוך הקונטיינר) שנרצה להריץ באותה המשימה. כשעשינו Build, העתקנו את ספריות הקוד לתיקיה infrastructure-jobs בתוך הקונטיינר. נניח שיש שם תת תיקיה בשם GCP-Monitoring ובה יושב הסקריפט InstanceReport.ps1. עכשיו ה-Argument יצטרך להיות כדלקמן: /infrastructure-jobs/GCP-Monitoring/Instance-report.ps1
- רוב הסקריפטים לא צריכים הרבה משאבים כדי לרוץ, לכן למעט מקרים חריגים נשתמש במינימום האפשרי שאפשר להקצות למשימה.
- Timeout – פה זה משתנה ממשימה למשימה. מומלץ בהתחלה להגדיר Timeout גבוה, לראות כמה פעמים כמה זמן לוקח למשימה להסתיים בהצלחה, ולהגדיר Timeout מתאים בהתאם למשך ריצה נורמלי של המשימה + 30%.
- Retries – כמה פעמים המשימה תנסה לחזור על עצמה במקרה של כשלון, עד שתרוץ בהצלחה או תוותר. זה תלוי בהעדפות. לפעמים נרצה להגדיר 0, לפעמים נגדיר 3.
- Parallelism – האם משימה הזו יכולה להפעיל במקביל עוד קונטיינרים, בזמן שקונטיינר אחר עוד מריץ משימה שהחלה קודם? כמה עותקים של המשימה הזו נאפשר לרוץ בו זמנית? גם זה תלוי במשימה ובהעדפה. אבל במקרה שלנו בדרך כלל זה יהיה 1. רק משימה אחת תרוץ בו זמנית, וזה המספר ששמים שם. נסמן את Limit the number of concurrent tasks וכשיפתח השדה Custom parallelism limit, נזין את הספרה 1.
- ללחוץ CREATE.
הבדלים במשימות מול Active-Directory:
- משימות שרצות מול Active-Directory ודאי תצטרכנה גישה לרשתות פנימיות, VPC וכאלה. את זה מגדירים תחת הלשונית CONNECTIONS.
- שם מסמנים את התיבה של Connect to VPC for outbound traffic.
- אם צריך לדבר רק עם Active Directory, זה קל. בוחרים Send traffic directly to a VPC ואז בוחרים ברשת שבה יושבים שרתי ה-DC.
- אם צריך לדבר עם גורמים נוספים ברשת, כדאי להתחבר לאיזו רשת מרכזית או התקן תקשורת שינתב את הפניות לכל מקום ברשת. דוגמה לכך נתתי במעבדה הרביעית – מבנה כוכב.
כעת ניתן להריץ את המשימה ולבדוק שהכל פועל כמו שצריך והמייל מגיע.
הערות:
כדוגמת אותו סקריפט שנתתי, אפשר להריץ המון סקריפטים שמנטרים, מעדכנים ומגדירים סביבות שונות, יוצרים אוטומציה וכו'.
דוגמה לכך היא סביבה ארגונית רבת פרויקטים, שבה נרצה לקבוע שבכל פרויקט שקם נאפשר באופן אוטומטי API מסוימים, שבכל רשת שנפתח נגדיר DNS מסוים וכו' וכו' וכו'. עם סקריפטים אפשר לעשות כמעט הכל, ולכן ברגע שהמערך קיים, ניתן להשתמש בו לכל דבר בכפוף לחשבון שירות שמפעיל את המשימה ויש לו גישה לאן שצריך, או חיבור רשת ליעד מסוים במקרים אחרים.
נספח א' – OrphanDisks:
כאן יופיע סקריפט שסורק את כל הפרויקטים בסביבת GCP ומוצא דיסקים שאינם שייכים לאף VM.
לבסוף הסקריפט שולח מייל עם המידע, כדי שמנהלי המערכת ידעו אם צריך למחוק או לעדכן משהו.
הסקריפט:
# Loop over all project to list all disks.
$projects = gcloud projects list --format=json | ConvertFrom-Json
$list = @()
foreach ($x in $projects.projectId) {
$disks = gcloud compute disks list --format "json" --project=$x | ConvertFrom-Json
$disks | Add-Member -MemberType NoteProperty -Name 'ProjectID' -Value $x
$list += $disks
}
# Set disk desired attributes.
$list | %{$_.sourceSnapshot = $_.sourceSnapshot.split("/")[-1]}
$list | %{$_.type = $_.type.split("/")[-1]}
$list | %{$_.users = $_.users.split("/")[-1]}
$list | %{$_.zone = $_.zone.split("/")[-1]}
$list | %{$_.lastAttachTimestamp = $_.lastAttachTimestamp.ToString("dd/MM/yyyy HH:mm")}
$list | %{$_.lastDetachTimestamp = $_.lastDetachTimestamp.ToString("dd/MM/yyyy HH:mm")}
# Get all disks which isn't attached to any VM.
$orphans = $list | ?{$_.users -eq $null} | select ProjectID, Name, SizeGB, Type, lastAttachTimestamp, lastDetachTimestamp
# Set table of orphaned disks.
$style = "<style>BODY{font-family: Arial; font-size: 10pt;}"
$style = $style + "TABLE{border: 1px solid black; border-collapse: collapse;}"
$style = $style + "TH{border: 1px solid black; background: #dddddd; padding: 5px; }"
$style = $style + "TD{border: 1px solid black; padding: 5px; }"
$style = $style + "</style>"
$orphans | ConvertTo-Html -Head $style -PreContent "<h2>דיסקים שאינם מחוברים לאף שרת</H2>" | Out-File /tmp/mail-body
# ====================================================================
# Configure mail attributes
$mailElements = '' | select To, Cc, Subject, Attachments
$mailElements.To = "CloudTeam@example.com"
$mailElements.Cc = "SystemManager@example.com,SOC@example.com"
$mailElements.Subject = "דיסקים שאינם מחוברים לאף שרת"
$mailElements.Attachments = "N/A"
$mailElements | Export-Csv -NoTypeInformation -Encoding UTF8 -Path /tmp/mail-elements.csv
if ($orphans.Count -gt 0){
/infrastructure-jobs/Send-Mail.ps1
}
נספח ב' – סיסמאות משתמשים שעומדות לפוג ב-Active-Directory:
יש לשים לב לרכיבים הבאים:
- הסקריפט הזה שולח מייל למשתמשים שסיסמתם תפוג בשבועיים הקרובים (או שכבר פגה).
- המייל מכיל תמונה מוטמעת. התמונה מכילה הנחיות לבחירת סיסמה חזקה.
- המייל שולח בנוסף כצרופה תמונה נוספת.
- הסקריפט מתחבר ל-Active-Directory מקונטיינר שרץ ב-Cloud Run. יש לשים לב לתצורת ההתחברות.
- תצורת ההתחברות נתמכת על ידי חבילות ומודולים שנוספו ל-Container Image במיוחד כדי להתחבר לשרתי ווינדוס בסביבת דומיין.
- בגלל התוכן המיוחד של המייל – כולל התמונה המוטמעת, יש לשים לב שהסקריפט הנוכחי עורך ויוצר מחדש את התהליך לשליחת המייל. התהליך ישמר בסקריפט זמני, אחר. ולבסוף משתמש בו לשליחת המייל.
הסקריפט:
# Get fictures for the mail
gsutil cp gs://ad-scripts/PasswordExpiry/* /tmp/
$sendscript = Get-Content /infrastructure-jobs/Send-Mail.ps1
$cmd1 = '$message = $message.Replace("text/csv", "image/jpeg")'
#$picid = $message[18].Replace("Type", "Id ") -replace('"image/jpeg"', '"att"')
$cmd2 = '$picid = $message[18].Replace("Type", "Id ") -replace(' + "'" +'"image/jpeg"' +"', '" + '"att"' + "')"
$cmd3 = '$message = $message[0..9] + $message[13..19] + $picid + $message[20..30]'
$sendscript[0..70] + $cmd1 + $cmd2 + $cmd3 + $sendscript[70] + $sendscript[71..75] | Out-File /tmp/sendscript.ps1
# ====================================================
# Set authentication for AD
$plain = gcloud secrets versions access latest --secret as-pass-expiry --project yostest
$user = "pass-expiry@example.com"
$pass = ConvertTo-SecureString $plain -AsPlainText -Force
$auth = New-Object System.Management.Automation.PSCredential($user, $pass)
$session = New-PSSession -ComputerName DC.example.com -Credential $auth -Authentication Negotiate
$users = Invoke-Command -Session $session -ScriptBlock {
Get-ADUser -Filter {enabled -eq $true} -Properties PasswordLastSet, mail, MemberOf | ?{$_.PasswordLastSet -ne $null}
}
# ===================================================
$msg1 = "שלום רב,"
$msg3 = "יש לשנות את הסיסמה לסיסמה חזקה."
foreach ($x in $users) {
if ($x.MemberOf -match "Admins") {$expiry = ($x.PasswordLastSet.AddDays(30) -(Get-Date)).days
} else {$expiry = ($x.PasswordLastSet.AddDays(90) -(Get-Date)).days}
if (($expiry -lt 15) -and ($expiry -ge 0)) {
if ($expiry -eq 0) {
$EmailSubject = "סיסמתך פגה"
$msg2 = "לידיעתך, סיסמת המחשב שלך פגה"
} else {
$EmailSubject = "סיסמתך תפוג בעוד " + $expiry + " ימים"
$msg2 = " לידיעתך, סיסמת המחשב שלך תפוג בעוד " + $expiry + " ימים."
}
$body = @"
<html>
<body style="font-family:calibri">
<div dir=RTL><font size="+1"> $msg1 </font></div>
<div dir=RTL><font size="+1"> $msg2 </font></div>
<div dir=RTL><font size="+1"> $msg3 </font></div>
<br><div dir=RTL><img src="cid:att" width="941" height="698"/></div><br><br>
</body>
</html>
"@
$body | Out-File /tmp/mail-body
# ====================================================================
# Configure mail attributes
$mailElements = '' | select To, Cc, Subject, Attachments
$mailElements.To = $x.mail
$mailElements.Cc = $x.mail
$mailElements.Subject = $EmailSubject
$mailElements.Attachments = (Get-ChildItem /tmp/ | %{$_.name} | ?{$_-match 'jpg'}) -join ","
$mailElements | Export-Csv -NoTypeInformation -Encoding UTF8 -Path /tmp/mail-elements.csv
/tmp/sendscript.ps1
}
}