From 065a72dbd88d6905deb217a186bc9cbd3c9b1a64 Mon Sep 17 00:00:00 2001 From: dcseifert Date: Fri, 8 Nov 2019 17:13:26 +0100 Subject: [PATCH 1/3] Added basic implementation of envoy-proxy --- go.mod | 3 + go.sum | 42 +++++++ internal/pkg/api/envoy-proxy.go | 193 ++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 internal/pkg/api/envoy-proxy.go diff --git a/go.mod b/go.mod index 562207f..25ecf22 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117 // indirect + github.com/envoyproxy/go-control-plane v0.9.0 github.com/fsnotify/fsnotify v1.4.7 github.com/ghodss/yaml v1.0.0 // indirect github.com/go-sql-driver/mysql v1.4.1 @@ -19,6 +20,8 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect github.com/sirupsen/logrus v1.4.2 github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b // indirect + google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 + google.golang.org/grpc v1.25.0 gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 ) diff --git a/go.sum b/go.sum index f7c6f13..d9439cd 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -10,10 +12,16 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0 h1:67WMNTvGrl7V1dWdKCeTwxDr7nio9clKoTlLhwIPnT4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -27,11 +35,15 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -67,6 +79,8 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= @@ -90,15 +104,26 @@ github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mo golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -109,14 +134,29 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c h1:+EXw7AwNOKzPFXMZ1yNjO40aW golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.3 h1:hvZejVcIxAKHR8Pq2gXaDggf6CWT1QEqO+JEBeOKCG8= google.golang.org/appengine v1.6.3/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.0 h1:ItERT+UbGdX+s4u+nQNlVM/Q7cbmf7icKfvzbWqVtq0= +google.golang.org/grpc v1.25.0/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= @@ -126,3 +166,5 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 h1:B0J02caTR6tpSJozBJyiAzT6CtBzjclw4pgm9gg8Ys0= gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/pkg/api/envoy-proxy.go b/internal/pkg/api/envoy-proxy.go new file mode 100644 index 0000000..acf2edc --- /dev/null +++ b/internal/pkg/api/envoy-proxy.go @@ -0,0 +1,193 @@ +package api + +import ( + "context" + "fmt" + "net" + "net/http" + "strings" + "sync" + "time" + + "github.com/Foundato/kelon/configs" + "github.com/Foundato/kelon/pkg/api" + "github.com/Foundato/kelon/pkg/opa" + ext_authz "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" + "google.golang.org/genproto/googleapis/rpc/code" + rpc_status "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +// Config represents the plugin configuration. +type EnvoyConfig struct { + Addr string `json:"addr"` + DryRun bool `json:"dry-run"` + EnableReflection bool `json:"enable-reflection"` +} + +type envoyExtAuthzGrpcServer struct { + cfg EnvoyConfig + server *grpc.Server + compiler *opa.PolicyCompiler + preparedQueryDoOnce *sync.Once +} + +type envoyProxy struct { + configured bool + appConf *configs.AppConfig + config *api.ClientProxyConfig + envoy *envoyExtAuthzGrpcServer +} + +// Implements api.ClientProxy by providing OPA's Data-REST-API. +func NewEnvoyProxy(config EnvoyConfig) api.ClientProxy { + if config.Addr == "" { + log.Warnln("EnvoyProxy was initialized with default properties! You may have missed some arguments when creating it!") + config.Addr = ":9191" + config.DryRun = false + config.EnableReflection = false + } + + return &envoyProxy{ + configured: false, + appConf: nil, + config: nil, + envoy: &envoyExtAuthzGrpcServer{ + cfg: config, + server: nil, + compiler: nil, + preparedQueryDoOnce: nil, + }, + } +} + +// See Configure() of api.ClientProxy +func (proxy *envoyProxy) Configure(appConf *configs.AppConfig, serverConf *api.ClientProxyConfig) error { + // Configure subcomponents + if serverConf.Compiler == nil { + return errors.New("EnvoyProxy: Compiler not configured! ") + } + compiler := *serverConf.Compiler + if err := compiler.Configure(appConf, &serverConf.PolicyCompilerConfig); err != nil { + return err + } + + // Assign variables + proxy.appConf = appConf + proxy.config = serverConf + proxy.envoy.compiler = serverConf.Compiler + proxy.configured = true + log.Infoln("Configured EnvoyProxy") + return nil +} + +// See Start() of api.ClientProxy +func (proxy *envoyProxy) Start() error { + if !proxy.configured { + return errors.New("RestProxy was not configured! Please call Configure(). ") + } + + // Init grpc server + proxy.envoy.server = grpc.NewServer() + // Register Authorization Server + ext_authz.RegisterAuthorizationServer(proxy.envoy.server, proxy.envoy) + + // Register reflection service on gRPC server + if proxy.envoy.cfg.EnableReflection { + reflection.Register(proxy.envoy.server) + } + + return proxy.envoy.Start(context.Background()) +} + +// See Stop() of api.ClientProxy +func (proxy *envoyProxy) Stop(deadline time.Duration) error { + if proxy.envoy.server == nil { + return errors.New("RestProxy has not bin started yet") + } + ctx, cancel := context.WithTimeout(context.Background(), deadline) + defer cancel() + + proxy.envoy.Stop(ctx) + return nil +} + +func (p *envoyExtAuthzGrpcServer) Start(ctx context.Context) error { + go p.listen() + return nil +} + +func (p *envoyExtAuthzGrpcServer) Stop(ctx context.Context) { + p.server.Stop() +} + +func (p *envoyExtAuthzGrpcServer) Reconfigure(ctx context.Context, config interface{}) { +} + +func (p *envoyExtAuthzGrpcServer) listen() { + // The listener is closed automatically by Serve when it returns. + l, err := net.Listen("tcp", p.cfg.Addr) + if err != nil { + log.WithField("err", err).Fatal("EnvoyProxy: Unable to create listener.") + } + + log.WithFields(log.Fields{ + "addr": p.cfg.Addr, + "dry-run": p.cfg.DryRun, + "enable-reflection": p.cfg.EnableReflection, + }).Info("EnvoyProxy: Starting gRPC server.") + + if err := p.server.Serve(l); err != nil { + log.WithField("err", err).Fatal("EnvoyProxy: Listener failed.") + } + + log.Info("EnvoyProxy: Listener exited.") +} + +func (p *envoyExtAuthzGrpcServer) Check(ctx context.Context, req *ext_authz.CheckRequest) (*ext_authz.CheckResponse, error) { + // Rebuild http request + r := req.GetAttributes().GetRequest().GetHttp() + httpRequest, err := http.NewRequest(r.GetMethod(), fmt.Sprintf("%s%s?%s", r.GetHost(), r.GetPath(), r.GetQuery()), strings.NewReader(r.GetBody())) + if err != nil { + return nil, errors.Wrap(err, "EnvoyProxy: Unable to reconstruct HTTP-Request") + } + // Set headers + for headerKey, headerValue := range r.GetHeaders() { + httpRequest.Header.Set(headerKey, headerValue) + } + + decision, err := (*p.compiler).Process(httpRequest) + if err != nil { + return nil, errors.Wrap(err, "EnvoyProxy: Error during request compilation") + } + + resp := &ext_authz.CheckResponse{} + resp.Status = &rpc_status.Status{Code: int32(code.Code_PERMISSION_DENIED)} + if decision { + resp.Status = &rpc_status.Status{Code: int32(code.Code_OK)} + } + + if log.IsLevelEnabled(log.DebugLevel) { + log.WithFields(log.Fields{ + "dry-run": p.cfg.DryRun, + "decision": decision, + "err": err, + }).Debug("Returning policy decision.") + } + + // If dry-run mode, override the Status code to unconditionally Allow the request + // DecisionLogging should reflect what "would" have happened + if p.cfg.DryRun { + if resp.Status.Code != int32(code.Code_OK) { + resp.Status = &rpc_status.Status{Code: int32(code.Code_OK)} + resp.HttpResponse = &ext_authz.CheckResponse_OkResponse{ + OkResponse: &ext_authz.OkHttpResponse{}, + } + } + } + + return resp, nil +} From a969d935e965e5caa72288bd339c3d3304954576 Mon Sep 17 00:00:00 2001 From: dcseifert Date: Fri, 8 Nov 2019 17:55:16 +0100 Subject: [PATCH 2/3] Finished envoy-proxy with simple positive test --- internal/pkg/api/envoy-proxy.go | 14 +++- internal/pkg/api/envoy-proxy_test.go | 103 +++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 internal/pkg/api/envoy-proxy_test.go diff --git a/internal/pkg/api/envoy-proxy.go b/internal/pkg/api/envoy-proxy.go index acf2edc..e9ba3e0 100644 --- a/internal/pkg/api/envoy-proxy.go +++ b/internal/pkg/api/envoy-proxy.go @@ -115,15 +115,18 @@ func (proxy *envoyProxy) Stop(deadline time.Duration) error { return nil } +// Start the underlying grpc-server func (p *envoyExtAuthzGrpcServer) Start(ctx context.Context) error { go p.listen() return nil } +// Stop the underlying grpc-server func (p *envoyExtAuthzGrpcServer) Stop(ctx context.Context) { p.server.Stop() } +// Reconfigure the underlying grpc-server (Unused! Just to be conform with the interface) func (p *envoyExtAuthzGrpcServer) Reconfigure(ctx context.Context, config interface{}) { } @@ -147,10 +150,19 @@ func (p *envoyExtAuthzGrpcServer) listen() { log.Info("EnvoyProxy: Listener exited.") } +// Check a new incoming request func (p *envoyExtAuthzGrpcServer) Check(ctx context.Context, req *ext_authz.CheckRequest) (*ext_authz.CheckResponse, error) { // Rebuild http request r := req.GetAttributes().GetRequest().GetHttp() - httpRequest, err := http.NewRequest(r.GetMethod(), fmt.Sprintf("%s%s?%s", r.GetHost(), r.GetPath(), r.GetQuery()), strings.NewReader(r.GetBody())) + protocol := "http" + if strings.HasPrefix(r.Protocol, "HTTPS") { + protocol = "https" + } + stringURL := fmt.Sprintf("%s://%s%s", protocol, r.GetHost(), r.GetPath()) + if r.Query != "" { + stringURL = fmt.Sprintf("%s?%s", stringURL, r.GetQuery()) + } + httpRequest, err := http.NewRequest(r.GetMethod(), stringURL, strings.NewReader(r.GetBody())) if err != nil { return nil, errors.Wrap(err, "EnvoyProxy: Unable to reconstruct HTTP-Request") } diff --git a/internal/pkg/api/envoy-proxy_test.go b/internal/pkg/api/envoy-proxy_test.go new file mode 100644 index 0000000..14c8292 --- /dev/null +++ b/internal/pkg/api/envoy-proxy_test.go @@ -0,0 +1,103 @@ +package api + +import ( + "context" + "errors" + "net/http" + "testing" + + "github.com/Foundato/kelon/configs" + "github.com/Foundato/kelon/pkg/api" + "github.com/Foundato/kelon/pkg/opa" + "github.com/open-policy-agent/opa/util" + + ext_authz "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2" + "google.golang.org/genproto/googleapis/rpc/code" +) + +const exampleAllowedRequest = `{ + "attributes": { + "request": { + "http": { + "id": "13359530607844510314", + "method": "GET", + "headers": { + ":authority": "192.168.99.100:31380", + ":method": "GET", + ":path": "/api/v1/products", + "accept": "*/*", + "authorization": "Basic Ym9iOnBhc3N3b3Jk", + "content-length": "0", + "user-agent": "curl/7.54.0", + "x-b3-sampled": "1", + "x-b3-spanid": "537f473f27475073", + "x-b3-traceid": "537f473f27475073", + "x-envoy-internal": "true", + "x-forwarded-for": "172.17.0.1", + "x-forwarded-proto": "http", + "x-istio-attributes": "Cj4KE2Rlc3RpbmF0aW9uLnNlcnZpY2USJxIlcHJvZHVjdHBhZ2UuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbApPCgpzb3VyY2UudWlkEkESP2t1YmVybmV0ZXM6Ly9pc3Rpby1pbmdyZXNzZ2F0ZXdheS02Nzk5NWM0ODZjLXFwOGpyLmlzdGlvLXN5c3RlbQpBChdkZXN0aW5hdGlvbi5zZXJ2aWNlLnVpZBImEiRpc3RpbzovL2RlZmF1bHQvc2VydmljZXMvcHJvZHVjdHBhZ2UKQwoYZGVzdGluYXRpb24uc2VydmljZS5ob3N0EicSJXByb2R1Y3RwYWdlLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwKKgodZGVzdGluYXRpb24uc2VydmljZS5uYW1lc3BhY2USCRIHZGVmYXVsdAopChhkZXN0aW5hdGlvbi5zZXJ2aWNlLm5hbWUSDRILcHJvZHVjdHBhZ2U=", + "x-request-id": "92a6c0f7-0250-944b-9cfc-ae10cbcedd8e" + }, + "path": "/api/v1/products", + "host": "192.168.99.100:31380", + "protocol": "HTTP/1.1", + "body": "{\"firstname\": \"foo\", \"lastname\": \"bar\"}" + } + } + } + }` + +type mockCompiler struct { + failOnConfigure bool + failOnProcess bool + decision bool +} + +func (c mockCompiler) Configure(appConfig *configs.AppConfig, compConfig *opa.PolicyCompilerConfig) error { + if c.failOnConfigure { + return errors.New("Mock config failure ") + } + return nil +} + +func (c mockCompiler) Process(request *http.Request) (bool, error) { + if c.failOnProcess { + return false, errors.New("Mock process failure ") + } + return c.decision, nil +} + +func TestCheckAllow(t *testing.T) { + // Example Envoy Check Request for input: + // curl --user bob:password -o /dev/null -s -w "%{http_code}\n" http://${GATEWAY_URL}/api/v1/products + + var req ext_authz.CheckRequest + if err := util.Unmarshal([]byte(exampleAllowedRequest), &req); err != nil { + panic(err) + } + + proxy := NewEnvoyProxy(EnvoyConfig{ + Addr: ":9191", + DryRun: false, + EnableReflection: true, + }) + + //nolint:gosimple + var compiler opa.PolicyCompiler + compiler = mockCompiler{ + failOnConfigure: false, + failOnProcess: false, + decision: true, + } + _ = proxy.Configure(&configs.AppConfig{}, &api.ClientProxyConfig{Compiler: &compiler}) + server, _ := proxy.(*envoyProxy) + + ctx := context.Background() + output, err := server.envoy.Check(ctx, &req) + if err != nil { + t.Fatal(err) + } + if output.Status.Code != int32(code.Code_OK) { + t.Fatal("Expected request to be allowed but got:", output) + } +} From 4ec26fc29b8af9f472e7c2615ae835dc84a67464 Mon Sep 17 00:00:00 2001 From: dcseifert Date: Thu, 14 Nov 2019 08:09:16 +0100 Subject: [PATCH 3/3] Integrated envoy proxy in kelon Signed-off-by: dcseifert --- cmd/kelon/kelon.go | 107 ++++++++++++++----- internal/pkg/api/envoy-proxy.go | 24 +++-- internal/pkg/api/envoy-proxy_test.go | 2 +- internal/pkg/api/rest-proxy.go | 8 +- internal/pkg/data/sql-datastore.go | 5 + internal/pkg/opa/default-compiler.go | 5 + internal/pkg/request/url-mapper.go | 5 + internal/pkg/request/url-processor.go | 5 + internal/pkg/translate/default-translator.go | 5 + 9 files changed, 131 insertions(+), 35 deletions(-) diff --git a/cmd/kelon/kelon.go b/cmd/kelon/kelon.go index 3e7ac6a..4393038 100644 --- a/cmd/kelon/kelon.go +++ b/cmd/kelon/kelon.go @@ -34,10 +34,18 @@ var ( //nolint:gochecknoglobals pathPrefix = app.Flag("path-prefix", "Prefix which is used to proxy OPA's Data-API.").Default("/v1").Envar("PATH_PREFIX").String() //nolint:gochecknoglobals - port = app.Flag("port", "port on which the proxy endpoint is served.").Short('p').Default("8181").Envar("PORT").Int32() + port = app.Flag("port", "Port on which the proxy endpoint is served.").Short('p').Default("8181").Envar("PORT").Uint32() + //nolint:gochecknoglobals + envoyPort = app.Flag("envoy-port", "Also start Envoy GRPC-Proxy on specified port so integrate kelon with Istio.").Default("9191").Envar("ENVOY_PORT").Uint32() + //nolint:gochecknoglobals + envoyDryRun = app.Flag("envoy-dry-run", "Enable/Disable the dry run feature of the envoy-proxy.").Default("false").Envar("ENVOY_DRY_RUN").Bool() + //nolint:gochecknoglobals + envoyReflection = app.Flag("envoy-reflection", "Enable/Disable the reflection feature of the envoy-proxy.").Default("true").Envar("ENVOY_REFLECTION").Bool() //nolint:gochecknoglobals proxy api.ClientProxy = nil //nolint:gochecknoglobals + envoy api.ClientProxy = nil + //nolint:gochecknoglobals configWatcher watcher.ConfigWatcher = nil ) @@ -55,7 +63,11 @@ func main() { app.HelpFlag.Short('h') app.Version(common.Version) - // Process args + + // Process args and initialize logger + log.SetFormatter(&log.TextFormatter{ + FullTimestamp: true, + }) switch kingpin.MustParse(app.Parse(os.Args[1:])) { case start.FullCommand(): log.SetOutput(os.Stdout) @@ -85,22 +97,63 @@ func onConfigLoaded(change watcher.ChangeType, loadedConf *configs.ExternalConfi } if change == watcher.ChangeAll { - startNewRestProxy(loadedConf) + // Configure application + var ( + config = new(configs.AppConfig) + compiler = opaInt.NewPolicyCompiler() + parser = requestInt.NewURLProcessor() + mapper = requestInt.NewPathMapper() + translator = translateInt.NewAstTranslator() + ) + // Build app config + config.API = loadedConf.API + config.Data = loadedConf.Data + // Build server config + serverConf := makeServerConfig(compiler, parser, mapper, translator, loadedConf) + + // Start rest proxy + startNewRestProxy(config, &serverConf) + + // Start envoy proxy in addition to rest proxy as soon as a port was specified! + if envoyPort != nil { + startNewEnvoyProxy(config, &serverConf) + } } } -func startNewRestProxy(loadedConf *configs.ExternalConfig) { - // Configure application - var ( - config = new(configs.AppConfig) - compiler = opaInt.NewPolicyCompiler() - parser = requestInt.NewURLProcessor() - mapper = requestInt.NewPathMapper() - translator = translateInt.NewAstTranslator() - ) - // Build app config - config.API = loadedConf.API - config.Data = loadedConf.Data +func startNewRestProxy(appConfig *configs.AppConfig, serverConf *api.ClientProxyConfig) { + // Create Rest proxy and start + proxy = apiInt.NewRestProxy(*pathPrefix, int32(*port)) + if err := proxy.Configure(appConfig, serverConf); err != nil { + log.Fatalln(err.Error()) + } + // Start proxy + if err := proxy.Start(); err != nil { + log.Fatalln(err.Error()) + } +} + +func startNewEnvoyProxy(appConfig *configs.AppConfig, serverConf *api.ClientProxyConfig) { + if *envoyPort == *port { + panic("Cannot start envoy proxy and rest proxy on same port!") + } + + // Create Rest proxy and start + envoy = apiInt.NewEnvoyProxy(apiInt.EnvoyConfig{ + Port: *envoyPort, + DryRun: *envoyDryRun, + EnableReflection: *envoyReflection, + }) + if err := envoy.Configure(appConfig, serverConf); err != nil { + log.Fatalln(err.Error()) + } + // Start proxy + if err := envoy.Start(); err != nil { + log.Fatalln(err.Error()) + } +} + +func makeServerConfig(compiler opa.PolicyCompiler, parser request.PathProcessor, mapper request.PathMapper, translator translate.AstTranslator, loadedConf *configs.ExternalConfig) api.ClientProxyConfig { // Build server config serverConf := api.ClientProxyConfig{ Compiler: &compiler, @@ -119,15 +172,7 @@ func startNewRestProxy(loadedConf *configs.ExternalConfig) { }, }, } - // Create Rest proxy and start - proxy = apiInt.NewRestProxy(*pathPrefix, *port) - if err := proxy.Configure(config, &serverConf); err != nil { - log.Fatalln(err.Error()) - } - // Start proxy - if err := proxy.Start(); err != nil { - log.Fatalln(err.Error()) - } + return serverConf } func stopOnSIGTERM() { @@ -138,9 +183,21 @@ func stopOnSIGTERM() { <-interruptChan log.Infoln("Caught SIGTERM...") + // Stop envoy proxy if started + if envoy != nil { + if err := envoy.Stop(time.Second * 10); err != nil { + log.Warnln(err.Error()) + } + } + + // Stop rest proxy if started if proxy != nil { if err := proxy.Stop(time.Second * 10); err != nil { - log.Fatalln(err.Error()) + log.Warnln(err.Error()) } } + + // Give components enough time for graceful shutdown + // This terminates earlier, because rest-proxy prints FATAL if http-server is closed + time.Sleep(5 * time.Second) } diff --git a/internal/pkg/api/envoy-proxy.go b/internal/pkg/api/envoy-proxy.go index e9ba3e0..ce92fda 100644 --- a/internal/pkg/api/envoy-proxy.go +++ b/internal/pkg/api/envoy-proxy.go @@ -23,7 +23,7 @@ import ( // Config represents the plugin configuration. type EnvoyConfig struct { - Addr string `json:"addr"` + Port uint32 `json:"port"` DryRun bool `json:"dry-run"` EnableReflection bool `json:"enable-reflection"` } @@ -44,11 +44,11 @@ type envoyProxy struct { // Implements api.ClientProxy by providing OPA's Data-REST-API. func NewEnvoyProxy(config EnvoyConfig) api.ClientProxy { - if config.Addr == "" { + if config.Port == 0 { log.Warnln("EnvoyProxy was initialized with default properties! You may have missed some arguments when creating it!") - config.Addr = ":9191" + config.Port = 9191 config.DryRun = false - config.EnableReflection = false + config.EnableReflection = true } return &envoyProxy{ @@ -66,6 +66,11 @@ func NewEnvoyProxy(config EnvoyConfig) api.ClientProxy { // See Configure() of api.ClientProxy func (proxy *envoyProxy) Configure(appConf *configs.AppConfig, serverConf *api.ClientProxyConfig) error { + // Exit if already configured + if proxy.configured { + return nil + } + // Configure subcomponents if serverConf.Compiler == nil { return errors.New("EnvoyProxy: Compiler not configured! ") @@ -87,7 +92,7 @@ func (proxy *envoyProxy) Configure(appConf *configs.AppConfig, serverConf *api.C // See Start() of api.ClientProxy func (proxy *envoyProxy) Start() error { if !proxy.configured { - return errors.New("RestProxy was not configured! Please call Configure(). ") + return errors.New("EnvoyProxy was not configured! Please call Configure(). ") } // Init grpc server @@ -100,14 +105,17 @@ func (proxy *envoyProxy) Start() error { reflection.Register(proxy.envoy.server) } + log.Infof("Starting envoy grpc-server at: http://0.0.0.0:%d", proxy.envoy.cfg.Port) return proxy.envoy.Start(context.Background()) } // See Stop() of api.ClientProxy func (proxy *envoyProxy) Stop(deadline time.Duration) error { if proxy.envoy.server == nil { - return errors.New("RestProxy has not bin started yet") + return errors.New("EnvoyProxy has not bin started yet") } + + log.Infof("Stopping envoy grpc-server at: http://0.0.0.0:%d", proxy.envoy.cfg.Port) ctx, cancel := context.WithTimeout(context.Background(), deadline) defer cancel() @@ -132,13 +140,13 @@ func (p *envoyExtAuthzGrpcServer) Reconfigure(ctx context.Context, config interf func (p *envoyExtAuthzGrpcServer) listen() { // The listener is closed automatically by Serve when it returns. - l, err := net.Listen("tcp", p.cfg.Addr) + l, err := net.Listen("tcp", fmt.Sprintf(":%d", p.cfg.Port)) if err != nil { log.WithField("err", err).Fatal("EnvoyProxy: Unable to create listener.") } log.WithFields(log.Fields{ - "addr": p.cfg.Addr, + "port": p.cfg.Port, "dry-run": p.cfg.DryRun, "enable-reflection": p.cfg.EnableReflection, }).Info("EnvoyProxy: Starting gRPC server.") diff --git a/internal/pkg/api/envoy-proxy_test.go b/internal/pkg/api/envoy-proxy_test.go index 14c8292..12685e3 100644 --- a/internal/pkg/api/envoy-proxy_test.go +++ b/internal/pkg/api/envoy-proxy_test.go @@ -77,7 +77,7 @@ func TestCheckAllow(t *testing.T) { } proxy := NewEnvoyProxy(EnvoyConfig{ - Addr: ":9191", + Port: 9191, DryRun: false, EnableReflection: true, }) diff --git a/internal/pkg/api/rest-proxy.go b/internal/pkg/api/rest-proxy.go index 4b282e5..c7ef790 100644 --- a/internal/pkg/api/rest-proxy.go +++ b/internal/pkg/api/rest-proxy.go @@ -39,6 +39,11 @@ func NewRestProxy(pathPrefix string, port int32) api.ClientProxy { // See Configure() of api.ClientProxy func (proxy *restProxy) Configure(appConf *configs.AppConfig, serverConf *api.ClientProxyConfig) error { + // Exit if already configured + if proxy.configured { + return nil + } + // Configure subcomponents if serverConf.Compiler == nil { return errors.New("RestProxy: Compiler not configured! ") @@ -76,7 +81,7 @@ func (proxy *restProxy) Start() error { // Start Server go func() { - log.Infof("Starting server at: http://localhost:%d%s", proxy.port, proxy.pathPrefix) + log.Infof("Starting server at: http://0.0.0.0:%d%s", proxy.port, proxy.pathPrefix) if err := proxy.server.ListenAndServe(); err != nil { log.Fatal(err) } @@ -89,6 +94,7 @@ func (proxy *restProxy) Stop(deadline time.Duration) error { if proxy.server == nil { return errors.New("RestProxy has not bin started yet") } + log.Infof("Stopping server at: http://localhost:%d%s", proxy.port, proxy.pathPrefix) ctx, cancel := context.WithTimeout(context.Background(), deadline) defer cancel() diff --git a/internal/pkg/data/sql-datastore.go b/internal/pkg/data/sql-datastore.go index a790736..764e994 100644 --- a/internal/pkg/data/sql-datastore.go +++ b/internal/pkg/data/sql-datastore.go @@ -52,6 +52,11 @@ func NewSQLDatastore() data.Datastore { } func (ds *sqlDatastore) Configure(appConf *configs.AppConfig, alias string) error { + // Exit if already configured + if ds.configured { + return nil + } + if appConf == nil { return errors.New("SqlDatastore: AppConfig not configured! ") } diff --git a/internal/pkg/opa/default-compiler.go b/internal/pkg/opa/default-compiler.go index 761e29b..5624d21 100644 --- a/internal/pkg/opa/default-compiler.go +++ b/internal/pkg/opa/default-compiler.go @@ -36,6 +36,11 @@ func NewPolicyCompiler() opa.PolicyCompiler { // See Configure() from opa.PolicyCompiler func (compiler *policyCompiler) Configure(appConf *configs.AppConfig, compConf *opa.PolicyCompilerConfig) error { + // Exit if already configured + if compiler.configured { + return nil + } + if e := initDependencies(compConf, appConf); e != nil { return errors.Wrap(e, "PolicyCompiler: Error while initializing dependencies.") } diff --git a/internal/pkg/request/url-mapper.go b/internal/pkg/request/url-mapper.go index e5f750d..6156a7c 100644 --- a/internal/pkg/request/url-mapper.go +++ b/internal/pkg/request/url-mapper.go @@ -42,6 +42,11 @@ func NewPathMapper() request.PathMapper { // See request.PathMapper. func (mapper *pathMapper) Configure(appConf *configs.AppConfig) error { + // Exit if already configured + if mapper.configured { + return nil + } + if appConf == nil { return errors.New("PathMapper: AppConfig not configured! ") } diff --git a/internal/pkg/request/url-processor.go b/internal/pkg/request/url-processor.go index 9377962..a29b7f6 100644 --- a/internal/pkg/request/url-processor.go +++ b/internal/pkg/request/url-processor.go @@ -34,6 +34,11 @@ func NewURLProcessor() request.PathProcessor { // See request.PathProcessor. func (processor *urlProcessor) Configure(appConf *configs.AppConfig, processorConf *request.PathProcessorConfig) error { + // Exit if already configured + if processor.configured { + return nil + } + // Configure subcomponents if processorConf.PathMapper == nil { return errors.New("UrlProcessor: PathMapper not configured! ") diff --git a/internal/pkg/translate/default-translator.go b/internal/pkg/translate/default-translator.go index 7af3a90..2d87065 100644 --- a/internal/pkg/translate/default-translator.go +++ b/internal/pkg/translate/default-translator.go @@ -25,6 +25,11 @@ func NewAstTranslator() translate.AstTranslator { // See translate.AstTranslator. func (trans *astTranslator) Configure(appConf *configs.AppConfig, transConf *translate.AstTranslatorConfig) error { + // Exit if already configured + if trans.configured { + return nil + } + // Configure subcomponents if transConf.Datastores == nil { return errors.New("AstTranslator: Datastores not configured! ")