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
-
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.