azure-credentials 0.1.4 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41054f1415829e4fd56cac250fe8d16c273eca28a1b223fe5803d3a9f14f3356
4
- data.tar.gz: 986b378c95b3fbfd7cfdad139f8c10df9c95fa12ca3eff87f65d061f8fce9982
3
+ metadata.gz: 16fd77b4ad168d224e9152465c5ba4cc09235a834b273f2ff39fc6cea1f6c03a
4
+ data.tar.gz: e243269ea8c3eed6eb9ec2809b1990203f1b67e2ca0025361c354733420b4e25
5
5
  SHA512:
6
- metadata.gz: beabe69bbf92b11a2a5189ddf09678f95f312dbce07c8208b3baba8c8474bd7845b8cdf81d83debfeae94eabde2c51bb3987fe3f7f3b3c89c2f9527eb8f5f6a0
7
- data.tar.gz: e327fcd93169b5b047150397fe8f295cfd74ff7d37113bc4f62a8ba7671790b9f65f96ec88b2ef37df4babd3d7ceaac617688c7bbaf04152c695e65697d88698
6
+ metadata.gz: 6304e8dffbb927c01b3548e1c91e02abf925429bd5b3f4421f7ca74a255a26793813cd343b888247a205797e23a55f26f1c4c1cfad18c70648c0fd9048cbbad1
7
+ data.tar.gz: 50f691b65bff35b410ad181cccf5e29e604106948d01a3e9687f4c490daf40f9cba7d29cfca2e1c4bfdba41c0df3bc3c715c635a668c7220684b8f0bcf41c967
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'azure/utility/credentials'
5
- Azure::Utility::Credentials.new
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'azure/utility/credentials'
5
+ Azure::Utility::Credentials.new
@@ -1,377 +1,395 @@
1
- # frozen_string_literal: true
2
-
3
- require 'io/console'
4
- require 'net/http'
5
- require 'uri'
6
- require 'json'
7
- require 'securerandom'
8
- require 'time'
9
- require 'logger'
10
- require 'mixlib/cli'
11
-
12
- module Azure
13
- module Utility
14
- #
15
- # Options
16
- #
17
- class Options
18
- include Mixlib::CLI
19
-
20
- option :username,
21
- short: '-u',
22
- long: '--username USERNAME',
23
- description: 'Enter the username (must be an Azure AD user)',
24
- required: false
25
-
26
- option :password,
27
- short: '-p',
28
- long: '--password PASSWORD',
29
- description: 'Enter the password for the Azure AD user',
30
- required: false
31
-
32
- option :subscription_id,
33
- short: '-s',
34
- long: '--subscription ID',
35
- description: 'Enter the Subscription ID to work against (default: process all subscriptions within the Azure tenant)',
36
- required: false,
37
- default: nil
38
-
39
- option :role,
40
- short: '-r',
41
- long: '--role ROLENAME',
42
- description: 'Enter the built-in Azure role to add the service principal to on your subscription (default: Contributor)',
43
- in: %w[Contributor Owner],
44
- default: 'Contributor',
45
- required: false
46
-
47
- option :type,
48
- short: '-t',
49
- long: '--type OUTPUTTYPE',
50
- description: 'Set the output type (default: chef)',
51
- in: %w[chef puppet terraform generic],
52
- required: false,
53
- default: 'chef'
54
-
55
- option :log_level,
56
- short: '-l',
57
- long: '--log_level LEVEL',
58
- description: 'Set the log level (debug, info, warn, error, fatal)',
59
- default: :info,
60
- required: false,
61
- in: %w[debug info warn error fatal],
62
- proc: proc { |l| l.to_sym }
63
-
64
- option :output_file,
65
- short: '-o',
66
- long: '--output FILENAME',
67
- description: 'Enter the filename to save the credentials to',
68
- default: './credentials',
69
- required: false
70
-
71
- option :out_to_screen,
72
- short: '-v',
73
- long: '--verbose',
74
- description: 'Display the credentials in STDOUT after creation? (warning: will contain secrets)',
75
- default: false,
76
- required: false
77
-
78
- option :help,
79
- short: '-h',
80
- long: '--help',
81
- description: 'Show this message',
82
- on: :tail,
83
- boolean: true,
84
- show_options: true,
85
- exit: 0
86
- end
87
-
88
- #
89
- # Logger
90
- #
91
- class CustomLogger
92
- def self.log
93
- if @logger.nil?
94
- cli = Options.new
95
- cli.parse_options
96
- @logger = Logger.new STDOUT
97
- @logger.level = logger_level_for(cli.config[:log_level])
98
- @logger.formatter = proc do |severity, datetime, _progname, msg|
99
- "#{severity} [#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{msg}\n"
100
- end
101
- end
102
- @logger
103
- end
104
-
105
- def self.logger_level_for(sym)
106
- case sym
107
- when :debug
108
- Logger::DEBUG
109
- when :info
110
- Logger::INFO
111
- when :warn
112
- Logger::WARN
113
- when :error
114
- Logger::ERROR
115
- when :fatal
116
- Logger::FATAL
117
- end
118
- end
119
- end
120
-
121
- #
122
- # Credentials
123
- #
124
- class Credentials
125
- AZURE_SERVICE_PRINCIPAL = '1950a258-227b-4e31-a9cf-717495945fc2'
126
- CONFIG_PATH = "#{ENV['HOME']}/.azure/credentials"
127
-
128
- def initialize
129
- cli = Options.new
130
- cli.parse_options
131
- CustomLogger.log.debug "Command line options: #{cli.config.inspect}"
132
-
133
- username = cli.config[:username] || username_stdin
134
- password = cli.config[:password] || password_stdin
135
-
136
- # Get Bearer token for user and pass through to main method
137
- token = azure_authenticate(username, password)
138
- if token.nil?
139
- error_message = 'Unable to acquire token from Azure AD provider.'
140
- CustomLogger.log.error error_message
141
- raise error_message
142
- end
143
- created_credentials = create_all_objects(token, cli.config)
144
- CustomLogger.log.debug "Credential details: #{created_credentials.inspect}"
145
- create_file(created_credentials, cli.config)
146
- CustomLogger.log.info 'Done!'
147
- end
148
-
149
- def username_stdin
150
- print 'Enter your Azure AD username (user@domain.com): '
151
- STDIN.gets.chomp
152
- end
153
-
154
- def password_stdin
155
- print 'Enter your password: '
156
- STDIN.noecho(&:gets).chomp
157
- end
158
-
159
- def create_file(created_credentials, config)
160
- file_name = config[:output_file] || './credentials'
161
- file_name_expanded = File.expand_path(file_name)
162
- CustomLogger.log.info "Creating credentials file at #{file_name_expanded}"
163
- output = ''
164
-
165
- style = config[:type] || 'chef'
166
- case style
167
- when 'chef' # ref: https://github.com/pendrica/chef-provisioning-azurerm#configuration
168
- created_credentials.each do |s|
169
- subscription_template = <<~CHEFEOH
170
- [#{s[:subscription_id]}]
171
- client_id = "#{s[:client_id]}"
172
- client_secret = "#{s[:client_secret]}"
173
- tenant_id = "#{s[:tenant_id]}"
174
-
175
- CHEFEOH
176
- output += subscription_template
177
- end
178
- when 'terraform' # ref: https://www.terraform.io/docs/providers/azurerm/index.html
179
- created_credentials.each do |s|
180
- subscription_template = <<~TFEOH
181
- provider "azurerm" {
182
- subscription_id = "#{s[:subscription_id]}"
183
- client_id = "#{s[:client_id]}"
184
- client_secret = "#{s[:client_secret]}"
185
- tenant_id = "#{s[:tenant_id]}"
186
- }
187
-
188
- TFEOH
189
- output += subscription_template
190
- end
191
- when 'puppet' # ref: https://github.com/puppetlabs/puppetlabs-azure#installing-the-azure-module
192
- created_credentials.each do |s|
193
- subscription_template = <<~PPEOH
194
- azure: {
195
- subscription_id: "#{s[:subscription_id]}"
196
- tenant_id: "#{s[:tenant_id]}"
197
- client_id: "#{s[:client_id]}"
198
- client_secret: "#{s[:client_secret]}"
199
- }
200
-
201
- PPEOH
202
- output += subscription_template
203
- end
204
- else # generic credentials output
205
- created_credentials.each do |s|
206
- subscription_template = <<~GENERICEOH
207
- azure_subscription_id = "#{s[:subscription_id]}"
208
- azure_tenant_id = "#{s[:tenant_id]}"
209
- azure_client_id = "#{s[:client_id]}"
210
- azure_client_secret = "#{s[:client_secret]}"
211
-
212
- GENERICEOH
213
- output += subscription_template
214
- end
215
- end
216
- File.open(file_name_expanded, 'w') do |file|
217
- file.write(output)
218
- end
219
- puts output if config[:out_to_screen]
220
- end
221
-
222
- def create_all_objects(token, config)
223
- tenant_id = get_tenant_id(token).first['tenantId']
224
- subscriptions = Array(config[:subscription_id])
225
- subscriptions = get_subscriptions(token) if subscriptions.empty?
226
- identifier = SecureRandom.hex(2)
227
- credentials = []
228
- subscriptions.each do |subscription|
229
- new_application_name = "azure_#{identifier}_#{subscription}"
230
- new_client_secret = SecureRandom.urlsafe_base64(16, true)
231
- application_id = create_application(tenant_id, token, new_application_name, new_client_secret)['appId']
232
- service_principal_object_id = create_service_principal(tenant_id, token, application_id)['objectId']
233
- role_name = config[:role] || 'Contributor'
234
- role_definition_id = get_role_definition(subscription, token, role_name).first['id']
235
- success = false
236
- counter = 0
237
- until success || counter > 5
238
- counter += 1
239
- CustomLogger.log.info "Waiting for service principal to be available in directory (retry #{counter})"
240
- sleep 2
241
- assigned_role = assign_service_principal_to_role_id(subscription, token, service_principal_object_id, role_definition_id)
242
- success = true unless assigned_role['error']
243
- end
244
- raise 'Failed to assign Service Principal to Role' unless success
245
- CustomLogger.log.info "Assigned service principal to role #{role_name} in subscription #{subscription}"
246
- new_credentials = {}
247
- new_credentials[:subscription_id] = subscription
248
- new_credentials[:client_id] = application_id
249
- new_credentials[:client_secret] = new_client_secret
250
- new_credentials[:tenant_id] = tenant_id
251
- credentials.push(new_credentials)
252
- end
253
- credentials
254
- end
255
-
256
- def get_subscriptions(token)
257
- CustomLogger.log.info 'Retrieving subscriptions info'
258
- subscriptions = []
259
- subscriptions_call = azure_call(:get, 'https://management.azure.com/subscriptions?api-version=2015-01-01', nil, token)
260
- subscriptions_call['value'].each do |subscription|
261
- subscriptions.push subscription['subscriptionId']
262
- end
263
- CustomLogger.log.debug "SubscriptionIDs returned: #{subscriptions.inspect}"
264
- subscriptions
265
- end
266
-
267
- def get_tenant_id(token)
268
- CustomLogger.log.info 'Retrieving tenant info'
269
- tenants = azure_call(:get, 'https://management.azure.com/tenants?api-version=2015-01-01', nil, token)
270
- tenants['value']
271
- end
272
-
273
- def create_application(tenant_id, token, new_application_name, new_client_secret)
274
- CustomLogger.log.info "Creating application #{new_application_name} in tenant #{tenant_id}"
275
- url = "https://graph.windows.net/#{tenant_id}/applications?api-version=1.42-previewInternal"
276
- payload_json = <<-JSONEOH
277
- {
278
- "availableToOtherTenants": false,
279
- "displayName": "#{new_application_name}",
280
- "homepage": "https://management.core.windows.net",
281
- "identifierUris": [
282
- "https://#{tenant_id}/#{new_application_name}"
283
- ],
284
- "passwordCredentials": [
285
- {
286
- "startDate": "#{Time.now.utc.iso8601}",
287
- "endDate": "#{(Time.now + (24 * 60 * 60 * 365 * 10)).utc.iso8601}",
288
- "keyId": "#{SecureRandom.uuid}",
289
- "value": "#{new_client_secret}"
290
- }
291
- ]
292
- }
293
- JSONEOH
294
- azure_call(:post, url, payload_json, token)
295
- end
296
-
297
- def create_service_principal(tenant_id, token, application_id)
298
- CustomLogger.log.info 'Creating service principal for application'
299
- url = "https://graph.windows.net/#{tenant_id}/servicePrincipals?api-version=1.42-previewInternal"
300
- payload_json = <<-PAYLOADEOH
301
- {
302
- "appId": "#{application_id}",
303
- "accountEnabled": true
304
- }
305
- PAYLOADEOH
306
- azure_call(:post, url, payload_json, token)
307
- end
308
-
309
- def assign_service_principal_to_role_id(subscription_id, token, service_principal_object_id, role_definition_id)
310
- CustomLogger.log.info 'Attempting to assign service principal to role'
311
- url = "https://management.azure.com/subscriptions/#{subscription_id}/providers/Microsoft.Authorization/roleAssignments/#{service_principal_object_id}?api-version=2015-07-01"
312
- payload_json = <<-PAYLOADEOH
313
- {
314
- "properties": {
315
- "roleDefinitionId": "#{role_definition_id}",
316
- "principalId": "#{service_principal_object_id}"
317
- }
318
- }
319
- PAYLOADEOH
320
- azure_call(:put, url, payload_json, token)
321
- end
322
-
323
- def get_role_definition(tenant_id, token, role_name)
324
- role_definitions = azure_call(:get, "https://management.azure.com/subscriptions/#{tenant_id}/providers/Microsoft.Authorization/roleDefinitions?$filter=roleName%20eq%20\'#{role_name}\'&api-version=2015-07-01", nil, token)
325
- role_definitions['value']
326
- end
327
-
328
- def azure_authenticate(username, password)
329
- CustomLogger.log.info 'Authenticating to Azure Active Directory'
330
- url = 'https://login.windows.net/Common/oauth2/token'
331
- data = "resource=https%3A%2F%2Fmanagement.core.windows.net%2F&client_id=#{AZURE_SERVICE_PRINCIPAL}" \
332
- "&grant_type=password&username=#{username}&scope=openid&password=#{password}"
333
- response = http_post(url, data)
334
- JSON.parse(response.body)['access_token']
335
- end
336
-
337
- def http_post(url, data)
338
- uri = URI(url)
339
- response = nil
340
- Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
341
- request = Net::HTTP::Post.new uri
342
- CustomLogger.log.debug "Request: #{request.uri} (#{request.method}) #{data}"
343
- request.body = data
344
- response = http.request request
345
- CustomLogger.log.debug "Response: #{response.body}"
346
- end
347
- response
348
- end
349
-
350
- def azure_call(method, url, data, token)
351
- uri = URI(url)
352
- response = nil
353
- Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
354
- case method
355
- when :put
356
- request = Net::HTTP::Put.new uri
357
- when :delete
358
- request = Net::HTTP::Delete.new uri
359
- when :get
360
- request = Net::HTTP::Get.new uri
361
- when :post
362
- request = Net::HTTP::Post.new uri
363
- when :patch
364
- request = Net::HTTP::Patch.new uri
365
- end
366
- request.body = data
367
- request['Authorization'] = "Bearer #{token}"
368
- request['Content-Type'] = 'application/json'
369
- CustomLogger.log.debug "Request: #{request.uri} (#{method}) #{data}"
370
- response = http.request request
371
- CustomLogger.log.debug "Response: #{response.body}"
372
- end
373
- JSON.parse(response.body) unless response.nil?
374
- end
375
- end
376
- end
377
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+ require 'net/http'
5
+ require 'uri'
6
+ require 'json'
7
+ require 'securerandom'
8
+ require 'time'
9
+ require 'logger'
10
+ require 'mixlib/cli'
11
+
12
+ module Azure
13
+ module Utility
14
+ #
15
+ # Options
16
+ #
17
+ class Options
18
+ include Mixlib::CLI
19
+
20
+ option :username,
21
+ short: '-u',
22
+ long: '--username USERNAME',
23
+ description: 'Enter the username (must be an Azure AD user)',
24
+ required: false
25
+
26
+ option :password,
27
+ short: '-p',
28
+ long: '--password PASSWORD',
29
+ description: 'Enter the password for the Azure AD user',
30
+ required: false
31
+
32
+ option :subscription_id,
33
+ short: '-s',
34
+ long: '--subscription ID',
35
+ description: 'Enter the Subscription ID to work against (default: process all subscriptions within the Azure tenant)',
36
+ required: false,
37
+ default: nil
38
+
39
+ option :role,
40
+ short: '-r',
41
+ long: '--role ROLENAME',
42
+ description: 'Enter the built-in Azure role to add the service principal to on your subscription (default: Contributor)',
43
+ in: %w[Contributor Owner],
44
+ default: 'Contributor',
45
+ required: false
46
+
47
+ option :type,
48
+ short: '-t',
49
+ long: '--type OUTPUTTYPE',
50
+ description: 'Set the output type (default: chef)',
51
+ in: %w[chef puppet terraform azurecli generic],
52
+ required: false,
53
+ default: 'chef'
54
+
55
+ option :log_level,
56
+ short: '-l',
57
+ long: '--log_level LEVEL',
58
+ description: 'Set the log level (debug, info, warn, error, fatal)',
59
+ default: :info,
60
+ required: false,
61
+ in: %w[debug info warn error fatal],
62
+ proc: proc { |l| l.to_sym }
63
+
64
+ option :output_file,
65
+ short: '-o',
66
+ long: '--output FILENAME',
67
+ description: 'Enter the filename to save the credentials to',
68
+ default: './credentials',
69
+ required: false
70
+
71
+ option :out_to_screen,
72
+ short: '-v',
73
+ long: '--verbose',
74
+ description: 'Display the credentials in STDOUT after creation? (warning: will contain secrets)',
75
+ default: false,
76
+ required: false
77
+
78
+ option :help,
79
+ short: '-h',
80
+ long: '--help',
81
+ description: 'Show this message',
82
+ on: :tail,
83
+ boolean: true,
84
+ show_options: true,
85
+ exit: 0
86
+ end
87
+
88
+ #
89
+ # Logger
90
+ #
91
+ class CustomLogger
92
+ def self.log
93
+ if @logger.nil?
94
+ cli = Options.new
95
+ cli.parse_options
96
+ @logger = Logger.new STDOUT
97
+ @logger.level = logger_level_for(cli.config[:log_level])
98
+ @logger.formatter = proc do |severity, datetime, _progname, msg|
99
+ "#{severity} [#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{msg}\n"
100
+ end
101
+ end
102
+ @logger
103
+ end
104
+
105
+ def self.logger_level_for(sym)
106
+ case sym
107
+ when :debug
108
+ Logger::DEBUG
109
+ when :info
110
+ Logger::INFO
111
+ when :warn
112
+ Logger::WARN
113
+ when :error
114
+ Logger::ERROR
115
+ when :fatal
116
+ Logger::FATAL
117
+ end
118
+ end
119
+ end
120
+
121
+ #
122
+ # Credentials
123
+ #
124
+ class Credentials
125
+ AZURE_SERVICE_PRINCIPAL = '1950a258-227b-4e31-a9cf-717495945fc2'
126
+ CONFIG_PATH = "#{ENV['HOME']}/.azure/credentials"
127
+
128
+ def initialize
129
+ cli = Options.new
130
+ cli.parse_options
131
+ CustomLogger.log.debug "Command line options: #{cli.config.inspect}"
132
+
133
+ username = cli.config[:username] || username_stdin
134
+ password = cli.config[:password] || password_stdin
135
+
136
+ # Get Bearer token for user and pass through to main method
137
+ token = azure_authenticate(username, password)
138
+ if token.nil?
139
+ error_message = 'Unable to acquire token from Azure AD provider.'
140
+ CustomLogger.log.error error_message
141
+ raise error_message
142
+ end
143
+ created_credentials = create_all_objects(token, cli.config)
144
+ CustomLogger.log.debug "Credential details: #{created_credentials.inspect}"
145
+ create_file(created_credentials, cli.config)
146
+ CustomLogger.log.info 'Done!'
147
+ end
148
+
149
+ def username_stdin
150
+ print 'Enter your Azure AD username (user@domain.com): '
151
+ STDIN.gets.chomp
152
+ end
153
+
154
+ def password_stdin
155
+ print 'Enter your password: '
156
+ STDIN.noecho(&:gets).chomp
157
+ end
158
+
159
+ def create_file(created_credentials, config)
160
+ file_name = config[:output_file] || './credentials'
161
+ file_name_expanded = File.expand_path(file_name)
162
+ CustomLogger.log.info "Creating credentials file at #{file_name_expanded}"
163
+ output = ''
164
+
165
+ style = config[:type] || 'chef'
166
+ case style
167
+ when 'chef' # ref: https://github.com/pendrica/chef-provisioning-azurerm#configuration
168
+ created_credentials.each do |s|
169
+ subscription_template = <<~CHEFEOH
170
+ [#{s[:subscription_id]}]
171
+ client_id = "#{s[:client_id]}"
172
+ client_secret = "#{s[:client_secret]}"
173
+ tenant_id = "#{s[:tenant_id]}"
174
+
175
+ CHEFEOH
176
+ output += subscription_template
177
+ end
178
+ when 'terraform' # ref: https://www.terraform.io/docs/providers/azurerm/index.html
179
+ created_credentials.each do |s|
180
+ subscription_template = <<~TFEOH
181
+ provider "azurerm" {
182
+ subscription_id = "#{s[:subscription_id]}"
183
+ client_id = "#{s[:client_id]}"
184
+ client_secret = "#{s[:client_secret]}"
185
+ tenant_id = "#{s[:tenant_id]}"
186
+ }
187
+
188
+ TFEOH
189
+ output += subscription_template
190
+ end
191
+ when 'puppet' # ref: https://github.com/puppetlabs/puppetlabs-azure#installing-the-azure-module
192
+ created_credentials.each do |s|
193
+ subscription_template = <<~PPEOH
194
+ azure: {
195
+ subscription_id: "#{s[:subscription_id]}"
196
+ tenant_id: "#{s[:tenant_id]}"
197
+ client_id: "#{s[:client_id]}"
198
+ client_secret: "#{s[:client_secret]}"
199
+ }
200
+
201
+ PPEOH
202
+ output += subscription_template
203
+ end
204
+ when 'azurecli'
205
+ created_credentials.each do |s|
206
+ subscription_template = <<~AZURECLIEOH
207
+ [default]
208
+ subscription_id=#{s[:subscription_id]}
209
+ tenant=#{s[:tenant_id]}
210
+ client_id=#{s[:client_id]}
211
+ secret=#{s[:client_secret]}
212
+
213
+ AZURECLIEOH
214
+ output += subscription_template
215
+ end
216
+ else # generic credentials output
217
+ created_credentials.each do |s|
218
+ subscription_template = <<~GENERICEOH
219
+ azure_subscription_id = "#{s[:subscription_id]}"
220
+ azure_tenant_id = "#{s[:tenant_id]}"
221
+ azure_client_id = "#{s[:client_id]}"
222
+ azure_client_secret = "#{s[:client_secret]}"
223
+
224
+ GENERICEOH
225
+ output += subscription_template
226
+ end
227
+ end
228
+ File.open(file_name_expanded, 'w') do |file|
229
+ file.write(output)
230
+ end
231
+ puts output if config[:out_to_screen]
232
+ end
233
+
234
+ def create_all_objects(token, config)
235
+ tenant_id = get_tenant_id(token).first['tenantId']
236
+ subscriptions = Array(config[:subscription_id])
237
+ subscriptions = get_subscriptions(token) if subscriptions.empty?
238
+ identifier = SecureRandom.hex(2)
239
+ credentials = []
240
+ subscriptions.each do |subscription|
241
+ new_application_name = "azure_#{identifier}_#{subscription}"
242
+
243
+ if config[:type] == 'azurecli' then
244
+ new_client_secret = SecureRandom.uuid
245
+ else
246
+ new_client_secret = SecureRandom.urlsafe_base64(16, true)
247
+ end
248
+
249
+ application_id = create_application(tenant_id, token, new_application_name, new_client_secret)['appId']
250
+ service_principal_object_id = create_service_principal(tenant_id, token, application_id)['objectId']
251
+ role_name = config[:role] || 'Contributor'
252
+ role_definition_id = get_role_definition(subscription, token, role_name).first['id']
253
+ success = false
254
+ counter = 0
255
+ until success || counter > 5
256
+ counter += 1
257
+ CustomLogger.log.info "Waiting for service principal to be available in directory (retry #{counter})"
258
+ sleep 2
259
+ assigned_role = assign_service_principal_to_role_id(subscription, token, service_principal_object_id, role_definition_id)
260
+ success = true unless assigned_role['error']
261
+ end
262
+ raise 'Failed to assign Service Principal to Role' unless success
263
+ CustomLogger.log.info "Assigned service principal to role #{role_name} in subscription #{subscription}"
264
+ new_credentials = {}
265
+ new_credentials[:subscription_id] = subscription
266
+ new_credentials[:client_id] = application_id
267
+ new_credentials[:client_secret] = new_client_secret
268
+ new_credentials[:tenant_id] = tenant_id
269
+ credentials.push(new_credentials)
270
+ end
271
+ credentials
272
+ end
273
+
274
+ def get_subscriptions(token)
275
+ CustomLogger.log.info 'Retrieving subscriptions info'
276
+ subscriptions = []
277
+ subscriptions_call = azure_call(:get, 'https://management.azure.com/subscriptions?api-version=2015-01-01', nil, token)
278
+ subscriptions_call['value'].each do |subscription|
279
+ subscriptions.push subscription['subscriptionId']
280
+ end
281
+ CustomLogger.log.debug "SubscriptionIDs returned: #{subscriptions.inspect}"
282
+ subscriptions
283
+ end
284
+
285
+ def get_tenant_id(token)
286
+ CustomLogger.log.info 'Retrieving tenant info'
287
+ tenants = azure_call(:get, 'https://management.azure.com/tenants?api-version=2015-01-01', nil, token)
288
+ tenants['value']
289
+ end
290
+
291
+ def create_application(tenant_id, token, new_application_name, new_client_secret)
292
+ CustomLogger.log.info "Creating application #{new_application_name} in tenant #{tenant_id}"
293
+ url = "https://graph.windows.net/#{tenant_id}/applications?api-version=1.42-previewInternal"
294
+ payload_json = <<-JSONEOH
295
+ {
296
+ "availableToOtherTenants": false,
297
+ "displayName": "#{new_application_name}",
298
+ "homepage": "https://management.core.windows.net",
299
+ "identifierUris": [
300
+ "https://#{tenant_id}/#{new_application_name}"
301
+ ],
302
+ "passwordCredentials": [
303
+ {
304
+ "startDate": "#{Time.now.utc.iso8601}",
305
+ "endDate": "#{(Time.now + (24 * 60 * 60 * 365 * 10)).utc.iso8601}",
306
+ "keyId": "#{SecureRandom.uuid}",
307
+ "value": "#{new_client_secret}"
308
+ }
309
+ ]
310
+ }
311
+ JSONEOH
312
+ azure_call(:post, url, payload_json, token)
313
+ end
314
+
315
+ def create_service_principal(tenant_id, token, application_id)
316
+ CustomLogger.log.info 'Creating service principal for application'
317
+ url = "https://graph.windows.net/#{tenant_id}/servicePrincipals?api-version=1.42-previewInternal"
318
+ payload_json = <<-PAYLOADEOH
319
+ {
320
+ "appId": "#{application_id}",
321
+ "accountEnabled": true
322
+ }
323
+ PAYLOADEOH
324
+ azure_call(:post, url, payload_json, token)
325
+ end
326
+
327
+ def assign_service_principal_to_role_id(subscription_id, token, service_principal_object_id, role_definition_id)
328
+ CustomLogger.log.info 'Attempting to assign service principal to role'
329
+ url = "https://management.azure.com/subscriptions/#{subscription_id}/providers/Microsoft.Authorization/roleAssignments/#{service_principal_object_id}?api-version=2015-07-01"
330
+ payload_json = <<-PAYLOADEOH
331
+ {
332
+ "properties": {
333
+ "roleDefinitionId": "#{role_definition_id}",
334
+ "principalId": "#{service_principal_object_id}"
335
+ }
336
+ }
337
+ PAYLOADEOH
338
+ azure_call(:put, url, payload_json, token)
339
+ end
340
+
341
+ def get_role_definition(tenant_id, token, role_name)
342
+ role_definitions = azure_call(:get, "https://management.azure.com/subscriptions/#{tenant_id}/providers/Microsoft.Authorization/roleDefinitions?$filter=roleName%20eq%20\'#{role_name}\'&api-version=2015-07-01", nil, token)
343
+ role_definitions['value']
344
+ end
345
+
346
+ def azure_authenticate(username, password)
347
+ CustomLogger.log.info 'Authenticating to Azure Active Directory'
348
+ url = 'https://login.windows.net/Common/oauth2/token'
349
+ data = "resource=https%3A%2F%2Fmanagement.core.windows.net%2F&client_id=#{AZURE_SERVICE_PRINCIPAL}" \
350
+ "&grant_type=password&username=#{username}&scope=openid&password=#{password}"
351
+ response = http_post(url, data)
352
+ JSON.parse(response.body)['access_token']
353
+ end
354
+
355
+ def http_post(url, data)
356
+ uri = URI(url)
357
+ response = nil
358
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
359
+ request = Net::HTTP::Post.new uri
360
+ CustomLogger.log.debug "Request: #{request.uri} (#{request.method}) #{data}"
361
+ request.body = data
362
+ response = http.request request
363
+ CustomLogger.log.debug "Response: #{response.body}"
364
+ end
365
+ response
366
+ end
367
+
368
+ def azure_call(method, url, data, token)
369
+ uri = URI(url)
370
+ response = nil
371
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
372
+ case method
373
+ when :put
374
+ request = Net::HTTP::Put.new uri
375
+ when :delete
376
+ request = Net::HTTP::Delete.new uri
377
+ when :get
378
+ request = Net::HTTP::Get.new uri
379
+ when :post
380
+ request = Net::HTTP::Post.new uri
381
+ when :patch
382
+ request = Net::HTTP::Patch.new uri
383
+ end
384
+ request.body = data
385
+ request['Authorization'] = "Bearer #{token}"
386
+ request['Content-Type'] = 'application/json'
387
+ CustomLogger.log.debug "Request: #{request.uri} (#{method}) #{data}"
388
+ response = http.request request
389
+ CustomLogger.log.debug "Response: #{response.body}"
390
+ end
391
+ JSON.parse(response.body) unless response.nil?
392
+ end
393
+ end
394
+ end
395
+ end
metadata CHANGED
@@ -1,15 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: azure-credentials
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stuart Preston
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-28 00:00:00.000000000 Z
11
+ date: 2019-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.8.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.8'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.8.3
33
+ - !ruby/object:Gem::Dependency
34
+ name: mixlib-cli
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1'
40
+ - - '='
41
+ - !ruby/object:Gem::Version
42
+ version: 1.5.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1'
50
+ - - '='
51
+ - !ruby/object:Gem::Version
52
+ version: 1.5.0
13
53
  - !ruby/object:Gem::Dependency
14
54
  name: bundler
15
55
  requirement: !ruby/object:Gem::Requirement
@@ -99,8 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
99
139
  - !ruby/object:Gem::Version
100
140
  version: '0'
101
141
  requirements: []
102
- rubyforge_project:
103
- rubygems_version: 2.7.6
142
+ rubygems_version: 3.0.3
104
143
  signing_key:
105
144
  specification_version: 4
106
145
  summary: AzureRM credential generator