Saltar para o conteudo
Avanet

Gerir Sophos Switch por CLI, API REST local e API Fusion

A CLI e a API REST locais acedem diretamente a um Sophos Switch. A Sophos Fusion Switch Management API é separada: usa credenciais de service principal ao nível do tenant e distribui políticas centrais aos switches. Este runbook identifica a identidade, o URL base e a validação aplicáveis a cada via.

Âmbito e decisões preliminares de segurança

Este runbook abrange o seguinte:

  • acesso local à CLI através de um canal de gestão autorizado para o dispositivo, em especial SSH;
  • orientação e diagnóstico na CLI;
  • início de sessão na API REST local do dispositivo;
  • ciclo de vida do token Bearer associado à sessão;
  • um teste documentado da API, só de leitura e que não efetua alterações, com GET /api/ports;
  • análise segura, resolução de problemas, rollback e encerramento da sessão.

Este runbook não pretende apresentar um catálogo completo de endpoints da API ou de comandos da CLI. A documentação central fornece orientação; contudo, antes de utilizar a API, confirme a disponibilidade no dispositivo de destino. Os caminhos aí indicados, como /ports, são relativos à base /api do servidor, pelo que o caminho completo da chamada é /api/ports. Os comandos, modos e parâmetros da CLI também podem variar consoante o modelo e o firmware. A verificação obrigatória é descrita na secção Variabilidade do firmware e do modelo.

Defina o seguinte antes do acesso:

  1. O sistema de registo é o Sophos Fusion ou a administração local?
  2. Qual é o switch afetado, o modelo, o firmware e o IP de gestão?
  3. O acesso só de leitura é suficiente ou é necessária uma alteração aprovada?
  4. Qual é o estado atual, o critério de sucesso e o rollback?
  5. Existe uma via de gestão independente caso a alteração interrompa o acesso normal?

Não misturar identidades e papéis

As contas sob People são contas locais do switch:

Tipo de privilégio localDireitos no switchUtilização típica
AdminVer e alterar todas as funções do switchAdministração local aprovada e chamadas de escrita à API
UserVer as definições, sem as alterarDiagnóstico e verificação segundo o princípio do menor privilégio

Estas funções locais não são equivalentes às funções de administrador no Sophos Fusion. Uma função do Fusion não concede automaticamente acesso local à CLI ou à API, e as credenciais locais não são credenciais do Fusion. As credenciais de uma conta local do switch são enviadas para /api/system/login.

As contas locais são geridas na interface local em People:

  1. Selecione Add para criar uma conta, ou escolha Edit ao lado de uma conta.
  2. Defina Username, Password e Privilege type.
  3. Selecione Privilege type como Admin ou User.
  4. Guarde com Apply.

Uma palavra-passe local deve cumprir todos os seguintes requisitos:

  • pelo menos 10 e, no máximo, 32 caracteres;
  • pelo menos uma letra e um número;
  • pelo menos um destes caracteres especiais: @ ~ % * # + - =.

Uma conta Admin local pode alterar as palavras-passe de outras contas, mas não a palavra-passe da conta admin predefinida. Esta só pode ser alterada pela própria conta admin ou pelo Sophos Fusion. Para a operação habitual, utilize contas locais individuais em vez de uma conta admin partilhada.

Preparar o acesso

Antes de uma sessão:

  • Compare o IP de gestão, o modelo, o estado do firmware, a localização e o número de série com o ticket de alteração.
  • Permita o acesso apenas a partir de uma rede de gestão administrativa e de um host de administração autorizado.
  • Não exponha os serviços de gestão a VLANs de utilizadores nem à Internet.
  • Verifique a fonte de hora e o relógio do host de administração; uma hora incorreta dificulta a análise dos registos e da API.
  • Se for efetuar uma alteração, tenha disponível uma cópia de segurança atual da configuração e uma via de recuperação independente.
  • Em ambientes geridos pelo Fusion, preserve o estado-alvo central e documente explicitamente a exceção local.
  • Nunca guarde credenciais ou tokens Bearer em tickets, conversas, capturas de ecrã, histórico da shell ou código-fonte.

Substitua os seguintes marcadores de posição:

Marcador de posiçãoSignificado
<Switch-IP address>IP de gestão ou nome de gestão fidedigno do dispositivo de destino
<LOCAL-USERNAME>Conta local do switch, não um utilizador do Fusion
your-passwordPalavra-passe local; é apenas um marcador de posição, nunca uma palavra-passe real
xxxxxxxx.yyyyyyyy.zzzzzzExemplo ocultado de um token Bearer, não um token real

Utilizar CLI com segurança

Estabelecer a ligação

O SSH faz parte dos serviços de gestão local. Tem de estar configurado no firmware em utilização e acessível a partir da rede de gestão. Um comando genérico do cliente é:

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

O comando é apenas um exemplo: substitua totalmente <LOCAL-USERNAME> e <Switch-IP-address>, incluindo os parênteses angulares. Antes de aceitar a chave do host, compare a respetiva impressão digital por um meio independente e fidedigno. Não ignore um aviso de alteração da chave do host; verifique primeiro se houve substituição do dispositivo, reposição de fábrica, conflito de IP ou um possível ataque man-in-the-middle.

Se o modelo específico disponibilizar acesso físico à consola, este pode servir como via de manutenção independente. A ligação e os parâmetros da porta série têm de corresponder ao modelo; não os copie de outro modelo de Sophos Switch.

Orientação na CLI

Comece por observar a linha de comandos atual e, consequentemente, o modo de comando ativo. Não pressuponha que um comando está disponível em todos os modos. As teclas e ajudas documentadas são:

EntradaEfeito
?Listar os comandos disponíveis
TABCompletar o comando
Seta para cima / para baixoMostrar os comandos executados anteriormente
Seta para a esquerda/direitaNavegar na linha atual
Backspace ou Ctrl + HApagar um caractere
HistoryMostrar a lista do histórico de comandos
QSair de uma apresentação de resultados e regressar à linha de comandos do switch

Confirme as maiúsculas e minúsculas e a disponibilidade exata com ? ou TAB no dispositivo de destino. Q termina uma apresentação paginada ou ativa; não encerra automaticamente a sessão SSH.

Sequência CLI segura

  1. Comece com uma conta User local quando o acesso só de leitura for suficiente.
  2. Confirme o dispositivo de destino, a linha de comandos e o modo de comando.
  3. Utilize ? para apresentar os comandos disponíveis nesse modo.
  4. Comece apenas por operações de estado ou de visualização.
  5. Antes de uma alteração, registe o estado atual completo e o rollback exato.
  6. Altere apenas um elemento técnico de cada vez e verifique-o imediatamente.
  7. Quando o resultado for paginado, regresse à linha de comandos com Q.
  8. Encerre corretamente a sessão com o comando exit/logout indicado por ? no dispositivo de destino e confirme depois o encerramento da ligação SSH.

Trate o histórico de comandos como informação sensível: pode conter endereços de gestão, nomes de utilizador ou parâmetros introduzidos. Nunca introduza palavras-passe ou tokens como parâmetros da CLI, salvo se o switch os solicitar de forma interativa.

API REST: início de sessão documentado

A API está disponível por HTTPS no endereço de gestão do switch. O switch cria um novo token Bearer para cada sessão. Quando a sessão atual terminar, terá de obter um novo token.

O procedimento documentado utiliza PATCH /api/system/login. O exemplo seguinte apresenta a sintaxe publicada sem alterações, com marcadores de posição neutros:

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

Uma resposta bem-sucedida de exemplo tem esta estrutura:

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

Aplicam-se as seguintes regras:

  • token é um segredo com os mesmos requisitos de proteção de uma palavra-passe.
  • utctimestamp e timeout fazem parte da sessão específica.
  • A resposta de exemplo documentada apresenta timeout: 900; a descrição estática não especifica a unidade. Para automatizar, consulte o significado na ajuda publicada da API, valide-o para o firmware efetivamente utilizado e não fixe o valor no código.
  • errCode: 0 e message: "OK" indicam a resposta bem-sucedida apresentada. Verifique também o estado HTTP.
  • Solicite um novo token após o fim da sessão ou se esta for rejeitada ou expirar; não reutilize um token antigo.

Padrão cURL endurecido

O padrão seguinte evita colocar a palavra-passe e os tokens nos argumentos do processo, verifica o certificado TLS e não cria um ficheiro com segredos. Requer curl, jq e uma shell que suporte here strings:

read -r -p 'Utilizador local do switch: ' SW_USER
read -r -s -p 'Palavra-passe local do 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' 'Falha no início de sessão na 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' 'O início de sessão na API não devolveu um token Bearer válido.' >&2
    unset SW_TOKEN SW_USER
    exit 1
    ;;
esac

Substitua /path/to/switch-ca.pem e <Switch-IP-address>. Utilize o nome do host para o qual o certificado foi emitido. Numa shell interativa, certifique-se de que não está ativo qualquer rastreio de depuração, como set -x. Não exponha variáveis através de env, export, resultados de depuração ou core dumps.

API REST: chamada e verificação

A chamada de exemplo documentada lê as portas e envia o token no cabeçalho Authorization:

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

<Token> é o marcador de posição canónico para o token Bearer. Por isso, este exemplo publicado não é executável: não substitua <Token> pelo token real nem introduza esse comando no histórico da shell. A chamada executável seguinte utiliza a variável SW_TOKEN definida durante o início de sessão.

Numa sessão real, utilize o token já protegido e a verificação do certificado. --config - lê a configuração do curl a partir da entrada padrão; assim, o cabeçalho Authorization não é passado como argumento do processo nem é criado um ficheiro temporário:

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 é o primeiro teste funcional adequado, porque a chamada documentada consulta o estado em vez de efetuar uma alteração de configuração. O teste só é bem-sucedido se:

  1. a verificação e a ligação TLS forem bem-sucedidas;
  2. não for devolvido qualquer erro HTTP;
  3. a resposta for sintática e tecnicamente plausível;
  4. o número, o nome e os estados esperados das portas corresponderem ao dispositivo de destino correto;
  5. não surgirem credenciais ou tokens nos resultados ou nos registos.

curl --fail-with-body devolve um estado de erro em caso de erro HTTP, mas preserva o corpo da resposta para diagnóstico local. Antes de partilhar esse corpo, verifique se contém tokens, endereços, números de série ou outros dados internos.

Controlar as chamadas de escrita à API

Efetue chamadas de escrita apenas no âmbito de alterações aprovadas, limitadas e reversíveis. Não reutilize sem verificação um payload de outro modelo, firmware ou script antigo.

Para cada chamada de escrita:

  1. Verifique o método, o caminho, os parâmetros, os tipos de dados e o esquema da resposta no esquema Swagger/OpenAPI do dispositivo de destino e confirme a disponibilidade no firmware em execução.
  2. Obtenha o estado afetado imediatamente antes, através de uma operação de leitura adequada, e guarde-o em segurança.
  3. Envie apenas os campos mínimos necessários; não tente adivinhar valores predefinidos desconhecidos.
  4. Utilize exatamente um switch e um âmbito reduzido e reversível.
  5. Avalie o estado HTTP e os campos específicos da aplicação, como errCode e message.
  6. Confirme o estado com um GET independente e, quando aplicável, com um teste funcional.
  7. Pare perante qualquer desvio; não envie repetidamente outras alterações.

Uma ligação HTTP bem-sucedida, por si só, não prova que a alteração foi bem-sucedida. Do mesmo modo, um corpo JSON plausível não prova que o caminho de dados pretendido continua a funcionar. Por exemplo, as alterações de portas, VLAN ou gestão também têm de ser testadas a partir do segmento de rede afetado.

Gerir o token Bearer em segurança durante todo o ciclo de vida

  1. Gerar: Obtenha um novo token para cada sessão da API através de PATCH /api/system/login.
  2. Verificar: Verifique o resultado HTTP, errCode, message, o campo do token e os valores da sessão sem exibir o token.
  3. Usar: Envie o token apenas no cabeçalho Authorization: Bearer <token> e apenas para o switch pretendido. O marcador canónico <Token> representa exclusivamente um valor não executável; os exemplos executáveis geram o cabeçalho a partir de SW_TOKEN.
  4. Limitar: Não exporte, armazene nem partilhe tokens e não os grave em ficheiros, no Git, em registos de CI ou em tickets. Utilize uma sessão controlada separada para cada trabalho em paralelo.
  5. Renovar: Após o fim ou a rejeição da sessão, não continue a utilizar o mesmo token; crie uma nova sessão. Evite ciclos automáticos intermináveis de novo início de sessão.
  6. Fechar: Chame a operação autenticada de logout documentada, PATCH /api/system/logout. Também aqui, o cabeçalho chega ao curl através da entrada padrão e não dos argumentos do processo:
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. Eliminar localmente: Após o logout, remova as variáveis locais. Se o logout já não for possível devido à interrupção da ligação ou a uma sessão inválida, elimine ainda assim os segredos localmente e confirme o encerramento da sessão de acordo com as especificações do firmware utilizado:
unset SW_TOKEN SW_USER LOGIN_RESPONSE SW_PASSWORD
  1. Verificar: Examine o histórico da shell, os ficheiros temporários e os registos dos trabalhos para detetar segredos expostos acidentalmente. Considere comprometido qualquer token divulgado, encerre a sessão e não efetue mais chamadas com esse token.

Sophos Fusion Switch Management API ao nível do tenant

Esta secção não utiliza https://<Switch-IP-address>/api/.... A API Fusion autentica um service principal no host regional do Sophos Fusion (anteriormente Sophos Central) e atua no tenant indicado. As contas locais em People, as funções Admin/User, os tokens de sessão locais e o esquema Swagger do dispositivo não se aplicam.

Pré-requisitos, funções e credenciais

O switch tem de estar registado no tenant correto e ser gerido pelo Sophos Fusion. O procedimento executável seguinte aplica-se exclusivamente às credenciais diretas da API desse tenant (client_id e client_secret). Apenas um Super Admin do tenant direto pode criá-las em Global Settings > Access Control > API Credentials; a função atribuída ao service principal tem de permitir as operações de leitura e escrita necessárias. Não utilize credenciais de Partner ou Enterprise com estes exemplos de shell. Para essas credenciais, siga primeiro o procedimento separado de seleção do tenant em Gerir as credenciais da API Sophos Fusion em segurança e só depois retome este procedimento com credenciais emitidas e validadas especificamente para o tenant direto selecionado.

Guarde o secret e o JWT num cofre, nunca em scripts, tickets, histórico da shell ou resultados de CI. São necessários curl, jq, Bash, uma janela de alteração aprovada e o registo do ID do tenant, da região, da lista atual, da lista-alvo completa e do rollback. As credenciais não substituem uma licença nem uma Support Subscription.

Autenticar o service principal e descobrir o host regional

O IDP emite o JWT através de POST https://id.sophos.com/api/v2/oauth2/token. Com as credenciais diretas de tenant exigidas por este procedimento, GET https://api.central.sophos.com/whoami/v1 devolve os campos id e apiHosts.dataRegion. O procedimento é interrompido se whoami não devolver uma identidade de tenant. Nunca envie um ID de Partner ou Organization como X-Tenant-ID; não tente adivinhar o host regional nem o copie de outro 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

Cada pedido ao switch exige os dois cabeçalhos Authorization: Bearer <token> e X-Tenant-ID: <tenant-id>, bem como o host <data-region> completo que foi determinado. Os estados de sucesso documentados são 200 ou 201; apenas confirmam que o pedido foi aceite.

Ler e substituir completamente filtros MAC em segurança

GET /switch/v1/settings/mac-filtering lê a lista de bloqueios de todo o tenant. PUT /switch/v1/settings/mac-filtering não acrescenta entradas: macAddresses tem de conter a lista-alvo completa e substitui a lista existente. {"macAddresses":[]} elimina todos os bloqueios.

O exemplo acrescenta um endereço 02: fictício e administrado localmente. Não publique um endereço MAC real. Proteja os ficheiros de trabalho, pois contêm dados operacionais.

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

Efetue a escrita apenas depois de o diff completo ser aprovado. Desde a recolha da linha de base das tarefas até ao registo do ID da tarefa correlacionada de forma inequívoca, não pode estar em curso qualquer outra alteração de macFilters no 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

Releitura, polling de tarefas e validação

Um PUT bem-sucedido não prova que todos os switches aplicaram a política. Volte a ler a definição exata e consulte depois GET /switch/v1/tasks. As tarefas são eliminadas após 30 dias e não constituem um registo de auditoria permanente. Os filtros documentados são type, pageSize e pageTotal. Como este procedimento não pressupõe a existência de outro parâmetro de paginação documentado, processa, no máximo, a primeira página completa de 50 tarefas e é interrompido se pages.total > 1, em vez de ignorar silenciosamente as páginas seguintes.

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("várias tarefas correspondentes") 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

O procedimento correlaciona exatamente uma nova tarefa através de CHANGE_STARTED_AT, de type: "macFilters" e dos IDs das tarefas guardados antes do PUT. Em seguida, consulta a coleção, no máximo, 30 vezes, com intervalos de dez segundos, e seleciona exclusivamente o ID da tarefa guardado. O sucesso exige um estado agregado bem-sucedido e, em cada entrada de switches[], o estado terminal succeeded; qualquer ambiguidade, paginação, timeout, erro ou valor noSupportSubscription provoca a interrupção. O PUT não é repetido.

Tratar precisamente os erros da API Fusion

  • 401/403: Verifique a validade do JWT, o service principal, o contexto do tenant e os cabeçalhos; as funções locais não têm qualquer efeito.
  • Tenant ou host regional errado: Obtenha novamente ID e host por whoami ou lista de tenants geridos.
  • HTTP 200/201 sem efeito: Verifique a releitura e a tarefa correspondente; prossiga com polling limitado, sem repetir o PUT às cegas.
  • noSupportSubscription: Corrija Support Subscription e estado do dispositivo no tenant certo; sem contorno local.
  • Código 10905 – Duplicate MAC filter policy: Consulte switches[].error, releia o estado e resolva o pedido duplicado ou obsoleto; não tente novamente às cegas.
  • Código 10906 – MAC filter list is exhausted: Pare e obtenha aprovação para uma lista completa mais pequena; nunca envie um subconjunto como se fosse um acréscimo.
  • Código 10908 – MAC address already allowed in the static MAC table: Resolva o conflito e avalie o impacto na segurança; não remova entradas estáticas permitidas sem análise.

Em caso de falha, registe em conjunto o estado HTTP, o ID da tarefa, o ID do switch, status, error, message e code, ocultando os dados sensíveis.

Limites do rollback e do restauro

O rollback consiste noutro PUT completo da lista guardada. Releia primeiro o estado e exclua a existência de alterações concorrentes; depois efetue a mesma releitura e validação até cada switch atingir um estado terminal.

# Garanta uma janela de alteração exclusiva antes desta nova leitura.
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("várias tarefas de reversão correspondentes") 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 é apenas um instantâneo desta definição, não uma cópia de segurança completa do switch. Se perder a linha de base, nunca envie uma lista vazia como reposição: isso elimina todos os bloqueios. Esta API não restaura a configuração local da CLI/API REST nem liberta o isolamento do Active Threat Response. No final, elimine as variáveis do token, proteja ou apague os ficheiros de forma segura e documente o resultado da tarefa.

Tratar erros por sintoma

Erro de TLS ou certificado

  • Verifique o nome de gestão, o IP, a validade, a cadeia de certificados e a hora do sistema.
  • Forneça a CA correta com --cacert ou substitua o certificado do dispositivo através do processo de gestão previsto.
  • Não utilize -k como solução permanente. Esta opção cifra o transporte, mas não autentica o switch.
  • Se o certificado ou a chave do host SSH tiver mudado, confirme primeiro que está efetivamente a aceder ao switch pretendido.

Ligação rejeitada, tempo limite ou nenhuma rota

  • Verifique o IP de gestão, a VLAN de gestão, o encaminhamento, a ACL e a disponibilidade do serviço.
  • Efetue o teste a partir de um host autorizado na rede de gestão prevista.
  • Não contorne abrindo o serviço para todas as redes ou a Internet.
  • Se uma alteração que acabou de efetuar tiver interrompido o acesso, utilize a via de gestão independente e o rollback preparado.

Falha no início de sessão

  • Certifique-se de que utiliza uma conta local do switch, não uma conta do Sophos Fusion.
  • Verifique o nome de utilizador, os requisitos da palavra-passe, o estado da conta e a função local.
  • Não faça tentativas automáticas repetidas; podem bloquear a conta e ocultar a causa.
  • Na conta admin predefinida, tenha em conta que a palavra-passe só pode ser alterada pela própria conta admin ou pelo Sophos Fusion.

HTTP 401 ou 403

  • Perante um 401, verifique se a sessão terminou e inicie uma nova sessão.
  • Perante um 403, verifique a função local e a autorização para a operação; não aumente indiscriminadamente os privilégios.
  • Tente obter um novo token, no máximo, uma vez e de forma controlada. Guarde em segurança o erro, a resposta e o estado do firmware, consulte a ajuda publicada da API e compare o modelo e o firmware do dispositivo de destino.

HTTP 400, 404 ou 405

  • Compare o caminho, o método, o cabeçalho e o JSON com a ajuda publicada da API e valide-os para o modelo e o firmware do dispositivo de destino.
  • Verifique as maiúsculas e minúsculas e o prefixo /api.
  • Um 404 ou 405 pode indicar um endpoint indisponível ou definido de forma diferente neste firmware. Não tente contornar o erro com operações de escrita semelhantes.

Erro HTTP ou errCode diferente de 0

  • Registe o estado HTTP e o corpo da resposta.
  • Documente message, oculte as informações internas antes da divulgação e não repita às cegas uma alteração idêntica.
  • Se não for claro se uma alteração foi aplicada parcialmente, determine primeiro o estado real através de um GET, da visualização na CLI e de um teste funcional.

Comando CLI em falta ou rejeitado

  • Com ?, verifique se o comando existe no modo atual.
  • Com TAB, complete a sintaxe disponibilizada pelo dispositivo.
  • Verifique a função local, o modo de comando, o modelo e o firmware.
  • Não utilize um comando de nome semelhante pertencente a outro firmware.

Rollback e encerramento da sessão

Um teste simples com GET /api/ports não altera qualquer configuração e não requer rollback. No entanto, o início de sessão na API cria uma sessão; encerre-a de acordo com o ciclo de vida do token e elimine as variáveis locais.

Para uma alteração de configuração, defina o rollback com antecedência:

  1. Exporte o estado atual e os objetos afetados ou registe-os através de operações de leitura.
  2. Prepare a operação inversa exata com base na ajuda publicada da API e na respetiva validação para o firmware utilizado, ou com base na ajuda da CLI do dispositivo de destino.
  3. Defina critérios para interromper imediatamente, como a perda de gestão, de uplink, de acesso à VLAN ou de alimentação PoE.
  4. Se ocorrer um erro, não tente outras otimizações; restaure o valor anterior através da via que ainda funciona.
  5. Em seguida, volte a verificar o acesso de gestão, o estado das portas, os uplinks e os serviços afetados.
  6. Se perder a via normal, efetue o rollback através da via de gestão independente previamente verificada, eventualmente o acesso à consola dependente do modelo. Uma reposição de fábrica não é um rollback normal, pois elimina a configuração.

Num switch gerido pelo Sophos Fusion, o rollback local, por si só, não é suficiente. Verifique o estado-alvo autorizado no Sophos Fusion e regularize aí a alteração de emergência aprovada ou remova-a totalmente a nível local. Não mantenha configurações divergentes entre os canais local e central.

No final:

  • saia de uma apresentação de resultados da CLI com Q e utilize na linha de comandos o comando exit/logout indicado pelo dispositivo;
  • feche a sessão da API com o PATCH /api/system/logout documentado;
  • elimine as variáveis locais de palavra-passe e token com unset;
  • verifique se nenhum ficheiro temporário ou registo de depuração contém segredos;
  • documente no ticket o resultado, o firmware, a via administrativa utilizada, a verificação e qualquer eventual rollback.

Reforço da segurança

  • VLAN de gestão dedicada, com ACL limitadas aos hosts de administração e protocolos necessários.
  • Ative HTTPS e SSH apenas quando necessário; desative os serviços de gestão inseguros ou não utilizados.
  • Utilize certificados fidedignos e chaves de host SSH verificadas.
  • Utilize contas locais individuais: User apenas para consulta e Admin apenas para alterações aprovadas.
  • Substitua imediatamente a palavra-passe predefinida; evite palavras-passe comuns e altere-a após mudanças de pessoal ou de prestador de serviços.
  • Mantenha os segredos da API fora do código-fonte, de ficheiros .env, do histórico da shell, dos argumentos dos processos e dos resultados de CI.
  • Nunca execute clientes da API com depuração detalhada dos cabeçalhos enquanto estiver definido um cabeçalho Authorization.
  • Mantenha as sessões curtas, use um novo token por sessão e, em seguida, apague as variáveis.
  • Mantenha cópias de segurança da configuração protegidas e recuperáveis fora do switch.
  • Correlacione temporalmente os registos locais, os eventos centrais e os tickets de alteração; verifique a hora do switch.
  • Reveja regularmente as contas locais, as ACL de gestão, os certificados, as chaves SSH e os acessos de automatização.

Variabilidade do firmware e do modelo

A página estática da API REST apresenta o caminho de início de sessão /api/system/login, o teste de leitura /api/ports e o cabeçalho Authorization; a ajuda publicada da API também documenta o caminho de logout /api/system/logout. Estes recursos centrais fornecem exemplos e orientação, mas não um esquema universalmente aplicável a todos os dispositivos. O esquema Swagger/OpenAPI específico é gerado pelo switch de destino em execução e reflete o respetivo modelo e estado do firmware. A documentação publicada da CLI confirma as funções de ajuda acima referidas, mas também não garante que todos os comandos adicionais sejam idênticos em todos os firmwares.

Antes da produção, portanto, por modelo e firmware:

  • documente a versão exata do firmware e o modelo de hardware;
  • verifique o modo e a sintaxe da CLI com ? e TAB;
  • verifique no esquema Swagger/OpenAPI os métodos, os caminhos e os esquemas disponibilizados pelo dispositivo de destino com o firmware em execução;
  • teste primeiro o início de sessão e GET /api/ports numa sessão controlada;
  • valide qualquer automatização de escrita precisamente em relação a esse esquema-alvo e, adicionalmente, num ambiente não produtivo ou claramente limitado;
  • após uma atualização do firmware, volte a testar o início de sessão, a verificação do certificado, o esquema da resposta, o tratamento do token e todos os endpoints utilizados;
  • perante desvios, não adapte o script antes de compreender a nova semântica e o rollback.

Auditoria final

  • Modelo, firmware e IP de gestão corretos confirmados.
  • Sistema de registo — Sophos Fusion ou gestão local — documentado.
  • Conta local User ou Admin adequada à tarefa utilizada.
  • Certificado TLS ou chave do host SSH verificados; nenhuma opção insegura permanente utilizada.
  • Token utilizado apenas na respetiva sessão e nunca divulgado.
  • Estado HTTP, errCode, message e estado técnico verificado.
  • Para alterações, o estado atual, o rollback e o acesso independente estão disponíveis.
  • Resultado verificado através de GET, da visualização na CLI e de qualquer teste funcional necessário.
  • Sessão encerrada, variáveis eliminadas e registos verificados quanto à presença de segredos.
  • Exceção local reconciliada com o Sophos Fusion e encerrada no ticket.