com.amazon.redshift.plugin.OktaCredentialsProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redshift-jdbc42 Show documentation
Show all versions of redshift-jdbc42 Show documentation
Java JDBC 4.2 (JRE 8+) driver for Redshift database
package com.amazon.redshift.plugin;
import com.amazon.redshift.logger.LogLevel;
import com.amazon.redshift.logger.RedshiftLogger;
import com.amazonaws.SdkClientException;
import com.amazonaws.util.IOUtils;
import com.amazonaws.util.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
public class OktaCredentialsProvider extends SamlCredentialsProvider
{
private static final String KEY_APP_URL = "app_id";
private static final String KEY_APP_NAME = "app_name";
protected String m_app_id;
protected String m_app_name;
@Override
public void addParameter(String key, String value)
{
super.addParameter(key, value);
if (KEY_APP_URL.equalsIgnoreCase(key))
{
m_app_id = value;
}
if (KEY_APP_NAME.equalsIgnoreCase(key))
{
m_app_name = value;
}
}
@Override
public String getPluginSpecificCacheKey() {
return ((m_app_id != null) ? m_app_id : "")
+ ((m_app_name != null) ? m_app_name : "")
;
}
@Override
protected String getSamlAssertion() throws IOException
{
checkRequiredParameters();
if (StringUtils.isNullOrEmpty(m_app_id))
{
throw new IOException("Missing required property: " + KEY_APP_URL);
}
CloseableHttpClient httpClient = null;
try
{
httpClient = getHttpClient();
String strOktaSessionToken = oktaAuthentication(httpClient);
return handleSamlAssertion(httpClient, strOktaSessionToken);
}
catch (GeneralSecurityException e)
{
throw new SdkClientException("Failed create SSLContext.", e);
}
finally
{
IOUtils.closeQuietly(httpClient, null);
}
}
/**
* Authenticates users credentials via Okta, return Okta session token.
*/
private String oktaAuthentication(CloseableHttpClient httpClient) throws IOException
{
CloseableHttpResponse responseAuthenticate = null;
try
{
ObjectMapper mapper = new ObjectMapper();
//HTTP Post request to Okta API for session token
String uri = "https://" + m_idpHost + "/api/v1/authn";
if (RedshiftLogger.isEnable())
m_log.logDebug("uri: {0}", uri);
validateURL(uri);
HttpPost httpost = new HttpPost(uri);
httpost.addHeader("Accept", "application/json");
httpost.addHeader("Content-Type", "application/json");
httpost.addHeader("Cache-Control", "no-cache");
//construction of JSON request
Map creds = new HashMap();
creds.put("username", m_userName);
creds.put("password", m_password);
StringWriter writer = new StringWriter();
mapper.writeValue(writer, creds);
StringEntity entity = new StringEntity(writer.toString(), "UTF-8");
entity.setContentType("application/json");
httpost.setEntity(entity);
responseAuthenticate = httpClient.execute(httpost);
String content = EntityUtils.toString(responseAuthenticate.getEntity());
if(RedshiftLogger.isEnable()) {
String maskedContent = content.replaceAll(getRegexForJsonKey("sessionToken"), "$1***masked***\"");
maskedContent = maskedContent.replaceAll(getRegexForJsonKey("id"), "$1***masked***\"");
maskedContent = maskedContent.replaceAll(getRegexForJsonKey("passwordChanged"), "$1***masked***\"");
m_log.log(LogLevel.DEBUG, "oktaAuthentication https response:" + maskedContent);
}
StatusLine statusLine = responseAuthenticate.getStatusLine();
int requestStatus = statusLine.getStatusCode();
if (requestStatus != 200)
{
throw new IOException(statusLine.getReasonPhrase());
}
//Retrieve and parse the Okta response for session token
JsonNode json = mapper.readTree(content);
if ("SUCCESS".equals(json.get("status").asText()))
{
return json.get("sessionToken").asText();
}
throw new IOException("No session token in the response.");
}
finally
{
IOUtils.closeQuietly(responseAuthenticate, null);
}
}
/**
* Retrieves SAML assertion from Okta containing AWS roles.
*/
private String handleSamlAssertion(CloseableHttpClient httpClient, String oktaSessionToken) throws IOException
{
// If no value was specified for m_app_name, use the current default.
if (StringUtils.isNullOrEmpty(m_app_name))
{
m_app_name = "amazon_aws";
}
else
{
// Ensure that the string is properly encoded.
m_app_name = URLEncoder.encode(m_app_name, "UTF-8");
}
String oktaAWSAppUrl = "https://" + m_idpHost + "/home/" + m_app_name + "/" + m_app_id;
String oktaAWSAppUrlWithToken = oktaAWSAppUrl + "?onetimetoken=" + oktaSessionToken;
if (RedshiftLogger.isEnable())
m_log.logDebug("oktaAWSAppUrl: {0}", oktaAWSAppUrl);
validateURL(oktaAWSAppUrlWithToken);
HttpGet httpget = new HttpGet(oktaAWSAppUrlWithToken);
CloseableHttpResponse responseSAML = httpClient.execute(httpget);
int requestStatus = responseSAML.getStatusLine().getStatusCode();
if (requestStatus != 200)
{
throw new RuntimeException("Failed : HTTP error code : " + responseSAML.getStatusLine().getStatusCode()
+ " : Reason : " + responseSAML.getStatusLine().getReasonPhrase());
}
String body = EntityUtils.toString(responseSAML.getEntity());
if (RedshiftLogger.isEnable())
m_log.logDebug("body: {0}", sanitizeResponseBody(body));
for (String inputTags : getInputTagsfromHTML(body)) {
String name = getValueByKey(inputTags, "name");
String value = getValueByKey(inputTags, "value");
if (RedshiftLogger.isEnable())
m_log.logDebug("name: {0}", name);
if ("SAMLResponse".equalsIgnoreCase(name))
{
return value.replace("+", "+").replace("=", "=");
}
}
throw new IOException("Failed to retrieve SAMLAssertion.");
}
private String sanitizeResponseBody(String response) {
Document document = Jsoup.parse(response);
// find input tag with name as SAMLResponse
Element samlElement = document.selectFirst("input[name=SAMLResponse]");
if(samlElement != null) {
samlElement.val("***masked***"); // mask the value attribute for this element
return document.toString();
}
return response;
}
}