×
Community Blog Using ESA as a Front Door for Private OSS: STS Authentication, Cost Optimization, and Security

Using ESA as a Front Door for Private OSS: STS Authentication, Cost Optimization, and Security

Alibaba Cloud OSS (Object Storage Service) is a solid storage backend, but exposing it directly to the internet has drawbacks: the bucket domain is vi.

Why Put ESA in Front of OSS?

Alibaba Cloud OSS (Object Storage Service) is a solid storage backend, but exposing it directly to the internet has drawbacks: the bucket domain is visible, traffic pricing is higher than ESA in many regions (ESA Pro plan traffic price is 0.03USD/GB globally and no request cost), and you miss out on edge-layer security. Putting ESA (Edge Security Acceleration) in front of your private OSS bucket addresses all three, but it introduces a non-obvious challenge around authentication and caching.

This guide walks through the complete architecture, the configuration steps, and the trade-offs you need to understand.

The Benefits

Hide your origin. With ESA as the front door, clients only see your custom domain (e.g., cdn.example.com). The actual OSS endpoint (mybucket.oss-ap-southeast-1.aliyuncs.com) is never exposed, reducing the attack surface and preventing direct bucket enumeration.

Lower egress cost for global traffic. OSS internet egress pricing varies by region but is generally higher than ESA's traffic unit price, especially on ESA Pro and above plans. For workloads serving significant overseas traffic (e.g., global SaaS platforms, game asset distribution, or media delivery), routing through ESA can reduce per-GB costs. For example, ESA Pro plan traffic in many overseas regions is priced lower than OSS direct internet egress in those same regions.

Edge-layer security. ESA provides a suite of security capabilities at the edge that OSS alone does not offer:

  1. WAF (Web Application Firewall): Managed rule sets, custom rules, rate limiting, and scan protection — all evaluated before traffic reaches your origin
  2. DDoS protection: Basic anti-DDoS is included; Enterprise plans add advanced scrubbing
  3. Bot management: Anti-bot rules to filter automated traffic
  4. IP reputation and geo-blocking: Block or challenge traffic by region or threat score
  5. HTTPS/TLS management: Automatic certificate provisioning, TLS version control, and post-quantum hybrid key exchange (X25519-ML-KEM-768)
  6. Access control: URL signing, referer-based restrictions, and IP whitelisting at the edge

The Core Challenge: Authentication vs. Caching

Here is the fundamental tension. ESA is a CDN — its job is to cache content at edge nodes close to users. But if your OSS bucket is private (as it should be), every request needs to be authenticated. The two mechanisms are in conflict:

  1. Origin authentication (STS): The client signs the request with temporary credentials. OSS verifies the signature and either allows or denies access. This only works if the request actually reaches the origin.
  2. Edge caching: Once a response is cached at an ESA node, subsequent requests are served from cache — they never reach the origin, so the origin's authentication check is bypassed.

This means you must choose between two models:

Model Caching Auth enforcement Use case
Strict auth Disabled (or bypass) Every request authenticated at origin Private/sensitive content
Edge auth + caching Enabled Auth checked at edge before serving cache High-traffic private content

We'll cover both.


Architecture: Strict Auth (No Caching)

Client                         ESA Edge                     OSS Origin
  │                               │                             │
  │  GET cdn.example.com/1.png    │                             │
  │  + STS signature headers      │                             │
  │──────────────────────────────►│                             │
  │                               │  No cache → forward to origin│
  │                               │  (preserves auth headers)    │
  │                               │────────────────────────────►│
  │                               │                             │ Verify STS
  │                               │                             │ signature
  │                               │  200 OK                     │
  │                               │◄────────────────────────────│
  │  200 OK                       │                             │
  │◄──────────────────────────────│                             │

With caching disabled, every request traverses the full path and must present valid STS credentials. ESA acts as a transparent reverse proxy, adding security and hiding the origin — but not caching.


Step-by-Step Configuration

Step 1: Create a RAM Role for STS

In the RAM console:

  1. Go to RAM Console → Roles → Create Role
  2. Choose Alibaba Cloud Account as the trusted entity type
  3. Name the role (e.g., CDNAccessingPrivateOSSRole)
  4. Attach a policy granting the minimum required OSS permissions:
{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["oss:GetObject"],
      "Resource": ["acs:oss:*:*:mybucket/*"]
    }
  ]
}

Step 2: Configure the Trust Policy

Edit the role's trust policy to specify who can assume it:

{
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Effect": "Allow",
      "Principal": {
        "RAM": ["acs:ram::<ACCOUNT_ID>:root"]
      }
    }
  ],
  "Version": "1"
}

To restrict to a specific sub-user:

"RAM": ["acs:ram::<ACCOUNT_ID>:user/myapp-user"]

Step 3: Grant the RAM User Permission to Call AssumeRole

The RAM user whose AK/SK your application uses needs the sts:AssumeRole action:

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "acs:ram::<ACCOUNT_ID>:role/CDNAccessingPrivateOSSRole"
    }
  ]
}

Without this, the AssumeRole API returns 403 NoPermission with PolicyType: AccountLevelIdentityBasedPolicy.

Step 4: Lock Down the OSS Bucket

Set the Bucket ACL to private.

Then audit the Bucket Policy. This is often overlooked: a bucket policy with Principal: "*" and Effect: Allow on oss:GetObject will override the private ACL and make the bucket publicly readable. Bucket Policy takes precedence over Bucket ACL in the permission evaluation logic.

Remove any statement like:

{
  "Effect": "Allow",
  "Principal": ["*"],
  "Action": ["oss:GetObject"],
  "Resource": ["acs:oss:*:*:mybucket/*"]
}

To verify your bucket is truly private, test anonymous access directly to the OSS endpoint:

curl -s -o /dev/null -w "%{http_code}" https://mybucket.oss-ap-southeast-1.aliyuncs.com/1.png
# Expected: 403

Step 5: Configure ESA

This is the critical step. In the ESA console:

  1. Add your site and configure DNS
  2. Create a record for your CDN domain (e.g., cdn.example.com)
  3. Add an origin with type "Custom Origin" (NOT "OSS Origin"):
    -Origin address: mybucket.oss-ap-southeast-1.aliyuncs.com

-Protocol: HTTPS, Port: 443

Why "Custom Origin" instead of "OSS Origin"? When you select "OSS Origin", ESA uses its own service-linked role to sign back-to-origin requests. This means ESA authenticates to OSS on behalf of every request — and every request succeeds, regardless of whether the end user is authenticated. A custom origin forces ESA to forward the client's request headers as-is, preserving the STS authentication boundary at the origin.

Disable caching: for the strict auth model: set cache TTL to 0 or configure a cache rule that bypasses all requests.

Step 6: Client-Side Code

import os
import oss2
from alibabacloud_sts20150401.client import Client as StsClient
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_sts20150401 import models as sts_models

ROLE_ARN    = "acs:ram::<ACCOUNT_ID>:role/CDNAccessingPrivateOSSRole"
ENDPOINT    = "https://cdn.example.com"
BUCKET_NAME = "mybucket"

def get_sts_credentials():
    ak = os.environ["OSS_ACCESS_KEY_ID"]
    sk = os.environ["OSS_ACCESS_KEY_SECRET"]

    sts_config = open_api_models.Config(
        access_key_id=ak, access_key_secret=sk,
    )
    sts_config.endpoint = "sts.ap-southeast-1.aliyuncs.com"
    sts_client = StsClient(sts_config)

    request = sts_models.AssumeRoleRequest(
        role_arn=ROLE_ARN,
        role_session_name="cdn-download",
        duration_seconds=3600,
    )
    response = sts_client.assume_role(request)
    c = response.body.credentials
    return c.access_key_id, c.access_key_secret, c.security_token

def download(sts_ak, sts_sk, sts_token, object_key, local_path):
    auth = oss2.StsAuth(sts_ak, sts_sk, sts_token)
    # is_cname=True is required for custom domains
    bucket = oss2.Bucket(auth, ENDPOINT, BUCKET_NAME, is_cname=True)
    result = bucket.get_object_to_file(object_key, local_path)
    print(f"OK: {local_path} ({os.path.getsize(local_path):,} bytes)")

if __name__ == "__main__":
    ak, sk, token = get_sts_credentials()
    download(ak, sk, token, "1.png", "output.png")

The is_cname=True parameter is mandatory with custom domains. Without it, the oss2 SDK prepends the bucket name to the domain (mybucket.cdn.example.com), which fails DNS resolution.

Run with:

pip install oss2 alibabacloud_sts20150401 alibabacloud_tea_openapi
OSS_ACCESS_KEY_ID=<ak> OSS_ACCESS_KEY_SECRET=<sk> python download.py

The Caching Problem (and Why It Matters)

Here's what happens if you enable caching without thinking it through:

  1. Client A makes an authenticated request with valid STS credentials
  2. ESA fetches from origin (STS signature verified by OSS → 200)
  3. ESA caches the response at the edge node
  4. Client B makes an unauthenticated request (no STS, no signature)
  5. ESA serves the cached response — 200, no auth check

The response headers will confirm it: X-Site-Cache-Status: HIT and a non-zero Age value. The private bucket ACL is only enforced on cache misses.

This is not a bug — it's how CDNs work. But it means you need an explicit strategy.


Workaround: Edge-Side Authentication with Caching

If you want both caching (for performance and cost) and access control, move the authentication check to the edge. ESA offers several mechanisms:

Option 1: Edge Routine (Custom Auth Logic)

For more complex authentication flows, deploy an Edge Routine (serverless JavaScript at the edge) that intercepts requests before the cache:

// Edge Routine: validate a Bearer token before serving
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const authHeader = request.headers.get('Authorization');
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return new Response('Unauthorized', { status: 401 });
  }

  const token = authHeader.replace('Bearer ', '');
  // Validate against your auth service or a pre-shared key
  const isValid = await validateToken(token);
  if (!isValid) {
    return new Response('Forbidden', { status: 403 });
  }

  // Auth passed — proceed to fetch from cache/origin
  return fetch(request);
}

async function validateToken(token) {
  // Option 1: Check against a pre-shared key
  // Option 2: Call an external auth API
  // Option 3: Verify a JWT signature locally
  const expected = "your-secret-key";
  return token === expected;
}

Edge Routine runs before the cache lookup, so even cached content requires valid credentials. This gives you the best of both worlds: authenticated access at the edge, with caching for performance.

Option 2: ESA WAF Rules for Access Control

Use ESA's WAF to enforce access policies at the edge:

  1. IP whitelist: Only allow requests from known IP ranges
  2. Custom header rules: Require a specific header value (e.g., X-Auth-Token: <secret>)
  3. Rate limiting: Restrict request rate per client IP to prevent abuse

These rules are evaluated before the cache, providing an additional layer of access control.


Comparison: Which Model to Choose?

Requirement Strict Auth (no cache) Edge Routine
Every request authenticated at origin Yes No (edge check)
Edge caching enabled No Yes
Implementation complexity Low High
Token expiration built-in STS token (1h default) Custom logic
Suitable for API backends, sensitive data Complex auth flows
Latency (repeat requests) Higher (always to origin) Low (cache hit)
Origin traffic cost Higher Lower

Summary

Putting ESA in front of a private OSS bucket gives you origin hiding, lower overseas egress costs, and edge-layer security (WAF, DDoS, bot management). The key configuration choices are:

  1. Use "Custom Origin" in ESA (not "OSS Origin") to preserve the authentication boundary
  2. Lock down the bucket — private ACL and no overly permissive bucket policy
  3. Decide on caching — if you need strict origin auth on every request, disable caching; if you want caching, add edge-side authentication (URL signing or Edge Routine)

The strict auth model is simpler but trades latency and origin cost for security. The edge auth model is more complex but lets you have both caching and access control — just enforced at a different layer.

0 1 0
Share on

ESA Big Fan

6 posts | 1 followers

You may also like

Comments

ESA Big Fan

6 posts | 1 followers

Related Products