diff --git a/docs/configuration.md b/docs/configuration.md index 04f12616..3696fc15 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -9,7 +9,7 @@ permalink: /configuration/ Export environment variables in your shell, or in a `~/.rd/rd.conf` file (unix only). -**Connection Info** +## Connection Info export RD_URL=http://rundeck:4440 @@ -19,7 +19,7 @@ Define a specific API version to use, by using the complete API base: All requests will be made using that API version. -**Credentials** +## Credentials Define access credentials as user/password or Token value: @@ -30,7 +30,7 @@ Define access credentials as user/password or Token value: export RD_USER=username export RD_PASSWORD=password -**Prompting** +## Prompting If you do not define the credentials as environment variables, you will be prompted to enter a username/password or token in @@ -41,7 +41,7 @@ You can disable automatic prompting: export RD_AUTH_PROMPT=false -**ANSI color** +## ANSI color By default, `rd` will print some output using ANSI escapes for colorized output. @@ -55,7 +55,7 @@ You can set the default colors used by info/output/error/warning output: export RD_COLOR_WARN=orange export RD_COLOR_ERROR=cyan -**Bypass an external URL**: +## Bypass an external URL If your Rundeck server has a different *external URL* than the one you are accessing, you can tell the `rd` tool to treat redirects to that external URL as @@ -70,7 +70,7 @@ as `http://internal-rundeck:4440/rundeck/blah`. Note: if you include the API version in your `RD_URL`, e.g. `http://internal-rundeck:4440/rundeck/api/12` then the `RD_BYPASS_URL` will be replaced by `http://internal-rundeck:4440/rundeck`. -**HTTP/connect timeout** +## HTTP/connect timeout Use `RD_HTTP_TIMEOUT` env var: @@ -80,7 +80,7 @@ Use `RD_HTTP_TIMEOUT` env var: Note: if the timeout seems longer than you specify, it is because the "connection retry" is set to true by default. -**Connection Retry** +## Connection Retry Retry in case of recoverable connection issue (e.g. failure to connect): @@ -89,10 +89,23 @@ Use `RD_CONNECT_RETRY` (default `true`): # don't retry export RD_CONNECT_RETRY=false -**Debug HTTP** +## Debug HTTP Use the `RD_DEBUG` env var to turn on HTTP debugging: export RD_DEBUG=1 # basic http request debug export RD_DEBUG=2 # http headers - export RD_DEBUG=3 # http body \ No newline at end of file + export RD_DEBUG=3 # http body + +## SSL Configuration + +See [SSL Configuration]({{site.url}}{{site.baseurl}}/configuration/ssl/) + +## Insecure SSL + +To disable *all* SSL certificate checks, and hostname verifications: + + export RD_INSECURE_SSL=true + +When enabled, a value of `RD_DEBUG=2` will also report SSL certificate +information. diff --git a/docs/ssl.md b/docs/ssl.md index 552e7ae2..5d45d6ad 100644 --- a/docs/ssl.md +++ b/docs/ssl.md @@ -5,13 +5,15 @@ title: SSL Configuration permalink: /configuration/ssl/ --- -# Setup SSL - To use a self-signed or custom server certificate for `rd`, you will need to do the following: 1. Import the certificate to a truststore/keystore 2. Set the JVM properties needed to use the truststore +(**Note**: if you want to skip the rigamarole, and simply accept *all* +SSL certificates without verification, +see [Configuration - Insecure SSL](({{site.url}}{{site.baseurl}}/configuration/#insecure-ssl)) + ## 1. Import the certificate You can get the server certificate in many ways, (e.g. connect to the server in a diff --git a/src/main/java/org/rundeck/client/Rundeck.java b/src/main/java/org/rundeck/client/Rundeck.java index 5486ed90..c546e097 100644 --- a/src/main/java/org/rundeck/client/Rundeck.java +++ b/src/main/java/org/rundeck/client/Rundeck.java @@ -10,8 +10,13 @@ import retrofit2.converter.jackson.JacksonConverterFactory; import retrofit2.converter.simplexml.SimpleXmlConverterFactory; +import javax.net.ssl.*; import java.net.CookieManager; import java.net.CookiePolicy; +import java.security.GeneralSecurityException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -24,6 +29,8 @@ public class Rundeck { public static final int API_VERS = 16; public static final Pattern API_VERS_PATTERN = Pattern.compile("^(.*)(/api/(\\d+)/?)$"); public static final String ENV_BYPASS_URL = "RD_BYPASS_URL"; + public static final String ENV_INSECURE_SSL = "RD_INSECURE_SSL"; + public static final int INSECURE_SSL_LOGGING = 2; /** * Create a client using the specified, or default version @@ -102,6 +109,10 @@ public static Client client( .addInterceptor(new StaticHeaderInterceptor("User-Agent", USER_AGENT)); String bypassUrl = System.getProperty("rundeck.client.bypass.url", System.getenv(ENV_BYPASS_URL)); + boolean insecureSsl = Boolean.parseBoolean(System.getProperty( + "rundeck.client.insecure.ssl", + System.getenv(ENV_INSECURE_SSL) + )); if (null != bypassUrl) { //fix redirects to external Rundeck URL by rewriting as to the baseurl @@ -110,6 +121,10 @@ public static Client client( bypassUrl )); } + + if (insecureSsl) { + addInsecureSsl(callFactory, httpLogging); + } if (httpLogging > 0) { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); @@ -210,6 +225,10 @@ public static Client client( )); String bypassUrl = System.getProperty("rundeck.client.bypass.url", System.getenv(ENV_BYPASS_URL)); + boolean insecureSsl = Boolean.parseBoolean(System.getProperty( + "rundeck.client.insecure.ssl", + System.getenv(ENV_INSECURE_SSL) + )); if (null != bypassUrl) { //fix redirects to external Rundeck URL by rewriting as to the baseurl @@ -218,6 +237,9 @@ public static Client client( normalizeUrlPath(bypassUrl) )); } + if (insecureSsl) { + addInsecureSsl(callFactory, httpLogging); + } if (httpLogging > 0) { HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); @@ -251,6 +273,60 @@ public static Client client( return new Client<>(build.create(RundeckApi.class), build, usedApiVers); } + private static void addInsecureSsl(final OkHttpClient.Builder callFactory, int logging) { + + X509TrustManager trustManager; + SSLSocketFactory sslSocketFactory; + try { + trustManager = createInsecureSslTrustManager(logging); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{trustManager}, null); + sslSocketFactory = sslContext.getSocketFactory(); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + callFactory.sslSocketFactory(sslSocketFactory, trustManager); + callFactory.hostnameVerifier((hostname, session) -> { + if (logging >= INSECURE_SSL_LOGGING) { + System.err.println("INSECURE_SSL:hostnameVerifier: trust host: " + hostname); + } + return true; + }); + } + + private static X509TrustManager createInsecureSslTrustManager(int logging) + throws GeneralSecurityException + { + return new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + X509Certificate[] cArrr = new X509Certificate[0]; + return cArrr; + } + + @Override + public void checkServerTrusted( + final X509Certificate[] chain, + final String authType + ) throws CertificateException + { + if (logging >= INSECURE_SSL_LOGGING) { + System.err.printf("INSECURE_SSL:TrustManager:checkServerTrusted: %s: chain: %s%n", authType, + Arrays.toString(chain) + ); + } + } + + @Override + public void checkClientTrusted( + final X509Certificate[] chain, + final String authType + ) throws CertificateException + { + } + }; + } + /** * @param baseUrl input url * @param apiVers api VERSION to append if /api/VERS is not present diff --git a/src/main/java/org/rundeck/client/tool/Main.java b/src/main/java/org/rundeck/client/tool/Main.java index 2f8b528a..d925ff3a 100644 --- a/src/main/java/org/rundeck/client/tool/Main.java +++ b/src/main/java/org/rundeck/client/tool/Main.java @@ -25,6 +25,8 @@ import java.util.Random; import java.util.function.Function; +import static org.rundeck.client.Rundeck.ENV_INSECURE_SSL; + /** * Entrypoint for commandline @@ -61,9 +63,13 @@ private static void setupFormat(final ToolBelt belt, AppConfig config) { representer.addClassTag(Execution.class, Tag.MAP); belt.formatter(new YamlFormatter(representer, dumperOptions)); belt.channels().infoEnabled(false); + belt.channels().warningEnabled(false); + belt.channels().errorEnabled(false); } else if ("json".equalsIgnoreCase(config.get("RD_FORMAT"))) { belt.formatter(new JsonFormatter()); belt.channels().infoEnabled(false); + belt.channels().warningEnabled(false); + belt.channels().errorEnabled(false); } else { NiceFormatter formatter = new NiceFormatter(null); formatter.setCollectionIndicator(""); @@ -96,6 +102,15 @@ public static Tool tool(final String name, final Rd rd) { .commandInput(new JewelInput()); setupColor(belt, rd); setupFormat(belt, rd); + + boolean insecureSsl = Boolean.parseBoolean(System.getProperty( + "rundeck.client.insecure.ssl", + System.getenv(ENV_INSECURE_SSL) + )); + if (insecureSsl) { + belt.finalOutput().warning( + "# WARNING: RD_INSECURE_SSL=true, no hostname or certificate trust verification will be performed"); + } return belt.buckle(); }