Aller au contenu
Avanet

Gérer Sophos Switch via la CLI, l’API REST locale et l’API Fusion

La CLI et l’API REST locales permettent d’accéder directement à un Sophos Switch. La Sophos Fusion Switch Management API est distincte : elle utilise les identifiants d’un principal de service au niveau du tenant et distribue des politiques centralisées aux switches. Ce guide précise l’identité, l’URL de base et les contrôles propres à chaque méthode.

Périmètre et décisions préalables de sécurité

Ce guide couvre les points suivants :

  • l’accès local à la CLI par un chemin de gestion autorisé pour l’équipement, notamment en SSH ;
  • la navigation et le diagnostic dans la CLI ;
  • la connexion à l’API REST locale de l’équipement ;
  • le cycle de vie du jeton Bearer associé à la session ;
  • un test documenté et non modificatif de l’API avec GET /api/ports ;
  • les contrôles, le dépannage, le retour arrière et la fermeture de session en toute sécurité.

Il ne constitue volontairement ni un catalogue exhaustif des endpoints de l’API ni un catalogue des commandes CLI. La documentation centrale fournit des indications, mais la disponibilité de chaque fonction doit être vérifiée sur l’équipement cible avant toute utilisation de l’API. Les chemins qui y figurent, comme /ports, sont relatifs à la racine /api du serveur : le chemin d’appel complet est donc /api/ports. Les commandes, les modes et les paramètres de la CLI peuvent également varier selon le modèle et le firmware. Les vérifications obligatoires sont décrites dans la section Variabilité selon le firmware et le modèle.

Déterminez avant l’accès :

  1. Sophos Fusion ou l’administration locale constitue-t-elle le système de référence ?
  2. Quel switch précis, quel modèle, quel firmware et quelle adresse IP de gestion sont concernés ?
  3. Un accès en lecture seule suffit-il, ou une modification approuvée est-elle nécessaire ?
  4. Quels sont l’état initial, le critère de réussite et la procédure de retour arrière ?
  5. Existe-t-il un chemin de gestion indépendant si la modification interrompt l’accès normal ?

Ne mélangez pas identités et rôles

Les comptes figurant sous People sont des comptes locaux du switch :

Type de privilège localDroits sur le commutateurUtilisation typique
AdminAfficher et modifier toutes les fonctions du switchAdministration locale approuvée et appels d’écriture à l’API
UserAfficher les paramètres sans les modifierDiagnostic et contrôle selon le principe du moindre privilège

Ces rôles locaux ne sont pas les mêmes que les rôles d’administrateur dans Sophos Fusion. Un rôle Fusion ne confère pas automatiquement de droits d’accès à la CLI ou à l’API locale, et les identifiants locaux ne sont pas des identifiants Fusion. Les identifiants d’un compte local du switch sont envoyés à /api/system/login.

Les comptes locaux sont gérés dans l’interface locale sous People :

  1. Sélectionnez Add pour créer un compte, ou choisissez Edit à côté d’un compte.
  2. Définissez Username, Password et Privilege type.
  3. Sélectionnez Privilege type comme Admin ou User.
  4. Enregistrez avec Apply.

Un mot de passe local doit satisfaire à toutes les exigences suivantes :

  • de 10 à 32 caractères ;
  • au moins une lettre et un chiffre ;
  • au moins un des caractères spéciaux suivants : @ ~ % * # + - =.

Un compte local Admin peut modifier le mot de passe des autres comptes, mais pas celui du compte standard admin. Seul le compte admin lui-même ou Sophos Fusion peut modifier ce mot de passe. Pour l’exploitation courante, utilisez des comptes locaux nominatifs plutôt qu’un compte admin partagé.

Préparer l’accès

Avant une session :

  • Comparez l’adresse IP de gestion, le modèle, la version du firmware, l’emplacement et le numéro de série avec le ticket de changement.
  • N’autorisez l’accès que depuis un réseau de gestion administrative et un poste d’administration autorisé.
  • Ne rendez pas les services de gestion accessibles depuis les VLAN utilisateurs ou Internet.
  • Vérifiez la source de temps et l’heure du poste d’administration ; une heure incorrecte complique l’analyse des journaux et des réponses de l’API.
  • Si une modification est prévue, disposez d’une sauvegarde à jour de la configuration et d’un chemin de retour indépendant.
  • Si l’administration passe par Fusion, consignez l’état cible central et documentez explicitement toute exception locale.
  • Ne stockez jamais d’identifiants ni de jetons Bearer dans un ticket, une conversation, une capture d’écran, l’historique du shell ou le code source.

Remplacez les espaces réservés suivants :

Espaces réservésSignification
<Switch-IP address>Adresse IP de gestion ou nom de gestion approuvé de l’équipement cible
<LOCAL-USERNAME>Compte local du switch, et non utilisateur Fusion
your-passwordMot de passe local ; il s’agit uniquement d’un espace réservé, à ne jamais utiliser comme véritable mot de passe
xxxxxxxx.yyyyyyyy.zzzzzzExemple masqué de jeton Bearer, et non véritable jeton

Utiliser la CLI en toute sécurité

Établir la connexion

SSH fait partie des services de gestion locaux. Il doit être configuré sur le firmware utilisé et accessible depuis le réseau de gestion. La syntaxe générale du client est la suivante :

ssh <LOCAL-USERNAME>@<Switch-IP-address>

Cette syntaxe est un exemple côté client : remplacez intégralement <LOCAL-USERNAME> et <Switch-IP-address>, chevrons compris. Avant d’accepter la clé d’hôte, comparez son empreinte par un moyen indépendant et fiable. N’ignorez jamais une alerte signalant une modification de la clé d’hôte : recherchez d’abord un remplacement de l’équipement, une réinitialisation d’usine, un conflit d’adresses IP ou une éventuelle attaque de l’homme du milieu.

Si le modèle concerné dispose d’un accès par console physique, celui-ci peut servir de chemin de maintenance indépendant. Les paramètres de connexion série doivent correspondre au modèle ; ne reprenez pas ceux d’un autre modèle de Sophos Switch.

Orientation dans la CLI

Commencez par observer l’invite, qui indique le mode de commande actif. Ne supposez pas qu’une commande est disponible dans tous les modes. Les touches et fonctions d’aide documentées sont les suivantes :

EntréeEffets
?Liste des commandes disponibles
TABCompléter la commande
Flèches vers le haut et vers le basAfficher les commandes précédemment exécutées
Flèches vers la gauche et vers la droiteSe déplacer dans la ligne active
Backspace ou Ctrl + HSupprimer un caractère
HistoryAfficher la liste de l’historique des commandes
QQuitter l’affichage et revenir à l’invite du switch

Vérifiez la casse et la disponibilité exacte avec ? ou TAB sur l’équipement cible. Q ferme un affichage paginé ou actif ; cette touche ne met pas automatiquement fin à la session SSH.

Séquence sécurisée dans la CLI

  1. Commencez par un compte local User si un accès en lecture seule suffit.
  2. Vérifiez l’équipement cible, l’invite et le mode de commande.
  3. Avec ?, affichez les commandes disponibles dans ce mode.
  4. Commencez par n’effectuer que des opérations d’affichage ou de consultation de l’état.
  5. Avant une modification, relevez l’état réel complet et définissez la procédure exacte de retour arrière.
  6. N’effectuez qu’une étape technique à la fois et contrôlez-la immédiatement.
  7. Si la sortie est paginée, revenez à l’invite avec Q.
  8. Fermez proprement la session avec la commande de sortie ou de déconnexion indiquée par ? sur l’équipement cible, puis vérifiez que la connexion SSH est fermée.

Traitez l’historique des commandes comme une information sensible : il peut contenir des adresses de gestion, des noms d’utilisateur ou des paramètres saisis. Ne saisissez jamais de mot de passe ni de jeton dans les arguments d’une commande CLI, sauf si le switch les demande de manière interactive.

API REST : connexion documentée

L’API est accessible en HTTPS à l’adresse de gestion du switch. Le switch crée un nouveau jeton Bearer pour chaque session. Lorsque la session en cours est fermée, vous devez obtenir un nouveau jeton.

La procédure documentée utilise PATCH /api/system/login. L’exemple suivant reprend à l’identique la syntaxe publiée, avec des espaces réservés neutres :

curl -k https://<Switch-IP address>/api/system/login -X PATCH -H 'Content-Type:application/json' -d '{"user":"admin","password":"your-password"}'

Une réponse réussie présente la structure suivante :

{
  "restful_res": {
    "token": "xxxxxxxx.yyyyyyyy.zzzzzz",
    "utctimestamp": "##########",
    "timeout": 900,
    "errCode": 0,
    "message": "OK"
  }
}

Les règles suivantes s’appliquent :

  • token est un secret soumis aux mêmes exigences de protection qu’un mot de passe.
  • utctimestamp et timeout sont propres à la session.
  • L’exemple de réponse documenté indique timeout: 900, mais la description statique n’en précise pas l’unité. Pour toute automatisation, vérifiez sa signification dans l’aide publiée de l’API, validez-la pour le firmware utilisé et ne codez pas cette valeur en dur.
  • errCode: 0 et message: "OK" signalent la réussite dans la réponse présentée. Vérifiez également le statut HTTP.
  • Demandez un nouveau jeton après la fermeture de la session, ou si celle-ci est rejetée ou a expiré ; ne réutilisez pas un ancien jeton.

Modèle cURL renforcé

Le modèle suivant évite de placer le mot de passe et les jetons dans les arguments des processus, vérifie le certificat TLS et n’écrit aucun fichier secret. Il nécessite curl, jq et un shell prenant en charge les here strings :

read -r -p 'Utilisateur local du switch : ' SW_USER
read -r -s -p 'Mot de passe local du switch : ' SW_PASSWORD
printf '\n'

LOGIN_RESPONSE="$({
  printf '%s\n%s\n' "$SW_USER" "$SW_PASSWORD" |
  jq -Rn '[inputs] | {user: .[0], password: .[1]}' |
  curl --disable --silent --show-error --fail-with-body \
    --cacert /path/to/switch-ca.pem \
    --request PATCH \
    --header 'Content-Type:application/json' \
    --data-binary @- \
    'https://<Switch-IP-address>/api/system/login'
})"
unset SW_PASSWORD

if [ "$(jq -r '.restful_res.errCode' <<<"$LOGIN_RESPONSE")" != "0" ]; then
  printf '%s\n' 'Échec de la connexion à l’API.' >&2
  unset LOGIN_RESPONSE SW_USER
  exit 1
fi

SW_TOKEN="$(jq -er '.restful_res.token' <<<"$LOGIN_RESPONSE")" || exit 1
unset LOGIN_RESPONSE

case "$SW_TOKEN" in
  ''|*[!A-Za-z0-9._~+/=-]*)
    printf '%s\n' 'La connexion à l’API n’a renvoyé aucun jeton Bearer valide.' >&2
    unset SW_TOKEN SW_USER
    exit 1
    ;;
esac

Remplacez /path/to/switch-ca.pem et <Switch-IP-address>. Utilisez le nom d’hôte pour lequel le certificat a été délivré. Dans un shell interactif, vérifiez qu’aucune trace de débogage telle que set -x n’est active. N’exposez pas les variables au moyen de env, export, de sorties de débogage ou de fichiers core.

API REST : appel et vérification

L’appel d’exemple documenté lit les ports et transmet le jeton dans l’en-tête Authorization :

curl -k https://<Switch IP Address>/api/ports -H 'authorization:Bearer <Token>'

<Token> est volontairement un simple espace réservé pour le jeton Bearer. Cet exemple publié n’est donc pas exécutable : ne remplacez pas <Token> par le véritable jeton et ne saisissez pas une telle commande dans un shell qui l’enregistrerait dans son historique. L’appel exécutable suivant utilise plutôt la variable SW_TOKEN définie lors de la connexion.

Pour une session réelle, utilisez le jeton déjà protégé et vérifiez le certificat. --config - lit la configuration de cURL sur l’entrée standard ; l’en-tête Authorization n’est ainsi ni transmis comme argument de processus ni écrit dans un fichier temporaire :

printf 'header = "Authorization: Bearer %s"\n' "$SW_TOKEN" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    --cacert /path/to/switch-ca.pem \
    'https://<Switch-IP-address>/api/ports'

GET /api/ports constitue un premier test fonctionnel approprié, car l’appel documenté consulte l’état au lieu de modifier la configuration. Le test ne réussit que si :

  1. la vérification TLS et la connexion réussissent ;
  2. aucune erreur HTTP n’est renvoyée ;
  3. la réponse est valide sur le plan syntaxique et plausible sur le plan technique ;
  4. les numéros, les noms et les états attendus des ports correspondent au bon équipement cible ;
  5. aucun identifiant ni jeton n’apparaît dans la sortie ou les journaux.

curl --fail-with-body renvoie un état d’erreur pour les erreurs HTTP tout en conservant le corps de la réponse à des fins de diagnostic local. Avant de partager cette réponse, vérifiez qu’elle ne contient aucun jeton, adresse, numéro de série ou autre donnée interne.

Contrôler les appels d’écriture à l’API

N’effectuez des appels d’écriture que dans le cadre de modifications approuvées, limitées et réversibles. Ne réutilisez jamais sans contrôle la charge utile d’un autre modèle, d’un autre firmware ou d’un ancien script.

Pour chaque appel d’écriture :

  1. Vérifiez la méthode, le chemin, les paramètres, les types de données et le schéma de réponse dans le schéma Swagger/OpenAPI de l’équipement cible, puis confirmez leur disponibilité sur le firmware en cours d’exécution.
  2. Relevez l’état concerné juste avant la modification au moyen d’une opération de lecture adaptée et conservez-le en lieu sûr.
  3. N’envoyez que les champs strictement nécessaires ; ne devinez pas les valeurs par défaut inconnues.
  4. Limitez l’opération à un seul switch et à un périmètre restreint et réversible.
  5. Évaluez le statut HTTP ainsi que les champs propres à l’application, tels que errCode et message.
  6. Confirmez l’état avec un GET indépendant et, le cas échéant, par un test fonctionnel.
  7. Arrêtez-vous au moindre écart ; n’envoyez pas d’autres modifications en boucle.

Une connexion HTTP réussie ne prouve pas que la modification a abouti. De même, un corps JSON plausible ne prouve pas que le chemin de données prévu fonctionne toujours. Les modifications de port, de VLAN ou de gestion doivent, par exemple, être également testées depuis le segment réseau concerné.

Gérer le jeton Bearer en toute sécurité pendant tout son cycle de vie

  1. Générer : Obtenez un nouveau jeton pour chaque session de l’API via PATCH /api/system/login.
  2. Vérifier : Vérifiez le résultat HTTP, errCode, message, le champ du jeton et les valeurs de session sans afficher le jeton.
  3. Utiliser : Envoyez le jeton uniquement dans l’en-tête Authorization: Bearer <token> et uniquement au switch prévu. <Token> est un espace réservé non exécutable ; les exemples exécutables génèrent l’en-tête à partir de SW_TOKEN.
  4. Limiter : N’exportez, ne conservez et ne partagez pas les jetons ; ne les écrivez pas non plus dans des fichiers, Git, des journaux CI ou des tickets. Utilisez une session contrôlée distincte pour chaque tâche parallèle.
  5. Renouveler : Après la fermeture ou le rejet de la session, n’utilisez plus le même jeton ; ouvrez une nouvelle session. Évitez les boucles infinies de reconnexion automatique.
  6. Fermer : Appelez l’opération de déconnexion documentée et authentifiée PATCH /api/system/logout. Ici encore, l’en-tête est transmis à cURL par l’entrée standard plutôt que par les arguments du processus :
printf 'header = "Authorization: Bearer %s"\n' "$SW_TOKEN" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    --cacert /path/to/switch-ca.pem \
    --request PATCH \
    'https://<Switch-IP-address>/api/system/logout'
  1. Supprimer localement : Après la déconnexion, supprimez les variables locales. Si la déconnexion n’est plus possible parce que la connexion a été interrompue ou que la session est déjà invalide, supprimez tout de même les secrets localement et vérifiez la fin de session selon les spécifications du firmware utilisé :
unset SW_TOKEN SW_USER LOGIN_RESPONSE SW_PASSWORD
  1. Contrôler : Vérifiez que l’historique du shell, les fichiers temporaires et les journaux de traitement ne contiennent aucun secret accidentel. Considérez tout jeton divulgué comme compromis, fermez la session et n’envoyez aucun autre appel.

Sophos Fusion Switch Management API au niveau du tenant

Cette section n’utilise pas https://<Switch-IP-address>/api/.... L’API Fusion authentifie un principal de service auprès de l’hôte régional de Sophos Fusion (anciennement Sophos Central) et agit sur le tenant indiqué. Les comptes locaux People, les rôles Admin/User, les jetons de session locaux et le schéma Swagger de l’équipement ne s’appliquent pas ici.

Prérequis, rôles et identifiants

Le switch doit être enregistré dans le bon tenant et géré par Sophos Fusion. La procédure exécutable ci-dessous s’applique exclusivement aux identifiants API directs de ce tenant (client_id et client_secret). Seul un Super Admin du tenant direct peut les créer sous Global Settings > Access Control > API Credentials ; le rôle attribué au principal de service doit autoriser les opérations de lecture et d’écriture requises. N’utilisez pas d’identifiants Partner ou Enterprise avec ces exemples de shell. Pour ces identifiants, suivez d’abord la procédure distincte de sélection du tenant décrite dans Gérer les identifiants d’API Sophos Fusion en toute sécurité, puis ne reprenez cette procédure qu’avec des identifiants créés et validés expressément pour le tenant direct sélectionné.

Conservez le secret et le JWT dans un coffre-fort de secrets, jamais dans un script, un ticket, l’historique du shell ou une sortie CI. Vous devez disposer de curl, de jq, de Bash, d’une fenêtre de changement approuvée, ainsi que de l’ID du tenant, de la région, de la liste actuelle, de la liste cible complète et d’une procédure de retour arrière documentés. Les identifiants d’API ne remplacent ni une licence ni une Support Subscription.

Authentifier le principal de service et découvrir l’hôte régional

Le fournisseur d’identité délivre le JWT via POST https://id.sophos.com/api/v2/oauth2/token. Avec les identifiants directs du tenant requis ici, GET https://api.central.sophos.com/whoami/v1 renvoie les champs id et apiHosts.dataRegion. La procédure s’arrête si whoami ne renvoie pas une identité de type tenant. N’envoyez jamais un ID Partner ou Organization dans X-Tenant-ID ; ne devinez pas l’hôte régional et ne reprenez pas celui d’un autre tenant.

read -r -p 'Service principal client ID: ' SP_CLIENT_ID
read -r -s -p 'Service principal client secret: ' SP_CLIENT_SECRET
printf '\n'

TOKEN_RESPONSE="$({
  jq -rn --arg id "$SP_CLIENT_ID" --arg secret "$SP_CLIENT_SECRET" \
    '"grant_type=client_credentials&client_id=\($id|@uri)&client_secret=\($secret|@uri)&scope=token"' |
  curl --disable --silent --show-error --fail-with-body \
    --header 'Content-Type: application/x-www-form-urlencoded' \
    --data-binary @- \
    'https://id.sophos.com/api/v2/oauth2/token'
})"
unset SP_CLIENT_SECRET
FUSION_TOKEN="$(jq -er '
  select(.token_type == "bearer") |
  .access_token | select(type == "string" and length > 0)
' <<<"$TOKEN_RESPONSE")" || exit 1
unset TOKEN_RESPONSE
WHOAMI_RESPONSE="$(
  printf 'header = "Authorization: Bearer %s"\n' "$FUSION_TOKEN" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    'https://api.central.sophos.com/whoami/v1'
)"
UUID_RE='^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$'
FUSION_TENANT_ID="$(jq -er --arg re "$UUID_RE" \
  'select(.idType == "tenant") | .id | select(type == "string" and test($re))' \
  <<<"$WHOAMI_RESPONSE")" || exit 1
FUSION_DATA_REGION="$(jq -er \
  '.apiHosts.dataRegion | select(type == "string" and test("^https://api-[a-z0-9-]+\\.central\\.sophos\\.com$"))' \
  <<<"$WHOAMI_RESPONSE")" || exit 1
unset WHOAMI_RESPONSE UUID_RE

Chaque requête Switch exige les deux en-têtes Authorization: Bearer <token> et X-Tenant-ID: <tenant-id>, ainsi que l’hôte <data-region> complet découvert précédemment. Les codes de réussite documentés sont 200 ou 201 ; ils prouvent uniquement que la requête a été acceptée.

Lire et remplacer intégralement les filtres MAC en toute sécurité

GET /switch/v1/settings/mac-filtering lit la liste des adresses bloquées à l’échelle du tenant. PUT /switch/v1/settings/mac-filtering n’ajoute pas d’éléments à la liste : macAddresses doit contenir la liste cible complète et remplace la liste existante. {"macAddresses":[]} efface toutes les entrées bloquées.

L’exemple ajoute une adresse synthétique 02: administrée localement. Ne publiez aucune adresse MAC réelle d’un client. Protégez les fichiers de travail, car ils contiennent des données opérationnelles.

printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
  "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    "$FUSION_DATA_REGION/switch/v1/settings/mac-filtering" \
    > mac-filter-before.json

jq -e '.macAddresses | type == "array"' mac-filter-before.json >/dev/null || exit 1
jq '{macAddresses: (.macAddresses + ["02:00:00:00:00:51"] | unique)}' \
  mac-filter-before.json > mac-filter-desired.json
jq -S '.macAddresses' mac-filter-before.json mac-filter-desired.json

N’effectuez l’écriture qu’après approbation du diff complet. Entre la capture de l’état de référence des tâches et l’enregistrement de l’ID de la tâche corrélée sans ambiguïté, aucune autre modification macFilters ne doit être exécutée dans le tenant :

printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
  "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    "$FUSION_DATA_REGION/switch/v1/tasks?type=macFilters&pageSize=50&pageTotal=true" \
    > mac-filter-tasks-before.json
jq -e '
  (.items | type == "array") and
  (.pages.current == 1) and
  (.pages.total >= 0) and (.pages.total <= 1) and
  ((.items | length) <= 50)
' mac-filter-tasks-before.json >/dev/null || exit 1

CHANGE_STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
  "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    --request PUT \
    --header 'Content-Type: application/json' \
    --data-binary @mac-filter-desired.json \
    "$FUSION_DATA_REGION/switch/v1/settings/mac-filtering" \
    > mac-filter-put-response.json

Relecture, interrogation des tâches et validation

La réussite d’un PUT ne prouve pas que chaque switch a appliqué la politique. Relisez le paramètre à l’identique, puis interrogez GET /switch/v1/tasks. Les tâches sont supprimées au bout de 30 jours et ne constituent pas une archive d’audit permanente. Les filtres documentés sont type, pageSize et pageTotal. Comme cette procédure ne suppose l’existence d’aucun autre paramètre de pagination documenté, elle ne traite qu’une première page complète de 50 tâches au maximum et s’arrête si pages.total > 1, au lieu d’ignorer silencieusement les pages suivantes.

printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
  "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    "$FUSION_DATA_REGION/switch/v1/settings/mac-filtering" \
    > mac-filter-after.json

jq -e '.macAddresses | type == "array"' mac-filter-after.json >/dev/null || exit 1
diff -u \
  <(jq -S '.macAddresses' mac-filter-desired.json) \
  <(jq -S '.macAddresses' mac-filter-after.json) || exit 1

CHANGE_TASK_ID=''
CHANGE_TASK_DONE=false
for CHANGE_POLL_ATTEMPT in $(seq 1 30); do
  printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
    "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
    curl --disable --silent --show-error --fail-with-body \
      --config - \
      "$FUSION_DATA_REGION/switch/v1/tasks?type=macFilters&pageSize=50&pageTotal=true" \
      > mac-filter-tasks.json

  jq -e '
    (.items | type == "array") and
    (.pages.current == 1) and
    (.pages.total >= 0) and (.pages.total <= 1) and
    ((.items | length) <= 50)
  ' mac-filter-tasks.json >/dev/null || exit 1

  if [ -z "$CHANGE_TASK_ID" ]; then
    jq -e -s --arg started "$CHANGE_STARTED_AT" '
      def epoch: sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601;
      [.[0].items[].id] as $before |
      [.[1].items[] |
        select(
          .type == "macFilters" and
          (.id as $id | ($before | index($id) | not)) and
          ((.createdAt | epoch) >= ($started | epoch)) and
          ((.updatedAt | epoch) >= ($started | epoch))
        )
      ] | if length > 1 then error("plusieurs tâches correspondantes") else . end
    ' mac-filter-tasks-before.json mac-filter-tasks.json \
      > mac-filter-task-matches.json || exit 1
    if jq -e 'length == 1' mac-filter-task-matches.json >/dev/null; then
      CHANGE_TASK_ID="$(jq -er '.[0].id | strings | select(length > 0)' \
        mac-filter-task-matches.json)" || exit 1
    fi
  fi

  if [ -n "$CHANGE_TASK_ID" ]; then
    jq -e --arg id "$CHANGE_TASK_ID" '
      [.items[] | select(.id == $id)] |
      select(length == 1) | .[0]
    ' mac-filter-tasks.json > mac-filter-task-current.json || exit 1

    if jq -e '.status.pending > 0' mac-filter-task-current.json >/dev/null; then
      :
    else
      jq -e '
        (.status.total | type == "number") and (.status.total > 0) and
        (.status.pending == 0) and (.status.failed == 0) and
        ((.status.noSupportSubscription // 0) == 0) and
        (.status.succeeded == .status.total) and
        (.switches | type == "array") and
        ((.switches | length) == .status.total) and
        all(.switches[];
          (.id | type == "string" and length > 0) and
          (.status == "succeeded") and
          (.error == null)
        )
      ' mac-filter-task-current.json >/dev/null || exit 1
      CHANGE_TASK_DONE=true
      break
    fi
  fi

  [ "$CHANGE_POLL_ATTEMPT" -lt 30 ] && sleep 10
done
[ "$CHANGE_TASK_DONE" = true ] || exit 1
jq '{id, type, createdAt, updatedAt, status, switches}' mac-filter-task-current.json

La procédure corrèle exactement une nouvelle tâche au moyen de CHANGE_STARTED_AT, de type: "macFilters" et des ID de tâches enregistrés avant le PUT. Elle interroge ensuite la collection au maximum 30 fois, à dix secondes d’intervalle, et ne sélectionne que l’ID de tâche enregistré. La réussite exige à la fois un état agrégé réussi et l’état terminal succeeded pour chaque entrée de switches[]. Toute ambiguïté, pagination, expiration du délai, erreur ou valeur noSupportSubscription entraîne l’arrêt de la procédure. Le PUT n’est jamais répété.

Traiter précisément les erreurs de l’API Fusion

  • 401/403 : Vérifiez l’expiration du JWT, le service principal, le contexte du tenant et les en-têtes ; les rôles locaux sont sans effet.
  • Tenant ou hôte régional incorrect : Redéterminez l’ID et l’hôte à partir de whoami ou de la liste des tenants gérés.
  • HTTP 200/201 sans effet : Vérifiez la relecture et la tâche correspondante ; poursuivez l’interrogation bornée sans répéter aveuglément le PUT.
  • noSupportSubscription : Corrigez la Support Subscription et l’état du switch dans le bon tenant ; il n’existe aucun contournement local.
  • Code 10905 – Duplicate MAC filter policy : Examinez switches[].error, relisez l’état et corrigez la requête dupliquée ou obsolète ; ne réessayez pas aveuglément.
  • Code 10906 – MAC filter list is exhausted : Arrêtez la procédure et faites approuver une liste complète plus courte ; n’envoyez jamais un sous-ensemble en guise d’ajout.
  • Code 10908 – MAC address already allowed in the static MAC table : Résolvez le conflit et évaluez son impact ; ne supprimez aucune entrée autorisée sans examen.

En cas d’échec, consignez ensemble le statut HTTP, l’ID de tâche, l’ID du switch, status, error, message et code, puis masquez les données sensibles.

Limites du retour arrière et de la restauration

Le retour arrière consiste en un autre PUT contenant l’intégralité de la liste sauvegardée. Effectuez d’abord une nouvelle lecture et excluez toute modification concurrente, puis appliquez la même procédure de relecture et de validation des tâches jusqu’à ce que chaque switch atteigne un état final.

# Maintenir une fenêtre de changement exclusive avant cette nouvelle lecture.
printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
  "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    "$FUSION_DATA_REGION/switch/v1/settings/mac-filtering" \
    > mac-filter-pre-restore.json
jq -e '.macAddresses | type == "array"' mac-filter-pre-restore.json >/dev/null || exit 1
diff -u \
  <(jq -S '.macAddresses' mac-filter-desired.json) \
  <(jq -S '.macAddresses' mac-filter-pre-restore.json) || exit 1

printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
  "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    "$FUSION_DATA_REGION/switch/v1/tasks?type=macFilters&pageSize=50&pageTotal=true" \
    > mac-filter-restore-tasks-before.json
jq -e '
  (.items | type == "array") and
  (.pages.current == 1) and
  (.pages.total >= 0) and (.pages.total <= 1) and
  ((.items | length) <= 50)
' mac-filter-restore-tasks-before.json >/dev/null || exit 1

jq '{macAddresses}' mac-filter-before.json > mac-filter-restore.json
RESTORE_STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
  "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    --request PUT \
    --header 'Content-Type: application/json' \
    --data-binary @mac-filter-restore.json \
    "$FUSION_DATA_REGION/switch/v1/settings/mac-filtering" \
    > mac-filter-restore-response.json

printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
  "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
  curl --disable --silent --show-error --fail-with-body \
    --config - \
    "$FUSION_DATA_REGION/switch/v1/settings/mac-filtering" \
    > mac-filter-restored.json
jq -e '.macAddresses | type == "array"' mac-filter-restored.json >/dev/null || exit 1
diff -u \
  <(jq -S '.macAddresses' mac-filter-restore.json) \
  <(jq -S '.macAddresses' mac-filter-restored.json) || exit 1

RESTORE_TASK_ID=''
RESTORE_TASK_DONE=false
for RESTORE_POLL_ATTEMPT in $(seq 1 30); do
  printf 'header = "Authorization: Bearer %s"\nheader = "X-Tenant-ID: %s"\n' \
    "$FUSION_TOKEN" "$FUSION_TENANT_ID" |
    curl --disable --silent --show-error --fail-with-body \
      --config - \
      "$FUSION_DATA_REGION/switch/v1/tasks?type=macFilters&pageSize=50&pageTotal=true" \
      > mac-filter-restore-tasks.json

  jq -e '
    (.items | type == "array") and
    (.pages.current == 1) and
    (.pages.total >= 0) and (.pages.total <= 1) and
    ((.items | length) <= 50)
  ' mac-filter-restore-tasks.json >/dev/null || exit 1

  if [ -z "$RESTORE_TASK_ID" ]; then
    jq -e -s --arg started "$RESTORE_STARTED_AT" '
      def epoch: sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601;
      [.[0].items[].id] as $before |
      [.[1].items[] |
        select(
          .type == "macFilters" and
          (.id as $id | ($before | index($id) | not)) and
          ((.createdAt | epoch) >= ($started | epoch)) and
          ((.updatedAt | epoch) >= ($started | epoch))
        )
      ] | if length > 1 then error("plusieurs tâches de restauration correspondantes") else . end
    ' mac-filter-restore-tasks-before.json mac-filter-restore-tasks.json \
      > mac-filter-restore-task-matches.json || exit 1
    if jq -e 'length == 1' mac-filter-restore-task-matches.json >/dev/null; then
      RESTORE_TASK_ID="$(jq -er '.[0].id | strings | select(length > 0)' \
        mac-filter-restore-task-matches.json)" || exit 1
    fi
  fi

  if [ -n "$RESTORE_TASK_ID" ]; then
    jq -e --arg id "$RESTORE_TASK_ID" '
      [.items[] | select(.id == $id)] |
      select(length == 1) | .[0]
    ' mac-filter-restore-tasks.json > mac-filter-restore-task-current.json || exit 1

    if jq -e '.status.pending > 0' mac-filter-restore-task-current.json >/dev/null; then
      :
    else
      jq -e '
        (.status.total | type == "number") and (.status.total > 0) and
        (.status.pending == 0) and (.status.failed == 0) and
        ((.status.noSupportSubscription // 0) == 0) and
        (.status.succeeded == .status.total) and
        (.switches | type == "array") and
        ((.switches | length) == .status.total) and
        all(.switches[];
          (.id | type == "string" and length > 0) and
          (.status == "succeeded") and
          (.error == null)
        )
      ' mac-filter-restore-task-current.json >/dev/null || exit 1
      RESTORE_TASK_DONE=true
      break
    fi
  fi

  [ "$RESTORE_POLL_ATTEMPT" -lt 30 ] && sleep 10
done
[ "$RESTORE_TASK_DONE" = true ] || exit 1
jq '{id, type, createdAt, updatedAt, status, switches}' \
  mac-filter-restore-task-current.json

unset FUSION_TOKEN SP_CLIENT_ID FUSION_TENANT_ID FUSION_DATA_REGION \
  CHANGE_STARTED_AT CHANGE_TASK_ID RESTORE_STARTED_AT RESTORE_TASK_ID

mac-filter-before.json n’est qu’un instantané de ce paramètre, et non une sauvegarde complète du switch. Si l’état initial est perdu, n’envoyez jamais une liste vide pour réinitialiser la configuration : elle effacerait toutes les entrées bloquées. Cette API ne restaure pas la configuration CLI/REST locale et ne met pas fin à un isolement Active Threat Response. Enfin, supprimez les variables contenant les jetons, protégez ou effacez les fichiers de manière sûre et consignez le résultat de la tâche.

Traiter les erreurs par symptôme

Erreur TLS ou certificat

  • Vérifiez le nom de gestion, l’adresse IP, la période de validité, la chaîne de certificats et l’heure du système.
  • Indiquez l’autorité de certification correcte avec --cacert ou remplacez le certificat de l’équipement selon la procédure de gestion prévue.
  • N’utilisez pas -k comme solution permanente. Cette option chiffre le transport, mais n’authentifie pas le switch.
  • Si le certificat ou la clé d’hôte SSH a changé, confirmez d’abord que vous vous adressez bien au switch prévu.

Connexion refusée, délai d’attente dépassé ou aucune route

  • Vérifiez l’adresse IP de gestion, le VLAN de gestion, le routage, les ACL et l’activation du service.
  • Effectuez le test depuis un hôte autorisé du réseau de gestion prévu.
  • Ne contournez pas le problème en ouvrant le service à tous les réseaux ou à Internet.
  • Si une modification récente a interrompu l’accès, utilisez le chemin de gestion indépendant et la procédure de retour arrière préparée.

Échec de la connexion

  • Assurez-vous d’utiliser un compte local du switch, et non un compte Sophos Fusion.
  • Vérifiez le nom d’utilisateur, les exigences relatives au mot de passe, l’état du compte et le rôle local.
  • Ne multipliez pas les tentatives automatiques fondées sur des valeurs devinées ; elles risquent de verrouiller le compte et de masquer la cause.
  • Pour le compte standard admin, rappelez-vous que seul admin lui-même ou Sophos Fusion peut modifier son mot de passe.

HTTP 401 ou 403

  • Pour une erreur 401, vérifiez si la session a pris fin et reconnectez-vous pour obtenir un nouveau jeton.
  • Pour une erreur 403, vérifiez le rôle local et l’autorisation requise par l’opération ; n’augmentez pas les privilèges sans justification précise.
  • Ne demandez un nouveau jeton que de manière contrôlée. Conservez l’erreur, la réponse et l’état du firmware en lieu sûr, consultez l’aide publiée de l’API et comparez le modèle et le firmware de l’équipement cible.

HTTP 400, 404 ou 405

  • Comparez le chemin, la méthode, les en-têtes et le JSON avec l’aide publiée de l’API, puis validez-les pour le modèle et le firmware de l’équipement cible.
  • Vérifiez la casse et le préfixe /api.
  • Une erreur 404 ou 405 peut signaler un endpoint indisponible ou défini différemment dans ce firmware. Ne tentez pas de la résoudre en essayant des opérations d’écriture similaires.

Erreur HTTP ou valeur errCode différente de 0

  • Relevez ensemble le statut HTTP et le corps de la réponse.
  • Consignez message, masquez les informations internes avant tout partage et ne répétez pas aveuglément une modification identique.
  • Si vous ignorez si une modification a été partiellement appliquée, déterminez d’abord l’état réel au moyen d’un GET, de l’affichage CLI et d’un test fonctionnel.

Commande CLI manquante ou rejetée

  • Avec ?, vérifiez si la commande existe dans le mode actif.
  • Avec TAB, complétez la syntaxe proposée par l’équipement.
  • Vérifiez le rôle local, le mode de commande, le modèle et le firmware.
  • Ne reprenez pas une commande au nom similaire provenant d’un autre firmware.

Retour arrière, déconnexion et fin de session

Un simple test GET /api/ports ne modifie aucune configuration et ne nécessite donc pas de retour arrière. La connexion à l’API crée toutefois une session : fermez-la conformément au cycle de vie du jeton, puis supprimez les variables locales.

Pour toute modification de configuration, définissez le retour arrière à l’avance :

  1. Exportez l’état réel et les objets concernés, ou enregistrez-les au moyen d’opérations de lecture.
  2. Préparez l’opération inverse exacte à partir de l’aide publiée de l’API, validée pour le firmware utilisé, ou de l’aide CLI de l’équipement cible.
  3. Définissez les critères de retour arrière immédiat, par exemple la perte de l’accès de gestion, de la liaison montante, de l’accès au VLAN ou de l’alimentation PoE.
  4. En cas d’erreur, n’essayez pas d’autres ajustements : restaurez la valeur précédente par le chemin qui fonctionne encore.
  5. Vérifiez ensuite de nouveau l’accès de gestion, l’état des ports, les liaisons montantes et les services concernés.
  6. Si le chemin normal est perdu, effectuez le retour arrière par le chemin de gestion indépendant vérifié au préalable, éventuellement par l’accès console propre au modèle. Une réinitialisation d’usine n’est pas une procédure normale de retour arrière, car elle efface la configuration.

Pour un switch géré dans Sophos Fusion, un retour arrière local ne suffit pas. Vérifiez l’état cible faisant autorité dans Sophos Fusion, puis reportez-y proprement la modification d’urgence approuvée ou supprimez-la entièrement en local. Ne laissez pas la configuration diverger entre les canaux local et central.

Pour terminer :

  • quittez un affichage CLI avec Q, puis utilisez à l’invite la commande de sortie ou de déconnexion indiquée par l’équipement ;
  • fermez la session API avec l’opération documentée PATCH /api/system/logout ;
  • supprimez les variables locales contenant le jeton et le mot de passe avec unset ;
  • vérifiez qu’aucun fichier temporaire ni journal de débogage ne contient de secret ;
  • consignez dans le ticket le résultat, le firmware, le chemin d’administration utilisé, les contrôles et l’éventuel retour arrière.

Renforcement de la sécurité

  • Utilisez un VLAN de gestion dédié, avec des ACL limitées à quelques postes d’administration et aux protocoles requis.
  • N’activez HTTPS et SSH que lorsque cela est nécessaire ; désactivez les services de gestion non sécurisés ou inutilisés.
  • Utilisez des certificats de confiance et des clés d’hôte SSH vérifiées.
  • Utilisez des comptes locaux nominatifs : User pour la consultation seule et Admin uniquement pour les modifications approuvées.
  • Remplacez immédiatement le mot de passe par défaut ; évitez les mots de passe courants et procédez à une rotation après tout changement de personnel ou de prestataire.
  • Conservez les secrets de l’API hors du code source, des fichiers .env, de l’historique du shell, des arguments de processus et des sorties CI.
  • N’exécutez jamais un client d’API avec un débogage détaillé des en-têtes lorsqu’un en-tête Authorization est défini.
  • Gardez les sessions courtes, utilisez un nouveau jeton pour chaque session, puis supprimez les variables.
  • Conservez les sauvegardes de configuration protégées et récupérables hors du switch.
  • Corrélez dans le temps les journaux locaux, les événements centraux et les tickets de changement ; vérifiez l’heure du switch.
  • Réévaluez régulièrement les comptes locaux, les ACL de gestion, les certificats, les clés SSH et les accès d’automatisation.

Variabilité selon le firmware et le modèle

La page statique de l’API REST documente le chemin de connexion /api/system/login, le test de lecture /api/ports et l’en-tête Authorization ; l’aide publiée de l’API documente également le chemin de déconnexion /api/system/logout. Cette documentation centrale fournit des exemples et des indications, mais pas un schéma applicable universellement à tous les équipements. Le schéma Swagger/OpenAPI propre au switch cible est généré par le firmware en cours d’exécution et reflète son modèle et sa version. La documentation publiée de la CLI confirme les fonctions d’aide mentionnées ci-dessus, mais ne garantit pas non plus que toutes les autres commandes soient identiques sur chaque firmware.

Avant toute utilisation en production, pour chaque modèle et chaque firmware :

  • documentez la version exacte du firmware et le modèle matériel ;
  • vérifiez le mode et la syntaxe de la CLI avec ? et TAB ;
  • vérifiez dans le schéma Swagger/OpenAPI les méthodes, les chemins et les schémas fournis par l’équipement cible exécutant ce firmware ;
  • testez d’abord la connexion et GET /api/ports dans une session contrôlée ;
  • validez toute automatisation d’écriture par rapport à ce schéma cible précis, ainsi que dans un environnement hors production ou clairement circonscrit ;
  • après une mise à jour du firmware, testez de nouveau la connexion, la vérification du certificat, le schéma de réponse, le traitement des jetons et tous les endpoints utilisés ;
  • en cas d’écart, n’« adaptez » pas le script avant d’avoir compris la nouvelle sémantique et la procédure de retour arrière.

Vérification finale

  • Modèle, firmware et adresse IP de gestion corrects confirmés.
  • Système de référence — Sophos Fusion ou administration locale — documenté.
  • Compte local User ou Admin adapté à la tâche utilisé.
  • Certificat TLS ou clé d’hôte SSH vérifiés ; aucune option non sécurisée permanente utilisée.
  • Jeton utilisé uniquement pour sa session et jamais divulgué.
  • Statut HTTP, errCode, message et état fonctionnel vérifiés.
  • Pour les modifications, état actuel, procédure de retour arrière et accès indépendant disponibles.
  • Résultat vérifié par un GET, l’affichage CLI et tout test fonctionnel requis.
  • Session fermée, variables supprimées et journaux contrôlés pour détecter d’éventuels secrets.
  • Exception locale réconciliée avec Sophos Fusion et clôturée dans le ticket.