Provider Authentication

API: Experimental/Alpha

UCloud/Core and Providers authenticate each-other using short-lived JWTs and long-lived opaque refresh-tokens.

Communication

All communication between UCloud and a provider is done via an HTTP(S) API, certain optional endpoints use a WebSocket API. Note that the WebSocket protocol is an extension of HTTP, as a result the WebSocket API shares the same security aspects as the HTTP API.

TLS is strictly required for all providers running in a production environment. The provider must use a valid certificate, which hasn’t expired and signed by a commonly recognized Certificate Authority (CA). TLS for HTTPS connections are handled internally in UCloud by OpenJDK11+. Notably, this means that TLSv1.3 is supported. We encourage providers to follow best practices. For inspiration, Mozilla hosts an online SSL configuration generator. Additionally, this document from SSL Labs can provide a good starting point.

Providers should treat UCloud similarly. An integration module should ensure that all certificates served by UCloud are valid and signed by a commonly recognized CA.

For local development purposes only UCloud can communicate with a local provider using HTTP. It is not possible to configure UCloud to use self-signed certificates, and as a result it is not possible to run a local provider with a self-signed certificate + TLS. This design choice has been made to simplify the code and avoid poorly configured UCloud deployments.

Authentication and Authorization

UCloud and the provider authenticates and authorizes all ingoing requests. Short-lived JSON Web Tokens (JWT) protect these requests.

Token Type Description
accessToken JWT A short-lived JWT token for authenticating regular requests
refreshToken Opaque An opaque token, with no explicit expiration, used for requesting new accessTokens

Table: The two token types used in UCloud ↔ Provider authentication

Because JWTs are short-lived, every provider must renew their JWT periodically. Providers do this by using an opaque token called the refreshToken. The diagram below shows how a provider can use their refreshToken to generate a new accessToken.

Figure: A provider requesting a new accessToken using their refreshToken

All calls use the HTTP bearer authentication scheme . As a result the refreshToken will be passed in the Authorization header like this:

HTTP/1.1 POST https://cloud.sdu.dk/auth/refresh
Authorization: Bearer $refreshToken

The accessToken is passed similarly:

HTTP/1.1 POST https://cloud.sdu.dk/some/call
Authorization: Bearer $accessToken

Internals of accessTokens

In this section we describe the internals of the accessToken and how to verify an accessToken from UCloud. It is important that all providers authenticate every request they receive.

The payload of an accessToken is follows the same schema for both UCloud and providers.

{
  // Token properties
  "iat": 1234,
  "exp": 5678,
  "iss": "cloud.sdu.dk",
   
  // Authorization properties
  "role": "<ROLE>", // "SERVICE" if the token authenticates UCloud. "PROVIDER" if the token authenticates a provider.

  // User metadata
  "sub": "<USERNAME>" // A unique identifier for the provider or "_UCloud" if the token authenticates UCloud.
}

All JWTs signed by UCloud will use the RS256 algorithm, internally this uses RSASSA-PKCS1-v1_5 with SHA-256 used for the signature. UCloud uses a unique private & public keypair for every provider. The provider receives UCloud’s public key when the refreshToken of the provider is issued. UCloud will generate a new keypair if the provider’s refreshToken is revoked.

Verifying accessTokens

As a provider, you must take the following steps to verify the authenticity of an accessToken:

  1. Verify that the accessToken is signed with the RS256 algorithm (alg field of the JWT header)

  2. Verify that the sub field is equal to "_UCloud" (Note the ‘_’ prefix)

  3. Verify that the iat (issued at) field is valid by comparing to the current time (See RFC7519 Section 4.1.6)

  4. Verify that the exp (expires at) field is valid by comparing to the current time (See RFC7519 Section 4.1.4)

  5. Verify that the iss (issuer) field is equal to "cloud.sdu.dk"

  6. Verify that the role field is equal to SERVICE

It is absolutely critical that JWT verification is configured correctly. For example, some JWT verifiers are known for having too relaxed defaults, which in the worst case will skip all verification. It is important that the verifier is configured to only accept the parameters mentioned above.

Extending the Protocol to Support Verification of Client


📝 NOTE: Very informal draft.


Some quick thoughts about how we can extend the protocol to support verification of the client sending the message. This is extremely useful as it would make UCloud incapable of impersonating a user at a different provider.

  1. Extend client-side RPC to include a signature of their message

    • This message should use public-private keypairs

    • Keypairs are generated in the browser

    • The public key is transferred to the provider by the user

    • This will happen when the user connects to the provider

    • Possible library: https://github.com/kjur/jsrsasign

    • This will support keygen and signature we need

    • The signature should be passed in a header

  2. UCloud receives this message, and extends it with additional information

  3. UCloud includes, verbatim, the original request along with the signature

    • How do we make this developer friendly?

  4. Provider verifies that the JWT is valid (by UCloud)

  5. Provider verifies that the original request has been signed

  6. Provider verifies that the original request also matches the additional context that UCloud provided

A possible alternative to signing the entire message is to sign a JWT (or any other document, we don’t really need the header). This JWT would provide similar security, the document should contain:

  • iat: Issued at

  • exp: Expires at

  • sub: Username (UCloud)

  • project: Project (UCloud)

  • callName: Potentially, this could contain the name of the call we are performing. This would allow the provider to verify that the correct call is also being made.

Table of Contents

1. Examples
Description
A Provider authenticating with UCloud/Core
2. Remote Procedure Calls
Name Description
retrievePublicKey No description
claim No description
generateKeyPair Generates an RSA key pair useful for JWT signatures
refresh No description
refreshAsOrchestrator Signs an access-token to be used by a UCloud service
register No description
renew No description
3. Data Models
Name Description
PublicKeyAndRefreshToken No description
RefreshToken No description
AuthProvidersRefreshAsProviderRequestItem No description
AuthProvidersRegisterRequestItem No description
AuthProvidersGenerateKeyPairResponse No description
AuthProvidersRegisterResponseItem No description
AuthProvidersRetrievePublicKeyResponse No description

Example: A Provider authenticating with UCloud/Core

Frequency of useCommon
Pre-conditions
  • The provider has already been registered with UCloud/Core
Actors
  • The UCloud/Core service user (ucloud)
  • The provider (provider)
Communication Flow: Kotlin
/* 📝 Note: The tokens shown here are not representative of tokens you will see in practice */

AuthProviders.refresh.call(
    bulkRequestOf(RefreshToken(
        refreshToken = "fb69e4367ee0fe4c76a4a926394aee547a41d998", 
    )),
    provider
).orThrow()

/*
BulkResponse(
    responses = listOf(AccessToken(
        accessToken = "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIjUF9leGFtcGxlIiwicm9sZSI6IlBST1ZJREVSIiwiaWF0IjoxNjMzNTIxMDA5LCJleHAiOjE2MzM1MjE5MTl9.P4zL-LBeahsga4eH0GqKpBmPf-Sa7pU70QhiXB1BchBe0DE9zuJ_6fws9cs9NOIo", 
    )), 
)
*/
Communication Flow: TypeScript
/* 📝 Note: The tokens shown here are not representative of tokens you will see in practice */

// Authenticated as provider
await callAPI(AuthProvidersApi.refresh(
    {
        "items": [
            {
                "refreshToken": "fb69e4367ee0fe4c76a4a926394aee547a41d998"
            }
        ]
    }
);

/*
{
    "responses": [
        {
            "accessToken": "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIjUF9leGFtcGxlIiwicm9sZSI6IlBST1ZJREVSIiwiaWF0IjoxNjMzNTIxMDA5LCJleHAiOjE2MzM1MjE5MTl9.P4zL-LBeahsga4eH0GqKpBmPf-Sa7pU70QhiXB1BchBe0DE9zuJ_6fws9cs9NOIo"
        }
    ]
}
*/
Communication Flow: Curl
# ------------------------------------------------------------------------------------------------------
# $host is the UCloud instance to contact. Example: 'http://localhost:8080' or 'https://cloud.sdu.dk'
# $accessToken is a valid access-token issued by UCloud
# ------------------------------------------------------------------------------------------------------

# 📝 Note: The tokens shown here are not representative of tokens you will see in practice

# Authenticated as provider
curl -XPOST -H "Authorization: Bearer $accessToken" -H "Content-Type: content-type: application/json; charset=utf-8" "$host/auth/providers/refresh" -d '{
    "items": [
        {
            "refreshToken": "fb69e4367ee0fe4c76a4a926394aee547a41d998"
        }
    ]
}'


# {
#     "responses": [
#         {
#             "accessToken": "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIjUF9leGFtcGxlIiwicm9sZSI6IlBST1ZJREVSIiwiaWF0IjoxNjMzNTIxMDA5LCJleHAiOjE2MzM1MjE5MTl9.P4zL-LBeahsga4eH0GqKpBmPf-Sa7pU70QhiXB1BchBe0DE9zuJ_6fws9cs9NOIo"
#         }
#     ]
# }
Communication Flow: Visual

Remote Procedure Calls

retrievePublicKey

API: Internal/Beta Auth: Services

Request Response Error
FindByStringId AuthProvidersRetrievePublicKeyResponse CommonErrorMessage

claim

API: Internal/Beta Auth: Services

Request Response Error
BulkRequest<AuthProvidersRegisterResponseItem> BulkResponse<PublicKeyAndRefreshToken> CommonErrorMessage

generateKeyPair

API: Internal/Beta Auth: Services

Generates an RSA key pair useful for JWT signatures

Request Response Error
Unit AuthProvidersGenerateKeyPairResponse CommonErrorMessage

Generates an RSA key pair and returns it to the client. The key pair is not stored or registered in any way by the authentication service.

refresh

API: Experimental/Alpha Auth: Public

Request Response Error
BulkRequest<RefreshToken> BulkResponse<AccessToken> CommonErrorMessage

refreshAsOrchestrator

API: Internal/Beta Auth: Services

Signs an access-token to be used by a UCloud service

Request Response Error
BulkRequest<AuthProvidersRefreshAsProviderRequestItem> BulkResponse<AccessToken> CommonErrorMessage

This RPC signs an access-token which will be used by authorized UCloud services to act as an orchestrator of resources.

register

API: Internal/Beta Auth: Services

Request Response Error
BulkRequest<AuthProvidersRegisterRequestItem> BulkResponse<AuthProvidersRegisterResponseItem> CommonErrorMessage

renew

API: Experimental/Alpha Auth: Services

Request Response Error
BulkRequest<FindByStringId> BulkResponse<PublicKeyAndRefreshToken> CommonErrorMessage

Data Models

PublicKeyAndRefreshToken

API: Internal/Beta

data class PublicKeyAndRefreshToken(
    val providerId: String,
    val publicKey: String,
    val refreshToken: String,
)
Properties
providerId: String
publicKey: String
refreshToken: String

RefreshToken

API: Internal/Beta

data class RefreshToken(
    val refreshToken: String,
)
Properties
refreshToken: String

AuthProvidersRefreshAsProviderRequestItem

API: Internal/Beta

data class AuthProvidersRefreshAsProviderRequestItem(
    val providerId: String,
)
Properties
providerId: String

AuthProvidersRegisterRequestItem

API: Internal/Beta

data class AuthProvidersRegisterRequestItem(
    val id: String,
)
Properties
id: String

AuthProvidersGenerateKeyPairResponse

API: Internal/Beta

data class AuthProvidersGenerateKeyPairResponse(
    val publicKey: String,
    val privateKey: String,
)
Properties
publicKey: String
privateKey: String

AuthProvidersRegisterResponseItem

API: Internal/Beta

data class AuthProvidersRegisterResponseItem(
    val claimToken: String,
)
Properties
claimToken: String

AuthProvidersRetrievePublicKeyResponse

API: Internal/Beta

data class AuthProvidersRetrievePublicKeyResponse(
    val publicKey: String,
)
Properties
publicKey: String