dlz 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 42f9794bb5edc9d8aab1314af69e25103c9a63d9f2a1158fe83484a1e2740e0c
4
+ data.tar.gz: 60374a816b862eddfdf271b97c3fcbed44c3f225833a4b6bb03664dcb5b91249
5
+ SHA512:
6
+ metadata.gz: 280f5e29740a01c2b8e0a5b0d1b78b054fc47f8b5d23d88c06e484fec59ab1861b9b07bf8a2b05e8300ce38444b30c04e8ec50db2e79099af3d0f26760de53ea
7
+ data.tar.gz: 02dde9770a7f200429bc5b0801a4a6cdec8863e87d170627505c4e473d69b795a029514da7841a1db659b217fd7c9fc0ec3e0bb8b93851ced818c8c4f099f929
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2019 Daniel Stamer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ # dlz - Dan's LZ: highly-opinionated AWS Landing Zone bootstrapping tool
2
+
3
+ `dlz` bootstraps AWS organizations and accounts.
data/bin/dlz ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'thor'
4
+ require 'dlz'
5
+
6
+ # Main Thor class for `dlz`
7
+ class DLZ < Thor
8
+ desc 'init', 'Create new `dlz` configuration skeleton'
9
+ def init
10
+ Config.init
11
+ end
12
+
13
+ desc 'config', 'Inspect `dlz` configuration'
14
+ def config
15
+ Config.print
16
+ end
17
+
18
+ desc 'version', 'Print `dlz` version info'
19
+ def version
20
+ Config.version
21
+ end
22
+
23
+ desc 'render', 'Render CloudFormation templates for `dlz` and local resources'
24
+ def render
25
+ Renderer.render_all
26
+ end
27
+
28
+ desc 'organization COMMAND', 'Configure organization and organizational units'
29
+ long_desc <<-DESC
30
+ Manage AWS Organization and organizational units.
31
+ Commands:
32
+ - 'deploy': `dlz` will try to deploy the organization and it's structure as
33
+ configured in the configuration.
34
+ - 'destroy': `dlz` will attempt to destroy the organization completely.
35
+ DESC
36
+ def organization(command)
37
+ cfg = Config.load
38
+ return Organization.deploy(config: cfg) if command.casecmp('deploy').zero?
39
+ return Organization.destroy(config: cfg) if command.casecmp('destroy').zero?
40
+
41
+ Interface.error(message: "unable to understand '#{command}'")
42
+ end
43
+
44
+ desc 'accounts COMMAND', 'Create accounts and attach to organization'
45
+ long_desc <<-DESC
46
+ Create and configure AWS accounts to the currently configured organization.
47
+ Commands:
48
+ - 'deploy': `dlz` will attempt to configure all accounts as configured and
49
+ will try to enroll them into the organization at the specific
50
+ organizational units.
51
+ - 'destroy': `dlz` will suspend accounts.
52
+ DESC
53
+ def accounts(command)
54
+ cfg = Config.load
55
+ return Accounts.deploy(config: cfg) if command.casecmp('deploy').zero?
56
+ return Accounts.destroy(config: cfg) if command.casecmp('destroy').zero?
57
+
58
+ Interface.error(message: "unable to understand '#{command}'")
59
+ end
60
+
61
+ desc 'resources COMMAND', 'Deploy resources per account'
62
+ long_desc <<-DESC
63
+ Manage AWS resources according to configuration.
64
+ Commands:
65
+ - 'deploy': `dlz` will try to deploy all defined stacks and provision
66
+ resources into AWS accounts.
67
+ - 'destroy': `dlz` will collapse all stacks and destroy all resources.
68
+ DESC
69
+ def resources(command)
70
+ cfg = Config.load
71
+ return Resources.deploy(config: cfg) if command.casecmp('deploy').zero?
72
+ return Resources.destroy(config: cfg) if command.casecmp('destroy').zero?
73
+
74
+ Interface.error(message: "unable to understand '#{command}'")
75
+ end
76
+ end
77
+
78
+ DLZ.start(ARGV)
@@ -0,0 +1,6 @@
1
+ require 'dlz/accounts'
2
+ require 'dlz/config'
3
+ require 'dlz/interface'
4
+ require 'dlz/organization'
5
+ require 'dlz/renderer'
6
+ require 'dlz/resources'
@@ -0,0 +1,12 @@
1
+ require 'dlz/interface'
2
+
3
+ # Module to create accounts and attach them to the organization
4
+ module Accounts
5
+ def self.deploy(*)
6
+ Interface.error(message: 'I am not implemented yet!') # TODO: implement me
7
+ end
8
+
9
+ def self.destroy(*)
10
+ Interface.error(message: 'I am not implemented yet!') # TODO: implement me
11
+ end
12
+ end
@@ -0,0 +1,92 @@
1
+ require 'fileutils'
2
+ require 'dlz/interface'
3
+ require 'awesome_print'
4
+ require 'yaml'
5
+
6
+ # Configuration singleton for `dlz`
7
+ class Config
8
+ class << self; attr_accessor :data end
9
+ @data = {}
10
+
11
+ def self.init
12
+ return Interface.panic(message: 'config seems to already exist!') if config?
13
+
14
+ FileUtils.mkdir_p(local_dlz_template_path)
15
+ FileUtils.cp(
16
+ "#{dlz_init_path}/dlz.yaml",
17
+ "#{local_path}/dlz.yaml"
18
+ )
19
+ Interface.info(message: 'created new default configuration.')
20
+ end
21
+
22
+ def self.load
23
+ unless File.exist?("#{local_path}/dlz.yaml")
24
+ return Interface.panic(message: 'no config file found. try `dlz init`.')
25
+ end
26
+
27
+ if @data.empty?
28
+ # Load, deserialize and symbolize keys
29
+ @data = YAML.load_file("#{local_path}/dlz.yaml")
30
+ .each_with_object({}) do |(key, value), obj|
31
+ obj[key.to_sym] = value
32
+ end
33
+ end
34
+ @data
35
+ end
36
+
37
+ def self.version
38
+ Interface.info(
39
+ message: "current version is 'dlz-#{Gem.loaded_specs['dlz'].version}'"
40
+ )
41
+ end
42
+
43
+ def self.print
44
+ ap load
45
+ end
46
+
47
+ def self.config?
48
+ return true if File.exist?("#{local_path}/dlz.yaml")
49
+
50
+ false
51
+ end
52
+
53
+ def self.dlz_path
54
+ File.expand_path(File.dirname(__dir__))
55
+ end
56
+
57
+ def self.dlz_init_path
58
+ "#{dlz_path}/#{dlz_init_path_trailing}"
59
+ end
60
+
61
+ def self.dlz_init_path_trailing
62
+ 'init'
63
+ end
64
+
65
+ def self.dlz_template_path
66
+ "#{dlz_path}/#{dlz_template_path_trailing}"
67
+ end
68
+
69
+ def self.dlz_template_path_trailing
70
+ 'templates'
71
+ end
72
+
73
+ def self.local_path
74
+ Dir.pwd
75
+ end
76
+
77
+ def self.local_template_path
78
+ "#{local_path}/#{local_template_path_trailing}"
79
+ end
80
+
81
+ def self.local_template_path_trailing
82
+ 'templates'
83
+ end
84
+
85
+ def self.local_dlz_template_path
86
+ "#{local_path}/#{local_dlz_template_path_trailing}"
87
+ end
88
+
89
+ def self.local_dlz_template_path_trailing
90
+ 'templates/dlz'
91
+ end
92
+ end
@@ -0,0 +1,24 @@
1
+ # Module to standardize formats for CLI communication.
2
+ module Interface
3
+ PROMPT = 'DLZ>'.freeze
4
+ def self.print(message: 'something happened.', level: :info)
5
+ puts "#{PROMPT} #{level.to_s.upcase}: #{message.downcase}"
6
+ end
7
+
8
+ def self.info(message: 'something informative happened.')
9
+ print(message: message, level: :info)
10
+ end
11
+
12
+ def self.warn(message: 'something almost terrible happened.')
13
+ print(message: message, level: :warn)
14
+ end
15
+
16
+ def self.error(message: 'something terrible happened.')
17
+ print(message: message, level: :error)
18
+ end
19
+
20
+ def self.panic(message: 'something really fucked up happened.')
21
+ print(message: message, level: :error)
22
+ exit(-1)
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ require 'dlz/interface'
2
+
3
+ # Module to create the organization and organizational units
4
+ module Organization
5
+ def self.deploy(*)
6
+ Interface.error(message: 'I am not implemented yet!') # TODO: implement me
7
+ end
8
+
9
+ def self.destroy(*)
10
+ Interface.error(message: 'I am not implemented yet!') # TODO: implement me
11
+ end
12
+ end
@@ -0,0 +1,60 @@
1
+ require 'erb'
2
+ require 'dlz/config'
3
+ require 'dlz/interface'
4
+ require 'pathname'
5
+
6
+ # Module to render cloudformation templates
7
+ module Renderer
8
+ def self.render_dlz
9
+ render(source: {
10
+ path: Config.dlz_template_path,
11
+ root: Config.dlz_path,
12
+ id: :dlz
13
+ }, sink: {
14
+ path: Config.local_dlz_template_path,
15
+ root: Config.local_path,
16
+ id: :local
17
+ })
18
+ end
19
+
20
+ def self.render_local
21
+ render(source: {
22
+ path: Config.local_template_path,
23
+ root: Config.local_path,
24
+ id: :local
25
+ }, sink: {
26
+ path: Config.local_template_path,
27
+ root: Config.local_path,
28
+ id: :local
29
+ })
30
+ end
31
+
32
+ def self.render_all
33
+ FileUtils.mkdir_p(Config.local_dlz_template_path)
34
+ render_dlz
35
+ render_local
36
+ end
37
+
38
+ def self.render(source:, sink:)
39
+ cfg = Config.load
40
+ Dir.glob("#{source[:path]}/*.erb") do |path|
41
+ template = IO.read(path)
42
+ render = ERB.new(template, nil, '-').result(binding)
43
+ n_path = "#{sink[:path]}/#{File.basename(path, File.extname(path))}.yaml"
44
+ File.open(n_path, 'w') do |file|
45
+ file.write(render)
46
+ end
47
+ render_result(source: source, sink: sink, path: path, n_path: n_path)
48
+ end
49
+ end
50
+
51
+ def self.render_result(source:, sink:, path:, n_path:)
52
+ from = Pathname.new(path).relative_path_from(Pathname.new(source[:root]))
53
+ to = Pathname.new(n_path).relative_path_from(Pathname.new(sink[:root]))
54
+
55
+ Interface.info(
56
+ message:
57
+ "RENDER <#{source[:id].to_sym}>/#{from} => <#{sink[:id].to_sym}>/#{to}"
58
+ )
59
+ end
60
+ end
@@ -0,0 +1,12 @@
1
+ require 'dlz/interface'
2
+
3
+ # Module to deploy resources to individual accounts
4
+ module Resources
5
+ def self.deploy(*)
6
+ Interface.error(message: 'I am not implemented yet!') # TODO: implement me
7
+ end
8
+
9
+ def self.destroy(*)
10
+ Interface.error(message: 'I am not implemented yet!') # TODO: implement me
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ # This is the central config for `dlz`.
2
+
3
+ # Account ID of organization master payer, this will be setup as organization
4
+ # root. It will also act as trusted authority to assume the admin roles from.
5
+ root_account_id: 123456789012
6
+
7
+ # Pre-configured external ID to harden access to cross-account admin role.
8
+ # CHANGE THIS!
9
+ sts_external_id: 'change-me'
10
+
11
+ # List of roles which will be assumed by the development teams. `dlz` will deploy
12
+ # it's own resources and protect them from modification by these roles.
13
+ restricted_roles:
14
+ - ReadOnly
15
+ - Developer
16
+ - Administrator
17
+
@@ -0,0 +1,27 @@
1
+ Description: >
2
+ This stack will provision the admin role which `dlz` will use to deploy the landing zone.
3
+
4
+ Resources:
5
+ dlzadm:
6
+ Type: AWS::IAM::Role
7
+ Properties:
8
+ Path: /dlz/admin/
9
+ RoleName: lzadm
10
+ AssumeRolePolicyDocument:
11
+ Version: 2012-10-17
12
+ Statement:
13
+ - Effect: Allow
14
+ Action: sts:AssumeRole
15
+ Principal:
16
+ AWS: arn:aws:iam::<%= cfg[:root_account_id] %>:root
17
+ Condition:
18
+ StringEquals:
19
+ sts:ExternalId: "<%= cfg[:sts_external_id] %>"
20
+ Policies:
21
+ - PolicyName: dlzadm-policy
22
+ PolicyDocument:
23
+ Version: 2012-10-17
24
+ Statement:
25
+ - Effect: Allow
26
+ Action: "*"
27
+ Resource: "*"
@@ -0,0 +1,70 @@
1
+ Description: >
2
+ This stack will restrict modification of `dlz` deployed resources.
3
+
4
+ Resources:
5
+ dlzprotectpolicy:
6
+ Type: AWS::IAM::ManagedPolicy
7
+ Properties:
8
+ Description: This policy will restrict modification of `dlz` deployed resources.
9
+ ManagedPolicyName: dlz-restriction-policy
10
+ Roles:
11
+ <% cfg[:restricted_roles].each do |role| -%>
12
+ - <%= role %>
13
+ <% end -%>
14
+ PolicyDocument:
15
+ Version: 2012-10-17
16
+ Statement:
17
+ - Effect: Deny
18
+ Action:
19
+ - iam:DeleteRole
20
+ - iam:UpdateRole
21
+ - iam:UpdateRoleDescription
22
+ Resources:
23
+ - !Sub 'arn:aws:iam::${AWS::AccountId}:role/dlzadm'
24
+ <% cfg[:restricted_roles].each do |role| -%>
25
+ - !Sub 'arn:aws:iam::${AWS::AccountId}:role/<%= role %>'
26
+ <% end -%>
27
+ - Effect: Deny
28
+ Action:
29
+ - 'organzations:*'
30
+ - iam:AddUserToGroup
31
+ - iam:AttachGroupPolicy
32
+ - iam:AttachUserPolicy
33
+ - iam:ChangePassword
34
+ - iam:CreateAccessKey
35
+ - iam:CreateAccountAlias
36
+ - iam:CreateGroup
37
+ - iam:CreateLoginProfile
38
+ - iam:CreateOpenIDConnectProvider
39
+ - iam:CreateSAMLProvider
40
+ - iam:CreateUser
41
+ - iam:CreateVirtualMFADevice
42
+ - iam:DeleteAccountAlias
43
+ - iam:DeleteAccountPasswordPolicy
44
+ - iam:DeleteGroup
45
+ - iam:DeleteLoginProfile
46
+ - iam:DeleteOpenIdConnectProvider
47
+ - iam:DeleteSAMLProvider
48
+ - iam:DeleteUser
49
+ - iam:DeleteUserPermissionBoundary
50
+ - iam:DeleteUserPolicy
51
+ - iam:DetachUserPolicy
52
+ - iam:EnableMFADevice
53
+ - iam:PutGroupPolicy
54
+ - iam:PutUserPermissionBoundary
55
+ - iam:PutUserPolicy
56
+ - iam:RemoveClientIDFromOpenIDConnectProvider
57
+ - iam:RemoveUserFromGroup
58
+ - iam:ResyncMFADevice
59
+ - iam:SetDefaultPolicyVersion
60
+ - iam:TagUser
61
+ - iam:UntagUser
62
+ - iam:UpdateAccessKey
63
+ - iam:UpdateAccountPasswordPolicy
64
+ - iam:UpdateGroup
65
+ - iam:UpdateLoginProfile
66
+ - iam:UpdateOpenIDConnectProviderThumbprint
67
+ - iam:UpdateSAMLProvider
68
+ - iam:UpdateUser
69
+ Resources:
70
+ - '*'
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dlz
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Stamer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |2
14
+ dlz is a highly-opinionated bootstrapping framework for AWS landing zones.
15
+ It focuses on 3 main areas: creating and configuring an AWS Organization,
16
+ creating and attaching accounts to that organization and deploying resources
17
+ into those accounts.'
18
+ email: dan@hello-world.sh
19
+ executables:
20
+ - dlz
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - LICENSE
25
+ - README.md
26
+ - bin/dlz
27
+ - lib/dlz.rb
28
+ - lib/dlz/accounts.rb
29
+ - lib/dlz/config.rb
30
+ - lib/dlz/interface.rb
31
+ - lib/dlz/organization.rb
32
+ - lib/dlz/renderer.rb
33
+ - lib/dlz/resources.rb
34
+ - lib/init/dlz.yaml
35
+ - lib/templates/admin_role.erb
36
+ - lib/templates/protect_resources.erb
37
+ homepage: https://hello-world.sh
38
+ licenses:
39
+ - MIT
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 2.7.8
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: dlz is an AWS landing zone bootstrapping tool.
61
+ test_files: []