first commit

This commit is contained in:
ston1th 2019-11-30 12:09:41 +01:00
commit ad18bb8ad3
9 changed files with 309 additions and 0 deletions

12
Dockerfile Normal file
View file

@ -0,0 +1,12 @@
FROM golang:alpine as builder
RUN adduser -D -g '' appuser
RUN mkdir /build
ADD . /build/
WORKDIR /build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -gcflags 'all=-e' -ldflags "-s -w" -o main .
FROM scratch
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /build/main /main
USER appuser
EXPOSE 8080
ENTRYPOINT ["/main"]

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
Copyright (C) 2018 Marius Schellenberger
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The names of the authors and/or contributors may not be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ston1th BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

35
README.md Normal file
View file

@ -0,0 +1,35 @@
# webhook-interseptor
The Webhook Interseptor checks for specific conditions when a webhook is received.
This way we can trigger cleanups when the PR is closed.
# HTTP Headers
## Push Event
```
X-Interseptor-Ref: refs/heads/master
```
## Pull-Request Event
```
X-Interseptor-Action: [create|delete]
```
## Issue Comment Event
**TBD**
```
X-Interseptor-Comment: /test
```
# Build and run
```
export KO_DOCKER_REPO='gcr.io/my-gcloud-project-name'
ko apply -f config/
```

4
config/00-namespace.yaml Normal file
View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: webhook-interseptor

View file

@ -0,0 +1,26 @@
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: webhook-interseptor
spec:
privileged: false
allowPrivilegeEscalation: false
volumes:
- 'secret'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: 'RunAsAny'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535
fsGroup:
rule: 'MustRunAs'
ranges:
- min: 1
max: 65535

28
config/02-sa.yaml Normal file
View file

@ -0,0 +1,28 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: webhook-interseptor
namespace: webhook-interseptor
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: webhook-interseptor
namespace: webhook-interseptor
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: webhook-interseptor
namespace: webhook-interseptor
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: webhook-interseptor
subjects:
- kind: ServiceAccount
name: webhook-interseptor

28
config/deployment.yaml Normal file
View file

@ -0,0 +1,28 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: webhook-interseptor
namespace: webhook-interseptor
labels:
app: webhook-interseptor
spec:
replicas: 1
selector:
matchLabels:
app: webhook-interseptor
template:
metadata:
labels:
app: webhook-interseptor
spec:
serviceAccountName: webhook-interseptor
containers:
- name: webhook-interseptor
image: git.giftfish.de/ston1th/webhook-interseptor
resources:
requests:
cpu: 100m
memory: 128Mi
imagePullPolicy: Always
ports:
- containerPort: 8080

15
config/service.yaml Normal file
View file

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: webhook-interseptor
namespace: webhook-interseptor
labels:
app: webhook-interseptor
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 8080
selector:
app: webhook-interseptor

137
main.go Normal file
View file

@ -0,0 +1,137 @@
package main
import (
"log"
"net/http"
"encoding/json"
"time"
"io/ioutil"
)
var version string
type PR struct {
Action string `json:"action"`
}
func prHandler(w http.ResponseWriter, r *http.Request) {
var pr PR
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("debug headers: %#v", r.Header)
log.Printf("debug body: %s", string(body))
err = json.Unmarshal(body, &pr)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
switch pr.Action {
case "opened", "reopened", "synchronize":
if r.Header.Get("X-Interseptor-Action") == "create" {
w.Write(body)
return
}
case "closed":
if r.Header.Get("X-Interseptor-Action") == "delete" {
w.Write(body)
return
}
}
w.WriteHeader(http.StatusBadRequest)
}
type Push struct {
Ref string `json:"ref"`
}
func pushHandler(w http.ResponseWriter, r *http.Request) {
var p Push
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("debug headers: %#v", r.Header)
log.Printf("debug: %s", string(body))
err = json.Unmarshal(body, &p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if r.Header.Get("X-Interseptor-Ref") == p.Ref {
w.Write(body)
return
}
w.WriteHeader(http.StatusBadRequest)
}
type CommentBody struct {
Comment Comment `json:"comment"`
}
type Comment struct {
Body string `json:"body"`
}
func commentHandler(w http.ResponseWriter, r *http.Request) {
var c CommentBody
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("debug headers: %#v", r.Header)
log.Printf("debug: %s", string(body))
err = json.Unmarshal(body, &c)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if r.Header.Get("X-Interseptor-Comment") == c.Comment.Body {
w.Write(body)
return
}
w.WriteHeader(http.StatusBadRequest)
}
func dumpHandler(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("debug headers: %#v", r.Header)
log.Printf("debug: %s", string(body))
w.WriteHeader(http.StatusBadRequest)
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
event := r.Header.Get("X-GitHub-Event")
log.Printf("%s %s %s: %s", r.RemoteAddr, r.Method, r.URL, event)
switch event {
case "pull_request":
prHandler(w, r)
case "push":
pushHandler(w, r)
//case "issue_comment":
// commentHandler(w, r)
default:
dumpHandler(w, r)
}
})
s := &http.Server{
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
Addr: ":8080",
}
log.Fatal(s.ListenAndServe())
}