Skip to main content

New Page

Siap! Berikut dokumentasi lengkapnya—runut, teknikal, plus analogi—mulai S3 → CloudFront (dengan IP whitelist + OAC) → Lambda → n8n → kirim ke Telegram/Slack.


1) Flow alur proses (ringkas)

[1] Aplikasi Meeting
    (Fireflies, Zoom, dll)
       |
       v
[2] Amazon S3
    - File hasil meeting (private bucket)
    - Origin Access Control (OAC) → hanya CloudFront yang bisa akses S3
       |
       v
[3] Amazon CloudFront
    - Distribusi CDN mengarah ke S3
    - Viewer Access Policy: Hanya IP publik VPN WireGuard yang di-allow
    - Request dari luar IP VPN → 403 Forbidden
       |
       v
[4] Lambda
    - Melakukan check s3 untuk memastikan apakah ada file baru?
    - Lambda membaca URL CloudFront dan memeriksa file baru tersebut lalu POST payload ke n8n Webhook.
    - Kirim link ke webhook n8n
       |
       v
[4] N8N
    - Trigger dari LambdaEvent (file baru)
    - Generate link CloudFront (misalnya https://cdn.company.com/file.mp3)
    - Kirim link ke telegram
       |
       v
[5] Slack
    - User klik link → hanya bisa diakses jika terkoneksi ke VPN WireGuard

Berikut real flow proses yang dilakukan setelah semuanya di setup dan terintegrasi

  1. User upload file hasil meeting ke S3 bucket.

  2. S3 Event Notification memicu AWS Lambda.

  3. Lambda mengambil URL CloudFront untuk file tersebut lalu POST payload ke n8n Webhook.

  4. n8n format pesan dan kirim ke Telegram atau Slack (sesuai node yang dipakai).

  5. Saat link di-klik, CloudFront memeriksa IP whitelist (CloudFront Function).

  6. Jika lolos, CloudFront mengambil file dari S3 melalui OAC (origin access control) → file dikirim ke user.


2) Penjelasan teknikal tiap alur

  • S3 → Lambda: S3 mengirim event ObjectCreated (PUT/MultipartComplete) berisi bucket, key, size, dll.

  • Lambda → n8n: Lambda membuat URL: https://<CLOUDFRONT_DOMAIN>/<key> (opsional tambah prefix), lalu POST JSON ke webhook n8n.

  • n8n → Telegram/Slack: n8n menerima JSON (bucket, filename, url, dll) → map ke node Telegram/Slack untuk kirim pesan.

  • CloudFront Function (Viewer Request): skrip ringan di edge memeriksa client IP terhadap daftar CIDR (IPv4). Jika tidak cocok → 403 Forbidden.

  • OAC + Bucket Policy: S3 hanya menerima request bertanda tangan dari CloudFront distribution tertentu, sehingga direct S3 URL tidak bisa dipakai untuk bypass.


3) Penjelasan dengan analogi

  • S3 = gudang dokumen.

  • CloudFront = gerbang utama ke gudang.

  • CloudFront Function = satpam di gerbang yang memeriksa daftar tamu (IP whitelist).

  • OAC = kunci gudang yang hanya cocok dengan satpam di gerbang; pintu belakang tidak bisa dipakai.

  • Lambda = operator yang, setiap ada dokumen baru disimpan, langsung kasih tahu kurir.

  • n8n = kurir cerdas yang membuat undangan (pesan) dan mengirimkannya ke grup Telegram/Slack.

  • User = tamu yang menerima undangan (URL) dan hanya bisa masuk kalau namanya ada di daftar (IP-nya di-whitelist).


4) Langkah lengkap pembuatan S3

  1. Create bucket

    • S3 Console → Create bucket.

    • Nama unik (mis. meeting-records-<unik>).

    • Block all public access = ON.

    • Object Ownership = Bucket owner enforced (disarankan).

    • Create.

  2. Upload file

    • Buka bucket → Upload → pilih file.

    • Biarkan object TIDAK public.

  3. (Opsional) Buat folder (prefix) meetings/ untuk merapikan event filter.


5) CloudFront lengkap (Function + OAC + policy + akses IP spesifik)

5.1 Buat CloudFront Function (IP whitelist)

  • CloudFront Console → Functions → Create function (mis. ip-whitelist-function).

  • Paste kode di bawah → Save → Publish:

function handler(event) {
    var request = event.request;

    // Ambil IP: test UI pakai event.viewer.ip, produksi pakai request.clientIp
    var clientIp = (event.viewer && event.viewer.ip)
        ? event.viewer.ip
        : (request.clientIp || '');

    // Ganti daftar ini sesuai kebutuhan (IPv4; bisa campur /32 dan /24)
    var allowed = [
        "203.0.113.25/32",
        "198.51.100.0/24"
    ];

    function ipToInt(ip) {
        var p = ip.split('.');
        if (p.length !== 4) return null;
        var n = 0;
        for (var i = 0; i < 4; i++) {
            var part = parseInt(p[i], 10);
            if (isNaN(part) || part < 0 || part > 255) return null;
            n = (n << 8) + part;
        }
        return n >>> 0;
    }
    function makeMask(prefix) {
        if (prefix === 0) return 0;
        var m = 0;
        for (var i = 0; i < prefix; i++) m = (m << 1) | 1;
        m = (m << (32 - prefix)) >>> 0;
        return m;
    }
    function cidrMatch(ip, cidr) {
        if (cidr.indexOf('/') === -1) return ip === cidr;
        var parts = cidr.split('/');
        var base = parts[0];
        var prefix = parseInt(parts[1], 10);
        var ipInt = ipToInt(ip);
        var baseInt = ipToInt(base);
        if (ipInt === null || baseInt === null) return false;
        var mask = makeMask(prefix);
        return ((ipInt & mask) >>> 0) === ((baseInt & mask) >>> 0);
    }

    for (var i = 0; i < allowed.length; i++) {
        if (cidrMatch(clientIp, allowed[i])) return request; // allow
    }
    return {
        statusCode: 403,
        statusDescription: 'Forbidden',
        headers: {
            'content-type': { value: 'text/plain' },
            'cache-control': { value: 'no-store' }
        },
        body: 'Access denied'
    };
}

Catatan: ini IPv4 only. Jika ada user IPv6, tambahkan dukungan IPv6 atau nonaktifkan IPv6 di setting distribusi.

5.2 Buat Distribution & attach Function

  1. Create distribution → Origin domain = bucket S3.

  2. Default cache behavior:

    • Viewer protocol policy: Redirect HTTP to HTTPS.

    • Allowed methods: GET, HEAD.

    • Function associations: Viewer Request → pilih ip-whitelist-function (Published).

  3. Create.

Jika tidak muncul saat create, tambahkan setelah jadi: Distributions → pilih → Behaviors → Edit → Function associations (Viewer Request).

5.3 Aktifkan OAC (Origin Access Control)

  1. CloudFront → Origin access controls → Create (S3, SigV4).

  2. Distributions → pilih distribusi → Origins → pilih origin S3 → Edit.

    • Origin access: Use an existing OAC → pilih OAC tadi → Save.

5.4 Bucket Policy untuk OAC (kunci akses langsung S3)

  • S3 → bucket → Permissions → Bucket policy → isi (ganti BUCKET_NAME, ACCOUNT_ID, DISTRIBUTION_ID):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipalReadOnly",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::BUCKET_NAME/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::ACCOUNT_ID:distribution/DISTRIBUTION_ID"
        }
      }
    }
  ]
}

Pastikan Block Public Access ON dan object ACL public-read tidak digunakan.

5.5 Cara test cepat

  • IP di-whitelist: curl -I https://<CF_DOMAIN>/<path> → 200.

  • IP lain: 403 dengan header X-Cache: FunctionGeneratedResponse.

  • Test Function UI: gunakan JSON yang menyertakan viewer.ip.


6) Lambda → trigger n8n → kirim ke Telegram/Slack (lengkap & detail)

6.1 Buat Lambda

  • Runtime: Python 3.12

  • Handler: lambda_function.lambda_handler

Environment variables (Configuration → Environment variables):

  • N8N_WEBHOOK_URL = URL webhook n8n (production)
    contoh: https://n8n.example.com/webhook/s3-new-file

  • CLOUDFRONT_DOMAIN = domain CloudFront, mis. d3xxxx.cloudfront.net

  • (opsional) CF_URL_PREFIX = prefix tambahan di URL, tanpa slash di awal/akhir (mis. meetings)

IAM Role (Execution role) → policy minimal:

{
  "Version": "2012-10-17",
  "Statement": [
    { "Effect": "Allow", "Action": [
        "logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"
      ], "Resource": "*" },
    { "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::BUCKET_NAME/*" }
  ]
}

Kode Lambda (tanpa dependency eksternal):

# file: lambda_function.py
import os
import json
import urllib.parse
import urllib.request

N8N_WEBHOOK_URL = os.environ["N8N_WEBHOOK_URL"]
CLOUDFRONT_DOMAIN = os.environ["CLOUDFRONT_DOMAIN"]
CF_URL_PREFIX = os.environ.get("CF_URL_PREFIX", "").strip("/")

def _post_json(url: str, payload: dict, timeout=10):
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        return resp.getcode(), resp.read().decode("utf-8")

def lambda_handler(event, context):
    for rec in event.get("Records", []):
        s3 = rec.get("s3", {})
        bucket = s3.get("bucket", {}).get("name")
        obj = s3.get("object", {})
        key = urllib.parse.unquote_plus(obj.get("key", ""))

        # Bangun URL CloudFront (URL-encode key)
        url_key = urllib.parse.quote(key)
        if CF_URL_PREFIX:
            file_url = f"https://{CLOUDFRONT_DOMAIN}/{CF_URL_PREFIX}/{url_key}"
        else:
            file_url = f"https://{CLOUDFRONT_DOMAIN}/{url_key}"

        payload = {
            "bucket": bucket,
            "filename": key,
            "url": file_url,
            "size": obj.get("size"),
            "etag": obj.get("eTag") or obj.get("etag")
        }

        try:
            status, body = _post_json(N8N_WEBHOOK_URL, payload)
            print({"sent_to": N8N_WEBHOOK_URL, "status": status, "resp": body})
        except Exception as e:
            print({"error": str(e), "payload": payload})

    return {"ok": True}

Keamanan webhook n8n (opsional):

  • Gunakan path yang unik/sulit ditebak.

  • Aktifkan Basic Auth atau header secret di n8n; kirimkan header itu dari Lambda (tambahkan header di _post_json).

6.2 n8n → kirim ke Telegram atau Slack

  • Telegram Node: gunakan kredensial Bot; isi pesan, contoh:

    ✅ File baru:
    
    Bucket: {{$json["bucket"]}}
    File: {{$json["filename"]}}
    Link: {{$json["url"]}}
    
  • Slack (dua opsi):

    • A. Slack node (OAuth2): Buat Slack App → scopes minimal chat:write → install → set credential di n8n → pilih channel → map pesan seperti di atas.

    • B. Incoming Webhook: Buat Incoming Webhook URL di Slack → di n8n pakai HTTP Request node (POST JSON ke webhook) dengan payload:

      { "text": "✅ File baru:\n\nBucket: {{$json[\"bucket\"]}}\nFile: {{$json[\"filename\"]}}\nLink: {{$json[\"url\"]}}" }
      

Kamu sudah berhasil kirim ke Telegram—untuk Slack tinggal ganti node pengiriman di workflow n8n, payload dari Lambda tetap sama.


7) Integrasi S3 → Lambda (agar tiap file baru memicu pengiriman)

  1. S3 → Bucket → tab Properties → Event notifications → Create event notification.

  2. Event types: centang All object create events (atau minimal PUT + CompleteMultipartUpload).

  3. (Opsional) Prefix: meetings/ jika hanya folder itu.

  4. Destination: pilih Lambda function yang dibuat.

  5. Save. (S3 akan menambahkan izin invoke ke Lambda otomatis.)

Uji coba:

  • Upload file ke S3 → lihat CloudWatch Logs Lambda → harus ada log status: 200 dari n8n.

  • Cek Telegram/Slack: pesan masuk dengan link CloudFront.

  • Coba klik dari IP yang di-whitelist vs bukan → verifikasi 200 vs 403.


Ringkasan & tips

  • Keamanan akses file: kombinasi CloudFront Function (IP whitelist) + OAC menutup akses langsung ke S3 dan memaksa semua akses via CloudFront.

  • IPv6: Skrip contoh hanya IPv4. Jika audience kamu berpotensi IPv6, pertimbangkan dukungan IPv6 atau matikan IPv6 di distribusi.

  • Perubahan daftar IP: edit function → Publish ulang → berlaku di edge.

  • Format pesan n8n: gunakan ekspresi {{$json["field"]}} (bukan ${...}).

  • Reliability: jika butuh retry kuat saat n8n down, tambahkan DLQ (SQS) untuk Lambda atau routing lewat EventBridge.

Kalau kamu mau, aku bisa sediakan template CloudFormation/Terraform untuk meng-automasi S3 + CloudFront (OAC + Function) + Lambda + permissionnya, biar tinggal sekali deploy.