diff --git a/roles/go_audit/defaults/main.yml b/roles/go_audit/defaults/main.yml new file mode 100644 index 00000000..95c789e5 --- /dev/null +++ b/roles/go_audit/defaults/main.yml @@ -0,0 +1,20 @@ +--- +# Directory in which to store the go-audit binary. +go_audit_bindir: /usr/local/bin + +# true: Build go-audit from source on the target host. +# false: Use the pre-built binaries located in the role's files/ directory. +go_audit_build: false + +# Directory to use when building go-audit. All source code and other temporary +# files will be stored here. The directory is *not* explicitly removed. +go_audit_build_dir: /tmp/go-audit-build + +# Golang environment used for building go-audit from source. +go_audit_go_env: + GOROOT: "{{ go_audit_build_dir }}/go" + GOPATH: "{{ go_audit_build_dir }}/go-work" + PATH: + "{{ go_audit_build_dir }}/go/bin:\ + {{ go_audit_build_dir }}/go-work/bin:\ + {{ ansible_env.PATH }}" diff --git a/roles/go_audit/files/go-audit b/roles/go_audit/files/go-audit new file mode 100755 index 00000000..4ee98f50 Binary files /dev/null and b/roles/go_audit/files/go-audit differ diff --git a/roles/go_audit/handlers/main.yml b/roles/go_audit/handlers/main.yml new file mode 100644 index 00000000..0e216370 --- /dev/null +++ b/roles/go_audit/handlers/main.yml @@ -0,0 +1,11 @@ +--- +- name: audit configuration changed + debug: + msg: > + The audit configuration changed. + For the settings to be applied a system reboot may be required. + +- name: Update grub2 configuration + command: update-grub + register: go_audit_update_grub_result + failed_when: "'error' in go_audit_update_grub_result.stderr" diff --git a/roles/go_audit/tasks/build.yml b/roles/go_audit/tasks/build.yml new file mode 100644 index 00000000..f133de46 --- /dev/null +++ b/roles/go_audit/tasks/build.yml @@ -0,0 +1,58 @@ +--- +- name: Ensure that the build dependencies are installed + package: + name: "{{ item }}" + state: present + with_items: + - git + - make + +- name: Ensure that the required build directories exist + file: + path: "{{ item }}" + state: directory + with_items: + - "{{ go_audit_build_dir }}" + - "{{ go_audit_go_env.GOROOT }}" + - "{{ go_audit_go_env.GOPATH }}/src/github.com/slackhq/go-audit" + +- name: "Download the Go distribution (version: {{ go_audit_go_version }})" + get_url: + url: "{{ go_audit_go_download_url }}" + dest: "{{ go_audit_build_dir }}/{{ go_audit_go_filename }}" + checksum: "sha256:{{ go_audit_go_checksum }}" + +- name: "Download go-audit (commit: {{ go_audit_commit }})" + get_url: + url: "{{ go_audit_download_url }}" + dest: "{{ go_audit_build_dir }}/{{ go_audit_filename }}" + checksum: "sha256:{{ go_audit_checksum }}" + +- name: Extract the Go distribution + unarchive: + src: "{{ go_audit_build_dir }}/{{ go_audit_go_filename }}" + dest: "{{ go_audit_go_env.GOROOT }}" + extra_opts: "--strip-components=1" + remote_src: true + creates: "{{ go_audit_go_env.GOROOT }}/LICENSE" + +- name: Extract go-audit + unarchive: + src: "{{ go_audit_build_dir }}/{{ go_audit_filename }}" + dest: "{{ go_audit_go_env.GOPATH }}/src/github.com/slackhq/go-audit" + extra_opts: "--strip-components=1" + remote_src: true + creates: "{{ go_audit_go_env.GOPATH }}/src/github.com/slackhq/go-audit/LICENSE" + +- name: Install govendor + command: go get -u github.com/kardianos/govendor + args: + creates: "{{ go_audit_go_env.GOPATH }}/bin/govendor" + environment: "{{ go_audit_go_env }}" + +- name: Build go-audit + command: make + args: + chdir: "{{ go_audit_go_env.GOPATH }}/src/github.com/slackhq/go-audit" + creates: "go-audit" + environment: "{{ go_audit_go_env }}" diff --git a/roles/go_audit/tasks/install.yml b/roles/go_audit/tasks/install.yml new file mode 100644 index 00000000..3969f464 --- /dev/null +++ b/roles/go_audit/tasks/install.yml @@ -0,0 +1,54 @@ +--- +- name: Install the auditd package (provides auditctl) + package: + name: auditd + state: present + +- name: "Ensure that the {{ go_audit_bindir }} directory exists" + file: + path: "{{ go_audit_bindir }}" + state: directory + +- name: Install the go-audit binary + copy: + src: "{{ go_audit_go_env.GOPATH }}/src/github.com/slackhq/go-audit/go-audit" + dest: "{{ go_audit_bindir }}/go-audit" + owner: root + group: "{{ root_group|default('root') }}" + mode: 0755 + remote_src: true + when: + - go_audit_build|bool + +- name: Install the pre-built go-audit binary + copy: + src: "go-audit" + dest: "{{ go_audit_bindir }}/go-audit" + owner: root + group: "{{ root_group|default('root') }}" + mode: 0755 + remote_src: false + when: + - not go_audit_build|bool + +- name: Calculate the checksum of the go-audit binary + stat: + path: "{{ go_audit_bindir }}/go-audit" + checksum_algorithm: sha256 + register: go_audit_binary_st + +- name: Verify the checksum of the go-audit binary + assert: + that: + - go_audit_binary_st.stat.checksum is defined + - go_audit_binary_st.stat.checksum == go_audit_binary_checksum + +- name: Generate the go-audit configuration file + template: + src: go-audit.yaml.j2 + dest: "{{ config_prefix|default('/') }}etc/go-audit.yaml" + owner: root + group: "{{ root_group|default('root') }}" + mode: 0644 + notify: + - audit configuration changed diff --git a/roles/go_audit/tasks/main.yml b/roles/go_audit/tasks/main.yml new file mode 100644 index 00000000..210f34dd --- /dev/null +++ b/roles/go_audit/tasks/main.yml @@ -0,0 +1,27 @@ +--- +- name: Check distribution and version + fail: + msg: > + {{ ansible_distribution }} version {{ ansible_distribution_version }} + ({{ ansible_machine }}) is not supported. + when: + - ansible_system != "Linux" or + ansible_machine not in ['amd64', 'x86_64'] or + ansible_distribution != "Ubuntu" + +- block: + - name: Build go-audit + include_tasks: build.yml + when: go_audit_build|bool + + - import_tasks: install.yml + + - name: Include tasks for Ubuntu + include_tasks: ubuntu.yml + when: ansible_distribution == 'Ubuntu' + rescue: + - debug: var=fail_hint + tags: always + - name: Fail + fail: + tags: always diff --git a/roles/go_audit/tasks/ubuntu.yml b/roles/go_audit/tasks/ubuntu.yml new file mode 100644 index 00000000..0b1346b1 --- /dev/null +++ b/roles/go_audit/tasks/ubuntu.yml @@ -0,0 +1,42 @@ +--- +# Adding a grub command-line argument via Ansible in a generic, idempotent +# manner is non-trivial. The regex below assumes that a line of the format +# GRUB_CMDLINE_LINUX=".*" exists and inserts audit=1 right before the closing +# quote. The regex fails if no GRUB_CMDLINE_LINUX key exists, the value is not +# enclosed in double quotes, or the value contains escaped double quotes. +- name: Ubuntu | Enable auditing for processes that start prior to go-audit + lineinfile: + dest: /etc/default/grub + regexp: '^(GRUB_CMDLINE_LINUX=(?!.*audit=1)".*)(".*)' + line: '\1 audit=1\2' + backrefs: true + state: present + notify: + - Update grub2 configuration + - audit configuration changed + +- name: Ubuntu | Generate the systemd unit configuration file + template: + src: systemd.go-audit.service.j2 + dest: /etc/systemd/system/go-audit.service + owner: root + group: "{{ root_group|default('root') }}" + mode: 0644 + +- name: Ubuntu | Stop and disable the auditd service + systemd: + name: auditd.service + state: stopped + enabled: false + +- name: Ubuntu | Start and enable the go-audit service + systemd: + name: go-audit.service + state: started + enabled: yes + +- name: Ubuntu | Prevent duplicate audit entries in syslog + systemd: + name: systemd-journald-audit.socket + state: stopped + masked: true diff --git a/roles/go_audit/templates/go-audit.yaml.j2 b/roles/go_audit/templates/go-audit.yaml.j2 new file mode 100644 index 00000000..f2896016 --- /dev/null +++ b/roles/go_audit/templates/go-audit.yaml.j2 @@ -0,0 +1,121 @@ +# go-audit configuration file +# +# For general documentation and specifications related to the Linux Audit +# project see https://github.com/linux-audit/audit-documentation/wiki +# The wiki contains definitions of all audit fields, messages, and ranges. + +output: + syslog: + enabled: true + tag: "go-audit" + + # Configure the type of socket this should be, default is unixgram + # This maps to `network` in golangs net.Dial: https://golang.org/pkg/net/#Dial + network: unixgram + + # Set the remote address to connect to, this can be a path or an ip address + # This maps to `address` in golangs net.Dial: https://golang.org/pkg/net/#Dial + address: /dev/log + + # Sets the facility and severity for all events. See the table below for help + # The default is 132 which maps to local0 | warn + priority: 129 # local0 | emerg + +# Based on CIS_Ubuntu_Linux_18.04_LTS_Benchmark_v1.0.0.pdf +# https://www.cisecurity.org/benchmark/ubuntu_linux/ +rules: + # Ensure events that modify date and time information are collected + - -a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change + - -a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time-change + - -a always,exit -F arch=b64 -S clock_settime -k time-change + - -a always,exit -F arch=b32 -S clock_settime -k time-change + - -w /etc/localtime -p wa -k time-change + + # Ensure events that modify user/group information are collected + - -w /etc/group -p wa -k identity + - -w /etc/passwd -p wa -k identity + - -w /etc/gshadow -p wa -k identity + - -w /etc/shadow -p wa -k identity + - -w /etc/security/opasswd -p wa -k identity + + # Ensure events that modify the system's network environment are collected + - -a always,exit -F arch=b64 -S sethostname -S setdomainname -k system-locale + - -a always,exit -F arch=b32 -S sethostname -S setdomainname -k system-locale + - -w /etc/issue -p wa -k system-locale + - -w /etc/issue.net -p wa -k system-locale + - -w /etc/hosts -p wa -k system-locale + - -w /etc/network -p wa -k system-locale + + # Ensure events that modify the system's Mandatory Access Controls are collected + - -w /etc/apparmor/ -p wa -k MAC-policy + - -w /etc/apparmor.d/ -p wa -k MAC-policy + + # Ensure login and logout events are collected + - -w /var/log/faillog -p wa -k logins + - -w /var/log/lastlog -p wa -k logins + - -w /var/log/tallylog -p wa -k logins + + # Ensure session initiation information is collected + - -w /var/run/utmp -p wa -k session + - -w /var/log/wtmp -p wa -k logins + - -w /var/log/btmp -p wa -k logins + + # Ensure discretionary access control permission modification events are collected + - -a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod + - -a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod + - -a always,exit -F arch=b64 -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod + - -a always,exit -F arch=b32 -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod + - -a always,exit -F arch=b64 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=1000 -F auid!=4294967295 -k perm_mod + - -a always,exit -F arch=b32 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=1000 -F auid!=4294967295 -k perm_mod + + # Ensure unsuccessful unauthorized file access attempts are collected + - -a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access + - -a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access + - -a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access + - -a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access + + # Ensure use of privileged commands is collected + # XXX Incomplete list. Chose some potentially relevant commands. Thoughts? + # For a full list run `find -xdev \( -perm -4000 -o -perm -2000 \) -type f` + - -a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/newgidmap -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/newuidmap -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/pkexec -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/expiry -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/bin/mount -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/bin/fusermount -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/bin/umount -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + - -a always,exit -F path=/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged + + # Ensure successful file system mounts are collected + - -a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=4294967295 -k mounts + - -a always,exit -F arch=b32 -S mount -F auid>=1000 -F auid!=4294967295 -k mounts + + # Ensure file deletion events by users are collected + - -a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete + - -a always,exit -F arch=b32 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete + + # Ensure changes to system administration scope (sudoers) is collected + - -w /etc/sudoers -p wa -k scope + - -w /etc/sudoers.d/ -p wa -k scope + + # Ensure system administrator actions (sudolog) are collected + - -w /var/log/sudo.log -p wa -k actions + + # Ensure kernel module loading and unloading is collected + - -w /sbin/insmod -p x -k modules + - -w /sbin/rmmod -p x -k modules + - -w /sbin/modprobe -p x -k modules + - -a always,exit -F arch=b64 -S init_module -S delete_module -k modules + + # Ensure the audit configuration is immutable + - -e 2 diff --git a/roles/go_audit/templates/systemd.go-audit.service.j2 b/roles/go_audit/templates/systemd.go-audit.service.j2 new file mode 100644 index 00000000..872a3907 --- /dev/null +++ b/roles/go_audit/templates/systemd.go-audit.service.j2 @@ -0,0 +1,11 @@ +[Unit] +Description = go-audit +After=network.target auditd.service +Conflicts = auditd.service + +[Service] +Type = simple +ExecStart = {{ go_audit_bindir }}/go-audit -config {{ config_prefix|default('/') }}etc/go-audit.yaml + +[Install] +WantedBy = multi-user.target diff --git a/roles/go_audit/tests/Vagrantfile b/roles/go_audit/tests/Vagrantfile new file mode 100644 index 00000000..71df3b10 --- /dev/null +++ b/roles/go_audit/tests/Vagrantfile @@ -0,0 +1,17 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + + config.vm.define "ubuntu18.04-amd64", primary: true do |ubuntu| + ubuntu.vm.box = "ubuntu/bionic64" + ubuntu.vm.network "private_network", ip: "192.168.144.64" + ubuntu.vm.provision "shell", inline: <<-SHELL + apt-get update + apt-get install -y --no-install-recommends python + SHELL + end + + config.vm.synced_folder ".", "/vagrant", disabled: true + +end diff --git a/roles/go_audit/tests/inventory b/roles/go_audit/tests/inventory new file mode 100644 index 00000000..6be1a9f8 --- /dev/null +++ b/roles/go_audit/tests/inventory @@ -0,0 +1,7 @@ +all: + hosts: + ubuntu18.04-amd64: + ansible_host: 192.168.144.64 + ansible_user: vagrant + ansible_ssh_private_key_file: + .vagrant/machines/ubuntu18.04-amd64/virtualbox/private_key diff --git a/roles/go_audit/tests/roles/go_audit b/roles/go_audit/tests/roles/go_audit new file mode 120000 index 00000000..6581736d --- /dev/null +++ b/roles/go_audit/tests/roles/go_audit @@ -0,0 +1 @@ +../../ \ No newline at end of file diff --git a/roles/go_audit/tests/test.yml b/roles/go_audit/tests/test.yml new file mode 100644 index 00000000..d56c0f97 --- /dev/null +++ b/roles/go_audit/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: all + become: yes + roles: + - go_audit diff --git a/roles/go_audit/vars/main.yml b/roles/go_audit/vars/main.yml new file mode 100644 index 00000000..f32c2219 --- /dev/null +++ b/roles/go_audit/vars/main.yml @@ -0,0 +1,18 @@ +--- +go_audit_go_version: 1.11.5 +go_audit_go_filename: "go{{ go_audit_go_version }}.linux-amd64.tar.gz" +go_audit_go_download_url: "https://storage.googleapis.com/golang/{{ go_audit_go_filename }}" +# SHA256 checksums copied from https://golang.org/dl/ +go_audit_go_checksum: ff54aafedff961eb94792487e827515da683d61a5f9482f668008832631e5d25 + +# go-audit is not versioned, therefore a commit hash is used instead. +go_audit_commit: daf7385d7bea16bb1b51fbceeba07e7f3ad78f24 +go_audit_filename: "{{ go_audit_commit }}.tar.gz" +go_audit_download_url: "https://github.com/slackhq/go-audit/archive/{{ go_audit_filename }}" +# SHA256 checksum manually computed from +# https://github.com/slackhq/go-audit/archive/{{ go_audit_commit }}.tar.gz +go_audit_checksum: 7327796309fbd08086e28f69b4091b7d2f977c4d1384becfe3bc256ccc6c053b + +# SHA256 checksums manually computed from the binaries located in this role's +# files/ directory. The binaries were build via this role (go_audit_build: yes). +go_audit_binary_checksum: 223c904f06dcb2f7205ec8b4e5509d88bd9987bc1d64adb7e59ea7fc7232bbae