├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── sgviz ├── docker ├── Dockerfile ├── README.md └── gemrc ├── docs ├── cfn │ ├── example.json │ └── example.rb └── images │ └── 1.png ├── lib ├── sgviz.rb └── sgviz │ ├── cli.rb │ ├── generator.rb │ ├── images │ ├── aws.png │ ├── internet.png │ └── vpc.png │ └── version.rb └── sgviz.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | vendor 16 | result.json 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in sgviz.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 y13i 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. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sgviz 2 | 3 | A visualization tool for AWS VPC Security Groups. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem 'sgviz' 11 | ``` 12 | 13 | And then execute: 14 | 15 | ```bash 16 | $ bundle 17 | ``` 18 | 19 | Or install it yourself as: 20 | 21 | ```bash 22 | $ gem install sgviz 23 | ``` 24 | 25 | Graphviz is required to generate graphs. 26 | 27 | ```bash 28 | $ brew install graphviz 29 | ``` 30 | 31 | ## Usage 32 | 33 | ```bash 34 | $ sgviz generate --output-path myvpc --region ap-northeast-1 --vpc-ids vpc-146fad71 35 | ``` 36 | 37 | will generate 38 | 39 | ![myvpc](docs/images/1.png) 40 | 41 | If you're using OSX, run `sgviz open` to view the graph instantly. 42 | 43 | Run `sgviz help` to view more usage. 44 | 45 | ## CloudFormation Template 46 | 47 | You can create example stack using bundled CloudFormation template. 48 | 49 | ```bash 50 | $ aws cloudformation create-stack --stack-name example --template-body file:////path/to/this/repo/docs/cfn/example.json 51 | ``` 52 | 53 | Or use [Kumogata](https://github.com/winebarrel/kumogata), powerful Ruby-CFn integration tool. 54 | 55 | ```bash 56 | $ kumogata create docs/cfn/example.rb example 57 | ``` 58 | 59 | Or use [cloudformation-ruby-dsl](https://github.com/bazaarvoice/cloudformation-ruby-dsl), another powerful CloudFormation templating tool. 60 | 61 | ## TODO, Known Bugs 62 | 63 | * **Rebuild** 64 | * Bug: Problem with outbound edges (duplicate with inbound?). 65 | * TODO: Internal IP address nodes. 66 | * TODO: VPC Peerings. 67 | * TODO: Add spec. (No test code now. Sorry.) 68 | * TODO: Integrate EC2/ELB/RDS/ElastiCache/Redshift components in graph. 69 | * etc... 70 | 71 | ## Contributing 72 | 73 | 1. Fork it ( https://github.com/y13i/sgviz/fork ) 74 | 2. Create your feature branch (`git checkout -b my-new-feature`) 75 | 3. Commit your changes (`git commit -am 'Add some feature'`) 76 | 4. Push to the branch (`git push origin my-new-feature`) 77 | 5. Create a new Pull Request 78 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /bin/sgviz: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "sgviz" 4 | 5 | Sgviz::CLI.start 6 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Pull base image. 2 | FROM gjyoung1974/aws-cli-tools:latest 3 | 4 | MAINTAINER Gordon Young 5 | 6 | ENV NOKOGIRI_USE_SYSTEM_LIBRARIES=1 7 | 8 | ADD gemrc /root/.gemrc 9 | 10 | RUN apk update \ 11 | && apk add ruby \ 12 | ruby-bigdecimal \ 13 | ruby-bundler \ 14 | ruby-io-console \ 15 | ruby-irb \ 16 | ca-certificates \ 17 | libressl \ 18 | gnupg \ 19 | tar \ 20 | curl \ 21 | bash \ 22 | procps \ 23 | sudo \ 24 | graphviz \ 25 | ttf-freefont \ 26 | && apk add --virtual build-dependencies \ 27 | build-base \ 28 | ruby-dev \ 29 | libressl-dev \ 30 | \ 31 | && bundle config build.nokogiri --use-system-libraries \ 32 | && bundle config git.allow_insecure irue \ 33 | && gem install json --no-rdoc --no-ri \ 34 | \ 35 | && gem cleanup \ 36 | && apk del build-dependencies \ 37 | && rm -rf /usr/lib/ruby/gems/*/cache/* \ 38 | /var/cache/apk/* \ 39 | /tmp/* \ 40 | /var/tmp/* 41 | 42 | # Add the sgviz user 43 | RUN adduser -u 1000 -G wheel -D sgviz 44 | RUN echo "sgviz ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 45 | WORKDIR /home/sgviz 46 | RUN chown -R sgviz:users /home/sgviz 47 | USER sgviz 48 | RUN sudo cp -R /root/.aws . && sudo chown -R sgviz:users /home/sgviz/.aws 49 | ADD gemrc /home/sgviz/.gemrc 50 | 51 | # install RVM, Ruby, and Bundler 52 | # Download and Build 53 | RUN curl -sSL https://rvm.io/mpapis.asc | gpg2 --import - 54 | RUN curl -L -o stable.tar.gz https://github.com/rvm/rvm/archive/stable.tar.gz && tar -xvf stable.tar.gz 55 | RUN cd ./rvm-stable && ./scripts/install 56 | RUN /bin/bash -l -c "sudo gem install bundler --no-ri --no-rdoc" 57 | RUN /bin/bash -l -c "sudo gem install sgviz --no-ri --no-rdoc" 58 | 59 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # SGViz docker image 2 | 3 | #### Purpose: 4 | - Automate the configuration review of AWS Security Groups, ACLS VPCs 5 | - Automate the diagramming of our AWS Security groups and VPCs 6 | 7 | *Implementation:* 8 | We can run this image form Kubernetes as ChronJob schedule at some interval 9 | Then use the aws-cli push the putput to an S3 audit bucket 10 | 11 | This is an effort to automate the review of AWS VPCs, SG', ACLs etc.. 12 | 13 | What is SGViz: 14 | A visualization tool for AWS VPC Security Groups. 15 | [SGViz](https://github.com/y13i/sgviz) 16 | 17 | how to run it: 18 | ```bash 19 | docker run -v /Users//Documents:/home/sgviz/diagrams sgviz:latest sgviz generate -k ${sgviz_key_id} -s ${sg_viz_key} -r ${aws_region} --vpc-ids=${vpc-id} --output-path=/home/sgviz/diagrams/${vpc-id} 20 | 21 | ``` 22 | 23 | How to Implement this: 24 | 1. Docker build . -t sgviz:latest 25 | 2. push the image somewhere 26 | 3. set up a k8s CronJob 27 | 4. add a command to run the above sgviz command 28 | 5. add an aws-cli command to 29 | ```bash 30 | $ aws s3 put 31 | ``` 32 | to our k8s CronJob 33 | 6. add a REST call to some ticketing sytem like Jira to push a ticket to someone go revivew the diagram 34 | 35 | -------------------------------------------------------------------------------- /docker/gemrc: -------------------------------------------------------------------------------- 1 | --- 2 | gem: --no-ri --no-rdoc --no-document --suggestions 3 | install: --no-rdoc --no-ri 4 | update: --no-rdoc --no-ri 5 | 6 | :benchmark: false 7 | 8 | :verbose: true 9 | 10 | :backtrace: true 11 | 12 | :update_sources: true 13 | sources: 14 | - http://gems.rubyforge.org/ 15 | - http://rubygems.org/ 16 | 17 | -------------------------------------------------------------------------------- /docs/cfn/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Sgviz example security group set template (Donburi version).\n", 4 | "Resources": { 5 | "Vpc": { 6 | "Type": "AWS::EC2::VPC", 7 | "Properties": { 8 | "CidrBlock": "10.0.0.0/16" 9 | } 10 | }, 11 | "ManageSecurityGroup": { 12 | "Type": "AWS::EC2::SecurityGroup", 13 | "Properties": { 14 | "GroupDescription": "Security group for manage role EC2 instances.", 15 | "VpcId": { 16 | "Ref": "Vpc" 17 | }, 18 | "SecurityGroupIngress": [ 19 | { 20 | "CidrIp": "123.45.67.89/32", 21 | "IpProtocol": "tcp", 22 | "FromPort": "22", 23 | "ToPort": "22" 24 | }, 25 | { 26 | "CidrIp": "123.45.67.89/32", 27 | "IpProtocol": "tcp", 28 | "FromPort": "80", 29 | "ToPort": "80" 30 | }, 31 | { 32 | "CidrIp": "123.45.67.89/32", 33 | "IpProtocol": "tcp", 34 | "FromPort": "443", 35 | "ToPort": "443" 36 | }, 37 | { 38 | "CidrIp": "234.56.78.90/32", 39 | "IpProtocol": "tcp", 40 | "FromPort": "22", 41 | "ToPort": "22" 42 | }, 43 | { 44 | "CidrIp": "234.56.78.90/32", 45 | "IpProtocol": "tcp", 46 | "FromPort": "80", 47 | "ToPort": "80" 48 | }, 49 | { 50 | "CidrIp": "234.56.78.90/32", 51 | "IpProtocol": "tcp", 52 | "FromPort": "443", 53 | "ToPort": "443" 54 | } 55 | ], 56 | "Tags": [ 57 | { 58 | "Key": "Name", 59 | "Value": "manage" 60 | } 61 | ] 62 | } 63 | }, 64 | "AppLoadBalancerSecurityGroup": { 65 | "Type": "AWS::EC2::SecurityGroup", 66 | "Properties": { 67 | "GroupDescription": "Security group for application load balancer.", 68 | "VpcId": { 69 | "Ref": "Vpc" 70 | }, 71 | "SecurityGroupIngress": [ 72 | { 73 | "CidrIp": "0.0.0.0/0", 74 | "IpProtocol": "tcp", 75 | "FromPort": "80", 76 | "ToPort": "80" 77 | }, 78 | { 79 | "CidrIp": "0.0.0.0/0", 80 | "IpProtocol": "tcp", 81 | "FromPort": "443", 82 | "ToPort": "443" 83 | } 84 | ], 85 | "Tags": [ 86 | { 87 | "Key": "Name", 88 | "Value": "app_load_balancer" 89 | } 90 | ] 91 | } 92 | }, 93 | "AppSecurityGroup": { 94 | "Type": "AWS::EC2::SecurityGroup", 95 | "Properties": { 96 | "GroupDescription": "Security group for application EC2 instances.", 97 | "VpcId": { 98 | "Ref": "Vpc" 99 | }, 100 | "SecurityGroupIngress": [ 101 | { 102 | "SourceSecurityGroupId": { 103 | "Ref": "AppLoadBalancerSecurityGroup" 104 | }, 105 | "IpProtocol": "tcp", 106 | "FromPort": "80", 107 | "ToPort": "80" 108 | }, 109 | { 110 | "SourceSecurityGroupId": { 111 | "Ref": "ManageSecurityGroup" 112 | }, 113 | "IpProtocol": "tcp", 114 | "FromPort": "22", 115 | "ToPort": "22" 116 | }, 117 | { 118 | "SourceSecurityGroupId": { 119 | "Ref": "ManageSecurityGroup" 120 | }, 121 | "IpProtocol": "tcp", 122 | "FromPort": "10050", 123 | "ToPort": "10050" 124 | } 125 | ], 126 | "Tags": [ 127 | { 128 | "Key": "Name", 129 | "Value": "app" 130 | } 131 | ] 132 | } 133 | }, 134 | "Port10051IngressFromAppToManage": { 135 | "Type": "AWS::EC2::SecurityGroupIngress", 136 | "Properties": { 137 | "GroupId": { 138 | "Ref": "ManageSecurityGroup" 139 | }, 140 | "SourceSecurityGroupId": { 141 | "Ref": "AppSecurityGroup" 142 | }, 143 | "IpProtocol": "tcp", 144 | "FromPort": "10051", 145 | "ToPort": "10051" 146 | } 147 | }, 148 | "RedisSecurityGroup": { 149 | "Type": "AWS::EC2::SecurityGroup", 150 | "Properties": { 151 | "GroupDescription": "Security group for ElastiCache Redis clusters.", 152 | "VpcId": { 153 | "Ref": "Vpc" 154 | }, 155 | "SecurityGroupIngress": [ 156 | { 157 | "SourceSecurityGroupId": { 158 | "Ref": "ManageSecurityGroup" 159 | }, 160 | "IpProtocol": "tcp", 161 | "FromPort": "6379", 162 | "ToPort": "6379" 163 | }, 164 | { 165 | "SourceSecurityGroupId": { 166 | "Ref": "AppSecurityGroup" 167 | }, 168 | "IpProtocol": "tcp", 169 | "FromPort": "6379", 170 | "ToPort": "6379" 171 | } 172 | ], 173 | "Tags": [ 174 | { 175 | "Key": "Name", 176 | "Value": "redis" 177 | } 178 | ] 179 | } 180 | }, 181 | "MysqlSecurityGroup": { 182 | "Type": "AWS::EC2::SecurityGroup", 183 | "Properties": { 184 | "GroupDescription": "Security group for RDS MySQL DB instances.", 185 | "VpcId": { 186 | "Ref": "Vpc" 187 | }, 188 | "SecurityGroupIngress": [ 189 | { 190 | "SourceSecurityGroupId": { 191 | "Ref": "ManageSecurityGroup" 192 | }, 193 | "IpProtocol": "tcp", 194 | "FromPort": "3306", 195 | "ToPort": "3306" 196 | }, 197 | { 198 | "SourceSecurityGroupId": { 199 | "Ref": "AppSecurityGroup" 200 | }, 201 | "IpProtocol": "tcp", 202 | "FromPort": "3306", 203 | "ToPort": "3306" 204 | } 205 | ], 206 | "Tags": [ 207 | { 208 | "Key": "Name", 209 | "Value": "mysql" 210 | } 211 | ] 212 | } 213 | } 214 | }, 215 | "Outputs": { 216 | "Vpc": { 217 | "Value": { 218 | "Ref": "Vpc" 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /docs/cfn/example.rb: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion "2010-09-09" 2 | 3 | Description <<-EOS.undent 4 | Sgviz example security group set template (Donburi version). 5 | EOS 6 | 7 | Resources do 8 | Vpc do 9 | Type "AWS::EC2::VPC" 10 | 11 | Properties do 12 | CidrBlock "10.0.0.0/16" 13 | end 14 | end 15 | 16 | ManageSecurityGroup do 17 | Type "AWS::EC2::SecurityGroup" 18 | 19 | Properties do 20 | GroupDescription "Security group for manage role EC2 instances." 21 | VpcId {Ref "Vpc"} 22 | 23 | SecurityGroupIngress( 24 | ["123.45.67.89/32", "234.56.78.90/32", ].product([22, 80, 443]).map do |my_ip, port| 25 | _{ 26 | CidrIp my_ip 27 | IpProtocol "tcp" 28 | FromPort port 29 | ToPort port 30 | } 31 | end 32 | ) 33 | 34 | Tags [ 35 | _{ 36 | Key "Name" 37 | Value "manage" 38 | } 39 | ] 40 | end 41 | end 42 | 43 | AppLoadBalancerSecurityGroup do 44 | Type "AWS::EC2::SecurityGroup" 45 | 46 | Properties do 47 | GroupDescription "Security group for application load balancer." 48 | VpcId {Ref "Vpc"} 49 | 50 | SecurityGroupIngress( 51 | [80, 443].map do |port| 52 | _{ 53 | CidrIp "0.0.0.0/0" 54 | IpProtocol "tcp" 55 | FromPort port 56 | ToPort port 57 | } 58 | end 59 | ) 60 | 61 | Tags [ 62 | _{ 63 | Key "Name" 64 | Value "app_load_balancer" 65 | } 66 | ] 67 | end 68 | end 69 | 70 | AppSecurityGroup do 71 | Type "AWS::EC2::SecurityGroup" 72 | 73 | Properties do 74 | GroupDescription "Security group for application EC2 instances." 75 | VpcId {Ref "Vpc"} 76 | 77 | SecurityGroupIngress [ 78 | _{ 79 | SourceSecurityGroupId {Ref "AppLoadBalancerSecurityGroup"} 80 | IpProtocol "tcp" 81 | FromPort 80 82 | ToPort 80 83 | }, 84 | 85 | *[22, 10050].map do |port| 86 | _{ 87 | SourceSecurityGroupId {Ref "ManageSecurityGroup"} 88 | IpProtocol "tcp" 89 | FromPort port 90 | ToPort port 91 | } 92 | end 93 | ] 94 | 95 | Tags [ 96 | _{ 97 | Key "Name" 98 | Value "app" 99 | } 100 | ] 101 | end 102 | end 103 | 104 | Port10051IngressFromAppToManage do 105 | Type "AWS::EC2::SecurityGroupIngress" 106 | 107 | Properties do 108 | GroupId {Ref "ManageSecurityGroup"} 109 | SourceSecurityGroupId {Ref "AppSecurityGroup"} 110 | IpProtocol "tcp" 111 | FromPort 10051 112 | ToPort 10051 113 | end 114 | end 115 | 116 | RedisSecurityGroup do 117 | Type "AWS::EC2::SecurityGroup" 118 | 119 | Properties do 120 | GroupDescription "Security group for ElastiCache Redis clusters." 121 | VpcId {Ref "Vpc"} 122 | 123 | SecurityGroupIngress( 124 | ["Manage", "App"].map do |role| 125 | _{ 126 | SourceSecurityGroupId {Ref "#{role}SecurityGroup"} 127 | IpProtocol "tcp" 128 | FromPort 6379 129 | ToPort 6379 130 | } 131 | end 132 | ) 133 | 134 | Tags [ 135 | _{ 136 | Key "Name" 137 | Value "redis" 138 | } 139 | ] 140 | end 141 | end 142 | 143 | MysqlSecurityGroup do 144 | Type "AWS::EC2::SecurityGroup" 145 | 146 | Properties do 147 | GroupDescription "Security group for RDS MySQL DB instances." 148 | VpcId {Ref "Vpc"} 149 | 150 | SecurityGroupIngress( 151 | ["Manage", "App"].map do |role| 152 | _{ 153 | SourceSecurityGroupId {Ref "#{role}SecurityGroup"} 154 | IpProtocol "tcp" 155 | FromPort 3306 156 | ToPort 3306 157 | } 158 | end 159 | ) 160 | 161 | Tags [ 162 | _{ 163 | Key "Name" 164 | Value "mysql" 165 | } 166 | ] 167 | end 168 | end 169 | end 170 | 171 | Outputs do 172 | Vpc do 173 | Value do 174 | Ref "Vpc" 175 | end 176 | end 177 | end 178 | -------------------------------------------------------------------------------- /docs/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y13i/sgviz/d321071abfa22635e1e3256d107dc0cf22364570/docs/images/1.png -------------------------------------------------------------------------------- /lib/sgviz.rb: -------------------------------------------------------------------------------- 1 | require "sgviz/version" 2 | require "sgviz/generator" 3 | require "sgviz/cli" 4 | -------------------------------------------------------------------------------- /lib/sgviz/cli.rb: -------------------------------------------------------------------------------- 1 | require "thor" 2 | 3 | class Sgviz::CLI < Thor 4 | class_option :profile, 5 | desc: "Load credentials by profile name from shared credentials file (~/.aws/credentials).", 6 | aliases: [:p] 7 | 8 | class_option :access_key_id, 9 | desc: "AWS access key id.", 10 | aliases: [:k] 11 | 12 | class_option :secret_access_key, 13 | desc: "AWS secret access key.", 14 | aliases: [:s] 15 | 16 | class_option :region, 17 | desc: "AWS region.", 18 | aliases: [:r] 19 | 20 | class_option :format, 21 | desc: "Output file format", 22 | aliases: [:f], 23 | default: "png" 24 | 25 | class_option :output_path, 26 | desc: "Output file path.", 27 | aliases: [:output, :o] 28 | 29 | class_option :vpc_ids, 30 | desc: "AWS VPC IDs (If specified, graph will include security groups in the VPCs).", 31 | aliases: [:vpcs, :vpc, :v], 32 | type: :array 33 | 34 | class_option :inbound_only, 35 | desc: "If specified, graph will exclude outbound rules.", 36 | type: :boolean, 37 | default: true 38 | 39 | class_option :global_label, 40 | desc: "Label of the diagram.", 41 | default: "" 42 | 43 | class_option :fontname, 44 | desc: "Font name used on labels.", 45 | default: "Futura" 46 | 47 | class_option :fontsize, 48 | desc: "Font size used on labels.", 49 | type: :numeric, 50 | default: 15 51 | 52 | desc "version", "Puts sgviz version." 53 | def version 54 | puts Sgviz::VERSION 55 | end 56 | 57 | desc "open", "Generate and open a graph file." 58 | method_option :output_path, required: true 59 | def open 60 | unless system "which open > /dev/null" 61 | abort "`open` command not found." 62 | end 63 | 64 | generate 65 | system "open #{options.output_path}.#{options.format}" 66 | end 67 | 68 | desc "generate", "Generate a graph file." 69 | method_option :output_path, required: true 70 | def generate 71 | generator.generate 72 | puts "Graph generated to `#{options.output_path}.#{options.format}`." 73 | end 74 | 75 | private 76 | 77 | def generator 78 | @generator ||= Sgviz::Generator.new :G, :digraph, options 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/sgviz/generator.rb: -------------------------------------------------------------------------------- 1 | require "gviz" 2 | require "aws-sdk-resources" 3 | 4 | class Sgviz::Generator < Gviz 5 | def initialize name = :G, type = :digraph, options = {} 6 | @options = options 7 | super(name, type) 8 | end 9 | 10 | def subgraph(name=:"cluster#{subgraphs.size}", &blk) 11 | self.class.new(name, :subgraph, options).tap do |graph| 12 | subgraphs << graph 13 | graph.instance_eval &blk 14 | end 15 | end 16 | 17 | def generate 18 | global( 19 | layout: "dot", 20 | label: options.global_label, 21 | labelloc: "b", 22 | fontsize: options.fontsize, 23 | fontname: options.fontname, 24 | fontcolor: "#54523F", 25 | margin: 20, 26 | ) 27 | 28 | nodes( 29 | fontsize: options.fontsize, 30 | fontname: options.fontname, 31 | ) 32 | 33 | vpc_security_groups.each do |vpc_id, security_groups| 34 | security_groups.each do |security_group| 35 | subgraph :clusterAWS do 36 | global( 37 | label: aws_configuration[:region], 38 | labelloc: "b", 39 | style: "rounded", 40 | color: "#999999", 41 | fontsize: options.fontsize, 42 | fontname: options.fontname, 43 | fontcolor: "#54523F", 44 | margin: 20, 45 | ) 46 | 47 | node :aws, 48 | label: "", 49 | image: "#{__dir__}/images/aws.png", 50 | peripheries: 0, 51 | fixedsize: true, 52 | imagescale: true, 53 | shape: "circle", 54 | width: 1.5 55 | 56 | subgraph :"cluster#{vpc_id ? vpc_id[4..-1] : 'ec2_classic'}" do 57 | global( 58 | label: vpc_id ? vpc_id : 'EC2-Classic', 59 | labelloc: "b", 60 | style: "rounded", 61 | color: "#999999", 62 | fontsize: options.fontsize, 63 | fontname: options.fontname, 64 | fontcolor: "#54523F", 65 | margin: 20, 66 | ) 67 | 68 | node vpc_id.to_id, 69 | label: "", 70 | image: "#{__dir__}/images/vpc.png", 71 | peripheries: 0, 72 | fixedsize: true, 73 | imagescale: true, 74 | shape: "circle", 75 | width: 1.5 76 | 77 | name_tag = security_group.tags.find {|tag| tag.key == "Name"} 78 | 79 | node security_group.id.to_id, 80 | shape: "note", 81 | style: "filled", 82 | color: "#EDEAD2", 83 | fillcolor: "#EDEAD2", 84 | label: "#{name_tag ? name_tag.value : security_group.group_name}\n(#{security_group.id})", 85 | fontsize: options.fontsize, 86 | fontname: options.fontname, 87 | fontcolor: "#54523F", 88 | margin: 0.2 89 | end 90 | end 91 | 92 | security_group.ip_permissions.each do |ip_permission| 93 | to = security_group.id.to_id 94 | 95 | ip_permission.ip_ranges.each do |ip_range| 96 | from = ip_range.cidr_ip.to_id 97 | 98 | node from, 99 | shape: "ellipse", 100 | style: "filled", 101 | color: "#E7FAFF", 102 | fillcolor: "#E7FAFF", 103 | label: ip_range.cidr_ip, 104 | fontsize: options.fontsize, 105 | fontname: options.fontname, 106 | fontcolor: "#003E2F" 107 | 108 | add_route :inbound, from, to, ip_permission 109 | end 110 | 111 | ip_permission.user_id_group_pairs.each do |user_id_group_pair| 112 | from = if user_id_group_pair.user_id == security_group.owner_id 113 | user_id_group_pair.group_id.to_id 114 | else 115 | node xaccount_sg(user_id_group_pair).to_id, xaccount_sg(user_id_group_pair) 116 | xaccount_sg(user_id_group_pair).to_id 117 | end 118 | 119 | add_route :inbound, from, to, ip_permission 120 | end 121 | end 122 | 123 | unless options.inbound_only 124 | security_group.ip_permissions_egress.each do |ip_permission| 125 | from = security_group.id.to_id 126 | 127 | ip_permission.ip_ranges.each do |ip_range| 128 | to = ip_range.cidr_ip.to_id 129 | 130 | node to, 131 | shape: "ellipse", 132 | style: "filled", 133 | color: "#E7FAFF", 134 | fillcolor: "#E7FAFF", 135 | label: ip_range.cidr_ip, 136 | fontsize: options.fontsize, 137 | fontname: options.fontname, 138 | fontcolor: "#003E2F" 139 | 140 | add_route :outbound, from, to, ip_permission 141 | end 142 | 143 | ip_permission.user_id_group_pairs.each do |user_id_group_pair| 144 | to = if user_id_group_pair.user_id == security_group.owner_id 145 | user_id_group_pair.group_id.to_id 146 | else 147 | node xaccount_sg(user_id_group_pair).to_id, xaccount_sg(user_id_group_pair) 148 | xaccount_sg(user_id_group_pair).to_id 149 | end 150 | 151 | add_route :outbound, from, to, ip_permission 152 | end 153 | end 154 | end 155 | end 156 | end 157 | 158 | node "0.0.0.0/0".to_id, 159 | label: "", 160 | image: "#{__dir__}/images/internet.png", 161 | peripheries: 0, 162 | fixedsize: true, 163 | imagescale: true, 164 | shape: "circle", 165 | width: 1.67 166 | 167 | save options.output_path, options.format 168 | end 169 | 170 | private 171 | 172 | def options 173 | @options 174 | end 175 | 176 | def aws_configuration 177 | hash = {} 178 | 179 | [:profile, :access_key_id, :secret_access_key, :region].each do |option| 180 | hash.update(option => options[option]) if options[option] 181 | end 182 | 183 | hash.update(region: own_region) if hash[:region].nil? 184 | hash 185 | end 186 | 187 | def own_region 188 | @own_region ||= begin 189 | require "net/http" 190 | 191 | timeout 3 do 192 | Net::HTTP.get("169.254.169.254", "/latest/meta-data/placement/availability-zone").chop 193 | end 194 | rescue 195 | nil 196 | end 197 | end 198 | 199 | def ec2 200 | @ec2 ||= Aws::EC2::Resource.new aws_configuration 201 | end 202 | 203 | # returns Hash like `{"vpc-1111111" => [sg1, sg2, ...], "vpc-11111112" => [sg3, sg4, ...], ...}` 204 | def vpc_security_groups 205 | ec2.security_groups.group_by(&:vpc_id).select do |vpc_id| 206 | if options.vpc_ids 207 | options.vpc_ids.include? vpc_id 208 | else 209 | true 210 | end 211 | end 212 | end 213 | 214 | def xaccount_sg user_id_group_pair 215 | "#{user_id_group_pair.user_id}/#{user_id_group_pair.group_id}" 216 | end 217 | 218 | def add_route in_or_out = :inbound, from, to, ip_permission 219 | id = :"#{from}_#{to}" 220 | color = (in_or_out == :inbound ? "#003E2F" : "#045280") 221 | 222 | traffic_map[id] = if traffic_map[id] 223 | [traffic_map[id], route_label(ip_permission)].join("\\n") 224 | else 225 | route_label(ip_permission) 226 | end 227 | 228 | edge id, 229 | label: traffic_map[id], 230 | style: "bold", 231 | arrowhead: (in_or_out == :inbound ? "normal" : "onormal"), 232 | color: color, 233 | fontname: options.fontname, 234 | fontsize: (options.fontsize * 0.75), 235 | fontcolor: color 236 | end 237 | 238 | def traffic_map 239 | @traffic_map ||= {} 240 | end 241 | 242 | def route_label ip_permission 243 | if ip_permission.ip_protocol == "-1" 244 | "All Traffic" 245 | elsif ip_permission.from_port == ip_permission.to_port 246 | "#{ip_permission.ip_protocol.upcase}:#{ip_permission.to_port}" 247 | else 248 | "#{ip_permission.ip_protocol.upcase}:#{ip_permission.from_port}-#{ip_permission.to_port}" 249 | end 250 | end 251 | end 252 | -------------------------------------------------------------------------------- /lib/sgviz/images/aws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y13i/sgviz/d321071abfa22635e1e3256d107dc0cf22364570/lib/sgviz/images/aws.png -------------------------------------------------------------------------------- /lib/sgviz/images/internet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y13i/sgviz/d321071abfa22635e1e3256d107dc0cf22364570/lib/sgviz/images/internet.png -------------------------------------------------------------------------------- /lib/sgviz/images/vpc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y13i/sgviz/d321071abfa22635e1e3256d107dc0cf22364570/lib/sgviz/images/vpc.png -------------------------------------------------------------------------------- /lib/sgviz/version.rb: -------------------------------------------------------------------------------- 1 | module Sgviz 2 | VERSION = "0.0.2" 3 | end 4 | -------------------------------------------------------------------------------- /sgviz.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'sgviz/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "sgviz" 8 | spec.version = Sgviz::VERSION 9 | spec.authors = ["y13i"] 10 | spec.email = ["email@y13i.com"] 11 | spec.summary = %(Visualize VPC Security Groups.) 12 | spec.description = %(A visualization tool for AWS VPC Security Groups.) 13 | spec.homepage = "https://github.com/y13i/sgviz" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 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_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | 24 | spec.add_dependency "gviz" 25 | spec.add_dependency "thor" 26 | spec.add_dependency "aws-sdk-resources" 27 | spec.add_dependency "aws-sdk-core" 28 | 29 | spec.add_development_dependency "pry" 30 | spec.add_development_dependency "awesome_print" 31 | spec.add_development_dependency "kumogata" 32 | end 33 | --------------------------------------------------------------------------------