azure-credentials 0.1.1 → 0.1.2

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
  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