Java tutorial
/*! * Copyright (c) 2016, Okta, Inc. and/or its affiliates. All rights reserved. * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.") * * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and limitations under the License. */ package com.okta.tools; import com.amazonaws.AmazonClientException; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicSessionCredentials; import com.amazonaws.auth.profile.ProfilesConfigFile; import com.amazonaws.services.identitymanagement.model.GetPolicyRequest; import com.amazonaws.services.identitymanagement.model.GetPolicyResult; import com.amazonaws.services.identitymanagement.model.GetPolicyVersionRequest; import com.amazonaws.services.identitymanagement.model.GetPolicyVersionResult; import com.amazonaws.services.identitymanagement.model.GetRoleRequest; import com.amazonaws.services.identitymanagement.model.GetRoleResult; import com.amazonaws.services.identitymanagement.model.Policy; import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; import com.amazonaws.services.securitytoken.model.AssumeRoleWithSAMLRequest; import com.amazonaws.services.securitytoken.model.AssumeRoleWithSAMLResult; import com.amazonaws.services.identitymanagement.*; import com.amazonaws.services.identitymanagement.model.*; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.okta.sdk.clients.AuthApiClient; import com.okta.sdk.clients.FactorsApiClient; import com.okta.sdk.clients.UserApiClient; import com.okta.sdk.exceptions.ApiException; import com.okta.sdk.framework.ApiClientConfiguration; import com.okta.sdk.models.auth.AuthResult; import com.okta.sdk.models.factors.Factor; import org.apache.commons.codec.binary.Base64; import org.apache.http.client.ClientProtocolException; 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.impl.client.HttpClients; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.*; import java.net.MalformedURLException; import java.net.URLDecoder; import java.net.UnknownHostException; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.*; import java.nio.charset.*; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; //Amazon SDK namespaces //Okta SDK namespaces public class awscli { //User specific variables private static String oktaOrg = ""; private static String oktaAWSAppURL = ""; private static String awsIamKey = null; private static String awsIamSecret = null; private static AuthApiClient authClient; private static final String DefaultProfileName = "default"; private static FactorsApiClient factorClient; private static UserApiClient userClient; private static String userId; private static String crossAccountRoleName = null; private static String roleToAssume; //the ARN of the role the user wants to eventually assume (not the cross-account role, the "real" role in the target account) private static int selectedPolicyRank; //the zero-based rank of the policy selected in the selected cross-account role (in case there is more than one policy tied to the current policy) private static final Logger logger = LogManager.getLogger(awscli.class); public static void main(String[] args) throws Exception { awsSetup(); extractCredentials(); // Step #1: Initiate the authentication and capture the SAML assertion. CloseableHttpClient httpClient = null; String resultSAML = ""; try { String strOktaSessionToken = oktaAuthntication(); if (!strOktaSessionToken.equalsIgnoreCase("")) //Step #2 get SAML assertion from Okta resultSAML = awsSamlHandler(strOktaSessionToken); } catch (MalformedURLException e) { e.printStackTrace(); } catch (UnknownHostException e) { logger.error( "\nUnable to establish a connection with AWS. \nPlease verify that your OKTA_AWS_APP_URL parameter is correct and try again"); System.exit(0); } catch (ClientProtocolException e) { logger.error("\nNo Org found, please specify an OKTA_ORG parameter in your config.properties file"); System.exit(0); } catch (IOException e) { e.printStackTrace(); } // Step #3: Assume an AWS role using the SAML Assertion from Okta AssumeRoleWithSAMLResult assumeResult = assumeAWSRole(resultSAML); com.amazonaws.services.securitytoken.model.AssumedRoleUser aru = assumeResult.getAssumedRoleUser(); String arn = aru.getArn(); // Step #4: Get the final role to assume and update the config file to add it to the user's profile GetRoleToAssume(crossAccountRoleName); logger.trace("Role to assume ARN: " + roleToAssume); // Step #5: Write the credentials to ~/.aws/credentials String profileName = setAWSCredentials(assumeResult, arn); UpdateConfigFile(profileName, roleToAssume); UpdateConfigFile(DefaultProfileName, roleToAssume); // Print Final message resultMessage(profileName); } /* Authenticates users credentials via Okta, return Okta session token * Postcondition: returns String oktaSessionToken * */ private static String oktaAuthntication() throws ClientProtocolException, JSONException, IOException { CloseableHttpResponse responseAuthenticate = null; int requestStatus = 0; //Redo sequence if response from AWS doesn't return 200 Status while (requestStatus != 200) { // Prompt for user credentials System.out.print("Username: "); Scanner scanner = new Scanner(System.in); String oktaUsername = scanner.next(); Console console = System.console(); String oktaPassword = null; if (console != null) { oktaPassword = new String(console.readPassword("Password: ")); } else { // hack to be able to debug in an IDE System.out.print("Password: "); oktaPassword = scanner.next(); } responseAuthenticate = authnticateCredentials(oktaUsername, oktaPassword); requestStatus = responseAuthenticate.getStatusLine().getStatusCode(); authnFailHandler(requestStatus, responseAuthenticate); } //Retrieve and parse the Okta response for session token BufferedReader br = new BufferedReader( new InputStreamReader((responseAuthenticate.getEntity().getContent()))); String outputAuthenticate = br.readLine(); JSONObject jsonObjResponse = new JSONObject(outputAuthenticate); responseAuthenticate.close(); if (jsonObjResponse.getString("status").equals("MFA_REQUIRED")) { return mfa(jsonObjResponse); } else { return jsonObjResponse.getString("sessionToken"); } } /*Uses user's credentials to obtain Okta session Token */ private static CloseableHttpResponse authnticateCredentials(String username, String password) throws JSONException, ClientProtocolException, IOException { HttpPost httpost = null; CloseableHttpClient httpClient = HttpClients.createDefault(); //HTTP Post request to Okta API for session token httpost = new HttpPost("https://" + oktaOrg + "/api/v1/authn"); httpost.addHeader("Accept", "application/json"); httpost.addHeader("Content-Type", "application/json"); httpost.addHeader("Cache-Control", "no-cache"); //construction of request JSON JSONObject jsonObjRequest = new JSONObject(); jsonObjRequest.put("username", username); jsonObjRequest.put("password", password); StringEntity entity = new StringEntity(jsonObjRequest.toString(), StandardCharsets.UTF_8); entity.setContentType("application/json"); httpost.setEntity(entity); return httpClient.execute(httpost); } /* creates required AWS credential file if necessary" */ private static void awsSetup() throws FileNotFoundException, UnsupportedEncodingException { //check if credentials file has been created File f = new File(System.getProperty("user.home") + "/.aws/credentials"); //creates credentials file if it doesn't exist yet if (!f.exists()) { f.getParentFile().mkdirs(); PrintWriter writer = new PrintWriter(f, "UTF-8"); writer.println("[default]"); writer.println("aws_access_key_id="); writer.println("aws_secret_access_key="); writer.close(); } f = new File(System.getProperty("user.home") + "/.aws/config"); //creates credentials file if it doesn't exist yet if (!f.exists()) { f.getParentFile().mkdirs(); PrintWriter writer = new PrintWriter(f, "UTF-8"); writer.println("[profile default]"); writer.println("output = json"); writer.println("region = us-east-1"); writer.close(); } } /* Parses application's config file for app URL and Okta Org */ private static void extractCredentials() throws IOException { //BufferedReader oktaBr = new BufferedReader(new FileReader(new File (System.getProperty("user.dir")) +"/oktaAWSCLI.config")); //RL, 2016-02-25, moving to properties file String strLocalFolder = System.getProperty("user.dir"); File propertiesFile = new File("config.properties"); FileReader reader = new FileReader(propertiesFile); Properties props = new Properties(); props.load(reader); //Properties configFile = new Properties(); //configFile.load(this.getClass().getClassLoader().getResourceAsStream("/config.properties")); //extract oktaOrg and oktaAWSAppURL from Okta settings file oktaOrg = props.getProperty("OKTA_ORG"); oktaAWSAppURL = props.getProperty("OKTA_AWS_APP_URL"); awsIamKey = props.getProperty("AWS_IAM_KEY"); awsIamSecret = props.getProperty("AWS_IAM_SECRET"); /* String line = oktaBr.readLine(); while(line!=null){ if(line.contains("OKTA_ORG")){ oktaOrg = line.substring(line.indexOf("=")+1).trim(); } else if( line.contains("OKTA_AWS_APP_URL")){ oktaAWSAppURL = line.substring(line.indexOf("=")+1).trim(); } line = oktaBr.readLine(); } oktaBr.close();*/ } /*Uses user's credentials to obtain Okta session Token */ private static AuthResult authenticateCredentials(String username, String password) throws ApiException, JSONException, ClientProtocolException, IOException { ApiClientConfiguration oktaSettings = new ApiClientConfiguration("https://" + oktaOrg, ""); AuthResult result = null; authClient = new AuthApiClient(oktaSettings); userClient = new UserApiClient(oktaSettings); factorClient = new FactorsApiClient(oktaSettings); // Check if the user credentials are valid result = authClient.authenticate(username, password, ""); // The result has a getStatus method which is a string of status of the request. // Example - SUCCESS for successful authentication String status = result.getStatus(); return result; } /*Handles possible authentication failures */ private static void authnFailHandler(int requestStatus, CloseableHttpResponse response) { //invalid creds if (requestStatus == 400 || requestStatus == 401) { logger.error("You provided invalid credentials, please run this program again."); } else if (requestStatus == 500) { //failed connection establishment logger.error("\nUnable to establish connection with: " + oktaOrg + " \nPlease verify that your Okta org url is correct and try again"); System.exit(0); } else if (requestStatus != 200) { //other throw new RuntimeException("Failed : HTTP error code : " + response.getStatusLine().getStatusCode()); } } /*Handles possible AWS assertion retrieval errors */ private static void samlFailHandler(int requestStatus, CloseableHttpResponse responseSAML) throws UnknownHostException { if (responseSAML.getStatusLine().getStatusCode() == 500) { //incorrectly formatted app url throw new UnknownHostException(); } else if (responseSAML.getStatusLine().getStatusCode() != 200) { //other throw new RuntimeException( "Failed : HTTP error code : " + responseSAML.getStatusLine().getStatusCode()); } } /* Handles user selection prompts */ private static int numSelection(int max) { Scanner scanner = new Scanner(System.in); int selection = -1; while (selection == -1) { //prompt user for selection System.out.print("Selection: "); String selectInput = scanner.nextLine(); try { selection = Integer.parseInt(selectInput) - 1; if (selection >= max) { InputMismatchException e = new InputMismatchException(); throw e; } } catch (InputMismatchException e) { //raised by something other than a number entered logger.error("Invalid input: Please enter a number corresponding to a role \n"); selection = -1; } catch (NumberFormatException e) { //raised by number too high or low selected logger.error("Invalid input: Please enter in a number \n"); selection = -1; } } return selection; } /* Retrieves SAML assertion from Okta containing AWS roles */ private static String awsSamlHandler(String oktaSessionToken) throws ClientProtocolException, IOException { HttpGet httpget = null; CloseableHttpResponse responseSAML = null; CloseableHttpClient httpClient = HttpClients.createDefault(); String resultSAML = ""; String outputSAML = ""; // Part 2: Get the Identity Provider and Role ARNs. // Request for AWS SAML response containing roles httpget = new HttpGet(oktaAWSAppURL + "?onetimetoken=" + oktaSessionToken); responseSAML = httpClient.execute(httpget); samlFailHandler(responseSAML.getStatusLine().getStatusCode(), responseSAML); //Parse SAML response BufferedReader brSAML = new BufferedReader(new InputStreamReader((responseSAML.getEntity().getContent()))); //responseSAML.close(); while ((outputSAML = brSAML.readLine()) != null) { if (outputSAML.contains("SAMLResponse")) { resultSAML = outputSAML.substring(outputSAML.indexOf("value=") + 7, outputSAML.indexOf("/>") - 1); break; } } httpClient.close(); return resultSAML; } /* Assumes SAML role selected by the user based on authorized Okta AWS roles given in SAML assertion result SAML * Precondition: String resultSAML * Postcondition: returns type AssumeRoleWithSAMLResult */ private static AssumeRoleWithSAMLResult assumeAWSRole(String resultSAML) { // Decode SAML response resultSAML = resultSAML.replace("+", "+").replace("=", "="); String resultSAMLDecoded = new String(Base64.decodeBase64(resultSAML)); ArrayList<String> principalArns = new ArrayList<String>(); ArrayList<String> roleArns = new ArrayList<String>(); //When the app is not assigned to you no assertion is returned if (!resultSAMLDecoded.contains("arn:aws")) { logger.error("\nYou do not have access to AWS through Okta. \nPlease contact your administrator."); System.exit(0); } System.out.println("\nPlease choose the role you would like to assume: "); //Gather list of applicable AWS roles int i = 0; while (resultSAMLDecoded.indexOf("arn:aws") != -1) { /*Trying to parse the value of the Role SAML Assertion that typically looks like this: <saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string"> arn:aws:iam::[AWS-ACCOUNT-ID]:saml-provider/Okta,arn:aws:iam::[AWS-ACCOUNT-ID]:role/[ROLE_NAME] </saml2:AttributeValue> </saml2:Attribute> */ int start = resultSAMLDecoded.indexOf("arn:aws"); int end = resultSAMLDecoded.indexOf("</saml2:", start); String resultSAMLRole = resultSAMLDecoded.substring(start, end); String[] parts = resultSAMLRole.split(","); principalArns.add(parts[0]); roleArns.add(parts[1]); System.out.println("[ " + (i + 1) + " ]: " + roleArns.get(i)); resultSAMLDecoded = (resultSAMLDecoded .substring(resultSAMLDecoded.indexOf("</saml2:AttributeValue") + 1)); i++; } //Prompt user for role selection int selection = numSelection(roleArns.size()); String principalArn = principalArns.get(selection); String roleArn = roleArns.get(selection); crossAccountRoleName = roleArn.substring(roleArn.indexOf("/") + 1); logger.debug("Cross-account role is " + crossAccountRoleName); //creates empty AWS credentials to prevent the AWSSecurityTokenServiceClient object from unintentionally loading the previous profile we just created BasicAWSCredentials awsCreds = new BasicAWSCredentials("", ""); //use user credentials to assume AWS role AWSSecurityTokenServiceClient stsClient = new AWSSecurityTokenServiceClient(awsCreds); AssumeRoleWithSAMLRequest assumeRequest = new AssumeRoleWithSAMLRequest().withPrincipalArn(principalArn) .withRoleArn(roleArn).withSAMLAssertion(resultSAML).withDurationSeconds(3600); //default token duration to 12 hours return stsClient.assumeRoleWithSAML(assumeRequest); } private static void GetRoleToAssume(String roleName) { if (roleName != null && !roleName.equals("") && awsIamKey != null && awsIamSecret != null && !awsIamKey.equals("") && !awsIamSecret.equals("")) { logger.debug("Creating the AWS Identity Management client"); AmazonIdentityManagementClient identityManagementClient = new AmazonIdentityManagementClient( new BasicAWSCredentials(awsIamKey, awsIamSecret)); logger.debug("Getting role: " + roleName); GetRoleResult roleresult = identityManagementClient .getRole(new GetRoleRequest().withRoleName(roleName)); logger.debug("GetRoleResult: " + roleresult.toString()); Role role = roleresult.getRole(); logger.debug("getRole: " + role.toString()); ListAttachedRolePoliciesResult arpr = identityManagementClient .listAttachedRolePolicies(new ListAttachedRolePoliciesRequest().withRoleName(roleName)); logger.debug("ListAttachedRolePoliciesResult: " + arpr.toString()); ListRolePoliciesResult lrpr = identityManagementClient .listRolePolicies(new ListRolePoliciesRequest().withRoleName(roleName)); logger.debug("ListRolePoliciesResult: " + lrpr.toString()); List<String> inlinePolicies = lrpr.getPolicyNames(); if (inlinePolicies.size() == 0) { logger.debug("There are no inlines policies"); } List<AttachedPolicy> managedPolicies = arpr.getAttachedPolicies(); if (managedPolicies.size() == 0) { logger.debug("There are no managed policies"); } selectedPolicyRank = 0; //by default, we select the first policy if (managedPolicies.size() >= 1) //we prioritize managed policies over inline policies { if (managedPolicies.size() > 1) //if there's more than one policy, we're asking the user to select one of them { List<String> lstManagedPolicies = new ArrayList<String>(); for (AttachedPolicy managedPolicy : managedPolicies) { lstManagedPolicies.add(managedPolicy.getPolicyName()); } logger.debug("Managed Policies: " + managedPolicies.toString()); selectedPolicyRank = SelectPolicy(lstManagedPolicies); } AttachedPolicy attachedPolicy = managedPolicies.get(selectedPolicyRank); logger.debug("Selected policy " + attachedPolicy.toString()); GetPolicyRequest gpr = new GetPolicyRequest().withPolicyArn(attachedPolicy.getPolicyArn()); GetPolicyResult rpr = identityManagementClient.getPolicy(gpr); logger.debug("GetPolicyResult: " + attachedPolicy.toString()); Policy policy = rpr.getPolicy(); GetPolicyVersionResult pvr = identityManagementClient.getPolicyVersion(new GetPolicyVersionRequest() .withPolicyArn(policy.getArn()).withVersionId(policy.getDefaultVersionId())); logger.debug("GetPolicyVersionResult: " + pvr.toString()); String policyDoc = pvr.getPolicyVersion().getDocument(); roleToAssume = ProcessPolicyDocument(policyDoc); } else if (inlinePolicies.size() >= 1) //processing inline policies if we have no managed policies { logger.debug("Inline Policies " + inlinePolicies.toString()); if (inlinePolicies.size() > 1) { //ask the user to select one policy if there are more than one logger.debug("Inline Policies: " + inlinePolicies.toString()); selectedPolicyRank = SelectPolicy(inlinePolicies); } //Have to set the role name and the policy name (both are mandatory fields //TODO: handle more than 1 policy (ask the user to choose it?) GetRolePolicyRequest grpr = new GetRolePolicyRequest().withRoleName(roleName) .withPolicyName(inlinePolicies.get(selectedPolicyRank)); GetRolePolicyResult rpr = identityManagementClient.getRolePolicy(grpr); String policyDoc = rpr.getPolicyDocument(); roleToAssume = ProcessPolicyDocument(policyDoc); } } } private static int SelectPolicy(List<String> lstPolicies) { String strSelectedPolicy = null; System.out.println("\nPlease select a role policy: "); //Gather list of policies for the selected role int i = 1; for (String strPolicyName : lstPolicies) { System.out.println("[ " + i + " ]: " + strPolicyName); i++; } //Prompt user for policy selection int selection = numSelection(lstPolicies.size()); return selection; } private static String ProcessPolicyDocument(String policyDoc) { String strRoleToAssume = null; try { String policyDocClean = URLDecoder.decode(policyDoc, "UTF-8"); logger.debug("Clean Policy Document: " + policyDocClean); ObjectMapper objectMapper = new ObjectMapper(); try { JsonNode rootNode = objectMapper.readTree(policyDocClean); JsonNode statement = rootNode.path("Statement"); logger.debug("Statement node: " + statement.toString()); JsonNode resource = null; if (statement.isArray()) { logger.debug("Statement is array"); for (int i = 0; i < statement.size(); i++) { String action = statement.get(i).path("Action").textValue(); if (action != null && action.equals("sts:AssumeRole")) { resource = statement.get(i).path("Resource"); logger.debug("Resource node: " + resource.toString()); break; } } } else { logger.debug("Statement is NOT array"); if (statement.get("Action").textValue().equals("sts:AssumeRole")) { resource = statement.path("Resource"); logger.debug("Resource node: " + resource.toString()); } } if (resource != null) { if (resource.isArray()) { //if we're handling a policy with an array of AssumeRole attributes ArrayList<String> lstRoles = new ArrayList<String>(); for (final JsonNode node : resource) { lstRoles.add(node.asText()); } strRoleToAssume = SelectRole(lstRoles); } else { strRoleToAssume = resource.textValue(); logger.debug("Role to assume: " + roleToAssume); } } } catch (IOException ioe) { } } catch (UnsupportedEncodingException uee) { } return strRoleToAssume; } /* Prompts the user to select a role in case the role policy contains an array of roles instead of a single role */ private static String SelectRole(List<String> lstRoles) { String strSelectedRole = null; System.out.println("\nPlease select the role you want to assume: "); //Gather list of roles for the selected managed policy int i = 1; for (String strRoleName : lstRoles) { System.out.println("[ " + i + " ]: " + strRoleName); i++; } //Prompt user for policy selection int selection = numSelection(lstRoles.size()); if (selection < 0 && lstRoles.size() > selection) { System.out.println("\nYou entered an invalid number. Please try again."); return SelectRole(lstRoles); } strSelectedRole = lstRoles.get(selection); return strSelectedRole; } /* Retrieves AWS credentials from AWS's assumedRoleResult and write the to aws credential file * Precondition : AssumeRoleWithSAMLResult assumeResult */ private static String setAWSCredentials(AssumeRoleWithSAMLResult assumeResult, String credentialsProfileName) throws FileNotFoundException, UnsupportedEncodingException, IOException { BasicSessionCredentials temporaryCredentials = new BasicSessionCredentials( assumeResult.getCredentials().getAccessKeyId(), assumeResult.getCredentials().getSecretAccessKey(), assumeResult.getCredentials().getSessionToken()); String awsAccessKey = temporaryCredentials.getAWSAccessKeyId(); String awsSecretKey = temporaryCredentials.getAWSSecretKey(); String awsSessionToken = temporaryCredentials.getSessionToken(); if (credentialsProfileName.startsWith("arn:aws:sts::")) { credentialsProfileName = credentialsProfileName.substring(13); } if (credentialsProfileName.contains(":assumed-role")) { credentialsProfileName = credentialsProfileName.replaceAll(":assumed-role", ""); } Object[] args = { new String(credentialsProfileName), selectedPolicyRank }; MessageFormat profileNameFormat = new MessageFormat("{0}/{1}"); credentialsProfileName = profileNameFormat.format(args); //update the credentials file with the unique profile name UpdateCredentialsFile(credentialsProfileName, awsAccessKey, awsSecretKey, awsSessionToken); //also override the default profile UpdateCredentialsFile(DefaultProfileName, awsAccessKey, awsSecretKey, awsSessionToken); return credentialsProfileName; } private static void UpdateCredentialsFile(String profileName, String awsAccessKey, String awsSecretKey, String awsSessionToken) throws IOException { ProfilesConfigFile profilesConfigFile = null; Object[] args = { new String(profileName) }; MessageFormat profileNameFormatWithBrackets = new MessageFormat("[{0}]"); String profileNameWithBrackets = profileNameFormatWithBrackets.format(args); try { profilesConfigFile = new ProfilesConfigFile(); } catch (AmazonClientException ace) { PopulateCredentialsFile(profileNameWithBrackets, awsAccessKey, awsSecretKey, awsSessionToken); } try { if (profilesConfigFile != null && profilesConfigFile.getCredentials(profileName) != null) { //if we end up here, it means we were able to find a matching profile PopulateCredentialsFile(profileNameWithBrackets, awsAccessKey, awsSecretKey, awsSessionToken); } } catch (AmazonClientException ace) { //this could happen if the default profile doesn't have a valid AWS Access Key ID //in this case, error would be "Unable to load credentials into profile [default]: AWS Access Key ID is not specified." PopulateCredentialsFile(profileNameWithBrackets, awsAccessKey, awsSecretKey, awsSessionToken); } catch (IllegalArgumentException iae) { //if we end up here, it means we were not able to find a matching profile so we need to append one PopulateCredentialsFile(profileNameWithBrackets, awsAccessKey, awsSecretKey, awsSessionToken); //FileWriter fileWriter = new FileWriter(System.getProperty("user.home") + "/.aws/credentials", true); //TODO: need to be updated to work with Windows //PrintWriter writer = new PrintWriter(fileWriter); //WriteNewProfile(writer, profileNameWithBrackets, awsAccessKey, awsSecretKey, awsSessionToken); //fileWriter.close(); } } private static void PopulateCredentialsFile(String profileNameLine, String awsAccessKey, String awsSecretKey, String awsSessionToken) throws IOException { File inFile = new File(System.getProperty("user.home") + "/.aws/credentials"); FileInputStream fis = new FileInputStream(inFile); BufferedReader br = new BufferedReader(new InputStreamReader(fis)); File tempFile = new File(inFile.getAbsolutePath() + ".tmp"); PrintWriter pw = new PrintWriter(new FileWriter(tempFile)); //first, we add our refreshed profile WriteNewProfile(pw, profileNameLine, awsAccessKey, awsSecretKey, awsSessionToken); String line = null; int lineCounter = 0; boolean bFileStart = true; //second, we're copying all the other profile from the original credentials file while ((line = br.readLine()) != null) { if (line.equalsIgnoreCase(profileNameLine) || (lineCounter > 0 && lineCounter < 4)) { //we found the line we must replace and we will skip 3 additional lines ++lineCounter; } else { if ((!line.equalsIgnoreCase("") && !line.equalsIgnoreCase("\n"))) { if (line.startsWith("[")) { //this is the start of a new profile, so we're adding a separator line pw.println(); } pw.println(line); } } } pw.flush(); pw.close(); br.close(); //delete the original credentials file if (!inFile.delete()) { System.out.println("Could not delete original credentials file"); } // Rename the new file to the filename the original file had. if (!tempFile.renameTo(inFile)) { System.out.println("Could not rename file"); } } private static void UpdateConfigFile(String profileName, String roleToAssume) throws IOException { File inFile = new File(System.getProperty("user.home") + "/.aws/config"); FileInputStream fis = new FileInputStream(inFile); BufferedReader br = new BufferedReader(new InputStreamReader(fis)); File tempFile = new File(inFile.getAbsolutePath() + ".tmp"); PrintWriter pw = new PrintWriter(new FileWriter(tempFile)); //first, we add our refreshed profile WriteNewRoleToAssume(pw, profileName, roleToAssume); String line = null; int lineCounter = 0; boolean bFileStart = true; //second, we're copying all the other profiles from the original config file while ((line = br.readLine()) != null) { if (line.contains(profileName)) { //we found the section we must replace but we don't necessarily know how many lines we need to skip while ((line = br.readLine()) != null) { if (line.startsWith("[")) { pw.println(line); //this is a new profile line, so we're copying it break; } } } else { if ((!line.contains(profileName) && !line.equalsIgnoreCase("\n"))) { pw.println(line); logger.debug(line); } } } pw.flush(); pw.close(); br.close(); //delete the original credentials file if (!inFile.delete()) { System.out.println("Could not delete original config file"); } else { // Rename the new file to the filename the original file had. if (!tempFile.renameTo(inFile)) System.out.println("Could not rename file"); } } public static void WriteNewProfile(PrintWriter pw, String profileNameLine, String awsAccessKey, String awsSecretKey, String awsSessionToken) { pw.println(profileNameLine); pw.println("aws_access_key_id=" + awsAccessKey); pw.println("aws_secret_access_key=" + awsSecretKey); pw.println("aws_session_token=" + awsSessionToken); //pw.println(); //pw.println(); } public static void WriteNewRoleToAssume(PrintWriter pw, String profileName, String roleToAssume) { pw.println("[profile " + profileName + "]"); if (roleToAssume != null && !roleToAssume.equals("")) pw.println("role_arn=" + roleToAssume); pw.println("source_profile=" + profileName); pw.println("region=us-east-1"); } private static String mfa(JSONObject authResponse) { try { //User selects which factor to use JSONObject factor = selectFactor(authResponse); String factorType = factor.getString("factorType"); String stateToken = authResponse.getString("stateToken"); //factor selection handler switch (factorType) { case ("question"): { //question factor handler String sessionToken = questionFactor(factor, stateToken); if (sessionToken.equals("change factor")) { System.out.println("Factor Change Initiated"); return mfa(authResponse); } return sessionToken; } case ("sms"): { //sms factor handler String sessionToken = smsFactor(factor, stateToken); if (sessionToken.equals("change factor")) { System.out.println("Factor Change Initiated"); return mfa(authResponse); } return sessionToken; } case ("token:software:totp"): { //token factor handler String sessionToken = totpFactor(factor, stateToken); if (sessionToken.equals("change factor")) { System.out.println("Factor Change Initiated"); return mfa(authResponse); } return sessionToken; } case ("push"): { //push factor handles String result = pushFactor(factor, stateToken); if (result.equals("timeout") || result.equals("change factor")) { return mfa(authResponse); } return result; } } } catch (JSONException e) { e.printStackTrace(); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return ""; } /*Handles factor selection based on factors found in parameter authResponse, returns the selected factor * Precondition: JSINObject authResponse * Postcondition: return session token as String sessionToken */ public static JSONObject selectFactor(JSONObject authResponse) throws JSONException { JSONArray factors = authResponse.getJSONObject("_embedded").getJSONArray("factors"); JSONObject factor; String factorType; System.out.println("\nMulti-Factor authentication is required. Please select a factor to use."); //list factor to select from to user System.out.println("Factors:"); for (int i = 0; i < factors.length(); i++) { factor = factors.getJSONObject(i); factorType = factor.getString("factorType"); if (factorType.equals("question")) { factorType = "Security Question"; } else if (factorType.equals("sms")) { factorType = "SMS Authentication"; } else if (factorType.equals("token:software:totp")) { String provider = factor.getString("provider"); if (provider.equals("GOOGLE")) { factorType = "Google Authenticator"; } else { factorType = "Okta Verify"; } } System.out.println("[ " + (i + 1) + " ] : " + factorType); } //Handles user factor selection int selection = numSelection(factors.length()); return factors.getJSONObject(selection); } private static String questionFactor(JSONObject factor, String stateToken) throws JSONException, ClientProtocolException, IOException { String question = factor.getJSONObject("profile").getString("questionText"); Scanner scanner = new Scanner(System.in); String sessionToken = ""; String answer = ""; //prompt user for answer System.out.println( "\nSecurity Question Factor Authentication\nEnter 'change factor' to use a different factor\n"); while (sessionToken == "") { if (answer != "") { System.out.println("Incorrect answer, please try again"); } System.out.println(question); System.out.println("Answer: "); answer = scanner.nextLine(); //verify answer is correct if (answer.toLowerCase().equals("change factor")) { return answer; } sessionToken = verifyAnswer(answer, factor, stateToken, "question"); } return sessionToken; } /*Handles sms factor authentication * Precondition: question factor as JSONObject factor, current state token stateToken * Postcondition: return session token as String sessionToken */ private static String smsFactor(JSONObject factor, String stateToken) throws ClientProtocolException, JSONException, IOException { Scanner scanner = new Scanner(System.in); String answer = ""; String sessionToken = ""; //prompt for sms verification System.out.println("\nSMS Factor Authentication \nEnter 'change factor' to use a different factor"); while (sessionToken == "") { if (answer != "") { System.out.println( "Incorrect passcode, please try again or type 'new code' to be sent a new sms token"); } else { //send initial code to user sessionToken = verifyAnswer("", factor, stateToken, "sms"); } System.out.println("SMS Code: "); answer = scanner.nextLine(); //resends code if (answer.equals("new code")) { answer = ""; System.out.println("New code sent! \n"); } else if (answer.toLowerCase().equals("change factor")) { return answer; } //verifies code sessionToken = verifyAnswer(answer, factor, stateToken, "sms"); } return sessionToken; } /*Handles token factor authentication, i.e: Google Authenticator or Okta Verify * Precondition: question factor as JSONObject factor, current state token stateToken * Postcondition: return session token as String sessionToken */ private static String totpFactor(JSONObject factor, String stateToken) throws ClientProtocolException, JSONException, IOException { Scanner scanner = new Scanner(System.in); String sessionToken = ""; String answer = ""; //prompt for token System.out.println("\n" + factor.getString("provider") + " Token Factor Authentication\nEnter 'change factor' to use a different factor"); while (sessionToken == "") { if (answer != "") { System.out.println("Invalid token, please try again"); } System.out.println("Token: "); answer = scanner.nextLine(); //verify auth Token if (answer.toLowerCase().equals("change factor")) { return answer; } sessionToken = verifyAnswer(answer, factor, stateToken, "token:software:totp"); } return sessionToken; } /*Handles push factor authentication * * */ private static String pushFactor(JSONObject factor, String stateToken) throws ClientProtocolException, JSONException, IOException { Calendar newTime = null; Calendar time = Calendar.getInstance(); String sessionToken = ""; System.out.println("\nPush Factor Authentication"); while (sessionToken == "") { //System.out.println("Token: "); //prints waiting tick marks //if( time.compareTo(newTime) > 4000){ // System.out.println("..."); //} //Verify if Okta Push has been pushed sessionToken = verifyAnswer(null, factor, stateToken, "push"); System.out.println(sessionToken); if (sessionToken.equals("Timeout")) { System.out.println("Session has timed out"); return "timeout"; } time = newTime; newTime = Calendar.getInstance(); } return sessionToken; } /*Handles verification for all Factor types * Precondition: question factor as JSONObject factor, current state token stateToken * Postcondition: return session token as String sessionToken */ private static String verifyAnswer(String answer, JSONObject factor, String stateToken, String factorType) throws JSONException, ClientProtocolException, IOException { String sessionToken = null; JSONObject profile = new JSONObject(); String verifyPoint = factor.getJSONObject("_links").getJSONObject("verify").getString("href"); profile.put("stateToken", stateToken); JSONObject jsonObjResponse = null; //if (factorType.equals("question")) { if (answer != null && answer != "") { profile.put("answer", answer); } //create post request CloseableHttpResponse responseAuthenticate = null; CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost httpost = new HttpPost(verifyPoint); httpost.addHeader("Accept", "application/json"); httpost.addHeader("Content-Type", "application/json"); httpost.addHeader("Cache-Control", "no-cache"); StringEntity entity = new StringEntity(profile.toString(), StandardCharsets.UTF_8); entity.setContentType("application/json"); httpost.setEntity(entity); responseAuthenticate = httpClient.execute(httpost); BufferedReader br = new BufferedReader( new InputStreamReader((responseAuthenticate.getEntity().getContent()))); String outputAuthenticate = br.readLine(); jsonObjResponse = new JSONObject(outputAuthenticate); if (jsonObjResponse.has("errorCode")) { String errorSummary = jsonObjResponse.getString("errorSummary"); System.out.println(errorSummary); System.out.println("Please try again"); if (factorType.equals("question")) { questionFactor(factor, stateToken); } if (factorType.equals("token:software:totp")) { totpFactor(factor, stateToken); } } //} if (jsonObjResponse != null && jsonObjResponse.has("sessionToken")) sessionToken = jsonObjResponse.getString("sessionToken"); String pushResult = null; if (factorType.equals("push")) { if (jsonObjResponse.has("_links")) { JSONObject linksObj = jsonObjResponse.getJSONObject("_links"); //JSONObject pollLink = links.getJSONObject("poll"); JSONArray names = linksObj.names(); JSONArray links = linksObj.toJSONArray(names); String pollUrl = ""; for (int i = 0; i < links.length(); i++) { JSONObject link = links.getJSONObject(i); String linkName = link.getString("name"); if (linkName.equals("poll")) { pollUrl = link.getString("href"); break; //System.out.println("[ " + (i+1) + " ] :" + factorType ); } } while (pushResult == null || pushResult.equals("WAITING")) { pushResult = null; CloseableHttpResponse responsePush = null; httpClient = HttpClients.createDefault(); HttpPost pollReq = new HttpPost(pollUrl); pollReq.addHeader("Accept", "application/json"); pollReq.addHeader("Content-Type", "application/json"); pollReq.addHeader("Cache-Control", "no-cache"); entity = new StringEntity(profile.toString(), StandardCharsets.UTF_8); entity.setContentType("application/json"); pollReq.setEntity(entity); responsePush = httpClient.execute(pollReq); br = new BufferedReader(new InputStreamReader((responsePush.getEntity().getContent()))); String outputTransaction = br.readLine(); JSONObject jsonTransaction = new JSONObject(outputTransaction); if (jsonTransaction.has("factorResult")) { pushResult = jsonTransaction.getString("factorResult"); } if (pushResult == null && jsonTransaction.has("status")) { pushResult = jsonTransaction.getString("status"); } System.out.println("Waiting for you to approve the Okta push notification on your device..."); try { Thread.sleep(500); } catch (InterruptedException iex) { } //if(pushResult.equals("SUCCESS")) { if (jsonTransaction.has("sessionToken")) { sessionToken = jsonTransaction.getString("sessionToken"); } //} /* if(pushResult.equals("TIMEOUT")) { sessionToken = "timeout"; } */ } } } if (sessionToken != null) return sessionToken; else return pushResult; } /*Handles question factor authentication, * Precondition: question factor as JSONObject factor, current state token stateToken * Postcondition: return session token as String sessionToken */ private static String questionFactor(Factor factor, String stateToken) throws JSONException, ClientProtocolException, IOException { /* String question = factor.getJSONObject("profile").getString("questionText"); Scanner scanner = new Scanner(System.in); String sessionToken = ""; String answer = ""; //prompt user for answer System.out.println("\nSecurity Question Factor Authentication\nEnter 'change factor' to use a different factor\n"); while (sessionToken == "") { if (answer != "") { System.out.println("Incorrect answer, please try again"); } System.out.println(question); System.out.println("Answer: "); answer = scanner.nextLine(); //verify answer is correct if (answer.toLowerCase().equals("change factor")) { return answer; } sessionToken = verifyAnswer(answer, factor, stateToken); } */ return "";//sessionToken; } /*Handles token factor authentication, i.e: Google Authenticator or Okta Verify * Precondition: question factor as JSONObject factor, current state token stateToken * Postcondition: return session token as String sessionToken */ private static String totpFactor(Factor factor, String stateToken) throws IOException { Scanner scanner = new Scanner(System.in); String sessionToken = ""; String answer = ""; //prompt for token System.out.println("\n" + factor.getProvider() + " Token Factor Authentication\nEnter 'change factor' to use a different factor"); while (sessionToken == "") { if (answer != "") { System.out.println("Invalid token, please try again"); } System.out.println("Token: "); answer = scanner.nextLine(); //verify auth Token if (answer.toLowerCase().equals("change factor")) { return answer; } sessionToken = verifyAnswer(answer, factor, stateToken); } return sessionToken; } /*Handles verification for all Factor types * Precondition: question factor as JSONObject factor, current state token stateToken * Postcondition: return session token as String sessionToken */ private static String verifyAnswer(String answer, Factor factor, String stateToken) throws IOException { String strAuthResult = ""; AuthResult authResult = authClient.authenticateWithFactor(stateToken, factor.getId(), answer); /* Verification verification = new Verification(); if(factor.getFactorType().equals("sms")) { verification.setPassCode(answer); } else if (factor.getFactorType().equals("token:software:totp")) { verification.setAnswer(answer); } verification.setAnswer(answer); FactorVerificationResponse mfaResponse = factorClient.verifyFactor(userId, factor.getId(), verification); if(mfaResponse.getFactorResult().equals("SUCCESS")) return mfaResponse.get */ if (!authResult.getStatus().equals("SUCCESS")) { System.out.println("\nThe second-factor verification failed."); } else { return authResult.getSessionToken(); } /*JSONObject profile = new JSONObject(); String verifyPoint = factor.getJSONObject("_links").getJSONObject("verify").getString("href"); profile.put("stateToken", stateToken); if (answer != "") { profile.put("answer", answer); } //create post request CloseableHttpResponse responseAuthenticate = null; CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost httpost = new HttpPost(verifyPoint); httpost.addHeader("Accept", "application/json"); httpost.addHeader("Content-Type", "application/json"); httpost.addHeader("Cache-Control", "no-cache"); StringEntity entity = new StringEntity(profile.toString(), HTTP.UTF_8); entity.setContentType("application/json"); httpost.setEntity(entity); responseAuthenticate = httpClient.execute(httpost); BufferedReader br = new BufferedReader(new InputStreamReader( (responseAuthenticate.getEntity().getContent()))); String outputAuthenticate = br.readLine(); JSONObject jsonObjResponse = new JSONObject(outputAuthenticate); //Handles request response if (jsonObjResponse.has("sessionToken")) { //session token returned return jsonObjResponse.getString("sessionToken"); } else if (jsonObjResponse.has("factorResult")) { if (jsonObjResponse.getString("sessionToken").equals("TIMEOUT")) { //push factor timeout return "timeout"; } else { return ""; } } else { //Unsuccessful verification return ""; } */ return ""; } /*Handles factor selection based on factors found in parameter authResult, returns the selected factor */ public static void selectFactor(AuthResult authResult) { ArrayList<LinkedHashMap> factors = (ArrayList<LinkedHashMap>) authResult.getEmbedded().get("factors"); String factorType; System.out.println("\nMulti-Factor authentication required. Please select a factor to use."); //list factor to select from to user System.out.println("Factors:"); for (int i = 0; i < factors.size(); i++) { LinkedHashMap<String, Object> factor = factors.get(i); //Factor factor = factors.get(i); factorType = (String) factor.get("factorType");// factor.getFactorType(); if (factorType.equals("question")) { factorType = "Security Question"; } else if (factorType.equals("sms")) { factorType = "SMS Authentication"; } else if (factorType.equals("token:software:totp")) { String provider = (String) factor.get("provider");//factor.getProvider(); if (provider.equals("GOOGLE")) { factorType = "Google Authenticator"; } else { factorType = "Okta Verify"; } } System.out.println("[ " + (i + 1) + " ] :" + factorType); } //Handles user factor selection int selection = numSelection(factors.size()); //return factors.get(selection); } /*Handles MFA for users, returns an Okta session token if user is authenticated * Precondition: question factor as JSONObject factor, current state token stateToken * Postcondition: return session token as String sessionToken */ private static String mfa(AuthResult authResult) throws IOException { /* try { //User selects which factor to use Factor selectedFactor = selectFactor(authResult); String factorType = selectedFactor.getFactorType(); String stateToken = authResult.getStateToken(); //factor selection handler switch (factorType) { case ("question"): { //question factor handler //String sessionToken = questionFactor(factor, stateToken); //if (sessionToken.equals("change factor")) { // System.out.println("Factor Change Initiated"); // return mfa(authResponse); //} //return sessionToken; } case ("sms"): { //sms factor handler //String sessionToken = smsFactor(factor, stateToken); //if (sessionToken.equals("change factor")) { // System.out.println("Factor Change Initiated"); // return mfa(authResponse); //} //return sessionToken; } case ("token:software:totp"): { //token factor handler String sessionToken = totpFactor(selectedFactor, stateToken); if (sessionToken.equals("change factor")) { System.out.println("Factor Change Initiated"); return mfa(authResult); } return sessionToken; } case ("push"): { //push factor handles /* String result = pushFactor(factor, stateToken); if (result.equals("timeout") || result.equals("change factor")) { return mfa(authResponse); } return result; } } } catch (JSONException e) { e.printStackTrace(); } /*catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } */ return ""; } /* prints final status message to user */ private static void resultMessage(String profileName) { Calendar date = Calendar.getInstance(); SimpleDateFormat dateFormat = new SimpleDateFormat(); date.add(Calendar.HOUR, 1); //change with file customization System.out.println( "\n----------------------------------------------------------------------------------------------------------------------"); System.out.println( "Your new access key pair has been stored in the aws configuration file with the following profile name: " + profileName); System.out.println( "The AWS Credentials file is located in " + System.getProperty("user.home") + "/.aws/credentials."); System.out.println("Note that it will expire at " + dateFormat.format(date.getTime())); System.out.println("After this time you may safely rerun this script to refresh your access key pair."); System.out.println("To use these credentials, please call the aws cli with the --profile option " + "(e.g. aws --profile " + profileName + " ec2 describe-instances)"); System.out.println( "You can also omit the --profile option to use the last configured profile " + "(e.g. aws s3 ls)"); System.out.println( "----------------------------------------------------------------------------------------------------------------------"); } /* Authenticates users credentials via Okta, return Okta session token * Postcondition: returns String oktaSessionToken * */ private static String oktaAuthentication() throws ClientProtocolException, JSONException, IOException { String strSessionToken = ""; AuthResult authResult = null; int requestStatus = 0; String strAuthStatus = ""; //Redo sequence if response from AWS doesn't return 200 Status while (!strAuthStatus.equalsIgnoreCase("SUCCESS") && !strAuthStatus.equalsIgnoreCase("MFA_REQUIRED")) { // Prompt for user credentials System.out.print("Username: "); //Scanner scanner = new Scanner(System.in); String oktaUsername = null; //scanner.next(); Console console = System.console(); String oktaPassword = null; if (console != null) { oktaPassword = new String(console.readPassword("Password: ")); } else { // hack to be able to debug in an IDE System.out.print("Password: "); } try { authResult = authenticateCredentials(oktaUsername, oktaPassword); strAuthStatus = authResult.getStatus(); if (strAuthStatus.equalsIgnoreCase("MFA_REQUIRED")) { if (userClient != null) { LinkedHashMap<String, Object> user = (LinkedHashMap<String, Object>) (authResult .getEmbedded().get("user")); userId = (String) user.get("id"); //userId = user.getId(); /*User user = userClient.getUser(oktaUsername); if(user!=null) userId = user.getId();*/ } } } catch (ApiException apiException) { String strEx = apiException.getMessage(); switch (apiException.getStatusCode()) { case 400: case 401: System.out.println("You provided invalid credentials, please try again."); break; case 500: System.out.println("\nUnable to establish connection with: " + oktaOrg + " \nPlease verify that your Okta org url is correct and try again"); System.exit(0); break; default: throw new RuntimeException("Failed : HTTP error code : " + apiException.getStatusCode() + " Error code: " + apiException.getErrorResponse().getErrorCode() + " Error summary: " + apiException.getErrorResponse().getErrorSummary()); } } //requestStatus = responseAuthenticate.getStatusLine().getStatusCode(); //authnFailHandler(requestStatus, responseAuthenticate); } //Retrieve and parse the Okta response for session token /*BufferedReader br = new BufferedReader(new InputStreamReader( (responseAuthenticate.getEntity().getContent()))); String outputAuthenticate = br.readLine(); JSONObject jsonObjResponse = new JSONObject(outputAuthenticate); responseAuthenticate.close();*/ if (strAuthStatus.equalsIgnoreCase("MFA_REQUIRED")) { return mfa(authResult); } //else { // return jsonObjResponse.getString("sessionToken"); //} if (authResult != null) strSessionToken = authResult.getSessionToken(); return strSessionToken; } }