conjur-api 5.3.8.pre.319 → 5.3.8.pre.321
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.
- checksums.yaml +4 -4
- data/.codeclimate.yml +10 -0
- data/.dockerignore +1 -0
- data/.github/CODEOWNERS +10 -0
- data/.gitignore +32 -0
- data/.gitleaks.toml +219 -0
- data/.overcommit.yml +16 -0
- data/.project +18 -0
- data/.rubocop.yml +3 -0
- data/.rubocop_settings.yml +86 -0
- data/.rubocop_todo.yml +709 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +448 -0
- data/CONTRIBUTING.md +138 -0
- data/Dockerfile +16 -0
- data/Gemfile +7 -0
- data/Jenkinsfile +136 -0
- data/LICENSE +202 -0
- data/README.md +162 -0
- data/Rakefile +47 -0
- data/SECURITY.md +42 -0
- data/VERSION +1 -1
- data/bin/parse-changelog.sh +12 -0
- data/ci/configure_v4.sh +12 -0
- data/ci/configure_v5.sh +19 -0
- data/ci/oauth/keycloak/create_client +18 -0
- data/ci/oauth/keycloak/create_user +21 -0
- data/ci/oauth/keycloak/fetch_certificate +18 -0
- data/ci/oauth/keycloak/keycloak_functions.sh +71 -0
- data/ci/oauth/keycloak/standalone.xml +578 -0
- data/ci/oauth/keycloak/wait_for_server +56 -0
- data/ci/submit-coverage +36 -0
- data/conjur-api.gemspec +41 -0
- data/dev/Dockerfile.dev +12 -0
- data/dev/docker-compose.yml +56 -0
- data/dev/start +22 -0
- data/dev/stop +5 -0
- data/docker-compose.yml +98 -0
- data/example/demo_v4.rb +49 -0
- data/example/demo_v5.rb +57 -0
- data/features/authenticators.feature +41 -0
- data/features/authn.feature +14 -0
- data/features/authn_local.feature +32 -0
- data/features/exists.feature +37 -0
- data/features/group.feature +11 -0
- data/features/host.feature +50 -0
- data/features/host_factory_create_host.feature +28 -0
- data/features/host_factory_token.feature +63 -0
- data/features/load_policy.feature +61 -0
- data/features/members.feature +51 -0
- data/features/new_api.feature +36 -0
- data/features/permitted.feature +70 -0
- data/features/permitted_roles.feature +30 -0
- data/features/public_keys.feature +11 -0
- data/features/resource_fields.feature +53 -0
- data/features/role_fields.feature +15 -0
- data/features/rotate_api_key.feature +13 -0
- data/features/step_definitions/api_steps.rb +52 -0
- data/features/step_definitions/policy_steps.rb +134 -0
- data/features/step_definitions/result_steps.rb +11 -0
- data/features/support/env.rb +19 -0
- data/features/support/hooks.rb +3 -0
- data/features/support/world.rb +12 -0
- data/features/update_password.feature +14 -0
- data/features/user.feature +58 -0
- data/features/variable_fields.feature +20 -0
- data/features/variable_value.feature +60 -0
- data/features_v4/authn_local.feature +27 -0
- data/features_v4/exists.feature +29 -0
- data/features_v4/host.feature +18 -0
- data/features_v4/host_factory_token.feature +49 -0
- data/features_v4/members.feature +39 -0
- data/features_v4/permitted.feature +15 -0
- data/features_v4/permitted_roles.feature +8 -0
- data/features_v4/resource_fields.feature +47 -0
- data/features_v4/rotate_api_key.feature +13 -0
- data/features_v4/step_definitions/api_steps.rb +17 -0
- data/features_v4/step_definitions/result_steps.rb +3 -0
- data/features_v4/support/env.rb +23 -0
- data/features_v4/support/policy.yml +34 -0
- data/features_v4/support/world.rb +12 -0
- data/features_v4/variable_fields.feature +11 -0
- data/features_v4/variable_value.feature +54 -0
- data/lib/conjur/acts_as_resource.rb +123 -0
- data/lib/conjur/acts_as_role.rb +142 -0
- data/lib/conjur/acts_as_rolsource.rb +32 -0
- data/lib/conjur/acts_as_user.rb +68 -0
- data/lib/conjur/api/authenticators.rb +43 -0
- data/lib/conjur/api/authn.rb +144 -0
- data/lib/conjur/api/host_factories.rb +71 -0
- data/lib/conjur/api/ldap_sync.rb +38 -0
- data/lib/conjur/api/policies.rb +56 -0
- data/lib/conjur/api/pubkeys.rb +53 -0
- data/lib/conjur/api/resources.rb +109 -0
- data/lib/conjur/api/roles.rb +98 -0
- data/lib/conjur/api/router/v4.rb +206 -0
- data/lib/conjur/api/router/v5.rb +269 -0
- data/lib/conjur/api/variables.rb +59 -0
- data/lib/conjur/api.rb +105 -0
- data/lib/conjur/base.rb +355 -0
- data/lib/conjur/base_object.rb +57 -0
- data/lib/conjur/build_object.rb +47 -0
- data/lib/conjur/cache.rb +26 -0
- data/lib/conjur/cert_utils.rb +63 -0
- data/lib/conjur/cidr.rb +71 -0
- data/lib/conjur/configuration.rb +460 -0
- data/lib/conjur/escape.rb +129 -0
- data/lib/conjur/exceptions.rb +4 -0
- data/lib/conjur/group.rb +41 -0
- data/lib/conjur/has_attributes.rb +98 -0
- data/lib/conjur/host.rb +27 -0
- data/lib/conjur/host_factory.rb +75 -0
- data/lib/conjur/host_factory_token.rb +78 -0
- data/lib/conjur/id.rb +71 -0
- data/lib/conjur/layer.rb +9 -0
- data/lib/conjur/log.rb +72 -0
- data/lib/conjur/log_source.rb +60 -0
- data/lib/conjur/policy.rb +34 -0
- data/lib/conjur/policy_load_result.rb +61 -0
- data/lib/conjur/query_string.rb +12 -0
- data/lib/conjur/resource.rb +29 -0
- data/lib/conjur/role.rb +29 -0
- data/lib/conjur/role_grant.rb +85 -0
- data/lib/conjur/routing.rb +29 -0
- data/lib/conjur/user.rb +40 -0
- data/lib/conjur/variable.rb +208 -0
- data/lib/conjur/webservice.rb +30 -0
- data/lib/conjur-api/version.rb +24 -0
- data/lib/conjur-api.rb +2 -0
- data/publish.sh +5 -0
- data/spec/api/host_factories_spec.rb +34 -0
- data/spec/api_spec.rb +254 -0
- data/spec/base_object_spec.rb +13 -0
- data/spec/cert_utils_spec.rb +173 -0
- data/spec/cidr_spec.rb +34 -0
- data/spec/configuration_spec.rb +330 -0
- data/spec/has_attributes_spec.rb +63 -0
- data/spec/helpers/errors_matcher.rb +34 -0
- data/spec/helpers/request_helpers.rb +10 -0
- data/spec/id_spec.rb +29 -0
- data/spec/ldap_sync_spec.rb +21 -0
- data/spec/log_source_spec.rb +13 -0
- data/spec/log_spec.rb +42 -0
- data/spec/roles_spec.rb +24 -0
- data/spec/spec_helper.rb +113 -0
- data/spec/ssl_spec.rb +109 -0
- data/spec/uri_escape_spec.rb +21 -0
- data/test.sh +76 -0
- data/tmp/.keep +0 -0
- metadata +194 -3
@@ -0,0 +1,59 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2013-2017 Conjur Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
# this software and associated documentation files (the "Software"), to deal in
|
6
|
+
# the Software without restriction, including without limitation the rights to
|
7
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
8
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
9
|
+
# subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in all
|
12
|
+
# copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
16
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
17
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
#
|
21
|
+
require 'conjur/variable'
|
22
|
+
|
23
|
+
module Conjur
|
24
|
+
class API
|
25
|
+
|
26
|
+
#@!group Variables
|
27
|
+
|
28
|
+
# Fetch the values of a list of variables. This operation is more efficient than fetching the
|
29
|
+
# values one by one.
|
30
|
+
#
|
31
|
+
# This method will fail unless:
|
32
|
+
# * All of the variables exist
|
33
|
+
# * You have permission to `'execute'` all of the variables
|
34
|
+
#
|
35
|
+
# @example Fetch multiple variable values
|
36
|
+
# values = variable_values ['myorg:variable:postgres_uri', 'myorg:variable:aws_secret_access_key', 'myorg:variable:aws_access_key_id']
|
37
|
+
# values # =>
|
38
|
+
# {
|
39
|
+
# "postgres://...",
|
40
|
+
# "the-secret-key",
|
41
|
+
# "the-access-key-id"
|
42
|
+
# }
|
43
|
+
#
|
44
|
+
# This method is used to implement the {http://developer.conjur.net/reference/tools/utilities/conjurenv `conjur env`}
|
45
|
+
# commands. You may consider using that instead to run your program in an environment with the necessary secrets.
|
46
|
+
#
|
47
|
+
# @param [Array<String>] variable_ids list of variable ids to fetch
|
48
|
+
# @return [Array<String>] a list of variable values corresponding to the variable ids.
|
49
|
+
# @raise [RestClient::Forbidden, RestClient::ResourceNotFound] if any of the variables don't exist or aren't accessible.
|
50
|
+
def variable_values variable_ids
|
51
|
+
raise ArgumentError, "Variables list must be an array" unless variable_ids.kind_of? Array
|
52
|
+
raise ArgumentError, "Variables list is empty" if variable_ids.empty?
|
53
|
+
|
54
|
+
JSON.parse(url_for(:secrets_values, credentials, variable_ids).get.body)
|
55
|
+
end
|
56
|
+
|
57
|
+
#@!endgroup
|
58
|
+
end
|
59
|
+
end
|
data/lib/conjur/api.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2013-2017 Conjur Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
# this software and associated documentation files (the "Software"), to deal in
|
6
|
+
# the Software without restriction, including without limitation the rights to
|
7
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
8
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
9
|
+
# subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in all
|
12
|
+
# copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
16
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
17
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
#
|
21
|
+
require 'active_support'
|
22
|
+
require 'active_support/deprecation'
|
23
|
+
|
24
|
+
require 'conjur/configuration'
|
25
|
+
require 'conjur/routing'
|
26
|
+
require 'conjur/id'
|
27
|
+
require 'conjur/base'
|
28
|
+
require 'conjur/exceptions'
|
29
|
+
require 'conjur/build_object'
|
30
|
+
require 'conjur/base_object'
|
31
|
+
require 'conjur/acts_as_resource'
|
32
|
+
require 'conjur/acts_as_role'
|
33
|
+
require 'conjur/acts_as_rolsource'
|
34
|
+
require 'conjur/acts_as_user'
|
35
|
+
require 'conjur/log_source'
|
36
|
+
require 'conjur/has_attributes'
|
37
|
+
require 'conjur/api/authenticators'
|
38
|
+
require 'conjur/api/authn'
|
39
|
+
require 'conjur/api/roles'
|
40
|
+
require 'conjur/api/resources'
|
41
|
+
require 'conjur/api/pubkeys'
|
42
|
+
require 'conjur/api/variables'
|
43
|
+
require 'conjur/api/policies'
|
44
|
+
require 'conjur/api/host_factories'
|
45
|
+
require 'conjur/api/ldap_sync'
|
46
|
+
require 'conjur/host'
|
47
|
+
require 'conjur/group'
|
48
|
+
require 'conjur/variable'
|
49
|
+
require 'conjur/layer'
|
50
|
+
require 'conjur/cache'
|
51
|
+
require 'conjur-api/version'
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
class RestClient::Resource
|
55
|
+
include Conjur::Escape
|
56
|
+
include Conjur::LogSource
|
57
|
+
|
58
|
+
# @api private
|
59
|
+
# This method exists so that all {RestClient::Resource}s support JSON serialization. It returns an
|
60
|
+
# empty hash.
|
61
|
+
# @return [Hash] the empty hash
|
62
|
+
def to_json(options = {})
|
63
|
+
{}
|
64
|
+
end
|
65
|
+
|
66
|
+
# Creates a Conjur API from this resource's authorization header.
|
67
|
+
#
|
68
|
+
# The new API is created using the token, so it will not be able to refresh
|
69
|
+
# when the token expires (after about 8 minutes). This is equivalent to creating
|
70
|
+
# an {Conjur::API} instance with {Conjur::API.new_from_token}.
|
71
|
+
#
|
72
|
+
# @return {Conjur::API} the new api
|
73
|
+
def conjur_api
|
74
|
+
api = Conjur::API.new_from_token token, remote_ip: remote_ip
|
75
|
+
api
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get an authentication token from the clients Authorization header.
|
79
|
+
#
|
80
|
+
# Useful fields in the token include `"data"`, which holds the username for which the
|
81
|
+
# token was issued, and `"timestamp"`, which contains the time at which the token was issued.
|
82
|
+
# The token will expire 8 minutes after timestamp, but we recommend you treat the lifespan as
|
83
|
+
# about 5 minutes to account for time differences.
|
84
|
+
#
|
85
|
+
# @return [Hash] the parsed authentication token
|
86
|
+
def token
|
87
|
+
authorization = options[:headers][:authorization]
|
88
|
+
if authorization && authorization.to_s[/^Token token="(.*)"/]
|
89
|
+
JSON.parse(Base64.decode64($1))
|
90
|
+
else
|
91
|
+
raise AuthorizationError.new("Authorization missing")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def remote_ip
|
96
|
+
options[:headers][:x_forwarded_for]
|
97
|
+
end
|
98
|
+
|
99
|
+
# The username this resource authenticates as.
|
100
|
+
#
|
101
|
+
# @return [String] the username
|
102
|
+
def username
|
103
|
+
options[:user] || options[:username]
|
104
|
+
end
|
105
|
+
end
|
data/lib/conjur/base.rb
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2013-2017 Conjur Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
# this software and associated documentation files (the "Software"), to deal in
|
6
|
+
# the Software without restriction, including without limitation the rights to
|
7
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
8
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
9
|
+
# subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in all
|
12
|
+
# copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
16
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
17
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
20
|
+
#
|
21
|
+
require 'rest-client'
|
22
|
+
require 'active_support'
|
23
|
+
require 'active_support/core_ext'
|
24
|
+
require 'json'
|
25
|
+
require 'base64'
|
26
|
+
|
27
|
+
require 'conjur/query_string'
|
28
|
+
require 'conjur/has_attributes'
|
29
|
+
require 'conjur/escape'
|
30
|
+
require 'conjur/log'
|
31
|
+
require 'conjur/log_source'
|
32
|
+
|
33
|
+
module Conjur
|
34
|
+
# NOTE: You have to put all 'class level' api docs here, because YARD is stoopid :-(
|
35
|
+
|
36
|
+
# This class provides access to the Conjur services.
|
37
|
+
class API
|
38
|
+
include Escape
|
39
|
+
include LogSource
|
40
|
+
include Routing
|
41
|
+
extend Routing
|
42
|
+
|
43
|
+
class << self
|
44
|
+
# Create a new {Conjur::API} instance from a username and a password or api key.
|
45
|
+
#
|
46
|
+
# @example Create an API with valid credentials
|
47
|
+
# api = Conjur::API.new_from_key 'admin', '<admin password>'
|
48
|
+
# api.current_role # => 'conjur:user:admin'
|
49
|
+
# api.token['data'] # => 'admin'
|
50
|
+
#
|
51
|
+
# @example Authentication is lazy
|
52
|
+
# api = Conjur::API.new_from_key 'admin', 'wrongpassword' # succeeds
|
53
|
+
# api.user 'foo' # raises a 401 error
|
54
|
+
#
|
55
|
+
# @param [String] username the username to use when making authenticated requests.
|
56
|
+
# @param [String] api_key the api key or password for `username`
|
57
|
+
# @param [String] remote_ip the optional IP address to be recorded in the audit record.
|
58
|
+
# @param [String] account The organization account.
|
59
|
+
# @return [Conjur::API] an api that will authenticate with the given username and api key.
|
60
|
+
def new_from_key username, api_key, account: Conjur.configuration.account, remote_ip: nil
|
61
|
+
self.new.init_from_key username, api_key, remote_ip: remote_ip, account: account
|
62
|
+
end
|
63
|
+
|
64
|
+
# Create a new {Conjur::API} instance from an access token.
|
65
|
+
#
|
66
|
+
# Generally, you will have a Conjur identitiy (username and API key), and create an {Conjur::API} instance
|
67
|
+
# for the identity using {.new_from_key}. This method is useful when you are performing authorization checks
|
68
|
+
# given a token. For example, a Conjur gateway that requires you to prove that you can 'read' a resource named
|
69
|
+
# 'super-secret' might get the token from a request header, create an {Conjur::API} instance with this method,
|
70
|
+
# and use {Conjur::Resource#permitted?} to decide whether to accept and forward the request.
|
71
|
+
#
|
72
|
+
# @example A simple gatekeeper
|
73
|
+
# RESOURCE_NAME = 'protected-service'
|
74
|
+
#
|
75
|
+
# def handle_request request
|
76
|
+
# token_header = request.header 'X-Conjur-Token'
|
77
|
+
# token = JSON.parse Base64.b64decode(token_header)
|
78
|
+
#
|
79
|
+
# api = Conjur::API.new_from_token token
|
80
|
+
# raise Forbidden unless api.resource(RESOURCE_NAME).permitted? 'read'
|
81
|
+
#
|
82
|
+
# proxy_to_service request
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# @param [Hash] token the authentication token as parsed JSON to use when making authenticated requests
|
86
|
+
# @param [String] remote_ip the optional IP address to be recorded in the audit record.
|
87
|
+
# @return [Conjur::API] an api that will authenticate with the token
|
88
|
+
def new_from_token token, remote_ip: nil
|
89
|
+
self.new.init_from_token token, remote_ip: remote_ip
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create a new {Conjur::API} instance from a file containing a token issued by the
|
93
|
+
# {http://developer.conjur.net/reference/services/authentication Conjur authentication service}.
|
94
|
+
# The file is read the first time that a token is required. It is also re-read
|
95
|
+
# whenever the API decides that the token it already has is getting close to expiration.
|
96
|
+
#
|
97
|
+
# This method is useful when an external process, such as a sidecar container, is continuously
|
98
|
+
# obtaining fresh tokens and writing them to a known file.
|
99
|
+
#
|
100
|
+
# @param [String] token_file the file path containing an authentication token as parsed JSON.
|
101
|
+
# @param [String] remote_ip the optional IP address to be recorded in the audit record.
|
102
|
+
# @return [Conjur::API] an api that will authenticate with the tokens provided in the file.
|
103
|
+
def new_from_token_file token_file, remote_ip: nil
|
104
|
+
self.new.init_from_token_file token_file, remote_ip: remote_ip
|
105
|
+
end
|
106
|
+
|
107
|
+
# Create a new {Conjur::API} instance which authenticates using +authn-local+
|
108
|
+
# using the specified username.
|
109
|
+
#
|
110
|
+
# @param [String] username the username to use when making authenticated requests.
|
111
|
+
# @param [String] account The organization account.
|
112
|
+
# @param [String] remote_ip the optional IP address to be recorded in the audit record.
|
113
|
+
# @param [String] expiration the optional expiration time of the token (supported in V5 only).
|
114
|
+
# @param [String] cidr the optional CIDR restriction on the token (supported in V5 only).
|
115
|
+
# @return [Conjur::API] an api that will authenticate with the given username.
|
116
|
+
def new_from_authn_local username, account: Conjur.configuration.account, remote_ip: nil, expiration: nil, cidr: nil
|
117
|
+
self.new.init_from_authn_local username, account: account, remote_ip: remote_ip, expiration: expiration, cidr: cidr
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
#@!attribute [r] api_key
|
122
|
+
# The api key used to create this instance. This is only present when you created the api with {Conjur::API.new_from_key}.#
|
123
|
+
#
|
124
|
+
# @return [String] the api key, or nil if this instance was created from a token.
|
125
|
+
attr_reader :api_key
|
126
|
+
|
127
|
+
#@!attribute [r] remote_ip
|
128
|
+
# An optional IP address to be recorded in the audit record for any actions performed by this API instance.
|
129
|
+
attr_reader :remote_ip
|
130
|
+
|
131
|
+
# The name of the user as which this api instance is authenticated. This is available whether the api
|
132
|
+
# instance was created from credentials or an authentication token. If the instance was created from
|
133
|
+
# credentials, we will use that value directly otherwise we will attempt to extract the username from
|
134
|
+
# the token (either the old-style data field or the new-style JWT `sub` field).
|
135
|
+
#
|
136
|
+
# @return [String] the login of the current user.
|
137
|
+
def username
|
138
|
+
@username || token['data'] || jwt_username(token)
|
139
|
+
end
|
140
|
+
|
141
|
+
# @api private
|
142
|
+
# used to delegate to host providing subclasses.
|
143
|
+
# @return [String] the host
|
144
|
+
def host
|
145
|
+
self.class.host
|
146
|
+
end
|
147
|
+
|
148
|
+
# The token used to authenticate requests made with the api. The token will be fetched,
|
149
|
+
# if possible, when not present or about to expire. Accordingly, this
|
150
|
+
# method may raise a RestClient::Unauthorized exception if the credentials are invalid.
|
151
|
+
#
|
152
|
+
# @return [Hash] the authentication token as a Hash
|
153
|
+
# @raise [RestClient::Unauthorized] if the username and api key are invalid.
|
154
|
+
def token
|
155
|
+
refresh_token if needs_token_refresh?
|
156
|
+
return @token
|
157
|
+
end
|
158
|
+
|
159
|
+
# @api private
|
160
|
+
# Force the API to obtain a new access token on the next invocation.
|
161
|
+
def force_token_refresh
|
162
|
+
@token = nil
|
163
|
+
end
|
164
|
+
|
165
|
+
# Credentials that can be merged with options to be passed to `RestClient::Resource` HTTP request methods.
|
166
|
+
# These include a username and an Authorization header containing the authentication token.
|
167
|
+
#
|
168
|
+
# @return [Hash] the options.
|
169
|
+
# @raise [RestClient::Unauthorized] if fetching the token fails.
|
170
|
+
def credentials
|
171
|
+
headers = {}.tap do |h|
|
172
|
+
h[:authorization] = "Token token=\"#{Base64.strict_encode64 token.to_json}\""
|
173
|
+
h[:x_forwarded_for] = @remote_ip if @remote_ip
|
174
|
+
end
|
175
|
+
{ headers: headers, username: username }
|
176
|
+
end
|
177
|
+
|
178
|
+
module TokenExpiration
|
179
|
+
|
180
|
+
# The four minutes is to work around a bug in Conjur < 4.7 causing a 404 on
|
181
|
+
# long-running operations (when the token is used right around the 5 minute mark).
|
182
|
+
TOKEN_STALE = 4.minutes
|
183
|
+
|
184
|
+
attr_accessor :token_born
|
185
|
+
|
186
|
+
def needs_token_refresh?
|
187
|
+
token_age > TOKEN_STALE
|
188
|
+
end
|
189
|
+
|
190
|
+
def update_token_born
|
191
|
+
self.token_born = gettime
|
192
|
+
end
|
193
|
+
|
194
|
+
def token_age
|
195
|
+
gettime - token_born
|
196
|
+
end
|
197
|
+
|
198
|
+
def gettime
|
199
|
+
Process.clock_gettime Process::CLOCK_MONOTONIC
|
200
|
+
rescue
|
201
|
+
# fall back to normal clock if there's no CLOCK_MONOTONIC
|
202
|
+
Time.now.to_f
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# When the API is constructed with an API key, the token can be refreshed using
|
207
|
+
# the username and API key. This authenticator assumes that the token was
|
208
|
+
# minted immediately before the API instance was created.
|
209
|
+
class APIKeyAuthenticator
|
210
|
+
include TokenExpiration
|
211
|
+
|
212
|
+
attr_reader :account, :username, :api_key
|
213
|
+
|
214
|
+
def initialize account, username, api_key
|
215
|
+
@account = account
|
216
|
+
@username = username
|
217
|
+
@api_key = api_key
|
218
|
+
|
219
|
+
update_token_born
|
220
|
+
end
|
221
|
+
|
222
|
+
def refresh_token
|
223
|
+
Conjur::API.authenticate(username, api_key, account: account).tap do
|
224
|
+
update_token_born
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Obtains access tokens from the +authn-local+ service.
|
230
|
+
class LocalAuthenticator
|
231
|
+
include TokenExpiration
|
232
|
+
|
233
|
+
attr_reader :account, :username, :expiration, :cidr
|
234
|
+
|
235
|
+
def initialize account, username, expiration, cidr
|
236
|
+
@account = account
|
237
|
+
@username = username
|
238
|
+
@expiration = expiration
|
239
|
+
@cidr = cidr
|
240
|
+
|
241
|
+
update_token_born
|
242
|
+
end
|
243
|
+
|
244
|
+
def refresh_token
|
245
|
+
Conjur::API.authenticate_local(username, account: account, expiration: expiration, cidr: cidr).tap do
|
246
|
+
update_token_born
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# When the API is constructed with a token, the token cannot be refreshed.
|
252
|
+
class UnableAuthenticator
|
253
|
+
def refresh_token
|
254
|
+
raise "Unable to re-authenticate using an access token"
|
255
|
+
end
|
256
|
+
|
257
|
+
def needs_token_refresh?
|
258
|
+
false
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Obtains fresh tokens by reading them from a file. Some other process is assumed
|
263
|
+
# to be acquiring tokens and storing them to the file on a regular basis.
|
264
|
+
#
|
265
|
+
# This authenticator assumes that the token was created immediately before
|
266
|
+
# it was written to the file.
|
267
|
+
class TokenFileAuthenticator
|
268
|
+
attr_reader :token_file
|
269
|
+
|
270
|
+
def initialize token_file
|
271
|
+
@token_file = token_file
|
272
|
+
end
|
273
|
+
|
274
|
+
attr_reader :last_mtime
|
275
|
+
|
276
|
+
def mtime
|
277
|
+
File.mtime token_file
|
278
|
+
end
|
279
|
+
|
280
|
+
def refresh_token
|
281
|
+
# There's a race condition here in which the file could be updated
|
282
|
+
# after we read the mtime but before we read the file contents. So to be
|
283
|
+
# conservative, use the oldest possible mtime.
|
284
|
+
mtime = self.mtime
|
285
|
+
File.open token_file, 'r' do |f|
|
286
|
+
JSON.load(f.read).tap { @last_mtime = mtime }
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def needs_token_refresh?
|
291
|
+
mtime != last_mtime
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def init_from_key username, api_key, account: Conjur.configuration.account, remote_ip: nil
|
296
|
+
@username = username
|
297
|
+
@api_key = api_key
|
298
|
+
@remote_ip = remote_ip
|
299
|
+
@authenticator = APIKeyAuthenticator.new(account, username, api_key)
|
300
|
+
self
|
301
|
+
end
|
302
|
+
|
303
|
+
def init_from_token token, remote_ip: nil
|
304
|
+
@token = token
|
305
|
+
@remote_ip = remote_ip
|
306
|
+
@authenticator = UnableAuthenticator.new
|
307
|
+
self
|
308
|
+
end
|
309
|
+
|
310
|
+
def init_from_token_file token_file, remote_ip: nil
|
311
|
+
@remote_ip = remote_ip
|
312
|
+
@authenticator = TokenFileAuthenticator.new(token_file)
|
313
|
+
self
|
314
|
+
end
|
315
|
+
|
316
|
+
def init_from_authn_local username, account: Conjur.configuration.account, remote_ip: nil, expiration: nil, cidr: nil
|
317
|
+
@username = username
|
318
|
+
@api_key = api_key
|
319
|
+
@remote_ip = remote_ip
|
320
|
+
@authenticator = LocalAuthenticator.new(account, username, expiration, cidr)
|
321
|
+
self
|
322
|
+
end
|
323
|
+
|
324
|
+
attr_reader :authenticator
|
325
|
+
|
326
|
+
private
|
327
|
+
|
328
|
+
# Tries to get the username (subject) from a JWT API token by examining
|
329
|
+
# its content.
|
330
|
+
#
|
331
|
+
# @return [String] of the 'sub' payload field from the JWT if present,
|
332
|
+
# otherwise return nil
|
333
|
+
def jwt_username raw_token
|
334
|
+
return nil unless raw_token
|
335
|
+
return nil unless raw_token.include? 'payload'
|
336
|
+
|
337
|
+
JSON.parse(Base64.strict_decode64(raw_token["payload"]))["sub"]
|
338
|
+
end
|
339
|
+
|
340
|
+
# Tries to refresh the token if possible.
|
341
|
+
#
|
342
|
+
# @return [Hash, false] false if the token couldn't be refreshed due to
|
343
|
+
# unavailable API key; otherwise, the new token.
|
344
|
+
def refresh_token
|
345
|
+
@token = @authenticator.refresh_token
|
346
|
+
end
|
347
|
+
|
348
|
+
# Checks if the token is old (or not present).
|
349
|
+
#
|
350
|
+
# @return [Boolean]
|
351
|
+
def needs_token_refresh?
|
352
|
+
!@token || @authenticator.needs_token_refresh?
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2013-2018 CyberArk Ltd.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Conjur
|
18
|
+
class BaseObject
|
19
|
+
include QueryString
|
20
|
+
include LogSource
|
21
|
+
include BuildObject
|
22
|
+
include Routing
|
23
|
+
|
24
|
+
attr_reader :id, :credentials
|
25
|
+
|
26
|
+
def initialize id, credentials
|
27
|
+
@id = Id.new id
|
28
|
+
@credentials = credentials
|
29
|
+
end
|
30
|
+
|
31
|
+
def as_json options={}
|
32
|
+
{
|
33
|
+
id: id.to_s
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def account
|
38
|
+
id.account
|
39
|
+
end
|
40
|
+
|
41
|
+
def kind
|
42
|
+
id.kind
|
43
|
+
end
|
44
|
+
|
45
|
+
def identifier
|
46
|
+
id.identifier
|
47
|
+
end
|
48
|
+
|
49
|
+
def username
|
50
|
+
credentials[:username] or raise "No username found in credentials"
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
"<#{self.class.name} id='#{id.to_s}'>"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2013-2018 CyberArk Ltd.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Conjur
|
18
|
+
module BuildObject
|
19
|
+
def self.included base
|
20
|
+
base.module_eval do
|
21
|
+
extend ClassMethods
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
def build_object id, credentials, default_class:
|
27
|
+
id = Id.new id
|
28
|
+
class_name = id.kind.classify.to_sym
|
29
|
+
find_class(class_name, default_class)
|
30
|
+
.new(id, credentials)
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_class class_name, default_class
|
34
|
+
cls = if Conjur.constants.member?(class_name)
|
35
|
+
Conjur.const_get(class_name)
|
36
|
+
else
|
37
|
+
default_class
|
38
|
+
end
|
39
|
+
cls < BaseObject ? cls : default_class
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_object id, default_class: Resource
|
44
|
+
self.class.build_object id, credentials, default_class: default_class
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/conjur/cache.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Conjur
|
2
|
+
# A cache which performs no caching.
|
3
|
+
class BaseCache
|
4
|
+
def fetch_attributes cache_key, &block
|
5
|
+
yield
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
@@cache = BaseCache.new
|
11
|
+
|
12
|
+
# Sets the global cache. It should implement +fetch_:method+ methods.
|
13
|
+
# The easy way to accomplish this is to extend BaseCache.
|
14
|
+
def cache= cache
|
15
|
+
@@cache = cache
|
16
|
+
end
|
17
|
+
|
18
|
+
# Gets the global cache.
|
19
|
+
def cache; @@cache; end
|
20
|
+
|
21
|
+
# Builds a cache key from a +username+, +url+ and optional +path+.
|
22
|
+
def cache_key username, url, path = nil
|
23
|
+
[ username, [ url, path ].compact.join ].join(".")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|