conjur-api 4.13.0 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
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