AWS Transfer Family ile SFTP Sunucusu Kurulumu ve Yönetimi

Dosya transferi konusu, sysadmin hayatının en can sıkıcı ama bir o kadar da kritik parçalarından biri. Müşteri “bize FTP kurun” dediğinde içinizden “2024’te hala mı?” diye geçiriyorsunuz ama iş gereklilikleri bazen bu kadar net. İşte tam bu noktada AWS Transfer Family devreye giriyor ve hem güvenli hem de yönetilebilir bir SFTP çözümü sunuyor. Bu yazıda sıfırdan production-ready bir SFTP sunucusu kuracağız, kullanıcı yönetimini yapılandıracağız ve gerçek dünya senaryolarını ele alacağız.

AWS Transfer Family Nedir ve Neden Kullanmalısınız?

AWS Transfer Family, S3 veya EFS üzerinde SFTP, FTPS ve FTP protokollerini managed servis olarak sunan bir AWS hizmetidir. Kendi SFTP sunucunuzu EC2 üzerinde kurup yönetmek yerine, AWS’in altyapısını kullanarak bu işi AWS’e devrediyorsunuz.

Peki neden kendi OpenSSH sunucunuzu kurmayıp bunu kullanasınız?

  • Sıfır sunucu yönetimi: Patch, güncelleme, yedekleme derdi yok
  • S3 entegrasyonu: Dosyalar direkt S3’e yazılıyor, depolama sınırı pratik olarak yok
  • IAM entegrasyonu: AWS’in kimlik yönetimi altyapısını kullanıyorsunuz
  • CloudWatch entegrasyonu: Log ve metrikler otomatik geliyor
  • Yüksek erişilebilirlik: AWS multi-AZ altyapısı üzerinde çalışıyor
  • VPC desteği: İnternete açmak zorunda değilsiniz, internal kullanım da mümkün

Dezavantajları da var tabii. Fiyatlandırma biraz tuhaf: endpoint başına saatlik ücret alıyorsunuz, aktarılan veri için ayrıca ödüyorsunuz. Düşük hacimli kullanım için EC2 üzerinde OpenSSH daha ucuza gelebilir. Ama enterprise senaryolarda ve özellikle S3 ile çalışıyorsanız, yönetim maliyeti hesaba katıldığında Transfer Family genellikle kazanıyor.

Ön Hazırlık: IAM ve S3 Yapılandırması

Önce S3 bucket’ımızı oluşturalım. Bu bucket SFTP kullanıcılarının dosyalarını depolayacak.

# S3 bucket oluştur
aws s3api create-bucket 
  --bucket my-sftp-storage-prod 
  --region eu-west-1 
  --create-bucket-configuration LocationConstraint=eu-west-1

# Bucket versioning aktif et (iyi pratik)
aws s3api put-bucket-versioning 
  --bucket my-sftp-storage-prod 
  --versioning-configuration Status=Enabled

# Public access kapat
aws s3api put-public-access-block 
  --bucket my-sftp-storage-prod 
  --public-access-block-configuration 
    BlockPublicAcls=true,IgnorePublicAcls=true,
    BlockPublicPolicy=true,RestrictPublicBuckets=true

Şimdi Transfer Family’nin S3’e yazabilmesi için bir IAM rolü oluşturmamız gerekiyor. Bu rol iki parçadan oluşuyor: trust policy (Transfer Family’nin bu rolü üstlenebileceğini söylüyor) ve permission policy (S3 izinleri).

# Trust policy dosyasını oluştur
cat > /tmp/transfer-trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "transfer.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

# IAM rolü oluştur
aws iam create-role 
  --role-name SFTPUserRole 
  --assume-role-policy-document file:///tmp/transfer-trust-policy.json

# S3 izin policy'sini oluştur
cat > /tmp/sftp-s3-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowListBucket",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetBucketLocation"
      ],
      "Resource": "arn:aws:s3:::my-sftp-storage-prod"
    },
    {
      "Sid": "AllowObjectOperations",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:GetObjectVersion"
      ],
      "Resource": "arn:aws:s3:::my-sftp-storage-prod/*"
    }
  ]
}
EOF

# Policy oluştur ve role bağla
aws iam put-role-policy 
  --role-name SFTPUserRole 
  --policy-name SFTPUserS3Access 
  --policy-document file:///tmp/sftp-s3-policy.json

SFTP Sunucusunu Oluşturma

Artık sunucuyu oluşturabiliriz. İnternetten erişilebilir (PUBLIC endpoint) veya sadece VPC içinden erişilebilir yapılandırabilirsiniz. Bu örnekte önce public endpoint ile başlayacağız.

# SFTP sunucusu oluştur
aws transfer create-server 
  --endpoint-type PUBLIC 
  --protocols SFTP 
  --identity-provider-type SERVICE_MANAGED 
  --logging-role arn:aws:iam::123456789012:role/SFTPLoggingRole 
  --tags Key=Environment,Value=Production Key=Team,Value=DataOps

# Sunucu bilgilerini görüntüle
aws transfer describe-server 
  --server-id s-1234567890abcdef0

# Server ID ve endpoint'i not al
# Endpoint genellikle: s-xxxx.server.transfer.eu-west-1.amazonaws.com formatında

Burada --identity-provider-type SERVICE_MANAGED seçeneği kullanıcıları doğrudan Transfer Family içinde yönettiğimiz anlamına geliyor. Alternatif olarak API_GATEWAY kullanarak kendi kimlik doğrulama sisteminizi (LDAP, Active Directory gibi) entegre edebilirsiniz. Bu konuya ileride döneceğiz.

Kullanıcı Oluşturma ve SSH Key Yönetimi

Kullanıcı oluşturmadan önce SSH key çiftini hazırlamamız gerekiyor. Gerçek hayatta kullanıcıya kendi key’ini oluşturmasını söylüyorsunuz ve sadece public key’i alıyorsunuz.

# Kullanıcı için SSH key çifti oluştur (test amaçlı)
ssh-keygen -t ed25519 -C "[email protected]" -f /tmp/sftp-user-key

# Public key içeriğini görüntüle
cat /tmp/sftp-user-key.pub

# Transfer Family kullanıcısı oluştur
aws transfer create-user 
  --server-id s-1234567890abcdef0 
  --user-name data-team-alice 
  --home-directory /my-sftp-storage-prod/alice 
  --home-directory-type PATH 
  --role arn:aws:iam::123456789012:role/SFTPUserRole 
  --ssh-public-key-body "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [email protected]"

Logical Directory Mapping ile Gelişmiş Erişim Kontrolü

Kullanıcının gördüğü dizin yapısını S3’teki gerçek yapıdan bağımsız tasarlayabilirsiniz. Bu özellik özellikle müşterilere SFTP erişimi açarken çok işe yarıyor. Kullanıcı /uploads dizinini görür ama aslında S3’te my-sftp-storage-prod/customers/acme-corp/uploads/ konumuna yazıyordur.

# Logical directory mapping ile kullanıcı oluştur
cat > /tmp/directory-mappings.json << 'EOF'
[
  {
    "Entry": "/",
    "Target": "/my-sftp-storage-prod/customers/acme-corp"
  },
  {
    "Entry": "/shared",
    "Target": "/my-sftp-storage-prod/shared-data"
  }
]
EOF

aws transfer create-user 
  --server-id s-1234567890abcdef0 
  --user-name acme-corp-sftp 
  --home-directory-type LOGICAL 
  --home-directory-mappings file:///tmp/directory-mappings.json 
  --role arn:aws:iam::123456789012:role/SFTPUserRole 
  --ssh-public-key-body "$(cat /tmp/sftp-user-key.pub)"

Bu yapıyla ACME Corp kullanıcısı SFTP’ye bağlandığında kendi S3 prefix’i dışına çıkamıyor. /shared dizini üzerinden ortak dosyalara da erişebiliniyor. Çok kiracılı (multi-tenant) senaryolar için bu özellik gerçekten güçlü.

VPC Endpoint ile Private SFTP Sunucusu

Production ortamlarında çoğunlukla SFTP sunucusunun internete açık olmasını istemiyorsunuz. Sadece belirli IP’lerden veya kendi network’ünüzden erişilebilir olmasını istiyorsunuz. Bunun için VPC endpoint kullanmanız gerekiyor.

# Önce VPC bilgilerini öğren
VPC_ID=$(aws ec2 describe-vpcs 
  --filters Name=isDefault,Values=false 
  --query 'Vpcs[0].VpcId' 
  --output text)

SUBNET_IDS=$(aws ec2 describe-subnets 
  --filters Name=vpc-id,Values=$VPC_ID Name=tag:Type,Values=Private 
  --query 'Subnets[*].SubnetId' 
  --output text | tr 't' ',')

# Security group oluştur
SG_ID=$(aws ec2 create-security-group 
  --group-name sftp-server-sg 
  --description "SFTP Server Security Group" 
  --vpc-id $VPC_ID 
  --query 'GroupId' 
  --output text)

# Sadece kendi network'ünüzden SFTP erişimine izin ver
aws ec2 authorize-security-group-ingress 
  --group-id $SG_ID 
  --protocol tcp 
  --port 22 
  --cidr 10.0.0.0/8

# Elastic IP ayır (sabit IP için)
EIP_ALLOC=$(aws ec2 allocate-address 
  --domain vpc 
  --query 'AllocationId' 
  --output text)

# VPC endpoint ile sunucu oluştur
aws transfer create-server 
  --endpoint-type VPC 
  --endpoint-details 
    VpcId=$VPC_ID,SubnetIds=$SUBNET_IDS,SecurityGroupIds=$SG_ID,AddressAllocationIds=$EIP_ALLOC 
  --protocols SFTP 
  --identity-provider-type SERVICE_MANAGED 
  --tags Key=Environment,Value=Production

VPC endpoint kullandığınızda Elastic IP alarak sabit bir IP adresine sahip olabilirsiniz. Bu özellikle müşteri firewall’larına “şu IP’ye izin verin” diyebilmek için kritik. Public endpoint’te IP adresi değişebilir.

Özel Domain ile Route 53 Yapılandırması

Müşterilere s-1234567890abcdef0.server.transfer.eu-west-1.amazonaws.com adresi vermek pek profesyonel durmuyor. Kendi domain’inizi kullanmak için Route 53’te bir CNAME kaydı oluşturmanız yeterli.

# Route 53'te CNAME kaydı ekle
cat > /tmp/route53-change.json << 'EOF'
{
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "sftp.company.com",
        "Type": "CNAME",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "s-1234567890abcdef0.server.transfer.eu-west-1.amazonaws.com"
          }
        ]
      }
    }
  ]
}
EOF

# Hosted zone ID'nizi bulun
HOSTED_ZONE_ID=$(aws route53 list-hosted-zones 
  --query "HostedZones[?Name=='company.com.'].Id" 
  --output text | cut -d'/' -f3)

# DNS kaydını oluştur
aws route53 change-resource-record-sets 
  --hosted-zone-id $HOSTED_ZONE_ID 
  --change-batch file:///tmp/route53-change.json

VPC endpoint kullanıyorsanız ve Elastic IP aldıysanız CNAME yerine A kaydı kullanabilirsiniz. Bu daha temiz bir DNS yapısı sağlar.

CloudWatch ile İzleme ve Alerting

Transfer Family otomatik olarak CloudWatch’a metrik gönderir. Ama bunu anlamlı alarmlarla desteklemeniz gerekiyor. Aşağıdaki alarm örnekleri production’da gerçekten işe yarayan şeyler.

# Başarısız kimlik doğrulama denemeleri için alarm
aws cloudwatch put-metric-alarm 
  --alarm-name "SFTP-FailedAuthentications" 
  --alarm-description "Too many failed SFTP authentication attempts" 
  --metric-name FilesIn 
  --namespace AWS/Transfer 
  --dimensions Name=ServerId,Value=s-1234567890abcdef0 
  --statistic Sum 
  --period 300 
  --evaluation-periods 1 
  --threshold 10 
  --comparison-operator LessThanThreshold 
  --alarm-actions arn:aws:sns:eu-west-1:123456789012:ops-alerts

# Aktif bağlantı sayısı için alarm
aws cloudwatch put-metric-alarm 
  --alarm-name "SFTP-HighConnectionCount" 
  --alarm-description "Unusually high SFTP connection count" 
  --metric-name ActiveConnections 
  --namespace AWS/Transfer 
  --dimensions Name=ServerId,Value=s-1234567890abcdef0 
  --statistic Average 
  --period 300 
  --evaluation-periods 2 
  --threshold 100 
  --comparison-operator GreaterThanThreshold 
  --alarm-actions arn:aws:sns:eu-west-1:123456789012:ops-alerts

CloudWatch Logs için bir Log Group oluşturun ve CloudWatch Insights ile query atabilirsiniz.

# Transfer Family log'larını sorgula (son 1 saatteki başarısız girişimler)
aws logs start-query 
  --log-group-name "/aws/transfer/s-1234567890abcdef0" 
  --start-time $(date -d '1 hour ago' +%s) 
  --end-time $(date +%s) 
  --query-string 'fields @timestamp, sourceIPAddress, username, @message
    | filter type = "DISCONNECT" and disconnectReason = "Authentication failed"
    | stats count(*) as failCount by sourceIPAddress
    | sort failCount desc
    | limit 20'

Gerçek Dünya Senaryosu: B2B Veri Transferi

Diyelim ki bir fintech şirketinde çalışıyorsunuz ve üç ayrı partner firmayla düzenli veri alışverişi yapıyorsunuz. Her partner kendi klasörüne dosya yükleyecek, siz de işlenen dosyaları onların indirmesi için başka bir klasöre koyacaksınız. Üstelik bir partner sadece upload yapabilmeli, diğeri sadece download yapabilmeli, üçüncüsü ise her ikisini de yapabilmeli.

Bunun için her partner için ayrı IAM policy oluşturuyorsunuz.

# Sadece upload yapabilen partner için policy
cat > /tmp/upload-only-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": "arn:aws:s3:::my-sftp-storage-prod/partners/${transfer:UserName}/incoming/*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::my-sftp-storage-prod",
      "Condition": {
        "StringLike": {
          "s3:prefix": ["partners/${transfer:UserName}/*"]
        }
      }
    }
  ]
}
EOF

# Farklı bir role ile download-only partner
cat > /tmp/download-only-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:GetObjectVersion"],
      "Resource": "arn:aws:s3:::my-sftp-storage-prod/partners/${transfer:UserName}/outgoing/*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::my-sftp-storage-prod",
      "Condition": {
        "StringLike": {
          "s3:prefix": ["partners/${transfer:UserName}/outgoing/*"]
        }
      }
    }
  ]
}
EOF

Burada ${transfer:UserName} Transfer Family’nin sağladığı bir session tag’i. IAM policy’de bu placeholder’ı kullandığınızda her kullanıcı otomatik olarak kendi prefix’iyle sınırlandırılıyor. Bu çok zarif bir çözüm ve ayrı ayrı policy yazmaktan kurtarıyor.

S3 Event ile Otomatik İşleme Pipeline’ı

Gerçek hayatta sadece dosya almak yetmiyor, gelen dosyayı işlemek de gerekiyor. Partner bir CSV yüklediğinde Lambda fonksiyonunuzun tetiklenmesini istiyorsunuz.

# S3 bucket notification ayarla
cat > /tmp/notification-config.json << 'EOF'
{
  "LambdaFunctionConfigurations": [
    {
      "Id": "SFTPFileProcessor",
      "LambdaFunctionArn": "arn:aws:lambda:eu-west-1:123456789012:function:process-sftp-upload",
      "Events": ["s3:ObjectCreated:*"],
      "Filter": {
        "Key": {
          "FilterRules": [
            {
              "Name": "prefix",
              "Value": "partners/"
            },
            {
              "Name": "suffix",
              "Value": ".csv"
            }
          ]
        }
      }
    }
  ]
}
EOF

aws s3api put-bucket-notification-configuration 
  --bucket my-sftp-storage-prod 
  --notification-configuration file:///tmp/notification-config.json

Bu yapıyla artık her CSV upload’unda Lambda fonksiyonunuz otomatik tetikleniyor. Lambda içinde dosyayı validate edip, işleyip, sonuçları başka bir S3 konumuna yazabilirsiniz. Partner sabah gece dosyayı upload eder, gündüz geldiğinde işlenmiş raporu indirir. Klasik ETL pipeline’ı, sıfır sunucu yönetimiyle.

Mevcut Kullanıcıları Yönetme ve Troubleshooting

Zamanla kullanıcı listesi uzuyor ve bazı şeyler ters gitmeye başlıyor. Günlük yönetim komutları:

# Tüm kullanıcıları listele
aws transfer list-users 
  --server-id s-1234567890abcdef0 
  --query 'Users[*].[UserName,HomeDirectory]' 
  --output table

# Kullanıcı detaylarını görüntüle
aws transfer describe-user 
  --server-id s-1234567890abcdef0 
  --user-name acme-corp-sftp

# Kullanıcıya yeni SSH key ekle (eski key'i değiştirmeden)
aws transfer import-ssh-public-key 
  --server-id s-1234567890abcdef0 
  --user-name acme-corp-sftp 
  --ssh-public-key-body "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... new-key"

# Eski key'i sil
aws transfer delete-ssh-public-key 
  --server-id s-1234567890abcdef0 
  --user-name acme-corp-sftp 
  --ssh-public-key-id key-1234567890abcdef0

# Bağlantı testi (client tarafından)
sftp -i /tmp/sftp-user-key -o "StrictHostKeyChecking=no" 
  [email protected]

# Detaylı bağlantı debug
sftp -vvv -i /tmp/sftp-user-key [email protected] 2>&1 | head -50

En sık karşılaşılan sorunlar ve çözümleri:

  • Permission denied hatası: IAM role’ünün doğru policy’ye sahip olduğunu ve S3 bucket ARN’ının doğru yazıldığını kontrol edin. ${transfer:UserName} kullanıyorsanız bucket prefix’inin kullanıcı adıyla eşleştiğinden emin olun.
  • Kullanıcı dizin göremiyork: Home directory path’inin S3’te gerçekten var olduğunu kontrol edin. S3’te boş bir klasör oluşturmak için aws s3api put-object --bucket my-sftp-storage-prod --key "alice/" komutunu kullanabilirsiniz.
  • Bağlantı timeout: Security group ve VPC yapılandırmasını kontrol edin. VPC endpoint kullanıyorsanız subnet’lerin doğru route table’a sahip olduğunu doğrulayın.
  • Key çalışmıyor: Key formatının doğru olduğundan emin olun. DSA key’leri desteklenmiyor, RSA (minimum 2048 bit) veya ED25519 kullanın.

Maliyet Optimizasyonu

Transfer Family’nin maliyeti beklenmedik seviyelere çıkabiliyor. Dikkat etmeniz gereken noktalar:

  • Endpoint başına saatlik ücret: Sunucu durdu olsa bile endpoint aktif olduğu sürece ücret ödersiniz. Kullanmadığınız sunucuları silin veya durdurun.
  • Veri transferi: S3’e yazılan veri Transfer Family aracılığıyla yapılıyorsa ek transfer ücreti var. Büyük dosyalar için birikim yapıyor.
  • S3 depolama: Transfer Family’nin kendisi değil ama S3 depolama maliyeti de hesaba katılmalı. Lifecycle policy ile eski dosyaları Glacier’a taşıyın.
# S3 lifecycle policy ile otomatik arşivleme
cat > /tmp/lifecycle-policy.json << 'EOF'
{
  "Rules": [
    {
      "Id": "ArchiveOldSFTPFiles",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "partners/"
      },
      "Transitions": [
        {
          "Days": 30,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 90,
          "StorageClass": "GLACIER"
        }
      ],
      "Expiration": {
        "Days": 365
      }
    }
  ]
}
EOF

aws s3api put-bucket-lifecycle-configuration 
  --bucket my-sftp-storage-prod 
  --lifecycle-configuration file:///tmp/lifecycle-policy.json

Sonuç

AWS Transfer Family, kendi SFTP sunucunuzu yönetmenin getirdiği operasyonel yükü ciddi ölçüde azaltıyor. Özellikle birden fazla partner veya müşteriyle dosya alışverişi yapan, S3 ile zaten entegre çalışan ve sunucu yönetiminden kaçınmak isteyen ekipler için güçlü bir seçenek. Logical directory mapping ve IAM session policy kombinasyonu, çok kiracılı senaryolarda gerçekten zarif çözümler sunuyor.

Öte yandan her şey güllük gülüstanlık değil. Maliyet yapısına dikkat etmeniz gerekiyor. Endpoint başına saatlik ücret, düşük kullanım senaryolarında EC2 tabanlı çözümlere kıyasla pahalı olabiliyor. Geliştirme ve test ortamları için sunucuyu ihtiyaç duymadığınızda durdurun.

Custom identity provider entegrasyonu (Active Directory, LDAP) bu yazının kapsamı dışında kaldı ama kurumsal ortamlarda büyük ihtimalle o yöne gideceksiniz. AWS’in bu konuda iyi dokümantasyonu ve örnek Lambda fonksiyonları var, oradan devam edebilirsiniz. Sonuç olarak Transfer Family, doğru use case için doğru anda oldukça işe yarayan, üretimdeki yönetim yükünü azaltan bir servistir.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir