Skip to content

Commit

Permalink
Merge pull request #1 from ros2/access_control
Browse files Browse the repository at this point in the history
Access control
  • Loading branch information
codebot authored Jan 28, 2017
2 parents 1b22af3 + 3bad13c commit 1c8c1eb
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 32 deletions.
44 changes: 41 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ source install/setup.bash

In one terminal (after preparing the environment as previously described), we
will create a keystore in `~/sros2/demo_keys` :
```
```bash
sros2 create_keystore demo_keys
```
Generate certificates and keys for our `talker` and `listener` nodes:
```bash
sros2 create_key demo_keys talker
sros2 create_key demo_keys listener
```
Expand All @@ -104,8 +107,43 @@ will do the same thing with the `listener` program:
ROS_SECURE_ROOT=~/sros2/demo_keys listener
```

At thsi point, your `talker` and `listener` nodes should be communicating
securely! Hooray!
At this point, your `talker` and `listener` nodes should be communicating
securely, using authentication and encryption! Hooray!

## Access Control
The previous demo used authentication and encryption, but not access control,
which means that any authenticated node would be able to publish and subscribe
to any data stream (aka topic). To increase the level of security in the
system, you can define strict limits, known as access control, which restrict
what each node is able to do. For example, one node would be able to publish to
a particular topic, and another node might be able to subscribe to that topic.
To do this, we will use the sample policy file provided in
`examples/sample_policy.yaml`

First, we will copy this sample policy file into our keystore:
```bash
cp ~/sros2/src/ros2/sros2/examples/sample_policy.yaml ./demo_keys/
```
And now we will use it to generate the XML permission files expected by the
middleware:
```bash
sros2 create_permission demo_keys talker demo_keys/policies.yaml
sros2 create_permission demo_keys listener demo_keys/policies.yaml
```
Then, in one terminal (after preparing the terminal as previously described),
we can set the `ROS_SECURE_ROOT` to our keystore path, and then run the
`talker` demo program:
```
ROS_SECURE_ROOT=~/sros2/demo_keys talker
```
In another terminal (after preparing the terminal as previously described), we
will do the same thing with the `listener` program:
```
ROS_SECURE_ROOT=~/sros2/demo_keys listener
```

At this point, your `talker` and `listener` nodes should be communicating
securely, using explicit access control lists! Hooray!

## Two different machines

Expand Down
31 changes: 31 additions & 0 deletions examples/sample_policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
nodes:
listener:
topics:
chatter:
allow: s # can subscribe to chatter
parameter_events:
allow: p # can publish on parameter_events
talker:
topics:
chatter:
allow: p # allow secure_publisher to publish onto chatter
parameter_events:
allow: p # can publish
listener_py:
topics:
#'*':
# allow: s # this would allow the listener to subscribe to all topics
chatter:
allow: s # can subscribe to chatter
chatter2:
allow: s # can subscribe to chatter2
talker_py:
topics:
#'*':
# allow: p # this would allow the talker to publish on all topics
chatter:
allow: p # allow publishing on chatter
chatter2:
allow: p # allow publishing on chatter2
chatter3:
allow: p # allow publishing on chatter3
125 changes: 96 additions & 29 deletions sros2.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def create_ca_key_cert(ecdsa_param_path, ca_conf_path, ca_key_path, ca_cert_path
(ecdsa_param_path, ca_key_path, ca_cert_path, ca_conf_path))


def create_governance_file(path):
def create_governance_file(path, domain_id):
# for this application we are only looking to authenticate and encrypt;
# we do not need/want access control at this point.
with open(path, 'w') as f:
Expand All @@ -90,7 +90,7 @@ def create_governance_file(path):
xsi:noNamespaceSchemaLocation="http://www.omg.org/spec/DDS-SECURITY/20140301/dds_security_governance.xsd">
<domain_access_rules>
<domain_rule>
<domain_id>*</domain_id>
<domain_id>%s</domain_id>
<allow_unauthenticated_join>FALSE</allow_unauthenticated_join>
<enable_join_access_control>TRUE</enable_join_access_control>
<discovery_protection_kind>ENCRYPT</discovery_protection_kind>
Expand All @@ -100,16 +100,16 @@ def create_governance_file(path):
<topic_rule>
<topic_expression>*</topic_expression>
<enable_discovery_protection>TRUE</enable_discovery_protection>
<enable_read_access_control>FALSE</enable_read_access_control>
<enable_write_access_control>FALSE</enable_write_access_control>
<enable_read_access_control>TRUE</enable_read_access_control>
<enable_write_access_control>TRUE</enable_write_access_control>
<metadata_protection_kind>ENCRYPT</metadata_protection_kind>
<data_protection_kind>ENCRYPT</data_protection_kind>
</topic_rule>
</topic_access_rules>
</domain_rule>
</domain_access_rules>
</dds>
""")
""" % domain_id)


def create_signed_governance_file(signed_gov_path, gov_path, ca_cert_path, ca_key_path):
Expand Down Expand Up @@ -152,9 +152,10 @@ def create_keystore(args):

# create governance file
gov_path = os.path.join(root, 'governance.xml')
domain_id = os.getenv('ROS_DOMAIN_ID', 0)
if not os.path.isfile(gov_path):
print("creating governance file: %s" % gov_path)
create_governance_file(gov_path)
create_governance_file(gov_path, domain_id)
else:
print("found governance file, not creating a new one!")

Expand Down Expand Up @@ -232,22 +233,75 @@ def create_cert(root_path, name):
(req_relpath, cert_relpath), root_path)


def create_permissions_file(path, name):
with open(path, 'w') as f:
f.write("""\
def create_permission_file(path, name, domain_id, permissions_dict):
permission_str = """\
<permissions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.omg.org/spec/DDS-SECURITY/20140301/dds_security_permissions.xsd">
<grant name="allow_everything">
<grant name="%s_policies">
<subject_name>CN=%s</subject_name>
<validity>
<!-- Format is YYYYMMDDHH in GMT -->
<not_before>2016122000</not_before>
<not_after>2026122000</not_after>
</validity>
<default>ALLOW</default>
<allow_rule>
<domain_id>%s</domain_id>
""" % (name, name, domain_id)
# access control only on topics for now
topic_dict = permissions_dict['topics']
if topic_dict is not None and topic_dict != {}:
# we have some policies to add !
for topic_name, policy in topic_dict.items():
if policy['allow'] == 's':
tag = 'subscribe'
elif policy['allow'] == 'p':
tag = 'publish'
else:
print("unknown permission policy '%s', skipping" % policy['allow'])
continue
permission_str += """\
<%s>
<topic>%s</topic>
</%s>
""" % (tag, topic_name, tag)
# DCPS* is necessary for builtin data readers
permission_str += """\
<subscribe>
<topic>DCPS*</topic>
</subscribe>
"""
else:
# no policy found: allow everything!
permission_str += """\
<publish>
<topic>*</topic>
</publish>
<subscribe>
<topic>*</topic>
</subscribe>
"""

permission_str += """\
</allow_rule>
<default>DENY</default>
</grant>
</permissions>
""" % name)
"""
with open(path, 'w') as f:
f.write(permission_str)


def get_permissions(name, policy_file_path):
import yaml
if not os.path.isfile(policy_file_path):
return {'topics': {}}
with open(policy_file_path, 'r') as graph_permissions_file:
try:
graph = yaml.load(graph_permissions_file)
except yaml.YAMLError as e:
print(e)
sys.exit(1)
return graph['nodes'][name]


def create_signed_permissions_file(
Expand All @@ -257,6 +311,27 @@ def create_signed_permissions_file(
(permissions_path, signed_permissions_path, ca_cert_path, ca_key_path))


def create_permission(args):
print(args)
root = args.ROOT
name = args.NAME
policy_file_path = args.POLICY_FILE_PATH
domain_id = os.getenv('ROS_DOMAIN_ID', 0)

key_dir = os.path.join(root, name)
print('key_dir %s' % key_dir)
permissions_dict = get_permissions(name, policy_file_path)
permissions_path = os.path.join(key_dir, 'permissions.xml')
create_permission_file(permissions_path, name, domain_id, permissions_dict)

signed_permissions_path = os.path.join(key_dir, 'permissions.p7s')
keystore_ca_cert_path = os.path.join(root, 'ca.cert.pem')
keystore_ca_key_path = os.path.join(root, 'ca.key.pem')
create_signed_permissions_file(
permissions_path, signed_permissions_path,
keystore_ca_cert_path, keystore_ca_key_path)


def create_key(args):
print(args)
root = args.ROOT
Expand Down Expand Up @@ -311,23 +386,6 @@ def create_key(args):
else:
print("found cert; not creating a new one!")

permissions_path = os.path.join(key_dir, 'permissions.txt')
if not os.path.isfile(permissions_path):
print("creating permissions file")
create_permissions_file(permissions_path, name)
else:
print("found permissions file; not creating a new one!")

signed_permissions_path = os.path.join(key_dir, 'permissions.p7s')
keystore_ca_cert_path = os.path.join(root, 'ca.cert.pem')
keystore_ca_key_path = os.path.join(root, 'ca.key.pem')
if not os.path.isfile(signed_permissions_path):
print("creating signed permissions file")
create_signed_permissions_file(
permissions_path, signed_permissions_path, keystore_ca_cert_path, keystore_ca_key_path)
else:
print("found signed permissions file; not creating a new one!")

return True


Expand Down Expand Up @@ -370,6 +428,13 @@ def main(sysargs=None):
parser_distribute_keys.add_argument('ROOT', help='root path of keystore')
parser_distribute_keys.add_argument('TARGET', help='target keystore path')

parser_create_perm = subparsers.add_parser('create_permission')
parser_create_perm.set_defaults(which='create_permission')
parser_create_perm.add_argument('ROOT', help='root path of keystore')
parser_create_perm.add_argument('NAME', help='key name, aka ROS node name')
parser_create_perm.add_argument(
'POLICY_FILE_PATH', help='path of the permission yaml file')

args = parser.parse_args(sysargs)

if '-h' in sysargs or '--help' in sysargs:
Expand All @@ -385,6 +450,8 @@ def main(sysargs=None):
result = create_keystore(args)
elif args.which == 'create_key':
result = create_key(args)
elif args.which == 'create_permission':
result = create_permission(args)
elif args.which == 'list_keys':
result = list_keys(args)
elif args.which == 'distribute_key':
Expand Down

0 comments on commit 1c8c1eb

Please sign in to comment.