Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Review: How Top CDN manage content authentication? #4438

Open
zeeshanakram3 opened this issue Nov 13, 2022 · 9 comments
Open

Review: How Top CDN manage content authentication? #4438

zeeshanakram3 opened this issue Nov 13, 2022 · 9 comments
Labels
argus Argus distributor node

Comments

@zeeshanakram3
Copy link
Contributor

zeeshanakram3 commented Nov 13, 2022

After reviewing the top CDN offerings, it seems that all of these follow more or less the same authentication approach to mitigate several problems, e,g, hotlinking, denial of services, etc.

I reviewed the following CDN providers.

Usually, a CDN involves three actors:

  1. CDN itself
  2. A content Consumer
  3. Client (an actor which is using the services of a CDN to serve content to consumers, e.g., A Web Application)

In Joystream network topology, I think there is not a clear distinction between these three actors. (It will be more clear after going over the authentication flow of an example provider)

For most of the web applications/clients using the above-mentioned providers, before a consumer requests the protected content, they must get a token from the web application. The web application logic (for instance, username and password) validates the request. If the validation passes, it generates a one-time token and returns it to the client.

When clients request the CDN contents, they must pass the token in a cookie, query string parameter, or request header. The request goes to the CDN Edge server, and the Edge server attempts to match it with the local token-auth setting. If the token is a verified match, the content is returned to the client. If the token doesn't match, the Edge server returns a 403 response.

IBM CDN Authentication Flow

image

Azure CDN Authentication Flow

image

AWS Cloudfront Authentication Flow

On CloudFront, applications configure CloudFront to require that users access your files using either signed URLs or signed cookies. The application has to choose a public-private keypair and then 1) sign URL/cookies using a private pair, 2) give that signed message to an authenticated user, and 3) which the user/consumer will use in a CDN request for private content.


The above authentication flows gives a hint that before a consumer could interact with the CDN, a backend communication happens b/w CDN and web application/client, during which the client configures the CDN and generates some token, which is then passed to the consumer, who uses this token in a request for some asset to the CDN.

However, in Joystream, the CDN (Argus) itself has to do the authentication, i.e., verifying infra access key, verifying persona, etc., at least for the first request; if we go without the lead node, and then it can issue a token just like client issues it in previous examples, which then can be reused in subsequent requests to the CDN.

Comments

  • First and foremost, all these CDN providers require that viewers/consumers use HTTPS so that connections are encrypted when a CDN communicates with viewers, hence whatever scheme is used, token or signed message, the communication is safe from token reuse or replay attacks from the malicious actors.
  • After studying all the providers, it seems to me that all the authentication schemes at most can help in limiting content access to only legitimate users; however, for parasitic users, you need pay as you go plan.
  • Also, one more thing that is clear from looking at the CDN flows is that when serving the request for private content, CDN only verifies whether the request contains a valid token/request and that it, it does not concern itself with authentication & permissions stuff, whether the user has right credentials or whether it can view a specific asset (that's the domain of client/application)
@zeeshanakram3 zeeshanakram3 added the argus Argus distributor node label Nov 13, 2022
@traumschule
Copy link
Collaborator

For our case this could be as simple as one token per gateway who would do fine grained restrictions per user (based on their plan with the App).
However since DP URLs are static as GW operator there might be use cases to request user specific tokens with option to expire. For example to map payments to user and assets internally.
Another use case are on-demand DP asset URLs per token. These "secret" hard to guess deep-links could be without auth for less smart clients like TV or podcast devices.
Alternatively a double map of Gateway worker <> channel might be useful if channel owners want to restrict their channels to specific GW. This would allow the council / GW lead to set payment requirements accordingly.

@bedeho
Copy link
Member

bedeho commented Nov 14, 2022

Great work @zeeshanakram3

I don't get this comment

Also, one more thing that is clear from looking at the CDN flows is that when serving the request for private content, CDN only verifies whether the request contains a valid token/request and that it, it does not concern itself with authentication & permissions stuff, whether the user has right credentials or whether it can view a specific asset (that's the domain of client/application)

Some how the CDN server has to be able to know what token gives access to what resource? If you are saying there is just one universal authorization mode: access everything or nothing, then that seeems super impractical? Apps typically need more granularity? I would assume this information somehow is embedded or represented in token. Obviously, with such a scheme, authentication is super fast, you dont need the sort of mapping we have been talking about for Argus.

Can you elaborate?

@zeeshanakram3
Copy link
Contributor Author

I would assume this information somehow is embedded or represented in token.

Yes, your assumption is correct. When I say CDN does not concern itself with authentication & permissions stuff, it actually means that CDN does not perform these operations itself, All the policy & permission constraints are part of the token and, CDN serves the request based on those policies constraints embedded in the token

@bedeho
Copy link
Member

bedeho commented Nov 14, 2022

Ok, great,

  • can you give an example of how this format works here? I guess it must be documented right?
  • Is there one standard that everyone follows, or is it bespoke?
  • What sort of state is needed on the CDN side to do the verification? and how is this state managed? is there interaction between CDN and app side in some way, either up front or ongoing?

@zeeshanakram3
Copy link
Contributor Author

zeeshanakram3 commented Nov 15, 2022

can you give an example of how this format works here? I guess it must be documented right?
Is there one standard that everyone follows, or is it bespoke?

So I looked at the internals of CDN token generation and verification on different providers. Although they use more or less the same format, however, there isn't a industry-wide standard that everyone needs to follow. Also, there is a lot of parameters customization available by each provider, and Applications can selectively choose some or all of the parameters to be included in the configured token.

Azure CDN token format

List of some of the important Azure token authentication parameter values in our context:

Parameter Description
ec_expire Assigns an expiration time to a token, after which the token expires
ec_url_allow Allows specific asset or path under which all assets all allowed, Example from Azure docs:

for input value /pictures, the following requests are allowed: http://www.mydomain.com/pictures.png http://www.mydomain.com/pictures/city/strasbourg.png http://www.mydomain.com/picturesnew/city/strasbourgh.png.

For param value /pictures/city/strasbourg.png: Only requests for this asset are allowed.
ec_proto_allow Only allow requests from the specified protocol. e.g. http, https, or http,https.
ec_clientip Restricts access to the specified requester's IP address. Both IPV4 and IPV6 are supported.

Azure uses symmetric encryption to encrypt the parameters to create the token. Then the same key is used by CDN to decrypt the token and then view the token params, and eventually serve requests based on those token params.

First. applications have to set up encryption parameters and generate a token/s. Then distribute those to consumers based onApp's internal authorization logic after he/she signs in using username/password (i.e., What privilege a user has, so give the user a token which includes a specific path set as the value of ec_url_allow). Here is an Example request URL from Azure CDN docs http://www.domain.com/content.mov?a4fbc3710fd3449a7c99986b. So when CDN gets a token along the request, it decrypts it and then applies the rules embedded In the token, and then serves/denies the request based on those rules.

IBM CDN token format

IBM Cloud CDN authentication token format uses a similar scheme used by Azure CDN, that is, generating the token by encrypting the parameter values. Looking in detail, it seems like IBM CDN does not have it does not have its own CDN infrastructure, and underlying, it uses Akamai's CDN services; in fact, the Akamai client library for token generation logic can be used by IBM client applications

Here is the list of token param options in the case of IBM/Akamai CDN

Parameter Description
acl_path Comma separated list of paths allowed by the token e.g. ["/akamai/edgeauth/??", "/akamai/edgeauth/list/*"]
start_time What is the start time? (Use string 'now' for the current time)
end_time When does this token expire? endTime overrides windowSeconds

AWS Cloudfront token format

CloudFront uses a signed URLs/cookies approach using public-private pairs instead of encrypted tokens approach as described above. From the AWS docs:

When you create a signed URL, you write a policy statement in JSON format that specifies the restrictions on the signed URL

Example of a sample policy:

{
    "Statement": [
        {
            "Resource": "URL or stream name of the file",
            "Condition": {
                "DateLessThan": {
                    "AWS:EpochTime": required ending date and time in Unix time format and UTC
                },
                "DateGreaterThan": {
                    "AWS:EpochTime": optional beginning date and time in Unix time format and UTC
                },
                "IpAddress": {
                    "AWS:SourceIp": "optional IP address"
                }
            }
        }
    ]
}

The Couldfront signed request URL consists of the following parts:

URL component Description
(1) Base Url Base URL for the file
(3) Query Params query string parameters, if any
(4) Policy base64 encoded version of policy statement
(5) Signature hashed and signed version of the policy statement
(6) Key-Pair-Id public key ID for the CloudFront public key whose corresponding private key you're using to generate the signature

So, an example URL would be as follows:

image

Here is an excellent description of the complete token authentication workflow from the docs:

  1. In your CloudFront distribution, specify one or more trusted key groups, which contain the public keys that CloudFront can use to verify the URL signature. You use the corresponding private keys to sign the URLs.
  2. Develop your application to determine whether a user should have access to your content and create signed URLs for the files or parts of your application that you want to restrict access to.
  3. A user requests a file for which you want to require signed URLs.
  4. Your application verifies that the user is entitled to access the file: they've signed in, they've paid for access to the content, or they've met some other requirement for access.
  5. Your application creates and returns a signed URL to the user.
  6. The signed URL allows the user to download or stream the content.
  7. This step is automatic; the user usually doesn't have to do anything additional to access the content. For example, if a user is accessing your content in a web browser, your application returns the signed URL to the browser. The browser immediately uses the signed URL to access the file in the CloudFront edge cache without any intervention from the user.
  8. CloudFront uses the public key to validate the signature and confirm that the URL hasn't been tampered with. If the signature is invalid, the request is rejected.
  9. If the signature is valid, CloudFront looks at the policy statement in the URL to confirm that the request is still valid. For example, if you specified a beginning and ending date and time for the URL, CloudFront confirms that the user is trying to access your content during the time period that you want to allow access.

What sort of state is needed on the CDN side to do the verification? and how is this state managed? is there interaction between CDN and app side in some way, either up front or ongoing?

I didn't find much info on the design and internal working of the CDN, as most of the information is available for Application developers as the target audience. However, based on the token format as described above, it seems that the only state that is needed on the CDN side is the client-configured key which will be used to verify the token content. So setting up this state requires upfront interaction between the app & CDN, and eventually, whenever the app has to do the CDN keys rotation/upgradation, etc.

@bedeho
Copy link
Member

bedeho commented Nov 16, 2022

First off, thank you, very well done analysis.

General

What is pretty clear is that the policies used are all

(a) Non-interactive between CDN provider and content authority infrastructure, both per access and per new app user entry.
(b) Verifiable in a stateless way, or near so. Only exception is remembering public key of content authority. Since at-scale signature verification is actually quite expensive, it could also be that the CDN node chooses to maintain an in-memory efficient lookup of already verified token signatures.

It seems like the original impetus for moving to token-based authentication was to avoid per-user scaling cost of session based authentication on the provider side

In the past, session-based authentication solved this issue. The server would create a session for the user after the user had successfully logged in. It would accomplish this by creating a session id which would store the relevant information in a cookie on the user’s browser and in the server’s memory. Each time the user made a request to the site, the session id would be sent along with it and compared to the session id on the server which solved the issue of state. However, as more users started accessing the same website, servers needed to manage the sessions of thousands or even millions of users. This influx of traffic resulted in scalability issues as web servers required an inordinate amount of memory to cope with managing millions of sessions.

Notice here that the problem is the memory footprint of doing the authentication of each request became too large. This means they were already not accepting the use of an on-disk database for holding this state, let alone a third party (QN) server with such a database, to be consulted per request. This is presumably because the per-request latency involved would be too large. Latency is indeed the key determinant to having Atlas give a good UX when rendering rich scenes with lots of assets. So I think we can add the following constraints

(c) server side verification must be memory-only and not scale with request volume, at worst, if not stateless.
(d) per-request must be non-interactive between client and server.

Problem

We are imagining an access policy which is sensitive to

  1. Your personae:
    1.1. Channel collaborators: currently just members
    1.2. Channel owner: content WG worker, lead or raw member
    1.3. Content lead: content WG worker
    1.4. Council member: currently just members
    1.5. Curator group member: content WG worker
    1.6. NFT owner: member
    1.7. CRT owner: member
    1.8. Raw member
    1.9. Gateway user: need not have any on-chain footprint
  2. Permissions of the data object you are attempting to fetch, e.g using a very flexible policy scheme like: Standardise Argus access policy signals in metadata #4332 (comment).
  3. Involvement of curators: Argus: screen censored content #4326

and at the same time satisfies (a),(d) and either (b) or (c), where (c) is a second best.

The by far most important case is personae 1.9, as this is where the UX really counts.

There is also a subsidiary question of the trust model, e.g. can we trust gateways blindly to just tell Argus nodes that a given gateway user indeed also has a CRT or NFT? If we can do this blindly, then that helps allow Argus from having to look at, and maintain, any memeory state that pertains to the content directory. We have also discussed the prospect of having a dedicated lead controlled auth server.

@traumschule
Copy link
Collaborator

Because it wasn't mentioned before

The Filecoin network ensures that data is safely stored. However, the processes of storing, verifying, and unsealing (referred to as sealing, proving, and retrieving, respectively) are computationally expensive and can take time. This is especially relevant for the retrieval of data, which should happen as fast as possible. For this reason, Filecoin enables an additional retrieval market where dedicated nodes can help quickly deliver content from the network for payment by keeping unsealed, cached copies. This delivery mechanism may make use of IPFS but is still in design. See ResNetLab’s Decentralized Data Delivery Market research for more information on delivery mechanisms.

Filecoin aims to add longer-term persistence to safely store large batches of data, while IPFS optimizes for the quick retrieval and distribution of content.

There are more listed here.

@Lezek123
Copy link
Contributor

Lezek123 commented Nov 29, 2022

After reading through this issue and #4332 (comment) I'm convinced that there is actually a good use-case for introducing a separate authentication node managed by a working group lead.

However, I think it only pays off if we make some non-trivial changes to Argus aswell.

What I imagine is that the lead node would be responsible for authenticating for very specific roles, like:

  • ChannelOwner(channel_id)
  • ChannelCollaborator(channel_id)
  • NftOwner(channel_id, nft_value, acquire_date)
  • CrtOwner(channel_id, amount, acquire_date)

And then if the authentication is successful, it would return a signed token that attests for a specific set of roles (the client would have to specify which roles they wish to authenticate for).

This token should be relatively short-lived, because the chain state w.r.t. what roles are available for a given member can change very frequently. However it should also be long-lived enough so that the authentication doesn't become a burden and slow down the page loads significantly. I think something like 15-30 minutes could be a good compromise.

Of course users can occupy hundreds of roles at a given time, so the client application will have to choose which roles to authenticate for and when + probably manage a local cache of tokens for different roles/sets of roles and choose which token to use depending on the asset being requested.

On the Argus side, I imagine it to be storing access policy for each asset in-memory in a structure like:

channelId => videoId => assetIds
assetId => { isPublic, accessPolicy, channelId, videoId, possiblySomeOtherUsefulData }

The first double-map would serve as a way to avoid unnecessary iterations over all assets in case the channel/video status changes.
If we consider each asset has ~30 bytes of data assigned, it should cost somewhere between 300 and 600 MB of memory to store 10 mln assets, which seems acceptable.

In order to keep this structure up to date, ideally Argus would have it's own on-chain events processor, alternatively to make things simple initially, it could subscribe to postgresql database updates made by the current Hydra processor.

Upon request for an asset Argus would then be able to very quickly execute the following steps:

  1. Verify that the access token was signed by the lead (on my home PC single signature verification takes just about 0,4 ms)
  2. Retrieve access policy of an asset from memory (almost instant)
  3. Verify that the client is allowed to access the content given the roles (signed by the lead) and the policy (also almost instant)
  4. Serve the asset / Reject the request

@bedeho
Copy link
Member

bedeho commented Jan 6, 2023

After reading through this issue and #4332 (comment) I'm convinced that there is actually a good use-case for introducing a separate authentication node managed by a working group lead.

Great! Let's start with the most important consideration: what should we call it 🤣

However, I think it only pays off if we make some non-trivial changes to Argus aswell.

This looks solid, I must admit I am a bit rusty on the full context here, but looks very plausible. What changes would be needed to accomodate 1.9, which arguably is the most important one?

ideally Argus would have it's own on-chain events processor

Why not just QN? If its for speed of processing, then are you thinking of a new Subsquid node?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
argus Argus distributor node
Projects
None yet
Development

No branches or pull requests

4 participants