keyrod 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 02571240ed3e856bc865d507a84a82c66c976ccc
4
+ data.tar.gz: c40032cbbb8ff3240daf558553af5a6e3f23dec2
5
+ SHA512:
6
+ metadata.gz: c910ad52f352c61592dd02b7d8c894d5d601ccfc7963a5a4a2e0c8a933a752fa25daef9dba4c0483f4c3d483a8001492547b9fcb8174e525deec6081c1c2f3dd
7
+ data.tar.gz: 5299b20133bda56bc0e0c5f42c4d985865c0b41389af48136efcee05a7a5ee61b84f324844ba8ecc152d4f24fa455fdd71d107e77cd674b642ee5698938f567c
data/bin/keyrod ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'keyrod'
3
+
4
+ Keyrod::CLI.start(ARGV)
data/config/config.yml ADDED
@@ -0,0 +1,15 @@
1
+ keyrod:
2
+ debug: false # Debug mode
3
+ ca-dir: # CA directory
4
+ verify-ssl: false # Verify SSL certificate
5
+ fedcloud:
6
+ site: https://took51.ics.muni.cz:5000 # EGI FedCloud Site to authenticate against
7
+ access_token: # Access token
8
+ group: # ID of the group to join
9
+ interactive-fallback: false # Fallback to interactive mode if group is not set
10
+ identity-provider: egi.eu # Identity provider for token
11
+ oidc:
12
+ site: https://aai-dev.egi.eu/oidc/token # OIDC site to authenticate refresh token against
13
+ refresh_token: eyJhbGciOiJub25lIn0.eyJqdGkiOiI0ZDQ5MzUxMC00MDJhLTQ3NDQtYjE4Yi1lYmM5ODUxZmVlMTcifQ. # Refresh token
14
+ client_id: keyrodcli # OIDC client ID
15
+ client_secret: PpbuS_i3HcNV88ex48UKYPWCI1OwresZAxMjh1Pdf1NRHS6QTYb-PuUtzkd3XthD0dghxKSfzmapLBNHFxdFWg # OIDC client secret
data/lib/keyrod/cli.rb ADDED
@@ -0,0 +1,150 @@
1
+ require 'thor'
2
+ require 'yell'
3
+
4
+ module Keyrod
5
+ class CLI < Thor
6
+ desc 'token', 'Get login token for FedCloud site'
7
+ option :debug,
8
+ required: false,
9
+ default: Keyrod::Settings['debug'],
10
+ type: :boolean,
11
+ desc: 'Runs Keyrod in debug mode'
12
+ option :'ca-dir',
13
+ required: false,
14
+ default: Keyrod::Settings['ca-dir'],
15
+ type: :string,
16
+ desc: 'CA directory'
17
+ option :'verify-ssl',
18
+ required: false,
19
+ default: Keyrod::Settings['verify-ssl'],
20
+ type: :boolean,
21
+ desc: 'Check SSL certificate of FedCloud site'
22
+ option :site,
23
+ required: true,
24
+ default: Keyrod::Settings['fedcloud']['site'],
25
+ type: :string,
26
+ desc: 'EGI FedCloud Site',
27
+ aliases: '-s'
28
+ option :'access-token',
29
+ required: false,
30
+ default: Keyrod::Settings['fedcloud']['access_token'],
31
+ type: :string,
32
+ desc: 'Access token for authentication',
33
+ aliases: '-a'
34
+ option :group,
35
+ required: false,
36
+ default: Keyrod::Settings['fedcloud']['group'],
37
+ type: :string,
38
+ desc: 'Group to join',
39
+ aliases: '-g'
40
+ option :'interactive-fallback',
41
+ required: false,
42
+ default: Keyrod::Settings['fedcloud']['interactive-fallback'],
43
+ type: :boolean,
44
+ desc: 'Fallback to interactive mode if group is not set',
45
+ aliases: '-f'
46
+ option :'identity-provider',
47
+ required: true,
48
+ default: Keyrod::Settings['fedcloud']['identity-provider'],
49
+ type: :string,
50
+ desc: 'Identity provider for token',
51
+ aliases: '-p'
52
+ option :'refresh-token',
53
+ required: false,
54
+ default: Keyrod::Settings['oidc']['refresh_token'],
55
+ type: :string,
56
+ desc: 'Refresh token for creating access token',
57
+ aliases: '-r'
58
+ option :'oidc-site',
59
+ required: false,
60
+ default: Keyrod::Settings['oidc']['site'],
61
+ type: :string,
62
+ desc: 'OIDC site for authenticating refresh token',
63
+ aliases: '-o'
64
+ option :'client-id',
65
+ required: false,
66
+ default: Keyrod::Settings['oidc']['client_id'],
67
+ type: :string,
68
+ desc: 'OIDC client ID',
69
+ aliases: '-i'
70
+ option :'client-secret',
71
+ required: false,
72
+ default: Keyrod::Settings['oidc']['client_secret'],
73
+ type: :string,
74
+ desc: 'OIDC client secret',
75
+ aliases: '-t'
76
+ def token
77
+ merge_config options
78
+ validate_config options
79
+ init_logger
80
+ process_tokens
81
+ rescue Keyrod::Errors::ParamsError => e
82
+ abort e.message
83
+ end
84
+
85
+ desc 'version', 'Prints Keyrod version'
86
+ def version
87
+ $stdout.puts Keyrod::VERSION
88
+ end
89
+
90
+ default_task :token
91
+
92
+ private
93
+
94
+ def init_logger
95
+ logging_level = Keyrod::Settings[:debug] ? [:debug] : [:info]
96
+ Yell.new :stdout, name: Object, level: logging_level
97
+ Object.send :include, Yell::Loggable
98
+ end
99
+
100
+ def merge_config(options)
101
+ Keyrod::Settings.clear
102
+ Keyrod::Settings.merge! options.to_hash
103
+ ssl_params = { verify: Keyrod::Settings[:'verify-ssl'] }
104
+ ssl_params[:ca_path] = Keyrod::Settings[:'ca-dir'] if Keyrod::Settings[:'ca-dir']
105
+ Keyrod::Settings[:ssl] = ssl_params
106
+ end
107
+
108
+ def validate_config(options)
109
+ raise Keyrod::Errors::ParamsError, 'Refresh/access token required' unless options[:'access-token'] || options[:'refresh-token']
110
+ raise Keyrod::Errors::ParamsError, 'Use one of refresh/access token' if options[:'access-token'] && options[:'refresh-token']
111
+
112
+ return unless options[:'refresh-token']
113
+ validate_config_group options,
114
+ ['oidc-site', 'client-id', 'client-secret'],
115
+ '--oidc-site, --client-id, --client-secret are required with refresh token'
116
+ end
117
+
118
+ def validate_config_group(options, group, message)
119
+ return if group.all? { |option| options[option] }
120
+
121
+ raise Keyrod::Errors::ParamsError, message
122
+ end
123
+
124
+ def process_tokens
125
+ access_token if Keyrod::Settings[:'refresh-token']
126
+
127
+ fedcloud_client = Keyrod::FedcloudClient.new
128
+ unscoped_token = fedcloud_client.unscoped_token
129
+ project = select_project(fedcloud_client.projects(unscoped_token))
130
+
131
+ $stdout.puts fedcloud_client.scoped_token(unscoped_token, project)
132
+ rescue Keyrod::Errors::ProjectError => e
133
+ abort e.message
134
+ end
135
+
136
+ def access_token
137
+ oidc_client = Keyrod::OIDCClient.new
138
+ Keyrod::Settings[:'access-token'] = oidc_client.access_token
139
+ end
140
+
141
+ def select_project(projects)
142
+ group = Keyrod::Settings[:group]
143
+ return group if projects.include? group
144
+
145
+ raise Keyrod::Errors::ProjectError, "Group #{group} is not available" if group && !Keyrod::Settings[:'interactive-fallback']
146
+
147
+ ask('Choose one of these groups:', limited_to: projects)
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,8 @@
1
+ module Keyrod
2
+ module Errors
3
+ class ResponseError < StandardError; end
4
+ class ConnectionError < StandardError; end
5
+ class ParamsError < StandardError; end
6
+ class ProjectError < StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,111 @@
1
+ require 'faraday'
2
+ require 'json'
3
+ require 'net/http'
4
+
5
+ module Keyrod
6
+ class FedcloudClient
7
+ attr_reader :site, :access_token, :auth_path, :ssl
8
+
9
+ PROJECTS_PATH = '/v3/auth/projects'.freeze
10
+ SCOPED_PATH = '/v3/auth/tokens'.freeze
11
+ REDIRECT_HEADER = 'WWW-Authenticate'.freeze
12
+
13
+ def initialize
14
+ @site = Keyrod::Settings[:site]
15
+ @access_token = Keyrod::Settings[:'access-token']
16
+ @auth_path = "/v3/OS-FEDERATION/identity_providers/#{Keyrod::Settings[:'identity-provider']}/protocols/oidc/auth"
17
+ @ssl = Keyrod::Settings[:ssl]
18
+ end
19
+
20
+ def unscoped_token
21
+ response = handle_response(unscoped_token_params, error_message: 'Response for getting unscoped token was')
22
+ response.headers[:'X-Subject-Token']
23
+ end
24
+
25
+ def projects(unscoped_token)
26
+ response = handle_response(projects_params(unscoped_token), error_message: 'Response for getting list of projects was')
27
+ parse_projects(response.body)
28
+ end
29
+
30
+ def scoped_token(unscoped_token, project)
31
+ response = handle_response(scoped_token_params, body: scoped_token_body(unscoped_token, project),
32
+ error_message: 'Response for getting scoped token was')
33
+ response.headers[:'X-Subject-Token']
34
+ end
35
+
36
+ private
37
+
38
+ def unscoped_token_params
39
+ {
40
+ site: site,
41
+ headers: { Authorization: "Bearer #{access_token}", Accept: 'application/json' },
42
+ path: auth_path
43
+ }
44
+ end
45
+
46
+ def projects_params(unscoped_token)
47
+ {
48
+ site: site,
49
+ headers: { 'X-Auth-Token': unscoped_token, Accept: 'application/json' },
50
+ path: PROJECTS_PATH
51
+ }
52
+ end
53
+
54
+ def scoped_token_params
55
+ {
56
+ site: site,
57
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
58
+ path: SCOPED_PATH
59
+ }
60
+ end
61
+
62
+ def scoped_token_body(unscoped_token, project)
63
+ {
64
+ auth: {
65
+ identity: {
66
+ methods: ['token'],
67
+ token: {
68
+ id: unscoped_token
69
+ }
70
+ },
71
+ scope: {
72
+ project: {
73
+ id: project
74
+ }
75
+ }
76
+ }
77
+ }.to_json
78
+ end
79
+
80
+ def parse_projects(projects_body)
81
+ project_json = JSON.parse(projects_body, symbolize_names: true)
82
+ project_json[:projects].map { |project| project[:id] }
83
+ end
84
+
85
+ def parse_redirect(response)
86
+ response.headers[REDIRECT_HEADER].downcase.sub('keystone uri=', '').delete("'")
87
+ end
88
+
89
+ def connection(params)
90
+ Faraday.new(File.join(params[:site], params[:path]), ssl: ssl, headers: params[:headers])
91
+ end
92
+
93
+ def handle_response(params, body: nil, error_message: '')
94
+ conn = connection(params)
95
+ logger.debug "Sending request with headers #{conn.headers}"
96
+ begin
97
+ response = body ? conn.post { |req| req.body = body } : conn.get
98
+
99
+ if response.status == 401 && response.headers[REDIRECT_HEADER]
100
+ params[:site] = parse_redirect(response)
101
+ response = handle_response(params, body: body, error_message: error_message)
102
+ end
103
+ rescue Faraday::ConnectionFailed => e
104
+ raise Keyrod::Errors::ConnectionError, e.message
105
+ end
106
+
107
+ raise Keyrod::Errors::ResponseError, "#{error_message} #{response.status}" unless response.success?
108
+ response
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,37 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Keyrod
5
+ class OIDCClient
6
+ attr_reader :oidc_site, :refresh_token, :client_id, :client_secret, :ssl
7
+
8
+ def initialize
9
+ @oidc_site = Keyrod::Settings[:'oidc-site']
10
+ @refresh_token = Keyrod::Settings[:'refresh-token']
11
+ @client_id = Keyrod::Settings[:'client-id']
12
+ @client_secret = Keyrod::Settings[:'client-secret']
13
+ @ssl = Keyrod::Settings[:ssl]
14
+ end
15
+
16
+ def access_token
17
+ conn = Faraday.new(oidc_site, ssl: ssl, params: params)
18
+
19
+ logger.debug "access_token: Sending request with params #{conn.params}"
20
+ response = conn.post
21
+ logger.debug "Received response with code #{response.status} and body #{response.body}"
22
+ raise Keyrod::Errors::ResponseError, "Response from OIDC server was #{response.status}" unless response.success?
23
+
24
+ JSON.parse(response.body, symbolize_names: true)[:access_token]
25
+ end
26
+
27
+ private
28
+
29
+ def params
30
+ { grant_type: 'refresh_token',
31
+ client_id: client_id,
32
+ client_secret: client_secret,
33
+ refresh_token: refresh_token,
34
+ scope: 'openid profile' }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ require 'settingslogic'
2
+
3
+ module Keyrod
4
+ class Settings < Settingslogic
5
+ config_file = 'config.yml'
6
+
7
+ source "#{ENV['HOME']}/.keyrod/#{config_file}"\
8
+ if File.exist?("#{ENV['HOME']}/.keyrod/#{config_file}")
9
+
10
+ source "/etc/keyrod/#{config_file}"\
11
+ if File.exist?("/etc/keyrod/#{config_file}")
12
+
13
+ source "#{File.dirname(__FILE__)}/../../config/#{config_file}"
14
+ namespace 'keyrod'
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Keyrod
2
+ VERSION = '1.0.0'.freeze
3
+ end
data/lib/keyrod.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Keyrod
2
+ autoload :CLI, 'keyrod/cli'
3
+ autoload :Settings, 'keyrod/settings'
4
+ autoload :OIDCClient, 'keyrod/oidc_client'
5
+ autoload :FedcloudClient, 'keyrod/fedcloud_client'
6
+ autoload :Errors, 'keyrod/errors'
7
+ end
8
+
9
+ require 'keyrod/version'
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: keyrod
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cuong Duong Tuan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-04-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '12.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '12.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.53'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.53'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.22'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.22'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: faraday
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.14'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.14'
111
+ - !ruby/object:Gem::Dependency
112
+ name: settingslogic
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: thor
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.20'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.20'
139
+ - !ruby/object:Gem::Dependency
140
+ name: yell
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.0'
153
+ description: CLI for authenticating OIDC clients in EGI Federated Cloud
154
+ email: cduongt@cesnet.cz
155
+ executables:
156
+ - keyrod
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - bin/keyrod
161
+ - config/config.yml
162
+ - lib/keyrod.rb
163
+ - lib/keyrod/cli.rb
164
+ - lib/keyrod/errors.rb
165
+ - lib/keyrod/fedcloud_client.rb
166
+ - lib/keyrod/oidc_client.rb
167
+ - lib/keyrod/settings.rb
168
+ - lib/keyrod/version.rb
169
+ homepage: https://github.com/cduongt/keyrod
170
+ licenses:
171
+ - Apache-2.0
172
+ metadata: {}
173
+ post_install_message:
174
+ rdoc_options: []
175
+ require_paths:
176
+ - lib
177
+ required_ruby_version: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: 2.2.0
182
+ required_rubygems_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ requirements: []
188
+ rubyforge_project:
189
+ rubygems_version: 2.6.13
190
+ signing_key:
191
+ specification_version: 4
192
+ summary: Keyrod CLI
193
+ test_files: []