sshconf: Lightweight SSH Config Library
sshconf is a Python library designed for reading and modifying your `~/.ssh/config` file in a non-intrusive way. It aims to keep the file's structure largely intact after modifications, providing a simple interface to manage SSH client configurations. The current version is 0.2.7. It has an irregular release cadence, with the last release in June 2024.
Common errors
-
Bad configuration option: <incorrect_option_name>
cause A directive (keyword) in the SSH configuration file has a typo or is not recognized by the OpenSSH client.fixCorrect the spelling of the SSH directive. Refer to `man ssh_config` for a list of valid options and their correct syntax. For example, `HostNamee` should be `Hostname`. -
Permission denied (publickey).
cause The SSH client cannot use your private key for authentication because its file permissions are too open, or the SSH config file itself has incorrect permissions and is being ignored.fixVerify that your `~/.ssh` directory has permissions `700`, `~/.ssh/config` has `600`, and your private key file (e.g., `~/.ssh/id_rsa`) has `600`. Use `chmod 700 ~/.ssh` and `chmod 600 ~/.ssh/config ~/.ssh/id_rsa`. -
SSH client is not using the specific configuration options (e.g., wrong user, key, or port) I set for a host.
cause Your `~/.ssh/config` file has conflicting `Host` patterns, and a more general pattern is matched first, preventing your specific host configuration from being applied.fixReorder the `Host` entries in your `~/.ssh/config` file so that more specific host definitions (e.g., `Host myhost.example.com`) appear *before* more general ones (e.g., `Host *.example.com` or `Host *`). Use `ssh -vvv <hostname>` to debug which configurations are being applied.
Warnings
- gotcha The `save()` method overwrites the SSH configuration file from which the `SshConfig` object was initially loaded. Use `write(filepath)` to save changes to a different file, or carefully use `save()` if overwriting is intended.
- gotcha When `sshconf` reads a configuration file containing `Include` directives and then writes or saves it, the included files' contents are merged into the main output file. This flattens the configuration and removes the original `Include` structure.
- gotcha OpenSSH clients process configuration options based on the first matching `Host` entry. If you have overlapping patterns (e.g., `Host *.example.com` and `Host specific.example.com`), the order matters. Less specific patterns appearing before more specific ones can lead to unexpected configurations being applied.
- gotcha SSH client configuration files (`~/.ssh/config`) and private key files (`~/.ssh/id_rsa`) require strict file permissions. Incorrect permissions (e.g., world-readable) will cause the SSH client to ignore them for security reasons, leading to authentication failures or ignored configurations.
Install
-
pip install sshconf
Imports
- read
import sshconf config = sshconf.read('~/.ssh/config') - empty
import sshconf config = sshconf.empty()
Quickstart
import sshconf
import os
# Create a temporary config file for demonstration
temp_config_path = os.path.expanduser('~/.ssh/config_temp')
# Ensure the .ssh directory exists
os.makedirs(os.path.dirname(temp_config_path), exist_ok=True)
# Start with an empty configuration
config = sshconf.empty()
# Add a new host entry
config.add(
'devserver',
Hostname='192.168.1.100',
User='devuser',
IdentityFile=os.path.expanduser('~/.ssh/id_rsa_dev')
)
# Update an existing entry or add a new parameter
config.set(
'devserver',
Port=2222,
ForwardAgent='yes'
)
# Add another host with minimal options
config.add(
'testserver',
Hostname='test.example.com',
User='testuser'
)
# Remove a parameter from a host
config.unset('devserver', 'ForwardAgent')
# Get configuration for a host
host_config = config.host('devserver')
print(f"Devserver config: {host_config.to_dict()}")
# Write the changes to a new file (or save() to overwrite original)
config.write(temp_config_path)
print(f"SSH config written to: {temp_config_path}")
print("Contents of the temporary config file:")
with open(temp_config_path, 'r') as f:
print(f.read())
# Clean up the temporary file (optional)
# os.remove(temp_config_path)