Skip to content

Commit

Permalink
Merge pull request #129 from mwielgoszewski/enroll-secret-tags
Browse files Browse the repository at this point in the history
Support passing tags w/ enroll secret to inherit tags when new node enrolls
  • Loading branch information
mwielgoszewski authored Sep 5, 2017
2 parents f3bbdee + bc5bf20 commit 7548f8e
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ Setting | Description
`DOORMAN_ENROLL_SECRET` | A list of valid enrollment keys to use. See osquery TLS [remoting settings](https://osquery.readthedocs.io/en/stable/deployment/remote/) for more information. By default, this list is empty.
`DOORMAN_EXPECTS_UNIQUE_HOST_ID` | If osquery is deployed on endpoints to start with the `--host_identifier=uuid` cli flag, set this value to `True`. Default is `True`.
`DOORMAN_CHECKIN_INTERVAL` | Time (in seconds) nodes are expected to check-in for configurations or call the distributed read endpoint. Nodes that fail to check-in within this time will be highlighted in red on the main nodes page.
`DOORMAN_ENROLL_DEFAULT_TAGS` | A default set of tags to apply to newly enrolled nodes.
`DOORMAN_ENROLL_DEFAULT_TAGS` | A default set of tags to apply to newly enrolled nodes. See also `DOORMAN_ENROLL_SECRET_TAG_DELIMITER`.
`DOORMAN_ENROLL_SECRET_TAG_DELIMITER` | A delimiter to separate the enroll secret from tag values (up to maximum of 10 tags) the node should inherit upon enrollment. Default is `None`, i.e., a node will not inherit any tags when first enrolled. This provides a little more flexibility than `DOORMAN_ENROLL_DEFAULT_TAGS`, allowing individual nodes to inherit different tags based on environment, asset class, etc. In the osquery configuration, you would supply an enroll secret in the format: `--enroll-secret=secret:tag1:tag2:tag3`, assuming `:` is your tag delimiter.
`DOORMAN_CAPTURE_NODE_INFO` | A list of tuples, containing a pair of osquery result column and label used to determine what information is captured about a node and presented on a node's information page. In order for this information to be captured, a node must execute a query which returns a result containing these columns. By default, the following information is captured: (i.e., `select * from system_info;`) <ul><li>`computer_name`</li><li>`hardware_vendor`</li><li>`hardware_model`</li><li>`hardware_serial`</li><li>`cpu_brand`</li><li>`cpu_physical_cores`</li><li>`physical_memory`</li></ul>
`DOORMAN_EXTRA_SCHEMA` | Doorman will validate queries against the expected set of tables from osquery. If you use any custom extensions, you'll need to add the corresponding schema here so you can use them in queries.
`DOORMAN_MINIMUM_OSQUERY_LOG_LEVEL` | The minimum osquery status log level to retain. Default is `0`, (all logs).
Expand Down
11 changes: 10 additions & 1 deletion doorman/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ def enroll():
# If we pre-populate node table with a per-node enroll_secret,
# let's query it now.

if current_app.config.get('DOORMAN_ENROLL_SECRET_TAG_DELIMITER'):
delimiter = current_app.config.get('DOORMAN_ENROLL_SECRET_TAG_DELIMITER')
enroll_secret, _, enroll_tags = enroll_secret.partition(delimiter)
enroll_tags = set([tag.strip() for tag in enroll_tags.split(delimiter)[:10]])
else:
enroll_secret, enroll_tags = enroll_secret, set()

node = Node.query.filter(Node.enroll_secret == enroll_secret).first()

if not node and enroll_secret not in current_app.config['DOORMAN_ENROLL_SECRET']:
Expand Down Expand Up @@ -176,7 +183,9 @@ def enroll():
enrolled_on=now,
last_ip=request.remote_addr)

for value in current_app.config.get('DOORMAN_ENROLL_DEFAULT_TAGS', []):
enroll_tags.update(current_app.config.get('DOORMAN_ENROLL_DEFAULT_TAGS', []))

for value in sorted((t.strip() for t in enroll_tags if t)):
tag = Tag.query.filter_by(value=value).first()
if tag and tag not in node.tags:
node.tags.append(tag)
Expand Down
3 changes: 2 additions & 1 deletion doorman/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Config(object):
DOORMAN_PACK_DELIMITER = '/'
DOORMAN_MINIMUM_OSQUERY_LOG_LEVEL = 0

DOORMAN_ENROLL_SECRET_TAG_DELIMITER = None
DOORMAN_ENROLL_DEFAULT_TAGS = [
]

Expand Down Expand Up @@ -272,7 +273,7 @@ class TestConfig(Config):
DOORMAN_ENROLL_SECRET = [
'secret',
]
DOORMAN_UNIQUE_HOST_ID = False
DOORMAN_EXPECTS_UNIQUE_HOST_ID = False

DOORMAN_AUTH_METHOD = None

Expand Down
84 changes: 84 additions & 0 deletions tests/test_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,90 @@ def test_reenrolling_node_does_not_get_new_tags(self, db, node, testapp):
assert node.is_active
assert node.last_ip == '127.0.0.1'

def test_enroll_secret_tags(self, db, node, testapp):
testapp.app.config['DOORMAN_ENROLL_SECRET_TAG_DELIMITER'] = ':'
testapp.app.config['DOORMAN_EXPECTS_UNIQUE_HOST_ID'] = True
enroll_secret = testapp.app.config['DOORMAN_ENROLL_SECRET'][0]
resp = testapp.post_json(url_for('api.enroll'), {
'enroll_secret': enroll_secret,
'host_identifier': 'foobaz'},
extra_environ=dict(REMOTE_ADDR='127.0.0.2')
)

assert resp.json['node_invalid'] is False
assert resp.json['node_key'] != node.node_key

n = Node.query.filter_by(node_key=resp.json['node_key']).one()
assert n.is_active
assert n.last_ip == '127.0.0.2'
assert not n.tags

resp = testapp.post_json(url_for('api.enroll'), {
'enroll_secret': ':'.join([enroll_secret, 'foo', 'bar']),
'host_identifier': 'barbaz'},
extra_environ=dict(REMOTE_ADDR='127.0.0.2')
)

assert resp.json['node_invalid'] is False
assert resp.json['node_key'] != node.node_key

n = Node.query.filter_by(node_key=resp.json['node_key']).one()
assert n.is_active
assert n.last_ip == '127.0.0.2'
assert len(n.tags) == 2
assert 'foo' in (t.value for t in n.tags)
assert 'bar' in (t.value for t in n.tags)

resp = testapp.post_json(url_for('api.enroll'), {
'enroll_secret': ':'.join([enroll_secret, 'foo', 'bar', 'baz']),
'host_identifier': 'barbaz'},
extra_environ=dict(REMOTE_ADDR='127.0.0.2')
)
assert resp.json['node_key'] != node.node_key
assert resp.json['node_key'] == n.node_key

n = Node.query.filter_by(node_key=resp.json['node_key']).one()
assert n.is_active
assert n.last_ip == '127.0.0.2'
assert len(n.tags) == 2
assert 'foo' in (t.value for t in n.tags)
assert 'bar' in (t.value for t in n.tags)

testapp.app.config['DOORMAN_ENROLL_SECRET'].append(':'.join(enroll_secret))
testapp.app.config['DOORMAN_ENROLL_SECRET_TAG_DELIMITER'] = ','
resp = testapp.post_json(url_for('api.enroll'), {
'enroll_secret': ':'.join(enroll_secret),
'host_identifier': 'bartab'},
extra_environ=dict(REMOTE_ADDR='127.0.0.2')
)

assert resp.json['node_invalid'] is False
assert resp.json['node_key'] != node.node_key

n = Node.query.filter_by(node_key=resp.json['node_key']).one()
assert n.is_active
assert n.last_ip == '127.0.0.2'
assert not n.tags

def test_enroll_max_secret_tags(self, db, node, testapp):
testapp.app.config['DOORMAN_ENROLL_SECRET_TAG_DELIMITER'] = ':'
testapp.app.config['DOORMAN_EXPECTS_UNIQUE_HOST_ID'] = True
enroll_secret = testapp.app.config['DOORMAN_ENROLL_SECRET'][0]
enroll_secret = ':'.join([enroll_secret] + list('abcdef1234567890'))
resp = testapp.post_json(url_for('api.enroll'), {
'enroll_secret': ':'.join([enroll_secret, 'foo', ]),
'host_identifier': 'barbaz'},
extra_environ=dict(REMOTE_ADDR='127.0.0.2')
)

assert resp.json['node_invalid'] is False
assert resp.json['node_key'] != node.node_key

n = Node.query.filter_by(node_key=resp.json['node_key']).one()
assert n.is_active
assert n.last_ip == '127.0.0.2'
assert len(n.tags) == 10 # max 10 tags when passing tags w/enroll secret


class TestConfiguration:

Expand Down

0 comments on commit 7548f8e

Please sign in to comment.