2014-05-23
What ssh-agent does with multiple keys loaded
Ssh-agent is probably normally used to handle a single identity key,
but it can hold more than one if you want. One of the things that the
ssh-agent
and ssh
manpages are a bit silent on is what happens if
you have multiple SSH keys loaded into a single ssh-agent
. Since I've
been dealing with this as the result of transitioning from one set of
SSH keys to another, I'm going to write down what I've learned so far.
(The simple version of why I decided to roll over my SSH keys is that I decided to transition from keys that had once been used without encryption to keys that were created encrypted.)
Before I started loading multiple keys into ssh-agent
, what I
expected to happen is that the choice of which ssh-agent key to use
would be controlled by the key that ssh
itself would normally
use. If you had, for example, 'IdentityFile .../key1-rsa
', then
I expected ssh
to ask ssh-agent
to do operations only with that
key. This is not what happens. Instead what happens by default is
that ssh tries all keys loaded into ssh-agent, one after another,
in the order that they were loaded into ssh-agent.
You can partly override this behavior with the IdentitiesOnly
configuration directive, which will restrict the keys that ssh
tries to only the identities listed either as IdentityFile
directives or supplied on the command line with -i
. However this
is an incomplete override because it doesn't prioritize the -i
identity the way a normal (agentless) ssh
will; ssh
will first
ask ssh-agent
for any IdentityFile
keys it has and only then
fall back to a non-agent key given with -i
. This implies that if
you have a script and you want it to always use a particular
restricted identity even if more general ones are available (as
I do in one case) you need to clear
$SSH_AUTH_SOCK
in the script.
(This can apply any time you have a remote system that accepts
multiple identities from you but applies different access permissions
or access restrictions to them. Remember that IdentityFile
directives add together and -i
stacks with with them too, so even
if you have a specific identity configured for something, a general
'Host *
' identity or the like will also be tried.)
There are a couple of interesting uses I can see for this multiple
key behavior. One of them is making a transition between old and
new SSH keys easier. First off, you can load both your new and
your old key into ssh-agent
; you'll then use your new key on
systems that have been updated to accept only it but have a transparent
fallback to systems that only have your old key. More cleverly you
can use this to uncover systems that haven't been updated to your
new key by loading only your new key into ssh-agent
but leaving
your old encrypted key configured as your IdentityFile
. If you
try to ssh
to somewhere but get prompted to unlock your old key,
you've found a host that either prefers your old key to your new
key or doesn't have your new key at all.
Another use is encrypting secondary keys (for example your Github
key) but still loading them into ssh-agent
for passwordless use.
Since ssh
with ssh-agent
will try multiple keys, pushing to
Github and other such uses will eventually try the right keys. You
can force this to happen earlier by setting IdentitiesOnly
in
.ssh/config
for the particular hosts involved; this will definitely
be necessary if you have a lot of SSH keys, because SSH servers
only accept so many key attempts (cf).
(Some of this information comes from this stackoverflow answer.)
(Talking of the interaction of ssh
and ssh-agent
, it's a pity
that as far as I know ssh
can't be told 'load keys into ssh-agent
when you unlock them'. This would make it very convenient to
incrementally load keys into ssh-agent
as you turn out to need
them while not having them sitting around unlocked in a session if
you didn't.)
Why Java is a compiled language and Python is not
A commentator on Charles Marsh's article Why are there so many Pythons asked an interesting question:
[...] If both Java and Python produces Bytecodes which in-turn is run by VM's why Java is called as Compiled language and Python is called Interpreted language? [...]
One comment's answer was 'marketing', which in a sense is correct; one reason we call Java a compiled language is that that's what Sun called it from the start. Another comment noted that Java has an explicit compilation phase that is separate from having the JVM execute your Java program by interpreting the bytecodes. All of this points us towards what I feel is the real answer:
In Java, bytecode is a first class object. In Python it's an internal implementation detail.
You've always been able to find specific documentation on the JVM and its bytecodes; as far as I know they were released along side the first definitions of Java the language. JVM bytecode has never been marked 'for internal use only' and in fact it's explicitly been a stable, public representation of Java programs. For example you don't download the source code for Java applets into your browser, you download the JVM bytecode (and in general JVM bytecode is the common delivery method of Java code even on servers). And the JVM guarantees that this will work regardless of what sort of machine you have (and in theory guarantees to make it work securely even in the face of maliciously generated bytecode).
This public visibility, this treatment of bytecode as a real part of the language environment with its own documentation and guarantees and specifications, makes JVM bytecode a first class object in the overall Java ecosystem. In turn this makes it accurate to say that Java programs are (usually) compiled to JVM bytecode and then executed by an interpreter of that bytecode. This is especially so when the language's implementation makes these two explicitly separate steps with a whole collection of artifacts that are this compiled bytecode.
In Python there is nothing like this; CPython's use of bytecode is just an internal detail of the interpreter implementation. CPython bytecode is not part of Python semantics, it is not really documented, and you are not really supposed to generate your own. There's no guarantee of any cross-version compatibility, as CPython bytecode is expected to be consumed by the same interpreter that generated it. And so on. CPython bytecode is an interesting curiosity, not a first class part of (C)Python.
(It's technically accurate to say that CPython compiles Python source to bytecodes and then interprets these bytecodes. But this is a narrow technicality, not how people really see CPython working, and can be said about any number of languages that we normally think of as interpreted.)