#!/usr/bin/env python

"""Sign a machine's host public key.

This script is used to sign a host's SSH public key using a certificate
authority's private key. The signed public key can be used to verify that a
machine is one of your own and very likely the machine you actually want to
talk to.

For example, imagine a server that is launched by a single user and later
accessed by dozens of engineers. The first engineer may be able (and may
actually) verify the host key fingerprint. However, users 2 through n probably
just type yes when prompted by SSH to verify the fingerprint.

Instead the user setting up the server can sign the host key of the host and
then all users in an organization can trust the certificate authority that
signed the host key. When a new user SSHs to a new host so long as the
certificate is valid the user will not be prompted to type yes.

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

For example, if the host key is /etc/ssh/ssh_host_rsa_key.pub do something
like:

    curl <THE URL> > /etc/ssh/ssh_host_rsa_key-cert.pub

sshd isn't configured out of the box to look for certs so you need to edit your
/etc/ssh/sshd_config and add a line like this one:

    HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub

Now you can send a HUP to the sshd parent process. I typically do a ps ax |
grep sshd and then kill -HUP the lowest numbered pid (or whichever one appears
most likely to be the parent).
"""
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(
        '--hostname', action='append', required=True, dest='hostnames',
        help='A principal (hostname) that clients can verify. '
             'This should match what the user types for "ssh <hostname>')
    parser.add_argument(
        '-t', '--expires-in', default='+104w',
        help='Expires in. A relative time like +1w. Or YYYYMMDDHHMMSS. '
             'Default: %(default)s',
    )
    args = parser.parse_args()

    environment = args.environment

    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:
        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)

    reason = 'New host cert for %r' % (args.hostnames,)

    public_key_contents = ca.get_host_rsa_key(args.hostnames[0])
    (fd, public_path) = tempfile.mkstemp()
    with closing(os.fdopen(fd, 'w')) as f:
        f.write(public_key_contents)

    cert_contents = ca.sign_public_host_key(
        public_path, args.expires_in, args.hostnames,
        reason, args.hostnames[0]
    )

    ca.upload_host_rsa_cert(args.hostnames[0], cert_contents)
