blob: fc0a7d5d1d4d0e33d0a640b0b4af72a4b9d407e6 [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 }
radexe36beba2023-10-11 00:41:48 +020037 if ad.Regexp {
38 if err := s.ingress.allowRegexp(ad.Namespace, ad.Dns); err != nil {
39 return nil, fmt.Errorf("config entry (regexp) %d: %v", i, err)
40 }
41 } else {
42 if err := s.ingress.allow(ad.Namespace, ad.Dns); err != nil {
43 return nil, fmt.Errorf("config entry %d: %v", i, err)
44 }
Serge Bazanskic6118642021-01-31 01:17:38 +010045 }
46 glog.Infof("Ingress: allowing %s in %s", ad.Dns, ad.Namespace)
47 }
Serge Bazanskic1f37252023-06-19 21:56:29 +000048 s.ingress.anythingGoesNamespaces = cfg.AnythingGoesNamespace
Serge Bazanskic6118642021-01-31 01:17:38 +010049 return &s, nil
50}
51
52// handler is the main HTTP handler of the admitomatic service. It servers the
53// AdmissionReview API, and is called by the Kubernetes API server to
54// permit/deny creation/updating of resources.
55func (s *service) handler(w http.ResponseWriter, r *http.Request) {
56 var body []byte
57 if r.Body != nil {
58 if data, err := ioutil.ReadAll(r.Body); err == nil {
59 body = data
60 }
61 }
62
63 if r.Method != "POST" {
64 glog.Errorf("%s %s: invalid method", r.Method, r.URL)
65 return
66 }
67
68 contentType := r.Header.Get("Content-Type")
69 if contentType != "application/json" {
70 glog.Errorf("%s %s: invalid content-type", r.Method, r.URL)
71 return
72 }
73
74 var review admission.AdmissionReview
75 if err := json.Unmarshal(body, &review); err != nil {
76 glog.Errorf("%s %s: cannot decode: %v", r.Method, r.URL, err)
77 return
78 }
79
80 if review.Kind != "AdmissionReview" {
81 glog.Errorf("%s %s: invalid Kind (%q)", r.Method, r.URL, review.Kind)
82 return
83 }
84
85 var err error
86 req := review.Request
87 resp := &admission.AdmissionResponse{
88 UID: req.UID,
89 Allowed: true,
90 }
91 switch {
92 case req.Kind.Group == "networking.k8s.io" && req.Kind.Kind == "Ingress":
93 resp, err = s.ingress.admit(req)
94 if err != nil {
95 glog.Errorf("%s %s %s: %v", req.Operation, req.Name, req.Namespace, err)
96 // Fail safe.
97 // TODO(q3k): monitor this?
98 resp = &admission.AdmissionResponse{
99 UID: req.UID,
100 Allowed: false,
101 Result: &meta.Status{
102 Code: 500,
103 Message: "admitomatic: internal server error",
104 },
105 }
106 }
107 }
108
109 glog.Infof("%s %s %s in %s: %v (%v)", req.Operation, req.Kind.Kind, req.Name, req.Namespace, resp.Allowed, resp.Result)
110 review.Response = resp
111 json.NewEncoder(w).Encode(review)
112}