forked from rlister/let-me-in
/
let-me-in.go
194 lines (159 loc) · 4.75 KB
/
let-me-in.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package main
import (
"flag"
"fmt"
"github.com/rlister/let-me-in/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws"
"github.com/rlister/let-me-in/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr"
"github.com/rlister/let-me-in/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/ec2"
"io/ioutil"
"net/http"
"os"
"os/exec"
)
var VERSION = "dev"
// error handler
func check(e error) {
if e != nil {
panic(e.Error())
}
}
// convert group names to ids, which are needed for vpcs
func getGroupIds(client *ec2.EC2, names []string) []*string {
// get names as array of aws.String objects
values := make([]*string, len(names))
for i, name := range names {
values[i] = aws.String(name)
}
// request params as filter for names
params := &ec2.DescribeSecurityGroupsInput{
Filters: []*ec2.Filter{
{
Name: aws.String("group-name"),
Values: values,
},
},
}
// send request
resp, err := client.DescribeSecurityGroups(params)
check(err)
// return ids
for i, group := range resp.SecurityGroups {
values[i] = group.GroupId
}
return values
}
// call authorize for all the requested security groups
func authorizeGroups(client *ec2.EC2, ids []*string, protocol *string, port *int, cidr *string) {
for _, id := range ids {
authorizeGroup(client, id, protocol, port, cidr)
}
}
// authorize given security group
func authorizeGroup(client *ec2.EC2, id *string, protocol *string, port *int, cidr *string) {
// make the request
params := &ec2.AuthorizeSecurityGroupIngressInput{
GroupId: aws.String(*id),
IpProtocol: aws.String(*protocol),
FromPort: aws.Int64(int64(*port)),
ToPort: aws.Int64(int64(*port)),
CidrIp: aws.String(*cidr),
}
_, err := client.AuthorizeSecurityGroupIngress(params)
// be idempotent, i.e. skip error if this permission already exists in group
if err != nil {
if err.(awserr.Error).Code() != "InvalidPermission.Duplicate" {
panic(err)
}
}
}
// call revoke for all the requested security groups
func revokeGroups(client *ec2.EC2, ids []*string, protocol *string, port *int, cidr *string) {
for _, id := range ids {
revokeGroup(client, id, protocol, port, cidr)
}
}
// revoke permission from security group
func revokeGroup(client *ec2.EC2, id *string, protocol *string, port *int, cidr *string) {
// make the request
params := &ec2.RevokeSecurityGroupIngressInput{
GroupId: aws.String(*id),
IpProtocol: aws.String(*protocol),
FromPort: aws.Int64(int64(*port)),
ToPort: aws.Int64(int64(*port)),
CidrIp: aws.String(*cidr),
}
_, err := client.RevokeSecurityGroupIngress(params)
// be idempotent, i.e. skip error if this permission does not exist in group
if err != nil {
if err.(awserr.Error).Code() != "InvalidPermission.NotFound" {
panic(err)
}
}
}
// get my external-facing IP as a string
func getMyIp(ident string) string {
resp, err := http.Get(ident)
check(err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
check(err)
return string(body)
}
// parse cmdline argv into args before '--' and cmd to exec afterwards
func parseArgs(argv []string) ([]string, []string) {
// if -- found, return slices before and after
for i, arg := range flag.Args() {
if arg == "--" {
return argv[0:i], argv[i+1:]
}
}
// no -- found, return all of argv
return argv, nil
}
func main() {
// cmdline options
versionFlag := flag.Bool("v", false, "show version and exit")
cidr := flag.String("c", "", "set a specific cidr block (default: current public ip)")
protocol := flag.String("P", "TCP", "protocol to allow (default: TCP)")
port := flag.Int("p", 22, "port number to allow (default: 22)")
revoke := flag.Bool("r", false, "revoke access from security groups (default: false)")
flag.Parse()
// show version and exit
if *versionFlag {
fmt.Printf("let-me-in %v\n", VERSION)
return
}
// if cidr not given get ip from external service
if *cidr == "" {
ident := os.Getenv("LMI_IDENT_URL")
if ident == "" {
ident = "http://ident.me/"
}
ip := getMyIp(ident) + "/32"
cidr = &ip
}
// configure aws-sdk from AWS_* env vars
client := ec2.New(&aws.Config{})
// get security group names and any command to exec after '--'
groups, cmd := parseArgs(flag.Args())
// convert security group names to ids for vpc
ids := getGroupIds(client, groups)
// revoke on -r option
if *revoke {
revokeGroups(client, ids, protocol, port, cidr)
} else {
authorizeGroups(client, ids, protocol, port, cidr)
// exec any command after '--', then revoke
if cmd != nil {
c := exec.Command(cmd[0], cmd[1:]...)
c.Stdout = os.Stdout
c.Stdin = os.Stdin
c.Stderr = os.Stderr
err := c.Run()
if err != nil {
fmt.Println(err) // show err and keep running so we hit revoke below
}
revokeGroups(client, ids, protocol, port, cidr)
}
}
}