From 9fae48cb95dc687704cd7b83ede88f4dc683b16a Mon Sep 17 00:00:00 2001 From: Igor Baiborodine Date: Thu, 9 Mar 2023 11:58:04 -0500 Subject: [PATCH] feat: Add source code for "Secure Your Services" chapter - part 2 (#35) --- SecureYourServices/Makefile | 53 ++++ SecureYourServices/README.md | 10 +- SecureYourServices/go.mod | 3 + SecureYourServices/go.sum | 100 +++++++ .../internal/auth/authorizer.go | 34 +++ SecureYourServices/internal/config/files.go | 29 +++ SecureYourServices/internal/config/tls.go | 53 ++++ SecureYourServices/internal/server/server.go | 82 +++++- .../internal/server/server_test.go | 245 ++++++++++++++++-- SecureYourServices/test/ca-config.json | 22 ++ SecureYourServices/test/ca-csr.json | 16 ++ SecureYourServices/test/client-csr.json | 17 ++ SecureYourServices/test/model.conf | 15 ++ SecureYourServices/test/policy.csv | 2 + SecureYourServices/test/server-csr.json | 20 ++ 15 files changed, 668 insertions(+), 33 deletions(-) create mode 100644 SecureYourServices/internal/auth/authorizer.go create mode 100644 SecureYourServices/internal/config/files.go create mode 100644 SecureYourServices/internal/config/tls.go create mode 100644 SecureYourServices/test/ca-config.json create mode 100644 SecureYourServices/test/ca-csr.json create mode 100644 SecureYourServices/test/client-csr.json create mode 100644 SecureYourServices/test/model.conf create mode 100644 SecureYourServices/test/policy.csv create mode 100644 SecureYourServices/test/server-csr.json diff --git a/SecureYourServices/Makefile b/SecureYourServices/Makefile index 19a3069..2279f7d 100644 --- a/SecureYourServices/Makefile +++ b/SecureYourServices/Makefile @@ -1,3 +1,14 @@ +CONFIG_PATH=${HOME}/.proglog/ + +.PHONY: clean +clean: + rm -rf ${CONFIG_PATH} + +.PHONY: init +init: + mkdir -p ${CONFIG_PATH} + +.PHONY: compile compile: protoc api/v1/*.proto \ --go_out=. \ @@ -6,5 +17,47 @@ compile: --go-grpc_opt=paths=source_relative \ --proto_path=. +.PHONY: gencert +gencert: + cfssl gencert \ + -initca test/ca-csr.json | cfssljson -bare ca + + cfssl gencert \ + -ca=ca.pem \ + -ca-key=ca-key.pem \ + -config=test/ca-config.json \ + -profile=server \ + test/server-csr.json | cfssljson -bare server + + cfssl gencert \ + -ca=ca.pem \ + -ca-key=ca-key.pem \ + -config=test/ca-config.json \ + -profile=client \ + test/client-csr.json | cfssljson -bare client + + cfssl gencert \ + -ca=ca.pem \ + -ca-key=ca-key.pem \ + -config=test/ca-config.json \ + -profile=client \ + -cn="root" \ + test/client-csr.json | cfssljson -bare root-client + + cfssl gencert \ + -ca=ca.pem \ + -ca-key=ca-key.pem \ + -config=test/ca-config.json \ + -profile=client \ + -cn="nobody" \ + test/client-csr.json | cfssljson -bare nobody-client + + mv *.pem *.csr ${CONFIG_PATH} + + cp test/model.conf $(CONFIG_PATH)/model.conf + + cp test/policy.csv $(CONFIG_PATH)/policy.csv + +.PHONY: test test: go test -race ./... diff --git a/SecureYourServices/README.md b/SecureYourServices/README.md index 140256e..f498323 100644 --- a/SecureYourServices/README.md +++ b/SecureYourServices/README.md @@ -2,13 +2,17 @@ ### Prerequisites -#### gRPC Plugin +#### CloudFlare CLI ```shell -$ go install google.golang.org/grpc@latest +$ go install github.com/cloudflare/cfssl/cmd/cfssl@latest +$ go install github.com/cloudflare/cfssl/cmd/cfssljson@latest ``` + ### Tests ```shell -$ make compile test +$ make clean init compile +$ make gencert +$ make test ``` diff --git a/SecureYourServices/go.mod b/SecureYourServices/go.mod index cba6dfc..2b2bcc0 100644 --- a/SecureYourServices/go.mod +++ b/SecureYourServices/go.mod @@ -3,6 +3,8 @@ module github.com/igor-baiborodine/proglog go 1.19 require ( + github.com/casbin/casbin v1.9.1 + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/stretchr/testify v1.8.2 github.com/tysonmote/gommap v0.0.2 google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 @@ -11,6 +13,7 @@ require ( ) require ( + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/SecureYourServices/go.sum b/SecureYourServices/go.sum index 90f73aa..0e3c150 100644 --- a/SecureYourServices/go.sum +++ b/SecureYourServices/go.sum @@ -1,33 +1,130 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/casbin/casbin v1.9.1 h1:ucjbS5zTrmSLtH4XogqOG920Poe6QatdXtz1FEbApeM= +github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog= +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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 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/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tysonmote/gommap v0.0.2 h1:TNTjXaXxiLWuWVTU9BfSb1bAEvfrptf8m5+N3LyTd6Q= github.com/tysonmote/gommap v0.0.2/go.mod h1:zZKhSp7mLDDzdl8MHbaDEJ3PH9VibPlFXV1t+4wmC00= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= +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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +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.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= @@ -36,7 +133,10 @@ google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/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= launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54= diff --git a/SecureYourServices/internal/auth/authorizer.go b/SecureYourServices/internal/auth/authorizer.go new file mode 100644 index 0000000..6ae5ce4 --- /dev/null +++ b/SecureYourServices/internal/auth/authorizer.go @@ -0,0 +1,34 @@ +package auth + +import ( + "fmt" + + "github.com/casbin/casbin" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func New(model, policy string) *Authorizer { + enforcer := casbin.NewEnforcer(model, policy) + return &Authorizer{ + enforcer: enforcer, + } +} + +type Authorizer struct { + enforcer *casbin.Enforcer +} + +func (a *Authorizer) Authorize(subject, object, action string) error { + if !a.enforcer.Enforce(subject, object, action) { + msg := fmt.Sprintf( + "%s not permitted to %s to %s", + subject, + action, + object, + ) + st := status.New(codes.PermissionDenied, msg) + return st.Err() + } + return nil +} diff --git a/SecureYourServices/internal/config/files.go b/SecureYourServices/internal/config/files.go new file mode 100644 index 0000000..2806f67 --- /dev/null +++ b/SecureYourServices/internal/config/files.go @@ -0,0 +1,29 @@ +package config + +import ( + "os" + "path/filepath" +) + +var ( + CAFile = configFile("ca.pem") + ServerCertFile = configFile("server.pem") + ServerKeyFile = configFile("server-key.pem") + RootClientCertFile = configFile("root-client.pem") + RootClientKeyFile = configFile("root-client-key.pem") + NobodyClientCertFile = configFile("nobody-client.pem") + NobodyClientKeyFile = configFile("nobody-client-key.pem") + ACLModelFile = configFile("model.conf") + ACLPolicyFile = configFile("policy.csv") +) + +func configFile(filename string) string { + if dir := os.Getenv("CONFIG_DIR"); dir != "" { + return filepath.Join(dir, filename) + } + homeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + return filepath.Join(homeDir, ".proglog", filename) +} diff --git a/SecureYourServices/internal/config/tls.go b/SecureYourServices/internal/config/tls.go new file mode 100644 index 0000000..1fb030c --- /dev/null +++ b/SecureYourServices/internal/config/tls.go @@ -0,0 +1,53 @@ +package config + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" +) + +func SetupTLSConfig(cfg TLSConfig) (*tls.Config, error) { + var err error + tlsConfig := &tls.Config{} + if cfg.CertFile != "" && cfg.KeyFile != "" { + tlsConfig.Certificates = make([]tls.Certificate, 1) + tlsConfig.Certificates[0], err = tls.LoadX509KeyPair( + cfg.CertFile, + cfg.KeyFile, + ) + if err != nil { + return nil, err + } + } + if cfg.CAFile != "" { + b, err := os.ReadFile(cfg.CAFile) + if err != nil { + return nil, err + } + ca := x509.NewCertPool() + ok := ca.AppendCertsFromPEM([]byte(b)) + if !ok { + return nil, fmt.Errorf( + "failed to parse root certificate: %q", + cfg.CAFile, + ) + } + if cfg.Server { + tlsConfig.ClientCAs = ca + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + } else { + tlsConfig.RootCAs = ca + } + tlsConfig.ServerName = cfg.ServerAddress + } + return tlsConfig, nil +} + +type TLSConfig struct { + CertFile string + KeyFile string + CAFile string + ServerAddress string + Server bool +} diff --git a/SecureYourServices/internal/server/server.go b/SecureYourServices/internal/server/server.go index 1826b44..0cacf2c 100644 --- a/SecureYourServices/internal/server/server.go +++ b/SecureYourServices/internal/server/server.go @@ -3,15 +3,29 @@ package server import ( "context" - "google.golang.org/grpc" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" api "github.com/igor-baiborodine/proglog/api/v1" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" ) type Config struct { - CommitLog CommitLog + CommitLog CommitLog + Authorizer Authorizer } +const ( + objectWildcard = "*" + produceAction = "produce" + consumeAction = "consume" +) + type grpcServer struct { *Config *api.UnimplementedLogServer @@ -25,8 +39,17 @@ func newgrpcServer(config *Config) (srv *grpcServer, err error) { return srv, nil } -func NewGRPCServer(config *Config) (*grpc.Server, error) { - gsrv := grpc.NewServer() +func NewGRPCServer(config *Config, opts ...grpc.ServerOption) ( + *grpc.Server, + error, +) { + opts = append(opts, grpc.StreamInterceptor( + grpc_middleware.ChainStreamServer( + grpc_auth.StreamServerInterceptor(authenticate), + )), grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( + grpc_auth.UnaryServerInterceptor(authenticate), + ))) + gsrv := grpc.NewServer(opts...) srv, err := newgrpcServer(config) if err != nil { return nil, err @@ -37,6 +60,13 @@ func NewGRPCServer(config *Config) (*grpc.Server, error) { func (s *grpcServer) Produce(ctx context.Context, req *api.ProduceRequest) ( *api.ProduceResponse, error) { + if err := s.Authorizer.Authorize( + subject(ctx), + objectWildcard, + produceAction, + ); err != nil { + return nil, err + } offset, err := s.CommitLog.Append(req.Record) if err != nil { return nil, err @@ -46,6 +76,13 @@ func (s *grpcServer) Produce(ctx context.Context, req *api.ProduceRequest) ( func (s *grpcServer) Consume(ctx context.Context, req *api.ConsumeRequest) ( *api.ConsumeResponse, error) { + if err := s.Authorizer.Authorize( + subject(ctx), + objectWildcard, + consumeAction, + ); err != nil { + return nil, err + } record, err := s.CommitLog.Read(req.Offset) if err != nil { return nil, api.ErrOffsetOutOfRange{Offset: req.Offset} @@ -53,9 +90,7 @@ func (s *grpcServer) Consume(ctx context.Context, req *api.ConsumeRequest) ( return &api.ConsumeResponse{Record: record}, nil } -func (s *grpcServer) ProduceStream( - stream api.Log_ProduceStreamServer, -) error { +func (s *grpcServer) ProduceStream(stream api.Log_ProduceStreamServer) error { for { req, err := stream.Recv() if err != nil { @@ -100,3 +135,36 @@ type CommitLog interface { Append(*api.Record) (uint64, error) Read(uint64) (*api.Record, error) } + +type Authorizer interface { + Authorize(subject, object, action string) error +} + +func authenticate(ctx context.Context) (context.Context, error) { + p, ok := peer.FromContext(ctx) + if !ok { + return ctx, status.New( + codes.Unknown, + "couldn't find p info", + ).Err() + } + + if p.AuthInfo == nil { + return ctx, status.New( + codes.Unauthenticated, + "no transport security being used", + ).Err() + } + + tlsInfo := p.AuthInfo.(credentials.TLSInfo) + subject := tlsInfo.State.VerifiedChains[0][0].Subject.CommonName + ctx = context.WithValue(ctx, subjectContextKey{}, subject) + + return ctx, nil +} + +func subject(ctx context.Context) string { + return ctx.Value(subjectContextKey{}).(string) +} + +type subjectContextKey struct{} diff --git a/SecureYourServices/internal/server/server_test.go b/SecureYourServices/internal/server/server_test.go index 1d644ca..dce8624 100644 --- a/SecureYourServices/internal/server/server_test.go +++ b/SecureYourServices/internal/server/server_test.go @@ -8,77 +8,122 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/status" api "github.com/igor-baiborodine/proglog/api/v1" + "github.com/igor-baiborodine/proglog/internal/auth" + "github.com/igor-baiborodine/proglog/internal/config" "github.com/igor-baiborodine/proglog/internal/log" ) func TestServer(t *testing.T) { for scenario, fn := range map[string]func( t *testing.T, - client api.LogClient, + rootClient api.LogClient, + nobodyClient api.LogClient, config *Config, ){ "produce/consume a message to/from the log succeeds": testProduceConsume, "produce/consume stream succeeds": testProduceConsumeStream, "consume past log boundary fails": testConsumePastBoundary, + "unauthorized fails": testUnauthorized, } { t.Run(scenario, func(t *testing.T) { - client, config, teardown := setupTest(t, nil) + rootClient, + nobodyClient, + cfg, + teardown := setupTest(t, nil) defer teardown() - fn(t, client, config) + fn(t, rootClient, nobodyClient, cfg) }) } } func setupTest(t *testing.T, fn func(*Config)) ( - client api.LogClient, - config *Config, + rootClient api.LogClient, + nobodyClient api.LogClient, + cfg *Config, teardown func(), ) { t.Helper() - l, err := net.Listen("tcp", ":0") + l, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) - clientOptions := []grpc.DialOption{ - grpc.WithTransportCredentials(insecure.NewCredentials()), + newClient := func(crtPath, keyPath string) ( + *grpc.ClientConn, + api.LogClient, + []grpc.DialOption, + ) { + tlsConfig, err := config.SetupTLSConfig(config.TLSConfig{ + CertFile: crtPath, + KeyFile: keyPath, + CAFile: config.CAFile, + Server: false, + }) + require.NoError(t, err) + tlsCreds := credentials.NewTLS(tlsConfig) + opts := []grpc.DialOption{grpc.WithTransportCredentials(tlsCreds)} + conn, err := grpc.Dial(l.Addr().String(), opts...) + require.NoError(t, err) + client := api.NewLogClient(conn) + return conn, client, opts } - cc, err := grpc.Dial(l.Addr().String(), clientOptions...) + + var rootConn *grpc.ClientConn + rootConn, rootClient, _ = newClient( + config.RootClientCertFile, + config.RootClientKeyFile, + ) + + var nobodyConn *grpc.ClientConn + nobodyConn, nobodyClient, _ = newClient( + config.NobodyClientCertFile, + config.NobodyClientKeyFile, + ) + + serverTLSConfig, err := config.SetupTLSConfig(config.TLSConfig{ + CertFile: config.ServerCertFile, + KeyFile: config.ServerKeyFile, + CAFile: config.CAFile, + ServerAddress: l.Addr().String(), + Server: true, + }) require.NoError(t, err) + serverCreds := credentials.NewTLS(serverTLSConfig) dir, err := os.MkdirTemp("", "server-test") require.NoError(t, err) - clog, err := log.NewLog(dir, log.Config{}) + cl, err := log.NewLog(dir, log.Config{}) require.NoError(t, err) + a := auth.New(config.ACLModelFile, config.ACLPolicyFile) - config = &Config{ - CommitLog: clog, + cfg = &Config{ + CommitLog: cl, + Authorizer: a, } if fn != nil { - fn(config) + fn(cfg) } - server, err := NewGRPCServer(config) + server, err := NewGRPCServer(cfg, grpc.Creds(serverCreds)) require.NoError(t, err) go func() { server.Serve(l) }() - client = api.NewLogClient(cc) - - return client, config, func() { + return rootClient, nobodyClient, cfg, func() { server.Stop() - cc.Close() + rootConn.Close() + nobodyConn.Close() l.Close() - clog.Remove() } } -func testProduceConsume(t *testing.T, client api.LogClient, config *Config) { +func testProduceConsume(t *testing.T, client, _ api.LogClient, config *Config) { ctx := context.Background() want := &api.Record{ @@ -103,7 +148,7 @@ func testProduceConsume(t *testing.T, client api.LogClient, config *Config) { func testConsumePastBoundary( t *testing.T, - client api.LogClient, + client, _ api.LogClient, config *Config, ) { ctx := context.Background() @@ -130,7 +175,7 @@ func testConsumePastBoundary( func testProduceConsumeStream( t *testing.T, - client api.LogClient, + client, _ api.LogClient, config *Config, ) { ctx := context.Background() @@ -182,3 +227,157 @@ func testProduceConsumeStream( } } } + +func testUnauthorized( + t *testing.T, + _, + client api.LogClient, + config *Config, +) { + ctx := context.Background() + produce, err := client.Produce(ctx, + &api.ProduceRequest{ + Record: &api.Record{ + Value: []byte("hello world"), + }, + }, + ) + if produce != nil { + t.Fatalf("produce response should be nil") + } + gotCode, wantCode := status.Code(err), codes.PermissionDenied + if gotCode != wantCode { + t.Fatalf("got code: %d, want: %d", gotCode, wantCode) + } + consume, err := client.Consume(ctx, &api.ConsumeRequest{ + Offset: 0, + }) + if consume != nil { + t.Fatalf("consume response should be nil") + } + gotCode, wantCode = status.Code(err), codes.PermissionDenied + if gotCode != wantCode { + t.Fatalf("got code: %d, want: %d", gotCode, wantCode) + } +} + +func setupTest1(t *testing.T, fn func(*Config)) ( + client api.LogClient, + cfg *Config, + teardown func(), +) { + t.Helper() + + t.Helper() + + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + clientTLSConfig, err := config.SetupTLSConfig(config.TLSConfig{ + CAFile: config.CAFile, + }) + require.NoError(t, err) + + clientCreds := credentials.NewTLS(clientTLSConfig) + cc, err := grpc.Dial( + l.Addr().String(), + grpc.WithTransportCredentials(clientCreds), + ) + require.NoError(t, err) + + client = api.NewLogClient(cc) + + serverTLSConfig, err := config.SetupTLSConfig(config.TLSConfig{ + CertFile: config.ServerCertFile, + KeyFile: config.ServerKeyFile, + CAFile: config.CAFile, + ServerAddress: l.Addr().String(), + }) + require.NoError(t, err) + serverCreds := credentials.NewTLS(serverTLSConfig) + + dir, err := os.MkdirTemp("", "server-test") + require.NoError(t, err) + + clog, err := log.NewLog(dir, log.Config{}) + require.NoError(t, err) + + cfg = &Config{ + CommitLog: clog, + } + if fn != nil { + fn(cfg) + } + server, err := NewGRPCServer(cfg, grpc.Creds(serverCreds)) + require.NoError(t, err) + + go func() { + server.Serve(l) + }() + + return client, cfg, func() { + server.Stop() + cc.Close() + l.Close() + } +} + +func setupTest2(t *testing.T, fn func(*Config)) ( + client api.LogClient, + cfg *Config, + teardown func(), +) { + t.Helper() + + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + clientTLSConfig, err := config.SetupTLSConfig(config.TLSConfig{ + CAFile: config.CAFile, + }) + require.NoError(t, err) + + clientCreds := credentials.NewTLS(clientTLSConfig) + cc, err := grpc.Dial( + l.Addr().String(), + grpc.WithTransportCredentials(clientCreds), + ) + require.NoError(t, err) + + client = api.NewLogClient(cc) + + serverTLSConfig, err := config.SetupTLSConfig(config.TLSConfig{ + CertFile: config.ServerCertFile, + KeyFile: config.ServerKeyFile, + CAFile: config.CAFile, + ServerAddress: l.Addr().String(), + Server: true, + }) + require.NoError(t, err) + serverCreds := credentials.NewTLS(serverTLSConfig) + + dir, err := os.MkdirTemp("", "server-test") + require.NoError(t, err) + + clog, err := log.NewLog(dir, log.Config{}) + require.NoError(t, err) + + cfg = &Config{ + CommitLog: clog, + } + if fn != nil { + fn(cfg) + } + server, err := NewGRPCServer(cfg, grpc.Creds(serverCreds)) + require.NoError(t, err) + + go func() { + server.Serve(l) + }() + + return client, cfg, func() { + server.Stop() + cc.Close() + l.Close() + } +} diff --git a/SecureYourServices/test/ca-config.json b/SecureYourServices/test/ca-config.json new file mode 100644 index 0000000..4363da0 --- /dev/null +++ b/SecureYourServices/test/ca-config.json @@ -0,0 +1,22 @@ +{ + "signing": { + "profiles": { + "server": { + "expiry": "8760h", + "usages": [ + "signing", + "key encipherment", + "server auth" + ] + }, + "client": { + "expiry": "8760h", + "usages": [ + "signing", + "key encipherment", + "client auth" + ] + } + } + } +} diff --git a/SecureYourServices/test/ca-csr.json b/SecureYourServices/test/ca-csr.json new file mode 100644 index 0000000..1a9a164 --- /dev/null +++ b/SecureYourServices/test/ca-csr.json @@ -0,0 +1,16 @@ +{ + "CN": "My Awesome CA", + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "CA", + "L": "ON", + "ST": "Toronto", + "O": "My Awesome Company", + "OU": "CA Services" + } + ] +} diff --git a/SecureYourServices/test/client-csr.json b/SecureYourServices/test/client-csr.json new file mode 100644 index 0000000..e883757 --- /dev/null +++ b/SecureYourServices/test/client-csr.json @@ -0,0 +1,17 @@ +{ + "CN": "client", + "hosts": [""], + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "CA", + "L": "ON", + "ST": "Toronto", + "O": "My Company", + "OU": "Distributed Services" + } + ] +} diff --git a/SecureYourServices/test/model.conf b/SecureYourServices/test/model.conf new file mode 100644 index 0000000..0fb08a6 --- /dev/null +++ b/SecureYourServices/test/model.conf @@ -0,0 +1,15 @@ +# Request definition +[request_definition] +r = sub, obj, act + +# Policy definition +[policy_definition] +p = sub, obj, act + +# Policy effect +[policy_effect] +e = some(where (p.eft == allow)) + +# Matchers +[matchers] +m = r.sub == p.sub && r.obj == p.obj && r.act == p.act diff --git a/SecureYourServices/test/policy.csv b/SecureYourServices/test/policy.csv new file mode 100644 index 0000000..4725f34 --- /dev/null +++ b/SecureYourServices/test/policy.csv @@ -0,0 +1,2 @@ +p, root, *, produce +p, root, *, consume diff --git a/SecureYourServices/test/server-csr.json b/SecureYourServices/test/server-csr.json new file mode 100644 index 0000000..4089137 --- /dev/null +++ b/SecureYourServices/test/server-csr.json @@ -0,0 +1,20 @@ +{ + "CN": "127.0.0.1", + "hosts": [ + "localhost", + "127.0.0.1" + ], + "key": { + "algo": "rsa", + "size": 2048 + }, + "names": [ + { + "C": "CA", + "L": "ON", + "ST": "Toronto", + "O": "My Awesome Company", + "OU": "Distributed Services" + } + ] +}