bootscript 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YTE3ZDJjZGM2OThiYzk4NzE2MjVkZTBhZTNhMTFmODc4ZDJlYTJhNQ==
5
+ data.tar.gz: !binary |-
6
+ N2VlMThiNzdhNDYwYTQwN2E3MzAwZjM0OWJkODVhYmRiMDg4MzU5Zg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZDFkYmVmN2M4ZGI3ODljMzUxODAwYjg4OWFmNTliOTU3NTcyN2M1YWZmY2Fj
10
+ OTRhNWUzNzM2OGUzODBmZjFhYWUzMzNhNjU0NWMyNDM2MzU5ZDE5OWU3YWJl
11
+ YjJkZmQ2ZDhkMzhjNjM2ZmIwOTNiNmViMmRmYTIwMmQ0ZDEwOWU=
12
+ data.tar.gz: !binary |-
13
+ NjgwZjE5MjA4ZDg3YWIyZGI4MzI2MTA4MDg4MzQ2NzM3MWViNDY2YmExZmY0
14
+ ZWM1Njg1ODg0YzAxNTE0M2ZjZDcyNzk4NmYxNjg5YTE3YjJlZGY1MmQxZmUw
15
+ OThlMDJhYmY1YjJkZmZmNjM4Y2MyM2Q5ODg4NGVlNDRhM2I5YTY=
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format documentation -r ./spec/spec_helper.rb
@@ -0,0 +1,30 @@
1
+ List of Template Variables
2
+ ================
3
+ Here is a list of all the keys that a call to `Bootscript.generate` will check for, and how they affect the gem's behavior.
4
+
5
+
6
+ Main settings
7
+ ----------------
8
+ * `:platform` - when set to `:unix` (default), the gem produces a Bash script as output. When set to `:windows`, the gem produces Powershell wrapped in a Batch script.
9
+ * `:startup_command` - The command to be executed after the archive is extracted. If Chef support is enabled (by setting `chef_validation_pem`), this value defaults to a platform-specific command that invokes the built-in Chef support. Otherwise, it defaults to `nil`, and no startup command is executed. In either case, it can be overridden.
10
+ * `:update_os` - If `true`, will attempt to upgrade all the operating system packages. Defaults to `false`. _(Debian/Ubuntu only for now.)_
11
+ * `:add_script_tags` - _(Windows only.)_ When set to `true`, encloses the output in `<SCRIPT>...</SCRIPT>` XML tags, usually required for AWS.
12
+
13
+
14
+ Chef settings
15
+ ----------------
16
+ The Chef-based examples in the README illustrate the included Chef support.
17
+ * `:chef_validation_pem` - When set to any non-nil value, enables the built-in Chef support. The value must be the key data itself, so read it into memory first.
18
+ * `:chef_databag_secret` - The secret used to decrypt the Chef org's encrypted data bag items.
19
+ * `:chef_attributes` - a Hash of Chef attributes that is read as `node[chef_client][config]` by the [Opscode chef-client cookbook][1]. This is where you specify your `node_name` (if desired), `chef_server_url`, `validation_client_name`, etc. *Always use strings for chef attribute keys, not symbols!*
20
+
21
+
22
+ RAMdisk settings
23
+ ----------------
24
+ * `:create_ramdisk` - Setting this to `true` generates a bootscript that creates a RAMdisk of a configurable size and at a configurable filesystem location. This happens even before the archive is unpacked, so you can extract files into the RAMdisk.
25
+ * `:ramdisk_mount` - The filesystem location where the RAMdisk is mounted. (defaults to `false`)
26
+ * `:ramdisk_size` - Size, in Megabytes, of the RAMdisk. (defaults to 20)
27
+
28
+
29
+ --------
30
+ [1]:https://github.com/opscode-cookbooks/chef-client
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Benton Roberts
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,176 @@
1
+ Bootscript
2
+ ================
3
+ Constructs a self-extracting archive, wrapped in a Bash script (or Windows batch script), for securely initializing cloud systems.
4
+
5
+ *BETA VERSION - needs more functional testing and broader OS support*
6
+
7
+ ----------------
8
+ What is it?
9
+ ----------------
10
+ The bootscript gem enables simple creation of a self-extracting "TAR archive within a Bash script", which can be composed from any set of binary or text files -- including support for ERB templates. Any Hash of Ruby values can be interpolated into these templates when they are rendered, and the resulting "boot script" (complete with the base64-encoded archive at the end) can be invoked on nearly any Unix or Windows system to:
11
+
12
+ * create a RAMdisk for holding the archive contents, which are presumably secrets (this step is optional, and does not yet work on Windows)
13
+ * extract the archived files
14
+ * delete itself
15
+ * execute a user-specified command for further configuration
16
+
17
+ An extra, optional submodule is also supplied that leverages the above process to install Chef (omnibus), assign arbitrary node attributes (using the same Hash mentioned above), and kick off convergence for a given run list.
18
+
19
+
20
+ ----------------
21
+ Why is it?
22
+ ----------------
23
+ * makes specification of complex, cross-platform boot data simple and portable.
24
+ * simplifies initial Chef setup
25
+
26
+ ----------------
27
+ Where is it? (Installation)
28
+ ----------------
29
+ Install the gem and its dependencies from RubyGems:
30
+
31
+ gem install bootscript
32
+
33
+
34
+ ----------------
35
+ How is it [done]? (Usage)
36
+ ----------------
37
+ Call the gem's main public method: `Bootscript.generate()`. It accepts a Hash of template variables as its first argument, which is passed directly to any ERB template files as they render. All the data in the Hash is available to the templates, but some of the key-value pairs also control the gem's rendering behavior, as demonstrated in the following examples. (There's also a [list of such variables](ERB_VARS.md).)
38
+
39
+
40
+ ### Simplest - make a RAMdisk
41
+
42
+ require 'bootscript'
43
+ script = Bootscript.generate(
44
+ create_ramdisk: true, # default mount is /etc/secrets
45
+ startup_command: 'df -h /etc/secrets' # show the RAMdisk's free space
46
+ )
47
+ puts "Now run this as root on any unix node that has `bash` installed:"
48
+ puts script
49
+
50
+
51
+ ### Simple - render and install a bash script, then run it
52
+
53
+ To include some files inside the script's archive, create a "data map", which is a Hash that maps locations on the boot target's filesystem to the values that will be written there when the script is run. The following example generates a script that, when executed, writes some text into the file `/root/hello.sh`.
54
+
55
+ # Define a simple shell script, to be written to the node's filesystem:
56
+ data_map = {'/root/hello.sh' => 'echo Hello, <%= my_name %>.'}
57
+ puts Bootscript.generate({
58
+ my_name: ENV['USER'], # evaluated now, at generation
59
+ startup_command: 'sh /root/hello.sh', # run on the node, after unarchiving
60
+ }, data_map
61
+ )
62
+
63
+ _(Can you guess what it will print on the node that runs the script?)_
64
+
65
+
66
+ ### Chef support, using the included templates (single node)
67
+
68
+ The software's Chef support includes some predefined template files that will install the Chef client sofware, and then kick off the convergence process. These templates are automatically included into the boot script when you pass a `:chef_validation_pem` to the `generate()` method, so no data map is required for this example.
69
+
70
+ The two Chef secrets are passed directly to `generate`, so they should be read from the filesystem if necessary...
71
+
72
+ VALIDATION_CERT = File.read "#{ENV['HOME']}/.chef/myorg-validator.pem"
73
+ DATABAG_SECRET = File.read "#{ENV['HOME']}/.chef/myorg-databag-secret.txt"
74
+
75
+ require 'uuid' # Make some unique boot data
76
+ NODE_UUID = UUID.generate # for just this one node...
77
+ puts Bootscript.generate(
78
+ logger: Logger.new(STDOUT), # Monitor progress
79
+ create_ramdisk: true, # make a RAMdisk
80
+ chef_validation_pem: VALIDATION_CERT, # the data, not the path!
81
+ chef_databag_secret: DATABAG_SECRET, # same here - the secret data
82
+ chef_attributes: { # ALWAYS USE STRINGS FOR CHEF ATTRIBUTE KEYS!
83
+ 'run_list' => 'role[my_app_server]',
84
+ 'chef_client' => {
85
+ 'config' => {
86
+ 'node_name' => "myproject-myenv-#{NODE_UUID}",
87
+ 'chef_server_url' => "https://api.opscode.com/organizations/myorg",
88
+ 'validation_client_name' => "myorg-validator",
89
+ }
90
+ }
91
+ }
92
+ )
93
+
94
+
95
+ The validation certificate and data bag secrets will be saved in a `chef` directory below the RAMdisk mount point, then symlinked into `/etc/chef`. You should use this technique for all the files you put into the `data_map` that contain secrets!
96
+
97
+ ### Chef support, with the node name determined by the node
98
+
99
+ This is just like the previous example, only first you create a ruby file that will run on the node at boot time, to compute the node name. This can also be a template, like `/tmp/set_node_name.rb.erb`:
100
+
101
+ unless node_name
102
+ # filled in at publish() time by the bootstrap gem
103
+ name = '<%= project %>.<%= stage %>.<%= tier %>'
104
+ require 'ohai'
105
+ ohai = Ohai::System.new
106
+ ohai.all_plugins
107
+ if ohai[:ec2] && ohai[:ec2][:instance_id]
108
+ name = "#{name}.#{ohai[:ec2][:instance_id]}"
109
+ else
110
+ name = "#{name}.#{rand(2**(0.size * 8 -2) -1)}"
111
+ end
112
+ puts "Setting node name to #{name}..."
113
+ node_name name
114
+ end
115
+
116
+ Now tell the boot script to put the ruby file where chef-client will pick it up (thanks, Opscode!). Note that when including an ERB file into the boot archive, the value in the data map should be an existing Ruby File object, not a String:
117
+
118
+ data_map = {
119
+ '/etc/chef/client.d/set_node_name.rb' => File.new("/tmp/set_node_name.rb.erb")
120
+ }
121
+
122
+ Finally, generate *without* an explicit node name, but filling in the other values that are known at the time. Don't forget to pass the data map as the second argument to `generate()`.
123
+
124
+ PROJECT, STAGE, TIER = 'myapp', 'testing', 'db'
125
+ script = Bootscript.generate({
126
+ project: PROJECT, # As before, these values are rendered
127
+ stage: STAGE, # into the above ruby template.
128
+ tier: TIER,
129
+ chef_attributes: {
130
+ 'run_list' => 'role[my_app_server]',
131
+ 'chef_client' => { # NOTE - no node_name passed here,
132
+ 'config' => { # but the rest is the same...
133
+ 'chef_server_url' => "https://api.opscode.com/organizations/myorg",
134
+ 'validation_client_name' => "myorg-validator",
135
+ }
136
+ }
137
+ },
138
+ chef_validation_pem: VALIDATION_CERT,
139
+ chef_databag_secret: DATABAG_SECRET,
140
+ }, data_map)
141
+
142
+
143
+ ----------------
144
+ *Known Limitations / Bugs*
145
+ ----------------
146
+ * bash and tar are required on Unix boot targets
147
+ * Powershell is required on Windows boot targets
148
+ * bash, tar and uudecode are required to run the tests
149
+
150
+
151
+ ----------------
152
+ Who is it? (Contribution)
153
+ ----------------
154
+ This Gem was created by Benton Roberts _(benton@bentonroberts.com)_
155
+
156
+ The project is still in its early stages. Helping hands are appreciated.
157
+
158
+ 1) Install project dependencies.
159
+
160
+ gem install rake bundler
161
+
162
+ 2) Fetch the project code and bundle up...
163
+
164
+ git clone https://github.com/benton/bootscript.git
165
+ cd bootscript
166
+ bundle
167
+
168
+ 3) Run the tests:
169
+
170
+ bundle exec rake
171
+
172
+ 4) Autotest while you work:
173
+
174
+ bundle exec autotest
175
+
176
+
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Default: Run specs'
5
+ task :default => :spec
6
+
7
+ desc "Run specs"
8
+ RSpec::Core::RakeTask.new # options read from .rspec
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bootscript/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bootscript"
8
+ spec.version = Bootscript::VERSION
9
+ spec.authors = ["Benton Roberts"]
10
+ spec.email = ["benton@bentonroberts.com"]
11
+ spec.description = %q{Constructs a self-extracting archive, wrapped in a script, for securely initializing cloud systems}
12
+ spec.summary = %q{Constructs a self-extracting archive, wrapped in a script, for securely initializing cloud systems}
13
+ spec.homepage = "http://github.com/benton/bootscript"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "erubis"
22
+ spec.add_dependency "minitar"
23
+ spec.add_dependency "rubyzip"
24
+ spec.add_dependency "json"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "rspec"
29
+ spec.add_development_dependency "ZenTest"
30
+ spec.add_development_dependency "yard"
31
+ end
@@ -0,0 +1,80 @@
1
+ require 'logger'
2
+ require 'bootscript/version'
3
+ require 'bootscript/script'
4
+ require 'bootscript/uu_writer'
5
+ require 'bootscript/chef'
6
+
7
+ # provides the software's only public method, generate()
8
+ module Bootscript
9
+
10
+ # These values are interpolated into all templates, and can be overridden
11
+ # in calls to {Bootscript#generate}
12
+ DEFAULT_VARS = {
13
+ platform: :unix, # or :windows
14
+ create_ramdisk: false,
15
+ startup_command: '', # customized by platform if chef used
16
+ ramdisk_mount: '', # customized by platform, see platform_defaults
17
+ ramdisk_size: 20, # Megabytes
18
+ add_script_tags: false,
19
+ script_name: 'bootscript', # base name of the boot script
20
+ strip_comments: true,
21
+ imdisk_url: 'http://www.ltr-data.se/files/imdiskinst.exe',
22
+ update_os: false
23
+ }
24
+
25
+ # Generates the full text of a boot script based on the supplied
26
+ # template_vars and data_map. If no optional destination is supplied,
27
+ # the full text is returned as a String. Otherwise, the text is
28
+ # written to the destination using write(), and the number of bytes
29
+ # written is returned.
30
+ def self.generate(template_vars = {}, data_map = {}, destination = nil)
31
+ script = Bootscript::Script.new(template_vars[:logger])
32
+ script.data_map = data_map
33
+ script.generate(template_vars, destination)
34
+ end
35
+
36
+ # Returns true if the passed Hash of erb_vars indicate a
37
+ # Windows boot target
38
+ def self.windows?(erb_vars)
39
+ (erb_vars[:platform] || '').to_s.downcase == 'windows'
40
+ end
41
+
42
+ # Returns a slightly-modified version of the default Ruby Logger
43
+ # @param output [STDOUT, File, etc.] where to write the logs
44
+ # @param level [DEBUG|INFO|etc.] desired minimum severity
45
+ # @return [Logger] a standard Ruby Logger with a nicer output format
46
+ def self.default_logger(output = nil, level = Logger::FATAL)
47
+ logger = ::Logger.new(output || STDOUT)
48
+ logger.sev_threshold = level
49
+ logger.formatter = proc {|lvl, time, prog, msg|
50
+ "#{lvl} #{time.strftime '%Y-%m-%d %H:%M:%S %Z'}: #{msg}\n"
51
+ }
52
+ logger
53
+ end
54
+
55
+ # Returns the passed Hash of template vars, merged over a set of
56
+ # computed, platform-specific default variables
57
+ def self.merge_platform_defaults(vars)
58
+ defaults = DEFAULT_VARS.merge(vars)
59
+ if defaults[:platform].to_s == 'windows'
60
+ defaults[:ramdisk_mount] = 'R:'
61
+ defaults[:script_name] = 'bootscript.ps1'
62
+ if Chef::included?(defaults)
63
+ defaults[:startup_command] = 'PowerShell -Command "& '+
64
+ '{C:/chef/chef-install.ps1}" > c:/chef/bootscript_setup.log 2>&1'
65
+ end
66
+ else
67
+ defaults[:ramdisk_mount] = '/etc/secrets'
68
+ defaults[:script_name] = 'bootscript.sh'
69
+ if Chef::included?(defaults)
70
+ defaults[:startup_command] = 'chef-install.sh'
71
+ end
72
+ end
73
+ defaults.merge(vars) # return user vars merged over platform defaults
74
+ end
75
+
76
+ BUILTIN_TEMPLATE_DIR = File.dirname(__FILE__)+"/templates"
77
+ UNIX_TEMPLATE = "#{BUILTIN_TEMPLATE_DIR}/bootscript.sh.erb"
78
+ WINDOWS_TEMPLATE = "#{BUILTIN_TEMPLATE_DIR}/bootscript.ps1.erb"
79
+
80
+ end
@@ -0,0 +1,70 @@
1
+ module Bootscript
2
+ # provides built-in Chef templates and attributes
3
+ module Chef
4
+
5
+ # returns a map of the built-in Chef templates, in the context of erb_vars
6
+ # The presence of :chef_validation_pem triggers the inclusion of Chef
7
+ def self.files(erb_vars)
8
+ if Bootscript.windows?(erb_vars)
9
+ files_for_windows(erb_vars)
10
+ else
11
+ files_for_unix(erb_vars)
12
+ end
13
+ end
14
+
15
+ # defines whether or not Chef support will be included in the boot script,
16
+ # based on the presence of a certain key or keys in erb_vars
17
+ # @param [Hash] erb_vars template vars to use for determining Chef inclusion
18
+ # @return [Boolean] true if erb_vars has the key :chef_validation_pem
19
+ def self.included?(erb_vars = {})
20
+ erb_vars.has_key? :chef_validation_pem
21
+ end
22
+
23
+ private
24
+
25
+ def self.files_for_unix(erb_vars)
26
+ template_dir = "#{Bootscript::BUILTIN_TEMPLATE_DIR}/chef"
27
+ { # built-in files
28
+ '/usr/local/sbin/chef-install.sh' =>
29
+ File.new("#{template_dir}/chef-install.sh.erb"),
30
+ '/etc/chef/attributes.json' =>
31
+ File.new("#{template_dir}/attributes.json.erb"),
32
+ '/etc/chef/client.d/include_json_attributes.rb' =>
33
+ File.new("#{template_dir}/json_attributes.rb.erb"),
34
+ '/etc/chef/client.rb' =>
35
+ File.new("#{template_dir}/chef_client.conf.erb"),
36
+ # files generated from required ERB vars
37
+ "#{erb_vars[:ramdisk_mount]}/chef/validation.pem" =>
38
+ erb_vars[:chef_validation_pem] || '',
39
+ "#{erb_vars[:ramdisk_mount]}/chef/encrypted_data_bag_secret" =>
40
+ erb_vars[:chef_databag_secret] || '',
41
+ }
42
+ end
43
+
44
+ def self.files_for_windows(erb_vars)
45
+ template_dir = "#{Bootscript::BUILTIN_TEMPLATE_DIR}/chef"
46
+ files = { # built-in files
47
+ 'chef/chef-install.ps1' =>
48
+ File.new("#{template_dir}/chef-install.ps1.erb"),
49
+ 'chef/client.rb' =>
50
+ File.new("#{template_dir}/chef_client.conf.erb"),
51
+ 'chef/attributes.json' =>
52
+ File.new("#{template_dir}/attributes.json.erb"),
53
+ 'chef/client.d/include_json_attributes.rb' =>
54
+ File.new("#{template_dir}/json_attributes.rb.erb"),
55
+ # files generated from required ERB vars
56
+ "chef/validation.pem" =>
57
+ erb_vars[:chef_validation_pem] || '',
58
+ "chef/encrypted_data_bag_secret" =>
59
+ erb_vars[:chef_databag_secret] || '',
60
+ }
61
+ if erb_vars[:create_ramdisk]
62
+ files.merge!(
63
+ 'chef/client.d/ramdisk_secrets.rb' =>
64
+ File.new("#{template_dir}/ramdisk_secrets.rb.erb"))
65
+ end
66
+ files
67
+ end
68
+
69
+ end
70
+ end