geofront.server — Key management service

Although Geofront provides geofront-server, a CLI to run the server, it also provides an interface as a WSGI application as well. Note that there might some limitations like lack of periodical master key renewal.

First of all, the server need a configuration, there are several ways to configure it.

app.config.from_pyfile()

If you can freely execute arbitrary Python code before start the server, the method is the most straightforward way to configure the server. Note that the argument should be an absolute path, because it interprets paths relative to the path of Geofront program, not the current working directory (CWD).

There also are other methods as well:

GEOFRONT_CONFIG

If you can’t execute any arbitrary Python code, set the GEOFRONT_CONFIG environment variable. It’s useful when to use a CLI frontend of the WSGI server e.g. gunicorn, waitress-serve.

$ GEOFRONT_CONFIG="/etc/geofront.cfg.py" gunicorn geofront.server:app

Then you can run a Geofront server using your favorite WSGI server. Pass the following WSGI application object to the server. It’s a documented endpoint for WSGI:

geofront.server.AUTHORIZATION_TIMEOUT = datetime.timedelta(0, 60)

(datetime.timedelta) How long does each temporary authorization keep alive after it’s issued. A minute.

class geofront.server.FingerprintConverter(*args, **kwargs)

Werkzeug custom converter which accepts valid public key fingerprints.

class geofront.server.Token(identity, expires_at)

The named tuple type that stores a token.

expires_at

Alias for field number 1

identity

Alias for field number 0

class geofront.server.TokenIdConverter(*args, **kwargs)

Werkzeug custom converter which accepts valid token ids.

geofront.server.add_public_key(token_id: str)

Register a public key to the token. It takes an OpenSSH public key line through the request content body.

POST /tokens/0123456789abcdef/keys/ HTTP/1.1
Accept: application/json
Content-Type: text/plain

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAEMUvjBcX.../MuLLzC/m8Q==
HTTP/1.1 201 Created
Content-Type: text/plain
Location: /tokens/0123456789abcdef/keys/50:5a:9a:12:75:8b:b0:88:7d:7a:8d:66:29:63:d0:47

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAEMUvjBcX.../MuLLzC/m8Q==
Parameters:token_id (str) – the token id that holds the identity
Status 201:when key registration is successful
Status 400:(unsupported-key-type) when the key type is unsupported, or (invalid-key) the key format is invalid, or (deuplicate-key) the key is already used
Status 415:(unsupported-content-type) when the Content-Type is not text/plain
geofront.server.app = <Flask 'geofront.server'>

(flask.Flask) The WSGI application of the server.

geofront.server.authenticate(token_id: str)

Finalize the authentication process. It will be shown on web browser.

Parameters:token_id (str) – token id created by create_access_token()
Status 400:when authentication is failed
Status 404:when the given token_id doesn’t exist
Status 403:when the token_id is already finalized
Status 200:when authentication is successfully done
geofront.server.authorize_remote(token_id: str, alias: str)

Temporarily authorize the token owner to access a remote. A made authorization keeps alive in a minute, and then will be expired.

POST /tokens/0123456789abcdef/remotes/web-1/ HTTP/1.1
Accept: application/json
Content-Length: 0
HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": "authorized",
  "remote": {"user": "ubuntu", "host": "192.168.0.5", "port": 22},
  "expires_at": "2014-04-14T14:57:49.822844+00:00"
}
Parameters:
  • token_id (str) – the token id that holds the identity
  • alias (str) – the alias of the remote to access
Status 200:

when successfully granted a temporary authorization

Status 404:

(not-found) when there’s no such remote

geofront.server.create_access_token(token_id: str)

Create a new access token.

PUT /tokens/0123456789abcdef/ HTTP/1.1
Accept: application/json
Content-Length: 0
HTTP/1.1 202 Accepted
Content-Type: application/json
Date: Tue, 15 Apr 2014 03:44:43 GMT
Expires: Tue, 15 Apr 2014 04:14:43 GMT
Link: <https://example.com/login/page/?redirect_uri=...>; rel=next

{
  "next_url": "https://example.com/login/page/?redirect_uri=..."
}
Parameters:token_id (str) – an arbitrary token id to create. it should be enough random to avoid duplication
Status 202:when the access token is prepared
Resheader Link:the link owner’s browser should redirect to
geofront.server.delete_public_key(token_id: str, fingerprint: bytes)

Delete a public key.

DELETE /tokens/0123456789abcdef/keys/50:5a:9a:12:75:8b:b0:88:7d:7a:8d:66:29:63:d0:47/ HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "72:00:60:24:66:e8:2d:4d:2a:2a:a2:0e:7b:7f:fc:af":
    "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCom2CDLekY...5CeYsvSdrTWA5 ",
  "78:8a:09:c8:c1:24:5c:89:76:92:b0:1e:93:95:5d:48":
    "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA16iSKKjFHOgj...kD62SYXNKY9c= ",
  "ab:3a:fb:30:44:e3:5e:1e:10:a0:c9:9a:86:f4:67:59":
    "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzzF8c07pzgKk...r+b6Q9VnWWQ== "
}
Parameters:
  • token_id (str) – the token id that holds the identity
  • fingerprint (bytes) – the fingerprint of a public key to delete
Status 200:

when the public key is successfully deleted

Status 404:

(not-found) when there’s no such public key

geofront.server.get_identity(token_id: str) → geofront.identity.Identity

Get the identity object from the given token_id.

Parameters:token_id (str) – the token id to get the identity it holds
Returns:the identity the token holds
Return type:Identity
Raises:werkzeug.exceptions.HTTPException404 Not Found (token-not-found) when the token does not exist. 412 Precondition Failed (unfinished-authentication) when the authentication process is not finished yet. 410 Gone (expired-token) when the token was expired. 403 Forbidden (not-authorized) when the token is not unauthorized.
geofront.server.get_key_store() → geofront.keystore.KeyStore

Get the configured key store implementation.

Returns:the configured key store
Return type:KeyStore
Raises:RuntimeError – when 'KEY_STORE' is not configured, or it’s not an instance of KeyStore
geofront.server.get_master_key_store() → geofront.masterkey.MasterKeyStore

Get the configured master key store implementation.

Returns:the configured master key store
Return type:MasterKeyStore
Raises:RuntimeError – when 'MASTER_KEY_STORE' is not configured, or it’s not an instance of MasterKeyStore
geofront.server.get_permission_policy() → geofront.remote.PermissionPolicy

Get the configured permission policy.

Returns:the configured permission policy
Return type:PermissionPolicy
Raises:RuntimeError – if 'PERMISSION_POLICY' is not configured, or it’s not an instance of PermissionPolicy

New in version 0.2.0.

geofront.server.get_public_key(token_id: str, fingerprint: bytes) → paramiko.pkey.PKey

Internal function to find the public key by its fingerprint.

Parameters:
  • token_id (str) – the token id that holds the identity
  • fingerprint (bytes) – the fingerprint of a public key to find
Returns:

the found public key

Return type:

paramiko.pkey.PKey

Raises:

werkzeug.exceptions.HTTPException – (not-found) when there’s no such public key

geofront.server.get_remote_set() → typing.Mapping[str, geofront.remote.Remote]

Get the configured remote set.

Returns:the configured remote set
Return type:RemoteSet
Raises:RuntimeError – if 'REMOTE_SET' is not configured, or it’s not a mapping object
geofront.server.get_team() → geofront.team.Team

Get the configured team implementation, an instance of team.Team.

It raises RuntimeError if 'TEAM' is not configured.

geofront.server.get_token_store() → werkzeug.contrib.cache.BaseCache

Get the configured token store, an instance of werkzeug.contrib.cache.BaseCache.

It raises RuntimeError if 'TOKEN_STORE' is not configured, but it just warns RuntimeWarning when it comes to debug mode.

Returns:the configured session store
Return type:werkzeug.contrib.cache.BaseCache
Raises:RuntimeError – when 'TOKEN_STORE' is not configured, or the value is not an instance of werkzeug.contrib.cache.BaseCache
geofront.server.list_public_keys(token_id: str)

List registered keys to the token owner.

GET /tokens/0123456789abcdef/keys/ HTTP/1.1
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "50:5a:9a:12:75:8b:b0:88:7d:7a:8d:66:29:63:d0:47":
    "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAEMUvjBcX.../MuLLzC/m8Q== ",
  "72:00:60:24:66:e8:2d:4d:2a:2a:a2:0e:7b:7f:fc:af":
    "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCom2CDLekY...5CeYsvSdrTWA5 ",
  "78:8a:09:c8:c1:24:5c:89:76:92:b0:1e:93:95:5d:48":
    "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA16iSKKjFHOgj...kD62SYXNKY9c= ",
  "ab:3a:fb:30:44:e3:5e:1e:10:a0:c9:9a:86:f4:67:59":
    "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzzF8c07pzgKk...r+b6Q9VnWWQ== "
}
Parameters:token_id (str) – the token id that holds the identity
Status 200:when listing is successful, even if there are no keys
geofront.server.main()

The main function for geofront-server CLI program.

geofront.server.main_parser() → argparse.ArgumentParser

Create an ArgumentParser object for geofront-server CLI program. It also is used for documentation through sphinxcontrib-autoprogram.

Returns:a properly configured ArgumentParser
Return type:argparse.ArgumentParser
geofront.server.master_key()

Public part of the master key in OpenSSH authorized_keys (public key) format.

GET /masterkey/ HTTP/1.1
Accept: text/plain
HTTP/1.1 200 OK
Content-Type: text/plain

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAEMUvjBcX.../MuLLzC/m8Q==
Status 200:when the master key is available
Status 500:when the master key is unavailable
geofront.server.public_key(token_id: str, fingerprint: bytes)

Find the public key by its fingerprint if it’s registered.

GET /tokens/0123456789abcdef/keys/50:5a:9a:12:75:8b:b0:88:7d:7a:8d:66:29:63:d0:47/ HTTP/1.1
Accept: text/plain
HTTP/1.1 200 OK
Content-Type: text/plain

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAEMUvjBcX.../MuLLzC/m8Q==
Parameters:
  • token_id (str) – the token id that holds the identity
  • fingerprint (bytes) – the fingerprint of a public key to find
Status 200:

when the public key is registered

Status 404:

(not-found) when there’s no such public key

geofront.server.remote_dict(remote: geofront.remote.Remote) → typing.Mapping[str, typing.Union[str, int]]

Convert a remote to a simple dictionary that can be serialized to JSON.

Parameters:remote (Remote) – a remote instance to serialize
Returns:the converted dictionary
Return type:Mapping[Union[str, int]]
geofront.server.server_endpoint()

The endpoint of HTTP API which provide the url to create a new token.

GET / HTTP/1.1
Accept: application/json
HTTP/1.0 200 OK
Content-Type: application/json
Link: <https://example.com/tokens/>; rel=tokens
Link: <https://example.com/masterkey/>; rel=masterkey

{
  "master_key_url": "https://example.com/masterkey/",
  "tokens_url": "https://example.com/tokens/"
}
Resheader Link:the url to create a new token. the equivalent to the response content
Status 200:when the server is available

New in version 0.4.0: Added "master_key_url" field in the result and Link header of rel=masterkey.

New in version 0.2.0.

geofront.server.server_version(response: flask.wrappers.Response) → flask.wrappers.Response

Indicate the version of Geofront server using Server and X-Geofront-Version headers.

geofront.server.token(token_id: str)

The owner identity that the given token holds if the token is authenticated. Otherwise it responds 403 Forbidden, 404 Not Found, 410 Gone, or 412 Precondition Failed. See also get_identity().

GET /tokens/0123456789abcdef/ HTTP/1.1
Accept: application/json
HTTP/1.0 200 OK
Content-Type: application/json
Link: <https://example.com/tokens/0123456789abcdef/remo...>; rel=remotes
Link: <https://example.com/tokens/0123456789abcdef/keys/>; rel=keys
Link: <https://example.com/masterkey/>; rel=masterkey

{
  "identifier": "dahlia",
  "team_type": "geofront.backends.github.GitHubOrganization",
  "remotes_url": "https://example.com/tokens/0123456789abcdef/remotes/",
  "keys_url": "https://example.com/tokens/0123456789abcdef/keys/",
  "master_key_url": "https://example.com/masterkey/"
}
Parameters:token_id (str) – the token id that holds the identity
Resheader Link:the url to list remotes (rel=remotes), public keys (rel=keys), and master key (rel=masterkey)
Status 200:when the token is authenticated

Changed in version 0.2.0: The response contains "remotes_url", "keys_url", and "master_key_url", and equivalent three Link headers.

geofront.server.token_master_key(token_id: str)

Public part of the master key in OpenSSH authorized_keys (public key) format.

GET /tokens/0123456789abcdef/masterkey/ HTTP/1.1
Accept: text/plain
HTTP/1.1 301 Moved Permanently
Content-Type: text/plain

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAEMUvjBcX.../MuLLzC/m8Q==
Parameters:token_id (str) – the token id that holds the identity
Status 200:when the master key is available
Status 500:when the master key is unavailable

Deprecated since version 4.0.0: Use GET /masterkey/ instead.

Changed in version 4.0.0: It now responds with 301 Moved Permanently instead of 200 OK. It redirects to GET /masterkey/ which is the new master key url.

geofront.server.url_for(endpoint, **kwargs)

The almost same to flask.url_for() except it’s sensitive to PREFERRED_URL_SCHEME configuration.