You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hey so you may have already read my other tickets, as you can guess I spent a lot of time and had lots of frustrating fun learning the ins and outs of AWS VPN with Okta SAML provider. My company ONLY uses Okta SAML so these changes will likely only work for OKTA but I'm guessing the "logic" should work for others with some tweaking.
I got to wondering why do we need to open a browser window and login and run a server.go to listen for any SAML responses, while a really ingenious idea I was curious if there might be another option. Now I won't lie I spent many many hours fighting with this stuff enough so that were I do it again I probably would have stopped with your solution, which is great and has the advantage of using the browser cache for tokens so you don't have to login constantly.
Using Firefox's Network Tools I was able to track the transactions going through to see how the process worked, and I could see how when the browser had an okta token cached it never had to login but just automatically sent the POST and got the SAML reponse. I was curious could I use normal REST calls to perform the same transactions and to perform an OKTA Login and get a SID and SessionToken which I could then submit to the AWS Client VPN endpoint and connect openvpn.
Well the short answer I was able to do so, now of course the main concern here is.. if we have to do a login everytime that would be a big pain for the user, which is one of the advantages of your solution that using the browser you usually don't have to login but 1x per day. However with lots of trial and error I learned that as long as you preserve the SessionToken and Okta SID in a file, you could use those to create a new SAML reponse multiple times and connect to AWS Client VPN without every having to enter your user/pass/mfa info. Great!
Now please bear in mind this code is rough and I'm no hard-core developer, so please feel free to improve if you like. But I created a NodeJS script that performs all the HTTP calls. The logic is pretty straight-forward.
when you run aws-connect-okta.sh it no longer needs the server.go instead it starts the nodejs script: get-saml-response.js
this script first checks to see if it already has a OKTA SID (saml-sid.txt) and Okta Session Token.
** If they exists it then tries to immediately get a new SAML response,
** if successful you are done we return back to aws-connect-okta.sh and opevpn connects and everyone is happy.
If the SID/Token do not exist or have expired the SAML response request will fail and
** this will trigger new HTTP requests to OKTA to "login w/ user/password" and
** perform an MFA challenge which in turn gets us a sessionToken.
** We then send the sessionToken to AWS VPN Client saml app and this returns us both an OKTA SID (useful to allow future SAML response requests without login) and the SAML Response, which the script then saves to files and exits returning back to aws-connect-okta.sh for the VPN connect.
Now as a reminder I have only tested this on OKTA, I'm sure other providers will require some changes to the HTTP URLs and if you don't use MFA the same, but the logic is pretty cool I think. I don't know if it's worth trying to explore this further for other scenarios, but my objective of OKTA Provider + AWS Client VPN seems to work fairly nicely. I'm sure there are a few bugs in there to work out, but I wanted to share and say:
Thank you for your hard work which made this all possible
Attached here are the two files: You will need to set the following to make this work: company, provider id, username, password
aws-connect.sh
Add the following just before the wait_file "saml_response.txt"
node get-saml-response.js
get-saml-response.js
#!/usr/bin/env node
constaxios=require("axios").default;constfs=require("fs").promises;consthe=require("he");constprompt=require("prompt");constusername="USERNAME/EMAIL";constpassword="PASSWORD";constproperties=[{name: 'mfaToken',hidden: true}];(asyncfunction(){varmyArgs=process.argv.slice(2);checkExistingCredentials=asyncfunction(){try{console.log("Reading saml-sid.txt")oktaSid=awaitfs.readFile("saml-sid.txt");console.log("Reading saml-sessionToken.txt")sessionToken=awaitfs.readFile("saml-sessionToken.txt");console.log(`Existing Creds Found: SessionToken: ${sessionToken} OktaSID: ${oktaSid}`)awaitgetSamlResponse(sessionToken,oktaSid);returntrue}catch(err){console.log("Error while checking for existing Credentials, proceeding with new session request.");console.log(err);returnfalse;}};getSamlResponse=asyncfunction(sessionToken,oktaSid){options={};if(oktaSid){options={headers: {Cookie: "sid="+oktaSid,}};}try{response=awaitaxios.get(`https://<company>.okta.com/app/aws_clientvpn/<provider id>/sso/saml?sessionToken=${sessionToken}`,options);oktaSid=null;response.headers["set-cookie"].forEach((cookie)=>{match=/sid=(.*); Path=.*/.exec(cookie);if(match){oktaSid=match[1];}});if(!oktaSid){console.log("Error parsing Sid cookie, we have to abort");process.exit(4);}console.log(`Okta SID: ${oktaSid}`);awaitfs.writeFile("saml-sid.txt",oktaSid);// Third.3 - We need to parse SAMLResponse out of XML responsexmlOneLine=response.data.toString().replace(/[\n\r]+/g,"");match=/.*SAMLResponse" type="hidden" value="(.*)"\/>\s+ <input name.*/g.exec(xmlOneLine);samlResponseBase64=he.decode(match[1]);samlResponseUriEncoded=encodeURIComponent(samlResponseBase64);awaitfs.writeFile("saml-response.txt",samlResponseUriEncoded);console.log('SamlResponse written')returntrue;}catch(err){if(err.response&&err.response.status===302){console.log("Warning: Redirect detects on SAML response, this usually means your token has expired");returnfalse;}else{console.log("Error Unknown, stopping");console.log(err);process.exit(3);}}};getOktaLogin=asyncfunction(username,password){try{response=awaitaxios.post("https://<company>.okta.com/api/v1/authn",{username: username,password: password,options: {multiOptionalFactorEnroll: true,warnBeforePasswordExpired: true,},});}catch(err){console.log("Error authenticating with user/pass");console.log(err.response.data);process.exit(1);}letstateToken=response.data.stateToken;letmfaVerifyUrl=response.data._embedded.factors[0]._links.verify.href;console.log(`StateToken received: ${stateToken}`);console.log(`MFA Verify URL: ${mfaVerifyUrl}`);return{ stateToken, mfaVerifyUrl }};getMfaVerify=asyncfunction(stateToken,mfaToken,mfaVerifyUrl){// Second we need to perform MFA Verifytry{response=awaitaxios.post(mfaVerifyUrl,{stateToken: stateToken,passCode: mfaToken,});}catch(err){console.log("Error verifying MFA, did you reuse a token?");console.log(response.data);process.exit(2);}sessionToken=response.data.sessionToken;console.log(`SessionToken: ${sessionToken}`);awaitfs.writeFile("saml-sessionToken.txt",sessionToken);returnsessionToken;};//////////////////////////////////////////////////////////////////////////////////////// M A I N //////////////////////////////////////////////////////////////////////////////////////////////credsCheck=awaitcheckExistingCredentials();if(credsCheck){console.log("Existing Credentials are still valid, SAML Response written to file.");process.exit(0);}// We need to prompt the user for the MFA Tokenprompt.start()const{ mfaToken }=awaitprompt.get(properties)const{ stateToken, mfaVerifyUrl }=awaitgetOktaLogin(username,password);sessionToken=awaitgetMfaVerify(stateToken,mfaToken,mfaVerifyUrl);credCheck=awaitgetSamlResponse(sessionToken,false);if(credCheck){console.log("Credentials appear valid, SAML Response written to file.");process.exit(0);}})();
The text was updated successfully, but these errors were encountered:
Hey so you may have already read my other tickets, as you can guess I spent a lot of time and had lots of frustrating fun learning the ins and outs of AWS VPN with Okta SAML provider. My company ONLY uses Okta SAML so these changes will likely only work for OKTA but I'm guessing the "logic" should work for others with some tweaking.
I got to wondering why do we need to open a browser window and login and run a
server.go
to listen for any SAML responses, while a really ingenious idea I was curious if there might be another option. Now I won't lie I spent many many hours fighting with this stuff enough so that were I do it again I probably would have stopped with your solution, which is great and has the advantage of using the browser cache for tokens so you don't have to login constantly.Using Firefox's Network Tools I was able to track the transactions going through to see how the process worked, and I could see how when the browser had an okta token cached it never had to login but just automatically sent the POST and got the SAML reponse. I was curious could I use normal REST calls to perform the same transactions and to perform an OKTA Login and get a SID and SessionToken which I could then submit to the AWS Client VPN endpoint and connect openvpn.
Well the short answer I was able to do so, now of course the main concern here is.. if we have to do a login everytime that would be a big pain for the user, which is one of the advantages of your solution that using the browser you usually don't have to login but 1x per day. However with lots of trial and error I learned that as long as you preserve the SessionToken and Okta SID in a file, you could use those to create a new SAML reponse multiple times and connect to AWS Client VPN without every having to enter your user/pass/mfa info. Great!
Now please bear in mind this code is rough and I'm no hard-core developer, so please feel free to improve if you like. But I created a NodeJS script that performs all the
HTTP
calls. The logic is pretty straight-forward.server.go
instead it starts the nodejs script:get-saml-response.js
** If they exists it then tries to immediately get a new SAML response,
** if successful you are done we return back to aws-connect-okta.sh and opevpn connects and everyone is happy.
** this will trigger new HTTP requests to OKTA to "login w/ user/password" and
** perform an MFA challenge which in turn gets us a sessionToken.
** We then send the sessionToken to AWS VPN Client saml app and this returns us both an OKTA SID (useful to allow future SAML response requests without login) and the SAML Response, which the script then saves to files and exits returning back to aws-connect-okta.sh for the VPN connect.
Now as a reminder I have only tested this on OKTA, I'm sure other providers will require some changes to the HTTP URLs and if you don't use MFA the same, but the logic is pretty cool I think. I don't know if it's worth trying to explore this further for other scenarios, but my objective of OKTA Provider + AWS Client VPN seems to work fairly nicely. I'm sure there are a few bugs in there to work out, but I wanted to share and say:
Thank you for your hard work which made this all possible
Attached here are the two files: You will need to set the following to make this work:
company, provider id, username, password
aws-connect.sh
Add the following just before the
wait_file "saml_response.txt"
get-saml-response.js
The text was updated successfully, but these errors were encountered: