Code Monkey home page Code Monkey logo

python-blessclient's Introduction

Blessclient -- DEPRECATED

NOTE: We have deprecated python-blessclient and it is no longer actively maintained. A recommended alternative is blessclient in Go.

A client for interacting with BLESS services from users' laptops. Blessclient optimizes to ensure that users can always use ssh as they normally would with a fixed key, with minimal delay.

Netflix's BLESS was designed to issue short-lived certificates to users after they logged into a bastion service, that would be used to authenticate the user to other hosts within the cluster. Lyft wanted to use ephemeral ssh certificates for our users too, but wanted to issue these certificates directly to users' laptops, instead of on the bastion. We were able to accomplish this by making a few modifications to Netflix's BLESS and deploying this project, blessclient, to our users' laptops. Doing this allowed Lyft to improve security by extending the existing multi-factor authentication (MFA) setup that we had with AWS to SSH, as well as simplifying our provisioning and deprovisioning process.

Requirements

Blessclient is a python client that should run without modification on OSX 10.10 - 10.12, and Ubuntu 16.04. Other linux versions should work fine, but we test the client on 16.04.

  • Users should be running a recent version of python 2.7 (OSX comes with 2.7.10), and need pip and virtualenv installed if they will be building the client locally. We distribute blessclient with a Makefile, but you can easily duplicate those steps in another scripting language if your users don't have make installed.

  • It's required that your AWS user names match the ssh username used by your users. The ssh certificate issued by the BLESS Lambda specifies the username allowed the login with the certificate, and we use the user's AWS username for this. The BLESS Lambda and kmsauth could be modified to change this requirement, but we don't support that at this time.

Installation

To get to the point where you can login to a server using your bless'ed SSH certificate, you will need:

  • Netflix's BLESS, using commit 8df7f6d or later, which signs your users' public keys, and is trusted by your SSH hosts.
  • Your SSH server configured to trust the Lambda as Certificate Authority
  • Blessclient (this project!) which talks to the Lambda to get a new SSH certificate
  • Some configuration work to have blessclient invoked when the user runs SSH

Run a BLESS Lambda in AWS

Run Netflix's BLESS in your AWS account.

The lambda execution role will need permissions to decrypt the CA private key in your configuration, as well as permission to decrypt kmsauth tokens (see below).

Blessclient also assumes that the user will assume an IAM role, and that role has permissions to execute the Lambda. You should create this role, give it permissions to execute the Lambda, and give your users permissions to assume the role. The kmsauth policy can be used to require MFA, however you may want to also require MFA to assume this role, in case the kmsauth control fails.

Setup a kmsauth key + policy in your AWS account

Kmsauth is a system where we use an AWS KMS key and AWS IAM policy to get proof that a particular user proved their identity to AWS at a specific time. For more context around kmsauth, see the announcement for Confidant, Lyft's secret management system.

To use kmsauth with blessclient,

  1. Add a kms key in each region where you want to be able to use kmsauth.

  2. Add a policy for your users to use the key that looks something like,

       {
          "Action": "kms:Encrypt",
          "Effect": "Allow",
          "Resource": [
              "arn:aws:kms:us-east-1:123456789011:key/12345678-abab-cdcd-efef-123456789011",
          ],
          "Condition": {
              "StringEquals": {
                  "kms:EncryptionContext:to": [
                      "bless-production",
                  ],
                  "kms:EncryptionContext:user_type": "user",
                  "kms:EncryptionContext:from": "${aws:username}"
              },
              "Bool": {
                  "aws:MultiFactorAuthPresent": "true"
              }
          }
      }

This allows your users to encrypt data with the kms auth key only if the "from" context matches their username. Your lambda's execution role will need a corresponding permission to decrypt data using this same kms key when the "to" context matches the the service name (in this example, "bless-production").

Setup your SSH server to trust your BLESS Lambda

Your sshd_config should have a TrustedUserCAKeys option setup to trust your BLESS Lambda. See the Netflix BLESS documentation for how to do this.

Build a client

At minimum, you can run make client to setup a virtualenv, install python-blessclient, and symlink to the resulting script. This requires users to have virtualenv and pip installed (and have reasonably recent versions of both).

By default, blessclient uses the private key ~/.ssh/blessid, and looks for a corresponding ~/.ssh/blessid.pub to get the public key. The key must be an RSA key to use the Lyft/Netflix BLESS Lambda, other key types are not supported. The ssh certificate will be written to <identity_file>-cert.pub (by default, ~/.ssh/blessid-cert.pub), where OSX's ssh-agent expects a corresponding ssh certificate. It seems to work best if you also symlink the '-cert.pub' to '-cert', because some ssh clients seem to only check for the '-cert' version.

You can generate these with something like,

ssh-keygen -f ~/.ssh/blessid -b 4096 -t rsa -C 'Temporary key for BLESS certificate' -N ''
ssh-keygen -y -f ~/.ssh/blessid > ~/.ssh/blessid.pub
touch ~/.ssh/blessid-cert.pub
ln -s ~/.ssh/blessid-cert.pub ~/.ssh/blessid-cert

You can use an alternate filename by setting the BLESS_IDENTITYFILE environment variable. Blessclient will also attempt to detect and use as the identity file a '-i' flag passed into the ssh command.

Blessclient will also use your user's AWS credentials to take actions in AWS on their behalf. These are typically set in ~/.aws/credentials, or by some other method (see Configuring Credentials).

Configure your client

By default, blessclient is configured by adding a blessclient.cfg file in the root of the directory where you downloaded blessclient. You can also specify a config file location by passing --config when invoking blessclient.

If you fork this project, you can include a configuration file in the fork for your users to download when they clone the repo, or you can add this repo as a git submodule to a deployment repo, and have the installation process copy your configuration file to the correct location.

You will probably want to start with the sample config (blessclient.cfg.sample) and fill in the information about your BLESS Lambda, kmsauth key, and blessclient. At minimum, you will likely need to set, kms_service_name, bastion_ips, domain_regex, user_role, account_id, functionname, and functionversion for things to work.

Integrate your client with SSH

Blessclient will need to be called shortly before your users can ssh into BLESS-configured servers. There are a couple of ways you can accomplish this. To ensure blessclient is always invoked for the most users, Lyft uses both methods, preventing a redundant second run with BLESS_COMPLETE.

  1. Wrap your ssh command with an alias that calls blessclient first. At Lyft, we alias ssh to a bash script that looks like,

    #!/bin/bash
    
    CLIENTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    
    # Only run blessclient if connect to a lyft server
    if echo "$@" | grep -q '\.lyft\.'; then
        echo 'Running bless...'
        "${CLIENTDIR}/blessclient.run"
    fi
    
    unalias ssh &> /dev/null
    BLESS_COMPLETE=1 ssh "$@"
    

    This is nice because the user can interact with blessclient and put their MFA code (if needed) into the shell prompt.

  2. Add a Match exec line to your ssh_config file. You can add something like,

    Match exec "env | grep -q BLESS_COMPLETE || /Users/stype/blessclient/blessclient.run --gui --host '%h'"
    	IdentityFile ~/.ssh/blessid
    

    The advantage of this method is that all uses of ssh (git, scp, rsync) will invoke blessclient when run. The down side is that when openssh client runs the command specified, it connects stderr but not stdin. As a result, blessclient can't prompt the user for their MFA code on the console, so we have to pass --gui to present a gui dialog (using tkinter). Also, 'Match exec' was added in openssh 6.5, so earlier clients will error on the syntax.

What blessclient does

When your users run blessclient, the rough list of things done is:

  • Prompt the user for their MFA code, and get a session token from AWS sts that proves the user's identity
  • Generate and encrypt a kmsauth token
  • Assume the user role that can invoke the BLESS Lambda
  • Invoke the BLESS Lambda, passing in the user's kmsauth token and public key
  • Get back the ssh certificate from the Lambda, and save it to the filesystem
  • Load identity into the running ssh-agent, so agent forwarding will work

Blessclient aggressively caches artifacts, and can issue a certificate with a single round-trip to call the Lambda if a current kmsauth token and role credentials are cached.

Automatically updating the client

After you've taken the time to get all of your users to install blessclient, it's useful to ensure that your users automatically update their copy of client. If you don't want to do this via a traditional endpoint management system, blessclient can be setup to run an update script automatically after 7 days of use. The update script is configurable in blessclient.cfg ('update_script' in the CLIENT section). The update script does not block the client's execution (we don't want to make users wait for a client update if they are responding to an emergency). The script could be as simple as git pull && make client. At Lyft, the update process verifies that the update target (in our deployment repo) is signed by a trusted GPG key.

Contributing

This project is governed by Lyft's code of conduct. For your PR's to be accepted, you'll need to sign our CLA.

To setup your development environment, run make develop to install the development python dependencies from pip. Test your work with make test. All new contributions should have 100% (or very close) test coverage.

python-blessclient's People

Contributors

asottile avatar dschaller avatar ecdavis avatar jaymed avatar masstamike avatar myoung34 avatar olemathias avatar stype avatar vivianho avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-blessclient's Issues

Unintentional Identity file signing

Hi, while troubleshooting some unrelated problems I noticed this client is generated signed key certs for any identity that we use in the in the ssh command.
The culprit seems to be this:
https://github.com/lyft/python-blessclient/blob/master/blessclient/client.py#L171

called here:
https://github.com/lyft/python-blessclient/blob/master/blessclient/client.py#L448

Given an ssh config as recommended:

Match exec "env | grep -q BLESS_COMPLETE || /Users/stype/blessclient/blessclient.run --gui --host '%h'"
	IdentityFile ~/.ssh/blessid

If we are to call any other ssh command as:
ssh -i ~/.ssh/mykey user@host
and we didn't have a filter on domain_regex: blessclient will still generate and sign the mykey key.

While I believe this could be in some case desired functionality(when doing ssh wrapping instead of ssh config), I think it might be better to just let it toggle via an env var or the existing BLESS_IDENTITYFILE as in most cases than not if you specify a particular identity on the command line, you want to use exactly that to auth and signing is unnecessary.

If this is accepted I can create a PR to cleanup/implement this.

cannot find credentials if AWS_PROFILE or AWS_DEFAULT_PROFILE environment vars are set

if either:
AWS_PROFILE or AWS_DEFAULT_PROFILE is set, the client will not find aws credentials:

or if you run the command with the AWS_PROFILE env variable it will also fail to find the creds

AWS_PROFILE=dev ./blessclient.run --nocache --region WEST --config ~/bin/python-blessclient/conf/dev.cfg

Can't get your user information from AWS! Either you don't have your user aws credentials set as [default] in ~/.aws/credentials, or you have another process setting AWS credentials for a service account in your environment.Could not connect to BLESS in any configured region.

Cannot refresh public IP randomly

On occasion, on the first cold run:

DEBUG:root:Cache get disabled                                                                                       
DEBUG:root:Cache get disabled                                                                                                    
DEBUG:root:Getting current public IP                                                                                                                                                                                                          DEBUG:root:Could not refresh public IP from http://api.ipify.org                                                                                                                                                                              Traceback (most recent call last):                                                                                                                                                                                                              File "/home/marc/.blessclient/blessclient/user_ip.py", line 53, in _fetchIP                                                                                                                                                    
    with contextlib.closing(urlopen(url, timeout=2)) as f:                                                                                                                                                                                      File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 154, in urlopen                                                                                                                                                         return opener.open(url, data, timeout)                                                                                                                                                                                                      File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 429, in open                                                                                                                                                            response = self._open(req, data)                                                                                                                                                                                                            File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 447, in _open                                                                                                                                        
    '_open', req)                                                                                                                                                                                                                               File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 407, in _call_chain                                                                                                                                                     result = func(*args)                                                                                                                                                     
  File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 1228, in http_open                                                                                                                                                      return self.do_open(httplib.HTTPConnection, req)                                                                                                                                                                                            File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 1201, in do_open                                                                                                                                                        r = h.getresponse(buffering=True)                                                                                                                                                                                                           File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/httplib.py", line 1136, in getresponse                                                                                                                                                    response.begin()                                                                                                                                                                                                                            File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/httplib.py", line 453, in begin                                                                                                                                                           version, status, reason = self._read_status()                                                                                                                                                                                               File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/httplib.py", line 409, in _read_status                                           
    line = self.fp.readline(_MAXLINE + 1)                                                                                                                                  
  File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/socket.py", line 480, in readline                                                                                                                                                         data = self._sock.recv(self._rbufsize)                                                                                                                                                                                                    timeout: timed out                                                                                                               
DEBUG:root:Could not refresh public IP from http://canihazip.com
Traceback (most recent call last):                                                                                  
  File "/home/marc/.blessclient/blessclient/user_ip.py", line 53, in _fetchIP                                                    
    with contextlib.closing(urlopen(url, timeout=2)) as f:                                                                                                                                                                                      File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 154, in urlopen                                                                                                                                                         return opener.open(url, data, timeout)                                                                                                                                                                                                      File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 429, in open                                                                                                                                           
    response = self._open(req, data)                                                                                                                                                                                                            File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 447, in _open                                                                                                                                                           '_open', req)                                                                                                                                                                                                                               File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 407, in _call_chain                                                                                                                                                     result = func(*args)                                                                                                                                                                                                                        File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 1228, in http_open                                                                                                                                   
    return self.do_open(httplib.HTTPConnection, req)                                                                                                                                                                                            File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/urllib2.py", line 1201, in do_open                                                                                                                                                        r = h.getresponse(buffering=True)                                                                                                                                        
  File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/httplib.py", line 1136, in getresponse                                                                                                                                                    response.begin()                                                                                                                                                                                                                            File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/httplib.py", line 453, in begin                                                                                                                                                           version, status, reason = self._read_status()                                                                                                                                                                                               File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/httplib.py", line 409, in _read_status                                                                                                                                                    line = self.fp.readline(_MAXLINE + 1)                                                                                                                                                                                                     
  File "/home/marc/.pyenv/versions/2.7.12/lib/python2.7/socket.py", line 480, in readline
    data = self._sock.recv(self._rbufsize)
timeout: timed out
Traceback (most recent call last):
  File "/home/marc/.blessclient/blessclient.run", line 11, in <module>
    load_entry_point('blessclient', 'console_scripts', 'blessclient')()
  File "/home/marc/.blessclient/blessclient/client.py", line 643, in main
    bless(region, args.nocache, args.gui, args.host, bless_config)
  File "/home/marc/.blessclient/blessclient/client.py", line 547, in bless
    my_ip = userIP.getIP()
  File "/home/marc/.blessclient/blessclient/user_ip.py", line 29, in getIP
    self._refreshIP()
  File "/home/marc/.blessclient/blessclient/user_ip.py", line 43, in _refreshIP
    raise Exception('Could not refresh public IP')
Exception: Could not refresh public IP

Immediately re-running this works.

mfa.sh

Hi,

Could you please point where to get mfa.sh? Only reference to use it, but no additional information.

ModuleNotFoundError: No module named 'awsmfautils'

Trying to set this up on my Centos7, using Python 3.6 from ius.io

Downloaded the master zip, and unpacked. After fiddling with requirements I got 'make client' to work
Successfully installed blessclient boto3-1.4.7 botocore-1.7.36 docutils-0.14 jmespath-0.9.3 kmsauth-0.2.0 psutil-5.4.0 python-dateutil-2.6.1 s3transfer-0.1.11 six-1.11.0
ln -s venv/bin/blessclient ./blessclient.run

But actually running the result fails:
[bent@c7vm python-blessclient-master]$ ./blessclient.run
Traceback (most recent call last):
File "./blessclient.run", line 11, in
load_entry_point('blessclient', 'console_scripts', 'blessclient')()
File "/tmp/python-blessclient-master/venv/lib/python3.6/site-packages/pkg_resources/init.py", line 570, in load_entry_point
return get_distribution(dist).load_entry_point(group, name)
File "/tmp/python-blessclient-master/venv/lib/python3.6/site-packages/pkg_resources/init.py", line 2755, in load_entry_point
return ep.load()
File "/tmp/python-blessclient-master/venv/lib/python3.6/site-packages/pkg_resources/init.py", line 2409, in load
return self.resolve()
File "/tmp/python-blessclient-master/venv/lib/python3.6/site-packages/pkg_resources/init.py", line 2415, in resolve
module = import(self.module_name, fromlist=['name'], level=0)
File "/tmp/python-blessclient-master/blessclient/client.py", line 22, in
import awsmfautils
ModuleNotFoundError: No module named 'awsmfautils'
[bent@c7vm python-blessclient-master]$

Should I package the contents of the venv somehow and install that?

osascript can't find brewed pythons

Homebrew installs python with the version in the name (e.g. python3.7).
The osascript throws an error

21:62: execution error: Finder got an error: Can't set process "python" to true. (-10006)

Blessid keys passphrase protection

when I add a passphrase to the blesskey, I have experienced the following error.

Requesting certificate for your public key (set BLESSQUIET=1 to suppress these messages)
Traceback (most recent call last):
  File "/home/sebastian/bin/blessclient.run", line 11, in <module>
    load_entry_point('blessclient', 'console_scripts', 'blessclient')()
  File "/home/sebastian/bin/python-blessclient/blessclient/client.py", line 868, in main
    bless(region, args.nocache, args.gui, args.host, bless_config)
  File "/home/sebastian/bin/python-blessclient/blessclient/client.py", line 809, in bless
    ssh_agent_add_bless(identity_file)
  File "/home/sebastian/bin/python-blessclient/blessclient/client.py", line 369, in ssh_agent_add_bless
    subprocess.check_call(['ssh-add', identity_file], stderr=DEVNULL)
  File "/usr/lib/python2.7/subprocess.py", line 186, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['ssh-add', '/home/sebastian/.ssh/blessid']' returned non-zero exit status 1
Debian GNU/Linux 9
Permission denied (publickey).

recreating the key without passphrase resolved the problem. #17 is related to this

Dont add to ssh-agent unless specified

I would suggest implementing a parameter that allows stopping bless from adding the Identity file to ssh-agent.
Namely this lines: https://github.com/lyft/python-blessclient/blob/master/blessclient/client.py#L594-L597

Given a config like:

Match exec "env | grep -q BLESS_COMPLETE || /Users/stype/blessclient/blessclient.run --gui --host '%h'"
	IdentityFile ~/.ssh/blessid

this is not required and could interfere with other credentials.

I think this is required when doing a wrapper around the ssh command but in that case its easy to toggle on in the script.

If this is accepted I can create a PR to cleanup/implement this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.