Logo

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

Sommaire

Connexion

Test basique

Déconnexion

Parcourir un grand nombre d'objets

Ajouter (onboarder) un compte

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

Pour aller plus loin

Les commandes qui suivent sont en PowerShell.

Connexion

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.

Test basique

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

Déconnexion

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

Parcourir un grand nombre d'objets

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)

Ajouter (onboarder) un compte

Le script d'exemple suivant:

# 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#

Faire des vérifications de conformité

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#

Automatiser la création de Safes

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#

Suppression de membership/de Safe

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

Collecte de toutes les données

Collecte de tous les Safes, contenus et permissions, pour une revue ou un audit
Remarque:

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#

Pour aller plus loin

Sample RestAPI scripts

Script complet de gestion des Safes

--
TG - 05/2024 update 07/2026

Cet article a été généré par un humain