@@ -22,9 +22,9 @@ In any case, ssh-ident:
2222 actually need them, once. No matter how many terminals, ssh or login
2323 sessions you have, no matter if your home is shared via NFS.
2424
25- - can prepare and use a different agent and different set of keys depending
26- on the host you are connecting to, or the directory you are using ssh
27- from.
25+ - can prepare and use a different agent, different set of keys and different
26+ ssh config file depending on the host you are connecting to, or the
27+ directory you are using ssh from.
2828 This allows for isolating keys when using agent forwarding with different
2929 sites (eg, university, work, home, secret evil internet identity, ...).
3030 It also allows to use multiple accounts on sites like github, unfuddle
@@ -199,6 +199,7 @@ To have multiple identities, all I have to do is:
199199 # need it.
200200 # Otherwise, provides options to be passed to 'ssh' for specific
201201 # identities.
202+ # Note that separate ssh config files per identity are possible too.
202203 SSH_OPTIONS = {
203204 # Disable forwarding of the agent, but enable X forwarding,
204205 # when using the work profile.
@@ -241,6 +242,11 @@ To have multiple identities, all I have to do is:
241242 # Generate keys to be used for work only, rsa
242243 $ ssh-keygen -t rsa -b 4096 -f ~/.ssh/identities/work/id_rsa
243244
245+ 5) Optionally create separate ssh config files for those identities that
246+ need special ssh settings in general or for specific hosts:
247+
248+ $ ${EDITOR} ~/.ssh/identities/secret/config
249+
244250 ...
245251
246252
@@ -260,18 +266,79 @@ ssh-ident will be invoked instead, and:
260266 access only to the agent for the identity work, and the corresponding
261267 keys.
262268
263- Note that ssh-ident needs to access both your private and public keys. Note
264- also that it identifies public keys by the .pub extension. All files in your
265- identities subdirectories will be considered keys.
266-
267- If you want to only load keys that have "key" in the name, you can add
268- to your .ssh-ident:
269-
270- PATTERN_KEYS = "key"
269+ Notes about key files:
270+ ssh-ident needs to access both your private and public keys. Both files of each
271+ key pair have to reside in the same directory.
272+ All files in your identities subdirectories that match PATTERN_KEYS will be
273+ considered key files (either private or public). If a different naming scheme
274+ is used, then make sure that PATTERN_KEYS matches filenames for both types.
275+ By default ssh-ident identifies public keys by the .pub extension or
276+ a "public" inside the filename, while private keys have no explicit extension
277+ or a "private" inside the filename. To recognize a key pair these specific name
278+ parts are removed, the remaining filenames compared and connected if they match.
279+ A key is only recognized and loaded if the key pair is complete.
280+ The public key file is necessary to detect if a key is already loaded into
281+ ssh-agent to avoid adding it again and therefore asking for password again.
282+ If a public key file is missing check out the '-y' parameter of 'ssh-keygen'.
283+ All patterns to detect key files in general plus public and private keys are
284+ defined in lists, which can hold multiple regular expressions or simple
285+ compare strings. The first match is taken and no further tests done.
286+ Patterns are tested against the filename, not the full path.
287+
288+ The defaults of PATTERN_KEYS against the filename for the general key file
289+ determination are:
290+
291+ PATTERN_KEYS = [
292+ r"^id_",
293+ r"^identity",
294+ r"^ssh[0-9]-",
295+ ]
296+
297+ The defaults of PATTERN_PUBKEYS and PATTERN_PRIVKEYS for the public and private
298+ key determination are:
299+
300+ PATTERN_PUBKEYS = [
301+ [r"\.pub$", 0],
302+ [r"public", 0],
303+ ]
304+ PATTERN_PRIVKEYS = [
305+ [r"private", 0],
306+ # Fallback for all remaining files.
307+ [r"", None],
308+ ]
309+
310+ Notes about PATTERN_PUBKEYS and PATTERN_PRIVKEYS:
311+ ssh-ident first checks if the file is a public key, then if it is a private key.
312+ The second parameter after the patterns defines which group to remove to
313+ recognize key pairs. A zero (0) means remove the whole match. 'None' means do
314+ not remove anything and leave filename as is.
315+
316+ If you want to only load keys that have "mykey" in their filename, you can
317+ define in your .ssh-ident:
318+
319+ PATTERN_KEYS = [
320+ "mykey",
321+ ]
322+
323+ If you want to also load keys that have the extension ".key" or ".pub", then
324+ you can define in your .ssh-ident:
325+
326+ PATTERN_KEYS = [
327+ r"^id_",
328+ r"^identity",
329+ r"^ssh[0-9]-",
330+ r"(\.key|\.pub)$",
331+ ]
332+ PATTERN_PRIVKEYS = [
333+ [r"\.key$", 0],
334+ [r"private", 0],
335+ # Fallback for all remaining files.
336+ [r"", None],
337+ ]
338+
339+ Note: As the ".pub" and ".key" patterns come first, those filenames can also
340+ have "public" or "private" in their name, e.g. their user name.
271341
272- The default is:
273-
274- PATTERN_KEYS = r"/(id_.*|identity.*|ssh[0-9]-.*)"
275342
276343You can also redefine:
277344
@@ -435,7 +502,21 @@ class Config(object):
435502 "DIR_AGENTS" : "$HOME/.ssh/agents" ,
436503
437504 # How to identify key files in the identities directory.
438- "PATTERN_KEYS" : r"/(id_.*|identity.*|ssh[0-9]-.*)" ,
505+ "PATTERN_KEYS" : [
506+ r"^id_" ,
507+ r"^identity" ,
508+ r"^ssh[0-9]-" ,
509+ ],
510+ # How to recognize public and private key files in the identities directory.
511+ "PATTERN_PUBKEYS" : [
512+ [r"\.pub$" , 0 ],
513+ [r"public" , 0 ],
514+ ],
515+ "PATTERN_PRIVKEYS" : [
516+ [r"private" , 0 ],
517+ # Fallback for all remaining files.
518+ [r"" , None ],
519+ ],
439520
440521 # How to identify ssh config files.
441522 "PATTERN_CONFIG" : r"/config$" ,
@@ -502,23 +583,49 @@ class Config(object):
502583 def Get (self , parameter ):
503584 """Returns the value of a parameter, or causes the script to exit."""
504585 if parameter in os .environ :
505- return self .Expand (os .environ [parameter ])
506- if parameter in self .values :
507- return self .Expand (self .values [parameter ])
508- if parameter in self .defaults :
509- return self .Expand (self .defaults [parameter ])
510-
511- print (
512- "Parameter '{0}' needs to be defined in "
513- "config file or defaults" .format (parameter ), file = sys .stderr ,
514- loglevel = LOG_ERROR )
515- sys .exit (2 )
586+ result = self .Expand (os .environ [parameter ])
587+ elif parameter in self .values :
588+ result = self .Expand (self .values [parameter ])
589+ elif parameter in self .defaults :
590+ result = self .Expand (self .defaults [parameter ])
591+ else :
592+ print (
593+ "Parameter '{0}' needs to be defined in "
594+ "config file or defaults" .format (parameter ), file = sys .stderr ,
595+ loglevel = LOG_ERROR )
596+ sys .exit (2 )
597+
598+ # Compile patterns for speed
599+ if parameter in ("PATTERN_KEYS" , "PATTERN_PUBKEYS" , "PATTERN_PRIVKEYS" , "MATCH_PATH" , "MATCH_ARGV" ):
600+ # Convert old format string, or wrongly used tuple, to list
601+ if not isinstance (result , list ):
602+ # Convert tuple to list, as we need it mutable
603+ if isinstance (result , tuple ):
604+ result = list (result )
605+ else :
606+ result = [result ]
607+ # Compile regex pattern [in first element] of each list entry
608+ for index , entry in enumerate (result ):
609+ # Convert tuple to list, as we need it mutable
610+ if isinstance (entry , tuple ):
611+ entry = result [index ] = list (entry )
612+ # Compile regex
613+ if isinstance (entry , list ):
614+ entry [0 ] = re .compile (entry [0 ])
615+ else :
616+ entry = result [index ] = re .compile (entry )
617+ #
618+ print ("{0} #{1}: {2}" .format (parameter , index + 1 , entry ),
619+ file = sys .stderr ,
620+ loglevel = LOG_DEBUG )
621+
622+ return result
516623
517624 def Set (self , parameter , value ):
518625 """Sets configuration option parameter to value."""
519626 self .values [parameter ] = value
520627
521- def FindIdentityInList (elements , identities , all_elements ):
628+ def FindIdentityInList (elements , identities , all_elements , pattern_name ):
522629 """Matches a list of identities to a list of elements.
523630
524631 Args:
@@ -532,18 +639,22 @@ def FindIdentityInList(elements, identities, all_elements):
532639 """
533640 # Test against each element separately
534641 for element in elements :
642+ index = 0
535643 for regex , identity in identities :
644+ index += 1
536645 if re .search (regex , element ):
537- print ("Matching: {0}" .format (element ),
646+ print ("Matching {0} #{1}: {2} " .format (pattern_name , index , element ),
538647 file = sys .stderr ,
539648 loglevel = LOG_DEBUG )
540649 return identity
541650 # Test against all elements in a single string
542651 if all_elements and len (elements ) > 1 :
543652 element = " " .join (elements )
653+ index = 0
544654 for regex , identity in identities :
655+ index += 1
545656 if re .search (regex , element ):
546- print ("Matching: {0}" .format (element ),
657+ print ("Matching {0} #{1}: {2} " .format (pattern_name , index , element ),
547658 file = sys .stderr ,
548659 loglevel = LOG_DEBUG )
549660 return identity
@@ -562,8 +673,8 @@ def FindIdentity(argv, config):
562673 """
563674 paths = set ([os .getcwd (), os .path .abspath (os .getcwd ()), os .path .normpath (os .getcwd ())])
564675 return (
565- FindIdentityInList (argv , config .Get ("MATCH_ARGV" ), True ) or
566- FindIdentityInList (paths , config .Get ("MATCH_PATH" ), False ) or
676+ FindIdentityInList (argv , config .Get ("MATCH_ARGV" ), True , "MATCH_ARGV" ) or
677+ FindIdentityInList (paths , config .Get ("MATCH_PATH" ), False , "MATCH_PATH" ) or
567678 config .Get ("DEFAULT_IDENTITY" ))
568679
569680def FindKeys (identity , config ):
@@ -587,7 +698,9 @@ def FindKeys(identity, config):
587698 if identity == getpass .getuser ():
588699 directories .append (os .path .expanduser ("~/.ssh" ))
589700
590- pattern = re .compile (config .Get ("PATTERN_KEYS" ))
701+ pattern_keys = config .Get ("PATTERN_KEYS" )
702+ pattern_pub = config .Get ("PATTERN_PUBKEYS" )
703+ pattern_priv = config .Get ("PATTERN_PRIVKEYS" )
591704 found = collections .defaultdict (dict )
592705 for directory in directories :
593706 try :
@@ -597,28 +710,52 @@ def FindKeys(identity, config):
597710 continue
598711 raise
599712
600- for key in keyfiles :
601- key = os .path .join (directory , key )
602- if not os .path .isfile (key ):
713+ for keyname in keyfiles :
714+ keypath = os .path .join (directory , keyname )
715+ if not os .path .isfile (keypath ):
603716 continue
604- if not pattern .search (key ):
717+ #
718+ match = None
719+ for pattern in pattern_keys :
720+ match = pattern .search (keyname )
721+ if match :
722+ break
723+ if match is None :
605724 continue
606-
607- kinds = (
608- ("private" , "priv" ),
609- ("public" , "pub" ),
610- (".pub" , "pub" ),
611- ("" , "priv" ),
612- )
613- for match , kind in kinds :
614- if match in key :
615- found [key .replace (match , "" )][kind ] = key
725+ #
726+ match = None
727+ if match is None :
728+ for pattern in pattern_pub :
729+ match = pattern [0 ].search (keyname )
730+ if match :
731+ kind = 'pub'
732+ break
733+ if match is None :
734+ for pattern in pattern_priv :
735+ match = pattern [0 ].search (keyname )
736+ if match :
737+ kind = 'priv'
738+ break
739+ if match is None :
740+ continue
741+ #
742+ if match and not pattern [1 ] is None :
743+ key = keyname [:match .start (pattern [1 ])] + keyname [match .end (pattern [1 ]):]
744+ else :
745+ key = keyname
746+ key = os .path .join (directory , key )
747+ found [key ][kind ] = keypath
616748
617749 if not found :
618750 print ("Warning: no keys found for identity {0} in:" .format (identity ),
619751 file = sys .stderr ,
620752 loglevel = LOG_WARN )
621753 print (directories , file = sys .stderr , loglevel = LOG_WARN )
754+ else :
755+ index = 0
756+ for keyname in found :
757+ index += 1
758+ print ("Found key pair #{0} {1}: {2}{3}" .format (index , keyname , found [keyname ], " MISSING PUB" if not 'pub' in found [keyname ] else " MISSING PRIV" if not 'priv' in found [keyname ] else "" ), file = sys .stderr , loglevel = LOG_DEBUG )
622759
623760 return found
624761
0 commit comments