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
-
User upload file hasil meeting ke S3 bucket.
-
S3 Event Notification memicu AWS Lambda.
-
Lambda mengambil URL CloudFront untuk file tersebut lalu POST payload ke n8n Webhook.
-
n8n format pesan dan kirim ke Telegram atau Slack (sesuai node yang dipakai).
-
Saat link di-klik, CloudFront memeriksa IP whitelist (CloudFront Function).
-
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) berisibucket
,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
-
Buat S3 bucket & upload file
🎯 Analogi: Ini seperti menyimpan rekaman seminar di gudang (S3).
Gudang ini aman, tidak ada pintu ke publik. Hanya petugas khusus yang bisa mengambil barang. -
Buat CloudFront Function (IP whitelist)
🎯 Analogi: Ini seperti menugaskan satpam di pintu masuk.
Satpam punya daftar tamu undangan (list IP) yang boleh masuk.
Kalau nama (IP) tidak ada di daftar → langsung bilang "Maaf, Anda tidak boleh masuk" (403 Forbidden). -
Buat Origin Access Control (OAC)
🎯 Analogi: Ini seperti memberikan ID khusus untuk satpam, supaya satpam ini diakui oleh petugas gudang (S3) dan diizinkan mengambil barang.
Jadi, tamu tidak bisa langsung masuk gudang — harus lewat satpam ini. -
Buat CloudFront Distribution & attach function
🎯 Analogi: Ini seperti membangun jalur resmi dari pintu satpam ke gudang.
Semua tamu yang lewat jalur ini akan diperiksa oleh satpam (function) sebelum masuk gudang.
Jalur ini punya alamat resmi (domain CloudFront), seperti alamat gedung seminar. -
Pasang bucket policy di S3 untuk hanya menerima dari CloudFront
🎯 Analogi: Ini seperti mengunci pintu gudang dan hanya membuka pintu jika satpam resmi datang dengan ID-nya.
Kalau ada orang coba masuk gudang langsung lewat pintu belakang, pasti ditolak. -
Test akses
🎯 Analogi: Kamu mencoba datang ke seminar:-
Kalau kamu ada di daftar tamu (IP di whitelist) → satpam mempersilakan masuk dan mengambil kursi (akses file sukses).
-
Kalau kamu bukan tamu undangan → satpam menolak masuk (403 Forbidden).
-
-
Mengubah daftar IP
🎯 Analogi: Kalau mau undang orang baru atau mencoret orang dari daftar, cukup ganti daftar tamu di meja satpam (edit function → publish lagi).
💡 Jadi singkatnya:
-
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
-
Create bucket
-
S3 Console → Create bucket.
-
Nama unik (mis.
meeting-records-<unik>
). -
Block all public access = ON.
-
Object Ownership = Bucket owner enforced (disarankan).
-
Create.
-
-
Upload file
-
Buka bucket → Upload → pilih file.
-
Biarkan object TIDAK public.
-
-
(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
-
Create distribution → Origin domain = bucket S3.
-
Default cache behavior:
-
Viewer protocol policy: Redirect HTTP to HTTPS.
-
Allowed methods: GET, HEAD.
-
Function associations: Viewer Request → pilih
ip-whitelist-function
(Published).
-
-
Create.
Jika tidak muncul saat create, tambahkan setelah jadi: Distributions → pilih → Behaviors → Edit → Function associations (Viewer Request).
5.3 Aktifkan OAC (Origin Access Control)
-
CloudFront → Origin access controls → Create (S3, SigV4).
-
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 headerX-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)
-
S3 → Bucket → tab Properties → Event notifications → Create event notification.
-
Event types: centang All object create events (atau minimal
PUT
+CompleteMultipartUpload
). -
(Opsional) Prefix:
meetings/
jika hanya folder itu. -
Destination: pilih Lambda function yang dibuat.
-
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
vs403
.
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.