diff --git a/cmd/zitadel/main.go b/cmd/zitadel/main.go index 24f3f57abd..f44be202ab 100644 --- a/cmd/zitadel/main.go +++ b/cmd/zitadel/main.go @@ -22,7 +22,6 @@ type Config struct { func main() { configPath := flag.String("config-file", "/zitadel/config/startup.yaml", "path to the config file") - eventstoreEnabled := flag.Bool("eventstore", true, "enable eventstore") managementEnabled := flag.Bool("management", true, "enable management api") authEnabled := flag.Bool("auth", true, "enable auth api") adminEnabled := flag.Bool("admin", true, "enable admin api") @@ -34,10 +33,6 @@ func main() { logging.Log("MAIN-FaF2r").OnError(err).Fatal("cannot read config") ctx := context.Background() - if *eventstoreEnabled { - err = eventstore.Start(ctx, conf.Eventstore) - logging.Log("MAIN-sj2Sd").OnError(err).Fatal("error starting eventstore") - } if *managementEnabled { err = management.Start(ctx, conf.Management) logging.Log("MAIN-39Nv5").OnError(err).Fatal("error starting management api") diff --git a/go.mod b/go.mod index 32e9d92da1..34c29cbd49 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module github.com/caos/zitadel go 1.14 require ( + cloud.google.com/go v0.53.0 // indirect + contrib.go.opencensus.io/exporter/stackdriver v0.13.0 github.com/BurntSushi/toml v0.3.1 github.com/Masterminds/goutils v1.1.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible + github.com/aws/aws-sdk-go v1.29.16 // indirect github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a - github.com/caos/utils v0.0.0-20200305060859-ac2fa70f313e // indirect - github.com/envoyproxy/protoc-gen-validate v0.1.0 - github.com/ghodss/yaml v1.0.0 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/mock v1.4.3 github.com/golang/protobuf v1.3.5 @@ -21,13 +21,18 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.14.3 github.com/huandu/xstrings v1.3.0 // indirect github.com/imdario/mergo v0.3.8 // indirect - github.com/magiconair/properties v1.8.1 github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/nicksnyder/go-i18n/v2 v2.0.3 + github.com/rs/cors v1.7.0 github.com/stretchr/testify v1.5.1 + go.opencensus.io v0.22.3 golang.org/x/crypto v0.0.0-20200320181102-891825fb96df golang.org/x/net v0.0.0-20200320220750-118fecf932d8 // indirect golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae // indirect + golang.org/x/text v0.3.2 golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56 - google.golang.org/genproto v0.0.0-20200319113533-08878b785e9c + google.golang.org/api v0.20.0 // indirect + google.golang.org/genproto v0.0.0-20200319113533-08878b785e9c // indirect google.golang.org/grpc v1.28.0 + gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index ea115d95ae..c277b44880 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,7 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.53.0 h1:MZQCQQaRwOrAcuKjiHWHrgKykt4fZyuwF2dtiG3fGW8= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= @@ -14,8 +15,10 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +contrib.go.opencensus.io/exporter/stackdriver v0.13.0 h1:Jaz7WbqjtfoCPa1KbfisCX+P5aM3DizEY9pQMU0oAQo= contrib.go.opencensus.io/exporter/stackdriver v0.13.0/go.mod h1:z2tyTZtPmQ2HvWH4cOmVDgtY+1lomfKdbLnkJvZdc8c= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -25,18 +28,14 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.29.16 h1:Gbtod7Y4W/Ai7wPtesdvgGVTkFN8JxAaGouRLlcQfQs= github.com/aws/aws-sdk-go v1.29.16/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a h1:HOU/3xL/afsZ+2aCstfJlrzRkwYMTFR1TIEgps5ny8s= github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0= -github.com/caos/utils v0.0.0-20200305060859-ac2fa70f313e h1:QSbTeoLPW7c1rWNJA2GOKunDJnRAfyg8+cb73qMYESM= -github.com/caos/utils v0.0.0-20200305060859-ac2fa70f313e/go.mod h1:CLEkNe7rs12GkdBWZxadA/mFiKeF6HzuA1BOKq+fX+Y= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -48,7 +47,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -69,17 +67,14 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -95,6 +90,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= @@ -102,7 +98,6 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg= github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= -github.com/grpc-ecosystem/grpc-gateway v1.13.0/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.14.3 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtguw7vR+nGtnDjY= github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -112,6 +107,7 @@ github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -126,12 +122,12 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/nicksnyder/go-i18n/v2 v2.0.3 h1:ks/JkQiOEhhuF6jpNvx+Wih1NIiXzUnZeZVnJuI8R8M= +github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo= 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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -148,7 +144,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -162,6 +157,7 @@ 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-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -191,6 +187,7 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.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= @@ -207,19 +204,20 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200320220750-118fecf932d8 h1:1+zQlQqEEhUeStBTi653GZAnAuivZq/2hz+Iz+OP7rg= golang.org/x/net v0.0.0-20200320220750-118fecf932d8/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/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20190227155943-e225da77a7e6/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 h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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= @@ -236,7 +234,6 @@ golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab h1:FvshnhkKW+LO3HWHodML8kuVX golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae h1:3tcmuaB7wwSZtelmiv479UjUB+vviwABz7a133ZwOKQ= golang.org/x/sys v0.0.0-20200321134203-328b4cd54aae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -272,6 +269,7 @@ golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56 h1:DFtSed2q3HtNuVazwVDZ4nSRS/JrZEig0gz2BY4VNrg= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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= @@ -286,12 +284,14 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.2/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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -307,7 +307,6 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200303153909-beee998c1893/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200319113533-08878b785e9c h1:5aI3/f/3eCZps9xwoEnmgfDJDhMbnJpfqeGpjVNgVEI= google.golang.org/genproto v0.0.0-20200319113533-08878b785e9c/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -326,6 +325,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= diff --git a/internal/api/grpc/client/middleware/tracing.go b/internal/api/grpc/client/middleware/tracing.go new file mode 100644 index 0000000000..dbcfedcfac --- /dev/null +++ b/internal/api/grpc/client/middleware/tracing.go @@ -0,0 +1,38 @@ +package middleware + +import ( + "context" + "strings" + + "go.opencensus.io/plugin/ocgrpc" + "go.opencensus.io/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/stats" + + "github.com/caos/zitadel/internal/api" + "github.com/caos/zitadel/internal/tracing" +) + +type GRPCMethod string + +func TracingStatsClient(ignoredMethods ...GRPCMethod) grpc.DialOption { + return grpc.WithStatsHandler(&tracingClientHandler{ignoredMethods, ocgrpc.ClientHandler{StartOptions: trace.StartOptions{Sampler: tracing.Sampler(), SpanKind: trace.SpanKindClient}}}) +} + +func DefaultTracingStatsClient() grpc.DialOption { + return TracingStatsClient(api.Healthz, api.Readiness, api.Validation) +} + +type tracingClientHandler struct { + IgnoredMethods []GRPCMethod + ocgrpc.ClientHandler +} + +func (s *tracingClientHandler) TagRPC(ctx context.Context, tagInfo *stats.RPCTagInfo) context.Context { + for _, method := range s.IgnoredMethods { + if strings.HasSuffix(tagInfo.FullMethodName, string(method)) { + return ctx + } + } + return s.ClientHandler.TagRPC(ctx, tagInfo) +} diff --git a/internal/api/grpc/header.go b/internal/api/grpc/header.go index 5d217b7518..f1498c1562 100644 --- a/internal/api/grpc/header.go +++ b/internal/api/grpc/header.go @@ -4,12 +4,8 @@ import ( "context" "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" -) -const ( - Authorization = "authorization" - - ZitadelOrgID = "x-zitadel-orgid" + "github.com/caos/zitadel/internal/api" ) func GetHeader(ctx context.Context, headername string) string { @@ -17,5 +13,5 @@ func GetHeader(ctx context.Context, headername string) string { } func GetAuthorizationHeader(ctx context.Context) string { - return GetHeader(ctx, Authorization) + return GetHeader(ctx, api.Authorization) } diff --git a/internal/api/grpc/server/gateway.go b/internal/api/grpc/server/gateway.go new file mode 100644 index 0000000000..955706b6ad --- /dev/null +++ b/internal/api/grpc/server/gateway.go @@ -0,0 +1,110 @@ +package server + +import ( + "context" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc" + + "github.com/caos/logging" + + client_middleware "github.com/caos/zitadel/internal/api/grpc/client/middleware" + http_util "github.com/caos/zitadel/internal/api/http" + http_mw "github.com/caos/zitadel/internal/api/http/middleware" +) + +const ( + defaultGatewayPort = "8080" + mimeWildcard = "*/*" +) + +var ( + DefaultJSONMarshaler = &runtime.JSONPb{OrigName: false, EmitDefaults: false} + + DefaultServeMuxOptions = []runtime.ServeMuxOption{ + runtime.WithMarshalerOption(DefaultJSONMarshaler.ContentType(), DefaultJSONMarshaler), + runtime.WithMarshalerOption(mimeWildcard, DefaultJSONMarshaler), + runtime.WithMarshalerOption(runtime.MIMEWildcard, DefaultJSONMarshaler), + runtime.WithIncomingHeaderMatcher(runtime.DefaultHeaderMatcher), + runtime.WithOutgoingHeaderMatcher(runtime.DefaultHeaderMatcher), + } +) + +type Gateway interface { + GRPCEndpoint() string + GatewayPort() string + Gateway() GatewayFunc +} + +type GatewayFunc func(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) error + +type gatewayCustomServeMuxOptions interface { + GatewayServeMuxOptions() []runtime.ServeMuxOption +} +type grpcGatewayCustomInterceptor interface { + GatewayHTTPInterceptor(http.Handler) http.Handler +} + +type gatewayCustomCallOptions interface { + GatewayCallOptions() []grpc.DialOption +} + +func StartGateway(ctx context.Context, g Gateway) { + mux := createMux(ctx, g) + serveGateway(ctx, mux, gatewayPort(g.GatewayPort()), g) +} + +func createMux(ctx context.Context, g Gateway) *runtime.ServeMux { + muxOptions := DefaultServeMuxOptions + if customOpts, ok := g.(gatewayCustomServeMuxOptions); ok { + muxOptions = customOpts.GatewayServeMuxOptions() + } + mux := runtime.NewServeMux(muxOptions...) + + opts := []grpc.DialOption{grpc.WithInsecure()} + opts = append(opts, client_middleware.DefaultTracingStatsClient()) + + if customOpts, ok := g.(gatewayCustomCallOptions); ok { + opts = append(opts, customOpts.GatewayCallOptions()...) + } + err := g.Gateway()(ctx, mux, g.GRPCEndpoint(), opts) + logging.Log("SERVE-7B7G0E").OnError(err).Panic("failed to create mux for grpc gateway") + + return mux +} + +func addInterceptors(handler http.Handler, g Gateway) http.Handler { + handler = http_mw.DefaultTraceHandler(handler) + if interceptor, ok := g.(grpcGatewayCustomInterceptor); ok { + handler = interceptor.GatewayHTTPInterceptor(handler) + } + return http_mw.CORSInterceptorOpts(http_mw.DefaultCORSOptions, handler) +} + +func serveGateway(ctx context.Context, handler http.Handler, port string, g Gateway) { + server := &http.Server{ + Handler: addInterceptors(handler, g), + } + + listener := http_util.CreateListener(port) + + go func() { + <-ctx.Done() + err := server.Shutdown(ctx) + logging.Log("SERVE-m7kBlq").OnError(err).Warn("error during graceful shutdown of grpc gateway") + }() + + go func() { + err := server.Serve(listener) + logging.Log("SERVE-tBHR60").OnError(err).Panic("grpc gateway serve failed") + }() + logging.LogWithFields("SERVE-KHh0Cb", "port", port).Info("grpc gateway is listening") +} + +func gatewayPort(port string) string { + if port == "" { + return defaultGatewayPort + } + return port +} diff --git a/internal/api/grpc/auth_interceptor.go b/internal/api/grpc/server/middleware/auth_interceptor.go similarity index 80% rename from internal/api/grpc/auth_interceptor.go rename to internal/api/grpc/server/middleware/auth_interceptor.go index 4e91d11714..fbdd42bc9b 100644 --- a/internal/api/grpc/auth_interceptor.go +++ b/internal/api/grpc/server/middleware/auth_interceptor.go @@ -1,4 +1,4 @@ -package grpc +package middleware import ( "context" @@ -7,7 +7,9 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "github.com/caos/zitadel/internal/api" "github.com/caos/zitadel/internal/api/auth" + grpc_util "github.com/caos/zitadel/internal/api/grpc" ) func AuthorizationInterceptor(verifier auth.TokenVerifier, authConfig *auth.Config, authMethods auth.MethodMapping) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { @@ -17,12 +19,12 @@ func AuthorizationInterceptor(verifier auth.TokenVerifier, authConfig *auth.Conf return handler(ctx, req) } - authToken := GetAuthorizationHeader(ctx) + authToken := grpc_util.GetAuthorizationHeader(ctx) if authToken == "" { return nil, status.Error(codes.Unauthenticated, "auth header missing") } - orgID := GetHeader(ctx, ZitadelOrgID) + orgID := grpc_util.GetHeader(ctx, api.ZitadelOrgID) ctx, err := auth.CheckUserAuthorization(ctx, req, authToken, orgID, verifier, authConfig, authOpt) if err != nil { diff --git a/internal/api/grpc/error_interceptor.go b/internal/api/grpc/server/middleware/error_interceptor.go similarity index 74% rename from internal/api/grpc/error_interceptor.go rename to internal/api/grpc/server/middleware/error_interceptor.go index 690ab9a2e9..2d8f7f3e47 100644 --- a/internal/api/grpc/error_interceptor.go +++ b/internal/api/grpc/server/middleware/error_interceptor.go @@ -1,14 +1,16 @@ -package grpc +package middleware import ( "context" "google.golang.org/grpc" + + grpc_util "github.com/caos/zitadel/internal/api/grpc" ) func ErrorHandler() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { resp, err := handler(ctx, req) - return resp, CaosToGRPCError(err) + return resp, grpc_util.CaosToGRPCError(err) } } diff --git a/internal/api/grpc/server/middleware/tracing.go b/internal/api/grpc/server/middleware/tracing.go new file mode 100644 index 0000000000..c8e2cc9d5d --- /dev/null +++ b/internal/api/grpc/server/middleware/tracing.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "context" + "strings" + + "go.opencensus.io/plugin/ocgrpc" + "go.opencensus.io/trace" + "google.golang.org/grpc" + "google.golang.org/grpc/stats" + + "github.com/caos/zitadel/internal/tracing" +) + +type GRPCMethod string + +func TracingStatsServer(ignoredMethods ...GRPCMethod) grpc.ServerOption { + return grpc.StatsHandler(&tracingServerHandler{ignoredMethods, ocgrpc.ServerHandler{StartOptions: trace.StartOptions{Sampler: tracing.Sampler(), SpanKind: trace.SpanKindServer}}}) +} + +type tracingServerHandler struct { + IgnoredMethods []GRPCMethod + ocgrpc.ServerHandler +} + +func (s *tracingServerHandler) TagRPC(ctx context.Context, tagInfo *stats.RPCTagInfo) context.Context { + for _, method := range s.IgnoredMethods { + if strings.HasSuffix(tagInfo.FullMethodName, string(method)) { + return ctx + } + } + return s.ServerHandler.TagRPC(ctx, tagInfo) +} diff --git a/internal/api/grpc/probes.go b/internal/api/grpc/server/probes.go similarity index 98% rename from internal/api/grpc/probes.go rename to internal/api/grpc/server/probes.go index 8ec10c585c..91379da51e 100644 --- a/internal/api/grpc/probes.go +++ b/internal/api/grpc/server/probes.go @@ -1,4 +1,4 @@ -package grpc +package server import ( "context" diff --git a/internal/api/grpc/server/server.go b/internal/api/grpc/server/server.go new file mode 100644 index 0000000000..eb6f9f51f7 --- /dev/null +++ b/internal/api/grpc/server/server.go @@ -0,0 +1,53 @@ +package server + +import ( + "context" + "net" + + "github.com/caos/logging" + "google.golang.org/grpc" + + "github.com/caos/zitadel/internal/api/http" +) + +const ( + defaultGrpcPort = "80" +) + +type Server interface { + GRPCPort() string + GRPCServer() (*grpc.Server, error) +} + +func StartServer(ctx context.Context, s Server) { + port := grpcPort(s.GRPCPort()) + listener := http.CreateListener(port) + server := createGrpcServer(s) + serveServer(ctx, server, listener, port) +} + +func createGrpcServer(s Server) *grpc.Server { + grpcServer, err := s.GRPCServer() + logging.Log("SERVE-k280HZ").OnError(err).Panic("failed to create grpc server") + return grpcServer +} + +func serveServer(ctx context.Context, server *grpc.Server, listener net.Listener, port string) { + go func() { + <-ctx.Done() + server.GracefulStop() + }() + + go func() { + err := server.Serve(listener) + logging.Log("SERVE-Ga3e94").OnError(err).Panic("grpc server serve failed") + }() + logging.LogWithFields("SERVE-bZ44QM", "port", port).Info("grpc server is listening") +} + +func grpcPort(port string) string { + if port == "" { + return defaultGrpcPort + } + return port +} diff --git a/internal/api/header.go b/internal/api/header.go new file mode 100644 index 0000000000..857a91ff3b --- /dev/null +++ b/internal/api/header.go @@ -0,0 +1,12 @@ +package api + +const ( + Authorization = "authorization" + Accept = "accept" + AcceptLanguage = "accept-language" + ContentType = "content-type" + Location = "location" + Origin = "origin" + + ZitadelOrgID = "x-zitadel-orgid" +) diff --git a/internal/api/html/i18n.go b/internal/api/html/i18n.go new file mode 100644 index 0000000000..dd5d272668 --- /dev/null +++ b/internal/api/html/i18n.go @@ -0,0 +1,108 @@ +package html + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "path" + + "github.com/BurntSushi/toml" + "github.com/caos/logging" + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/text/language" + "gopkg.in/yaml.v2" + + "github.com/caos/zitadel/internal/api" + http_util "github.com/caos/zitadel/internal/api/http" + "github.com/caos/zitadel/internal/errors" +) + +type Translator struct { + bundle *i18n.Bundle + cookieName string + cookieHandler *http_util.CookieHandler +} + +type TranslatorConfig struct { + Path string + DefaultLanguage language.Tag + CookieName string +} + +func NewTranslator(config TranslatorConfig) (*Translator, error) { + t := new(Translator) + var err error + t.bundle, err = newBundle(config.Path, config.DefaultLanguage) + if err != nil { + return nil, err + } + t.cookieHandler = http_util.NewCookieHandler() + t.cookieName = config.CookieName + return t, nil +} + +func newBundle(i18nDir string, defaultLanguage language.Tag) (*i18n.Bundle, error) { + bundle := i18n.NewBundle(defaultLanguage) + bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) + bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal) + bundle.RegisterUnmarshalFunc("json", json.Unmarshal) + bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + files, err := ioutil.ReadDir(i18nDir) + if err != nil { + return nil, errors.ThrowNotFound(err, "HTML-MnXRie", "path not found") + } + for _, file := range files { + bundle.MustLoadMessageFile(path.Join(i18nDir, file.Name())) + } + return bundle, nil +} + +func (t *Translator) LocalizeFromRequest(r *http.Request, id string, args map[string]interface{}) string { + s, err := t.localizerFromRequest(r).Localize(&i18n.LocalizeConfig{ + MessageID: id, + TemplateData: args, + }) + if err != nil { + logging.Log("HTML-MsF5sx").WithError(err).Warnf("missing translation") + return id + } + return s +} + +func (t *Translator) Localize(id string, args map[string]interface{}) string { + s, _ := t.localizer().Localize(&i18n.LocalizeConfig{ + MessageID: id, + TemplateData: args, + }) + return s +} + +func (t *Translator) Lang(r *http.Request) language.Tag { + matcher := language.NewMatcher(t.bundle.LanguageTags()) + tag, _ := language.MatchStrings(matcher, t.langsFromRequest(r)...) + return tag +} + +func (t *Translator) SetLangCookie(w http.ResponseWriter, lang language.Tag) { + t.cookieHandler.SetCookie(w, t.cookieName, lang.String()) +} + +func (t *Translator) localizerFromRequest(r *http.Request) *i18n.Localizer { + return t.localizer(t.langsFromRequest(r)...) +} + +func (t *Translator) localizer(langs ...string) *i18n.Localizer { + return i18n.NewLocalizer(t.bundle, langs...) +} + +func (t *Translator) langsFromRequest(r *http.Request) []string { + langs := make([]string, 0) + if r != nil { + lang, err := t.cookieHandler.GetCookieValue(r, t.cookieName) + if err == nil { + langs = append(langs, lang) + } + langs = append(langs, r.Header.Get(api.AcceptLanguage)) + } + return langs +} diff --git a/internal/api/html/renderer.go b/internal/api/html/renderer.go new file mode 100644 index 0000000000..182087e31d --- /dev/null +++ b/internal/api/html/renderer.go @@ -0,0 +1,82 @@ +package html + +import ( + "net/http" + "path" + "text/template" + + "github.com/caos/logging" + "golang.org/x/text/language" +) + +const ( + TranslateFn = "t" +) + +type Renderer struct { + Templates map[string]*template.Template + i18n *Translator +} + +func NewRenderer(templatesDir string, tmplMapping map[string]string, funcs map[string]interface{}, translatorConfig TranslatorConfig) (*Renderer, error) { + var err error + r := new(Renderer) + r.i18n, err = NewTranslator(translatorConfig) + if err != nil { + return nil, err + } + r.loadTemplates(templatesDir, tmplMapping, funcs) + return r, nil +} + +func (r *Renderer) RenderTemplate(w http.ResponseWriter, req *http.Request, tmpl *template.Template, data interface{}, reqFuncs map[string]interface{}) { + reqFuncs = r.registerTranslateFn(req, reqFuncs) + if err := tmpl.Funcs(reqFuncs).Execute(w, data); err != nil { + logging.Log("HTML-lF8F6w").WithError(err).WithField("template", tmpl.Name).Error("error rendering template") + } +} + +func (r *Renderer) Localize(id string, args map[string]interface{}) string { + return r.i18n.Localize(id, args) +} + +func (r *Renderer) LocalizeFromRequest(req *http.Request, id string, args map[string]interface{}) string { + return r.i18n.LocalizeFromRequest(req, id, args) +} +func (r *Renderer) Lang(req *http.Request) language.Tag { + return r.i18n.Lang(req) +} + +func (r *Renderer) loadTemplates(templatesDir string, tmplMapping map[string]string, funcs map[string]interface{}) { + funcs = r.registerTranslateFn(nil, funcs) + funcs[TranslateFn] = func(id string, args ...interface{}) string { + return id + } + tmpls := template.Must(template.New("").Funcs(funcs).ParseGlob(path.Join(templatesDir, "*.html"))) + r.Templates = make(map[string]*template.Template, len(tmplMapping)) + for name, file := range tmplMapping { + r.Templates[name] = tmpls.Lookup(file) + } +} + +func (r *Renderer) registerTranslateFn(req *http.Request, funcs map[string]interface{}) map[string]interface{} { + if funcs == nil { + funcs = make(map[string]interface{}) + } + funcs[TranslateFn] = func(id string, args ...interface{}) string { + m := map[string]interface{}{} + var key string + for i, arg := range args { + if i%2 == 0 { + key = arg.(string) + continue + } + m[key] = arg + } + if r == nil { + return r.Localize(id, m) + } + return r.LocalizeFromRequest(req, id, m) + } + return funcs +} diff --git a/internal/api/http/listener.go b/internal/api/http/listener.go new file mode 100644 index 0000000000..3e9056c384 --- /dev/null +++ b/internal/api/http/listener.go @@ -0,0 +1,21 @@ +package http + +import ( + "net" + "strings" + + "github.com/caos/logging" +) + +func CreateListener(endpoint string) net.Listener { + l, err := net.Listen("tcp", listenerEndpoint(endpoint)) + logging.Log("SERVE-6vasef").OnError(err).Fatal("creating listener failed") + return l +} + +func listenerEndpoint(endpoint string) string { + if strings.Contains(endpoint, ":") { + return endpoint + } + return ":" + endpoint +} diff --git a/internal/api/http/middleware/cors_interceptor.go b/internal/api/http/middleware/cors_interceptor.go new file mode 100644 index 0000000000..6fd154b469 --- /dev/null +++ b/internal/api/http/middleware/cors_interceptor.go @@ -0,0 +1,47 @@ +package middleware + +import ( + "net/http" + + "github.com/rs/cors" + + "github.com/caos/zitadel/internal/api" +) + +var ( + DefaultCORSOptions = cors.Options{ + AllowCredentials: true, + AllowedHeaders: []string{ + api.Origin, + api.ContentType, + api.Accept, + api.AcceptLanguage, + api.Authorization, + api.ZitadelOrgID, + "x-grpc-web", //TODO: needed + }, + AllowedMethods: []string{ + http.MethodOptions, + http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + }, + ExposedHeaders: []string{ + api.Location, + }, + AllowedOrigins: []string{ + "http://localhost:*", + }, + } +) + +func CORSInterceptorOpts(opts cors.Options, h http.Handler) http.Handler { + return cors.New(opts).Handler(h) +} + +func CORSInterceptor(h http.Handler) http.Handler { + return CORSInterceptorOpts(DefaultCORSOptions, h) +} diff --git a/internal/api/http/middleware/trace_interceptor.go b/internal/api/http/middleware/trace_interceptor.go new file mode 100644 index 0000000000..4f95cc67c1 --- /dev/null +++ b/internal/api/http/middleware/trace_interceptor.go @@ -0,0 +1,12 @@ +package middleware + +import ( + "net/http" + + "github.com/caos/zitadel/internal/api" + "github.com/caos/zitadel/internal/tracing" +) + +func DefaultTraceHandler(handler http.Handler) http.Handler { + return tracing.TraceHandler(handler, api.Probes...) +} diff --git a/internal/api/probes.go b/internal/api/probes.go new file mode 100644 index 0000000000..9ae0f6a1d9 --- /dev/null +++ b/internal/api/probes.go @@ -0,0 +1,11 @@ +package api + +const ( + Healthz = "/Healthz" + Readiness = "/Ready" + Validation = "/Validate" +) + +var ( + Probes = []string{Healthz, Readiness, Validation} +) diff --git a/internal/config/config.go b/internal/config/config.go index 5c524e1525..5740cd5f1d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,31 +7,21 @@ import ( "path/filepath" "github.com/BurntSushi/toml" - "github.com/ghodss/yaml" + "gopkg.in/yaml.v2" "github.com/caos/zitadel/internal/errors" ) -type Reader interface { - Unmarshal(data []byte, o interface{}) error -} - type ValidatableConfiguration interface { Validate() error } type ReaderFunc func(data []byte, o interface{}) error -func (c ReaderFunc) Unmarshal(data []byte, o interface{}) error { - return c(data, o) -} - var ( - JSONReader = ReaderFunc(json.Unmarshal) - TOMLReader = ReaderFunc(toml.Unmarshal) - YAMLReader = ReaderFunc(func(y []byte, o interface{}) error { - return yaml.Unmarshal(y, o) - }) + JSONReader = json.Unmarshal + TOMLReader = toml.Unmarshal + YAMLReader = yaml.Unmarshal ) // Read deserializes each config file to the target obj @@ -39,11 +29,11 @@ var ( // env vars are replaced in the config file as well as the file path func Read(obj interface{}, configFiles ...string) error { for _, cf := range configFiles { - configReader, err := configReaderForFile(cf) + readerFunc, err := readerFuncForFile(cf) if err != nil { return err } - if err := readConfigFile(configReader, cf, obj); err != nil { + if err := readConfigFile(readerFunc, cf, obj); err != nil { return err } } @@ -57,13 +47,9 @@ func Read(obj interface{}, configFiles ...string) error { return nil } -func readConfigFile(configReader Reader, configFile string, obj interface{}) error { +func readConfigFile(readerFunc ReaderFunc, configFile string, obj interface{}) error { configFile = os.ExpandEnv(configFile) - if _, err := os.Stat(configFile); err != nil { - return errors.ThrowNotFoundf(err, "CONFI-Hs93M", "config file %s does not exist", configFile) - } - configStr, err := ioutil.ReadFile(configFile) if err != nil { return errors.ThrowInternalf(err, "CONFI-nJk2a", "failed to read config file %s", configFile) @@ -71,14 +57,14 @@ func readConfigFile(configReader Reader, configFile string, obj interface{}) err configStr = []byte(os.ExpandEnv(string(configStr))) - if err := configReader.Unmarshal(configStr, obj); err != nil { + if err := readerFunc(configStr, obj); err != nil { return errors.ThrowInternalf(err, "CONFI-2Mc3c", "error parse config file %s", configFile) } return nil } -func configReaderForFile(configFile string) (Reader, error) { +func readerFuncForFile(configFile string) (ReaderFunc, error) { ext := filepath.Ext(configFile) switch ext { case ".yaml", ".yml": diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000000..ba517b6061 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,198 @@ +package config + +import ( + "errors" + "reflect" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" +) + +type test struct { + Test bool +} + +type validatable struct { + Test bool +} + +func (v *validatable) Validate() error { + if v.Test { + return nil + } + return errors.New("invalid") +} + +func TestRead(t *testing.T) { + type args struct { + obj interface{} + configFiles []string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + "not supoorted config file error", + args{ + configFiles: []string{"notsupported.unknown"}, + obj: nil, + }, + true, + }, + { + "non existing config file error", + args{ + configFiles: []string{"nonexisting.yaml"}, + obj: nil, + }, + true, + }, + { + "non parsable config file error", + args{ + configFiles: []string{"./testdata/non_parsable.json"}, + obj: &test{}, + }, + true, + }, + { + "invalid parsable config file error", + args{ + configFiles: []string{"./testdata/invalid.json"}, + obj: &validatable{}, + }, + true, + }, + { + "parsable config file ok", + args{ + configFiles: []string{"./testdata/valid.json"}, + obj: &test{}, + }, + false, + }, + { + "valid parsable config file ok", + args{ + configFiles: []string{"./testdata/valid.json"}, + obj: &validatable{}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Read(tt.args.obj, tt.args.configFiles...); (err != nil) != tt.wantErr { + t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_readerFuncForFile(t *testing.T) { + type args struct { + configFile string + } + tests := []struct { + name string + args args + want ReaderFunc + wantErr bool + }{ + { + "unknown extension error", + args{configFile: "test.unknown"}, + nil, + true, + }, + { + "toml", + args{configFile: "test.toml"}, + TOMLReader, + false, + }, + { + "json", + args{configFile: "test.json"}, + JSONReader, + false, + }, + { + "yaml", + args{configFile: "test.yaml"}, + YAMLReader, + false, + }, + { + "yml", + args{configFile: "test.yml"}, + YAMLReader, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := readerFuncForFile(tt.args.configFile) + if (err != nil) != tt.wantErr { + t.Errorf("configReaderForFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + funcName1 := runtime.FuncForPC(reflect.ValueOf(got).Pointer()).Name() + funcName2 := runtime.FuncForPC(reflect.ValueOf(tt.want).Pointer()).Name() + if !assert.Equal(t, funcName1, funcName2) { + t.Errorf("configReaderForFile() got = %v, want %v", funcName1, funcName2) + } + }) + } +} + +func Test_readConfigFile(t *testing.T) { + type args struct { + configReader ReaderFunc + configFile string + obj interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + "non existing config file error", + args{ + configReader: YAMLReader, + configFile: "nonexisting.json", + obj: nil, + }, + true, + }, + { + "non parsable config file error", + args{ + configReader: YAMLReader, + configFile: "./testdata/non_parsable.json", + obj: &test{}, + }, + true, + }, + { + "parsable config file no error", + args{ + configReader: YAMLReader, + configFile: "./testdata/valid.json", + obj: &test{}, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := readConfigFile(tt.args.configReader, tt.args.configFile, tt.args.obj); (err != nil) != tt.wantErr { + t.Errorf("readConfigFile() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/config/testdata/invalid.json b/internal/config/testdata/invalid.json new file mode 100644 index 0000000000..454b39367e --- /dev/null +++ b/internal/config/testdata/invalid.json @@ -0,0 +1,3 @@ +{ + "Test" : false +} \ No newline at end of file diff --git a/internal/config/testdata/non_parsable.json b/internal/config/testdata/non_parsable.json new file mode 100644 index 0000000000..8318c86b35 --- /dev/null +++ b/internal/config/testdata/non_parsable.json @@ -0,0 +1 @@ +Test \ No newline at end of file diff --git a/internal/config/testdata/valid.json b/internal/config/testdata/valid.json new file mode 100644 index 0000000000..65f3700045 --- /dev/null +++ b/internal/config/testdata/valid.json @@ -0,0 +1,3 @@ +{ + "Test" : true +} \ No newline at end of file diff --git a/internal/errors/generate/error_creator.go b/internal/errors/generate/error_creator.go index 02a5538136..52139196a1 100644 --- a/internal/errors/generate/error_creator.go +++ b/internal/errors/generate/error_creator.go @@ -32,7 +32,7 @@ func main() { fmt.Print(` !!!!! - Add status mapping in grpc/errors/caos_errors.go + Add status mapping in internal/api/grpc/caos_errors.go !!!!!`) } diff --git a/internal/tracing/caller.go b/internal/tracing/caller.go new file mode 100644 index 0000000000..c5dfb9a319 --- /dev/null +++ b/internal/tracing/caller.go @@ -0,0 +1,24 @@ +package tracing + +import ( + "runtime" + + "github.com/caos/logging" +) + +func GetCaller() string { + fpcs := make([]uintptr, 1) + + n := runtime.Callers(3, fpcs) + if n == 0 { + logging.Log("HELPE-rWjfC").Debug("no caller") + } + + caller := runtime.FuncForPC(fpcs[0] - 1) + if caller == nil { + logging.Log("HELPE-25POw").Debug("caller was nil") + } + + // Print the name of the function + return caller.Name() +} diff --git a/internal/tracing/config/config.go b/internal/tracing/config/config.go new file mode 100644 index 0000000000..e2310fbce3 --- /dev/null +++ b/internal/tracing/config/config.go @@ -0,0 +1,61 @@ +package config + +import ( + "encoding/json" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/caos/zitadel/internal/tracing" + tracing_g "github.com/caos/zitadel/internal/tracing/google" + tracing_log "github.com/caos/zitadel/internal/tracing/log" +) + +type TracingConfig struct { + Type string + Config tracing.Config +} + +var tracer = map[string]func() tracing.Config{ + "google": func() tracing.Config { return &tracing_g.Config{} }, + "log": func() tracing.Config { return &tracing_log.Config{} }, +} + +func (c *TracingConfig) UnmarshalJSON(data []byte) error { + var rc struct { + Type string + Config json.RawMessage + } + + if err := json.Unmarshal(data, &rc); err != nil { + return status.Errorf(codes.Internal, "%v parse config: %v", "TRACE-vmjS", err) + } + + c.Type = rc.Type + + var err error + c.Config, err = newTracingConfig(c.Type, rc.Config) + if err != nil { + return status.Errorf(codes.Internal, "%v parse config: %v", "TRACE-Ws9E", err) + } + + return c.Config.NewTracer() +} + +func newTracingConfig(tracerType string, configData []byte) (tracing.Config, error) { + t, ok := tracer[tracerType] + if !ok { + return nil, status.Errorf(codes.Internal, "%v No config: %v", "TRACE-HMEJ", tracerType) + } + + tracingConfig := t() + if len(configData) == 0 { + return tracingConfig, nil + } + + if err := json.Unmarshal(configData, tracingConfig); err != nil { + return nil, status.Errorf(codes.Internal, "%v Could not read conifg: %v", "TRACE-1tSS", err) + } + + return tracingConfig, nil +} diff --git a/internal/tracing/generate.go b/internal/tracing/generate.go new file mode 100644 index 0000000000..21b97622cb --- /dev/null +++ b/internal/tracing/generate.go @@ -0,0 +1,3 @@ +package tracing + +//go:generate mockgen -package mock -destination mock/tracing_mock.go github.com/caos/zitadel/internal/tracing Tracer diff --git a/internal/tracing/google/config.go b/internal/tracing/google/config.go new file mode 100644 index 0000000000..92d93f61bd --- /dev/null +++ b/internal/tracing/google/config.go @@ -0,0 +1,25 @@ +package google + +import ( + "go.opencensus.io/trace" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/caos/zitadel/internal/tracing" +) + +type Config struct { + ProjectID string + MetricPrefix string + Fraction float64 +} + +func (c *Config) NewTracer() error { + if !envIsSet() { + return status.Error(codes.InvalidArgument, "env not properly set, GOOGLE_APPLICATION_CREDENTIALS is misconfigured or missing") + } + + tracing.T = &Tracer{projectID: c.ProjectID, metricPrefix: c.MetricPrefix, sampler: trace.ProbabilitySampler(c.Fraction)} + + return tracing.T.Start() +} diff --git a/internal/tracing/google/googletracing.go b/internal/tracing/google/googletracing.go new file mode 100644 index 0000000000..3958592d00 --- /dev/null +++ b/internal/tracing/google/googletracing.go @@ -0,0 +1,95 @@ +package google + +import ( + "context" + "net/http" + "os" + "strings" + + "contrib.go.opencensus.io/exporter/stackdriver" + "go.opencensus.io/plugin/ocgrpc" + "go.opencensus.io/plugin/ochttp" + "go.opencensus.io/stats/view" + "go.opencensus.io/trace" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/tracing" +) + +type Tracer struct { + Exporter *stackdriver.Exporter + projectID string + metricPrefix string + sampler trace.Sampler +} + +func (t *Tracer) Start() (err error) { + t.Exporter, err = stackdriver.NewExporter(stackdriver.Options{ + ProjectID: t.projectID, + MetricPrefix: t.metricPrefix, + }) + if err != nil { + return errors.ThrowInternal(err, "GOOGL-4dCnX", "unable to start exporter") + } + + views := append(ocgrpc.DefaultServerViews, ocgrpc.DefaultClientViews...) + views = append(views, ochttp.DefaultClientViews...) + views = append(views, ochttp.DefaultServerViews...) + + if err = view.Register(views...); err != nil { + return errors.ThrowInternal(err, "GOOGL-Q6L6w", "unable to register view") + } + + trace.RegisterExporter(t.Exporter) + trace.ApplyConfig(trace.Config{DefaultSampler: t.sampler}) + + return nil +} + +func (t *Tracer) Sampler() trace.Sampler { + return t.sampler +} + +func (t *Tracer) NewServerInterceptorSpan(ctx context.Context, name string) (context.Context, *tracing.Span) { + return t.newSpanFromName(ctx, name, trace.WithSpanKind(trace.SpanKindServer)) +} + +func (t *Tracer) NewServerSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) { + return t.newSpan(ctx, caller, trace.WithSpanKind(trace.SpanKindServer)) +} + +func (t *Tracer) NewClientInterceptorSpan(ctx context.Context, name string) (context.Context, *tracing.Span) { + return t.newSpanFromName(ctx, name, trace.WithSpanKind(trace.SpanKindClient)) +} + +func (t *Tracer) NewClientSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) { + return t.newSpan(ctx, caller, trace.WithSpanKind(trace.SpanKindClient)) +} + +func (t *Tracer) NewSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) { + return t.newSpan(ctx, caller) +} + +func (t *Tracer) newSpan(ctx context.Context, caller string, options ...trace.StartOption) (context.Context, *tracing.Span) { + return t.newSpanFromName(ctx, caller, options...) +} + +func (t *Tracer) newSpanFromName(ctx context.Context, name string, options ...trace.StartOption) (context.Context, *tracing.Span) { + ctx, span := trace.StartSpan(ctx, name, options...) + return ctx, tracing.CreateSpan(span) +} + +func (t *Tracer) NewSpanHTTP(r *http.Request, caller string) (*http.Request, *tracing.Span) { + ctx, span := t.NewSpan(r.Context(), caller) + r = r.WithContext(ctx) + return r, span +} + +func envIsSet() bool { + gAuthCred := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") + return strings.Contains(gAuthCred, ".json") +} + +func (t *Tracer) SetErrStatus(span *trace.Span, code int32, err error, obj ...string) { + span.SetStatus(trace.Status{Code: code, Message: err.Error() + strings.Join(obj, ", ")}) +} diff --git a/internal/tracing/http_handler.go b/internal/tracing/http_handler.go new file mode 100644 index 0000000000..a3b9631863 --- /dev/null +++ b/internal/tracing/http_handler.go @@ -0,0 +1,30 @@ +package tracing + +import ( + "net/http" + "strings" + + "go.opencensus.io/plugin/ochttp" + "go.opencensus.io/trace" +) + +func TraceHandler(handler http.Handler, ignoredMethods ...string) http.Handler { + healthEndpoints := strings.Join(ignoredMethods, ";;") + + return &ochttp.Handler{ + Handler: handler, + FormatSpanName: func(r *http.Request) string { + host := r.URL.Host + if host == "" { + host = r.Host + } + return host + r.URL.Path + }, + + StartOptions: trace.StartOptions{Sampler: Sampler()}, + IsHealthEndpoint: func(r *http.Request) bool { + n := strings.Contains(healthEndpoints, r.URL.RequestURI()) + return n + }, + } +} diff --git a/internal/tracing/log/config.go b/internal/tracing/log/config.go new file mode 100644 index 0000000000..be2d9bec7a --- /dev/null +++ b/internal/tracing/log/config.go @@ -0,0 +1,21 @@ +package log + +import ( + "go.opencensus.io/trace" + + "github.com/caos/zitadel/internal/tracing" +) + +type Config struct { + Fraction float64 +} + +func (c *Config) NewTracer() error { + if c.Fraction < 1 { + c.Fraction = 1 + } + + tracing.T = &Tracer{trace.ProbabilitySampler(c.Fraction)} + + return tracing.T.Start() +} diff --git a/internal/tracing/log/logTracing.go b/internal/tracing/log/logTracing.go new file mode 100644 index 0000000000..ce34c68fce --- /dev/null +++ b/internal/tracing/log/logTracing.go @@ -0,0 +1,74 @@ +package log + +import ( + "context" + "net/http" + + "go.opencensus.io/examples/exporter" + "go.opencensus.io/plugin/ocgrpc" + "go.opencensus.io/plugin/ochttp" + "go.opencensus.io/stats/view" + "go.opencensus.io/trace" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/tracing" +) + +type Tracer struct { + sampler trace.Sampler +} + +func (t *Tracer) Start() error { + trace.RegisterExporter(&exporter.PrintExporter{}) + + views := append(ocgrpc.DefaultServerViews, ocgrpc.DefaultClientViews...) + views = append(views, ochttp.DefaultClientViews...) + views = append(views, ochttp.DefaultServerViews...) + + if err := view.Register(views...); err != nil { + return errors.ThrowInternal(err, "LOG-PoFiB", "unable to register view") + } + + trace.ApplyConfig(trace.Config{DefaultSampler: t.sampler}) + + return nil +} + +func (t *Tracer) Sampler() trace.Sampler { + return t.sampler +} + +func (t *Tracer) NewServerInterceptorSpan(ctx context.Context, name string) (context.Context, *tracing.Span) { + return t.newSpanFromName(ctx, name, trace.WithSpanKind(trace.SpanKindServer)) +} + +func (t *Tracer) NewServerSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) { + return t.newSpan(ctx, caller, trace.WithSpanKind(trace.SpanKindServer)) +} + +func (t *Tracer) NewClientInterceptorSpan(ctx context.Context, name string) (context.Context, *tracing.Span) { + return t.newSpanFromName(ctx, name, trace.WithSpanKind(trace.SpanKindClient)) +} + +func (t *Tracer) NewClientSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) { + return t.newSpan(ctx, caller, trace.WithSpanKind(trace.SpanKindClient)) +} + +func (t *Tracer) NewSpan(ctx context.Context, caller string) (context.Context, *tracing.Span) { + return t.newSpan(ctx, caller) +} + +func (t *Tracer) newSpan(ctx context.Context, caller string, options ...trace.StartOption) (context.Context, *tracing.Span) { + return t.newSpanFromName(ctx, caller, options...) +} + +func (t *Tracer) newSpanFromName(ctx context.Context, name string, options ...trace.StartOption) (context.Context, *tracing.Span) { + ctx, span := trace.StartSpan(ctx, name, options...) + return ctx, tracing.CreateSpan(span) +} + +func (t *Tracer) NewSpanHTTP(r *http.Request, caller string) (*http.Request, *tracing.Span) { + ctx, span := t.NewSpan(r.Context(), caller) + r = r.WithContext(ctx) + return r, span +} diff --git a/internal/tracing/mock/tracing_mock.go b/internal/tracing/mock/tracing_mock.go new file mode 100644 index 0000000000..d765782360 --- /dev/null +++ b/internal/tracing/mock/tracing_mock.go @@ -0,0 +1,155 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/caos/zitadel/internal/tracing (interfaces: Tracer) + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + tracing "github.com/caos/zitadel/internal/tracing" + gomock "github.com/golang/mock/gomock" + trace "go.opencensus.io/trace" + http "net/http" + reflect "reflect" +) + +// MockTracer is a mock of Tracer interface +type MockTracer struct { + ctrl *gomock.Controller + recorder *MockTracerMockRecorder +} + +// MockTracerMockRecorder is the mock recorder for MockTracer +type MockTracerMockRecorder struct { + mock *MockTracer +} + +// NewMockTracer creates a new mock instance +func NewMockTracer(ctrl *gomock.Controller) *MockTracer { + mock := &MockTracer{ctrl: ctrl} + mock.recorder = &MockTracerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockTracer) EXPECT() *MockTracerMockRecorder { + return m.recorder +} + +// NewClientInterceptorSpan mocks base method +func (m *MockTracer) NewClientInterceptorSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewClientInterceptorSpan", arg0, arg1) + ret0, _ := ret[0].(context.Context) + ret1, _ := ret[1].(*tracing.Span) + return ret0, ret1 +} + +// NewClientInterceptorSpan indicates an expected call of NewClientInterceptorSpan +func (mr *MockTracerMockRecorder) NewClientInterceptorSpan(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientInterceptorSpan", reflect.TypeOf((*MockTracer)(nil).NewClientInterceptorSpan), arg0, arg1) +} + +// NewClientSpan mocks base method +func (m *MockTracer) NewClientSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewClientSpan", arg0, arg1) + ret0, _ := ret[0].(context.Context) + ret1, _ := ret[1].(*tracing.Span) + return ret0, ret1 +} + +// NewClientSpan indicates an expected call of NewClientSpan +func (mr *MockTracerMockRecorder) NewClientSpan(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientSpan", reflect.TypeOf((*MockTracer)(nil).NewClientSpan), arg0, arg1) +} + +// NewServerInterceptorSpan mocks base method +func (m *MockTracer) NewServerInterceptorSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewServerInterceptorSpan", arg0, arg1) + ret0, _ := ret[0].(context.Context) + ret1, _ := ret[1].(*tracing.Span) + return ret0, ret1 +} + +// NewServerInterceptorSpan indicates an expected call of NewServerInterceptorSpan +func (mr *MockTracerMockRecorder) NewServerInterceptorSpan(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewServerInterceptorSpan", reflect.TypeOf((*MockTracer)(nil).NewServerInterceptorSpan), arg0, arg1) +} + +// NewServerSpan mocks base method +func (m *MockTracer) NewServerSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewServerSpan", arg0, arg1) + ret0, _ := ret[0].(context.Context) + ret1, _ := ret[1].(*tracing.Span) + return ret0, ret1 +} + +// NewServerSpan indicates an expected call of NewServerSpan +func (mr *MockTracerMockRecorder) NewServerSpan(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewServerSpan", reflect.TypeOf((*MockTracer)(nil).NewServerSpan), arg0, arg1) +} + +// NewSpan mocks base method +func (m *MockTracer) NewSpan(arg0 context.Context, arg1 string) (context.Context, *tracing.Span) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewSpan", arg0, arg1) + ret0, _ := ret[0].(context.Context) + ret1, _ := ret[1].(*tracing.Span) + return ret0, ret1 +} + +// NewSpan indicates an expected call of NewSpan +func (mr *MockTracerMockRecorder) NewSpan(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSpan", reflect.TypeOf((*MockTracer)(nil).NewSpan), arg0, arg1) +} + +// NewSpanHTTP mocks base method +func (m *MockTracer) NewSpanHTTP(arg0 *http.Request, arg1 string) (*http.Request, *tracing.Span) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewSpanHTTP", arg0, arg1) + ret0, _ := ret[0].(*http.Request) + ret1, _ := ret[1].(*tracing.Span) + return ret0, ret1 +} + +// NewSpanHTTP indicates an expected call of NewSpanHTTP +func (mr *MockTracerMockRecorder) NewSpanHTTP(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewSpanHTTP", reflect.TypeOf((*MockTracer)(nil).NewSpanHTTP), arg0, arg1) +} + +// Sampler mocks base method +func (m *MockTracer) Sampler() trace.Sampler { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Sampler") + ret0, _ := ret[0].(trace.Sampler) + return ret0 +} + +// Sampler indicates an expected call of Sampler +func (mr *MockTracerMockRecorder) Sampler() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sampler", reflect.TypeOf((*MockTracer)(nil).Sampler)) +} + +// Start mocks base method +func (m *MockTracer) Start() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Start") + ret0, _ := ret[0].(error) + return ret0 +} + +// Start indicates an expected call of Start +func (mr *MockTracerMockRecorder) Start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockTracer)(nil).Start)) +} diff --git a/internal/tracing/mock/tracing_mock_impl.go b/internal/tracing/mock/tracing_mock_impl.go new file mode 100644 index 0000000000..7e7335d851 --- /dev/null +++ b/internal/tracing/mock/tracing_mock_impl.go @@ -0,0 +1,20 @@ +package mock + +import ( + "context" + "testing" + + "github.com/golang/mock/gomock" + + "github.com/caos/zitadel/internal/tracing" +) + +func NewSimpleMockTracer(t *testing.T) *MockTracer { + return NewMockTracer(gomock.NewController(t)) +} + +func ExpectServerSpan(ctx context.Context, mock interface{}) { + m := mock.(*MockTracer) + any := gomock.Any() + m.EXPECT().NewServerSpan(any, any).AnyTimes().Return(ctx, &tracing.Span{}) +} diff --git a/internal/tracing/span.go b/internal/tracing/span.go new file mode 100644 index 0000000000..d958cb4b52 --- /dev/null +++ b/internal/tracing/span.go @@ -0,0 +1,89 @@ +package tracing + +import ( + "fmt" + "strconv" + + "go.opencensus.io/trace" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type Span struct { + span *trace.Span + attributes []trace.Attribute +} + +func CreateSpan(span *trace.Span) *Span { + return &Span{span: span, attributes: []trace.Attribute{}} +} + +func (s *Span) End() { + if s.span == nil { + return + } + s.span.AddAttributes(s.attributes...) + s.span.End() +} + +func (s *Span) EndWithError(err error) { + s.SetStatusByError(err) + s.End() +} + +func (s *Span) SetStatusByError(err error) { + if s.span == nil { + return + } + s.span.SetStatus(statusFromError(err)) +} + +func statusFromError(err error) trace.Status { + if statusErr, ok := status.FromError(err); ok { + return trace.Status{Code: int32(statusErr.Code()), Message: statusErr.Message()} + } + return trace.Status{Code: int32(codes.Unknown), Message: "Unknown"} +} + +// AddAnnotation creates an annotation. The annotation will not be added to the tracing use Annotate(msg) afterwards +func (s *Span) AddAnnotation(key string, value interface{}) *Span { + attribute, err := toTraceAttribute(key, value) + if err != nil { + return s + } + s.attributes = append(s.attributes, attribute) + return s +} + +// Annotate creates an annotation in tracing. Before added annotations will be set +func (s *Span) Annotate(message string) *Span { + if s.span == nil { + return s + } + s.span.Annotate(s.attributes, message) + s.attributes = []trace.Attribute{} + return s +} + +func (s *Span) Annotatef(format string, addiations ...interface{}) *Span { + s.Annotate(fmt.Sprintf(format, addiations...)) + return s +} + +func toTraceAttribute(key string, value interface{}) (attr trace.Attribute, err error) { + switch value := value.(type) { + case bool: + return trace.BoolAttribute(key, value), nil + case string: + return trace.StringAttribute(key, value), nil + } + if valueInt, err := convertToInt64(value); err == nil { + return trace.Int64Attribute(key, valueInt), nil + } + return attr, status.Error(codes.InvalidArgument, "Attribute is not of type bool, string or int64") +} + +func convertToInt64(value interface{}) (int64, error) { + valueString := fmt.Sprintf("%v", value) + return strconv.ParseInt(valueString, 10, 64) +} diff --git a/internal/tracing/tracing.go b/internal/tracing/tracing.go new file mode 100644 index 0000000000..61fdc4b318 --- /dev/null +++ b/internal/tracing/tracing.go @@ -0,0 +1,74 @@ +package tracing + +import ( + "context" + "net/http" + + "go.opencensus.io/trace" +) + +type Tracer interface { + Start() error + NewSpan(ctx context.Context, caller string) (context.Context, *Span) + NewClientSpan(ctx context.Context, caller string) (context.Context, *Span) + NewServerSpan(ctx context.Context, caller string) (context.Context, *Span) + NewClientInterceptorSpan(ctx context.Context, name string) (context.Context, *Span) + NewServerInterceptorSpan(ctx context.Context, name string) (context.Context, *Span) + NewSpanHTTP(r *http.Request, caller string) (*http.Request, *Span) + Sampler() trace.Sampler +} + +type Config interface { + NewTracer() error +} + +var T Tracer + +func Sampler() trace.Sampler { + if T == nil { + return trace.NeverSample() + } + return T.Sampler() +} + +func NewSpan(ctx context.Context) (context.Context, *Span) { + if T == nil { + return ctx, CreateSpan(nil) + } + return T.NewSpan(ctx, GetCaller()) +} + +func NewClientSpan(ctx context.Context) (context.Context, *Span) { + if T == nil { + return ctx, CreateSpan(nil) + } + return T.NewClientSpan(ctx, GetCaller()) +} + +func NewServerSpan(ctx context.Context) (context.Context, *Span) { + if T == nil { + return ctx, CreateSpan(nil) + } + return T.NewServerSpan(ctx, GetCaller()) +} + +func NewClientInterceptorSpan(ctx context.Context, name string) (context.Context, *Span) { + if T == nil { + return ctx, CreateSpan(nil) + } + return T.NewClientInterceptorSpan(ctx, name) +} + +func NewServerInterceptorSpan(ctx context.Context, name string) (context.Context, *Span) { + if T == nil { + return ctx, CreateSpan(nil) + } + return T.NewServerInterceptorSpan(ctx, name) +} + +func NewSpanHTTP(r *http.Request) (*http.Request, *Span) { + if T == nil { + return r, CreateSpan(nil) + } + return T.NewSpanHTTP(r, GetCaller()) +}