This is a Clojure lib to natively access the OCI Vault API.
You could of course use the Oracle-provided Java libs, but I find it cumbersome to convert to and from Java POJO's all the time. Also, they pull in lots of transitive dependencies which I want to avoid. This library is more Clojure-esque and uses HttpKit instead for a lower footprint.
First include the lib in your project:
;; Leiningen
:dependencies [[com.monkeyprojects/oci-vault "<version>"]]
Or with deps.edn
:
{:deps
{com.monkeyprojects/oci-vault {:mvn/version "<version>"}}}
The functionality can be found in monkey.oci.vault
ns. First create a client,
using your OCI configuration, then you can start invoking endpoints.
(require '[monkey.oci.vault :as v])
(def config
{:tenancy-ocid "your-tenancy-ocid"
:user-ocid "your-user-ocid"
:private-key <private-key>
:key-fingerprint "key-fingerprint"
:region "oci-region"})
;; Create a client
(def client (v/make-client config))
(def cid "<compartment-ocid>")
;; The client can be used to do general calls
(def vault (v/get-vault client {:vault-id "vault-ocid" :compartment-id cid}))
(def vault-keys (v/list-keys client {:vault-id (:id vault) :compartment-id cid}))
;; Encryption
(def key-id (-> vault-keys first :id))
;; Note that the text must be base64-encoded
(require '[monkey.oci.vault.b64 :as b])
(def enc (v/encrypt client {:key-id key-id :plaintext (b/->b64 "secret message")}))
;; => {:ciphertext "<base64-encoded cipher>" ...}
;; Decrypt it back
(def msg (-> (v/decrypt client {:key-id key-id :ciphertext (:ciphertext enc)})
:plaintext
(b/b64->str))) ; Make sure to decode back from base64
;; => "secret message"
The lib actually uses Martian to do the HTTP calls. This means all functions take an options map which is actually the arguments expected by the Martian call. This can be a combination of path params, query args and a request body. Check the [Oracle API docs](https://docs.oracle.com/en-us/iaas/api/] for more on which request requires which parameters.
The high-level calls declared in the vault
ns hide the complexities of working
with the Martian responses. Instead, they automatically check if the response is
successful, and if so, they unwrap the body of the response. Otherwise an exception
is thrown. If you need more control over how the requests are handled, check the
low-level calls below. Pagination is also handled by the wrapper functions for
list-
calls.
Currently, these calls are exposed by the vault library:
get-vault
list-vaults
list-keys
encrypt
decrypt
generate-data-encryption-key
list-secrets
create-secret
update-secret
get-secret
get-secret-bundle
get-secret-bundle-by-name
More will be added as needed, or you can add some yourself. PR's are welcome!
If you need more control over how the calls are processed, including using them in an async fashion, you can invoke the lower-level calls directly. These reside in several namespaces, depending on which kind of category you need.
The raw response of each call is returned, as a future
. This is intended
to allow the maximum flexibility when using the lib. You can inspect the http
status code yourself. The body is automatically parsed from JSON
, depending
on the Content-Type
header.
Note also that pagination is not handled by these low-level calls, so you will have to do that yourselves.
In order to do vault specific calls, you need to use the crypto endpoint of the vault itself. The same goes for key-specific calls, where you need the management endpoint. For this you have to create a special client, using either the vault id, or the endpoint itself. If the vault id is specified, the vault details will be fetched first in order to obtain the actual http endpoint.
(require '[monkey.oci.vault.crypto :as vc])
(require '[monkey.oci.vault.b64 :as b])
;; This will look up the endpoint
(def crypto-client (vc/make-crypto-client (assoc config :vault-id "vault-ocid")))
;; Alternatively you can add the endpoint directly
(def crypto-client (vc/make-crypto-client (assoc config :crypto-endpoint "http://crypto")))
;; Encryption expects a base64 string. There are utility functions for this.
(def enc (-> (vc/encrypt crypto-client {:key-id "key-ocid"
:key-version-id "version-ocid"
:plaintext (b/->b64 "very secret text to encrypt")})
deref
:body
:ciphertext))
;; Decryption returns base64 encoded plain text
(def dec (-> (vc/decrypt crypto-client {:key-id "key-ocid"
:key-version-id "version-ocid"
:ciphertext enc})
deref
:body
:plaintext
u/b64->
(String.)))
In a similar fashion you can create a management client using make-mgmt-client
.
You can specify a :key-id
or :mgmt-endpoint
. See the monkey.oci.vault.mgmt
namespace for this.
You can also generate a data encryption key. The Oracle documentation is a bit sparse about this, but it returns an AES key that can be used to do encryption/decryption without having to go to the Vault API for every call. For instance, using buddy.core:
;; Generate key
(def key (-> (vc/generate-data-encryption-key client
{:key-details
{:key-id "key-ocid"
:include-plaintext-key true
:key-shape
{:algorithm "AES"
:length 32}}}) ; Must be 16, 24 or 32
(deref)
:body
:plaintext))
(require '[buddy.core.codecs :as codecs])
(require '[buddy.core.crypto :as bcc])
(require '[buddy.core.nonce :as bcn])
;; Generate initialization vector, must be 16 bytes long
(def iv (bcn/random-nonce 16))
;; Convert key to bytes
(def key-bytes (codecs/b64->bytes key))
;; Do some encryption
(def enc (bcc/encrypt (codecs/str->bytes "my secret message")
key-bytes
iv
{:algo :aes-256-gcm})) ; Algo depends on your AES key size
You can also create, update or retrieve secrets stored in the vault. To this end, you first
need to create the appropriate client. Then you can invoke the appropriate functions: create-secret
,
update-secret
and get-secret
.
(require '[monkey.oci.vault.secrets :as vs])
(def client (vs/make-secret-client config))
;; Create new secret
(def s (-> (vs/create-secret client
{:secret
{:compartment-id "compartment-ocid"
:vault-id "vault-ocid"
:key-id "key-ocid"
:secret-name "my-test-secret"
:secret-content
{:content-type "BASE64"
:content "<some base64 string>"}}})
(deref)
:body)
;; Update it
@(vs/update-secret client {:secret-id (:id s)
:secret {:description "updated description"}})
;; Retrieve it
(-> (vs/get-secret client {:secret-id (:id s)})
(deref)
:body)
Note that get-secret
does not return the secret contents! For this you need to
create another client (why, Oracle?) using make-secret-retrieval-client
. Then you
can use get-secret-bundle
or get-secret-bundle-by-name
to fetch the contents.
(require '[monkey.oci.vault.secrets :as vs])
(def client (vs/make-secret-retrieval-client config))
(-> (vs/get-secret-bundle client
{:secret-id "secret-ocid"
:stage "CURRENT"})
(deref)
:body)
Copyright (c) 2024 by Monkey Projects BV
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close