Tutorial - Custom Authentication
Conjur web service functions require an access token to authenticate virtually all requests. An access token is a cryptographically signed, time-limited JSON object.
A client can obtain an access token using the
authenticate method which is described fully in the Authentication reference. The credential presented to
authenticate is an API key, which can be strengthened using an IP or CIDR restriction.
Taken together, the API key and IP/CIDR restriction can serve as reasonably strong credentials which are useful for many use cases. However, there are situations where an API key and IP/CIDR is not ideal. For example:
- When clients are rapidly created and destroyed.
- When IP addresses are not stable or cannot be constrained in a useful way.
For these situations, Conjur enables you to implement your own authentication provider. Custom authenticators should accept some domain-specific credentials, verify them, and issue an access token. This is the proper way to authenticate when BOTH of the following conditions are satisfied:
- Clients are highly ephemeral.
- An external authority is available to help verify client identity.
Some examples of environments where custom authentication is useful include Kubernetes, OpenShift, Mesos, Docker Swarm, Pivotal CloudFoundry, Jenkins, and IaaS e.g. AWS (in some cases).
Custom authenticators can be written in any language. However, this tutorial uses Ruby examples. So, you’ll need a working Ruby environment.
It’s also helpful to have a local Conjur server (e.g. in your laptop’s Docker engine) so that you can directly access the database and inspect the token-signing keys.
The simplest way to write a custom authenticator is to write a Ruby webservice using a simple framework like Sinatra. This way, you can use the slosilo Ruby gem which has built-in support for issuing (and verifying) Conjur access tokens. It’s certainly possible to port the token-issuing code to other languages, since it uses standard cryptographic techniques. However, be aware that the Slosilo library has been reviewed by a professional cryptographic audit; therefore it is advantageous to use it without modification.
To issue an access token, you need two things:
- A signing key, which is a 2048-bit RSA private key.
- The identity of the role for whom you want to issue the token.
Here’s a snippet showing how easy it is to issue an access token for a user called “alice”:
In the example above, we generated a new RSA key to sign the token. You can’t use this approach to make a custom authenticator, because your Conjur server won’t recognize the signing key that you used.
What you need to do is use the signing key for the organization account for which you’ll be issuing tokens. There are two ways that you can obtain this signing key:
- Run your custom authenticator with a connection to the Conjur database.
- Extract the signing key from the database and provide it to your custom authenticator.
Using the Database-Stored Signing Key
The Conjur server stores the signing keys encrypted in the database. If your custom authenticator is configured with a database connection, you can fetch the signing key using SQL (or the Ruby object-relational helper code).
Since the signing keys are encrypted, connecting to the database is not sufficient to read one. You also need to have the encryption key, which you provide to the Conjur server using the environment variable
In Ruby code, it looks like this:
Extracting the Signing Key
Keep in mind that a signing key is a very sensitive piece of data. Someone with the signing key can issue access tokens for any role in the organization account (including “admin”). So, if you extract the key from the Conjur database, be sure and keep it tighly secured. Use of an HSM or key store such as Amazon KMS is recommended.
To extract a signing key, you can run the following Ruby command:
The URL to obtain an access token is
POST /:account/:login/authenticate. The parameters are:
accountThe organization account.
loginThe login name of the authenticating role. Because the login is part of the URL route, it must be URL-encoded.
For users, the
login is the username (example: “alice”). For machines, the
login is the prefix “host” followed by the host id (example: “host/prod/frontend/frontend-001”, or as a URI path component “host%2Fprod%2Ffrontend%2Ffrontend-001”).
login is the value that should be the payload of the access token.
As an example, we will implement an authenticator which will always issue an access token for the user named “public”.
First, install dependencies:
$ gem install sinatra slosilo sequel
Then create the file “public.rb”:
Now run the authenticator in the background:
Then send a
POST request to authenticate as the account user “public”:
Now send a
POST request to authenticate as the (invalid) account user “alice”:
In the example above, we used cURL to interact with the custom authenticator. How about the Conjur API clients and CLI?
These can be configured to use a custom authenticator by setting the environment variable
CONJUR_AUTHN_URL or by setting the configuration setting
Here’s how it works with the Conjur API for Ruby:
In this tutorial, we’ve explored Conjur authentication in detail. Custom authenticators can issue access tokens if they have:
- A strong means of authenticating the client.
- Access to the organization account signing key.
When these two pieces of information are available, custom authentication offers a powerful strategy for authenticating ephemeral jobs and processes. Note however that an improperly developed custom authenticator is a severe security risk to the Conjur system. With great power comes great responsibility, so proceed with caution and enlist feedback and review from experienced community members.