TUTORIAL – RUBY API
Introduction
The Conjur API for Ruby provides a robust programmatic interface to Conjur. You can use the Ruby API to authenticate with Conjur, load policies, fetch secrets, perform permission checks, and more.
Prerequisites
- A Conjur server endpoint.
- The Conjur API for Ruby, version 5.0 or later.
Setup
To demonstrate the usage the Conjur API, we need some sample data loaded into the server.
Save this file as “conjur.yml”:
- !policy
id: db
body:
- &variables
- !variable password
- !group secrets-users
- !permit
resource: *variables
privileges: [ read, execute ]
roles: !group secrets-users
- !policy
id: backend
body:
- !webservice
- !group clients
- !permit
resource: !webservice
privilege: execute
roles: !group clients
- !policy
id: myapp
body:
- !layer
annotations:
description: My application layer
- !host myapp-01
- !grant
role: !layer myapp
member: !host myapp-01
- !grant
role: !group db/secrets-users
member: !host myapp-01
- !grant
role: !group backend/clients
member: !host myapp-01
It defines:
variable:db/password
Contains the database password.webservice:backend
layer:myapp
A layer (group of hosts) with access to the password and the webservice.
In addition, it ensures that the host myapp-01
is able to access the db/password
secret and is a member of the backend/clients
group that has permission to execute
the backend
webservice.
Load the policy using the following command:
$ conjur policy load --replace root conjur.yml
Loaded policy 'root'
{
"created_roles": {
"myorg:host:myapp-01": {
"id": "myorg:host:myapp-01",
"api_key": "1wgv7h2pw1vta2a7dnzk370ger03nnakkq33sex2a1jmbbnz3h8cye9"
}
},
"version": 1
}
Now, use OpenSSL to generate a random secret, and load it into the database password variable:
$ password=$(openssl rand -hex 12)
$ echo $password
ac8932bccf835a5a13586100
$ conjur variable values add db/password $password
Value added
$ conjur variable value db/password
ac8932bccf835a5a13586100
Configuration
The Ruby API is configured using the Conjur.configuration
object. The most important options are:
appliance_url
The URL to the Conjur server.account
The Conjur organization account name.
Create a new Ruby program, require the conjur-api
library, and set these two parameters in the following manner:
irb(main)> require 'conjur-api'
irb(main)> Conjur.configuration.appliance_url = "https://eval.conjur.org"
irb(main)> Conjur.configuration.account = "myorg" # <- REPLACE ME!
Authentication
Once the server connection is configured, the next step is to authenticate to obtain an access token. When you create a Conjur Host, the server issues an API key which you can use to authenticate as that host. Here’s how you use it in Ruby (note: token formatted and abridged in the interests of readability):
irb(main)> host_id = "host/myapp-01"
irb(main)> api_key = "1vgw4jzvyzmay95mrx2s5ad1d28gt3gh2gesb1411kqcah3nrv01r"
irb(main)> conjur = Conjur::API.new_from_key host_id, api_key
irb(main)> puts conjur.token
{
"protected": "eyJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJra
WQiOiIzZTY4N2E3N2Q0ZjkzOTkxYzZmMzBkMzkzYTNmZGM1MyJ9",
"payload": "eyJzdWIiOiJhbGljZSIsImlhdCI6MTUwNTgzMjg1NX0=",
"signature": "jzwY1MmbYQUEl[...]6l2OyHzDx"
}
CONJUR_AUTHN_API_KEY for the API key.
Secrets Fetching
Once authenticated, the API client can be used to fetch the database password:
irb(main)> variable = conjur.resource("#{Conjur.configuration.account}:variable:db/password")
irb(main)> puts variable.value
ef0a4822539369659fbfb267
Permission Checking
To check a permission, load the Conjur resource (typically a Webservice) on which the permission is defined.
Then use the permitted?
method to test whether the Conjur user has a specified privilege on the resource.
In this example, we determine that host:myapp-01
is permitted to execute
but not update
the resource webservice:backend
:
irb(main)> webservice = conjur.resource("#{Conjur.configuration.account}:webservice:backend")
irb(main)> puts webservice.permitted? 'execute'
true
irb(main)> puts webservice.permitted? 'update'
false
Webservice Authorization
Conjur can provide a declarative system for authenticating and authorizing access to web services. As we have seen above, the first step is to create a !webservice
object in a policy. Then, privileges on the web service can be managed using !permit
and !grant
.
In the runtime environment, a bit of code will intercept the inbound request and check for authentication and authorization. Here’s how to simulate that in an interactive Ruby session.
First, the web service client will authenticate with Conjur to obtain an access token. Then this token is formed into an HTTP Authorization header:
irb(main)> token = conjur.token
irb(main)> require 'base64'
irb(main)> token_header = %Q(Token token="#{Base64.strict_encode64 token.to_json}")
=> "Authorization: Token token=\"eyJkYXRh...k4YjQifQ==\""
Of course, the client does not have to be Ruby, it can be any language or even a tool like cURL.
On the server side, the HTTP request is intercepted and the access token is parsed out of the header.
irb(main)> token_header[/^Token token="(.*)"/]
irb(main)> token = JSON.parse(Base64.decode64($1))
Once the token is obtained, it can be used to construct a Conjur::API
object. Then the webservice
resource is obtained from the Conjur API, and the permission check is performed:
irb(main)> conjur = Conjur::API.new_from_token token
irb(main)> webservice = conjur.resource("#{Conjur.configuration.account}:webservice:backend")
irb(main)> puts webservice.permitted? 'execute'
true
If the token is expired or invalid, then the Conjur API will raise an authentication error. If the token is valid, but the client does not have the requested privilege, then permitted?
will return false
. In either case, the authorization interceptor should deny access to the web service function.
Note that different web service functions may have different required privileges. For example, a GET
method may require read
, and a DELETE
method may require update
. The semantic mapping between the web service methods and the Conjur privileges is up to you.
Sinatra Example
Now let’s run through an example of implementing the above using Sinatra.
First, let’s create a new folder for our demo project MyApp
:
$ mkdir my_app
$ cd my_app
Next, let’s create Gemfile
to manage our dependencies:
# Gemfile
source "https://rubygems.org"
gem 'conjur-api', git: 'https://github.com/conjurinc/api-ruby.git', branch: 'possum'
gem 'sinatra', '~> 2.0'
Note that we’re building the Conjur API gem from source rather than downloading it from Ruby Gems. The Conjur CE release marks a major overhaul of the API. This version will be published to Ruby Gems prior to our public release.
Now we can install all our required gems with:
$ bundle install
Now let’s setup our Rackup file:
# config.ru
require 'rubygems'
require 'bundler'
Bundler.require
# Setup our Conjur configuration
Conjur.configuration.account = ENV['CONJUR_ACCOUNT']
Conjur.configuration.appliance_url = ENV['CONJUR_URL']
require './my_app'
run Sinatra::Application
The above will let us pass our Conjur Account name and URL when we start the app.
Now let’s write a simple page that displays the secret stored in Conjur using the Host API token above:
# my_app.rb
class MyApp < Sinatra::Base
get "/" do
<<-CONTENT
<html>
<head></head>
<body>
<h1>Welcome to the Demo App</h1>
<p><strong>DB Password:</strong> #{retrieve_secret('db/password')}</p>
</body>
</html>
CONTENT
end
protected
# Helper method for managing a connection to the Conjur API
def conjur_api
@conjur_api ||= begin
Conjur::API.new_from_key(ENV['CONJUR_USER'], ENV['CONJUR_API_KEY'])
end
end
# Helper method to access the variable by its resource id (`account:resource_type:name`)
# The `resource` method will fail with:
# RestClient::Unauthorized if the entity does not have execute permission.
# RestClient::ResourceNotFound if the resource does not exist.
def retrieve_secret(variable)
conjur_api.resource("#{Conjur.configuration.account}:variable:#{variable}").value
end
end
The above application can be run with:
$ CONJUR_USER=host/myapp-01 \
CONJUR_API_KEY=host/myapp-01 API as used prior # <- REPLACE ME! \
CONJUR_ACCOUNT=myorg # <- REPLACE ME! \
CONJUR_URL=https://eval.conjur.org \
rackup
and displays the value of the secret stored in Conjur.
Next Steps
- Read the Ruby API code on GitHub.
- Visit the Ruby API code on RubyGems.