mirror of
https://github.com/trailofbits/algo.git
synced 2025-09-06 12:03:38 +02:00
Merge master security enhancements into PKI refactor
This merge preserves all critical security enhancements from master while
using the modern Ansible crypto modules approach:
Security features preserved:
- Name constraints to restrict CA certificate scope
- Extended Key Usage (EKU) restrictions for server vs client certificates
- Subject Alternative Name (SAN) requirements for certificate validation
- Password-protected CA private keys
- Certificate Revocation List (CRL) generation
- Proper file permissions and directory structure
The refactored approach eliminates shell commands and uses Ansible's
community.crypto modules for better security and maintainability.
🚨 Security-critical merge - all defensive measures retained
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
commit
b9cb08a980
9 changed files with 233 additions and 11 deletions
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
- name: daemon reload
|
- name: daemon-reload
|
||||||
systemd:
|
systemd:
|
||||||
daemon_reload: true
|
daemon_reload: true
|
||||||
|
|
||||||
|
|
|
@ -62,3 +62,38 @@
|
||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
notify:
|
notify:
|
||||||
- restart dnscrypt-proxy
|
- restart dnscrypt-proxy
|
||||||
|
|
||||||
|
- name: Ubuntu | Apply systemd security hardening for dnscrypt-proxy
|
||||||
|
copy:
|
||||||
|
dest: /etc/systemd/system/dnscrypt-proxy.service.d/90-security-hardening.conf
|
||||||
|
content: |
|
||||||
|
# Algo VPN systemd security hardening for dnscrypt-proxy
|
||||||
|
# Additional hardening on top of comprehensive AppArmor
|
||||||
|
[Service]
|
||||||
|
# Privilege restrictions
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
|
||||||
|
# Filesystem isolation (complements AppArmor)
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
PrivateDevices=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
|
||||||
|
# Network restrictions
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6
|
||||||
|
|
||||||
|
# Allow access to dnscrypt-proxy cache (AppArmor also controls this)
|
||||||
|
ReadWritePaths=/var/cache/dnscrypt-proxy
|
||||||
|
|
||||||
|
# System call filtering (complements AppArmor restrictions)
|
||||||
|
SystemCallFilter=@system-service @network-io
|
||||||
|
SystemCallFilter=~@debug @mount @swap @reboot @raw-io
|
||||||
|
SystemCallErrorNumber=EPERM
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: 0644
|
||||||
|
notify:
|
||||||
|
- daemon-reload
|
||||||
|
- restart dnscrypt-proxy
|
||||||
|
|
|
@ -11,7 +11,12 @@ algo_ondemand_wifi_exclude: _null
|
||||||
algo_dns_adblocking: false
|
algo_dns_adblocking: false
|
||||||
ipv6_support: false
|
ipv6_support: false
|
||||||
dns_encryption: true
|
dns_encryption: true
|
||||||
|
# Random UUID for CA name constraints - prevents certificate reuse across different Algo deployments
|
||||||
|
# This unique identifier ensures each CA can only issue certificates for its specific server instance
|
||||||
openssl_constraint_random_id: "{{ IP_subject_alt_name | to_uuid }}.algo"
|
openssl_constraint_random_id: "{{ IP_subject_alt_name | to_uuid }}.algo"
|
||||||
|
# Subject Alternative Name (SAN) configuration - CRITICAL for client compatibility
|
||||||
|
# Modern clients (especially macOS/iOS) REQUIRE SAN extension in server certificates
|
||||||
|
# Without SAN, IKEv2 connections will fail with certificate validation errors
|
||||||
subjectAltName_type: "{{ 'DNS' if IP_subject_alt_name|regex_search('[a-z]') else 'IP' }}"
|
subjectAltName_type: "{{ 'DNS' if IP_subject_alt_name|regex_search('[a-z]') else 'IP' }}"
|
||||||
subjectAltName: >-
|
subjectAltName: >-
|
||||||
{{ subjectAltName_type }}:{{ IP_subject_alt_name }}
|
{{ subjectAltName_type }}:{{ IP_subject_alt_name }}
|
||||||
|
@ -21,12 +26,16 @@ nameConstraints: >-
|
||||||
critical,permitted;{{ subjectAltName_type }}:{{ IP_subject_alt_name }}{{- '/255.255.255.255' if subjectAltName_type == 'IP' else '' -}}
|
critical,permitted;{{ subjectAltName_type }}:{{ IP_subject_alt_name }}{{- '/255.255.255.255' if subjectAltName_type == 'IP' else '' -}}
|
||||||
{%- if subjectAltName_type == 'IP' -%}
|
{%- if subjectAltName_type == 'IP' -%}
|
||||||
,permitted;DNS:{{ openssl_constraint_random_id }}
|
,permitted;DNS:{{ openssl_constraint_random_id }}
|
||||||
|
,excluded;DNS:.com,excluded;DNS:.org,excluded;DNS:.net,excluded;DNS:.gov,excluded;DNS:.edu,excluded;DNS:.mil,excluded;DNS:.int
|
||||||
|
,excluded;IP:10.0.0.0/255.0.0.0,excluded;IP:172.16.0.0/255.240.0.0,excluded;IP:192.168.0.0/255.255.0.0
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
,excluded;IP:0.0.0.0/0.0.0.0
|
,excluded;IP:0.0.0.0/0.0.0.0
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
,permitted;email:{{ openssl_constraint_random_id }}
|
,permitted;email:{{ openssl_constraint_random_id }}
|
||||||
|
,excluded;email:.com,excluded;email:.org,excluded;email:.net,excluded;email:.gov,excluded;email:.edu,excluded;email:.mil,excluded;email:.int
|
||||||
{%- if ipv6_support -%}
|
{%- if ipv6_support -%}
|
||||||
,permitted;IP:{{ ansible_default_ipv6['address'] }}/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
,permitted;IP:{{ ansible_default_ipv6['address'] }}/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||||
|
,excluded;IP:fc00:0:0:0:0:0:0:0/fe00:0:0:0:0:0:0:0,excluded;IP:fe80:0:0:0:0:0:0:0/ffc0:0:0:0:0:0:0:0,excluded;IP:2001:db8:0:0:0:0:0:0/ffff:fff8:0:0:0:0:0:0
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
,excluded;IP:0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0
|
,excluded;IP:0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
with_items:
|
with_items:
|
||||||
- "{{ users }}"
|
- "{{ users }}"
|
||||||
|
|
||||||
|
|
||||||
- name: Build the client ipsec secret file
|
- name: Build the client ipsec secret file
|
||||||
template:
|
template:
|
||||||
src: client_ipsec.secrets.j2
|
src: client_ipsec.secrets.j2
|
||||||
|
|
|
@ -13,15 +13,18 @@
|
||||||
dest: "{{ ipsec_pki_path }}/{{ item }}"
|
dest: "{{ ipsec_pki_path }}/{{ item }}"
|
||||||
state: directory
|
state: directory
|
||||||
recurse: true
|
recurse: true
|
||||||
|
mode: "0700"
|
||||||
with_items:
|
with_items:
|
||||||
- certs
|
- certs
|
||||||
- private
|
- private
|
||||||
|
- public
|
||||||
|
|
||||||
- name: Ensure the config directories exist
|
- name: Ensure the config directories exist
|
||||||
file:
|
file:
|
||||||
dest: "{{ ipsec_config_path }}/{{ item }}"
|
dest: "{{ ipsec_config_path }}/{{ item }}"
|
||||||
state: directory
|
state: directory
|
||||||
recurse: true
|
recurse: true
|
||||||
|
mode: "0700"
|
||||||
with_items:
|
with_items:
|
||||||
- apple
|
- apple
|
||||||
- manual
|
- manual
|
||||||
|
@ -34,7 +37,10 @@
|
||||||
curve: secp384r1
|
curve: secp384r1
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
|
|
||||||
- name: Create certificate signing request (CSR) for CA certificate
|
# CRITICAL: Create CA certificate with proper security constraints
|
||||||
|
# Name constraints provide defense-in-depth security by restricting the scope of certificates
|
||||||
|
# this CA can issue, preventing misuse if the CA key is compromised
|
||||||
|
- name: Create certificate signing request (CSR) for CA certificate with security constraints
|
||||||
community.crypto.openssl_csr_pipe:
|
community.crypto.openssl_csr_pipe:
|
||||||
privatekey_path: "{{ ipsec_pki_path }}/private/cakey.pem"
|
privatekey_path: "{{ ipsec_pki_path }}/private/cakey.pem"
|
||||||
privatekey_passphrase: "{{ CA_password }}"
|
privatekey_passphrase: "{{ CA_password }}"
|
||||||
|
@ -42,10 +48,35 @@
|
||||||
use_common_name_for_san: true
|
use_common_name_for_san: true
|
||||||
basic_constraints:
|
basic_constraints:
|
||||||
- 'CA:TRUE'
|
- 'CA:TRUE'
|
||||||
|
- 'pathlen:0'
|
||||||
basic_constraints_critical: true
|
basic_constraints_critical: true
|
||||||
key_usage:
|
key_usage:
|
||||||
- keyCertSign
|
- keyCertSign
|
||||||
|
- cRLSign
|
||||||
key_usage_critical: true
|
key_usage_critical: true
|
||||||
|
# Restrict CA to only sign VPN-related certificates
|
||||||
|
extended_key_usage:
|
||||||
|
- serverAuth
|
||||||
|
- clientAuth
|
||||||
|
- '1.3.6.1.5.5.7.3.17' # IPsec End Entity
|
||||||
|
extended_key_usage_critical: true
|
||||||
|
# Name constraints to restrict certificate scope
|
||||||
|
name_constraints_permitted:
|
||||||
|
- "{{ subjectAltName_type }}:{{ IP_subject_alt_name }}{{ '/255.255.255.255' if subjectAltName_type == 'IP' else '' }}"
|
||||||
|
- "DNS:{{ openssl_constraint_random_id }}"
|
||||||
|
- "email:{{ openssl_constraint_random_id }}"
|
||||||
|
name_constraints_excluded:
|
||||||
|
- "DNS:.com"
|
||||||
|
- "DNS:.org"
|
||||||
|
- "DNS:.net"
|
||||||
|
- "DNS:.gov"
|
||||||
|
- "DNS:.edu"
|
||||||
|
- "DNS:.mil"
|
||||||
|
- "DNS:.int"
|
||||||
|
- "IP:10.0.0.0/255.0.0.0"
|
||||||
|
- "IP:172.16.0.0/255.240.0.0"
|
||||||
|
- "IP:192.168.0.0/255.255.0.0"
|
||||||
|
name_constraints_critical: true
|
||||||
register: ca_csr
|
register: ca_csr
|
||||||
|
|
||||||
- name: Create self-signed CA certificate from CSR
|
- name: Create self-signed CA certificate from CSR
|
||||||
|
@ -55,8 +86,14 @@
|
||||||
privatekey_path: "{{ ipsec_pki_path }}/private/cakey.pem"
|
privatekey_path: "{{ ipsec_pki_path }}/private/cakey.pem"
|
||||||
privatekey_passphrase: "{{ CA_password }}"
|
privatekey_passphrase: "{{ CA_password }}"
|
||||||
provider: selfsigned
|
provider: selfsigned
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
- name: Create private keys
|
- name: Copy the CA certificate
|
||||||
|
copy:
|
||||||
|
src: "{{ ipsec_pki_path }}/cacert.pem"
|
||||||
|
dest: "{{ ipsec_config_path }}/manual/cacert.pem"
|
||||||
|
|
||||||
|
- name: Create private keys for users and server
|
||||||
community.crypto.openssl_privatekey:
|
community.crypto.openssl_privatekey:
|
||||||
path: "{{ ipsec_pki_path }}/private/{{ item }}.key"
|
path: "{{ ipsec_pki_path }}/private/{{ item }}.key"
|
||||||
type: ECC
|
type: ECC
|
||||||
|
@ -67,17 +104,59 @@
|
||||||
- "{{ IP_subject_alt_name }}"
|
- "{{ IP_subject_alt_name }}"
|
||||||
register: client_key_jobs
|
register: client_key_jobs
|
||||||
|
|
||||||
- name: Create CSRs
|
# Create CSRs with proper Subject Alternative Names
|
||||||
|
# CRITICAL: Server certificates need SAN extension for modern clients,
|
||||||
|
# especially macOS/iOS which perform strict certificate validation for IKEv2.
|
||||||
|
# Without SAN containing the server IP, clients will reject the certificate.
|
||||||
|
- name: Create CSRs for server certificate with SAN
|
||||||
|
community.crypto.openssl_csr_pipe:
|
||||||
|
privatekey_path: "{{ ipsec_pki_path }}/private/{{ IP_subject_alt_name }}.key"
|
||||||
|
subject_alt_name: "{{ subjectAltName.split(',') }}"
|
||||||
|
common_name: "{{ IP_subject_alt_name }}"
|
||||||
|
key_usage:
|
||||||
|
- digitalSignature
|
||||||
|
- keyEncipherment
|
||||||
|
key_usage_critical: false
|
||||||
|
# Server authentication for IKEv2 VPN connections
|
||||||
|
extended_key_usage:
|
||||||
|
- serverAuth
|
||||||
|
- '1.3.6.1.5.5.7.3.17' # IPsec End Entity
|
||||||
|
extended_key_usage_critical: false
|
||||||
|
register: server_csr
|
||||||
|
|
||||||
|
- name: Create CSRs for client certificates
|
||||||
community.crypto.openssl_csr_pipe:
|
community.crypto.openssl_csr_pipe:
|
||||||
privatekey_path: "{{ ipsec_pki_path }}/private/{{ item }}.key"
|
privatekey_path: "{{ ipsec_pki_path }}/private/{{ item }}.key"
|
||||||
subject_alt_name: "{{ subjectAltName | split(',') if item == IP_subject_alt_name else [subjectAltName_USER] }}"
|
subject_alt_name:
|
||||||
|
- "email:{{ item }}@{{ openssl_constraint_random_id }}"
|
||||||
common_name: "{{ item }}"
|
common_name: "{{ item }}"
|
||||||
with_items:
|
key_usage:
|
||||||
- "{{ users }}"
|
- digitalSignature
|
||||||
- "{{ IP_subject_alt_name }}"
|
- keyEncipherment
|
||||||
|
key_usage_critical: false
|
||||||
|
# Client certificates should not have serverAuth
|
||||||
|
extended_key_usage:
|
||||||
|
- clientAuth
|
||||||
|
- '1.3.6.1.5.5.7.3.17' # IPsec End Entity
|
||||||
|
extended_key_usage_critical: false
|
||||||
|
with_items: "{{ users }}"
|
||||||
register: client_csr_jobs
|
register: client_csr_jobs
|
||||||
|
|
||||||
- name: Sign clients certificates with our CA
|
# Sign server certificate with proper extensions
|
||||||
|
- name: Sign server certificate with CA
|
||||||
|
community.crypto.x509_certificate:
|
||||||
|
csr_content: "{{ server_csr.csr }}"
|
||||||
|
path: "{{ ipsec_pki_path }}/certs/{{ IP_subject_alt_name }}.crt"
|
||||||
|
provider: ownca
|
||||||
|
ownca_path: "{{ ipsec_pki_path }}/cacert.pem"
|
||||||
|
ownca_privatekey_path: "{{ ipsec_pki_path }}/private/cakey.pem"
|
||||||
|
ownca_privatekey_passphrase: "{{ CA_password }}"
|
||||||
|
ownca_not_after: +3650d
|
||||||
|
ownca_not_before: "-1d"
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
# Sign client certificates with CA
|
||||||
|
- name: Sign client certificates with CA
|
||||||
community.crypto.x509_certificate:
|
community.crypto.x509_certificate:
|
||||||
csr_content: "{{ item.csr }}"
|
csr_content: "{{ item.csr }}"
|
||||||
path: "{{ ipsec_pki_path }}/certs/{{ item.item }}.crt"
|
path: "{{ ipsec_pki_path }}/certs/{{ item.item }}.crt"
|
||||||
|
@ -87,6 +166,7 @@
|
||||||
ownca_privatekey_passphrase: "{{ CA_password }}"
|
ownca_privatekey_passphrase: "{{ CA_password }}"
|
||||||
ownca_not_after: +3650d
|
ownca_not_after: +3650d
|
||||||
ownca_not_before: "-1d"
|
ownca_not_before: "-1d"
|
||||||
|
mode: "0644"
|
||||||
with_items: "{{ client_csr_jobs.results }}"
|
with_items: "{{ client_csr_jobs.results }}"
|
||||||
register: client_sign_results
|
register: client_sign_results
|
||||||
|
|
||||||
|
@ -101,6 +181,19 @@
|
||||||
encryption_level: "compatibility2022"
|
encryption_level: "compatibility2022"
|
||||||
with_items: "{{ users }}"
|
with_items: "{{ users }}"
|
||||||
|
|
||||||
|
- name: Generate p12 files with CA certificate included
|
||||||
|
community.crypto.openssl_pkcs12:
|
||||||
|
path: "{{ ipsec_pki_path }}/private/{{ item }}_ca.p12"
|
||||||
|
friendly_name: "{{ item }}"
|
||||||
|
privatekey_path: "{{ ipsec_pki_path }}/private/{{ item }}.key"
|
||||||
|
certificate_path: "{{ ipsec_pki_path }}/certs/{{ item }}.crt"
|
||||||
|
other_certificates:
|
||||||
|
- "{{ ipsec_pki_path }}/cacert.pem"
|
||||||
|
passphrase: "{{ p12_export_password }}"
|
||||||
|
mode: "0600"
|
||||||
|
encryption_level: "compatibility2022"
|
||||||
|
with_items: "{{ users }}"
|
||||||
|
|
||||||
- name: Copy the p12 certificates
|
- name: Copy the p12 certificates
|
||||||
copy:
|
copy:
|
||||||
src: "{{ ipsec_pki_path }}/private/{{ item }}.p12"
|
src: "{{ ipsec_pki_path }}/private/{{ item }}.p12"
|
||||||
|
@ -108,6 +201,13 @@
|
||||||
with_items:
|
with_items:
|
||||||
- "{{ users }}"
|
- "{{ users }}"
|
||||||
|
|
||||||
|
- name: Build openssh public keys
|
||||||
|
community.crypto.openssl_publickey:
|
||||||
|
path: "{{ ipsec_pki_path }}/public/{{ item }}.pub"
|
||||||
|
privatekey_path: "{{ ipsec_pki_path }}/private/{{ item }}.key"
|
||||||
|
format: OpenSSH
|
||||||
|
with_items: "{{ users }}"
|
||||||
|
|
||||||
- name: Add all users to the file
|
- name: Add all users to the file
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
path: "{{ ipsec_pki_path }}/all-users"
|
path: "{{ ipsec_pki_path }}/all-users"
|
||||||
|
@ -142,6 +242,7 @@
|
||||||
issuer:
|
issuer:
|
||||||
CN: "{{ IP_subject_alt_name }}"
|
CN: "{{ IP_subject_alt_name }}"
|
||||||
revoked_certificates: "{{ revoked_certificates }}"
|
revoked_certificates: "{{ revoked_certificates }}"
|
||||||
|
mode: "0644"
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
become: false
|
become: false
|
||||||
vars:
|
vars:
|
||||||
|
@ -152,4 +253,4 @@
|
||||||
src: "{{ ipsec_pki_path }}/crl.pem"
|
src: "{{ ipsec_pki_path }}/crl.pem"
|
||||||
dest: "{{ config_prefix|default('/') }}etc/ipsec.d/crls/algo.root.pem"
|
dest: "{{ config_prefix|default('/') }}etc/ipsec.d/crls/algo.root.pem"
|
||||||
notify:
|
notify:
|
||||||
- rereadcrls
|
- rereadcrls
|
|
@ -1,2 +1,24 @@
|
||||||
|
# Algo VPN systemd security hardening for StrongSwan
|
||||||
|
# Enhanced hardening on top of existing AppArmor
|
||||||
[Service]
|
[Service]
|
||||||
MemoryLimit=16777216
|
# Privilege restrictions
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
|
||||||
|
# Filesystem isolation (complements AppArmor)
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
|
||||||
|
# Network restrictions - include IPsec kernel communication requirements
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_PACKET
|
||||||
|
|
||||||
|
# Allow access to IPsec configuration, state, and kernel interfaces
|
||||||
|
ReadWritePaths=/etc/ipsec.d /var/lib/strongswan
|
||||||
|
ReadOnlyPaths=/proc/net/pfkey
|
||||||
|
|
||||||
|
# System call filtering (complements AppArmor restrictions)
|
||||||
|
# Allow crypto operations, remove cpu-emulation restriction for crypto algorithms
|
||||||
|
SystemCallFilter=@system-service @network-io
|
||||||
|
SystemCallFilter=~@debug @mount @swap @reboot
|
||||||
|
SystemCallErrorNumber=EPERM
|
||||||
|
|
|
@ -73,10 +73,13 @@
|
||||||
</dict>
|
</dict>
|
||||||
<key>DeadPeerDetectionRate</key>
|
<key>DeadPeerDetectionRate</key>
|
||||||
<string>Medium</string>
|
<string>Medium</string>
|
||||||
|
<!-- MOBIKE allows VPN to survive network changes (WiFi to cellular) -->
|
||||||
<key>DisableMOBIKE</key>
|
<key>DisableMOBIKE</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
|
<!-- Disable IKEv2 redirects for security -->
|
||||||
<key>DisableRedirect</key>
|
<key>DisableRedirect</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
|
<!-- Disable CRL checking for performance and reliability -->
|
||||||
<key>EnableCertificateRevocationCheck</key>
|
<key>EnableCertificateRevocationCheck</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
<key>EnablePFS</key>
|
<key>EnablePFS</key>
|
||||||
|
@ -96,19 +99,24 @@
|
||||||
<string>{{ item.0 }}@{{ openssl_constraint_random_id }}</string>
|
<string>{{ item.0 }}@{{ openssl_constraint_random_id }}</string>
|
||||||
<key>PayloadCertificateUUID</key>
|
<key>PayloadCertificateUUID</key>
|
||||||
<string>{{ pkcs12_PayloadCertificateUUID }}</string>
|
<string>{{ pkcs12_PayloadCertificateUUID }}</string>
|
||||||
|
<!-- Use ECDSA P-384 certificates for strong security -->
|
||||||
<key>CertificateType</key>
|
<key>CertificateType</key>
|
||||||
<string>ECDSA384</string>
|
<string>ECDSA384</string>
|
||||||
<key>ServerCertificateIssuerCommonName</key>
|
<key>ServerCertificateIssuerCommonName</key>
|
||||||
<string>{{ IP_subject_alt_name }}</string>
|
<string>{{ IP_subject_alt_name }}</string>
|
||||||
|
<key>ServerCertificateCommonName</key>
|
||||||
|
<string>{{ IP_subject_alt_name }}</string>
|
||||||
<key>RemoteAddress</key>
|
<key>RemoteAddress</key>
|
||||||
<string>{{ IP_subject_alt_name }}</string>
|
<string>{{ IP_subject_alt_name }}</string>
|
||||||
<key>RemoteIdentifier</key>
|
<key>RemoteIdentifier</key>
|
||||||
<string>{{ IP_subject_alt_name }}</string>
|
<string>{{ IP_subject_alt_name }}</string>
|
||||||
|
<!-- Use server-provided internal IP assignment -->
|
||||||
<key>UseConfigurationAttributeInternalIPSubnet</key>
|
<key>UseConfigurationAttributeInternalIPSubnet</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>IPv4</key>
|
<key>IPv4</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<!-- Override primary network interface for full VPN routing -->
|
||||||
<key>OverridePrimary</key>
|
<key>OverridePrimary</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
---
|
---
|
||||||
|
- name: daemon-reload
|
||||||
|
systemd:
|
||||||
|
daemon_reload: true
|
||||||
|
|
||||||
- name: restart wireguard
|
- name: restart wireguard
|
||||||
service:
|
service:
|
||||||
name: "{{ service_name }}"
|
name: "{{ service_name }}"
|
||||||
|
|
|
@ -10,3 +10,45 @@
|
||||||
set_fact:
|
set_fact:
|
||||||
service_name: wg-quick@{{ wireguard_interface }}
|
service_name: wg-quick@{{ wireguard_interface }}
|
||||||
tags: always
|
tags: always
|
||||||
|
|
||||||
|
- name: Ubuntu | Ensure that the WireGuard service directory exists
|
||||||
|
file:
|
||||||
|
path: /etc/systemd/system/wg-quick@{{ wireguard_interface }}.service.d/
|
||||||
|
state: directory
|
||||||
|
mode: 0755
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
|
||||||
|
- name: Ubuntu | Apply systemd security hardening for WireGuard
|
||||||
|
copy:
|
||||||
|
dest: /etc/systemd/system/wg-quick@{{ wireguard_interface }}.service.d/90-security-hardening.conf
|
||||||
|
content: |
|
||||||
|
# Algo VPN systemd security hardening for WireGuard
|
||||||
|
[Service]
|
||||||
|
# Privilege restrictions
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
|
||||||
|
# Filesystem isolation
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
|
||||||
|
# Network restrictions - WireGuard needs NETLINK for interface management
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK
|
||||||
|
|
||||||
|
# Allow access to WireGuard configuration
|
||||||
|
ReadWritePaths=/etc/wireguard
|
||||||
|
ReadOnlyPaths=/etc/resolv.conf
|
||||||
|
|
||||||
|
# System call filtering - allow network and system service calls
|
||||||
|
SystemCallFilter=@system-service @network-io
|
||||||
|
SystemCallFilter=~@debug @mount @swap @reboot @raw-io
|
||||||
|
SystemCallErrorNumber=EPERM
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: 0644
|
||||||
|
notify:
|
||||||
|
- daemon-reload
|
||||||
|
- restart wireguard
|
||||||
|
|
Loading…
Add table
Reference in a new issue