azure-credentials 0.1.4 → 0.1.5

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