Replacing community.general ipa modules with ansible-freeipa
The community.general ipa modules used in tripleo-ipa don't work under FIPS deployment. This patch is fixing that by replacing it with the ansible-freeipa ipa modules. Co-Author: Ade Lee <alee@redhat.com> Co-Author: Grzegorz Grasza <xek@redhat.com> Change-Id: Ibfd1b34fdf3d533579512f531ac8619b356f9ba0
This commit is contained in:
parent
6c0dc79e0d
commit
fa63e4f3d9
@ -5,6 +5,7 @@ collections:
|
|||||||
- name: https://github.com/ansible-collections/community.general
|
- name: https://github.com/ansible-collections/community.general
|
||||||
type: git
|
type: git
|
||||||
version: main
|
version: main
|
||||||
|
- freeipa.ansible_freeipa
|
||||||
- ansible.posix
|
- ansible.posix
|
||||||
- ansible.netcommon
|
- ansible.netcommon
|
||||||
- openstack.cloud
|
- openstack.cloud
|
||||||
|
12
tox.ini
12
tox.ini
@ -23,9 +23,9 @@ whitelist_externals =
|
|||||||
[testenv:molecule]
|
[testenv:molecule]
|
||||||
install_command = pip install {opts} {packages}
|
install_command = pip install {opts} {packages}
|
||||||
setenv =
|
setenv =
|
||||||
ANSIBLE_FILTER_PLUGINS={toxinidir}/tripleo_ipa/ansible_plugins/filter
|
ANSIBLE_FILTER_PLUGINS=~/.ansible/plugins/filter:/usr/share/ansible/plugins/filter:{toxinidir}/tripleo_ipa/ansible_plugins/filter
|
||||||
ANSIBLE_LIBRARY={toxinidir}/tripleo_ipa/roles.galaxy/config_template/library:{toxinidir}/tripleo_ipa/ansible_plugins/modules
|
ANSIBLE_LIBRARY=~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules:{toxinidir}/tripleo_ipa/ansible_plugins/modules
|
||||||
ANSIBLE_ROLES_PATH={toxinidir}/tripleo_ipa/roles.galaxy:{toxinidir}/tripleo_ipa/roles
|
ANSIBLE_ROLES_PATH=~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:{toxinidir}/tripleo_ipa/roles
|
||||||
deps =
|
deps =
|
||||||
-r {toxinidir}/requirements.txt
|
-r {toxinidir}/requirements.txt
|
||||||
-r {toxinidir}/molecule-requirements.txt
|
-r {toxinidir}/molecule-requirements.txt
|
||||||
@ -47,9 +47,9 @@ commands =
|
|||||||
|
|
||||||
[testenv:linters]
|
[testenv:linters]
|
||||||
setenv =
|
setenv =
|
||||||
ANSIBLE_FILTER_PLUGINS={toxinidir}/tripleo_ipa/ansible_plugins/filter
|
ANSIBLE_FILTER_PLUGINS=~/.ansible/plugins/filter:/usr/share/ansible/plugins/filter:{toxinidir}/tripleo_ipa/ansible_plugins/filter
|
||||||
ANSIBLE_LIBRARY={toxinidir}/tripleo_ipa/roles.galaxy/config_template/library:{toxinidir}/tripleo_ipa/ansible_plugins/modules
|
ANSIBLE_LIBRARY=~/.ansible/plugins/modules:/usr/share/ansible/plugins/modules:{toxinidir}/tripleo_ipa/ansible_plugins/modules
|
||||||
ANSIBLE_ROLES_PATH={toxinidir}/tripleo_ipa/roles.galaxy:{toxinidir}/tripleo_ipa/roles
|
ANSIBLE_ROLES_PATH=~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:{toxinidir}/tripleo_ipa/roles
|
||||||
deps =
|
deps =
|
||||||
-r {toxinidir}/ansible-requirements.txt
|
-r {toxinidir}/ansible-requirements.txt
|
||||||
-r {toxinidir}/test-requirements.txt
|
-r {toxinidir}/test-requirements.txt
|
||||||
|
@ -42,70 +42,11 @@
|
|||||||
ipa_principal: "{{ tripleo_ipa_principal | default(lookup('env', 'IPA_PRINCIPAL')) }}"
|
ipa_principal: "{{ tripleo_ipa_principal | default(lookup('env', 'IPA_PRINCIPAL')) }}"
|
||||||
ipa_password: "{{ tripleo_ipa_password | default(lookup('env', 'IPA_PASSWORD')) }}"
|
ipa_password: "{{ tripleo_ipa_password | default(lookup('env', 'IPA_PASSWORD')) }}"
|
||||||
|
|
||||||
- name: set keytab permissions facts
|
- name: set perms, privs, roles
|
||||||
set_fact:
|
include_role:
|
||||||
tripleo_ipa_perms:
|
name: triple_ipa_setup
|
||||||
- {name: 'Modify host password', right: "write", type: "host", attrs: "userpassword"}
|
tasks_from: setup
|
||||||
- {name: 'Write host certificate', right: "write", type: "host", attrs: "usercertificate"}
|
apply:
|
||||||
- {name: 'Modify host userclass', right: "write", type: "host", attrs: "userclass"}
|
environment:
|
||||||
- {name: 'Modify service managedBy attribute', right: "write", type: "service", attrs: "managedby"}
|
IPA_USER: "{ ipa_principal }"
|
||||||
tripleo_ipa_privilege_perms:
|
IPA_PASS: "{ ipa_password }"
|
||||||
- 'System: add hosts'
|
|
||||||
- 'System: remove hosts'
|
|
||||||
- 'Modify host password'
|
|
||||||
- 'Modify host userclass'
|
|
||||||
- 'System: Modify hosts'
|
|
||||||
- 'Modify service managedBy attribute'
|
|
||||||
- 'System: Add krbPrincipalName to a Host'
|
|
||||||
- 'System: Add Services'
|
|
||||||
- 'System: Remove Services'
|
|
||||||
- 'Revoke certificate'
|
|
||||||
- 'System: manage host keytab'
|
|
||||||
- 'System: Manage host certificates'
|
|
||||||
- 'System: modify services'
|
|
||||||
- 'System: manage service keytab'
|
|
||||||
- 'System: read dns entries'
|
|
||||||
- 'System: remove dns entries'
|
|
||||||
- 'System: add dns entries'
|
|
||||||
- 'System: update dns entries'
|
|
||||||
- 'System: Modify Realm Domains'
|
|
||||||
- 'Retrieve Certificates from the CA'
|
|
||||||
|
|
||||||
# unfortunately we don't have ansible module yet to create perms
|
|
||||||
# TODO(d34dh0r53): we should be able to obtain a token via curl
|
|
||||||
# which will allow us to perform these operations without a kinit first.
|
|
||||||
- name: add nova host management permissions
|
|
||||||
shell: |
|
|
||||||
ipa permission-find "{{ item.name }}"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
ipa permission-add "{{ item.name }}" --right "{{ item.right }}" \
|
|
||||||
--type "{{ item.type }}" --attrs "{{ item.attrs }}"
|
|
||||||
fi
|
|
||||||
loop: "{{ tripleo_ipa_perms|flatten(levels=1) }}"
|
|
||||||
|
|
||||||
# unfortunately we don't have ansible module yet to create privileges
|
|
||||||
- name: add nova host privilege
|
|
||||||
shell: |
|
|
||||||
ipa privilege-find 'Nova Host Management'
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
ipa privilege-add --desc='Nova Host Management' 'Nova Host Management'
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: add permissions to the nova host privilege
|
|
||||||
shell: |
|
|
||||||
ipa privilege-add-permission 'Nova Host Management' \
|
|
||||||
--permission "{{ item }}"
|
|
||||||
register: add_perm_command
|
|
||||||
failed_when:
|
|
||||||
- add_perm_command.rc !=0
|
|
||||||
- '"This entry is already a member" not in add_perm_command.stdout'
|
|
||||||
loop: "{{ tripleo_ipa_privilege_perms|flatten(levels=1) }}"
|
|
||||||
|
|
||||||
- name: add nova host manager role
|
|
||||||
ipa_role:
|
|
||||||
name: Nova Host Manager
|
|
||||||
description: Nova Host Manager
|
|
||||||
ipa_user: "{{ ipa_principal }}"
|
|
||||||
ipa_pass: "{{ ipa_password }}"
|
|
||||||
privilege:
|
|
||||||
- Nova Host Management
|
|
||||||
|
@ -43,17 +43,39 @@
|
|||||||
record_type: "{{ 'A' if record_value| ansible.netcommon.ipv4 else 'AAAA' }}"
|
record_type: "{{ 'A' if record_value| ansible.netcommon.ipv4 else 'AAAA' }}"
|
||||||
|
|
||||||
- name: add dns zone
|
- name: add dns zone
|
||||||
ipa_dnszone:
|
freeipa.ansible_freeipa.ipadnszone:
|
||||||
zone_name: "{{ zone_name }}"
|
name: "{{ zone_name }}"
|
||||||
become: true
|
become: true
|
||||||
|
|
||||||
- name: add forward dns record
|
- name: Modify or add forward dns
|
||||||
ipa_dnsrecord:
|
block:
|
||||||
zone_name: "{{ zone_name }}"
|
- name: try modifying forward dns record
|
||||||
record_name: "{{ record_name }}"
|
freeipa.ansible_freeipa.ipadnsrecord:
|
||||||
record_type: "{{ record_type }}"
|
zone_name: "{{ zone_name }}"
|
||||||
record_value: "{{ record_value }}"
|
record_name: "{{ record_name }}"
|
||||||
become: true
|
record_type: "{{ record_type }}"
|
||||||
|
a_rec: "{{ record_value }}"
|
||||||
|
a_ip_address: ""
|
||||||
|
when: record_type == 'A'
|
||||||
|
become: true
|
||||||
|
|
||||||
|
- name: try modifying forward dns record
|
||||||
|
freeipa.ansible_freeipa.ipadnsrecord:
|
||||||
|
zone_name: "{{ zone_name }}"
|
||||||
|
record_name: "{{ record_name }}"
|
||||||
|
record_type: "{{ record_type }}"
|
||||||
|
aaaa_rec: "{{ record_value }}"
|
||||||
|
aaaa_ip_address: ""
|
||||||
|
when: record_type == 'AAAA'
|
||||||
|
become: true
|
||||||
|
rescue:
|
||||||
|
- name: add forward dns record
|
||||||
|
freeipa.ansible_freeipa.ipadnsrecord:
|
||||||
|
zone_name: "{{ zone_name }}"
|
||||||
|
record_name: "{{ record_name }}"
|
||||||
|
record_type: "{{ record_type }}"
|
||||||
|
record_value: "{{ record_value }}"
|
||||||
|
become: true
|
||||||
|
|
||||||
- name: get reverse record data
|
- name: get reverse record data
|
||||||
set_fact:
|
set_fact:
|
||||||
@ -72,23 +94,30 @@
|
|||||||
when: record_type == 'AAAA'
|
when: record_type == 'AAAA'
|
||||||
|
|
||||||
- name: add reverse record dns zone
|
- name: add reverse record dns zone
|
||||||
ipa_dnszone:
|
freeipa.ansible_freeipa.ipadnszone:
|
||||||
zone_name: "{{ reverse_record_zone }}"
|
name: "{{ reverse_record_zone }}"
|
||||||
register: reverse_zone_result
|
register: reverse_zone_result
|
||||||
failed_when:
|
failed_when: reverse_zone_result.failed and 'already exists in DNS' not in reverse_zone_result.msg
|
||||||
- "'zone' not in reverse_zone_result"
|
|
||||||
- "'already exists in DNS' not in reverse_zone_result.msg"
|
|
||||||
become: true
|
become: true
|
||||||
|
|
||||||
- name: add reverse dns record
|
- name: Modify or add reverse dns record
|
||||||
ipa_dnsrecord:
|
block:
|
||||||
zone_name: "{{ reverse_record_zone }}"
|
- name: try modifying reverse dns record
|
||||||
record_name: "{{ reverse_record_name }}"
|
freeipa.ansible_freeipa.ipadnsrecord:
|
||||||
record_value: "{{ record_name }}.{{ zone_name }}."
|
zone_name: "{{ reverse_record_zone }}"
|
||||||
record_type: "PTR"
|
record_name: "{{ reverse_record_name }}"
|
||||||
register: reverse_record_result
|
record_type: "PTR"
|
||||||
failed_when:
|
ptr_rec: "{{ record_name }}.{{ zone_name }}."
|
||||||
- "'record' not in reverse_record_result"
|
ptr_hostname: ""
|
||||||
- "'DNS zone not found' not in reverse_record_result.msg"
|
become: true
|
||||||
become: true
|
rescue:
|
||||||
|
- name: add reverse dns record
|
||||||
|
freeipa.ansible_freeipa.ipadnsrecord:
|
||||||
|
zone_name: "{{ reverse_record_zone }}"
|
||||||
|
record_name: "{{ reverse_record_name }}"
|
||||||
|
record_type: "PTR"
|
||||||
|
record_value: "{{ record_name }}.{{ zone_name }}."
|
||||||
|
register: reverse_record_result
|
||||||
|
failed_when: reverse_zone_result.failed and 'already exists in DNS' not in reverse_zone_result.msg
|
||||||
|
become: true
|
||||||
when: zone_name is match("^(|.+\.)" + cloud_domain + "$")
|
when: zone_name is match("^(|.+\.)" + cloud_domain + "$")
|
||||||
|
@ -44,43 +44,22 @@
|
|||||||
when: enroll_base_server|bool
|
when: enroll_base_server|bool
|
||||||
become: true
|
become: true
|
||||||
block:
|
block:
|
||||||
- name: destroy the old keytab
|
- name: add new host with one-time password
|
||||||
command: "kdestroy -A"
|
freeipa.ansible_freeipa.ipahost:
|
||||||
|
name: "{{ base_server_fqdn }}"
|
||||||
|
random: true
|
||||||
|
force: true
|
||||||
|
state: present
|
||||||
|
register: ipa_host
|
||||||
|
failed_when: ipa_host.failed and "Password cannot be set on enrolled host" not in ipa_host.msg
|
||||||
|
|
||||||
- name: get a new keytab
|
- name: set otp as a host fact
|
||||||
command: "kinit -kt /etc/novajoin/krb5.keytab {{ principal }}"
|
set_fact:
|
||||||
|
ipa_host_otp: "{{ ipa_host.host.randompassword }}"
|
||||||
- name: get host raw data and keytab info
|
no_log: true
|
||||||
command: "ipa host-show --raw --all {{ base_server_fqdn }}"
|
delegate_facts: true
|
||||||
register: host_raw_data
|
delegate_to: "{{ tripleo_ipa_delegate_server }}"
|
||||||
changed_when: false
|
when: "'host' in ipa_host"
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Print debug data
|
|
||||||
debug: var=host_raw_data
|
|
||||||
|
|
||||||
- name: confirm that host is not already registered with current keytab
|
|
||||||
when: '"has_keytab: TRUE" not in host_raw_data.stdout'
|
|
||||||
block:
|
|
||||||
- name: remove stale host if present
|
|
||||||
when: host_raw_data.rc == 0
|
|
||||||
ipa_host:
|
|
||||||
fqdn: "{{ base_server_fqdn }}"
|
|
||||||
state: absent
|
|
||||||
|
|
||||||
- name: add new host with random one-time password
|
|
||||||
ipa_host:
|
|
||||||
fqdn: "{{ base_server_fqdn }}"
|
|
||||||
random_password: true
|
|
||||||
force: true
|
|
||||||
register: ipa_host
|
|
||||||
|
|
||||||
- name: set otp as a host fact
|
|
||||||
set_fact:
|
|
||||||
ipa_host_otp: "{{ ipa_host.host.randompassword }}"
|
|
||||||
no_log: true
|
|
||||||
delegate_facts: true
|
|
||||||
delegate_to: "{{ tripleo_ipa_delegate_server }}"
|
|
||||||
|
|
||||||
- name: add required services
|
- name: add required services
|
||||||
include: services.yml
|
include: services.yml
|
||||||
|
@ -31,28 +31,22 @@
|
|||||||
service: "{{ item.1 }}"
|
service: "{{ item.1 }}"
|
||||||
|
|
||||||
- name: add sub_host
|
- name: add sub_host
|
||||||
ipa_host:
|
freeipa.ansible_freeipa.ipahost:
|
||||||
fqdn: "{{ sub_host }}"
|
fqdn: "{{ sub_host }}"
|
||||||
force: true
|
force: true
|
||||||
state: present
|
state: present
|
||||||
validate_certs: false
|
|
||||||
become: true
|
become: true
|
||||||
|
|
||||||
- name: add service
|
- name: add service
|
||||||
ipa_service:
|
freeipa.ansible_freeipa.ipaservice:
|
||||||
name: "{{ service }}/{{ sub_host }}"
|
name: "{{ service }}/{{ sub_host }}"
|
||||||
force: true
|
force: true
|
||||||
state: present
|
state: present
|
||||||
validate_certs: false
|
|
||||||
become: true
|
become: true
|
||||||
register: my_service
|
|
||||||
|
|
||||||
- name: add host to managed_hosts if needed
|
- name: add host to managed_hosts if needed (shell)
|
||||||
when: base_server_fqdn not in my_service['host']['managedby_host']
|
shell: |
|
||||||
ipa_service:
|
ipa service-add-host --hosts "{{ base_server_fqdn }}" "{{ service }}"/"{{ sub_host }}"
|
||||||
name: "{{ service }}/{{ sub_host }}"
|
register: service_add_out
|
||||||
force: true
|
failed_when: service_add_out.failed and 'This entry is already a member' not in service_add_out.stdout
|
||||||
state: present
|
|
||||||
hosts: "{{ my_service['host']['managedby_host'] + [ base_server_fqdn ] }}"
|
|
||||||
validate_certs: false
|
|
||||||
become: true
|
become: true
|
||||||
|
@ -24,33 +24,20 @@
|
|||||||
nova_service: "nova/{{ undercloud_fqdn }}"
|
nova_service: "nova/{{ undercloud_fqdn }}"
|
||||||
|
|
||||||
- name: add nova service
|
- name: add nova service
|
||||||
ipa_service:
|
freeipa.ansible_freeipa.ipaservice:
|
||||||
name: "{{ nova_service }}"
|
name: "{{ nova_service }}"
|
||||||
state: present
|
state: present
|
||||||
force: true
|
force: true
|
||||||
|
|
||||||
# TODO(dsedgmen): remove when community ipa modules are replaced with ansible-freeipa
|
|
||||||
# From looking at the ansible-freeipa modules they take into account exsisting
|
|
||||||
# services assigned to the role
|
|
||||||
# https://review.opendev.org/c/x/tripleo-ipa/+/771065
|
|
||||||
- name: get current list of services assigned role Nova Host Manager
|
|
||||||
ipa_role:
|
|
||||||
name: Nova Host Manager
|
|
||||||
register: services_roles
|
|
||||||
|
|
||||||
# TODO(dsedgmen): remove when community ipa modules are replaced with ansible-freeipa
|
|
||||||
# From looking at the ansible-freeipa modules they take into account exsisting
|
|
||||||
# services assigned to the role
|
|
||||||
# https://review.opendev.org/c/x/tripleo-ipa/+/771065
|
|
||||||
- name: create list of services for role
|
|
||||||
set_fact:
|
|
||||||
nova_service: "{{ [ nova_service ] + services_roles.role.member_service }}"
|
|
||||||
when: services_roles.role.member_service is defined
|
|
||||||
|
|
||||||
- name: add Nova Host Manager role
|
- name: add Nova Host Manager role
|
||||||
ipa_role:
|
freeipa.ansible_freeipa.iparole:
|
||||||
name: Nova Host Manager
|
name: Nova Host Manager
|
||||||
description: Nova Host Manager
|
description: Nova Host Manager
|
||||||
privilege:
|
privilege:
|
||||||
- Nova Host Management
|
- Nova Host Management
|
||||||
|
|
||||||
|
- name: add service to the Nova Host Manager role
|
||||||
|
freeipa.ansible_freeipa.iparole:
|
||||||
|
name: Nova Host Manager
|
||||||
service: "{{ nova_service }}"
|
service: "{{ nova_service }}"
|
||||||
|
action: member
|
||||||
|
@ -23,10 +23,10 @@
|
|||||||
- name: set keytab permissions facts
|
- name: set keytab permissions facts
|
||||||
set_fact:
|
set_fact:
|
||||||
novajoin_perms:
|
novajoin_perms:
|
||||||
- {name: 'Modify host password', right: "write", type: "host", attrs: "userpassword"}
|
- {name: 'Modify host password', right: "write", type: "host", attrs: ["userpassword"]}
|
||||||
- {name: 'Write host certificate', right: "write", type: "host", attrs: "usercertificate"}
|
- {name: 'Write host certificate', right: "write", type: "host", attrs: ["usercertificate"]}
|
||||||
- {name: 'Modify host userclass', right: "write", type: "host", attrs: "userclass"}
|
- {name: 'Modify host userclass', right: "write", type: "host", attrs: ["userclass"]}
|
||||||
- {name: 'Modify service managedBy attribute', right: "write", type: "service", attrs: "managedby"}
|
- {name: 'Modify service managedBy attribute', right: "write", type: "service", attrs: ["managedby"]}
|
||||||
novajoin_privilege_perms:
|
novajoin_privilege_perms:
|
||||||
- 'System: add hosts'
|
- 'System: add hosts'
|
||||||
- 'System: remove hosts'
|
- 'System: remove hosts'
|
||||||
@ -49,36 +49,32 @@
|
|||||||
- 'System: Modify Realm Domains'
|
- 'System: Modify Realm Domains'
|
||||||
- 'Retrieve Certificates from the CA'
|
- 'Retrieve Certificates from the CA'
|
||||||
|
|
||||||
# unfortunately we don't have ansible module yet to create perms
|
|
||||||
- name: add nova host management permissions
|
- name: add nova host management permissions
|
||||||
shell: |
|
freeipa.ansible_freeipa.ipapermission:
|
||||||
ipa permission-find "{{ item.name }}"
|
name: "{{ item.name }}"
|
||||||
if [ $? -ne 0 ]; then
|
right: "{{ item.right }}"
|
||||||
ipa permission-add "{{ item.name }}" --right "{{ item.right }}" \
|
object_type: "{{ item.type }}"
|
||||||
--type "{{ item.type }}" --attrs "{{ item.attrs }}"
|
attrs: "{{ item.attrs }}"
|
||||||
fi
|
|
||||||
loop: "{{ novajoin_perms|flatten(levels=1) }}"
|
loop: "{{ novajoin_perms|flatten(levels=1) }}"
|
||||||
|
|
||||||
# unfortunately we don't have ansible module yet to create privileges
|
|
||||||
- name: add Nova Host privilege
|
- name: add Nova Host privilege
|
||||||
shell: |
|
freeipa.ansible_freeipa.ipaprivilege:
|
||||||
ipa privilege-find 'Nova Host Management'
|
name: Nova Host Management
|
||||||
if [ $? -ne 0 ]; then
|
description: Nova Host Management
|
||||||
ipa privilege-add --desc='Nova Host Management' 'Nova Host Management'
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: add permissions to the Nova Host privilege
|
- name: add permissions to the Nova Host privilege
|
||||||
shell: |
|
freeipa.ansible_freeipa.ipaprivilege:
|
||||||
ipa privilege-add-permission 'Nova Host Management' \
|
name: Nova Host Management
|
||||||
--permission "{{ item }}"
|
action: member
|
||||||
|
permission: "{{ item }}"
|
||||||
register: add_perm_command
|
register: add_perm_command
|
||||||
failed_when:
|
failed_when:
|
||||||
- add_perm_command.rc !=0
|
- add_perm_command.failed
|
||||||
- '"This entry is already a member" not in add_perm_command.stdout'
|
- '"This entry is already a member" not in add_perm_command.msg'
|
||||||
loop: "{{ novajoin_privilege_perms|flatten(levels=1) }}"
|
loop: "{{ novajoin_privilege_perms }}"
|
||||||
|
|
||||||
- name: add Nova Host Manager role
|
- name: add Nova Host Manager role
|
||||||
ipa_role:
|
freeipa.ansible_freeipa.iparole:
|
||||||
name: Nova Host Manager
|
name: Nova Host Manager
|
||||||
description: Nova Host Manager
|
description: Nova Host Manager
|
||||||
privilege:
|
privilege:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user