{"id":3798,"date":"2019-07-06T15:39:49","date_gmt":"2019-07-06T20:39:49","guid":{"rendered":"https:\/\/dev.iachieved.it\/iachievedit\/?p=3798"},"modified":"2019-07-06T15:39:49","modified_gmt":"2019-07-06T20:39:49","slug":"auditing-shared-account-usage","status":"publish","type":"post","link":"https:\/\/dev.iachieved.it\/iachievedit\/auditing-shared-account-usage\/","title":{"rendered":"Auditing Shared Account Usage"},"content":{"rendered":"<p>Occasionally you find yourself in a situation where utilizing a shared account cannot be avoided.  One such scenario is managing the deployment of NodeJS applications with <a href=\"https:\/\/github.com\/shipitjs\/shipit\">Shipit<\/a> and <a href=\"https:\/\/pm2.keymetrics.io\">PM2<\/a>.  Here&#8217;s how the scenario typically works:<\/p>\n<p>Alice, Bob, and Carol are three developers working on NodeJS applications that need to be deployed to their staging server.  They&#8217;ve decided on the use of PM2 as their process manager, and ShipIt as their deployment tool.  Their <code>shipitfile.js<\/code> file contains a block for the staging server, and it looks something like:<\/p>\n<p>[code lang=text]<br \/>\nstaging: {<br \/>\n      servers: [<br \/>\n        {<br \/>\n          host: &#039;apps.staging.iachieved.it&#039;,<br \/>\n          user: &#039;deployer&#039;,<br \/>\n        },<br \/>\n      ],<br \/>\n      environment:  &#039;staging&#039;,<br \/>\n      branch: &#039;develop&#039;,<br \/>\n    },<br \/>\n[\/code]<\/p>\n<p>As we can see the <code>deployer<\/code> user will be used to deploy our application, and by extension, will be the user that <code>pm2<\/code> runs the application under.  Alice, Bob, and Carol all have their SSH keys put in <code>\/home\/deployer\/.ssh\/authorized_keys<\/code> so they can deploy.  Makes sense.<\/p>\n<p>Unfortunately what this also means is Alice, Bob, or Carol can <code>ssh<\/code> to the staging server as the <code>deployer<\/code> user.  Even though <code>deployer<\/code> is an unprivileged user, we really don&#8217;t want that.  Moreover, by default, we can&#8217;t trace who deployed, or if someone is misusing the <code>deployer<\/code> user.  Let&#8217;s take a look at how we can address this.<\/p>\n<h2>Creating a Deployment Group<\/h2>\n<p>The first thing we want to do is to create a security group for those that are authorized to perform deployments.  I&#8217;ll be using an Active Directory security group in this example, but a standard Unix group would work as well.  We can use <code>getent<\/code> to see the members of the group.  <code>getent<\/code> will come in handy to help determine whether someone attempting to deploy is authorized.<\/p>\n<p>[code lang=text]<br \/>\n# getent group &quot;application deployment@iachieved.it&quot;<br \/>\napplication deployment@iachieved.it:*:1068601118:alice@iachieved.it,bob@iachieved.it<br \/>\n[\/code]<\/p>\n<h2>SSH authorized_keys command<\/h2>\n<p>Until I started researching this problem of auditing and restricting shared account usage I was unaware of the <code>command<\/code> option in the SSH <code>authorized_keys<\/code> file.  One learns something new every day.  What the <code>command<\/code> option provides for is executing a command immediately upon SSHing via a given key.  Consider that we put the following entry in the <code>deployer<\/code> user <code>~\/.ssh\/authorized_keys<\/code> file:<\/p>\n<p>[code lang=text]<br \/>\nssh-rsa AAAA&#8230;sCBR alice<br \/>\n[\/code]<\/p>\n<p>and this is Alice&#8217;s public key.  We would expect that Alice would be able to <code>ssh deployer@apps.iachieved.it<\/code> and get a shell.  But what if we wanted to intercept this SSH and run a script instead?  Let&#8217;s try it out:<\/p>\n<p><code>deployer@apps.iachieved.it:~\/.ssh\/authorized_keys<\/code>:<\/p>\n<p>[code lang=text]<br \/>\ncommand=&quot;\/usr\/bin\/logger -p auth.INFO Not permitted&quot; ssh-rsa AAAA&#8230;sCBR alice<br \/>\n[\/code]<\/p>\n<p>When Alice tries to ssh as the <code>deployer<\/code> user, we get an entry in <code>auth.log<\/code>:<\/p>\n<p>[code lang=text]<br \/>\nJul  5 22:30:58 apps deployer: Not permitted<br \/>\n[\/code]<\/p>\n<p>and Alice sees <code>Connection to apps closed.<\/code>.<\/p>\n<p>Well that&#8217;s no good!  We <em>do<\/em> want Alice to be able to use the <code>deployer<\/code> account to deploy code.<\/p>\n<h2>A Wrapper Script<\/h2>\n<p>First, we want Alice to be able to deploy code with the <code>deployer<\/code> user, but we also want to:<\/p>\n<ul>\n<li>know that it was Alice<\/li>\n<li>ensure Alice is an authorized deployer<\/li>\n<li>not allow Alice to get a shell<\/li>\n<\/ul>\n<p>Let&#8217;s look at how we can create a script to execute each SSH invocation that will meet all of these criteria.<\/p>\n<p>Step 1, let&#8217;s log and execute whatever Alice was attempting to do.<\/p>\n<p><code>\/usr\/local\/bin\/deploy.sh<\/code>:<\/p>\n<pre class=\"toolbar-overlay:false lang:sh decode:true \" >#!\/bin\/bash\n\nlogger -p auth.INFO $SSH_REMOTE_USER executed \"$SSH_ORIGINAL_COMMAND\"\nexec \/bin\/bash -c \"$SSH_ORIGINAL_COMMAND\"<\/pre>\n<p><code>SSH_ORIGINAL_COMMAND<\/code> will be set automatically by <code>sshd<\/code>, but we need to provide <code>SSH_REMOTE_USER<\/code>, so in the <code>authorized_keys<\/code> file:<\/p>\n<p>[code lang=text]<br \/>\ncommand=&quot;export SSH_REMOTE_USER=alice@iachieved.it;\/usr\/local\/bin\/deploy.sh&quot; ssh-rsa AAAA&#8230;sCBR alice<br \/>\n[\/code]<\/p>\n<p>Note that we explicitly set <code>SSH_REMOTE_USER<\/code> to <code>alice@iachieved.it<\/code>.  The takeaway here is that it associates any attempt by Alice to use the <code>deployer<\/code> account to her userid.  We then execute <code>deploy.sh<\/code> which logs the invocation.  If Alice tries to <code>ssh<\/code> and get a shell with <code>ssh deployer@apps<\/code> the connection will still be closed, as <code>SSH_ORIGINAL_COMMAND<\/code> is null.  But, let&#8217;s say she runs <code>ssh deployer@apps ls \/<\/code>:<\/p>\n<p>[code lang=text]<br \/>\nalice@iachieved.it@apps ~&gt; ssh deployer@apps ls \/<br \/>\nbin<br \/>\nboot<br \/>\ndev<br \/>\netc<br \/>\n[\/code]<\/p>\n<p>In <code>\/var\/log\/auth.log<\/code> we see:<\/p>\n<p>[code lang=text]<br \/>\nJul  6 13:43:25 apps sshd[18554]: Accepted publickey for deployer from ::1 port 48832 ssh2: RSA SHA256:thZna7v6go5EzcZABkieCmaZzp+6WSlYx37a3uPOMSs<br \/>\nJul  6 13:43:25 apps sshd[18554]: pam_unix(sshd:session): session opened for user deployer by (uid=0)<br \/>\nJul  6 13:43:25 apps systemd-logind[945]: New session 54 of user deployer.<br \/>\nJul  6 13:43:25 apps systemd: pam_unix(systemd-user:session): session opened for user deployer by (uid=0)<br \/>\nJul  6 13:43:26 apps deployer: alice@iachieved.it executed ls \/<br \/>\n[\/code]<\/p>\n<p>What is important here is that we can trace what Alice is executing.<\/p>\n<h2>Managing a Deployment Security Group<\/h2>\n<p>Left as is this technique is much preferred to a free-for-all with the <code>deployer<\/code> user, but more can be done using security groups to have finer control of who can use the account at any given time.  Let&#8217;s add an additional check in the <code>\/usr\/local\/bin\/deploy.sh<\/code> script with the <code>uig<\/code> function introduced in the <a href=\"https:\/\/dev.iachieved.it\/iachievedit\/a-script-for-testing-membership-in-a-unix-group\/\">last post<\/a>.<\/p>\n<pre class=\"toolbar-overlay:false lang:sh decode:true\">\n#!\/bin\/bash\n\n. \/usr\/local\/bin\/uig.sh\n\nuig \"$SSH_REMOTE_USER\" \"$DEPLOY_GROUP\"\nif [ \"$?\" == 0 ]; then\n  logger -p auth.INFO $SSH_REMOTE_USER is in $DEPOY_GROUP and executed \"$SSH_ORIGINAL_COMMAND\"\n  exec \/bin\/bash -c \"$SSH_ORIGINAL_COMMAND\"\nelse\n  logger -p auth.INFO $SSH_REMOTE_USER is not in $DEPLOY_GROUP and is not authorized to execute \"$SSH_ORIGINAL_COMMAND\"\n  exit -1\nfi\n<\/pre>\n<p>The <code>authorized_keys<\/code> file gets updated, and let&#8217;s add Bob and Carol&#8217;s keys for our additional positive (Bob) and negative (Carol) test:<\/p>\n<pre class=\"toolbar-overlay:false lang:sh decode:true\">\ncommand=\"export SSH_REMOTE_USER=alice@iachieved.it DEPLOY_GROUP='application deployment@iachieved.it';\/usr\/local\/bin\/deploy.sh\" ssh-rsa AAAA...sCBR alice\ncommand=\"export SSH_REMOTE_USER=bob@iachieved.it DEPLOY_GROUP='application deployment@iachieved.it';\/usr\/local\/bin\/deploy.sh\" ssh-rsa AAAA...r4Gz bob\ncommand=\"export SSH_REMOTE_USER=carol@iachieved.it DEPLOY_GROUP='application deployment@iachieved.it';\/usr\/local\/bin\/deploy.sh\" ssh-rsa AAAA...f5Xy carol\n<\/pre>\n<p>Since Bob is a member of the <code>application deployment@iachieved.it<\/code> group, he can proceed:<\/p>\n<p>[code lang=text]<br \/>\nJul  6 20:09:26 apps sshd[21886]: Accepted publickey for deployer from ::1 port 49148 ssh2: RSA SHA256:gs3j1xHvwJcSMBXxaqag6Pb7A595HVXIz2fMoCX2J\/I<br \/>\nJul  6 20:09:26 apps sshd[21886]: pam_unix(sshd:session): session opened for user deployer by (uid=0)<br \/>\nJul  6 20:09:26 apps systemd-logind[945]: New session 79 of user deployer.<br \/>\nJul  6 20:09:26 apps systemd: pam_unix(systemd-user:session): session opened for user deployer by (uid=0)<br \/>\nJul  6 20:09:27 apps deployer: bob@iachieved.it is in application deployment@iachieved.it and executed ls \/<br \/>\n[\/code]<\/p>\n<p>Now, Carol&#8217;s turn to try <code>ssh deployer@apps ls \/<\/code>:<\/p>\n<p>[code lang=text]<br \/>\nJul  6 20:15:37 apps deployer: carol@iachieved.it is not in application deployment@iachieved.it and is not authorized to execute ls \/<br \/>\n[\/code]<\/p>\n<p>Poor Carol.<\/p>\n<h2>Closing Thoughts<\/h2>\n<p>For some teams the idea of having to manage a deployment security group and bespoke <code>authorized_keys<\/code> file may be overkill.  If you&#8217;re in an environment with enhanced audit controls and accountability the ability to implement safeguards and audits to code deployments may be a welcome addition.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Occasionally you find yourself in a situation where utilizing a shared account cannot be avoided. One such scenario is managing the deployment of NodeJS applications with Shipit and PM2. Here&#8217;s how the scenario typically works: Alice, Bob, and Carol are three developers working on NodeJS applications that need to be deployed to their staging server. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3318,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[],"class_list":["post-3798","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/3798"}],"collection":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/comments?post=3798"}],"version-history":[{"count":12,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/3798\/revisions"}],"predecessor-version":[{"id":3810,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/3798\/revisions\/3810"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media\/3318"}],"wp:attachment":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media?parent=3798"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/categories?post=3798"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/tags?post=3798"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}