Skip to content

Commit c5a83bb

Browse files
authored
Merge pull request #4 from pmbenjamin/feat/automate-cfn-templates
Refactor & Add new feature
2 parents 5670232 + 63f8d66 commit c5a83bb

14 files changed

+394
-171
lines changed
File renamed without changes.

README.md

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
Restacker
2-
=======
3-
Restacker is the DevSecOps deployment swiss army knife. You can use it do deploy,
4-
migrate and/or remove stacks. Although not feature complete yet, you can begin
5-
using it to deploy or restack your existing apps.
6-
7-
Install it:
8-
--------------
9-
````
1+
# Restacker
2+
3+
Restacker is the DevSecOps deployment swiss army knife. You can use it safely & securely deploy, update, migrate, and/or remove AWS stacks.
4+
Although not feature complete yet, you can begin using it to deploy or re-stack your existing AWS accounts/instances.
5+
6+
## Install it:
7+
- Grab the binary from [GitHub Releases](https://github.com/devsecops/restacker/releases)
8+
- Or build from source:
9+
```
1010
git clone https://github.com/devsecops/restacker.git
1111
cd restacker/source
1212
gem build restacker.gemspec
13-
gem install restacker-0.0.11.gem
14-
rbenv init -
15-
````
13+
gem install restacker
14+
# if you're using rbenv, then: rbenv rehash
15+
```
1616

17-
Use it:
18-
--------------
19-
````
17+
## Use it:
18+
19+
```
2020
$ restacker
2121
Please specify an ACTION
2222
@@ -60,29 +60,60 @@ Notes:
6060
- If no template file path is provided when restacking restacker will use the same
6161
template as if currently deployed.
6262
- Deployed stack name will be in the form of NAME-DATE using today's date
63-
````
63+
```
6464

65-
Configuration
66-
--------------
67-
Restacker is out of the box configured to use KCP as the master account and KSP
68-
as the target account (deployment account). To configure another target account
69-
just add a section to ~/.restacker/restacker.yml listing the master & target
70-
account properties. The below configuration is an example of KSP and KVP as
71-
target accounts and KCP as master.
65+
## Configure it
66+
- `restacker configure -l <location>`
67+
- Or copy the `restacker-sample.yml` to `~/.restacker/restacker.yml` & update the configurations
68+
The below configuration is an example of MyApp1 and MyApp2 as target accounts and CTRL as master.
7269

73-
````
70+
```
7471
$ cat ~/.restacker/restacker.yml
75-
---
76-
:myapp:
72+
73+
:default:
74+
:label: myapp1
75+
76+
:ctrl: &ctrl_default
77+
:label: ctrlAcct
78+
:account_number: '123456789012'
79+
:role_name: ctrl-ctrl-DeployAdmin
80+
:role_prefix: "/dso/ctrl/ctrl/"
81+
:bucket:
82+
:name: kaos-installers
83+
:prefix: cloudformation
84+
:ami_key: latest_amis
85+
86+
:ctrlAcct:
87+
:region: us-west-2
88+
:ctrl:
89+
<<: *ctrl_default
90+
:target:
91+
<<: *ctrl_default
92+
93+
:myapp1:
7794
:region: us-west-2
78-
:master:
79-
:label: control
80-
:account_number: '123456789012'
81-
:role_name: CTL-my-app-DeploymentAdmin
82-
:role_prefix: "/dso/ctrl/my-app/"
95+
:ctrl:
96+
<<: *ctrl_default
97+
:role_name: ctrl-myapp1-DeployAdmin
8398
:target:
84-
:label: target
99+
:label: myapp1
85100
:account_number: '098765432123'
86-
:role_name: TGT-dso-DeploymentAdmin
87-
:role_prefix: "/human/dso/"
88-
````
101+
:role_name: myapp1-dso-DeployAdmin
102+
:role_prefix: "/dso/human/"
103+
104+
:myapp2:
105+
:region: us-west-2
106+
:ctrl:
107+
<<: *ctrl_default
108+
:role_name: ctrl-myapp2-DeployAdmin
109+
:target:
110+
:label: myapp2
111+
:account_number: '123098456765'
112+
:role_name: myapp2-dso-DeployAdmin
113+
:role_prefix: "/dso/human/"
114+
115+
...
116+
```
117+
118+
## More Info
119+
Checkout the [docs](./docs/) for detailed information.

docs/01-RESTACKER_INTRO.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# INTRODUCTION
2+
3+
The Control-Plane pattern/architecture is designed to mitigate and limit risk/exposure if an account were to be compromised.
4+
In DevSecOps, we call this **Blast Radius**.
5+
6+
## CONTROL PLANE
7+
In a typical Control-Plane architecture, an account is designated as the Control Plane.
8+
It does not have any instances (e.g. EC2, RDS ...etc).
9+
The main purpose of this account is to maintain users and roles.
10+
11+
## TARGET PLANE
12+
The target plane, or account, will host the instances, databases, and any other AWS services needed.
13+
The roles in this account trusts roles from the Control Plane/Account
14+
15+
## WORKFLOW
16+
In a Control-Plane architecture, the workflow for performing operations on the Target Account will look like this:
17+
- Authenticate against the Control Account to obtain an AWS STS token.
18+
- Pass that STS token to the next Target Account to assume a specific role (e.g. Read-Only, Deploy-Admin, Incident-Response ...etc).

docs/02-RESTACKER_YML.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# RESTACKER.YML
2+
This is the configuration file for Restacker CLI.
3+
See the sample [here](../source/restacker-sample.yml).
4+
5+
## STRUCTURE
6+
In order for Restacker to work as expected, the following key:value pairs are required:
7+
- `:default:`: specifies the default location/plane for all Restacker operations. This is intended to save you from having to specify the required `-l <location>` everytime.
8+
- `:label:`: the name of the default location.
9+
- `:ctrl: &ctrl_default`: default configuration for the Control Account
10+
- `:label:`: name of the account
11+
- `:role-name:`
12+
- `:role-prefix:`
13+
- `:bucket:`: S3 Bucket configuration to read/consume files from.
14+
- `:name:`: Bucket name
15+
- `:prefix:`: **optional** bucket prefix/path
16+
- `:ami_key:`: **optional** name of object on S3 that contains list of approved AMIs
17+
- `:Account_Name:`: name of target account
18+
- `:region:`: default region to deploy instances in (e.g. `us-west-2`)
19+
- `:ctrl:`: control account for this account
20+
- `<<: *ctrl_default`: if the control account is the default account specified in `&ctrl_default`, then just insert default configurations here
21+
- `:target:`: the target account configuration
22+
- `:label:`: name of target account
23+
- `:account_number:`: target account number
24+
- `:role_name:`: target role name
25+
- `:role_prefix:`: target role prefix
26+
27+
## Example Restacker Configuration:
28+
```
29+
:default:
30+
:label: myapp1
31+
32+
:ctrl: &ctrl_default
33+
:label: ctrlAcct
34+
:account_number: '123456789012'
35+
:role_name: ctrl-ctrl-DeployAdmin
36+
:role_prefix: "/dso/ctrl/ctrl/"
37+
:bucket:
38+
:name: kaos-installers
39+
:prefix: cloudformation
40+
:ami_key: latest_amis
41+
42+
:ctrlAcct:
43+
:region: us-west-2
44+
:ctrl:
45+
<<: *ctrl_default
46+
:target:
47+
<<: *ctrl_default
48+
49+
:myapp1:
50+
:region: us-west-2
51+
:ctrl:
52+
<<: *ctrl_default
53+
:role_name: ctrl-myapp1-DeployAdmin
54+
:target:
55+
:label: myapp1
56+
:account_number: '098765432123'
57+
:role_name: myapp1-dso-DeployAdmin
58+
:role_prefix: "/dso/human/"
59+
60+
:myapp2:
61+
:region: us-west-2
62+
:ctrl:
63+
<<: *ctrl_default
64+
:role_name: ctrl-myapp2-DeployAdmin
65+
:target:
66+
:label: myapp2
67+
:account_number: '123098456765'
68+
:role_name: myapp2-dso-DeployAdmin
69+
:role_prefix: "/dso/human/"
70+
71+
```

docs/03-PARAMETERS_YML.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# PARAMETERS.YML
2+
Configurations to be passed to the CloudFormation template.
3+
4+
<!-- Explain each Parameter here -->
5+
6+
**NOTE:** Any Environment variable that you'd like to persist past boot, place them here, **not** in `userdata.sh`

docs/04-USERDATA_SH.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# USERDATA.SH
2+
Configuration needed only during instance-creation stage

source/Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
source 'https://rubygems.org'
22

33
gem 'aws-sdk', '~> 2'
4+
gem 'rainbow'

source/bin/restacker

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ACTIONS = {
1414
remove: 'Removes the given deployment',
1515
configure: 'Configure the target account in the restacker.yml file',
1616
dump: 'Dumps the default configuration for a given template or module',
17+
amis: 'Shows latest AMIs in S3 bucket',
1718
console: 'Opens the AWS Console'
1819
}
1920
ACTIONS_HELP = "ACTIONS\n\n" + ACTIONS.to_yaml.sub("---\n", '').gsub(': ', "\t").gsub(':', " ")
@@ -47,7 +48,7 @@ class Parser
4748
exit
4849
end
4950

50-
o.on('-l LOCATION', '--location=LOCATION', 'Where to deploy, ksp, kvp, kcp...') do |location|
51+
o.on('-l LOCATION', '--location=LOCATION', 'Target Location or Plane to deploy to') do |location|
5152
opts[:location] = location
5253
end
5354

@@ -63,7 +64,7 @@ class Parser
6364
opts[:options] = options
6465
end
6566

66-
o.on('-p PARAMS', '--params=PARAMS', 'Parameters to override current stack parameters in the form of', 'k1=v1,k2=v2. E.g., -p AmiId=ami-a4jd7928') do |params|
67+
o.on('-p PARAMS', '--params=PARAMS', 'Parameters to override current stack parameters in the form of', 'k1=v1,k2=v2. E.g., -p AmiId=ami-a1b2c3d4') do |params|
6768
opts[:params] = params
6869
end
6970

@@ -116,36 +117,32 @@ begin
116117
action = unparsed.pop
117118
puts(VERSION) || exit(0) if options[:version]
118119
usage("Please specify an ACTION") && exit(0) if action.nil?
120+
plane = RestackerConfig.get_plane(options)
119121

120122
if action == 'configure'
121-
plane = options[:location] ? options[:location] : DEFAULT_PLANE
122-
if !plane
123-
puts "Location not specified, plane is set to default: [#{DEFAULT_PLANE}]"
124-
end
125-
puts "Configuring target plane [#{plane}]: "
126-
RestackerConfig.load_config(plane.to_sym)
127-
Restacker.configure(plane.to_sym)
123+
printf "%-30s : %s\n", Rainbow("CONFIGURING PLANE").white.bright.underline, plane
124+
RestackerConfig.configure(plane)
128125
exit(0)
129126
end
130127

131128
if ACTIONS.keys.push(:aws).include?(action.to_sym)
132-
restacker = Restacker.new(options.to_h) unless ['dump'].include?(action)
129+
restacker = Restacker.new(options.to_h) unless ['dump', 'amis'].include?(action)
133130
case action
134131
when 'list'
135-
puts "Listing stacks"
132+
printf "%-30s : %s\n", Rainbow("LISTING STACKS").white.bright.underline, plane
136133
restacker.list_stacks
137134
when 'desc', 'describe'
138135
if options[:name]
139-
puts "Describing #{options[:name]}"
136+
puts Rainbow("DESCRIBING STACK").white.bright.underline
140137
restacker.describe_stack(options[:name])
141138
else
142139
usage "Please specify a stack name (-n) to describe"
143140
end
144141
when 'restack'
145142
if options[:name]
146-
puts "Restacking #{options[:name]}"
143+
printf "%-30s : %s\n", Rainbow("RESTACKING").white.bright.underline, options[:name]
147144
restacker.restack_by_name(options[:name])
148-
puts "Now run migrate followed by remove"
145+
puts Rainbow("Now run migrate followed by remove").white.bright
149146
else
150147
usage "Please specify a stack name (-n) to restack"
151148
end
@@ -165,28 +162,20 @@ begin
165162
end
166163
when 'remove'
167164
if options[:name]
168-
puts "Removing stack"
165+
printf "%-30s : %s\n", Rainbow("REMOVING STACK").white.bright.underline, options[:name]
169166
restacker.delete_stack(options[:name])
170167
else
171168
usage "Please specify a stack name (-n) to remove"
172169
end
173-
# when 'configure'
174-
# plane = ""
175-
# if !options[:location]
176-
# puts "Location not specified, default plane #{DEFAULT_PLANE} used."
177-
# plane = DEFAULT_PLANE
178-
# else
179-
# plane = options[:location]
180-
# end
181-
# puts "Configuring target account for plane [#{plane}]:\n"
182-
# RestackerConfig.rc_configure(options[:location])
183170
when 'dump'
184171
if options[:template]
185-
# puts "Dumping stack parameters"
186172
Restacker.dump_stack_params(options)
187173
else
188174
usage "Please specify a stack template (-t) or module (-m)"
189175
end
176+
when 'amis'
177+
puts Rainbow("SHOWING AMIS").white.bright.underline
178+
Restacker.amis
190179
when 'console'
191180
AwsCli.new(options).console(options)
192181
when 'aws'

source/lib/auth.rb

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,26 @@
55
class Auth
66

77
def self.get_mfa_code
8-
print "Enter MFA: "
8+
print Rainbow("Enter MFA: ").yellow
99
STDOUT.flush
1010
STDIN.gets(7).chomp
1111
end
1212

1313
def self.get_creds(username, defaults)
1414
region = defaults.fetch(:region)
15-
master = defaults.fetch(:master)
16-
master_account_number = master.fetch(:account_number)
17-
master_role_prefix = master.fetch(:role_prefix)
18-
master_role_name = master.fetch(:role_name)
15+
ctrl = defaults.fetch(:ctrl)
16+
ctrl_account_number = ctrl.fetch(:account_number)
17+
ctrl_role_prefix = ctrl.fetch(:role_prefix)
18+
ctrl_role_name = ctrl.fetch(:role_name)
1919

2020
target = defaults.fetch(:target)
2121
target_account_number = target.fetch(:account_number)
2222
target_role_prefix = target.fetch(:role_prefix)
2323
target_role_name = target.fetch(:role_name)
24-
25-
serial_number = "arn:aws:iam::#{master_account_number}:mfa/#{username}"
26-
puts "Logging into #{target.fetch(:label).upcase} using MFA: #{serial_number} (#{region})"
27-
role_arn = "arn:aws:iam::#{master_account_number}:role#{master_role_prefix}#{master_role_name}"
24+
target_label = target.fetch(:label)
25+
serial_number = "arn:aws:iam::#{ctrl_account_number}:mfa/#{username}"
26+
puts "Logging into #{Rainbow(target_label.upcase).yellow} using MFA: #{serial_number} (#{region})"
27+
role_arn = "arn:aws:iam::#{ctrl_account_number}:role#{ctrl_role_prefix}#{ctrl_role_name}"
2828
session_name = username[0..31]
2929

3030
sts_client = Aws::STS::Client.new(region: region)
@@ -53,7 +53,12 @@ def self.login(options, defaults, plane)
5353
Aws.config[:credentials] = Aws::SharedCredentials.new(profile_name: profile_name)
5454
creds = get_creds(options.fetch(:username), defaults)
5555
rescue KeyError => e
56-
raise "Error parsing #{CONFIG_FILE}, (#{e.message}), please ensure it is properly formatted"
56+
error = Rainbow("Error parsing #{CONFIG_FILE}, (#{e.message}), please ensure it is properly formatted").red
57+
raise error
58+
rescue => err
59+
error = Rainbow(err.message).red
60+
raise error
61+
exit
5762
end
5863
# now save to yaml
5964
File.open(auth_file, 'w') do |f|

0 commit comments

Comments
 (0)