|
| 1 | +from __future__ import print_function |
| 2 | +from netrc import netrc |
| 3 | +from urlparse import urlparse |
| 4 | +import getpass |
| 5 | +import argparse |
| 6 | +import sys |
| 7 | +import os |
| 8 | +import conjur |
| 9 | +import inflection |
| 10 | +import json |
| 11 | +from tabulate import tabulate |
| 12 | + |
| 13 | +def eprint(*args, **kwargs): |
| 14 | + print(*args, file=sys.stderr, **kwargs) |
| 15 | + |
| 16 | +def netrc_path(): |
| 17 | + try: |
| 18 | + return os.environ['CONJURRC'] |
| 19 | + except KeyError: |
| 20 | + return os.path.expanduser('~/.netrc') |
| 21 | + |
| 22 | +def touch_netrc(): |
| 23 | + path = netrc_path() |
| 24 | + with open(path, 'a'): |
| 25 | + os.utime(path, None) |
| 26 | + os.chmod(path, 0o600) |
| 27 | + |
| 28 | +def netrc_str(netrc): |
| 29 | + """Dump the class data in the format of a .netrc file.""" |
| 30 | + rep = "" |
| 31 | + for host in netrc.hosts.keys(): |
| 32 | + attrs = netrc.hosts[host] |
| 33 | + rep = rep + "machine "+ host + "\n\tlogin " + str(attrs[0]) + "\n" |
| 34 | + if attrs[1]: |
| 35 | + rep = rep + "account " + str(attrs[1]) |
| 36 | + rep = rep + "\tpassword " + str(attrs[2]) + "\n" |
| 37 | + for macro in netrc.macros.keys(): |
| 38 | + rep = rep + "macdef " + macro + "\n" |
| 39 | + for line in netrc.macros[macro]: |
| 40 | + rep = rep + line |
| 41 | + rep = rep + "\n" |
| 42 | + return rep |
| 43 | + |
| 44 | +def credentials_from_netrc(): |
| 45 | + try: |
| 46 | + creds = netrc(netrc_path()) |
| 47 | + except IOError: |
| 48 | + raise Exception("Not logged in. File %s does not exist" % netrc_path()) |
| 49 | + |
| 50 | + result = creds.authenticators(conjur.config.url) |
| 51 | + try: |
| 52 | + return ( result[0], result[2] ) |
| 53 | + except TypeError: |
| 54 | + raise Exception("Conjur URL %s is not in %s, therefore you're not logged in" % ( conjur.config.url, netrc_path() )) |
| 55 | + |
| 56 | +def credentials(): |
| 57 | + try: |
| 58 | + ( os.environ['CONJUR_AUTHN_LOGIN'], os.environ['CONJUR_AUTHN_API_KEY'] ) |
| 59 | + except KeyError: |
| 60 | + return credentials_from_netrc() |
| 61 | + |
| 62 | +def connect(): |
| 63 | + return conjur.new_from_key(*credentials()) |
| 64 | + |
| 65 | +def current_roleid(): |
| 66 | + # Ensure the login is valid |
| 67 | + api = connect() |
| 68 | + api.authenticate() |
| 69 | + username = api.login |
| 70 | + account = conjur.config.account |
| 71 | + if username.find('/') != -1: |
| 72 | + kind, _, id = username.partition('/') |
| 73 | + else: |
| 74 | + kind, id = ( 'user', username) |
| 75 | + return "%s:%s:%s" % ( account, kind, id ) |
| 76 | + |
| 77 | +def find_object(kind, id): |
| 78 | + api = connect() |
| 79 | + if len(id.split(':')) > 1: |
| 80 | + return api.resource_qualified(id) |
| 81 | + else: |
| 82 | + return api.resource(kind, id) |
| 83 | + |
| 84 | +def find_variable(id): |
| 85 | + return find_object('variable', id) |
| 86 | + |
| 87 | +def find_policy(id): |
| 88 | + return find_object('policy', id) |
| 89 | + |
| 90 | +def interpret_login(role): |
| 91 | + tokens = role.split(':', 1) |
| 92 | + if len(tokens) == 2: |
| 93 | + return tokens |
| 94 | + else: |
| 95 | + return ( 'user', tokens[0] ) |
| 96 | + |
| 97 | +def login_handler(args): |
| 98 | + role = args.role |
| 99 | + if not role: |
| 100 | + eprint("Enter your username to log into Possum: ", end="") |
| 101 | + role = raw_input() |
| 102 | + kind, identifier = interpret_login(role) |
| 103 | + username = '/'.join((kind, identifier)) |
| 104 | + |
| 105 | + if args.rotate: |
| 106 | + api = connect() |
| 107 | + role = conjur.Role.from_roleid(api, ":".join((kind, identifier))) |
| 108 | + api_key = role.rotate_api_key() |
| 109 | + else: |
| 110 | + password = getpass.getpass("Enter password for %s %s (it will not be echoed): " % ( kind, identifier )) |
| 111 | + api_key = conjur.new_from_password(username, password).api_key |
| 112 | + |
| 113 | + save_api_key(username, api_key) |
| 114 | + print("Logged in") |
| 115 | + |
| 116 | +def save_api_key(username, api_key): |
| 117 | + touch_netrc() |
| 118 | + logins = netrc(netrc_path()) |
| 119 | + logins.hosts[conjur.config.url] = ( username, None, api_key ) |
| 120 | + with open(netrc_path(), 'w') as f: |
| 121 | + f.write(netrc_str(logins)) |
| 122 | + |
| 123 | +def authenticate_handler(args): |
| 124 | + import base64 |
| 125 | + token = connect().authenticate() |
| 126 | + if args.H: |
| 127 | + token = base64.b64encode(token) |
| 128 | + print(token) |
| 129 | + |
| 130 | +def whoami_handler(args): |
| 131 | + print(current_roleid()) |
| 132 | + |
| 133 | +def rotate_api_key_handler(args): |
| 134 | + api = connect() |
| 135 | + role = conjur.Role.from_roleid(api, args.role or current_roleid()) |
| 136 | + api_key = role.rotate_api_key() |
| 137 | + if not args.role: |
| 138 | + save_api_key(api.login, api_key) |
| 139 | + print(api_key) |
| 140 | + |
| 141 | +def list_handler(args): |
| 142 | + def flatten_record(record): |
| 143 | + result = [ record['id'], record['owner'] ] |
| 144 | + if 'policy' in record.keys(): |
| 145 | + result.append(record['policy']) |
| 146 | + return result |
| 147 | + |
| 148 | + api = connect() |
| 149 | + resources = [ flatten_record(resource) for resource in api.get(api._resources_url(kind=args.kind)).json() ] |
| 150 | + print(tabulate(resources, headers=['Id', 'Owner', 'Policy'])) |
| 151 | + |
| 152 | +def show_handler(args): |
| 153 | + api = connect() |
| 154 | + resource = api.resource_qualified(args.id) |
| 155 | + resource = resource.api.get(resource.url()).json() |
| 156 | + keys = resource.keys() |
| 157 | + keys.sort() |
| 158 | + def format_value(value): |
| 159 | + if isinstance(value, basestring): |
| 160 | + return value |
| 161 | + else: |
| 162 | + return json.dumps(value) |
| 163 | + |
| 164 | + data = [ ( inflection.camelize(key), format_value(resource[key]) ) for key in keys ] |
| 165 | + print(tabulate(data)) |
| 166 | + |
| 167 | +def policy_load_handler(args): |
| 168 | + if args.policy == '-': |
| 169 | + value = sys.stdin.read() |
| 170 | + else: |
| 171 | + value = args.policy |
| 172 | + policy = find_policy(args.id) |
| 173 | + url = '/'.join([ |
| 174 | + policy.api.config.url, |
| 175 | + 'policies', |
| 176 | + policy.api.config.account, |
| 177 | + 'policy', |
| 178 | + conjur.util.urlescape(policy.identifier) |
| 179 | + ]) |
| 180 | + |
| 181 | + response = policy.api.post(url, data=value).json() |
| 182 | + created_roles = response['created_roles'] |
| 183 | + print("") |
| 184 | + print("Loaded policy version %s" % response['version']) |
| 185 | + if len(created_roles) > 0: |
| 186 | + print("Created %s roles" % len(created_roles)) |
| 187 | + print("") |
| 188 | + print(tabulate([ ( record['id'], record['api_key'] ) for record in created_roles.values() ], ("Id", "API Key"))) |
| 189 | + print("") |
| 190 | + |
| 191 | +def fetch_handler(args): |
| 192 | + value = find_variable(args.id).secret(args.version) |
| 193 | + if value: |
| 194 | + print(value) |
| 195 | + else: |
| 196 | + sys.exit(1) |
| 197 | + |
| 198 | +def store_handler(args): |
| 199 | + if args.value == '-': |
| 200 | + value = sys.stdin.read() |
| 201 | + else: |
| 202 | + value = args.value |
| 203 | + find_variable(args.id).add_secret(value) |
| 204 | + print("Value added") |
| 205 | + |
| 206 | +parser = argparse.ArgumentParser(description='Possum command-line interface.') |
| 207 | +subparsers = parser.add_subparsers() |
| 208 | + |
| 209 | +login = subparsers.add_parser('login') |
| 210 | +login.add_argument('-r', '--role') |
| 211 | +login.add_argument('--rotate', action='store_true') |
| 212 | +login.set_defaults(func=login_handler) |
| 213 | + |
| 214 | +whoami = subparsers.add_parser('whoami') |
| 215 | +whoami.set_defaults(func=whoami_handler) |
| 216 | + |
| 217 | +authenticate = subparsers.add_parser('authenticate') |
| 218 | +authenticate.add_argument('-H', action='store_true') |
| 219 | +authenticate.set_defaults(func=authenticate_handler) |
| 220 | + |
| 221 | +rotate_api_key = subparsers.add_parser('rotate_api_key') |
| 222 | +rotate_api_key.add_argument('-r', '--role') |
| 223 | +rotate_api_key.set_defaults(func=rotate_api_key_handler) |
| 224 | + |
| 225 | +list_ = subparsers.add_parser('list') |
| 226 | +list_.add_argument('-k', '--kind', help='Resource kind') |
| 227 | +list_.set_defaults(func=list_handler) |
| 228 | + |
| 229 | +show = subparsers.add_parser('show') |
| 230 | +show.add_argument('id') |
| 231 | +show.set_defaults(func=show_handler) |
| 232 | + |
| 233 | +policy_load = subparsers.add_parser('policy:load') |
| 234 | +policy_load.add_argument('id') |
| 235 | +policy_load.add_argument('policy') |
| 236 | +policy_load.set_defaults(func=policy_load_handler) |
| 237 | + |
| 238 | +store = subparsers.add_parser('store') |
| 239 | +store.add_argument('id') |
| 240 | +store.add_argument('value') |
| 241 | +store.set_defaults(func=store_handler) |
| 242 | + |
| 243 | +fetch = subparsers.add_parser('fetch') |
| 244 | +fetch.add_argument('id') |
| 245 | +fetch.add_argument('-V', '--version', help='Variable version') |
| 246 | +fetch.set_defaults(func=fetch_handler) |
| 247 | + |
| 248 | +args = parser.parse_args() |
| 249 | +args.func(args) |
0 commit comments