Skip to content

Authentication and Namespaces

Shaken Fist uses JWT tokens for authentication and access control. These tokens are created with a request to the REST API and then passed as part of subsequent calls in the form of a HTTP header on the request. The tokens can expire, in which case a caller needs to re-authenticate and then retry their request. The process to create and use a token is discussed further in the Authentication section below.

Shaken Fist logically divides objects into "namespaces". These namespaces can be thought of as tenants, although there might be other reasons to divide resources into their own buckets -- for example the Shaken Fist CI system uses a namespace to store an archive of the images used for CI runs, and that namespace is referred to by the namespaces actually running tests. The process to create a namespace is discussed in the Creating namespaces section below.

Namespaces

All resources (instances, networks, network interfaces, and artifacts) are assigned to a namespace. Notably, blobs are not within namespaces and more than one artifact can refer to a given blob even if those artifacts are in different namespaces. It is assumed that knowing the UUID of a given blob implies that you can access it.

All requests to Shaken Fist have a namespace context. The namespace "system" is reserved and is used for administrative actions. Please note that the authentication configuration created by the getsf installer is for the system namespace, and if used directly will result in instances and other objects being created in that namespace. While this is supported and will function as expected, it is probably undesirable for anything other than a single user installation.

By default only requests in the system namespace are able to access resources in other (foreign) namespaces. Before Shaken Fist v0.7 this behavior was hard coded and not configurable. As of Shaken Fist v0.7, this is implemented in the form of "trusts", where every namespace is configured to "trust" the system namespace. This makes the resources visible to the system namespace. You cannot remove the trust of the system namespace from your namespaces. However, you can choose to trust additional namespaces, and this is done via the sf-client namespace trust ... series of commands and associated API calls.

Authentication

When the getsf installer ran, it created two authentication artifacts on the primary node which are useful to get started with Shaken Fist. First off, there is /etc/sf/sfrc, which is a file you can source in your shell to provide authentication environment variables. These environment variables can be used by Shaken Fist command line clients, Ansible modules, and the Python API client implementation itself. An example sfrc looks like this:

# Command line hinting
eval "$(_SF_CLIENT_COMPLETE=bash_source sf-client)"

# Use the v3 etcd API
export ETCDCTL_API=3

# Client auth
export SHAKENFIST_NAMESPACE="system"
export SHAKENFIST_KEY="oisoSe7T"
export SHAKENFIST_API_URL="https://shakenfist/api"

The first two lines of the file enable tab completion for sf-client in a bash shell. The middle section defaults etcd to use the v3 API and can be ignored for now. The last three lines are the important authentication details:

  • the namespace we want to use is called "system".
  • our access key is "oisoSe7T".
  • the URL the API exists at is "https://shakenfist/api"

sfrc is only useful to users of Unix-like shells, so there is also a JSON form of this configuration information, which is written by getsf at /etc/sf/shakenfist.json. Here's an example:

{
    "namespace": "system",
    "key": "oisoSe7T",
    "apiurl": "https://shakenfist/api"
}

The Shaken Fist command line clients, Ansible modules, and the Python API client will look for configuration in the following locations:

  • environment variables.
  • .shakenfist in your home directory, that is ~/.shakenfist.
  • /etc/sf/shakenfist.json.

Creating namespaces

You can create your first namespace like this, assuming you are authenticated as the system namespace:

sf-client namespace create new-namespace

By default a new namespace has no access keys or trusts configured, and therefore is only accessible to users of the system namespace.

Key management

Namespaces are accessed by providing a valid "key" for the namespace. While keys have names, they do not have to be usernames and passwords -- my mental model is more like API access tokens in something like GitHub than usernames and passwords. I tend to create a new key for each program which is interacting with the namespace, and then give it a descriptive name.

You can create a new key like this:

sf-client namespace add-key namespace-name keyname key

There can be more than one key for a namespace. The key name is not used as part of the authentication process, and is largely used for key management (deleting the key) and logging which access token was used in the event logs.

Info

Please note the key prefix "_service_key" is reserved for internal use within Shaken Fist. This usage is discussed in the Inter-node Authentication section below.

Authenticating directly to the REST API

The authentication endpoint /auth is used to obtain a token to authenticate future API requests. For example, I can obtain an authentication token from the REST API using curl like this:

curl -X POST https://shakenfist/api/auth -d '{"namespace": "system", "key": "oisoSe7T"}'
{
    "access_token": "eyJhbG...IkpXVCJ9.eyJmc...wwQ",
    "token_type": "Bearer",
    "expires_in": 900
}

That is, a HTTP POST request to the /auth endpoint for the REST API (in our case hosted at https://shakenfist/api) with a JSON body containing a dictionary of the namespace name and the key to use.

In the response the access_token value of eyJhbG...IkpXVCJ9.eyJmc...wwQ is our JWT token and has been truncated in this example for readability. Authentication tokens expire after a fixed period of time (nominally 15 minutes), but you will be informed that the token as expired by receiving a 401 Unauthorized response. If that occurs, simply create a new token as above and retry your request.

Subsequent requests to the REST API pass the token via an Authorization HTTP header, and should request a Content-Type of application/json. For example, to list the namespaces in our deployment we would make a curl request like this:

curl -X GET https://shakenfist/api/auth/namespaces \
    -H 'Authorization: Bearer eyJhbG...IkpXVCJ9.eyJmc...wwQ' \
    -H 'Content-Type: application/json'
[
    {
        "name": "adhoc",
        "state": "created",
        "trust": {"full": ["system"]}
    }, {
        "name": "ci",
        "state": "created",
        "trust": {"full": ["system"]}
    }, {
        "name": "system",
        "state": "created",
        "trust": {"full": ["system"]}
    }
]

The JSON response here has been formatted for readability.

Info

Note the word "Bearer" before the access token in the Authorization header.

Contents of the JWT tokens

JWT authentication tokens are base64 encoded parts separated by the . character. They are therefore trivial to decode. A decoded example (generated by the online decoder at https://jwt.io/) is:

{
    "alg": "HS256",
    "typ": "JWT"
}
.
{
    "fresh": false,
    "iat": 1669786988,
    "jti": "906f4bfa-3218-4d07-a036-ac6b44ded67e",
    "type": "access",
    "sub": [
        "system",
        "deploy"
    ],
    "nbf": 1669786988,
    "exp": 1669787888,
    "iss": "shakenfist",
    "nonce": "ByKNRUVBfMBoQC1Z"
}
.
HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    your-256-bit-secret
)

You can see here that Shaken Fist stores the authenticated namespace system and the key used to authenticate deploy under the sub key in this token. You should not assume that the content of JWT tokens produced by Shaken Fist are opaque to users.

For releases prior to v0.7, the token was blindly trusted for authentication. From v0.7 we verify that the named key still exists in the namespace before authorizing API requests. This test is performed by updating a "nonce" value for a given key when the key is updated. The JWT token a caller is handed includes this nonce, and if the nonce we are handed on a request does not match the current value in the database the request is rejected.

Inter-node Authentication

Requests between Shaken Fist nodes use the same authentication system and REST API as external API requests. When a node makes an API request to another node, the originating node will create (or reuse) a "service key" specific to the namespace of the original request.

When a request is made from the "system" namespace for a resource in a different namespace, the API request is made using the foreign namespace and the foreign namespace's service key.

Service keys exist in the namespace's key data structures just as other keys do, and are therefore visible when you list keys. As of v0.7, service keys expire after five minutes, and are never reused. Before v0.7 service keys were always named "_service_key". From v0.7 service keys have a name of the form "_service_key[a-zA-Z]+".

Key Storage

Shaken Fist stores the access keys in etcd. The keys are stored as the base64 encoding of the key post salting and hashing. The python bcrypt library is used to perform salting, hashing, and key verification.