# Object Storage Security

Comprehensive security features to protect your data in DanubeData Object Storage.

## Overview

DanubeData Object Storage provides multiple layers of security to ensure your data is protected at rest and in transit. This guide covers encryption, access control, and security best practices.

## Encryption

### Encryption at Rest

All data stored in DanubeData Object Storage is automatically encrypted using **AES-256** encryption.

- **Server-Side Encryption (SSE-S3)**: Enabled by default
- **No configuration required**: Automatic for all objects
- **Zero performance impact**: Hardware-accelerated encryption

### Encryption in Transit

All connections use **TLS 1.3** for maximum security:

- **HTTPS only**: HTTP connections are not accepted
- **Strong cipher suites**: Modern TLS configuration
- **Certificate validation**: Automatic certificate management

## Access Control

### Access Keys

Access keys are the primary method for authenticating to your buckets via the S3 API.

#### Creating Access Keys

**Via Dashboard:**
1. Navigate to your bucket
2. Click **Access Keys** tab
3. Click **Create Access Key**
4. Configure name and permissions
5. Copy the secret key immediately (shown only once)

**Via API:**
```bash
curl -X POST https://api.danubedata.ro/v1/storage/buckets/{bucket_id}/access-keys \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-app-key",
    "permissions": ["read", "write"]
  }'
```

#### Permission Levels

| Permission | Description | Operations |
|------------|-------------|------------|
| **read** | Read-only access | GetObject, ListObjects, HeadObject |
| **write** | Create and update objects | PutObject, CopyObject |
| **delete** | Remove objects | DeleteObject, DeleteObjects |
| **admin** | Full bucket control | All operations including policy changes |

#### Best Practices for Access Keys

1. **Use least privilege**: Only grant permissions that are needed
2. **Rotate regularly**: Create new keys and retire old ones periodically
3. **Set expiration dates**: Use expiring keys for temporary access
4. **Monitor usage**: Check last-used timestamps to identify unused keys
5. **Never commit to code**: Use environment variables or secrets management

### Public Access Control

By default, all buckets are private. You can enable public read access for specific use cases.

#### Enabling Public Access

**Via Dashboard:**
1. Navigate to your bucket
2. Click **Settings**
3. Toggle **Public Access** to enabled
4. Confirm the security warning

**Via API:**
```bash
curl -X PATCH https://api.danubedata.ro/v1/storage/buckets/{bucket_id} \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "public_access": true
  }'
```

#### Public Access Warning

When public access is enabled:
- Anyone can read objects in your bucket
- Objects are accessible via direct URL
- Egress traffic will count against your quota
- Use for static websites, public assets only

### Bucket Policies

For fine-grained access control, you can apply bucket policies from the bucket's **Settings** tab. The most common use is an **IP allow-list** — restricting a bucket so its objects are only reachable from source IP ranges you trust (office, VPN, or application servers), expressed in CIDR notation.

You can build a policy two ways:

- **Visual editor** — Choose an effect (Allow or Deny), actions, resources, and source-IP conditions without hand-writing JSON.
- **JSON editor** — Paste or write policy statements directly, with live validation as you type.

Custom statements merge automatically with the bucket's public-access setting and any scoped access keys, so you never have to restate them. Built-in guardrails reject any policy that would stop you (or DanubeData) from managing the bucket's own policy, so you can always recover.

> The policy editor is available on buckets hosted on the current high-durability storage endpoint.

#### Example: Allow Read from Specific IP

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::my-bucket/*"],
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "192.168.1.0/24"
        }
      }
    }
  ]
}
```

#### Example: Deny Delete Operations

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": ["s3:DeleteObject", "s3:DeleteBucket"],
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}
```

## Object Lock (WORM)

Object Lock provides write-once-read-many (WORM) protection. When a bucket has Object Lock enabled, an object version can be locked so that it cannot be deleted or overwritten until its retention period expires — useful for regulatory compliance, ransomware protection, and tamper-proof audit trails.

### Retention modes

| Mode | Who can remove or shorten the lock |
|------|------------------------------------|
| **Governance** | Normal requests cannot delete or overwrite a locked version; a user with elevated permissions can shorten or remove the lock when genuinely needed. |
| **Compliance** | No one — including the bucket owner — can delete a locked version or shorten its retention until the retention period expires. |

### Enabling Object Lock

- **Enable at bucket creation.** Object Lock can only be turned on when the bucket is created — it cannot be added to an existing bucket. Enabling it automatically enables versioning, which Object Lock requires.
- **Default retention (optional).** Set a default mode and a retention period (1–36,500 days) that applies automatically to every new object version.

### Per-object controls

- **Retention** — Apply or extend a retention date on an individual object version with `PutObjectRetention`.
- **Legal hold** — Place an indefinite hold on a specific object version with `PutObjectLegalHold`, independent of any retention period. The version cannot be deleted until you explicitly remove the hold.

```bash
# Apply Compliance-mode retention until a specific date
aws --endpoint-url https://s3.danubedata.ro s3api put-object-retention \
  --bucket my-bucket \
  --key important.log \
  --retention '{"Mode":"COMPLIANCE","RetainUntilDate":"2027-01-01T00:00:00Z"}'

# Place a legal hold on an object
aws --endpoint-url https://s3.danubedata.ro s3api put-object-legal-hold \
  --bucket my-bucket \
  --key important.log \
  --legal-hold '{"Status":"ON"}'
```

> Because a locked object version consumes storage until its retention expires (and delete markers don't reclaim it), pair Object Lock with a realistic retention period to keep costs predictable.

## SFTP Access

In addition to the S3 API, you can reach a bucket over **SFTP** — handy for legacy tools, batch jobs, or users that speak SFTP but not S3.

```
Host: sftp.new-s3.danubedata.ro
Port: 2222
```

- Create SFTP users from the bucket's **SFTP** section in the dashboard.
- Each SFTP user is bound to a single bucket and one of your S3 access keys, so its permissions follow that key (read-only, read-write, or full). This gives you per-key access control over SFTP, exactly as with the S3 API.
- The dashboard shows the exact username to use; the password is that access key's secret. Revoking or rotating the underlying access key immediately revokes the SFTP login too.

## CORS Configuration

Configure Cross-Origin Resource Sharing (CORS) to allow web applications to access your bucket.

### Why CORS?

Browsers block cross-origin requests by default. If your web application needs to upload or download files directly from Object Storage, you must configure CORS.

### Configuring CORS

**Via Dashboard:**
1. Navigate to your bucket
2. Click **Settings** → **CORS**
3. Add CORS rules
4. Save changes

**Via API:**
```bash
curl -X PUT https://api.danubedata.ro/v1/storage/buckets/{bucket_id}/cors \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cors_rules": [
      {
        "allowed_origins": ["https://myapp.com", "https://*.myapp.com"],
        "allowed_methods": ["GET", "PUT", "POST", "DELETE"],
        "allowed_headers": ["*"],
        "expose_headers": ["ETag", "x-amz-meta-*"],
        "max_age_seconds": 3600
      }
    ]
  }'
```

### CORS Rule Options

| Field | Description | Example |
|-------|-------------|---------|
| `allowed_origins` | Domains allowed to make requests | `["https://myapp.com"]` |
| `allowed_methods` | HTTP methods allowed | `["GET", "PUT"]` |
| `allowed_headers` | Request headers allowed | `["Content-Type", "Authorization"]` |
| `expose_headers` | Response headers exposed to browser | `["ETag"]` |
| `max_age_seconds` | How long browser caches preflight | `3600` |

### CORS Best Practices

1. **Be specific with origins**: Avoid using `*` in production
2. **Limit methods**: Only allow methods your app needs
3. **Set appropriate max_age**: Balance security and performance
4. **Test thoroughly**: Use browser dev tools to verify CORS

## Presigned URLs

Generate temporary, secure URLs for sharing objects without exposing credentials.

### Download URL

```python
import boto3

s3 = boto3.client(
    's3',
    endpoint_url='https://s3.danubedata.ro',
    aws_access_key_id='YOUR_ACCESS_KEY',
    aws_secret_access_key='YOUR_SECRET_KEY'
)

# Valid for 1 hour
url = s3.generate_presigned_url(
    'get_object',
    Params={'Bucket': 'my-bucket', 'Key': 'secret-file.pdf'},
    ExpiresIn=3600
)
```

### Upload URL

```python
# Generate upload URL
upload_url = s3.generate_presigned_url(
    'put_object',
    Params={
        'Bucket': 'my-bucket',
        'Key': 'uploads/user-file.txt',
        'ContentType': 'text/plain'
    },
    ExpiresIn=3600
)

# Client can upload using:
# curl -X PUT -H "Content-Type: text/plain" --data-binary @file.txt "$upload_url"
```

### Presigned URL Security

- **Time-limited**: URLs expire after the specified duration
- **Operation-specific**: Each URL is valid for one operation
- **Cannot be revoked**: Once generated, valid until expiry
- **Audit trail**: Track which key generated the URL

## Security Best Practices

### 1. Principle of Least Privilege

Create separate access keys for different applications with only the permissions they need:

```bash
# Read-only key for analytics
curl -X POST .../access-keys -d '{"name": "analytics", "permissions": ["read"]}'

# Write-only key for uploads
curl -X POST .../access-keys -d '{"name": "uploader", "permissions": ["write"]}'

# Full access for backups
curl -X POST .../access-keys -d '{"name": "backup", "permissions": ["read", "write", "delete"]}'
```

### 2. Enable Versioning for Critical Data

Protect against accidental deletion or overwrites:

```bash
curl -X PATCH https://api.danubedata.ro/v1/storage/buckets/{bucket_id} \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -d '{"versioning_enabled": true}'
```

### 3. Implement Lifecycle Rules

Automatically delete temporary files and old versions:

```json
{
  "rules": [
    {
      "id": "delete-temp-files",
      "prefix": "temp/",
      "expiration_days": 7
    },
    {
      "id": "delete-old-versions",
      "noncurrent_version_expiration_days": 30
    }
  ]
}
```

### 4. Monitor Access Key Usage

Regularly review access keys:

1. Check last-used timestamps
2. Delete unused keys
3. Rotate keys periodically
4. Set expiration dates for temporary access

### 5. Use Separate Buckets for Sensitivity

Organize data by sensitivity level:

- `production-public` - Public assets, CDN content
- `production-private` - Application data, user uploads
- `production-sensitive` - PII, financial data (most restricted)

### 6. Audit and Logging

Enable access logging to track all bucket operations:

- Who accessed what objects
- When operations occurred
- Success/failure status
- Source IP addresses

## Compliance

### GDPR Compliance

DanubeData Object Storage is fully GDPR compliant:

- **Data residency**: All data stored in Germany (EU)
- **Encryption**: AES-256 at rest, TLS 1.3 in transit
- **Access control**: Fine-grained permission management
- **Data deletion**: Objects can be permanently deleted
- **Audit trail**: Complete access logging available
- **Data Processing Agreement**: Download a signed DPA (PDF) for your records from your account's legal settings
- **Immutability**: Use [Object Lock](#object-lock-worm) in Compliance mode to enforce retention for records that must not be altered or deleted

### Data Retention

Use lifecycle rules to implement data retention policies:

```json
{
  "rules": [
    {
      "id": "gdpr-retention",
      "prefix": "user-data/",
      "expiration_days": 365,
      "noncurrent_version_expiration_days": 90
    }
  ]
}
```

## Troubleshooting

### "Access Denied" Errors

1. **Verify credentials**: Check access key and secret
2. **Check permissions**: Ensure key has required permissions
3. **Bucket ownership**: Verify key belongs to bucket's team
4. **Bucket policy**: Check for deny rules blocking access

### CORS Errors in Browser

1. **Check origin**: Ensure your domain is in allowed_origins
2. **Check method**: Verify HTTP method is allowed
3. **Check headers**: Ensure required headers are allowed
4. **Browser cache**: Clear preflight cache and retry

### Presigned URL Not Working

1. **Check expiry**: URL may have expired
2. **Clock sync**: Ensure server clock is accurate
3. **URL encoding**: Don't manually modify the URL
4. **Key revocation**: Original key may have been deleted

## Next Steps

- [Object Storage Product Overview](https://docs.danubedata.ro/object-storage) - Full feature documentation
- [Object Storage Quick Start](https://docs.danubedata.ro/object-storage-quickstart) - Get started guide

---

**Questions?** Contact support at support@danubedata.ro
