Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import all certificates from propagated bundles into Keycloak's truststore #560

Merged
merged 1 commit into from
Dec 9, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 64 additions & 12 deletions pkg/deploy/identity-provider/deployment_keycloak.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ func getSpecKeycloakDeployment(
cheCertSecretVersion := getSecretResourceVersion("self-signed-certificate", deployContext.CheCluster.Namespace, deployContext.ClusterAPI)
openshiftApiCertSecretVersion := getSecretResourceVersion("openshift-api-crt", deployContext.CheCluster.Namespace, deployContext.ClusterAPI)

// holds bash functions that should be available when run init commands in shell
bashFunctions := ""

// add various certificates to Java trust store so that Keycloak can connect to OpenShift API
// certificate that OpenShift router uses (for 4.0 only)
addRouterCrt := "if [ ! -z \"${CHE_SELF__SIGNED__CERT}\" ]; then echo \"${CHE_SELF__SIGNED__CERT}\" > " + jbossDir + "/che.crt && " +
Expand Down Expand Up @@ -153,9 +156,10 @@ func getSpecKeycloakDeployment(
}
addCustomPublicCertsCommand := "if [[ -d \"" + customPublicCertsDir + "\" && -n \"$(find " + customPublicCertsDir + " -type f)\" ]]; then " +
"for certfile in " + customPublicCertsDir + "/* ; do " +
"keytool -importcert -alias CERT_$(basename $certfile) -keystore " + jbossDir + "/openshift.jks -file $certfile -storepass " + trustpass + " -noprompt; " +
"jks_import_ca_bundle $certfile " + jbossDir + "/openshift.jks " + trustpass + " ; " +
"done; fi"

bashFunctions += getImportCABundleScript()
addCertToTrustStoreCommand := addRouterCrt + " && " + addOpenShiftAPICrt + " && " + addMountedCrt + " && " + addMountedServiceCrt + " && " + importJavaCacerts + " && " + addCustomPublicCertsCommand

// upstream Keycloak has a bit different mechanism of adding jks
Expand Down Expand Up @@ -493,27 +497,27 @@ func getSpecKeycloakDeployment(
}
hostname := keycloakURL.Hostname()
enableFixedHostNameProvider = " && echo 'Use fixed hostname provider to make working internal network requests' && " +
"echo -e \"embed-server --server-config=standalone.xml --std-out=echo \n" +
"/subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value=\"fixed\") \n" +
"/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.hostname,value=\"" + hostname + "\") \n"
"echo -e \"embed-server --server-config=standalone.xml --std-out=echo \n" +
"/subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value=\"fixed\") \n" +
"/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.hostname,value=\"" + hostname + "\") \n"
if deployContext.CheCluster.Spec.Server.TlsSupport {
enableFixedHostNameProvider += "/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.httpsPort,value=\"443\") \n" +
"/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.alwaysHttps,value=\"true\") \n"
enableFixedHostNameProvider += "/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.httpsPort,value=\"443\") \n" +
"/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.alwaysHttps,value=\"true\") \n"
} else {
enableFixedHostNameProvider += "/subsystem=keycloak-server/spi=hostname/provider=fixed:write-attribute(name=properties.httpPort,value=\"80\") \n"
}
enableFixedHostNameProvider += "stop-embedded-server\" > " + jbossDir + "/use_fixed_hostname_provider.cli && " +
jbossCli + " --file=" + jbossDir + "/use_fixed_hostname_provider.cli "
enableFixedHostNameProvider += "stop-embedded-server\" > " + jbossDir + "/use_fixed_hostname_provider.cli && " +
jbossCli + " --file=" + jbossDir + "/use_fixed_hostname_provider.cli "
}
if cheFlavor == "codeready" {
keycloakEnv = append(keycloakEnv, corev1.EnvVar{
Name: "KEYCLOAK_FRONTEND_URL",
Name: "KEYCLOAK_FRONTEND_URL",
Value: deployContext.CheCluster.Status.KeycloakURL + "/auth",
});
})
}
}

command := addCertToTrustStoreCommand + addProxyCliCommand + applyProxyCliCommand + " && " + changeConfigCommand + enableFixedHostNameProvider +
command := bashFunctions + "\n" + addCertToTrustStoreCommand + addProxyCliCommand + applyProxyCliCommand + " && " + changeConfigCommand + enableFixedHostNameProvider +
" && /opt/jboss/docker-entrypoint.sh --debug -b 0.0.0.0 -c standalone.xml"
command += " -Dkeycloak.profile.feature.token_exchange=enabled -Dkeycloak.profile.feature.admin_fine_grained_authz=enabled"
if cheFlavor == "codeready" {
Expand All @@ -530,7 +534,7 @@ func getSpecKeycloakDeployment(
"pattern=\"[a-z]([-a-z0-9]{0,61}[a-z0-9])?\" " +
"title=\"Username has to comply with the DNS naming convention. An alphanumeric (a-z, and 0-9) string, with a maximum length of 63 characters, with the '-' character allowed anywhere except the first or last character.\" " +
"name=\"username\"|g' ${baseTemplate}"
command = addUsernameReadonlyTheme + " && " + addUsernameValidationForKeycloakTheme + " && " + addCertToTrustStoreCommand + addProxyCliCommand + applyProxyCliCommand +
command = bashFunctions + "\n" + addUsernameReadonlyTheme + " && " + addUsernameValidationForKeycloakTheme + " && " + addCertToTrustStoreCommand + addProxyCliCommand + applyProxyCliCommand +
" && echo \"feature.token_exchange=enabled\nfeature.admin_fine_grained_authz=enabled\" > /opt/eap/standalone/configuration/profile.properties " +
" && sed -i 's/WILDCARD/ANY/g' /opt/eap/bin/launch/keycloak-spi.sh && /opt/eap/bin/openshift-launch.sh -b 0.0.0.0"
}
Expand Down Expand Up @@ -714,3 +718,51 @@ func ProvisionKeycloakResources(deployContext *deploy.DeployContext) error {
_, err = util.K8sclient.ExecIntoPod(podToExec, keycloakProvisionCommand, "create realm, client and user", deployContext.CheCluster.Namespace)
return err
}

// getImportCABundleScript returns string which contains bash function that imports ca-bundle into jks
// The function has three arguments:
// - absolute path to ca-bundle file
// - absolute path to java keystore
// - java keystore password
func getImportCABundleScript() string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you manage the case where there are duplicate certificates (I assume we should be robust and ignore this error) ?
What about other errors when importing a certificate, Do we expect them to fail the start of the server ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you manage the case where there are duplicate certificates (I assume we should be robust and ignore this error) ?

When adding a certificate into trust store we pass unique alias for it. So if user provides identical certificates - both will be added to trust store without any error.

What about other errors when importing a certificate, Do we expect them to fail the start of the server ?

Invalid certificates will not be imported, Keycloak and Che server will be started without invalid certificates

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation.

return `
function jks_import_ca_bundle {
CA_FILE=$1
KEYSTORE_PATH=$2
KEYSTORE_PASSWORD=$3

if [ ! -f $CA_FILE ]; then
# CA bundle file doesn't exist, skip it
echo "Failed to import CA certificates from ${CA_FILE}. File doesn't exist"
return
fi

bundle_name=$(basename $CA_FILE)
certs_imported=0
cert_index=0
tmp_file=/tmp/cert.pem
is_cert=false
while IFS= read -r line; do
if [ "$line" == "-----BEGIN CERTIFICATE-----" ]; then
# Start copying a new certificate
is_cert=true
cert_index=$((cert_index+1))
# Reset destination file and add header line
echo $line > ${tmp_file}
elif [ "$line" == "-----END CERTIFICATE-----" ]; then
# End of the certificate is reached, add it to trust store
is_cert=false
echo $line >> ${tmp_file}
keytool -importcert -alias "${bundle_name}_${cert_index}" -keystore $KEYSTORE_PATH -file $tmp_file -storepass $KEYSTORE_PASSWORD -noprompt && \
certs_imported=$((certs_imported+1))
elif [ "$is_cert" == true ]; then
# In the middle of a certificate, copy line to target file
echo $line >> ${tmp_file}
fi
done < "$CA_FILE"
echo "Imported ${certs_imported} certificates from ${CA_FILE}"
# Clean up
rm -f $tmp_file
}
`
}