Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: jans-linux-setup-key key-regeneration fix spanner host #1229

Merged
merged 6 commits into from
Apr 20, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 101 additions & 89 deletions jans-linux-setup/tools/key_regeneration.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
pyDes = ModuleType('mod')
exec(bz2.decompress(base64.b64decode(pydes_b64)).decode(), pyDes.__dict__)

_VENDOR_ = 'jans'
_AUTH_NAME_ = 'oxAuth' if _VENDOR_ == 'gluu' else 'jans-auth'

parser = argparse.ArgumentParser('This script removes current key and creates new key for oxauth.')
parser = argparse.ArgumentParser('This script removes current key and creates new key for {}.'.format(_AUTH_NAME_))
ldap_group = parser.add_mutually_exclusive_group()
ldap_group.add_argument('-expiration_hours', help="Keys expire in hours", type=int)
ldap_group.add_argument('-expiration', help="Keys expire in days", default=365, type=int)
ldap_group.add_argument('-jans-auth-client', help="Path to jans-auth-client-jar-with-dependencies.jar")
ldap_group.add_argument('-data-dir', help="Directory to keep keys", default='/opt/jans/keys')
ldap_group.add_argument('-auth-client', help="Path to {}-client-jar-with-dependencies.jar".format(_AUTH_NAME_.lower()))
ldap_group.add_argument('-data-dir', help="Directory to keep keys", default='/opt/{}/keys'.format(_VENDOR_))
argsp = parser.parse_args()


Expand Down Expand Up @@ -86,7 +88,6 @@ def run_command(args):


def unobscure(s, key):
engine = pyDes.triple_des(key, pyDes.ECB, pad=None, padmode=pyDes.PAD_PKCS5)
cipher = pyDes.triple_des(key)
decrypted = cipher.decrypt(base64.b64decode(s), padmode=pyDes.PAD_PKCS5)
return decrypted.decode()
Expand All @@ -108,7 +109,8 @@ def __init__(self, host, admin, password):
def exec_query(self, query):
print("Executing n1ql {}".format(query))
data = {'statement': query}
result = requests.post(self.n1ql_api, data=data, auth=self.auth, verify=False)
verify_ssl = False
result = requests.post(self.n1ql_api, data=data, auth=self.auth, verify=verify_ssl)
return result


Expand All @@ -121,8 +123,9 @@ def __init__(self, credentials):
def get_session(self):
emulator_host = self.credentials.get('connection.emulator-host')
if emulator_host:
parsed_host = urlparse(emulator_host)
spanner_base_url = 'http://{}:9020/v1/'.format(parsed_host.scheme)
host, port = emulator_host.split(':')
scheme = 'http'
spanner_base_url = '{}://{}:9020/v1/'.format(scheme, host)
session_url = os.path.join(
spanner_base_url,
'projects/{}/instances/{}/databases/{}/sessions'.format(
Expand Down Expand Up @@ -186,27 +189,34 @@ class KeyRegenerator:

def __init__(self):

self.conf_dir = '/etc/jans/conf'
self.conf_dir = os.path.join('/etc', _VENDOR_, 'conf')

# vendor specific definitions
self.conf_dyn = 'jansConfDyn'
self.conf_web_keys = 'jansConfWebKeys'
self.conf_rev = 'jansRevision'
self.conf_objc = 'jansAppConf'
self.dnname = 'CN=Jans Auth CA Certificates'
self.prop_dn = 'jansAuth_ConfigurationEntryDN'

self.conf_keystore_secret = 'keyStoreSecret'
self.key_regenerator_jar = '{}-client-jar-with-dependencies.jar'.format(_AUTH_NAME_.lower())

self.java_cmd = '/opt/jre/bin/java'
self.keytool_cmd = '/opt/jre/bin/keytool'
if not os.path.exists(self.java_cmd):
self.java_cmd = shutil.which('java')
self.keytool_cmd = shutil.which('keytool')

self.doc_id = 'jans-auth'
self.key = 'configuration_jansauth'

self.data_dir = argsp.data_dir
if not os.path.exists(self.data_dir):
os.makedirs(self.data_dir)

self.keys_json_fn = os.path.join(self.data_dir, 'keys.json')
self.keystore_fn = os.path.join(self.data_dir, 'jans-auth-keys.p12')
store_ext = 'p12' if _VENDOR_ == 'jans' else 'pkcs12'
self.keystore_fn = os.path.join(self.data_dir, '{}-keys.{}'.format(_AUTH_NAME_.lower(), store_ext))


self.sig_keys = 'RS256 RS384 RS512 ES256 ES256K ES384 ES512 PS256 PS384 PS512'
self.enc_keys = 'RSA1_5 RSA-OAEP ECDH-ES'
self.dnname = 'CN=Jans Auth CA Certificates'
salt_fn = os.path.join(self.conf_dir, 'salt')
salt_dict = jproperties_parser(salt_fn)
self.salt = salt_dict['encodeSalt']
Expand All @@ -229,18 +239,22 @@ def __init__(self):


def find_auth_client_key_path(self):
if argsp.jans_auth_client:
self.client_jar_fn = argsp.jans_auth_client
if argsp.auth_client:
self.client_jar_fn = argsp.auth_client
else:
cur_dir = os.path.dirname(__file__)

file_list = glob.glob(os.path.join(cur_dir, 'jans-auth-client-jar-with-dependencies*.jar'))
if file_list:
self.client_jar_fn = max(file_list)
cur_dir_jar = os.path.join(cur_dir, self.key_regenerator_jar)
if os.path.exists(cur_dir_jar):
self.client_jar_fn
else:
self.client_jar_fn = '/opt/dist/jans/jans-auth-client-jar-with-dependencies.jar'
self.client_jar_fn = os.path.join('/opt/dist', _VENDOR_, self.key_regenerator_jar)

if not os.path.exists(self.client_jar_fn):
print("Can't find {}. Exiting ...".format(self.key_regenerator_jar))
sys.exit()

print("Determining oxauth key generator path")
print("Determining {} key generator path".format(_AUTH_NAME_))
# Determine oxauth key generator path
oxauth_client_jar_zf = zipfile.ZipFile(self.client_jar_fn)
for fn in oxauth_client_jar_zf.namelist():
Expand All @@ -249,8 +263,8 @@ def find_auth_client_key_path(self):
self.key_gen_path = fp.replace('/','.')
break
else:
print("Can't determine jans-auth-client KeyGenerator path. Exiting...")
sys.exit(False)
print("Can't determine {}-client KeyGenerator path. Exiting...".format(_AUTH_NAME_.lower()))
sys.exit()


def get_sig_enc_algs(self, web_keys):
Expand All @@ -264,33 +278,30 @@ def get_sig_enc_algs(self, web_keys):


def get_persistence_type(self):
prop_fn = os.path.join(self.conf_dir, 'jans.properties')
jans_properties = jproperties_parser(prop_fn)
self.auth_config_dn = jans_properties['jansAuth_ConfigurationEntryDN']
self.persistence_type = getattr(PersistenceType, jans_properties['persistence.type'])
prop_fn = os.path.join(self.conf_dir, '{}.properties'.format(_VENDOR_))
auth_properties = jproperties_parser(prop_fn)
self.auth_config_dn = auth_properties[self.prop_dn]
self.persistence_type = getattr(PersistenceType, auth_properties['persistence.type'])
dn_s = self.auth_config_dn.split(',')
self.doc_id = dn_s[0].split('=')[1]
self.key = dn_s[1].split('=')[1] + '_' + self.doc_id


def read_credidentials(self):
prop_fn = os.path.join(self.conf_dir, 'jans-{}.properties'.format(self.persistence_type.name))
prop_fn = os.path.join(self.conf_dir, '{}-{}.properties'.format(_VENDOR_, self.persistence_type.name))
self.credidentials = jproperties_parser(prop_fn)


def obtain_data_spanner(self):
self.spanner = Spanner(self.credidentials)
data = self.spanner.execute_sql('SELECT dn, jansConfDyn, jansConfWebKeys, jansRevision from jansAppConf WHERE doc_id ="{}"'.format(self.doc_id))

ox_auth_conf_dynamic = json.loads(data['jansConfDyn'])
self.key_store_secret = ox_auth_conf_dynamic['keyStoreSecret']
self.get_sig_enc_algs(json.loads(data['jansConfWebKeys']))
self.revision = int(data['jansRevision']) + 1
data = self.spanner.execute_sql('SELECT dn, {}, {}, {} from {} WHERE doc_id ="{}"'.format(self.conf_dyn, self.conf_web_keys, self.conf_rev, self.conf_objc, self.doc_id))

ox_auth_conf_dynamic = json.loads(data[self.conf_dyn])
self.key_store_secret = ox_auth_conf_dynamic[self.conf_keystore_secret]
self.get_sig_enc_algs(json.loads(data[self.conf_web_keys]))
self.revision = int(data[self.conf_rev]) + 1

def obtain_data_ldap(self):
print(self.credidentials)

ldap_password = unobscure(self.credidentials['bindPassword'], key=self.salt)
ldap_host, ldap_port = self.credidentials['servers'].split(',')[0].split(':')

Expand All @@ -302,20 +313,21 @@ def obtain_data_ldap(self):
self.ldap_conn.search(
search_base=self.auth_config_dn,
search_scope=ldap3.BASE,
search_filter='(objectClass=jansAppConf)',
attributes=['jansConfDyn', 'jansConfWebKeys', 'jansRevision']
search_filter='(objectClass={})'.format(self.conf_objc),
attributes=[self.conf_dyn, self.conf_web_keys, self.conf_rev]
)

result = self.ldap_conn.response

ox_auth_conf_dynamic = json.loads(result[0]['attributes']['jansConfDyn'][0])
self.key_store_secret = ox_auth_conf_dynamic['keyStoreSecret']
self.get_sig_enc_algs(json.loads(result[0]['attributes']['jansConfWebKeys'][0]))
self.revision = int(result[0]['attributes']['jansRevision'][0])
attributes_s = 'attributes'
ox_auth_conf_dynamic = json.loads(result[0][attributes_s][self.conf_dyn][0])
self.key_store_secret = ox_auth_conf_dynamic[self.conf_keystore_secret]
self.get_sig_enc_algs(json.loads(result[0][attributes_s][self.conf_web_keys][0]))
self.revision = int(result[0][attributes_s][self.conf_rev][0])


def obtain_data_sql(self):
sql_type, sql_host, sql_port, sql_db = re.match(r'jdbc:(.*?):\/\/(.*?):(\d*?)\/(.*?)$', self.credidentials['connection.uri']).groups()
sql_type, sql_host, sql_port, sql_db = re.match(r'jdbc:(.*?):\/\/(.*?):(\d*?)\/(.*)$', self.credidentials['connection.uri']).groups()
sql_db = sql_db.split('?')[0]
sql_password = unobscure(self.credidentials['auth.userPassword'], key=self.salt)

if sql_type == 'mysql':
Expand All @@ -328,14 +340,14 @@ def obtain_data_sql(self):
cursorclass=pymysql.cursors.DictCursor)

self.cursor = self.sql_conn.cursor()
sql = 'SELECT `jansConfDyn`, `jansConfWebKeys`, `jansRevision` from `jansAppConf` WHERE `doc_id`=%s'
sql = 'SELECT `{}`, `{}`, `{}` from `{}` WHERE `doc_id`=%s'.format(self.conf_dyn, self.conf_web_keys, self.conf_rev, self.conf_objc)
self.cursor.execute(sql, (self.doc_id,))
result = self.cursor.fetchone()

ox_auth_conf_dynamic = json.loads(result['jansConfDyn'])
self.key_store_secret = ox_auth_conf_dynamic['keyStoreSecret']
self.get_sig_enc_algs(json.loads(result['jansConfWebKeys']))
self.revision = int(result['jansRevision'])
ox_auth_conf_dynamic = json.loads(result[self.conf_dyn])
self.key_store_secret = ox_auth_conf_dynamic[self.conf_keystore_secret]
self.get_sig_enc_algs(json.loads(result[self.conf_web_keys]))
self.revision = int(result[self.conf_rev])


def obtain_data_couchbase(self):
Expand All @@ -345,37 +357,37 @@ def obtain_data_couchbase(self):
self.default_bucket = self.credidentials['bucket.default']
result = self.cbm.exec_query('SELECT * FROM {} USE KEYS "{}"'.format(self.default_bucket, self.key))
configuration_oxauth = result.json()
self.key_store_secret = configuration_oxauth['results'][0][self.default_bucket]['jansConfDyn']['keyStoreSecret']
self.get_sig_enc_algs(configuration_oxauth['results'][0][self.default_bucket]['jansConfWebKeys'])
self.revision = configuration_oxauth['results'][0][self.default_bucket]['jansRevision']
results_s = 'results'
self.key_store_secret = configuration_oxauth[results_s][0][self.default_bucket][self.conf_dyn][self.conf_keystore_secret]
self.get_sig_enc_algs(configuration_oxauth[results_s][0][self.default_bucket][self.conf_web_keys])
self.revision = configuration_oxauth[results_s][0][self.default_bucket][self.conf_rev]



def generate_keys(self):

#with open(self.keys_json_fn) as f:
# self.keys_json = f.read()
#return
if _VENDOR_ == 'jans':

print("Creating empty JKS keystore")
run_command([
self.keytool_cmd, '-genkey',
'-alias', 'dummy',
'-keystore', self.keystore_fn,
'-storepass', self.key_store_secret,
'-keypass', self.key_store_secret,
'-dname', '"{}"'.format(self.dnname)
])

print("Delete dummy key from JKS")
run_command([
self.keytool_cmd, '-delete',
'-alias', 'dummy',
'-keystore', self.keystore_fn,
'-storepass', self.key_store_secret,
'-keypass', self.key_store_secret,
'-dname', '"{}"'.format(self.dnname)
])

print("Creating empty JKS keystore")
run_command([
self.keytool_cmd, '-genkey',
'-alias', 'dummy',
'-keystore', self.keystore_fn,
'-storepass', self.key_store_secret,
'-keypass', self.key_store_secret,
'-dname', '"{}"'.format(self.dnname)
])

print("Delete dummy key from JKS")
run_command([
self.keytool_cmd, '-delete',
'-alias', 'dummy',
'-keystore', self.keystore_fn,
'-storepass', self.key_store_secret,
'-keypass', self.key_store_secret,
'-dname', '"{}"'.format(self.dnname)
])
print("Generating keys")
args = [self.java_cmd, '-Dlog4j.defaultInitOverride=true',
'-cp', self.client_jar_fn, self.key_gen_path,
Expand All @@ -395,13 +407,11 @@ def generate_keys(self):

backup_file(self.keys_json_fn)

output = run_command(args)
run_command(args)

with open(self.keys_json_fn) as f:
self.keys_json = f.read()

run_command(['cp', '-f', self.keys_json_fn, '/etc/certs'])


def validate_keys(self):

Expand All @@ -414,29 +424,27 @@ def validate_keys(self):
])


print(output)
jsk_aliases = []
for l in output[0].splitlines():
ls = l.decode().strip()
n = ls.find(':')
alias_name = ls[n+1:].strip()
jsk_aliases.append(alias_name)

print(jsk_aliases)

keys = json.loads(self.keys_json)

json_aliases = [ wkey['kid'] for wkey in keys['keys'] ]

valid1 = True
for alias_name in json_aliases:
if not alias_name in jsk_aliases:
if alias_name not in jsk_aliases:
print(keystore_fn, "does not contain", alias_name)
valid1 = False

valid2 = True
for alias_name in jsk_aliases:
if not alias_name in json_aliases:
if alias_name not in json_aliases:
print(oxauth_keys_json_fn, "does not contain", alias_name)
valid2 = False

Expand All @@ -446,11 +454,13 @@ def validate_keys(self):
print("Validation failed, not updating db")
sys.exit(1)

# validation passed, we can copy keystore to /etc/certs
run_command(['cp', '-f', self.keystore_fn, '/etc/certs'])


def update_spanner(self):
print("Updating Spanner db")
self.spanner.put_data('jansAppConf', ['doc_id', 'jansRevision', 'jansConfWebKeys'], [[self.doc_id, str(self.revision+1), self.keys_json]])
self.spanner.put_data(self.conf_objc, ['doc_id', self.conf_rev, self.conf_web_keys], [[self.doc_id, str(self.revision+1), self.keys_json]])


def update_ldap(self):
Expand All @@ -459,24 +469,26 @@ def update_ldap(self):
self.ldap_conn.modify(
self.auth_config_dn,
{
"jansConfWebKeys": [ldap3.MODIFY_REPLACE, self.keys_json],
"jansRevision": [ldap3.MODIFY_REPLACE, str(self.revision+1)]
self.conf_web_keys: [ldap3.MODIFY_REPLACE, self.keys_json],
self.conf_rev: [ldap3.MODIFY_REPLACE, str(self.revision+1)]
}
)

self.ldap_conn.unbind()


def update_sql(self):
sql = 'UPDATE `jansAppConf` SET `jansConfWebKeys`=%s, `jansRevision`=%s WHERE `doc_id`=%s'
print("Updating SQL db")
sql = 'UPDATE `{}` SET `{}`=%s, `{}`=%s WHERE `doc_id`=%s'.format(self.conf_objc, self.conf_web_keys, self.conf_rev)
self.cursor.execute(sql, (self.keys_json, self.revision+1, self.doc_id))
self.sql_conn.commit()
self.sql_conn.close()


def update_couchbase(self):
result = self.cbm.exec_query("UPDATE {0} USE KEYS '{1}' set {0}.jansConfWebKeys={2}".format(self.default_bucket, self.key, self.keys_json))
result = self.cbm.exec_query("UPDATE {0} USE KEYS '{1}' set {0}.jansRevision={2}".format(self.default_bucket, self.key, self.revision+1))
print("Updating Couchbase db")
self.cbm.exec_query("UPDATE {0} USE KEYS '{1}' set {0}.{3}={2}".format(self.default_bucket, self.key, self.keys_json, self.conf_web_keys))
self.cbm.exec_query("UPDATE {0} USE KEYS '{1}' set {0}.{3}={2}".format(self.default_bucket, self.key, self.revision+1, self.conf_rev))


key_regenerator = KeyRegenerator()