Skip to content

Commit

Permalink
security: update the SSO process
Browse files Browse the repository at this point in the history
After receiving a privsec breach report, we updated our SSO process.
This fixes the main issues:
- Add-on APIs using the example password and sso_salt from this
  documentation
- The `token` is not signing all the fields that we send to the SSO URL.
  You can impersonate a user by using their email.

The email has never be documented as a secure identification method, but
some providers used it anyway. It’s best to acknowledge the breach and
sign the whole body.
  • Loading branch information
judu committed Feb 10, 2025
1 parent 46ee263 commit 799a499
Showing 1 changed file with 30 additions and 28 deletions.
58 changes: 30 additions & 28 deletions content/doc/marketplace/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ First, provide a JSON manifest file that describes your add-on:
"api": {
"config_vars": [ "ADDON_NAME_MY_VAR" ],
"regions": [ "eu" ],
"password": "44ca82ddf8d4e74d52494ce2895152ee",
"sso_salt": "fcb5b3add85d65e1dddda87a115b429f",
"password": "<YOUR BEST RANDOM 35+ CHARS>",
"sso_salt": "<YOUR VERY BEST RANDOM 35+ CHARS>",
"production": {
"base_url": "https://yourservice.com/clevercloud/resources",
"sso_url": "https://yourservice.com/clevercloud/sso/login"
Expand Down Expand Up @@ -81,7 +81,6 @@ Request Body: {
"plan": "basic",
"region": "EU",
"callback_url": "https://api.clever-cloud.com/v2/vendor/apps/addon_xxx",
"logplex_token": "logtoken_yyy",
"options": {}
}
Response Body: {
Expand All @@ -106,7 +105,6 @@ the Clever Cloud platform. We send you the slug of the given plan,
not its name.
* `region` - The region to provision the add-on. As for now, only "EU" will be sent.
* `callback_url` - The URL you can use to get details about the add-on and the user. This URL is available as soon as the provisioning is done. You can't use this URL during the POST call.
* `logplex_token` - Deprecated, don't use it.
* `options` - String -> String map with options.
The response body contains the following fields:
* `id` - The add-on id as seen from your side. It *MUST* be a String.
Expand Down Expand Up @@ -299,7 +297,7 @@ def check_auth(username, password):
"""This function is called to check if a username /
password combination is valid.
"""
return password == '44ca82ddf8d4e74d52494ce2895152ee'
return password == '<THE PASSWORD>'

def authenticate():
"""Sends a 401 response that enables basic auth"""
Expand All @@ -320,55 +318,59 @@ def requires_auth(f):

## SSO

Your service probably has a web UI admin panel that your users log into to manage and view their resources. Clever Cloud customers will be able to access the admin panel for their resource if you implement single sign-on (SSO).
Your service probably has a web UI admin panel that your users log into to manage and view their resources.

Check warning on line 321 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L321

[Google.WordList] Use 'administrator' instead of 'admin'.
Raw output
{"message": "[Google.WordList] Use 'administrator' instead of 'admin'.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 321, "column": 36}}}, "severity": "WARNING"}
Clever Cloud customers will be able to access the admin panel for their resource if you implement single sign-on (SSO).

Check warning on line 322 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L322

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 322, "column": 24}}}, "severity": "WARNING"}

Check warning on line 322 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L322

[Google.WordList] Use 'administrator' instead of 'admin'.
Raw output
{"message": "[Google.WordList] Use 'administrator' instead of 'admin'.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 322, "column": 51}}}, "severity": "WARNING"}

Check warning on line 322 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L322

[Google.WordList] Use 'single sign-on' instead of 'sign-on'.
Raw output
{"message": "[Google.WordList] Use 'single sign-on' instead of 'sign-on'.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 322, "column": 106}}}, "severity": "WARNING"}

Check notice on line 322 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L322

[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 322, "column": 115}}}, "severity": "INFO"}

Clever Cloud will generate a single sign-on token by combining the salt (a shared secret), timestamp, and resource ID. The user’s browser will be redirected to your site with this token. Your site can confirm the authenticity of the token, then set a cookie for the user session and redirect them to the admin panel for their resource.
Clever Cloud will generate a single sign-on signature by combining the salt (a shared secret you defined in your manifest) with the rest of the body (see below).

Check warning on line 324 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L324

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 324, "column": 14}}}, "severity": "WARNING"}

Check warning on line 324 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L324

[Google.WordList] Use 'single sign-on' instead of 'sign-on'.
Raw output
{"message": "[Google.WordList] Use 'single sign-on' instead of 'sign-on'.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 324, "column": 37}}}, "severity": "WARNING"}
Clever Cloud redirects the user’s browser to your SSO URL with this signature.

Check notice on line 325 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L325

[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 325, "column": 51}}}, "severity": "INFO"}
Your site can confirm the authenticity of the signature, then set a cookie for the user session and redirect them to the admin panel for their resource.

Check warning on line 326 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L326

[Google.WordList] Use 'administrator' instead of 'admin'.
Raw output
{"message": "[Google.WordList] Use 'administrator' instead of 'admin'.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 326, "column": 122}}}, "severity": "WARNING"}

When the user clicks your add-on in their add-on menu, they will be directed via HTTP POST to a URL defined in your manifest.
When the user opens your add-on dashboard in their add-on menu, they will be directed via HTTP POST to the SSO URL defined in your manifest.

Check warning on line 328 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L328

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 328, "column": 70}}}, "severity": "WARNING"}

Check notice on line 328 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L328

[Google.Passive] In general, use active voice instead of passive voice ('be directed').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('be directed').", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 328, "column": 75}}}, "severity": "INFO"}

Check notice on line 328 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L328

[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 328, "column": 108}}}, "severity": "INFO"}

```http
POST <production/sso_url>
Request Body: id=<id>&token=<token>&timestamp=<timestamp>&nav-data=<nav-data>&email=<email>
Content-Type: application/x-www-form-urlencoded
id=<id>&timestamp=<timestamp>&nav-data=<nav-data>&email=<email>&user_id=<user_id>&signature=<signature>
```

* The hostname or `sso_url` comes from your add-on manifest
* The `id` is the ID for the previously provisioned resource
* The `timestamp` is a millisecond timestamp. You *SHOULD* verify that it's not older than 15 minutes
* The `token` is computed using the formula below
* The `nav-data` contains information like the current app name and installed add-ons for Clever Cloud's Console.
* The `timestamp` is a millisecond timestamp. You *SHOULD* verify that it's not older than a few minutes (like 5)
* The `user_id` is a unique string identifying the current user on the Clever Cloud platform
* The `email` is the current primary email of the current user on the Clever Cloud platform
* The `nav-data` contains information like the current app name and installed add-ons for Clever Cloud's Console. At the time of writing this doc, this field is always empty
* The `signature` is computed using the formula below

Check notice on line 343 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L343

[Google.Passive] In general, use active voice instead of passive voice ('is computed').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is computed').", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 343, "column": 19}}}, "severity": "INFO"}

### Token

The token field in the SSO call, is created as follows:
The `signature` field in the SSO call is created as follows:

Check notice on line 347 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L347

[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.
Raw output
{"message": "[Google.Acronyms] Spell out 'SSO', if it's unfamiliar to the audience.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 347, "column": 30}}}, "severity": "INFO"}

Check notice on line 347 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L347

[Google.Passive] In general, use active voice instead of passive voice ('is created').
Raw output
{"message": "[Google.Passive] In general, use active voice instead of passive voice ('is created').", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 347, "column": 39}}}, "severity": "INFO"}

```javascript
sha1sum(id + ':' + sso_salt + ':' + timestamp)
sha512sum(id + ':' + user_id + ':' + email + ':' + nav-data + ':' + sso_salt + ':' + timestamp)
```

Where:

* `id` - The id of the connecting add-on. This is the id you returned on
the provision call.

* `sso_salt` - The `sso_salt` field defined in your manifest.

* `timestamp` - The timestamp field of the SSO request.
Where `sso_salt` is the shared secret you defined while registering in the marketplace.
The other fields are the url-decoded fields previously enumerated.

Check warning on line 354 in content/doc/marketplace/_index.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/doc/marketplace/_index.md#L354

[Google.WordList] Use 'URL' instead of 'url'.
Raw output
{"message": "[Google.WordList] Use 'URL' instead of 'url'.", "location": {"path": "content/doc/marketplace/_index.md", "range": {"start": {"line": 354, "column": 26}}}, "severity": "WARNING"}

### Sample in Python

```python
from hashlib import sha1
from hashlib import sha512
import time

id = "1234"
salt = "fcb5b3add85d65e1dddda87a115b429f"
salt = "<SOME RANDOM STRING>"
user_id = "user_cccdddee-efff-4445-5566-6777888999aa"
email = "[email protected]"
nav_data = ""
timestamp = str(time.time())
token = sha1(id + ':' + salt + ':' + timestamp).hexdigest()
print token
sig = sha512((id + ':' + user_id + ':' + email + ':' + nav_data + ':' + sso_salt + ':' + timestamp).encode("utf-8")).hexdigest()
print sig
```

This code returns:

```text
'aca601ba464437cbaa12b2fedd7db755c32ddb5e'
```python
'2a79420ccb4dccb2f18985da60393d1383d4ef4ac02cef3274a543bb3fe82d15e5dee19cbca753e8eac24e6383d332ef258daea6ea3340c3526af175329e7dd8'
```

0 comments on commit 799a499

Please sign in to comment.