Skip to content

Commit d5d2563

Browse files
committed
Policies now working at docker infrastructure level
Need to write more policies tests
1 parent 0ebed1e commit d5d2563

File tree

7 files changed

+131
-59
lines changed

7 files changed

+131
-59
lines changed

nipyapi/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104

105105
# --- Security Context
106106
# This allows easy reference to a set of certificates for use in automation
107-
# By default it points to our demo certs, you should change it for your environment
107+
# By default it points to our demo certs, change it for your environment
108108
default_certs_path = os.path.join(PROJECT_ROOT_DIR, 'demo/keys')
109109
default_ssl_context = {
110110
'ca_file': os.path.join(default_certs_path, 'localhost-ts.pem'),

nipyapi/demo/secure_connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def connect_nifi_to_registry():
112112
# Make NiFi server a trusted proxy in NiFi Registry
113113
proxy_access_policies = [
114114
("write", "/proxy"),
115-
("read", "/buckets"),
115+
("read", "/buckets")
116116
]
117117
for action, resource in proxy_access_policies:
118118
pol = nipyapi.security.get_access_policy_for_resource(

nipyapi/security.py

Lines changed: 116 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,36 @@
99
import ssl
1010
import six
1111
import urllib3
12+
from copy import copy
1213
import nipyapi
1314

1415

1516
log = logging.getLogger(__name__)
1617

1718

18-
__all__ = ['create_service_user', 'create_service_user_group', 'service_login',
19+
__all__ = ['create_service_user', 'create_service_user_group',
1920
'set_service_auth_token', 'service_logout',
2021
'get_service_access_status', 'add_user_to_access_policy',
2122
'update_access_policy', 'get_access_policy_for_resource',
2223
'create_access_policy', 'list_service_users', 'get_service_user',
23-
'set_service_ssl_context', 'add_user_group_to_access_policy']
24+
'set_service_ssl_context', 'add_user_group_to_access_policy',
25+
'bootstrap_security_policies', 'service_login'
26+
]
2427

2528
# These are the known-valid policy actions
2629
_valid_actions = ['read', 'write', 'delete']
2730
# These are the services that these functions know how to configure
2831
_valid_services = ['nifi', 'registry']
2932

3033

31-
def create_service_user(identity, service='nifi'):
34+
def create_service_user(identity, service='nifi', strict=True):
3235
"""
3336
Attempts to create a user with the provided identity in the given service
3437
3538
Args:
3639
identity (str): Identity string for the user
3740
service (str): 'nifi' or 'registry'
41+
strict (bool): If Strict, will error if user already exists
3842
3943
Returns:
4044
The new (User) or (UserEntity) object
@@ -61,6 +65,8 @@ def create_service_user(identity, service='nifi'):
6165
except (
6266
nipyapi.nifi.rest.ApiException,
6367
nipyapi.registry.rest.ApiException) as e:
68+
if 'already exists' in e.body and not strict:
69+
return get_service_user(identity, service=service)
6470
raise ValueError(e.body)
6571

6672

@@ -274,15 +280,18 @@ def get_service_access_status(service='nifi', bool_response=False):
274280
raise e
275281

276282

277-
def add_user_to_access_policy(user, policy, service='nifi', refresh=True):
283+
def add_user_to_access_policy(user, policy, service='nifi', refresh=True,
284+
strict=True):
278285
"""
279286
Attempts to add the given user object to the given access policy
280287
281288
Args:
282289
user (User) or (UserEntity): User object to add
283290
policy (AccessPolicyEntity) or (AccessPolicy): Access Policy object
284291
service (str): 'nifi' or 'registry' to identify the target service
285-
refresh (bool): Whether to refresh the policy object before submission
292+
refresh (bool): Whether to refresh the policy object before submit
293+
strict (bool): If True, will return error if user already present,
294+
if False will ignore the already exists
286295
287296
Returns:
288297
Updated Policy object
@@ -322,15 +331,17 @@ def add_user_to_access_policy(user, policy, service='nifi', refresh=True):
322331
policy_user_ids = [
323332
i.identifier if service == 'registry' else i.id for i in policy_users
324333
]
334+
if user_id not in policy_user_ids:
335+
if service == 'registry':
336+
policy_tgt.users.append(user)
337+
elif service == 'nifi':
338+
policy_tgt.component.users.append({'id': user_id})
325339

326-
assert user_id not in policy_user_ids
327-
328-
if service == 'registry':
329-
policy_tgt.users.append(user)
330-
elif service == 'nifi':
331-
policy_tgt.component.users.append({'id': user_id})
332-
333-
return nipyapi.security.update_access_policy(policy_tgt, service)
340+
return nipyapi.security.update_access_policy(policy_tgt, service)
341+
else:
342+
if strict:
343+
assert user_id not in policy_user_ids, "Strict is True and user already " \
344+
"in Policy"
334345

335346

336347
def add_user_group_to_access_policy(user_group, policy, service='nifi',
@@ -450,18 +461,25 @@ def get_access_policy_for_resource(resource,
450461
log.info("Called get_access_policy_for_resource with Args %s", locals())
451462

452463
# Strip leading '/' from resource as lookup endpoint prepends a '/'
453-
stripped_resource = resource[1:] if resource.startswith(
454-
'/') else resource
455-
log.info("Getting %s Policy for %s:%s:%s", service, action, resource, str(r_id))
464+
resource = resource[1:] if resource.startswith('/') else resource
465+
log.info("Getting %s Policy for %s:%s:%s", service, action,
466+
resource, str(r_id))
456467
if service == 'nifi':
457468
pol_api = nipyapi.nifi.PoliciesApi()
469+
config = nipyapi.config.nifi_config
458470
else:
459471
pol_api = nipyapi.registry.PoliciesApi()
472+
config = nipyapi.config.registry_config
473+
default_safe_chars = copy(config.safe_chars_for_path_param)
460474
try:
461-
return pol_api.get_access_policy_for_resource(
475+
if '/' not in config.safe_chars_for_path_param:
476+
config.safe_chars_for_path_param += '/'
477+
response = pol_api.get_access_policy_for_resource(
462478
action=action,
463-
resource=stripped_resource
479+
resource='/'.join([resource, r_id]) if r_id else resource
464480
)
481+
config.safe_chars_for_path_param = copy(default_safe_chars)
482+
return response
465483
except nipyapi.nifi.rest.ApiException as e:
466484
if 'Unable to find access policy' in e.body:
467485
log.info("Access policy not found")
@@ -472,6 +490,8 @@ def get_access_policy_for_resource(resource,
472490
)
473491
log.info("Unexpected Error, raising...")
474492
raise ValueError(e.body)
493+
finally:
494+
config.safe_chars_for_path_param = copy(default_safe_chars)
475495

476496

477497
def create_access_policy(resource, action, r_id=None, service='nifi'):
@@ -610,3 +630,81 @@ def set_service_ssl_context(
610630
if service == 'registry':
611631
nipyapi.config.registry_config.ssl_context = ssl_context
612632
nipyapi.config.nifi_config.ssl_context = ssl_context
633+
634+
635+
def bootstrap_security_policies(service, admin='CN=user1, OU=nifi',
636+
proxy='CN=localhost, OU=nifi'):
637+
assert service in _valid_services
638+
if 'nifi' in service:
639+
rpg_id = nipyapi.canvas.get_root_pg_id()
640+
nifi_user_identity = nipyapi.security.get_service_user(
641+
admin,
642+
service='nifi'
643+
)
644+
access_policies = [
645+
('write', 'process-groups', rpg_id),
646+
('read', 'process-groups', rpg_id),
647+
('write', 'data/process-groups', rpg_id),
648+
('read', 'data/process-groups', rpg_id),
649+
('read', 'system', None),
650+
]
651+
for pol in access_policies:
652+
ap = nipyapi.security.get_access_policy_for_resource(
653+
action=pol[0],
654+
resource=pol[1],
655+
r_id=pol[2],
656+
service='nifi',
657+
auto_create=True
658+
)
659+
nipyapi.security.add_user_to_access_policy(
660+
user=nifi_user_identity,
661+
policy=ap,
662+
service='nifi',
663+
strict=False
664+
)
665+
else:
666+
reg_user_identity = nipyapi.security.get_service_user(
667+
admin,
668+
service='registry'
669+
)
670+
all_buckets_access_policies = [
671+
("read", "/buckets"),
672+
("write", "/buckets"),
673+
("delete", "/buckets")
674+
]
675+
for action, resource in all_buckets_access_policies:
676+
pol = nipyapi.security.get_access_policy_for_resource(
677+
resource=resource,
678+
action=action,
679+
service='registry',
680+
auto_create=True
681+
)
682+
nipyapi.security.add_user_to_access_policy(
683+
user=reg_user_identity,
684+
policy=pol,
685+
service='registry',
686+
strict=False
687+
)
688+
# Setup Proxy Access
689+
nifi_proxy = nipyapi.security.create_service_user(
690+
identity=proxy,
691+
service='registry',
692+
strict=False
693+
)
694+
proxy_access_policies = [
695+
("write", "/proxy"),
696+
("read", "/buckets")
697+
]
698+
for action, resource in proxy_access_policies:
699+
pol = nipyapi.security.get_access_policy_for_resource(
700+
resource=resource,
701+
action=action,
702+
service='registry',
703+
auto_create=True
704+
)
705+
nipyapi.security.add_user_to_access_policy(
706+
user=nifi_proxy,
707+
policy=pol,
708+
service='registry',
709+
strict=False
710+
)

resources/docker/secure/docker-compose.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
version: '2.1'
22
services:
3-
nifi-secure:
3+
secure-nifi:
44
image: apache/nifi:1.9.1
5-
container_name: nifi-secure
5+
container_name: secure-nifi
6+
hostname: secure-nifi
67
ports:
78
- "8443:8443"
89
volumes:
9-
- ../../../nipyapi/demo/keys:/opt/certs:ro
10+
- ../../../nipyapi/demo/keys:/opt/certs:ro # Min docker version tested 18, does not work on old docker
1011
environment:
1112
- AUTH=tls
1213
- KEYSTORE_PATH=/opt/certs/localhost-ks.jks
@@ -16,9 +17,10 @@ services:
1617
- TRUSTSTORE_PASSWORD=localhostTruststorePassword
1718
- TRUSTSTORE_TYPE=JKS
1819
- INITIAL_ADMIN_IDENTITY=CN=user1, OU=nifi
19-
registry-secure:
20+
secure-registry:
2021
image: apache/nifi-registry:0.3.0
21-
container_name: registry-secure
22+
container_name: secure-registry
23+
hostname: secure-registry
2224
ports:
2325
- "18443:18443"
2426
volumes:

tests/conftest.py

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
]
6969
secure_registry_endpoints = [
7070
('https://' + test_host + ':18443/nifi-registry-api',
71-
'http://secure-registry:18443',
71+
'https://secure-registry:18443',
7272
'https://' + test_host + ':8443/nifi-api'
7373
)]
7474

@@ -165,30 +165,6 @@ def regress_flow_reg(request):
165165
nipyapi.config.registry_local_name = request.param[1]
166166

167167

168-
def bootstrap_security_policies():
169-
access_status = nipyapi.security.get_service_access_status('nifi')
170-
rpg_id = nipyapi.canvas.get_root_pg_id()
171-
nifi_user_identity = nipyapi.security.get_service_user(access_status.access_status.identity)
172-
access_policies = [
173-
('write', 'process-groups', rpg_id),
174-
('read', 'process-groups', rpg_id),
175-
('read', 'system', None)
176-
]
177-
for pol in access_policies:
178-
ap = nipyapi.security.get_access_policy_for_resource(
179-
action=pol[0],
180-
resource=pol[1],
181-
r_id=pol[2],
182-
service='nifi',
183-
auto_create=True
184-
)
185-
nipyapi.security.add_user_to_access_policy(
186-
nifi_user_identity,
187-
policy=ap,
188-
service='nifi'
189-
)
190-
191-
192168
# Tests that the Docker test environment is available before running test suite
193169
@pytest.fixture(scope="session", autouse=True)
194170
def session_setup(request):
@@ -217,12 +193,14 @@ def session_setup(request):
217193
log.info("Tested NiFi client connection, got response from %s",
218194
url)
219195
if 'https://' in url:
220-
bootstrap_security_policies()
196+
nipyapi.security.bootstrap_security_policies(service='nifi')
221197
cleanup_nifi()
222198
elif 'nifi-registry-api' in url:
223199
if nipyapi.registry.FlowsApi().get_available_flow_fields():
224200
log.info("Tested NiFi-Registry client connection, got "
225201
"response from %s", url)
202+
if 'https://' in url:
203+
nipyapi.security.bootstrap_security_policies(service='registry')
226204
cleanup_reg()
227205
else:
228206
raise ValueError("No Response from NiFi-Registry test call"

tests/test_security.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,7 @@ def test_update_access_policy():
4848

4949

5050
def test_get_access_policy_for_resource(regress_nifi):
51-
# Test backwards compatibility issue on unsecured NiFi
52-
# Returns an error stating the NiFi isn't set up for this, rather than
53-
# the bad parameter error reported in issue #66
54-
with pytest.raises(ValueError, match='This NiFi is not configured'):
55-
_ = security.get_access_policy_for_resource('flow', 'read')
56-
# Note that on a secured NiFi with no valid policy you will get the error:
57-
# "No applicable policies could be found"
51+
pass
5852

5953

6054
def test_create_access_policy():

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,5 @@ deps =
4242
-r{toxinidir}/requirements.txt
4343
commands =
4444
pip install -U pip
45-
coverage run -m py.test --basetemp={envtmpdir} -x --log-cli-level=INFO
45+
coverage run -m py.test --basetemp={envtmpdir} -x --log-cli-level=WARNING
4646
- coveralls

0 commit comments

Comments
 (0)