2626import com .cloudbees .jenkins .plugins .sshcredentials .SSHUserPrivateKey ;
2727import com .cloudbees .plugins .credentials .CredentialsProvider ;
2828import com .cloudbees .plugins .credentials .CredentialsScope ;
29- import com .cloudbees .plugins .credentials .CredentialsSnapshotTaker ;
3029import edu .umd .cs .findbugs .annotations .CheckForNull ;
3130import edu .umd .cs .findbugs .annotations .NonNull ;
3231import hudson .DescriptorExtensionList ;
3332import hudson .Extension ;
3433import hudson .model .AbstractDescribableImpl ;
3534import hudson .model .Descriptor ;
35+ import hudson .model .Items ;
3636import hudson .remoting .Channel ;
3737import hudson .util .Secret ;
3838import java .io .File ;
3939import java .io .IOException ;
4040import java .io .ObjectStreamException ;
4141import java .io .Serializable ;
4242import java .io .StringReader ;
43+ import java .lang .reflect .InvocationTargetException ;
44+ import java .lang .reflect .Method ;
4345import java .util .ArrayList ;
4446import java .util .Arrays ;
4547import java .util .Collections ;
4648import java .util .List ;
4749import java .util .concurrent .TimeUnit ;
4850import java .util .logging .Level ;
4951import java .util .logging .Logger ;
52+
53+ import hudson .util .XStream2 ;
5054import jenkins .model .Jenkins ;
5155import net .jcip .annotations .GuardedBy ;
5256import org .apache .commons .io .FileUtils ;
@@ -148,16 +152,6 @@ private synchronized Object readResolve() throws ObjectStreamException {
148152 return this ;
149153 }
150154
151- private Object writeReplace () {
152- if (/* XStream */ Channel .current () == null ) {
153- return this ;
154- }
155- if (privateKeySource == null || privateKeySource .isSnapshotSource ()) {
156- return this ;
157- }
158- return CredentialsProvider .snapshot (this );
159- }
160-
161155 /**
162156 * {@inheritDoc}
163157 */
@@ -290,7 +284,9 @@ public long getPrivateKeysLastModified() {
290284 *
291285 * @return {@code true} if and only if the source is self contained.
292286 * @since 1.7
287+ * @deprecated no more used since FileOnMaster- and Users- PrivateKeySource are deprecated too
293288 */
289+ @ Deprecated
294290 public boolean isSnapshotSource () {
295291 return false ;
296292 }
@@ -371,7 +367,9 @@ public String getDisplayName() {
371367
372368 /**
373369 * Let the user reference a file on the disk.
370+ * @deprecated This approach has security vulnerability and should be migrated to {@link DirectEntryPrivateKeySource}
374371 */
372+ @ Deprecated
375373 public static class FileOnMasterPrivateKeySource extends PrivateKeySource {
376374
377375 /**
@@ -394,8 +392,6 @@ public static class FileOnMasterPrivateKeySource extends PrivateKeySource {
394392 */
395393 private transient volatile long nextCheckLastModified ;
396394
397-
398- @ DataBoundConstructor
399395 public FileOnMasterPrivateKeySource (String privateKeyFile ) {
400396 this .privateKeyFile = privateKeyFile ;
401397 }
@@ -436,7 +432,12 @@ private Object readResolve() {
436432 // this is a borked upgrade, not actually the file name but is actually the key contents
437433 return new DirectEntryPrivateKeySource (privateKeyFile );
438434 }
439- return this ;
435+
436+ Jenkins .getActiveInstance ().checkPermission (Jenkins .RUN_SCRIPTS );
437+
438+ LOGGER .log (Level .INFO , "SECURITY-440: Migrating FileOnMasterPrivateKeySource to DirectEntryPrivateKeySource" );
439+ // read the content of the file and then migrate to Direct
440+ return new DirectEntryPrivateKeySource (getPrivateKeys ());
440441 }
441442
442443 @ Override
@@ -453,26 +454,13 @@ public long getPrivateKeysLastModified() {
453454 }
454455 return lastModified ;
455456 }
456-
457- /**
458- * {@inheritDoc}
459- */
460- @ Extension
461- public static class DescriptorImpl extends PrivateKeySourceDescriptor {
462-
463- /**
464- * {@inheritDoc}
465- */
466- @ Override
467- public String getDisplayName () {
468- return Messages .BasicSSHUserPrivateKey_FileOnMasterPrivateKeySourceDisplayName ();
469- }
470- }
471457 }
472458
473459 /**
474460 * Let the user
461+ * @deprecated This approach has security vulnerability and should be migrated to {@link DirectEntryPrivateKeySource}
475462 */
463+ @ Deprecated
476464 public static class UsersPrivateKeySource extends PrivateKeySource {
477465
478466 /**
@@ -490,10 +478,6 @@ public static class UsersPrivateKeySource extends PrivateKeySource {
490478 */
491479 private transient volatile long nextCheckLastModified ;
492480
493- @ DataBoundConstructor
494- public UsersPrivateKeySource () {
495- }
496-
497481 private List <File > files () {
498482 List <File > files = new ArrayList <File >();
499483 File sshHome = new File (new File (System .getProperty ("user.home" )), ".ssh" );
@@ -535,51 +519,27 @@ public long getPrivateKeysLastModified() {
535519 return lastModified ;
536520 }
537521
538- /**
539- * {@inheritDoc}
540- */
541- @ Extension
542- public static class DescriptorImpl extends PrivateKeySourceDescriptor {
522+ private Object readResolve () {
523+ Jenkins .getActiveInstance ().checkPermission (Jenkins .RUN_SCRIPTS );
543524
544- /**
545- * {@inheritDoc}
546- */
547- @ Override
548- public String getDisplayName () {
549- return Messages .BasicSSHUserPrivateKey_UsersPrivateKeySourceDisplayName ();
550- }
525+ LOGGER .log (Level .INFO , "SECURITY-440: Migrating UsersPrivateKeySource to DirectEntryPrivateKeySource" );
526+ // read the content of the file and then migrate to Direct
527+ return new DirectEntryPrivateKeySource (getPrivateKeys ());
551528 }
552529 }
553530
554- /**
555- * @since 1.7
556- */
557- @ Extension
558- public static class CredentialsSnapshotTakerImpl extends CredentialsSnapshotTaker <SSHUserPrivateKey > {
559-
560- /**
561- * {@inheritDoc}
562- */
563- @ Override
564- public Class <SSHUserPrivateKey > type () {
565- return SSHUserPrivateKey .class ;
566- }
567-
568- /**
569- * {@inheritDoc}
570- */
571- @ Override
572- public SSHUserPrivateKey snapshot (SSHUserPrivateKey credentials ) {
573- if (credentials instanceof BasicSSHUserPrivateKey ) {
574- final PrivateKeySource keySource = ((BasicSSHUserPrivateKey ) credentials ).getPrivateKeySource ();
575- if (keySource .isSnapshotSource ()) {
576- return credentials ;
577- }
578- }
579- final Secret passphrase = credentials .getPassphrase ();
580- return new BasicSSHUserPrivateKey (credentials .getScope (), credentials .getId (), credentials .getUsername (),
581- new DirectEntryPrivateKeySource (credentials .getPrivateKeys ()),
582- passphrase == null ? null : passphrase .getEncryptedValue (), credentials .getDescription ());
531+ static {
532+ try {
533+ // the critical field allow the permission check to make the XML read to fail completely in case of violation
534+ // TODO: Remove reflection once baseline is updated past 2.85.
535+ Method m = XStream2 .class .getMethod ("addCriticalField" , Class .class , String .class );
536+ m .invoke (Items .XSTREAM2 , BasicSSHUserPrivateKey .class , "privateKeySource" );
537+ } catch (IllegalAccessException e ) {
538+ throw new ExceptionInInitializerError (e );
539+ } catch (InvocationTargetException e ) {
540+ throw new ExceptionInInitializerError (e );
541+ } catch (NoSuchMethodException e ) {
542+ throw new ExceptionInInitializerError (e );
583543 }
584544 }
585545}
0 commit comments