blob: 8fa2698825777045220151607855056c7778524d [file] [log] [blame]
Serge Bazanskic6118642021-01-31 01:17:38 +01001package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "io/ioutil"
7 "net/http"
8
9 "github.com/golang/glog"
10 "google.golang.org/protobuf/encoding/prototext"
11 admission "k8s.io/api/admission/v1beta1"
12 meta "k8s.io/apimachinery/pkg/apis/meta/v1"
13
14 pb "code.hackerspace.pl/hscloud/cluster/admitomatic/config"
15)
16
17type service struct {
18 ingress ingressFilter
19}
20
21// newService creates an admitomatic service from a given prototext config.
22func newService(configuration []byte) (*service, error) {
23 var cfg pb.Config
24 if err := prototext.Unmarshal(configuration, &cfg); err != nil {
25 return nil, fmt.Errorf("parsing config: %v", err)
26 }
27
28 s := service{}
29
30 for i, ad := range cfg.AllowDomain {
31 if ad.Namespace == "" {
32 ad.Namespace = "default"
33 }
34 if ad.Dns == "" {
35 return nil, fmt.Errorf("config entry %d: dns must be set", i)
36 }
37 if err := s.ingress.allow(ad.Namespace, ad.Dns); err != nil {
38 return nil, fmt.Errorf("config entry %d: %v", i, err)
39 }
40 glog.Infof("Ingress: allowing %s in %s", ad.Dns, ad.Namespace)
41 }
42 return &s, nil
43}
44
45// handler is the main HTTP handler of the admitomatic service. It servers the
46// AdmissionReview API, and is called by the Kubernetes API server to
47// permit/deny creation/updating of resources.
48func (s *service) handler(w http.ResponseWriter, r *http.Request) {
49 var body []byte
50 if r.Body != nil {
51 if data, err := ioutil.ReadAll(r.Body); err == nil {
52 body = data
53 }
54 }
55
56 if r.Method != "POST" {
57 glog.Errorf("%s %s: invalid method", r.Method, r.URL)
58 return
59 }
60
61 contentType := r.Header.Get("Content-Type")
62 if contentType != "application/json" {
63 glog.Errorf("%s %s: invalid content-type", r.Method, r.URL)
64 return
65 }
66
67 var review admission.AdmissionReview
68 if err := json.Unmarshal(body, &review); err != nil {
69 glog.Errorf("%s %s: cannot decode: %v", r.Method, r.URL, err)
70 return
71 }
72
73 if review.Kind != "AdmissionReview" {
74 glog.Errorf("%s %s: invalid Kind (%q)", r.Method, r.URL, review.Kind)
75 return
76 }
77
78 var err error
79 req := review.Request
80 resp := &admission.AdmissionResponse{
81 UID: req.UID,
82 Allowed: true,
83 }
84 switch {
85 case req.Kind.Group == "networking.k8s.io" && req.Kind.Kind == "Ingress":
86 resp, err = s.ingress.admit(req)
87 if err != nil {
88 glog.Errorf("%s %s %s: %v", req.Operation, req.Name, req.Namespace, err)
89 // Fail safe.
90 // TODO(q3k): monitor this?
91 resp = &admission.AdmissionResponse{
92 UID: req.UID,
93 Allowed: false,
94 Result: &meta.Status{
95 Code: 500,
96 Message: "admitomatic: internal server error",
97 },
98 }
99 }
100 }
101
102 glog.Infof("%s %s %s in %s: %v (%v)", req.Operation, req.Kind.Kind, req.Name, req.Namespace, resp.Allowed, resp.Result)
103 review.Response = resp
104 json.NewEncoder(w).Encode(review)
105}