diff options
author | Hansen, Tony (th1395) <th1395@att.com> | 2018-08-22 00:52:37 +0000 |
---|---|---|
committer | Hansen, Tony (th1395) <th1395@att.com> | 2018-08-22 00:53:55 +0000 |
commit | 03bac46783200b036b769411b773c87d0ee2cd1e (patch) | |
tree | 7bc1bb16c5667a31aa29d2c37cae0fac87a091be | |
parent | 59e33384d34340c2608e089bb03c5c118ff15142 (diff) |
add mechanism to force a password change
for plugins/pgaas_plugin
Change-Id: Iac969f330683304bc6b6f6a81187cf75ac70815e
Signed-off-by: Hansen, Tony (th1395) <th1395@att.com>
Issue-ID: CCSDK-479
Signed-off-by: Hansen, Tony (th1395) <th1395@att.com>
-rw-r--r-- | pgaas/pgaas/pgaas_plugin.py | 91 | ||||
-rw-r--r-- | pgaas/pgaas_types.yaml | 1 | ||||
-rw-r--r-- | pgaas/tests/test_plugin.py | 27 |
3 files changed, 102 insertions, 17 deletions
diff --git a/pgaas/pgaas/pgaas_plugin.py b/pgaas/pgaas/pgaas_plugin.py index d04cc2e..a5e4440 100644 --- a/pgaas/pgaas/pgaas_plugin.py +++ b/pgaas/pgaas/pgaas_plugin.py @@ -16,6 +16,8 @@ # limitations under the License. # ============LICENSE_END====================================================== +from __future__ import print_function + from cloudify import ctx from cloudify.decorators import operation from cloudify.exceptions import NonRecoverableError @@ -228,7 +230,7 @@ def rootdesc(data, dbname, initialpassword=None): 'host': hostportion(data['rw']), 'port': portportion(data['rw']), 'user': 'postgres', - 'password': initialpassword if initialpassword else getpass(data, 'postgres') + 'password': initialpassword if initialpassword else getpass(data, 'postgres', data['rw'], 'postgres') } def rootconn(data, dbname='postgres', initialpassword=None): @@ -250,7 +252,7 @@ def onedesc(data, dbname, role, access): 'host': hostportion(data[access]), 'port': portportion(data[access]), 'user': user, - 'password': getpass(data, user) + 'password': getpass(data, user, data['rw'], dbname) } def dbdescs(data, dbname): @@ -263,12 +265,26 @@ def dbdescs(data, dbname): 'viewer': onedesc(data, dbname, 'viewer', 'ro') } -def getpass(data, ident): +def getpass(data, ident, hostport, dbname): """ generate the password for a given user on a specific server """ m = hashlib.sha256() m.update(ident) + + # mix in the seed (the last line) for that database, if one exists + hostport = hostport.lower() + dbname = dbname.lower() + hostPortDbname = '{0}/pgaas/{1}:{2}'.format(OPT_MANAGER_RESOURCES, hostport, dbname) + try: + lastLine = '' + with open(hostPortDbname, "r") as fp: + for line in fp: + lastLine = line.strip() + m.update(line) + except IOError: + pass + m.update(base64.b64decode(data['data'])) return m.hexdigest() @@ -290,7 +306,7 @@ def chkfqdn(fqdn): verify that a FQDN is valid """ hp = hostportion(fqdn) - pp = portportion(fqdn) + # not needed right now: pp = portportion(fqdn) # TODO need to augment this for IPv6 addresses return re.match('^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$', hp) is not None @@ -317,15 +333,15 @@ def getclusterinfo(wfqdn, reuse, rfqdn, initialpassword, related): if len(related) != 0: raiseNonRecoverableError('Cluster SSH keypair must not be specified when using an existing cluster') try: - fn = '{0}/pgaas/{1}'.format(OPT_MANAGER_RESOURCES, wfqdn).lower() - with open('{0}/pgaas/{1}'.format(OPT_MANAGER_RESOURCES, wfqdn).lower(), 'r') as f: + fn = '{0}/pgaas/{1}'.format(OPT_MANAGER_RESOURCES, wfqdn.lower()) + with open(fn, 'r') as f: data = json.load(f) data['rw'] = wfqdn return data except Exception as e: warn("Error: {0}".format(e)) warn("Stack: {0}".format(traceback.format_exc())) - raiseNonRecoverableError('Cluster must be deployed when using an existing cluster: fqdn={0}, err={1}'.format(safestr(wfqdn),e)) + raiseNonRecoverableError('Cluster must be deployed when using an existing cluster. Check your domain name: fqdn={0}, err={1}'.format(safestr(wfqdn),e)) if rfqdn == '': rfqdn = wfqdn elif not chkfqdn(rfqdn): @@ -339,17 +355,17 @@ def getclusterinfo(wfqdn, reuse, rfqdn, initialpassword, related): except: pass try: - with open('{0}/pgaas/{1}'.format(OPT_MANAGER_RESOURCES, wfqdn).lower(), 'w') as f: + with open('{0}/pgaas/{1}'.format(OPT_MANAGER_RESOURCES, wfqdn.lower()), 'w') as f: f.write(json.dumps(data)) except Exception as e: warn("Error: {0}".format(e)) warn("Stack: {0}".format(traceback.format_exc())) - raiseNonRecoverableError('Cannot write cluster information to {0}/pgaas: fqdn={1}, err={2}'.format(OPT_MANAGER_RESOURCES, safestr(wfqdn),e)) + raiseNonRecoverableError('Cannot write cluster information to {0}/pgaas: fqdn={1}, err={2}'.format(OPT_MANAGER_RESOURCES, safestr(wfqdn), e)) data['rw'] = wfqdn if initialpassword: with rootconn(data, initialpassword=initialpassword) as conn: crr = conn.cursor() - dbexecute(crr, "ALTER USER postgres WITH PASSWORD %s", (getpass(data, 'postgres'),)) + dbexecute(crr, "ALTER USER postgres WITH PASSWORD %s", (getpass(data, 'postgres', wfqdn, 'postgres'),)) crr.close() return(data) @@ -368,7 +384,7 @@ def add_pgaas_cluster(**kwargs): find_related_nodes('dcae.relationships.pgaas_cluster_uses_sshkeypair')) ctx.instance.runtime_properties['public'] = data['pubkey'] ctx.instance.runtime_properties['base64private'] = data['data'] - # ctx.instance.runtime_properties['postgrespswd'] = getpass(data, 'postgres') + # not needed right now: ctx.instance.runtime_properties['postgrespswd'] = getpass(data, 'postgres', ctx.node.properties['writerfqdn'], 'postgres') warn('All done') except Exception as e: ctx.logger.warn("Error: {0}".format(e)) @@ -515,3 +531,56 @@ def delete_database(**kwargs): ctx.logger.warn("Error: {0}".format(e)) ctx.logger.warn("Stack: {0}".format(traceback.format_exc())) raise e + +@operation +def update_database(**kwargs): + """ + dcae.nodes.pgaas.database: + Update the password for a database from a cluster + """ + try: + debug("update_database() invoked") + dbname = ctx.node.properties['name'] + warn("update_database({0})".format(safestr(dbname))) + if not chkdbname(dbname): + return + debug('update_database(): dbname checked out') + if ctx.node.properties['use_existing']: + return + debug('update_database(): !use_existing') + hostport = ctx.node.properties['writerfqdn'] + debug('update_database(): wfqdn={}'.format(hostport)) + info = dbgetinfo(ctx) + debug('Got db server info') + hostPortDbname = '{0}/pgaas/{1}:{2}'.format(OPT_MANAGER_RESOURCES, hostport.lower(), dbname.lower()) + debug('update_database(): hostPortDbname={}'.format(hostPortDbname)) + try: + appended = False + with open(hostPortDbname, "a") as fp: + with open("/dev/urandom", "rb") as rp: + b = rp.read(16) + import binascii + print(binascii.hexlify(b).decode('utf-8'), file=fp) + appended = True + if not appended: + ctx.logger.warn("Error: the password for {} {} was not successfully changed".format(hostport, dbname)) + except Exception as e: + ctx.logger.warn("Error: {0}".format(e)) + ctx.logger.warn("Stack: {0}".format(traceback.format_exc())) + raise e + + with rootconn(info) as conn: + crx = conn.cursor() + admu = ctx.instance.runtime_properties['admin']['user'] + usru = ctx.instance.runtime_properties['user']['user'] + vwru = ctx.instance.runtime_properties['viewer']['user'] + cusr = '{0}_common_user_role'.format(dbname) + cvwr = '{0}_common_viewer_role'.format(dbname) + for r in [ usru, vwru, admu ]: + dbexecute(crx,"ALTER USER {} WITH PASSWORD '{}'".format(r, getpass(info, r, hostport, dbname))) + + warn('All users updated for database {}'.format(dbname)) + except Exception as e: + ctx.logger.warn("Error: {0}".format(e)) + ctx.logger.warn("Stack: {0}".format(traceback.format_exc())) + raise e diff --git a/pgaas/pgaas_types.yaml b/pgaas/pgaas_types.yaml index d98d326..2118231 100644 --- a/pgaas/pgaas_types.yaml +++ b/pgaas/pgaas_types.yaml @@ -53,6 +53,7 @@ node_types: cloudify.interfaces.lifecycle: create: pgaas.pgaas.pgaas_plugin.create_database delete: pgaas.pgaas.pgaas_plugin.delete_database + update: pgaas.pgaas.pgaas_plugin.update_database relationships: dcae.relationships.pgaas_cluster_uses_sshkeypair: diff --git a/pgaas/tests/test_plugin.py b/pgaas/tests/test_plugin.py index 197654e..d3c29b9 100644 --- a/pgaas/tests/test_plugin.py +++ b/pgaas/tests/test_plugin.py @@ -31,6 +31,8 @@ from cloudify import ctx import sys, os sys.path.append(os.path.realpath(os.path.dirname(__file__))) +TMPNAME = "/tmp/pgaas_plugin_tests" + class MockKeyPair(object): def __init__(self, type_hierarchy=None, target=None): self._type_hierarchy = type_hierarchy @@ -74,7 +76,7 @@ def _connect(h,p): def set_mock_context(msg, monkeypatch): print("================ %s ================" % msg) - os.system("echo Before test; ls -l /tmp/pgaas") #### DELETE + os.system("exec >> {0}.out 2>&1; echo Before test".format(TMPNAME)) #### DELETE props = { 'writerfqdn': 'test.bar.example.com', 'use_existing': False, @@ -106,18 +108,22 @@ def set_mock_context(msg, monkeypatch): current_ctx.set(mock_ctx) monkeypatch.setattr(socket.socket, 'connect', _connect) # monkeypatch.setattr(psycopg2, 'connect', _connect) - pgaas.pgaas_plugin.setOptManagerResources("/tmp") + pgaas.pgaas_plugin.setOptManagerResources(TMPNAME) @pytest.mark.dependency() +def test_start(monkeypatch): + os.system("exec > {0}.out 2>&1; echo Before any test; rm -rf {0}; mkdir -p {0}".format(TMPNAME)) #### DELETE + +@pytest.mark.dependency(depends=['test_start']) def test_add_pgaas_cluster(monkeypatch): try: set_mock_context('test_add_pgaas_cluster', monkeypatch) pgaas.pgaas_plugin.add_pgaas_cluster(args={}) finally: current_ctx.clear() - os.system("echo After test; ls -l /tmp/pgaas") #### DELETE + os.system("exec >> {0}.out 2>&1; echo After add_pgaas_cluster test; ls -lR {0}; head -1000 /dev/null {0}/pgaas/*;echo".format(TMPNAME)) #### DELETE @pytest.mark.dependency(depends=['test_add_pgaas_cluster']) def test_add_database(monkeypatch): @@ -126,16 +132,25 @@ def test_add_database(monkeypatch): pgaas.pgaas_plugin.create_database(args={}) finally: current_ctx.clear() - os.system("echo After test; ls -l /tmp/pgaas") #### DELETE + os.system("exec >> {0}.out 2>&1; echo After add_database test; ls -lR {0}; head -1000 /dev/null {0}/pgaas/*;echo".format(TMPNAME)) #### DELETE @pytest.mark.dependency(depends=['test_add_database']) +def test_update_database(monkeypatch): + try: + set_mock_context('test_update_database', monkeypatch) + pgaas.pgaas_plugin.update_database(args={}) + finally: + current_ctx.clear() + os.system("exec >> {0}.out 2>&1; echo After update_database test; ls -lR {0}; head -1000 /dev/null {0}/pgaas/*;echo".format(TMPNAME)) #### DELETE + +@pytest.mark.dependency(depends=['test_update_database']) def test_delete_database(monkeypatch): try: set_mock_context('test_delete_database', monkeypatch) pgaas.pgaas_plugin.delete_database(args={}) finally: current_ctx.clear() - os.system("echo After test; ls -l /tmp/pgaas") #### DELETE + os.system("exec >> {0}.out 2>&1; echo After delete_database test; ls -lR {0}; head -1000 /dev/null {0}/pgaas/*;echo".format(TMPNAME)) #### DELETE @pytest.mark.dependency(depends=['test_delete_database']) def test_rm_pgaas_cluster(monkeypatch): @@ -144,5 +159,5 @@ def test_rm_pgaas_cluster(monkeypatch): pgaas.pgaas_plugin.rm_pgaas_cluster(args={}) finally: current_ctx.clear() - os.system("echo After test; ls -l /tmp/pgaas") #### DELETE + os.system("exec >> {0}.out 2>&1; echo After delete_database test; ls -lR {0}; head -1000 /dev/null {0}/pgaas/*;echo".format(TMPNAME)) #### DELETE |