# Alibaba Cloud CDN

Aliyun CDN is an independent download adapter. It can be paired with OSS or any
other supported storage provider. CDN URL authentication uses its own key, not
the OSS AccessKey secret.

```elixir
config :my_app, MyApp.Storage,
  provider: :aliyun,
  region: "cn-hangzhou",
  access_key_id: System.fetch_env!("OSS_ACCESS_KEY_ID"),
  secret_access_key: System.fetch_env!("OSS_ACCESS_KEY_SECRET"),
  cdn: [
    provider: :aliyun,
    endpoint: "https://cdn.example.com",
    aliyun_auth_key: System.fetch_env!("CDN_AUTH_KEY"),
    aliyun_auth_type: :a,
    aliyun_auth_ttl: 1800
  ]
```

The endpoint must be an HTTP(S) origin without a path or query. Configure the
same authentication type, key and TTL on the CDN domain. The key is redacted
from client inspection. Use either the domain's primary or secondary key.

## Authentication types

| Type | Configuration | Signed URL |
|------|---------------|------------|
| A (default) | `aliyun_auth_type: :a` | `/file?auth_key=timestamp-rand-0-hash` |
| B | `aliyun_auth_type: :b` | `/YYYYMMDDHHMM/hash/file` |
| C path (default C format) | `aliyun_auth_type: :c` | `/hash/HEX_TIMESTAMP/file` |
| C query | `aliyun_auth_type: :c, aliyun_auth_format: :query` | `/file?sign=hash&time=HEX_TIMESTAMP` |
| F | `aliyun_auth_type: :f` | `/file?sign=hash&time=timestamp` |

For C query/F, set `aliyun_sign_param` and `aliyun_time_param` to the domain's
configured parameter names (defaults `"sign"` and `"time"`). For F,
`aliyun_time_format` accepts `:decimal` (default) or `:hex`. C always uses
hexadecimal timestamps. A generates a random 32-character hexadecimal nonce;
`opts[:rand]` can supply an alphanumeric value for reproducible signing.

All types hash the encoded resource path, excluding query parameters. Object
keys are raw strings: do not pre-encode them. Slash separators are preserved.
A/B/C accept `query_params`; these parameters are not authenticated by the CDN
path-signing formula. Authentication parameter collisions are rejected. F
rejects existing query parameters, as required by its documented contract.

## Expiration semantics

The domain expires a URL at **timestamp + console TTL**. Set `aliyun_auth_ttl`
to that console value, not to the requested URL lifetime. The library calculates
`timestamp = now + expires_in - aliyun_auth_ttl`, so `expires_in` retains its
meaning of remaining lifetime. The default console TTL assumed is 1800 seconds.
A mismatch between library and domain TTL changes the actual lifetime.

B uses UTC+8 timestamps with minute precision, rounded down, so links can expire
up to 59 seconds earlier than requested. Avoid very short B lifetimes.

```elixir
MyApp.Storage.authorize_download_url("my-bucket", "images/avatar.png", 3600)
```

`headers` cannot be bound by CDN URL authentication and nonempty values are
rejected on this adapter. `att_name` is also rejected: set Content-Disposition
on the object or configure the origin/CDN response instead.

## Uploads and origin access

With Aliyun CDN configured, `upload_credential` always returns the underlying
storage provider's presigned PUT URL, including its required headers, even when
`cdn_upload` is true. CDN download authentication alone does not authorize OSS
writes. Server-side storage operations also use the storage endpoint.

`cdn_download: false` returns direct storage download URLs. `public_url` returns
an unsigned CDN URL when CDN download is enabled; on an authenticated domain,
use `authorize_download_url` for actual access.

Configure the CDN origin and private-OSS origin authorization separately in
Alibaba Cloud. Client-to-CDN authentication and CDN-to-OSS authentication are
separate checks. This library does not create CDN domains, configure origin
permissions, CORS, cache rules or refresh jobs.

## Console setup and live test

For an initial test, use type A authentication with a private OSS bucket in
the same Alibaba Cloud account:

1. In the CDN console, open **Domain Names (域名管理)** and add or manage your
   acceleration domain, for example `cdn.example.com`. Select **OSS domain** as
   the origin type and the same bucket used by `OSS_TEST_BUCKET`. Point the
   domain's DNS CNAME at the address assigned by CDN. For HTTPS, configure a
   certificate on the CDN domain; HTTP endpoints are also supported by ExOss.
2. On that domain, open **Origin Settings (回源配置)** and locate
   **Private OSS Bucket Origin (阿里云OSS私有Bucket回源)**. Complete the authorization
   prompt if shown, then enable the switch and choose same-account origin access.
   Account authorization and enabling the domain's switch are separate steps.
   Keep the bucket private. Cross-account buckets require the corresponding
   cross-account origin authorization instead.
3. Open **Access Control (访问控制) > URL Authentication (URL鉴权)**, click
   **Modify Configuration (修改配置)** and enable authentication. Select **A**,
   set a primary key (6–128 letters/digits), set the validity period to **1800**
   seconds, and leave rule conditions unused so all paths are protected.
   The authentication key is independent of the OSS AccessKey secret.
4. Save and allow the domain/DNS settings to take effect. The test uses the
   acceleration domain itself, not the CDN-assigned CNAME target or OSS endpoint.

Keep the OSS environment variables from the [OSS live test](aliyun-oss.html#optional-live-tests)
and add:

```sh
export OSS_TEST_CDN_ENDPOINT='http://cdn.example.com'
export OSS_TEST_CDN_AUTH_KEY='your-cdn-authentication-key'
export OSS_TEST_CDN_AUTH_TYPE='a'
export OSS_TEST_CDN_TTL='1800'

OSS_TEST_KEEP_OBJECTS=1 mix test test/ex_oss/aliyun_integration_test.exs --include aliyun_integration --only cdn_live --trace
```

Use `https://` when HTTPS is configured. The test uploads `cdn-test` directly to
OSS, downloads it through the signed CDN URL, and checks its contents. The URL
is valid for 300 seconds even though the console TTL is 1800: ExOss adjusts the
signature timestamp as described above. `OSS_TEST_CDN_TTL` must match the console.

With `OSS_TEST_KEEP_OBJECTS=1`, the test prints the signed URL before requesting
it and retains the object for manual checking. Open the link, then remove the
`auth_key` query parameter and verify that access is rejected with HTTP 403.
Delete the printed OSS object after testing. Omit the retention flag to restore
automatic cleanup. This test verifies the selected authentication type only.

### Diagnosing HTTP 403

Failures include selected response headers and the response body:

| Evidence | What to check |
|----------|---------------|
| `X-Tengine-Error` reports `invalid md5hash` | Authentication type, key and signed path |
| `X-Tengine-Error` reports `expired timestamp` | Console TTL, `OSS_TEST_CDN_TTL` and system clock |
| `x-swift-error: orig response 4XX error`, OSS XML `AccessDenied`, and `because of bucket acl` | Private OSS origin authorization and the domain's private-bucket origin switch |
| Referer or IP access-rule error | The domain's access rules must allow the test request |

An OSS-origin 403 does not by itself indicate a CDN URL signature error. After
correcting the settings, rerun the test to get a new object path and URL.

## References

- [Configure CDN for OSS](https://help.aliyun.com/zh/oss/user-guide/cdn-acceleration)
- [Private OSS bucket origin authorization](https://help.aliyun.com/zh/cdn/user-guide/grant-alibaba-cloud-cdn-access-permissions-on-private-oss-buckets)
- [Configure URL signing and TTL](https://www.alibabacloud.com/help/en/cdn/user-guide/configure-url-signing)
- [Type A](https://help.aliyun.com/en/cdn/user-guide/type-a-signing)
- [Type B](https://www.alibabacloud.com/help/en/cdn/user-guide/type-b-signing)
- [Type C](https://www.alibabacloud.com/help/en/cdn/user-guide/type-c-signing)
- [Type F](https://www.alibabacloud.com/help/en/cdn/user-guide/authentication-method-f-description)
