Mengumpulkan log SD-WAN Cisco vManage

Didukung di:

Dokumen ini menjelaskan cara menyerap log SD-WAN Cisco vManage ke Google Security Operations menggunakan Amazon S3.

Sebelum memulai

Pastikan Anda memenuhi prasyarat berikut:

  • Instance Google SecOps
  • Akses istimewa ke konsol pengelolaan Cisco vManage SD-WAN
  • Akses istimewa ke AWS (S3, IAM, Lambda, EventBridge)

Kumpulkan prasyarat Cisco vManage SD-WAN (kredensial dan URL dasar)

  1. Login ke Cisco vManage Management Console.
  2. Buka Administrasi > Setelan > Pengguna.
  3. Buat pengguna baru atau gunakan pengguna admin yang sudah ada dengan hak istimewa akses API.
  4. Salin dan simpan detail berikut di lokasi yang aman:
    • Username
    • Password
    • URL Dasar vManage (misalnya, https://your-vmanage-server:8443)

Mengonfigurasi bucket AWS S3 dan IAM untuk Google SecOps

  1. Buat bucket Amazon S3 dengan mengikuti panduan pengguna ini: Membuat bucket
  2. Simpan Name dan Region bucket untuk referensi di masa mendatang (misalnya, cisco-sdwan-logs-bucket).
  3. Buat pengguna dengan mengikuti panduan pengguna ini: Membuat pengguna IAM.
  4. Pilih Pengguna yang dibuat.
  5. Pilih tab Kredensial keamanan.
  6. Klik Create Access Key di bagian Access Keys.
  7. Pilih Layanan pihak ketiga sebagai Kasus penggunaan.
  8. Klik Berikutnya.
  9. Opsional: tambahkan tag deskripsi.
  10. Klik Create access key.
  11. Klik Download CSV file untuk menyimpan Access Key dan Secret Access Key untuk digunakan nanti.
  12. Klik Selesai.
  13. Pilih tab Izin.
  14. Klik Tambahkan izin di bagian Kebijakan izin.
  15. Pilih Tambahkan izin.
  16. Pilih Lampirkan kebijakan secara langsung
  17. Telusuri dan pilih kebijakan AmazonS3FullAccess.
  18. Klik Berikutnya.
  19. Klik Add permissions.

Mengonfigurasi kebijakan dan peran IAM untuk upload S3

  1. Di konsol AWS, buka IAM > Policies > Create policy > JSON tab.
  2. Masukkan kebijakan berikut:

    {
      "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"
        }
      ]
    }
    
    • Ganti cisco-sdwan-logs-bucket jika Anda memasukkan nama bucket yang berbeda.
  3. Klik Berikutnya > Buat kebijakan.

  4. Buka IAM > Roles.

  5. Klik Create role > AWS service > Lambda.

  6. Lampirkan kebijakan yang baru dibuat.

  7. Beri nama peran cisco-sdwan-lambda-role, lalu klik Buat peran.

Buat fungsi Lambda

  1. Di Konsol AWS, buka Lambda > Functions > Create function.
  2. Klik Buat dari awal.
  3. Berikan detail konfigurasi berikut:

    Setelan Nilai
    Nama cisco-sdwan-log-collector
    Runtime Python 3.13
    Arsitektur x86_64
    Peran eksekusi cisco-sdwan-lambda-role
  4. Setelah fungsi dibuat, buka tab Code, hapus stub, dan masukkan kode berikut (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. Buka Configuration > Environment variables.

  6. Klik Edit > Tambahkan variabel lingkungan baru.

  7. Masukkan variabel lingkungan berikut, ganti dengan nilai Anda:

    Kunci Nilai contoh
    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. Setelah fungsi dibuat, tetap buka halamannya (atau buka Lambda > Functions > cisco-sdwan-log-collector).

  9. Pilih tab Configuration

  10. Di panel General configuration, klik Edit.

  11. Ubah Waktu tunggu menjadi 5 menit (300 detik), lalu klik Simpan.

Membuat jadwal EventBridge

  1. Buka Amazon EventBridge > Scheduler > Create schedule.
  2. Berikan detail konfigurasi berikut:
    • Jadwal berulang: Tarif (1 hour)
    • Target: fungsi Lambda Anda cisco-sdwan-log-collector
    • Nama: cisco-sdwan-log-collector-1h
  3. Klik Buat jadwal.

Opsional: Buat pengguna & kunci IAM hanya baca untuk Google SecOps

  1. Di Konsol AWS, buka IAM > Pengguna > Tambahkan pengguna.
  2. Klik Add users.
  3. Berikan detail konfigurasi berikut:
    • Pengguna: secops-reader
    • Jenis akses: Kunci akses — Akses terprogram
  4. Klik Buat pengguna.
  5. Lampirkan kebijakan baca minimal (kustom): Pengguna > secops-reader > Izin > Tambahkan izin > Lampirkan kebijakan secara langsung > Buat kebijakan.
  6. Di editor JSON, masukkan kebijakan berikut:

    {
      "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. Beri nama kebijakan secops-reader-policy.

  8. Klik Create policy.

  9. Kembali ke pembuatan pengguna, telusuri dan pilih secops-reader-policy.

  10. Klik Berikutnya: Tanda.

  11. Klik Berikutnya: Tinjau.

  12. Klik Buat pengguna.

  13. Download CSV (nilai ini dimasukkan ke dalam feed).

Mengonfigurasi feed di Google SecOps untuk memproses log SD-WAN Cisco vManage

  1. Buka Setelan SIEM > Feed.
  2. Klik + Tambahkan Feed Baru.
  3. Di kolom Nama feed, masukkan nama untuk feed (misalnya, Cisco SD-WAN logs).
  4. Pilih Amazon S3 V2 sebagai Jenis sumber.
  5. Pilih Cisco vManage SD-WAN sebagai Jenis log.
  6. Klik Berikutnya.
  7. Tentukan nilai untuk parameter input berikut:
    • URI S3: s3://cisco-sdwan-logs-bucket/cisco-sdwan/
    • Opsi penghapusan sumber: Pilih opsi penghapusan sesuai preferensi Anda.
    • Usia File Maksimum: Menyertakan file yang diubah dalam jumlah hari terakhir. Default 180 Hari.
    • ID Kunci Akses: Kunci akses pengguna dengan akses ke bucket S3.
    • Kunci Akses Rahasia: Kunci rahasia pengguna dengan akses ke bucket S3.
    • Namespace aset: namespace aset.
    • Label penyerapan: label yang diterapkan ke peristiwa dari feed ini.
  8. Klik Berikutnya.
  9. Tinjau konfigurasi feed baru Anda di layar Selesaikan, lalu klik Kirim.

Perlu bantuan lain? Dapatkan jawaban dari anggota Komunitas dan profesional Google SecOps.