🔑 Open sourcing Keyscope!

Keyscope is an open source one-stop-shop tool built in Rust, made for developers and security engineers that need to automatically validate (and rotate, in the future) keys, tokens, passwords that were issued by a wide array of any of the SaaS APIs they use such as: Dropbox, Github, Zendesk, Twilio, and more (currently supporting more than 30 providers).

You might also want to take a look at service-policy-kit which is what powers Keyscope.

When should you validate your API keys?

During different occasions in building software, and throughout the organization, there are a few ways a key needs to be validated to assess an active risk and extract mitigation actions, or invalidated when wanting to remediate a risk to your organization:

  • When mistakenly exposing a secret or key to the public. For example, a mistaken commit finding itself to a public repository.
  • When leaking a secret from a customer by mistake. For example when a customer would send out an issue report on Github Issues, and you’d find one of their keys there.
  • When finding and removing secrets in code, that shouldn’t have been hardcoded.
  • As a security engineer, when you get a list of keys that should have been invalidated, due to company policies — but you’re not sure that it’s been actually done, and you have to validate yourself.
  • When a SaaS vendor notifies you of a breach, and recommends to re-issue all of its keys.

Using Keyscope

Grab a release from releases, or install via Homebrew:

brew tap spectralops/tap && brew install keyscope

You can try out validating a key for a provider, say, Github (assuming the key is in the GITHUB_TOKEN environment variable):

$ keyscope validate github $GITHUB_TOKEN

You can see which other providers are supported by running:

$ keyscope validate --providers  .
:
.
twilio:validation
keyscope validate twilio -p p1 p2
twitter:validation
keyscope validate twitter -p p1
zendesk:validation
keyscope validate zendesk -p p1 p1
Total 33 providers available.

And what parameters are required for a certain provider by running (say, stripe):

$ keyscope validate stripe --requirementsprovider stripe requires:
- param: p1
desc: stripe key
$

Finally the general structure of the validate command is:

$ keyscope validate PROVIDER -p PARAM1 PARAM2 .. PARAM_N

✅ Validation: key should be active

You can validate a specific provider like so:

$ keyscope validate twilio -p $TWILIO_KEY

With the general pattern of:

$ keyscope validate PROVIDER -p PARAM1 PARAM2 ...

The number of keys/params would change based on authentication type:

  • Bearer - usually just a single key (token)
  • Basic Auth - usually 2 keys: user, password
  • OAuth - usually 2 keys: client_id, client_secret
  • And others.

Each provider in Keyscope will tell you what it requires using requirements:

$ keyscope validate twilio --requirements

You’ll get a report:

$ keyscope --verbose validate stripe -p $STRIPE_KEY✔ stripe:validation: ok 766msRan 1 interactions with 1 checks in 766msSuccess: 1
Failure: 0
Error: 0
Skipped: 0

And an executable exit code that reflects success/failure.

You can use the --verbose flag to see API responses:

$ keyscope --verbose validate stripe -p $STRIPE_KEY✗ stripe:validation: failed 413ms
status_code: 200 != 401 Unauthorized
Ran 1 interactions with 1 checks in 413msSuccess: 0
Failure: 1
Error: 0
Skipped: 0

In this case the exit code is 1 (failure).

❌ Validation: key should be inactive

When you are validating keys that are supposed to be inactive, you can use the flip flag. In this mode, a failed API access is a good thing, and the exit code will reflect that.

$ keyscope --flip validate stripe -p $STRIPE_KEY✔ stripe:validation: ok 766msRan 1 interactions with 1 checks in 766ms

In this case, the key is active — which is bad for us. Using --flip, the exit code will be 1 (failure).

🍰 Adding your own providers: Dropbox

As a simple tutorial here’s how to add Dropbox by yourself.

$ keyscope -f providers.yaml validate dropbox -p MY_KEY

To validate if a dropbox API key works, we first need to learn about the canonical way to authenticate against that API.

First stop, API docs:

Next stop, we want to find an API call that is a representative for:

  • Has to be authenticated
  • Has to indicate that when accessed successfully with our candidate key, the key has some authoritative value. Which means, that if exposed, contains significant risk.

For this example, getting our current account sounds like something that only when we identify who we are — we’re able to do.

We’ll select get_current_account.

Let’s start forming our interaction. First the needed skeleton: containing the name of the provider (dropbox), its ID and description below, as well as parameters required and their name and description:

dropbox:
validation:
request:
id: "dropbox:validation"
desc: "dropbox: valid API credentials"
params:
- name: dropbox_1
desc: dropbox token

We keep the name of the parameter with a special convention that helps when feeding keyscope automatically:

PROVIDER_N
Where 'N' starts in 1 e.g.:
dropbox_1
dropbox_2
aws_1
...

Then, details about actually making an HTTP call, as required by Dropbox (Bearer token authentication).

uri: https://api.dropboxapi.com/2/users/get_current_account
method: post
headers:
Authorization:
- Bearer {{dropbox_1}}

Note that per standard, all HTTP header fields are actually arrays. It’s OK to always make an array of size one if you only have one value (most common case).

We also see variable interpolation here. Where {{dropbox_1}} will get replaced by keyscope in time before making the actual call.

Finally, we want to make sure we answer the question:

  • What does it mean to have a successful call?

In our case, the Dropbox API call returns HTTP OK on success, which means a 200 status code.

And the final, complete result is this:

dropbox:
validation:
request:
id: "dropbox:validation"
desc: "dropbox: valid API credentials"
params:
- name: dropbox_1
desc: dropbox token
uri: https://api.dropboxapi.com/2/users/get_current_account
method: post
headers:
Authorization:
- Bearer {{dropbox_1}}
response:
status_code: "200"

Meanwhile, you can drop this provider in your own providers.yaml file and run keyscope:

$ keyscope -f providers.yaml validate dropbox -p MY_KEY

Now you can keep this in your private providers.yaml file or contribute it back to keyscope if you think other people might enjoy using it - we're happy to accept pull requests.

If you’d like to contribute to Keyscope or service-policy-kit — you’re more than welcome. Please feel free to submit PR or just ask around and we’ll be there for explaining how things work.

--

--

--

@jondot | Founder & CEO @ Spectral. Rust + FP + Hacking + Cracking + OSS. Previously CTO @ HiredScore, Como, Conduit.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Direct Left-Recursive Alternative Patterns

5 Reasons Why You Should be Integrating Your Microservices On A Message Bus

🕵🏻‍♂️ New Airdrop: The MetaFire

BGP Flowspec — The SDN Dark Horse (part 2)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Dotan Nahum

Dotan Nahum

@jondot | Founder & CEO @ Spectral. Rust + FP + Hacking + Cracking + OSS. Previously CTO @ HiredScore, Como, Conduit.

More from Medium

InitController for Sidecar IPTables

FATA[0009] FAILED TO CREATE API: UNABLE TO RUN POST-SCAFFOLD TASKS OF “BASE.GO.KUBEBUILDER.IO/V3”:

How to use “Type safe routing” of axum

Importing private repos from Github