{"record":{"id":"6d13e08b94be3cdb","repo":"SonarSource/sonarqube","slug":"failed-to-validate-configuration-check-url-and-pr","errorCode":null,"errorMessage":"Failed to validate configuration, check URL and Private Key","messagePattern":"Failed to validate configuration, check URL and Private Key","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java","lineNumber":224,"sourceCode":"      throw new IllegalArgumentException(\"Missing permissions; permission granted on \" + message);\n    }\n  }\n\n  @Override\n  public List<String> findMissingAppPermissions(GithubAppConfiguration githubAppConfiguration, Map<String, String> permissions) {\n    return computeMissingPermissions(permissions, getAppPermissions(githubAppConfiguration));\n  }\n\n  private Map<String, String> getAppPermissions(GithubAppConfiguration githubAppConfiguration) {\n    AppToken appToken = appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey());\n\n    String endPoint = \"/app\";\n    GetResponse response;\n    try {\n      response = githubApplicationHttpClient.get(githubAppConfiguration.getApiEndpoint(), appToken, endPoint);\n    } catch (IOException e) {\n      LOG.warn(FAILED_TO_REQUEST_BEGIN_MSG + githubAppConfiguration.getApiEndpoint() + endPoint, e);\n      throw new IllegalArgumentException(\"Failed to validate configuration, check URL and Private Key\");\n    }\n    if (response.getCode() == HTTP_OK) {\n      return handleResponse(response, endPoint, GsonApp.class)\n        .map(GsonApp::getPermissions)\n        .orElseThrow(() -> new IllegalArgumentException(\"Failed to get app permissions, unexpected response body\"));\n    } else if (response.getCode() == HTTP_UNAUTHORIZED || response.getCode() == HTTP_FORBIDDEN) {\n      throw new IllegalArgumentException(\"Authentication failed, verify the Client Id, Client Secret and Private Key fields\");\n    } else {\n      throw new IllegalArgumentException(\"Failed to check permissions with Github, check the configuration\");\n    }\n  }\n\n  private static List<String> computeMissingPermissions(Map<String, String> requiredPermissions, Map<String, String> grantedPermissions) {\n    return requiredPermissions.entrySet().stream()\n      .filter(permission -> !Objects.equals(permission.getValue(), grantedPermissions.get(permission.getKey())))\n      .map(Map.Entry::getKey)\n      // sorted for a deterministic message: REQUIRED_PERMISSIONS is a Map.of, whose iteration order is randomized per JVM\n      .sorted()","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java#L206-L242","documentation":"getAppPermissions() performs an authenticated GET /app request to GitHub using a JWT built from the app's private key. If the HTTP call throws an IOException (connection failure, DNS error, TLS problem), this IllegalArgumentException is thrown telling you the URL or Private Key is likely wrong. The original IOException is logged at WARN level with the endpoint.","triggerScenarios":"Calling getAppPermissions (via validateConfig or findMissingAppPermissions) when githubApplicationHttpClient.get() raises IOException: unreachable host, wrong port, TLS handshake failure, or a malformed private key that prevents creating a valid app JWT.","commonSituations":"Private Key field contains the raw .pem with wrong formatting/encoding; wrong api endpoint URL or hostname typo; firewall/proxy blocking outbound HTTPS to GitHub; private key not matching the app's registered key (e.g. old rotated key).","solutions":["Check the api endpoint URL is correct and reachable (curl it from the SonarQube host)","Re-paste the GitHub App private key exactly as downloaded from the .pem file, including BEGIN/END lines","Verify the private key belongs to the configured App ID (regenerating a key invalidates old ones)","Check network/proxy/firewall allows outbound HTTPS from the SonarQube server","Inspect the server log for the WARN line with the underlying IOException for the root cause"],"exampleFix":"// before (key without headers, breaks JWT signing)\nMIIEvQIBADANBgkqh...\n// after (full PEM contents of the .pem file)\n-----BEGIN RSA PRIVATE KEY-----\nMIIEvQIBADANBgkqh...\n-----END RSA PRIVATE KEY-----","handlingStrategy":"try-catch","validationCode":"try {\n  new URL(apiEndpoint).openConnection().connect(); // or an HTTP HEAD check\n} catch (IOException e) {\n  throw new IllegalStateException(\"GitHub endpoint unreachable from this host: \" + apiEndpoint, e);\n}\n// also verify the private key parses:\ntry {\n  KeyFactory.getInstance(\"RSA\").generatePrivate(new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(pemBody)));\n} catch (GeneralSecurityException e) {\n  throw new IllegalStateException(\"Private key is malformed\", e);\n}","typeGuard":null,"tryCatchPattern":"try {\n  githubApplicationClient.validateConfig(config);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().startsWith(\"Failed to validate configuration\")) {\n    // check server WARN log for the underlying IOException; verify URL connectivity and key format\n  }\n}","preventionTips":["Store the private key as the complete .pem file contents including BEGIN/END markers","Verify outbound HTTPS connectivity to the endpoint from the SonarQube host before configuring","Re-copy the key after regenerating it in GitHub App settings","Check the WARN log entry for the root IOException — it names the real cause"],"tags":["github","authentication","network","configuration","ioexception"],"backgroundTag":"http-request-failed","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}