NAME ==== KHPH.pm6 VERSION ======= 0.0.7 TITLE ===== Keep Honest People Honest SUBTITLE ======== Unsecure Reversible Obfuscated Character Storage & Retrieval Disclaimer ========== > Scenario: If neighborhood dogs regularly enjoyed leaving personal gifts on your rear lawn, would you consider installing a privacy fence that fully encloses your backyard? It wouldn't deter burglars, but it would certainly remedy your immediate problem. This module scrambles and stashes a string to help keep it private, but, by definition, that means that the scrambled string can be unscrambled by someone other than the owner. One might ask, "Why even bother providing a scrambling function when it can definitely be reversed?" It's helpful to have such a function because there are still cases of intolerable data exposure in the real world. We network-isolate our systems, register users on an as-needed-only basis, and employ dozens of other measures to prevent unwanted intrusions. However, passwords and other private items are still often left surprisingly exposed. What if you you need to run a program that absolutely requires you to include a *password* in its command line invocation? myuid@myserver> /usr/local/bin/srvrconn -acct=U028441 -password=pAsSwOrD57! START INSTANCE ABC If you run it interactively, your shell history will record the entire command line for posterity, including the exposed password. Then your system backup will make a copy of that, and who knows where that goes and for how long? What if your program must be executed via a job scheduler? 00 01 * * 7 /opt/SomeApp/bin/someappmgr --id=xyx259 --pa=PaSsWoRd22@! run_weekly_reports The password would be exposed in multiple places: crontab, logs, email, backups, etc. You might consider a solution where you put the secret characters in a directory/file and judiciously apply DAC controls to restrict access (chown/chgrp/chmod). When it's time to use the password, you could read the secret string from the file. But *root* would be able to look at your secret with a quick `cat` command, and then your secret wouldn't be a secret anymore. And don't forget that the pesky system backup scooped it up too, so it's probably floating around in some cloud somewhere. This module offers you a way to reduce the likelihood of exposing your unprotected secret to curious people who are just poking around. It also aims to reduce the number of surfaces where your private data is exposed openly. It does not purport to fully protect your secret information from prying eyes, but rather to make it opaque to glances. In terms of snoop prevention, the idea is that if you sufficiently scramble the secret string before storing it, reversing the scramble manually could induce just enough ennui to dissuade the amateur cracker sitting in the next cubicle. Anyone trying to unscramble your scrambled string would have to put in a little effort. They would be deliberately committing themselves to a bad act to get at your secret, unable to claim that they were just innocently browsing files. Why make it effortless for them? Use this module if you are planning to store your clear-text secret string in a vulnerable place because you've determined that you don't have a choice. Just remember, this module's obfuscation is relatively trivial. You'll probably be able to deter most nosy people, but not a committed cracker. Use this module as a last resort, which is better than nothing, until you find more effective protection for your private data. Think of it like the difference in playing hide-and-go-seek with a 2-year-old versus a 10-year-old. The 2-year-old will stand against a wall in plain view with their hands over their eyes giggling, while the 10-year-old will stay out of sight and make you work for it in total silence. > ALWAYS ENCRYPT CUSTOMER DATA. Customer data requires Fort Knox treatment, not a backyard privacy fence. Description =========== This module will scramble a string, stash it wherever you specify, then expose it to you whole again when you ask for it, interactively or in batch (I.e. CRON). *root* can’t expose it directly, unless *root* originally stored it. `su`’ing into the owner’s account from a different account won’t expose it directly either. It’s not in the direct line of sight by anyone other than the owner. Synopsis ======== ```perl6 use KHPH; my $userid = 'testid'; my KHPH $secret-string .= new( :herald('myapp credentials'), :prompt($userid ~ ' password'), :stash-path('/tmp/myapp/' ~ $userid ~ '/mysecret'), :user-exclusive-at('/tmp/myapp/' ~ $userid); ); say $secret-string.expose; ``` Methods ======= .new() ------ Generate a KHPH object #### :herald? * Optional announcement used only when interactively stashing the secret. #### :prompt? * Optional prompt string used only when interactively stashing the secret. #### :secret? * Optionally send the constructor the secret string. No prompting will occur. #### :stash-path! * Specify the path (directories/file) to create or find the stash file. Always use a subdirectory to store the secret, as KHPH will `chmod` the directory containing the stash file. #### :user-exclusive-at? * Optionally specify a segment of the :stash-path to exclude all group & other access (0700). .expose() --------- Return the secret as a clear-text Str. Example I ========= > First, acknowledge that someone devious would be able to unscramble the scrambled string produced by this module in the below example. The following stand-alone script, named `myapp-pass.p6`, will manage the password stash of `myapp`. Run it interactively one time to stash your secret, then you (not someone else) can run it anytime to expose the secret. The `myapp-pass.p6` script: ```perl6 #!/usr/local/bin/perl6 use KHPH; KHPH.new(:stash-path('/tmp/.myapp/password.khph')).expose.print; ``` Run ~/myapp-pass.p6 once interactively to stash the secret: me@mysystem> ~/myapp-pass.p6 && echo [1/2] Enter secret> aW3S0m3pA55w0rDI'LlN3VeRr3m3mB3R [2/2] Enter secret> aW3S0m3pA55w0rDI'LlN3VeRr3m3mB3R aW3S0m3pA55w0rDI'LlN3VeRr3m3mB3R me@mysystem> > _Notice how the script dumps the secret when you personally run it? Have someone else log into the same system, have them run the same script, and see what they get. Have them `su` to your account and try again. Have them log in as *root* and give it a go. Have them `su` from root into your account and try. Have them `sudo su -` into your account and try again._ Then in your application client: me@mysystem> /usr/bin/dsmadmc -id=MYSELF -password=`~/myapp-pass.p6` QUERY SESSION FORMAT=DETAILED The password will be inserted into the command line and authentication will succeed. > __Note__: _The above example demonstrates a particular application client (familiar to some backup admins) that is smarter than most, in that it re-writes the process' args after the program launches. `ps` will only display the string `-password=*******` instead of the actual password string. Not all application vendors pay attention to such details, so beware -- `ps` could be displaying the secret despite your efforts to protect it._ Example II ========== > First, acknowledge that someone devious would be able to unscramble the scrambled strings produced by this module in the following example. The following contrived `acme-connect` script, which manages the fictitious ACME application, is implemented so that all passwords are stored in a common directory: /var/raduko/.credentials Ensure that all users can descend to that directory. It would be ideal to set 1777 to the last directory in that path. Different users run the script to connect to instances of the ACME application on multiple hosts. | OS Login | Application Host | Application UserId | Application Password | | -------- | ----------------- | ------------------ | --------------------- | | user_a | acme1.myco.com | ACMEUSERX | pAsSwOrDx | | user_b | acme1.myco.com | ACMEUSERY | pAsSwOrDy | | user_c | acme2.myco.com | ACMEUSERZ | pAsSwOrDz | The `acme-connect` script: ```perl6 #!/opt/rakudo/bin/perl6 use KHPH; sub MAIN ( :$acme-host is required, #= ACME Host :$acme-id is required, #= ACME UserId :$start-monthly-batch, #= Launch monthly batch processing ) { my KHPH $passwd .= new( :herald('ACME credentials'), :prompt($acme-id ~ '@' ~ $acme-host ~ ' password'), :stash-path('/var/raduko/.credentials/' ~ $*USER ~ '/ACME/' ~ $acme-host ~ '/' ~ $acme-id), :user-exclusive-at('/var/raduko/.credentials/' ~ $*USER), ); # Assemble the acme-manager command parts my @cmd = '/usr/bin/acme-manager', '-serv=' ~ $acme-host, '-acct=' ~ $acme-id, '-pass=' ~ $passwd.expose; if $start-monthly-batch { @cmd.push: '-m_end'; } else { @cmd.push: '-stat'; } # Run ACME run @cmd; # hope /usr/bin/acme-manager masks the -pass=... } ``` The first time the `acme-connect` script is run interactively by **user_a** from the **linux5** server: ``` user_a@linux5> acme-connect --acme-host=acme1.myco.com --acme-id=ACMEUSERX ACME credentials [1/2] ACMEUSERX@acme1.myco.com password> pAsSwOrDx [2/2] ACMEUSERX@acme1.myco.com password> pAsSwOrDx ACME Status Report: A-OK user_a@linux5> ```` KHPH will produce the following hierarchy: * 1777 /var/raduko/.credentials/ user_a 0700 /var/raduko/.credentials/user_a/ user_a 0700 /var/raduko/.credentials/user_a/ACME/ user_a 0700 /var/raduko/.credentials/user_a/ACME/acme1.myco.com/ user_a 0600 /var/raduko/.credentials/user_a/ACME/acme1.myco.com/APPUSERX The `acme-connect` script will not prompt **user_a** for the application password again when connecting to acme1.myco.com as APPUSERX from **linux5**. **user_a** can run the `acme-connect` script as above, along with additional switches, in a job scheduler for subsequent unattended execution on the **linux5** server. When the `acme-connect` script is run interactively by **user_c** from the **linux5** server: user_c@linux5> acme-connect --acme-host=acme2.myco.com --acme-id=ACMEUSERZ ACME credentials [1/2] ACMEUSERZ@acme2.myco.com password> pAsSwOrDz [2/2] ACMEUSERZ@acme2.myco.com password> pAsSwOrDz ACME Status Report: A-OK user_a@linux5> The following hierarchy will result: * 1777 /var/raduko/.credentials/ user_a 0700 /var/raduko/.credentials/user_a/ user_a 0700 /var/raduko/.credentials/user_a/ACME/ user_a 0700 /var/raduko/.credentials/user_a/ACME/acme1.myco.com/ user_a 0600 /var/raduko/.credentials/user_a/ACME/acme1.myco.com/APPUSERX user_c 0700 /var/raduko/.credentials/user_c/ user_c 0700 /var/raduko/.credentials/user_c/ACME/ user_c 0700 /var/raduko/.credentials/user_c/ACME/acme2.myco.com/ user_c 0600 /var/raduko/.credentials/user_c/ACME/acme2.myco.com/APPUSERZ The `acme-connect` script will not prompt **user_c** for the application password again when connecting to acme2.myco.com as APPUSERZ from **linux5**. **user_c** can run the `acme-connect` script as above, along with additional switches, in a job scheduler for subsequent unattended execution on the **linux5** server. Example III =========== When crafting REST API clients, servers will often issue a session token to serve as a reusable authentication mechanism. These session tokens remain valid for long intervals of time (days, weeks) and should be protected like passwords. When stashing a token locally for reuse, and you plan to store it in a file in clear-text form, minimally use KHPH instead to scramble it so that it isn't easily viewed by passers-by. Usage Recommendation ==================== Since the intent of using this module is to obfuscate, it is recommended to specify a :stash-path that doesn't indicate what's being stored. This looks innocuous: :stash-path($*HOME ~ '/.metrics/' ~ $account ~ '/' ~ $server ~ '/stats') This wouldn't generate much interest: :stash-path('/var/dynaplex/.perf/' ~ $account ~ '/dynaplex.' ~ $server) :user-exclusive-at('/var/dynaplex/.perf/' ~ $account) These are misleading paths, resulting in added camouflage. Every little bit helps. Limitations =========== Only developed on Linux. Author === Mark Devine