Coletar registros da SD-WAN do Cisco vManage

Compatível com:

Este documento explica como ingerir registros de SD-WAN do Cisco vManage no Google Security Operations usando o Amazon S3.

Antes de começar

Verifique se você tem os pré-requisitos a seguir:

  • Uma instância do Google SecOps
  • Acesso privilegiado ao console de gerenciamento do Cisco vManage SD-WAN
  • Acesso privilegiado à AWS (S3, IAM, Lambda, EventBridge)

Coletar os pré-requisitos da SD-WAN do Cisco vManage (credenciais e URL de base)

  1. Faça login no Cisco vManage Management Console.
  2. Acesse Administração > Configurações > Usuários.
  3. Crie um usuário ou use um administrador com privilégios de acesso à API.
  4. Copie e salve em um local seguro os seguintes detalhes:
    • Nome de usuário
    • Senha
    • URL base do vManage (por exemplo, https://your-vmanage-server:8443)

Configurar o bucket do AWS S3 e o IAM para o Google SecOps

  1. Crie um bucket do Amazon S3 seguindo este guia do usuário: Como criar um bucket
  2. Salve o Nome e a Região do bucket para referência futura (por exemplo, cisco-sdwan-logs-bucket).
  3. Crie um usuário seguindo este guia: Como criar um usuário do IAM.
  4. Selecione o usuário criado.
  5. Selecione a guia Credenciais de segurança.
  6. Clique em Criar chave de acesso na seção Chaves de acesso.
  7. Selecione Serviço de terceiros como o Caso de uso.
  8. Clique em Próxima.
  9. Opcional: adicione uma tag de descrição.
  10. Clique em Criar chave de acesso.
  11. Clique em Fazer o download do arquivo CSV para salvar a chave de acesso e a chave de acesso secreta para uso posterior.
  12. Clique em Concluído.
  13. Selecione a guia Permissões.
  14. Clique em Adicionar permissões na seção Políticas de permissões.
  15. Selecione Adicionar permissões.
  16. Selecione Anexar políticas diretamente.
  17. Pesquise e selecione a política AmazonS3FullAccess.
  18. Clique em Próxima.
  19. Clique em Adicionar permissões

Configurar a política e o papel do IAM para uploads do S3

  1. No console da AWS, acesse IAM > Políticas > Criar política > guia JSON.
  2. Insira a seguinte política:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "AllowPutObjects",
          "Effect": "Allow",
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::cisco-sdwan-logs-bucket/*"
        },
        {
          "Sid": "AllowGetStateObject",
          "Effect": "Allow",
          "Action": "s3:GetObject",
          "Resource": "arn:aws:s3:::cisco-sdwan-logs-bucket/cisco-sdwan/state.json"
        }
      ]
    }
    
    • Substitua cisco-sdwan-logs-bucket se você tiver inserido um nome de bucket diferente.
  3. Clique em Próxima > Criar política.

  4. Acesse IAM > Papéis.

  5. Clique em Criar função > Serviço da AWS > Lambda.

  6. Anexe a política recém-criada.

  7. Nomeie a função como cisco-sdwan-lambda-role e clique em Criar função.

Criar a função Lambda

  1. No console da AWS, acesse Lambda > Functions > Create function.
  2. Clique em Criar do zero.
  3. Informe os seguintes detalhes de configuração:

    Configuração Valor
    Nome cisco-sdwan-log-collector
    Ambiente de execução Python 3.13
    Arquitetura x86_64
    Função de execução cisco-sdwan-lambda-role
  4. Depois que a função for criada, abra a guia Código, exclua o stub e insira o seguinte código (cisco-sdwan-log-collector.py):

    import json
    import boto3
    import os
    import urllib3
    import urllib.parse
    from datetime import datetime, timezone
    from botocore.exceptions import ClientError
    
    # Disable SSL warnings for self-signed certificates
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    
    # Environment variables
    VMANAGE_HOST = os.environ['VMANAGE_HOST']
    VMANAGE_USERNAME = os.environ['VMANAGE_USERNAME']  
    VMANAGE_PASSWORD = os.environ['VMANAGE_PASSWORD']
    S3_BUCKET = os.environ['S3_BUCKET']
    S3_PREFIX = os.environ['S3_PREFIX']
    STATE_KEY = os.environ['STATE_KEY']
    
    s3_client = boto3.client('s3')
    http = urllib3.PoolManager(cert_reqs='CERT_NONE')
    
    class VManageAPI:
        def __init__(self, host, username, password):
            self.host = host.rstrip('/')
            self.username = username
            self.password = password
            self.cookies = None
            self.token = None
    
        def authenticate(self):
            """Authenticate with vManage and get session tokens"""
            try:
                # Login to get JSESSIONID
                login_url = f"{self.host}/j_security_check"
                login_data = urllib.parse.urlencode({
                    'j_username': self.username,
                    'j_password': self.password
                })
    
                response = http.request(
                    'POST',
                    login_url,
                    body=login_data,
                    headers={'Content-Type': 'application/x-www-form-urlencoded'},
                    timeout=30
                )
    
                # Check if login was successful (vManage returns HTML on failure)
                if b'<html>' in response.data or response.status != 200:
                    raise Exception("Authentication failed")
    
                # Extract cookies
                self.cookies = {}
                if 'Set-Cookie' in response.headers:
                    cookie_header = response.headers['Set-Cookie']
                    for cookie in cookie_header.split(';'):
                        if 'JSESSIONID=' in cookie:
                            self.cookies['JSESSIONID'] = cookie.split('JSESSIONID=')[1].split(';')[0]
                            break
    
                if not self.cookies.get('JSESSIONID'):
                    raise Exception("Failed to get JSESSIONID")
    
                # Get XSRF token
                token_url = f"{self.host}/dataservice/client/token"
                headers = {
                    'Content-Type': 'application/json',
                    'Cookie': f"JSESSIONID={self.cookies['JSESSIONID']}"
                }
    
                response = http.request('GET', token_url, headers=headers, timeout=30)
    
                if response.status == 200:
                    self.token = response.data.decode('utf-8')
                    return True
                else:
                    raise Exception(f"Failed to get XSRF token: {response.status}")
    
            except Exception as e:
                print(f"Authentication error: {e}")
                return False
    
        def get_headers(self):
            """Get headers for API requests"""
            return {
                'Content-Type': 'application/json',
                'Cookie': f"JSESSIONID={self.cookies['JSESSIONID']}",
                'X-XSRF-TOKEN': self.token
            }
    
        def get_audit_logs(self, last_timestamp=None):
            """Get audit logs from vManage"""
            try:
                url = f"{self.host}/dataservice/auditlog"
                headers = self.get_headers()
    
                # Build query for recent logs
                query = {
                    "query": {
                        "condition": "AND",
                        "rules": []
                    },
                    "size": 10000
                }
    
                # Add timestamp filter if provided
                if last_timestamp:
                    # Convert timestamp to epoch milliseconds for vManage API
                    if isinstance(last_timestamp, str):
                        try:
                            dt = datetime.fromisoformat(last_timestamp.replace('Z', '+00:00'))
                            epoch_ms = int(dt.timestamp() * 1000)
                        except:
                            epoch_ms = int(last_timestamp)
                    else:
                        epoch_ms = int(last_timestamp)
    
                    query["query"]["rules"].append({
                        "value": [str(epoch_ms)],
                        "field": "entry_time",
                        "type": "date",
                        "operator": "greater"
                    })
                else:
                    # Get last 1 hour of logs by default
                    query["query"]["rules"].append({
                        "value": ["1"],
                        "field": "entry_time", 
                        "type": "date",
                        "operator": "last_n_hours"
                    })
    
                response = http.request(
                    'POST',
                    url,
                    body=json.dumps(query),
                    headers=headers,
                    timeout=60
                )
    
                if response.status == 200:
                    return json.loads(response.data.decode('utf-8'))
                else:
                    print(f"Failed to get audit logs: {response.status}")
                    return None
    
            except Exception as e:
                print(f"Error getting audit logs: {e}")
                return None
    
        def get_alarms(self, last_timestamp=None):
            """Get alarms from vManage"""
            try:
                url = f"{self.host}/dataservice/alarms"
                headers = self.get_headers()
    
                # Build query for recent alarms
                query = {
                    "query": {
                        "condition": "AND",
                        "rules": []
                    },
                    "size": 10000
                }
    
                # Add timestamp filter if provided
                if last_timestamp:
                    # Convert timestamp to epoch milliseconds for vManage API
                    if isinstance(last_timestamp, str):
                        try:
                            dt = datetime.fromisoformat(last_timestamp.replace('Z', '+00:00'))
                            epoch_ms = int(dt.timestamp() * 1000)
                        except:
                            epoch_ms = int(last_timestamp)
                    else:
                        epoch_ms = int(last_timestamp)
    
                    query["query"]["rules"].append({
                        "value": [str(epoch_ms)],
                        "field": "entry_time",
                        "type": "date",
                        "operator": "greater"
                    })
                else:
                    # Get last 1 hour of alarms by default
                    query["query"]["rules"].append({
                        "value": ["1"],
                        "field": "entry_time",
                        "type": "date", 
                        "operator": "last_n_hours"
                    })
    
                response = http.request(
                    'POST',
                    url,
                    body=json.dumps(query),
                    headers=headers,
                    timeout=60
                )
    
                if response.status == 200:
                    return json.loads(response.data.decode('utf-8'))
                else:
                    print(f"Failed to get alarms: {response.status}")
                    return None
    
            except Exception as e:
                print(f"Error getting alarms: {e}")
                return None
    
        def get_events(self, last_timestamp=None):
            """Get events from vManage"""
            try:
                url = f"{self.host}/dataservice/events"
                headers = self.get_headers()
    
                # Build query for recent events
                query = {
                    "query": {
                        "condition": "AND",
                        "rules": []
                    },
                    "size": 10000
                }
    
                # Add timestamp filter if provided  
                if last_timestamp:
                    # Convert timestamp to epoch milliseconds for vManage API
                    if isinstance(last_timestamp, str):
                        try:
                            dt = datetime.fromisoformat(last_timestamp.replace('Z', '+00:00'))
                            epoch_ms = int(dt.timestamp() * 1000)
                        except:
                            epoch_ms = int(last_timestamp)
                    else:
                        epoch_ms = int(last_timestamp)
    
                    query["query"]["rules"].append({
                        "value": [str(epoch_ms)],
                        "field": "entry_time",
                        "type": "date",
                        "operator": "greater"
                    })
                else:
                    # Get last 1 hour of events by default
                    query["query"]["rules"].append({
                        "value": ["1"],
                        "field": "entry_time",
                        "type": "date",
                        "operator": "last_n_hours"
                    })
    
                response = http.request(
                    'POST',
                    url,
                    body=json.dumps(query),
                    headers=headers,
                    timeout=60
                )
    
                if response.status == 200:
                    return json.loads(response.data.decode('utf-8'))
                else:
                    print(f"Failed to get events: {response.status}")
                    return None
    
            except Exception as e:
                print(f"Error getting events: {e}")
                return None
    
    def get_last_run_time():
        """Get the last successful run timestamp from S3"""
        try:
            response = s3_client.get_object(Bucket=S3_BUCKET, Key=STATE_KEY)
            state_data = json.loads(response['Body'].read())
            return state_data.get('last_run_time')
        except ClientError as e:
            if e.response['Error']['Code'] == 'NoSuchKey':
                print("No previous state found, collecting last hour of logs")
                return None
            else:
                print(f"Error reading state: {e}")
                return None
        except Exception as e:
            print(f"Error reading state: {e}")
            return None
    
    def update_last_run_time(timestamp):
        """Update the last successful run timestamp in S3"""
        try:
            state_data = {
                'last_run_time': timestamp,
                'updated_at': datetime.now(timezone.utc).isoformat()
            }
    
            s3_client.put_object(
                Bucket=S3_BUCKET,
                Key=STATE_KEY,
                Body=json.dumps(state_data),
                ContentType='application/json'
            )
    
            print(f"Updated state with timestamp: {timestamp}")
    
        except Exception as e:
            print(f"Error updating state: {e}")
    
    def upload_logs_to_s3(logs_data, log_type, timestamp):
        """Upload logs to S3 bucket"""
        try:
            if not logs_data or 'data' not in logs_data or not logs_data['data']:
                print(f"No {log_type} data to upload")
                return
    
            # Create filename with timestamp
            dt = datetime.now(timezone.utc)
            filename = f"{S3_PREFIX}{log_type}/{dt.strftime('%Y/%m/%d')}/{log_type}_{dt.strftime('%Y%m%d_%H%M%S')}.json"
    
            # Upload to S3
            s3_client.put_object(
                Bucket=S3_BUCKET,
                Key=filename,
                Body=json.dumps(logs_data),
                ContentType='application/json'
            )
    
            print(f"Uploaded {len(logs_data['data'])} {log_type} records to s3://{S3_BUCKET}/{filename}")
    
        except Exception as e:
            print(f"Error uploading {log_type} to S3: {e}")
    
    def lambda_handler(event, context):
        """Main Lambda handler function"""
        print(f"Starting Cisco vManage log collection at {datetime.now(timezone.utc)}")
    
        try:
            # Get last run time
            last_run_time = get_last_run_time()
    
            # Initialize vManage API client
            vmanage = VManageAPI(VMANAGE_HOST, VMANAGE_USERNAME, VMANAGE_PASSWORD)
    
            # Authenticate
            if not vmanage.authenticate():
                return {
                    'statusCode': 500,
                    'body': json.dumps('Failed to authenticate with vManage')
                }
    
            print("Successfully authenticated with vManage")
    
            # Current timestamp for state tracking (store as epoch milliseconds)
            current_time = int(datetime.now(timezone.utc).timestamp() * 1000)
    
            # Collect different types of logs
            log_types = [
                ('audit_logs', vmanage.get_audit_logs),
                ('alarms', vmanage.get_alarms), 
                ('events', vmanage.get_events)
            ]
    
            total_records = 0
    
            for log_type, get_function in log_types:
                try:
                    print(f"Collecting {log_type}...")
                    logs_data = get_function(last_run_time)
    
                    if logs_data:
                        upload_logs_to_s3(logs_data, log_type, current_time)
                        if 'data' in logs_data:
                            total_records += len(logs_data['data'])
    
                except Exception as e:
                    print(f"Error processing {log_type}: {e}")
                    continue
    
            # Update state with current timestamp
            update_last_run_time(current_time)
    
            print(f"Collection completed. Total records processed: {total_records}")
    
            return {
                'statusCode': 200,
                'body': json.dumps({
                    'message': 'Log collection completed successfully',
                    'total_records': total_records,
                    'timestamp': datetime.now(timezone.utc).isoformat()
                })
            }
    
        except Exception as e:
            print(f"Lambda execution error: {e}")
            return {
                'statusCode': 500,
                'body': json.dumps(f'Error: {str(e)}')
            }
    
  5. Acesse Configuração > Variáveis de ambiente.

  6. Clique em Editar > Adicionar nova variável de ambiente.

  7. Insira as seguintes variáveis de ambiente, substituindo pelos seus valores:

    Chave Valor de exemplo
    S3_BUCKET cisco-sdwan-logs-bucket
    S3_PREFIX cisco-sdwan/
    STATE_KEY cisco-sdwan/state.json
    VMANAGE_HOST https://your-vmanage-server:8443
    VMANAGE_USERNAME your-vmanage-username
    VMANAGE_PASSWORD your-vmanage-password
  8. Depois que a função for criada, permaneça na página dela ou abra Lambda > Functions > cisco-sdwan-log-collector.

  9. Selecione a guia Configuração.

  10. No painel Configuração geral, clique em Editar.

  11. Mude Tempo limite para 5 minutos (300 segundos) e clique em Salvar.

Criar uma programação do EventBridge

  1. Acesse Amazon EventBridge > Scheduler > Criar programação.
  2. Informe os seguintes detalhes de configuração:
    • Programação recorrente: Taxa (1 hour)
    • Destino: sua função Lambda cisco-sdwan-log-collector
    • Nome: cisco-sdwan-log-collector-1h
  3. Clique em Criar programação.

Opcional: criar um usuário e chaves do IAM somente leitura para o Google SecOps

  1. No console da AWS, acesse IAM > Usuários > Adicionar usuários.
  2. Clique em Add users.
  3. Informe os seguintes detalhes de configuração:
    • Usuário: secops-reader
    • Tipo de acesso: Chave de acesso — Acesso programático
  4. Clique em Criar usuário.
  5. Anexe a política de leitura mínima (personalizada): Usuários > secops-reader > Permissões > Adicionar permissões > Anexar políticas diretamente > Criar política.
  6. No editor JSON, insira a seguinte política:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket"],
          "Resource": "arn:aws:s3:::cisco-sdwan-logs-bucket",
          "Condition": {
            "StringLike": {
              "s3:prefix": ["cisco-sdwan/*"]
            }
          }
        },
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": "arn:aws:s3:::cisco-sdwan-logs-bucket/cisco-sdwan/*"
        }
      ]
    }
    
  7. Nomeie a política como secops-reader-policy.

  8. Clique em Criar política.

  9. Volte para a criação de usuários, pesquise e selecione secops-reader-policy.

  10. Clique em Avançar: tags.

  11. Clique em PRÓXIMO: REVISAR.

  12. Clique em Criar usuário.

  13. Faça o download do CSV (esses valores são inseridos no feed).

Configurar um feed no Google SecOps para ingerir registros de SD-WAN do Cisco vManage

  1. Acesse Configurações do SIEM > Feeds.
  2. Clique em + Adicionar novo feed.
  3. No campo Nome do feed, insira um nome para o feed (por exemplo, Cisco SD-WAN logs).
  4. Selecione Amazon S3 V2 como o Tipo de origem.
  5. Selecione Cisco vManage SD-WAN como o Tipo de registro.
  6. Clique em Próxima.
  7. Especifique valores para os seguintes parâmetros de entrada:
    • URI do S3: s3://cisco-sdwan-logs-bucket/cisco-sdwan/
    • Opções de exclusão de fontes: selecione a opção de exclusão de acordo com sua preferência.
    • Idade máxima do arquivo: inclui arquivos modificados no último número de dias. Padrão de 180 dias.
    • ID da chave de acesso: chave de acesso do usuário com acesso ao bucket do S3.
    • Chave de acesso secreta: chave secreta do usuário com acesso ao bucket do S3.
    • Namespace do recurso: o namespace do recurso.
    • Rótulos de ingestão: o rótulo aplicado aos eventos deste feed.
  8. Clique em Próxima.
  9. Revise a nova configuração do feed na tela Finalizar e clique em Enviar.

Precisa de mais ajuda? Receba respostas de membros da comunidade e profissionais do Google SecOps.