Understanding OpenSSH `~/.ssh/config` Permission Enforcement
A common misconception suggests that OpenSSH's client ignores the permissions on your `~/.ssh/config` file. This belief often stems from straightforward testing that fails to capture the full picture. When a draft article claimed OpenSSH doesn't enforce permissions on `~/.ssh/config`, the evidence presented was a series of `chmod` commands followed by `ssh` executions, all yielding an exit code of 0. This included tests with permissions set to 600, 644, 664, and 666.
The flaw in this testing methodology lies not in the commands or their output, but in the path taken to reach that output. Simply re-running the same commands with different permissions repeatedly shows an exit code of 0. However, this consistent success masks a critical distinction: the specific command-line flag used. When you point `ssh` directly to a configuration file using the `-F` option, OpenSSH's built-in permission check for the default `~/.ssh/config` location is entirely bypassed. The four successful test runs, each using `-F` and a different permission setting, were interpreted as proof that the permission check doesn't exist, when in reality, they simply skipped the check altogether.
This highlights a fundamental principle in testing: genuine, reproducible output does not automatically mean the test exercised the intended code path. The gap between claiming a feature exists and demonstrating its execution is significant. The permission check for `~/.ssh/config` is indeed enforced, but only under specific conditions.
When OpenSSH Enforces Permissions
OpenSSH enforces permissions on the `~/.ssh/config` file when it is accessed through its default lookup mechanism. This means when you simply run `ssh hostname` and the SSH client automatically searches for configuration directives in the standard location (`~/.ssh/config`), it performs a security check on the file's permissions. The expected and secure permission setting is 0600 (read/write for the owner only). Any other permissions, particularly those that allow group or world readability or writability, will cause the SSH client to refuse to load the configuration. This is a crucial security measure to prevent sensitive connection details, such as private keys or host aliases, from being exposed to other users on the same system.
The client will typically refuse to use a configuration file with insecure permissions by printing an error message to stderr, such as:
$ ssh somehost
@@@
@@@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @@@
@@@
... [rest of SSH output]
@@@
Too many authentication failures
# OR if permissions are wrong:
$ ssh somehost
@@@
@@@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @@@
@@@
...
@@@
OpenSSH_9.0p1, LibreSSL 3.3.5
Usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address]
[-c cipher_spec] [-D [bind_address:]port]
[-E log_file] [-e escape_char] [-F configfile]
[-i identity_file] [-J destination] [-l login_name]
[-m mac_spec] [-O control_cmd] [-o option] [-p port]
[-Q query_option] [-R [bind_address:]port]
[-S control_path] [-W host:port]
[-w local_tun[:remote_tun]]
[user@]hostname [command]
# Example error for incorrect permissions:
$ ssh user@example.com
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
Permissions 0644 for '/home/user/.ssh/id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key file is readable by the system's group and others.
# The above is for private keys, the config file error is more direct:
$ ssh example.com
@ WARNING: UNPROTECTED CONFIG FILE! @
Permissions 0644 for '/home/user/.ssh/config' are too open.
It is required that your config file is NOT accessible by others.
This config file is readable by the system's group and others.
# If the file is writable by others, you might see:
$ ssh example.com
@ WARNING: UNPROTECTED CONFIG FILE! @
Permissions 0664 for '/home/user/.ssh/config' are too open.
It is required that your config file is NOT accessible by others.
This config file is writable by the system's group.
If the `~/.ssh/config` file has permissions that are too permissive (e.g., 644, 664, 666), OpenSSH will refuse to load it, preventing potential security risks. The SSH client will exit with an error, and no configuration directives from that file will be applied. This behavior is consistent across various OpenSSH versions that include this security feature.
The `-F` Flag: A Bypass for Security Checks
The critical distinction emerges when using the `-F` flag. The command `ssh -F /path/to/your/config user@hostname` explicitly tells SSH which configuration file to use. In this scenario, OpenSSH trusts that the user has correctly managed the permissions of the specified file. It does not perform its own permission checks on the file provided via `-F`. This is because the responsibility for securing that file is implicitly placed on the user who explicitly designated it. If a user chooses to use a configuration file with insecure permissions via `-F`, OpenSSH will proceed to read it without complaint. This is not a bug, but a feature that allows for flexibility, such as using temporary or shared configuration files in controlled environments.
However, for the vast majority of users, relying on the default `~/.ssh/config` lookup is the standard practice. For these users, the permission check remains an active and essential security safeguard. The `-F` flag is typically used for more advanced scenarios, such as testing configurations or managing multiple distinct sets of SSH configurations without modifying the default file.
The `Include` Directive: A Different Enforcement Model
The `Include` directive within an SSH configuration file operates differently from the `-F` flag. When `~/.ssh/config` contains an `Include` directive, such as Include /path/to/other/config, the SSH client still first checks the permissions of the primary `~/.ssh/config` file. If the primary file has insecure permissions, SSH will refuse to load it, and consequently, it will not process any `Include` directives within it. This ensures that the entry point to your configuration is secure.
Once the primary `~/.ssh/config` file is loaded (meaning its permissions are secure), OpenSSH then proceeds to read the files specified by `Include` directives. The permission enforcement for these included files is more relaxed. OpenSSH will read included configuration files regardless of their permissions. This means that if your primary `~/.ssh/config` is properly secured (e.g., 600), you can include other configuration files that might have more permissive settings (e.g., 644). The security risk here is that if these included files contain sensitive information (like private key paths), they could be exposed to other users on the system if their permissions are not also managed carefully.
The distinction is critical: the primary `~/.ssh/config` must be secure for any configuration to load. Subsequent files included via the `Include` directive are read without strict permission checks on themselves, shifting the onus of security for those files back to the user managing them. This behavior allows for modular configuration management but requires diligent attention to the permissions of all files involved in the configuration chain.
Summary of Enforcement Differences
To summarize, the enforcement of permissions on SSH configuration files is nuanced:
- Default `~/.ssh/config` lookup: Permissions are strictly enforced. Files with insecure permissions (not 600) are rejected.
- `ssh -F /path/to/config` flag: Permissions are NOT checked by OpenSSH. The user is responsible for the security of the specified file.
- `Include` directive: The primary `~/.ssh/config` file's permissions are checked first. If secure, included files are read without strict permission checks on themselves.
Understanding these differences is vital for maintaining secure SSH connections. Developers and security professionals must ensure that their primary configuration files adhere to strict permissions, while also being mindful of the security implications when using the `-F` flag or `Include` directives.
