zepplen_aws 0.0.2 → 0.0.3
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.
- data/README.md +57 -2
- data/bin/test.yaml +19 -0
- data/bin/zepplen_users +49 -0
- data/bin/zepplen_users_admin +257 -0
- data/lib/zepplen_aws.rb +50 -0
- data/lib/zepplen_aws/auto_dns.rb +2 -0
- data/lib/zepplen_aws/aws.rb +4 -0
- data/lib/zepplen_aws/aws/dynamo_db.rb +29 -0
- data/lib/zepplen_aws/aws/instance_data.rb +76 -0
- data/lib/zepplen_aws/aws/s3.rb +29 -0
- data/lib/zepplen_aws/env.rb +10 -2
- data/lib/zepplen_aws/exceptions.rb +6 -0
- data/lib/zepplen_aws/exceptions/base.rb +6 -0
- data/lib/zepplen_aws/exceptions/users.rb +10 -0
- data/lib/zepplen_aws/server_local_users.rb +256 -0
- data/lib/zepplen_aws/server_user.rb +395 -0
- data/lib/zepplen_aws/server_users.rb +259 -0
- metadata +20 -6
data/lib/zepplen_aws/auto_dns.rb
CHANGED
@@ -21,6 +21,7 @@ module ZepplenAWS
|
|
21
21
|
class AutoDNS
|
22
22
|
require 'colorize'
|
23
23
|
|
24
|
+
# Initializes the object.
|
24
25
|
def initialize()
|
25
26
|
@dns_precidence = 0
|
26
27
|
@dns_pool = []
|
@@ -54,6 +55,7 @@ module ZepplenAWS
|
|
54
55
|
end
|
55
56
|
reduce_dns_pool()
|
56
57
|
process_zone_updates()
|
58
|
+
return nil
|
57
59
|
end
|
58
60
|
|
59
61
|
private
|
data/lib/zepplen_aws/aws.rb
CHANGED
@@ -23,9 +23,13 @@ module ZepplenAWS
|
|
23
23
|
require 'aws-sdk'
|
24
24
|
|
25
25
|
autoload :EC2, 'zepplen_aws/aws/ec2'
|
26
|
+
autoload :S3, 'zepplen_aws/aws/s3'
|
26
27
|
autoload :ELB, 'zepplen_aws/aws/elb'
|
27
28
|
autoload :Route53, 'zepplen_aws/aws/route53'
|
29
|
+
autoload :DynamoDB, 'zepplen_aws/aws/dynamo_db'
|
30
|
+
autoload :InstanceData, 'zepplen_aws/aws/instance_data'
|
28
31
|
|
32
|
+
# Sets the AWS Configuration from the Env class.
|
29
33
|
def self.init!()
|
30
34
|
::AWS.config(:access_key_id => Env.options[:aws_access_key_id], :secret_access_key => Env.options[:aws_secret_access_key])
|
31
35
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#Copyright 2013 Mark Trimmer
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module ZepplenAWS
|
16
|
+
module AWS
|
17
|
+
class DynamoDB
|
18
|
+
|
19
|
+
def initialize()
|
20
|
+
@object = ::AWS::DynamoDB.new()
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(method, *args)
|
24
|
+
@object.public_send(method, *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#Copyright 2013 Mark Trimmer
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module ZepplenAWS
|
16
|
+
module AWS
|
17
|
+
class InstanceData
|
18
|
+
|
19
|
+
def initialize(address=nil, base_path=nil)
|
20
|
+
if(base_path)
|
21
|
+
@base_path = base_path
|
22
|
+
else
|
23
|
+
@base_path = '/latest/meta-data/'
|
24
|
+
end
|
25
|
+
if(address)
|
26
|
+
@address = address
|
27
|
+
else
|
28
|
+
@address = '169.254.169.254'
|
29
|
+
end
|
30
|
+
@net = Net::HTTP.new('169.254.169.254')
|
31
|
+
@values = []
|
32
|
+
@dirs = []
|
33
|
+
response = @net.get(@base_path)
|
34
|
+
response.body.split("\n").each do |line|
|
35
|
+
line.chomp.match(/^([^\/]*)(\/?)$/) do |match|
|
36
|
+
if(match[2] != '')
|
37
|
+
@dirs << match[1]
|
38
|
+
else
|
39
|
+
@values << match[1]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s()
|
46
|
+
return list.to_yaml
|
47
|
+
end
|
48
|
+
|
49
|
+
def dirs()
|
50
|
+
return @dirs
|
51
|
+
end
|
52
|
+
|
53
|
+
def values()
|
54
|
+
return @values
|
55
|
+
end
|
56
|
+
|
57
|
+
def list()
|
58
|
+
to_return = []
|
59
|
+
to_return = @dirs.map{|item| "#{item}/"}
|
60
|
+
to_return += @values
|
61
|
+
return to_return
|
62
|
+
end
|
63
|
+
|
64
|
+
def [](key)
|
65
|
+
if(@dirs.include?(key))
|
66
|
+
return InstanceData.new(@address, "#{@base_path}#{key}/")
|
67
|
+
end
|
68
|
+
if(@values.include?(key))
|
69
|
+
return @net.get("#{@base_path}#{key}/").body.chomp
|
70
|
+
end
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#Copyright 2013 Mark Trimmer
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module ZepplenAWS
|
16
|
+
module AWS
|
17
|
+
class S3
|
18
|
+
|
19
|
+
def initialize()
|
20
|
+
@object = ::AWS::S3.new()
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(method, *args)
|
24
|
+
@object.public_send(method, *args)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/zepplen_aws/env.rb
CHANGED
@@ -62,13 +62,21 @@ module ZepplenAWS
|
|
62
62
|
|
63
63
|
# Allows user access to single option
|
64
64
|
#
|
65
|
-
# @param [
|
65
|
+
# @param [Object] Key to retrieve
|
66
66
|
#
|
67
|
-
# @return [
|
67
|
+
# @return [Object] Request option
|
68
68
|
def self.[](key)
|
69
69
|
return @options[key]
|
70
70
|
end
|
71
71
|
|
72
|
+
# Allow user access to set single option
|
73
|
+
#
|
74
|
+
# @param [Object] option key
|
75
|
+
# @param [Object] option value
|
76
|
+
def self.[]=(key, value)
|
77
|
+
@options[key] = value
|
78
|
+
end
|
79
|
+
|
72
80
|
|
73
81
|
# Not intented for general use
|
74
82
|
#
|
@@ -0,0 +1,256 @@
|
|
1
|
+
#Copyright 2013 Mark Trimmer
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module ZepplenAWS
|
16
|
+
|
17
|
+
# = Local Server Users
|
18
|
+
#
|
19
|
+
# This class is not inteded to be used by 3rd parties
|
20
|
+
class ServerLocalUsers
|
21
|
+
require 'etc'
|
22
|
+
|
23
|
+
def initialize(dynamo_table = nil)
|
24
|
+
@dynamo_table = dynamo_table || Env[:dynamo_table] || 'users'
|
25
|
+
|
26
|
+
if(@dynamo_table == nil)
|
27
|
+
raise Exceptions::Users::MissingOption, "DynamoDB Table Name Required"
|
28
|
+
end
|
29
|
+
|
30
|
+
@server_users = ServerUsers.new(@dynamo_table)
|
31
|
+
|
32
|
+
@dynamo = AWS::DynamoDB.new()
|
33
|
+
@table = @dynamo.tables[@dynamo_table]
|
34
|
+
@table.hash_key = {:type => :string}
|
35
|
+
@table.range_key = {:user_name => :string}
|
36
|
+
@metadata = @table.items['METADATA', '__metadata__']
|
37
|
+
|
38
|
+
@instance_data = AWS::InstanceData.new
|
39
|
+
@tags = {}
|
40
|
+
load_instance_tags()
|
41
|
+
@local_user_data = {}
|
42
|
+
@local_user_data[:local_users] = {}
|
43
|
+
|
44
|
+
@local_user_file = '/etc/zepplen_aws/local_users.yaml'
|
45
|
+
end
|
46
|
+
|
47
|
+
def local_user_file()
|
48
|
+
return @local_user_file
|
49
|
+
end
|
50
|
+
|
51
|
+
def local_user_file=(user_file)
|
52
|
+
@local_user_file = user_file
|
53
|
+
end
|
54
|
+
|
55
|
+
def update!()
|
56
|
+
dynamo_users = load_metadata_from_dynamo()
|
57
|
+
local_users = load_from_file()
|
58
|
+
static_updates = {}
|
59
|
+
if(local_users.has_key?(:metadata) && local_users[:metadata].has_key?(:identity) && local_users[:metadata][:identity] == dynamo_users[:metadata][:identity])
|
60
|
+
users = local_users
|
61
|
+
users[:local_users].each_pair do |user_name, user_data|
|
62
|
+
static_updates[user_name] = false
|
63
|
+
end
|
64
|
+
else
|
65
|
+
users = load_users_from_dynamo(dynamo_users)
|
66
|
+
users[:local_users].each_pair do |user_name, user_data|
|
67
|
+
if(local_users[:local_users].has_key?(user_name) && local_users[:local_users][user_name][:identity] == users[:local_users][user_name][:identity])
|
68
|
+
static_updates[user_name] = false
|
69
|
+
else
|
70
|
+
static_updates[user_name] = true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
make_server_users(users, static_updates)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def make_server_users(users, static_update)
|
80
|
+
#TODO: Write Linux User Admin Object
|
81
|
+
begin
|
82
|
+
# Yes, we could probably just assume 0, but this seemed safer
|
83
|
+
etc_user = Etc.getpwnam('root')
|
84
|
+
root_uid = etc_user['uid']
|
85
|
+
rescue ArgumentError
|
86
|
+
raise "Can Not Find Root User!!!"
|
87
|
+
end
|
88
|
+
|
89
|
+
users[:local_users].each_pair do |user_name, user_data|
|
90
|
+
begin
|
91
|
+
etc_user = Etc.getpwnam(user_name)
|
92
|
+
rescue ArgumentError
|
93
|
+
if(user_data[:home])
|
94
|
+
home_dir = user_data[:home]
|
95
|
+
else
|
96
|
+
home_dir = "/home/#{user_name}"
|
97
|
+
end
|
98
|
+
system "useradd -d #{home_dir} -c \"#{user_data[:full_name]}\" -m -s #{user_data[:shell]} -u #{user_data[:user_id]} -U #{user_name}"
|
99
|
+
etc_user = Etc.getpwnam(user_name)
|
100
|
+
end
|
101
|
+
home_dir = etc_user['dir']
|
102
|
+
user_uid = etc_user['uid']
|
103
|
+
user_gid = etc_user['gid']
|
104
|
+
if(user_data[:sudo])
|
105
|
+
system "usermod -a -G #{users[:metadata][:sudo_group]} #{user_name}"
|
106
|
+
else
|
107
|
+
system "usermod -G #{user_name} #{user_name}"
|
108
|
+
end
|
109
|
+
if(!File.exist?("#{home_dir}/.ssh"))
|
110
|
+
system "mkdir #{home_dir}/.ssh"
|
111
|
+
system "chmod 750 #{home_dir}/.ssh"
|
112
|
+
system "chown root:#{user_name} #{home_dir}/.ssh"
|
113
|
+
end
|
114
|
+
if(user_data[:public_key] && Date.parse(user_data[:public_key_expire]) > Date.today)
|
115
|
+
write_local_file("#{home_dir}/.ssh/authorized_keys", '640', user_data[:public_key], root_uid, user_gid)
|
116
|
+
else
|
117
|
+
write_local_file("#{home_dir}/.ssh/authorized_keys", '640', 'Revoked', root_uid, user_gid)
|
118
|
+
end
|
119
|
+
if(static_update[user_name] && user_data[:files])
|
120
|
+
s3 = AWS::S3.new()
|
121
|
+
bucket = s3.buckets[users[:metadata][:user_file_bucket]]
|
122
|
+
user_data[:files].each_pair do |file_name, file_data|
|
123
|
+
s3object = bucket.objects[file_data['s3_path']]
|
124
|
+
write_local_file("#{home_dir}/#{file_name}", file_data['mode'], s3object.read, user_uid, user_gid)
|
125
|
+
end
|
126
|
+
else
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
users[:local_remove_users].each do |user_name|
|
131
|
+
begin
|
132
|
+
if(user_name != 'root')
|
133
|
+
etc_user = Etc.getpwnam(user_name)
|
134
|
+
if(File.exists?("#{etc_user['dir']}/.ssh/authorized_keys"))
|
135
|
+
File.delete("#{etc_user['dir']}/.ssh/authorized_keys")
|
136
|
+
end
|
137
|
+
system "pkill -u #{user_name}"
|
138
|
+
system "userdel -f #{user_name}"
|
139
|
+
end
|
140
|
+
rescue ArgumentError
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def write_local_file(path, mode, contents, user_id, group_id)
|
146
|
+
begin
|
147
|
+
mode = mode.to_i(8)
|
148
|
+
if(!Dir.exist?(File.dirname(path)))
|
149
|
+
system "mkdir -p #{File.dirname(path)}"
|
150
|
+
end
|
151
|
+
fout = File.open(path, 'w')
|
152
|
+
fout.chmod(mode)
|
153
|
+
fout.chown(user_id, group_id)
|
154
|
+
fout.write(contents)
|
155
|
+
fout.close
|
156
|
+
rescue => e
|
157
|
+
puts e
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def load_users_from_dynamo(users)
|
162
|
+
@table.items.where(:type => 'USER').where(:state => 'ACTIVE').each do |row|
|
163
|
+
user = ServerUser.new(row.attributes[:user_name], @dynamo_table, row, @metadata, @server_users)
|
164
|
+
@tags.each_pair do |instance_tag, instance_value|
|
165
|
+
if(user.access_tags.has_key?(instance_tag) && user.access_tags[instance_tag].has_key?(instance_value))
|
166
|
+
add_user(user, instance_tag, instance_value, users)
|
167
|
+
else
|
168
|
+
remove_user(user.user_name, users)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
@table.items.where(:type => 'USER').where(:state => 'INACTIVE').each do |row|
|
173
|
+
remove_user(row.attributes[:user_name])
|
174
|
+
end
|
175
|
+
save_to_file(users)
|
176
|
+
return users
|
177
|
+
end
|
178
|
+
|
179
|
+
def load_metadata_from_dynamo()
|
180
|
+
metadata = @metadata
|
181
|
+
users = {}
|
182
|
+
users[:metadata] = {}
|
183
|
+
users[:local_users] = {}
|
184
|
+
users[:local_remove_users] = []
|
185
|
+
users[:metadata][:identity] = metadata.attributes['identity'].to_i
|
186
|
+
users[:metadata][:max_key_age] = metadata.attributes['max_key_age'].to_i
|
187
|
+
users[:metadata][:sudo_group] = metadata.attributes['sudo_group']
|
188
|
+
users[:metadata][:user_file_bucket] = metadata.attributes['user_file_bucket']
|
189
|
+
return users
|
190
|
+
end
|
191
|
+
|
192
|
+
def save_to_file(users)
|
193
|
+
folder_name = File.dirname(@local_user_file)
|
194
|
+
if(!Dir.exist?(folder_name))
|
195
|
+
base_path = '/'
|
196
|
+
folder_name.split('/').each do |dir|
|
197
|
+
if(dir != '')
|
198
|
+
base_path += "#{dir}/"
|
199
|
+
if(!Dir.exist?(base_path))
|
200
|
+
begin
|
201
|
+
Dir.mkdir(base_path, 0700)
|
202
|
+
rescue SystemCallError
|
203
|
+
raise "Can not create folder #{base_path}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
fout = File.open(@local_user_file, 'w')
|
210
|
+
fout.write(users.to_yaml)
|
211
|
+
fout.chmod(0600)
|
212
|
+
fout.close()
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
def load_from_file()
|
217
|
+
if(File.readable?(@local_user_file))
|
218
|
+
users = YAML::load_stream(File.open(@local_user_file))[0]
|
219
|
+
else
|
220
|
+
users = {}
|
221
|
+
users[:local_users] = {}
|
222
|
+
users[:local_remove_users] = []
|
223
|
+
end
|
224
|
+
return users
|
225
|
+
end
|
226
|
+
|
227
|
+
def remove_user(user_name, users)
|
228
|
+
users[:local_remove_users] << user_name
|
229
|
+
end
|
230
|
+
|
231
|
+
def add_user(user_object, tag_name, tag_value, users)
|
232
|
+
user = {}
|
233
|
+
user[:user_name] = user_object.user_name
|
234
|
+
user[:full_name] = user_object.full_name
|
235
|
+
user[:shell] = user_object.shell
|
236
|
+
user[:public_key] = user_object.public_key
|
237
|
+
user[:public_key_expire] = user_object.public_key_expire
|
238
|
+
user[:user_id] = user_object.user_id.to_i
|
239
|
+
user[:identity] = user_object.identity.to_i
|
240
|
+
user[:sudo] = user_object.access_tags[tag_name][tag_value]['sudo']
|
241
|
+
user[:files] = user_object.files
|
242
|
+
users[:local_users][user_object.user_name] = user
|
243
|
+
end
|
244
|
+
|
245
|
+
def load_instance_tags()
|
246
|
+
instance_id = @instance_data['instance-id']
|
247
|
+
ec2 = AWS::EC2.new()
|
248
|
+
intsance_tags = ec2.instances[instance_id].tags.to_h
|
249
|
+
tag_names = intsance_tags.keys & @server_users.tags.to_a
|
250
|
+
tag_names.each do |tag|
|
251
|
+
@tags[tag] = intsance_tags[tag]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
end
|