TL;DR:
Cet article fournit quelques exemples de scripts API utiles pour commencer à automatiser des tâches sur un PAM CyberArk.
Ces scripts sont fournis en Powershell en environnement Privilege Cloud mais fonctionneront aussi sur un déploiement on-prem.
Ce document fait partie d'une série d'articles sur le PAM CyberArk.
Concepts & outils:
PAM, automatisation, scripting PowerShell, RestAPI
Parcourir un grand nombre d'objets
Faire des vérifications de conformité
Automatiser la création de Safes
Suppression de membership/de Safe
Collecte de tous les Safes, contenus et permissions pour revue/audit
Les commandes qui suivent sont en PowerShell.
Définir l'URL d'accès au PVWA (dans l'exemple qui suit, un tenant PrivilegeCloud):
[string]$API_URL = "https://tenant_name.privilegecloud.cyberark.com/PasswordVault"
Définir les credentials:
$username = "johndoe@contoso.lab"
$securePassword = Read-host "Enter password for $username" -AsSecureString
Se connecter au PAM et récupérer un token API pour la session:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$headers = @{ "Content-Type"="application/json" }
$body = @{ "UserName" = $username; "Password" = $password } | ConvertTo-Json
$ApiKey = Invoke-RestMethod -Uri "$API_URL/api/Auth/ldap/Logon" -Method Post -Headers $headers -Body $body
A partir de là, le token API de session contenu dans la variable $ApiKey va permettre de faire des requêtes sans rejouer l'authentification.
Pour faire une requête API, par exemple, récupérer tous les membres d'un Safe:
$action = "API/Safes/LinuxAdmins/Members"
$Request = @{
Method = "GET"
Uri = "$API_URL/$action"
ContentType = "application/json; charset=utf-8"
Headers = @{"Authorization" = "$ApiKey"}
}
$r = Invoke-RestMethod @Request
Les données sont ainsi récupérées dans la variable $r, il ne reste plus qu'à la parser. Par exemple:
foreach($item in $r.value) {
$a = $item.memberType
$b = $item.memberName
"$a`t$b"
}
Pour récupérer la liste des Safes, faire la même requête avec:
$action = "API/Safes/"
Pour récupérer la liste des comptes, faire la même requête avec:
$action = "API/Accounts"
Pour parcourir la doc de référence et découvrir toutes les commandes API disponibles, visiter ce lien: Rest API | CyberArk Docs
A la fin de l'exécution d'un script API c'est une bonne pratique de fermer proprement la session, ce qui révoque la permission liée au token API sans attendre le timeout.
Exemple:
$action = "API/Auth/Logoff"
$Request = @{
Method = "POST"
Uri = "$API_URL/$action"
ContentType = "application/json; charset=utf-8"
Headers = @{"Authorization" = "$ApiKey"}
}
$r = Invoke-RestMethod @Request
Les requêtes API sont toujours limitées en nombre d'objets pouvant être retournés en une seule fois, auquel cas il faut faire plusieurs requêtes successives tant qu'il existe des objets à retourner.
Supposons le script naïf suivant pour collecter tous les Safes:
$action = "API/Safes/"
$Request = @{
Method = "GET"
Uri = "$API_URL/$action"
ContentType = "application/json; charset=utf-8"
Headers = @{"Authorization" = "$ApiKey"}
}
$r = Invoke-RestMethod @Request
Pour faire la même requête avec de la pagination, en limitant le nombre d'objets retournés, et en itérant sur les différentes pages:
$action = "API/Safes"
$LIMIT = 1000
$AllSafes = @()
$Offset = 0
do {
$Request = @{
Method = "GET"
Uri = "$API_URL/$action?limit=$LIMIT&offset=$Offset"
ContentType = "application/json; charset=utf-8"
Headers = @{"Authorization" = $ApiKey}
}
$r = Invoke-RestMethod @Request
if ($r.PSObject.Properties.Name -contains "value") {
$Page = $r.value
}
else {
$Page = $r
}
$AllSafes += @($Page)
$Offset += $LIMIT
} while (@($Page).Count -eq $LIMIT)
Le script d'exemple suivant:
api-creator disposant des droits nécessaires,# Paramètres
$account = "toto"
$domain = "contoso.lab"
$owner = "johndoe@consoto.lab"
# Connexion au PVWA
[string]$API_URL = "https://tenant_name.privilegecloud.cyberark.com/PasswordVault"
[String]$ContentType = "application/json; charset=utf-8"
$username = "api-creator"
$AUTHMETHOD = "cyberark"
$securePassword = Read-host "Enter password for $username" -AsSecureString
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$headers = @{ "Content-Type"="application/json" }
$body = @{ "UserName" = $username; "Password" = $password } | ConvertTo-Json
$ApiKey = Invoke-RestMethod -Uri "$API_URL/api/Auth/$AUTHMETHOD/Logon" -Method Post -Headers $headers -Body $body
# Référence du compte de réconciliation
$ReconcileAccount = "Operating System-WindowsDomain-contoso.lab-CyberArk_ReconcileAccount"
# Nom du Safe à utiliser
$safeName = "SAFE-JOHNDOE"
# Nom de la plateforme à utiliser
$platformId = "WindowsDomainAccounts"
# 1/2 : Onboard du compte
$Request = @{
Method = "POST"
Uri = "$API_URL/API/Accounts"
Headers = @{"Authorization" = "$ApiKey"}
Body = @{
"address" = "$domain"
"userName" = "$account"
"platformId" = "$platformId"
"safeName" = "$safeName"
"secretType" = "password"
}
}
$create = Invoke-RestMethod @Request
$Id = $create.id
# 2/2 : Association du compte de réconciliation (optionnel - si pas dans la plateforme)
$Request = @{
Method = "POST"
Uri = "$API_URL/API/Accounts/$Id/LinkAccount"
Headers = @{"Authorization" = "$ApiKey"}
Body = @{
"safe" = "CyberarkReconcileAccounts"
"extraPasswordIndex" = "3"
"name" = "$ReconcileAccount"
"folder" = "Root"
}
}
Invoke-RestMethod @Request
$Request = @{
Method = "POST"
Uri = "$API_URL/API/Accounts/$Id/Reconcile"
Headers = @{"Authorization" = "$ApiKey"}
}
Invoke-RestMethod @Request
# Déconnexion
$Request = @{
Method = "POST"
Uri = "$API_URL/API/Auth/Logoff"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$logout = Invoke-RestMethod @Request
#end#
Le script qui suit est un exemple de vérifications de conformité. Il suppose beaucoup de choses: une convention de nommage des objets CyberArk et AD, un modèle de délégation des droits dans le PAM avec différents types de Safes (partagés, individuels), etc.
Par conséquent il devra être adapté dans une large mesure à chaque environnement.
Les recherches de déviances données en exemple dans ce script comprennent:
Préparation et connexion au PVWA:
[string]$API_URL = "https://tenant_name.privilegecloud.cyberark.com/PasswordVault"
[String]$ContentType = "application/json; charset=utf-8"
$username = "api-auditor"
$AUTHMETHOD = "cyberark"
$LIMIT = 1000
$securePassword = Read-host "Enter password for $username" -AsSecureString
# Ignorer les Safes internes
$ignoreSafes = @("VaultInternal", "Notification Engine", "PVWAReports", "PVWATicketingSystem", "PVWAPublicData", "PasswordManager_Pending", "AccountsFeedADAccounts", "AccountsFeedDiscoveryLogs", "PSM", "PSMUniversalConnectors", "CPM_STD", "PSMRecordings", "CyberarkReconcileAccounts", "TelemetryConfig")
# Comptes membres standards
$standardMembers = @("NotificationEngine", "PVWAAppUsers", "PVWAMonitor", "internal_admin", "PVWAUsers", "CPM_STD", "PVWAAppUser", "PVWAAppUser2", "PSMAppUsers", "PSMMaster", "PSMApp_12ab34d", "TelemetryUser1", "TelemetryUser", "TelemetryUser2", "TelemetryUser3", "CyberArkAdmins", "CyberArkAuditors", "CyberArkSafeAdmins")
# Convention de nommage des Safes
$regex_naming_convention = "..."
# Exemple:
$regex_naming_convention = "(^(EMEA|APAC|AMER|GLB)[0]*-(AD|INFRA|L2|L3|WINDOWS|LINUX)-[A-Za-z0-9_-]+$)|(^PERSONAL-[A-Z]+[0-9]*(-(EMEA|APAC)$|$))|(^KEYSTORE-(EMEA|APAC|AMERGLB)-[A-Za-z0-9_-]+$)";
# Connexion
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$headers = @{ "Content-Type"="application/json" }
$body = @{ "UserName" = $username; "Password" = $password } | ConvertTo-Json
$ApiKey = Invoke-RestMethod -Uri "$API_URL/api/Auth/$AUTHMETHOD/Logon" -Method Post -Headers $headers -Body $body
Collecte des données
# Collecte des comptes
"Data Collect/Get Accounts.."
$method = "GET"
$action = "API/Accounts?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$accounts = Invoke-RestMethod @Request
# Collecte des utilisateurs actifs
"Data Collect/Get active Users.."
$method = "GET"
$action = "API/Users?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$Activeusers = Invoke-RestMethod @Request
# Collecte des Safes
"Data Collect/Get Safes.."
$method = "GET"
$action = "API/Safes?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$safes = Invoke-RestMethod @Request
# Collecte des groupes et des membres
"Data Collect/Get Usergroups/members.."
$method = "GET"
$action = "API/Usergroups?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$usergroups = Invoke-RestMethod @Request
$method = "GET"
$action = "API/Users?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$users = Invoke-RestMethod @Request
for($i = 0 ; $i -le ($usergroups.count - 1) ; $i ++)
{
$ID = $usergroups.value[$i].id
$method = "GET"
$action = "API/Usergroups/" + $ID + "?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$groupmembers = ""
$groupmembers = Invoke-RestMethod @Request
$usergroups.value[$i].members = $groupmembers.members
}
$null = ($usergroups.value | Where-Object { $_.members.id -eq 21 }).directory[0]
""
$STEP = 0
Recherche des Safes ne contenant aucun credential:
# Recherche des Safes ne contenant aucun credential
$STEP ++ ; $nb =0
"CyberArk analysis - $STEP - The following Safes have no onboarded credentials:"
foreach ($item in $safes.value)
{
$safeName = $item.safeName
if(-Not $ignoreSafes.Contains($safeName))
{
$onboarded = $accounts.value | where {$_.safeName -eq "$safeName"}
if($null -eq $onboarded) { "- $safeName"; $nb ++ }
}
}
if ($nb -eq 0) { "- Nope, all good !" }
""
Recherche des Safes ne contenant aucun membre autre que ceux par défaut:
# Recherche des Safes ne contenant aucun membre autre que ceux par défaut
$STEP ++ ; $nb =0
"CyberArk analysis - $STEP - The following Safes don't have any member (besides default memberships):"
foreach ($item in $safes.value)
{
$safeName = $item.safeName
if(-Not $ignoreSafes.Contains($safeName))
{
$safeNumber = $item.safeNumber
$safeUrlId = $item.safeUrlId
$description = $item.description
$action = "API/Safes/$safeUrlId/Members"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$count = 0
$m = Invoke-RestMethod @Request
foreach ($member in $m.value)
{
$memberName = $member.memberName
if(-Not $standardMembers.contains($memberName)) { $count ++ }
}
if($count -eq 0) { "- $safeName"; $nb ++ }
}
}
if ($nb -eq 0) { "- Nope, all good !" }
""
Recherche des Safes partagés avec des membres individuels plutôt que des groupes ADi:
# Recherche des Safes partagés avec des membres individuels plutôt que des groupes AD
$STEP ++ ; $nb =0
"CyberArk analysis - $STEP - The following non-individual Safes have nominative members instead of AD groups:"
foreach ($item in $safes.value)
{
$safeName = $item.safeName
if(-Not $ignoreSafes.Contains($safeName) -and ($safeName -notmatch "^PERSONAL"))
{
$safeNumber = $item.safeNumber
$safeUrlId = $item.safeUrlId
$description = $item.description
$action = "API/Safes/$safeUrlId/Members"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$list = @()
$m = Invoke-RestMethod @Request
foreach ($member in $m.value)
{
$memberName = $member.memberName
if($memberName -match "@") { $list += $membername }
}
if($list -ne @()) { "- $safeName ($list)"; $nb ++ }
}
}
if ($nb -eq 0) { "- Nope, all good !" }
""
Recherche des Safes individuels avec plus d'un membre nominatif:
# Recherche des Safes individuels avec plus d'un membre nominatif
$STEP ++ ; $nb =0
"CyberArk analysis - $STEP - The following PERSONAL Safes have more than one member:"
foreach ($item in $safes.value)
{
$safeName = $item.safeName
if(-Not $ignoreSafes.Contains($safeName) -and ($safeName -match "^PERSONAL"))
{
$safeNumber = $item.safeNumber
$safeUrlId = $item.safeUrlId
$description = $item.description
$action = "API/Safes/$safeUrlId/Members"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$list = @()
$m = Invoke-RestMethod @Request
foreach ($member in $m.value)
{
$memberName = $member.memberName
if(-Not $standardMembers.contains($memberName)) { $list += $membername }
}
if($list.count -gt 1) { "- $safeName ($list)"; $nb ++ }
}
}
if ($nb -eq 0) { "- Nope, all good !" }
""
Recherche des Safes ne respectant pas la convention de nommage:
# Recherche des Safes ne respectant pas la convention de nommage
$STEP ++ ; $nb =0
"CyberArk analysis - $STEP - The following Safes don't respect the naming convention:"
foreach ($item in ($safes.value.safeName | where {$_ -notmatch $regex_naming_convention}))
{
if(-Not $ignoreSafes.Contains($item)) { "- $item"; $nb ++ }
}
if ($nb -eq 0) { "- Nope, all good !" }
""
Recherche des credentials qui sont onboardés dans CyberArk alors qu'ils sont déprovisionnés dans l'AD:
# Recherche des credentials qui sont onboardés dans CyberArk alors qu'ils sont déprovisionnés dans l'AD
$STEP ++ ; $nb =0
"CyberArk analysis - $STEP - The following onboarded credentials are deprovisioned in the AD or do not exist:"
foreach ($item in $accounts.value)
{
$dom = $item.address
$user = $item.userName
$name = ""
try { $name = (Get-ADUser -server $item.address $item.userName -properties name).name } catch { "- $dom\$user" ; $nb ++ }
If($name -match "Deprovisioned") { "- $dom\$user" ; $nb ++ }
}
if ($nb -eq 0) { "- Nope, all good !" }
""
Recherche des Safes individuels sur lesquels un utilisateur ADM ou un compte de service a des droits:
# Recherche des Safes individuels sur lesquels un utilisateur ADM ou un compte de service a des droits
$STEP ++ ; $nb =0
"CyberArk analysis - $STEP - The following users have full READ permissions (show/copy password) over their individual Safes:"
$nb =0
foreach($item in ($safes.value |where {$_.safeName -match "^PERSONAL"}))
{
$safeUrlId = $item.safeUrlId
$action = "API/Safes/$safeUrlId/Members" + "?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$m = Invoke-RestMethod @Request
foreach ($member in $m.value)
{
$object = $users.Users | Where-Object { $_.id -eq $member.memberId }
if($member.memberType -eq "User" -and ($object.userName -match "^ADM" -Or $object.userName -match "^SA") -and $member.permissions.retrieveAccounts -eq "True")
{
"- User: " + $member.memberName + " (" + $item.safeName + ")" ; $nb ++
}
}
}
"(Total: $nb users)"
if ($nb -eq 0) { "- Nope, all good !" }
""
Déconnexion:
# Déconnexion
$method = "POST"
$action = "API/Auth/Logoff"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$logout = Invoke-RestMethod @Request
#end#
Le script qui suit:
[string]$API_URL = "https://tenant_name.privilegecloud.cyberark.com/PasswordVault"
[String]$ContentType = "application/json; charset=utf-8"
$username = "api-creator"
$AUTHMETHOD = "cyberark"
$LIMIT = 1000
$securePassword = Read-host "Enter password for $username" -AsSecureString
# Connexion
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$headers = @{ "Content-Type"="application/json" }
$body = @{ "UserName" = $username; "Password" = $password } | ConvertTo-Json
$ApiKey = Invoke-RestMethod -Uri "$API_URL/api/Auth/$AUTHMETHOD/Logon" -Method Post -Headers $headers -Body $body
# Paramètres des Safes
$SEARCH = "CONTOSO.LAB"
# Matrices de permissions standard sur les Safes:
# [Admin ConnectOnly ReadOnly Auditor SafeAdmin]
$PermAdmin = @{
useAccounts = $True
retrieveAccounts = $True
listAccounts = $True
addAccounts = $True
updateAccountContent = $True
updateAccountProperties = $True
initiateCPMAccountManagementOperations = $True
specifyNextAccountContent = $True
renameAccounts = $True
deleteAccounts = $True
unlockAccounts = $True
manageSafe = $True
manageSafeMembers = $True
backupSafe = $True
viewAuditLog = $True
viewSafeMembers = $True
accessWithoutConfirmation = $True
createFolders = $True
deleteFolders = $True
moveAccountsAndFolders = $True
requestsAuthorizationLevel1 = $True
requestsAuthorizationLevel2 = $False
}
$PermConnectOnly = @{
useAccounts = $True
retrieveAccounts = $False
listAccounts = $True
addAccounts = $False
updateAccountContent = $False
updateAccountProperties = $False
initiateCPMAccountManagementOperations = $False
specifyNextAccountContent = $False
renameAccounts = $False
deleteAccounts = $False
unlockAccounts = $False
manageSafe = $False
manageSafeMembers = $False
backupSafe = $False
viewAuditLog = $False
viewSafeMembers = $False
accessWithoutConfirmation = $False
createFolders = $False
deleteFolders = $False
moveAccountsAndFolders = $False
requestsAuthorizationLevel1 = $False
requestsAuthorizationLevel2 = $False
}
$PermReadOnly = @{
useAccounts = $True
retrieveAccounts = $True
listAccounts = $True
addAccounts = $False
updateAccountContent = $False
updateAccountProperties = $False
initiateCPMAccountManagementOperations = $False
specifyNextAccountContent = $False
renameAccounts = $False
deleteAccounts = $False
unlockAccounts = $False
manageSafe = $False
manageSafeMembers = $False
backupSafe = $False
viewAuditLog = $False
viewSafeMembers = $False
accessWithoutConfirmation = $False
createFolders = $False
deleteFolders = $False
moveAccountsAndFolders = $False
requestsAuthorizationLevel1 = $False
requestsAuthorizationLevel2 = $False
}
$PermAuditor = @{
useAccounts = $False
retrieveAccounts = $False
listAccounts = $True
addAccounts = $False
updateAccountContent = $False
updateAccountProperties = $False
initiateCPMAccountManagementOperations = $False
specifyNextAccountContent = $False
renameAccounts = $False
deleteAccounts = $False
unlockAccounts = $False
manageSafe = $False
manageSafeMembers = $False
backupSafe = $False
viewAuditLog = $True
viewSafeMembers = $True
accessWithoutConfirmation = $False
createFolders = $False
deleteFolders = $False
moveAccountsAndFolders = $False
requestsAuthorizationLevel1 = $False
requestsAuthorizationLevel2 = $False
}
$PermSafeAdmin = @{
useAccounts = $False
retrieveAccounts = $False
listAccounts = $True
addAccounts = $True
updateAccountContent = $True
updateAccountProperties = $True
initiateCPMAccountManagementOperations = $True
specifyNextAccountContent = $True
renameAccounts = $True
deleteAccounts = $True
unlockAccounts = $True
manageSafe = $True
manageSafeMembers = $True
backupSafe = $True
viewAuditLog = $True
viewSafeMembers = $True
accessWithoutConfirmation = $True
createFolders = $False
deleteFolders = $False
moveAccountsAndFolders = $False
requestsAuthorizationLevel1 = $True
requestsAuthorizationLevel2 = $False
}
# Chargement de la liste des Safes à créer
$newSafeList = @{}
$list = Get-Content .\list.txt
foreach ($user in $list)
{
$safeName = $description = ""
$safeName = $user -replace "(^.+),.*$","`$1"
$description = $user -replace "^.+,(.*$)","`$1"
# Checker si le Safe existe déjà
$method = "GET"
$action = "API/Safes/$safeName"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
try { $s = Invoke-RestMethod @Request }
catch { "$safeName : NOT exists" ; $newSafeList["$safeName"] = $description }
}
# Création des Safes et ajout des memberships
foreach ($SAFENAME in $newSafeList.Keys)
{
# Création
"Create Safe : $SAFENAME"
Start-Sleep -s 2
$description = ""
$description = $newSafeList["$SAFENAME"]
$method = "POST"
$action = "API/safes"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
Headers = @{"Authorization" = "$ApiKey"}
Body = @{
"SafeName" = "$SAFENAME"
"Description" = "$description"
"ManagingCPM" = "CPM_STD"
"NumberOfDaysRetention" = "7"
}
}
$create = Invoke-RestMethod @Request
# Ajout des permissions pour le groupe AD CyberArkAdmins
"Safe $SAFENAME : add membership for CyberArkAdmins"
Start-Sleep -s 2
$MEMBER = "CyberArkAdmins"
$Permissions = $PermAdmin
$method = "POST"
$action = "API/safes/$SAFENAME/members"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "application/json"
Headers = @{"Authorization" = $ApiKey}
Body = @{
"MemberName" = "$MEMBER"
"SearchIn" = "$SEARCH"
"MembershipExpirationDate" = "$null"
"MemberType" = "Group"
"Permissions" = $Permissions
} | ConvertTo-Json
}
$addmember = Invoke-RestMethod @Request
# Ajout des permissions pour le groupe AD CyberArkAuditors
"Safe $SAFENAME : add membership for CyberArkAuditors"
Start-Sleep -s 2
$MEMBER = "CyberArkAuditors"
$Permissions = $PermAuditor
$method = "POST"
$action = "API/safes/$SAFENAME/members"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "application/json"
Headers = @{"Authorization" = $ApiKey}
Body = @{
"MemberName" = "$MEMBER"
"SearchIn" = "$SEARCH"
"MembershipExpirationDate" = "$null"
"MemberType" = "Group"
"Permissions" = $Permissions
} | ConvertTo-Json
}
$addmember = Invoke-RestMethod @Request
}
# Déconnexion
$method = "POST"
$action = "API/Auth/Logoff"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$logout = Invoke-RestMethod @Request
#end#
Deux petits exemples utilisant la méthode HTTP DELETE.
Supprimer un membre d'un Safe:
$method = "DELETE"
$action = "API/safes/$SAFENAME"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action/members/$MEMBER"
Headers = @{"Authorization" = "$ApiKey"}
}
$delete = Invoke-RestMethod @Request
Supprimer un Safe:
# Delete Safe
$method = "DELETE"
$action = "API/safes/$SAFENAME"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
Headers = @{"Authorization" = "$ApiKey"}
}
$delete = Invoke-RestMethod @Request
Ca va sans dire que pour cet exemple comme pour les précédents, le compte utilisé doit avoir les permissions pour accéder aux données convoitées.
Dans l'exemple de collecte qui suit, on a créé un compte interne Cyberark nommé api-auditor qui dispose des droits en lecture seule sur tous les objets (permission Auditor).
L'objectif n'est pas de fournir un script universel mais de montrer comment exploiter les API CyberArk pour construire ses propres contrôles de conformité. Dans un environnement réel, les règles dépendront de votre modèle de délégation, de vos conventions de nommage et de votre gouvernance.
[string]$API_URL = "https://tenant_name.privilegecloud.cyberark.com/PasswordVault"
[String]$ContentType = "application/json; charset=utf-8"
$username = "api-auditor"
$AUTHMETHOD = "cyberark"
$LIMIT = 1000
$securePassword = Read-host "Enter password for $username" -AsSecureString
# Connexion au PAM
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePassword)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$headers = @{ "Content-Type"="application/json" }
$body = @{ "UserName" = $username; "Password" = $password } | ConvertTo-Json
$ApiKey = Invoke-RestMethod -Uri "$API_URL/api/Auth/$AUTHMETHOD/Logon" -Method Post -Headers $headers -Body $body
# Récupération de la liste des comptes
$method = "GET"
$action = "API/Accounts?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$accounts = Invoke-RestMethod @Request
# Récupération de la liste des Safes
$method = "GET"
$action = "API/Safes?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$safes = Invoke-RestMethod @Request
# Récupération de la liste des Usergroups + Members
$method = "GET"
$action = "API/Usergroups?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$usergroups = Invoke-RestMethod @Request
$action = "API/Users?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$users = Invoke-RestMethod @Request
for($i = 0 ; $i -le ($usergroups.count - 1) ; $i ++)
{
$ID = $usergroups.value[$i].id
$method = "GET"
$action = "API/Usergroups/" + $ID + "?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$groupmembers = ""
$groupmembers = Invoke-RestMethod @Request
$usergroups.value[$i].members = $groupmembers.members
}
# Récupération dans la variable "$null" de l'objet correspondant à un credential sans domaine AD
$null = ($usergroups.value | Where-Object { $_.members.id -eq 21 }).directory[0]
# Header CSV
"id;name;description;member-type;member-domain;member-name;expiration;useAccounts;retrieveAccounts;listAccounts;addAccounts;updateAccountContent;updateAccountProperties;initiateCPMAccountManagementOperations;specifyNextAccountContent;renameAccounts;deleteAccounts;unlockAccounts;manageSafe;manageSafeMembers;backupSafe;viewAuditLog;viewSafeMembers;accessWithoutConfirmation;createFolders;deleteFolders;moveAccountsAndFolders;requestsAuthorizationLevel1;requestsAuthorizationLevel2;accounts"
# Output des données
foreach($item in $safes.value)
{
$safeNumber = $item.safeNumber
$safeUrlId = $item.safeUrlId
$safeName = $item.safeName
$description = $item.description
$action = "API/Safes/$safeUrlId/Members" + "?limit=$LIMIT"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$m = Invoke-RestMethod @Request
$members = @()
foreach ($member in $m.value)
{
if($member.memberType -eq "Group")
{
$object = $usergroups.value | Where-Object { $_.id -eq $member.memberId }
if($object.directory) { $line = "group;" + $object.directory + ";" + $object.groupName }
else { $line = "group;" + $object.groupType + ";" + $object.groupName }
}
if($member.memberType -eq "User")
{
$object = $users.Users | Where-Object { $_.id -eq $member.memberId }
$domain = ""
if($object.source -eq "LDAP")
{
try
{
$domains = ($usergroups.value | Where-Object { $_.members.id -eq $member.memberId }).directory
if($domains.count -gt 1) { $domain = $domains[0] } else { $domain = $domains }
}
catch { }
}
if($domain -eq "" -or $domain -eq $null) { $domain = $object.source }
if($domain -eq "" -or $domain -eq $null) { $domain = "Vault" }
if($domain -eq "CyberArk") { $domain = "Vault" }
if($object.userName -ne $null) { $line = "user;" + $domain + ";" + $object.userName }
else { $line = "user;" + $domain + ";" + $member.memberName }
}
$expiration = $member.membershipExpirationDate
if($expiration -match "^[0-9]+$")
{
$expiration = Get-Date ( (Get-Date -Date "01-01-1970") + ([System.TimeSpan]::FromSeconds($expiration)) ) -Format "dd/MM/yyyy"
}
$line += ";$expiration"
$permissions = $member.permissions
$permissions = @(
$permissions.useAccounts -replace "True", "x" -replace "False", "";
$permissions.retrieveAccounts -replace "True", "x" -replace "False", "";
$permissions.listAccounts -replace "True", "x" -replace "False", "";
$permissions.addAccounts -replace "True", "x" -replace "False", "";
$permissions.updateAccountContent -replace "True", "x" -replace "False", "";
$permissions.updateAccountProperties -replace "True", "x" -replace "False", "";
$permissions.initiateCPMAccountManagementOperations -replace "True", "x" -replace "False", "";
$permissions.specifyNextAccountContent -replace "True", "x" -replace "False", "";
$permissions.renameAccounts -replace "True", "x" -replace "False", "";
$permissions.deleteAccounts -replace "True", "x" -replace "False", "";
$permissions.unlockAccounts -replace "True", "x" -replace "False", "";
$permissions.manageSafe -replace "True", "x" -replace "False", "";
$permissions.manageSafeMembers -replace "True", "x" -replace "False", "";
$permissions.backupSafe -replace "True", "x" -replace "False", "";
$permissions.viewAuditLog -replace "True", "x" -replace "False", "";
$permissions.viewSafeMembers -replace "True", "x" -replace "False", "";
$permissions.accessWithoutConfirmation -replace "True", "x" -replace "False", "";
$permissions.createFolders -replace "True", "x" -replace "False", "";
$permissions.deleteFolders -replace "True", "x" -replace "False", "";
$permissions.moveAccountsAndFolders -replace "True", "x" -replace "False", "";
$permissions.requestsAuthorizationLevel1 -replace "True", "x" -replace "False", "";
$permissions.requestsAuthorizationLevel2 -replace "True", "x" -replace "False", ""
) -join ";"
$line += ";$permissions"
$members += $line
}
$list =@()
foreach ($accnt in ($accounts.value | where {$_.safeName -eq "$safeName"}))
{
$domain = ""
$userName = ""
$domain = $accnt.address.ToUpper()
$userName = $accnt.userName
if($username -notmatch "\\")
{
if($domain -ne "") { $list += "$domain" + "\" + "$userName" }
else { $list += "$userName" }
}
else { $list += "$userName" }
}
$list = $list -join ","
foreach ($member in $members) { "$safeNumber;$safeName;$description;$member;$list" }
}
# Déconnexion
$method = "POST"
$action = "API/Auth/Logoff"
$Request = @{
Method = "$method"
Uri = "$API_URL/$action"
ContentType = "$ContentType"
Headers = @{"Authorization" = "$ApiKey"}
}
$logout = Invoke-RestMethod @Request
#end#
--
TG - 05/2024 update 07/2026