#!/usr/bin/env python

"""Sign a user's SSH public key.

This script is used to sign a user's SSH public key using a certificate
authority's private key. The signed public key can be presented along with the
user's private key to get access to servers that trust the CA.

The final output of this script is an S3 URL containing the user's signed
certificate. The user needs to take this URL and download the file it points
at. The downloaded file should be named exactly like their private SSH key but
with the suffix "-cert.pub".

For example, if the user's key is ~/.ssh/id_rsa they should do something like

    curl <THE URL> > ~/.ssh/id_rsa-cert.pub

"""

import argparse
import ConfigParser
import os
import sys
import tempfile

from contextlib import closing

import ssh_ca
import ssh_ca.s3


if __name__ == '__main__':
    default_authority = os.getenv('SSH_CA_AUTHORITY', 's3')
    default_config = os.path.expanduser(
        os.getenv('SSH_CA_CONFIG', '~/.ssh_ca/config'))

    parser = argparse.ArgumentParser(__doc__)
    parser.add_argument('-a', '--authority',
        dest='authority', default=default_authority,
        help='Pick one: s3',
    )
    parser.add_argument('-c', '--config-file',
        default=default_config,
        help='The configuration file to use.  Can also be '
             'specified in the SSH_CA_CONFIG environment '
             'variable.  Default: %(default)s',
    )
    parser.add_argument('-e', '--environment',
        required=True,
        help='Environment name',
    )
    parser.add_argument('--principal',
        action='append',
        help='A principal (username) that the user is allowed to use',
    )
    parser.add_argument('-p',
        dest='public_path',
        help='Path to public key. If set we try to upload this. '
             'Otherwise we try to download one.',
    )
    parser.add_argument('-u',
        required=True, dest='username',
        help='username / email address',
    )
    parser.add_argument('--upload',
        dest='upload_only', action='store_true',
        help='Only upload the public key',
    )
    parser.add_argument('-r', '--reason',
        help='Specify the reason for the user needing this cert.',
    )
    parser.add_argument('-t', '--expires-in',
        default='+2h',
        help='Expires in. A relative time like +1w. Or YYYYMMDDHHMMSS. '
             'Default: %(default)s',
    )
    args = parser.parse_args()

    public_path = args.public_path
    environment = args.environment
    username = args.username

    ssh_ca_section = 'ssh-ca-' + args.authority

    config = None
    if args.config_file:
        config = ConfigParser.ConfigParser()
        config.read(args.config_file)

    # Get a valid CA key file
    ca_key = ssh_ca.get_config_value(config, environment, 'private_key')
    if ca_key:
        ca_key = os.path.expanduser(ca_key)
    else:
        ca_key = os.path.expanduser('~/.ssh/ssh_ca_%s' % (environment,))
    if not os.path.isfile(ca_key):
        print 'CA key file %s does not exist.' % (ca_key,)
        sys.exit(1)

    try:
        # Create our CA
        ca = ssh_ca.s3.S3Authority(config, ssh_ca_section, ca_key)
    except ssh_ca.SSHCAInvalidConfiguration, e:
        print 'Issue with creating CA: %s' % e.message
        sys.exit(1)

    if args.upload_only:
        if not public_path:
            print 'Upload needs a public key specified.'
            sys.exit(1)
        ca.upload_public_key(username, public_path)
        print 'Public key %s for username %s uploaded.' % (public_path,
                                                           username)
        sys.exit(0)

    # Figure out if we use a local new public key or an existing one
    if public_path:
        ca.upload_public_key(username, public_path)
        delete_public_key = False
    else:
        public_key_contents = ca.get_public_key(username, environment)
        if public_key_contents is None:
            print 'Key for user %s not found.' % (username)
            sys.exit(1)
        (fd, public_path) = tempfile.mkstemp()
        with closing(os.fdopen(fd, 'w')) as f:
            f.write(public_key_contents)
        delete_public_key = True

    if args.reason:
        reason = args.reason
    else:
        prompt = 'Specify the reason for the user needing this cert:\n'
        reason = raw_input(prompt).strip()
        if len(reason) > 256:
            print 'Reason is way too long. Type less.'
            sys.exit(1)

    if args.principal:
        principal = args.principal
    else:
        principal = ['ec2-user', 'ubuntu']

    # Sign the key
    cert_contents = ca.sign_public_user_key(
        public_path, username, args.expires_in, reason, principal)

    print
    print 'Public key signed, certificate available for download here:'
    print ca.upload_public_key_cert(username, cert_contents)

    if delete_public_key:
        os.remove(public_path)
