diff --git a/infrastructure/ansible/README.md b/infrastructure/ansible/README.md index 8383910b..f2f8f091 100644 --- a/infrastructure/ansible/README.md +++ b/infrastructure/ansible/README.md @@ -12,17 +12,19 @@ ## Infrastructure and Servers -Please see the `/inventories/{ENVIRONMENT}/hosts` file for IP details of the designated services. Set these to the server that you created via terraform. +Please see the `/inventories/{ENVIRONMENT}/hosts` file for IP details of the designated services. Set these to the server's domain name/s that you created via terraform. ## Ansible ### SSH Access -To authenticate yourself on the remote servers your ssh key will need to be added to the `sudoers` var in the _/inventories/{ENVIRONMENT}/group_vars/all.yml_. +To authenticate users and to allow them to have sudo access on the remote servers your ssh key will need to be added to the `sudoers` var in the _/inventories/{ENVIRONMENT}/group_vars/all.yml_. -To have docker access you need to add your ssh key to the `docker_users` var in the _/inventories/{ENVIRONMENT}/group_vars/all.yml_. +To authenticate users and to allow them to have docker access you need to add your ssh key to the `docker_users` var in the _/inventories/{ENVIRONMENT}/group_vars/all.yml_. -An authorised user will need to run the `provision_servers.yml` playbook to add your ssh key to the servers. +Ensure that you remove all users that you don't want to have access. The default development files have a bunch of Jembi staff's user credentials. + +An pre-authorised user will need to run the `provision_servers.yml` playbook the first time to add your ssh key to the servers. ### Configuration @@ -32,6 +34,8 @@ Before running the ansible script add the server to your known hosts file else a ssh-keyscan -H >> ~/.ssh/known_hosts ``` +Next, ensure that you configure the `firewall_subnet_restriction` property of the _/inventories/{ENVIRONMENT}/group_vars/all.yml_ file if you are setting up multiple nodes in a Docker swarm. Docker swarm nodes need to communicate with each other, this property adds a restriction on the software firewall on each node (UFW) which only allow that communication to happen on the particular subset specified by this property. + To run a playbook you should do: ```bash diff --git a/infrastructure/ansible/playbooks/provision_servers.yml b/infrastructure/ansible/playbooks/provision_servers.yml index 070a93a2..9d6692e5 100644 --- a/infrastructure/ansible/playbooks/provision_servers.yml +++ b/infrastructure/ansible/playbooks/provision_servers.yml @@ -4,3 +4,4 @@ - common - docker - ufw + - auditd diff --git a/infrastructure/ansible/roles/auditd/tasks/main.yml b/infrastructure/ansible/roles/auditd/tasks/main.yml new file mode 100644 index 00000000..45b130f3 --- /dev/null +++ b/infrastructure/ansible/roles/auditd/tasks/main.yml @@ -0,0 +1,22 @@ +--- +- name: "install auditd" + apt: + name: auditd + state: latest + +- name: "fetch best practice Auditd config" + get_url: + url: https://raw.githubusercontent.com/Neo23x0/auditd/master/audit.rules + dest: /etc/audit/rules.d/audit.rules + +- name: Ensure name_format is set to HOSTNAME + lineinfile: + path: /etc/audit/auditd.conf + regexp: '^name_format\s*=' + line: "name_format = HOSTNAME" + state: present + +- name: "restart auditd service" + ansible.builtin.service: + name: auditd + state: restarted diff --git a/infrastructure/ansible/roles/docker/handlers/main.yml b/infrastructure/ansible/roles/docker/handlers/main.yml index 5b9fafc1..02dfb759 100644 --- a/infrastructure/ansible/roles/docker/handlers/main.yml +++ b/infrastructure/ansible/roles/docker/handlers/main.yml @@ -1,4 +1,4 @@ --- -- include: reload_ufw.yml -- include: reload_docker.yml -- include: restart_docker.yml +- import_tasks: reload_ufw.yml +- import_tasks: reload_docker.yml +- import_tasks: restart_docker.yml diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml index 3431a17b..0683cc74 100644 --- a/monitoring/docker-compose.yml +++ b/monitoring/docker-compose.yml @@ -64,6 +64,8 @@ services: source: openhim_transactions_dashboard.json - target: /etc/grafana/provisioning/dashboards/containers/logging-universal-dashboard_rev1.json source: logging-universal-dashboard_rev1.json + - target: /etc/grafana/provisioning/dashboards/security/auditlogs.json + source: auditlogs.json networks: keycloak: reverse-proxy: @@ -228,6 +230,11 @@ configs: name: logging-universal-dashboard_rev1.json-${logging_universal_dashboard_rev1_json_DIGEST:?err} labels: name: grafana + auditlogs.json: + file: ./grafana/dashboards/security/auditlogs.json + name: auditlogs.json-${auditlogs_json_DIGEST:?err} + labels: + name: grafana prometheus.yml: file: ./prometheus/prometheus.yml name: prometheus.yml-${prometheus_yml_DIGEST:?err} diff --git a/monitoring/grafana/dashboards/security/auditlogs.json b/monitoring/grafana/dashboards/security/auditlogs.json new file mode 100644 index 00000000..97f0f6db --- /dev/null +++ b/monitoring/grafana/dashboards/security/auditlogs.json @@ -0,0 +1,277 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 6, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "# Notes\n\n* Some `proctitles` are hex encoded due to the posibility of special chars. Use a hex to ascii decoder to view these.\n* Multiple lines might refer to the same event, in that case the audit identifier (i.e. `msg=audit(...:...)`) will be the same. Use the find input to easily see all line for an event by searching for this audit identifier.", + "mode": "markdown" + }, + "pluginVersion": "9.2.3", + "title": "Notes", + "type": "text" + }, + { + "datasource": { + "type": "loki", + "uid": "P00201832B18B88C3" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 4 + }, + "id": 3, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": true, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "9.2.3", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "P00201832B18B88C3" + }, + "editorMode": "builder", + "expr": "sum(count_over_time({label=~\"T1219.*|recon|.*susp.*\", node=~\"$node\"} |= `$query` [$__interval]))", + "queryType": "range", + "refId": "A" + } + ], + "title": "Suspicious activity", + "type": "heatmap" + }, + { + "datasource": { + "type": "loki", + "uid": "P00201832B18B88C3" + }, + "description": "Filter for security auditlogs that that are potentially suspicious.", + "gridPos": { + "h": 17, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 1, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "P00201832B18B88C3" + }, + "editorMode": "builder", + "expr": "{job=\"auditlogs\", label=~\"T1219.*|recon|.*susp.*\", node=~\"$node\"} |= `$query`", + "key": "Q-9181c263-cf75-42fe-bf50-036eeff7207a-0", + "queryType": "range", + "refId": "A" + } + ], + "title": "Suspicious activity", + "type": "logs" + }, + { + "datasource": { + "type": "loki", + "uid": "P00201832B18B88C3" + }, + "description": "All captured logs from auditd", + "gridPos": { + "h": 17, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 4, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Descending", + "wrapLogMessage": true + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "P00201832B18B88C3" + }, + "editorMode": "builder", + "expr": "{job=\"auditlogs\", node=~\"$node\"} |= `$query`", + "key": "Q-9181c263-cf75-42fe-bf50-036eeff7207a-0", + "queryType": "range", + "refId": "A" + } + ], + "title": "All audit logs", + "type": "logs" + } + ], + "refresh": false, + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "", + "value": "" + }, + "hide": 0, + "label": "Find", + "name": "query", + "options": [ + { + "selected": true, + "text": "", + "value": "" + } + ], + "query": "", + "skipUrlSync": false, + "type": "textbox" + }, + { + "current": { + "selected": true, + "text": ["All"], + "value": ["$__all"] + }, + "datasource": { + "type": "loki", + "uid": "P00201832B18B88C3" + }, + "definition": "", + "hide": 0, + "includeAll": true, + "label": "Hostname", + "multi": true, + "name": "node", + "options": [], + "query": { + "label": "node", + "refId": "LokiVariableQueryEditor-VariableQuery", + "stream": "", + "type": 1 + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Audit logs", + "uid": "1KG6epL4z", + "version": 1, + "weekStart": "" +} diff --git a/monitoring/promtail/promtail-config.yml b/monitoring/promtail/promtail-config.yml index 1cce8fe2..29b4904e 100644 --- a/monitoring/promtail/promtail-config.yml +++ b/monitoring/promtail/promtail-config.yml @@ -6,42 +6,64 @@ positions: filename: /tmp/positions.yaml clients: -- url: http://loki:3100/loki/api/v1/push + - url: http://loki:3100/loki/api/v1/push scrape_configs: + - job_name: containers + static_configs: + - targets: + - localhost + labels: + job: containerlogs + __path__: /host/containers/*/*log -- job_name: containers - static_configs: - - targets: - - localhost - labels: - job: containerlogs - __path__: /host/containers/*/*log + pipeline_stages: + - json: + expressions: + log: log + stream: stream + time: time + tag: attrs.tag + stack_name: attrs."com.docker.stack.namespace" + swarm_service_name: attrs."com.docker.swarm.service.name" + swarm_task_name: attrs."com.docker.swarm.task.name" + swarm_node_id: attrs."com.docker.swarm.node.id" + - regex: + expression: "^/host/containers/(?P.{12}).+/.+-json.log$" + source: filename + - timestamp: + format: RFC3339Nano + source: time + - labels: + stream: + container_id: + tag: + stack_name: + swarm_service_name: + swarm_task_name: + swarm_node_id: + - output: + source: log - pipeline_stages: - - json: - expressions: - log: log - stream: stream - time: time - tag: attrs.tag - stack_name: attrs."com.docker.stack.namespace" - swarm_service_name: attrs."com.docker.swarm.service.name" - swarm_task_name: attrs."com.docker.swarm.task.name" - swarm_node_id: attrs."com.docker.swarm.node.id" - - regex: - expression: "^/host/containers/(?P.{12}).+/.+-json.log$" - source: filename - - timestamp: - format: RFC3339Nano - source: time - - labels: - stream: - container_id: - tag: - stack_name: - swarm_service_name: - swarm_task_name: - swarm_node_id: - - output: - source: log + - job_name: auditd + static_configs: + - targets: + - localhost # Auditd logs are collected from the local host + labels: + job: auditlogs + __path__: /var/log/audit/audit.log + + pipeline_stages: + - regex: + expression: 'type=(?P\S*)' + - regex: + expression: 'key="(?P