From fa12540d5da7777f5fcb4e6599e9202453c0834c Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Fri, 13 Sep 2019 21:51:41 -0400 Subject: [PATCH 01/30] introduce consul-1.5.0 acl policies --- consulate/api/acl.py | 34 ++++++++++++++++++++++++++++++++++ consulate/models/acl.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index aae9665..92afe9a 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -16,6 +16,40 @@ class ACL(base.Endpoint): tokens. """ + + def read_self_token(self): + """ Retrieve the currently used token. + """ + return self._get(["token", "self"]) + + def list_policies(self): + """ List all policies available in cluster + """ + return self._get(["policies"]) + + def create_policy(self, name, datacenters=None, description=None, rules=None): + """ Create policy with name given and rules. + + :param str name: name of the policy + :param list() datacenters: A list of datacenters to filter the policy for. Default is an empty list for all datacenters. + :param str description: Human readable description of the policy. + :param str rules: A json serializable string for the ACL rules to define for the policy. + """ + + return self._put_response_body(["policy"], {}, dict( + model.ACLPolicy(name=name, datacenters=datacenters, + description=description, rules=rules) + )) + + def read_policy(self, id): + """ Read an existing policy with the given ID. + :param str id: The ID of the policy. + """ + + return self._get(["policy", id]) + + # NOTE: Everything below here is deprecated post consul-1.4.0. + def bootstrap(self): """This endpoint does a special one-time bootstrap of the ACL system, making the first management token if the acl_master_token is not diff --git a/consulate/models/acl.py b/consulate/models/acl.py index bf8c775..cd70492 100644 --- a/consulate/models/acl.py +++ b/consulate/models/acl.py @@ -5,6 +5,37 @@ from consulate.models import base +class ACLPolicy(base.Model): + """Defins the model used fur an ACL policy. + """ + __slots__ = ['datacenters', 'description', 'id', 'name', 'rules'] + + __attributes__ = { + 'datacenters': { + 'key': 'Datacenters', + 'type': list, + }, + 'description': { + 'key': 'Description', + 'type': str, + }, + 'id': { + 'key': 'ID', + 'type': uuid.UUID, + 'cast_from': str, + 'cast_to': str, + }, + 'name': { + 'key': 'Name', + 'type': str, + }, + 'rules': { + 'key': 'Rules', + 'type': str, + } + } + + class ACL(base.Model): """Defines the model used for an individual ACL token.""" __slots__ = ['id', 'name', 'type', 'rules'] From d28db6214eed6ef85f8224dfc427af933dc97e51 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Fri, 13 Sep 2019 21:52:21 -0400 Subject: [PATCH 02/30] add delete policy with wrapper for adapter --- consulate/api/acl.py | 7 +++++++ consulate/api/base.py | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 92afe9a..0ef38ee 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -48,6 +48,13 @@ def read_policy(self, id): return self._get(["policy", id]) + def delete_policy(self, id): + """ Delete an existing policy with the given ID. + :param str id: The ID of the policy. + """ + + return self._delete(["policy", id]) + # NOTE: Everything below here is deprecated post consul-1.4.0. def bootstrap(self): diff --git a/consulate/api/base.py b/consulate/api/base.py index 46ac6ff..396035c 100644 --- a/consulate/api/base.py +++ b/consulate/api/base.py @@ -67,6 +67,20 @@ def _get(self, params, query_params=None, raise_on_404=False, return response.body return [] + def _delete(self, params, raise_on_404=False, + ): + """Perform a DELETE request + + :param list params: List of path parts + :rtype: dict or list or None + + """ + response = self._adapter.delete( + self._build_uri(params)) + if utils.response_ok(response, raise_on_404): + return response.body + return [] + def _get_list(self, params, query_params=None): """Return a list queried from Consul From 04222a5cb8e0d936d09da8986ac9d2a46bcaae06 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Fri, 13 Sep 2019 22:42:50 -0400 Subject: [PATCH 03/30] complete add of policy acl --- consulate/api/acl.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 0ef38ee..7eff72e 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -27,6 +27,12 @@ def list_policies(self): """ return self._get(["policies"]) + def read_policy(self, id): + """ Read an existing policy with the given ID. + :param str id: The ID of the policy. + """ + return self._get(["policy", id]) + def create_policy(self, name, datacenters=None, description=None, rules=None): """ Create policy with name given and rules. @@ -35,18 +41,23 @@ def create_policy(self, name, datacenters=None, description=None, rules=None): :param str description: Human readable description of the policy. :param str rules: A json serializable string for the ACL rules to define for the policy. """ - return self._put_response_body(["policy"], {}, dict( model.ACLPolicy(name=name, datacenters=datacenters, description=description, rules=rules) )) - def read_policy(self, id): - """ Read an existing policy with the given ID. - :param str id: The ID of the policy. + def update_policy(self, id, datacenters=None, description=None, name=None, rules=None): + """ Update policy with id given. + :param str id: A UUID for the policy to update. + :param list() datacenters: A list of datacenters to filter the policy for. Default is an empty list for all datacenters. + :param str description: Human readable description of the policy. + :param str name: name of the policy + :param str rules: A json serializable string for the ACL rules to define for the policy. """ - - return self._get(["policy", id]) + return self._put_response_body(["policy", id], {}, dict( + model.ACLPolicy(name=name, datacenters=datacenters, + description=description, rules=rules) + )) def delete_policy(self, id): """ Delete an existing policy with the given ID. From 7911a54e124db2064b7f451ccfe99773d4a7f6a4 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Fri, 13 Sep 2019 23:20:15 -0400 Subject: [PATCH 04/30] initial addition of role acl --- consulate/api/acl.py | 19 ++++++++++++++++++- consulate/models/acl.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 7eff72e..afc0f13 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -23,7 +23,7 @@ def read_self_token(self): return self._get(["token", "self"]) def list_policies(self): - """ List all policies available in cluster + """ List all ACL policies available in cluster """ return self._get(["policies"]) @@ -66,6 +66,23 @@ def delete_policy(self, id): return self._delete(["policy", id]) + def list_roles(self): + """ List all ACL roles available in cluster + """ + return self._get(["roles"]) + + def create_role(self, name, description=None, policies=None, service_identities=None): + """ Create an ACL role from a list of policies and or service service_identities. + :param str name: The name of the ACL role. Must be unique alphanumeral and dashes and underscores. + :param str description: The description of the ACL role. + :param PolicyLinks policies: An array of PolicyLink. + :param ServiceIdentities service_identities: An array of ServiceIdentity. + """ + return self._put_response_body(["role"], {}, dict( + model.ACLPolicy(name=name, description=description, + policies=policies, service_identities=service_identities) + )) + # NOTE: Everything below here is deprecated post consul-1.4.0. def bootstrap(self): diff --git a/consulate/models/acl.py b/consulate/models/acl.py index cd70492..c0f651c 100644 --- a/consulate/models/acl.py +++ b/consulate/models/acl.py @@ -6,8 +6,7 @@ class ACLPolicy(base.Model): - """Defins the model used fur an ACL policy. - """ + """Defines the model used fur an ACL policy.""" __slots__ = ['datacenters', 'description', 'id', 'name', 'rules'] __attributes__ = { @@ -36,6 +35,31 @@ class ACLPolicy(base.Model): } +class ACLRole(base.Model): + """Defines the model used fur an ACL role.""" + __slots__ = ['description', 'name', 'policies', 'service_identities'] + + __attributes__ = { + 'description': { + 'key': 'Description', + 'type': str, + }, + 'name': { + 'key': 'Name', + 'type': str, + 'required': True, + }, + 'policies': { + 'key': 'Policies', + 'type': list, + }, + "service_identities": { + 'key': 'ServiceIdentities', + 'type': list, + } + } + + class ACL(base.Model): """Defines the model used for an individual ACL token.""" __slots__ = ['id', 'name', 'type', 'rules'] From 430fcb5d1c5b80818a9de0c4dfbfe08dc3ce50f0 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Sun, 15 Sep 2019 15:56:24 -0400 Subject: [PATCH 05/30] acl: fix string format --- consulate/api/acl.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index afc0f13..7badf5e 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -18,41 +18,51 @@ class ACL(base.Endpoint): """ def read_self_token(self): - """ Retrieve the currently used token. + """Retrieve the currently used token. + :param rtype: dict + """ return self._get(["token", "self"]) def list_policies(self): - """ List all ACL policies available in cluster + """List all ACL policies available in cluster. + :param rtype: list + """ return self._get(["policies"]) def read_policy(self, id): - """ Read an existing policy with the given ID. + """Read an existing policy with the given ID. :param str id: The ID of the policy. + :param rtype: dict + """ return self._get(["policy", id]) def create_policy(self, name, datacenters=None, description=None, rules=None): - """ Create policy with name given and rules. + """Create policy with name given and rules. :param str name: name of the policy :param list() datacenters: A list of datacenters to filter the policy for. Default is an empty list for all datacenters. :param str description: Human readable description of the policy. :param str rules: A json serializable string for the ACL rules to define for the policy. + :param rtype: dict + """ return self._put_response_body(["policy"], {}, dict( model.ACLPolicy(name=name, datacenters=datacenters, description=description, rules=rules) )) - def update_policy(self, id, datacenters=None, description=None, name=None, rules=None): - """ Update policy with id given. + def update_policy(self, id, name, datacenters=None, description=None, rules=None): + """Update policy with id given. :param str id: A UUID for the policy to update. + :param str name: name of the policy :param list() datacenters: A list of datacenters to filter the policy for. Default is an empty list for all datacenters. :param str description: Human readable description of the policy. - :param str name: name of the policy :param str rules: A json serializable string for the ACL rules to define for the policy. + :param rtype: dict + """ return self._put_response_body(["policy", id], {}, dict( model.ACLPolicy(name=name, datacenters=datacenters, @@ -60,23 +70,29 @@ def update_policy(self, id, datacenters=None, description=None, name=None, rules )) def delete_policy(self, id): - """ Delete an existing policy with the given ID. + """Delete an existing policy with the given ID. :param str id: The ID of the policy. + :param rtype: dict + """ return self._delete(["policy", id]) def list_roles(self): - """ List all ACL roles available in cluster + """List all ACL roles available in cluster + :param rtype: list + """ return self._get(["roles"]) def create_role(self, name, description=None, policies=None, service_identities=None): - """ Create an ACL role from a list of policies and or service service_identities. + """Create an ACL role from a list of policies and or service service_identities. :param str name: The name of the ACL role. Must be unique alphanumeral and dashes and underscores. :param str description: The description of the ACL role. :param PolicyLinks policies: An array of PolicyLink. :param ServiceIdentities service_identities: An array of ServiceIdentity. + :param rtype: dict + """ return self._put_response_body(["role"], {}, dict( model.ACLPolicy(name=name, description=description, From fabc61cc2003f7139c9b15874c48cd86cad242cf Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Sun, 15 Sep 2019 15:57:06 -0400 Subject: [PATCH 06/30] add tests for acl policies --- tests/acl_tests.py | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/tests/acl_tests.py b/tests/acl_tests.py index ffad0b0..108f949 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -12,7 +12,7 @@ from . import base -ACL_RULES = """key "" { +ACL_OLD_RULES = """key "" { policy = "read" } key "foo/" { @@ -20,6 +20,14 @@ } """ +ACL_NEW_RULES = """key_prefix "" { + policy = "read +} +key "foo/" { + policy = "write" +} +""" + class TestCase(base.TestCase): @@ -67,9 +75,9 @@ def test_create_and_destroy(self): self.assertTrue(self.consul.acl.destroy(acl_id)) def test_create_with_rules(self): - acl_id = self.consul.acl.create(self.uuidv4(), rules=ACL_RULES) + acl_id = self.consul.acl.create(self.uuidv4(), rules=ACL_OLD_RULES) value = self.consul.acl.info(acl_id) - self.assertEqual(value['Rules'], ACL_RULES) + self.assertEqual(value['Rules'], ACL_OLD_RULES) def test_create_and_info(self): acl_id = self.consul.acl.create(self.uuidv4()) @@ -125,10 +133,35 @@ def test_update_not_found_adds_new_key(self): self.assertIn(acl_id, [r.get('ID') for r in data]) def test_update_with_rules(self): - acl_id = self.consul.acl.update(self.uuidv4(), name='test', rules=ACL_RULES) + acl_id = self.consul.acl.update( + self.uuidv4(), name='test', rules=ACL_OLD_RULES) value = self.consul.acl.info(acl_id) - self.assertEqual(value['Rules'], ACL_RULES) + self.assertEqual(value['Rules'], ACL_OLD_RULES) def test_update_forbidden(self): with self.assertRaises(consulate.Forbidden): self.forbidden_consul.acl.update(self.uuidv4(), name='test') + + # NOTE: Everything above here is deprecated post consul-1.4.0 + + def test_create_policy(self): + result = self.consul.acl.create_policy( + "unittest_create_policy", rules=ACL_NEW_RULES) + self.assertEqual(result['Rules'], ACL_NEW_RULES) + + def test_create_and_read_policy(self): + value = self.consul.acl.create_policy( + "unittest_read_policy", rules=ACL_NEW_RULES) + result = self.consul.acl.read_policy(value["ID"]) + self.assertEqual(result['Rules'], ACL_NEW_RULES) + + def test_create_and_delete_policy(self): + value = self.consul.acl.create_policy( + "unittest_delete_policy", rules=ACL_NEW_RULES) + result = self.consul.acl.delete_policy(value["ID"]) + self.assertTrue(result) + + def test_list_policy_exception(self): + with httmock.HTTMock(base.raise_oserror): + with self.assertRaises(exceptions.RequestError): + self.consul.acl.list_policies() From b9ce92558f0b72caf68537498364d9da50e0d0fc Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Sun, 15 Sep 2019 16:10:52 -0400 Subject: [PATCH 07/30] update testing to consul-1.6.0 settings --- docker-compose.yml | 2 +- testing/consul.json | 13 ++++++++++--- tests/api_tests.py | 4 ++-- tests/base.py | 4 ++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 01a1d32..7fe7e7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ %YAML 1.2 --- consul: - image: consul:1.0.6 + image: consul:1.6.0 ports: - 8500 volumes: diff --git a/testing/consul.json b/testing/consul.json index d74dcd4..3368abc 100644 --- a/testing/consul.json +++ b/testing/consul.json @@ -1,9 +1,16 @@ { - "acl_datacenter": "test", - "acl_master_token": "9ae5fe1a-6b38-47e5-a0e7-f06b8b2fa645", - "bootstrap": true, + "acl": { + "enabled": true, + "enable_key_list_policy": true, + "tokens": { + "master": "9ae5fe1a-6b38-47e5-a0e7-f06b8b2fa645" + } + }, + "bootstrap_expect": 1, "data_dir": "/tmp/consul", "datacenter": "test", "server": true, + "bind_addr": "{{ GetPrivateIP }}", + "client_addr": "0.0.0.0", "enable_script_checks": true } diff --git a/tests/api_tests.py b/tests/api_tests.py index 481e510..cad5710 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -35,7 +35,7 @@ def setUp(self, status, session, event, acl, health, coordinate, kv, catalog, ag self.host = '127.0.0.1' self.port = 8500 self.dc = CONSUL_CONFIG['datacenter'] - self.token = CONSUL_CONFIG['acl_master_token'] + self.token = CONSUL_CONFIG['acl']['tokens']['master'] self.acl = acl self.adapter = adapter @@ -98,7 +98,7 @@ def test_health_initialization(self): def test_coordinate_initialization(self): self.assertTrue( self.coordinate.called_once_with(self.base_uri, self.adapter, self.dc, - self.token)) + self.token)) def test_session_initialization(self): self.assertTrue( diff --git a/tests/base.py b/tests/base.py index 714abff..0685d65 100644 --- a/tests/base.py +++ b/tests/base.py @@ -33,7 +33,7 @@ def setUp(self): self.consul = consulate.Consul( host=os.environ['CONSUL_HOST'], port=os.environ['CONSUL_PORT'], - token=CONSUL_CONFIG['acl_master_token']) + token=CONSUL_CONFIG['acl']['tokens']['master']) self.forbidden_consul = consulate.Consul( host=os.environ['CONSUL_HOST'], port=os.environ['CONSUL_PORT'], @@ -53,7 +53,7 @@ def tearDown(self): self.consul.agent.service.deregister(services[name]['ID']) for acl in self.consul.acl.list(): - if acl['ID'] == CONSUL_CONFIG['acl_master_token']: + if acl['ID'] == CONSUL_CONFIG['acl']['tokens']['master']: continue try: uuid.UUID(acl['ID']) From e01c068d8e44e029a19b6f54e8fcc8fe224c7d0d Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 12:56:30 -0400 Subject: [PATCH 08/30] applying yapf to all of acl --- consulate/api/acl.py | 86 ++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 30 deletions(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 7badf5e..7658ebc 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -16,7 +16,6 @@ class ACL(base.Endpoint): tokens. """ - def read_self_token(self): """Retrieve the currently used token. :param rtype: dict @@ -39,35 +38,50 @@ def read_policy(self, id): """ return self._get(["policy", id]) - def create_policy(self, name, datacenters=None, description=None, rules=None): + def create_policy(self, + name, + datacenters=None, + description=None, + rules=None): """Create policy with name given and rules. :param str name: name of the policy - :param list() datacenters: A list of datacenters to filter the policy for. Default is an empty list for all datacenters. + :param list() datacenters: A list of datacenters to filter on policy. :param str description: Human readable description of the policy. - :param str rules: A json serializable string for the ACL rules to define for the policy. + :param str rules: A json serializable string for ACL rules. :param rtype: dict """ - return self._put_response_body(["policy"], {}, dict( - model.ACLPolicy(name=name, datacenters=datacenters, - description=description, rules=rules) - )) - - def update_policy(self, id, name, datacenters=None, description=None, rules=None): + return self._put_response_body(["policy"], {}, + dict( + model.ACLPolicy( + name=name, + datacenters=datacenters, + description=description, + rules=rules))) + + def update_policy(self, + id, + name, + datacenters=None, + description=None, + rules=None): """Update policy with id given. :param str id: A UUID for the policy to update. :param str name: name of the policy - :param list() datacenters: A list of datacenters to filter the policy for. Default is an empty list for all datacenters. + :param list() datacenters: A list of datacenters to filter on policy. :param str description: Human readable description of the policy. - :param str rules: A json serializable string for the ACL rules to define for the policy. + :param str rules: A json serializable string for ACL rules. :param rtype: dict """ - return self._put_response_body(["policy", id], {}, dict( - model.ACLPolicy(name=name, datacenters=datacenters, - description=description, rules=rules) - )) + return self._put_response_body(["policy", id], {}, + dict( + model.ACLPolicy( + name=name, + datacenters=datacenters, + description=description, + rules=rules))) def delete_policy(self, id): """Delete an existing policy with the given ID. @@ -85,19 +99,26 @@ def list_roles(self): """ return self._get(["roles"]) - def create_role(self, name, description=None, policies=None, service_identities=None): - """Create an ACL role from a list of policies and or service service_identities. - :param str name: The name of the ACL role. Must be unique alphanumeral and dashes and underscores. + def create_role(self, + name, + description=None, + policies=None, + service_identities=None): + """Create an ACL role from a list of policies or service identities. + :param str name: The name of the ACL role. Must be unique. :param str description: The description of the ACL role. :param PolicyLinks policies: An array of PolicyLink. - :param ServiceIdentities service_identities: An array of ServiceIdentity. + :param ServiceIdentities service_identities: A ServiceIdentity array. :param rtype: dict """ - return self._put_response_body(["role"], {}, dict( - model.ACLPolicy(name=name, description=description, - policies=policies, service_identities=service_identities) - )) + return self._put_response_body( + ["role"], {}, + dict( + model.ACLPolicy(name=name, + description=description, + policies=policies, + service_identities=service_identities))) # NOTE: Everything below here is deprecated post consul-1.4.0. @@ -154,9 +175,11 @@ def create(self, name, acl_type='client', rules=None): :raises: consulate.exceptions.Forbidden """ - return self._put_response_body( - ['create'], {}, dict(model.ACL( - name=name, type=acl_type, rules=rules)))['ID'] + return self._put_response_body(['create'], {}, + dict( + model.ACL(name=name, + type=acl_type, + rules=rules)))['ID'] def clone(self, acl_id): """Clone an existing ACL returning the new ACL ID @@ -232,6 +255,9 @@ def update(self, acl_id, name, acl_type='client', rules=None): :raises: consulate.exceptions.Forbidden """ - return self._put_response_body( - ['update'], {}, dict(model.ACL( - id=acl_id, name=name, type=acl_type, rules=rules)))['ID'] + return self._put_response_body(['update'], {}, + dict( + model.ACL(id=acl_id, + name=name, + type=acl_type, + rules=rules)))['ID'] From aeb8242017b3690952af539eca9d5f655b2edc82 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 13:54:04 -0400 Subject: [PATCH 09/30] add checks for PolicyLinks and ServiceIdentities --- consulate/api/acl.py | 43 +++++++++++++++++++++++++++++++++++++++++ consulate/exceptions.py | 7 +++++++ 2 files changed, 50 insertions(+) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 7658ebc..9c7e330 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -3,6 +3,7 @@ """ import logging +import json from consulate.models import acl as model from consulate.api import base @@ -11,6 +12,48 @@ LOGGER = logging.getLogger(__name__) +def __check_policylinks(policies): + """ Checks if policies is formatted correctly. + :param list policies: A list of PolicyLink. + :param rtype: bool + :raises: consulate.exceptions.ACLFormatError + + """ + for policy in policies: + if not ('ID' in policy or 'Name' in policy): + raise exceptions.ACLPolicyFormatError(str(policy)) + + return True + + +def __check_service_identities(service_identities): + """ Checks if service_identities is formatted correctly. + :param list service_identities: A ServiceIdentity list + :param rtype: bool + :raises: consulate.exceptions.ACLFormatError + + """ + for service_identity in service_identities: + if 'ServiceName' not in service_identity: + raise exceptions.ACLPolicyFormatError(str(service_identity)) + + return True + + +def __create_json_format(structures, check): + """Creates a json string from a structures provided check passes. + :param list structure: a PolicyLinks or ServiceIdentities. + :param function check: a function to check structure + :param rtype: str + + """ + formatted = None + if structures is not None and check(structures): + formatted = json.dumps(structures) + + return formatted + + class ACL(base.Endpoint): """The ACL endpoints are used to create, update, destroy, and query ACL tokens. diff --git a/consulate/exceptions.py b/consulate/exceptions.py index 5533fbb..591a3ca 100644 --- a/consulate/exceptions.py +++ b/consulate/exceptions.py @@ -24,6 +24,13 @@ class ACLDisabled(ConsulateException): """Raised when ACL related calls are made while ACLs are disabled""" +class ACLFormatError(ConsulateException): + """Raised when PolicyLinks is missing 'ID' and 'Name' in a PolicyLink or + when ServiceIdentities is missing 'ServiceName' field in a ServiceIdentity. + + """ + + class Forbidden(ConsulateException): """Raised when ACLs are enabled and the token does not validate""" From 8132c2372c6838a3db2d6b6aa3ca51924dbdd3c0 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 14:14:44 -0400 Subject: [PATCH 10/30] add typing for acl roles --- consulate/api/acl.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 9c7e330..e88258a 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -8,9 +8,15 @@ from consulate.models import acl as model from consulate.api import base from consulate import exceptions +# from typing import List, Dict, Union LOGGER = logging.getLogger(__name__) +# ServiceIdentity = Dict[str, Union[str, List[str]]] +# ServiceIdentities = List[ServiceIdentity] +# PolicyLink = Dict[str, str] +# PolicyLinks = List[PolicyLink] + def __check_policylinks(policies): """ Checks if policies is formatted correctly. From 007c9646afd497659eb2d6b57dcfc7ef039b2873 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 14:33:36 -0400 Subject: [PATCH 11/30] add update and read role --- consulate/api/acl.py | 45 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index e88258a..ed10c65 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -138,7 +138,6 @@ def delete_policy(self, id): :param rtype: dict """ - return self._delete(["policy", id]) def list_roles(self): @@ -148,6 +147,20 @@ def list_roles(self): """ return self._get(["roles"]) + def read_role(self, id=None, name=None): + """Read an existing role with the given ID or Name. + :param str id: The ID of the role. + :param str name: The name of the role. + :param rtype: dict + + """ + if id is not None: + return self._get(["role", id]) + elif name is not None: + return self._get(["role", "name", name]) + else: + raise exceptions.NotFound("Either id or name must be specified") + def create_role(self, name, description=None, @@ -161,6 +174,10 @@ def create_role(self, :param rtype: dict """ + policies = __create_json_format(policies, __check_policylinks) + service_identities = __create_json_format(service_identities, + __check_service_identities) + return self._put_response_body( ["role"], {}, dict( @@ -169,6 +186,32 @@ def create_role(self, policies=policies, service_identities=service_identities))) + def update_role(self, + id, + name, + description=None, + policies=None, + service_identities=None): + """Update role with id given. + :param str id: A UUID for the policy to update. + :param str name: name of the policy + :param list() datacenters: A list of datacenters to filter on policy. + :param str description: Human readable description of the policy. + :param str rules: A json serializable string for ACL rules. + :param rtype: dict + + """ + policies = __create_json_format(policies, __check_policylinks) + service_identities = __create_json_format(service_identities, + __check_service_identities) + return self._put_response_body( + ["role", id], {}, + dict( + model.ACLPolicy(name=name, + description=description, + policies=policies, + service_identities=service_identities))) + # NOTE: Everything below here is deprecated post consul-1.4.0. def bootstrap(self): From aba929bac154e28f5df74be7bb6ec62a94275ce5 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 14:35:13 -0400 Subject: [PATCH 12/30] format acl tests with yapf --- tests/acl_tests.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/acl_tests.py b/tests/acl_tests.py index 108f949..874ea7f 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -30,13 +30,11 @@ class TestCase(base.TestCase): - @staticmethod def uuidv4(): return str(uuid.uuid4()) def test_bootstrap_request_exception(self): - @httmock.all_requests def response_content(_url_unused, _request): raise OSError @@ -50,8 +48,8 @@ def test_bootstrap_success(self): @httmock.all_requests def response_content(_url_unused, request): - return httmock.response( - 200, json.dumps({'ID': expectation}), {}, None, 0, request) + return httmock.response(200, json.dumps({'ID': expectation}), {}, + None, 0, request) with httmock.HTTMock(response_content): result = self.consul.acl.bootstrap() @@ -133,8 +131,9 @@ def test_update_not_found_adds_new_key(self): self.assertIn(acl_id, [r.get('ID') for r in data]) def test_update_with_rules(self): - acl_id = self.consul.acl.update( - self.uuidv4(), name='test', rules=ACL_OLD_RULES) + acl_id = self.consul.acl.update(self.uuidv4(), + name='test', + rules=ACL_OLD_RULES) value = self.consul.acl.info(acl_id) self.assertEqual(value['Rules'], ACL_OLD_RULES) @@ -145,19 +144,19 @@ def test_update_forbidden(self): # NOTE: Everything above here is deprecated post consul-1.4.0 def test_create_policy(self): - result = self.consul.acl.create_policy( - "unittest_create_policy", rules=ACL_NEW_RULES) + result = self.consul.acl.create_policy("unittest_create_policy", + rules=ACL_NEW_RULES) self.assertEqual(result['Rules'], ACL_NEW_RULES) def test_create_and_read_policy(self): - value = self.consul.acl.create_policy( - "unittest_read_policy", rules=ACL_NEW_RULES) + value = self.consul.acl.create_policy("unittest_read_policy", + rules=ACL_NEW_RULES) result = self.consul.acl.read_policy(value["ID"]) self.assertEqual(result['Rules'], ACL_NEW_RULES) def test_create_and_delete_policy(self): - value = self.consul.acl.create_policy( - "unittest_delete_policy", rules=ACL_NEW_RULES) + value = self.consul.acl.create_policy("unittest_delete_policy", + rules=ACL_NEW_RULES) result = self.consul.acl.delete_policy(value["ID"]) self.assertTrue(result) From db25705da41f77d2470851bcb036c9d2625d8594 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 14:58:43 -0400 Subject: [PATCH 13/30] add tests for role --- tests/acl_tests.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/acl_tests.py b/tests/acl_tests.py index 874ea7f..1a71373 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -28,6 +28,12 @@ } """ +POLICYLINKS_SAMPLE = [ + dict(ID="783beef3-783f-f41f-7422-7087dc272765"), +] + +SERVICE_IDENTITIES_SAMPLE = [dict(ServiceName="db", Datacenters=["dc1"])] + class TestCase(base.TestCase): @staticmethod @@ -164,3 +170,33 @@ def test_list_policy_exception(self): with httmock.HTTMock(base.raise_oserror): with self.assertRaises(exceptions.RequestError): self.consul.acl.list_policies() + + def test_create_role(self): + result = self.consul.acl.create_create_role( + "unittest_create_role", + policies=POLICYLINKS_SAMPLE, + service_identities=SERVICE_IDENTITIES_SAMPLE) + self.assertEqual(result['Policies'][0]['ID'], + POLICYLINKS_SAMPLE['Policies'][0]['ID']) + + def test_create_and_read_role(self): + value = self.consul.acl.create_role( + "unittest_read_role", + policies=POLICYLINKS_SAMPLE, + service_identities=SERVICE_IDENTITIES_SAMPLE) + result = self.consul.acl.read_role(value["ID"]) + self.assertEqual(result['Policies'][0]['ID'], + POLICYLINKS_SAMPLE[0]["ID"]) + + def test_create_and_delete_role(self): + value = self.consul.acl.create_role( + "unittest_delete_role", + policies=POLICYLINKS_SAMPLE, + service_identities=SERVICE_IDENTITIES_SAMPLE) + result = self.consul.acl.delete_role(value["ID"]) + self.assertTrue(result) + + def test_list_roles_exception(self): + with httmock.HTTMock(base.raise_oserror): + with self.assertRaises(exceptions.RequestError): + self.consul.acl.list_roles() From cea26d1dfbc4c20854bb27f503675a1b84d21eb0 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 15:04:36 -0400 Subject: [PATCH 14/30] add acl delete role --- consulate/api/acl.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index ed10c65..7ad02d2 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -135,7 +135,7 @@ def update_policy(self, def delete_policy(self, id): """Delete an existing policy with the given ID. :param str id: The ID of the policy. - :param rtype: dict + :param rtype: bool """ return self._delete(["policy", id]) @@ -212,6 +212,14 @@ def update_role(self, policies=policies, service_identities=service_identities))) + def delete_role(self, id): + """Delete an existing role with the given ID. + :param str id: The ID of the role. + :param rtype: bool + + """ + return self._delete(["policy", id]) + # NOTE: Everything below here is deprecated post consul-1.4.0. def bootstrap(self): From 29ac4b5646a37255a7e3a6cc7b00a827c9d49677 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 15:08:43 -0400 Subject: [PATCH 15/30] run yapf on api/base.py --- consulate/api/base.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/consulate/api/base.py b/consulate/api/base.py index 396035c..b9abb8a 100644 --- a/consulate/api/base.py +++ b/consulate/api/base.py @@ -61,22 +61,24 @@ def _get(self, params, query_params=None, raise_on_404=False, :rtype: dict or list or None """ - response = self._adapter.get( - self._build_uri(params, query_params), timeout=timeout) + response = self._adapter.get(self._build_uri(params, query_params), + timeout=timeout) if utils.response_ok(response, raise_on_404): return response.body return [] - def _delete(self, params, raise_on_404=False, - ): + def _delete( + self, + params, + raise_on_404=False, + ): """Perform a DELETE request :param list params: List of path parts :rtype: dict or list or None """ - response = self._adapter.delete( - self._build_uri(params)) + response = self._adapter.delete(self._build_uri(params)) if utils.response_ok(response, raise_on_404): return response.body return [] @@ -119,8 +121,8 @@ def _put_no_response_body(self, url_parts, query=None, payload=None): self._adapter.put(self._build_uri(url_parts, query), payload)) def _put_response_body(self, url_parts, query=None, payload=None): - response = self._adapter.put( - self._build_uri(url_parts, query), data=payload) + response = self._adapter.put(self._build_uri(url_parts, query), + data=payload) if utils.response_ok(response): return response.body From 559f5cac3f552fb1ffe4d4302a2015b08d497161 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 15:16:54 -0400 Subject: [PATCH 16/30] fix delete response --- consulate/api/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consulate/api/base.py b/consulate/api/base.py index b9abb8a..deca849 100644 --- a/consulate/api/base.py +++ b/consulate/api/base.py @@ -75,13 +75,13 @@ def _delete( """Perform a DELETE request :param list params: List of path parts - :rtype: dict or list or None + :rtype: bool """ response = self._adapter.delete(self._build_uri(params)) if utils.response_ok(response, raise_on_404): return response.body - return [] + return False def _get_list(self, params, query_params=None): """Return a list queried from Consul From b3cdb246f8078066b3f5ae1d5cf1f77bc0fbd395 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 15:59:20 -0400 Subject: [PATCH 17/30] add initial token creation --- consulate/api/acl.py | 30 ++++++++++++++++++++++++++++++ consulate/models/acl.py | 16 ++++++++++++++-- tests/acl_tests.py | 14 ++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 7ad02d2..e1aa600 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -16,6 +16,8 @@ # ServiceIdentities = List[ServiceIdentity] # PolicyLink = Dict[str, str] # PolicyLinks = List[PolicyLink] +# RoleLink = Dict[str, str] +# RoleLinks = List[RoleLink] def __check_policylinks(policies): @@ -220,6 +222,34 @@ def delete_role(self, id): """ return self._delete(["policy", id]) + def create_token(self, + accessor_id=None, + description=None, + expiration_time=None, + expiration_ttl=None, + local=False, + policies=None, + roles=None, + secret_id=None, + service_identities=None): + """Create a token from the roles, policies, and service identities + provided. + :param str accessor_id: A UUID for accessing the token. + :param str description: A human-readable description of the token. + :param str expiration_time: The amount of time till the token expires. + :param str expiration_ttl: Sets expiration_time to creation time + + expiration_ttl value. + :param bool local: Whether the token is only locally available in the + current datacenter or to all datacenters defined. + :param PolicyLinks policies: A PolicyLink array. + :param RoleLinks roles: A RoleLink array. + :param str secret_id: A UUID for making requests to consul. + :param ServiceIdentities service_identities: A ServiceIdentity array. + :param rtype: dict + + """ + pass + # NOTE: Everything below here is deprecated post consul-1.4.0. def bootstrap(self): diff --git a/consulate/models/acl.py b/consulate/models/acl.py index c0f651c..04c4b23 100644 --- a/consulate/models/acl.py +++ b/consulate/models/acl.py @@ -6,7 +6,7 @@ class ACLPolicy(base.Model): - """Defines the model used fur an ACL policy.""" + """Defines the model used for an ACL policy.""" __slots__ = ['datacenters', 'description', 'id', 'name', 'rules'] __attributes__ = { @@ -36,7 +36,7 @@ class ACLPolicy(base.Model): class ACLRole(base.Model): - """Defines the model used fur an ACL role.""" + """Defines the model used for an ACL role.""" __slots__ = ['description', 'name', 'policies', 'service_identities'] __attributes__ = { @@ -60,6 +60,18 @@ class ACLRole(base.Model): } +class ACLToken(base.Model): + """Defines the model used for an ACL token.""" + __slots__ = [ + 'accessor_id', 'description', 'expiration_time', 'expiration_ttl', + 'local', 'policies', 'roles', 'secret_id', 'service_identities' + ] + pass + + +# NOTE: Everything below here is deprecated post consul-1.4.0. + + class ACL(base.Model): """Defines the model used for an individual ACL token.""" __slots__ = ['id', 'name', 'type', 'rules'] diff --git a/tests/acl_tests.py b/tests/acl_tests.py index 1a71373..e4b0132 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -34,6 +34,8 @@ SERVICE_IDENTITIES_SAMPLE = [dict(ServiceName="db", Datacenters=["dc1"])] +ROLELINKS_SAMPLE = [dict(Name="some_role_name")] + class TestCase(base.TestCase): @staticmethod @@ -200,3 +202,15 @@ def test_list_roles_exception(self): with httmock.HTTMock(base.raise_oserror): with self.assertRaises(exceptions.RequestError): self.consul.acl.list_roles() + + def test_create_token(self): + secret_id = self.uuidv4() + accessor_id = self.uuidv4() + result = self.consul.acl.create_create_token( + accessor_id=accessor_id, + secret_id=secret_id, + roles=ROLELINKS_SAMPLE, + policies=POLICYLINKS_SAMPLE, + service_identities=SERVICE_IDENTITIES_SAMPLE) + self.assertEqual(result['AccessorID'], accessor_id) + self.assertEqual(result['SecretID'], secret_id) From 7f3805e152aea08303007ac617c74bfa8bb551ae Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 16:31:25 -0400 Subject: [PATCH 18/30] add validate model for roles, policies, and token --- consulate/models/acl.py | 72 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/consulate/models/acl.py b/consulate/models/acl.py index 04c4b23..afdd170 100644 --- a/consulate/models/acl.py +++ b/consulate/models/acl.py @@ -5,6 +5,29 @@ from consulate.models import base +def _validate_link_array(value, model): + """ Validate the policies or roles links are formatted correctly. + :param list(dict()) value: An array of PolicyLink or RoleLink. + :param rtype: bool + :param consulate.models.agent.Check model: The model instance. + :param rtype: bool + + """ + return all(['ID' in link or 'Name' in link + for link in value]) and not model.args + + +def _validate_service_identities(value, model): + """ Validate service_identities is formatted correctly. + :param list(dict()) value: A ServiceIdentity list + :param rtype: bool + + """ + return all( + ['ServiceName' in service_identity + for service_identity in value]) and not model.args + + class ACLPolicy(base.Model): """Defines the model used for an ACL policy.""" __slots__ = ['datacenters', 'description', 'id', 'name', 'rules'] @@ -52,10 +75,12 @@ class ACLRole(base.Model): 'policies': { 'key': 'Policies', 'type': list, + 'validator': _validate_link_array, }, "service_identities": { 'key': 'ServiceIdentities', 'type': list, + 'validator': _validate_service_identities, } } @@ -66,7 +91,52 @@ class ACLToken(base.Model): 'accessor_id', 'description', 'expiration_time', 'expiration_ttl', 'local', 'policies', 'roles', 'secret_id', 'service_identities' ] - pass + + __attributes__ = { + 'accessor_id': { + 'key': 'AccessorID', + 'type': uuid.UUID, + 'cast_from': str, + 'cast_to': str, + }, + 'description': { + 'key': 'Description', + 'type': str, + }, + 'expiration_time': { + 'key': 'ExpirationTime', + 'type': str, + }, + 'expiration_ttl': { + 'key': 'ExpirationTTL', + 'type': str, + }, + 'local': { + 'key': 'Local', + 'type': bool, + }, + 'policies': { + 'key': 'Policies', + 'type': list, + 'validator': _validate_link_array, + }, + 'roles': { + 'key': 'Roles', + 'type': list, + 'validator': _validate_link_array, + }, + 'secret_id': { + 'key': 'SecretID', + 'type': uuid.UUID, + 'cast_from': str, + 'cast_to': str, + }, + "service_identities": { + 'key': 'ServiceIdentities', + 'type': list, + 'validator': _validate_service_identities, + } + } # NOTE: Everything below here is deprecated post consul-1.4.0. From a85c8ccd7bb1c8892448ba058fcda5442366ce8d Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 16:34:10 -0400 Subject: [PATCH 19/30] remove __check functions for validator model --- consulate/api/acl.py | 50 -------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index e1aa600..03d0723 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -3,7 +3,6 @@ """ import logging -import json from consulate.models import acl as model from consulate.api import base @@ -20,48 +19,6 @@ # RoleLinks = List[RoleLink] -def __check_policylinks(policies): - """ Checks if policies is formatted correctly. - :param list policies: A list of PolicyLink. - :param rtype: bool - :raises: consulate.exceptions.ACLFormatError - - """ - for policy in policies: - if not ('ID' in policy or 'Name' in policy): - raise exceptions.ACLPolicyFormatError(str(policy)) - - return True - - -def __check_service_identities(service_identities): - """ Checks if service_identities is formatted correctly. - :param list service_identities: A ServiceIdentity list - :param rtype: bool - :raises: consulate.exceptions.ACLFormatError - - """ - for service_identity in service_identities: - if 'ServiceName' not in service_identity: - raise exceptions.ACLPolicyFormatError(str(service_identity)) - - return True - - -def __create_json_format(structures, check): - """Creates a json string from a structures provided check passes. - :param list structure: a PolicyLinks or ServiceIdentities. - :param function check: a function to check structure - :param rtype: str - - """ - formatted = None - if structures is not None and check(structures): - formatted = json.dumps(structures) - - return formatted - - class ACL(base.Endpoint): """The ACL endpoints are used to create, update, destroy, and query ACL tokens. @@ -176,10 +133,6 @@ def create_role(self, :param rtype: dict """ - policies = __create_json_format(policies, __check_policylinks) - service_identities = __create_json_format(service_identities, - __check_service_identities) - return self._put_response_body( ["role"], {}, dict( @@ -203,9 +156,6 @@ def update_role(self, :param rtype: dict """ - policies = __create_json_format(policies, __check_policylinks) - service_identities = __create_json_format(service_identities, - __check_service_identities) return self._put_response_body( ["role", id], {}, dict( From 2ebd6ceef1a9fad7f00bd9bebf7b8a33948dc081 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 16:49:48 -0400 Subject: [PATCH 20/30] add read and list token --- consulate/api/acl.py | 41 +++++++++++++++++++++++++++++++++-------- tests/acl_tests.py | 26 +++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 03d0723..f9c634d 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -24,13 +24,6 @@ class ACL(base.Endpoint): tokens. """ - def read_self_token(self): - """Retrieve the currently used token. - :param rtype: dict - - """ - return self._get(["token", "self"]) - def list_policies(self): """List all ACL policies available in cluster. :param rtype: list @@ -172,6 +165,28 @@ def delete_role(self, id): """ return self._delete(["policy", id]) + def list_tokens(self): + """List all ACL tokens available in cluster. + :param rtype: list + + """ + return self._get(["tokens"]) + + def read_token(self, accessor_id): + """Read an existing token with the given ID. + :param str id: The ID of the role. + :param rtype: dict + + """ + return self._get(["token", accessor_id]) + + def read_self_token(self): + """Retrieve the currently used token. + :param rtype: dict + + """ + return self._get(["token", "self"]) + def create_token(self, accessor_id=None, description=None, @@ -198,7 +213,17 @@ def create_token(self, :param rtype: dict """ - pass + return self._put_response_body( + ["token"], {}, + dict( + model.ACLToken(accessor_id=accessor_id, + description=description, + expiration_time=expiration_time, + expiration_ttl=expiration_ttl, + local=local, + policies=policies, + roles=roles, + service_identities=service_identities))) # NOTE: Everything below here is deprecated post consul-1.4.0. diff --git a/tests/acl_tests.py b/tests/acl_tests.py index e4b0132..5b25d3f 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -206,7 +206,7 @@ def test_list_roles_exception(self): def test_create_token(self): secret_id = self.uuidv4() accessor_id = self.uuidv4() - result = self.consul.acl.create_create_token( + result = self.consul.acl.create_token( accessor_id=accessor_id, secret_id=secret_id, roles=ROLELINKS_SAMPLE, @@ -214,3 +214,27 @@ def test_create_token(self): service_identities=SERVICE_IDENTITIES_SAMPLE) self.assertEqual(result['AccessorID'], accessor_id) self.assertEqual(result['SecretID'], secret_id) + + def test_create_and_read_token(self): + secret_id = self.uuidv4() + accessor_id = self.uuidv4() + value = self.consul.acl.create_token( + accessor_id=accessor_id, + secret_id=secret_id, + roles=ROLELINKS_SAMPLE, + policies=POLICYLINKS_SAMPLE, + service_identities=SERVICE_IDENTITIES_SAMPLE) + result = self.consul.acl.read_token(value["AccessorID"]) + self.assertEqual(result['AccessorID'], accessor_id) + + def test_create_and_delete_token(self): + secret_id = self.uuidv4() + accessor_id = self.uuidv4() + value = self.consul.acl.create_token( + accessor_id=accessor_id, + secret_id=secret_id, + roles=ROLELINKS_SAMPLE, + policies=POLICYLINKS_SAMPLE, + service_identities=SERVICE_IDENTITIES_SAMPLE) + result = self.consul.acl.delete_token(value["AccessorID"]) + self.assertTrue(result) From 61ca857f1b610f28c4f77c848a2a4e840e68f1dd Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 17:04:15 -0400 Subject: [PATCH 21/30] add a space for acl function descriptions --- consulate/api/acl.py | 21 +++++++++++++++++++++ consulate/models/acl.py | 2 ++ 2 files changed, 23 insertions(+) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index f9c634d..c99858a 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -26,6 +26,7 @@ class ACL(base.Endpoint): """ def list_policies(self): """List all ACL policies available in cluster. + :param rtype: list """ @@ -33,6 +34,7 @@ def list_policies(self): def read_policy(self, id): """Read an existing policy with the given ID. + :param str id: The ID of the policy. :param rtype: dict @@ -68,6 +70,7 @@ def update_policy(self, description=None, rules=None): """Update policy with id given. + :param str id: A UUID for the policy to update. :param str name: name of the policy :param list() datacenters: A list of datacenters to filter on policy. @@ -86,6 +89,7 @@ def update_policy(self, def delete_policy(self, id): """Delete an existing policy with the given ID. + :param str id: The ID of the policy. :param rtype: bool @@ -101,6 +105,7 @@ def list_roles(self): def read_role(self, id=None, name=None): """Read an existing role with the given ID or Name. + :param str id: The ID of the role. :param str name: The name of the role. :param rtype: dict @@ -119,6 +124,7 @@ def create_role(self, policies=None, service_identities=None): """Create an ACL role from a list of policies or service identities. + :param str name: The name of the ACL role. Must be unique. :param str description: The description of the ACL role. :param PolicyLinks policies: An array of PolicyLink. @@ -141,6 +147,7 @@ def update_role(self, policies=None, service_identities=None): """Update role with id given. + :param str id: A UUID for the policy to update. :param str name: name of the policy :param list() datacenters: A list of datacenters to filter on policy. @@ -159,6 +166,7 @@ def update_role(self, def delete_role(self, id): """Delete an existing role with the given ID. + :param str id: The ID of the role. :param rtype: bool @@ -167,6 +175,7 @@ def delete_role(self, id): def list_tokens(self): """List all ACL tokens available in cluster. + :param rtype: list """ @@ -174,6 +183,7 @@ def list_tokens(self): def read_token(self, accessor_id): """Read an existing token with the given ID. + :param str id: The ID of the role. :param rtype: dict @@ -182,6 +192,7 @@ def read_token(self, accessor_id): def read_self_token(self): """Retrieve the currently used token. + :param rtype: dict """ @@ -199,6 +210,7 @@ def create_token(self, service_identities=None): """Create a token from the roles, policies, and service identities provided. + :param str accessor_id: A UUID for accessing the token. :param str description: A human-readable description of the token. :param str expiration_time: The amount of time till the token expires. @@ -225,6 +237,15 @@ def create_token(self, roles=roles, service_identities=service_identities))) + def delete_token(self, accessor_id): + """Delete an existing token with the given AcccessorID. + + :param str id: The AccessorID of the token. + :param rtype: bool + + """ + return self._delete(["token", accessor_id]) + # NOTE: Everything below here is deprecated post consul-1.4.0. def bootstrap(self): diff --git a/consulate/models/acl.py b/consulate/models/acl.py index afdd170..9b19b5e 100644 --- a/consulate/models/acl.py +++ b/consulate/models/acl.py @@ -7,6 +7,7 @@ def _validate_link_array(value, model): """ Validate the policies or roles links are formatted correctly. + :param list(dict()) value: An array of PolicyLink or RoleLink. :param rtype: bool :param consulate.models.agent.Check model: The model instance. @@ -19,6 +20,7 @@ def _validate_link_array(value, model): def _validate_service_identities(value, model): """ Validate service_identities is formatted correctly. + :param list(dict()) value: A ServiceIdentity list :param rtype: bool From 408594338818cc60a316697f53c809d7514167e4 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 17:18:24 -0400 Subject: [PATCH 22/30] add tests for all updates with token update fn --- consulate/api/acl.py | 39 +++++++++++++++++++++++++++++++++++++++ tests/acl_tests.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index c99858a..45f15a1 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -237,6 +237,45 @@ def create_token(self, roles=roles, service_identities=service_identities))) + def update_token(self, + accessor_id, + description=None, + expiration_time=None, + expiration_ttl=None, + local=False, + policies=None, + roles=None, + secret_id=None, + service_identities=None): + """Create a token from the roles, policies, and service identities + provided. + + :param str accessor_id: A UUID for accessing the token. + :param str description: A human-readable description of the token. + :param str expiration_time: The amount of time till the token expires. + :param str expiration_ttl: Sets expiration_time to creation time + + expiration_ttl value. + :param bool local: Whether the token is only locally available in the + current datacenter or to all datacenters defined. + :param PolicyLinks policies: A PolicyLink array. + :param RoleLinks roles: A RoleLink array. + :param str secret_id: A UUID for making requests to consul. + :param ServiceIdentities service_identities: A ServiceIdentity array. + :param rtype: dict + + """ + return self._put_response_body( + ["token", accessor_id], {}, + dict( + model.ACLToken(accessor_id=accessor_id, + description=description, + expiration_time=expiration_time, + expiration_ttl=expiration_ttl, + local=local, + policies=policies, + roles=roles, + service_identities=service_identities))) + def delete_token(self, accessor_id): """Delete an existing token with the given AcccessorID. diff --git a/tests/acl_tests.py b/tests/acl_tests.py index 5b25d3f..d71ad55 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -28,10 +28,23 @@ } """ +ACL_NEW_UPDATE_RULES = """key_prefix "" { + policy = "deny" +} +key "foo/" { + policy = "read" +} +""" + POLICYLINKS_SAMPLE = [ dict(ID="783beef3-783f-f41f-7422-7087dc272765"), ] +POLICYLINKS_UPDATE_SAMPLE = [ + dict(ID="783beef3-783f-f41f-7422-7087dc272765"), + dict(Name="some_policy_name") +] + SERVICE_IDENTITIES_SAMPLE = [dict(ServiceName="db", Datacenters=["dc1"])] ROLELINKS_SAMPLE = [dict(Name="some_role_name")] @@ -162,6 +175,14 @@ def test_create_and_read_policy(self): result = self.consul.acl.read_policy(value["ID"]) self.assertEqual(result['Rules'], ACL_NEW_RULES) + def test_create_and_update_policy(self): + value = self.consul.acl.create_policy("unittest_read_policy", + rules=ACL_NEW_RULES) + result = self.consul.acl.update_policy(value["ID"], + value["Name"], + policy=ACL_NEW_UPDATE_RULES) + self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) + def test_create_and_delete_policy(self): value = self.consul.acl.create_policy("unittest_delete_policy", rules=ACL_NEW_RULES) @@ -190,6 +211,15 @@ def test_create_and_read_role(self): self.assertEqual(result['Policies'][0]['ID'], POLICYLINKS_SAMPLE[0]["ID"]) + def test_create_and_update_role(self): + value = self.consul.acl.create_role( + "unittest_read_role", + policies=POLICYLINKS_SAMPLE, + service_identities=SERVICE_IDENTITIES_SAMPLE) + result = self.consul.acl.update_role( + value["ID"], policies=POLICYLINKS_UPDATE_SAMPLE) + self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) + def test_create_and_delete_role(self): value = self.consul.acl.create_role( "unittest_delete_role", @@ -227,6 +257,19 @@ def test_create_and_read_token(self): result = self.consul.acl.read_token(value["AccessorID"]) self.assertEqual(result['AccessorID'], accessor_id) + def test_create_and_update_token(self): + secret_id = self.uuidv4() + accessor_id = self.uuidv4() + value = self.consul.acl.create_token( + accessor_id=accessor_id, + secret_id=secret_id, + roles=ROLELINKS_SAMPLE, + policies=POLICYLINKS_SAMPLE, + service_identities=SERVICE_IDENTITIES_SAMPLE) + result = self.consul.acl.update_token( + value["AccessorID"], policies=POLICYLINKS_UPDATE_SAMPLE) + self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) + def test_create_and_delete_token(self): secret_id = self.uuidv4() accessor_id = self.uuidv4() From c590d581022d7144114685b4d575f1fa8abf633c Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 17:29:16 -0400 Subject: [PATCH 23/30] add clone token --- consulate/api/acl.py | 12 ++++++++++++ tests/acl_tests.py | 14 ++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 45f15a1..b9735ab 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -276,6 +276,18 @@ def update_token(self, roles=roles, service_identities=service_identities))) + def clone_token(self, accessor_id, description=None): + """Clone a token by the accessor_id. + + :param str accessor_id: A UUID for accessing the token. + :param str description: A human-readable description of the token. + :param rtype: dict + + """ + return self._put_response_body( + ["token", accessor_id, "clone"], {}, + dict(model.ACLToken(description=description))) + def delete_token(self, accessor_id): """Delete an existing token with the given AcccessorID. diff --git a/tests/acl_tests.py b/tests/acl_tests.py index d71ad55..2b61bde 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -270,6 +270,20 @@ def test_create_and_update_token(self): value["AccessorID"], policies=POLICYLINKS_UPDATE_SAMPLE) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) + def test_create_and_clone_token(self): + secret_id = self.uuidv4() + accessor_id = self.uuidv4() + clone_description = "clone token of " + accessor_id + value = self.consul.acl.create_token( + accessor_id=accessor_id, + secret_id=secret_id, + roles=ROLELINKS_SAMPLE, + policies=POLICYLINKS_SAMPLE, + service_identities=SERVICE_IDENTITIES_SAMPLE) + result = self.consul.acl.clone_token(value["AccessorID"], + description="clone") + self.assertEqual(result["Description"], clone_description) + def test_create_and_delete_token(self): secret_id = self.uuidv4() accessor_id = self.uuidv4() From ce2cf4b3e665c29935203f278255eecf3bbf5920 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Mon, 16 Sep 2019 23:14:52 -0400 Subject: [PATCH 24/30] fix errors found on tests --- tests/acl_tests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/acl_tests.py b/tests/acl_tests.py index 2b61bde..e61abc4 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -180,7 +180,7 @@ def test_create_and_update_policy(self): rules=ACL_NEW_RULES) result = self.consul.acl.update_policy(value["ID"], value["Name"], - policy=ACL_NEW_UPDATE_RULES) + rules=ACL_NEW_UPDATE_RULES) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_delete_policy(self): @@ -195,12 +195,11 @@ def test_list_policy_exception(self): self.consul.acl.list_policies() def test_create_role(self): - result = self.consul.acl.create_create_role( + result = self.consul.acl.create_role( "unittest_create_role", policies=POLICYLINKS_SAMPLE, service_identities=SERVICE_IDENTITIES_SAMPLE) - self.assertEqual(result['Policies'][0]['ID'], - POLICYLINKS_SAMPLE['Policies'][0]['ID']) + self.assertEqual(result[0]['ID'], POLICYLINKS_SAMPLE[0]['ID']) def test_create_and_read_role(self): value = self.consul.acl.create_role( @@ -217,7 +216,9 @@ def test_create_and_update_role(self): policies=POLICYLINKS_SAMPLE, service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.update_role( - value["ID"], policies=POLICYLINKS_UPDATE_SAMPLE) + value["ID"], + "unittest_read_role", + policies=POLICYLINKS_UPDATE_SAMPLE) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_delete_role(self): From 2f6e3ff4c0e5421fc5dfa6df85d783a9d5ad5c56 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Tue, 17 Sep 2019 09:34:14 -0400 Subject: [PATCH 25/30] add missing secret_id argument --- consulate/api/acl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index b9735ab..8d6c3df 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -235,6 +235,7 @@ def create_token(self, local=local, policies=policies, roles=roles, + secret_id=secret_id, service_identities=service_identities))) def update_token(self, @@ -274,6 +275,7 @@ def update_token(self, local=local, policies=policies, roles=roles, + secret_id=secret_id, service_identities=service_identities))) def clone_token(self, accessor_id, description=None): From be4a07e8b92f339941966afaf1ecfe086b350324 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Tue, 17 Sep 2019 13:45:13 -0400 Subject: [PATCH 26/30] fix errors on role and validate --- consulate/api/acl.py | 16 ++++++++-------- consulate/models/acl.py | 12 ++++-------- tests/acl_tests.py | 8 ++++---- tests/base.py | 8 ++++---- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/consulate/api/acl.py b/consulate/api/acl.py index 8d6c3df..c10317c 100644 --- a/consulate/api/acl.py +++ b/consulate/api/acl.py @@ -135,10 +135,10 @@ def create_role(self, return self._put_response_body( ["role"], {}, dict( - model.ACLPolicy(name=name, - description=description, - policies=policies, - service_identities=service_identities))) + model.ACLRole(name=name, + description=description, + policies=policies, + service_identities=service_identities))) def update_role(self, id, @@ -159,10 +159,10 @@ def update_role(self, return self._put_response_body( ["role", id], {}, dict( - model.ACLPolicy(name=name, - description=description, - policies=policies, - service_identities=service_identities))) + model.ACLRole(name=name, + description=description, + policies=policies, + service_identities=service_identities))) def delete_role(self, id): """Delete an existing role with the given ID. diff --git a/consulate/models/acl.py b/consulate/models/acl.py index 9b19b5e..3c334fe 100644 --- a/consulate/models/acl.py +++ b/consulate/models/acl.py @@ -8,26 +8,22 @@ def _validate_link_array(value, model): """ Validate the policies or roles links are formatted correctly. - :param list(dict()) value: An array of PolicyLink or RoleLink. - :param rtype: bool - :param consulate.models.agent.Check model: The model instance. + :param list value: An array of PolicyLink or RoleLink. :param rtype: bool """ - return all(['ID' in link or 'Name' in link - for link in value]) and not model.args + return all(['ID' in link or 'Name' in link for link in value]) def _validate_service_identities(value, model): """ Validate service_identities is formatted correctly. - :param list(dict()) value: A ServiceIdentity list + :param ServiceIdentities value: A ServiceIdentity list :param rtype: bool """ return all( - ['ServiceName' in service_identity - for service_identity in value]) and not model.args + ['ServiceName' in service_identity for service_identity in value]) class ACLPolicy(base.Model): diff --git a/tests/acl_tests.py b/tests/acl_tests.py index e61abc4..d3de5a8 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -37,17 +37,17 @@ """ POLICYLINKS_SAMPLE = [ - dict(ID="783beef3-783f-f41f-7422-7087dc272765"), + dict(Name="policylink_sample"), ] POLICYLINKS_UPDATE_SAMPLE = [ - dict(ID="783beef3-783f-f41f-7422-7087dc272765"), - dict(Name="some_policy_name") + dict(Name="policylink_sample"), + dict(Name="policylink_update_sample") ] SERVICE_IDENTITIES_SAMPLE = [dict(ServiceName="db", Datacenters=["dc1"])] -ROLELINKS_SAMPLE = [dict(Name="some_role_name")] +ROLELINKS_SAMPLE = [dict(Name="role_sample")] class TestCase(base.TestCase): diff --git a/tests/base.py b/tests/base.py index 0685d65..80f6075 100644 --- a/tests/base.py +++ b/tests/base.py @@ -52,11 +52,11 @@ def tearDown(self): for name in services: self.consul.agent.service.deregister(services[name]['ID']) - for acl in self.consul.acl.list(): - if acl['ID'] == CONSUL_CONFIG['acl']['tokens']['master']: + for acl in self.consul.acl.list_tokens(): + if acl['AccessorID'] == CONSUL_CONFIG['acl']['tokens']['master']: continue try: - uuid.UUID(acl['ID']) - self.consul.acl.destroy(acl['ID']) + uuid.UUID(acl['AccessorID']) + self.consul.acl.delete_token(acl['AccessorID']) except (ValueError, exceptions.ConsulateException): pass From cdf88bdaa89c1cf3320ed9cc956ff1b05b929c82 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Tue, 17 Sep 2019 14:45:44 -0400 Subject: [PATCH 27/30] update to abaez.consulate --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 66bc6b7..2eeb10e 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,8 @@ import setuptools setuptools.setup( - name='consulate', - version='1.0.0', + name='abaez.consulate', + version='1.1.0', description='A Client library and command line application for the Consul', maintainer='Gavin M. Roy', maintainer_email='gavinr@aweber.com', From 4b841191466878f61a5dd60731dae9b0ea3d9041 Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Tue, 17 Sep 2019 14:48:40 -0400 Subject: [PATCH 28/30] make generator for roles and policy tests --- tests/acl_tests.py | 48 +++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/tests/acl_tests.py b/tests/acl_tests.py index d3de5a8..49e44b2 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -4,6 +4,7 @@ """ import json import uuid +import random import httmock @@ -55,6 +56,18 @@ class TestCase(base.TestCase): def uuidv4(): return str(uuid.uuid4()) + @staticmethod + def random(): + return str(random.random()) + + def _generate_policies(self): + sample = self.consul.acl.create_policy(self.random()) + sample_update = self.consul.acl.create_policy(self.random()) + return [dict(ID=sample["ID"]), dict(ID=sample_update["ID"])] + + def _generate_roles(self): + return [dict(ID=self.consul.acl.create_role(self.random()))] + def test_bootstrap_request_exception(self): @httmock.all_requests def response_content(_url_unused, _request): @@ -176,7 +189,7 @@ def test_create_and_read_policy(self): self.assertEqual(result['Rules'], ACL_NEW_RULES) def test_create_and_update_policy(self): - value = self.consul.acl.create_policy("unittest_read_policy", + value = self.consul.acl.create_policy("unittest_update_policy", rules=ACL_NEW_RULES) result = self.consul.acl.update_policy(value["ID"], value["Name"], @@ -197,34 +210,33 @@ def test_list_policy_exception(self): def test_create_role(self): result = self.consul.acl.create_role( "unittest_create_role", - policies=POLICYLINKS_SAMPLE, - service_identities=SERVICE_IDENTITIES_SAMPLE) - self.assertEqual(result[0]['ID'], POLICYLINKS_SAMPLE[0]['ID']) + policies=self._generate_policies(), + service_identities=SERVICE_IDENTITIES_SAMPLE, + self.assertEqual(result['ServiceIdentities'][0]['ID'], SERVICE_IDENTITIES_SAMPLE[0]['ID']) def test_create_and_read_role(self): value = self.consul.acl.create_role( "unittest_read_role", - policies=POLICYLINKS_SAMPLE, + policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.read_role(value["ID"]) - self.assertEqual(result['Policies'][0]['ID'], - POLICYLINKS_SAMPLE[0]["ID"]) + self.assertEqual(result['ID'], value['ID']) def test_create_and_update_role(self): value = self.consul.acl.create_role( - "unittest_read_role", - policies=POLICYLINKS_SAMPLE, + "unittest_update_role", + policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.update_role( value["ID"], - "unittest_read_role", + "unittest_update_role", policies=POLICYLINKS_UPDATE_SAMPLE) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_delete_role(self): value = self.consul.acl.create_role( "unittest_delete_role", - policies=POLICYLINKS_SAMPLE, + policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.delete_role(value["ID"]) self.assertTrue(result) @@ -240,8 +252,8 @@ def test_create_token(self): result = self.consul.acl.create_token( accessor_id=accessor_id, secret_id=secret_id, - roles=ROLELINKS_SAMPLE, - policies=POLICYLINKS_SAMPLE, + roles=self._generate_roles(), + policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) self.assertEqual(result['AccessorID'], accessor_id) self.assertEqual(result['SecretID'], secret_id) @@ -252,8 +264,8 @@ def test_create_and_read_token(self): value = self.consul.acl.create_token( accessor_id=accessor_id, secret_id=secret_id, - roles=ROLELINKS_SAMPLE, - policies=POLICYLINKS_SAMPLE, + roles=self._generate_roles(), + policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.read_token(value["AccessorID"]) self.assertEqual(result['AccessorID'], accessor_id) @@ -264,11 +276,11 @@ def test_create_and_update_token(self): value = self.consul.acl.create_token( accessor_id=accessor_id, secret_id=secret_id, - roles=ROLELINKS_SAMPLE, - policies=POLICYLINKS_SAMPLE, + roles=self._generate_roles(), + policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.update_token( - value["AccessorID"], policies=POLICYLINKS_UPDATE_SAMPLE) + value["AccessorID"], policies=self._generate_policies()) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_clone_token(self): From d79796ab14d603a0b198e6284ed7c7545f79e20b Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Tue, 17 Sep 2019 15:31:35 -0400 Subject: [PATCH 29/30] clean up tests for acl --- tests/acl_tests.py | 61 ++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/tests/acl_tests.py b/tests/acl_tests.py index 49e44b2..30027cc 100644 --- a/tests/acl_tests.py +++ b/tests/acl_tests.py @@ -22,7 +22,7 @@ """ ACL_NEW_RULES = """key_prefix "" { - policy = "read + policy = "read" } key "foo/" { policy = "write" @@ -46,9 +46,7 @@ dict(Name="policylink_update_sample") ] -SERVICE_IDENTITIES_SAMPLE = [dict(ServiceName="db", Datacenters=["dc1"])] - -ROLELINKS_SAMPLE = [dict(Name="role_sample")] +SERVICE_IDENTITIES_SAMPLE = [dict(ServiceName="db", Datacenters=list("dc1"))] class TestCase(base.TestCase): @@ -58,7 +56,7 @@ def uuidv4(): @staticmethod def random(): - return str(random.random()) + return str(random.randint(0, 999999)) def _generate_policies(self): sample = self.consul.acl.create_policy(self.random()) @@ -66,7 +64,8 @@ def _generate_policies(self): return [dict(ID=sample["ID"]), dict(ID=sample_update["ID"])] def _generate_roles(self): - return [dict(ID=self.consul.acl.create_role(self.random()))] + role = self.consul.acl.create_role(self.random()) + return [dict(ID=role["ID"])] def test_bootstrap_request_exception(self): @httmock.all_requests @@ -178,27 +177,27 @@ def test_update_forbidden(self): # NOTE: Everything above here is deprecated post consul-1.4.0 def test_create_policy(self): - result = self.consul.acl.create_policy("unittest_create_policy", - rules=ACL_NEW_RULES) + name = self.random() + result = self.consul.acl.create_policy(name=name, rules=ACL_NEW_RULES) self.assertEqual(result['Rules'], ACL_NEW_RULES) def test_create_and_read_policy(self): - value = self.consul.acl.create_policy("unittest_read_policy", - rules=ACL_NEW_RULES) + name = self.random() + value = self.consul.acl.create_policy(name=name, rules=ACL_NEW_RULES) result = self.consul.acl.read_policy(value["ID"]) self.assertEqual(result['Rules'], ACL_NEW_RULES) def test_create_and_update_policy(self): - value = self.consul.acl.create_policy("unittest_update_policy", - rules=ACL_NEW_RULES) + name = self.random() + value = self.consul.acl.create_policy(name=name, rules=ACL_NEW_RULES) result = self.consul.acl.update_policy(value["ID"], - value["Name"], + str(value["Name"]), rules=ACL_NEW_UPDATE_RULES) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_delete_policy(self): - value = self.consul.acl.create_policy("unittest_delete_policy", - rules=ACL_NEW_RULES) + name = self.random() + value = self.consul.acl.create_policy(name=name, rules=ACL_NEW_RULES) result = self.consul.acl.delete_policy(value["ID"]) self.assertTrue(result) @@ -208,34 +207,38 @@ def test_list_policy_exception(self): self.consul.acl.list_policies() def test_create_role(self): + name = self.random() result = self.consul.acl.create_role( - "unittest_create_role", + name=name, policies=self._generate_policies(), - service_identities=SERVICE_IDENTITIES_SAMPLE, - self.assertEqual(result['ServiceIdentities'][0]['ID'], SERVICE_IDENTITIES_SAMPLE[0]['ID']) + service_identities=SERVICE_IDENTITIES_SAMPLE) + self.assertEqual(result['Name'], name) def test_create_and_read_role(self): + name = self.random() value = self.consul.acl.create_role( - "unittest_read_role", + name=name, policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.read_role(value["ID"]) self.assertEqual(result['ID'], value['ID']) def test_create_and_update_role(self): + name = self.random() value = self.consul.acl.create_role( - "unittest_update_role", + name=name, policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.update_role( value["ID"], - "unittest_update_role", - policies=POLICYLINKS_UPDATE_SAMPLE) + str(value["Name"]), + policies=self._generate_policies()) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_delete_role(self): + name = self.random() value = self.consul.acl.create_role( - "unittest_delete_role", + name=name, policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.delete_role(value["ID"]) @@ -280,7 +283,7 @@ def test_create_and_update_token(self): policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.update_token( - value["AccessorID"], policies=self._generate_policies()) + str(value["AccessorID"]), policies=self._generate_policies()) self.assertGreater(result["ModifyIndex"], result["CreateIndex"]) def test_create_and_clone_token(self): @@ -290,11 +293,11 @@ def test_create_and_clone_token(self): value = self.consul.acl.create_token( accessor_id=accessor_id, secret_id=secret_id, - roles=ROLELINKS_SAMPLE, - policies=POLICYLINKS_SAMPLE, + roles=self._generate_roles(), + policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.clone_token(value["AccessorID"], - description="clone") + description=clone_description) self.assertEqual(result["Description"], clone_description) def test_create_and_delete_token(self): @@ -303,8 +306,8 @@ def test_create_and_delete_token(self): value = self.consul.acl.create_token( accessor_id=accessor_id, secret_id=secret_id, - roles=ROLELINKS_SAMPLE, - policies=POLICYLINKS_SAMPLE, + roles=self._generate_roles(), + policies=self._generate_policies(), service_identities=SERVICE_IDENTITIES_SAMPLE) result = self.consul.acl.delete_token(value["AccessorID"]) self.assertTrue(result) From e4b46d78d7a9ad811ff412bb0f4b25a77a48c1bc Mon Sep 17 00:00:00 2001 From: Alejandro Baez Date: Tue, 17 Sep 2019 16:11:12 -0400 Subject: [PATCH 30/30] add environment defaults with addr for client --- consulate/client.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/consulate/client.py b/consulate/client.py index bf9fc39..bf8e8fb 100644 --- a/consulate/client.py +++ b/consulate/client.py @@ -2,11 +2,14 @@ Consul client object """ +import os from consulate import adapters, api, utils -DEFAULT_HOST = 'localhost' -DEFAULT_PORT = 8500 +DEFAULT_HOST = os.environ.get('CONSUL_HOST') or 'localhost' +DEFAULT_PORT = os.environ.get('CONSUL_PORT') or 8500 +DEFAULT_ADDR = os.environ.get('CONSUL_HTTP_ADDR') DEFAULT_SCHEME = 'http' +DEFAULT_TOKEN = os.environ.get('CONSUL_HTTP_TOKEN') API_VERSION = 'v1' @@ -23,6 +26,7 @@ class Consul(object): adapter=consulate.adapters.UnixSocketRequest) services = consul.agent.services() + :param str addr: The CONSUL_HTTP_ADDR if available (Default: None) :param str host: The host name to connect to (Default: localhost) :param int port: The port to connect on (Default: 8500) :param str datacenter: Specify a specific data center @@ -36,17 +40,21 @@ class Consul(object): """ def __init__(self, + addr=DEFAULT_ADDR, host=DEFAULT_HOST, port=DEFAULT_PORT, datacenter=None, - token=None, + token=DEFAULT_TOKEN, scheme=DEFAULT_SCHEME, adapter=None, verify=True, cert=None, timeout=None): """Create a new instance of the Consul class""" - base_uri = self._base_uri(scheme, host, port) + base_uri = self._base_uri(addr=addr, + scheme=scheme, + host=host, + port=port) self._adapter = adapter() if adapter else adapters.Request( timeout=timeout, verify=verify, cert=cert) self._acl = api.ACL(base_uri, self._adapter, datacenter, token) @@ -54,12 +62,13 @@ def __init__(self, self._catalog = api.Catalog(base_uri, self._adapter, datacenter, token) self._event = api.Event(base_uri, self._adapter, datacenter, token) self._health = api.Health(base_uri, self._adapter, datacenter, token) - self._coordinate = api.Coordinate(base_uri, self._adapter, datacenter, token) + self._coordinate = api.Coordinate(base_uri, self._adapter, datacenter, + token) self._kv = api.KV(base_uri, self._adapter, datacenter, token) self._session = api.Session(base_uri, self._adapter, datacenter, token) self._status = api.Status(base_uri, self._adapter, datacenter, token) - self._lock = api.Lock( - base_uri, self._adapter, self._session, datacenter, token) + self._lock = api.Lock(base_uri, self._adapter, self._session, + datacenter, token) @property def acl(self): @@ -172,7 +181,7 @@ def status(self): return self._status @staticmethod - def _base_uri(scheme, host, port): + def _base_uri(scheme, host, port, addr=None): """Return the base URI to use for API requests. Set ``port`` to None when creating a UNIX Socket URL. @@ -182,7 +191,10 @@ def _base_uri(scheme, host, port): :rtype: str """ - if port: - return '{0}://{1}:{2}/{3}'.format(scheme, host, port, API_VERSION) - return '{0}://{1}/{2}'.format( - scheme, utils.quote(host, ''), API_VERSION) + if addr is None: + if port: + return '{0}://{1}:{2}/{3}'.format(scheme, host, port, + API_VERSION) + return '{0}://{1}/{2}'.format(scheme, utils.quote(host, ''), + API_VERSION) + return '{0}/{1}'.format(addr, API_VERSION)