-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsyslogify.py
More file actions
238 lines (199 loc) · 9.41 KB
/
syslogify.py
File metadata and controls
238 lines (199 loc) · 9.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
"""
syslogify.py
------------
Automates syslog forwarding configuration across multiple Linux servers via SSH.
Supports RHEL/CentOS, Debian/Ubuntu, and SUSE distributions.
Authentication supports both SSH key-based (recommended) and password-based methods.
Security note:
Password-based sudo is supported for legacy environments, but SSH key authentication
with scoped sudoers rules is strongly recommended for production use.
Usage:
python syslogify.py
"""
import paramiko
import getpass
import logging
import datetime
import sys
import os
# ── Logging setup ────────────────────────────────────────────────────────────
log_filename = f"syslogify_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
handlers=[
logging.FileHandler(log_filename),
logging.StreamHandler(sys.stdout),
],
)
log = logging.getLogger(__name__)
# ── OS detection ─────────────────────────────────────────────────────────────
def detect_os(ssh):
"""
Detect the Linux distribution on the remote server.
Returns the first non-empty output from a set of standard OS identification commands.
"""
probes = [
"cat /etc/os-release",
"lsb_release -a",
"cat /etc/redhat-release",
"cat /etc/issue",
"uname -a",
]
for cmd in probes:
try:
_, stdout, _ = ssh.exec_command(cmd)
output = stdout.read().decode().strip()
if output:
return output
except Exception:
continue
return "Unknown"
def resolve_syslog_config(os_info):
"""
Return (config_file_path, restart_command) based on the detected OS string.
Falls back to syslog.conf / sysvinit for unrecognised systems.
"""
if any(d in os_info for d in ("Red Hat", "CentOS", "Fedora")):
if "release 5" in os_info or "release 6" in os_info:
return "/etc/syslog.conf", "/etc/init.d/syslog restart"
return "/etc/rsyslog.conf", "systemctl restart rsyslog"
if any(d in os_info for d in ("Debian", "Ubuntu")):
return "/etc/rsyslog.conf", "systemctl restart rsyslog"
if "SUSE" in os_info:
if "Enterprise Server 11" in os_info:
return "/etc/syslog-ng/syslog-ng.conf", "/etc/init.d/syslog-ng restart"
return "/etc/syslog-ng/syslog-ng.conf", "systemctl restart syslog-ng"
log.warning("Unrecognised OS — falling back to /etc/syslog.conf")
return "/etc/syslog.conf", "/etc/init.d/syslog restart"
# ── Remote command helper ─────────────────────────────────────────────────────
def run(ssh, command, use_sudo=False, password=None):
"""
Execute a command on the remote host.
For sudo operations the command is wrapped so the password is passed via
stdin rather than embedded in the command string, avoiding exposure in
the remote process list.
Returns (exit_code, stdout_text, stderr_text).
"""
if use_sudo and password:
# Pass password through stdin — not visible in ps aux
full_cmd = f"sudo -S {command}"
stdin, stdout, stderr = ssh.exec_command(full_cmd)
stdin.write(password + "\n")
stdin.flush()
else:
stdin, stdout, stderr = ssh.exec_command(command)
exit_code = stdout.channel.recv_exit_status()
return exit_code, stdout.read().decode().strip(), stderr.read().decode().strip()
# ── Syslog configuration ──────────────────────────────────────────────────────
def configure_syslog(ssh, host, log_server, log_port, password=None):
"""
Configure syslog forwarding on the remote host and verify the service
is running after restart.
Returns True on success, False on any failure.
"""
os_info = detect_os(ssh)
log.info(f"[{host}] Detected OS: {os_info[:80]}")
config_file, restart_cmd = resolve_syslog_config(os_info)
forwarding_rule = f"*.* @{log_server}:{log_port}"
# Ensure config file exists
code, _, err = run(ssh, f"test -f {config_file} || touch {config_file}",
use_sudo=True, password=password)
if code != 0:
log.error(f"[{host}] Could not access config file {config_file}: {err}")
return False
# Check if rule already present (idempotent)
code, out, _ = run(ssh, f"grep -qF '{forwarding_rule}' {config_file}")
if code == 0:
log.info(f"[{host}] Forwarding rule already present — skipping write.")
else:
# Append forwarding rule
code, _, err = run(
ssh,
f"bash -c 'echo \"{forwarding_rule}\" >> {config_file}'",
use_sudo=True,
password=password,
)
if code != 0:
log.error(f"[{host}] Failed to write forwarding rule: {err}")
return False
log.info(f"[{host}] Forwarding rule added to {config_file}.")
# Restart syslog service
code, _, err = run(ssh, restart_cmd, use_sudo=True, password=password)
if code != 0:
log.error(f"[{host}] Service restart failed: {err}")
return False
log.info(f"[{host}] Syslog service restarted.")
# Verify service is running
service_name = restart_cmd.split()[-1] # e.g. rsyslog, syslog-ng
code, out, _ = run(ssh, f"systemctl is-active {service_name}")
if code == 0 and "active" in out:
log.info(f"[{host}] Service '{service_name}' confirmed active. ✓")
return True
# Fallback check for non-systemd hosts
code, out, _ = run(ssh, f"ps aux | grep -v grep | grep {service_name}")
if code == 0 and out:
log.info(f"[{host}] Service '{service_name}' confirmed running (ps). ✓")
return True
log.warning(f"[{host}] Could not confirm service is running after restart.")
return False
# ── SSH connection ────────────────────────────────────────────────────────────
def connect(host, username, password=None, key_path=None):
"""
Open an SSH connection using key-based auth (preferred) or password auth.
Returns a connected SSHClient or None on failure.
"""
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
if key_path:
ssh.connect(host, username=username, key_filename=key_path)
log.info(f"[{host}] Connected via SSH key.")
else:
ssh.connect(host, username=username, password=password)
log.info(f"[{host}] Connected via password.")
return ssh
except paramiko.AuthenticationException:
log.error(f"[{host}] Authentication failed.")
except Exception as e:
log.error(f"[{host}] Connection error: {e}")
return None
# ── Entry point ───────────────────────────────────────────────────────────────
def main():
print("\n── Syslogify — Automated Syslog Deployment ──\n")
username = input("SSH username: ").strip()
log_server = input("Log server IP: ").strip()
log_port = input("Log server port (press Enter to use default port 514): ").strip() or "514"
hosts_input = input("Target hosts (comma-separated IPs): ").strip()
hosts = [h.strip() for h in hosts_input.split(",") if h.strip()]
auth_method = input("Auth method — [1] SSH key [2] Password: ").strip()
key_path = None
password = None
if auth_method == "1":
default_key = os.path.expanduser("~/.ssh/id_rsa")
key_input = input(f"Path to private key [{default_key}]: ").strip()
key_path = key_input or default_key
# Password may still be needed for sudo on remote host
sudo_pass = getpass.getpass("Sudo password on remote hosts (leave blank if passwordless sudo): ")
password = sudo_pass or None
else:
password = getpass.getpass("SSH password: ")
results = {"success": [], "failed": []}
for host in hosts:
log.info(f"\n── Processing {host} ──")
ssh = connect(host, username, password=password, key_path=key_path)
if ssh is None:
results["failed"].append(host)
continue
try:
ok = configure_syslog(ssh, host, log_server, log_port, password=password)
(results["success"] if ok else results["failed"]).append(host)
finally:
ssh.close()
# ── Summary ───────────────────────────────────────────────────────────────
print("\n── Deployment Summary ──")
print(f" Successful : {len(results['success'])} host(s): {', '.join(results['success']) or 'none'}")
print(f" Failed : {len(results['failed'])} host(s): {', '.join(results['failed']) or 'none'}")
print(f"\nFull log saved to: {log_filename}")
if __name__ == "__main__":
main()