cyberark/conjur-api-ruby

View on GitHub
lib/conjur/variable.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
90%
#
# Copyright 2013-2017 Conjur Inc
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
module Conjur

  # Protected (secret) data stored in Conjur.
  # 
  # The code responsible for the actual encryption of variables is open source as part of the
  # {https://github.com/conjurinc/slosilo Slosilo} library.
  #
  # Each variables has some standard metadata (`mime-type` and secret `kind`).
  #
  # Variables are *versioned*.  Storing secrets in multiple places is a bad security practice, but
  # overwriting a secret accidentally can create a major problem for development and ops teams.  Conjur
  # discourages bad security practices while avoiding ops disasters by storing previous versions of
  # a secret (up to a fixed limit, to avoid unbounded database growth).
  #
  # ### Important
  # A common pitfall when trying to access older versions of a variable is to assume that `0` is the oldest
  # version.  Variable versions are `1`-based, with `1` being the oldest.
  #
  # ### Permissions
  #
  # * To *fetch* the value of a `variable`, you must have permission to `'execute'` the variable.
  # * To *add* a value to a `variable`, you must have permission to `'update'` the variable.
  # * To *show* metadata associated with a variable, but *not* the value of the secret, you must have `'read'`
  #     permission on the variable.
  #
  # @example Get a variable and access its metadata and the latest value
  #   variable = api.resource 'myorg:variable:example'
  #   puts variable.kind      # "example-secret"
  #   puts variable.mime_type # "text/plain"
  #   puts variable.value     # "supahsecret"

  # @example Variables are versioned
  #   variable = api.resource 'myorg:variable:example'
  #   # Unless you set a variables value when you create it, the variable starts out without a value and version_count
  #   # is 0.
  #   var.version_count # => 0
  #   var.value # raises RestClient::ResourceNotFound (404)
  #
  #   # Add a value
  #   var.add_value 'value 1'
  #   var.version_count # => 1
  #   var.value # => 'value 1'
  #
  #   # Add another value
  #   var.add_value 'value 2'
  #   var.version_count # => 2
  #
  #   # 'value' with no argument returns the most recent value
  #   var.value # => 'value 2'
  #
  #   # We can access older versions by their 1 based index:
  #   var.value 1 # => 'value 1'
  #   var.value 2 # => 'value 2'
  #   # Notice that version 0 of a variable is always the most recent:
  #   var.value 0 # => 'value 2'
  #
  class Variable < BaseObject
    include ActsAsResource

    def as_json options={}
      result = super(options)
      result["mime_type"] = mime_type
      result["kind"] = kind
      result
    end
    
    # The kind of secret represented by this variable,  for example, `'postgres-url'` or
    # `'aws-secret-access-key'`.
    #
    # You must have the **`'read'`** permission on a variable to call this method.
    #
    # This attribute is only for human consumption, and does not take part in the Conjur permissions
    # model.
    #
    # @note this is **not** the same as the `kind` part of a qualified Conjur id.
    # @return [String] a string representing the kind of secret.
    def kind
      parser_for(:variable_kind, variable_attributes) || "secret"
    end

    # The MIME Type of the variable's value.
    #
    # You must have the **`'read'`** permission on a variable to call this method.
    #
    # This attribute is used by the Conjur services to set a response `Content-Type` header when
    # returning the value of a variable.  Conjur applies the same MIME Type to all versions of a variable,
    # so if you plan on accessing the variable in a way that depends on a correct `Content-Type` header
    # you should make sure to store appropriate data for the mime type in all versions.
    #
    # @return [String] a MIME type, such as `'text/plain'` or `'application/octet-stream'`.
    def mime_type
      parser_for(:variable_mime_type, variable_attributes) || "text/plain"
    end

    # Add a new value to the variable.
    #
    # You must have the **`'update'`** permission on a variable to call this method.
    #
    # @example Add a value to a variable
    #   var = api.variable 'my-secret'
    #   puts var.version_count     #  1
    #   puts var.value             #  'supersecret'
    #   var.add_value "new_secret"
    #   puts var.version_count     # 2
    #   puts var.value             # 'new_secret'
    # @param [String] value the new value to add
    # @return [void]
    def add_value value
      log do |logger|
        logger << "Adding a value to variable #{id}"
      end
      invalidate do
        route = url_for(:secrets_add, credentials, id)
        route.post value
      end
    end

    # Return the number of versions of the variable.
    #
    # You must have the **`'read'`** permission on a variable to call this method.
    #
    # @example
    #   var.version_count # => 4
    #   var.add_value "something new"
    #   var.version_count # => 5
    #
    # @return [Integer] the number of versions
    def version_count
      secrets = attributes['secrets']
      if secrets.empty?
        0
      else
        secrets.last['version']
      end
    end

    # Return the version of a variable.
    #
    # You must have the **`'execute'`** permission on a variable to call this method.
    #
    # When no argument is given, the most recent version is returned.
    #
    # When a `version` argument is given, the method returns a version according to the following rules:
    #  * If `version` is 0, the *most recent* version is returned.
    #  * If `version` is less than 0 or greater than {#version_count}, a `RestClient::ResourceNotFound` exception
    #   will be raised.
    #  * If {#version_count} is 0, a `RestClient::ResourceNotFound` exception will be raised.
    #  * If `version` is >= 1 and `version` <= {#version_count}, the version at the **1 based** index given by `version`
    #    will be returned.
    #
    # @example Fetch all versions of a variable
    #   versions = (1..var.version_count).map do |version|
    #     var.value version
    #   end
    #
    # @example Get the current version of a variable
    #   # All of these return the same thing:
    #   var.value
    #   var.value 0
    #   var.value var.version_count
    #
    # @example Get the value of an expired variable
    #   var.value nil, show_expired: true
    #
    # @param [Integer] version the **1 based** version.
    # @param options [Hash]
    # @option options [Boolean, false] :show_expired show value even if variable has expired
    # @return [String] the value of the variable
    def value version = nil, options = {}
      options['version'] = version if version
      url_for(:secrets_value, credentials, id, options).get.body
    end

    private

    def variable_attributes
      @variable_attributes ||= url_for(:variable_attributes, credentials, self, id)
    end
  end  
end