-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy paths3-webhook.go
204 lines (177 loc) · 5.34 KB
/
s3-webhook.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
195
196
197
198
199
200
201
202
203
204
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
)
var actionScript string
// Generate hmac_sha256_hex
func HmacSha256hex(message string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
return hex.EncodeToString(h.Sum(nil))
}
// Generate hmac_sha256
func HmacSha256(message string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
return string(h.Sum(nil))
}
// Send subscription confirmation
func SubscriptionConfirmation(w http.ResponseWriter, req *http.Request, body []byte) {
type S3Request struct {
Timestamp string `json:"Timestamp"`
Type string `json:"Type"`
Message string `json:"Message"`
TopicArn string `json:"TopicArn"`
SignatureVersion int `json:"SignatureVersion"`
Token string `json:"Token"`
}
var s3req S3Request
// Decode json fields
err := json.Unmarshal(body, &s3req)
if err != nil {
// bad JSON or unrecognized json field
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Log token and uri
fullURI := "http://" + req.Host + req.URL.Path
log.Printf("Got timestamp: %s TopicArn: %s Token: %s URL: %s\n", s3req.Timestamp, s3req.TopicArn, s3req.Token, fullURI)
// Construct sinature responce
signature := HmacSha256hex(fullURI, HmacSha256(s3req.TopicArn, HmacSha256(s3req.Timestamp, s3req.Token)))
log.Printf("Generate responce signature: %s \n", signature)
// Send responce
type Signature struct {
Signature string `json:"signature"`
}
var s Signature
s.Signature = signature
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(s)
}
// Send subscription confirmation
func GotRecords(w http.ResponseWriter, req *http.Request, body []byte) {
// struct for call from s3
type S3Request struct {
Records []struct {
S3 struct {
Object struct {
ETag string `json:"eTag"`
Sequencer int `json:"sequencer"`
Key string `json:"key"`
Size int `json:"size"`
} `json:"object"`
ConfigurationID string `json:"configurationId"`
Bucket struct {
Name string `json:"name"`
OwnerIdentity struct {
PrincipalID string `json:"principalId"`
} `json:"ownerIdentity"`
} `json:"bucket"`
S3SchemaVersion string `json:"s3SchemaVersion"`
} `json:"s3"`
EventVersion string `json:"eventVersion"`
RequestParameters struct {
SourceIPAddress string `json:"sourceIPAddress"`
} `json:"requestParameters"`
UserIdentity struct {
PrincipalID string `json:"principalId"`
} `json:"userIdentity"`
EventName string `json:"eventName"`
AwsRegion string `json:"awsRegion"`
EventSource string `json:"eventSource"`
ResponseElements struct {
XAmzRequestID string `json:"x-amz-request-id"`
} `json:"responseElements"`
} `json:"Records"`
}
var s3req S3Request
// Decode json fields
err := json.Unmarshal(body, &s3req)
if err != nil {
// bad JSON or unrecognized json field
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var action string
// Get all records
for _, record := range s3req.Records {
// Check add action
if strings.Contains(record.EventName, "ObjectCreated") || strings.Contains(record.EventName, "PutObject") || strings.Contains(record.EventName, "PutObjectCopy") {
action = "copy"
} else
// check remove action
if strings.Contains(record.EventName, "ObjectRemoved") || strings.Contains(record.EventName, "DeleteObject") {
action = "delete"
}
if action == "copy" || action == "delete" {
scriptPath, err := exec.LookPath(actionScript)
if err != nil {
// Path not found
http.Error(w, err.Error(), http.StatusBadRequest)
log.Println(err.Error())
return
}
// Exec struct
cmdScript := &exec.Cmd{
Path: scriptPath,
Args: []string{scriptPath, record.S3.Bucket.Name, record.S3.Object.Key, action},
Stdout: os.Stdout,
Stderr: os.Stdout,
}
// Run script
err = cmdScript.Run()
if err != nil {
// Script error
http.Error(w, err.Error(), http.StatusBadRequest)
log.Println(err.Error())
return
}
}
}
}
// Liveness probe
func Ping(w http.ResponseWriter, req *http.Request) {
// log request
log.Printf("[%s] incoming HTTP Ping request from %s\n", req.Method, req.RemoteAddr)
fmt.Fprintf(w, "Pong\n")
}
//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {
// Read body
body, err := ioutil.ReadAll(req.Body)
defer req.Body.Close()
if err != nil {
http.Error(w, err.Error(), 500)
return
}
// log request
log.Printf("[%s] incoming HTTP request from %s\n", req.Method, req.RemoteAddr)
// check if we got subscription confirmation request
if strings.Contains(string(body), "\"Type\":\"SubscriptionConfirmation\"") {
SubscriptionConfirmation(w, req, body)
} else {
GotRecords(w, req, body)
}
}
func main() {
// get command line args
bindPort := flag.Int("port", 80, "number between 1-65535")
bindAddr := flag.String("address", "", "ip address in dot format")
flag.StringVar(&actionScript, "script", "", "external script to execute")
flag.Parse()
http.HandleFunc("/ping", Ping)
http.HandleFunc("/webhook", Webhook)
log.Fatal(http.ListenAndServe(*bindAddr+":"+strconv.Itoa(*bindPort), nil))
}