Skip to content

Commit

Permalink
Merge pull request #24 from luhring/same-vpc
Browse files Browse the repository at this point in the history
Same VPC
  • Loading branch information
luhring authored Nov 16, 2019
2 parents 69bcde9 + fbe2730 commit fe66d61
Show file tree
Hide file tree
Showing 85 changed files with 1,945 additions and 319 deletions.
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ jobs:
working_directory: ~/repo
steps:
- checkout
- run: curl -sS https://releases.hashicorp.com/terraform/0.12.7/terraform_0.12.7_linux_amd64.zip -o ./terraform.zip && unzip terraform.zip && sudo mv terraform /usr/local/bin/ && which terraform
- run: go test -cover ./reach/... -test.v
- run: curl -sS https://releases.hashicorp.com/terraform/0.12.15/terraform_0.12.15_linux_amd64.zip -o ./terraform.zip && unzip terraform.zip && sudo mv terraform /usr/local/bin/ && which terraform
- run: go test -cover ./reach/analyzer -test.v -acceptance -log-tf -timeout 60m

workflows:
version: 2
commit:
push:
jobs:
- build
- unit_tests
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/luhring/reach)](https://goreportcard.com/report/github.com/luhring/reach)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/luhring/reach/blob/master/LICENSE)

Reach is a tool for discovering the impact your AWS configuration has on the flow of network traffic.
Reach is a tool for analyzing the network traffic allowed to flow in AWS. Reach doesn't need any access to your network — it simply queries the AWS API for your network configuration.

## Getting Started

Expand Down Expand Up @@ -50,7 +50,7 @@ $ reach web-instance database-instance
$ reach web data
```

**Note:** Right now, Reach can only analyze the path between two EC2 instances when the instances are **in the same subnet**. Adding support for multiple subnets is the top priority and is currently in development.
**Note:** Right now, Reach can analyze the path between two EC2 instances only when the instances are **_in the same VPC_**.

## Initial Setup

Expand Down Expand Up @@ -102,7 +102,7 @@ In this case, Reach will provide significantly more detail about the analysis. S
## Feature Ideas

- ~~**Same-subnet analysis:** Between two EC2 instances within the same subnet~~ (done!)
- **Same-VPC analysis:** Between two EC2 instances within the same VPC, including for EC2 instances in separate subnets
- ~~**Same-VPC analysis:** Between two EC2 instances within the same VPC, including for EC2 instances in separate subnets~~ (done!)
- **IP address analysis:** Between an EC2 instance and a specified IP address that may be outside of AWS entirely (enhancement idea: provide shortcuts for things like the user's own IP address, a specified hostname's resolved IP address, etc.)
- **Filtered analysis:** Specify a particular kind of network traffic to analyze (e.g. a single TCP port) and return results only for that filter
- **Other AWS resources:** Analyze other kinds of AWS resources than just EC2 instances (e.g. ELB, Lambda, VPC endpoints, etc.)
Expand Down
72 changes: 54 additions & 18 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,37 +1,73 @@
#!/bin/bash

set -ex
# This script takes an argument for which OS to build for: darwin, linux, or windows.
# If no argument is provided, the script builds for all three.

# To build for a specific version, set the `REACH_VERSION` variable to something like "2.0.1" before running the script.

set -e

export REACH_VERSION=${REACH_VERSION:-"0.0.0"}
export SPECIFIED_OS=""

if [[ -z "$1" ]]
then
export SPECIFIED_OS="$1"
fi

set -u

export CGO_ENABLED=0
export GOARCH=amd64
export REACH_DIR_DARWIN=$(printf "reach_%s_darwin_amd64" $REACH_VERSION)
export REACH_DIR_LINUX=$(printf "reach_%s_linux_amd64" $REACH_VERSION)
export REACH_DIR_WINDOWS=$(printf "reach_%s_windows_amd64" $REACH_VERSION)

mkdir -p ./build
set -x

function build_for_os {
local GOOS="$1"
local REACH_EXECUTABLE

GOOS=darwin go build -a -tags netgo -o "./build/$REACH_DIR_DARWIN/reach"
GOOS=linux go build -a -tags netgo -o "./build/$REACH_DIR_LINUX/reach"
GOOS=windows go build -a -tags netgo -o "./build/$REACH_DIR_WINDOWS/reach.exe"
if [[ "$GOOS" == "windows" ]]
then
REACH_EXECUTABLE="reach.exe"
else
REACH_EXECUTABLE="reach"
fi

cp -nv ./LICENSE ./README.md "./build/$REACH_DIR_DARWIN"
cp -nv ./LICENSE ./README.md "./build/$REACH_DIR_LINUX"
cp -nv ./LICENSE ./README.md "./build/$REACH_DIR_WINDOWS"
local REACH_DIR_FOR_OS
REACH_DIR_FOR_OS=$(printf "reach_%s_%s_amd64" "$REACH_VERSION" "$GOOS")

mkdir -p "./$REACH_DIR_FOR_OS"

GOOS=$GOOS go build -a -v -tags netgo -o "./$REACH_DIR_FOR_OS/$REACH_EXECUTABLE" ..
cp -nv ../LICENSE ../README.md "./$REACH_DIR_FOR_OS/"

if [[ "$GOOS" == "windows" ]]
then
zip "$REACH_DIR_FOR_OS.zip" "./$REACH_DIR_FOR_OS"/*
openssl dgst -sha256 "./$REACH_DIR_FOR_OS.zip" >> ./checksums.txt
else
tar -cvzf "$REACH_DIR_FOR_OS.tar.gz" "./$REACH_DIR_FOR_OS"/*
openssl dgst -sha256 "./$REACH_DIR_FOR_OS.tar.gz" >> ./checksums.txt
fi
}

rm -rf ./build
mkdir -p ./build

pushd ./build
tar -cvzf $REACH_DIR_DARWIN.tar.gz ./$REACH_DIR_DARWIN/*
tar -cvzf $REACH_DIR_LINUX.tar.gz ./$REACH_DIR_LINUX/*
tar -cvzf $REACH_DIR_WINDOWS.tar.gz ./$REACH_DIR_WINDOWS/*
if [[ ! -z "$SPECIFIED_OS" ]]
then
build_for_os "$SPECIFIED_OS"
else
for CURRENT_OS in "darwin" "linux" "windows"
do
build_for_os "$CURRENT_OS"
done
fi

openssl dgst -sha256 ./$REACH_DIR_DARWIN.tar.gz >> ./checksums.txt
openssl dgst -sha256 ./$REACH_DIR_LINUX.tar.gz >> ./checksums.txt
openssl dgst -sha256 ./$REACH_DIR_WINDOWS.tar.gz >> ./checksums.txt
set +x

cat ./checksums.txt
popd

set +eux
set +eu
42 changes: 42 additions & 0 deletions cmd/assert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cmd

import (
"fmt"
"os"

"github.com/mgutz/ansi"

"github.com/luhring/reach/reach"
)

func doAssertReachable(analysis reach.Analysis) {
if analysis.PassesAssertReachable() {
exitSuccessfulAssertion("source is able to reach destination")
} else {
exitFailedAssertion("one or more forward or return paths of network traffic is obstructed")
}
}

func doAssertNotReachable(analysis reach.Analysis) {
if analysis.PassesAssertNotReachable() {
exitSuccessfulAssertion("source is unable to reach destination")
} else {
exitFailedAssertion("source is able to send network traffic to destination")
}
}

func exitFailedAssertion(text string) {
failedMessage := ansi.Color("assertion failed:", "red+b")
secondaryMessage := ansi.Color(text, "red")
_, _ = fmt.Fprintf(os.Stderr, "\n%v %v\n", failedMessage, secondaryMessage)

os.Exit(2)
}

func exitSuccessfulAssertion(text string) {
succeededMessage := ansi.Color("assertion succeeded:", "green+b")
secondaryMessage := ansi.Color(text, "green")
_, _ = fmt.Fprintf(os.Stderr, "\n%v %v\n", succeededMessage, secondaryMessage)

os.Exit(0)
}
17 changes: 0 additions & 17 deletions cmd/exit.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"fmt"
"github.com/mgutz/ansi"
"os"
)

Expand All @@ -11,19 +10,3 @@ func exitWithError(err error) {

os.Exit(1)
}

func exitWithFailedAssertion(text string) {
failedMessage := ansi.Color("assertion failed:", "red+b")
secondaryMessage := ansi.Color(text, "red")
_, _ = fmt.Fprintf(os.Stderr, "\n%v %v\n", failedMessage, secondaryMessage)

os.Exit(2)
}

func exitWithSuccessfulAssertion(text string) {
succeededMessage := ansi.Color("assertion succeeded:", "green+b")
secondaryMessage := ansi.Color(text, "green")
_, _ = fmt.Fprintf(os.Stderr, "\n%v %v\n", succeededMessage, secondaryMessage)

os.Exit(0)
}
54 changes: 26 additions & 28 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package cmd
import (
"errors"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
Expand All @@ -16,11 +15,13 @@ import (

const explainFlag = "explain"
const vectorsFlag = "vectors"
const jsonFlag = "json"
const assertReachableFlag = "assert-reachable"
const assertNotReachableFlag = "assert-not-reachable"

var explain bool
var showVectors bool
var outputJSON bool
var assertReachable bool
var assertNotReachable bool

Expand Down Expand Up @@ -58,7 +59,7 @@ See https://github.com/luhring/reach for documentation.`,
}
destination.SetRoleToDestination()

if !explain && !showVectors {
if !outputJSON && !explain && !showVectors {
fmt.Printf("source: %s\ndestination: %s\n\n", source.ID, destination.ID)
}

Expand All @@ -73,48 +74,49 @@ See https://github.com/luhring/reach for documentation.`,
exitWithError(err)
}

if explain {
if outputJSON {
fmt.Println(analysis.ToJSON())
} else if explain {
ex := explainer.New(*analysis)
fmt.Print(ex.Explain())
} else if showVectors {
var vectorOutputs []string

for _, v := range analysis.NetworkVectors {
output := ""
output += v.String()

vectorOutputs = append(vectorOutputs, output)
vectorOutputs = append(vectorOutputs, v.String())
}

fmt.Print(strings.Join(vectorOutputs, "\n"))
} else {
fmt.Print("network traffic allowed from source to destination:" + "\n")
fmt.Print(mergedTraffic.ColorStringWithSymbols())

if len(analysis.NetworkVectors) > 1 {
if len(analysis.NetworkVectors) > 1 { // handling this case with care; this view isn't optimized for multi-vector output!
printMergedResultsWarning()
warnIfAnyVectorHasRestrictedReturnTraffic(analysis.NetworkVectors)
} else {
// calculate merged return traffic
mergedReturnTraffic, err := analysis.MergedReturnTraffic()
if err != nil {
exitWithError(err)
}

restrictedProtocols := mergedTraffic.ProtocolsWithRestrictedReturnPath(mergedReturnTraffic)
if len(restrictedProtocols) > 0 {
found, warnings := explainer.WarningsFromRestrictedReturnPath(restrictedProtocols)
if found {
fmt.Print("\n" + warnings + "\n")
}
}
}
}

// fmt.Println(analysis.ToJSON()) // for debugging

const canReach = "source is able to reach destination"
const cannotReach = "source is unable to reach destination"

if assertReachable {
if mergedTraffic.None() {
exitWithFailedAssertion(cannotReach)
} else {
exitWithSuccessfulAssertion(canReach)
}
doAssertReachable(*analysis)
}

if assertNotReachable {
if mergedTraffic.None() {
exitWithSuccessfulAssertion(cannotReach)
} else {
exitWithFailedAssertion(canReach)
}
doAssertNotReachable(*analysis)
}
},
}
Expand All @@ -129,11 +131,7 @@ func Execute() {
func init() {
rootCmd.Flags().BoolVar(&explain, explainFlag, false, "explain how the configuration was analyzed")
rootCmd.Flags().BoolVar(&showVectors, vectorsFlag, false, "show allowed traffic in terms of network vectors")
rootCmd.Flags().BoolVar(&outputJSON, jsonFlag, false, "output full analysis as JSON (overrides other display flags)")
rootCmd.Flags().BoolVar(&assertReachable, assertReachableFlag, false, "exit non-zero if no traffic is allowed from source to destination")
rootCmd.Flags().BoolVar(&assertNotReachable, assertNotReachableFlag, false, "exit non-zero if any traffic can reach destination from source")
}

func printMergedResultsWarning() {
const mergedResultsWarning = "IMPORTANT: Reach detected more than one network path between the source and destination. Reach calls these paths \"network vectors\". The analysis result shown above is the merging of all network vectors' analysis results. The impact that infrastructure configuration has on actual network reachability might vary based on the way hosts are configured to use their network interfaces, and Reach is unable to access any configuration internal to a host. To see the network reachability across individual network vectors, run the command again with '--vectors'.\n\n"
_, _ = fmt.Fprint(os.Stderr, "\n"+mergedResultsWarning)
}
24 changes: 24 additions & 0 deletions cmd/warnings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cmd

import (
"fmt"
"os"

"github.com/luhring/reach/reach"
)

func printMergedResultsWarning() {
const mergedResultsWarning = "WARNING: Reach detected more than one network path between the source and destination. Reach calls these paths \"network vectors\". The analysis result shown above is the merging of all network vectors' analysis results. The impact that infrastructure configuration has on actual network reachability might vary based on the way hosts are configured to use their network interfaces, and Reach is unable to access any configuration internal to a host. To see the network reachability across individual network vectors, run the command again with '--" + vectorsFlag + "'.\n"
_, _ = fmt.Fprint(os.Stderr, "\n"+mergedResultsWarning)
}

func warnIfAnyVectorHasRestrictedReturnTraffic(vectors []reach.NetworkVector) {
for _, v := range vectors {
if !v.ReturnTraffic.All() {
const restrictedVectorReturnTraffic = "WARNING: One or more of the analyzed network vectors has restrictions on network traffic allowed to return from the destination to the source. For details, run the command again with '--" + vectorsFlag + "'.\n"
_, _ = fmt.Fprintf(os.Stderr, "\n"+restrictedVectorReturnTraffic)

return
}
}
}
25 changes: 0 additions & 25 deletions reach/acceptance/data/tf/ec2_instance_source_and_destination.tf

This file was deleted.

Loading

0 comments on commit fe66d61

Please sign in to comment.