mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-03-31 15:53:06 +00:00
Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6a95e7649 | ||
|
|
6b2b9ea787 | ||
|
|
3487efcf9f | ||
|
|
695ad6307d | ||
|
|
cf54d139c2 | ||
|
|
ae471de0bd | ||
|
|
927329326c | ||
|
|
26444b42b0 | ||
|
|
1d196bca67 | ||
|
|
d73b6e3920 | ||
|
|
b80d6b061d | ||
|
|
5a290565f3 | ||
|
|
40048c781a | ||
|
|
f7ca62e52b | ||
|
|
da87f1648e | ||
|
|
0bcb4eaf0f | ||
|
|
0456424821 | ||
|
|
ff03b3153b | ||
|
|
0abfe8ab90 | ||
|
|
89ba344a0b | ||
|
|
c49fa4e899 | ||
|
|
66936d1afa | ||
|
|
c26b9c27b1 | ||
|
|
ad139256df | ||
|
|
d3b0fcebda | ||
|
|
19b117a71c | ||
|
|
02705c9e47 | ||
|
|
10e7b4094d | ||
|
|
0acf576d99 | ||
|
|
fd7eb4f85f | ||
|
|
2e7df27fe3 | ||
|
|
561cd4fb98 | ||
|
|
4ad1033685 | ||
|
|
3981dafd7b | ||
|
|
1cf251baf8 | ||
|
|
c9210ca2d1 | ||
|
|
d7a3b7533c | ||
|
|
46caacd0ae | ||
|
|
5406c60157 | ||
|
|
341078ed5d | ||
|
|
95d90ef31f | ||
|
|
cf0b710047 | ||
|
|
bf9024f79f | ||
|
|
f44e33c6b3 | ||
|
|
6b5f034912 | ||
|
|
bf0b1ed75f | ||
|
|
a052160cc9 | ||
|
|
851c6a9f39 | ||
|
|
59cb7eebd9 | ||
|
|
55e86c924f | ||
|
|
56b1368441 | ||
|
|
4ada6e1d24 | ||
|
|
b48b81a030 | ||
|
|
09fefbb2d4 | ||
|
|
8e6d433df8 | ||
|
|
578d08c796 | ||
|
|
2408a9b7c6 | ||
|
|
0372fec0e3 | ||
|
|
07d7e2fa86 | ||
|
|
4221213f1e | ||
|
|
05a1aaed53 | ||
|
|
5b53862871 | ||
|
|
7ca6c15fee | ||
|
|
44af47d93a | ||
|
|
89bc267d98 | ||
|
|
583d46b020 | ||
|
|
315f93c09a | ||
|
|
91094ce4d4 | ||
|
|
848959ca6a | ||
|
|
c236fe3d62 | ||
|
|
bf15351c07 | ||
|
|
ac61f597d5 | ||
|
|
fdcdad2c7e | ||
|
|
6a69bbeafb | ||
|
|
571cc210b5 | ||
|
|
a432c3ff50 | ||
|
|
14d4502019 | ||
|
|
b0a067d5d5 | ||
|
|
f1c733d867 | ||
|
|
e36961f35e | ||
|
|
e8317b281a | ||
|
|
60c8be19a5 | ||
|
|
1f1762bd25 | ||
|
|
2b084e6d15 | ||
|
|
b3d5b32e31 | ||
|
|
67261c3dcd | ||
|
|
84d8fc0cf3 | ||
|
|
791c4703b1 | ||
|
|
457050c6ac | ||
|
|
703ee1c9cd | ||
|
|
efbc50b257 | ||
|
|
cf1fe72616 | ||
|
|
6b0cf1e777 | ||
|
|
0677af0714 | ||
|
|
5d7c0ec3d9 | ||
|
|
5643cfc20d | ||
|
|
4155f2f3ac | ||
|
|
7897bd4d8e | ||
|
|
871cce5258 | ||
|
|
5e734e847e | ||
|
|
9d348cb368 | ||
|
|
4ba34077f9 | ||
|
|
3a37325a36 | ||
|
|
57d407f15f | ||
|
|
cd5429a534 | ||
|
|
ffd8585d19 | ||
|
|
2897267440 | ||
|
|
2712e39bc4 | ||
|
|
a972beb484 | ||
|
|
50a1c2f9cd | ||
|
|
0fb05dfaca | ||
|
|
2205907220 | ||
|
|
d7af454d77 | ||
|
|
35d7658834 | ||
|
|
aeaeaadd27 | ||
|
|
abe2605a55 | ||
|
|
492a2bf39e | ||
|
|
4ab38e8bc6 | ||
|
|
3400f9556b | ||
|
|
2ed7e21c1f | ||
|
|
e76047edb0 | ||
|
|
b211b50b2d | ||
|
|
d31a132a59 | ||
|
|
7576732525 | ||
|
|
cfdf2896ba | ||
|
|
8c2268a560 | ||
|
|
81179b709b | ||
|
|
d33935583c | ||
|
|
708675d9c2 | ||
|
|
7cf80c59b8 |
149
README-config.md
Normal file
149
README-config.md
Normal file
@@ -0,0 +1,149 @@
|
||||
Config module
|
||||
===========
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The config module allows the setting of global config parameters within IPA. If no parameters are specified it returns the list of all current parameters.
|
||||
|
||||
The config module is as compatible as possible to the Ansible upstream `ipa_config` module, but adds many additional parameters
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
* IPA server configuration management
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.4.0 and up are supported by the ipaconfig module.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.test.local
|
||||
```
|
||||
|
||||
|
||||
Example playbook to read config options:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to handle global config options
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
tasks:
|
||||
- name: return current values of the global configuration options
|
||||
ipaconfig:
|
||||
ipaadmin_password: password
|
||||
register: result
|
||||
- name: display default login shell
|
||||
debug:
|
||||
msg: '{{result.config.defaultlogin }}'
|
||||
|
||||
- name: ensure defaultloginshell and maxusernamelength are set as required
|
||||
ipaconfig:
|
||||
ipaadmin_password: password
|
||||
defaultlogin: /bin/bash
|
||||
maxusername: 64
|
||||
```
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to ensure some config options are set
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
tasks:
|
||||
- name: set defaultlogin and maxusername
|
||||
ipaconfig:
|
||||
ipaadmin_password: password
|
||||
defaultlogin: /bin/bash
|
||||
maxusername: 64
|
||||
```
|
||||
|
||||
|
||||
Variables
|
||||
=========
|
||||
|
||||
ipauser
|
||||
-------
|
||||
|
||||
**General Variables:**
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`maxusername` \| `ipamaxusernamelength` | Set the maximum username length (1 to 255) | no
|
||||
`maxhostname` \| `ipamaxhostnamelength` | Set the maximum hostname length between 64-255 | no
|
||||
`homedirectory` \| `ipahomesrootdir` | Set the default location of home directories | no
|
||||
`defaultshell` \| `ipadefaultloginshell` | Set the default shell for new users | no
|
||||
`defaultgroup` \| `ipadefaultprimarygroup` | Set the default group for new users | no
|
||||
`emaildomain`\| `ipadefaultemaildomain` | Set the default e-mail domain | false
|
||||
`searchtimelimit` \| `ipasearchtimelimit` | Set maximum amount of time (seconds) for a search -1 to 2147483647 (-1 or 0 is unlimited) | no
|
||||
`searchrecordslimit` \| `ipasearchrecordslimit` | Set maximum number of records to search -1 to 2147483647 (-1 or 0 is unlimited) | no
|
||||
`usersearch` \| `ipausersearchfields` | Set list of fields to search when searching for users | no
|
||||
`groupsearch` \| `ipagroupsearchfields` | Set list of fields to search in when searching for groups | no
|
||||
`enable_migration` \| `ipamigrationenabled` | Enable migration mode (choices: True, False ) | no
|
||||
`groupobjectclasses` \| `ipagroupobjectclasses` | Set default group objectclasses (list) | no
|
||||
`userobjectclasses` \| `ipauserobjectclasses` | Set default user objectclasses (list) | no
|
||||
`pwdexpnotify` \| `ipapwdexpadvnotify` | Set number of days's notice of impending password expiration (0 to 2147483647) | no
|
||||
`configstring` \| `ipaconfigstring` | Set extra hashes to generate in password plug-in (choices:`AllowNThash`, `KDC:Disable Last Success`, `KDC:Disable Lockout`, `KDC:Disable Default Preauth for SPNs`). Use `""` to clear this variable. | no
|
||||
`selinuxusermaporder` \| `ipaselinuxusermaporder`| Set ordered list in increasing priority of SELinux users | no
|
||||
`selinuxusermapdefault`\| `ipaselinuxusermapdefault` | Set default SELinux user when no match is found in SELinux map rule | no
|
||||
`pac_type` \| `ipakrbauthzdata` | set default types of PAC supported for services (choices: `MS-PAC`, `PAD`, `nfs:NONE`). Use `""` to clear this variable. | no
|
||||
`user_auth_type` \| `ipauserauthtype` | set default types of supported user authentication (choices: `password`, `radius`, `otp`, `disabled`). Use `""` to clear this variable. | no
|
||||
`domain_resolution_order` \| `ipadomainresolutionorder` | Set list of domains used for short name qualification | no
|
||||
`ca_renewal_master_server` \| `ipacarenewalmasterserver`| Renewal master for IPA certificate authority. | no
|
||||
|
||||
|
||||
Return Values
|
||||
=============
|
||||
|
||||
Variable | Description | Returned When
|
||||
-------- | ----------- | -------------
|
||||
`config` | config dict <br />Fields: | No values to configure are specified
|
||||
| `maxusername` |
|
||||
| `maxhostname` |
|
||||
| `homedirectory` |
|
||||
| `defaultshell` |
|
||||
| `defaultgroup` |
|
||||
| `emaildomain` |
|
||||
| `searchtimelimit` |
|
||||
| `searchrecordslimit` |
|
||||
| `usersearch` |
|
||||
| `groupsearch` |
|
||||
| `enable_migration` |
|
||||
| `groupobjectclasses` |
|
||||
| `userobjectclasses` |
|
||||
| `pwdexpnotify` |
|
||||
| `configstring` |
|
||||
| `selinuxusermapdefault` |
|
||||
| `selinuxusermaporder` |
|
||||
| `pac_type` |
|
||||
| `user_auth_type` |
|
||||
| `domain_resolution_order` |
|
||||
| `ca_renewal_master_server` |
|
||||
|
||||
All returned fields take the same form as their namesake input parameters
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Chris Procter
|
||||
112
README-dnsforwardzone.md
Normal file
112
README-dnsforwardzone.md
Normal file
@@ -0,0 +1,112 @@
|
||||
Dnsforwardzone module
|
||||
=====================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The dnsforwardzone module allows the addition and removal of dns forwarders from the IPA DNS config.
|
||||
|
||||
It is desgined to follow the IPA api as closely as possible while ensuring ease of use.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
* DNS zone management
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.4.0 and up are supported by the ipadnsforwardzone module.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.test.local
|
||||
```
|
||||
|
||||
|
||||
Example playbook to ensure presence of a forwardzone to ipa DNS:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to handle add a forwarder
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: ensure presence of forwardzone for DNS requests for example.com to 8.8.8.8
|
||||
ipadnsforwardzone:
|
||||
ipaadmin_password: password01
|
||||
state: present
|
||||
name: example.com
|
||||
forwarders:
|
||||
- 8.8.8.8
|
||||
forwardpolicy: first
|
||||
skip_overlap_check: true
|
||||
|
||||
- name: ensure the forward zone is disabled
|
||||
ipadnsforwardzone:
|
||||
ipaadmin_password: password01
|
||||
name: example.com
|
||||
state: disabled
|
||||
|
||||
- name: ensure presence of multiple upstream DNS servers for example.com
|
||||
ipadnsforwardzone:
|
||||
ipaadmin_password: password01
|
||||
state: present
|
||||
name: example.com
|
||||
forwarders:
|
||||
- 8.8.8.8
|
||||
- 4.4.4.4
|
||||
|
||||
- name: ensure presence of another forwarder to any existing ones for example.com
|
||||
ipadnsforwardzone:
|
||||
ipaadmin_password: password01
|
||||
state: present
|
||||
name: example.com
|
||||
forwarders:
|
||||
- 1.1.1.1
|
||||
action: member
|
||||
|
||||
- name: ensure the forwarder for example.com does not exists (delete it if needed)
|
||||
ipadnsforwardzone:
|
||||
ipaadmin_password: password01
|
||||
name: example.com
|
||||
state: absent
|
||||
```
|
||||
|
||||
Variables
|
||||
=========
|
||||
|
||||
ipagroup
|
||||
-------
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`name` \| `cn` | Zone name (FQDN). | yes if `state` == `present`
|
||||
`forwarders` \| `idnsforwarders` | Per-zone conditional forwarding policy. Possible values are `only`, `first`, `none`) | no
|
||||
`forwardpolicy` \| `idnsforwardpolicy` | Per-zone conditional forwarding policy. Set to "none" to disable forwarding to global forwarder for this zone. In that case, conditional zone forwarders are disregarded. | no
|
||||
`skip_overlap_check` | Force DNS zone creation even if it will overlap with an existing zone. Defaults to False. | no
|
||||
`action` | Work on group or member level. It can be on of `member` or `dnsforwardzone` and defaults to `dnsforwardzone`. | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled` or `disabled`, default: `present`. | yes
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Chris Procter
|
||||
357
README-dnsrecord.md
Normal file
357
README-dnsrecord.md
Normal file
@@ -0,0 +1,357 @@
|
||||
DNSRecord module
|
||||
================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The dnsrecord module allows management of DNS records and is as compatible as possible with the Ansible upstream `ipa_dnsrecord` module, but provide some other features like multiple record management in one execution and support for more DNS record types.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
* DNS record management.
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.4.0 and up are supported by the ipadnsrecord module.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
Example inventory file
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.example.com
|
||||
```
|
||||
|
||||
Example playbook to ensure an AAAA record is present:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host01
|
||||
zone_name: example.com
|
||||
record_type: 'AAAA'
|
||||
record_value: '::1'
|
||||
```
|
||||
|
||||
Example playbook to ensure an AAAA record is present, with a TTL of 300:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host01
|
||||
zone_name: example.com
|
||||
record_type: 'AAAA'
|
||||
record_value: '::1'
|
||||
record_ttl: 300
|
||||
```
|
||||
|
||||
Example playbook to ensure an AAAA record is present, with a reverse PTR record:
|
||||
```yaml
|
||||
---
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host02
|
||||
zone_name: example.com
|
||||
record_type: 'AAAA'
|
||||
record_value: 'fd00::0002'
|
||||
create_reverse: yes
|
||||
```
|
||||
|
||||
Example playbook to ensure a LOC record is present, given its individual attributes:
|
||||
```yaml
|
||||
---
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
name: host03
|
||||
loc_lat_deg: 52
|
||||
loc_lat_min: 22
|
||||
loc_lat_sec: 23.000
|
||||
loc_lat_dir: N
|
||||
loc_lon_deg: 4
|
||||
loc_lon_min: 53
|
||||
loc_lon_sec: 32.00
|
||||
loc_lon_dir: E
|
||||
loc_altitude: -2.00
|
||||
loc_size: 1.00
|
||||
loc_h_precision: 10000
|
||||
loc_v_precision: 10
|
||||
```
|
||||
|
||||
Example playbook to ensure multiple DNS records are present:
|
||||
|
||||
```yaml
|
||||
---
|
||||
ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
records:
|
||||
- name: host02
|
||||
zone_name: example.com
|
||||
record_type: A
|
||||
record_value:
|
||||
- "{{ ipv4_prefix }}.112"
|
||||
- "{{ ipv4_prefix }}.122"
|
||||
- name: host02
|
||||
zone_name: example.com
|
||||
record_type: AAAA
|
||||
record_value: ::1
|
||||
```
|
||||
|
||||
Example playbook to ensure multiple CNAME records are present:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Ensure that 'host03' and 'host04' have CNAME records.
|
||||
ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
records:
|
||||
- name: host03
|
||||
cname_hostname: host03.example.com
|
||||
- name: host04
|
||||
cname_hostname: host04.example.com
|
||||
```
|
||||
|
||||
Example playbook to ensure NS record is absent:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
name: host04
|
||||
ns_hostname: host04
|
||||
state: absent
|
||||
```
|
||||
|
||||
Example playbook to ensure LOC record is present, with fields:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
name: host04
|
||||
loc_lat_deg: 52
|
||||
loc_lat_min: 22
|
||||
loc_lat_sec: 23.000
|
||||
loc_lat_dir: N
|
||||
loc_lon_deg: 4
|
||||
loc_lon_min: 53
|
||||
loc_lon_sec: 32.000
|
||||
loc_lon_dir: E
|
||||
loc_altitude: -2.00
|
||||
loc_size: 0.00
|
||||
loc_h_precision: 10000
|
||||
loc_v_precision: 10
|
||||
```
|
||||
|
||||
Change value of an existing LOC record:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
name: host04
|
||||
loc_size: 1.00
|
||||
loc_rec: 52 22 23 N 4 53 32 E -2 0 10000 10
|
||||
```
|
||||
|
||||
Example playbook to ensure multiple A records are present:
|
||||
|
||||
```yaml
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
name: host04
|
||||
a_rec:
|
||||
- 192.168.122.221
|
||||
- 192.168.122.222
|
||||
- 192.168.122.223
|
||||
- 192.168.122.224
|
||||
```
|
||||
|
||||
Example playbook to ensure A and AAAA records are present, with reverse records (PTR):
|
||||
```yaml
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
name: host01
|
||||
a_rec:
|
||||
- 192.168.122.221
|
||||
- 192.168.122.222
|
||||
aaaa_rec:
|
||||
- fd00:;0001
|
||||
- fd00::0002
|
||||
create_reverse: yes
|
||||
```
|
||||
|
||||
Example playbook to ensure multiple A and AAAA records are present, but only A records have reverse records:
|
||||
```yaml
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
name: host01
|
||||
a_ip_address: 192.168.122.221
|
||||
aaaa_ip_address: fd00::0001
|
||||
a_create_reverse: yes
|
||||
```
|
||||
|
||||
Example playbook to ensure multiple DNS records are absent:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
records:
|
||||
- name: host01
|
||||
del_all: yes
|
||||
- name: host02
|
||||
del_all: yes
|
||||
- name: host03
|
||||
del_all: yes
|
||||
- name: host04
|
||||
del_all: yes
|
||||
- name: _ftp._tcp
|
||||
del_all: yes
|
||||
- name: _sip._udp
|
||||
del_all: yes
|
||||
state: absent
|
||||
```
|
||||
|
||||
Variables
|
||||
=========
|
||||
|
||||
ipadnsrecord
|
||||
------------
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`zone_name` \| `dnszone` | The DNS zone name to which DNS record needs to be managed. You can use one global zone name for multiple records. | no
|
||||
required: true
|
||||
`records` | The list of dns records dicts. Each `records` dict entry can contain **record variables**. | no
|
||||
| **Record variables** | no
|
||||
**Record variables** | Used when defining a single record. | no
|
||||
`state` | The state to ensure. It can be one of `present` or `absent`, and defaults to `present`. | yes
|
||||
|
||||
|
||||
**Record Variables:**
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`zone_name` \| `dnszone` | The DNS zone name to which DNS record needs to be managed. You can use one global zone name for multiple records. When used on a `records` dict, overrides the global `zone_name`. | yes
|
||||
`name` \| `record_name` | The DNS record name to manage. | yes
|
||||
`record_type` | The type of DNS record. Supported values are `A`, `AAAA`, `A6`, `AFSDB`, `CERT`, `CNAME`, `DLV`, `DNAME`, `DS`, `KX`, `LOC`, `MX`, `NAPTR`, `NS`, `PTR`, `SRV`, `SSHFP`, `TLSA`, `TXT`, `URI`, and defaults to `A`. | no
|
||||
`record_value` | Manage DNS record name with this values. | no
|
||||
`record_ttl` | Set the TTL for the record. (int) | no
|
||||
`del_all` | Delete all associated records. (bool) | no
|
||||
`a_rec` \| `a_record` | Raw A record. | no
|
||||
`aaaa_rec` \| `aaaa_record` | Raw AAAA record. | no
|
||||
`a6_rec` \| `a6_record` | Raw A6 record data. | no
|
||||
`afsdb_rec` \| `afsdb_record` | Raw AFSDB record. | no
|
||||
`cert_rec` \| `cert_record` | Raw CERT record. | no
|
||||
`cname_rec` \| `cname_record` | Raw CNAME record. | no
|
||||
`dlv_rec` \| `dlv_record` | Raw DLV record. | no
|
||||
`dname_rec` \| `dname_record` | Raw DNAM record. | no
|
||||
`ds_rec` \| `ds_record` | Raw DS record. | no
|
||||
`kx_rec` \| `kx_record` | Raw KX record. | no
|
||||
`loc_rec` \| `loc_record` | Raw LOC record. | no
|
||||
`mx_rec` \| `mx_record` | Raw MX record. | no
|
||||
`naptr_rec` \| `naptr_record` | Raw NAPTR record. | no
|
||||
`ns_rec` \| `ns_record` | Raw NS record. | no
|
||||
`ptr_rec` \| `ptr_record` | Raw PTR record. | no
|
||||
`srv_rec` \| `srv_record` | Raw SRV record. | no
|
||||
`sshfp_rec` \| `sshfp_record` | Raw SSHFP record. | no
|
||||
`tlsa_rec` \| `tlsa_record` | Raw TLSA record. | no
|
||||
`txt_rec` \| `txt_record` | Raw TXT record. | no
|
||||
`uri_rec` \| `uri_record` | Raw URI record. | no
|
||||
`ip_address` | IP adress for A or AAAA records. Set `record_type` to `A` or `AAAA`. | no
|
||||
`create_reverse` \| `reverse` | Create reverse records for `A` and `AAAA` record types. There is no equivalent to remove reverse records. (bool) | no
|
||||
`a_ip_address` | IP adress for A records. Set `record_type` to `A`. | no
|
||||
`a_create_reverse` | Create reverse records only for `A` records. There is no equivalent to remove reverse records. (bool) | no
|
||||
`aaaa_ip_address` | IP adress for AAAA records. Set `record_type` `AAAA`. | no
|
||||
`aaaa_create_reverse` | Create reverse records only for `AAAA` record types. There is no equivalent to remove reverse records. (bool) | no
|
||||
`a6_data` | A6 record. Set `record_type` to `A6`. | no
|
||||
`afsdb_subtype` | AFSDB Subtype. Set `record_type` to `AFSDB`. (int) | no
|
||||
`afsdb_hostname` | AFSDB Hostname. Set `record_type` to `AFSDB`. | no
|
||||
`cert_type` | CERT Certificate Type. Set `record_type` to `CERT`. (int) | no
|
||||
`cert_key_tag` | CERT Key Tag. Set `record_type` to `CERT`. (int) | no
|
||||
`cert_algorithm` | CERT Algorithm. Set `record_type` to `CERT`. (int) | no
|
||||
`cert_certificate_or_crl` | CERT Certificate or Certificate Revocation List (CRL). Set `record_type` to `CERT`. | no
|
||||
`cname_hostname` | A hostname which this alias hostname points to. Set `record_type` to `CNAME`. | no
|
||||
`dlv_key_tag` | DS Key Tag. Set `record_type` to `DLV`. (int) | no
|
||||
`dlv_algorithm` | DLV Algorithm. Set `record_type` to `DLV`. (int) | no
|
||||
`dlv_digest_type` | DLV Digest Type. Set `record_type` to `DLV`. (int) | no
|
||||
`dlv_digest` | DLV Digest. Set `record_type` to `DLV`. | no
|
||||
`dname_target` | DNAME Target. Set `record_type` to `DNAME`. | no
|
||||
`ds_key_tag` | DS Key Tag. Set `record_type` to `DS`. (int) | no
|
||||
`ds_algorithm` | DS Algorithm. Set `record_type` to `DS`. (int) | no
|
||||
`ds_digest_type` | DS Digest Type. Set `record_type` to `DS`. (int) | no
|
||||
`ds_digest` | DS Digest. Set `record_type` to `DS`. | no
|
||||
`kx_preference` | Preference given to this exchanger. Lower values are more preferred. Set `record_type` to `KX`. (int) | no
|
||||
`kx_exchanger` | A host willing to act as a key exchanger. Set `record_type` to `KX`. | no
|
||||
`loc_lat_deg` | LOC Degrees Latitude. Set `record_type` to `LOC`. (int) | no
|
||||
`loc_lat_min` | LOC Minutes Latitude. Set `record_type` to `LOC`. (int) | no
|
||||
`loc_lat_sec` | LOC Seconds Latitude. Set `record_type` to `LOC`. (float) | no
|
||||
`loc_lat_dir` | LOC Direction Latitude. Valid values are `N` or `S`. Set `record_type` to `LOC`. (int) | no
|
||||
`loc_lon_deg` | LOC Degrees Longitude. Set `record_type` to `LOC`. (int) | no
|
||||
`loc_lon_min` | LOC Minutes Longitude. Set `record_type` to `LOC`. (int) | no
|
||||
`loc_lon_sec` | LOC Seconds Longitude. Set `record_type` to `LOC`. (float) | no
|
||||
`loc_lon_dir` | LOC Direction Longitude. Valid values are `E` or `W`. Set `record_type` to `LOC`. (int) | no
|
||||
`loc_altitude` | LOC Altitude. Set `record_type` to `LOC`. (float) | no
|
||||
`loc_size` | LOC Size. Set `record_type` to `LOC`. (float) | no
|
||||
`loc_h_precision` | LOC Horizontal Precision. Set `record_type` to `LOC`. (float) | no
|
||||
`loc_v_precision` | LOC Vertical Precision. Set `record_type` to `LOC`. (float) | no
|
||||
`mx_preference` | Preference given to this exchanger. Lower values are more preferred. Set `record_type` to `MX`. (int) | no
|
||||
`mx_exchanger` | A host willing to act as a mail exchanger. Set `record_type` to `LOC`. | no
|
||||
`naptr_order` | NAPTR Order. Set `record_type` to `NAPTR`. (int) | no
|
||||
`naptr_preference` | NAPTR Preference. Set `record_type` to `NAPTR`. (int) | no
|
||||
`naptr_flags` | NAPTR Flags. Set `record_type` to `NAPTR`. | no
|
||||
`naptr_service` | NAPTR Service. Set `record_type` to `NAPTR`. | no
|
||||
`naptr_regexp` | NAPTR Regular Expression. Set `record_type` to `NAPTR`. | no
|
||||
`naptr_replacement` | NAPTR Replacement. Set `record_type` to `NAPTR`. | no
|
||||
`ns_hostname` | NS Hostname. Set `record_type` to `NS`. | no
|
||||
`ptr_hostname` | The hostname this reverse record points to. . Set `record_type` to `PTR`. | no
|
||||
`srv_priority` | Lower number means higher priority. Clients will attempt to contact the server with the lowest-numbered priority they can reach. Set `record_type` to `SRV`. (int) | no
|
||||
`srv_weight` | Relative weight for entries with the same priority. Set `record_type` to `SRV`. (int) | no
|
||||
`srv_port` | SRV Port. Set `record_type` to `SRV`. (int) | no
|
||||
`srv_target` | The domain name of the target host or '.' if the service is decidedly not available at this domain. Set `record_type` to `SRV`. | no
|
||||
`sshfp_algorithm` | SSHFP Algorithm. Set `record_type` to `SSHFP`. (int) | no
|
||||
`sshfp_fp_type` | SSHFP Fingerprint Type. Set `record_type` to `SSHFP`. (int) | no
|
||||
`sshfp_fingerprint`| SSHFP Fingerprint. Set `record_type` to `SSHFP`. (int) | no
|
||||
`txt_data` | TXT Text Data. Set `record_type` to `TXT`. | no
|
||||
`tlsa_cert_usage` | TLSA Certificate Usage. Set `record_type` to `TLSA`. (int) | no
|
||||
`tlsa_selector` | TLSA Selector. Set `record_type` to `TLSA`. (int) | no
|
||||
`tlsa_matching_type` | TLSA Matching Type. Set `record_type` to `TLSA`. (int) | no
|
||||
`tlsa_cert_association_data` | TLSA Certificate Association Data. Set `record_type` to `TLSA`. | no
|
||||
`uri_target` | Target Uniform Resource Identifier according to RFC 3986. Set `record_type` to `URI`. | no
|
||||
`uri_priority` | Lower number means higher priority. Clients will attempt to contact the URI with the lowest-numbered priority they can reach. Set `record_type` to `URI`. (int) | no
|
||||
`uri_weight` | Relative weight for entries with the same priority. Set `record_type` to `URI`. (int) | no
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Rafael Guterres Jeffman
|
||||
195
README-dnszone.md
Normal file
195
README-dnszone.md
Normal file
@@ -0,0 +1,195 @@
|
||||
DNSZone Module
|
||||
==============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The dnszone module allows to configure zones in DNS server.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Add, remove, modify, enable or disable DNS zones.
|
||||
|
||||
|
||||
Supported FreeIPA Versions
|
||||
--------------------------
|
||||
|
||||
FreeIPA versions 4.4.0 and up are supported by ipadnszone module.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
**Controller**
|
||||
* Ansible version: 2.8+
|
||||
|
||||
|
||||
**Node**
|
||||
* Supported FreeIPA version (see above)
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
|
||||
```ini
|
||||
[ipaserver]
|
||||
ipaserver.test.local
|
||||
```
|
||||
|
||||
Example playbook to create a simple DNS zone:
|
||||
|
||||
```yaml
|
||||
|
||||
---
|
||||
- name: dnszone present
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Ensure zone is present.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
state: present
|
||||
|
||||
```
|
||||
|
||||
|
||||
Example playbook to create a DNS zone with all currently supported variables:
|
||||
```yaml
|
||||
|
||||
---
|
||||
- name: dnszone present
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Ensure zone is present.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
allow_sync_ptr: true
|
||||
dynamic_update: true
|
||||
dnssec: true
|
||||
allow_transfer:
|
||||
- 1.1.1.1
|
||||
- 2.2.2.2
|
||||
allow_query:
|
||||
- 1.1.1.1
|
||||
- 2.2.2.2
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
- ip_address: 8.8.4.4
|
||||
port: 52
|
||||
serial: 1234
|
||||
refresh: 3600
|
||||
retry: 900
|
||||
expire: 1209600
|
||||
minimum: 3600
|
||||
ttl: 60
|
||||
default_ttl: 90
|
||||
name_server: ipaserver.test.local.
|
||||
admin_email: admin.admin@example.com
|
||||
nsec3param_rec: "1 7 100 0123456789abcdef"
|
||||
skip_overlap_check: true
|
||||
skip_nameserver_check: true
|
||||
state: present
|
||||
```
|
||||
|
||||
|
||||
Example playbook to disable a zone:
|
||||
|
||||
```yaml
|
||||
|
||||
---
|
||||
- name: Playbook to disable DNS zone
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Disable zone.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
state: disabled
|
||||
```
|
||||
|
||||
|
||||
Example playbook to enable a zone:
|
||||
```yaml
|
||||
|
||||
---
|
||||
- name: Playbook to enable DNS zone
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Enable zone.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
state: enabled
|
||||
```
|
||||
|
||||
|
||||
Example playbook to remove a zone:
|
||||
```yaml
|
||||
|
||||
---
|
||||
- name: Playbook to remove DNS zone
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Remove zone.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
state: absent
|
||||
|
||||
```
|
||||
|
||||
|
||||
Variables
|
||||
=========
|
||||
|
||||
ipadnszone
|
||||
----------
|
||||
|
||||
Variable | Description | Required
|
||||
-------- | ----------- | --------
|
||||
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`name` \| `zone_name` | The zone name string. | yes
|
||||
`forwarders` | The list of forwarders dicts. Each `forwarders` dict entry has:| no
|
||||
| `ip_address` - The IPv4 or IPv6 address of the DNS server. | yes
|
||||
| `port` - The custom port that should be used on this server. | no
|
||||
`forward_policy` | The global forwarding policy. It can be one of `only`, `first`, or `none`. | no
|
||||
`allow_sync_ptr` | Allow synchronization of forward (A, AAAA) and reverse (PTR) records (bool). | no
|
||||
`state` | The state to ensure. It can be one of `present`, `enabled`, `disabled` or `absent`, default: `present`. | yes
|
||||
`name_server`| Authoritative nameserver domain name | no
|
||||
`admin_email`| Administrator e-mail address | no
|
||||
`update_policy`| BIND update policy | no
|
||||
`dynamic_update` \| `dynamicupdate` | Allow dynamic updates | no
|
||||
`dnssec`| Allow inline DNSSEC signing of records in the zone | no
|
||||
`allow_transfer`| List of IP addresses or networks which are allowed to transfer the zone | no
|
||||
`allow_query`| List of IP addresses or networks which are allowed to issue queries | no
|
||||
`serial`| SOA record serial number | no
|
||||
`refresh`| SOA record refresh time | no
|
||||
`retry`| SOA record retry time | no
|
||||
`expire`| SOA record expire time | no
|
||||
`minimum`| How long should negative responses be cached | no
|
||||
`ttl`| Time to live for records at zone apex | no
|
||||
`default_ttl`| Time to live for records without explicit TTL definition | no
|
||||
`nsec3param_rec`| NSEC3PARAM record for zone in format: hash_algorithm flags iterations salt | no
|
||||
`skip_overlap_check`| Force DNS zone creation even if it will overlap with an existing zone | no
|
||||
`skip_nameserver_check` | Force DNS zone creation even if nameserver is not resolvable | no
|
||||
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Sergio Oliveira Campos
|
||||
@@ -143,6 +143,8 @@ Variable | Description | Required
|
||||
`user` | List of user name strings assigned to this group. | no
|
||||
`group` | List of group name strings assigned to this group. | no
|
||||
`service` | List of service name strings assigned to this group. Only usable with IPA versions 4.7 and up. | no
|
||||
`membermanager_user` | List of member manager users assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
|
||||
`membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
|
||||
`action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no
|
||||
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes
|
||||
|
||||
|
||||
@@ -138,9 +138,9 @@ Variable | Description | Required
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`name` \| `cn` | The list of hbacrule name strings. | yes
|
||||
`description` | The hbacrule description string. | no
|
||||
`usercategory` \| `usercat` | User category the rule applies to. Choices: ["all"] | no
|
||||
`hostcategory` \| `hostcat` | Host category the rule applies to. Choices: ["all"] | no
|
||||
`servicecategory` \| `servicecat` | HBAC service category the rule applies to. Choices: ["all"] | no
|
||||
`usercategory` \| `usercat` | User category the rule applies to. Choices: ["all", ""] | no
|
||||
`hostcategory` \| `hostcat` | Host category the rule applies to. Choices: ["all", ""] | no
|
||||
`servicecategory` \| `servicecat` | HBAC service category the rule applies to. Choices: ["all", ""] | no
|
||||
`nomembers` | Suppress processing of membership attributes. (bool) | no
|
||||
`host` | List of host name strings assigned to this hbacrule. | no
|
||||
`hostgroup` | List of host group name strings assigned to this hbacrule. | no
|
||||
|
||||
@@ -173,14 +173,14 @@ Example playbook to ensure host presence with a random password:
|
||||
name: host01.example.com
|
||||
random: yes
|
||||
force: yes
|
||||
update_password: on_create
|
||||
register: ipahost
|
||||
|
||||
- name: Print generated random password
|
||||
debug:
|
||||
var: ipahost.host.randompassword
|
||||
```
|
||||
Please remember that the `force` tag will also force the generation of a new random password even if the host already exists and if `update_password` is limited to `on_create`.
|
||||
|
||||
Please remember that a new random password will be generated for an existing but not enrolled host if `update_password` is not limited to `on_create`. For an already enrolled host the task will fail with `update_password` default setting `always`.
|
||||
|
||||
Example playbook to ensure presence of several hosts with a random password:
|
||||
|
||||
@@ -198,9 +198,11 @@ Example playbook to ensure presence of several hosts with a random password:
|
||||
- name: host01.example.com
|
||||
random: yes
|
||||
force: yes
|
||||
update_password: on_create
|
||||
- name: host02.example.com
|
||||
random: yes
|
||||
force: yes
|
||||
update_password: on_create
|
||||
register: ipahost
|
||||
|
||||
- name: Print generated random password for host01.example.com
|
||||
@@ -211,7 +213,7 @@ Example playbook to ensure presence of several hosts with a random password:
|
||||
debug:
|
||||
var: ipahost.host["host02.example.com"].randompassword
|
||||
```
|
||||
Please remember that the `force` tag will also force the generation of a new random password even if the host alreay exists and if `update_password` is limited to `on_create`.
|
||||
Please remember that a new random password will be generated for an existing but not enrolled host if `update_password` is not limited to `on_create`. For an already enrolled host the task will fail with `update_password` default setting `always`.
|
||||
|
||||
|
||||
Example playbook to ensure presence of host member principal:
|
||||
@@ -337,8 +339,8 @@ Variable | Description | Required
|
||||
`location` \| `ns_host_location` | Host location (e.g. "Lab 2"). | no
|
||||
`platform` \| `ns_hardware_platform` | Host hardware platform (e.g. "Lenovo T61"). | no
|
||||
`os` \| `ns_os_version` | Host operating system and version (e.g. "Fedora 9"). | no
|
||||
`password` \| `user_password` \| `userpassword` | Password used in bulk enrollment. | no
|
||||
`random` \| `random_password` | Initiate the generation of a random password to be used in bulk enrollment. | no
|
||||
`password` \| `user_password` \| `userpassword` | Password used in bulk enrollment for absent or not enrolled hosts. | no
|
||||
`random` \| `random_password` | Initiate the generation of a random password to be used in bulk enrollment for absent or not enrolled hosts. | no
|
||||
`certificate` \| `usercertificate` | List of base-64 encoded host certificates | no
|
||||
`managedby` \| `principalname` \| `krbprincipalname` | List of hosts that can manage this host | no
|
||||
`principal` \| `principalname` \| `krbprincipalname` | List of principal aliases for this host | no
|
||||
|
||||
@@ -137,6 +137,8 @@ Variable | Description | Required
|
||||
`nomembers` | Suppress processing of membership attributes. (bool) | no
|
||||
`host` | List of host name strings assigned to this hostgroup. | no
|
||||
`hostgroup` | List of hostgroup name strings assigned to this hostgroup. | no
|
||||
`membermanager_user` | List of member manager users assigned to this hostgroup. Only usable with IPA versions 4.8.4 and up. | no
|
||||
`membermanager_group` | List of member manager groups assigned to this hostgroup. Only usable with IPA versions 4.8.4 and up. | no
|
||||
`action` | Work on hostgroup or member level. It can be on of `member` or `hostgroup` and defaults to `hostgroup`. | no
|
||||
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | no
|
||||
|
||||
|
||||
@@ -310,6 +310,7 @@ Variable | Description | Required
|
||||
`allow_retrieve_keytab_group` \| `ipaallowedtoperform_read_keys_group` | Groups allowed to retrieve a keytab of this host. | no
|
||||
`allow_retrieve_keytab_host` \| `ipaallowedtoperform_read_keys_host` | Hosts allowed to retrieve a keytab from of host. | no
|
||||
`allow_retrieve_keytab_hostgroup` \| `ipaallowedtoperform_read_keys_hostgroup` | Host groups allowed to retrieve a keytab of this host. | no
|
||||
`continue` | Continuous mode: don't stop on errors. Valid only if `state` is `absent`. Default: `no` (bool) | no
|
||||
`action` | Work on service or member level. It can be on of `member` or `service` and defaults to `service`. | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent`, or `disabled`, default: `present`. | no
|
||||
|
||||
|
||||
@@ -122,11 +122,11 @@ Variable | Description | Required
|
||||
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
|
||||
`name` \| `cn` | The list of sudorule name strings. | yes
|
||||
`description` | The sudorule description string. | no
|
||||
`usercategory` | User category the rule applies to. Choices: ["all"] | no
|
||||
`hostcategory` | Host category the rule applies to. Choices: ["all"] | no
|
||||
`cmdcategory` | Command category the rule applies to. Choices: ["all"] | no
|
||||
`runasusercategory` | RunAs User category the rule applies to. Choices: ["all"] | no
|
||||
`runasgroupcategory` | RunAs Group category the rule applies to. Choices: ["all"] | no
|
||||
`usercategory` \| `usercat` | User category the rule applies to. Choices: ["all", ""] | no
|
||||
`hostcategory` \| `hostcat` | Host category the rule applies to. Choices: ["all", ""] | no
|
||||
`cmdcategory` \| `cmdcat` | Command category the rule applies to. Choices: ["all", ""] | no
|
||||
`runasusercategory` \| `rusasusercat` | RunAs User category the rule applies to. Choices: ["all", ""] | no
|
||||
`runasgroupcategory` \| `runasgroupcat` | RunAs Group category the rule applies to. Choices: ["all", ""] | no
|
||||
`nomembers` | Suppress processing of membership attributes. (bool) | no
|
||||
`host` | List of host name strings assigned to this sudorule. | no
|
||||
`hostgroup` | List of host group name strings assigned to this sudorule. | no
|
||||
|
||||
@@ -417,10 +417,11 @@ Variable | Description | Required
|
||||
`employeetype` | Employee Type | no
|
||||
`preferredlanguage` | Preferred Language | no
|
||||
`certificate` | List of base-64 encoded user certificates. | no
|
||||
`certmapdata` | List of certificate mappings. Either `certificate` or `issuer` together with `subject` need to be specified. <br>Options: | no
|
||||
| `certificate` - Base-64 encoded user certificate | no
|
||||
| `issuer` - Issuer of the certificate | no
|
||||
| `subject` - Subject of the certificate | no
|
||||
`certmapdata` | List of certificate mappings. Either `data` or `certificate` or `issuer` together with `subject` need to be specified. Only usable with IPA versions 4.5 and up. <br>Options: | no
|
||||
| `certificate` - Base-64 encoded user certificate, not usable with other certmapdata options. | no
|
||||
| `issuer` - Issuer of the certificate, only usable together with `usbject` option. | no
|
||||
| `subject` - Subject of the certificate, only usable together with `issuer` option. | no
|
||||
| `data` - Certmap data, not usable with other certmapdata options. | no
|
||||
`noprivate` | Do not create user private group. (bool) | no
|
||||
`nomembers` | Suppress processing of membership attributes. (bool) | no
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ Example inventory file
|
||||
ipaserver.test.local
|
||||
```
|
||||
|
||||
Example playbook to make sure vault is present:
|
||||
Example playbook to make sure vault is present (by default, vault type is `symmetric`):
|
||||
|
||||
```yaml
|
||||
---
|
||||
@@ -53,8 +53,7 @@ Example playbook to make sure vault is present:
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
vault_password: MyVaultPassword123
|
||||
password: SomeVAULTpassword
|
||||
description: A standard private vault.
|
||||
```
|
||||
|
||||
@@ -124,13 +123,30 @@ Example playbook to make sure vault data is present in a symmetric vault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
vault_password: MyVaultPassword123
|
||||
vault_data: >
|
||||
password: SomeVAULTpassword
|
||||
data: >
|
||||
Data archived.
|
||||
More data archived.
|
||||
action: member
|
||||
```
|
||||
|
||||
Example playbook to retrieve vault data from a symmetric vault:
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Playbook to handle vaults
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
password: SomeVAULTpassword
|
||||
state: retrieved
|
||||
```
|
||||
|
||||
Example playbook to make sure vault data is absent in a symmetric vault:
|
||||
|
||||
```yaml
|
||||
@@ -144,7 +160,7 @@ Example playbook to make sure vault data is absent in a symmetric vault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
vault_password: MyVaultPassword123
|
||||
password: SomeVAULTpassword
|
||||
action: member
|
||||
state: absent
|
||||
```
|
||||
@@ -163,6 +179,9 @@ Example playbook to make sure vault is absent:
|
||||
name: symvault
|
||||
username: admin
|
||||
state: absent
|
||||
register: result
|
||||
- debug:
|
||||
msg: "{{ result.data }}"
|
||||
```
|
||||
|
||||
Variables
|
||||
@@ -178,17 +197,37 @@ Variable | Description | Required
|
||||
`name` \| `cn` | The list of vault name strings. | yes
|
||||
`description` | The vault description string. | no
|
||||
`nomembers` | Suppress processing of membership attributes. (bool) | no
|
||||
`vault_public_key` \| `ipavaultpublickey` | Vault public key. | no
|
||||
`vault_salt` \| `ipavaultsalt` | Vault salt. | no
|
||||
`password ` \| `vault_password` \| `ipavaultpassword` | Vault password. | no
|
||||
`public_key ` \| `vault_public_key` \| `ipavaultpublickey` | Base64 encoded vault public key. | no
|
||||
`public_key_file` \| `vault_public_key_file` | Path to file with public key. | no
|
||||
`private_key `\| `vault_private_key` | Base64 encoded vault private key. Used only to retrieve data. | no
|
||||
`private_key_file` \| `vault_private_key_file` | Path to file with private key. Used only to retrieve data. | no
|
||||
`salt` \| `vault_salt` \| `ipavaultsalt` | Vault salt. | no
|
||||
`vault_type` \| `ipavaulttype` | Vault types are based on security level. It can be one of `standard`, `symmetric` or `asymmetric`, default: `symmetric` | no
|
||||
`user` \| `username` | Any user can own one or more user vaults. | no
|
||||
`service` | Any service can own one or more service vaults. | no
|
||||
`user` | Any user can own one or more user vaults. | no
|
||||
`shared` | Vault is shared. Default to false. (bool) | no
|
||||
`users` | Users that are members of the vault. | no
|
||||
`groups` | Groups that are member of the vault. | no
|
||||
`vault_data` \| `ipavaultdata` | Data to be stored in the vault. | no
|
||||
`services` | Services that are member of the vault. | no
|
||||
`data` \|`vault_data` \| `ipavaultdata` | Data to be stored in the vault. | no
|
||||
`in` \| `datafile_in` | Path to file with data to be stored in the vault. | no
|
||||
`out` \| `datafile_out` | Path to file to store data retrieved from the vault. | no
|
||||
`action` | Work on vault or member level. It can be on of `member` or `vault` and defaults to `vault`. | no
|
||||
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | no
|
||||
`state` | The state to ensure. It can be one of `present`, `absent` or `retrieved`, default: `present`. | no
|
||||
|
||||
|
||||
Return Values
|
||||
=============
|
||||
|
||||
ipavault
|
||||
--------
|
||||
|
||||
There is only a return value if `state` is `retrieved`.
|
||||
|
||||
Variable | Description | Returned When
|
||||
-------- | ----------- | -------------
|
||||
`data` | The data stored in the vault. | If `state` is `retrieved`.
|
||||
|
||||
|
||||
Notes
|
||||
|
||||
@@ -11,6 +11,9 @@ Features
|
||||
* Cluster deployments: Server, replicas and clients in one playbook
|
||||
* One-time-password (OTP) support for client installation
|
||||
* Repair mode for clients
|
||||
* Modules for dns forwarder management
|
||||
* Modules for dns record management
|
||||
* Modules for dns zone management
|
||||
* Modules for group management
|
||||
* Modules for hbacrule management
|
||||
* Modules for hbacsvc management
|
||||
@@ -408,6 +411,9 @@ Modules in plugin/modules
|
||||
=========================
|
||||
|
||||
* [ipadnsconfig](README-dnsconfig.md)
|
||||
* [ipadnsforwardzone](README-dnsforwardzone.md)
|
||||
* [ipadnsrecord](README-dnsrecord.md)
|
||||
* [ipadnszone](README-dnszone.md)
|
||||
* [ipagroup](README-group.md)
|
||||
* [ipahbacrule](README-hbacrule.md)
|
||||
* [ipahbacsvc](README-hbacsvc.md)
|
||||
@@ -423,3 +429,5 @@ Modules in plugin/modules
|
||||
* [ipatopologysuffix](README-topology.md)
|
||||
* [ipauser](README-user.md)
|
||||
* [ipavault](README-vault.md)
|
||||
|
||||
If you want to write a new module please read [writing a new module](plugins/modules/README.md).
|
||||
|
||||
22
azure-pipelines.yml
Normal file
22
azure-pipelines.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
trigger:
|
||||
- master
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '3.6'
|
||||
|
||||
- script: python -m pip install --upgrade pip setuptools wheel
|
||||
displayName: Install tools
|
||||
|
||||
- script: pip install pydocstyle flake8
|
||||
displayName: Install dependencies
|
||||
|
||||
- script: flake8 .
|
||||
displayName: Run flake8 checks
|
||||
|
||||
- script: pydocstyle .
|
||||
displayName: Verify docstings
|
||||
@@ -13,11 +13,11 @@ issues: "https://github.com/freeipa/ansible-freeipa/issues"
|
||||
|
||||
readme: "README.md"
|
||||
license: "GPL-3.0-or-later"
|
||||
license_file: "COPYING"
|
||||
|
||||
dependencies:
|
||||
|
||||
tags:
|
||||
- "system"
|
||||
- "identity"
|
||||
- "ipa"
|
||||
- "freeipa"
|
||||
|
||||
14
playbooks/config/retrieve-config.yml
Normal file
14
playbooks/config/retrieve-config.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
- name: Playbook to handle global DNS configuration
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: Query IPA global configuration
|
||||
ipaconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
register: serverconfig
|
||||
|
||||
- debug:
|
||||
msg: "{{ serverconfig }}"
|
||||
11
playbooks/config/set-ca-renewal-master-server.yml
Normal file
11
playbooks/config/set-ca-renewal-master-server.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Playbook to handle global DNS configuration
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: set ca_renewal_master_server
|
||||
ipaconfig:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
ca_renewal_master_server: carenewal.example.com
|
||||
18
playbooks/dnsrecord/ensure-A-and-AAAA-records-are-absent.yml
Normal file
18
playbooks/dnsrecord/ensure-A-and-AAAA-records-are-absent.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: Test PTR Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure a PTR record is present
|
||||
- name: Ensure that 'host04' has A and AAAA records.
|
||||
ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: ipatest.local
|
||||
records:
|
||||
- name: host04
|
||||
a_ip_address: 192.168.122.104
|
||||
- name: host04
|
||||
aaaa_ip_address: ::1
|
||||
state: absent
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Test PTR Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure a PTR record is present
|
||||
- name: Ensure that 'host04' has A and AAAA records.
|
||||
ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: ipatest.local
|
||||
records:
|
||||
- name: host04
|
||||
a_ip_address: 192.168.122.104
|
||||
- name: host04
|
||||
aaaa_ip_address: ::1
|
||||
13
playbooks/dnsrecord/ensure-CNAME-record-is-absent.yml
Normal file
13
playbooks/dnsrecord/ensure-CNAME-record-is-absent.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Test CNAME Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure that 'host04' has CNAME, with cname_hostname
|
||||
- ipadnsrecord:
|
||||
zone_name: example.com
|
||||
name: host04
|
||||
cname_hostname: host04.example.com
|
||||
state: absent
|
||||
12
playbooks/dnsrecord/ensure-CNAME-record-is-present.yml
Normal file
12
playbooks/dnsrecord/ensure-CNAME-record-is-present.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: Test CNAME Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure that 'host04' has CNAME, with cname_hostname
|
||||
- ipadnsrecord:
|
||||
zone_name: example.com
|
||||
name: host04
|
||||
cname_hostname: host04.example.com
|
||||
15
playbooks/dnsrecord/ensure-MX-record-is-present.yml
Normal file
15
playbooks/dnsrecord/ensure-MX-record-is-present.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Ensure MX Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure an MX record is absent
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: '@'
|
||||
record_type: 'MX'
|
||||
record_value: '1 mailserver.example.com'
|
||||
zone_name: example.com
|
||||
state: present
|
||||
15
playbooks/dnsrecord/ensure-PTR-record-is-present.yml
Normal file
15
playbooks/dnsrecord/ensure-PTR-record-is-present.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Test PTR Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure a PTR record is present
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: 5
|
||||
record_type: 'PTR'
|
||||
record_value: 'internal.ipa.example.com'
|
||||
zone_name: 2.168.192.in-addr.arpa
|
||||
state: present
|
||||
15
playbooks/dnsrecord/ensure-SRV-record-is-present.yml
Normal file
15
playbooks/dnsrecord/ensure-SRV-record-is-present.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Test SRV Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure a SRV record is present
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: _kerberos._udp.example.com
|
||||
record_type: 'SRV'
|
||||
record_value: '10 50 88 ipa.example.com'
|
||||
zone_name: example.com
|
||||
state: present
|
||||
16
playbooks/dnsrecord/ensure-SSHFP-record-is-present.yml
Normal file
16
playbooks/dnsrecord/ensure-SSHFP-record-is-present.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
- name: Test SSHFP Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure a SSHFP record is present
|
||||
# SSHFP fingerprint generated with `ssh-keygen -r host04.testzone.local`
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
name: host04
|
||||
sshfp_algorithm: 1
|
||||
sshfp_fp_type: 1
|
||||
sshfp_fingerprint: d21802c61733e055b8d16296cbce300efb8a167a
|
||||
16
playbooks/dnsrecord/ensure-TLSA-record-is-present.yml
Normal file
16
playbooks/dnsrecord/ensure-TLSA-record-is-present.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
- name: Test SSHFP Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure a SSHFP record is present
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: example.com
|
||||
name: host04
|
||||
tlsa_cert_usage: 3
|
||||
tlsa_selector: 1
|
||||
tlsa_matching_type: 1
|
||||
tlsa_cert_association_data: 9c0ad776dbeae8d9d55b0ad42899d30235c114d5f918fd69746e4279e47bdaa2
|
||||
15
playbooks/dnsrecord/ensure-TXT-record-is-present.yml
Normal file
15
playbooks/dnsrecord/ensure-TXT-record-is-present.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Test TXT Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure a TXT record is absent
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: _kerberos
|
||||
record_type: 'TXT'
|
||||
record_value: 'EXAMPLE.COM'
|
||||
zone_name: example.com
|
||||
state: present
|
||||
17
playbooks/dnsrecord/ensure-URI-record-is-present.yml
Normal file
17
playbooks/dnsrecord/ensure-URI-record-is-present.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Test URI Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure a URI record is absent
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: _ftp._tcp
|
||||
record_type: 'URI'
|
||||
uri_priority: 10
|
||||
uri_weight: 1
|
||||
uri_target: ftp://ftp.example.com/public
|
||||
zone_name: example.com
|
||||
state: present
|
||||
15
playbooks/dnsrecord/ensure-dnsrecord-is-absent.yml
Normal file
15
playbooks/dnsrecord/ensure-dnsrecord-is-absent.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Test DNS Record is absent.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure that dns record is absent
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host01
|
||||
zone_name: example.com
|
||||
record_type: 'AAAA'
|
||||
record_value: '::1'
|
||||
state: absent
|
||||
15
playbooks/dnsrecord/ensure-dnsrecord-is-present.yml
Normal file
15
playbooks/dnsrecord/ensure-dnsrecord-is-present.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Test DNS Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure that dns record is present
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host01
|
||||
zone_name: example.com
|
||||
record_type: 'AAAA'
|
||||
record_value: '::1'
|
||||
state: present
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Test DNS Record is present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure that dns record is present
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: host01
|
||||
zone_name: example.com
|
||||
ip_address: 192.160.123.45
|
||||
create_reverse: yes
|
||||
state: present
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Playbook to manage DNS records.
|
||||
hosts: ipaserver
|
||||
become: yes
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: Ensure that 'host04' has multiple A records.
|
||||
ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
zone_name: ipatest.local
|
||||
name: host01
|
||||
a_rec:
|
||||
- 192.168.122.221
|
||||
- 192.168.122.222
|
||||
- 192.168.122.223
|
||||
- 192.168.122.224
|
||||
21
playbooks/dnsrecord/ensure-presence-multiple-records.yml
Normal file
21
playbooks/dnsrecord/ensure-presence-multiple-records.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: Test multiple DNS Records are present.
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
tasks:
|
||||
# Ensure that multiple dns records are present
|
||||
- ipadnsrecord:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
records:
|
||||
- name: host01
|
||||
zone_name: example.com
|
||||
record_type: A
|
||||
record_value:
|
||||
- 192.168.122.112
|
||||
- 192.168.122.122
|
||||
- name: host01
|
||||
zone_name: testzone.local
|
||||
record_type: AAAA
|
||||
record_value: ::1
|
||||
11
playbooks/dnszone/disable-zone-forwarders.yml
Normal file
11
playbooks/dnszone/disable-zone-forwarders.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Playbook to disable DNS zone forwarders
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Disable zone forwarders.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
forward_policy: none
|
||||
11
playbooks/dnszone/dnszone-absent.yml
Normal file
11
playbooks/dnszone/dnszone-absent.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Playbook to ensure DNS zone is absent
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Remove zone.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
state: absent
|
||||
35
playbooks/dnszone/dnszone-all-params.yml
Normal file
35
playbooks/dnszone/dnszone-all-params.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
- name: dnszone present
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Ensure zone is present.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
allow_sync_ptr: true
|
||||
dynamic_update: true
|
||||
dnssec: true
|
||||
allow_transfer:
|
||||
- 1.1.1.1
|
||||
- 2.2.2.2
|
||||
allow_query:
|
||||
- 1.1.1.1
|
||||
- 2.2.2.2
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
- ip_address: 8.8.4.4
|
||||
port: 52
|
||||
#serial: 1234
|
||||
refresh: 3600
|
||||
retry: 900
|
||||
expire: 1209600
|
||||
minimum: 3600
|
||||
ttl: 60
|
||||
default_ttl: 90
|
||||
name_server: ipaserver.test.local.
|
||||
admin_email: admin.admin@example.com
|
||||
nsec3param_rec: "1 7 100 0123456789abcdef"
|
||||
skip_overlap_check: true
|
||||
skip_nameserver_check: true
|
||||
state: present
|
||||
11
playbooks/dnszone/dnszone-disable.yml
Normal file
11
playbooks/dnszone/dnszone-disable.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Playbook to disable DNS zone
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Disable zone.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
state: disabled
|
||||
11
playbooks/dnszone/dnszone-enable.yml
Normal file
11
playbooks/dnszone/dnszone-enable.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Playbook to enable DNS zone
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Enable zone.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
state: enabled
|
||||
10
playbooks/dnszone/dnszone-present.yml
Normal file
10
playbooks/dnszone/dnszone-present.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
- name: dnszone present
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Ensure zone is present.
|
||||
ipadnszone:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: testzone.local
|
||||
state: present
|
||||
18
playbooks/vault/change-password-symmetric-vault.yml
Normal file
18
playbooks/vault/change-password-symmetric-vault.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
- name: Playbook to change password of symmetric vault.
|
||||
hosts: ipaserver
|
||||
become: yes
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: Create vault.
|
||||
ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
password: SomeVAULTpassword
|
||||
- name: Change vault passord.
|
||||
ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
password: SomeVAULTpassword
|
||||
new_password: SomeNEWpassword
|
||||
@@ -9,6 +9,6 @@
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
vault_password: MyVaultPassword123
|
||||
vault_password: SomeVAULTpassword
|
||||
vault_data: The world of π is half rounded.
|
||||
action: member
|
||||
|
||||
@@ -9,5 +9,4 @@
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
vault_password: MyVaultPassword123
|
||||
vault_type: symmetric
|
||||
vault_password: SomeVAULTpassword
|
||||
|
||||
1
playbooks/vault/password.txt
Normal file
1
playbooks/vault/password.txt
Normal file
@@ -0,0 +1 @@
|
||||
SomeVAULTpassword
|
||||
27
playbooks/vault/private.pem
Normal file
27
playbooks/vault/private.pem
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEArM5/f6dd/YIm/a9eoGVTW8jobEgrf9PXRA3aHsA7kJo6fB18
|
||||
HD4+RVUwx/lqlkPYbUi9bXV/rJAkUwAEDOnJeqXESZ+gVCVmigRzmKWK2ad9agmY
|
||||
SiqyyNxFIJvZAo0dG4CAWjYK27tLg4Ih6oGsZIDG+WVES5W89K+L0bwVjq4tshhe
|
||||
DMO57unvmIKEmaBE0ewPfvkdZh5k8Gts9H4fh0fGk5tbIYa0bhwMUpL+WHOm6nbd
|
||||
+n7BbaVc820TgZDO/rSYtnuXaIc6Wx0U9LXZkUmk3apMnzknNaTqguAQdTn79G8P
|
||||
qrGqmyWd/E1cH2b5jzIxiGo8psL5sxWVY7WJdwIDAQABAoIBAA6e9iit14UAgx4J
|
||||
vX7is9fbOtcWkB+jo94NMfxSFXgZpIMl139oQMqK97KjxsHqAaDVe7mMLH5EP96J
|
||||
7M3O5g4rgl0cVWtpMrDQyZsLvqDFzBWxtCHqVPAruumUZhsSJ3lROQro8ag/w5bf
|
||||
5tC5ogVq4+rsB4hBphgp1jGrsUM+E8O7DXXFH68F8WgBi725WvcjnbI9irkb0Gcq
|
||||
1bCPJwN3fA1i2VWiRwVYWbNTWnDoNM9ZdYYxK0kuUkD+QtreycWPf9V49lvUi1Vp
|
||||
FVNmBUDvGK3K1MwbgXRwOXhacY7Ptjkdvaeb2Qcu5RjTkruGhzUYsOP3p/cw+wKV
|
||||
vzQqceECgYEA5Wz7V2SlRa2r//z+ETQkJfENJ0KDnCb0pMClCQh3jTNPA6DbhiMk
|
||||
FTkcoNbqcpTiVSlvhh6TKscSgqYQUjQ/OqyG7SkjKVjQ72j5beQLxiLTtUyj1OmP
|
||||
Xh9cWJXx8iQ+45cPon+kMOAIiTwiB3mmFRfQjIGve1DPUo9J+NZ4XdECgYEAwNKg
|
||||
OdGYxxKtCrXVz1mdg6PDlV8qh7nxxZbPch+aMIQl1+oTCgSiw8oOYEd8g0HOdV6t
|
||||
1G+IWhvPxiiWy3/AE0QhgoKk2GUsSjWSMLcJbaUzDoEHFjTLjecRlqdzo7qxRXqB
|
||||
meN4L5WJYKnLC482K7hvufS+uo5fB5qwPmt13McCgYAe4TVPRP+tyjttYCr+O8tl
|
||||
w/UmRKCcQu4Iwtkzxwz4V2CaN2t0uYQgyygcSfESbRGtrr8RCUp7poHKTfnCZr/f
|
||||
8NrUTwYpiYfNwY5ZCSnAiG2AaIlgnfMrEwOF9OC028YPMgTrtUxvO6hKeGqIIQqG
|
||||
qkbqsoXhDjZpgVnOgWeAEQKBgGuiZ0w/IqAlXbC31fUb2iBMfvXXnJ8M/dfFGmFj
|
||||
IKfqbFF9WUljUxQlqya1YNzIFB5STohiBeP+2FmN+Lb5xdc7VdVLZgdhWnrGMqe8
|
||||
1Kd+6uQyxCjyKZo5nQjSymtf4GqfOs8TOdieCYSK40u9koiPONa9tuXeaU+OWslN
|
||||
JQqrAoGBAJ3MKOvsnQzuZVP2vz0ZqLwIE3XjRiFGveVpizq4hwOVeuNsV08JvA0t
|
||||
pueNIy9klPScFc9OUdiZWkEX09BwJkVIrOHotuSB8AStO5UAntNnuyWLJEFC4Uq4
|
||||
GpB8lbj9jkxSKaU7X3Gac23K9JL8euLh7E7rPuZRYa6mYN4nbKqu
|
||||
-----END RSA PRIVATE KEY-----
|
||||
9
playbooks/vault/public.pem
Normal file
9
playbooks/vault/public.pem
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArM5/f6dd/YIm/a9eoGVT
|
||||
W8jobEgrf9PXRA3aHsA7kJo6fB18HD4+RVUwx/lqlkPYbUi9bXV/rJAkUwAEDOnJ
|
||||
eqXESZ+gVCVmigRzmKWK2ad9agmYSiqyyNxFIJvZAo0dG4CAWjYK27tLg4Ih6oGs
|
||||
ZIDG+WVES5W89K+L0bwVjq4tshheDMO57unvmIKEmaBE0ewPfvkdZh5k8Gts9H4f
|
||||
h0fGk5tbIYa0bhwMUpL+WHOm6nbd+n7BbaVc820TgZDO/rSYtnuXaIc6Wx0U9LXZ
|
||||
kUmk3apMnzknNaTqguAQdTn79G8PqrGqmyWd/E1cH2b5jzIxiGo8psL5sxWVY7WJ
|
||||
dwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
17
playbooks/vault/retrive-data-asymmetric-vault.yml
Normal file
17
playbooks/vault/retrive-data-asymmetric-vault.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Tests
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: Retrieve data from assymetric vault with a private key file.
|
||||
ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: asymvault
|
||||
username: user01
|
||||
private_key_file: private.pem
|
||||
state: retrieved
|
||||
register: result
|
||||
- debug:
|
||||
msg: "Data: {{ result.data }}"
|
||||
17
playbooks/vault/retrive-data-symmetric-vault.yml
Normal file
17
playbooks/vault/retrive-data-symmetric-vault.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Tests
|
||||
hosts: ipaserver
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
tasks:
|
||||
- name: Retrieve data from symmetric vault.
|
||||
ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
password: SomeVAULTpassword
|
||||
state: retrieved
|
||||
register: result
|
||||
- debug:
|
||||
msg: "{{ result.data | b64decode }}"
|
||||
22
playbooks/vault/vault-is-present-with-password-file.yml
Normal file
22
playbooks/vault/vault-is-present-with-password-file.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
- name: Tests
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: True
|
||||
|
||||
tasks:
|
||||
- copy:
|
||||
src: "{{ playbook_dir }}/password.txt"
|
||||
dest: "{{ ansible_env.HOME }}/password.txt"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: 0600
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
vault_type: symmetric
|
||||
vault_password_file: "{{ ansible_env.HOME }}/password.txt"
|
||||
- file:
|
||||
path: "{{ ansible_env.HOME }}/password.txt"
|
||||
state: absent
|
||||
27
playbooks/vault/vault-is-present-with-public-key-file.yml
Normal file
27
playbooks/vault/vault-is-present-with-public-key-file.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
#
|
||||
# Example keys for this playbook were generated with the commands:
|
||||
# $ openssl genrsa -out private.pem 2048
|
||||
# $ openssl rsa -in private.pem -pubout > public.pem
|
||||
#
|
||||
- name: Tests
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
gather_facts: True
|
||||
|
||||
tasks:
|
||||
- copy:
|
||||
src: "{{ playbook_dir }}/public.pem"
|
||||
dest: "{{ ansible_env.HOME }}/public.pem"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
mode: 0600
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: asymvault
|
||||
username: admin
|
||||
vault_type: asymmetric
|
||||
vault_public_key_file: "{{ ansible_env.HOME }}/public.pem"
|
||||
- file:
|
||||
path: "{{ ansible_env.HOME }}/public.pem"
|
||||
state: absent
|
||||
@@ -2,6 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Sergio Oliveira Campos <seocam@redhat.com>
|
||||
# Thomas Woerner <twoerner@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2019 Red Hat
|
||||
@@ -27,35 +28,50 @@ import tempfile
|
||||
import shutil
|
||||
import gssapi
|
||||
from datetime import datetime
|
||||
from pprint import pformat
|
||||
from ipalib import api
|
||||
from ipalib import errors as ipalib_errors
|
||||
from ipalib import errors as ipalib_errors # noqa
|
||||
from ipalib.config import Env
|
||||
from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
|
||||
|
||||
try:
|
||||
from ipalib.install.kinit import kinit_password, kinit_keytab
|
||||
except ImportError:
|
||||
from ipapython.ipautil import kinit_password, kinit_keytab
|
||||
from ipapython.ipautil import run
|
||||
from ipapython.dn import DN
|
||||
from ipaplatform.paths import paths
|
||||
from ipalib.krb_utils import get_credentials_if_valid
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_text
|
||||
|
||||
try:
|
||||
from ipalib.x509 import Encoding
|
||||
except ImportError:
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
|
||||
try:
|
||||
from ipalib.x509 import load_pem_x509_certificate
|
||||
except ImportError:
|
||||
from ipalib.x509 import load_certificate
|
||||
load_pem_x509_certificate = None
|
||||
|
||||
import socket
|
||||
import base64
|
||||
import six
|
||||
|
||||
try:
|
||||
from collections.abc import Mapping # noqa
|
||||
except ImportError:
|
||||
from collections import Mapping # noqa
|
||||
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
|
||||
def valid_creds(module, principal):
|
||||
"""
|
||||
Get valid credintials matching the princial, try GSSAPI first
|
||||
"""
|
||||
def valid_creds(module, principal): # noqa
|
||||
"""Get valid credentials matching the princial, try GSSAPI first."""
|
||||
if "KRB5CCNAME" in os.environ:
|
||||
ccache = os.environ["KRB5CCNAME"]
|
||||
module.debug('KRB5CCNAME set to %s' % ccache)
|
||||
@@ -93,9 +109,7 @@ def valid_creds(module, principal):
|
||||
|
||||
|
||||
def temp_kinit(principal, password):
|
||||
"""
|
||||
kinit with password using a temporary ccache
|
||||
"""
|
||||
"""Kinit with password using a temporary ccache."""
|
||||
if not password:
|
||||
raise RuntimeError("The password is not set")
|
||||
if not principal:
|
||||
@@ -109,22 +123,27 @@ def temp_kinit(principal, password):
|
||||
except RuntimeError as e:
|
||||
raise RuntimeError("Kerberos authentication failed: {}".format(e))
|
||||
|
||||
os.environ["KRB5CCNAME"] = ccache_name
|
||||
return ccache_dir, ccache_name
|
||||
|
||||
|
||||
def temp_kdestroy(ccache_dir, ccache_name):
|
||||
"""
|
||||
Destroy temporary ticket and remove temporary ccache
|
||||
"""
|
||||
"""Destroy temporary ticket and remove temporary ccache."""
|
||||
if ccache_name is not None:
|
||||
run([paths.KDESTROY, '-c', ccache_name], raiseonerr=False)
|
||||
del os.environ['KRB5CCNAME']
|
||||
if ccache_dir is not None:
|
||||
shutil.rmtree(ccache_dir, ignore_errors=True)
|
||||
|
||||
|
||||
def api_connect(context=None):
|
||||
"""
|
||||
Create environment, initialize api and connect to ldap2
|
||||
Initialize IPA API with the provided context.
|
||||
|
||||
`context` can be any of:
|
||||
* `server` (default)
|
||||
* `ansible-freeipa`
|
||||
* `cli_installer`
|
||||
"""
|
||||
env = Env()
|
||||
env._bootstrap()
|
||||
@@ -143,32 +162,33 @@ def api_connect(context=None):
|
||||
backend = api.Backend.rpcclient
|
||||
|
||||
if not backend.isconnected():
|
||||
backend.connect()
|
||||
backend.connect(ccache=os.environ.get('KRB5CCNAME', None))
|
||||
|
||||
|
||||
def api_command(module, command, name, args):
|
||||
"""
|
||||
Call ipa.Command
|
||||
"""
|
||||
"""Call ipa.Command."""
|
||||
return api.Command[command](name, **args)
|
||||
|
||||
|
||||
def api_command_no_name(module, command, args):
|
||||
"""
|
||||
Call ipa.Command without a name.
|
||||
"""
|
||||
"""Call ipa.Command without a name."""
|
||||
return api.Command[command](**args)
|
||||
|
||||
|
||||
def api_check_command(command):
|
||||
"""Return if command exists in command list."""
|
||||
return command in api.Command
|
||||
|
||||
|
||||
def api_check_param(command, name):
|
||||
"""
|
||||
Return if param exists in command param list
|
||||
"""
|
||||
"""Check if param exists in command param list."""
|
||||
return name in api.Command[command].params
|
||||
|
||||
|
||||
def execute_api_command(module, principal, password, command, name, args):
|
||||
"""
|
||||
Execute an API command.
|
||||
|
||||
Get KRB ticket if not already there, initialize api, connect,
|
||||
execute command and destroy ticket again if it has been created also.
|
||||
"""
|
||||
@@ -205,9 +225,24 @@ def date_format(value):
|
||||
raise ValueError("Invalid date '%s'" % value)
|
||||
|
||||
|
||||
def compare_args_ipa(module, args, ipa):
|
||||
def compare_args_ipa(module, args, ipa): # noqa
|
||||
"""Compare IPA obj attrs with the command args.
|
||||
|
||||
This function compares IPA objects attributes with the args the
|
||||
module is intending to use to call a command. This is useful to know
|
||||
if call to IPA server will be needed or not.
|
||||
In other to compare we have to prepare the perform slight changes in
|
||||
data formats.
|
||||
|
||||
Returns True if they are the same and False otherwise.
|
||||
"""
|
||||
base_debug_msg = "Ansible arguments and IPA commands differed. "
|
||||
|
||||
for key in args.keys():
|
||||
if key not in ipa:
|
||||
module.debug(
|
||||
base_debug_msg + "Command key not present in IPA: %s" % key
|
||||
)
|
||||
return False
|
||||
else:
|
||||
arg = args[key]
|
||||
@@ -220,25 +255,35 @@ def compare_args_ipa(module, args, ipa):
|
||||
if isinstance(ipa_arg, list):
|
||||
if not isinstance(arg, list):
|
||||
arg = [arg]
|
||||
if len(ipa_arg) != len(arg):
|
||||
module.debug(
|
||||
base_debug_msg
|
||||
+ "List length doesn't match for key %s: %d %d"
|
||||
% (key, len(arg), len(ipa_arg),)
|
||||
)
|
||||
return False
|
||||
if isinstance(ipa_arg[0], str) and isinstance(arg[0], int):
|
||||
arg = [to_text(_arg) for _arg in arg]
|
||||
if isinstance(ipa_arg[0], unicode) and isinstance(arg[0], int):
|
||||
arg = [to_text(_arg) for _arg in arg]
|
||||
# module.warn("%s <=> %s" % (repr(arg), repr(ipa_arg)))
|
||||
try:
|
||||
arg_set = set(arg)
|
||||
ipa_arg_set = set(ipa_arg)
|
||||
except TypeError:
|
||||
if arg != ipa_arg:
|
||||
# module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
|
||||
module.debug(
|
||||
base_debug_msg
|
||||
+ "Different values: %s %s" % (arg, ipa_arg)
|
||||
)
|
||||
return False
|
||||
else:
|
||||
if arg_set != ipa_arg_set:
|
||||
# module.warn("%s != %s" % (repr(arg), repr(ipa_arg)))
|
||||
module.debug(
|
||||
base_debug_msg
|
||||
+ "Different set content: %s %s"
|
||||
% (arg_set, ipa_arg_set,)
|
||||
)
|
||||
return False
|
||||
|
||||
# module.warn("%s == %s" % (repr(arg), repr(ipa_arg)))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -265,10 +310,11 @@ def api_get_realm():
|
||||
|
||||
|
||||
def gen_add_del_lists(user_list, res_list):
|
||||
"""
|
||||
Generate the lists for the addition and removal of members using the
|
||||
provided user and ipa settings
|
||||
"""
|
||||
"""Generate the lists for the addition and removal of members."""
|
||||
# The user list is None, therefore the parameter should not be touched
|
||||
if user_list is None:
|
||||
return [], []
|
||||
|
||||
add_list = list(set(user_list or []) - set(res_list or []))
|
||||
del_list = list(set(res_list or []) - set(user_list or []))
|
||||
|
||||
@@ -277,8 +323,9 @@ def gen_add_del_lists(user_list, res_list):
|
||||
|
||||
def encode_certificate(cert):
|
||||
"""
|
||||
Encode a certificate using base64 with also taking FreeIPA and Python
|
||||
versions into account
|
||||
Encode a certificate using base64.
|
||||
|
||||
It also takes FreeIPA and Python versions into account.
|
||||
"""
|
||||
if isinstance(cert, (str, unicode, bytes)):
|
||||
encoded = base64.b64encode(cert)
|
||||
@@ -289,10 +336,42 @@ def encode_certificate(cert):
|
||||
return encoded
|
||||
|
||||
|
||||
def load_cert_from_str(cert):
|
||||
cert = cert.strip()
|
||||
if not cert.startswith("-----BEGIN CERTIFICATE-----"):
|
||||
cert = "-----BEGIN CERTIFICATE-----\n" + cert
|
||||
if not cert.endswith("-----END CERTIFICATE-----"):
|
||||
cert += "\n-----END CERTIFICATE-----"
|
||||
|
||||
if load_pem_x509_certificate is not None:
|
||||
cert = load_pem_x509_certificate(cert.encode('utf-8'))
|
||||
else:
|
||||
cert = load_certificate(cert.encode('utf-8'))
|
||||
return cert
|
||||
|
||||
|
||||
def DN_x500_text(text):
|
||||
if hasattr(DN, "x500_text"):
|
||||
return DN(text).x500_text()
|
||||
else:
|
||||
# Emulate x500_text
|
||||
dn = DN(text)
|
||||
dn.rdns = reversed(dn.rdns)
|
||||
return str(dn)
|
||||
|
||||
|
||||
def is_valid_port(port):
|
||||
if not isinstance(port, int):
|
||||
return False
|
||||
|
||||
if 1 <= port <= 65535:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_ipv4_addr(ipaddr):
|
||||
"""
|
||||
Test if figen IP address is a valid IPv4 address
|
||||
"""
|
||||
"""Test if given IP address is a valid IPv4 address."""
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET, ipaddr)
|
||||
except socket.error:
|
||||
@@ -301,11 +380,309 @@ def is_ipv4_addr(ipaddr):
|
||||
|
||||
|
||||
def is_ipv6_addr(ipaddr):
|
||||
"""
|
||||
Test if figen IP address is a valid IPv6 address
|
||||
"""
|
||||
"""Test if given IP address is a valid IPv6 address."""
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, ipaddr)
|
||||
except socket.error:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class AnsibleFreeIPAParams(Mapping):
|
||||
def __init__(self, ansible_module):
|
||||
self.mapping = ansible_module.params
|
||||
self.ansible_module = ansible_module
|
||||
|
||||
def __getitem__(self, key):
|
||||
param = self.mapping[key]
|
||||
if param is not None:
|
||||
return _afm_convert(param)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.mapping)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.mapping)
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
return self.name
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.get(name)
|
||||
|
||||
|
||||
class FreeIPABaseModule(AnsibleModule):
|
||||
"""
|
||||
Base class for FreeIPA Ansible modules.
|
||||
|
||||
Provides methods useful methods to be used by our modules.
|
||||
|
||||
This class should be overriten and instantiated for the module.
|
||||
A basic implementation of an Ansible FreeIPA module expects its
|
||||
class to:
|
||||
|
||||
1. Define a class attribute ``ipa_param_mapping``
|
||||
2. Implement the method ``define_ipa_commands()``
|
||||
3. Implement the method ``check_ipa_params()`` (optional)
|
||||
|
||||
After instantiating the class the method ``ipa_run()`` should be called.
|
||||
|
||||
Example (ansible-freeipa/plugins/modules/ipasomemodule.py):
|
||||
|
||||
class SomeIPAModule(FreeIPABaseModule):
|
||||
ipa_param_mapping = {
|
||||
"arg_to_be_passed_to_ipa_command": "module_param",
|
||||
"another_arg": "get_another_module_param",
|
||||
}
|
||||
|
||||
def get_another_module_param(self):
|
||||
another_module_param = self.ipa_params.another_module_param
|
||||
# Validate or modify another_module_param
|
||||
# ...
|
||||
return another_module_param
|
||||
|
||||
def check_ipa_params(self):
|
||||
# Validate your params here
|
||||
# Example:
|
||||
if not self.ipa_params.module_param in VALID_OPTIONS:
|
||||
self.fail_json(msg="Invalid value for argument module_param")
|
||||
|
||||
def define_ipa_commands(self):
|
||||
args = self.get_ipa_command_args()
|
||||
|
||||
self.add_ipa_command(
|
||||
"some_ipa_command",
|
||||
name="obj-name",
|
||||
args=args,
|
||||
)
|
||||
|
||||
def main():
|
||||
ipa_module = SomeIPAModule(argument_spec=dict(
|
||||
module_param=dict(
|
||||
type="str",
|
||||
default=None,
|
||||
required=False,
|
||||
),
|
||||
another_module_param=dict(
|
||||
type="str",
|
||||
default=None,
|
||||
required=False,
|
||||
),
|
||||
))
|
||||
ipa_module.ipa_run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
"""
|
||||
|
||||
ipa_param_mapping = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FreeIPABaseModule, self).__init__(*args, **kwargs)
|
||||
|
||||
# Attributes to store kerberos credentials (if needed)
|
||||
self.ccache_dir = None
|
||||
self.ccache_name = None
|
||||
|
||||
# Status of an execution. Will be changed to True
|
||||
# if something is actually peformed.
|
||||
self.changed = False
|
||||
|
||||
# Status of the connection with the IPA server.
|
||||
# We need to know if the connection was actually stablished
|
||||
# before we start sending commands.
|
||||
self.ipa_connected = False
|
||||
|
||||
# Commands to be executed
|
||||
self.ipa_commands = []
|
||||
|
||||
# Module exit arguments.
|
||||
self.exit_args = {}
|
||||
|
||||
# Wrapper around the AnsibleModule.params.
|
||||
# Return the actual params but performing transformations
|
||||
# when needed.
|
||||
self.ipa_params = AnsibleFreeIPAParams(self)
|
||||
|
||||
def get_ipa_command_args(self):
|
||||
"""
|
||||
Return a dict to be passed to an IPA command.
|
||||
|
||||
The keys of ``ipa_param_mapping`` are also the keys of the return dict.
|
||||
|
||||
The values of ``ipa_param_mapping`` needs to be either:
|
||||
* A str with the name of a defined method; or
|
||||
* A key of ``AnsibleModule.param``.
|
||||
|
||||
In case of a method the return of the method will be set as value
|
||||
for the return dict.
|
||||
|
||||
In case of a AnsibleModule.param the value of the param will be
|
||||
set in the return dict. In addition to that boolean values will be
|
||||
automaticaly converted to uppercase strings (as required by FreeIPA
|
||||
server).
|
||||
|
||||
"""
|
||||
args = {}
|
||||
for ipa_param_name, param_name in self.ipa_param_mapping.items():
|
||||
|
||||
# Check if param_name is actually a param
|
||||
if param_name in self.ipa_params:
|
||||
value = self.ipa_params.get(param_name)
|
||||
if isinstance(value, bool):
|
||||
value = "TRUE" if value else "FALSE"
|
||||
|
||||
# Since param wasn't a param check if it's a method name
|
||||
elif hasattr(self, param_name):
|
||||
method = getattr(self, param_name)
|
||||
if callable(method):
|
||||
value = method()
|
||||
|
||||
# We don't have a way to guess the value so fail.
|
||||
else:
|
||||
self.fail_json(
|
||||
msg=(
|
||||
"Couldn't get a value for '%s'. Option '%s' is not "
|
||||
"a module argument neither a defined method."
|
||||
)
|
||||
% (ipa_param_name, param_name)
|
||||
)
|
||||
|
||||
if value is not None:
|
||||
args[ipa_param_name] = value
|
||||
|
||||
return args
|
||||
|
||||
def check_ipa_params(self):
|
||||
"""Validate ipa_params before command is called."""
|
||||
pass
|
||||
|
||||
def define_ipa_commands(self):
|
||||
"""Define commands that will be run in IPA server."""
|
||||
raise NotImplementedError
|
||||
|
||||
def api_command(self, command, name=None, args=None):
|
||||
"""Execute a single command in IPA server."""
|
||||
if args is None:
|
||||
args = {}
|
||||
|
||||
if name is None:
|
||||
return api_command_no_name(self, command, args)
|
||||
|
||||
return api_command(self, command, name, args)
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
Connect to IPA server.
|
||||
|
||||
Check the there are working Kerberos credentials to connect to
|
||||
IPA server. If there are not we perform a temporary kinit
|
||||
that will be terminated when exiting the context.
|
||||
|
||||
If the connection fails ``ipa_connected`` attribute will be set
|
||||
to False.
|
||||
"""
|
||||
principal = self.ipa_params.ipaadmin_principal
|
||||
password = self.ipa_params.ipaadmin_password
|
||||
|
||||
try:
|
||||
if not valid_creds(self, principal):
|
||||
self.ccache_dir, self.ccache_name = temp_kinit(
|
||||
principal, password,
|
||||
)
|
||||
|
||||
api_connect()
|
||||
|
||||
except Exception as excpt:
|
||||
self.fail_json(msg=str(excpt))
|
||||
else:
|
||||
self.ipa_connected = True
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""
|
||||
Terminate a connection with the IPA server.
|
||||
|
||||
Deal with exceptions, destroy temporary kinit credentials and
|
||||
exit the module with proper arguments.
|
||||
|
||||
"""
|
||||
if exc_val:
|
||||
self.fail_json(msg=str(exc_val))
|
||||
|
||||
# TODO: shouldn't we also disconnect from api backend?
|
||||
temp_kdestroy(self.ccache_dir, self.ccache_name)
|
||||
|
||||
self.exit_json(changed=self.changed, user=self.exit_args)
|
||||
|
||||
def get_command_errors(self, command, result):
|
||||
"""Look for erros into command results."""
|
||||
# Get all errors
|
||||
# All "already a member" and "not a member" failures in the
|
||||
# result are ignored. All others are reported.
|
||||
errors = []
|
||||
for item in result.get("failed", tuple()):
|
||||
failed_item = result["failed"][item]
|
||||
for member_type in failed_item:
|
||||
for member, failure in failed_item[member_type]:
|
||||
if (
|
||||
"already a member" in failure
|
||||
or "not a member" in failure
|
||||
):
|
||||
continue
|
||||
errors.append(
|
||||
"%s: %s %s: %s"
|
||||
% (command, member_type, member, failure)
|
||||
)
|
||||
|
||||
if len(errors) > 0:
|
||||
self.fail_json(", ".join("errors"))
|
||||
|
||||
def add_ipa_command(self, command, name=None, args=None):
|
||||
"""Add a command to the list of commands to be executed."""
|
||||
self.ipa_commands.append((name, command, args or {}))
|
||||
|
||||
def _run_ipa_commands(self):
|
||||
"""Execute commands in self.ipa_commands."""
|
||||
result = None
|
||||
|
||||
for name, command, args in self.ipa_commands:
|
||||
try:
|
||||
result = self.api_command(command, name, args)
|
||||
except Exception as excpt:
|
||||
self.fail_json(msg="%s: %s: %s" % (command, name, str(excpt)))
|
||||
else:
|
||||
if "completed" in result:
|
||||
if result["completed"] > 0:
|
||||
self.changed = True
|
||||
else:
|
||||
self.changed = True
|
||||
|
||||
self.get_command_errors(command, result)
|
||||
|
||||
def require_ipa_attrs_change(self, command_args, ipa_attrs):
|
||||
"""
|
||||
Compare given args with current object attributes.
|
||||
|
||||
Returns True in case current IPA object attributes differ from
|
||||
args passed to the module.
|
||||
"""
|
||||
equal = compare_args_ipa(self, command_args, ipa_attrs)
|
||||
return not equal
|
||||
|
||||
def pdebug(self, value):
|
||||
"""Debug with pretty formatting."""
|
||||
self.debug(pformat(value))
|
||||
|
||||
def ipa_run(self):
|
||||
"""Execute module actions."""
|
||||
with self:
|
||||
if not self.ipa_connected:
|
||||
return
|
||||
|
||||
self.check_ipa_params()
|
||||
self.define_ipa_commands()
|
||||
self._run_ipa_commands()
|
||||
|
||||
80
plugins/modules/README.md
Normal file
80
plugins/modules/README.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Writing a new Ansible FreeIPA module
|
||||
|
||||
## Minimum requirements
|
||||
A ansible-freeipa module should have:
|
||||
|
||||
* Code:
|
||||
* A module file placed in `plugins/modules/<ipa_module_name>.py`
|
||||
|
||||
* Documentation:
|
||||
* `README-<module_name>.md` file in the root directory and linked from the main README.md
|
||||
* Example playbooks in `playbooks/<module_name>/` directory
|
||||
|
||||
* Tests:
|
||||
* Test cases (also playbooks) defined in `tests/<module_name>/test_<something>.yml`. It's ok to have multiple files in this directory.
|
||||
|
||||
## Code
|
||||
|
||||
The module file have to start with the python shebang line, license header and definition of the constants `ANSIBLE_METADATA`, `DOCUMENTATION`, `EXAMPLES` and `RETURNS`. Those constants need to be defined before the code (even imports). See https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#starting-a-new-module for more information.
|
||||
|
||||
|
||||
Although it's use is not yet required, ansible-freeipa provides `FreeIPABaseModule` as a helper class for the implementation of new modules. See the example bellow:
|
||||
|
||||
```python
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import FreeIPABaseModule
|
||||
|
||||
|
||||
class SomeIPAModule(FreeIPABaseModule):
|
||||
ipa_param_mapping = {
|
||||
"arg_to_be_passed_to_ipa_command": "module_param",
|
||||
"another_arg": "get_another_module_param",
|
||||
}
|
||||
|
||||
def get_another_module_param(self):
|
||||
another_module_param = self.ipa_params.another_module_param
|
||||
|
||||
# Validate or modify another_module_param ...
|
||||
|
||||
return another_module_param
|
||||
|
||||
def check_ipa_params(self):
|
||||
|
||||
# Validate your params here ...
|
||||
|
||||
# Example:
|
||||
if not self.ipa_params.module_param in VALID_OPTIONS:
|
||||
self.fail_json(msg="Invalid value for argument module_param")
|
||||
|
||||
def define_ipa_commands(self):
|
||||
args = self.get_ipa_command_args()
|
||||
|
||||
self.add_ipa_command("some_ipa_command", name="obj-name", args=args)
|
||||
|
||||
|
||||
def main():
|
||||
ipa_module = SomeIPAModule(argument_spec=dict(
|
||||
module_param=dict(type="str", default=None, required=False),
|
||||
another_module_param=dict(type="str", default=None, required=False),
|
||||
))
|
||||
ipa_module.ipa_run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
In the example above, the module will call the command `some_ipa_command`, using "obj-name" as name and, `arg_to_be_passed_to_ipa_command` and `another_arg` as arguments.
|
||||
|
||||
The values of the arguments will be determined by the class attribute `ipa_param_mapping`.
|
||||
|
||||
In the case of `arg_to_be_passed_to_ipa_command` the key (`module_param`) is defined in the module `argument_specs` so the value of the argument is actually used.
|
||||
|
||||
On the other hand, `another_arg` as mapped to something else: a callable method. In this case the method will be called and it's result used as value for `another_arg`.
|
||||
|
||||
**NOTE**: Keep mind that to take advantage of the parameters mapping defined in `ipa_param_mapping` you will have to call `args = self.get_ipa_command_args()` and use `args` in your command. There is no implicit call of this method.
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
||||
The `FreeIPABaseModule` is new and might not be suitable to all cases and every module yet. In case you need to extend it's functionality for a new module please open an issue or PR and we'll be happy to discuss it.
|
||||
479
plugins/modules/ipaconfig.py
Normal file
479
plugins/modules/ipaconfig.py
Normal file
@@ -0,0 +1,479 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Chris Procter <cprocter@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program 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.
|
||||
#
|
||||
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
"metadata_version": "1.0",
|
||||
"supported_by": "community",
|
||||
"status": ["preview"],
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ipa_config
|
||||
author: chris procter
|
||||
short_description: Modify IPA global config options
|
||||
description:
|
||||
- Modify IPA global config options
|
||||
options:
|
||||
ipaadmin_principal:
|
||||
description: The admin principal
|
||||
default: admin
|
||||
ipaadmin_password:
|
||||
description: The admin password
|
||||
required: false
|
||||
maxusername:
|
||||
description: Set the maximum username length between 1-255
|
||||
required: false
|
||||
aliases: ['ipamaxusernamelength']
|
||||
maxhostname:
|
||||
description: Set the maximum hostname length between 64-255
|
||||
required: false
|
||||
aliases: ['ipamaxhostnamelength']
|
||||
homedirectory:
|
||||
description: Set the default location of home directories
|
||||
required: false
|
||||
aliases: ['ipahomesrootdir']
|
||||
defaultshell:
|
||||
description: Set the default shell for new users
|
||||
required: false
|
||||
aliases: ['ipadefaultloginshell', 'loginshell']
|
||||
defaultgroup:
|
||||
description: Set the default group for new users
|
||||
required: false
|
||||
aliases: ['ipadefaultprimarygroup']
|
||||
emaildomain:
|
||||
description: Set the default e-mail domain
|
||||
required: false
|
||||
aliases: ['ipadefaultemaildomain']
|
||||
searchtimelimit:
|
||||
description:
|
||||
- Set maximum amount of time (seconds) for a search
|
||||
- values -1 to 2147483647 (-1 or 0 is unlimited)
|
||||
required: false
|
||||
aliases: ['ipasearchtimelimit']
|
||||
searchrecordslimit:
|
||||
description:
|
||||
- Set maximum number of records to search
|
||||
- values -1 to 2147483647 (-1 or 0 is unlimited)
|
||||
required: false
|
||||
aliases: ['ipasearchrecordslimit']
|
||||
usersearch:
|
||||
description:
|
||||
- Set comma-separated list of fields to search for user search
|
||||
required: false
|
||||
aliases: ['ipausersearchfields']
|
||||
groupsearch:
|
||||
description:
|
||||
- Set comma-separated list of fields to search for group search
|
||||
required: false
|
||||
aliases: ['ipagroupsearchfields']
|
||||
enable_migration:
|
||||
description: Enable migration mode
|
||||
type: bool
|
||||
required: false
|
||||
aliases: ['ipamigrationenabled']
|
||||
groupobjectclasses:
|
||||
description: Set default group objectclasses (comma-separated list)
|
||||
required: false
|
||||
type: list
|
||||
aliases: ['ipagroupobjectclasses']
|
||||
userobjectclasses:
|
||||
description: Set default user objectclasses (comma-separated list)
|
||||
required: false
|
||||
type: list
|
||||
aliases: ['ipauserobjectclasses']
|
||||
pwdexpnotify:
|
||||
description:
|
||||
- Set number of days's notice of impending password expiration
|
||||
- values 0 to 2147483647
|
||||
required: false
|
||||
aliases: ['ipapwdexpadvnotify']
|
||||
configstring:
|
||||
description: Set extra hashes to generate in password plug-in
|
||||
required: false
|
||||
type: list
|
||||
choices:
|
||||
- "AllowNThash"
|
||||
- "KDC:Disable Last Success"
|
||||
- "KDC:Disable Lockout"
|
||||
- "KDC:Disable Default Preauth for SPNs"
|
||||
- ""
|
||||
aliases: ['ipaconfigstring']
|
||||
selinuxusermaporder:
|
||||
description: Set order in increasing priority of SELinux users
|
||||
required: false
|
||||
type: list
|
||||
aliases: ['ipaselinuxusermaporder']
|
||||
selinuxusermapdefault:
|
||||
description: Set default SELinux user when no match found in map rule
|
||||
required: false
|
||||
aliases: ['ipaselinuxusermapdefault']
|
||||
pac_type:
|
||||
description: set default types of PAC supported for services
|
||||
required: false
|
||||
type: list
|
||||
choices: ["MS-PAC", "PAD", "nfs:NONE", ""]
|
||||
aliases: ["ipakrbauthzdata"]
|
||||
user_auth_type:
|
||||
description: set default types of supported user authentication
|
||||
required: false
|
||||
type: list
|
||||
choices: ["password", "radius", "otp", "disabled", ""]
|
||||
aliases: ["ipauserauthtype"]
|
||||
ca_renewal_master_server:
|
||||
description: Renewal master for IPA certificate authority.
|
||||
required: false
|
||||
type: string
|
||||
domain_resolution_order:
|
||||
description: set list of domains used for short name qualification
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipadomainresolutionorder"]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
---
|
||||
- name: Playbook to handle global configuration options
|
||||
hosts: ipaserver
|
||||
become: true
|
||||
tasks:
|
||||
- name: return current values of the global configuration options
|
||||
ipaconfig:
|
||||
ipaadmin_password: password
|
||||
register: result
|
||||
- name: display default login shell
|
||||
debug:
|
||||
msg: '{{result.config.defaultshell[0] }}'
|
||||
|
||||
- name: set defaultshell and maxusername
|
||||
ipaconfig:
|
||||
ipaadmin_password: password
|
||||
defaultshell: /bin/bash
|
||||
maxusername: 64
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
config:
|
||||
description: Dict of all global config options
|
||||
returned: When no options are set
|
||||
type: dict
|
||||
options:
|
||||
maxusername:
|
||||
description: maximum username length
|
||||
returned: always
|
||||
maxhostname:
|
||||
description: maximum hostname length
|
||||
returned: always
|
||||
homedirectory:
|
||||
description: default location of home directories
|
||||
returned: always
|
||||
defaultshell:
|
||||
description: default shell for new users
|
||||
returned: always
|
||||
defaultgroup:
|
||||
description: default group for new users
|
||||
returned: always
|
||||
emaildomain:
|
||||
description: default e-mail domain
|
||||
returned: always
|
||||
searchtimelimit:
|
||||
description: maximum amount of time (seconds) for a search
|
||||
returned: always
|
||||
searchrecordslimit:
|
||||
description: maximum number of records to search
|
||||
returned: always
|
||||
usersearch:
|
||||
description: comma-separated list of fields to search in user search
|
||||
type: list
|
||||
returned: always
|
||||
groupsearch:
|
||||
description: comma-separated list of fields to search in group search
|
||||
type: list
|
||||
returned: always
|
||||
enable_migration:
|
||||
description: Enable migration mode
|
||||
type: bool
|
||||
returned: always
|
||||
groupobjectclasses:
|
||||
description: default group objectclasses (comma-separated list)
|
||||
type: list
|
||||
returned: always
|
||||
userobjectclasses:
|
||||
description: default user objectclasses (comma-separated list)
|
||||
type: list
|
||||
returned: always
|
||||
pwdexpnotify:
|
||||
description: number of days's notice of impending password expiration
|
||||
returned: always
|
||||
configstring:
|
||||
description: extra hashes to generate in password plug-in
|
||||
type: list
|
||||
returned: always
|
||||
selinuxusermaporder:
|
||||
description: order in increasing priority of SELinux users
|
||||
returned: always
|
||||
selinuxusermapdefault:
|
||||
description: default SELinux user when no match is found in map rule
|
||||
returned: always
|
||||
pac_type:
|
||||
description: default types of PAC supported for services
|
||||
type: list
|
||||
returned: always
|
||||
user_auth_type:
|
||||
description: default types of supported user authentication
|
||||
returned: always
|
||||
ca_renewal_master_server:
|
||||
description: master for IPA certificate authority.
|
||||
returned: always
|
||||
domain_resolution_order:
|
||||
description: list of domains used for short name qualification
|
||||
returned: always
|
||||
'''
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command_no_name, \
|
||||
compare_args_ipa, module_params_get
|
||||
import ipalib.errors
|
||||
|
||||
|
||||
def config_show(module):
|
||||
_result = api_command_no_name(module, "config_show", {})
|
||||
|
||||
return _result["result"]
|
||||
|
||||
|
||||
def gen_args(params):
|
||||
_args = {}
|
||||
for k, v in params.items():
|
||||
if v is not None:
|
||||
_args[k] = v
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
# general
|
||||
ipaadmin_principal=dict(type="str", default="admin"),
|
||||
ipaadmin_password=dict(type="str", required=False, no_log=True),
|
||||
maxusername=dict(type="int", required=False,
|
||||
aliases=['ipamaxusernamelength']),
|
||||
maxhostname=dict(type="int", required=False,
|
||||
aliases=['ipamaxhostnamelength']),
|
||||
homedirectory=dict(type="str", required=False,
|
||||
aliases=['ipahomesrootdir']),
|
||||
defaultshell=dict(type="str", required=False,
|
||||
aliases=['ipadefaultloginshell',
|
||||
'loginshell']),
|
||||
defaultgroup=dict(type="str", required=False,
|
||||
aliases=['ipadefaultprimarygroup']),
|
||||
emaildomain=dict(type="str", required=False,
|
||||
aliases=['ipadefaultemaildomain']),
|
||||
searchtimelimit=dict(type="int", required=False,
|
||||
aliases=['ipasearchtimelimit']),
|
||||
searchrecordslimit=dict(type="int", required=False,
|
||||
aliases=['ipasearchrecordslimit']),
|
||||
usersearch=dict(type="list", required=False,
|
||||
aliases=['ipausersearchfields']),
|
||||
groupsearch=dict(type="list", required=False,
|
||||
aliases=['ipagroupsearchfields']),
|
||||
enable_migration=dict(type="bool", required=False,
|
||||
aliases=['ipamigrationenabled']),
|
||||
groupobjectclasses=dict(type="list", required=False,
|
||||
aliases=['ipagroupobjectclasses']),
|
||||
userobjectclasses=dict(type="list", required=False,
|
||||
aliases=['ipauserobjectclasses']),
|
||||
pwdexpnotify=dict(type="int", required=False,
|
||||
aliases=['ipapwdexpadvnotify']),
|
||||
configstring=dict(type="list", required=False,
|
||||
aliases=['ipaconfigstring'],
|
||||
choices=["AllowNThash",
|
||||
"KDC:Disable Last Success",
|
||||
"KDC:Disable Lockout",
|
||||
"KDC:Disable Default Preauth for SPNs",
|
||||
""]), # noqa E128
|
||||
selinuxusermaporder=dict(type="list", required=False,
|
||||
aliases=['ipaselinuxusermaporder']),
|
||||
selinuxusermapdefault=dict(type="str", required=False,
|
||||
aliases=['ipaselinuxusermapdefault']),
|
||||
pac_type=dict(type="list", required=False,
|
||||
aliases=["ipakrbauthzdata"],
|
||||
choices=["MS-PAC", "PAD", "nfs:NONE", ""]),
|
||||
user_auth_type=dict(type="list", required=False,
|
||||
choices=["password", "radius", "otp",
|
||||
"disabled", ""],
|
||||
aliases=["ipauserauthtype"]),
|
||||
ca_renewal_master_server=dict(type="str", required=False),
|
||||
domain_resolution_order=dict(type="list", required=False,
|
||||
aliases=["ipadomainresolutionorder"])
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
|
||||
# Get parameters
|
||||
|
||||
# general
|
||||
ipaadmin_principal = module_params_get(ansible_module,
|
||||
"ipaadmin_principal")
|
||||
ipaadmin_password = module_params_get(ansible_module,
|
||||
"ipaadmin_password")
|
||||
|
||||
field_map = {
|
||||
"maxusername": "ipamaxusernamelength",
|
||||
"maxhostname": "ipamaxhostnamelength",
|
||||
"homedirectory": "ipahomesrootdir",
|
||||
"defaultshell": "ipadefaultloginshell",
|
||||
"defaultgroup": "ipadefaultprimarygroup",
|
||||
"emaildomain": "ipadefaultemaildomain",
|
||||
"searchtimelimit": "ipasearchtimelimit",
|
||||
"searchrecordslimit": "ipasearchrecordslimit",
|
||||
"usersearch": "ipausersearchfields",
|
||||
"groupsearch": "ipagroupsearchfields",
|
||||
"enable_migration": "ipamigrationenabled",
|
||||
"groupobjectclasses": "ipagroupobjectclasses",
|
||||
"userobjectclasses": "ipauserobjectclasses",
|
||||
"pwdexpnotify": "ipapwdexpadvnotify",
|
||||
"configstring": "ipaconfigstring",
|
||||
"selinuxusermaporder": "ipaselinuxusermaporder",
|
||||
"selinuxusermapdefault": "ipaselinuxusermapdefault",
|
||||
"pac_type": "ipakrbauthzdata",
|
||||
"user_auth_type": "ipauserauthtype",
|
||||
"ca_renewal_master_server": "ca_renewal_master_server",
|
||||
"domain_resolution_order": "ipadomainresolutionorder"
|
||||
}
|
||||
reverse_field_map = {v: k for k, v in field_map.items()}
|
||||
|
||||
params = {}
|
||||
for x in field_map.keys():
|
||||
val = module_params_get(ansible_module, x)
|
||||
|
||||
if val is not None:
|
||||
params[field_map.get(x, x)] = val
|
||||
|
||||
if params.get("ipamigrationenabled") is not None:
|
||||
params["ipamigrationenabled"] = \
|
||||
str(params["ipamigrationenabled"]).upper()
|
||||
|
||||
if params.get("ipaselinuxusermaporder", None):
|
||||
params["ipaselinuxusermaporder"] = \
|
||||
"$".join(params["ipaselinuxusermaporder"])
|
||||
|
||||
if params.get("ipadomainresolutionorder", None):
|
||||
params["ipadomainresolutionorder"] = \
|
||||
":".join(params["ipadomainresolutionorder"])
|
||||
|
||||
if params.get("ipausersearchfields", None):
|
||||
params["ipausersearchfields"] = \
|
||||
",".join(params["ipausersearchfields"])
|
||||
|
||||
if params.get("ipagroupsearchfields", None):
|
||||
params["ipagroupsearchfields"] = \
|
||||
",".join(params["ipagroupsearchfields"])
|
||||
|
||||
# verify limits on INT values.
|
||||
args_with_limits = [
|
||||
("ipamaxusernamelength", 1, 255),
|
||||
("ipamaxhostnamelength", 64, 255),
|
||||
("ipasearchtimelimit", -1, 2147483647),
|
||||
("ipasearchrecordslimit", -1, 2147483647),
|
||||
("ipapwdexpadvnotify", 0, 2147483647),
|
||||
]
|
||||
for arg, min, max in args_with_limits:
|
||||
if arg in params and (params[arg] > max or params[arg] < min):
|
||||
ansible_module.fail_json(
|
||||
msg="Argument '%s' must be between %d and %d."
|
||||
% (arg, min, max))
|
||||
|
||||
changed = False
|
||||
exit_args = {}
|
||||
ccache_dir = None
|
||||
ccache_name = None
|
||||
res_show = None
|
||||
try:
|
||||
if not valid_creds(ansible_module, ipaadmin_principal):
|
||||
ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
|
||||
ipaadmin_password)
|
||||
api_connect()
|
||||
if params:
|
||||
res_show = config_show(ansible_module)
|
||||
params = {
|
||||
k: v for k, v in params.items()
|
||||
if k not in res_show or res_show[k] != v
|
||||
}
|
||||
if params \
|
||||
and not compare_args_ipa(ansible_module, params, res_show):
|
||||
changed = True
|
||||
api_command_no_name(ansible_module, "config_mod", params)
|
||||
|
||||
else:
|
||||
rawresult = api_command_no_name(ansible_module, "config_show", {})
|
||||
result = rawresult['result']
|
||||
del result['dn']
|
||||
for key, v in result.items():
|
||||
k = reverse_field_map.get(key, key)
|
||||
if ansible_module.argument_spec.get(k):
|
||||
if k == 'ipaselinuxusermaporder':
|
||||
exit_args['ipaselinuxusermaporder'] = \
|
||||
result.get(key)[0].split('$')
|
||||
elif k == 'domain_resolution_order':
|
||||
exit_args['domain_resolution_order'] = \
|
||||
result.get(key)[0].split('$')
|
||||
elif k == 'usersearch':
|
||||
exit_args['usersearch'] = \
|
||||
result.get(key)[0].split(',')
|
||||
elif k == 'groupsearch':
|
||||
exit_args['groupsearch'] = \
|
||||
result.get(key)[0].split(',')
|
||||
elif isinstance(v, str) and \
|
||||
ansible_module.argument_spec[k]['type'] == "list":
|
||||
exit_args[k] = [v]
|
||||
elif isinstance(v, list) and \
|
||||
ansible_module.argument_spec[k]['type'] == "str":
|
||||
exit_args[k] = ",".join(v)
|
||||
elif isinstance(v, list) and \
|
||||
ansible_module.argument_spec[k]['type'] == "int":
|
||||
exit_args[k] = ",".join(v)
|
||||
elif isinstance(v, list) and \
|
||||
ansible_module.argument_spec[k]['type'] == "bool":
|
||||
exit_args[k] = (v[0] == "TRUE")
|
||||
else:
|
||||
exit_args[k] = v
|
||||
except ipalib.errors.EmptyModlist:
|
||||
changed = False
|
||||
except Exception as e:
|
||||
ansible_module.fail_json(msg="%s %s" % (params, str(e)))
|
||||
|
||||
finally:
|
||||
temp_kdestroy(ccache_dir, ccache_name)
|
||||
|
||||
# Done
|
||||
ansible_module.exit_json(changed=changed, config=exit_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -97,11 +97,10 @@ RETURN = """
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, \
|
||||
temp_kdestroy, valid_creds, api_connect, \
|
||||
api_command_no_name, compare_args_ipa, module_params_get, \
|
||||
gen_add_del_lists, is_ipv4_addr, is_ipv6_addr, ipalib_errors
|
||||
is_ipv4_addr, is_ipv6_addr
|
||||
|
||||
|
||||
def find_dnsconfig(module):
|
||||
@@ -116,7 +115,7 @@ def find_dnsconfig(module):
|
||||
_result["result"]['idnsforwarders'] = ['']
|
||||
return _result["result"]
|
||||
else:
|
||||
module.fail("Could not retrieve current DNS configuration.")
|
||||
module.fail_json(msg="Could not retrieve current DNS configuration.")
|
||||
return None
|
||||
|
||||
|
||||
@@ -130,7 +129,7 @@ def gen_args(module, state, dnsconfig, forwarders, forward_policy,
|
||||
ip_address = forwarder.get('ip_address')
|
||||
port = forwarder.get('port')
|
||||
if not (is_ipv4_addr(ip_address) or is_ipv6_addr(ip_address)):
|
||||
module.fail(
|
||||
module.fail_json(
|
||||
msg="Invalid IP for DNS forwarder: %s" % ip_address)
|
||||
if port is None:
|
||||
_forwarders.append(ip_address)
|
||||
@@ -154,7 +153,7 @@ def gen_args(module, state, dnsconfig, forwarders, forward_policy,
|
||||
|
||||
else:
|
||||
# shouldn't happen, but let's be paranoid.
|
||||
module.fail(msg="Invalid state: %s" % state)
|
||||
module.fail_json(msg="Invalid state: %s" % state)
|
||||
|
||||
if forward_policy is not None:
|
||||
_args['idnsforwardpolicy'] = forward_policy
|
||||
|
||||
316
plugins/modules/ipadnsforwardzone.py
Normal file
316
plugins/modules/ipadnsforwardzone.py
Normal file
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Chris Procter <cprocter@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2019 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program 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.
|
||||
#
|
||||
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
"metadata_version": "1.0",
|
||||
"supported_by": "community",
|
||||
"status": ["preview"],
|
||||
}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ipa_dnsforwardzone
|
||||
author: chris procter
|
||||
short_description: Manage FreeIPA DNS Forwarder Zones
|
||||
description:
|
||||
- Add and delete an IPA DNS Forwarder Zones using IPA API
|
||||
options:
|
||||
ipaadmin_principal:
|
||||
description: The admin principal
|
||||
default: admin
|
||||
ipaadmin_password:
|
||||
description: The admin password
|
||||
required: false
|
||||
name:
|
||||
description:
|
||||
- The DNS zone name which needs to be managed.
|
||||
required: true
|
||||
aliases: ["cn"]
|
||||
state:
|
||||
description: State to ensure
|
||||
required: false
|
||||
default: present
|
||||
choices: ["present", "absent", "enabled", "disabled"]
|
||||
forwarders:
|
||||
description:
|
||||
- List of the DNS servers to forward to
|
||||
required: true
|
||||
type: list
|
||||
aliases: ["idnsforwarders"]
|
||||
forwardpolicy:
|
||||
description: Per-zone conditional forwarding policy
|
||||
required: false
|
||||
default: only
|
||||
choices: ["only", "first", "none"]
|
||||
aliases: ["idnsforwarders"]
|
||||
skip_overlap_check:
|
||||
description:
|
||||
- Force DNS zone creation even if it will overlap with an existing zone.
|
||||
required: false
|
||||
default: false
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure dns zone is present
|
||||
- ipadnsforwardzone:
|
||||
ipaadmin_password: MyPassword123
|
||||
state: present
|
||||
name: example.com
|
||||
forwarders:
|
||||
- 8.8.8.8
|
||||
- 4.4.4.4
|
||||
forwardpolicy: first
|
||||
skip_overlap_check: true
|
||||
|
||||
# Ensure that dns zone is removed
|
||||
- ipadnsforwardzone:
|
||||
ipaadmin_password: MyPassword123
|
||||
name: example.com
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
|
||||
module_params_get
|
||||
|
||||
|
||||
def find_dnsforwardzone(module, name):
|
||||
_args = {
|
||||
"all": True,
|
||||
"idnsname": name
|
||||
}
|
||||
_result = api_command(module, "dnsforwardzone_find", name, _args)
|
||||
|
||||
if len(_result["result"]) > 1:
|
||||
module.fail_json(
|
||||
msg="There is more than one dnsforwardzone '%s'" % (name))
|
||||
elif len(_result["result"]) == 1:
|
||||
return _result["result"][0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def gen_args(forwarders, forwardpolicy, skip_overlap_check):
|
||||
_args = {}
|
||||
|
||||
if forwarders is not None:
|
||||
_args["idnsforwarders"] = forwarders
|
||||
if forwardpolicy is not None:
|
||||
_args["idnsforwardpolicy"] = forwardpolicy
|
||||
if skip_overlap_check is not None:
|
||||
_args["skip_overlap_check"] = skip_overlap_check
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def main():
|
||||
ansible_module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
# general
|
||||
ipaadmin_principal=dict(type="str", default="admin"),
|
||||
ipaadmin_password=dict(type="str", required=False, no_log=True),
|
||||
name=dict(type="str", aliases=["cn"], default=None,
|
||||
required=True),
|
||||
forwarders=dict(type='list', aliases=["idnsforwarders"],
|
||||
required=False),
|
||||
forwardpolicy=dict(type='str', aliases=["idnsforwardpolicy"],
|
||||
required=False,
|
||||
choices=['only', 'first', 'none']),
|
||||
skip_overlap_check=dict(type='bool', required=False),
|
||||
action=dict(type="str", default="dnsforwardzone",
|
||||
choices=["member", "dnsforwardzone"]),
|
||||
# state
|
||||
state=dict(type='str', default='present',
|
||||
choices=['present', 'absent', 'enabled', 'disabled']),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
|
||||
# Get parameters
|
||||
ipaadmin_principal = module_params_get(ansible_module,
|
||||
"ipaadmin_principal")
|
||||
ipaadmin_password = module_params_get(ansible_module,
|
||||
"ipaadmin_password")
|
||||
name = module_params_get(ansible_module, "name")
|
||||
action = module_params_get(ansible_module, "action")
|
||||
forwarders = module_params_get(ansible_module, "forwarders")
|
||||
forwardpolicy = module_params_get(ansible_module, "forwardpolicy")
|
||||
skip_overlap_check = module_params_get(ansible_module,
|
||||
"skip_overlap_check")
|
||||
state = module_params_get(ansible_module, "state")
|
||||
|
||||
# absent stae means delete if the action is NOT member but update if it is
|
||||
# if action is member then update an exisiting resource
|
||||
# and if action is not member then create a resource
|
||||
if state == "absent" and action == "dnsforwardzone":
|
||||
operation = "del"
|
||||
elif action == "member":
|
||||
operation = "update"
|
||||
else:
|
||||
operation = "add"
|
||||
|
||||
if state == "disabled":
|
||||
wants_enable = False
|
||||
else:
|
||||
wants_enable = True
|
||||
|
||||
if operation == "del":
|
||||
invalid = ["forwarders", "forwardpolicy", "skip_overlap_check"]
|
||||
for x in invalid:
|
||||
if vars()[x] is not None:
|
||||
ansible_module.fail_json(
|
||||
msg="Argument '%s' can not be used with action "
|
||||
"'%s'" % (x, action))
|
||||
|
||||
changed = False
|
||||
exit_args = {}
|
||||
args = {}
|
||||
ccache_dir = None
|
||||
ccache_name = None
|
||||
is_enabled = "IGNORE"
|
||||
try:
|
||||
# we need to determine 3 variables
|
||||
# args = the values we want to change/set
|
||||
# command = the ipa api command to call del, add, or mod
|
||||
# is_enabled = is the current resource enabled (True)
|
||||
# disabled (False) and do we care (IGNORE)
|
||||
|
||||
if not valid_creds(ansible_module, ipaadmin_principal):
|
||||
ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
|
||||
ipaadmin_password)
|
||||
api_connect()
|
||||
|
||||
# Make sure forwardzone exists
|
||||
existing_resource = find_dnsforwardzone(ansible_module, name)
|
||||
|
||||
if existing_resource is None and operation == "update":
|
||||
# does not exist and is updating
|
||||
# trying to update something that doesn't exist, so error
|
||||
ansible_module.fail_json(msg="""dnsforwardzone '%s' is not
|
||||
valid""" % (name))
|
||||
elif existing_resource is None and operation == "del":
|
||||
# does not exists and should be absent
|
||||
# set command
|
||||
command = None
|
||||
# enabled or disabled?
|
||||
is_enabled = "IGNORE"
|
||||
elif existing_resource is not None and operation == "del":
|
||||
# exists but should be absent
|
||||
# set command
|
||||
command = "dnsforwardzone_del"
|
||||
# enabled or disabled?
|
||||
is_enabled = "IGNORE"
|
||||
elif forwarders is None:
|
||||
# forwarders are not defined its not a delete, update state?
|
||||
# set command
|
||||
command = None
|
||||
# enabled or disabled?
|
||||
if existing_resource is not None:
|
||||
is_enabled = existing_resource["idnszoneactive"][0]
|
||||
else:
|
||||
is_enabled = "IGNORE"
|
||||
elif existing_resource is not None and operation == "update":
|
||||
# exists and is updating
|
||||
# calculate the new forwarders and mod
|
||||
# determine args
|
||||
if state != "absent":
|
||||
forwarders = list(set(existing_resource["idnsforwarders"]
|
||||
+ forwarders))
|
||||
else:
|
||||
forwarders = list(set(existing_resource["idnsforwarders"])
|
||||
- set(forwarders))
|
||||
args = gen_args(forwarders, forwardpolicy,
|
||||
skip_overlap_check)
|
||||
if skip_overlap_check is not None:
|
||||
del args['skip_overlap_check']
|
||||
|
||||
# command
|
||||
if not compare_args_ipa(ansible_module, args, existing_resource):
|
||||
command = "dnsforwardzone_mod"
|
||||
else:
|
||||
command = None
|
||||
|
||||
# enabled or disabled?
|
||||
is_enabled = existing_resource["idnszoneactive"][0]
|
||||
|
||||
elif existing_resource is None and operation == "add":
|
||||
# does not exist but should be present
|
||||
# determine args
|
||||
args = gen_args(forwarders, forwardpolicy,
|
||||
skip_overlap_check)
|
||||
# set command
|
||||
command = "dnsforwardzone_add"
|
||||
# enabled or disabled?
|
||||
is_enabled = "TRUE"
|
||||
|
||||
elif existing_resource is not None and operation == "add":
|
||||
# exists and should be present, has it changed?
|
||||
# determine args
|
||||
args = gen_args(forwarders, forwardpolicy, skip_overlap_check)
|
||||
if skip_overlap_check is not None:
|
||||
del args['skip_overlap_check']
|
||||
|
||||
# set command
|
||||
if not compare_args_ipa(ansible_module, args, existing_resource):
|
||||
command = "dnsforwardzone_mod"
|
||||
else:
|
||||
command = None
|
||||
|
||||
# enabled or disabled?
|
||||
is_enabled = existing_resource["idnszoneactive"][0]
|
||||
|
||||
# if command is set then run it with the args
|
||||
if command is not None:
|
||||
api_command(ansible_module, command, name, args)
|
||||
changed = True
|
||||
|
||||
# does the enabled state match what we want (if we care)
|
||||
if is_enabled != "IGNORE":
|
||||
if wants_enable and is_enabled != "TRUE":
|
||||
api_command(ansible_module, "dnsforwardzone_enable",
|
||||
name, {})
|
||||
changed = True
|
||||
elif not wants_enable and is_enabled != "FALSE":
|
||||
api_command(ansible_module, "dnsforwardzone_disable",
|
||||
name, {})
|
||||
changed = True
|
||||
|
||||
except Exception as e:
|
||||
ansible_module.fail_json(msg=str(e))
|
||||
|
||||
finally:
|
||||
temp_kdestroy(ccache_dir, ccache_name)
|
||||
|
||||
# Done
|
||||
ansible_module.exit_json(changed=changed, dnsforwardzone=exit_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1509
plugins/modules/ipadnsrecord.py
Normal file
1509
plugins/modules/ipadnsrecord.py
Normal file
File diff suppressed because it is too large
Load Diff
474
plugins/modules/ipadnszone.py
Normal file
474
plugins/modules/ipadnszone.py
Normal file
@@ -0,0 +1,474 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authors:
|
||||
# Sergio Oliveira Campos <seocam@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2020 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program 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.
|
||||
#
|
||||
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
"metadata_version": "1.0",
|
||||
"supported_by": "community",
|
||||
"status": ["preview"],
|
||||
}
|
||||
|
||||
DOCUMENTATION = """
|
||||
module: ipadnszone
|
||||
short description: Manage FreeIPA dnszone
|
||||
description: Manage FreeIPA dnszone
|
||||
options:
|
||||
ipaadmin_principal:
|
||||
description: The admin principal
|
||||
default: admin
|
||||
ipaadmin_password:
|
||||
description: The admin password
|
||||
required: false
|
||||
|
||||
name:
|
||||
description: The zone name string.
|
||||
required: true
|
||||
type: str
|
||||
alises: ["zone_name"]
|
||||
forwarders:
|
||||
description: The list of global DNS forwarders.
|
||||
required: false
|
||||
options:
|
||||
ip_address:
|
||||
description: The forwarder nameserver IP address list (IPv4 and IPv6).
|
||||
required: true
|
||||
port:
|
||||
description: The port to forward requests to.
|
||||
required: false
|
||||
forward_policy:
|
||||
description:
|
||||
Global forwarding policy. Set to "none" to disable any configured
|
||||
global forwarders.
|
||||
required: false
|
||||
choices: ['only', 'first', 'none']
|
||||
allow_sync_ptr:
|
||||
description:
|
||||
Allow synchronization of forward (A, AAAA) and reverse (PTR) records.
|
||||
required: false
|
||||
type: bool
|
||||
state:
|
||||
description: State to ensure
|
||||
default: present
|
||||
choices: ["present", "absent", "enabled", "disabled"]
|
||||
name_server:
|
||||
description: Authoritative nameserver domain name
|
||||
required: false
|
||||
type: str
|
||||
admin_email:
|
||||
description: Administrator e-mail address
|
||||
required: false
|
||||
type: str
|
||||
update_policy:
|
||||
description: BIND update policy
|
||||
required: false
|
||||
type: str
|
||||
dynamic_update:
|
||||
description: Allow dynamic updates
|
||||
required: false
|
||||
type: bool
|
||||
alises: ["dynamicupdate"]
|
||||
dnssec:
|
||||
description: Allow inline DNSSEC signing of records in the zone
|
||||
required: false
|
||||
type: bool
|
||||
allow_transfer:
|
||||
description: List of IP addresses or networks which are allowed to transfer the zone
|
||||
required: false
|
||||
type: bool
|
||||
allow_query:
|
||||
description: List of IP addresses or networks which are allowed to issue queries
|
||||
required: false
|
||||
type: bool
|
||||
serial:
|
||||
description: SOA record serial number
|
||||
required: false
|
||||
type: int
|
||||
refresh:
|
||||
description: SOA record refresh time
|
||||
required: false
|
||||
type: int
|
||||
retry:
|
||||
description: SOA record retry time
|
||||
required: false
|
||||
type: int
|
||||
expire:
|
||||
description: SOA record expire time
|
||||
required: false
|
||||
type: int
|
||||
minimum:
|
||||
description: How long should negative responses be cached
|
||||
required: false
|
||||
type: int
|
||||
ttl:
|
||||
description: Time to live for records at zone apex
|
||||
required: false
|
||||
type: int
|
||||
default_ttl:
|
||||
description: Time to live for records without explicit TTL definition
|
||||
required: false
|
||||
type: int
|
||||
nsec3param_rec:
|
||||
description: NSEC3PARAM record for zone in format: hash_algorithm flags iterations salt.
|
||||
required: false
|
||||
type: str
|
||||
skip_overlap_check:
|
||||
description: Force DNS zone creation even if it will overlap with an existing zone
|
||||
required: false
|
||||
type: bool
|
||||
skip_nameserver_check:
|
||||
description: Force DNS zone creation even if nameserver is not resolvable
|
||||
required: false
|
||||
type: bool
|
||||
""" # noqa: E501
|
||||
|
||||
EXAMPLES = """
|
||||
---
|
||||
# Ensure the zone is present (very minimal)
|
||||
- ipadnszone:
|
||||
name: test.example.com
|
||||
|
||||
# Ensure the zone is present (all available arguments)
|
||||
- ipadnszone:
|
||||
name: test.example.com
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
allow_sync_ptr: true
|
||||
dynamic_update: true
|
||||
dnssec: true
|
||||
allow_transfer:
|
||||
- 1.1.1.1
|
||||
- 2.2.2.2
|
||||
allow_query:
|
||||
- 1.1.1.1
|
||||
- 2.2.2.2
|
||||
forwarders:
|
||||
- ip_address: 8.8.8.8
|
||||
- ip_address: 8.8.4.4
|
||||
port: 52
|
||||
serial: 1234
|
||||
refresh: 3600
|
||||
retry: 900
|
||||
expire: 1209600
|
||||
minimum: 3600
|
||||
ttl: 60
|
||||
default_ttl: 90
|
||||
name_server: ipaserver.test.local.
|
||||
admin_email: admin.admin@example.com
|
||||
nsec3param_rec: "1 7 100 0123456789abcdef"
|
||||
skip_overlap_check: true
|
||||
skip_nameserver_check: true
|
||||
state: present
|
||||
|
||||
# Ensure zone is present and disabled
|
||||
- ipadnszone:
|
||||
name: test.example.com
|
||||
state: disabled
|
||||
|
||||
# Ensure zone is present and enabled
|
||||
- ipadnszone:
|
||||
name: test.example.com
|
||||
state: enabled
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
"""
|
||||
|
||||
from ipapython.dnsutil import DNSName # noqa: E402
|
||||
from ansible.module_utils.ansible_freeipa_module import (
|
||||
FreeIPABaseModule,
|
||||
is_ipv4_addr,
|
||||
is_ipv6_addr,
|
||||
is_valid_port,
|
||||
) # noqa: E402
|
||||
|
||||
|
||||
class DNSZoneModule(FreeIPABaseModule):
|
||||
|
||||
ipa_param_mapping = {
|
||||
# Direct Mapping
|
||||
"idnsforwardpolicy": "forward_policy",
|
||||
"idnssoaserial": "serial",
|
||||
"idnssoarefresh": "refresh",
|
||||
"idnssoaretry": "retry",
|
||||
"idnssoaexpire": "expire",
|
||||
"idnssoaminimum": "minimum",
|
||||
"dnsttl": "ttl",
|
||||
"dnsdefaultttl": "default_ttl",
|
||||
"idnsallowsyncptr": "allow_sync_ptr",
|
||||
"idnsallowdynupdate": "dynamic_update",
|
||||
"idnssecinlinesigning": "dnssec",
|
||||
"idnsupdatepolicy": "update_policy",
|
||||
# Mapping by method
|
||||
"idnsforwarders": "get_ipa_idnsforwarders",
|
||||
"idnsallowtransfer": "get_ipa_idnsallowtransfer",
|
||||
"idnsallowquery": "get_ipa_idnsallowquery",
|
||||
"idnssoamname": "get_ipa_idnssoamname",
|
||||
"idnssoarname": "get_ipa_idnssoarname",
|
||||
"skip_nameserver_check": "get_ipa_skip_nameserver_check",
|
||||
"skip_overlap_check": "get_ipa_skip_overlap_check",
|
||||
"nsec3paramrecord": "get_ipa_nsec3paramrecord",
|
||||
}
|
||||
|
||||
def validate_ips(self, ips, error_msg):
|
||||
invalid_ips = [
|
||||
ip for ip in ips if not is_ipv4_addr(ip) or is_ipv6_addr(ip)
|
||||
]
|
||||
if any(invalid_ips):
|
||||
self.fail_json(msg=error_msg % invalid_ips)
|
||||
|
||||
def is_valid_nsec3param_rec(self, nsec3param_rec):
|
||||
try:
|
||||
part1, part2, part3, part4 = nsec3param_rec.split(" ")
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
if not all([part1.isdigit(), part2.isdigit(), part3.isdigit()]):
|
||||
return False
|
||||
|
||||
if not 0 <= int(part1) <= 255:
|
||||
return False
|
||||
|
||||
if not 0 <= int(part2) <= 255:
|
||||
return False
|
||||
|
||||
if not 0 <= int(part3) <= 65535:
|
||||
return False
|
||||
|
||||
try:
|
||||
int(part4, 16)
|
||||
except ValueError:
|
||||
is_hex = False
|
||||
else:
|
||||
is_hex = True
|
||||
|
||||
even_digits = len(part4) % 2 == 0
|
||||
is_dash = part4 == "-"
|
||||
|
||||
# If not hex with even digits or dash then
|
||||
# part4 is invalid
|
||||
if not ((is_hex and even_digits) or is_dash):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def get_ipa_nsec3paramrecord(self):
|
||||
nsec3param_rec = self.ipa_params.nsec3param_rec
|
||||
if nsec3param_rec is not None:
|
||||
error_msg = (
|
||||
"Invalid nsec3param_rec: %s. "
|
||||
"Expected format: <0-255> <0-255> <0-65535> "
|
||||
"even-length_hexadecimal_digits_or_hyphen"
|
||||
) % nsec3param_rec
|
||||
if not self.is_valid_nsec3param_rec(nsec3param_rec):
|
||||
self.fail_json(msg=error_msg)
|
||||
return nsec3param_rec
|
||||
|
||||
def get_ipa_idnsforwarders(self):
|
||||
if self.ipa_params.forwarders is not None:
|
||||
forwarders = []
|
||||
for forwarder in self.ipa_params.forwarders:
|
||||
ip_address = forwarder.get("ip_address")
|
||||
if not (is_ipv4_addr(ip_address) or is_ipv6_addr(ip_address)):
|
||||
self.fail_json(
|
||||
msg="Invalid IP for DNS forwarder: %s" % ip_address
|
||||
)
|
||||
|
||||
port = forwarder.get("port", None)
|
||||
if port and not is_valid_port(port):
|
||||
self.fail_json(
|
||||
msg="Invalid port number for DNS forwarder: %s %s"
|
||||
% (ip_address, port)
|
||||
)
|
||||
formatted_forwarder = ip_address
|
||||
port = forwarder.get("port")
|
||||
if port:
|
||||
formatted_forwarder += " port %d" % port
|
||||
forwarders.append(formatted_forwarder)
|
||||
|
||||
return forwarders
|
||||
|
||||
def get_ipa_idnsallowtransfer(self):
|
||||
if self.ipa_params.allow_transfer is not None:
|
||||
error_msg = "Invalid ip_address for DNS allow_transfer: %s"
|
||||
self.validate_ips(self.ipa_params.allow_transfer, error_msg)
|
||||
|
||||
return (";".join(self.ipa_params.allow_transfer) or "none") + ";"
|
||||
|
||||
def get_ipa_idnsallowquery(self):
|
||||
if self.ipa_params.allow_query is not None:
|
||||
error_msg = "Invalid ip_address for DNS allow_query: %s"
|
||||
self.validate_ips(self.ipa_params.allow_query, error_msg)
|
||||
|
||||
return (";".join(self.ipa_params.allow_query) or "any") + ";"
|
||||
|
||||
@staticmethod
|
||||
def _replace_at_symbol_in_rname(rname):
|
||||
"""
|
||||
See RFC 1035 for more information.
|
||||
|
||||
Section 8. MAIL SUPPORT
|
||||
https://tools.ietf.org/html/rfc1035#section-8
|
||||
"""
|
||||
if "@" not in rname:
|
||||
return rname
|
||||
|
||||
name, domain = rname.split("@")
|
||||
name = name.replace(".", r"\.")
|
||||
|
||||
return ".".join((name, domain))
|
||||
|
||||
def get_ipa_idnssoarname(self):
|
||||
if self.ipa_params.admin_email is not None:
|
||||
return DNSName(
|
||||
self._replace_at_symbol_in_rname(self.ipa_params.admin_email)
|
||||
)
|
||||
|
||||
def get_ipa_idnssoamname(self):
|
||||
if self.ipa_params.name_server is not None:
|
||||
return DNSName(self.ipa_params.name_server)
|
||||
|
||||
def get_ipa_skip_overlap_check(self):
|
||||
if not self.zone and self.ipa_params.skip_overlap_check is not None:
|
||||
return self.ipa_params.skip_overlap_check
|
||||
|
||||
def get_ipa_skip_nameserver_check(self):
|
||||
if not self.zone and self.ipa_params.skip_nameserver_check is not None:
|
||||
return self.ipa_params.skip_nameserver_check
|
||||
|
||||
def get_zone(self, zone_name):
|
||||
get_zone_args = {"idnsname": zone_name, "all": True}
|
||||
response = self.api_command("dnszone_find", args=get_zone_args)
|
||||
|
||||
if response["count"] == 1:
|
||||
self.zone = response["result"][0]
|
||||
self.is_zone_active = self.zone.get("idnszoneactive") == ["TRUE"]
|
||||
return self.zone
|
||||
|
||||
# Zone doesn't exist yet
|
||||
self.zone = None
|
||||
self.is_zone_active = False
|
||||
|
||||
@property
|
||||
def zone_name(self):
|
||||
return self.ipa_params.name
|
||||
|
||||
def define_ipa_commands(self):
|
||||
# Look for existing zone in IPA
|
||||
self.get_zone(self.zone_name)
|
||||
args = self.get_ipa_command_args()
|
||||
just_added = False
|
||||
|
||||
if self.ipa_params.state in ["present", "enabled", "disabled"]:
|
||||
if not self.zone:
|
||||
# Since the zone doesn't exist we just create it
|
||||
# with given args
|
||||
self.add_ipa_command("dnszone_add", self.zone_name, args)
|
||||
self.is_zone_active = True
|
||||
just_added = True
|
||||
|
||||
else:
|
||||
# Zone already exist so we need to verify if given args
|
||||
# matches the current config. If not we updated it.
|
||||
if self.require_ipa_attrs_change(args, self.zone):
|
||||
self.add_ipa_command("dnszone_mod", self.zone_name, args)
|
||||
|
||||
if self.ipa_params.state == "enabled" and not self.is_zone_active:
|
||||
self.add_ipa_command("dnszone_enable", self.zone_name)
|
||||
|
||||
if self.ipa_params.state == "disabled" and self.is_zone_active:
|
||||
self.add_ipa_command("dnszone_disable", self.zone_name)
|
||||
|
||||
if self.ipa_params.state == "absent":
|
||||
if self.zone:
|
||||
self.add_ipa_command("dnszone_del", self.zone_name)
|
||||
|
||||
# Due to a bug in FreeIPA dnszone-add won't set
|
||||
# SOA Serial. The good news is that dnszone-mod does the job.
|
||||
# See: https://pagure.io/freeipa/issue/8227
|
||||
# Because of that, if the zone was just added with a given serial
|
||||
# we run mod just after to workaround the bug
|
||||
if just_added and self.ipa_params.serial is not None:
|
||||
args = {
|
||||
"idnssoaserial": self.ipa_params.serial,
|
||||
}
|
||||
self.add_ipa_command("dnszone_mod", self.zone_name, args)
|
||||
|
||||
|
||||
def get_argument_spec():
|
||||
forwarder_spec = dict(
|
||||
ip_address=dict(type=str, required=True),
|
||||
port=dict(type=int, required=False, default=None),
|
||||
)
|
||||
|
||||
return dict(
|
||||
state=dict(
|
||||
type="str",
|
||||
default="present",
|
||||
choices=["present", "absent", "enabled", "disabled"],
|
||||
),
|
||||
ipaadmin_principal=dict(type="str", default="admin"),
|
||||
ipaadmin_password=dict(type="str", required=False, no_log=True),
|
||||
name=dict(
|
||||
type="str", default=None, required=True, aliases=["zone_name"]
|
||||
),
|
||||
forwarders=dict(
|
||||
type="list",
|
||||
default=None,
|
||||
required=False,
|
||||
options=dict(**forwarder_spec),
|
||||
),
|
||||
forward_policy=dict(
|
||||
type="str",
|
||||
required=False,
|
||||
default=None,
|
||||
choices=["only", "first", "none"],
|
||||
),
|
||||
name_server=dict(type="str", required=False, default=None),
|
||||
admin_email=dict(type="str", required=False, default=None),
|
||||
allow_sync_ptr=dict(type="bool", required=False, default=None),
|
||||
update_policy=dict(type="str", required=False, default=None),
|
||||
dynamic_update=dict(
|
||||
type="bool",
|
||||
required=False,
|
||||
default=None,
|
||||
aliases=["dynamicupdate"],
|
||||
),
|
||||
dnssec=dict(type="bool", required=False, default=None),
|
||||
allow_transfer=dict(type="list", required=False, default=None),
|
||||
allow_query=dict(type="list", required=False, default=None),
|
||||
serial=dict(type="int", required=False, default=None),
|
||||
refresh=dict(type="int", required=False, default=None),
|
||||
retry=dict(type="int", required=False, default=None),
|
||||
expire=dict(type="int", required=False, default=None),
|
||||
minimum=dict(type="int", required=False, default=None),
|
||||
ttl=dict(type="int", required=False, default=None),
|
||||
default_ttl=dict(type="int", required=False, default=None),
|
||||
nsec3param_rec=dict(type="str", required=False, default=None),
|
||||
skip_nameserver_check=dict(type="bool", required=False, default=None),
|
||||
skip_overlap_check=dict(type="bool", required=False, default=None),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
DNSZoneModule(argument_spec=get_argument_spec()).ipa_run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -75,6 +75,18 @@ options:
|
||||
- Only usable with IPA versions 4.7 and up.
|
||||
required: false
|
||||
type: list
|
||||
membermanager_user:
|
||||
description:
|
||||
- List of member manager users assigned to this group.
|
||||
- Only usable with IPA versions 4.8.4 and up.
|
||||
required: false
|
||||
type: list
|
||||
membermanager_group:
|
||||
description:
|
||||
- List of member manager groups assigned to this group.
|
||||
- Only usable with IPA versions 4.8.4 and up.
|
||||
required: false
|
||||
type: list
|
||||
action:
|
||||
description: Work on group or member level
|
||||
default: group
|
||||
@@ -141,7 +153,7 @@ RETURN = """
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
|
||||
api_check_param, module_params_get
|
||||
api_check_param, module_params_get, gen_add_del_lists, api_check_command
|
||||
|
||||
|
||||
def find_group(module, name):
|
||||
@@ -207,6 +219,9 @@ def main():
|
||||
user=dict(required=False, type='list', default=None),
|
||||
group=dict(required=False, type='list', default=None),
|
||||
service=dict(required=False, type='list', default=None),
|
||||
membermanager_user=dict(required=False, type='list', default=None),
|
||||
membermanager_group=dict(required=False, type='list',
|
||||
default=None),
|
||||
action=dict(type="str", default="group",
|
||||
choices=["member", "group"]),
|
||||
# state
|
||||
@@ -221,7 +236,10 @@ def main():
|
||||
# Get parameters
|
||||
|
||||
# general
|
||||
ipaadmin_principal = module_params_get(ansible_module, "ipaadmin_principal")
|
||||
ipaadmin_principal = module_params_get(
|
||||
ansible_module,
|
||||
"ipaadmin_principal",
|
||||
)
|
||||
ipaadmin_password = module_params_get(ansible_module, "ipaadmin_password")
|
||||
names = module_params_get(ansible_module, "name")
|
||||
|
||||
@@ -234,6 +252,10 @@ def main():
|
||||
user = module_params_get(ansible_module, "user")
|
||||
group = module_params_get(ansible_module, "group")
|
||||
service = module_params_get(ansible_module, "service")
|
||||
membermanager_user = module_params_get(ansible_module,
|
||||
"membermanager_user")
|
||||
membermanager_group = module_params_get(ansible_module,
|
||||
"membermanager_group")
|
||||
action = module_params_get(ansible_module, "action")
|
||||
# state
|
||||
state = module_params_get(ansible_module, "state")
|
||||
@@ -284,6 +306,14 @@ def main():
|
||||
msg="Managing a service as part of a group is not supported "
|
||||
"by your IPA version")
|
||||
|
||||
has_add_membermanager = api_check_command("group_add_member_manager")
|
||||
if ((membermanager_user is not None or
|
||||
membermanager_group is not None) and not has_add_membermanager):
|
||||
ansible_module.fail_json(
|
||||
msg="Managing a membermanager user or group is not supported "
|
||||
"by your IPA version"
|
||||
)
|
||||
|
||||
commands = []
|
||||
|
||||
for name in names:
|
||||
@@ -314,24 +344,14 @@ def main():
|
||||
if not compare_args_ipa(ansible_module, member_args,
|
||||
res_find):
|
||||
# Generate addition and removal lists
|
||||
user_add = list(
|
||||
set(user or []) -
|
||||
set(res_find.get("member_user", [])))
|
||||
user_del = list(
|
||||
set(res_find.get("member_user", [])) -
|
||||
set(user or []))
|
||||
group_add = list(
|
||||
set(group or []) -
|
||||
set(res_find.get("member_group", [])))
|
||||
group_del = list(
|
||||
set(res_find.get("member_group", [])) -
|
||||
set(group or []))
|
||||
service_add = list(
|
||||
set(service or []) -
|
||||
set(res_find.get("member_service", [])))
|
||||
service_del = list(
|
||||
set(res_find.get("member_service", [])) -
|
||||
set(service or []))
|
||||
user_add, user_del = gen_add_del_lists(
|
||||
user, res_find.get("member_user"))
|
||||
|
||||
group_add, group_del = gen_add_del_lists(
|
||||
group, res_find.get("member_group"))
|
||||
|
||||
service_add, service_del = gen_add_del_lists(
|
||||
service, res_find.get("member_service"))
|
||||
|
||||
if has_add_member_service:
|
||||
# Add members
|
||||
@@ -367,6 +387,41 @@ def main():
|
||||
"user": user_del,
|
||||
"group": group_del,
|
||||
}])
|
||||
|
||||
membermanager_user_add, membermanager_user_del = \
|
||||
gen_add_del_lists(
|
||||
membermanager_user,
|
||||
res_find.get("membermanager_user")
|
||||
)
|
||||
|
||||
membermanager_group_add, membermanager_group_del = \
|
||||
gen_add_del_lists(
|
||||
membermanager_group,
|
||||
res_find.get("membermanager_group")
|
||||
)
|
||||
|
||||
if has_add_membermanager:
|
||||
# Add membermanager users and groups
|
||||
if len(membermanager_user_add) > 0 or \
|
||||
len(membermanager_group_add) > 0:
|
||||
commands.append(
|
||||
[name, "group_add_member_manager",
|
||||
{
|
||||
"user": membermanager_user_add,
|
||||
"group": membermanager_group_add,
|
||||
}]
|
||||
)
|
||||
# Remove member manager
|
||||
if len(membermanager_user_del) > 0 or \
|
||||
len(membermanager_group_del) > 0:
|
||||
commands.append(
|
||||
[name, "group_remove_member_manager",
|
||||
{
|
||||
"user": membermanager_user_del,
|
||||
"group": membermanager_group_del,
|
||||
}]
|
||||
)
|
||||
|
||||
elif action == "member":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(msg="No group '%s'" % name)
|
||||
@@ -384,6 +439,18 @@ def main():
|
||||
"group": group,
|
||||
}])
|
||||
|
||||
if has_add_membermanager:
|
||||
# Add membermanager users and groups
|
||||
if membermanager_user is not None or \
|
||||
membermanager_group is not None:
|
||||
commands.append(
|
||||
[name, "group_add_member_manager",
|
||||
{
|
||||
"user": membermanager_user,
|
||||
"group": membermanager_group,
|
||||
}]
|
||||
)
|
||||
|
||||
elif state == "absent":
|
||||
if action == "group":
|
||||
if res_find is not None:
|
||||
@@ -393,12 +460,32 @@ def main():
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(msg="No group '%s'" % name)
|
||||
|
||||
commands.append([name, "group_remove_member",
|
||||
{
|
||||
"user": user,
|
||||
"group": group,
|
||||
"service": service,
|
||||
}])
|
||||
if has_add_member_service:
|
||||
commands.append([name, "group_remove_member",
|
||||
{
|
||||
"user": user,
|
||||
"group": group,
|
||||
"service": service,
|
||||
}])
|
||||
else:
|
||||
commands.append([name, "group_remove_member",
|
||||
{
|
||||
"user": user,
|
||||
"group": group,
|
||||
}])
|
||||
|
||||
if has_add_membermanager:
|
||||
# Remove membermanager users and groups
|
||||
if membermanager_user is not None or \
|
||||
membermanager_group is not None:
|
||||
commands.append(
|
||||
[name, "group_remove_member_manager",
|
||||
{
|
||||
"user": membermanager_user,
|
||||
"group": membermanager_group,
|
||||
}]
|
||||
)
|
||||
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
|
||||
|
||||
@@ -49,17 +49,17 @@ options:
|
||||
description: User category the rule applies to
|
||||
required: false
|
||||
aliases: ["usercat"]
|
||||
choices: ["all"]
|
||||
choices: ["all", ""]
|
||||
hostcategory:
|
||||
description: Host category the rule applies to
|
||||
required: false
|
||||
aliases: ["hostcat"]
|
||||
choices: ["all"]
|
||||
choices: ["all", ""]
|
||||
servicecategory:
|
||||
description: Service category the rule applies to
|
||||
required: false
|
||||
aliases: ["servicecat"]
|
||||
choices: ["all"]
|
||||
choices: ["all", ""]
|
||||
nomembers:
|
||||
description: Suppress processing of membership attributes
|
||||
required: false
|
||||
@@ -159,7 +159,7 @@ RETURN = """
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
|
||||
module_params_get
|
||||
module_params_get, gen_add_del_lists
|
||||
|
||||
|
||||
def find_hbacrule(module, name):
|
||||
@@ -208,11 +208,11 @@ def main():
|
||||
# present
|
||||
description=dict(type="str", default=None),
|
||||
usercategory=dict(type="str", default=None,
|
||||
aliases=["usercat"], choices=["all"]),
|
||||
aliases=["usercat"], choices=["all", ""]),
|
||||
hostcategory=dict(type="str", default=None,
|
||||
aliases=["hostcat"], choices=["all"]),
|
||||
aliases=["hostcat"], choices=["all", ""]),
|
||||
servicecategory=dict(type="str", default=None,
|
||||
aliases=["servicecat"], choices=["all"]),
|
||||
aliases=["servicecat"], choices=["all", ""]),
|
||||
nomembers=dict(required=False, type='bool', default=None),
|
||||
host=dict(required=False, type='list', default=None),
|
||||
hostgroup=dict(required=False, type='list', default=None),
|
||||
@@ -270,6 +270,16 @@ def main():
|
||||
ansible_module.fail_json(
|
||||
msg="Argument '%s' can not be used with action "
|
||||
"'%s'" % (x, action))
|
||||
else:
|
||||
if hostcategory == 'all' and any([host, hostgroup]):
|
||||
ansible_module.fail_json(
|
||||
msg="Hosts cannot be added when host category='all'")
|
||||
if usercategory == 'all' and any([user, group]):
|
||||
ansible_module.fail_json(
|
||||
msg="Users cannot be added when user category='all'")
|
||||
if servicecategory == 'all' and any([hbacsvc, hbacsvcgroup]):
|
||||
ansible_module.fail_json(
|
||||
msg="Services cannot be added when service category='all'")
|
||||
|
||||
elif state == "absent":
|
||||
if len(names) < 1:
|
||||
@@ -342,44 +352,24 @@ def main():
|
||||
res_find = {}
|
||||
|
||||
# Generate addition and removal lists
|
||||
host_add = list(
|
||||
set(host or []) -
|
||||
set(res_find.get("memberhost_host", [])))
|
||||
host_del = list(
|
||||
set(res_find.get("memberhost_host", [])) -
|
||||
set(host or []))
|
||||
hostgroup_add = list(
|
||||
set(hostgroup or []) -
|
||||
set(res_find.get("memberhost_hostgroup", [])))
|
||||
hostgroup_del = list(
|
||||
set(res_find.get("memberhost_hostgroup", [])) -
|
||||
set(hostgroup or []))
|
||||
host_add, host_del = gen_add_del_lists(
|
||||
host, res_find.get("memberhost_host"))
|
||||
|
||||
hbacsvc_add = list(
|
||||
set(hbacsvc or []) -
|
||||
set(res_find.get("memberservice_hbacsvc", [])))
|
||||
hbacsvc_del = list(
|
||||
set(res_find.get("memberservice_hbacsvc", [])) -
|
||||
set(hbacsvc or []))
|
||||
hbacsvcgroup_add = list(
|
||||
set(hbacsvcgroup or []) -
|
||||
set(res_find.get("memberservice_hbacsvcgroup", [])))
|
||||
hbacsvcgroup_del = list(
|
||||
set(res_find.get("memberservice_hbacsvcgroup", [])) -
|
||||
set(hbacsvcgroup or []))
|
||||
hostgroup_add, hostgroup_del = gen_add_del_lists(
|
||||
hostgroup, res_find.get("memberhost_hostgroup"))
|
||||
|
||||
user_add = list(
|
||||
set(user or []) -
|
||||
set(res_find.get("memberuser_user", [])))
|
||||
user_del = list(
|
||||
set(res_find.get("memberuser_user", [])) -
|
||||
set(user or []))
|
||||
group_add = list(
|
||||
set(group or []) -
|
||||
set(res_find.get("memberuser_group", [])))
|
||||
group_del = list(
|
||||
set(res_find.get("memberuser_group", [])) -
|
||||
set(group or []))
|
||||
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
|
||||
hbacsvc, res_find.get("memberservice_hbacsvc"))
|
||||
|
||||
hbacsvcgroup_add, hbacsvcgroup_del = gen_add_del_lists(
|
||||
hbacsvcgroup,
|
||||
res_find.get("memberservice_hbacsvcgroup"))
|
||||
|
||||
user_add, user_del = gen_add_del_lists(
|
||||
user, res_find.get("memberuser_user"))
|
||||
|
||||
group_add, group_del = gen_add_del_lists(
|
||||
group, res_find.get("memberuser_group"))
|
||||
|
||||
# Add hosts and hostgroups
|
||||
if len(host_add) > 0 or len(hostgroup_add) > 0:
|
||||
|
||||
@@ -104,7 +104,8 @@ RETURN = """
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
|
||||
gen_add_del_lists
|
||||
|
||||
|
||||
def find_hbacsvcgroup(module, name):
|
||||
@@ -249,12 +250,8 @@ def main():
|
||||
if not compare_args_ipa(ansible_module, member_args,
|
||||
res_find):
|
||||
# Generate addition and removal lists
|
||||
hbacsvc_add = list(
|
||||
set(hbacsvc or []) -
|
||||
set(res_find.get("member_hbacsvc", [])))
|
||||
hbacsvc_del = list(
|
||||
set(res_find.get("member_hbacsvc", [])) -
|
||||
set(hbacsvc or []))
|
||||
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
|
||||
hbacsvc, res_find.get("member_hbacsvc"))
|
||||
|
||||
# Add members
|
||||
if len(hbacsvc_add) > 0:
|
||||
|
||||
@@ -420,23 +420,22 @@ if six.PY3:
|
||||
def find_host(module, name):
|
||||
_args = {
|
||||
"all": True,
|
||||
"fqdn": to_text(name),
|
||||
}
|
||||
|
||||
_result = api_command(module, "host_find", to_text(name), _args)
|
||||
try:
|
||||
_result = api_command(module, "host_show", to_text(name), _args)
|
||||
except ipalib_errors.NotFound as e:
|
||||
msg = str(e)
|
||||
if "host not found" in msg:
|
||||
return None
|
||||
module.fail_json(msg="host_show failed: %s" % msg)
|
||||
|
||||
if len(_result["result"]) > 1:
|
||||
module.fail_json(
|
||||
msg="There is more than one host '%s'" % (name))
|
||||
elif len(_result["result"]) == 1:
|
||||
_res = _result["result"][0]
|
||||
certs = _res.get("usercertificate")
|
||||
if certs is not None:
|
||||
_res["usercertificate"] = [encode_certificate(cert) for
|
||||
cert in certs]
|
||||
return _res
|
||||
else:
|
||||
return None
|
||||
_res = _result["result"]
|
||||
certs = _res.get("usercertificate")
|
||||
if certs is not None:
|
||||
_res["usercertificate"] = [encode_certificate(cert) for
|
||||
cert in certs]
|
||||
return _res
|
||||
|
||||
|
||||
def find_dnsrecord(module, name):
|
||||
@@ -445,24 +444,19 @@ def find_dnsrecord(module, name):
|
||||
|
||||
_args = {
|
||||
"all": True,
|
||||
"idnsname": to_text(host_name),
|
||||
"idnsname": to_text(host_name)
|
||||
}
|
||||
|
||||
_result = api_command(module, "dnsrecord_find", to_text(domain_name),
|
||||
_args)
|
||||
try:
|
||||
_result = api_command(module, "dnsrecord_show", to_text(domain_name),
|
||||
_args)
|
||||
except ipalib_errors.NotFound as e:
|
||||
msg = str(e)
|
||||
if "record not found" in msg or "zone not found" in msg:
|
||||
return None
|
||||
module.fail_json(msg="dnsrecord_show failed: %s" % msg)
|
||||
|
||||
if len(_result["result"]) > 1:
|
||||
module.fail_json(
|
||||
msg="There is more than one host '%s'" % (name))
|
||||
elif len(_result["result"]) == 1:
|
||||
_res = _result["result"][0]
|
||||
certs = _res.get("usercertificate")
|
||||
if certs is not None:
|
||||
_res["usercertificate"] = [encode_certificate(cert) for
|
||||
cert in certs]
|
||||
return _res
|
||||
else:
|
||||
return None
|
||||
return _result["result"]
|
||||
|
||||
|
||||
def show_host(module, name):
|
||||
@@ -875,9 +869,11 @@ def main():
|
||||
res_find_dnsrecord = find_dnsrecord(ansible_module, name)
|
||||
except ipalib_errors.NotFound as e:
|
||||
msg = str(e)
|
||||
if ip_address is None and \
|
||||
("DNS is not configured" in msg or \
|
||||
"DNS zone not found" in msg):
|
||||
dns_not_configured = "DNS is not configured" in msg
|
||||
dns_zone_not_found = "DNS zone not found" in msg
|
||||
if ip_address is None and (
|
||||
dns_not_configured or dns_zone_not_found
|
||||
):
|
||||
# IP address(es) not given and no DNS support in IPA
|
||||
# -> Ignore failure
|
||||
# IP address(es) not given and DNS zone is not found
|
||||
@@ -901,9 +897,25 @@ def main():
|
||||
# Found the host
|
||||
if res_find is not None:
|
||||
# Ignore password with update_password == on_create
|
||||
if update_password == "on_create" and \
|
||||
"userpassword" in args:
|
||||
del args["userpassword"]
|
||||
if update_password == "on_create":
|
||||
# Ignore userpassword and random for existing
|
||||
# host if update_password is "on_create"
|
||||
if "userpassword" in args:
|
||||
del args["userpassword"]
|
||||
if "random" in args:
|
||||
del args["random"]
|
||||
elif "userpassword" in args or "random" in args:
|
||||
# Allow an existing OTP to be reset but don't
|
||||
# allow a OTP or to be added to an enrolled host.
|
||||
# Also do not allow to change the password for an
|
||||
# enrolled host.
|
||||
|
||||
if not res_find["has_password"] and \
|
||||
res_find["has_keytab"]:
|
||||
ansible_module.fail_json(
|
||||
msg="%s: Password cannot be set on "
|
||||
"enrolled host." % host
|
||||
)
|
||||
|
||||
# Ignore force, ip_address and no_reverse for mod
|
||||
for x in ["force", "ip_address", "no_reverse"]:
|
||||
@@ -951,7 +963,7 @@ def main():
|
||||
principal_add, principal_del = gen_add_del_lists(
|
||||
principal, res_find.get("principal"))
|
||||
# Principals are not returned as utf8 for IPA using
|
||||
# python2 using host_find, therefore we need to
|
||||
# python2 using host_show, therefore we need to
|
||||
# convert the principals that we should remove.
|
||||
principal_del = [to_text(x) for x in principal_del]
|
||||
|
||||
|
||||
@@ -58,6 +58,18 @@ options:
|
||||
description: List of hostgroup names assigned to this hostgroup.
|
||||
required: false
|
||||
type: list
|
||||
membermanager_user:
|
||||
description:
|
||||
- List of member manager users assigned to this hostgroup.
|
||||
- Only usable with IPA versions 4.8.4 and up.
|
||||
required: false
|
||||
type: list
|
||||
membermanager_group:
|
||||
description:
|
||||
- List of member manager groups assigned to this hostgroup.
|
||||
- Only usable with IPA versions 4.8.4 and up.
|
||||
required: false
|
||||
type: list
|
||||
action:
|
||||
description: Work on hostgroup or member level
|
||||
default: hostgroup
|
||||
@@ -117,7 +129,7 @@ RETURN = """
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
|
||||
module_params_get
|
||||
module_params_get, gen_add_del_lists, api_check_command
|
||||
|
||||
|
||||
def find_hostgroup(module, name):
|
||||
@@ -171,6 +183,9 @@ def main():
|
||||
nomembers=dict(required=False, type='bool', default=None),
|
||||
host=dict(required=False, type='list', default=None),
|
||||
hostgroup=dict(required=False, type='list', default=None),
|
||||
membermanager_user=dict(required=False, type='list', default=None),
|
||||
membermanager_group=dict(required=False, type='list',
|
||||
default=None),
|
||||
action=dict(type="str", default="hostgroup",
|
||||
choices=["member", "hostgroup"]),
|
||||
# state
|
||||
@@ -196,6 +211,10 @@ def main():
|
||||
nomembers = module_params_get(ansible_module, "nomembers")
|
||||
host = module_params_get(ansible_module, "host")
|
||||
hostgroup = module_params_get(ansible_module, "hostgroup")
|
||||
membermanager_user = module_params_get(ansible_module,
|
||||
"membermanager_user")
|
||||
membermanager_group = module_params_get(ansible_module,
|
||||
"membermanager_group")
|
||||
action = module_params_get(ansible_module, "action")
|
||||
# state
|
||||
state = module_params_get(ansible_module, "state")
|
||||
@@ -239,6 +258,15 @@ def main():
|
||||
ipaadmin_password)
|
||||
api_connect()
|
||||
|
||||
has_add_membermanager = api_check_command(
|
||||
"hostgroup_add_member_manager")
|
||||
if ((membermanager_user is not None or
|
||||
membermanager_group is not None) and not has_add_membermanager):
|
||||
ansible_module.fail_json(
|
||||
msg="Managing a membermanager user or group is not supported "
|
||||
"by your IPA version"
|
||||
)
|
||||
|
||||
commands = []
|
||||
|
||||
for name in names:
|
||||
@@ -268,18 +296,11 @@ def main():
|
||||
if not compare_args_ipa(ansible_module, member_args,
|
||||
res_find):
|
||||
# Generate addition and removal lists
|
||||
host_add = list(
|
||||
set(host or []) -
|
||||
set(res_find.get("member_host", [])))
|
||||
host_del = list(
|
||||
set(res_find.get("member_host", [])) -
|
||||
set(host or []))
|
||||
hostgroup_add = list(
|
||||
set(hostgroup or []) -
|
||||
set(res_find.get("member_hostgroup", [])))
|
||||
hostgroup_del = list(
|
||||
set(res_find.get("member_hostgroup", [])) -
|
||||
set(hostgroup or []))
|
||||
host_add, host_del = gen_add_del_lists(
|
||||
host, res_find.get("member_host"))
|
||||
|
||||
hostgroup_add, hostgroup_del = gen_add_del_lists(
|
||||
hostgroup, res_find.get("member_hostgroup"))
|
||||
|
||||
# Add members
|
||||
if len(host_add) > 0 or len(hostgroup_add) > 0:
|
||||
@@ -295,6 +316,41 @@ def main():
|
||||
"host": host_del,
|
||||
"hostgroup": hostgroup_del,
|
||||
}])
|
||||
|
||||
membermanager_user_add, membermanager_user_del = \
|
||||
gen_add_del_lists(
|
||||
membermanager_user,
|
||||
res_find.get("membermanager_user")
|
||||
)
|
||||
|
||||
membermanager_group_add, membermanager_group_del = \
|
||||
gen_add_del_lists(
|
||||
membermanager_group,
|
||||
res_find.get("membermanager_group")
|
||||
)
|
||||
|
||||
if has_add_membermanager:
|
||||
# Add membermanager users and groups
|
||||
if len(membermanager_user_add) > 0 or \
|
||||
len(membermanager_group_add) > 0:
|
||||
commands.append(
|
||||
[name, "hostgroup_add_member_manager",
|
||||
{
|
||||
"user": membermanager_user_add,
|
||||
"group": membermanager_group_add,
|
||||
}]
|
||||
)
|
||||
# Remove member manager
|
||||
if len(membermanager_user_del) > 0 or \
|
||||
len(membermanager_group_del) > 0:
|
||||
commands.append(
|
||||
[name, "hostgroup_remove_member_manager",
|
||||
{
|
||||
"user": membermanager_user_del,
|
||||
"group": membermanager_group_del,
|
||||
}]
|
||||
)
|
||||
|
||||
elif action == "member":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(
|
||||
@@ -306,6 +362,19 @@ def main():
|
||||
"host": host,
|
||||
"hostgroup": hostgroup,
|
||||
}])
|
||||
|
||||
if has_add_membermanager:
|
||||
# Add membermanager users and groups
|
||||
if membermanager_user is not None or \
|
||||
membermanager_group is not None:
|
||||
commands.append(
|
||||
[name, "hostgroup_add_member_manager",
|
||||
{
|
||||
"user": membermanager_user,
|
||||
"group": membermanager_group,
|
||||
}]
|
||||
)
|
||||
|
||||
elif state == "absent":
|
||||
if action == "hostgroup":
|
||||
if res_find is not None:
|
||||
@@ -322,6 +391,19 @@ def main():
|
||||
"host": host,
|
||||
"hostgroup": hostgroup,
|
||||
}])
|
||||
|
||||
if has_add_membermanager:
|
||||
# Remove membermanager users and groups
|
||||
if membermanager_user is not None or \
|
||||
membermanager_group is not None:
|
||||
commands.append(
|
||||
[name, "hostgroup_remove_member_manager",
|
||||
{
|
||||
"user": membermanager_user,
|
||||
"group": membermanager_group,
|
||||
}]
|
||||
)
|
||||
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
|
||||
|
||||
@@ -90,6 +90,14 @@ options:
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["krbprincipalname"]
|
||||
smb:
|
||||
description: Add a SMB service. Can only be used with new services.
|
||||
required: false
|
||||
type: bool
|
||||
netbiosname:
|
||||
description: NETBIOS name for the SMB service.
|
||||
required: false
|
||||
type: str
|
||||
host:
|
||||
description: Host that can manage the service.
|
||||
required: false
|
||||
@@ -135,6 +143,12 @@ options:
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipaallowedtoperform_read_keys_hostgroup"]
|
||||
continue:
|
||||
description:
|
||||
Continuous mode. Don't stop on errors. Valid only if `state` is `absent`.
|
||||
required: false
|
||||
default: True
|
||||
type: bool
|
||||
action:
|
||||
description: Work on service or member level
|
||||
default: service
|
||||
@@ -142,7 +156,7 @@ options:
|
||||
state:
|
||||
description: State to ensure
|
||||
default: present
|
||||
choices: ["present", "absent", "enabled", "disabled"]
|
||||
choices: ["present", "absent", "disabled"]
|
||||
author:
|
||||
- Rafael Jeffman
|
||||
"""
|
||||
@@ -217,20 +231,31 @@ from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
|
||||
encode_certificate, gen_add_del_lists, module_params_get, to_text, \
|
||||
api_check_param
|
||||
import ipalib.errors
|
||||
|
||||
|
||||
def find_service(module, name):
|
||||
def find_service(module, name, netbiosname):
|
||||
_args = {
|
||||
"all": True,
|
||||
}
|
||||
|
||||
_result = api_command(module, "service_find", to_text(name), _args)
|
||||
# Search for a SMB/cifs service.
|
||||
if netbiosname is not None:
|
||||
_result = api_command(
|
||||
module, "service_find", to_text(netbiosname), _args)
|
||||
|
||||
if len(_result["result"]) > 1:
|
||||
module.fail_json(
|
||||
msg="There is more than one service '%s'" % (name))
|
||||
elif len(_result["result"]) == 1:
|
||||
_res = _result["result"][0]
|
||||
for _res_find in _result.get('result', []):
|
||||
for uid in _res_find.get('uid', []):
|
||||
if uid.startswith("%s$@" % netbiosname):
|
||||
return _res_find
|
||||
|
||||
try:
|
||||
_result = api_command(module, "service_show", to_text(name), _args)
|
||||
except ipalib.errors.NotFound:
|
||||
return None
|
||||
|
||||
if "result" in _result:
|
||||
_res = _result["result"]
|
||||
certs = _res.get("usercertificate")
|
||||
if certs is not None:
|
||||
_res["usercertificate"] = [encode_certificate(cert) for
|
||||
@@ -268,7 +293,7 @@ def check_parameters(module, state, action, names, parameters):
|
||||
# invalid parameters for everything but state 'present', action 'service'.
|
||||
invalid = ['pac_type', 'auth_ind', 'skip_host_check',
|
||||
'force', 'requires_pre_auth', 'ok_as_delegate',
|
||||
'ok_to_auth_as_delegate']
|
||||
'ok_to_auth_as_delegate', 'smb', 'netbiosname']
|
||||
|
||||
# invalid parameters when not handling service members.
|
||||
invalid_not_member = \
|
||||
@@ -283,7 +308,19 @@ def check_parameters(module, state, action, names, parameters):
|
||||
module.fail_json(msg="Only one service can be added at a time.")
|
||||
|
||||
if action == 'service':
|
||||
invalid = []
|
||||
invalid = ['delete_continue']
|
||||
|
||||
if parameters.get('smb', False):
|
||||
invalid.extend(['force', 'auth_ind', 'skip_host_check',
|
||||
'requires_pre_auth', 'auth_ind', 'pac_type'])
|
||||
|
||||
for _invalid in invalid:
|
||||
if parameters.get(_invalid, False):
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with SMB "
|
||||
"service." % _invalid)
|
||||
else:
|
||||
invalid.append('delete_continue')
|
||||
|
||||
elif state == 'absent':
|
||||
if len(names) < 1:
|
||||
@@ -291,9 +328,12 @@ def check_parameters(module, state, action, names, parameters):
|
||||
|
||||
if action == "service":
|
||||
invalid.extend(invalid_not_member)
|
||||
else:
|
||||
invalid.extend('delete_continue')
|
||||
|
||||
elif state == 'disabled':
|
||||
invalid.extend(invalid_not_member)
|
||||
invalid.append('delete_continue')
|
||||
if action != "service":
|
||||
module.fail_json(
|
||||
msg="Invalid action '%s' for state '%s'" % (action, state))
|
||||
@@ -302,10 +342,10 @@ def check_parameters(module, state, action, names, parameters):
|
||||
module.fail_json(msg="Invalid state '%s'" % (state))
|
||||
|
||||
for _invalid in invalid:
|
||||
if parameters[_invalid] is not None:
|
||||
if _invalid in parameters and parameters[_invalid] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with state '%s'" %
|
||||
(_invalid, state))
|
||||
msg="Argument '%s' can not be used with state '%s', "
|
||||
"action '%s'" % (_invalid, state, action))
|
||||
|
||||
|
||||
def init_ansible_module():
|
||||
@@ -322,11 +362,13 @@ def init_ansible_module():
|
||||
default=None, required=False),
|
||||
principal=dict(type="list", aliases=["krbprincipalname"],
|
||||
default=None),
|
||||
smb=dict(type="bool", required=False),
|
||||
netbiosname=dict(type="str", required=False),
|
||||
pac_type=dict(type="list", aliases=["ipakrbauthzdata"],
|
||||
choices=["MS-PAC", "PAD", "NONE"]),
|
||||
auth_ind=dict(type="str",
|
||||
auth_ind=dict(type="list",
|
||||
aliases=["krbprincipalauthind"],
|
||||
choices=["otp", "radius", "pkinit", "hardened"]),
|
||||
choices=["otp", "radius", "pkinit", "hardened", ""]),
|
||||
skip_host_check=dict(type="bool"),
|
||||
force=dict(type="bool"),
|
||||
requires_pre_auth=dict(
|
||||
@@ -359,13 +401,14 @@ def init_ansible_module():
|
||||
allow_retrieve_keytab_hostgroup=dict(
|
||||
type="list", required=False,
|
||||
aliases=['ipaallowedtoperform_read_keys_hostgroup']),
|
||||
delete_continue=dict(type="bool", required=False,
|
||||
aliases=['continue']),
|
||||
# action
|
||||
action=dict(type="str", default="service",
|
||||
choices=["member", "service"]),
|
||||
# state
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent",
|
||||
"enabled", "disabled"]),
|
||||
choices=["present", "absent", "disabled"]),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
@@ -398,6 +441,9 @@ def main():
|
||||
ok_to_auth_as_delegate = module_params_get(ansible_module,
|
||||
"ok_to_auth_as_delegate")
|
||||
|
||||
smb = module_params_get(ansible_module, "smb")
|
||||
netbiosname = module_params_get(ansible_module, "netbiosname")
|
||||
|
||||
host = module_params_get(ansible_module, "host")
|
||||
|
||||
allow_create_keytab_user = module_params_get(
|
||||
@@ -417,6 +463,7 @@ def main():
|
||||
ansible_module, "allow_create_keytab_host")
|
||||
allow_retrieve_keytab_hostgroup = module_params_get(
|
||||
ansible_module, "allow_retrieve_keytab_hostgroup")
|
||||
delete_continue = module_params_get(ansible_module, "delete_continue")
|
||||
|
||||
# action
|
||||
action = module_params_get(ansible_module, "action")
|
||||
@@ -447,9 +494,11 @@ def main():
|
||||
commands = []
|
||||
|
||||
for name in names:
|
||||
res_find = find_service(ansible_module, name)
|
||||
res_find = find_service(ansible_module, name, netbiosname)
|
||||
|
||||
if state == "present":
|
||||
# if service exists, 'smb' cannot be used.
|
||||
|
||||
if action == "service":
|
||||
args = gen_args(
|
||||
pac_type, auth_ind, skip_host_check, force,
|
||||
@@ -459,7 +508,12 @@ def main():
|
||||
del args['skip_host_check']
|
||||
|
||||
if res_find is None:
|
||||
commands.append([name, 'service_add', args])
|
||||
if smb:
|
||||
if netbiosname is not None:
|
||||
args['ipantflatname'] = netbiosname
|
||||
commands.append([name, 'service_add_smb', args])
|
||||
else:
|
||||
commands.append([name, 'service_add', args])
|
||||
|
||||
certificate_add = certificate or []
|
||||
certificate_del = []
|
||||
@@ -699,7 +753,8 @@ def main():
|
||||
elif state == "absent":
|
||||
if action == "service":
|
||||
if res_find is not None:
|
||||
commands.append([name, 'service_del', {}])
|
||||
args = {'continue': True if delete_continue else False}
|
||||
commands.append([name, 'service_del', args])
|
||||
|
||||
elif action == "member":
|
||||
if res_find is None:
|
||||
|
||||
@@ -110,7 +110,8 @@ RETURN = """
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
|
||||
gen_add_del_lists
|
||||
|
||||
|
||||
def find_sudocmdgroup(module, name):
|
||||
@@ -257,12 +258,10 @@ def main():
|
||||
if not compare_args_ipa(ansible_module, member_args,
|
||||
res_find):
|
||||
# Generate addition and removal lists
|
||||
sudocmdgroup_add = list(
|
||||
set(sudocmdgroup or []) -
|
||||
set(res_find.get("member_sudocmdgroup", [])))
|
||||
sudocmdgroup_del = list(
|
||||
set(res_find.get("member_sudocmdgroup", [])) -
|
||||
set(sudocmdgroup or []))
|
||||
sudocmdgroup_add, sudocmdgroup_del = \
|
||||
gen_add_del_lists(
|
||||
sudocmdgroup,
|
||||
res_find.get("member_sudocmdgroup"))
|
||||
|
||||
# Add members
|
||||
if len(sudocmdgroup_add) > 0:
|
||||
|
||||
@@ -51,18 +51,21 @@ options:
|
||||
usercategory:
|
||||
description: User category the sudo rule applies to
|
||||
required: false
|
||||
choices: ["all"]
|
||||
choices: ["all", ""]
|
||||
aliases: ["usercat"]
|
||||
usergroup:
|
||||
description: List of user groups assigned to the sudo rule.
|
||||
required: false
|
||||
runasgroupcategory:
|
||||
description: RunAs Group category applied to the sudo rule.
|
||||
required: false
|
||||
choices: ["all"]
|
||||
choices: ["all", ""]
|
||||
aliases: ["runasgroupcat"]
|
||||
runasusercategory:
|
||||
description: RunAs User category applied to the sudorule.
|
||||
required: false
|
||||
choices: ["all"]
|
||||
choices: ["all", ""]
|
||||
aliases: ["runasusercat"]
|
||||
nomembers:
|
||||
description: Suppress processing of membership attributes
|
||||
required: false
|
||||
@@ -78,7 +81,8 @@ options:
|
||||
hostcategory:
|
||||
description: Host category the sudo rule applies to.
|
||||
required: false
|
||||
choices: ["all"]
|
||||
choices: ["all", ""]
|
||||
aliases: ["hostcat"]
|
||||
allow_sudocmd:
|
||||
description: List of allowed sudocmds assigned to this sudorule.
|
||||
required: false
|
||||
@@ -98,7 +102,8 @@ options:
|
||||
cmdcategory:
|
||||
description: Command category the sudo rule applies to
|
||||
required: false
|
||||
choices: ["all"]
|
||||
choices: ["all", ""]
|
||||
aliases: ["cmdcat"]
|
||||
order:
|
||||
description: Order to apply this rule.
|
||||
required: false
|
||||
@@ -241,9 +246,9 @@ def main():
|
||||
# present
|
||||
description=dict(required=False, type="str", default=None),
|
||||
usercategory=dict(required=False, type="str", default=None,
|
||||
choices=["all"]),
|
||||
choices=["all", ""], aliases=['usercat']),
|
||||
hostcategory=dict(required=False, type="str", default=None,
|
||||
choices=["all"]),
|
||||
choices=["all", ""], aliases=['hostcat']),
|
||||
nomembers=dict(required=False, type='bool', default=None),
|
||||
host=dict(required=False, type='list', default=None),
|
||||
hostgroup=dict(required=False, type='list', default=None),
|
||||
@@ -254,11 +259,13 @@ def main():
|
||||
allow_sudocmdgroup=dict(required=False, type="list", default=None),
|
||||
deny_sudocmdgroup=dict(required=False, type="list", default=None),
|
||||
cmdcategory=dict(required=False, type="str", default=None,
|
||||
choices=["all"]),
|
||||
choices=["all", ""], aliases=['cmdcat']),
|
||||
runasusercategory=dict(required=False, type="str", default=None,
|
||||
choices=["all"]),
|
||||
choices=["all", ""],
|
||||
aliases=['runasusercat']),
|
||||
runasgroupcategory=dict(required=False, type="str", default=None,
|
||||
choices=["all"]),
|
||||
choices=["all", ""],
|
||||
aliases=['runasgroupcat']),
|
||||
runasuser=dict(required=False, type="list", default=None),
|
||||
runasgroup=dict(required=False, type="list", default=None),
|
||||
order=dict(type="int", required=False, aliases=['sudoorder']),
|
||||
@@ -332,6 +339,17 @@ def main():
|
||||
ansible_module.fail_json(
|
||||
msg="Argument '%s' can not be used with action "
|
||||
"'%s'" % (arg, action))
|
||||
else:
|
||||
if hostcategory == 'all' and any([host, hostgroup]):
|
||||
ansible_module.fail_json(
|
||||
msg="Hosts cannot be added when host category='all'")
|
||||
if usercategory == 'all' and any([user, group]):
|
||||
ansible_module.fail_json(
|
||||
msg="Users cannot be added when user category='all'")
|
||||
if cmdcategory == 'all' \
|
||||
and any([allow_sudocmd, allow_sudocmdgroup]):
|
||||
ansible_module.fail_json(
|
||||
msg="Commands cannot be added when command category='all'")
|
||||
|
||||
elif state == "absent":
|
||||
if len(names) < 1:
|
||||
|
||||
@@ -186,7 +186,9 @@ options:
|
||||
description: List of base-64 encoded user certificates
|
||||
required: false
|
||||
certmapdata:
|
||||
description: List of certificate mappings
|
||||
description:
|
||||
- List of certificate mappings
|
||||
- Only usable with IPA versions 4.5 and up.
|
||||
options:
|
||||
certificate:
|
||||
description: Base-64 encoded user certificate
|
||||
@@ -197,6 +199,9 @@ options:
|
||||
subject:
|
||||
description: Subject of the certificate
|
||||
required: false
|
||||
data:
|
||||
description: Certmap data
|
||||
required: false
|
||||
required: false
|
||||
noprivate:
|
||||
description: Don't create user private group
|
||||
@@ -346,7 +351,9 @@ options:
|
||||
description: List of base-64 encoded user certificates
|
||||
required: false
|
||||
certmapdata:
|
||||
description: List of certificate mappings
|
||||
description:
|
||||
- List of certificate mappings
|
||||
- Only usable with IPA versions 4.5 and up.
|
||||
options:
|
||||
certificate:
|
||||
description: Base-64 encoded user certificate
|
||||
@@ -357,6 +364,9 @@ options:
|
||||
subject:
|
||||
description: Subject of the certificate
|
||||
required: false
|
||||
data:
|
||||
description: Certmap data
|
||||
required: false
|
||||
required: false
|
||||
noprivate:
|
||||
description: Don't create user private group
|
||||
@@ -467,7 +477,8 @@ from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, date_format, \
|
||||
compare_args_ipa, module_params_get, api_check_param, api_get_realm, \
|
||||
api_command_no_name
|
||||
api_command_no_name, gen_add_del_lists, encode_certificate, \
|
||||
load_cert_from_str, DN_x500_text, api_check_command
|
||||
import six
|
||||
|
||||
|
||||
@@ -497,6 +508,11 @@ def find_user(module, name, preserved=False):
|
||||
for x in _result["krbprincipalname"]:
|
||||
_list.append(str(x))
|
||||
_result["krbprincipalname"] = _list
|
||||
certs = _result.get("usercertificate")
|
||||
if certs is not None:
|
||||
_result["usercertificate"] = [encode_certificate(x)
|
||||
for x in certs]
|
||||
|
||||
return _result
|
||||
else:
|
||||
return None
|
||||
@@ -640,13 +656,21 @@ def check_parameters(module, state, action,
|
||||
certificate = x.get("certificate")
|
||||
issuer = x.get("issuer")
|
||||
subject = x.get("subject")
|
||||
data = x.get("data")
|
||||
|
||||
if data is not None:
|
||||
if certificate is not None or issuer is not None or \
|
||||
subject is not None:
|
||||
module.fail_json(
|
||||
msg="certmapdata: data can not be used with "
|
||||
"certificate, issuer or subject")
|
||||
check_certmapdata(data)
|
||||
if certificate is not None \
|
||||
and (issuer is not None or subject is not None):
|
||||
module.fail_json(
|
||||
msg="certmapdata: certificate can not be used with "
|
||||
"issuer or subject")
|
||||
if certificate is None:
|
||||
if data is None and certificate is None:
|
||||
if issuer is None:
|
||||
module.fail_json(msg="certmapdata: issuer is missing")
|
||||
if subject is None:
|
||||
@@ -655,25 +679,54 @@ def check_parameters(module, state, action,
|
||||
|
||||
def extend_emails(email, default_email_domain):
|
||||
if email is not None:
|
||||
return [ "%s@%s" % (_email, default_email_domain)
|
||||
if "@" not in _email else _email
|
||||
for _email in email]
|
||||
return ["%s@%s" % (_email, default_email_domain)
|
||||
if "@" not in _email else _email
|
||||
for _email in email]
|
||||
return email
|
||||
|
||||
|
||||
def gen_certmapdata_args(certmapdata):
|
||||
certificate = certmapdata.get("certificate")
|
||||
issuer = certmapdata.get("issuer")
|
||||
subject = certmapdata.get("subject")
|
||||
def convert_certmapdata(certmapdata):
|
||||
if certmapdata is None:
|
||||
return None
|
||||
|
||||
_args = {}
|
||||
if certificate is not None:
|
||||
_args["certificate"] = certificate
|
||||
if issuer is not None:
|
||||
_args["issuer"] = issuer
|
||||
if subject is not None:
|
||||
_args["subject"] = subject
|
||||
return _args
|
||||
_result = []
|
||||
for x in certmapdata:
|
||||
certificate = x.get("certificate")
|
||||
issuer = x.get("issuer")
|
||||
subject = x.get("subject")
|
||||
data = x.get("data")
|
||||
|
||||
if data is None:
|
||||
if issuer is None and subject is None:
|
||||
cert = load_cert_from_str(certificate)
|
||||
issuer = cert.issuer
|
||||
subject = cert.subject
|
||||
|
||||
_result.append("X509:<I>%s<S>%s" % (DN_x500_text(issuer),
|
||||
DN_x500_text(subject)))
|
||||
else:
|
||||
_result.append(data)
|
||||
|
||||
return _result
|
||||
|
||||
|
||||
def check_certmapdata(data):
|
||||
if not data.startswith("X509:"):
|
||||
return False
|
||||
|
||||
i = data.find("<I>", 4)
|
||||
s = data.find("<S>", i)
|
||||
issuer = data[i+3:s]
|
||||
subject = data[s+3:]
|
||||
|
||||
if i < 0 or s < 0 or "CN" not in issuer or "CN" not in subject:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def gen_certmapdata_args(certmapdata):
|
||||
return {"ipacertmapdata": to_text(certmapdata)}
|
||||
|
||||
|
||||
def main():
|
||||
@@ -735,7 +788,8 @@ def main():
|
||||
# Here certificate is a simple string
|
||||
certificate=dict(type="str", default=None),
|
||||
issuer=dict(type="str", default=None),
|
||||
subject=dict(type="str", default=None)
|
||||
subject=dict(type="str", default=None),
|
||||
data=dict(type="str", default=None)
|
||||
),
|
||||
elements='dict', required=False),
|
||||
noprivate=dict(type='bool', default=None),
|
||||
@@ -763,7 +817,7 @@ def main():
|
||||
preserve=dict(required=False, type='bool', default=None),
|
||||
|
||||
# mod
|
||||
update_password=dict(type='str', default=None,
|
||||
update_password=dict(type='str', default=None, no_log=False,
|
||||
choices=['always', 'on_create']),
|
||||
|
||||
# general
|
||||
@@ -870,6 +924,7 @@ def main():
|
||||
departmentnumber, employeenumber, employeetype, preferredlanguage,
|
||||
certificate, certmapdata, noprivate, nomembers, preserve,
|
||||
update_password)
|
||||
certmapdata = convert_certmapdata(certmapdata)
|
||||
|
||||
# Use users if names is None
|
||||
if users is not None:
|
||||
@@ -967,6 +1022,7 @@ def main():
|
||||
employeetype, preferredlanguage, certificate,
|
||||
certmapdata, noprivate, nomembers, preserve,
|
||||
update_password)
|
||||
certmapdata = convert_certmapdata(certmapdata)
|
||||
|
||||
# Extend email addresses
|
||||
|
||||
@@ -996,6 +1052,16 @@ def main():
|
||||
msg="The use of passwordexpiration is not supported by "
|
||||
"your IPA version")
|
||||
|
||||
# Check certmapdata availability.
|
||||
# We need the connected API for this test, therefore it can not
|
||||
# be part of check_parameters as this is used also before the
|
||||
# connection to the API has been established.
|
||||
if certmapdata is not None and \
|
||||
not api_check_command("user_add_certmapdata"):
|
||||
ansible_module.fail_json(
|
||||
msg="The use of certmapdata is not supported by "
|
||||
"your IPA version")
|
||||
|
||||
# Make sure user exists
|
||||
res_find = find_user(ansible_module, name)
|
||||
# Also search for preserved user if the user could not be found
|
||||
@@ -1063,36 +1129,21 @@ def main():
|
||||
# certmapdata
|
||||
if res_find is not None:
|
||||
# Generate addition and removal lists
|
||||
manager_add = list(
|
||||
set(manager or []) -
|
||||
set(res_find.get("manager", [])))
|
||||
manager_del = list(
|
||||
set(res_find.get("manager", [])) -
|
||||
set(manager or []))
|
||||
principal_add = list(
|
||||
set(principal or []) -
|
||||
set(res_find.get("krbprincipalname", [])))
|
||||
principal_del = list(
|
||||
set(res_find.get("krbprincipalname", [])) -
|
||||
set(principal or []))
|
||||
manager_add, manager_del = gen_add_del_lists(
|
||||
manager, res_find.get("manager"))
|
||||
|
||||
principal_add, principal_del = gen_add_del_lists(
|
||||
principal, res_find.get("krbprincipalname"))
|
||||
# Principals are not returned as utf8 for IPA using
|
||||
# python2 using user_find, therefore we need to
|
||||
# convert the principals that we should remove.
|
||||
principal_del = [to_text(x) for x in principal_del]
|
||||
|
||||
certificate_add = list(
|
||||
set(certificate or []) -
|
||||
set(res_find.get("certificate", [])))
|
||||
certificate_del = list(
|
||||
set(res_find.get("certificate", [])) -
|
||||
set(certificate or []))
|
||||
certmapdata_add = list(
|
||||
set(certmapdata or []) -
|
||||
set(res_find.get("ipaCertMapData", [])))
|
||||
certmapdata_del = list(
|
||||
set(res_find.get("ipaCertMapData", [])) -
|
||||
set(certmapdata or []))
|
||||
certificate_add, certificate_del = gen_add_del_lists(
|
||||
certificate, res_find.get("usercertificate"))
|
||||
|
||||
certmapdata_add, certmapdata_del = gen_add_del_lists(
|
||||
certmapdata, res_find.get("ipacertmapdata"))
|
||||
|
||||
else:
|
||||
# Use given managers and principals
|
||||
@@ -1179,7 +1230,7 @@ def main():
|
||||
# Remove certmapdata
|
||||
if len(certmapdata_del) > 0:
|
||||
for _data in certmapdata_del:
|
||||
commands.append([name, "user_add_certmapdata",
|
||||
commands.append([name, "user_remove_certmapdata",
|
||||
gen_certmapdata_args(_data)])
|
||||
|
||||
elif action == "member":
|
||||
@@ -1376,7 +1427,6 @@ def main():
|
||||
temp_kdestroy(ccache_dir, ccache_name)
|
||||
|
||||
# Done
|
||||
|
||||
ansible_module.exit_json(changed=changed, user=exit_args)
|
||||
|
||||
|
||||
|
||||
@@ -45,21 +45,41 @@ options:
|
||||
description:
|
||||
description: The vault description
|
||||
required: false
|
||||
vault_public_key:
|
||||
description: Base64 encoded public key.
|
||||
public_key:
|
||||
description: Base64 encode public key.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipavaultpublickey"]
|
||||
vault_salt:
|
||||
description: Vault salt.
|
||||
type: string
|
||||
aliases: ["ipavaultpublickey", "vault_public_key"]
|
||||
public_key_file:
|
||||
description: Path to file with public key.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipavaultsalt"]
|
||||
vault_password:
|
||||
type: string
|
||||
aliases: ["vault_public_key_file"]
|
||||
private_key:
|
||||
description: Base64 encode private key.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["ipavaultprivatekey", "vault_private_key"]
|
||||
private_key_file:
|
||||
description: Path to file with private key.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["vault_private_key_file"]
|
||||
password:
|
||||
description: password to be used on symmetric vault.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["ipavaultpassword"]
|
||||
aliases: ["ipavaultpassword", "vault_password"]
|
||||
password_file:
|
||||
description: file with password to be used on symmetric vault.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["vault_password_file"]
|
||||
salt:
|
||||
description: Vault salt.
|
||||
required: false
|
||||
type: list
|
||||
aliases: ["ipavaultsalt", "vault_salt"]
|
||||
vault_type:
|
||||
description: Vault types are based on security level.
|
||||
required: true
|
||||
@@ -79,23 +99,45 @@ options:
|
||||
description: Vault is shared.
|
||||
required: false
|
||||
type: boolean
|
||||
vault_data:
|
||||
description: Data to be stored in the vault.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["ipavaultdata"]
|
||||
owners:
|
||||
description: Users that are owners of the container.
|
||||
required: false
|
||||
type: list
|
||||
users:
|
||||
description: Users that are member of the container.
|
||||
description: Users that are member of the vault.
|
||||
required: false
|
||||
type: list
|
||||
groups:
|
||||
description: Groups that are member of the container.
|
||||
description: Groups that are member of the vault.
|
||||
required: false
|
||||
type: list
|
||||
owners:
|
||||
description: Users that are owners of the vault.
|
||||
required: false
|
||||
type: list
|
||||
ownergroups:
|
||||
description: Groups that are owners of the vault.
|
||||
required: false
|
||||
type: list
|
||||
ownerservices:
|
||||
description: Services that are owners of the vault.
|
||||
required: false
|
||||
type: list
|
||||
services:
|
||||
description: Services that are member of the container.
|
||||
required: false
|
||||
type: list
|
||||
data:
|
||||
description: Data to be stored in the vault.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["ipavaultdata", "vault_data"]
|
||||
in:
|
||||
description: Path to file with data to be stored in the vault.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["datafile_in"]
|
||||
out:
|
||||
description: Path to file to store data retrieved from the vault.
|
||||
required: false
|
||||
type: string
|
||||
aliases: ["datafile_out"]
|
||||
action:
|
||||
description: Work on vault or member level.
|
||||
default: vault
|
||||
@@ -103,7 +145,7 @@ options:
|
||||
state:
|
||||
description: State to ensure
|
||||
default: present
|
||||
choices: ["present", "absent"]
|
||||
choices: ["present", "absent", "retrieved"]
|
||||
author:
|
||||
- Rafael Jeffman
|
||||
"""
|
||||
@@ -114,9 +156,9 @@ EXAMPLES = """
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
vault_password: MyVaultPassword123
|
||||
vault_salt: MTIzNDU2Nzg5MAo=
|
||||
vault_type: symmetric
|
||||
password: SomeVAULTpassword
|
||||
salt: MTIzNDU2Nzg5MAo=
|
||||
|
||||
# Ensure group ipausers is a vault member.
|
||||
- ipavault:
|
||||
@@ -178,12 +220,23 @@ EXAMPLES = """
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
vault_password: MyVaultPassword123
|
||||
vault_data: >
|
||||
password: SomeVAULTpassword
|
||||
data: >
|
||||
Data archived.
|
||||
More data archived.
|
||||
action: member
|
||||
|
||||
# Retrieve data archived from a symmetric vault
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: symvault
|
||||
username: admin
|
||||
password: SomeVAULTpassword
|
||||
state: retrieved
|
||||
register: result
|
||||
- debug:
|
||||
msg: "{{ result.data | b64decode }}"
|
||||
|
||||
# Ensure vault symvault is absent
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -198,7 +251,7 @@ EXAMPLES = """
|
||||
username: user01
|
||||
description: An asymmetric vault
|
||||
vault_type: asymmetric
|
||||
vault_public_key:
|
||||
public_key:
|
||||
LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTR
|
||||
HTkFEQ0JpUUtCZ1FDdGFudjRkK3ptSTZ0T3ova1RXdGowY3AxRAowUENoYy8vR0pJMTUzTi
|
||||
9CN3UrN0h3SXlRVlZoNUlXZG1UcCtkWXYzd09yeVpPbzYvbHN5eFJaZ2pZRDRwQ3VGCjlxM
|
||||
@@ -211,11 +264,19 @@ EXAMPLES = """
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: asymvault
|
||||
username: admin
|
||||
vault_data: >
|
||||
data: >
|
||||
Data archived.
|
||||
More data archived.
|
||||
action: member
|
||||
|
||||
# Retrive data archived in an asymmetric vault, using a private key file.
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
name: asymvault
|
||||
username: admin
|
||||
private_key_file: private.pem
|
||||
state: retrieved
|
||||
|
||||
# Ensure asymmetric vault is absent.
|
||||
- ipavault:
|
||||
ipaadmin_password: SomeADMINpassword
|
||||
@@ -226,9 +287,14 @@ EXAMPLES = """
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
user:
|
||||
description: The vault data.
|
||||
returned: If state is retrieved.
|
||||
type: string
|
||||
"""
|
||||
|
||||
import os
|
||||
from base64 import b64decode
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
|
||||
temp_kdestroy, valid_creds, api_connect, api_command, \
|
||||
@@ -261,7 +327,8 @@ def find_vault(module, name, username, service, shared):
|
||||
|
||||
|
||||
def gen_args(description, username, service, shared, vault_type, salt,
|
||||
public_key, vault_data):
|
||||
password, password_file, public_key, public_key_file, vault_data,
|
||||
datafile_in, datafile_out):
|
||||
_args = {}
|
||||
|
||||
if description is not None:
|
||||
@@ -277,14 +344,16 @@ def gen_args(description, username, service, shared, vault_type, salt,
|
||||
if salt is not None:
|
||||
_args['ipavaultsalt'] = salt
|
||||
if public_key is not None:
|
||||
_args['ipavaultpublickey'] = public_key
|
||||
if vault_data is not None:
|
||||
_args['data'] = vault_data.encode('utf-8')
|
||||
_args['ipavaultpublickey'] = b64decode(public_key.encode('utf-8'))
|
||||
if public_key_file is not None:
|
||||
with open(public_key_file, 'r') as keyfile:
|
||||
keydata = keyfile.read()
|
||||
_args['ipavaultpublickey'] = keydata.strip().encode('utf-8')
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def gen_member_args(args, users, groups):
|
||||
def gen_member_args(args, users, groups, services):
|
||||
_args = args.copy()
|
||||
|
||||
for arg in ['ipavaulttype', 'description', 'ipavaultpublickey',
|
||||
@@ -292,13 +361,21 @@ def gen_member_args(args, users, groups):
|
||||
if arg in _args:
|
||||
del _args[arg]
|
||||
|
||||
_args['user'] = users
|
||||
_args['group'] = groups
|
||||
if any([users, groups, services]):
|
||||
if users is not None:
|
||||
_args['user'] = users
|
||||
if groups is not None:
|
||||
_args['group'] = groups
|
||||
if services is not None:
|
||||
_args['services'] = services
|
||||
|
||||
return _args
|
||||
return _args
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def data_storage_args(args, data, password):
|
||||
def data_storage_args(args, data, password, password_file, private_key,
|
||||
private_key_file, datafile_in, datafile_out):
|
||||
_args = {}
|
||||
|
||||
if 'username' in args:
|
||||
@@ -310,53 +387,104 @@ def data_storage_args(args, data, password):
|
||||
|
||||
if password is not None:
|
||||
_args['password'] = password
|
||||
if password_file is not None:
|
||||
_args['password_file'] = password_file
|
||||
|
||||
_args['data'] = data
|
||||
if private_key is not None:
|
||||
_args['private_key'] = private_key
|
||||
if private_key_file is not None:
|
||||
_args['private_key_file'] = private_key_file
|
||||
|
||||
if datafile_in is not None:
|
||||
_args['in'] = datafile_in
|
||||
else:
|
||||
if data is None:
|
||||
_args['data'] = b''
|
||||
else:
|
||||
_args['data'] = data.encode('utf-8')
|
||||
|
||||
if datafile_out is not None:
|
||||
_args['out'] = datafile_out
|
||||
|
||||
if private_key_file is not None:
|
||||
_args['private_key_file'] = private_key_file
|
||||
|
||||
return _args
|
||||
|
||||
|
||||
def check_parameters(module, state, action, description, username, service,
|
||||
shared, users, groups, owners, ownergroups, vault_type,
|
||||
salt, password, public_key, vault_data):
|
||||
shared, users, groups, services, owners, ownergroups,
|
||||
ownerservices, vault_type, salt, password, password_file,
|
||||
public_key, public_key_file, private_key,
|
||||
private_key_file, vault_data, datafile_in, datafile_out):
|
||||
invalid = []
|
||||
if state == "present":
|
||||
if action == "member":
|
||||
invalid = ['description', 'public_key', 'salt']
|
||||
invalid = ['private_key', 'private_key_file', 'datafile_out']
|
||||
|
||||
for param in invalid:
|
||||
if vars()[param] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with action '%s'" %
|
||||
(param, action))
|
||||
if action == "member":
|
||||
invalid.extend(['description'])
|
||||
|
||||
elif state == "absent":
|
||||
invalid = ['description', 'salt']
|
||||
invalid = ['description', 'salt', 'vault_type', 'private_key',
|
||||
'private_key_file', 'datafile_in', 'datafile_out',
|
||||
'vault_data']
|
||||
|
||||
if action == "vault":
|
||||
invalid.extend(['users', 'groups', 'owners', 'ownergroups',
|
||||
'password', 'public_key'])
|
||||
invalid.extend(['users', 'groups', 'services', 'owners',
|
||||
'ownergroups', 'ownerservices', 'password',
|
||||
'password_file', 'public_key', 'public_key_file'])
|
||||
|
||||
for arg in invalid:
|
||||
if vars()[arg] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with action '%s'" %
|
||||
(arg, state))
|
||||
elif state == "retrieved":
|
||||
invalid = ['description', 'salt', 'datafile_in', 'users', 'groups',
|
||||
'owners', 'ownergroups', 'public_key', 'public_key_file',
|
||||
'vault_data']
|
||||
if action == 'member':
|
||||
module.fail_json(
|
||||
msg="State `retrieved` do not support action `member`.")
|
||||
|
||||
for arg in invalid:
|
||||
if vars()[arg] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with state '%s', "
|
||||
"action '%s'" % (arg, state, action))
|
||||
|
||||
for arg in invalid:
|
||||
if vars()[arg] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' can not be used with state '%s', "
|
||||
"action '%s'" % (arg, state, action))
|
||||
|
||||
|
||||
def check_encryption_params(module, state, vault_type, password, public_key,
|
||||
vault_data, res_find):
|
||||
if state == "present":
|
||||
if vault_type == "symmetric":
|
||||
if password is None \
|
||||
and (vault_data is not None or res_find is None):
|
||||
module.fail_json(
|
||||
msg="Vault password required for symmetric vault.")
|
||||
def check_encryption_params(module, state, action, vault_type, salt,
|
||||
password, password_file, public_key,
|
||||
public_key_file, private_key, private_key_file,
|
||||
vault_data, datafile_in, datafile_out, res_find):
|
||||
vault_type_invalid = []
|
||||
if vault_type == "standard":
|
||||
vault_type_invalid = ['public_key', 'public_key_file', 'password',
|
||||
'password_file', 'salt']
|
||||
|
||||
if vault_type == "asymmetric":
|
||||
if public_key is None and res_find is None:
|
||||
module.fail_json(
|
||||
msg="Public Key required for asymmetric vault.")
|
||||
if vault_type is None or vault_type == "symmetric":
|
||||
vault_type_invalid = ['public_key', 'public_key_file',
|
||||
'private_key', 'private_key_file']
|
||||
|
||||
if password is None and password_file is None and action != 'member':
|
||||
module.fail_json(
|
||||
msg="Symmetric vault requires password or password_file "
|
||||
"to store data or change `salt`.")
|
||||
|
||||
if vault_type == "asymmetric":
|
||||
vault_type_invalid = ['password', 'password_file']
|
||||
if not any([public_key, public_key_file]) and res_find is None:
|
||||
module.fail_json(
|
||||
msg="Assymmetric vault requires public_key "
|
||||
"or public_key_file to store data.")
|
||||
|
||||
for param in vault_type_invalid:
|
||||
if vars()[param] is not None:
|
||||
module.fail_json(
|
||||
msg="Argument '%s' cannot be used with vault type '%s'" %
|
||||
(param, vault_type or 'symmetric'))
|
||||
|
||||
|
||||
def main():
|
||||
@@ -369,16 +497,23 @@ def main():
|
||||
name=dict(type="list", aliases=["cn"], default=None,
|
||||
required=True),
|
||||
|
||||
# present
|
||||
|
||||
description=dict(required=False, type="str", default=None),
|
||||
vault_type=dict(type="str", aliases=["ipavaulttype"],
|
||||
default=None, required=False,
|
||||
choices=["standard", "symmetric", "asymmetric"]),
|
||||
vault_public_key=dict(type="str", required=False, default=None,
|
||||
aliases=['ipavaultpublickey']),
|
||||
aliases=['ipavaultpublickey', 'public_key']),
|
||||
vault_public_key_file=dict(type="str", required=False,
|
||||
default=None,
|
||||
aliases=['public_key_file']),
|
||||
vault_private_key=dict(
|
||||
type="str", required=False, default=None, no_log=True,
|
||||
aliases=['ipavaultprivatekey', 'private_key']),
|
||||
vault_private_key_file=dict(type="str", required=False,
|
||||
default=None,
|
||||
aliases=['private_key_file']),
|
||||
vault_salt=dict(type="str", required=False, default=None,
|
||||
aliases=['ipavaultsalt']),
|
||||
aliases=['ipavaultsalt', 'salt']),
|
||||
username=dict(type="str", required=False, default=None,
|
||||
aliases=['user']),
|
||||
service=dict(type="str", required=False, default=None),
|
||||
@@ -386,23 +521,33 @@ def main():
|
||||
|
||||
users=dict(required=False, type='list', default=None),
|
||||
groups=dict(required=False, type='list', default=None),
|
||||
owners=dict(required=False, type='list', default=None),
|
||||
services=dict(required=False, type='list', default=None),
|
||||
owners=dict(required=False, type='list', default=None,
|
||||
aliases=['ownerusers']),
|
||||
ownergroups=dict(required=False, type='list', default=None),
|
||||
|
||||
ownerservices=dict(required=False, type='list', default=None),
|
||||
vault_data=dict(type="str", required=False, default=None,
|
||||
aliases=['ipavaultdata']),
|
||||
no_log=True, aliases=['ipavaultdata', 'data']),
|
||||
datafile_in=dict(type="str", required=False, default=None,
|
||||
aliases=['in']),
|
||||
datafile_out=dict(type="str", required=False, default=None,
|
||||
aliases=['out']),
|
||||
vault_password=dict(type="str", required=False, default=None,
|
||||
no_log=True, aliases=['ipavaultpassword']),
|
||||
|
||||
aliases=['ipavaultpassword', 'password'],
|
||||
no_log=True),
|
||||
vault_password_file=dict(type="str", required=False, default=None,
|
||||
no_log=False, aliases=['password_file']),
|
||||
# state
|
||||
action=dict(type="str", default="vault",
|
||||
choices=["vault", "data", "member"]),
|
||||
state=dict(type="str", default="present",
|
||||
choices=["present", "absent"]),
|
||||
choices=["present", "absent", "retrieved"]),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
mutually_exclusive=[['username', 'service', 'shared']],
|
||||
required_one_of=[['username', 'service', 'shared']]
|
||||
mutually_exclusive=[['username', 'service', 'shared'],
|
||||
['datafile_in', 'vault_data'],
|
||||
['vault_password', 'vault_password_file'],
|
||||
['vault_public_key', 'vault_public_key_file']],
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
@@ -422,18 +567,28 @@ def main():
|
||||
|
||||
users = module_params_get(ansible_module, "users")
|
||||
groups = module_params_get(ansible_module, "groups")
|
||||
services = module_params_get(ansible_module, "services")
|
||||
owners = module_params_get(ansible_module, "owners")
|
||||
ownergroups = module_params_get(ansible_module, "ownergroups")
|
||||
ownerservices = module_params_get(ansible_module, "ownerservices")
|
||||
|
||||
vault_type = module_params_get(ansible_module, "vault_type")
|
||||
salt = module_params_get(ansible_module, "vault_salt")
|
||||
password = module_params_get(ansible_module, "vault_password")
|
||||
password_file = module_params_get(ansible_module, "vault_password_file")
|
||||
public_key = module_params_get(ansible_module, "vault_public_key")
|
||||
public_key_file = module_params_get(ansible_module,
|
||||
"vault_public_key_file")
|
||||
private_key = module_params_get(ansible_module, "vault_private_key")
|
||||
private_key_file = module_params_get(ansible_module,
|
||||
"vault_private_key_file")
|
||||
|
||||
vault_data = module_params_get(ansible_module, "vault_data")
|
||||
|
||||
datafile_in = module_params_get(ansible_module, "datafile_in")
|
||||
datafile_out = module_params_get(ansible_module, "datafile_out")
|
||||
|
||||
action = module_params_get(ansible_module, "action")
|
||||
# state
|
||||
state = module_params_get(ansible_module, "state")
|
||||
|
||||
# Check parameters
|
||||
@@ -447,12 +602,19 @@ def main():
|
||||
if len(names) < 1:
|
||||
ansible_module.fail_json(msg="No name given.")
|
||||
|
||||
elif state == "retrieved":
|
||||
if len(names) != 1:
|
||||
ansible_module.fail_json(
|
||||
msg="Only one vault can be retrieved at a time.")
|
||||
|
||||
else:
|
||||
ansible_module.fail_json(msg="Invalid state '%s'" % state)
|
||||
|
||||
check_parameters(ansible_module, state, action, description, username,
|
||||
service, shared, users, groups, owners, ownergroups,
|
||||
vault_type, salt, password, public_key, vault_data)
|
||||
service, shared, users, groups, services, owners,
|
||||
ownergroups, ownerservices, vault_type, salt, password,
|
||||
password_file, public_key, public_key_file, private_key,
|
||||
private_key_file, vault_data, datafile_in, datafile_out)
|
||||
# Init
|
||||
|
||||
changed = False
|
||||
@@ -463,6 +625,9 @@ def main():
|
||||
if not valid_creds(ansible_module, ipaadmin_principal):
|
||||
ccache_dir, ccache_name = temp_kinit(ipaadmin_principal,
|
||||
ipaadmin_password)
|
||||
# Need to set krb5 ccache name, due to context='ansible-freeipa'
|
||||
if ccache_name is not None:
|
||||
os.environ["KRB5CCNAME"] = ccache_name
|
||||
|
||||
api_connect(context='ansible-freeipa')
|
||||
|
||||
@@ -475,7 +640,10 @@ def main():
|
||||
|
||||
# Generate args
|
||||
args = gen_args(description, username, service, shared, vault_type,
|
||||
salt, public_key, vault_data)
|
||||
salt, password, password_file, public_key,
|
||||
public_key_file, vault_data, datafile_in,
|
||||
datafile_out)
|
||||
pwdargs = None
|
||||
|
||||
# Set default vault_type if needed.
|
||||
if vault_type is None and vault_data is not None:
|
||||
@@ -485,12 +653,14 @@ def main():
|
||||
else:
|
||||
args['ipavaulttype'] = vault_type = "symmetric"
|
||||
|
||||
# verify data encription args
|
||||
check_encryption_params(ansible_module, state, vault_type,
|
||||
password, public_key, vault_data, res_find)
|
||||
|
||||
# Create command
|
||||
if state == "present":
|
||||
# verify data encription args
|
||||
check_encryption_params(
|
||||
ansible_module, state, action, vault_type, salt, password,
|
||||
password_file, public_key, public_key_file, private_key,
|
||||
private_key_file, vault_data, datafile_in, datafile_out,
|
||||
res_find)
|
||||
|
||||
# Found the vault
|
||||
if action == "vault":
|
||||
@@ -501,16 +671,13 @@ def main():
|
||||
if not compare_args_ipa(ansible_module, args,
|
||||
res_find):
|
||||
commands.append([name, "vault_mod_internal", args])
|
||||
else:
|
||||
if 'ipavaultsault' not in args:
|
||||
args['ipavaultsalt'] = os.urandom(32)
|
||||
commands.append([name, "vault_add_internal", args])
|
||||
# archive empty data to set password
|
||||
pwdargs = data_storage_args(
|
||||
args, args.get('data', ''), password)
|
||||
commands.append([name, "vault_archive", pwdargs])
|
||||
|
||||
# Set res_find to empty dict for next step # noqa
|
||||
else:
|
||||
commands.append([name, "vault_add_internal", args])
|
||||
if vault_type != 'standard' and vault_data is None:
|
||||
vault_data = ''
|
||||
|
||||
# Set res_find to empty dict for next steps
|
||||
res_find = {}
|
||||
|
||||
# Generate adittion and removal lists
|
||||
@@ -520,54 +687,98 @@ def main():
|
||||
group_add, group_del = \
|
||||
gen_add_del_lists(groups,
|
||||
res_find.get('member_group', []))
|
||||
service_add, service_del = \
|
||||
gen_add_del_lists(services,
|
||||
res_find.get('member_service', []))
|
||||
|
||||
owner_add, owner_del = \
|
||||
gen_add_del_lists(owners,
|
||||
res_find.get('owner_user', []))
|
||||
|
||||
ownergroups_add, ownergroups_del = \
|
||||
gen_add_del_lists(ownergroups,
|
||||
res_find.get('owner_group', []))
|
||||
|
||||
ownerservice_add, ownerservice_del = \
|
||||
gen_add_del_lists(ownerservices,
|
||||
res_find.get('owner_service', []))
|
||||
|
||||
# Add users and groups
|
||||
if len(user_add) > 0 or len(group_add) > 0:
|
||||
user_add_args = gen_member_args(args, user_add,
|
||||
group_add)
|
||||
commands.append([name, 'vault_add_member',
|
||||
user_add_args])
|
||||
user_add_args = gen_member_args(args, user_add,
|
||||
group_add, service_add)
|
||||
if user_add_args is not None:
|
||||
commands.append(
|
||||
[name, 'vault_add_member', user_add_args])
|
||||
|
||||
# Remove users and groups
|
||||
if len(user_del) > 0 or len(group_del) > 0:
|
||||
user_del_args = gen_member_args(args, user_del,
|
||||
group_del)
|
||||
commands.append([name, 'vault_remove_member',
|
||||
user_del_args])
|
||||
user_del_args = gen_member_args(args, user_del,
|
||||
group_del, service_del)
|
||||
if user_del_args is not None:
|
||||
commands.append(
|
||||
[name, 'vault_remove_member', user_del_args])
|
||||
|
||||
# Add owner users and groups
|
||||
if len(user_add) > 0 or len(group_add) > 0:
|
||||
owner_add_args = gen_member_args(args, owner_add,
|
||||
ownergroups_add)
|
||||
commands.append([name, 'vault_add_owner',
|
||||
owner_add_args])
|
||||
owner_add_args = gen_member_args(
|
||||
args, owner_add, ownergroups_add, ownerservice_add)
|
||||
if owner_add_args is not None:
|
||||
# ansible_module.warn("OWNER ADD: %s" % owner_add_args)
|
||||
commands.append(
|
||||
[name, 'vault_add_owner', owner_add_args])
|
||||
|
||||
# Remove owner users and groups
|
||||
if len(user_del) > 0 or len(group_del) > 0:
|
||||
owner_del_args = gen_member_args(args, owner_del,
|
||||
ownergroups_del)
|
||||
commands.append([name, 'vault_remove_owner',
|
||||
owner_del_args])
|
||||
owner_del_args = gen_member_args(
|
||||
args, owner_del, ownergroups_del, ownerservice_del)
|
||||
if owner_del_args is not None:
|
||||
# ansible_module.warn("OWNER DEL: %s" % owner_del_args)
|
||||
commands.append(
|
||||
[name, 'vault_remove_owner', owner_del_args])
|
||||
|
||||
if vault_type == 'symmetric' \
|
||||
and 'ipavaultsalt' not in args:
|
||||
args['ipavaultsalt'] = os.urandom(32)
|
||||
|
||||
if vault_type == 'symmetric' \
|
||||
and 'ipavaultsalt' not in args:
|
||||
args['ipavaultsalt'] = os.urandom(32)
|
||||
|
||||
elif action in "member":
|
||||
# Add users and groups
|
||||
if users is not None or groups is not None:
|
||||
user_args = gen_member_args(args, users, groups)
|
||||
if any([users, groups, services]):
|
||||
user_args = gen_member_args(args, users, groups,
|
||||
services)
|
||||
commands.append([name, 'vault_add_member', user_args])
|
||||
if owners is not None or ownergroups is not None:
|
||||
owner_args = gen_member_args(args, owners, ownergroups)
|
||||
if any([owners, ownergroups, ownerservices]):
|
||||
owner_args = gen_member_args(args, owners, ownergroups,
|
||||
ownerservices)
|
||||
commands.append([name, 'vault_add_owner', owner_args])
|
||||
|
||||
if vault_data is not None:
|
||||
data_args = data_storage_args(
|
||||
args, args.get('data', ''), password)
|
||||
commands.append([name, 'vault_archive', data_args])
|
||||
pwdargs = data_storage_args(
|
||||
args, vault_data, password, password_file, private_key,
|
||||
private_key_file, datafile_in, datafile_out)
|
||||
if any([vault_data, datafile_in]):
|
||||
commands.append([name, "vault_archive", pwdargs])
|
||||
|
||||
elif state == "retrieved":
|
||||
if res_find is None:
|
||||
ansible_module.fail_json(
|
||||
msg="Vault `%s` not found to retrieve data." % name)
|
||||
|
||||
vault_type = res_find['cn']
|
||||
|
||||
# verify data encription args
|
||||
check_encryption_params(
|
||||
ansible_module, state, action, vault_type, salt, password,
|
||||
password_file, public_key, public_key_file, private_key,
|
||||
private_key_file, vault_data, datafile_in, datafile_out,
|
||||
res_find)
|
||||
|
||||
pwdargs = data_storage_args(
|
||||
args, vault_data, password, password_file, private_key,
|
||||
private_key_file, datafile_in, datafile_out)
|
||||
if 'data' in pwdargs:
|
||||
del pwdargs['data']
|
||||
|
||||
commands.append([name, "vault_retrieve", pwdargs])
|
||||
|
||||
elif state == "absent":
|
||||
if 'ipavaulttype' in args:
|
||||
@@ -579,32 +790,46 @@ def main():
|
||||
|
||||
elif action == "member":
|
||||
# remove users and groups
|
||||
if users is not None or groups is not None:
|
||||
user_args = gen_member_args(args, users, groups)
|
||||
commands.append([name, 'vault_remove_member',
|
||||
user_args])
|
||||
if any([users, groups, services]):
|
||||
user_args = gen_member_args(
|
||||
args, users, groups, services)
|
||||
commands.append(
|
||||
[name, 'vault_remove_member', user_args])
|
||||
|
||||
if owners is not None or ownergroups is not None:
|
||||
owner_args = gen_member_args(args, owners, ownergroups)
|
||||
commands.append([name, 'vault_remove_owner',
|
||||
owner_args])
|
||||
if any([owners, ownergroups, ownerservices]):
|
||||
owner_args = gen_member_args(
|
||||
args, owners, ownergroups, ownerservices)
|
||||
commands.append(
|
||||
[name, 'vault_remove_owner', owner_args])
|
||||
else:
|
||||
ansible_module.fail_json(
|
||||
msg="Invalid action '%s' for state '%s'" %
|
||||
(action, state))
|
||||
else:
|
||||
ansible_module.fail_json(msg="Unkown state '%s'" % state)
|
||||
ansible_module.fail_json(msg="Unknown state '%s'" % state)
|
||||
|
||||
# Execute commands
|
||||
|
||||
errors = []
|
||||
for name, command, args in commands:
|
||||
try:
|
||||
# ansible_module.warn("RUN: %s %s %s" % (command, name, args))
|
||||
result = api_command(ansible_module, command, name, args)
|
||||
|
||||
if command == 'vault_archive':
|
||||
changed = 'Archived data into' in result['summary']
|
||||
elif command == 'vault_retrieve':
|
||||
if 'result' not in result:
|
||||
raise Exception("No result obtained.")
|
||||
if 'data' in result['result']:
|
||||
exit_args['data'] = result['result']['data']
|
||||
elif 'vault_data' in result['result']:
|
||||
exit_args['data'] = result['result']['vault_data']
|
||||
else:
|
||||
raise Exception("No data retrieved.")
|
||||
changed = False
|
||||
else:
|
||||
# ansible_module.warn("RESULT: %s" % (result))
|
||||
if "completed" in result:
|
||||
if result["completed"] > 0:
|
||||
changed = True
|
||||
|
||||
2
pytest.ini
Normal file
2
pytest.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
python_files = test_*.py
|
||||
@@ -33,9 +33,7 @@ from ansible.plugins.action import ActionBase
|
||||
|
||||
|
||||
def run_cmd(args, stdin=None):
|
||||
"""
|
||||
Execute an external command.
|
||||
"""
|
||||
"""Execute an external command."""
|
||||
p_in = None
|
||||
p_out = subprocess.PIPE
|
||||
p_err = subprocess.PIPE
|
||||
@@ -53,8 +51,10 @@ def run_cmd(args, stdin=None):
|
||||
|
||||
def kinit_password(principal, password, ccache_name, config):
|
||||
"""
|
||||
Perform kinit using principal/password, with the specified config file
|
||||
and store the TGT in ccache_name.
|
||||
Perform kinit using principal/password.
|
||||
|
||||
It uses the specified config file to kinit and stores the TGT
|
||||
in ccache_name.
|
||||
"""
|
||||
args = ["/usr/bin/kinit", principal, '-c', ccache_name]
|
||||
old_config = os.environ.get('KRB5_CONFIG')
|
||||
@@ -71,8 +71,10 @@ def kinit_password(principal, password, ccache_name, config):
|
||||
|
||||
def kinit_keytab(principal, keytab, ccache_name, config):
|
||||
"""
|
||||
Perform kinit using principal/keytab, with the specified config file
|
||||
and store the TGT in ccache_name.
|
||||
Perform kinit using principal/keytab.
|
||||
|
||||
It uses the specified config file to kinit and stores the TGT
|
||||
in ccache_name.
|
||||
"""
|
||||
if gssapi is None:
|
||||
raise ImportError("gssapi is not available")
|
||||
@@ -126,7 +128,7 @@ class ActionModule(ActionBase):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
"""
|
||||
handler for credential cache transfer
|
||||
Handle credential cache transfer.
|
||||
|
||||
ipa* commands can either provide a password or a keytab file
|
||||
in order to authenticate on the managed node with Kerberos.
|
||||
@@ -142,7 +144,6 @@ class ActionModule(ActionBase):
|
||||
|
||||
Then the IPA commands can use this credential cache file.
|
||||
"""
|
||||
|
||||
if task_vars is None:
|
||||
task_vars = dict()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Test ipaclient python3 binding
|
||||
from ipaclient.install.client import SECURE_PATH
|
||||
from ipaclient.install.client import SECURE_PATH # noqa: F401
|
||||
|
||||
# Check ipapython version to be >= 4.6
|
||||
from ipapython.version import NUM_VERSION, VERSION
|
||||
|
||||
@@ -76,6 +76,7 @@ import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging,
|
||||
paths, x509, NUM_VERSION, serialization, certdb, api,
|
||||
delete_persistent_client_session_data, write_tmp_file,
|
||||
ipa_generate_password, CalledProcessError, errors, disable_ra, DN,
|
||||
@@ -95,9 +96,10 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
realm = module.params.get('realm')
|
||||
hostname = module.params.get('hostname')
|
||||
servers = module.params.get('servers')
|
||||
debug = module.params.get('debug')
|
||||
|
||||
host_principal = 'host/%s@%s' % (hostname, realm)
|
||||
|
||||
@@ -67,6 +67,7 @@ import os
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging,
|
||||
SECURE_PATH, paths, sysrestore, options, NUM_VERSION, get_ca_cert,
|
||||
get_ca_certs, errors
|
||||
)
|
||||
@@ -83,6 +84,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
servers = module.params.get('servers')
|
||||
realm = module.params.get('realm')
|
||||
basedn = module.params.get('basedn')
|
||||
|
||||
@@ -53,7 +53,7 @@ RETURN = '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
paths, sysrestore
|
||||
setup_logging, paths, sysrestore
|
||||
)
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
backup = module.params.get('backup')
|
||||
|
||||
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
|
||||
|
||||
@@ -13,7 +13,7 @@ from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
# pylint: disable=unused-import
|
||||
try:
|
||||
from ipalib import api
|
||||
from ipalib import api # noqa: F401
|
||||
except ImportError:
|
||||
HAS_IPALIB = False
|
||||
else:
|
||||
@@ -27,7 +27,7 @@ else:
|
||||
from ipapython import sysrestore
|
||||
|
||||
try:
|
||||
import ipaserver
|
||||
import ipaserver # noqa: F401
|
||||
except ImportError:
|
||||
HAS_IPASERVER = False
|
||||
else:
|
||||
@@ -41,7 +41,7 @@ VAR_LIB_PKI_TOMCAT = "/var/lib/pki/pki-tomcat"
|
||||
def is_ntpd_configured():
|
||||
# ntpd is configured when sysrestore.state contains the line
|
||||
# [ntpd]
|
||||
ntpd_conf_section = re.compile('^\s*\[ntpd\]\s*$')
|
||||
ntpd_conf_section = re.compile(r'^\s*\[ntpd\]\s*$')
|
||||
|
||||
try:
|
||||
with open(SERVER_SYSRESTORE_STATE) as f:
|
||||
@@ -56,7 +56,7 @@ def is_ntpd_configured():
|
||||
def is_dns_configured():
|
||||
# dns is configured when /etc/named.conf contains the line
|
||||
# dyndb "ipa" "/usr/lib64/bind/ldap.so" {
|
||||
bind_conf_section = re.compile('^\s*dyndb\s+"ipa"\s+"[^"]+"\s+{$')
|
||||
bind_conf_section = re.compile(r'^\s*dyndb\s+"ipa"\s+"[^"]+"\s+{$')
|
||||
|
||||
try:
|
||||
with open(NAMED_CONF) as f:
|
||||
|
||||
@@ -135,8 +135,7 @@ if six.PY3:
|
||||
|
||||
def get_host_diff(ipa_host, module_host):
|
||||
"""
|
||||
Compares two dictionaries containing host attributes and builds a dict
|
||||
of differences.
|
||||
Build a dict with the differences from two host dicts.
|
||||
|
||||
:param ipa_host: the host structure seen from IPA
|
||||
:param module_host: the target host structure seen from the module params
|
||||
@@ -164,7 +163,7 @@ def get_host_diff(ipa_host, module_host):
|
||||
|
||||
def get_module_host(module):
|
||||
"""
|
||||
Creates a structure representing the host information
|
||||
Create a structure representing the host information.
|
||||
|
||||
Reads the module parameters and builds the host structure as expected from
|
||||
the module
|
||||
@@ -189,7 +188,7 @@ def get_module_host(module):
|
||||
|
||||
def ensure_host_present(module, api, ipahost):
|
||||
"""
|
||||
Ensures that the host exists in IPA and has the same attributes.
|
||||
Ensure host exists in IPA and has the same attributes.
|
||||
|
||||
:param module: the ansible module
|
||||
:param api: IPA api handle
|
||||
@@ -246,7 +245,7 @@ def ensure_host_present(module, api, ipahost):
|
||||
|
||||
def ensure_host_absent(module, api, host):
|
||||
"""
|
||||
Ensures that the host does not exist in IPA
|
||||
Ensure host does not exist in IPA.
|
||||
|
||||
:param module: the ansible module
|
||||
:param api: the IPA API handle
|
||||
@@ -271,9 +270,7 @@ def ensure_host_absent(module, api, host):
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main routine for the ansible module.
|
||||
"""
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
principal=dict(default='admin'),
|
||||
@@ -288,7 +285,6 @@ def main():
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
principal = module.params.get('principal', 'admin')
|
||||
ccache = module.params.get('ccache')
|
||||
fqdn = unicode(module.params.get('fqdn'))
|
||||
state = module.params.get('state')
|
||||
|
||||
@@ -70,7 +70,7 @@ RETURN = '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
paths, sysrestore, configure_ipa_conf
|
||||
setup_logging, paths, sysrestore, configure_ipa_conf
|
||||
)
|
||||
|
||||
|
||||
@@ -87,6 +87,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
servers = module.params.get('servers')
|
||||
domain = module.params.get('domain')
|
||||
realm = module.params.get('realm')
|
||||
|
||||
@@ -127,6 +127,7 @@ import tempfile
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging,
|
||||
SECURE_PATH, sysrestore, paths, options, configure_krb5_conf,
|
||||
realm_to_suffix, kinit_keytab, GSSError, kinit_password, NUM_VERSION,
|
||||
get_ca_cert, get_ca_certs, errors, run
|
||||
@@ -155,6 +156,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
servers = module.params.get('servers')
|
||||
domain = module.params.get('domain')
|
||||
realm = module.params.get('realm')
|
||||
|
||||
@@ -54,7 +54,7 @@ RETURN = '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
sysrestore, paths, tasks
|
||||
setup_logging, sysrestore, paths, tasks
|
||||
)
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
hostname = module.params.get('hostname')
|
||||
|
||||
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
|
||||
|
||||
@@ -60,7 +60,7 @@ RETURN = '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
options, configure_automount
|
||||
setup_logging, options, configure_automount
|
||||
)
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ def main():
|
||||
# os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
options.servers = module.params.get('servers')
|
||||
options.server = options.servers
|
||||
options.sssd = module.params.get('sssd')
|
||||
|
||||
@@ -60,7 +60,7 @@ RETURN = '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
sysrestore, paths, options, configure_firefox
|
||||
setup_logging, sysrestore, paths, options, configure_firefox
|
||||
)
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
domain = module.params.get('domain')
|
||||
options.firefox_dir = module.params.get('firefox_dir')
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ RETURN = '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
sysrestore, paths, configure_krb5_conf, logger
|
||||
setup_logging, sysrestore, paths, configure_krb5_conf, logger
|
||||
)
|
||||
|
||||
|
||||
@@ -103,6 +103,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
servers = module.params.get('servers')
|
||||
domain = module.params.get('domain')
|
||||
realm = module.params.get('realm')
|
||||
|
||||
@@ -58,7 +58,7 @@ import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
options, sysrestore, paths, configure_nisdomain
|
||||
setup_logging, options, sysrestore, paths, configure_nisdomain
|
||||
)
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
domain = module.params.get('domain')
|
||||
options.nisdomain = module.params.get('nisdomain')
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging,
|
||||
options, sysrestore, paths, ansible_module_get_parsed_ip_addresses,
|
||||
api, errors, create_ipa_nssdb, ipautil, ScriptError, CLIENT_INSTALL_ERROR,
|
||||
get_certs_from_ldap, DN, certstore, x509, logger, certdb,
|
||||
@@ -179,6 +180,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
cli_server = module.params.get('servers')
|
||||
cli_realm = module.params.get('realm')
|
||||
hostname = module.params.get('hostname')
|
||||
|
||||
@@ -67,6 +67,7 @@ import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging,
|
||||
options, sysrestore, paths, sync_time, logger, ipadiscovery,
|
||||
timeconf
|
||||
)
|
||||
@@ -89,6 +90,8 @@ def main():
|
||||
)
|
||||
|
||||
# module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
options.ntp_servers = module.params.get('ntp_servers')
|
||||
options.ntp_pool = module.params.get('ntp_pool')
|
||||
options.no_ntp = module.params.get('no_ntp')
|
||||
|
||||
@@ -68,6 +68,7 @@ RETURN = '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging,
|
||||
options, sysrestore, paths, configure_ssh_config, configure_sshd_config
|
||||
)
|
||||
|
||||
@@ -85,6 +86,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
options.servers = module.params.get('servers')
|
||||
options.server = options.servers
|
||||
options.no_ssh = module.params.get('no_ssh')
|
||||
|
||||
@@ -101,7 +101,7 @@ RETURN = '''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
options, sysrestore, paths, configure_sssd_conf, logger
|
||||
setup_logging, options, sysrestore, paths, configure_sssd_conf, logger
|
||||
)
|
||||
|
||||
|
||||
@@ -130,6 +130,8 @@ def main():
|
||||
# options.set_logger(ansible_log)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
cli_server = module.params.get('servers')
|
||||
cli_domain = module.params.get('domain')
|
||||
cli_realm = module.params.get('realm')
|
||||
|
||||
@@ -199,6 +199,7 @@ except ImportError:
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging,
|
||||
paths, sysrestore, options, CheckedIPAddress, validate_domain_name,
|
||||
logger, x509, normalize_hostname, installer, version, ScriptError,
|
||||
CLIENT_INSTALL_ERROR, tasks, check_ldap_conf, timeconf, constants,
|
||||
@@ -234,7 +235,6 @@ def is_client_configured():
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return (os.path.isfile(paths.IPA_DEFAULT_CONF) and
|
||||
os.path.isfile(os.path.join(paths.IPA_CLIENT_SYSRESTORE,
|
||||
sysrestore.SYSRESTORE_STATEFILE)))
|
||||
@@ -242,11 +242,10 @@ def is_client_configured():
|
||||
|
||||
def get_ipa_conf():
|
||||
"""
|
||||
Return IPA configuration read from /etc/ipa/default.conf
|
||||
Return IPA configuration read from `/etc/ipa/default.conf`.
|
||||
|
||||
:returns: dict containing key,value
|
||||
"""
|
||||
|
||||
parser = RawConfigParser()
|
||||
parser.read(paths.IPA_DEFAULT_CONF)
|
||||
result = dict()
|
||||
@@ -290,6 +289,8 @@ def main():
|
||||
)
|
||||
|
||||
# module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
options.domain_name = module.params.get('domain')
|
||||
options.servers = module.params.get('servers')
|
||||
options.realm_name = module.params.get('realm')
|
||||
|
||||
@@ -103,6 +103,7 @@ import tempfile
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_client import (
|
||||
setup_logging,
|
||||
SECURE_PATH, paths, kinit_keytab, run, GSSError, configure_krb5_conf
|
||||
)
|
||||
|
||||
@@ -121,6 +122,8 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
servers = module.params.get('servers')
|
||||
domain = module.params.get('domain')
|
||||
realm = module.params.get('realm')
|
||||
|
||||
@@ -259,9 +259,6 @@ if NUM_VERSION >= 40400:
|
||||
sssd_enable_ifp = None
|
||||
|
||||
logger = logging.getLogger("ipa-client-install")
|
||||
standard_logging_setup(
|
||||
paths.IPACLIENT_INSTALL_LOG, verbose=False, debug=False,
|
||||
filemode='a', console_format='%(message)s')
|
||||
root_logger = logger
|
||||
|
||||
else:
|
||||
@@ -270,6 +267,12 @@ else:
|
||||
raise Exception("freeipa version '%s' is too old" % VERSION)
|
||||
|
||||
|
||||
def setup_logging():
|
||||
standard_logging_setup(
|
||||
paths.IPACLIENT_INSTALL_LOG, verbose=False, debug=False,
|
||||
filemode='a', console_format='%(message)s')
|
||||
|
||||
|
||||
def ansible_module_get_parsed_ip_addresses(ansible_module,
|
||||
param='ip_addresses'):
|
||||
ip_addresses = ansible_module.params.get(param)
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
# 5 - Principal name or realm not found in keytab
|
||||
failed_when: result_ipa_rmkeytab.rc != 0 and
|
||||
result_ipa_rmkeytab.rc != 3 and result_ipa_rmkeytab.rc != 5
|
||||
when: ipaclient_use_otp | bool or ipaclient_force_join | bool
|
||||
when: (ipaclient_use_otp | bool or ipaclient_force_join | bool) and not ipaclient_on_master | bool
|
||||
|
||||
- name: Install - Backup and set hostname
|
||||
ipaclient_set_hostname:
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
|
||||
# Test ipaerver python3 binding
|
||||
try:
|
||||
from ipaserver.install.server.replicainstall import install_check
|
||||
from ipaserver.install.server.replicainstall import ( # noqa: F401
|
||||
install_check,
|
||||
)
|
||||
except ImportError:
|
||||
from ipaserver.install.server.replicainstall import promote_check
|
||||
from ipaserver.install.server.replicainstall import ( # noqa: F401
|
||||
promote_check,
|
||||
)
|
||||
|
||||
# Check ipapython version to be >= 4.6
|
||||
from ipapython.version import NUM_VERSION, VERSION
|
||||
|
||||
@@ -67,7 +67,7 @@ import six
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, installer, paths,
|
||||
AnsibleModuleLog, setup_logging, installer, paths,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_remote_api, api
|
||||
)
|
||||
@@ -91,6 +91,7 @@ def main():
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
setup_logging()
|
||||
ansible_log = AnsibleModuleLog(ansible_module)
|
||||
|
||||
# get parameters #
|
||||
|
||||
@@ -137,7 +137,7 @@ import os
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, installer, DN, paths,
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths,
|
||||
ansible_module_get_parsed_ip_addresses, sysrestore,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, create_ipa_conf
|
||||
@@ -186,6 +186,7 @@ def main():
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
setup_logging()
|
||||
ansible_log = AnsibleModuleLog(ansible_module)
|
||||
|
||||
# get parameters #
|
||||
|
||||
@@ -99,7 +99,7 @@ import inspect
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, installer, DN, paths,
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, redirect_stdout, custodiainstance
|
||||
)
|
||||
@@ -131,6 +131,7 @@ def main():
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
setup_logging()
|
||||
ansible_log = AnsibleModuleLog(ansible_module)
|
||||
|
||||
# get parameters #
|
||||
|
||||
@@ -100,7 +100,7 @@ import os
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, installer, DN, paths,
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout,
|
||||
replica_ds_init_info, dsinstance, upgradeinstance, installutils
|
||||
@@ -123,8 +123,8 @@ def main():
|
||||
ccache=dict(required=True),
|
||||
_ca_enabled=dict(required=False, type='bool'),
|
||||
_ca_file=dict(required=False),
|
||||
_dirsrv_pkcs12_info=dict(required=False),
|
||||
_pkinit_pkcs12_info=dict(required=False),
|
||||
_dirsrv_pkcs12_info=dict(required=False, type='list'),
|
||||
_pkinit_pkcs12_info=dict(required=False, type='list'),
|
||||
_top_dir=dict(required=True),
|
||||
dirman_password=dict(required=True, no_log=True),
|
||||
ds_ca_subject=dict(required=True),
|
||||
@@ -133,6 +133,7 @@ def main():
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
setup_logging()
|
||||
ansible_log = AnsibleModuleLog(ansible_module)
|
||||
|
||||
# get parameters #
|
||||
|
||||
@@ -97,7 +97,7 @@ import os
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, installer, DN, paths,
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout,
|
||||
replica_ds_init_info
|
||||
@@ -119,8 +119,8 @@ def main():
|
||||
ccache=dict(required=True),
|
||||
_ca_enabled=dict(required=False, type='bool'),
|
||||
_ca_file=dict(required=False),
|
||||
_dirsrv_pkcs12_info=dict(required=False),
|
||||
_pkinit_pkcs12_info=dict(required=False),
|
||||
_dirsrv_pkcs12_info=dict(required=False, type='list'),
|
||||
_pkinit_pkcs12_info=dict(required=False, type='list'),
|
||||
_top_dir=dict(required=True),
|
||||
dirman_password=dict(required=True, no_log=True),
|
||||
ds_ca_subject=dict(required=True),
|
||||
@@ -129,6 +129,7 @@ def main():
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
setup_logging()
|
||||
ansible_log = AnsibleModuleLog(ansible_module)
|
||||
|
||||
# get parameters #
|
||||
|
||||
@@ -77,7 +77,7 @@ import os
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, installer, DN, paths,
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, service,
|
||||
find_providing_servers, services
|
||||
@@ -103,6 +103,7 @@ def main():
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
setup_logging()
|
||||
ansible_log = AnsibleModuleLog(ansible_module)
|
||||
|
||||
# get parameters #
|
||||
|
||||
@@ -137,7 +137,7 @@ import os
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, installer, DN, paths,
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths,
|
||||
ansible_module_get_parsed_ip_addresses,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, api, redirect_stdout, ipaldap,
|
||||
@@ -186,6 +186,7 @@ def main():
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
setup_logging()
|
||||
ansible_log = AnsibleModuleLog(ansible_module)
|
||||
|
||||
# get parameters #
|
||||
|
||||
@@ -86,7 +86,7 @@ import os
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
AnsibleModuleLog, installer, DN, paths, sysrestore,
|
||||
AnsibleModuleLog, setup_logging, installer, DN, paths, sysrestore,
|
||||
gen_env_boostrap_finalize_core, constants, api_bootstrap_finalize,
|
||||
gen_ReplicaConfig, gen_remote_api, api, krbinstance, redirect_stdout
|
||||
)
|
||||
@@ -106,7 +106,7 @@ def main():
|
||||
ccache=dict(required=True),
|
||||
_ca_enabled=dict(required=False, type='bool'),
|
||||
_ca_file=dict(required=False),
|
||||
_pkinit_pkcs12_info=dict(required=False),
|
||||
_pkinit_pkcs12_info=dict(required=False, type='list'),
|
||||
_top_dir=dict(required=True),
|
||||
dirman_password=dict(required=True, no_log=True),
|
||||
),
|
||||
@@ -114,6 +114,7 @@ def main():
|
||||
)
|
||||
|
||||
ansible_module._ansible_debug = True
|
||||
setup_logging()
|
||||
ansible_log = AnsibleModuleLog(ansible_module)
|
||||
|
||||
# get parameters #
|
||||
|
||||
@@ -53,7 +53,7 @@ password:
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ansible_ipa_replica import (
|
||||
ipa_generate_password
|
||||
setup_logging, ipa_generate_password
|
||||
)
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ def main():
|
||||
)
|
||||
|
||||
module._ansible_debug = True
|
||||
setup_logging()
|
||||
|
||||
master_password = module.params.get('master_password')
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user