From 14ce9bcf6582797a6c1da671ee68954023c9f373 Mon Sep 17 00:00:00 2001 From: "Volodymyr Shcherbinin (vovin)" Date: Thu, 27 Aug 2015 11:10:53 +0200 Subject: [PATCH 01/18] feature(aws_elb_external_inventory): added prudentia/inventories/aws_elb.py external inventory script --- prudentia/inventories/aws_elb.py | 134 +++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100755 prudentia/inventories/aws_elb.py diff --git a/prudentia/inventories/aws_elb.py b/prudentia/inventories/aws_elb.py new file mode 100755 index 0000000..e874592 --- /dev/null +++ b/prudentia/inventories/aws_elb.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python + +#################################################################################### +### +### Get EC2 instances inventory under ELB +### ===================================== +### +### Generates inventory that Ansible can understand by making API request to +### AWS EC2 using the Boto library. +### +### NOTE: This script assumes Ansible is being executed where the environment +### variables needed for Boto have already been set: +### export AWS_ACCESS_KEY_ID='ABC123' +### export AWS_SECRET_ACCESS_KEY='abc123' +### +#################################################################################### + +import sys +import os +import string +from boto import boto +from boto import ec2 +from optparse import OptionParser +try: + import json +except: + import simplejson as json + +### Functions +# Check env vars +def check_vars(): + if None in [os.environ.get('AWS_ACCESS_KEY_ID'), os.environ.get('AWS_SECRET_ACCESS_KEY')]: + boto_path = ['/etc/boto.cfg', '~/.boto', '~/.aws/credentials'] + boto_config_found = list(p for p in boto_path if os.path.isfile(os.path.expanduser(p))) + if len(boto_config_found) <= 0: + error("Cannot find AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY variables or read credentials from %s" % boto_path, "checking the envronment") + +# Log an error to stderr and quit +def error(err_msg, err_operation=None): + if err_operation: + err_msg = 'Error {err_operation}: "{err_msg}"\n'.format(err_msg=err_msg, err_operation=err_operation) + else: + err_msg = 'Error: {err_msg}\n'.format(err_msg=err_msg) + sys.stderr.write(err_msg) + sys.exit(1) + +# Get instance info +def get_instance_info(instances): + # Init ELB instance list + info = [] + # Walk through the instance list + for instance in instances: + # Get instance info + try: + ec2_instance = ec2_conn.get_only_instances(instance.id) + # Walk through instance info + except boto.exception.BotoServerError as e: + error("%s (%s)" % (e.message, e.error_code) , "fetching ELB instance properties") + for i in ec2_instance: + # Q: shall we check if ec2_instance and i.id match? + # Append instance info to the list + #info.append([i.id, i.ip_address, i.dns_name]) + info.append(i.ip_address) + # Return info + return info + +# Get full instance list under each ELB +def get_elb_list(): + # Init function return dictionary + elb_result = {} + # Walk through the ELB list + try: + for elb in elb_conn.get_all_load_balancers(): + # Add list as a value to ELB name + elb_result[elb.name] = get_instance_info(elb.instances) + except boto.exception.BotoServerError as e: + error("%s (%s)" % (e.message, e.error_code) , "fetching ELB list") + # Return here + return elb_result + +# Get full instance list under particular ELB +def get_elb_info(elb_name): + # Init function return dictionary + elb_result = {} + # Get ELB info + try: + elb = elb_conn.get_all_load_balancers(elb_name) + for i in elb: + # Add list as a value to ELB name + elb_result[i.name] = get_instance_info(i.instances) + except boto.exception.BotoServerError as e: + error("%s (%s)" % (e.message, e.error_code) , "fetching instances under ELB") + # Return here + return elb_result + +### Start the ball +# Parse the options +parser = OptionParser(usage="%prog [options] --list | --host ") +parser.add_option('--list', default=False, dest="list", action="store_true", help="Produce a JSON consumable grouping of instances under ELB") +parser.add_option('--host', default=None, dest="elb", help="Generate additional host specific details for given host for Ansible") +(options, args) = parser.parse_args() + +# Check if we have options and print help +if not options.list and not options.elb: + parser.print_help() + sys.exit(0) + +# Check if env vars are in place +check_vars() + +# Try to connect to AWS +try: + elb_conn = boto.connect_elb() +except boto.exception.BotoServerError as e: + error("%s (%s)" % (e.message, e.error_code) , "connecting to ELB") + +try: + ec2_conn = boto.connect_ec2() +except boto.exception.BotoServerError as e: + error("%s (%s)" % (e.message, e.error_code) , "connecting to EC2") + +# Get instance details under all ELB +if options.list: + elb_list = get_elb_list() + print json.dumps(elb_list, indent=4) + sys.exit(0) + +# Get instance details under particular ELB +elif options.elb: + elb_info = get_elb_info(options.elb) + print json.dumps(elb_info, indent=4) + sys.exit(0) + +### EOF From e61d19dee5d5242ade4b702b59b2296a007c93ad Mon Sep 17 00:00:00 2001 From: "Volodymyr Shcherbinin (vovin)" Date: Thu, 27 Aug 2015 11:57:17 +0200 Subject: [PATCH 02/18] feature(aws_elb_external_inventory): code cleanup --- prudentia/inventories/aws_elb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/prudentia/inventories/aws_elb.py b/prudentia/inventories/aws_elb.py index e874592..3c8af90 100755 --- a/prudentia/inventories/aws_elb.py +++ b/prudentia/inventories/aws_elb.py @@ -17,9 +17,7 @@ import sys import os -import string from boto import boto -from boto import ec2 from optparse import OptionParser try: import json From 466aec495d509659895d9da0409a8910ea177d98 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 15 Sep 2015 15:32:43 +0200 Subject: [PATCH 03/18] feature(lookup): Adds the possibility to have Prudentia specific lookup plugins. Copies Hashicorp Vault plugin from Ansible 2.0 beta --- prudentia/cli.py | 1 + prudentia/plugins/lookup/hashi_vault.py | 90 +++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 prudentia/plugins/lookup/hashi_vault.py diff --git a/prudentia/cli.py b/prudentia/cli.py index 77ab801..1690aa2 100644 --- a/prudentia/cli.py +++ b/prudentia/cli.py @@ -10,6 +10,7 @@ cwd = path.dirname(path.realpath(__file__)) os.environ['ANSIBLE_CONFIG'] = path.join(cwd, 'ansible.cfg') os.environ['ANSIBLE_ROLES_PATH'] = path.join(cwd, 'roles') + os.pathsep + '/etc/ansible/roles' +os.environ['ANSIBLE_LOOKUP_PLUGINS'] = path.join(cwd, 'plugins', 'lookup') + os.pathsep + '/usr/share/ansible_plugins/lookup_plugins' from digital_ocean import DigitalOceanCli from local import LocalCli diff --git a/prudentia/plugins/lookup/hashi_vault.py b/prudentia/plugins/lookup/hashi_vault.py new file mode 100644 index 0000000..b0fcf95 --- /dev/null +++ b/prudentia/plugins/lookup/hashi_vault.py @@ -0,0 +1,90 @@ +# (c) 2015, Jonathan Davila +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +# USAGE: {{ lookup('hashi_vault', 'secret=secret/hello token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200')}} +# +# You can skip setting the url if you set the VAULT_ADDR environment variable +# or if you want it to default to localhost:8200 +# +# NOTE: Due to a current limitation in the HVAC library there won't +# necessarily be an error if a bad endpoint is specified. +# +# Requires hvac library. Install with pip. +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.errors import * +from ansible.plugins.lookup import LookupBase + + +ANSIBLE_HASHI_VAULT_ADDR = 'http://127.0.0.1:8200' + +if os.getenv('VAULT_ADDR') is not None: + ANSIBLE_HASHI_VAULT_ADDR = os.environ['VAULT_ADDR'] + +class HashiVault: + def __init__(self, **kwargs): + try: + import hvac + except ImportError: + AnsibleError("Please pip install hvac to use this module") + + self.url = kwargs.pop('url') + self.secret = kwargs.pop('secret') + self.token = kwargs.pop('token') + + self.client = hvac.Client(url=self.url, token=self.token) + + if self.client.is_authenticated(): + pass + else: + raise AnsibleError("Invalid Hashicorp Vault Token Specified") + + def get(self): + value = "" + + data = self.client.read(self.secret) + if data == None: + raise AnsibleError("The secret %s doesn't seem to exist" % self.secret) + else: + return data['data']['value'] + + + +class LookupModule(LookupBase): + + def run(self, terms, variables, **kwargs): + + vault_args = terms[0].split(' ') + vault_dict = {} + ret = [] + + for param in vault_args: + key, value = param.split('=') + vault_dict[key] = value + + vault_conn = HashiVault(**vault_dict) + + for term in terms: + key = term.split()[0] + value = vault_conn.get() + ret.append(value) + return ret From c4bd59046277970faaa16c927a92b89ca78238a1 Mon Sep 17 00:00:00 2001 From: Vyacheslav Voronenko Date: Tue, 15 Sep 2015 23:27:29 +0300 Subject: [PATCH 04/18] adjusted plugin to be compatible with 1.9 signatures --- prudentia/plugins/lookup/hashi_vault.py | 12 +++++++----- requirements.txt | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/prudentia/plugins/lookup/hashi_vault.py b/prudentia/plugins/lookup/hashi_vault.py index b0fcf95..a481e50 100644 --- a/prudentia/plugins/lookup/hashi_vault.py +++ b/prudentia/plugins/lookup/hashi_vault.py @@ -32,8 +32,7 @@ import os from ansible.errors import * -from ansible.plugins.lookup import LookupBase - +from ansible.utils import safe_eval ANSIBLE_HASHI_VAULT_ADDR = 'http://127.0.0.1:8200' @@ -68,12 +67,15 @@ def get(self): return data['data']['value'] +class LookupModule(object): -class LookupModule(LookupBase): + def __init__(self, basedir=None, **kwargs): + pass - def run(self, terms, variables, **kwargs): + def run(self, terms, inject=None, **kwargs): - vault_args = terms[0].split(' ') + terms = safe_eval(terms) + vault_args = terms.split(' ') vault_dict = {} ret = [] diff --git a/requirements.txt b/requirements.txt index 37028a5..b43cc6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,3 +21,4 @@ pycrypto==2.6.1 requests==2.7.0 sh==1.11 six==1.9.0 +hvac==0.2.4 From c83bfa9ce46095361240ae756c5edb72cf10c58f Mon Sep 17 00:00:00 2001 From: Vyacheslav Voronenko Date: Wed, 16 Sep 2015 10:17:26 +0300 Subject: [PATCH 05/18] improve(hashi_vault) Fixed security error for ansible 1.9 by changing parameter separator. --- prudentia/plugins/lookup/hashi_vault.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/prudentia/plugins/lookup/hashi_vault.py b/prudentia/plugins/lookup/hashi_vault.py index a481e50..4595262 100644 --- a/prudentia/plugins/lookup/hashi_vault.py +++ b/prudentia/plugins/lookup/hashi_vault.py @@ -39,6 +39,7 @@ if os.getenv('VAULT_ADDR') is not None: ANSIBLE_HASHI_VAULT_ADDR = os.environ['VAULT_ADDR'] + class HashiVault: def __init__(self, **kwargs): try: @@ -74,19 +75,16 @@ def __init__(self, basedir=None, **kwargs): def run(self, terms, inject=None, **kwargs): - terms = safe_eval(terms) +# terms = safe_eval(terms) vault_args = terms.split(' ') vault_dict = {} ret = [] for param in vault_args: - key, value = param.split('=') + key, value = param.split('|') vault_dict[key] = value vault_conn = HashiVault(**vault_dict) - - for term in terms: - key = term.split()[0] - value = vault_conn.get() - ret.append(value) + value = vault_conn.get() + ret.append(value) return ret From 635f7f9930fea87ca04db0d4870a7342d0712fcf Mon Sep 17 00:00:00 2001 From: Vyacheslav Voronenko Date: Wed, 16 Sep 2015 12:03:17 +0300 Subject: [PATCH 06/18] hack(hashi_vault) due to json config being corrupted, support for writing output directly to local file. --- prudentia/plugins/lookup/hashi_vault.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/prudentia/plugins/lookup/hashi_vault.py b/prudentia/plugins/lookup/hashi_vault.py index 4595262..92aed01 100644 --- a/prudentia/plugins/lookup/hashi_vault.py +++ b/prudentia/plugins/lookup/hashi_vault.py @@ -87,4 +87,10 @@ def run(self, terms, inject=None, **kwargs): vault_conn = HashiVault(**vault_dict) value = vault_conn.get() ret.append(value) + + if 'write_to_file' in vault_dict.keys(): + text_file = open(vault_dict['write_to_file'], "w") + text_file.write(value) + text_file.close() + return ret From 78068b4201b915fcd5c7ce92a9f7d2a17e000591 Mon Sep 17 00:00:00 2001 From: Vyacheslav Voronenko Date: Wed, 16 Sep 2015 21:08:21 +0300 Subject: [PATCH 07/18] improve(vault) implemented hashi_vault_template module for clear syntax for vault binary files. --- prudentia/cli.py | 1 + prudentia/modules/hashi_vault_template.py | 71 +++++++++++++++++++++++ requirements.txt | 2 +- 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 prudentia/modules/hashi_vault_template.py diff --git a/prudentia/cli.py b/prudentia/cli.py index 1690aa2..59c0fbf 100644 --- a/prudentia/cli.py +++ b/prudentia/cli.py @@ -11,6 +11,7 @@ os.environ['ANSIBLE_CONFIG'] = path.join(cwd, 'ansible.cfg') os.environ['ANSIBLE_ROLES_PATH'] = path.join(cwd, 'roles') + os.pathsep + '/etc/ansible/roles' os.environ['ANSIBLE_LOOKUP_PLUGINS'] = path.join(cwd, 'plugins', 'lookup') + os.pathsep + '/usr/share/ansible_plugins/lookup_plugins' +os.environ['ANSIBLE_LIBRARY'] = path.join(cwd, 'modules') from digital_ocean import DigitalOceanCli from local import LocalCli diff --git a/prudentia/modules/hashi_vault_template.py b/prudentia/modules/hashi_vault_template.py new file mode 100644 index 0000000..e844118 --- /dev/null +++ b/prudentia/modules/hashi_vault_template.py @@ -0,0 +1,71 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# [..] various imports + +# this line must be written exactly that way, +# as Ansible will replace it with the "imported" code +from ansible.module_utils.basic import * + +ANSIBLE_HASHI_VAULT_ADDR = 'http://127.0.0.1:8200' + +if os.getenv('VAULT_ADDR') is not None: + ANSIBLE_HASHI_VAULT_ADDR = os.environ['VAULT_ADDR'] + + +class HashiVault: + def __init__(self, **kwargs): + try: + import hvac + except ImportError: + raise Exception("Please pip install hvac to use this module") + + self.url = kwargs.pop('url') + self.secret = kwargs.pop('secret') + self.token = kwargs.pop('token') + + self.client = hvac.Client(url=self.url, token=self.token) + + if self.client.is_authenticated(): + pass + else: + raise Exception("Invalid Hashicorp Vault Token Specified") + + def get(self): + value = "" + + data = self.client.read(self.secret) + if data == None: + raise Exception("The secret %s doesn't seem to exist" % self.secret) + else: + return data['data']['value'] + + +if __name__ == '__main__': + global module + module = AnsibleModule( + argument_spec={ + 'secret': { 'required': True, 'type': 'str' }, + 'dest': { 'required': True, 'type': 'str' }, + 'token': { 'required': False, 'type': 'str' }, + }, + supports_check_mode=False + ) + + args = module.params + + args['url'] = ANSIBLE_HASHI_VAULT_ADDR + try: + vault_conn = HashiVault(**args) + value = vault_conn.get() + + dest_file = os.path.abspath(args['dest']) + + text_file = open(dest_file, "w") + text_file.write(value) + text_file.close() + + module.exit_json(changed=True, file=dest_file) + + except Exception, e: + module.fail_json(msg=str(e)) diff --git a/requirements.txt b/requirements.txt index b43cc6c..6e5dd21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ cryptography==0.9.3 dopy==0.3.6 ecdsa==0.13 enum34==1.0.4 +hvac==0.2.4 idna==2.0 ipaddress==1.0.14 ndg-httpsclient==0.4.0 @@ -21,4 +22,3 @@ pycrypto==2.6.1 requests==2.7.0 sh==1.11 six==1.9.0 -hvac==0.2.4 From 28a4254d4bf5c1122b6f79451298849837778810 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Fri, 18 Sep 2015 09:55:03 +0200 Subject: [PATCH 08/18] chore(example): Updates client nginx ssl configuration --- examples/components/client.yml | 13 +++++++++++++ examples/components/templates/client/nginx.conf.j2 | 6 ++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/components/client.yml b/examples/components/client.yml index 4dbc8a2..050ce14 100644 --- a/examples/components/client.yml +++ b/examples/components/client.yml @@ -31,6 +31,19 @@ sudo: yes tags: client + - name: Client | Copy SSL Certificates + copy: src={{ssl_dir_path}}/{{item}} dest={{ssl_install_path}}/{{item}} + with_items: ssl_files + sudo: yes + tags: + - client + - update + + - name: Client | Generate nginx dhparam.pem if not exists + command: openssl dhparam -out {{ssl_install_path}}/dhparam.pem 4096 creates={{ssl_install_path}}/dhparam.pem + sudo: yes + tags: client + - name: Client | Copy Nginx configuration template: src={{root_dir}}/components/templates/client/nginx.conf.j2 dest=/etc/nginx/sites-available/example.com backup=yes sudo: yes diff --git a/examples/components/templates/client/nginx.conf.j2 b/examples/components/templates/client/nginx.conf.j2 index 7d9ba22..28a8f40 100644 --- a/examples/components/templates/client/nginx.conf.j2 +++ b/examples/components/templates/client/nginx.conf.j2 @@ -294,9 +294,11 @@ server { # Protect against the BEAST attack by preferring RC4-SHA when using SSLv3 and TLS protocols. # Note that TLSv1.1 and TLSv1.2 are immune to the beast attack but only work with OpenSSL v1.0.1 and higher and has limited client support. - # ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; - # ssl_ciphers RC4:HIGH:!aNULL:!MD5; + # Follow config per https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + # ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + # ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED"; # ssl_prefer_server_ciphers on; + # ssl_dhparam /etc/nginx/dhparam.pem; # Optimize SSL by caching session parameters for 10 minutes. This cuts down on the number of expensive SSL handshakes. From c35ffd2f8a2d7aef728b3ebfd7e0884f85a7fb68 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Fri, 18 Sep 2015 09:55:28 +0200 Subject: [PATCH 09/18] chore(monit): Avoids useless restart at the end of the installation --- prudentia/tasks/monit.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/prudentia/tasks/monit.yml b/prudentia/tasks/monit.yml index d043895..625640d 100644 --- a/prudentia/tasks/monit.yml +++ b/prudentia/tasks/monit.yml @@ -21,8 +21,3 @@ when: monit_check_deb.rc == 1 sudo: yes tags: monit - - - name: Monit | Restart - service: name=monit state=restarted - sudo: yes - tags: monit From c0f1aa5ff76800e674e024bb116ce2894d660d46 Mon Sep 17 00:00:00 2001 From: "Volodymyr Shcherbinin (vovin)" Date: Fri, 18 Sep 2015 14:46:55 +0200 Subject: [PATCH 10/18] fix(ufw): reset ufw rules at start --- prudentia/tasks/ufw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prudentia/tasks/ufw.yml b/prudentia/tasks/ufw.yml index 39c4c65..519be4a 100644 --- a/prudentia/tasks/ufw.yml +++ b/prudentia/tasks/ufw.yml @@ -5,7 +5,7 @@ # ufw_rules_allow_from_hosts (optional) - name: UFW | Disable it - ufw: state=disabled + ufw: state=reset sudo: yes tags: ufw From d77bc4ccc96637f698ce0ba69ca63631168f0a1a Mon Sep 17 00:00:00 2001 From: "Volodymyr Shcherbinin (vovin)" Date: Fri, 18 Sep 2015 14:48:30 +0200 Subject: [PATCH 11/18] fix(ufw): amend description --- prudentia/tasks/ufw.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prudentia/tasks/ufw.yml b/prudentia/tasks/ufw.yml index 519be4a..197e4eb 100644 --- a/prudentia/tasks/ufw.yml +++ b/prudentia/tasks/ufw.yml @@ -4,7 +4,7 @@ # ufw_rules_allow (provided) # ufw_rules_allow_from_hosts (optional) - - name: UFW | Disable it + - name: UFW | Reset it ufw: state=reset sudo: yes tags: ufw From d38d9a32c79b93a4b3aea8a961a643056ac193da Mon Sep 17 00:00:00 2001 From: Vyacheslav Voronenko Date: Tue, 22 Sep 2015 09:37:04 +0300 Subject: [PATCH 12/18] improve(mongo) introduced mongo3 recipe --- prudentia/tasks/mongodb_3.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 prudentia/tasks/mongodb_3.yml diff --git a/prudentia/tasks/mongodb_3.yml b/prudentia/tasks/mongodb_3.yml new file mode 100644 index 0000000..edac821 --- /dev/null +++ b/prudentia/tasks/mongodb_3.yml @@ -0,0 +1,27 @@ +--- + # Parameters: + # prudentia_dir (provided) + + - name: MongoDB | Check if is present + command: test -x /usr/bin/mongo + when: ansible_system == "Linux" + ignore_errors: yes + register: mongo_present + tags: mongodb3 + + - name: MongoDB | Add GPG key to apt keyring + apt_key: id=7F0CEB10 url=http://docs.mongodb.org/10gen-gpg-key.asc + sudo: yes + tags: mongodb3 + + - name: MongoDB | Add Debian apt repository + apt_repository: repo="deb http://repo.mongodb.org/apt/ubuntu {{ ansible_distribution_release }}/mongodb-org/3.0 multiverse" + when: ansible_os_family == "Debian" and mongo_present|failed + tags: mongodb3 + sudo: yes + + - name: MongoDB | Install + apt: update-cache=yes force=yes state=present pkg=mongodb-org + when: ansible_os_family == "Debian" and mongo_present|failed + sudo: yes + tags: mongodb3 From 212c26e611f2224dd004b858eaac11d4103f6284 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 29 Sep 2015 13:33:03 +0200 Subject: [PATCH 13/18] chore(vault): Renaming module hashi_vault_template in hashi_vault_file. Cleaning unused imports and useless vars. --- ...ashi_vault_template.py => hashi_vault_file.py} | 15 ++++++--------- prudentia/plugins/lookup/hashi_vault.py | 12 +++--------- 2 files changed, 9 insertions(+), 18 deletions(-) rename prudentia/modules/{hashi_vault_template.py => hashi_vault_file.py} (85%) diff --git a/prudentia/modules/hashi_vault_template.py b/prudentia/modules/hashi_vault_file.py similarity index 85% rename from prudentia/modules/hashi_vault_template.py rename to prudentia/modules/hashi_vault_file.py index e844118..6507a89 100644 --- a/prudentia/modules/hashi_vault_template.py +++ b/prudentia/modules/hashi_vault_file.py @@ -13,7 +13,7 @@ ANSIBLE_HASHI_VAULT_ADDR = os.environ['VAULT_ADDR'] -class HashiVault: +class HashiVaultFile: def __init__(self, **kwargs): try: import hvac @@ -32,10 +32,8 @@ def __init__(self, **kwargs): raise Exception("Invalid Hashicorp Vault Token Specified") def get(self): - value = "" - data = self.client.read(self.secret) - if data == None: + if data is None: raise Exception("The secret %s doesn't seem to exist" % self.secret) else: return data['data']['value'] @@ -45,9 +43,9 @@ def get(self): global module module = AnsibleModule( argument_spec={ - 'secret': { 'required': True, 'type': 'str' }, - 'dest': { 'required': True, 'type': 'str' }, - 'token': { 'required': False, 'type': 'str' }, + 'secret': {'required': True, 'type': 'str'}, + 'dest': {'required': True, 'type': 'str'}, + 'token': {'required': False, 'type': 'str'}, }, supports_check_mode=False ) @@ -56,7 +54,7 @@ def get(self): args['url'] = ANSIBLE_HASHI_VAULT_ADDR try: - vault_conn = HashiVault(**args) + vault_conn = HashiVaultFile(**args) value = vault_conn.get() dest_file = os.path.abspath(args['dest']) @@ -66,6 +64,5 @@ def get(self): text_file.close() module.exit_json(changed=True, file=dest_file) - except Exception, e: module.fail_json(msg=str(e)) diff --git a/prudentia/plugins/lookup/hashi_vault.py b/prudentia/plugins/lookup/hashi_vault.py index 92aed01..4756dd1 100644 --- a/prudentia/plugins/lookup/hashi_vault.py +++ b/prudentia/plugins/lookup/hashi_vault.py @@ -32,7 +32,6 @@ import os from ansible.errors import * -from ansible.utils import safe_eval ANSIBLE_HASHI_VAULT_ADDR = 'http://127.0.0.1:8200' @@ -40,7 +39,7 @@ ANSIBLE_HASHI_VAULT_ADDR = os.environ['VAULT_ADDR'] -class HashiVault: +class HashiVaultLookup: def __init__(self, **kwargs): try: import hvac @@ -59,23 +58,18 @@ def __init__(self, **kwargs): raise AnsibleError("Invalid Hashicorp Vault Token Specified") def get(self): - value = "" - data = self.client.read(self.secret) - if data == None: + if data is None: raise AnsibleError("The secret %s doesn't seem to exist" % self.secret) else: return data['data']['value'] class LookupModule(object): - def __init__(self, basedir=None, **kwargs): pass def run(self, terms, inject=None, **kwargs): - -# terms = safe_eval(terms) vault_args = terms.split(' ') vault_dict = {} ret = [] @@ -84,7 +78,7 @@ def run(self, terms, inject=None, **kwargs): key, value = param.split('|') vault_dict[key] = value - vault_conn = HashiVault(**vault_dict) + vault_conn = HashiVaultLookup(**vault_dict) value = vault_conn.get() ret.append(value) From 3658cd25c522172bc99d411e81d3a606e2e6a386 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 29 Sep 2015 16:50:11 +0200 Subject: [PATCH 14/18] feature(elb): Uses environment variables to get parameters. Introduces ELB_NAME and HOSTS_NAME to get the correct list of instance addresses. --- prudentia/inventories/aws_elb.py | 153 +++++++++++++++---------------- 1 file changed, 74 insertions(+), 79 deletions(-) diff --git a/prudentia/inventories/aws_elb.py b/prudentia/inventories/aws_elb.py index 3c8af90..cddc815 100755 --- a/prudentia/inventories/aws_elb.py +++ b/prudentia/inventories/aws_elb.py @@ -8,11 +8,6 @@ ### Generates inventory that Ansible can understand by making API request to ### AWS EC2 using the Boto library. ### -### NOTE: This script assumes Ansible is being executed where the environment -### variables needed for Boto have already been set: -### export AWS_ACCESS_KEY_ID='ABC123' -### export AWS_SECRET_ACCESS_KEY='abc123' -### #################################################################################### import sys @@ -24,72 +19,68 @@ except: import simplejson as json -### Functions -# Check env vars -def check_vars(): - if None in [os.environ.get('AWS_ACCESS_KEY_ID'), os.environ.get('AWS_SECRET_ACCESS_KEY')]: - boto_path = ['/etc/boto.cfg', '~/.boto', '~/.aws/credentials'] - boto_config_found = list(p for p in boto_path if os.path.isfile(os.path.expanduser(p))) - if len(boto_config_found) <= 0: - error("Cannot find AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY variables or read credentials from %s" % boto_path, "checking the envronment") + +AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID', '') +AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY', '') +ELB_NAME = os.getenv('ELB_NAME', '') +HOSTS_NAME = os.getenv('HOSTS_NAME', '') + # Log an error to stderr and quit def error(err_msg, err_operation=None): - if err_operation: - err_msg = 'Error {err_operation}: "{err_msg}"\n'.format(err_msg=err_msg, err_operation=err_operation) - else: - err_msg = 'Error: {err_msg}\n'.format(err_msg=err_msg) - sys.stderr.write(err_msg) - sys.exit(1) + if err_operation: + err_msg = 'Error {err_operation}: "{err_msg}"\n'.format(err_msg=err_msg, err_operation=err_operation) + else: + err_msg = 'Error: {err_msg}\n'.format(err_msg=err_msg) + sys.stderr.write(err_msg) + sys.exit(1) # Get instance info def get_instance_info(instances): - # Init ELB instance list - info = [] - # Walk through the instance list - for instance in instances: - # Get instance info - try: - ec2_instance = ec2_conn.get_only_instances(instance.id) - # Walk through instance info - except boto.exception.BotoServerError as e: - error("%s (%s)" % (e.message, e.error_code) , "fetching ELB instance properties") - for i in ec2_instance: - # Q: shall we check if ec2_instance and i.id match? - # Append instance info to the list - #info.append([i.id, i.ip_address, i.dns_name]) - info.append(i.ip_address) - # Return info - return info + # Init ELB instance list + info = [] + # Walk through the instance list + for instance in instances: + # Get instance info + try: + ec2_instance = ec2_conn.get_only_instances(instance.id) + for i in ec2_instance: + # Append instance info to the list + info.append(i.ip_address) + # Walk through instance info + except boto.exception.BotoServerError as e: + error("%s (%s)" % (e.message, e.error_code) , "fetching ELB instance properties") + # Return info + return info # Get full instance list under each ELB def get_elb_list(): - # Init function return dictionary - elb_result = {} - # Walk through the ELB list - try: - for elb in elb_conn.get_all_load_balancers(): - # Add list as a value to ELB name - elb_result[elb.name] = get_instance_info(elb.instances) - except boto.exception.BotoServerError as e: - error("%s (%s)" % (e.message, e.error_code) , "fetching ELB list") - # Return here - return elb_result + # Init function return dictionary + elb_result = {} + # Walk through the ELB list + try: + for elb in elb_conn.get_all_load_balancers(): + # Add list as a value to ELB name + elb_result[elb.name] = get_instance_info(elb.instances) + except boto.exception.BotoServerError as e: + error("%s (%s)" % (e.message, e.error_code) , "fetching ELB list") + # Return here + return elb_result # Get full instance list under particular ELB def get_elb_info(elb_name): - # Init function return dictionary - elb_result = {} - # Get ELB info - try: - elb = elb_conn.get_all_load_balancers(elb_name) - for i in elb: - # Add list as a value to ELB name - elb_result[i.name] = get_instance_info(i.instances) - except boto.exception.BotoServerError as e: - error("%s (%s)" % (e.message, e.error_code) , "fetching instances under ELB") - # Return here - return elb_result + # Init function return dictionary + elb_result = [] + # Get ELB info + try: + elb = elb_conn.get_all_load_balancers(elb_name) + for i in elb: + # Add list as a value to ELB name + elb_result = get_instance_info(i.instances) + except boto.exception.BotoServerError as e: + error("%s (%s)" % (e.message, e.error_code) , "fetching instances under ELB") + + return elb_result ### Start the ball # Parse the options @@ -100,33 +91,37 @@ def get_elb_info(elb_name): # Check if we have options and print help if not options.list and not options.elb: - parser.print_help() - sys.exit(0) + parser.print_help() + sys.exit(0) -# Check if env vars are in place -check_vars() # Try to connect to AWS try: - elb_conn = boto.connect_elb() + elb_conn = boto.connect_elb() except boto.exception.BotoServerError as e: - error("%s (%s)" % (e.message, e.error_code) , "connecting to ELB") + error("%s (%s)" % (e.message, e.error_code) , "connecting to ELB") try: - ec2_conn = boto.connect_ec2() + ec2_conn = boto.connect_ec2() except boto.exception.BotoServerError as e: - error("%s (%s)" % (e.message, e.error_code) , "connecting to EC2") + error("%s (%s)" % (e.message, e.error_code) , "connecting to EC2") # Get instance details under all ELB -if options.list: - elb_list = get_elb_list() - print json.dumps(elb_list, indent=4) - sys.exit(0) - -# Get instance details under particular ELB -elif options.elb: - elb_info = get_elb_info(options.elb) - print json.dumps(elb_info, indent=4) - sys.exit(0) - -### EOF +if ELB_NAME == '': + if options.list: + elb_list = get_elb_list() + print json.dumps(elb_list) + sys.exit(0) + + # Get instance details under particular ELB + elif options.elb: + elb_info = get_elb_info(options.elb) + print json.dumps(elb_info) + sys.exit(0) +else: + elb_info = get_elb_info(ELB_NAME) + elb_hosts = elb_info + host_group = {HOSTS_NAME: {}} + host_group[HOSTS_NAME]['hosts'] = elb_hosts + print json.dumps(host_group) + sys.exit(0) From 872f89fdb70bb4b40d01eb0b84ab0053fd41b79a Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 29 Sep 2015 16:51:08 +0200 Subject: [PATCH 15/18] chore(elb): Renames script. --- prudentia/inventories/{aws_elb.py => aws_elb_instances.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename prudentia/inventories/{aws_elb.py => aws_elb_instances.py} (100%) diff --git a/prudentia/inventories/aws_elb.py b/prudentia/inventories/aws_elb_instances.py similarity index 100% rename from prudentia/inventories/aws_elb.py rename to prudentia/inventories/aws_elb_instances.py From d7f16d845b835363c740f164a5371b27475402b8 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 29 Sep 2015 17:00:19 +0200 Subject: [PATCH 16/18] chore(deps): Updates to Ansible 1.9.3 and Jinja 2.8 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 6e5dd21..8134f62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -Jinja2==2.7.3 +Jinja2==2.8 MarkupSafe==0.23 PyYAML==3.11 -ansible==1.9.2 +ansible==1.9.3 boto==2.38.0 cffi==1.1.2 coverage==3.7.1 From dd4f9acfa12f5d1514edd002d93c425ae1405b47 Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 29 Sep 2015 17:06:41 +0200 Subject: [PATCH 17/18] chore(global vars): We have been problems in overriding those vars because they take precedence, so we stop that. --- prudentia/simple.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/prudentia/simple.py b/prudentia/simple.py index 5b92b45..4db211e 100644 --- a/prudentia/simple.py +++ b/prudentia/simple.py @@ -193,9 +193,7 @@ def __init__(self, name, general_type=None, box_extra_type=None): self.vault_password = False self.provisioned = False self.tags = {} - pd = prudentia_python_dir() - self.extra_vars = {'prudentia_dir': pd} - self.load_vars(os.path.join(pd, 'vars', 'global.yml'), False) + self.extra_vars = {'prudentia_dir': prudentia_python_dir()} self.load_tags() def boxes(self): From f794e43b794fc70a0fab77ed5eef13596bd5811d Mon Sep 17 00:00:00 2001 From: Tiziano Perrucci Date: Tue, 29 Sep 2015 17:30:48 +0200 Subject: [PATCH 18/18] Bumps version to 0.15 and updates history. --- HISTORY.rst | 16 ++++++++++++++++ prudentia/__init__.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index a99858a..df9dadf 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,22 @@ Release History --------------- +0.15 (2015-09-29) ++++++++++++++++++ + +**Improvements** + +- Adds script that can generate dynamically an Ansible inventory based on the instances connected to an AWS ELB. +- Adds HashiCorp Vault Ansible lookup plugin. +- Adds HashiCorp File Ansible module. +- Adds bundled task: mongodb_3. +- Updates Ngnix example and improves Monit task. +- Updates to Ansible 1.9.3. + +**Bugfixes** + +- Changed state for UFW from 'disabled' to 'reset' to avoid old and new rules to be merged. + 0.14 (2015-09-04) +++++++++++++++++ diff --git a/prudentia/__init__.py b/prudentia/__init__.py index 0e05dad..9d220f9 100644 --- a/prudentia/__init__.py +++ b/prudentia/__init__.py @@ -1,2 +1,2 @@ -__version__ = '0.14' +__version__ = '0.15' __author__ = 'Tiziano Perrucci'