3535import hudson .util .FormValidation ;
3636import jenkins .model .Jenkins ;
3737import jenkins .plugins .openstack .compute .auth .OpenstackCredential ;
38+ import org .apache .commons .codec .Charsets ;
39+ import org .apache .commons .codec .binary .Base64 ;
40+ import org .apache .commons .codec .digest .DigestUtils ;
41+ import org .jenkinsci .main .modules .instance_identity .InstanceIdentity ;
3842import org .kohsuke .accmod .Restricted ;
3943import org .kohsuke .accmod .restrictions .NoExternalUse ;
4044import org .openstack4j .api .Builders ;
108112public class Openstack {
109113
110114 private static final Logger LOGGER = Logger .getLogger (Openstack .class .getName ());
111- public static final String FINGERPRINT_KEY = "jenkins-instance" ;
115+ public static final String FINGERPRINT_KEY_URL = "jenkins-instance" ;
116+ public static final String FINGERPRINT_KEY_FINGERPRINT = "jenkins-identity" ;
117+
118+ private static String INSTANCE_FINGERPRINT ;
112119
113120 private static final Comparator <Date > ACCEPT_NULLS = Comparator .nullsLast (Comparator .naturalOrder ());
114121 private static final Comparator <Flavor > FLAVOR_COMPARATOR = Comparator .nullsLast (Comparator .comparing (Flavor ::getName ));
@@ -359,7 +366,7 @@ public Openstack(@Nonnull final OSClient<?> client) {
359366 for (NetFloatingIP ip : clientProvider .get ().networking ().floatingip ().list ()) {
360367 if (ip .getFixedIpAddress () != null ) continue ; // Used
361368
362- String serverId = FipScope .getServerId (instanceFingerprint (), ip .getDescription ());
369+ String serverId = FipScope .getServerId (instanceUrl (), instanceFingerprint (), ip .getDescription ());
363370 if (serverId == null ) continue ; // Not ours
364371
365372 freeIps .add (ip .getId ());
@@ -481,20 +488,41 @@ public static boolean isOccupied(@Nonnull Server server) {
481488 }
482489
483490 private boolean isOurs (@ Nonnull Server server ) {
484- return instanceFingerprint ().equals (server .getMetadata ().get (FINGERPRINT_KEY ));
491+ Map <String , String > metadata = server .getMetadata ();
492+ String serverFingerprint = metadata .get (FINGERPRINT_KEY_FINGERPRINT );
493+ return serverFingerprint == null
494+ ? Objects .equals (instanceUrl (), metadata .get (FINGERPRINT_KEY_URL )) // Earlier versions ware only using URL, collect severs provisioned by those
495+ : Objects .equals (instanceUrl (), metadata .get (FINGERPRINT_KEY_URL )) && Objects .equals (instanceFingerprint (), serverFingerprint )
496+ ;
485497 }
486498
487499 /**
488500 * Identification for instances launched by this instance via this plugin.
489501 *
490502 * @return Identifier to filter instances we control.
491503 */
492- private @ Nonnull String instanceFingerprint () {
504+ @ VisibleForTesting
505+ public @ Nonnull String instanceUrl () {
493506 String rootUrl = Jenkins .get ().getRootUrl ();
494507 if (rootUrl == null ) throw new IllegalStateException ("Jenkins instance URL is not configured" );
495508 return rootUrl ;
496509 }
497510
511+ /**
512+ * Binary fingerprint to be unique worldwide.
513+ */
514+ @ VisibleForTesting
515+ public @ Nonnull String instanceFingerprint () {
516+ if (INSTANCE_FINGERPRINT == null ) {
517+ // Use salted hash not to disclose the public key. The key is used to authenticate agent connections.
518+ INSTANCE_FINGERPRINT = DigestUtils .sha1Hex (
519+ "openstack-cloud-plugin-identity-fingerprint:"
520+ + new String (Base64 .encodeBase64 (InstanceIdentity .get ().getPublic ().getEncoded ()), Charsets .UTF_8 )
521+ );
522+ }
523+ return INSTANCE_FINGERPRINT ;
524+ }
525+
498526 public @ Nonnull Server getServerById (@ Nonnull String id ) throws NoSuchElementException {
499527 Server server = clientProvider .get ().compute ().servers ().get (id );
500528 if (server == null ) throw new NoSuchElementException ("No such server running: " + id );
@@ -518,6 +546,9 @@ private boolean isOurs(@Nonnull Server server) {
518546 */
519547 public @ Nonnull Server bootAndWaitActive (@ Nonnull ServerCreateBuilder request , @ Nonnegative int timeout ) throws ActionFailed {
520548 debug ("Booting machine" );
549+
550+ // Mark the server as ours
551+ attachFingerprint (request );
521552 try {
522553 Server server = _bootAndWaitActive (request , timeout );
523554 if (server == null ) {
@@ -550,9 +581,14 @@ private boolean isOurs(@Nonnull Server server) {
550581 }
551582 }
552583
584+ @ VisibleForTesting // mocking
585+ public void attachFingerprint (@ Nonnull ServerCreateBuilder request ) {
586+ request .addMetadataItem (FINGERPRINT_KEY_URL , instanceUrl ());
587+ request .addMetadataItem (FINGERPRINT_KEY_FINGERPRINT , instanceFingerprint ());
588+ }
589+
553590 @ Restricted (NoExternalUse .class ) // Test hook
554591 public Server _bootAndWaitActive (@ Nonnull ServerCreateBuilder request , @ Nonnegative int timeout ) {
555- request .addMetadataItem (FINGERPRINT_KEY , instanceFingerprint ());
556592 return clientProvider .get ().compute ().servers ().bootAndWaitActive (request .build (), timeout );
557593 }
558594
@@ -618,7 +654,7 @@ public void destroyServer(@Nonnull Server server) throws ActionFailed {
618654 debug ("Allocating floating IP for {0} in {1}" , server .getName (), server .getName ());
619655 NetworkingService networking = clientProvider .get ().networking ();
620656
621- String desc = FipScope .getDescription (instanceFingerprint (), server );
657+ String desc = FipScope .getDescription (instanceUrl (), instanceFingerprint (), server );
622658
623659 Port port = getServerPorts (server ).get (0 );
624660 Network network = networking .network ().list (Collections .singletonMap ("name" , poolName )).get (0 );
0 commit comments