Remove incidentals without coverage (#71788)
* Remove incidental_lookup_hashi_vault * Remove incidental_connection_chroot * Remove incidental_selinux * Remove incidental_win_hosts
This commit is contained in:
parent
2d2c5d5ed7
commit
e6e9840717
29 changed files with 0 additions and 2283 deletions
|
@ -1,3 +0,0 @@
|
||||||
needs/root
|
|
||||||
shippable/posix/incidental
|
|
||||||
needs/target/connection
|
|
|
@ -1,18 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
# Connection tests for POSIX platforms use this script by linking to it from the appropriate 'connection_' target dir.
|
|
||||||
# The name of the inventory group to test is extracted from the directory name following the 'connection_' prefix.
|
|
||||||
|
|
||||||
group=$(python -c \
|
|
||||||
"from os import path; print(path.basename(path.abspath(path.dirname('$0'))).replace('incidental_connection_', ''))")
|
|
||||||
|
|
||||||
cd ../connection
|
|
||||||
|
|
||||||
INVENTORY="../incidental_connection_${group}/test_connection.inventory" ./test.sh \
|
|
||||||
-e target_hosts="${group}" \
|
|
||||||
-e action_prefix= \
|
|
||||||
-e local_tmp=/tmp/ansible-local \
|
|
||||||
-e remote_tmp=/tmp/ansible-remote \
|
|
||||||
"$@"
|
|
|
@ -1,7 +0,0 @@
|
||||||
[chroot]
|
|
||||||
chroot-pipelining ansible_ssh_pipelining=true
|
|
||||||
chroot-no-pipelining ansible_ssh_pipelining=false
|
|
||||||
[chroot:vars]
|
|
||||||
ansible_host=/
|
|
||||||
ansible_connection=chroot
|
|
||||||
ansible_python_interpreter="{{ ansible_playbook_python }}"
|
|
|
@ -1,7 +0,0 @@
|
||||||
shippable/posix/incidental
|
|
||||||
destructive
|
|
||||||
needs/target/incidental_setup_openssl
|
|
||||||
needs/file/test/lib/ansible_test/_data/requirements/constraints.txt
|
|
||||||
skip/aix
|
|
||||||
skip/power/centos
|
|
||||||
skip/python2.6
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
vault_gen_path: 'gen/testproject'
|
|
||||||
vault_kv1_path: 'kv1/testproject'
|
|
||||||
vault_kv2_path: 'kv2/data/testproject'
|
|
|
@ -1,21 +0,0 @@
|
||||||
- name: 'Create an approle policy'
|
|
||||||
shell: "echo '{{ policy }}' | {{ vault_cmd }} policy write approle-policy -"
|
|
||||||
vars:
|
|
||||||
policy: |
|
|
||||||
path "auth/approle/login" {
|
|
||||||
capabilities = [ "create", "read" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: 'Enable the AppRole auth method'
|
|
||||||
command: '{{ vault_cmd }} auth enable approle'
|
|
||||||
|
|
||||||
- name: 'Create a named role'
|
|
||||||
command: '{{ vault_cmd }} write auth/approle/role/test-role policies="test-policy,approle-policy"'
|
|
||||||
|
|
||||||
- name: 'Fetch the RoleID of the AppRole'
|
|
||||||
command: '{{ vault_cmd }} read -field=role_id auth/approle/role/test-role/role-id'
|
|
||||||
register: role_id_cmd
|
|
||||||
|
|
||||||
- name: 'Get a SecretID issued against the AppRole'
|
|
||||||
command: '{{ vault_cmd }} write -field=secret_id -f auth/approle/role/test-role/secret-id'
|
|
||||||
register: secret_id_cmd
|
|
|
@ -1,45 +0,0 @@
|
||||||
- vars:
|
|
||||||
role_id: '{{ role_id_cmd.stdout }}'
|
|
||||||
secret_id: '{{ secret_id_cmd.stdout }}'
|
|
||||||
block:
|
|
||||||
- name: 'Fetch secrets using "hashi_vault" lookup'
|
|
||||||
set_fact:
|
|
||||||
secret1: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret1 auth_method=approle secret_id=' ~ secret_id ~ ' role_id=' ~ role_id) }}"
|
|
||||||
secret2: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret2 auth_method=approle secret_id=' ~ secret_id ~ ' role_id=' ~ role_id) }}"
|
|
||||||
|
|
||||||
- name: 'Check secret values'
|
|
||||||
fail:
|
|
||||||
msg: 'unexpected secret values'
|
|
||||||
when: secret1['value'] != 'foo1' or secret2['value'] != 'foo2'
|
|
||||||
|
|
||||||
- name: 'Failure expected when erroneous credentials are used'
|
|
||||||
vars:
|
|
||||||
secret_wrong_cred: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret2 auth_method=approle secret_id=toto role_id=' ~ role_id) }}"
|
|
||||||
debug:
|
|
||||||
msg: 'Failure is expected ({{ secret_wrong_cred }})'
|
|
||||||
register: test_wrong_cred
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: 'Failure expected when unauthorized secret is read'
|
|
||||||
vars:
|
|
||||||
secret_unauthorized: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret3 auth_method=approle secret_id=' ~ secret_id ~ ' role_id=' ~ role_id) }}"
|
|
||||||
debug:
|
|
||||||
msg: 'Failure is expected ({{ secret_unauthorized }})'
|
|
||||||
register: test_unauthorized
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: 'Failure expected when inexistent secret is read'
|
|
||||||
vars:
|
|
||||||
secret_inexistent: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret4 auth_method=approle secret_id=' ~ secret_id ~ ' role_id=' ~ role_id) }}"
|
|
||||||
debug:
|
|
||||||
msg: 'Failure is expected ({{ secret_inexistent }})'
|
|
||||||
register: test_inexistent
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: 'Check expected failures'
|
|
||||||
assert:
|
|
||||||
msg: "an expected failure didn't occur"
|
|
||||||
that:
|
|
||||||
- test_wrong_cred is failed
|
|
||||||
- test_unauthorized is failed
|
|
||||||
- test_inexistent is failed
|
|
|
@ -1,155 +0,0 @@
|
||||||
---
|
|
||||||
- name: Install Hashi Vault on controlled node and test
|
|
||||||
|
|
||||||
vars:
|
|
||||||
vault_version: '0.11.0'
|
|
||||||
vault_uri: 'https://ansible-ci-files.s3.amazonaws.com/test/integration/targets/lookup_hashi_vault/vault_{{ vault_version }}_{{ ansible_system | lower }}_{{ vault_arch }}.zip'
|
|
||||||
vault_cmd: '{{ local_temp_dir }}/vault'
|
|
||||||
|
|
||||||
block:
|
|
||||||
- name: Create a local temporary directory
|
|
||||||
tempfile:
|
|
||||||
state: directory
|
|
||||||
register: tempfile_result
|
|
||||||
|
|
||||||
- set_fact:
|
|
||||||
local_temp_dir: '{{ tempfile_result.path }}'
|
|
||||||
|
|
||||||
- when: pyopenssl_version.stdout is version('0.15', '>=')
|
|
||||||
block:
|
|
||||||
- name: Generate privatekey
|
|
||||||
openssl_privatekey:
|
|
||||||
path: '{{ local_temp_dir }}/privatekey.pem'
|
|
||||||
|
|
||||||
- name: Generate CSR
|
|
||||||
openssl_csr:
|
|
||||||
path: '{{ local_temp_dir }}/csr.csr'
|
|
||||||
privatekey_path: '{{ local_temp_dir }}/privatekey.pem'
|
|
||||||
subject:
|
|
||||||
commonName: localhost
|
|
||||||
|
|
||||||
- name: Generate selfsigned certificate
|
|
||||||
openssl_certificate:
|
|
||||||
path: '{{ local_temp_dir }}/cert.pem'
|
|
||||||
csr_path: '{{ local_temp_dir }}/csr.csr'
|
|
||||||
privatekey_path: '{{ local_temp_dir }}/privatekey.pem'
|
|
||||||
provider: selfsigned
|
|
||||||
selfsigned_digest: sha256
|
|
||||||
register: selfsigned_certificate
|
|
||||||
|
|
||||||
- name: 'Install unzip'
|
|
||||||
package:
|
|
||||||
name: unzip
|
|
||||||
when: ansible_distribution != "MacOSX" # unzip already installed
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
# Linux: x86_64, FreeBSD: amd64
|
|
||||||
that: ansible_architecture in ['i386', 'x86_64', 'amd64']
|
|
||||||
- set_fact:
|
|
||||||
vault_arch: '386'
|
|
||||||
when: ansible_architecture == 'i386'
|
|
||||||
- set_fact:
|
|
||||||
vault_arch: amd64
|
|
||||||
when: ansible_architecture in ['x86_64', 'amd64']
|
|
||||||
|
|
||||||
- name: 'Download vault binary'
|
|
||||||
unarchive:
|
|
||||||
src: '{{ vault_uri }}'
|
|
||||||
dest: '{{ local_temp_dir }}'
|
|
||||||
remote_src: true
|
|
||||||
|
|
||||||
- environment:
|
|
||||||
# used by vault command
|
|
||||||
VAULT_DEV_ROOT_TOKEN_ID: '47542cbc-6bf8-4fba-8eda-02e0a0d29a0a'
|
|
||||||
block:
|
|
||||||
- name: 'Create configuration file'
|
|
||||||
template:
|
|
||||||
src: vault_config.hcl.j2
|
|
||||||
dest: '{{ local_temp_dir }}/vault_config.hcl'
|
|
||||||
|
|
||||||
- name: 'Start vault service'
|
|
||||||
environment:
|
|
||||||
VAULT_ADDR: 'http://localhost:8200'
|
|
||||||
block:
|
|
||||||
- name: 'Start vault server (dev mode enabled)'
|
|
||||||
shell: 'nohup {{ vault_cmd }} server -dev -config {{ local_temp_dir }}/vault_config.hcl </dev/null >/dev/null 2>&1 &'
|
|
||||||
|
|
||||||
- name: 'Create generic secrets engine'
|
|
||||||
command: '{{ vault_cmd }} secrets enable -path=gen generic'
|
|
||||||
|
|
||||||
- name: 'Create KV v1 secrets engine'
|
|
||||||
command: '{{ vault_cmd }} secrets enable -path=kv1 -version=1 kv'
|
|
||||||
|
|
||||||
- name: 'Create KV v2 secrets engine'
|
|
||||||
command: '{{ vault_cmd }} secrets enable -path=kv2 -version=2 kv'
|
|
||||||
|
|
||||||
- name: 'Create a test policy'
|
|
||||||
shell: "echo '{{ policy }}' | {{ vault_cmd }} policy write test-policy -"
|
|
||||||
vars:
|
|
||||||
policy: |
|
|
||||||
path "{{ vault_gen_path }}/secret1" {
|
|
||||||
capabilities = ["read"]
|
|
||||||
}
|
|
||||||
path "{{ vault_gen_path }}/secret2" {
|
|
||||||
capabilities = ["read", "update"]
|
|
||||||
}
|
|
||||||
path "{{ vault_gen_path }}/secret3" {
|
|
||||||
capabilities = ["deny"]
|
|
||||||
}
|
|
||||||
path "{{ vault_kv1_path }}/secret1" {
|
|
||||||
capabilities = ["read"]
|
|
||||||
}
|
|
||||||
path "{{ vault_kv1_path }}/secret2" {
|
|
||||||
capabilities = ["read", "update"]
|
|
||||||
}
|
|
||||||
path "{{ vault_kv1_path }}/secret3" {
|
|
||||||
capabilities = ["deny"]
|
|
||||||
}
|
|
||||||
path "{{ vault_kv2_path }}/secret1" {
|
|
||||||
capabilities = ["read"]
|
|
||||||
}
|
|
||||||
path "{{ vault_kv2_path }}/secret2" {
|
|
||||||
capabilities = ["read", "update"]
|
|
||||||
}
|
|
||||||
path "{{ vault_kv2_path }}/secret3" {
|
|
||||||
capabilities = ["deny"]
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: 'Create generic secrets'
|
|
||||||
command: '{{ vault_cmd }} write {{ vault_gen_path }}/secret{{ item }} value=foo{{ item }}'
|
|
||||||
loop: [1, 2, 3]
|
|
||||||
|
|
||||||
- name: 'Create KV v1 secrets'
|
|
||||||
command: '{{ vault_cmd }} kv put {{ vault_kv1_path }}/secret{{ item }} value=foo{{ item }}'
|
|
||||||
loop: [1, 2, 3]
|
|
||||||
|
|
||||||
- name: 'Create KV v2 secrets'
|
|
||||||
command: '{{ vault_cmd }} kv put {{ vault_kv2_path | regex_replace("/data") }}/secret{{ item }} value=foo{{ item }}'
|
|
||||||
loop: [1, 2, 3]
|
|
||||||
|
|
||||||
- name: setup approle auth
|
|
||||||
import_tasks: approle_setup.yml
|
|
||||||
when: ansible_distribution != 'RedHat' or ansible_distribution_major_version is version('7', '>')
|
|
||||||
|
|
||||||
- name: setup token auth
|
|
||||||
import_tasks: token_setup.yml
|
|
||||||
|
|
||||||
- import_tasks: tests.yml
|
|
||||||
vars:
|
|
||||||
auth_type: approle
|
|
||||||
when: ansible_distribution != 'RedHat' or ansible_distribution_major_version is version('7', '>')
|
|
||||||
|
|
||||||
- import_tasks: tests.yml
|
|
||||||
vars:
|
|
||||||
auth_type: token
|
|
||||||
|
|
||||||
always:
|
|
||||||
- name: 'Kill vault process'
|
|
||||||
shell: "kill $(cat {{ local_temp_dir }}/vault.pid)"
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
always:
|
|
||||||
- name: 'Delete temp dir'
|
|
||||||
file:
|
|
||||||
path: '{{ local_temp_dir }}'
|
|
||||||
state: absent
|
|
|
@ -1,35 +0,0 @@
|
||||||
- name: 'test {{ auth_type }} auth without SSL (lookup parameters)'
|
|
||||||
include_tasks: '{{ auth_type }}_test.yml'
|
|
||||||
vars:
|
|
||||||
conn_params: 'url=http://localhost:8200 '
|
|
||||||
|
|
||||||
- name: 'test {{ auth_type }} auth without SSL (environment variable)'
|
|
||||||
include_tasks: '{{ auth_type }}_test.yml'
|
|
||||||
args:
|
|
||||||
apply:
|
|
||||||
vars:
|
|
||||||
conn_params: ''
|
|
||||||
environment:
|
|
||||||
VAULT_ADDR: 'http://localhost:8200'
|
|
||||||
|
|
||||||
- when: pyopenssl_version.stdout is version('0.15', '>=')
|
|
||||||
block:
|
|
||||||
- name: 'test {{ auth_type }} auth with certs (validation enabled, lookup parameters)'
|
|
||||||
include_tasks: '{{ auth_type }}_test.yml'
|
|
||||||
vars:
|
|
||||||
conn_params: 'url=https://localhost:8201 ca_cert={{ local_temp_dir }}/cert.pem validate_certs=True '
|
|
||||||
|
|
||||||
- name: 'test {{ auth_type }} auth with certs (validation enabled, environment variables)'
|
|
||||||
include_tasks: '{{ auth_type }}_test.yml'
|
|
||||||
args:
|
|
||||||
apply:
|
|
||||||
vars:
|
|
||||||
conn_params: ''
|
|
||||||
environment:
|
|
||||||
VAULT_ADDR: 'https://localhost:8201'
|
|
||||||
VAULT_CACERT: '{{ local_temp_dir }}/cert.pem'
|
|
||||||
|
|
||||||
- name: 'test {{ auth_type }} auth with certs (validation disabled, lookup parameters)'
|
|
||||||
include_tasks: '{{ auth_type }}_test.yml'
|
|
||||||
vars:
|
|
||||||
conn_params: 'url=https://localhost:8201 validate_certs=False '
|
|
|
@ -1,3 +0,0 @@
|
||||||
- name: 'Create a test credentials (token)'
|
|
||||||
command: '{{ vault_cmd }} token create -policy test-policy -field token'
|
|
||||||
register: user_token_cmd
|
|
|
@ -1,58 +0,0 @@
|
||||||
- vars:
|
|
||||||
user_token: '{{ user_token_cmd.stdout }}'
|
|
||||||
block:
|
|
||||||
- name: 'Fetch secrets using "hashi_vault" lookup'
|
|
||||||
set_fact:
|
|
||||||
gen_secret1: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_gen_path ~ '/secret1 auth_method=token token=' ~ user_token) }}"
|
|
||||||
gen_secret2: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_gen_path ~ '/secret2 token=' ~ user_token) }}"
|
|
||||||
kv1_secret1: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv1_path ~ '/secret1 auth_method=token token=' ~ user_token) }}"
|
|
||||||
kv1_secret2: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv1_path ~ '/secret2 token=' ~ user_token) }}"
|
|
||||||
kv2_secret1: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret1 auth_method=token token=' ~ user_token) }}"
|
|
||||||
kv2_secret2: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret2 token=' ~ user_token) }}"
|
|
||||||
|
|
||||||
- name: 'Check secret generic values'
|
|
||||||
fail:
|
|
||||||
msg: 'unexpected secret values'
|
|
||||||
when: gen_secret1['value'] != 'foo1' or gen_secret2['value'] != 'foo2'
|
|
||||||
|
|
||||||
- name: 'Check secret kv1 values'
|
|
||||||
fail:
|
|
||||||
msg: 'unexpected secret values'
|
|
||||||
when: kv1_secret1['value'] != 'foo1' or kv1_secret2['value'] != 'foo2'
|
|
||||||
|
|
||||||
- name: 'Check secret kv2 values'
|
|
||||||
fail:
|
|
||||||
msg: 'unexpected secret values'
|
|
||||||
when: kv2_secret1['value'] != 'foo1' or kv2_secret2['value'] != 'foo2'
|
|
||||||
|
|
||||||
- name: 'Failure expected when erroneous credentials are used'
|
|
||||||
vars:
|
|
||||||
secret_wrong_cred: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret2 auth_method=token token=wrong_token') }}"
|
|
||||||
debug:
|
|
||||||
msg: 'Failure is expected ({{ secret_wrong_cred }})'
|
|
||||||
register: test_wrong_cred
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: 'Failure expected when unauthorized secret is read'
|
|
||||||
vars:
|
|
||||||
secret_unauthorized: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret3 token=' ~ user_token) }}"
|
|
||||||
debug:
|
|
||||||
msg: 'Failure is expected ({{ secret_unauthorized }})'
|
|
||||||
register: test_unauthorized
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: 'Failure expected when inexistent secret is read'
|
|
||||||
vars:
|
|
||||||
secret_inexistent: "{{ lookup('hashi_vault', conn_params ~ 'secret=' ~ vault_kv2_path ~ '/secret4 token=' ~ user_token) }}"
|
|
||||||
debug:
|
|
||||||
msg: 'Failure is expected ({{ secret_inexistent }})'
|
|
||||||
register: test_inexistent
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: 'Check expected failures'
|
|
||||||
assert:
|
|
||||||
msg: "an expected failure didn't occur"
|
|
||||||
that:
|
|
||||||
- test_wrong_cred is failed
|
|
||||||
- test_unauthorized is failed
|
|
||||||
- test_inexistent is failed
|
|
|
@ -1,10 +0,0 @@
|
||||||
# {{ ansible_managed }}
|
|
||||||
pid_file = "{{ local_temp_dir }}/vault.pid"
|
|
||||||
{% if pyopenssl_version.stdout is version('0.15', '>=') %}
|
|
||||||
listener "tcp" {
|
|
||||||
tls_key_file = "{{ local_temp_dir }}/privatekey.pem"
|
|
||||||
tls_cert_file = "{{ local_temp_dir }}/cert.pem"
|
|
||||||
tls_disable = false
|
|
||||||
address = "localhost:8201"
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
|
@ -1,19 +0,0 @@
|
||||||
- hosts: localhost
|
|
||||||
tasks:
|
|
||||||
- name: Install openssl
|
|
||||||
import_role:
|
|
||||||
name: incidental_setup_openssl
|
|
||||||
|
|
||||||
- name: "RedHat <= 7, select last version compatible with request 2.6.0 (this version doesn't support approle auth)"
|
|
||||||
set_fact:
|
|
||||||
hvac_package: 'hvac==0.2.5'
|
|
||||||
when: ansible_distribution == 'RedHat' and ansible_distribution_major_version is version('7', '<=')
|
|
||||||
|
|
||||||
- name: 'CentOS < 7, select last version compatible with Python 2.6'
|
|
||||||
set_fact:
|
|
||||||
hvac_package: 'hvac==0.5.0'
|
|
||||||
when: ansible_distribution == 'CentOS' and ansible_distribution_major_version is version('7', '<')
|
|
||||||
|
|
||||||
- name: 'Install hvac Python package'
|
|
||||||
pip:
|
|
||||||
name: "{{ hvac_package|default('hvac') }}"
|
|
|
@ -1,9 +0,0 @@
|
||||||
- hosts: localhost
|
|
||||||
tasks:
|
|
||||||
- name: register pyOpenSSL version
|
|
||||||
command: "{{ ansible_python.executable }} -c 'import OpenSSL; print(OpenSSL.__version__)'"
|
|
||||||
register: pyopenssl_version
|
|
||||||
|
|
||||||
- name: Test lookup hashi_vault
|
|
||||||
import_role:
|
|
||||||
name: incidental_lookup_hashi_vault/lookup_hashi_vault
|
|
|
@ -1,23 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
# First install pyOpenSSL, then test lookup in a second playbook in order to
|
|
||||||
# workaround this error which occurs on OS X 10.11 only:
|
|
||||||
#
|
|
||||||
# TASK [lookup_hashi_vault : test token auth with certs (validation enabled, lookup parameters)] ***
|
|
||||||
# included: lookup_hashi_vault/tasks/token_test.yml for testhost
|
|
||||||
#
|
|
||||||
# TASK [lookup_hashi_vault : Fetch secrets using "hashi_vault" lookup] ***
|
|
||||||
# From cffi callback <function _verify_callback at 0x106f995f0>:
|
|
||||||
# Traceback (most recent call last):
|
|
||||||
# File "/usr/local/lib/python2.7/site-packages/OpenSSL/SSL.py", line 309, in wrapper
|
|
||||||
# _lib.X509_up_ref(x509)
|
|
||||||
# AttributeError: 'module' object has no attribute 'X509_up_ref'
|
|
||||||
# fatal: [testhost]: FAILED! => { "msg": "An unhandled exception occurred while running the lookup plugin 'hashi_vault'. Error was a <class 'requests.exceptions.SSLError'>, original message: HTTPSConnectionPool(host='localhost', port=8201): Max retries exceeded with url: /v1/auth/token/lookup-self (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'ssl3_get_server_certificate', 'certificate verify failed')],)\",),))"}
|
|
||||||
|
|
||||||
ANSIBLE_ROLES_PATH=../ \
|
|
||||||
ansible-playbook playbooks/install_dependencies.yml -v "$@"
|
|
||||||
|
|
||||||
ANSIBLE_ROLES_PATH=../ \
|
|
||||||
ansible-playbook playbooks/test_lookup_hashi_vault.yml -v "$@"
|
|
|
@ -1,3 +0,0 @@
|
||||||
needs/root
|
|
||||||
shippable/posix/incidental
|
|
||||||
skip/aix
|
|
|
@ -1,36 +0,0 @@
|
||||||
# (c) 2017, Sam Doran <sdoran@redhat.com>
|
|
||||||
|
|
||||||
# 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
msg: SELinux is disabled
|
|
||||||
when: ansible_selinux is defined and ansible_selinux == False
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
msg: SELinux is {{ ansible_selinux.status }}
|
|
||||||
when: ansible_selinux is defined and ansible_selinux != False
|
|
||||||
|
|
||||||
- include: selinux.yml
|
|
||||||
when:
|
|
||||||
- ansible_selinux is defined
|
|
||||||
- ansible_selinux != False
|
|
||||||
- ansible_selinux.status == 'enabled'
|
|
||||||
|
|
||||||
- include: selogin.yml
|
|
||||||
when:
|
|
||||||
- ansible_selinux is defined
|
|
||||||
- ansible_selinux != False
|
|
||||||
- ansible_selinux.status == 'enabled'
|
|
|
@ -1,364 +0,0 @@
|
||||||
# (c) 2017, Sam Doran <sdoran@redhat.com>
|
|
||||||
|
|
||||||
# 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
# First Test
|
|
||||||
# ##############################################################################
|
|
||||||
# Test changing the state, which requires a reboot
|
|
||||||
|
|
||||||
- name: TEST 1 | Get current SELinux config file contents
|
|
||||||
set_fact:
|
|
||||||
selinux_config_original: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}"
|
|
||||||
before_test_sestatus: "{{ ansible_selinux }}"
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: "{{ item }}"
|
|
||||||
verbosity: 1
|
|
||||||
with_items:
|
|
||||||
- selinux_config_original
|
|
||||||
- before_test_sestatus
|
|
||||||
- ansible_selinux
|
|
||||||
|
|
||||||
- name: TEST 1 | Setup SELinux configuration for tests
|
|
||||||
selinux:
|
|
||||||
state: enforcing
|
|
||||||
policy: targeted
|
|
||||||
|
|
||||||
- name: TEST 1 | Disable SELinux
|
|
||||||
selinux:
|
|
||||||
state: disabled
|
|
||||||
policy: targeted
|
|
||||||
register: _disable_test1
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: _disable_test1
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 1 | Re-gather facts
|
|
||||||
setup:
|
|
||||||
|
|
||||||
- name: TEST 1 | Assert that status was changed, reboot_required is True, a warning was displayed, and SELinux is configured properly
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _disable_test1 is changed
|
|
||||||
- _disable_test1.reboot_required
|
|
||||||
- (_disable_test1.warnings | length ) >= 1
|
|
||||||
- ansible_selinux.config_mode == 'disabled'
|
|
||||||
- ansible_selinux.type == 'targeted'
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: ansible_selinux
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 1 | Disable SELinux again
|
|
||||||
selinux:
|
|
||||||
state: disabled
|
|
||||||
policy: targeted
|
|
||||||
register: _disable_test2
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: _disable_test2
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 1 | Assert that no change is reported, a warnking was dispalyed, and reboot_required is True
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _disable_test2 is not changed
|
|
||||||
- (_disable_test1.warnings | length ) >= 1
|
|
||||||
- _disable_test2.reboot_required
|
|
||||||
|
|
||||||
- name: TEST 1 | Get modified config file
|
|
||||||
set_fact:
|
|
||||||
selinux_config_after: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}"
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: selinux_config_after
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 1 | Ensure SELinux config file is properly formatted
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- selinux_config_original | length == selinux_config_after | length
|
|
||||||
- selinux_config_after[selinux_config_after.index('SELINUX=disabled')] is search("^SELINUX=\w+$")
|
|
||||||
- selinux_config_after[selinux_config_after.index('SELINUXTYPE=targeted')] is search("^SELINUXTYPE=\w+$")
|
|
||||||
|
|
||||||
- name: TEST 1 | Reset SELinux configuration for next test
|
|
||||||
selinux:
|
|
||||||
state: enforcing
|
|
||||||
policy: targeted
|
|
||||||
|
|
||||||
|
|
||||||
# Second Test
|
|
||||||
# ##############################################################################
|
|
||||||
# Test changing only the policy, which does not require a reboot
|
|
||||||
|
|
||||||
- name: TEST 2 | Make sure the policy is present
|
|
||||||
package:
|
|
||||||
name: selinux-policy-mls
|
|
||||||
state: present
|
|
||||||
|
|
||||||
- name: TEST 2 | Set SELinux policy
|
|
||||||
selinux:
|
|
||||||
state: enforcing
|
|
||||||
policy: mls
|
|
||||||
register: _state_test1
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: _state_test1
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 2 | Re-gather facts
|
|
||||||
setup:
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: ansible_selinux
|
|
||||||
tags: debug
|
|
||||||
|
|
||||||
- name: TEST 2 | Assert that status was changed, reboot_required is False, no warnings were displayed, and SELinux is configured properly
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _state_test1 is changed
|
|
||||||
- not _state_test1.reboot_required
|
|
||||||
- _state_test1.warnings is not defined
|
|
||||||
- ansible_selinux.config_mode == 'enforcing'
|
|
||||||
- ansible_selinux.type == 'mls'
|
|
||||||
|
|
||||||
- name: TEST 2 | Set SELinux policy again
|
|
||||||
selinux:
|
|
||||||
state: enforcing
|
|
||||||
policy: mls
|
|
||||||
register: _state_test2
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: _state_test2
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 2 | Assert that no change was reported, no warnings were dispalyed, and reboot_required is False
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _state_test2 is not changed
|
|
||||||
- _state_test2.warnings is not defined
|
|
||||||
- not _state_test2.reboot_required
|
|
||||||
|
|
||||||
- name: TEST 2 | Get modified config file
|
|
||||||
set_fact:
|
|
||||||
selinux_config_after: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}"
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: selinux_config_after
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 2 | Ensure SELinux config file is properly formatted
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- selinux_config_original | length == selinux_config_after | length
|
|
||||||
- selinux_config_after[selinux_config_after.index('SELINUX=enforcing')] is search("^SELINUX=\w+$")
|
|
||||||
- selinux_config_after[selinux_config_after.index('SELINUXTYPE=mls')] is search("^SELINUXTYPE=\w+$")
|
|
||||||
|
|
||||||
- name: TEST 2 | Reset SELinux configuration for next test
|
|
||||||
selinux:
|
|
||||||
state: enforcing
|
|
||||||
policy: targeted
|
|
||||||
|
|
||||||
|
|
||||||
# Third Test
|
|
||||||
# ##############################################################################
|
|
||||||
# Test changing non-existing policy
|
|
||||||
|
|
||||||
- name: TEST 3 | Set SELinux policy
|
|
||||||
selinux:
|
|
||||||
state: enforcing
|
|
||||||
policy: non-existing-selinux-policy
|
|
||||||
register: _state_test1
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: _state_test1
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 3 | Re-gather facts
|
|
||||||
setup:
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: ansible_selinux
|
|
||||||
tags: debug
|
|
||||||
|
|
||||||
- name: TEST 3 | Assert that status was not changed, the task failed, the msg contains proper information and SELinux was not changed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _state_test1 is not changed
|
|
||||||
- _state_test1 is failed
|
|
||||||
- _state_test1.msg == 'Policy non-existing-selinux-policy does not exist in /etc/selinux/'
|
|
||||||
- ansible_selinux.config_mode == 'enforcing'
|
|
||||||
- ansible_selinux.type == 'targeted'
|
|
||||||
|
|
||||||
|
|
||||||
# Fourth Test
|
|
||||||
# ##############################################################################
|
|
||||||
# Test if check mode returns correct changed values and
|
|
||||||
# doesn't make any changes
|
|
||||||
|
|
||||||
|
|
||||||
- name: TEST 4 | Set SELinux to enforcing
|
|
||||||
selinux:
|
|
||||||
state: enforcing
|
|
||||||
policy: targeted
|
|
||||||
register: _check_mode_test1
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: _check_mode_test1
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 4 | Set SELinux to enforcing in check mode
|
|
||||||
selinux:
|
|
||||||
state: enforcing
|
|
||||||
policy: targeted
|
|
||||||
register: _check_mode_test1
|
|
||||||
check_mode: yes
|
|
||||||
|
|
||||||
- name: TEST 4 | Re-gather facts
|
|
||||||
setup:
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: ansible_selinux
|
|
||||||
verbosity: 1
|
|
||||||
tags: debug
|
|
||||||
|
|
||||||
- name: TEST 4 | Assert that check mode is idempotent
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _check_mode_test1 is success
|
|
||||||
- not _check_mode_test1.reboot_required
|
|
||||||
- ansible_selinux.config_mode == 'enforcing'
|
|
||||||
- ansible_selinux.type == 'targeted'
|
|
||||||
|
|
||||||
- name: TEST 4 | Set SELinux to permissive in check mode
|
|
||||||
selinux:
|
|
||||||
state: permissive
|
|
||||||
policy: targeted
|
|
||||||
register: _check_mode_test2
|
|
||||||
check_mode: yes
|
|
||||||
|
|
||||||
- name: TEST 4 | Re-gather facts
|
|
||||||
setup:
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: ansible_selinux
|
|
||||||
verbosity: 1
|
|
||||||
tags: debug
|
|
||||||
|
|
||||||
- name: TEST 4 | Assert that check mode doesn't set state permissive and returns changed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _check_mode_test2 is changed
|
|
||||||
- not _check_mode_test2.reboot_required
|
|
||||||
- ansible_selinux.config_mode == 'enforcing'
|
|
||||||
- ansible_selinux.type == 'targeted'
|
|
||||||
|
|
||||||
- name: TEST 4 | Disable SELinux in check mode
|
|
||||||
selinux:
|
|
||||||
state: disabled
|
|
||||||
register: _check_mode_test3
|
|
||||||
check_mode: yes
|
|
||||||
|
|
||||||
- name: TEST 4 | Re-gather facts
|
|
||||||
setup:
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: ansible_selinux
|
|
||||||
verbosity: 1
|
|
||||||
tags: debug
|
|
||||||
|
|
||||||
- name: TEST 4 | Assert that check mode didn't change anything, status is changed, reboot_required is True, a warning was displayed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _check_mode_test3 is changed
|
|
||||||
- _check_mode_test3.reboot_required
|
|
||||||
- (_check_mode_test3.warnings | length ) >= 1
|
|
||||||
- ansible_selinux.config_mode == 'enforcing'
|
|
||||||
- ansible_selinux.type == 'targeted'
|
|
||||||
|
|
||||||
- name: TEST 4 | Set SELinux to permissive
|
|
||||||
selinux:
|
|
||||||
state: permissive
|
|
||||||
policy: targeted
|
|
||||||
register: _check_mode_test4
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: _check_mode_test4
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 4 | Disable SELinux in check mode
|
|
||||||
selinux:
|
|
||||||
state: disabled
|
|
||||||
register: _check_mode_test4
|
|
||||||
check_mode: yes
|
|
||||||
|
|
||||||
- name: TEST 4 | Re-gather facts
|
|
||||||
setup:
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: ansible_selinux
|
|
||||||
verbosity: 1
|
|
||||||
tags: debug
|
|
||||||
|
|
||||||
- name: TEST 4 | Assert that check mode didn't change anything, status is changed, reboot_required is True, a warning was displayed
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _check_mode_test4 is changed
|
|
||||||
- _check_mode_test4.reboot_required
|
|
||||||
- (_check_mode_test3.warnings | length ) >= 1
|
|
||||||
- ansible_selinux.config_mode == 'permissive'
|
|
||||||
- ansible_selinux.type == 'targeted'
|
|
||||||
|
|
||||||
- name: TEST 4 | Set SELinux to enforcing
|
|
||||||
selinux:
|
|
||||||
state: enforcing
|
|
||||||
policy: targeted
|
|
||||||
register: _check_mode_test5
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: _check_mode_test5
|
|
||||||
verbosity: 1
|
|
||||||
|
|
||||||
- name: TEST 4 | Disable SELinux
|
|
||||||
selinux:
|
|
||||||
state: disabled
|
|
||||||
register: _check_mode_test5
|
|
||||||
|
|
||||||
- name: TEST 4 | Disable SELinux in check mode
|
|
||||||
selinux:
|
|
||||||
state: disabled
|
|
||||||
register: _check_mode_test5
|
|
||||||
check_mode: yes
|
|
||||||
|
|
||||||
- name: TEST 4 | Re-gather facts
|
|
||||||
setup:
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: ansible_selinux
|
|
||||||
verbosity: 1
|
|
||||||
tags: debug
|
|
||||||
|
|
||||||
- name: TEST 4 | Assert that in check mode status was not changed, reboot_required is True, a warning was displayed, and SELinux is configured properly
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- _check_mode_test5 is success
|
|
||||||
- _check_mode_test5.reboot_required
|
|
||||||
- (_check_mode_test5.warnings | length ) >= 1
|
|
||||||
- ansible_selinux.config_mode == 'disabled'
|
|
||||||
- ansible_selinux.type == 'targeted'
|
|
|
@ -1,81 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
- name: create user for testing
|
|
||||||
user:
|
|
||||||
name: seuser
|
|
||||||
|
|
||||||
- name: attempt to add mapping without 'seuser'
|
|
||||||
selogin:
|
|
||||||
login: seuser
|
|
||||||
register: selogin_error
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: verify failure
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- selogin_error is failed
|
|
||||||
|
|
||||||
- name: map login to SELinux user
|
|
||||||
selogin:
|
|
||||||
login: seuser
|
|
||||||
seuser: staff_u
|
|
||||||
register: selogin_new_mapping
|
|
||||||
check_mode: "{{ item }}"
|
|
||||||
with_items:
|
|
||||||
- yes
|
|
||||||
- no
|
|
||||||
- yes
|
|
||||||
- no
|
|
||||||
|
|
||||||
- name: new mapping- verify functionality and check_mode
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- selogin_new_mapping.results[0] is changed
|
|
||||||
- selogin_new_mapping.results[1] is changed
|
|
||||||
- selogin_new_mapping.results[2] is not changed
|
|
||||||
- selogin_new_mapping.results[3] is not changed
|
|
||||||
|
|
||||||
- name: change SELinux user login mapping
|
|
||||||
selogin:
|
|
||||||
login: seuser
|
|
||||||
seuser: user_u
|
|
||||||
register: selogin_mod_mapping
|
|
||||||
check_mode: "{{ item }}"
|
|
||||||
with_items:
|
|
||||||
- yes
|
|
||||||
- no
|
|
||||||
- yes
|
|
||||||
- no
|
|
||||||
|
|
||||||
- name: changed mapping- verify functionality and check_mode
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- selogin_mod_mapping.results[0] is changed
|
|
||||||
- selogin_mod_mapping.results[1] is changed
|
|
||||||
- selogin_mod_mapping.results[2] is not changed
|
|
||||||
- selogin_mod_mapping.results[3] is not changed
|
|
||||||
|
|
||||||
- name: remove SELinux user mapping
|
|
||||||
selogin:
|
|
||||||
login: seuser
|
|
||||||
state: absent
|
|
||||||
register: selogin_del_mapping
|
|
||||||
check_mode: "{{ item }}"
|
|
||||||
with_items:
|
|
||||||
- yes
|
|
||||||
- no
|
|
||||||
- yes
|
|
||||||
- no
|
|
||||||
|
|
||||||
- name: delete mapping- verify functionality and check_mode
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- selogin_del_mapping.results[0] is changed
|
|
||||||
- selogin_del_mapping.results[1] is changed
|
|
||||||
- selogin_del_mapping.results[2] is not changed
|
|
||||||
- selogin_del_mapping.results[3] is not changed
|
|
||||||
|
|
||||||
- name: remove test user
|
|
||||||
user:
|
|
||||||
name: seuser
|
|
||||||
state: absent
|
|
|
@ -1,2 +0,0 @@
|
||||||
shippable/windows/incidental
|
|
||||||
windows
|
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
test_win_hosts_cname: testhost
|
|
||||||
test_win_hosts_ip: 192.168.168.1
|
|
||||||
|
|
||||||
test_win_hosts_aliases_set:
|
|
||||||
- alias1
|
|
||||||
- alias2
|
|
||||||
- alias3
|
|
||||||
- alias4
|
|
||||||
|
|
||||||
test_win_hosts_aliases_remove:
|
|
||||||
- alias3
|
|
||||||
- alias4
|
|
|
@ -1,2 +0,0 @@
|
||||||
dependencies:
|
|
||||||
- setup_remote_tmp_dir
|
|
|
@ -1,17 +0,0 @@
|
||||||
---
|
|
||||||
- name: take a copy of the original hosts file
|
|
||||||
win_copy:
|
|
||||||
src: C:\Windows\System32\drivers\etc\hosts
|
|
||||||
dest: '{{ remote_tmp_dir }}\hosts'
|
|
||||||
remote_src: yes
|
|
||||||
|
|
||||||
- block:
|
|
||||||
- name: run tests
|
|
||||||
include_tasks: tests.yml
|
|
||||||
|
|
||||||
always:
|
|
||||||
- name: restore hosts file
|
|
||||||
win_copy:
|
|
||||||
src: '{{ remote_tmp_dir }}\hosts'
|
|
||||||
dest: C:\Windows\System32\drivers\etc\hosts
|
|
||||||
remote_src: yes
|
|
|
@ -1,189 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
- name: add a simple host with address
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
register: add_ip
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "add_ip.changed == true"
|
|
||||||
|
|
||||||
- name: get actual dns result
|
|
||||||
win_shell: "try{ [array]$t = [Net.DNS]::GetHostEntry('{{ test_win_hosts_cname }}') } catch { return 'false' } if ($t[0].HostName -eq '{{ test_win_hosts_cname }}' -and $t[0].AddressList[0].toString() -eq '{{ test_win_hosts_ip }}'){ return 'true' } else { return 'false' }"
|
|
||||||
register: add_ip_actual
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "add_ip_actual.stdout_lines[0]|lower == 'true'"
|
|
||||||
|
|
||||||
- name: add a simple host with ipv4 address (idempotent)
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
register: add_ip
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "add_ip.changed == false"
|
|
||||||
|
|
||||||
- name: remove simple host
|
|
||||||
win_hosts:
|
|
||||||
state: absent
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
register: remove_ip
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "remove_ip.changed == true"
|
|
||||||
|
|
||||||
- name: get actual dns result
|
|
||||||
win_shell: "try{ [array]$t = [Net.DNS]::GetHostEntry('{{ test_win_hosts_cname}}') } catch { return 'false' } if ($t[0].HostName -eq '{{ test_win_hosts_cname }}' -and $t[0].AddressList[0].toString() -eq '{{ test_win_hosts_ip }}'){ return 'true' } else { return 'false' }"
|
|
||||||
register: remove_ip_actual
|
|
||||||
failed_when: "remove_ip_actual.rc == 0"
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "remove_ip_actual.stdout_lines[0]|lower == 'false'"
|
|
||||||
|
|
||||||
- name: remove simple host (idempotent)
|
|
||||||
win_hosts:
|
|
||||||
state: absent
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
register: remove_ip
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "remove_ip.changed == false"
|
|
||||||
|
|
||||||
- name: add host and set aliases
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
aliases: "{{ test_win_hosts_aliases_set | union(test_win_hosts_aliases_remove) }}"
|
|
||||||
action: set
|
|
||||||
register: set_aliases
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "set_aliases.changed == true"
|
|
||||||
|
|
||||||
- name: get actual dns result for host
|
|
||||||
win_shell: "try{ [array]$t = [Net.DNS]::GetHostEntry('{{ test_win_hosts_cname }}') } catch { return 'false' } if ($t[0].HostName -eq '{{ test_win_hosts_cname }}' -and $t[0].AddressList[0].toString() -eq '{{ test_win_hosts_ip }}'){ return 'true' } else { return 'false' }"
|
|
||||||
register: set_aliases_actual_host
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "set_aliases_actual_host.stdout_lines[0]|lower == 'true'"
|
|
||||||
|
|
||||||
- name: get actual dns results for aliases
|
|
||||||
win_shell: "try{ [array]$t = [Net.DNS]::GetHostEntry('{{ item }}') } catch { return 'false' } if ($t[0].HostName -eq '{{ test_win_hosts_cname }}' -and $t[0].AddressList[0].toString() -eq '{{ test_win_hosts_ip }}'){ return 'true' } else { return 'false' }"
|
|
||||||
register: set_aliases_actual
|
|
||||||
with_items: "{{ test_win_hosts_aliases_set | union(test_win_hosts_aliases_remove) }}"
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "item.stdout_lines[0]|lower == 'true'"
|
|
||||||
with_items: "{{ set_aliases_actual.results }}"
|
|
||||||
|
|
||||||
- name: add host and set aliases (idempotent)
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
aliases: "{{ test_win_hosts_aliases_set | union(test_win_hosts_aliases_remove) }}"
|
|
||||||
action: set
|
|
||||||
register: set_aliases
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "set_aliases.changed == false"
|
|
||||||
|
|
||||||
- name: remove aliases from the list
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
aliases: "{{ test_win_hosts_aliases_remove }}"
|
|
||||||
action: remove
|
|
||||||
register: remove_aliases
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "remove_aliases.changed == true"
|
|
||||||
|
|
||||||
- name: get actual dns result for removed aliases
|
|
||||||
win_shell: "try{ [array]$t = [Net.DNS]::GetHostEntry('{{ item }}') } catch { return 'false' } if ($t[0].HostName -eq '{{ test_win_hosts_cname }}' -and $t[0].AddressList[0].toString() -eq '{{ test_win_hosts_ip }}'){ return 'true' } else { return 'false' }"
|
|
||||||
register: remove_aliases_removed_actual
|
|
||||||
failed_when: "remove_aliases_removed_actual.rc == 0"
|
|
||||||
with_items: "{{ test_win_hosts_aliases_remove }}"
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "item.stdout_lines[0]|lower == 'false'"
|
|
||||||
with_items: "{{ remove_aliases_removed_actual.results }}"
|
|
||||||
|
|
||||||
- name: get actual dns result for remaining aliases
|
|
||||||
win_shell: "try{ [array]$t = [Net.DNS]::GetHostEntry('{{ item }}') } catch { return 'false' } if ($t[0].HostName -eq '{{ test_win_hosts_cname }}' -and $t[0].AddressList[0].toString() -eq '{{ test_win_hosts_ip }}'){ return 'true' } else { return 'false' }"
|
|
||||||
register: remove_aliases_remain_actual
|
|
||||||
with_items: "{{ test_win_hosts_aliases_set | difference(test_win_hosts_aliases_remove) }}"
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "item.stdout_lines[0]|lower == 'true'"
|
|
||||||
with_items: "{{ remove_aliases_remain_actual.results }}"
|
|
||||||
|
|
||||||
- name: remove aliases from the list (idempotent)
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
aliases: "{{ test_win_hosts_aliases_remove }}"
|
|
||||||
action: remove
|
|
||||||
register: remove_aliases
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "remove_aliases.changed == false"
|
|
||||||
|
|
||||||
- name: add aliases back
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
aliases: "{{ test_win_hosts_aliases_remove }}"
|
|
||||||
action: add
|
|
||||||
register: add_aliases
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "add_aliases.changed == true"
|
|
||||||
|
|
||||||
- name: get actual dns results for aliases
|
|
||||||
win_shell: "try{ [array]$t = [Net.DNS]::GetHostEntry('{{ item }}') } catch { return 'false' } if ($t[0].HostName -eq '{{ test_win_hosts_cname }}' -and $t[0].AddressList[0].toString() -eq '{{ test_win_hosts_ip }}'){ return 'true' } else { return 'false' }"
|
|
||||||
register: add_aliases_actual
|
|
||||||
with_items: "{{ test_win_hosts_aliases_set | union(test_win_hosts_aliases_remove) }}"
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "item.stdout_lines[0]|lower == 'true'"
|
|
||||||
with_items: "{{ add_aliases_actual.results }}"
|
|
||||||
|
|
||||||
- name: add aliases back (idempotent)
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
ip_address: "{{ test_win_hosts_ip }}"
|
|
||||||
canonical_name: "{{ test_win_hosts_cname }}"
|
|
||||||
aliases: "{{ test_win_hosts_aliases_remove }}"
|
|
||||||
action: add
|
|
||||||
register: add_aliases
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that:
|
|
||||||
- "add_aliases.changed == false"
|
|
|
@ -1,208 +0,0 @@
|
||||||
# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
|
||||||
#
|
|
||||||
# (c) 2013, Maykel Moya <mmoya@speedyrails.com>
|
|
||||||
# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com>
|
|
||||||
# Copyright (c) 2017 Ansible Project
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
author: Maykel Moya <mmoya@speedyrails.com>
|
|
||||||
connection: chroot
|
|
||||||
short_description: Interact with local chroot
|
|
||||||
description:
|
|
||||||
- Run commands or put/fetch files to an existing chroot on the Ansible controller.
|
|
||||||
version_added: "1.1"
|
|
||||||
options:
|
|
||||||
remote_addr:
|
|
||||||
description:
|
|
||||||
- The path of the chroot you want to access.
|
|
||||||
default: inventory_hostname
|
|
||||||
vars:
|
|
||||||
- name: ansible_host
|
|
||||||
executable:
|
|
||||||
description:
|
|
||||||
- User specified executable shell
|
|
||||||
ini:
|
|
||||||
- section: defaults
|
|
||||||
key: executable
|
|
||||||
env:
|
|
||||||
- name: ANSIBLE_EXECUTABLE
|
|
||||||
vars:
|
|
||||||
- name: ansible_executable
|
|
||||||
default: /bin/sh
|
|
||||||
chroot_exe:
|
|
||||||
version_added: '2.8'
|
|
||||||
description:
|
|
||||||
- User specified chroot binary
|
|
||||||
ini:
|
|
||||||
- section: chroot_connection
|
|
||||||
key: exe
|
|
||||||
env:
|
|
||||||
- name: ANSIBLE_CHROOT_EXE
|
|
||||||
vars:
|
|
||||||
- name: ansible_chroot_exe
|
|
||||||
default: chroot
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import subprocess
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
from ansible.errors import AnsibleError
|
|
||||||
from ansible.module_utils.basic import is_executable
|
|
||||||
from ansible.module_utils.common.process import get_bin_path
|
|
||||||
from ansible.module_utils.six.moves import shlex_quote
|
|
||||||
from ansible.module_utils._text import to_bytes, to_native
|
|
||||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
|
||||||
from ansible.utils.display import Display
|
|
||||||
|
|
||||||
display = Display()
|
|
||||||
|
|
||||||
|
|
||||||
class Connection(ConnectionBase):
|
|
||||||
''' Local chroot based connections '''
|
|
||||||
|
|
||||||
transport = 'chroot'
|
|
||||||
has_pipelining = True
|
|
||||||
# su currently has an undiagnosed issue with calculating the file
|
|
||||||
# checksums (so copy, for instance, doesn't work right)
|
|
||||||
# Have to look into that before re-enabling this
|
|
||||||
has_tty = False
|
|
||||||
|
|
||||||
default_user = 'root'
|
|
||||||
|
|
||||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
|
||||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
|
||||||
|
|
||||||
self.chroot = self._play_context.remote_addr
|
|
||||||
|
|
||||||
if os.geteuid() != 0:
|
|
||||||
raise AnsibleError("chroot connection requires running as root")
|
|
||||||
|
|
||||||
# we're running as root on the local system so do some
|
|
||||||
# trivial checks for ensuring 'host' is actually a chroot'able dir
|
|
||||||
if not os.path.isdir(self.chroot):
|
|
||||||
raise AnsibleError("%s is not a directory" % self.chroot)
|
|
||||||
|
|
||||||
chrootsh = os.path.join(self.chroot, 'bin/sh')
|
|
||||||
# Want to check for a usable bourne shell inside the chroot.
|
|
||||||
# is_executable() == True is sufficient. For symlinks it
|
|
||||||
# gets really complicated really fast. So we punt on finding that
|
|
||||||
# out. As long as it's a symlink we assume that it will work
|
|
||||||
if not (is_executable(chrootsh) or (os.path.lexists(chrootsh) and os.path.islink(chrootsh))):
|
|
||||||
raise AnsibleError("%s does not look like a chrootable dir (/bin/sh missing)" % self.chroot)
|
|
||||||
|
|
||||||
def _connect(self):
|
|
||||||
''' connect to the chroot '''
|
|
||||||
if os.path.isabs(self.get_option('chroot_exe')):
|
|
||||||
self.chroot_cmd = self.get_option('chroot_exe')
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
self.chroot_cmd = get_bin_path(self.get_option('chroot_exe'))
|
|
||||||
except ValueError as e:
|
|
||||||
raise AnsibleError(to_native(e))
|
|
||||||
|
|
||||||
super(Connection, self)._connect()
|
|
||||||
if not self._connected:
|
|
||||||
display.vvv("THIS IS A LOCAL CHROOT DIR", host=self.chroot)
|
|
||||||
self._connected = True
|
|
||||||
|
|
||||||
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
|
|
||||||
''' run a command on the chroot. This is only needed for implementing
|
|
||||||
put_file() get_file() so that we don't have to read the whole file
|
|
||||||
into memory.
|
|
||||||
|
|
||||||
compared to exec_command() it looses some niceties like being able to
|
|
||||||
return the process's exit code immediately.
|
|
||||||
'''
|
|
||||||
executable = self.get_option('executable')
|
|
||||||
local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd]
|
|
||||||
|
|
||||||
display.vvv("EXEC %s" % (local_cmd), host=self.chroot)
|
|
||||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
|
||||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
|
|
||||||
return p
|
|
||||||
|
|
||||||
def exec_command(self, cmd, in_data=None, sudoable=False):
|
|
||||||
''' run a command on the chroot '''
|
|
||||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
|
||||||
|
|
||||||
p = self._buffered_exec_command(cmd)
|
|
||||||
|
|
||||||
stdout, stderr = p.communicate(in_data)
|
|
||||||
return (p.returncode, stdout, stderr)
|
|
||||||
|
|
||||||
def _prefix_login_path(self, remote_path):
|
|
||||||
''' Make sure that we put files into a standard path
|
|
||||||
|
|
||||||
If a path is relative, then we need to choose where to put it.
|
|
||||||
ssh chooses $HOME but we aren't guaranteed that a home dir will
|
|
||||||
exist in any given chroot. So for now we're choosing "/" instead.
|
|
||||||
This also happens to be the former default.
|
|
||||||
|
|
||||||
Can revisit using $HOME instead if it's a problem
|
|
||||||
'''
|
|
||||||
if not remote_path.startswith(os.path.sep):
|
|
||||||
remote_path = os.path.join(os.path.sep, remote_path)
|
|
||||||
return os.path.normpath(remote_path)
|
|
||||||
|
|
||||||
def put_file(self, in_path, out_path):
|
|
||||||
''' transfer a file from local to chroot '''
|
|
||||||
super(Connection, self).put_file(in_path, out_path)
|
|
||||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.chroot)
|
|
||||||
|
|
||||||
out_path = shlex_quote(self._prefix_login_path(out_path))
|
|
||||||
try:
|
|
||||||
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
|
|
||||||
if not os.fstat(in_file.fileno()).st_size:
|
|
||||||
count = ' count=0'
|
|
||||||
else:
|
|
||||||
count = ''
|
|
||||||
try:
|
|
||||||
p = self._buffered_exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), stdin=in_file)
|
|
||||||
except OSError:
|
|
||||||
raise AnsibleError("chroot connection requires dd command in the chroot")
|
|
||||||
try:
|
|
||||||
stdout, stderr = p.communicate()
|
|
||||||
except Exception:
|
|
||||||
traceback.print_exc()
|
|
||||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
|
||||||
if p.returncode != 0:
|
|
||||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
|
||||||
except IOError:
|
|
||||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
|
||||||
|
|
||||||
def fetch_file(self, in_path, out_path):
|
|
||||||
''' fetch a file from chroot to local '''
|
|
||||||
super(Connection, self).fetch_file(in_path, out_path)
|
|
||||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.chroot)
|
|
||||||
|
|
||||||
in_path = shlex_quote(self._prefix_login_path(in_path))
|
|
||||||
try:
|
|
||||||
p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE))
|
|
||||||
except OSError:
|
|
||||||
raise AnsibleError("chroot connection requires dd command in the chroot")
|
|
||||||
|
|
||||||
with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb+') as out_file:
|
|
||||||
try:
|
|
||||||
chunk = p.stdout.read(BUFSIZE)
|
|
||||||
while chunk:
|
|
||||||
out_file.write(chunk)
|
|
||||||
chunk = p.stdout.read(BUFSIZE)
|
|
||||||
except Exception:
|
|
||||||
traceback.print_exc()
|
|
||||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
|
||||||
stdout, stderr = p.communicate()
|
|
||||||
if p.returncode != 0:
|
|
||||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
''' terminate the connection; nothing to do here '''
|
|
||||||
super(Connection, self).close()
|
|
||||||
self._connected = False
|
|
|
@ -1,302 +0,0 @@
|
||||||
# (c) 2015, Jonathan Davila <jonathan(at)davila.io>
|
|
||||||
# (c) 2017 Ansible Project
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
lookup: hashi_vault
|
|
||||||
author: Jonathan Davila <jdavila(at)ansible.com>
|
|
||||||
version_added: "2.0"
|
|
||||||
short_description: retrieve secrets from HashiCorp's vault
|
|
||||||
requirements:
|
|
||||||
- hvac (python library)
|
|
||||||
description:
|
|
||||||
- retrieve secrets from HashiCorp's vault
|
|
||||||
notes:
|
|
||||||
- Due to a current limitation in the HVAC library there won't necessarily be an error if a bad endpoint is specified.
|
|
||||||
- As of Ansible 2.10, only the latest secret is returned when specifying a KV v2 path.
|
|
||||||
options:
|
|
||||||
secret:
|
|
||||||
description: query you are making.
|
|
||||||
required: True
|
|
||||||
token:
|
|
||||||
description: vault token.
|
|
||||||
env:
|
|
||||||
- name: VAULT_TOKEN
|
|
||||||
url:
|
|
||||||
description: URL to vault service.
|
|
||||||
env:
|
|
||||||
- name: VAULT_ADDR
|
|
||||||
default: 'http://127.0.0.1:8200'
|
|
||||||
username:
|
|
||||||
description: Authentication user name.
|
|
||||||
password:
|
|
||||||
description: Authentication password.
|
|
||||||
role_id:
|
|
||||||
description: Role id for a vault AppRole auth.
|
|
||||||
env:
|
|
||||||
- name: VAULT_ROLE_ID
|
|
||||||
secret_id:
|
|
||||||
description: Secret id for a vault AppRole auth.
|
|
||||||
env:
|
|
||||||
- name: VAULT_SECRET_ID
|
|
||||||
auth_method:
|
|
||||||
description:
|
|
||||||
- Authentication method to be used.
|
|
||||||
- C(userpass) is added in version 2.8.
|
|
||||||
env:
|
|
||||||
- name: VAULT_AUTH_METHOD
|
|
||||||
choices:
|
|
||||||
- userpass
|
|
||||||
- ldap
|
|
||||||
- approle
|
|
||||||
mount_point:
|
|
||||||
description: vault mount point, only required if you have a custom mount point.
|
|
||||||
default: ldap
|
|
||||||
ca_cert:
|
|
||||||
description: path to certificate to use for authentication.
|
|
||||||
aliases: [ cacert ]
|
|
||||||
validate_certs:
|
|
||||||
description: controls verification and validation of SSL certificates, mostly you only want to turn off with self signed ones.
|
|
||||||
type: boolean
|
|
||||||
default: True
|
|
||||||
namespace:
|
|
||||||
version_added: "2.8"
|
|
||||||
description: namespace where secrets reside. requires HVAC 0.7.0+ and Vault 0.11+.
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
- debug:
|
|
||||||
msg: "{{ lookup('hashi_vault', 'secret=secret/hello:value token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200')}}"
|
|
||||||
|
|
||||||
- name: Return all secrets from a path
|
|
||||||
debug:
|
|
||||||
msg: "{{ lookup('hashi_vault', 'secret=secret/hello token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200')}}"
|
|
||||||
|
|
||||||
- name: Vault that requires authentication via LDAP
|
|
||||||
debug:
|
|
||||||
msg: "{{ lookup('hashi_vault', 'secret=secret/hello:value auth_method=ldap mount_point=ldap username=myuser password=mypas url=http://myvault:8200')}}"
|
|
||||||
|
|
||||||
- name: Vault that requires authentication via username and password
|
|
||||||
debug:
|
|
||||||
msg: "{{ lookup('hashi_vault', 'secret=secret/hello:value auth_method=userpass username=myuser password=mypas url=http://myvault:8200')}}"
|
|
||||||
|
|
||||||
- name: Using an ssl vault
|
|
||||||
debug:
|
|
||||||
msg: "{{ lookup('hashi_vault', 'secret=secret/hola:value token=c975b780-d1be-8016-866b-01d0f9b688a5 url=https://myvault:8200 validate_certs=False')}}"
|
|
||||||
|
|
||||||
- name: using certificate auth
|
|
||||||
debug:
|
|
||||||
msg: "{{ lookup('hashi_vault', 'secret=secret/hi:value token=xxxx-xxx-xxx url=https://myvault:8200 validate_certs=True cacert=/cacert/path/ca.pem')}}"
|
|
||||||
|
|
||||||
- name: authenticate with a Vault app role
|
|
||||||
debug:
|
|
||||||
msg: "{{ lookup('hashi_vault', 'secret=secret/hello:value auth_method=approle role_id=myroleid secret_id=mysecretid url=http://myvault:8200')}}"
|
|
||||||
|
|
||||||
- name: Return all secrets from a path in a namespace
|
|
||||||
debug:
|
|
||||||
msg: "{{ lookup('hashi_vault', 'secret=secret/hello token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200 namespace=teama/admins')}}"
|
|
||||||
|
|
||||||
# When using KV v2 the PATH should include "data" between the secret engine mount and path (e.g. "secret/data/:path")
|
|
||||||
# see: https://www.vaultproject.io/api/secret/kv/kv-v2.html#read-secret-version
|
|
||||||
- name: Return latest KV v2 secret from path
|
|
||||||
debug:
|
|
||||||
msg: "{{ lookup('hashi_vault', 'secret=secret/data/hello token=my_vault_token url=http://myvault_url:8200') }}"
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
_raw:
|
|
||||||
description:
|
|
||||||
- secrets(s) requested
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from ansible.errors import AnsibleError
|
|
||||||
from ansible.module_utils.parsing.convert_bool import boolean
|
|
||||||
from ansible.plugins.lookup import LookupBase
|
|
||||||
|
|
||||||
HAS_HVAC = False
|
|
||||||
try:
|
|
||||||
import hvac
|
|
||||||
HAS_HVAC = True
|
|
||||||
except ImportError:
|
|
||||||
HAS_HVAC = False
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
|
||||||
|
|
||||||
self.url = kwargs.get('url', ANSIBLE_HASHI_VAULT_ADDR)
|
|
||||||
self.namespace = kwargs.get('namespace', None)
|
|
||||||
self.avail_auth_method = ['approle', 'userpass', 'ldap']
|
|
||||||
|
|
||||||
# split secret arg, which has format 'secret/hello:value' into secret='secret/hello' and secret_field='value'
|
|
||||||
s = kwargs.get('secret')
|
|
||||||
if s is None:
|
|
||||||
raise AnsibleError("No secret specified for hashi_vault lookup")
|
|
||||||
|
|
||||||
s_f = s.rsplit(':', 1)
|
|
||||||
self.secret = s_f[0]
|
|
||||||
if len(s_f) >= 2:
|
|
||||||
self.secret_field = s_f[1]
|
|
||||||
else:
|
|
||||||
self.secret_field = ''
|
|
||||||
|
|
||||||
self.verify = self.boolean_or_cacert(kwargs.get('validate_certs', True), kwargs.get('cacert', ''))
|
|
||||||
|
|
||||||
# If a particular backend is asked for (and its method exists) we call it, otherwise drop through to using
|
|
||||||
# token auth. This means if a particular auth backend is requested and a token is also given, then we
|
|
||||||
# ignore the token and attempt authentication against the specified backend.
|
|
||||||
#
|
|
||||||
# to enable a new auth backend, simply add a new 'def auth_<type>' method below.
|
|
||||||
#
|
|
||||||
self.auth_method = kwargs.get('auth_method', os.environ.get('VAULT_AUTH_METHOD'))
|
|
||||||
self.verify = self.boolean_or_cacert(kwargs.get('validate_certs', True), kwargs.get('cacert', ''))
|
|
||||||
if self.auth_method and self.auth_method != 'token':
|
|
||||||
try:
|
|
||||||
if self.namespace is not None:
|
|
||||||
self.client = hvac.Client(url=self.url, verify=self.verify, namespace=self.namespace)
|
|
||||||
else:
|
|
||||||
self.client = hvac.Client(url=self.url, verify=self.verify)
|
|
||||||
# prefixing with auth_ to limit which methods can be accessed
|
|
||||||
getattr(self, 'auth_' + self.auth_method)(**kwargs)
|
|
||||||
except AttributeError:
|
|
||||||
raise AnsibleError("Authentication method '%s' not supported."
|
|
||||||
" Available options are %r" % (self.auth_method, self.avail_auth_method))
|
|
||||||
else:
|
|
||||||
self.token = kwargs.get('token', os.environ.get('VAULT_TOKEN', None))
|
|
||||||
if self.token is None and os.environ.get('HOME'):
|
|
||||||
token_filename = os.path.join(
|
|
||||||
os.environ.get('HOME'),
|
|
||||||
'.vault-token'
|
|
||||||
)
|
|
||||||
if os.path.exists(token_filename):
|
|
||||||
with open(token_filename) as token_file:
|
|
||||||
self.token = token_file.read().strip()
|
|
||||||
|
|
||||||
if self.token is None:
|
|
||||||
raise AnsibleError("No Vault Token specified")
|
|
||||||
|
|
||||||
if self.namespace is not None:
|
|
||||||
self.client = hvac.Client(url=self.url, token=self.token, verify=self.verify, namespace=self.namespace)
|
|
||||||
else:
|
|
||||||
self.client = hvac.Client(url=self.url, token=self.token, verify=self.verify)
|
|
||||||
|
|
||||||
if not self.client.is_authenticated():
|
|
||||||
raise AnsibleError("Invalid Hashicorp Vault Token Specified for hashi_vault lookup")
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
data = self.client.read(self.secret)
|
|
||||||
|
|
||||||
# Check response for KV v2 fields and flatten nested secret data.
|
|
||||||
#
|
|
||||||
# https://vaultproject.io/api/secret/kv/kv-v2.html#sample-response-1
|
|
||||||
try:
|
|
||||||
# sentinel field checks
|
|
||||||
check_dd = data['data']['data']
|
|
||||||
check_md = data['data']['metadata']
|
|
||||||
# unwrap nested data
|
|
||||||
data = data['data']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if data is None:
|
|
||||||
raise AnsibleError("The secret %s doesn't seem to exist for hashi_vault lookup" % self.secret)
|
|
||||||
|
|
||||||
if self.secret_field == '':
|
|
||||||
return data['data']
|
|
||||||
|
|
||||||
if self.secret_field not in data['data']:
|
|
||||||
raise AnsibleError("The secret %s does not contain the field '%s'. for hashi_vault lookup" % (self.secret, self.secret_field))
|
|
||||||
|
|
||||||
return data['data'][self.secret_field]
|
|
||||||
|
|
||||||
def check_params(self, **kwargs):
|
|
||||||
username = kwargs.get('username')
|
|
||||||
if username is None:
|
|
||||||
raise AnsibleError("Authentication method %s requires a username" % self.auth_method)
|
|
||||||
|
|
||||||
password = kwargs.get('password')
|
|
||||||
if password is None:
|
|
||||||
raise AnsibleError("Authentication method %s requires a password" % self.auth_method)
|
|
||||||
|
|
||||||
mount_point = kwargs.get('mount_point')
|
|
||||||
|
|
||||||
return username, password, mount_point
|
|
||||||
|
|
||||||
def auth_userpass(self, **kwargs):
|
|
||||||
username, password, mount_point = self.check_params(**kwargs)
|
|
||||||
if mount_point is None:
|
|
||||||
mount_point = 'userpass'
|
|
||||||
|
|
||||||
self.client.auth_userpass(username, password, mount_point=mount_point)
|
|
||||||
|
|
||||||
def auth_ldap(self, **kwargs):
|
|
||||||
username, password, mount_point = self.check_params(**kwargs)
|
|
||||||
if mount_point is None:
|
|
||||||
mount_point = 'ldap'
|
|
||||||
|
|
||||||
self.client.auth.ldap.login(username, password, mount_point=mount_point)
|
|
||||||
|
|
||||||
def boolean_or_cacert(self, validate_certs, cacert):
|
|
||||||
validate_certs = boolean(validate_certs, strict=False)
|
|
||||||
'''' return a bool or cacert '''
|
|
||||||
if validate_certs is True:
|
|
||||||
if cacert != '':
|
|
||||||
return cacert
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def auth_approle(self, **kwargs):
|
|
||||||
role_id = kwargs.get('role_id', os.environ.get('VAULT_ROLE_ID', None))
|
|
||||||
if role_id is None:
|
|
||||||
raise AnsibleError("Authentication method app role requires a role_id")
|
|
||||||
|
|
||||||
secret_id = kwargs.get('secret_id', os.environ.get('VAULT_SECRET_ID', None))
|
|
||||||
if secret_id is None:
|
|
||||||
raise AnsibleError("Authentication method app role requires a secret_id")
|
|
||||||
|
|
||||||
self.client.auth_approle(role_id, secret_id)
|
|
||||||
|
|
||||||
|
|
||||||
class LookupModule(LookupBase):
|
|
||||||
def run(self, terms, variables=None, **kwargs):
|
|
||||||
if not HAS_HVAC:
|
|
||||||
raise AnsibleError("Please pip install hvac to use the hashi_vault lookup module.")
|
|
||||||
|
|
||||||
vault_args = terms[0].split()
|
|
||||||
vault_dict = {}
|
|
||||||
ret = []
|
|
||||||
|
|
||||||
for param in vault_args:
|
|
||||||
try:
|
|
||||||
key, value = param.split('=')
|
|
||||||
except ValueError:
|
|
||||||
raise AnsibleError("hashi_vault lookup plugin needs key=value pairs, but received %s" % terms)
|
|
||||||
vault_dict[key] = value
|
|
||||||
|
|
||||||
if 'ca_cert' in vault_dict.keys():
|
|
||||||
vault_dict['cacert'] = vault_dict['ca_cert']
|
|
||||||
vault_dict.pop('ca_cert', None)
|
|
||||||
|
|
||||||
vault_conn = HashiVault(**vault_dict)
|
|
||||||
|
|
||||||
for term in terms:
|
|
||||||
key = term.split()[0]
|
|
||||||
value = vault_conn.get()
|
|
||||||
ret.append(value)
|
|
||||||
|
|
||||||
return ret
|
|
|
@ -1,266 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2012, Derek Carter<goozbach@friocorte.com>
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'core'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = r'''
|
|
||||||
---
|
|
||||||
module: selinux
|
|
||||||
short_description: Change policy and state of SELinux
|
|
||||||
description:
|
|
||||||
- Configures the SELinux mode and policy.
|
|
||||||
- A reboot may be required after usage.
|
|
||||||
- Ansible will not issue this reboot but will let you know when it is required.
|
|
||||||
version_added: "0.7"
|
|
||||||
options:
|
|
||||||
policy:
|
|
||||||
description:
|
|
||||||
- The name of the SELinux policy to use (e.g. C(targeted)) will be required if state is not C(disabled).
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- The SELinux mode.
|
|
||||||
required: true
|
|
||||||
choices: [ disabled, enforcing, permissive ]
|
|
||||||
configfile:
|
|
||||||
description:
|
|
||||||
- The path to the SELinux configuration file, if non-standard.
|
|
||||||
default: /etc/selinux/config
|
|
||||||
aliases: [ conf, file ]
|
|
||||||
requirements: [ libselinux-python ]
|
|
||||||
author:
|
|
||||||
- Derek Carter (@goozbach) <goozbach@friocorte.com>
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = r'''
|
|
||||||
- name: Enable SELinux
|
|
||||||
selinux:
|
|
||||||
policy: targeted
|
|
||||||
state: enforcing
|
|
||||||
|
|
||||||
- name: Put SELinux in permissive mode, logging actions that would be blocked.
|
|
||||||
selinux:
|
|
||||||
policy: targeted
|
|
||||||
state: permissive
|
|
||||||
|
|
||||||
- name: Disable SELinux
|
|
||||||
selinux:
|
|
||||||
state: disabled
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = r'''
|
|
||||||
msg:
|
|
||||||
description: Messages that describe changes that were made.
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
sample: Config SELinux state changed from 'disabled' to 'permissive'
|
|
||||||
configfile:
|
|
||||||
description: Path to SELinux configuration file.
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
sample: /etc/selinux/config
|
|
||||||
policy:
|
|
||||||
description: Name of the SELinux policy.
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
sample: targeted
|
|
||||||
state:
|
|
||||||
description: SELinux mode.
|
|
||||||
returned: always
|
|
||||||
type: str
|
|
||||||
sample: enforcing
|
|
||||||
reboot_required:
|
|
||||||
description: Whether or not an reboot is required for the changes to take effect.
|
|
||||||
returned: always
|
|
||||||
type: bool
|
|
||||||
sample: true
|
|
||||||
'''
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import tempfile
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
SELINUX_IMP_ERR = None
|
|
||||||
try:
|
|
||||||
import selinux
|
|
||||||
HAS_SELINUX = True
|
|
||||||
except ImportError:
|
|
||||||
SELINUX_IMP_ERR = traceback.format_exc()
|
|
||||||
HAS_SELINUX = False
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
|
||||||
from ansible.module_utils.facts.utils import get_file_lines
|
|
||||||
|
|
||||||
|
|
||||||
# getter subroutines
|
|
||||||
def get_config_state(configfile):
|
|
||||||
lines = get_file_lines(configfile, strip=False)
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
stateline = re.match(r'^SELINUX=.*$', line)
|
|
||||||
if stateline:
|
|
||||||
return line.split('=')[1].strip()
|
|
||||||
|
|
||||||
|
|
||||||
def get_config_policy(configfile):
|
|
||||||
lines = get_file_lines(configfile, strip=False)
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
stateline = re.match(r'^SELINUXTYPE=.*$', line)
|
|
||||||
if stateline:
|
|
||||||
return line.split('=')[1].strip()
|
|
||||||
|
|
||||||
|
|
||||||
# setter subroutines
|
|
||||||
def set_config_state(module, state, configfile):
|
|
||||||
# SELINUX=permissive
|
|
||||||
# edit config file with state value
|
|
||||||
stateline = 'SELINUX=%s' % state
|
|
||||||
lines = get_file_lines(configfile, strip=False)
|
|
||||||
|
|
||||||
tmpfd, tmpfile = tempfile.mkstemp()
|
|
||||||
|
|
||||||
with open(tmpfile, "w") as write_file:
|
|
||||||
for line in lines:
|
|
||||||
write_file.write(re.sub(r'^SELINUX=.*', stateline, line) + '\n')
|
|
||||||
|
|
||||||
module.atomic_move(tmpfile, configfile)
|
|
||||||
|
|
||||||
|
|
||||||
def set_state(module, state):
|
|
||||||
if state == 'enforcing':
|
|
||||||
selinux.security_setenforce(1)
|
|
||||||
elif state == 'permissive':
|
|
||||||
selinux.security_setenforce(0)
|
|
||||||
elif state == 'disabled':
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
msg = 'trying to set invalid runtime state %s' % state
|
|
||||||
module.fail_json(msg=msg)
|
|
||||||
|
|
||||||
|
|
||||||
def set_config_policy(module, policy, configfile):
|
|
||||||
if not os.path.exists('/etc/selinux/%s/policy' % policy):
|
|
||||||
module.fail_json(msg='Policy %s does not exist in /etc/selinux/' % policy)
|
|
||||||
|
|
||||||
# edit config file with state value
|
|
||||||
# SELINUXTYPE=targeted
|
|
||||||
policyline = 'SELINUXTYPE=%s' % policy
|
|
||||||
lines = get_file_lines(configfile, strip=False)
|
|
||||||
|
|
||||||
tmpfd, tmpfile = tempfile.mkstemp()
|
|
||||||
|
|
||||||
with open(tmpfile, "w") as write_file:
|
|
||||||
for line in lines:
|
|
||||||
write_file.write(re.sub(r'^SELINUXTYPE=.*', policyline, line) + '\n')
|
|
||||||
|
|
||||||
module.atomic_move(tmpfile, configfile)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=dict(
|
|
||||||
policy=dict(type='str'),
|
|
||||||
state=dict(type='str', required='True', choices=['enforcing', 'permissive', 'disabled']),
|
|
||||||
configfile=dict(type='str', default='/etc/selinux/config', aliases=['conf', 'file']),
|
|
||||||
),
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if not HAS_SELINUX:
|
|
||||||
module.fail_json(msg=missing_required_lib('libselinux-python'), exception=SELINUX_IMP_ERR)
|
|
||||||
|
|
||||||
# global vars
|
|
||||||
changed = False
|
|
||||||
msgs = []
|
|
||||||
configfile = module.params['configfile']
|
|
||||||
policy = module.params['policy']
|
|
||||||
state = module.params['state']
|
|
||||||
runtime_enabled = selinux.is_selinux_enabled()
|
|
||||||
runtime_policy = selinux.selinux_getpolicytype()[1]
|
|
||||||
runtime_state = 'disabled'
|
|
||||||
reboot_required = False
|
|
||||||
|
|
||||||
if runtime_enabled:
|
|
||||||
# enabled means 'enforcing' or 'permissive'
|
|
||||||
if selinux.security_getenforce():
|
|
||||||
runtime_state = 'enforcing'
|
|
||||||
else:
|
|
||||||
runtime_state = 'permissive'
|
|
||||||
|
|
||||||
if not os.path.isfile(configfile):
|
|
||||||
module.fail_json(msg="Unable to find file {0}".format(configfile),
|
|
||||||
details="Please install SELinux-policy package, "
|
|
||||||
"if this package is not installed previously.")
|
|
||||||
|
|
||||||
config_policy = get_config_policy(configfile)
|
|
||||||
config_state = get_config_state(configfile)
|
|
||||||
|
|
||||||
# check to see if policy is set if state is not 'disabled'
|
|
||||||
if state != 'disabled':
|
|
||||||
if not policy:
|
|
||||||
module.fail_json(msg="Policy is required if state is not 'disabled'")
|
|
||||||
else:
|
|
||||||
if not policy:
|
|
||||||
policy = config_policy
|
|
||||||
|
|
||||||
# check changed values and run changes
|
|
||||||
if policy != runtime_policy:
|
|
||||||
if module.check_mode:
|
|
||||||
module.exit_json(changed=True)
|
|
||||||
# cannot change runtime policy
|
|
||||||
msgs.append("Running SELinux policy changed from '%s' to '%s'" % (runtime_policy, policy))
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if policy != config_policy:
|
|
||||||
if module.check_mode:
|
|
||||||
module.exit_json(changed=True)
|
|
||||||
set_config_policy(module, policy, configfile)
|
|
||||||
msgs.append("SELinux policy configuration in '%s' changed from '%s' to '%s'" % (configfile, config_policy, policy))
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if state != runtime_state:
|
|
||||||
if runtime_enabled:
|
|
||||||
if state == 'disabled':
|
|
||||||
if runtime_state != 'permissive':
|
|
||||||
# Temporarily set state to permissive
|
|
||||||
if not module.check_mode:
|
|
||||||
set_state(module, 'permissive')
|
|
||||||
module.warn("SELinux state temporarily changed from '%s' to 'permissive'. State change will take effect next reboot." % (runtime_state))
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
module.warn('SELinux state change will take effect next reboot')
|
|
||||||
reboot_required = True
|
|
||||||
else:
|
|
||||||
if not module.check_mode:
|
|
||||||
set_state(module, state)
|
|
||||||
msgs.append("SELinux state changed from '%s' to '%s'" % (runtime_state, state))
|
|
||||||
|
|
||||||
# Only report changes if the file is changed.
|
|
||||||
# This prevents the task from reporting changes every time the task is run.
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
module.warn("Reboot is required to set SELinux state to '%s'" % state)
|
|
||||||
reboot_required = True
|
|
||||||
|
|
||||||
if state != config_state:
|
|
||||||
if not module.check_mode:
|
|
||||||
set_config_state(module, state, configfile)
|
|
||||||
msgs.append("Config SELinux state changed from '%s' to '%s'" % (config_state, state))
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
module.exit_json(changed=changed, msg=', '.join(msgs), configfile=configfile, policy=policy, state=state, reboot_required=reboot_required)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,257 +0,0 @@
|
||||||
#!powershell
|
|
||||||
|
|
||||||
# Copyright: (c) 2018, Micah Hunsberger (@mhunsber)
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
#AnsibleRequires -CSharpUtil Ansible.Basic
|
|
||||||
|
|
||||||
Set-StrictMode -Version 2
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
|
|
||||||
$spec = @{
|
|
||||||
options = @{
|
|
||||||
state = @{ type = "str"; choices = "absent", "present"; default = "present" }
|
|
||||||
aliases = @{ type = "list"; elements = "str" }
|
|
||||||
canonical_name = @{ type = "str" }
|
|
||||||
ip_address = @{ type = "str" }
|
|
||||||
action = @{ type = "str"; choices = "add", "remove", "set"; default = "set" }
|
|
||||||
}
|
|
||||||
required_if = @(,@( "state", "present", @("canonical_name", "ip_address")))
|
|
||||||
supports_check_mode = $true
|
|
||||||
}
|
|
||||||
|
|
||||||
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
|
||||||
|
|
||||||
$state = $module.Params.state
|
|
||||||
$aliases = $module.Params.aliases
|
|
||||||
$canonical_name = $module.Params.canonical_name
|
|
||||||
$ip_address = $module.Params.ip_address
|
|
||||||
$action = $module.Params.action
|
|
||||||
|
|
||||||
$tmp = [ipaddress]::None
|
|
||||||
if($ip_address -and -not [ipaddress]::TryParse($ip_address, [ref]$tmp)){
|
|
||||||
$module.FailJson("win_hosts: Argument ip_address needs to be a valid ip address, but was $ip_address")
|
|
||||||
}
|
|
||||||
$ip_address_type = $tmp.AddressFamily
|
|
||||||
|
|
||||||
$hosts_file = Get-Item -LiteralPath "$env:SystemRoot\System32\drivers\etc\hosts"
|
|
||||||
|
|
||||||
Function Get-CommentIndex($line) {
|
|
||||||
$c_index = $line.IndexOf('#')
|
|
||||||
if($c_index -lt 0) {
|
|
||||||
$c_index = $line.Length
|
|
||||||
}
|
|
||||||
return $c_index
|
|
||||||
}
|
|
||||||
|
|
||||||
Function Get-HostEntryParts($line) {
|
|
||||||
$success = $true
|
|
||||||
$c_index = Get-CommentIndex -line $line
|
|
||||||
$pure_line = $line.Substring(0,$c_index).Trim()
|
|
||||||
$bits = $pure_line -split "\s+"
|
|
||||||
if($bits.Length -lt 2){
|
|
||||||
return @{
|
|
||||||
success = $false
|
|
||||||
ip_address = ""
|
|
||||||
ip_type = ""
|
|
||||||
canonical_name = ""
|
|
||||||
aliases = @()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$ip_obj = [ipaddress]::None
|
|
||||||
if(-not [ipaddress]::TryParse($bits[0], [ref]$ip_obj) ){
|
|
||||||
$success = $false
|
|
||||||
}
|
|
||||||
$cname = $bits[1]
|
|
||||||
$als = New-Object string[] ($bits.Length - 2)
|
|
||||||
[array]::Copy($bits, 2, $als, 0, $als.Length)
|
|
||||||
return @{
|
|
||||||
success = $success
|
|
||||||
ip_address = $ip_obj.IPAddressToString
|
|
||||||
ip_type = $ip_obj.AddressFamily
|
|
||||||
canonical_name = $cname
|
|
||||||
aliases = $als
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Function Find-HostName($line, $name) {
|
|
||||||
$c_idx = Get-CommentIndex -line $line
|
|
||||||
$re = New-Object regex ("\s+$($name.Replace('.',"\."))(\s|$)", [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
|
|
||||||
$match = $re.Match($line, 0, $c_idx)
|
|
||||||
return $match
|
|
||||||
}
|
|
||||||
|
|
||||||
Function Remove-HostEntry($list, $idx) {
|
|
||||||
$module.Result.changed = $true
|
|
||||||
$list.RemoveAt($idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
Function Add-HostEntry($list, $cname, $aliases, $ip) {
|
|
||||||
$module.Result.changed = $true
|
|
||||||
$line = "$ip $cname $($aliases -join ' ')"
|
|
||||||
$list.Add($line) | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
Function Remove-HostnamesFromEntry($list, $idx, $aliases) {
|
|
||||||
$line = $list[$idx]
|
|
||||||
$line_removed = $false
|
|
||||||
|
|
||||||
foreach($name in $aliases){
|
|
||||||
$match = Find-HostName -line $line -name $name
|
|
||||||
if($match.Success){
|
|
||||||
$line = $line.Remove($match.Index + 1, $match.Length -1)
|
|
||||||
# was this the last alias? (check for space characters after trimming)
|
|
||||||
if($line.Substring(0,(Get-CommentIndex -line $line)).Trim() -inotmatch "\s") {
|
|
||||||
$list.RemoveAt($idx)
|
|
||||||
$line_removed = $true
|
|
||||||
# we're done
|
|
||||||
return @{
|
|
||||||
line_removed = $line_removed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if($line -ne $list[$idx]){
|
|
||||||
$module.Result.changed = $true
|
|
||||||
$list[$idx] = $line
|
|
||||||
}
|
|
||||||
return @{
|
|
||||||
line_removed = $line_removed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Function Add-AliasesToEntry($list, $idx, $aliases) {
|
|
||||||
$line = $list[$idx]
|
|
||||||
foreach($name in $aliases){
|
|
||||||
$match = Find-HostName -line $line -name $name
|
|
||||||
if(-not $match.Success) {
|
|
||||||
# just add the alias before the comment
|
|
||||||
$line = $line.Insert((Get-CommentIndex -line $line), " $name ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if($line -ne $list[$idx]){
|
|
||||||
$module.Result.changed = $true
|
|
||||||
$list[$idx] = $line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$hosts_lines = New-Object System.Collections.ArrayList
|
|
||||||
|
|
||||||
Get-Content -LiteralPath $hosts_file.FullName | ForEach-Object { $hosts_lines.Add($_) } | Out-Null
|
|
||||||
$module.Diff.before = ($hosts_lines -join "`n") + "`n"
|
|
||||||
|
|
||||||
if ($state -eq 'absent') {
|
|
||||||
# go through and remove canonical_name and ip
|
|
||||||
for($idx = 0; $idx -lt $hosts_lines.Count; $idx++) {
|
|
||||||
$entry = $hosts_lines[$idx]
|
|
||||||
# skip comment lines
|
|
||||||
if(-not $entry.Trim().StartsWith('#')) {
|
|
||||||
$entry_parts = Get-HostEntryParts -line $entry
|
|
||||||
if($entry_parts.success) {
|
|
||||||
if(-not $ip_address -or $entry_parts.ip_address -eq $ip_address) {
|
|
||||||
if(-not $canonical_name -or $entry_parts.canonical_name -eq $canonical_name) {
|
|
||||||
if(Remove-HostEntry -list $hosts_lines -idx $idx){
|
|
||||||
# keep index correct if we removed the line
|
|
||||||
$idx = $idx - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if($state -eq 'present') {
|
|
||||||
$entry_idx = -1
|
|
||||||
$aliases_to_keep = @()
|
|
||||||
# go through lines, find the entry and determine what to remove based on action
|
|
||||||
for($idx = 0; $idx -lt $hosts_lines.Count; $idx++) {
|
|
||||||
$entry = $hosts_lines[$idx]
|
|
||||||
# skip comment lines
|
|
||||||
if(-not $entry.Trim().StartsWith('#')) {
|
|
||||||
$entry_parts = Get-HostEntryParts -line $entry
|
|
||||||
if($entry_parts.success) {
|
|
||||||
$aliases_to_remove = @()
|
|
||||||
if($entry_parts.ip_address -eq $ip_address) {
|
|
||||||
if($entry_parts.canonical_name -eq $canonical_name) {
|
|
||||||
$entry_idx = $idx
|
|
||||||
|
|
||||||
if($action -eq 'set') {
|
|
||||||
$aliases_to_remove = $entry_parts.aliases | Where-Object { $aliases -notcontains $_ }
|
|
||||||
} elseif($action -eq 'remove') {
|
|
||||||
$aliases_to_remove = $aliases
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
# this is the right ip_address, but not the cname we were looking for.
|
|
||||||
# we need to make sure none of aliases or canonical_name exist for this entry
|
|
||||||
# since the given canonical_name should be an A/AAAA record,
|
|
||||||
# and aliases should be cname records for the canonical_name.
|
|
||||||
$aliases_to_remove = $aliases + $canonical_name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
# this is not the ip_address we are looking for
|
|
||||||
if ($ip_address_type -eq $entry_parts.ip_type) {
|
|
||||||
if ($entry_parts.canonical_name -eq $canonical_name) {
|
|
||||||
Remove-HostEntry -list $hosts_lines -idx $idx
|
|
||||||
$idx = $idx - 1
|
|
||||||
if ($action -ne "set") {
|
|
||||||
# keep old aliases intact
|
|
||||||
$aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ }
|
|
||||||
}
|
|
||||||
} elseif ($action -eq "remove") {
|
|
||||||
$aliases_to_remove = $canonical_name
|
|
||||||
} elseif ($aliases -contains $entry_parts.canonical_name) {
|
|
||||||
Remove-HostEntry -list $hosts_lines -idx $idx
|
|
||||||
$idx = $idx - 1
|
|
||||||
if ($action -eq "add") {
|
|
||||||
# keep old aliases intact
|
|
||||||
$aliases_to_keep += $entry_parts.aliases | Where-Object { ($aliases + $aliases_to_keep + $canonical_name) -notcontains $_ }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$aliases_to_remove = $aliases + $canonical_name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
# TODO: Better ipv6 support. There is odd behavior for when an alias can be used for both ipv6 and ipv4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($aliases_to_remove) {
|
|
||||||
if((Remove-HostnamesFromEntry -list $hosts_lines -idx $idx -aliases $aliases_to_remove).line_removed) {
|
|
||||||
$idx = $idx - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if($entry_idx -ge 0) {
|
|
||||||
$aliases_to_add = @()
|
|
||||||
$entry_parts = Get-HostEntryParts -line $hosts_lines[$entry_idx]
|
|
||||||
if($action -eq 'remove') {
|
|
||||||
$aliases_to_add = $aliases_to_keep | Where-Object { $entry_parts.aliases -notcontains $_ }
|
|
||||||
} else {
|
|
||||||
$aliases_to_add = ($aliases + $aliases_to_keep) | Where-Object { $entry_parts.aliases -notcontains $_ }
|
|
||||||
}
|
|
||||||
|
|
||||||
if($aliases_to_add) {
|
|
||||||
Add-AliasesToEntry -list $hosts_lines -idx $entry_idx -aliases $aliases_to_add
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
# add the entry at the end
|
|
||||||
if($action -eq 'remove') {
|
|
||||||
if($aliases_to_keep) {
|
|
||||||
Add-HostEntry -list $hosts_lines -ip $ip_address -cname $canonical_name -aliases $aliases_to_keep
|
|
||||||
} else {
|
|
||||||
Add-HostEntry -list $hosts_lines -ip $ip_address -cname $canonical_name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Add-HostEntry -list $hosts_lines -ip $ip_address -cname $canonical_name -aliases ($aliases + $aliases_to_keep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$module.Diff.after = ($hosts_lines -join "`n") + "`n"
|
|
||||||
if( $module.Result.changed -and -not $module.CheckMode ) {
|
|
||||||
Set-Content -LiteralPath $hosts_file.FullName -Value $hosts_lines
|
|
||||||
}
|
|
||||||
|
|
||||||
$module.ExitJson()
|
|
|
@ -1,126 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2018, Micah Hunsberger (@mhunsber)
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
|
|
||||||
# this is a windows documentation stub. actual code lives in the .ps1
|
|
||||||
# file of the same name
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
DOCUMENTATION = r'''
|
|
||||||
---
|
|
||||||
module: win_hosts
|
|
||||||
version_added: '2.8'
|
|
||||||
short_description: Manages hosts file entries on Windows.
|
|
||||||
description:
|
|
||||||
- Manages hosts file entries on Windows.
|
|
||||||
- Maps IPv4 or IPv6 addresses to canonical names.
|
|
||||||
- Adds, removes, or sets cname records for ip and hostname pairs.
|
|
||||||
- Modifies %windir%\\system32\\drivers\\etc\\hosts.
|
|
||||||
options:
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- Whether the entry should be present or absent.
|
|
||||||
- If only I(canonical_name) is provided when C(state=absent), then
|
|
||||||
all hosts entries with the canonical name of I(canonical_name)
|
|
||||||
will be removed.
|
|
||||||
- If only I(ip_address) is provided when C(state=absent), then all
|
|
||||||
hosts entries with the ip address of I(ip_address) will be removed.
|
|
||||||
- If I(ip_address) and I(canonical_name) are both omitted when
|
|
||||||
C(state=absent), then all hosts entries will be removed.
|
|
||||||
choices:
|
|
||||||
- absent
|
|
||||||
- present
|
|
||||||
default: present
|
|
||||||
type: str
|
|
||||||
canonical_name:
|
|
||||||
description:
|
|
||||||
- A canonical name for the host entry.
|
|
||||||
- required for C(state=present).
|
|
||||||
type: str
|
|
||||||
ip_address:
|
|
||||||
description:
|
|
||||||
- The ip address for the host entry.
|
|
||||||
- Can be either IPv4 (A record) or IPv6 (AAAA record).
|
|
||||||
- Required for C(state=present).
|
|
||||||
type: str
|
|
||||||
aliases:
|
|
||||||
description:
|
|
||||||
- A list of additional names (cname records) for the host entry.
|
|
||||||
- Only applicable when C(state=present).
|
|
||||||
type: list
|
|
||||||
action:
|
|
||||||
choices:
|
|
||||||
- add
|
|
||||||
- remove
|
|
||||||
- set
|
|
||||||
description:
|
|
||||||
- Controls the behavior of I(aliases).
|
|
||||||
- Only applicable when C(state=present).
|
|
||||||
- If C(add), each alias in I(aliases) will be added to the host entry.
|
|
||||||
- If C(set), each alias in I(aliases) will be added to the host entry,
|
|
||||||
and other aliases will be removed from the entry.
|
|
||||||
default: set
|
|
||||||
type: str
|
|
||||||
author:
|
|
||||||
- Micah Hunsberger (@mhunsber)
|
|
||||||
notes:
|
|
||||||
- Each canonical name can only be mapped to one IPv4 and one IPv6 address.
|
|
||||||
If I(canonical_name) is provided with C(state=present) and is found
|
|
||||||
to be mapped to another IP address that is the same type as, but unique
|
|
||||||
from I(ip_address), then I(canonical_name) and all I(aliases) will
|
|
||||||
be removed from the entry and added to an entry with the provided IP address.
|
|
||||||
- Each alias can only be mapped to one canonical name. If I(aliases) is provided
|
|
||||||
with C(state=present) and an alias is found to be mapped to another canonical
|
|
||||||
name, then the alias will be removed from the entry and either added to or removed
|
|
||||||
from (depending on I(action)) an entry with the provided canonical name.
|
|
||||||
seealso:
|
|
||||||
- module: win_template
|
|
||||||
- module: win_file
|
|
||||||
- module: win_copy
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = r'''
|
|
||||||
- name: Add 127.0.0.1 as an A record for localhost
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
canonical_name: localhost
|
|
||||||
ip_address: 127.0.0.1
|
|
||||||
|
|
||||||
- name: Add ::1 as an AAAA record for localhost
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
canonical_name: localhost
|
|
||||||
ip_address: '::1'
|
|
||||||
|
|
||||||
- name: Remove 'bar' and 'zed' from the list of aliases for foo (192.168.1.100)
|
|
||||||
win_hosts:
|
|
||||||
state: present
|
|
||||||
canoncial_name: foo
|
|
||||||
ip_address: 192.168.1.100
|
|
||||||
action: remove
|
|
||||||
aliases:
|
|
||||||
- bar
|
|
||||||
- zed
|
|
||||||
|
|
||||||
- name: Remove hosts entries with canonical name 'bar'
|
|
||||||
win_hosts:
|
|
||||||
state: absent
|
|
||||||
canonical_name: bar
|
|
||||||
|
|
||||||
- name: Remove 10.2.0.1 from the list of hosts
|
|
||||||
win_hosts:
|
|
||||||
state: absent
|
|
||||||
ip_address: 10.2.0.1
|
|
||||||
|
|
||||||
- name: Ensure all name resolution is handled by DNS
|
|
||||||
win_hosts:
|
|
||||||
state: absent
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = r'''
|
|
||||||
'''
|
|
Loading…
Reference in a new issue