Skip to content

Commit fe66d61

Browse files
authored
Merge pull request #24 from luhring/same-vpc
Same VPC
2 parents 69bcde9 + fbe2730 commit fe66d61

File tree

85 files changed

+1945
-319
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+1945
-319
lines changed

.circleci/config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ jobs:
2525
working_directory: ~/repo
2626
steps:
2727
- checkout
28-
- 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
29-
- run: go test -cover ./reach/... -test.v
28+
- 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
29+
- run: go test -cover ./reach/analyzer -test.v -acceptance -log-tf -timeout 60m
3030

3131
workflows:
3232
version: 2
33-
commit:
33+
push:
3434
jobs:
3535
- build
3636
- unit_tests

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![Go Report Card](https://goreportcard.com/badge/github.com/luhring/reach)](https://goreportcard.com/report/github.com/luhring/reach)
55
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/luhring/reach/blob/master/LICENSE)
66

7-
Reach is a tool for discovering the impact your AWS configuration has on the flow of network traffic.
7+
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.
88

99
## Getting Started
1010

@@ -50,7 +50,7 @@ $ reach web-instance database-instance
5050
$ reach web data
5151
```
5252

53-
**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.
53+
**Note:** Right now, Reach can analyze the path between two EC2 instances only when the instances are **_in the same VPC_**.
5454

5555
## Initial Setup
5656

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

104104
- ~~**Same-subnet analysis:** Between two EC2 instances within the same subnet~~ (done!)
105-
- **Same-VPC analysis:** Between two EC2 instances within the same VPC, including for EC2 instances in separate subnets
105+
- ~~**Same-VPC analysis:** Between two EC2 instances within the same VPC, including for EC2 instances in separate subnets~~ (done!)
106106
- **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.)
107107
- **Filtered analysis:** Specify a particular kind of network traffic to analyze (e.g. a single TCP port) and return results only for that filter
108108
- **Other AWS resources:** Analyze other kinds of AWS resources than just EC2 instances (e.g. ELB, Lambda, VPC endpoints, etc.)

build.sh

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,73 @@
11
#!/bin/bash
22

3-
set -ex
3+
# This script takes an argument for which OS to build for: darwin, linux, or windows.
4+
# If no argument is provided, the script builds for all three.
5+
6+
# To build for a specific version, set the `REACH_VERSION` variable to something like "2.0.1" before running the script.
7+
8+
set -e
49

510
export REACH_VERSION=${REACH_VERSION:-"0.0.0"}
11+
export SPECIFIED_OS=""
12+
13+
if [[ -z "$1" ]]
14+
then
15+
export SPECIFIED_OS="$1"
16+
fi
617

718
set -u
819

920
export CGO_ENABLED=0
1021
export GOARCH=amd64
11-
export REACH_DIR_DARWIN=$(printf "reach_%s_darwin_amd64" $REACH_VERSION)
12-
export REACH_DIR_LINUX=$(printf "reach_%s_linux_amd64" $REACH_VERSION)
13-
export REACH_DIR_WINDOWS=$(printf "reach_%s_windows_amd64" $REACH_VERSION)
1422

15-
mkdir -p ./build
23+
set -x
24+
25+
function build_for_os {
26+
local GOOS="$1"
27+
local REACH_EXECUTABLE
1628

17-
GOOS=darwin go build -a -tags netgo -o "./build/$REACH_DIR_DARWIN/reach"
18-
GOOS=linux go build -a -tags netgo -o "./build/$REACH_DIR_LINUX/reach"
19-
GOOS=windows go build -a -tags netgo -o "./build/$REACH_DIR_WINDOWS/reach.exe"
29+
if [[ "$GOOS" == "windows" ]]
30+
then
31+
REACH_EXECUTABLE="reach.exe"
32+
else
33+
REACH_EXECUTABLE="reach"
34+
fi
2035

21-
cp -nv ./LICENSE ./README.md "./build/$REACH_DIR_DARWIN"
22-
cp -nv ./LICENSE ./README.md "./build/$REACH_DIR_LINUX"
23-
cp -nv ./LICENSE ./README.md "./build/$REACH_DIR_WINDOWS"
36+
local REACH_DIR_FOR_OS
37+
REACH_DIR_FOR_OS=$(printf "reach_%s_%s_amd64" "$REACH_VERSION" "$GOOS")
38+
39+
mkdir -p "./$REACH_DIR_FOR_OS"
40+
41+
GOOS=$GOOS go build -a -v -tags netgo -o "./$REACH_DIR_FOR_OS/$REACH_EXECUTABLE" ..
42+
cp -nv ../LICENSE ../README.md "./$REACH_DIR_FOR_OS/"
43+
44+
if [[ "$GOOS" == "windows" ]]
45+
then
46+
zip "$REACH_DIR_FOR_OS.zip" "./$REACH_DIR_FOR_OS"/*
47+
openssl dgst -sha256 "./$REACH_DIR_FOR_OS.zip" >> ./checksums.txt
48+
else
49+
tar -cvzf "$REACH_DIR_FOR_OS.tar.gz" "./$REACH_DIR_FOR_OS"/*
50+
openssl dgst -sha256 "./$REACH_DIR_FOR_OS.tar.gz" >> ./checksums.txt
51+
fi
52+
}
53+
54+
rm -rf ./build
55+
mkdir -p ./build
2456

2557
pushd ./build
26-
tar -cvzf $REACH_DIR_DARWIN.tar.gz ./$REACH_DIR_DARWIN/*
27-
tar -cvzf $REACH_DIR_LINUX.tar.gz ./$REACH_DIR_LINUX/*
28-
tar -cvzf $REACH_DIR_WINDOWS.tar.gz ./$REACH_DIR_WINDOWS/*
58+
if [[ ! -z "$SPECIFIED_OS" ]]
59+
then
60+
build_for_os "$SPECIFIED_OS"
61+
else
62+
for CURRENT_OS in "darwin" "linux" "windows"
63+
do
64+
build_for_os "$CURRENT_OS"
65+
done
66+
fi
2967

30-
openssl dgst -sha256 ./$REACH_DIR_DARWIN.tar.gz >> ./checksums.txt
31-
openssl dgst -sha256 ./$REACH_DIR_LINUX.tar.gz >> ./checksums.txt
32-
openssl dgst -sha256 ./$REACH_DIR_WINDOWS.tar.gz >> ./checksums.txt
68+
set +x
3369

3470
cat ./checksums.txt
3571
popd
3672

37-
set +eux
73+
set +eu

cmd/assert.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/mgutz/ansi"
8+
9+
"github.com/luhring/reach/reach"
10+
)
11+
12+
func doAssertReachable(analysis reach.Analysis) {
13+
if analysis.PassesAssertReachable() {
14+
exitSuccessfulAssertion("source is able to reach destination")
15+
} else {
16+
exitFailedAssertion("one or more forward or return paths of network traffic is obstructed")
17+
}
18+
}
19+
20+
func doAssertNotReachable(analysis reach.Analysis) {
21+
if analysis.PassesAssertNotReachable() {
22+
exitSuccessfulAssertion("source is unable to reach destination")
23+
} else {
24+
exitFailedAssertion("source is able to send network traffic to destination")
25+
}
26+
}
27+
28+
func exitFailedAssertion(text string) {
29+
failedMessage := ansi.Color("assertion failed:", "red+b")
30+
secondaryMessage := ansi.Color(text, "red")
31+
_, _ = fmt.Fprintf(os.Stderr, "\n%v %v\n", failedMessage, secondaryMessage)
32+
33+
os.Exit(2)
34+
}
35+
36+
func exitSuccessfulAssertion(text string) {
37+
succeededMessage := ansi.Color("assertion succeeded:", "green+b")
38+
secondaryMessage := ansi.Color(text, "green")
39+
_, _ = fmt.Fprintf(os.Stderr, "\n%v %v\n", succeededMessage, secondaryMessage)
40+
41+
os.Exit(0)
42+
}

cmd/exit.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cmd
22

33
import (
44
"fmt"
5-
"github.com/mgutz/ansi"
65
"os"
76
)
87

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

1211
os.Exit(1)
1312
}
14-
15-
func exitWithFailedAssertion(text string) {
16-
failedMessage := ansi.Color("assertion failed:", "red+b")
17-
secondaryMessage := ansi.Color(text, "red")
18-
_, _ = fmt.Fprintf(os.Stderr, "\n%v %v\n", failedMessage, secondaryMessage)
19-
20-
os.Exit(2)
21-
}
22-
23-
func exitWithSuccessfulAssertion(text string) {
24-
succeededMessage := ansi.Color("assertion succeeded:", "green+b")
25-
secondaryMessage := ansi.Color(text, "green")
26-
_, _ = fmt.Fprintf(os.Stderr, "\n%v %v\n", succeededMessage, secondaryMessage)
27-
28-
os.Exit(0)
29-
}

cmd/root.go

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cmd
33
import (
44
"errors"
55
"fmt"
6-
"os"
76
"strings"
87

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

1716
const explainFlag = "explain"
1817
const vectorsFlag = "vectors"
18+
const jsonFlag = "json"
1919
const assertReachableFlag = "assert-reachable"
2020
const assertNotReachableFlag = "assert-not-reachable"
2121

2222
var explain bool
2323
var showVectors bool
24+
var outputJSON bool
2425
var assertReachable bool
2526
var assertNotReachable bool
2627

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

61-
if !explain && !showVectors {
62+
if !outputJSON && !explain && !showVectors {
6263
fmt.Printf("source: %s\ndestination: %s\n\n", source.ID, destination.ID)
6364
}
6465

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

76-
if explain {
77+
if outputJSON {
78+
fmt.Println(analysis.ToJSON())
79+
} else if explain {
7780
ex := explainer.New(*analysis)
7881
fmt.Print(ex.Explain())
7982
} else if showVectors {
8083
var vectorOutputs []string
8184

8285
for _, v := range analysis.NetworkVectors {
83-
output := ""
84-
output += v.String()
85-
86-
vectorOutputs = append(vectorOutputs, output)
86+
vectorOutputs = append(vectorOutputs, v.String())
8787
}
8888

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

94-
if len(analysis.NetworkVectors) > 1 {
94+
if len(analysis.NetworkVectors) > 1 { // handling this case with care; this view isn't optimized for multi-vector output!
9595
printMergedResultsWarning()
96+
warnIfAnyVectorHasRestrictedReturnTraffic(analysis.NetworkVectors)
97+
} else {
98+
// calculate merged return traffic
99+
mergedReturnTraffic, err := analysis.MergedReturnTraffic()
100+
if err != nil {
101+
exitWithError(err)
102+
}
103+
104+
restrictedProtocols := mergedTraffic.ProtocolsWithRestrictedReturnPath(mergedReturnTraffic)
105+
if len(restrictedProtocols) > 0 {
106+
found, warnings := explainer.WarningsFromRestrictedReturnPath(restrictedProtocols)
107+
if found {
108+
fmt.Print("\n" + warnings + "\n")
109+
}
110+
}
96111
}
97112
}
98113

99-
// fmt.Println(analysis.ToJSON()) // for debugging
100-
101-
const canReach = "source is able to reach destination"
102-
const cannotReach = "source is unable to reach destination"
103-
104114
if assertReachable {
105-
if mergedTraffic.None() {
106-
exitWithFailedAssertion(cannotReach)
107-
} else {
108-
exitWithSuccessfulAssertion(canReach)
109-
}
115+
doAssertReachable(*analysis)
110116
}
111117

112118
if assertNotReachable {
113-
if mergedTraffic.None() {
114-
exitWithSuccessfulAssertion(cannotReach)
115-
} else {
116-
exitWithFailedAssertion(canReach)
117-
}
119+
doAssertNotReachable(*analysis)
118120
}
119121
},
120122
}
@@ -129,11 +131,7 @@ func Execute() {
129131
func init() {
130132
rootCmd.Flags().BoolVar(&explain, explainFlag, false, "explain how the configuration was analyzed")
131133
rootCmd.Flags().BoolVar(&showVectors, vectorsFlag, false, "show allowed traffic in terms of network vectors")
134+
rootCmd.Flags().BoolVar(&outputJSON, jsonFlag, false, "output full analysis as JSON (overrides other display flags)")
132135
rootCmd.Flags().BoolVar(&assertReachable, assertReachableFlag, false, "exit non-zero if no traffic is allowed from source to destination")
133136
rootCmd.Flags().BoolVar(&assertNotReachable, assertNotReachableFlag, false, "exit non-zero if any traffic can reach destination from source")
134137
}
135-
136-
func printMergedResultsWarning() {
137-
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"
138-
_, _ = fmt.Fprint(os.Stderr, "\n"+mergedResultsWarning)
139-
}

cmd/warnings.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/luhring/reach/reach"
8+
)
9+
10+
func printMergedResultsWarning() {
11+
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"
12+
_, _ = fmt.Fprint(os.Stderr, "\n"+mergedResultsWarning)
13+
}
14+
15+
func warnIfAnyVectorHasRestrictedReturnTraffic(vectors []reach.NetworkVector) {
16+
for _, v := range vectors {
17+
if !v.ReturnTraffic.All() {
18+
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"
19+
_, _ = fmt.Fprintf(os.Stderr, "\n"+restrictedVectorReturnTraffic)
20+
21+
return
22+
}
23+
}
24+
}

reach/acceptance/data/tf/ec2_instance_source_and_destination.tf

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)