Skip to content

Commit

Permalink
initial commit (cassandra 'grant' and 'role' modules)
Browse files Browse the repository at this point in the history
  • Loading branch information
sadams committed Nov 10, 2016
0 parents commit d65156c
Show file tree
Hide file tree
Showing 6 changed files with 598 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
The MIT License (MIT)

Copyright (c) 2015, 2016 Ensighten

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Cassandra Ansible Module

Supplies modules for administering Cassandra roles and granting permissions to those roles.

## Pre-requisites

1. Enable authentication in Cassandra
1. Have `cassandra-driver` python package installed on the target machine

### Enable auth in Cassandra

E.g. set the following properties in `/etc/cassandra/cassandra.yml`:

authenticator: PasswordAuthenticator
authorizer: CassandraAuthorizer

Once this is updated and the cluster restarted a default user of `cassandra` with password `cassandra` will be required
to login.

This config change and restart **must be applied before you can add permissions**.

### Install cassandra-driver

The package is: `cassandra-driver` and it can be install via `pip`.

Docs: https://datastax.github.io/python-driver/api/cassandra/query.html

## Why is there no `cassandra_user` module?

In Cassandra's permissioning system, there are just roles.
However, roles can (optionally) login and roles can inherit other roles (so roles can be used in a very user-like way).

A suggested setup would be have 'role' roles which *can't login* (but are granted the keyspace permissions),
and 'user' roles which *can login* and inherit their permissions from roles.

e.g.
1. Create role `role_select_all` who can not login but is granted access to select anything from any keyspace/table.
1. Create 'pseudo-user' (a role which can login) and assign them the role `role_select_all`.

See `example-tasks` for how this can be done.

## Docs

Written in comments in `library/*`.
36 changes: 36 additions & 0 deletions example-tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---

- block:

- name: add users (as roles)
cassandra_role:
state: present
name: a_user
superuser: False
enable_login: True
password: a_user_password

- name: add roles
cassandra_role:
state: present
name: x_role
superuser: False
enable_login: False #so a role can't login

- name: grant perms to roles
cassandra_grant:
mode: grant # ["grant", "revoke"]
role: x_role
permission: all # ["all", "alter", "drop", "select", "modify", "authorise", "describe", "execute"]
keyspace: "mykeyspace"
all_keyspaces: "False"

- name: grant roles to users (aka roles to roles)
cassandra_grant:
mode: grant # ["grant", "revoke"]
role: a_user
inherit_role: x_role

login_hosts: localhost
login_user: cassandra
login_password: cassandra
271 changes: 271 additions & 0 deletions library/cassandra_grant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-


DOCUMENTATION = '''
---
module: cassandra_role
short_description: Grant Cassandra permissions
description:
- Add/remove Cassandra Users
- requires `pip install cassandra-driver`
- Related Docs: https://datastax.github.io/python-driver/api/cassandra/query.html
- Related Docs: https://docs.datastax.com/en/cql/3.3/cql/cql_reference/grant_r.html
author: "Sam Adams"
options:
permission:
description:
- what permission to grant
required: true
choices: ["all", "create", "alter", "drop", "select", "modify", "authorize"]
keyspace:
description:
- required if `all_keyspaces` == false
- ignored if `inherit_role` is set
required: false
default: false
all_keyspaces:
description:
- if true, `on` is ignored and the `what` is granted to all keyspaces
- ignored if `inherit_role` is set
required: false
default: false
role:
description:
- which role to modify
- the role must already exist in cassandra
required: true
inherit_role:
description:
- which role permission this role should inherit (no keyspace required)
- requires that the role already exists
required: true
login_hosts:
description:
- List of hosts to login to Cassandra with
required: true
login_user:
description:
- The superuser to login to Cassandra with
required: true
login_password:
description:
- The superuser password to login to Cassandra with
required: true
login_port:
description:
- Port to connect to cassandra on
default: 9042
mode:
description:
- Whether access should be granted or revoked.
required: false
default: grant
choices: [ "grant", "revoke" ]
notes:
- "requires cassandra-driver to be installed"
'''

EXAMPLES = '''
# Grant select for all keyspaces
- cassandra_grant: permission='select' all_keyspaces=True role=read_only login_hosts=localhost login_pass=cassandra login_user=cassandra
# Revoke modify permission to foo keyspace
- cassandra_grant: mode=revoke permission=modify keyspace=foo role=no_modify_foo login_hosts=localhost login_pass=cassandra login_user=cassandra
# Inherit roles
- cassandra_grant: mode=grant inherit_role=read_only role=my_user_role login_hosts=localhost login_pass=cassandra login_user=cassandra
'''

try:
from cassandra.cluster import Cluster
from cassandra.auth import PlainTextAuthProvider
from cassandra.query import dict_factory
except ImportError:
cassandra_dep_found = False
else:
cassandra_dep_found = True

GET_ROLE = 'SELECT * FROM system_auth.roles WHERE role = %s limit 1;'
GET_ROLE_MEMBERS = 'SELECT * FROM system_auth.role_members WHERE role = %s'
GRANT_ROLE_TO_ROLE = 'GRANT %(inherit_role)s TO %(role)s'
REVOKE_ROLE_FROM_ROLE = 'REVOKE %(inherit_role)s FROM %(role)s'
GRANT_PERMISSION_TO_ROLE_FOR_KESYPACE_FORMAT = 'GRANT {permission} ON KEYSPACE {keyspace} TO %(role)s'
REVOKE_PERMISSION_FROM_ROLE_FOR_KESYPACE_FORMAT = 'REVOKE {permission} ON KEYSPACE {keyspace} FROM %(role)s'
GRANT_PERMISSION_TO_ROLE_FOR_ALL_KESYPACES_FORMAT = 'GRANT {permission} ON ALL KEYSPACES TO %(role)s'
REVOKE_PERMISSION_FROM_ROLE_FOR_ALL_KESYPACES_FORMAT = 'REVOKE {permission} ON ALL KEYSPACES FROM %(role)s'


def get_role(session, name):
rows = session.execute(GET_ROLE, [name])
for row in rows:
return row


def would_change(existing_role, can_login, is_superuser, password):
if password or not existing_role:
# even setting existing password updates the `salted_hash` in the db, so no way to check has changed.
return True
else:
return bool(existing_role['can_login'] != can_login or existing_role['is_superuser'] != is_superuser)


def role_has_role(session, role, inherit_role):
rows = session.execute(GET_ROLE_MEMBERS, [inherit_role])
for row in rows:
if row['member'] == role:
return True
return False


def assign_role(session, check_mode, is_revoke, inherit_role, role):
has_role = role_has_role(session, role, inherit_role)

# check if we need to do anything
if has_role and not is_revoke:
return False
elif not has_role and is_revoke:
return False

if not is_revoke:
query = GRANT_ROLE_TO_ROLE
else:
query = REVOKE_ROLE_FROM_ROLE

if not check_mode:
session.execute(query, {'role': role, 'inherit_role': inherit_role})

return True


def grant_role_permission(session, in_check_mode, is_revoke, permission, all_keyspaces, keyspace, role):
permission = permission.upper()
if is_revoke and all_keyspaces:
# revoking for all keyspaces
query = REVOKE_PERMISSION_FROM_ROLE_FOR_ALL_KESYPACES_FORMAT.format(permission=permission)
elif all_keyspaces:
# granting for all keyspaces
query = GRANT_PERMISSION_TO_ROLE_FOR_ALL_KESYPACES_FORMAT.format(permission=permission)
elif is_revoke:
# revoking for a specific keyspace
query = REVOKE_PERMISSION_FROM_ROLE_FOR_KESYPACE_FORMAT.format(permission=permission, keyspace=keyspace)
else:
# granting for a specific keyspace
query = GRANT_PERMISSION_TO_ROLE_FOR_KESYPACE_FORMAT.format(permission=permission, keyspace=keyspace)

if not in_check_mode:
session.execute(query, {'role': role})

# too complex to work out what will/has changed
return True


def grant_access(session, in_check_mode, permission, role, inherit_role, keyspace, all_keyspaces, mode):
if keyspace and all_keyspaces:
raise Exception("Specify a keyspace or all keyspaces, not both")
if keyspace and inherit_role:
raise Exception("If you are inheriting a role you can't specify a keyspace")
if all_keyspaces and inherit_role:
raise Exception("If you are inheriting a role you can't specify all keyspaces")

mode = mode.upper()
is_revoke = mode != 'GRANT'

if inherit_role:
return assign_role(session, in_check_mode, is_revoke, inherit_role, role)
else:
return grant_role_permission(session, in_check_mode, is_revoke, permission, all_keyspaces, keyspace, role)


def main():
module = AnsibleModule(
argument_spec={
'login_user': {
'required': True,
'type': 'str'
},
'login_password': {
'required': True,
'no_log': True,
'type': 'str'
},
'login_hosts': {
'required': True,
'type': 'list'
},
'login_port': {
'default': 9042,
'type': 'int'
},
'permission': {
'required': False,
'choices': ["all", "create", "alter", "drop", "select", "modify", "authorize"]
},
'role': {
'required': True,
'aliases': ['name']
},
'inherit_role': {
'required': False,
'default': None
},
'keyspace': {
'required': False,
'default': None,
'type': 'str'
},
'all_keyspaces': {
'default': False,
'type': 'bool'
},
'mode': {
'default': "grant",
'choices': ["grant", "revoke"]
}
},
supports_check_mode=True
)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
login_hosts = module.params["login_hosts"]
login_port = module.params["login_port"]
permission = module.params["permission"]
role = module.params["role"]
inherit_role = module.params["inherit_role"]
keyspace = module.params["keyspace"]
all_keyspaces = module.params["all_keyspaces"]
mode = module.params["mode"]

if not cassandra_dep_found:
module.fail_json(msg="the python cassandra-driver module is required")

session = None
changed = False
try:
if not login_user:
cluster = Cluster(login_hosts, port=login_port)
else:
auth_provider = PlainTextAuthProvider(username=login_user, password=login_password)
cluster = Cluster(login_hosts, auth_provider=auth_provider, protocol_version=2, port=login_port)
session = cluster.connect()
session.row_factory = dict_factory
except Exception, e:
module.fail_json(
msg="unable to connect to cassandra, check login_user and login_password are correct. Exception message: %s"
% e)

try:
changed = grant_access(session, module.check_mode, permission, role, inherit_role, keyspace, all_keyspaces,
mode)
except Exception, e:
module.fail_json(msg=str(e))
module.exit_json(changed=changed, name=role)


from ansible.module_utils.basic import *

if __name__ == '__main__':
main()
Loading

0 comments on commit d65156c

Please sign in to comment.