|
| 1 | +## (c) 2018 integrate.ai |
| 2 | +## Helper module for use in EMR bootstrap actions and in |
| 3 | +## Corporate Realm machines that require keytabs |
| 4 | +## |
| 5 | +## Assumes SSM parameters in region like: |
| 6 | +## |
| 7 | +## <customer_identifier>-realm-name |
| 8 | +## <customer_indentifier>-realm-principal |
| 9 | +## <customer_identifier>-realm-principal-creds |
| 10 | +## |
| 11 | +## see github.com/deployment/terraform/emr/<customer_identifier>/ssm.tf |
| 12 | +## |
| 13 | +## If no customer identifier is passed, assumes: |
| 14 | +## |
| 15 | +## corp-iai-realm-name |
| 16 | +## corp-iai-realm-principal |
| 17 | +## corp-iai-realm-principal-creds |
| 18 | +## |
| 19 | +## Machine EC2 role must have access to decryption key |
| 20 | +## for these SSM Parameters in order to execute this |
| 21 | +## script successful. These keys are assumed, not checked |
| 22 | +## for. |
| 23 | +## |
| 24 | +## see github.com/deployment/terraform/network/auth_services.tf |
| 25 | +## |
| 26 | +## Only one public method: |
| 27 | +## create_host_principal_keytab( customer, keytabfilename ) |
| 28 | + |
| 29 | +import logging |
| 30 | +import boto3 |
| 31 | +import kadmin |
| 32 | +import socket |
| 33 | + |
| 34 | + |
| 35 | +## Creates or gets the Host Principal and then makes a new |
| 36 | +## keytab file using the keytabfilename parameter |
| 37 | +## throws an exception if the keytabfilename exists and |
| 38 | +## contains an existing keytab |
| 39 | +def create_host_principal_keytab( customer, keytabfilename ): |
| 40 | + return create_service_principal_keytab( customer, "host", keytabfilename) |
| 41 | + |
| 42 | +def create_service_principal_keytab( customer, service, keytabfilename ): |
| 43 | + |
| 44 | + result = False |
| 45 | + realm = _get_realm_name( customer ) |
| 46 | + admin_principal_name = _get_realm_admin_princ_name(customer) |
| 47 | + admin_cred = _get_realm_admin_princ_cred(customer) |
| 48 | + |
| 49 | + kadm_sess = _get_kadm_session (admin_principal_name, admin_cred) |
| 50 | + |
| 51 | + srv_princ_name = _get_principal_name(realm, service) |
| 52 | + |
| 53 | + if not _principal_exists(kadm_sess, srv_princ_name): |
| 54 | + _create_principal(kadm_sess, srv_princ_name) |
| 55 | + |
| 56 | + srv_principal = _get_principal(kadm_sess, srv_princ_name) |
| 57 | + |
| 58 | + if srv_principal is not None: |
| 59 | + result = _kadmin_ktadd(kadm_sess, srv_principal.principal, keytabfilename ) |
| 60 | + |
| 61 | + logging.info("keytab file %s created", keytabfilename) |
| 62 | + |
| 63 | + return result; |
| 64 | + |
| 65 | + |
| 66 | +### get param values from SSM |
| 67 | +def _get_ssm_param_value ( param_name, decrypt_flag ): |
| 68 | + ssmClient = boto3.client("ssm") |
| 69 | + response = ssmClient.get_parameter(Name = param_name, WithDecryption = decrypt_flag ); |
| 70 | + |
| 71 | + return response["Parameter"]["Value"] |
| 72 | + |
| 73 | +## use socket.getfqdn to build host principal name |
| 74 | +def _get_principal_name(realm, prefix = None): |
| 75 | + if prefix is None: |
| 76 | + prefix = "host"; |
| 77 | + |
| 78 | + host_fqdn = socket.getfqdn() |
| 79 | + return prefix + "/" + host_fqdn + "@" + realm |
| 80 | + |
| 81 | +## get kadm session |
| 82 | +def _get_kadm_session( user_name, user_cred ): |
| 83 | + return kadmin.init_with_password(user_name, user_cred) |
| 84 | + |
| 85 | +## get realm from SSM key prefixed by iai_realm_name |
| 86 | +def _get_realm_name( customer = None ): |
| 87 | + if customer is None: |
| 88 | + customer = "corp-iai" |
| 89 | + |
| 90 | + sparam = customer + "-realm-name" |
| 91 | + |
| 92 | + return _get_ssm_param_value( sparam, True ) |
| 93 | + |
| 94 | +## get realm admin name from SSM key prefixed by iai_realm_amdin_principal_ |
| 95 | +## This does not need to be full admin - but should be able to create |
| 96 | +## principals, look up principals and export keys (so nearly a full admin) |
| 97 | +def _get_realm_admin_princ_name( customer = None ): |
| 98 | + if customer is None: |
| 99 | + customer = "corp-iai" |
| 100 | + |
| 101 | + sparam = customer + "-realm-principal" |
| 102 | + |
| 103 | + return _get_ssm_param_value( sparam, True ) |
| 104 | + |
| 105 | +## get realm admin credential value - this is a password for now |
| 106 | +## but it could be a base64 keytab in the future |
| 107 | +def _get_realm_admin_princ_cred( customer = None ): |
| 108 | + if customer is None: |
| 109 | + customer = "corp-iai" |
| 110 | + |
| 111 | + sparam = customer + "-realm-principal-creds" |
| 112 | + |
| 113 | + return _get_ssm_param_value( sparam, True ) |
| 114 | + |
| 115 | +## Returns the specifically named principal |
| 116 | +def _get_principal ( kadm_sess, principal_name ): |
| 117 | + if kadm_sess is not None: |
| 118 | + principal = kadm_sess.getprinc(principal_name) |
| 119 | + return principal |
| 120 | + else: |
| 121 | + return None #TODO: Throw here! |
| 122 | + |
| 123 | +## Checks if the principal exists |
| 124 | +def _principal_exists( kadm_sess, principal_name ): |
| 125 | + exists = False |
| 126 | + if kadm_sess is not None: |
| 127 | + exists = kadm_sess.principal_exists(principal_name) |
| 128 | + |
| 129 | + return exists |
| 130 | + |
| 131 | +## Creates principal - throws DuplicateError if principal exists |
| 132 | +def _create_principal( kadm_sess, princ_name ): |
| 133 | + created = False |
| 134 | + if kadm_sess is not None: |
| 135 | + try: |
| 136 | + created = kadm_sess.add_principal(princ_name, None) |
| 137 | + except kadmin.DuplicateError: |
| 138 | + created = True |
| 139 | + |
| 140 | + return created |
| 141 | + |
| 142 | +## Creates Keytab file - uses enhanced python-kadmin library |
| 143 | +## github.com/integrateai/python-kadmin |
| 144 | +## forked from rjancewicz/python-kadmin |
| 145 | +## and enhanced with very based ktadd functionality |
| 146 | +## |
| 147 | +def _kadmin_ktadd( kadm_sess, princ_name, ktname ): |
| 148 | + created = False |
| 149 | + if kadm_sess is not None: |
| 150 | + created = kadm_sess.ktadd(princ_name, ktname) |
| 151 | + |
| 152 | + return created |
0 commit comments