github-ssh-auth


Namegithub-ssh-auth JSON
Version 1.0.2 PyPI version JSON
download
home_pagehttps://github.com/oorabona/github-ssh-auth
SummaryAuthenticate SSH users using their GitHub keys
upload_time2023-09-18 21:47:50
maintainerOlivier Orabona
docs_urlNone
authorOlivier Orabona
requires_python>=3.9
licenseGPL-3.0
keywords github ssh authentication
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # GitHub-SSH-Auth

[![CICD](https://github.com/oorabona/github-ssh-auth/actions/workflows/main.yml/badge.svg)](https://github.com/oorabona/github-ssh-auth/actions/workflows/main.yml)

[![PyPI](https://img.shields.io/pypi/v/github-ssh-auth)](https://pypi.org/project/github-ssh-auth/)
[![Supported Python
versions](https://img.shields.io/pypi/pyversions/github-ssh-auth.svg)](https://pypi.org/project/github-ssh-auth/)
[![Code style:
black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Downloads](https://pepy.tech/badge/github-ssh-auth/month)](https://pepy.tech/project/github-ssh-auth/)

## About

If you are familiar with `authorized_keys` file, each time you want to update keys, you have to first copy them using `ssh-copy-id` for example or any similar method.

But what happens if you fail to copy keys? You might be locked out of your box.

Same applies when you want to deploy your configuration on several machines, or when you want to update keys for a whole team.
All these tasks are tedious and error-prone.

`Ansible` or `Chef` can help but they are not always available or suitable for the task.
They usually require some kind of infrastructure to work, and are designed for the whole system, not only for `SSH` authentication of potentially *some* users.

Another possible solution would be to have some common infrastructure like `sssd` or `LDAP` to deport the authentication some place else, or to somewhat automagically update the keys upon valid logon credentials.

But what if you don't have such infrastructure? Or what if you don't want to have one?

This is where this tool can help you out !

## How it works

This project provides a way for `SSH` daemon (aka `sshd`) to authenticate users from your organization using their GitHub SSH keys.

`OpenSSH` has two options in its configuration file (e.g. `/etc/ssh/sshd.conf`), namely `AuthorizedKeysCommand` and `AuthorizedKeysCommandUser`. These two options are used to specify the command to run and the user to run it as.

Therefore here is another technique that can help in such scenarios.

Everytime a user connects, the script will be called with the login as command line parameter.

In detail, the following happens :

1. `sshd` deamon runs `github-ssh-auth` under the user defined by `AuthorizedKeysCommandUser` option.

2. `github-ssh-auth` reads its configuration file (by default `/etc/github-ssh/conf`)

3. according to the configuration file, it looks up the username given by `sshd` and checks if that user is granted permission to connect to this host

4. if yes, it tries to read the cache file (recommended but can be disabled) to find user's keys. If no cache file found or if disabled, then it queries GitHub API to get the keys and creates the cache (if enabled).

5. github-ssh-auth returns a list of eligible ssh public keys to standard output to be processed by `sshd`

The rest is handled by `sshd` itself, i.e. checking validity of that public key and the rest of the connection handling.

> It does not interfere with the rest of the system, including anything PAM related.

## Updating keys and cache use

To avoid flooding GitHub API and consequently being temporarily banned from using their API in case of massive connects, it is recommended to have cache enabled and update the keys only few times a day. The periodicity is yours and that is why there is a special `update` command line parameter for that.

Consider the following scenario:

- cache is enabled
- cache file already exists
- a new user has joined the team OR an existing user replaced his/her keys

In such case, the cache file will **NOT** be updated when authentication happens, this is the behavior set by design to separate concerns and prevent connection to the outside world being in the critical path for authentication.

Instead, a locally defined `cron` should either:

- call `github-ssh-update` to update cache
- delete cache file (by default `/etc/github-ssh/cache.json`) which will force recreation when next auth happens

Both will have the same outcome but the former is cleaner than the latter.

All in all, choice is yours :wink:

## Installation

Since this Python module deals with SSH authentication, it should be installed globally, hence:

```shell
sudo pip install github-ssh-auth
```

This will install the following program and its shortcuts in `/usr/local/bin`:

## github-ssh

The real application, handling all options, but for convenience the shortcuts described after can be used.

### Usage

```shell
Usage: github-ssh [OPTIONS] COMMAND [ARGS]...

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Commands:
  auth    Authenticate user.
  init    Initialize GitHub SSH Authentication configuration file.
  update  Update GitHub SSH Auth cache file (users, teams, keys).
```

## github-ssh-auth

Responsible for authentication itself, this one is to be called by `sshd` itself.

```shell
Usage: github-ssh-auth [OPTIONS] LOGIN

  Authenticate user.

Options:
  -c, --config FILE  Config file to use.  [default: /etc/github-ssh/conf]
  --help             Show this message and exit.
```

## github-ssh-init

This command initializes the configuration file.
It will also launch an editor of your choice (or the one specified in `EDITOR` environment variable) to edit the configuration file.

> Note: if the configuration file already exists, it will *NOT* be overwritten.

```shell
Usage: github-ssh-init [OPTIONS]

  Initialize GitHub SSH Authentication configuration file.

Options:
  -c, --config FILE  Config file to use.  [default: /etc/github-ssh/conf]
  -e, --editor FILE  Editor to use.  [default: vim]
  --help             Show this message and exit.
```

## github-ssh-update

Responsible for updating cache file, it can be scheduled to run periodicaly to ensure synchronization with updated keys from Github.

```shell
Usage: github-ssh-update [OPTIONS]

  Update GitHub SSH Auth cache file (users, teams, keys).

Options:
  -c, --config FILE  Config file to use.  [default: /etc/github-ssh/conf]
  --help             Show this message and exit.
```

> Note:
In some distros, `/usr/local/bin` is not eligible for `sshd` daemon because of some obscure group permissions reason. Moving the binaries (or create symlinks) from `/usr/local/bin` to `/usr/bin` make them work like a charm !

## Configuration

## SSH configuration in `/etc/ssh/sshd_config`

These lines should be somewhere in your `sshd` configuration file. Usually in `/etc/ssh/sshd_config` :

```shell
AuthorizedKeysCommand /usr/bin/github-ssh-auth %u
AuthorizedKeysCommandUser nobody
```

### What do they mean?

The first line tells `sshd` to run `github-ssh-auth` with the username as command line parameter.
As previously said, recent versions of `sshd` support `AuthorizedKeysCommand` option and only if command happens to live in `/usr/bin` not `/usr/local/bin`.

The second line tells `sshd` to run `github-ssh-auth` under `nobody` user. This is to prevent any possible privilege escalation.

## GitHub token requirements

Since this application is dealing with some sensitive data (users and their team memberships) within an organization, we will need to create a so-called `Personal access tokens`.

To do that, fire up your GitHub organization dashboard, look for `Settings` then `Developer settings`.

Then click on `Generate new token` and set its permissions to:
> read:org

This is the only requirement so that the API can be queried for users and teams memberships. All users keys are public by default and can be accessed from the outside world without authentication against GitHub API.

See for yourself, go to `https://github.com/<yourhandle>.keys`. :rocket:

## GitHub SSH Auth configuration file

It resides by default in `/etc/github-ssh/conf` but of course you can change it using `-c` flag when calling (see above).

The format is a standard INI style.

### Configuration file template

Below is the complete grammar with inline comments:

```ini
[global]
# Comments should start with a # and must be full lines
# The following two lines are mandatory
access_token = <token>
organization = <org>

# In case of connectivity lost and to prevent too many connections to GitHub API,
# it is strongly recommended to set it to an absolute filepath.
# Default (when not present) is set to /etc/github-ssh/cache.json and equivalent to:
# cache_file = /etc/github-ssh/cache.json

# If you want to disable, set it to 'false'
# cache_file = [/path/to/file | false]

# Unless overridden after, users (whether individual or teams) will have these configurations applied.
# By default, nothing is set so basically no one will be granted access
# The '<' case means that if a local exists with the same name as a GitHub user,
# it will be granted access. It is a shorthand to avoid a too
# complex, verbose yet common use case where every developer would
# like to have his/her own shell account.
teams_default = [ list,of,local,users,or,< ]
users_default = [ list,of,local,users,or,< ]

# Configuration below will override team defaults
[teams]
<team_name> = [ list,of,local,users,or,< ]
...

# And below to override default users setup
[users]
<user_name> = [ list,of,local,users,or,< ]
...
```

### Special note on the '<'

Just a quick focus on that special caracter, designed to allow a GitHub `user` to connect provided that:

- `user` is the Github handle, being part of the organization, **and**
- `user` exists in the local user base (i.e. it is present when you do a `getent passwd`)

#### Real world example

Let's say Acme Corp. wants all its developers connect with their GitHub accounts. Let's say users `jdoe` `bob` `alice` are such handles. Also these user handles are also the respective handlers in GitHub.

Then with this simple configuration, we allow globally all these users to login with their handles:

```ini
[global]
...other config skipped for brevety...

users_default = <
```

Now if you only want `bob` to connect using login `bob`, the configuration file would look like this one:

```ini
[global]
...other config skipped for brevety...

users_default =

[users]
bob = <
```

### Other real world examples

Some other ready-to-be-deployed-or-almost can be found in the `example` directory.

## Testing

As this is my first Python module, and even my first Python program ever, I tried different methods to handle testing.

Have had a lot of shortcomings four years ago when I started this project, now I found a way to test it properly using `tox` and `pytest`.
Of course code is `coverage`-driven, so you can use `coverage` to get an idea of what is covered and what is not.

An html report is also generated by `coverage` and can be found in `.tox/htmlcov/index.html`.

I therefore removed the previous stack of tests using `Dockerfile` and `Makefile`.

## Contributions

Comments, issues, PR as :beer: will be warmly welcomed !

## License

GPLv3+

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/oorabona/github-ssh-auth",
    "name": "github-ssh-auth",
    "maintainer": "Olivier Orabona",
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "olivier.orabona@gmail.com",
    "keywords": "github,ssh,authentication",
    "author": "Olivier Orabona",
    "author_email": "olivier.orabona@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/7a/b1/8f6a46581cec5830dfa28d7d894cc183596871446ce72e787a3f5622a6ff/github_ssh_auth-1.0.2.tar.gz",
    "platform": "any",
    "description": "# GitHub-SSH-Auth\n\n[![CICD](https://github.com/oorabona/github-ssh-auth/actions/workflows/main.yml/badge.svg)](https://github.com/oorabona/github-ssh-auth/actions/workflows/main.yml)\n\n[![PyPI](https://img.shields.io/pypi/v/github-ssh-auth)](https://pypi.org/project/github-ssh-auth/)\n[![Supported Python\nversions](https://img.shields.io/pypi/pyversions/github-ssh-auth.svg)](https://pypi.org/project/github-ssh-auth/)\n[![Code style:\nblack](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Downloads](https://pepy.tech/badge/github-ssh-auth/month)](https://pepy.tech/project/github-ssh-auth/)\n\n## About\n\nIf you are familiar with `authorized_keys` file, each time you want to update keys, you have to first copy them using `ssh-copy-id` for example or any similar method.\n\nBut what happens if you fail to copy keys? You might be locked out of your box.\n\nSame applies when you want to deploy your configuration on several machines, or when you want to update keys for a whole team.\nAll these tasks are tedious and error-prone.\n\n`Ansible` or `Chef` can help but they are not always available or suitable for the task.\nThey usually require some kind of infrastructure to work, and are designed for the whole system, not only for `SSH` authentication of potentially *some* users.\n\nAnother possible solution would be to have some common infrastructure like `sssd` or `LDAP` to deport the authentication some place else, or to somewhat automagically update the keys upon valid logon credentials.\n\nBut what if you don't have such infrastructure? Or what if you don't want to have one?\n\nThis is where this tool can help you out !\n\n## How it works\n\nThis project provides a way for `SSH` daemon (aka `sshd`) to authenticate users from your organization using their GitHub SSH keys.\n\n`OpenSSH` has two options in its configuration file (e.g. `/etc/ssh/sshd.conf`), namely `AuthorizedKeysCommand` and `AuthorizedKeysCommandUser`. These two options are used to specify the command to run and the user to run it as.\n\nTherefore here is another technique that can help in such scenarios.\n\nEverytime a user connects, the script will be called with the login as command line parameter.\n\nIn detail, the following happens :\n\n1. `sshd` deamon runs `github-ssh-auth` under the user defined by `AuthorizedKeysCommandUser` option.\n\n2. `github-ssh-auth` reads its configuration file (by default `/etc/github-ssh/conf`)\n\n3. according to the configuration file, it looks up the username given by `sshd` and checks if that user is granted permission to connect to this host\n\n4. if yes, it tries to read the cache file (recommended but can be disabled) to find user's keys. If no cache file found or if disabled, then it queries GitHub API to get the keys and creates the cache (if enabled).\n\n5. github-ssh-auth returns a list of eligible ssh public keys to standard output to be processed by `sshd`\n\nThe rest is handled by `sshd` itself, i.e. checking validity of that public key and the rest of the connection handling.\n\n> It does not interfere with the rest of the system, including anything PAM related.\n\n## Updating keys and cache use\n\nTo avoid flooding GitHub API and consequently being temporarily banned from using their API in case of massive connects, it is recommended to have cache enabled and update the keys only few times a day. The periodicity is yours and that is why there is a special `update` command line parameter for that.\n\nConsider the following scenario:\n\n- cache is enabled\n- cache file already exists\n- a new user has joined the team OR an existing user replaced his/her keys\n\nIn such case, the cache file will **NOT** be updated when authentication happens, this is the behavior set by design to separate concerns and prevent connection to the outside world being in the critical path for authentication.\n\nInstead, a locally defined `cron` should either:\n\n- call `github-ssh-update` to update cache\n- delete cache file (by default `/etc/github-ssh/cache.json`) which will force recreation when next auth happens\n\nBoth will have the same outcome but the former is cleaner than the latter.\n\nAll in all, choice is yours :wink:\n\n## Installation\n\nSince this Python module deals with SSH authentication, it should be installed globally, hence:\n\n```shell\nsudo pip install github-ssh-auth\n```\n\nThis will install the following program and its shortcuts in `/usr/local/bin`:\n\n## github-ssh\n\nThe real application, handling all options, but for convenience the shortcuts described after can be used.\n\n### Usage\n\n```shell\nUsage: github-ssh [OPTIONS] COMMAND [ARGS]...\n\nOptions:\n  --version  Show the version and exit.\n  --help     Show this message and exit.\n\nCommands:\n  auth    Authenticate user.\n  init    Initialize GitHub SSH Authentication configuration file.\n  update  Update GitHub SSH Auth cache file (users, teams, keys).\n```\n\n## github-ssh-auth\n\nResponsible for authentication itself, this one is to be called by `sshd` itself.\n\n```shell\nUsage: github-ssh-auth [OPTIONS] LOGIN\n\n  Authenticate user.\n\nOptions:\n  -c, --config FILE  Config file to use.  [default: /etc/github-ssh/conf]\n  --help             Show this message and exit.\n```\n\n## github-ssh-init\n\nThis command initializes the configuration file.\nIt will also launch an editor of your choice (or the one specified in `EDITOR` environment variable) to edit the configuration file.\n\n> Note: if the configuration file already exists, it will *NOT* be overwritten.\n\n```shell\nUsage: github-ssh-init [OPTIONS]\n\n  Initialize GitHub SSH Authentication configuration file.\n\nOptions:\n  -c, --config FILE  Config file to use.  [default: /etc/github-ssh/conf]\n  -e, --editor FILE  Editor to use.  [default: vim]\n  --help             Show this message and exit.\n```\n\n## github-ssh-update\n\nResponsible for updating cache file, it can be scheduled to run periodicaly to ensure synchronization with updated keys from Github.\n\n```shell\nUsage: github-ssh-update [OPTIONS]\n\n  Update GitHub SSH Auth cache file (users, teams, keys).\n\nOptions:\n  -c, --config FILE  Config file to use.  [default: /etc/github-ssh/conf]\n  --help             Show this message and exit.\n```\n\n> Note:\nIn some distros, `/usr/local/bin` is not eligible for `sshd` daemon because of some obscure group permissions reason. Moving the binaries (or create symlinks) from `/usr/local/bin` to `/usr/bin` make them work like a charm !\n\n## Configuration\n\n## SSH configuration in `/etc/ssh/sshd_config`\n\nThese lines should be somewhere in your `sshd` configuration file. Usually in `/etc/ssh/sshd_config` :\n\n```shell\nAuthorizedKeysCommand /usr/bin/github-ssh-auth %u\nAuthorizedKeysCommandUser nobody\n```\n\n### What do they mean?\n\nThe first line tells `sshd` to run `github-ssh-auth` with the username as command line parameter.\nAs previously said, recent versions of `sshd` support `AuthorizedKeysCommand` option and only if command happens to live in `/usr/bin` not `/usr/local/bin`.\n\nThe second line tells `sshd` to run `github-ssh-auth` under `nobody` user. This is to prevent any possible privilege escalation.\n\n## GitHub token requirements\n\nSince this application is dealing with some sensitive data (users and their team memberships) within an organization, we will need to create a so-called `Personal access tokens`.\n\nTo do that, fire up your GitHub organization dashboard, look for `Settings` then `Developer settings`.\n\nThen click on `Generate new token` and set its permissions to:\n> read:org\n\nThis is the only requirement so that the API can be queried for users and teams memberships. All users keys are public by default and can be accessed from the outside world without authentication against GitHub API.\n\nSee for yourself, go to `https://github.com/<yourhandle>.keys`. :rocket:\n\n## GitHub SSH Auth configuration file\n\nIt resides by default in `/etc/github-ssh/conf` but of course you can change it using `-c` flag when calling (see above).\n\nThe format is a standard INI style.\n\n### Configuration file template\n\nBelow is the complete grammar with inline comments:\n\n```ini\n[global]\n# Comments should start with a # and must be full lines\n# The following two lines are mandatory\naccess_token = <token>\norganization = <org>\n\n# In case of connectivity lost and to prevent too many connections to GitHub API,\n# it is strongly recommended to set it to an absolute filepath.\n# Default (when not present) is set to /etc/github-ssh/cache.json and equivalent to:\n# cache_file = /etc/github-ssh/cache.json\n\n# If you want to disable, set it to 'false'\n# cache_file = [/path/to/file | false]\n\n# Unless overridden after, users (whether individual or teams) will have these configurations applied.\n# By default, nothing is set so basically no one will be granted access\n# The '<' case means that if a local exists with the same name as a GitHub user,\n# it will be granted access. It is a shorthand to avoid a too\n# complex, verbose yet common use case where every developer would\n# like to have his/her own shell account.\nteams_default = [ list,of,local,users,or,< ]\nusers_default = [ list,of,local,users,or,< ]\n\n# Configuration below will override team defaults\n[teams]\n<team_name> = [ list,of,local,users,or,< ]\n...\n\n# And below to override default users setup\n[users]\n<user_name> = [ list,of,local,users,or,< ]\n...\n```\n\n### Special note on the '<'\n\nJust a quick focus on that special caracter, designed to allow a GitHub `user` to connect provided that:\n\n- `user` is the Github handle, being part of the organization, **and**\n- `user` exists in the local user base (i.e. it is present when you do a `getent passwd`)\n\n#### Real world example\n\nLet's say Acme Corp. wants all its developers connect with their GitHub accounts. Let's say users `jdoe` `bob` `alice` are such handles. Also these user handles are also the respective handlers in GitHub.\n\nThen with this simple configuration, we allow globally all these users to login with their handles:\n\n```ini\n[global]\n...other config skipped for brevety...\n\nusers_default = <\n```\n\nNow if you only want `bob` to connect using login `bob`, the configuration file would look like this one:\n\n```ini\n[global]\n...other config skipped for brevety...\n\nusers_default =\n\n[users]\nbob = <\n```\n\n### Other real world examples\n\nSome other ready-to-be-deployed-or-almost can be found in the `example` directory.\n\n## Testing\n\nAs this is my first Python module, and even my first Python program ever, I tried different methods to handle testing.\n\nHave had a lot of shortcomings four years ago when I started this project, now I found a way to test it properly using `tox` and `pytest`.\nOf course code is `coverage`-driven, so you can use `coverage` to get an idea of what is covered and what is not.\n\nAn html report is also generated by `coverage` and can be found in `.tox/htmlcov/index.html`.\n\nI therefore removed the previous stack of tests using `Dockerfile` and `Makefile`.\n\n## Contributions\n\nComments, issues, PR as :beer: will be warmly welcomed !\n\n## License\n\nGPLv3+\n",
    "bugtrack_url": null,
    "license": "GPL-3.0",
    "summary": "Authenticate SSH users using their GitHub keys",
    "version": "1.0.2",
    "project_urls": {
        "Changelog": "https://github.com/oorabona/github-ssh-auth/releases",
        "Homepage": "https://github.com/oorabona/github-ssh-auth",
        "Source": "https://github.com/oorabona/github-ssh-auth",
        "Tracker": "https://github.com/oorabona/github-ssh-auth/issues"
    },
    "split_keywords": [
        "github",
        "ssh",
        "authentication"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "46401f1e8d28c488c23c6168aa785d83e68a4bf3b9ea4e5d96dc5cd76e22376a",
                "md5": "befd2b866c0433f776fd468100a13c99",
                "sha256": "8eb72406f3129c1c4ea0fa2ceb5710d738a8c777026d99d552b5d09d3b7bae6d"
            },
            "downloads": -1,
            "filename": "github_ssh_auth-1.0.2-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "befd2b866c0433f776fd468100a13c99",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": ">=3.9",
            "size": 25353,
            "upload_time": "2023-09-18T21:47:48",
            "upload_time_iso_8601": "2023-09-18T21:47:48.978972Z",
            "url": "https://files.pythonhosted.org/packages/46/40/1f1e8d28c488c23c6168aa785d83e68a4bf3b9ea4e5d96dc5cd76e22376a/github_ssh_auth-1.0.2-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7ab18f6a46581cec5830dfa28d7d894cc183596871446ce72e787a3f5622a6ff",
                "md5": "7e2df69d5bedc2e0fb74f24abf428524",
                "sha256": "418a453b48c4e7933420a3f659435e1e71074be28d60955ebde4c450b42a7c5f"
            },
            "downloads": -1,
            "filename": "github_ssh_auth-1.0.2.tar.gz",
            "has_sig": false,
            "md5_digest": "7e2df69d5bedc2e0fb74f24abf428524",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 30703,
            "upload_time": "2023-09-18T21:47:50",
            "upload_time_iso_8601": "2023-09-18T21:47:50.802289Z",
            "url": "https://files.pythonhosted.org/packages/7a/b1/8f6a46581cec5830dfa28d7d894cc183596871446ce72e787a3f5622a6ff/github_ssh_auth-1.0.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-09-18 21:47:50",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "oorabona",
    "github_project": "github-ssh-auth",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "github-ssh-auth"
}
        
Elapsed time: 0.11634s