Skip to content

Commit c1fa3d3

Browse files
committedOct 18, 2016
Andrew Huffman - PR changes
1 parent faf6fcf commit c1fa3d3

File tree

8 files changed

+309
-76
lines changed

8 files changed

+309
-76
lines changed
 

‎LICENSE

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
The MIT License (MIT)
22

33
Copyright (c) 2016 Tyler Cross
4+
Copyright (c) 2016 Andrew Huffman
45

56
Permission is hereby granted, free of charge, to any person obtaining a copy
67
of this software and associated documentation files (the "Software"), to deal

‎README.md

+123-51
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,129 @@
1-
# sudoers
2-
> An Ansible role for comprehensive management of a sudoers configuration.
3-
4-
[![Build Status](https://travis-ci.org/wtcross/ansible-sudoers.svg?branch=master)](https://travis-ci.org/wtcross/ansible-sudoers)
1+
# ahuffman.sudoers
2+
An Ansible role for configuring the /etc/sudoers file and /etc/sudoers.d files.
53

64
This role makes it possible to completely define your sudoers configuration with Ansible. All of the following are configurable:
75
- defaults
86
- aliases
7+
* Users
8+
* Runas
9+
* Hosts
10+
* Commands
911
- specifications
1012

11-
*Tip:* Here's a [great document about sudoers configuration](https://help.ubuntu.com/community/Sudoers)
13+
## About and Usage
14+
The top level `/etc/sudoers` file can be kept as light as possible by specifying sudoer_separate_specs: True in either the defaults or your playbook. sudoer_separate_specs is set to True by default.
15+
***Warning, this role will clean out /etc/sudoers.d/ if sudoer_separate_specs is set to false. You will lose any files stored there even if not generated by this role.
16+
17+
If sudoer_separate_specs is set to true, it will include all defaults and aliases in /etc/sudoers rather than breaking the specs out into their own files in /etc/sudoers.d/.
1218

13-
## Design
14-
The top level `/etc/sudoers` file is kept as light as possible. It includes all defaults and aliases. All sudoer specifications will each be placed in their own file within the `/etc/sudoers.d/` directory. When it comes to sudoer specifications this role favors explicit over implicit configuration. That means you must set each of these properties for a specification:
19+
All sudoer specifications will each be placed in their own file within the `/etc/sudoers.d/` directory. A specification consists of the following:
1520
- `name`: the name of the specification (file name in `/etc/sudoers.d/`)
1621
- `users`: user list or user alias
1722
- `hosts`: host list or host alias
1823
- `operators`: operator list or runas alias
19-
- `commands`: command list or command alias
24+
- `commands`: command list or
2025

2126
The following properties are optional:
2227
- `tags`: list of tags (ex: NOPASSWD)
28+
- `comment`: A comment you'd like to add to your spec for clarity
29+
30+
Valid sudoer tags are: NOPASSWD, PASSWD, NOEXEC, EXEC, SETENV, NOSETENV, LOG_INPUT, NOLOG_INPUT, LOG_OUTPUT and NOLOG_OUTPUT.
31+
32+
User/Group specific defaults can be added to the defaults list by a preceding ':' followed by the user/group whitespace then the option. For example:
33+
34+
---
35+
sudoer_defaults:
36+
- :MONITOR_USER !logfile
37+
38+
39+
This will generate a line:
40+
Defaults:MONITOR_USER !logfile
41+
2342

2443
## Example Playbook
25-
```yaml
26-
# a super contrived example
27-
- hosts: all
28-
29-
vars:
30-
sudoer_aliases:
31-
user:
32-
- name: ADMINS
33-
users:
34-
- %admin
35-
runas:
36-
- name: ROOT
37-
users:
38-
- '#0'
39-
host:
40-
- name: SERVERS
41-
hosts:
42-
- 192.168.0.1
43-
- 192.168.0.2
44-
command:
45-
- name: ADMIN_CMNDS
46-
commands:
47-
- /usr/sbin/passwd
48-
- /usr/sbin/useradd
49-
- /usr/sbin/userdel
50-
- /usr/sbin/usermod
51-
- /usr/sbin/visudo
52-
sudoer_specs:
53-
- name: administrators
54-
users: ADMIN
55-
hosts: SERVERS
56-
operators: ROOT
57-
tags:
58-
- NOPASSWD
59-
commands: ADMIN_CMNDS
60-
defaults:
61-
- '!requiretty'
62-
63-
roles:
64-
- role: sudoers
65-
```
44+
- hosts: all
45+
vars:
46+
sudoer_aliases:
47+
user:
48+
- name: ADMINS
49+
comment: Group of admin users
50+
users:
51+
- %admin
52+
runas:
53+
- name: ROOT
54+
comment: Root stuff
55+
users:
56+
- '#0'
57+
host:
58+
- name: SERVERS
59+
comment: XYZ servers
60+
hosts:
61+
- 192.168.0.1
62+
- 192.168.0.2
63+
command:
64+
- name: ADMIN_CMNDS
65+
comment: Stuff admins need
66+
commands:
67+
- /usr/sbin/passwd
68+
- /usr/sbin/useradd
69+
- /usr/sbin/userdel
70+
- /usr/sbin/usermod
71+
- /usr/sbin/visudo
72+
sudoer_specs:
73+
- name: administrators
74+
comment: Stuff for admins
75+
users: ADMIN
76+
hosts: SERVERS
77+
operators: ROOT
78+
tags:
79+
- NOPASSWD
80+
commands: ADMIN_CMNDS
81+
82+
roles:
83+
- ahuffman.sudoers
84+
85+
86+
## Defaults:
87+
sudoer_aliases: {}
88+
sudoer_specs: []
89+
sudoer_defaults:
90+
* # - requiretty (disabled, just uncomment if required)
91+
* - "!visiblepw"
92+
* - always_set_home
93+
* - env_reset
94+
* - env_keep:
95+
- COLORS
96+
- DISPLAY
97+
- HOSTNAME
98+
- HISTSIZE
99+
- INPUTRC
100+
- KDEDIR
101+
- LS_COLORS
102+
- MAIL
103+
- PS1
104+
- PS2
105+
- QTDIR
106+
- USERNAME
107+
- LANG
108+
- LC_ADDRESS
109+
- LC_CTYPE
110+
- LC_COLLATE
111+
- LC_IDENTIFICATION
112+
- LC_MEASUREMENT
113+
- LC_MESSAGES
114+
- LC_MONETARY
115+
- LC_NAME
116+
- LC_NUMERIC
117+
- LC_PAPER
118+
- LC_TELEPHONE
119+
- LC_TIME
120+
- LC_ALL
121+
- LANGUAGE
122+
- LINGUAS
123+
- _XKB_CHARSET
124+
- XAUTHORITY
125+
* - secure_path: /sbin:/bin:/usr/sbin:/usr/bin
126+
* sudoer_separate_specs: True
66127

67128
## Requirements
68129
The host operating system must be a member of one of the following OS families:
@@ -79,27 +140,30 @@ None
79140
# host alias
80141
name: string
81142
hosts: string|[hostnames]
143+
comment: string #procedes the alias with a comment
82144

83145
# user alias
84146
name: string
85147
users: string|[username|%group]
148+
comment: string #procedes the alias with a comment
86149

87150
# runas alias
88151
name: string
89152
users: string|[username|%group|#uid]
153+
comment: string #procedes the alias with a comment
90154

91155
# cmnd alias
92156
name: string
93157
commands: string|[string]
158+
comment: string #procedes the alias with a comment
94159

95160
# sudoer specification
96161
name: string
97162
users: string|[string]
98163
hosts: string|[string]
99164
operators: string|[string]
100165
tags: string|[string]
101-
defaults: string|[string]
102-
```
166+
comment: string #procedes the alias with a comment
103167

104168
## Role Variables
105169
- `sudoer_aliases`: a dictionary that specifies which aliases to configure
@@ -113,6 +177,14 @@ defaults: string|[string]
113177
- `string`
114178
- `string: string`
115179
- `string: [string]`
180+
```
116181
117182
## License
118183
[MIT](LICENSE)
184+
185+
## Author Information
186+
### Original Author:
187+
[Tyler Cross](https://github.com/wtcross)
188+
189+
### Improved By:
190+
[Andrew J. Huffman](https://github.com/ahuffman)

‎defaults/main.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
sudoer_aliases: {}
33
sudoer_specs: []
44
sudoer_defaults:
5-
- requiretty
5+
# - requiretty
66
- "!visiblepw"
77
- always_set_home
88
- env_reset
@@ -38,3 +38,4 @@ sudoer_defaults:
3838
- _XKB_CHARSET
3939
- XAUTHORITY
4040
- secure_path: /sbin:/bin:/usr/sbin:/usr/bin
41+
sudoer_separate_specs: True

‎meta/main.yml

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
galaxy_info:
2-
author: Tyler Cross
3-
description:
4-
issue_tracker_url: https://github.com/wtcross/ansible-sudoers/issues
2+
author:
3+
- Tyler Cross
4+
- Andrew J. Huffman
5+
description: Controls the configuration of the sudoers file and /etc/sudoers.d/ files
56
license: MIT
67
min_ansible_version: 1.2
7-
github_branch: master
8+
#github_branch: master
89
platforms:
910
- name: EL
1011
versions:
@@ -31,6 +32,7 @@ galaxy_info:
3132
galaxy_tags:
3233
- sudo
3334
- sudoers
35+
- sudoers.d
3436
- admin
3537
- system
3638

‎tasks/main.yml

+38-18
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@
44
name: sudo
55
update_cache: yes
66
state: present
7-
when: "{{ ansible_os_family == 'RedHat' }}"
7+
when: ansible_os_family == 'RedHat'
88

99
- name: Ensure sudo is installed for Debian family OSs
1010
apt:
1111
name: sudo
1212
update_cache: yes
1313
state: present
14-
when: "{{ ansible_os_family == 'Debian' }}"
14+
when: ansible_os_family == 'Debian'
1515

1616
- name: Ensure sudo is installed for SUSE family OSs
1717
zypper:
1818
name: sudo
1919
state: present
20-
when: "{{ ansible_os_family == 'SUSE' }}"
20+
when: ansible_os_family == 'SUSE'
2121

2222
- name: Ensure the sudoers.d directory is created
2323
file:
@@ -27,42 +27,62 @@
2727
mode: 0755
2828
state: directory
2929

30-
- name: Find all existing sudoer specs
30+
- name: Ensure the sudoers file is valid and up to date (separate specs)
31+
template:
32+
src: sudoers_nospec.j2
33+
dest: /etc/sudoers
34+
owner: root
35+
group: root
36+
mode: 0440
37+
validate: visudo -cf %s
38+
when: sudoer_separate_specs == True
39+
40+
- name: Ensure the sudoers file is valid and up to date (specs all in one)
41+
template:
42+
src: sudoers_plus_spec.j2
43+
dest: /etc/sudoers
44+
owner: root
45+
group: root
46+
mode: 0440
47+
validate: visudo -cf %s
48+
when: sudoer_separate_specs == False
49+
50+
- name: Find all existing separate sudoer specs
3151
shell: find /etc/sudoers.d -type f -printf "%f\n"
3252
register: existing_sudoer_spec_list
3353
changed_when: False
3454

35-
- name: Get a list of all existing sudoer specs
55+
- name: Get a list of all existing separate sudoer specs
3656
set_fact:
3757
existing_sudoer_specs: "{{ existing_sudoer_spec_list.stdout_lines }}"
3858
changed_when: False
3959

40-
- name: Get a list of all authorized sudoer spec names
60+
- name: Get a list of all authorized separate sudoer spec names
4161
set_fact:
4262
authorized_sudoer_specs: "{{ sudoer_specs | map(attribute='name') | list }}"
4363
changed_when: False
4464

45-
- name: Ensure all authorized sudoer specs are properly configured
65+
- name: Ensure all authorized separate sudoer specs are properly configured
4666
template:
4767
src: sudoer_spec.j2
4868
dest: "/etc/sudoers.d/{{ item.name }}"
4969
owner: root
5070
group: root
5171
mode: 0440
5272
validate: visudo -cf %s
53-
with_items: "{{ sudoer_specs }}"
54-
55-
- name: Ensure the sudoers file is valid and up to date
56-
template:
57-
src: sudoers.j2
58-
dest: /etc/sudoers
59-
owner: root
60-
group: root
61-
mode: 0440
62-
validate: visudo -cf %s
73+
with_items: '{{ sudoer_specs }}'
74+
when: sudoer_separate_specs == True
6375

64-
- name: Remove sudoers that are not authorized
76+
- name: Remove separate sudoer specs that are not authorized
6577
file:
6678
path: "/etc/sudoers.d/{{ item }}"
6779
state: absent
6880
with_items: "{{ existing_sudoer_specs | difference(authorized_sudoer_specs) }}"
81+
when: sudoer_separate_specs == True
82+
83+
- name: Remove separate sudoer specs if not using separate specs
84+
file:
85+
path: "/etc/sudoers.d/{{ item }}"
86+
state: absent
87+
with_items: "{{ existing_sudoer_specs }}"
88+
when: sudoer_separate_specs == False

‎templates/sudoer_spec.j2

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
{% if item.defaults is defined and item.defaults %}
2-
Defaults:{{ item.users | to_list | join(',') }} {{ item.defaults | to_list | join(',') }}
1+
#{{ ansible_managed }}
2+
3+
{% if item.comment is defined and item.comment %}
4+
#{{ item.comment }}
35
{% endif %}
46
{{ item.users | to_list | join(',') }} {{ item.hosts | to_list | join(',') }}={% if item.operators is defined and item.operators %}({{ item.operators | to_list | join(',') }}){% endif %} {% if item.tags is defined and item.tags %}{{ item.tags | to_list | join(':') }}: {% endif %}{{ item.commands | to_list | join(',') }}

‎templates/sudoers_nospec.j2

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#{{ ansible_managed }}
2+
3+
{% for default in sudoer_defaults %}
4+
{% if default is mapping %}
5+
{% for name, values in default.iteritems() %}
6+
{% for items in values | to_list | slice(6) %}
7+
{% if items %}
8+
Defaults {{ name }} {% if not loop.first %}+{% endif %}= "{{ items | to_list | join(' ') }}"
9+
{% endif -%}
10+
{% endfor %}
11+
{% endfor %}
12+
{% elif default|first == ':' %}
13+
Defaults{{ default }}
14+
{% else %}
15+
Defaults {{ default }}
16+
{% endif %}
17+
{% endfor %}
18+
19+
{% if sudoer_aliases.user is defined and sudoer_aliases.user %}
20+
#User Aliases
21+
{% for alias in sudoer_aliases.user %}
22+
{% if alias.comment is defined and alias.comment %}
23+
#**{{ alias.comment }}
24+
{% endif %}
25+
User_Alias {{ alias.name }} = {{ alias.users | join(',') }}
26+
{% endfor %}
27+
28+
{% endif %}
29+
{% if sudoer_aliases.runas is defined and sudoer_aliases.runas %}
30+
#Runas Aliases
31+
{% for alias in sudoer_aliases.runas %}
32+
{% if alias.comment is defined and alias.comment %}
33+
#**{{ alias.comment }}
34+
{% endif %}
35+
Runas_Alias {{ alias.name }} = {{ alias.users | join(',') }}
36+
{% endfor %}
37+
38+
{% endif %}
39+
{% if sudoer_aliases.host is defined and sudoer_aliases.host %}
40+
#Host Aliases
41+
{% for alias in sudoer_aliases.host %}
42+
{% if alias.comment is defined and alias.comment %}
43+
#**{{ alias.comment }}
44+
{% endif %}
45+
Host_Alias {{ alias.name }} = {{ alias.hosts | join(',') }}
46+
{% endfor %}
47+
48+
{% endif %}
49+
{% if sudoer_aliases.command is defined and sudoer_aliases.command %}
50+
#Command Aliases
51+
{% for alias in sudoer_aliases.command %}
52+
{% if alias.comment is defined and alias.comment %}
53+
#**{{ alias.comment }}
54+
{% endif %}
55+
Cmnd_Alias {{ alias.name }} = {{ alias.commands | join(',') }}
56+
{% endfor %}
57+
58+
{% endif %}
59+
60+
## Allow root to run any commands anywhere
61+
root ALL=(ALL) ALL
62+
63+
# Include all sudoer specifications
64+
#includedir /etc/sudoers.d

‎templates/sudoers_plus_spec.j2

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#{{ ansible_managed }}
2+
3+
{% for default in sudoer_defaults %}
4+
{% if default is mapping %}
5+
{% for name, values in default.iteritems() %}
6+
{% for items in values | to_list | slice(6) %}
7+
{% if items %}
8+
Defaults {{ name }} {% if not loop.first %}+{% endif %}= "{{ items | to_list | join(' ') }}"
9+
{% endif -%}
10+
{% endfor %}
11+
{% endfor %}
12+
{% elif default|first == ':' %}
13+
Defaults{{ default }}
14+
{% else %}
15+
Defaults {{ default }}
16+
{% endif %}
17+
{% endfor %}
18+
19+
{% if sudoer_aliases.user is defined and sudoer_aliases.user %}
20+
#User Aliases
21+
{% for alias in sudoer_aliases.user %}
22+
{% if alias.comment is defined %}
23+
#**{{ alias.comment }}
24+
{% endif %}
25+
User_Alias {{ alias.name }} = {{ alias.users | join(',') }}
26+
{% endfor %}
27+
28+
{% endif %}
29+
{% if sudoer_aliases.runas is defined and sudoer_aliases.runas %}
30+
#Runas Aliases
31+
{% for alias in sudoer_aliases.runas %}
32+
{% if alias.comment is defined %}
33+
#**{{ alias.comment }}
34+
{% endif %}
35+
Runas_Alias {{ alias.name }} = {{ alias.users | join(',') }}
36+
{% endfor %}
37+
38+
{% endif %}
39+
{% if sudoer_aliases.host is defined and sudoer_aliases.host %}
40+
#Host Aliases
41+
{% for alias in sudoer_aliases.host %}
42+
{% if alias.comment is defined %}
43+
#**{{ alias.comment }}
44+
{% endif %}
45+
Host_Alias {{ alias.name }} = {{ alias.hosts | join(',') }}
46+
{% endfor %}
47+
48+
{% endif %}
49+
{% if sudoer_aliases.command is defined and sudoer_aliases.command %}
50+
#Command Aliases
51+
{% for alias in sudoer_aliases.command %}
52+
{% if alias.comment is defined %}
53+
#**{{ alias.comment }}
54+
{% endif %}
55+
Cmnd_Alias {{ alias.name }} = {{ alias.commands | join(',') }}
56+
{% endfor %}
57+
58+
{% endif %}
59+
60+
## Allow root to run any commands anywhere
61+
root ALL=(ALL) ALL
62+
63+
{% if sudoer_specs %}
64+
#Sudoer specifications
65+
{% for spec in sudoer_specs %}
66+
{% if spec.comment is defined %}
67+
#**{{ spec.comment }}
68+
{% endif %}
69+
{{ spec.users | to_list | join(',') }} {{ spec.hosts | to_list | join(',') }}={% if spec.operators is defined and spec.operators %}({{ spec.operators | to_list | join(',') }}){% endif %} {% if spec.tags is defined and spec.tags %}{{ spec.tags | to_list | join(':') }}: {% endif %}{{ spec.commands | to_list | join(',') }}
70+
{% endfor %}
71+
{% endif %}

0 commit comments

Comments
 (0)
Please sign in to comment.