azure-credentials 0.1.1 → 0.1.2

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