conjur-api 4.13.0 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile +0 -1
  5. data/README.md +59 -18
  6. data/conjur-api.gemspec +2 -1
  7. data/lib/conjur/acts_as_asset.rb +6 -9
  8. data/lib/conjur/acts_as_role.rb +54 -7
  9. data/lib/conjur/annotations.rb +52 -14
  10. data/lib/conjur/api/audit.rb +54 -8
  11. data/lib/conjur/api/authn.rb +54 -9
  12. data/lib/conjur/api/groups.rb +67 -4
  13. data/lib/conjur/api/hosts.rb +62 -3
  14. data/lib/conjur/api/layers.rb +48 -3
  15. data/lib/conjur/api/pubkeys.rb +140 -9
  16. data/lib/conjur/api.rb +13 -1
  17. data/lib/conjur/base.rb +93 -6
  18. data/lib/conjur/configuration.rb +247 -20
  19. data/lib/conjur/exists.rb +26 -0
  20. data/lib/conjur/group.rb +50 -4
  21. data/lib/conjur/has_attributes.rb +66 -5
  22. data/lib/conjur/resource.rb +155 -13
  23. data/lib/conjur/role.rb +1 -1
  24. data/lib/conjur/standard_methods.rb +7 -2
  25. data/lib/conjur/variable.rb +168 -6
  26. data/lib/conjur-api/version.rb +1 -1
  27. data/lib/conjur-api.rb +2 -0
  28. data/spec/api/authn_spec.rb +6 -3
  29. data/spec/api/groups_spec.rb +1 -1
  30. data/spec/api/pubkeys_spec.rb +4 -4
  31. data/spec/api/roles_spec.rb +4 -2
  32. data/spec/api/users_spec.rb +2 -2
  33. data/spec/api/variables_spec.rb +1 -1
  34. data/spec/helpers/errors_matcher.rb +34 -0
  35. data/spec/helpers/request_helpers.rb +10 -0
  36. data/spec/lib/annotations_spec.rb +5 -2
  37. data/spec/lib/audit_spec.rb +5 -5
  38. data/spec/lib/group_spec.rb +1 -1
  39. data/spec/lib/resource_spec.rb +11 -11
  40. data/spec/lib/role_spec.rb +8 -7
  41. data/spec/lib/user_spec.rb +7 -3
  42. data/spec/ssl_spec.rb +85 -0
  43. data/spec/standard_methods_helper.rb +2 -0
  44. data/spec/variable_spec.rb +5 -3
  45. metadata +34 -7
  46. data/lib/conjur/patches/rest-client.rb +0 -32
@@ -21,8 +21,11 @@
21
21
  require 'conjur/host'
22
22
 
23
23
  module Conjur
24
+
24
25
  class API
25
26
  class << self
27
+ # @api private
28
+ # TODO WTF does this do!
26
29
  def enroll_host(url)
27
30
  if Conjur.log
28
31
  Conjur.log << "Enrolling host with URL #{url}\n"
@@ -36,13 +39,69 @@ module Conjur
36
39
  [ mime_type, body ]
37
40
  end
38
41
  end
39
-
40
- def create_host options = {}
42
+
43
+ #@!group Directory: Hosts
44
+
45
+ # Create a new `host` asset.
46
+ #
47
+ # By default this method will create a host with a random id. However, you may create a host with a
48
+ # specific name by passing an `:id` option.
49
+ #
50
+ # ### Permissions
51
+ #
52
+ # * Any Conjur role may perform this method without an `:ownerid` option. The new hosts owner will be the current role.
53
+ # * If you pass an ``:ownerid` option, you must be a member of the given role.
54
+ #
55
+ # @example
56
+ # # Create a host with a random id
57
+ # anon = api.create_host
58
+ # anon.id # => "wyzg17"
59
+ #
60
+ # # Create a host with a given id
61
+ # foo = api.create_host id: 'foo'
62
+ # foo.id # => "foo"
63
+ #
64
+ # # Trying to create a new host named foo fails
65
+ # foo2 = api.create_host id: 'foo' # raises RestClient::Conflict
66
+ #
67
+ # # Create a host owned by user 'alice' (assuming we're authenticated as
68
+ # # a role of which alice is a member).
69
+ # alice_host = api.create_host id: "host-for-alice", ownerid: 'conjur:user:ailce'
70
+ # alice_host.ownerid # => "conjur:user:alice"
71
+ #
72
+ # @param [Hash,nil] options options for the new host
73
+ # @option options [String] :id the id for the new host
74
+ # @option options [String] :ownerid set the new hosts owner to this role
75
+ # @return [Conjur::Host] the created host
76
+ # @raise RestClient::Conflict when id is given and a host with that id already exists.
77
+ # @raise RestClient::Forbidden when ownerid is given and the owner role does not exist, or you are not
78
+ # a member of the owner role.
79
+ #
80
+ def create_host options = nil
41
81
  standard_create Conjur::Core::API.host, :host, nil, options
42
82
  end
43
-
83
+
84
+ # Get a host by its *unqualified id*.
85
+ #
86
+ # Like other Conjur methods, this will return a {Conjur::Host} whether
87
+ # or not the record is found, and you must use the {Conjur::Exists#exists?} method
88
+ # to check this.
89
+ #
90
+ # @example
91
+ # api.create_host id: 'foo'
92
+ # foo = api.host "foo" # => returns a Conjur::Host
93
+ # puts foo.resourceid # => "conjur:host:foo"
94
+ # puts foo.id # => "foo"
95
+ # mistake = api.host "doesnotexist" # => Also returns a Conjur::Host
96
+ # foo.exists? # => true
97
+ # mistake.exists? # => false
98
+ #
99
+ # @param [String] id the unqualified id of the host
100
+ # @return [Conjur::Host] an object representing the host, which may or may not exist.
44
101
  def host id
45
102
  standard_show Conjur::Core::API.host, :host, id
46
103
  end
104
+
105
+ #@!endgroup
47
106
  end
48
107
  end
@@ -2,16 +2,61 @@ require 'conjur/layer'
2
2
 
3
3
  module Conjur
4
4
  class API
5
+ #@!group Directory: Layers
6
+
7
+ # Create a new layer with the given id
8
+ #
9
+ # @example
10
+ # # create a new layer named 'webservers'
11
+ # webservers = api.create_layer 'webservices'
12
+ #
13
+ # # create layer is *not* idempotent
14
+ # api.create_layer 'webservices' # raises RestClient::Conflict
15
+ #
16
+ # # create a layer owned by user 'alice'
17
+ # api.create_layer 'webservices', ownerid: 'alice'
18
+ # api.owner # => 'conjur:user:alice'
19
+ #
20
+ # @param [String] id an *unqualified* id for the layer.
21
+ # @return [Conjur::Layer]
5
22
  def create_layer(id, options = {})
6
23
  standard_create Conjur::API.layer_asset_host, :layer, id, options
7
24
  end
8
-
9
- def layers(options = {})
25
+
26
+ # Get all layers visible to the current role.
27
+ #
28
+ # The `options` parameter is only included for backwards
29
+ # compatibility and has no effect. You should call this method
30
+ # without arguments.
31
+ #
32
+ # @param [Hash] options deprecated, unused
33
+ # @return [Array<Conjur::Layer>] all layers visible to the current role
34
+ def layers options={}
10
35
  standard_list Conjur::API.layer_asset_host, :layer, options
11
36
  end
12
-
37
+
38
+
39
+ # Get a layer by its *unqualified id*.
40
+ #
41
+ # Like other Conjur methods, this will return a {Conjur::Layer} whether
42
+ # or not the record is found, and you must use the {Conjur::Exists#exists?} method
43
+ # to check this.
44
+ #
45
+ # @example
46
+ # api.create_layer id: 'foo'
47
+ # foo = api.layer "foo" # => returns a Conjur::Layer
48
+ # puts foo.resourceid # => "conjur:layer:foo"
49
+ # puts foo.id # => "foo"
50
+ # mistake = api.layer "doesnotexist" # => Also returns a Conjur::Layer
51
+ # foo.exists? # => true
52
+ # mistake.exists? # => false
53
+ #
54
+ # @param [String] id the unqualified id of the layer
55
+ # @return [Conjur::Layer] an object representing the layer, which may or may not exist.
13
56
  def layer id
14
57
  standard_show Conjur::API.layer_asset_host, :layer, id
15
58
  end
59
+
60
+ #@!endgroup
16
61
  end
17
62
  end
@@ -20,33 +20,164 @@
20
20
  #
21
21
 
22
22
  module Conjur
23
- class API
24
- # Return all of a user's public keys, as a newline delimited string
25
- # (the format expected by authorized-keys)
23
+
24
+ class API
25
+ # @!group Public Keys Service
26
+
27
+ # Fetch *all* public keys for the user. This method returns a newline delimited
28
+ # String for compatibility with the authorized_keys SSH format.
29
+ #
30
+ #
31
+ # If the given user does not exist, an empty String will be returned. This is to prevent attackers from determining whether
32
+ # a user exists.
33
+ #
34
+ # ## Permissions
35
+ # You do not need any special permissions to call this method, since public keys are, well, public.
36
+ #
37
+ #
38
+ # @example
39
+ # puts api.public_keys('jon')
40
+ # # ssh-rsa [big long string] jon@albert
41
+ # # ssh-rsa [big long string] jon@conjurops
42
+ #
43
+ # @param [String] username the *unqualified* Conjur username
44
+ # @return [String] newline delimited public keys
26
45
  def public_keys username
27
46
  public_keys_resource(username).get
28
47
  end
48
+
29
49
 
30
- # Return a specific public key for a given user and key name
50
+ # Fetch a specific key by name. The key name is the last token in the public key itself,
51
+ # typically formatted as `'<login>@<hostname>'`.
52
+ #
53
+ # ## Permissions
54
+ # You do not need any special permissions to call this method, since public keys are, well, public.
55
+ #
56
+ # @example Get bob's key for 'bob@somehost'
57
+ # key = begin
58
+ # api.public_key 'bob', 'bob@somehost'
59
+ # rescue RestClient::ResourceNotFound
60
+ # puts "Key or user not found!"
61
+ # # Deal with it
62
+ # end
63
+ #
64
+ #
65
+ # @param [String] username A Conjur username
66
+ # @param [String] keyname The name or identifier of the key
67
+ # @return [String] the public key
68
+ # @raise [RestClient::ResourceNotFound] if the user or key does not exist.
31
69
  def public_key username, keyname
32
70
  public_keys_resource(username, keyname).get
33
71
  end
34
-
35
- # Add a public key for the given user
72
+
73
+ # List the public key names for the given user.
74
+ #
75
+ # If the given user does not exist, an empty Array will be returned. This is to prevent attackers from determining whether
76
+ # a user exists.
77
+ #
78
+ # ## Permissions
79
+ # You do not need any special permissions to call this method, since public keys are, well, public.
80
+ #
81
+ #
82
+ # @example List the names of public keys for 'otto'
83
+ # api.public_key_names('otto').each{|n| puts n}
84
+ # # otto@somehost
85
+ # # admin@someotherhost
86
+ #
87
+ # @example A non existent user has no public keys
88
+ # user = api.user('doesnotexist')
89
+ # user.exists? # => false
90
+ # user.public_key_names # => []
91
+ #
92
+ # @param [String] username the Conjur username
93
+ # @return [Array<String>] the names of the user's public keys
94
+ def public_key_names username
95
+ public_keys(username).lines.map{|s| s.split(' ')[-1]}
96
+ end
97
+
98
+ # Add an SSH public key for `username`.
99
+ #
100
+ # ## Key Format
101
+ #
102
+ # This method will raise an exception if `key` is not of the format
103
+ # `"<algorithm> <data> <name>"` (that is, key.split(\s+\)).length must be 3). The `<name>` field is used by the service
104
+ # to identify individual keys for a user.
105
+ #
106
+ # ## Permissions
107
+ #
108
+ # You must have permission to `'update'` the pubkeys service resource. When the Conjur appliance
109
+ # is configured, it creates the pubkeys service resource with this identifier
110
+ # `'<organizational account>:service:pubkeys-1.0/public-keys'`.
111
+ #
112
+ # Rather than granting permissions to this resource directly to user roles, we recommend that you add them to the
113
+ # 'key-managers' group, whose *unqualified identifier* is 'pubkeys-1.0/key-managers', which has permission to add public
114
+ # keys.
115
+ #
116
+ # ## Hiding Existence
117
+ #
118
+ # Because attackers could use this method to determine the existence of Conjur users, it will not
119
+ # raise an error if the user does not exist.
120
+ #
121
+ # @example add a user's public key
122
+ # # Check that the user exists so that we can fail when he doesn't. Otherwise, this method
123
+ # # will silently fail.
124
+ # raise "No such user!" unless api.user('bob').exists?
125
+ #
126
+ # # Add a key from a file
127
+ # key = File.read('/path/to/public/key.pub')
128
+ # api.add_public_key('bob', key)
129
+ #
130
+ # @param [String] username the name of the Conjur
131
+ # @param [String] key an SSH formated public key
132
+ # @return void
133
+ # @raise RestClient::BadRequest when the key is not in the correct format.
36
134
  def add_public_key username, key
37
135
  public_keys_resource(username).post key
38
136
  end
39
-
40
- # Delete a public key for the given user and key name
137
+
138
+ # Delete a specific public key for a user.
139
+ #
140
+ # ## Permissions
141
+ # You must have permission to `'update'` the pubkeys service resource. When the Conjur appliance
142
+ # is configured, it creates the pubkeys service resource with this identifier
143
+ # `'<organizational account>:service:pubkeys-1.0/public-keys'`.
144
+ #
145
+ # Rather than granting permissions to this resource directly to user roles, we recommend that you add them to the
146
+ # 'key-managers' group, whose *unqualified identifier* is 'pubkeys-1.0/key-managers', which has permission to add public
147
+ # keys.
148
+ #
149
+ # ## Hiding Existence
150
+ #
151
+ # Because attackers could use this method to determine the existence of Conjur users, it will not
152
+ # raise an error if the user does not exist.
153
+ #
154
+ # @example Delete all public keys for 'bob'
155
+ #
156
+ # api.public_key_names('bob').count # => 6
157
+ # api.public_key_names('bob').each do |keyname|
158
+ # api.delete_public_key 'bob', keyname
159
+ # end
160
+ # api.public_key_names('bob').count # => 0
161
+ #
162
+ #
163
+ # @param [String] username the Conjur username/login
164
+ # @param [String] keyname the individual key to delete.
165
+ # @return [void]
41
166
  def delete_public_key username, keyname
42
167
  public_keys_resource(username, keyname).delete
43
168
  end
169
+
170
+ #@!endgroup
44
171
 
45
172
  protected
173
+ # @api private
174
+ # Returns a RestClient::Resource with the pubkeys host and the given path.
46
175
  def public_keys_resource *path
47
176
  RestClient::Resource.new(Conjur::API.pubkeys_asset_host, credentials)[public_keys_path *path]
48
177
  end
49
-
178
+
179
+ # @api private
180
+ # This method simply escapes each segment in `args` and joins them around `/`.
50
181
  def public_keys_path *args
51
182
  args.map{|a| fully_escape(a)}.join('/')
52
183
  end
data/lib/conjur/api.rb CHANGED
@@ -47,6 +47,18 @@ class RestClient::Resource
47
47
  include Conjur::LogSource
48
48
  include Conjur::Cast
49
49
  extend Conjur::BuildFromResponse
50
+
51
+ alias_method :initialize_without_defaults, :initialize
52
+
53
+ def initialize url, options = nil, &block
54
+ initialize_without_defaults url, default_options.merge(options || {}), &block
55
+ end
56
+
57
+ def default_options
58
+ {
59
+ ssl_cert_store: OpenSSL::SSL::SSLContext::DEFAULT_CERT_STORE
60
+ }
61
+ end
50
62
 
51
63
  def core_conjur_account
52
64
  Conjur::Core::API.conjur_account
@@ -72,4 +84,4 @@ class RestClient::Resource
72
84
  def username
73
85
  options[:user] || options[:username]
74
86
  end
75
- end
87
+ end
data/lib/conjur/base.rb CHANGED
@@ -22,8 +22,6 @@ require 'rest-client'
22
22
  require 'json'
23
23
  require 'base64'
24
24
 
25
- require 'conjur/patches/rest-client'
26
-
27
25
  require 'conjur/exists'
28
26
  require 'conjur/has_attributes'
29
27
  require 'conjur/has_owner'
@@ -35,6 +33,33 @@ require 'conjur/standard_methods'
35
33
  require 'conjur/cast'
36
34
 
37
35
  module Conjur
36
+ # NOTE: You have to put all 'class level' api docs here, because YARD is stoopid :-(
37
+
38
+ # This class provides access to Conjur services
39
+ # **TODO MOAR**
40
+ #
41
+ # # Conjur Services
42
+ #
43
+ # ### Public Keys Service
44
+ # The {http://developer.conjur.net/reference/services/pubkeys Conjur Public Keys} service provides a
45
+ # simple database of public keys with access controlled by Conjur. Reading a user's public keys requires
46
+ # no authentication at all -- the user's public keys are public information, after all.
47
+ #
48
+ # Adding or deleting a public key may only be done if you have permission to update the *public keys
49
+ # resource*, which is created when the appliance is launched, and has a resource id
50
+ # `'<organizational account>:service:pubkeys-1.0/public-keys'`. The appliance also comes with a Group named
51
+ # `'pubkeys-1.0/key-managers'` that has this permission. Rather than granting each user permission to
52
+ # modify the public keys database, you should consider adding users to this group.
53
+ #
54
+ # A very common use case is {http://developer.conjur.net/tutorials/ssh public key management for SSH}
55
+ #
56
+ #
57
+ # ### Audit Service
58
+ #
59
+ # The {http://developer.conjur.net/reference/services/audit Conjur Audit Service} allows you to
60
+ # fetch audit records.
61
+ #
62
+ # Generally you will need to have *at least one* privilege on the subject of an event in order to see it.
38
63
  class API
39
64
  include Escape
40
65
  include LogSource
@@ -42,6 +67,7 @@ module Conjur
42
67
  include Cast
43
68
 
44
69
  class << self
70
+ # @api private
45
71
  # Parse a role id into [ account, 'roles', kind, id ]
46
72
  def parse_role_id(id)
47
73
  id = id.role if id.respond_to?(:role)
@@ -54,6 +80,7 @@ module Conjur
54
80
  end
55
81
  end
56
82
 
83
+ # @api private
57
84
  # Parse a resource id into [ account, 'resources', kind, id ]
58
85
  def parse_resource_id(id)
59
86
  id = id.resource if id.respond_to?(:resource)
@@ -66,7 +93,8 @@ module Conjur
66
93
  end
67
94
  end
68
95
 
69
- # Converts flat id into path components, with mixed-in "super-kind"
96
+ # @api private
97
+ # Converts flat id into path components, with mixed-in "super-kind"
70
98
  # (not that kind which is part of id)
71
99
  # NOTE: name is a bit confusing, as result of 'parse' is just recombined
72
100
  # representation of parts, not an object of higher abstraction level
@@ -83,15 +111,72 @@ module Conjur
83
111
  [ paths[0], kind, paths[1], paths[2..-1].join(':') ]
84
112
  end
85
113
 
114
+
115
+ # Create a new {Conjur::API} instance from a username and a password or api key.
116
+ #
117
+ # @example Create an API with valid credentials
118
+ # api = Conjur::API.new_from_key 'admin', '<admin password>'
119
+ # api.current_role # => 'conjur:user:admin'
120
+ # api.token['data'] # => 'admin'
121
+ #
122
+ # @example Authentication is lazy
123
+ # api = Conjur::API.new_from_key 'admin', 'wrongpassword' # succeeds
124
+ # api.user 'foo' # raises a 401 error
125
+ #
126
+ # @param [String] username the username to use when making authenticated requests.
127
+ # @param [Sring] api_key the api key or password for `username`
128
+ # @return [Conjur::API] an api that will authenticate with the given username and api key.
86
129
  def new_from_key(username, api_key)
87
130
  self.new username, api_key, nil
88
131
  end
89
132
 
133
+
134
+ # Create a new {Conjur::API} instance from a token issued by the
135
+ # {http://developer.conjur.net/reference/services/authentication Conjur authentication service}
136
+ #
137
+ # Generally, you will have a Conjur identitiy (username and api key), and create an {Conjur::API} instance
138
+ # for the identity using {.new_from_key}. This method is useful when you are performing authorization checks
139
+ # given a token. For example, a Conjur gateway that requires you to prove that you can 'read' a resource named
140
+ # 'super-secret' might get the token from a request header, create an {Conjur::API} instance with this method,
141
+ # and use {Conjur::Resource#permitted?} to decide whether to accept and forward the request.
142
+ #
143
+ # Note that Conjur tokens are issued as JSON. This method expects to get the token as a parsed JSON Hash.
144
+ # When sending tokens as headers, you will normally use base64 encoded strings. Authorization headers
145
+ # used by Conjur have the form `'Token token="#{b64encode token.to_json}"'`, but this format is in no way
146
+ # required.
147
+ #
148
+ # @example A simple gatekeeper
149
+ # RESOURCE_NAME = 'protected-service'
150
+ #
151
+ # def handle_request request
152
+ # token_header = request.header 'X-Conjur-Token'
153
+ # token = JSON.parse Base64.b64decode(token_header)
154
+ #
155
+ # api = Conjur::API.new_from_token token
156
+ # raise Forbidden unless api.resource(RESOURCE_NAME).permitted? 'read'
157
+ #
158
+ # proxy_to_service request
159
+ # end
160
+ #
161
+ # @param [Hash] token the authentication token as parsed JSON to use when making authenticated requests
162
+ # @return [Conjur::API] an api that will authenticate with the token
90
163
  def new_from_token(token)
91
164
  self.new nil, nil, token
92
165
  end
93
166
  end
94
167
 
168
+ # Create a new instance from a username and api key or a token.
169
+ #
170
+ # @note You should use {Conjur::API.new_from_token} or {Conjur::API.new_from_key} instead of calling this method
171
+ # directly.
172
+ #
173
+ # This method requires that you pass **either** a username and api_key **or** a token Hash.
174
+ #
175
+ # @param [String] username the username to authenticate as
176
+ # @param [String] api_key the api key or password to use when authenticating
177
+ # @param [Hash] token the token to use when making authenticated requuests.
178
+ #
179
+ # @api internal
95
180
  def initialize username, api_key, token
96
181
  @username = username
97
182
  @api_key = api_key
@@ -100,12 +185,14 @@ module Conjur
100
185
  raise "Expecting ( username and api_key ) or token" unless ( username && api_key ) || token
101
186
  end
102
187
 
103
- attr_reader :api_key, :username
104
-
188
+ attr_reader :api_key
189
+
190
+ #
105
191
  def username
106
192
  @username || @token['data']
107
193
  end
108
-
194
+
195
+
109
196
  def host
110
197
  self.class.host
111
198
  end