40
40
import java .net .SocketTimeoutException ;
41
41
import java .net .URL ;
42
42
import java .net .UnknownHostException ;
43
+ import java .security .PublicKey ;
44
+ import java .security .UnrecoverableKeyException ;
45
+ import java .security .cert .Certificate ;
46
+ import java .security .interfaces .DSAPublicKey ;
47
+ import java .security .interfaces .ECPublicKey ;
48
+ import java .security .interfaces .RSAPublicKey ;
43
49
import java .util .ArrayList ;
44
50
import java .util .Base64 ;
45
51
import java .util .Collection ;
52
58
import java .util .logging .Logger ;
53
59
import javax .servlet .ServletException ;
54
60
import jenkins .authentication .tokens .api .AuthenticationTokens ;
61
+ import jenkins .bouncycastle .api .PEMEncodable ;
55
62
import jenkins .metrics .api .Metrics ;
56
63
import jenkins .model .Jenkins ;
57
64
import jenkins .model .JenkinsLocationConfiguration ;
65
+ import jenkins .security .FIPS140 ;
58
66
import jenkins .util .SystemProperties ;
59
67
import jenkins .websocket .WebSockets ;
60
68
import net .sf .json .JSONObject ;
@@ -268,6 +276,7 @@ public String getServerUrl() {
268
276
269
277
@ DataBoundSetter
270
278
public void setServerUrl (@ NonNull String serverUrl ) {
279
+ ensureKubernetesUrlInFipsMode (serverUrl );
271
280
this .serverUrl = Util .fixEmpty (serverUrl );
272
281
}
273
282
@@ -277,6 +286,7 @@ public String getServerCertificate() {
277
286
278
287
@ DataBoundSetter
279
288
public void setServerCertificate (String serverCertificate ) {
289
+ ensureServerCertificateInFipsMode (serverCertificate );
280
290
this .serverCertificate = Util .fixEmpty (serverCertificate );
281
291
}
282
292
@@ -286,6 +296,7 @@ public boolean isSkipTlsVerify() {
286
296
287
297
@ DataBoundSetter
288
298
public void setSkipTlsVerify (boolean skipTlsVerify ) {
299
+ ensureSkipTlsVerifyInFipsMode (skipTlsVerify );
289
300
this .skipTlsVerify = skipTlsVerify ;
290
301
}
291
302
@@ -651,6 +662,75 @@ public Collection<NodeProvisioner.PlannedNode> provision(
651
662
return Collections .emptyList ();
652
663
}
653
664
665
+ /**
666
+ * Checks if URL is using HTTPS, required in FIPS mode
667
+ * Continues if URL is secure or not in FIPS mode, throws an {@link IllegalArgumentException} if not.
668
+ * @param url Kubernetes server URL
669
+ */
670
+ private static void ensureKubernetesUrlInFipsMode (String url ) {
671
+ if (!FIPS140 .useCompliantAlgorithms () || StringUtils .isBlank (url )) {
672
+ return ;
673
+ }
674
+ if (!url .startsWith ("https:" )) {
675
+ throw new IllegalArgumentException (Messages .KubernetesCloud_kubernetesServerUrlIsNotSecure ());
676
+ }
677
+ }
678
+
679
+ /**
680
+ * Checks if TLS verification is being skipped, which is not allowed in FIPS mode
681
+ * Continues if not being skipped or not in FIPS mode, throws an {@link IllegalArgumentException} if not.
682
+ * @param skipTlsVerify value to check
683
+ */
684
+ private static void ensureSkipTlsVerifyInFipsMode (boolean skipTlsVerify ) {
685
+ if (FIPS140 .useCompliantAlgorithms () && skipTlsVerify ) {
686
+ throw new IllegalArgumentException (Messages .KubernetesCloud_skipTlsVerifyNotAllowedInFIPSMode ());
687
+ }
688
+ }
689
+
690
+ /**
691
+ * Checks if server certificate is allowed if FIPS mode.
692
+ * Allowed certificates use a public key with the following algorithms and sizes:
693
+ * <ul>
694
+ * <li>DSA with key size >= 2048</li>
695
+ * <li>RSA with key size >= 2048</li>
696
+ * <li>Elliptic curve (ED25519) with field size >= 224</li>
697
+ * </ul>
698
+ * If certificate is valid and allowed or not in FIPS mode method will just exit.
699
+ * If not it will throw an {@link IllegalArgumentException}.
700
+ * @param serverCertificate String containing the certificate PEM.
701
+ */
702
+ private static void ensureServerCertificateInFipsMode (String serverCertificate ) {
703
+ if (!FIPS140 .useCompliantAlgorithms ()) {
704
+ return ;
705
+ }
706
+ if (StringUtils .isBlank (serverCertificate )) {
707
+ throw new IllegalArgumentException (Messages .KubernetesCloud_serverCertificateKeyEmpty ());
708
+ }
709
+ try {
710
+ PEMEncodable pem = PEMEncodable .decode (serverCertificate );
711
+ Certificate cert = pem .toCertificate ();
712
+ if (cert == null ) {
713
+ throw new IllegalArgumentException (Messages .KubernetesCloud_serverCertificateNotACertificate ());
714
+ }
715
+ PublicKey publicKey = cert .getPublicKey ();
716
+ if (publicKey instanceof RSAPublicKey ) {
717
+ if (((RSAPublicKey ) publicKey ).getModulus ().bitLength () < 2048 ) {
718
+ throw new IllegalArgumentException (Messages .KubernetesCloud_serverCertificateKeySize ());
719
+ }
720
+ } else if (publicKey instanceof DSAPublicKey ) {
721
+ if (((DSAPublicKey ) publicKey ).getParams ().getP ().bitLength () < 2048 ) {
722
+ throw new IllegalArgumentException (Messages .KubernetesCloud_serverCertificateKeySize ());
723
+ }
724
+ } else if (publicKey instanceof ECPublicKey ) {
725
+ if (((ECPublicKey ) publicKey ).getParams ().getCurve ().getField ().getFieldSize () < 224 ) {
726
+ throw new IllegalArgumentException (Messages .KubernetesCloud_serverCertificateKeySizeEC ());
727
+ }
728
+ }
729
+ } catch (RuntimeException | UnrecoverableKeyException | IOException e ) {
730
+ throw new IllegalArgumentException (e .getMessage (), e );
731
+ }
732
+ }
733
+
654
734
@ Override
655
735
public void replaceTemplate (PodTemplate oldTemplate , PodTemplate newTemplate ) {
656
736
this .removeTemplate (oldTemplate );
@@ -913,6 +993,44 @@ public FormValidation doTestConnection(
913
993
}
914
994
}
915
995
996
+ @ RequirePOST
997
+ @ SuppressWarnings ({"unused" , "lgtm[jenkins/csrf]"
998
+ }) // used by jelly and already fixed jenkins security scan warning
999
+ public FormValidation doCheckSkipTlsVerify (@ QueryParameter boolean skipTlsVerify ) {
1000
+ Jenkins .get ().checkPermission (Jenkins .MANAGE );
1001
+ try {
1002
+ ensureSkipTlsVerifyInFipsMode (skipTlsVerify );
1003
+ } catch (IllegalArgumentException ex ) {
1004
+ return FormValidation .error (ex , ex .getLocalizedMessage ());
1005
+ }
1006
+ return FormValidation .ok ();
1007
+ }
1008
+
1009
+ @ RequirePOST
1010
+ @ SuppressWarnings ({"unused" , "lgtm[jenkins/csrf]"
1011
+ }) // used by jelly and already fixed jenkins security scan warning
1012
+ public FormValidation doCheckServerCertificate (@ QueryParameter String serverCertificate ) {
1013
+ Jenkins .get ().checkPermission (Jenkins .MANAGE );
1014
+ try {
1015
+ ensureServerCertificateInFipsMode (serverCertificate );
1016
+ } catch (IllegalArgumentException ex ) {
1017
+ return FormValidation .error (ex , ex .getLocalizedMessage ());
1018
+ }
1019
+ return FormValidation .ok ();
1020
+ }
1021
+
1022
+ @ RequirePOST
1023
+ @ SuppressWarnings ("unused" ) // used by jelly
1024
+ public FormValidation doCheckServerUrl (@ QueryParameter String serverUrl ) {
1025
+ Jenkins .get ().checkPermission (Jenkins .MANAGE );
1026
+ try {
1027
+ ensureKubernetesUrlInFipsMode (serverUrl );
1028
+ } catch (IllegalArgumentException ex ) {
1029
+ return FormValidation .error (ex .getLocalizedMessage ());
1030
+ }
1031
+ return FormValidation .ok ();
1032
+ }
1033
+
916
1034
@ RequirePOST
917
1035
@ SuppressWarnings ("unused" ) // used by jelly
918
1036
public ListBoxModel doFillCredentialsIdItems (
@@ -1126,6 +1244,11 @@ private Object readResolve() {
1126
1244
Level .INFO , "Upgraded Kubernetes server certificate key: {0}" , serverCertificate .substring (0 , 80 ));
1127
1245
}
1128
1246
1247
+ // FIPS checks if in FIPS mode
1248
+ ensureServerCertificateInFipsMode (serverCertificate );
1249
+ ensureKubernetesUrlInFipsMode (serverUrl );
1250
+ ensureSkipTlsVerifyInFipsMode (skipTlsVerify );
1251
+
1129
1252
if (maxRequestsPerHost == 0 ) {
1130
1253
maxRequestsPerHost = DEFAULT_MAX_REQUESTS_PER_HOST ;
1131
1254
}
0 commit comments