mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 02:54:20 +00:00
commit
e3f9e49ef6
295
cmd/zitadel/authz.yaml
Normal file
295
cmd/zitadel/authz.yaml
Normal file
@ -0,0 +1,295 @@
|
||||
AuthZ:
|
||||
RolePermissionMappings:
|
||||
- Role: 'IAM_OWNER'
|
||||
Permissions:
|
||||
- "org.read"
|
||||
- "org.write"
|
||||
- "org.member.read"
|
||||
- "org.member.write"
|
||||
- "org.member.delete"
|
||||
- "user.read"
|
||||
- "user.write"
|
||||
- "user.delete"
|
||||
- "user.grant.read"
|
||||
- "user.grant.write"
|
||||
- "policy.read"
|
||||
- "policy.write"
|
||||
- "policy.delete"
|
||||
- "project.read"
|
||||
- "project.write"
|
||||
- "project.member.read"
|
||||
- "project.member.write"
|
||||
- "project.member.delete"
|
||||
- "project.role.read"
|
||||
- "project.role.write"
|
||||
- "project.role.delete"
|
||||
- "project.app.read"
|
||||
- "project.app.write"
|
||||
- "project.grant.read"
|
||||
- "project.grant.write"
|
||||
- "project.grant.member.read"
|
||||
- "project.grant.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- Role: 'ORG_OWNER'
|
||||
Permissions:
|
||||
- "org.read"
|
||||
- "org.write"
|
||||
- "org.member.read"
|
||||
- "org.member.write"
|
||||
- "org.member.delete"
|
||||
- "user.read"
|
||||
- "user.write"
|
||||
- "user.delete"
|
||||
- "user.grant.read"
|
||||
- "user.grant.write"
|
||||
- "policy.read"
|
||||
- "policy.write"
|
||||
- "policy.delete"
|
||||
- "project.read"
|
||||
- "project.write"
|
||||
- "project.member.read"
|
||||
- "project.member.write"
|
||||
- "project.member.delete"
|
||||
- "project.role.read"
|
||||
- "project.role.write"
|
||||
- "project.role.delete"
|
||||
- "project.app.read"
|
||||
- "project.app.write"
|
||||
- "project.grant.read"
|
||||
- "project.grant.write"
|
||||
- "project.grant.member.read"
|
||||
- "project.grant.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- Role: 'ORG_EDITOR'
|
||||
Permissions:
|
||||
- "org.read"
|
||||
- "org.write"
|
||||
- Role: 'ORG_VIEWER'
|
||||
Permissions:
|
||||
- "org.read"
|
||||
- Role: 'ORG_MEMBER_EDITOR'
|
||||
Permissions:
|
||||
- "org.read"
|
||||
- "org.member.read"
|
||||
- "org.member.write"
|
||||
- "org.member.delete"
|
||||
- Role: 'ORG_MEMBER_VIEWER'
|
||||
Permissions:
|
||||
- "org.read"
|
||||
- "org.member.read"
|
||||
- Role: 'ORG_PROJECT_CREATOR'
|
||||
Permissions:
|
||||
- "project.read:self"
|
||||
- "project.write"
|
||||
- Role: 'ORG_PROJECT_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.write"
|
||||
- "project.member.read"
|
||||
- "project.member.write"
|
||||
- "project.member.delete"
|
||||
- "project.role.read"
|
||||
- "project.role.write"
|
||||
- "project.role.delete"
|
||||
- "project.app.read"
|
||||
- "project.app.write"
|
||||
- "project.grant.read"
|
||||
- "project.grant.write"
|
||||
- "project.grant.member.read"
|
||||
- "project.grant.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- Role: 'ORG_PROJECT_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.member.read"
|
||||
- "project.role.read"
|
||||
- "project.app.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.member.read"
|
||||
- Role: 'ORG_PROJECT_MEMBER_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.member.read"
|
||||
- "project.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- Role: 'ORG_PROJECT_MEMBER_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.member.read"
|
||||
- Role: 'ORG_PROJECT_ROLE_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.role.read"
|
||||
- "project.role.write"
|
||||
- "project.role.delete"
|
||||
- Role: 'ORG_PROJECT_ROLE_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.role.read"
|
||||
- Role: 'ORG_PROJECT_APP_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.app.read"
|
||||
- "project.app.write"
|
||||
- Role: 'ORG_PROJECT_APP_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.app.read"
|
||||
- Role: 'ORG_PROJECT_GRANT_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.write"
|
||||
- "project.grant.member.read"
|
||||
- "project.grant.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- Role: 'ORG_PROJECT_GRANT_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- Role: 'ORG_PROJECT_GRANT_MEMBER_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.member.read"
|
||||
- "project.grant.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- Role: 'ORG_PROJECT_GRANT_MEMBER_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.member.read"
|
||||
- Role: 'ORG_USER_EDITOR'
|
||||
Permissions:
|
||||
- "user.read"
|
||||
- "user.write"
|
||||
- "user.delete"
|
||||
- Role: 'ORG_USER_VIEWER'
|
||||
Permissions:
|
||||
- "user.read"
|
||||
- Role: 'ORG_USER_GRANT_EDITOR'
|
||||
Permissions:
|
||||
- "user.read"
|
||||
- "user.grant.read"
|
||||
- "user.grant.write"
|
||||
- "project.read"
|
||||
- Role: 'ORG_USER_GRANT_VIEWER'
|
||||
Permissions:
|
||||
- "user.read"
|
||||
- "user.grant.read"
|
||||
- Role: 'ORG_POLICY_EDITOR'
|
||||
Permissions:
|
||||
- "policy.read"
|
||||
- "policy.write"
|
||||
- "policy.delete"
|
||||
- Role: 'ORG_POLICY_VIEWER'
|
||||
Permissions:
|
||||
- "policy.read"
|
||||
- Role: 'PROJECT_OWNER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.write"
|
||||
- "project.member.read"
|
||||
- "project.member.write"
|
||||
- "project.member.delete"
|
||||
- "project.role.read"
|
||||
- "project.role.write"
|
||||
- "project.role.delete"
|
||||
- "project.app.read"
|
||||
- "project.app.write"
|
||||
- "project.grant.read"
|
||||
- "project.grant.write"
|
||||
- "project.grant.member.read"
|
||||
- "project.grant.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- "project.user.grant.read"
|
||||
- "project.user.grant.write"
|
||||
- "project.user.grant.delete"
|
||||
- Role: 'PROJECT_MEMBER_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.member.read"
|
||||
- "project.member.write"
|
||||
- "project.member.delete"
|
||||
- Role: 'PROJECT_MEMBER_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.member.read"
|
||||
- Role: 'PROJECT_ROLE_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.role.read"
|
||||
- "project.role.write"
|
||||
- "project.role.delete"
|
||||
- Role: 'PROJECT_APP_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.app.read"
|
||||
- "project.app.write"
|
||||
- Role: 'PROJECT_APP_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.app.read"
|
||||
- Role: 'PROJECT_GRANT_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.write"
|
||||
- Role: 'PROJECT_GRANT_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- Role: 'PROJECT_GRANT_MEMBER_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.member.read"
|
||||
- "project.grant.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- Role: 'PROJECT_GRANT_MEMBER_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.member.read"
|
||||
- Role: 'PROJECT_USER_GRANT_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.user.grant.read"
|
||||
- "project.user.grant.write"
|
||||
- "project.user.grant.delete"
|
||||
- Role: 'PROJECT_USER_GRANT_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.user.grant.read"
|
||||
- Role: 'PROJECT_GRANT_OWNER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.write"
|
||||
- "project.grant.member.read"
|
||||
- "project.grant.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- Role: 'PROJECT_GRANT_MEMBER_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.member.read"
|
||||
- "project.grant.member.write"
|
||||
- "project.grant.member.delete"
|
||||
- Role: 'PROJECT_GRANT_MEMBER_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.member.read"
|
||||
- Role: 'PROJECT_GRANT_USER_GRANT_EDITOR'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.user.grant.read"
|
||||
- "project.grant.user.grant.write"
|
||||
- "project.grant.user.grant.delete"
|
||||
- Role: 'PROJECT_GRANT_USER_GRANT_VIEWER'
|
||||
Permissions:
|
||||
- "project.read"
|
||||
- "project.grant.read"
|
||||
- "project.grant.user.grant.read"
|
@ -6,50 +6,63 @@ import (
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
authz "github.com/caos/zitadel/internal/api/auth"
|
||||
"github.com/caos/zitadel/internal/config"
|
||||
tracing "github.com/caos/zitadel/internal/tracing/config"
|
||||
"github.com/caos/zitadel/pkg/admin"
|
||||
"github.com/caos/zitadel/pkg/auth"
|
||||
"github.com/caos/zitadel/pkg/eventstore"
|
||||
"github.com/caos/zitadel/pkg/console"
|
||||
"github.com/caos/zitadel/pkg/login"
|
||||
"github.com/caos/zitadel/pkg/management"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Eventstore eventstore.Config
|
||||
Management management.Config
|
||||
Auth auth.Config
|
||||
Admin admin.Config
|
||||
Mgmt management.Config
|
||||
Auth auth.Config
|
||||
Login login.Config
|
||||
Admin admin.Config
|
||||
Console console.Config
|
||||
|
||||
Log logging.Config
|
||||
Tracing tracing.TracingConfig
|
||||
AuthZ authz.Config
|
||||
}
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config-file", "/zitadel/config/startup.yaml", "path to the config file")
|
||||
eventstoreEnabled := flag.Bool("eventstore", true, "enable eventstore")
|
||||
var configPaths config.ArrayFlags
|
||||
flag.Var(&configPaths, "config-files", "path to the config files")
|
||||
managementEnabled := flag.Bool("management", true, "enable management api")
|
||||
authEnabled := flag.Bool("auth", true, "enable auth api")
|
||||
loginEnabled := flag.Bool("login", true, "enable login ui")
|
||||
adminEnabled := flag.Bool("admin", true, "enable admin api")
|
||||
|
||||
consoleEnabled := flag.Bool("console", true, "enable console ui")
|
||||
flag.Parse()
|
||||
|
||||
conf := new(Config)
|
||||
err := config.Read(conf, *configPath)
|
||||
err := config.Read(conf, configPaths...)
|
||||
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)
|
||||
err = management.Start(ctx, conf.Mgmt, conf.AuthZ)
|
||||
logging.Log("MAIN-39Nv5").OnError(err).Fatal("error starting management api")
|
||||
}
|
||||
if *authEnabled {
|
||||
err = auth.Start(ctx, conf.Auth)
|
||||
err = auth.Start(ctx, conf.Auth, conf.AuthZ)
|
||||
logging.Log("MAIN-x0nD2").OnError(err).Fatal("error starting auth api")
|
||||
}
|
||||
if *loginEnabled {
|
||||
err = login.Start(ctx, conf.Login)
|
||||
logging.Log("MAIN-53RF2").OnError(err).Fatal("error starting login ui")
|
||||
}
|
||||
if *adminEnabled {
|
||||
err = admin.Start(ctx, conf.Admin)
|
||||
err = admin.Start(ctx, conf.Admin, conf.AuthZ)
|
||||
logging.Log("MAIN-0na71").OnError(err).Fatal("error starting admin api")
|
||||
}
|
||||
if *consoleEnabled {
|
||||
err = console.Start(ctx, conf.Console)
|
||||
logging.Log("MAIN-3Dfuc").OnError(err).Fatal("error starting console ui")
|
||||
}
|
||||
<-ctx.Done()
|
||||
logging.Log("MAIN-s8d2h").Info("stopping zitadel")
|
||||
}
|
||||
|
42
cmd/zitadel/startup.yaml
Normal file
42
cmd/zitadel/startup.yaml
Normal file
@ -0,0 +1,42 @@
|
||||
Tracing:
|
||||
Type: google
|
||||
Config:
|
||||
ProjectID: $TRACING_PROJECT_ID
|
||||
MetricPrefix: ZITADEL-V1
|
||||
Fraction: 1
|
||||
|
||||
Log:
|
||||
Level: debug
|
||||
Formatter:
|
||||
Format: text
|
||||
|
||||
Mgmt:
|
||||
API:
|
||||
GRPC:
|
||||
ServerPort: 50010
|
||||
GatewayPort: 50011
|
||||
CustomHeaders:
|
||||
- x-caos-
|
||||
|
||||
Auth:
|
||||
API:
|
||||
GRPC:
|
||||
ServerPort: 50020
|
||||
GatewayPort: 50021
|
||||
CustomHeaders:
|
||||
- x-caos-
|
||||
|
||||
Login:
|
||||
# will get port range 5003x
|
||||
|
||||
Admin:
|
||||
API:
|
||||
GRPC:
|
||||
ServerPort: 50040
|
||||
GatewayPort: 50041
|
||||
CustomHeaders:
|
||||
- x-caos-
|
||||
|
||||
Console:
|
||||
Port: 50050
|
||||
StaticDir: /app/console/dist
|
34
go.mod
34
go.mod
@ -3,10 +3,38 @@ 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/caos/logging v0.0.0-20191210002624-b3260f690a6a
|
||||
github.com/caos/utils v0.0.0-20200305060859-ac2fa70f313e // indirect
|
||||
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.1
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/golang/mock v1.4.3
|
||||
github.com/golang/protobuf v1.3.5
|
||||
github.com/google/uuid v1.1.1 // indirect
|
||||
github.com/gorilla/schema v1.1.0
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
|
||||
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/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.3
|
||||
github.com/rs/cors v1.7.0
|
||||
github.com/sirupsen/logrus v1.5.0 // indirect
|
||||
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-20200327173247-9dae0f8f5775 // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56
|
||||
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 // indirect
|
||||
)
|
||||
|
93
go.sum
93
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,31 +15,38 @@ 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=
|
||||
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/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
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/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/caos/logging v0.0.1 h1:YSGtO2/+5OWdwilBCou50akoDHAT/OhkbrolkVlR6U0=
|
||||
github.com/caos/logging v0.0.1/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
|
||||
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=
|
||||
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/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@ -49,39 +57,57 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
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/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
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=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
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=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
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=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/huandu/xstrings v1.3.0 h1:gvV6jG9dTgFEncxo+AF7PH6MZXi/vZl25owA/8Dg8Wo=
|
||||
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
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=
|
||||
@ -91,9 +117,17 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/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.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
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/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=
|
||||
@ -102,14 +136,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
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/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=
|
||||
@ -117,14 +153,18 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df h1:lDWgvUvNnaTnNBc/dwOty86cFeKoKWbwy2wQj0gIxbU=
|
||||
golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -149,6 +189,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=
|
||||
@ -165,17 +206,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=
|
||||
@ -192,12 +236,12 @@ 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-20200317113312-5766fd39f98d h1:62ap6LNOjDU6uGmKXHJbSfciMoV+FeI1sRXx/pLDL44=
|
||||
golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE=
|
||||
golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -210,6 +254,7 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
@ -226,9 +271,11 @@ 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=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
@ -239,12 +286,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=
|
||||
@ -260,19 +309,25 @@ 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=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
@ -285,5 +340,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
4
internal/admin/config.go
Normal file
4
internal/admin/config.go
Normal file
@ -0,0 +1,4 @@
|
||||
package admin
|
||||
|
||||
type Config struct {
|
||||
}
|
108
internal/api/auth/authorization.go
Normal file
108
internal/api/auth/authorization.go
Normal file
@ -0,0 +1,108 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
authenticated = "authenticated"
|
||||
)
|
||||
|
||||
func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID string, verifier TokenVerifier, authConfig *Config, requiredAuthOption Option) (context.Context, error) {
|
||||
ctx, err := VerifyTokenAndWriteCtxData(ctx, token, orgID, verifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if requiredAuthOption.Permission == authenticated {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
ctx, perms, err := getUserMethodPermissions(ctx, verifier, requiredAuthOption.Permission, authConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkUserPermissions(req, perms, requiredAuthOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func checkUserPermissions(req interface{}, userPerms []string, authOpt Option) error {
|
||||
if len(userPerms) == 0 {
|
||||
return errors.ThrowPermissionDenied(nil, "AUTH-5mWD2", "No matching permissions found")
|
||||
}
|
||||
|
||||
if authOpt.CheckParam == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if HasGlobalPermission(userPerms) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasContextPermission(req, authOpt.CheckParam, userPerms) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.ThrowPermissionDenied(nil, "AUTH-3jknH", "No matching permissions found")
|
||||
}
|
||||
|
||||
func SplitPermission(perm string) (string, string) {
|
||||
splittedPerm := strings.Split(perm, ":")
|
||||
if len(splittedPerm) == 1 {
|
||||
return splittedPerm[0], ""
|
||||
}
|
||||
return splittedPerm[0], splittedPerm[1]
|
||||
}
|
||||
|
||||
func hasContextPermission(req interface{}, fieldName string, permissions []string) bool {
|
||||
for _, perm := range permissions {
|
||||
_, ctxID := SplitPermission(perm)
|
||||
if checkPermissionContext(req, fieldName, ctxID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkPermissionContext(req interface{}, fieldName, roleContextID string) bool {
|
||||
field := getFieldFromReq(req, fieldName)
|
||||
return field != "" && field == roleContextID
|
||||
}
|
||||
|
||||
func getFieldFromReq(req interface{}, field string) string {
|
||||
v := reflect.Indirect(reflect.ValueOf(req)).FieldByName(field)
|
||||
if reflect.ValueOf(v).IsZero() {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", v.Interface())
|
||||
}
|
||||
|
||||
func HasGlobalPermission(perms []string) bool {
|
||||
for _, perm := range perms {
|
||||
_, ctxID := SplitPermission(perm)
|
||||
if ctxID == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetPermissionCtxIDs(perms []string) []string {
|
||||
ctxIDs := make([]string, 0)
|
||||
for _, perm := range perms {
|
||||
_, ctxID := SplitPermission(perm)
|
||||
if ctxID != "" {
|
||||
ctxIDs = append(ctxIDs, ctxID)
|
||||
}
|
||||
}
|
||||
return ctxIDs
|
||||
}
|
278
internal/api/auth/authorization_test.go
Normal file
278
internal/api/auth/authorization_test.go
Normal file
@ -0,0 +1,278 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type TestRequest struct {
|
||||
Test string
|
||||
}
|
||||
|
||||
func Test_CheckUserPermissions(t *testing.T) {
|
||||
type args struct {
|
||||
req *TestRequest
|
||||
perms []string
|
||||
authOpt Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no permissions",
|
||||
args: args{
|
||||
req: &TestRequest{},
|
||||
perms: []string{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "has permission and no context requested",
|
||||
args: args{
|
||||
req: &TestRequest{},
|
||||
perms: []string{"project.read"},
|
||||
authOpt: Option{CheckParam: ""},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "context requested and has global permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "Test"},
|
||||
perms: []string{"project.read", "project.read:1"},
|
||||
authOpt: Option{CheckParam: "Test"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "context requested and has specific permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "Test"},
|
||||
perms: []string{"project.read:Test"},
|
||||
authOpt: Option{CheckParam: "Test"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "context requested and has no permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "Hodor"},
|
||||
perms: []string{"project.read:Test"},
|
||||
authOpt: Option{CheckParam: "Test"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := checkUserPermissions(tt.args.req, tt.args.perms, tt.args.authOpt)
|
||||
if tt.wantErr && err == nil {
|
||||
t.Errorf("got wrong result, should get err: actual: %v ", err)
|
||||
}
|
||||
|
||||
if !tt.wantErr && err != nil {
|
||||
t.Errorf("shouldn't get err: %v ", err)
|
||||
}
|
||||
|
||||
if tt.wantErr && !errors.IsPermissionDenied(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SplitPermission(t *testing.T) {
|
||||
type args struct {
|
||||
perm string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
permName string
|
||||
permCtxID string
|
||||
}{
|
||||
{
|
||||
name: "permission with context id",
|
||||
args: args{
|
||||
perm: "project.read:ctxID",
|
||||
},
|
||||
permName: "project.read",
|
||||
permCtxID: "ctxID",
|
||||
},
|
||||
{
|
||||
name: "permission without context id",
|
||||
args: args{
|
||||
perm: "project.read",
|
||||
},
|
||||
permName: "project.read",
|
||||
permCtxID: "",
|
||||
},
|
||||
{
|
||||
name: "permission to many parts",
|
||||
args: args{
|
||||
perm: "project.read:1:0",
|
||||
},
|
||||
permName: "project.read",
|
||||
permCtxID: "1",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
name, id := SplitPermission(tt.args.perm)
|
||||
if name != tt.permName {
|
||||
t.Errorf("got wrong result on name, expecting: %v, actual: %v ", tt.permName, name)
|
||||
}
|
||||
if id != tt.permCtxID {
|
||||
t.Errorf("got wrong result on id, expecting: %v, actual: %v ", tt.permCtxID, id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HasContextPermission(t *testing.T) {
|
||||
type args struct {
|
||||
req *TestRequest
|
||||
fieldname string
|
||||
perms []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
name: "existing context permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "right"},
|
||||
fieldname: "Test",
|
||||
perms: []string{"test:wrong", "test:right"},
|
||||
},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "not existing context permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "test"},
|
||||
fieldname: "Test",
|
||||
perms: []string{"test:wrong", "test:wrong2"},
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := hasContextPermission(tt.args.req, tt.args.fieldname, tt.args.perms)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetFieldFromReq(t *testing.T) {
|
||||
type args struct {
|
||||
req *TestRequest
|
||||
fieldname string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result string
|
||||
}{
|
||||
{
|
||||
name: "existing field",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "TestValue"},
|
||||
fieldname: "Test",
|
||||
},
|
||||
result: "TestValue",
|
||||
},
|
||||
{
|
||||
name: "not existing field",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "TestValue"},
|
||||
fieldname: "Test2",
|
||||
},
|
||||
result: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := getFieldFromReq(tt.args.req, tt.args.fieldname)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HasGlobalPermission(t *testing.T) {
|
||||
type args struct {
|
||||
perms []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
name: "global perm existing",
|
||||
args: args{
|
||||
perms: []string{"perm:1", "perm:2", "perm"},
|
||||
},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "global perm not existing",
|
||||
args: args{
|
||||
perms: []string{"perm:1", "perm:2", "perm:3"},
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := HasGlobalPermission(tt.args.perms)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetPermissionCtxIDs(t *testing.T) {
|
||||
type args struct {
|
||||
perms []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result []string
|
||||
}{
|
||||
{
|
||||
name: "no specific permission",
|
||||
args: args{
|
||||
perms: []string{"perm"},
|
||||
},
|
||||
result: []string{},
|
||||
},
|
||||
{
|
||||
name: "ctx id",
|
||||
args: args{
|
||||
perms: []string{"perm:1", "perm", "perm:3"},
|
||||
},
|
||||
result: []string{"1", "3"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GetPermissionCtxIDs(tt.args.perms)
|
||||
if !equalStringArray(result, tt.result) {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
26
internal/api/auth/config.go
Normal file
26
internal/api/auth/config.go
Normal file
@ -0,0 +1,26 @@
|
||||
package auth
|
||||
|
||||
type Config struct {
|
||||
RolePermissionMappings []RoleMapping
|
||||
}
|
||||
|
||||
type RoleMapping struct {
|
||||
Role string
|
||||
Permissions []string
|
||||
}
|
||||
|
||||
type MethodMapping map[string]Option
|
||||
|
||||
type Option struct {
|
||||
Permission string
|
||||
CheckParam string
|
||||
}
|
||||
|
||||
func (a *Config) getPermissionsFromRole(role string) []string {
|
||||
for _, roleMap := range a.RolePermissionMappings {
|
||||
if roleMap.Role == role {
|
||||
return roleMap.Permissions
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
60
internal/api/auth/context.go
Normal file
60
internal/api/auth/context.go
Normal file
@ -0,0 +1,60 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
)
|
||||
|
||||
type key int
|
||||
|
||||
var (
|
||||
permissionsKey key
|
||||
dataKey key
|
||||
)
|
||||
|
||||
type CtxData struct {
|
||||
UserID string
|
||||
OrgID string
|
||||
ProjectID string
|
||||
AgentID string
|
||||
}
|
||||
|
||||
func (ctxData CtxData) IsZero() bool {
|
||||
return ctxData.UserID == "" || ctxData.OrgID == ""
|
||||
}
|
||||
|
||||
type Grants []*Grant
|
||||
|
||||
type Grant struct {
|
||||
OrgID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
type TokenVerifier interface {
|
||||
VerifyAccessToken(ctx context.Context, token string) (string, string, string, error)
|
||||
ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error)
|
||||
GetProjectIDByClientID(ctx context.Context, clientID string) (string, error)
|
||||
}
|
||||
|
||||
func VerifyTokenAndWriteCtxData(ctx context.Context, token, orgID string, t TokenVerifier) (_ context.Context, err error) {
|
||||
userID, clientID, agentID, err := verifyAccessToken(ctx, token, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectID, err := t.GetProjectIDByClientID(ctx, clientID)
|
||||
logging.LogWithFields("AUTH-GfAoV", "clientID", clientID).OnError(err).Warn("could not read projectid by clientid")
|
||||
|
||||
return context.WithValue(ctx, dataKey, CtxData{UserID: userID, OrgID: orgID, ProjectID: projectID, AgentID: agentID}), nil
|
||||
}
|
||||
|
||||
func GetCtxData(ctx context.Context) CtxData {
|
||||
ctxData, _ := ctx.Value(dataKey).(CtxData)
|
||||
return ctxData
|
||||
}
|
||||
|
||||
func GetPermissionsFromCtx(ctx context.Context) []string {
|
||||
ctxPermission, _ := ctx.Value(permissionsKey).([]string)
|
||||
return ctxPermission
|
||||
}
|
61
internal/api/auth/permissions.go
Normal file
61
internal/api/auth/permissions.go
Normal file
@ -0,0 +1,61 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func getUserMethodPermissions(ctx context.Context, t TokenVerifier, requiredPerm string, authConfig *Config) (context.Context, []string, error) {
|
||||
ctxData := GetCtxData(ctx)
|
||||
if ctxData.IsZero() {
|
||||
return nil, nil, errors.ThrowUnauthenticated(nil, "AUTH-rKLWEH", "context missing")
|
||||
}
|
||||
grants, err := t.ResolveGrants(ctx, ctxData.UserID, ctxData.OrgID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
permissions := mapGrantsToPermissions(requiredPerm, grants, authConfig)
|
||||
return context.WithValue(ctx, permissionsKey, permissions), permissions, nil
|
||||
}
|
||||
|
||||
func mapGrantsToPermissions(requiredPerm string, grants []*Grant, authConfig *Config) []string {
|
||||
resolvedPermissions := make([]string, 0)
|
||||
for _, grant := range grants {
|
||||
for _, role := range grant.Roles {
|
||||
resolvedPermissions = mapRoleToPerm(requiredPerm, role, authConfig, resolvedPermissions)
|
||||
}
|
||||
}
|
||||
return resolvedPermissions
|
||||
}
|
||||
|
||||
func mapRoleToPerm(requiredPerm, actualRole string, authConfig *Config, resolvedPermissions []string) []string {
|
||||
roleName, roleContextID := SplitPermission(actualRole)
|
||||
perms := authConfig.getPermissionsFromRole(roleName)
|
||||
|
||||
for _, p := range perms {
|
||||
if p == requiredPerm {
|
||||
p = addRoleContextIDToPerm(p, roleContextID)
|
||||
if !existsPerm(resolvedPermissions, p) {
|
||||
resolvedPermissions = append(resolvedPermissions, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolvedPermissions
|
||||
}
|
||||
|
||||
func addRoleContextIDToPerm(perm, roleContextID string) string {
|
||||
if roleContextID != "" {
|
||||
perm = perm + ":" + roleContextID
|
||||
}
|
||||
return perm
|
||||
}
|
||||
|
||||
func existsPerm(existing []string, perm string) bool {
|
||||
for _, e := range existing {
|
||||
if e == perm {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
428
internal/api/auth/permissions_test.go
Normal file
428
internal/api/auth/permissions_test.go
Normal file
@ -0,0 +1,428 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func getTestCtx(userID, orgID string) context.Context {
|
||||
return context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID})
|
||||
}
|
||||
|
||||
type testVerifier struct {
|
||||
grants []*Grant
|
||||
}
|
||||
|
||||
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
|
||||
return "", "", "", nil
|
||||
}
|
||||
|
||||
func (v *testVerifier) ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error) {
|
||||
return v.grants, nil
|
||||
}
|
||||
|
||||
func (v *testVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func equalStringArray(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Test_GetUserMethodPermissions(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
verifier TokenVerifier
|
||||
requiredPerm string
|
||||
authConfig *Config
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
result []string
|
||||
}{
|
||||
{
|
||||
name: "Empty Context",
|
||||
args: args{
|
||||
ctx: getTestCtx("", ""),
|
||||
verifier: &testVerifier{grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER"}}}},
|
||||
requiredPerm: "project.read",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsUnauthenticated,
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "No Grants",
|
||||
args: args{
|
||||
ctx: getTestCtx("", ""),
|
||||
verifier: &testVerifier{grants: []*Grant{}},
|
||||
requiredPerm: "project.read",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: make([]string, 0),
|
||||
},
|
||||
{
|
||||
name: "Get Permissions",
|
||||
args: args{
|
||||
ctx: getTestCtx("userID", "orgID"),
|
||||
verifier: &testVerifier{grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER"}}}},
|
||||
requiredPerm: "project.read",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, perms, err := getUserMethodPermissions(tt.args.ctx, tt.args.verifier, tt.args.requiredPerm, tt.args.authConfig)
|
||||
|
||||
if tt.wantErr && err == nil {
|
||||
t.Errorf("got wrong result, should get err: actual: %v ", err)
|
||||
}
|
||||
|
||||
if tt.wantErr && !tt.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
|
||||
if !tt.wantErr && !equalStringArray(perms, tt.result) {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, perms)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MapGrantsToPermissions(t *testing.T) {
|
||||
type args struct {
|
||||
requiredPerm string
|
||||
grants []*Grant
|
||||
authConfig *Config
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result []string
|
||||
}{
|
||||
{
|
||||
name: "One Role existing perm",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER"}}},
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "One Role not existing perm",
|
||||
args: args{
|
||||
requiredPerm: "project.write",
|
||||
grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER"}}},
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{},
|
||||
},
|
||||
{
|
||||
name: "Multiple Roles one existing",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER", "IAM_OWNER"}}},
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "Multiple Roles, global and specific",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER", "PROJECT_OWNER:1"}}},
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "PROJECT_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{"project.read", "project.read:1"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := mapGrantsToPermissions(tt.args.requiredPerm, tt.args.grants, tt.args.authConfig)
|
||||
if !equalStringArray(result, tt.result) {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MapRoleToPerm(t *testing.T) {
|
||||
type args struct {
|
||||
requiredPerm string
|
||||
actualRole string
|
||||
authConfig *Config
|
||||
resolvedPermissions []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result []string
|
||||
}{
|
||||
{
|
||||
name: "first perm without context id",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "ORG_OWNER",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolvedPermissions: []string{},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "existing perm without context id",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "ORG_OWNER",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolvedPermissions: []string{"project.read"},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "first perm with context id",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "PROJECT_OWNER:1",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "PROJECT_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolvedPermissions: []string{},
|
||||
},
|
||||
result: []string{"project.read:1"},
|
||||
},
|
||||
{
|
||||
name: "perm with context id, existing global",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "PROJECT_OWNER:1",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "PROJECT_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolvedPermissions: []string{"project.read"},
|
||||
},
|
||||
result: []string{"project.read", "project.read:1"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := mapRoleToPerm(tt.args.requiredPerm, tt.args.actualRole, tt.args.authConfig, tt.args.resolvedPermissions)
|
||||
if !equalStringArray(result, tt.result) {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AddRoleContextIDToPerm(t *testing.T) {
|
||||
type args struct {
|
||||
perm string
|
||||
ctxID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result string
|
||||
}{
|
||||
{
|
||||
name: "with ctx id",
|
||||
args: args{
|
||||
perm: "perm1",
|
||||
ctxID: "2",
|
||||
},
|
||||
result: "perm1:2",
|
||||
},
|
||||
{
|
||||
name: "with ctx id",
|
||||
args: args{
|
||||
perm: "perm1",
|
||||
ctxID: "",
|
||||
},
|
||||
result: "perm1",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := addRoleContextIDToPerm(tt.args.perm, tt.args.ctxID)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ExistisPerm(t *testing.T) {
|
||||
type args struct {
|
||||
existing []string
|
||||
perm string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
name: "not existing perm",
|
||||
args: args{
|
||||
existing: []string{"perm1", "perm2", "perm3"},
|
||||
perm: "perm4",
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
name: "existing perm",
|
||||
args: args{
|
||||
existing: []string{"perm1", "perm2", "perm3"},
|
||||
perm: "perm2",
|
||||
},
|
||||
result: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := existsPerm(tt.args.existing, tt.args.perm)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
20
internal/api/auth/token.go
Normal file
20
internal/api/auth/token.go
Normal file
@ -0,0 +1,20 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
BearerPrefix = "Bearer "
|
||||
)
|
||||
|
||||
func verifyAccessToken(ctx context.Context, token string, t TokenVerifier) (string, string, string, error) {
|
||||
parts := strings.Split(token, BearerPrefix)
|
||||
if len(parts) != 2 {
|
||||
return "", "", "", errors.ThrowUnauthenticated(nil, "AUTH-7fs1e", "invalid auth header")
|
||||
}
|
||||
return t.VerifyAccessToken(ctx, parts[1])
|
||||
}
|
63
internal/api/auth/token_test.go
Normal file
63
internal/api/auth/token_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func Test_VerifyAccessToken(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
token string
|
||||
verifier *testVerifier
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no auth header set",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "wrong auth header set",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: "Basic sds",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "auth header set",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: "Bearer AUTH",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, _, _, err := verifyAccessToken(tt.args.ctx, tt.args.token, tt.args.verifier)
|
||||
if tt.wantErr && err == nil {
|
||||
t.Errorf("got wrong result, should get err: actual: %v ", err)
|
||||
}
|
||||
|
||||
if !tt.wantErr && err != nil {
|
||||
t.Errorf("got wrong result, should not get err: actual: %v ", err)
|
||||
}
|
||||
|
||||
if tt.wantErr && !errors.IsUnauthenticated(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
46
internal/api/grpc/caos_errors.go
Normal file
46
internal/api/grpc/caos_errors.go
Normal file
@ -0,0 +1,46 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func CaosToGRPCError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
code, msg, ok := Extract(err)
|
||||
if !ok {
|
||||
return status.Convert(err).Err()
|
||||
}
|
||||
return status.Error(code, msg)
|
||||
}
|
||||
|
||||
func Extract(err error) (c codes.Code, msg string, ok bool) {
|
||||
switch caosErr := err.(type) {
|
||||
case *caos_errs.AlreadyExistsError:
|
||||
return codes.AlreadyExists, caosErr.GetMessage(), true
|
||||
case *caos_errs.DeadlineExceededError:
|
||||
return codes.DeadlineExceeded, caosErr.GetMessage(), true
|
||||
case caos_errs.InternalError:
|
||||
return codes.Internal, caosErr.GetMessage(), true
|
||||
case *caos_errs.InvalidArgumentError:
|
||||
return codes.InvalidArgument, caosErr.GetMessage(), true
|
||||
case *caos_errs.NotFoundError:
|
||||
return codes.NotFound, caosErr.GetMessage(), true
|
||||
case *caos_errs.PermissionDeniedError:
|
||||
return codes.PermissionDenied, caosErr.GetMessage(), true
|
||||
case *caos_errs.PreconditionFailedError:
|
||||
return codes.FailedPrecondition, caosErr.GetMessage(), true
|
||||
case *caos_errs.UnauthenticatedError:
|
||||
return codes.Unauthenticated, caosErr.GetMessage(), true
|
||||
case *caos_errs.UnavailableError:
|
||||
return codes.Unavailable, caosErr.GetMessage(), true
|
||||
case *caos_errs.UnimplementedError:
|
||||
return codes.Unimplemented, caosErr.GetMessage(), true
|
||||
default:
|
||||
return codes.Unknown, err.Error(), false
|
||||
}
|
||||
}
|
38
internal/api/grpc/client/middleware/tracing.go
Normal file
38
internal/api/grpc/client/middleware/tracing.go
Normal file
@ -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)
|
||||
}
|
31
internal/api/grpc/config.go
Normal file
31
internal/api/grpc/config.go
Normal file
@ -0,0 +1,31 @@
|
||||
package grpc
|
||||
|
||||
type Config struct {
|
||||
ServerPort string
|
||||
GatewayPort string
|
||||
CustomHeaders []string
|
||||
}
|
||||
|
||||
func (c Config) ToServerConfig() ServerConfig {
|
||||
return ServerConfig{
|
||||
Port: c.ServerPort,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) ToGatewayConfig() GatewayConfig {
|
||||
return GatewayConfig{
|
||||
Port: c.GatewayPort,
|
||||
GRPCEndpoint: c.ServerPort,
|
||||
CustomHeaders: c.CustomHeaders,
|
||||
}
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port string
|
||||
}
|
||||
|
||||
type GatewayConfig struct {
|
||||
Port string
|
||||
GRPCEndpoint string
|
||||
CustomHeaders []string
|
||||
}
|
17
internal/api/grpc/header.go
Normal file
17
internal/api/grpc/header.go
Normal file
@ -0,0 +1,17 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
|
||||
|
||||
"github.com/caos/zitadel/internal/api"
|
||||
)
|
||||
|
||||
func GetHeader(ctx context.Context, headername string) string {
|
||||
return metautils.ExtractIncoming(ctx).Get(headername)
|
||||
}
|
||||
|
||||
func GetAuthorizationHeader(ctx context.Context) string {
|
||||
return GetHeader(ctx, api.Authorization)
|
||||
}
|
110
internal/api/grpc/server/gateway.go
Normal file
110
internal/api/grpc/server/gateway.go
Normal file
@ -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
|
||||
}
|
36
internal/api/grpc/server/middleware/auth_interceptor.go
Normal file
36
internal/api/grpc/server/middleware/auth_interceptor.go
Normal file
@ -0,0 +1,36 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"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) {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
authOpt, needsToken := authMethods[info.FullMethod]
|
||||
if !needsToken {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
authToken := grpc_util.GetAuthorizationHeader(ctx)
|
||||
if authToken == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, "auth header missing")
|
||||
}
|
||||
|
||||
orgID := grpc_util.GetHeader(ctx, api.ZitadelOrgID)
|
||||
|
||||
ctx, err := auth.CheckUserAuthorization(ctx, req, authToken, orgID, verifier, authConfig, authOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
16
internal/api/grpc/server/middleware/error_interceptor.go
Normal file
16
internal/api/grpc/server/middleware/error_interceptor.go
Normal file
@ -0,0 +1,16 @@
|
||||
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, grpc_util.CaosToGRPCError(err)
|
||||
}
|
||||
}
|
33
internal/api/grpc/server/middleware/tracing.go
Normal file
33
internal/api/grpc/server/middleware/tracing.go
Normal file
@ -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)
|
||||
}
|
53
internal/api/grpc/server/probes.go
Normal file
53
internal/api/grpc/server/probes.go
Normal file
@ -0,0 +1,53 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
structpb "github.com/golang/protobuf/ptypes/struct"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/proto"
|
||||
)
|
||||
|
||||
type ValidationFunction func(ctx context.Context) error
|
||||
|
||||
type Validator struct {
|
||||
validations map[string]ValidationFunction
|
||||
}
|
||||
|
||||
func NewValidator(validations map[string]ValidationFunction) *Validator {
|
||||
return &Validator{validations: validations}
|
||||
}
|
||||
|
||||
func (v *Validator) Healthz(_ context.Context, e *empty.Empty) (*empty.Empty, error) {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (v *Validator) Ready(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {
|
||||
return e, ready(ctx, v.validations)
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(ctx context.Context, _ *empty.Empty) (*structpb.Struct, error) {
|
||||
validations := validate(ctx, v.validations)
|
||||
return proto.ToPBStruct(validations)
|
||||
}
|
||||
|
||||
func ready(ctx context.Context, validations map[string]ValidationFunction) error {
|
||||
if len(validate(ctx, validations)) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.ThrowInternal(nil, "API-2jD9a", "not ready")
|
||||
}
|
||||
|
||||
func validate(ctx context.Context, validations map[string]ValidationFunction) map[string]error {
|
||||
errors := make(map[string]error)
|
||||
for id, validation := range validations {
|
||||
if err := validation(ctx); err != nil {
|
||||
logging.Log("API-vf823").WithError(err).Error("validation failed")
|
||||
errors[id] = err
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
53
internal/api/grpc/server/server.go
Normal file
53
internal/api/grpc/server/server.go
Normal file
@ -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
|
||||
}
|
12
internal/api/header.go
Normal file
12
internal/api/header.go
Normal file
@ -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"
|
||||
)
|
109
internal/api/html/i18n.go
Normal file
109
internal/api/html/i18n.go
Normal file
@ -0,0 +1,109 @@
|
||||
package html
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/caos/logging"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"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)
|
||||
yamlUnmarshal := func(data []byte, v interface{}) error { return yaml.Unmarshal(data, v) }
|
||||
bundle.RegisterUnmarshalFunc("yaml", yamlUnmarshal)
|
||||
bundle.RegisterUnmarshalFunc("yml", yamlUnmarshal)
|
||||
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 {
|
||||
return langs
|
||||
}
|
||||
lang, err := t.cookieHandler.GetCookieValue(r, t.cookieName)
|
||||
if err == nil {
|
||||
langs = append(langs, lang)
|
||||
}
|
||||
return append(langs, r.Header.Get(api.AcceptLanguage))
|
||||
}
|
81
internal/api/html/renderer.go
Normal file
81
internal/api/html/renderer.go
Normal file
@ -0,0 +1,81 @@
|
||||
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)
|
||||
err := tmpl.Funcs(reqFuncs).Execute(w, data)
|
||||
logging.LogWithFields("HTML-lF8F6w", "template", tmpl.Name).OnError(err).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
|
||||
}
|
122
internal/api/http/cookie.go
Normal file
122
internal/api/http/cookie.go
Normal file
@ -0,0 +1,122 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type CookieHandler struct {
|
||||
securecookie *securecookie.SecureCookie
|
||||
secureOnly bool
|
||||
sameSite http.SameSite
|
||||
path string
|
||||
maxAge int
|
||||
domain string
|
||||
}
|
||||
|
||||
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
|
||||
c := &CookieHandler{
|
||||
secureOnly: true,
|
||||
sameSite: http.SameSiteLaxMode,
|
||||
path: "/",
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
type CookieHandlerOpt func(*CookieHandler)
|
||||
|
||||
func WithEncryption(hashKey, encryptKey []byte) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.securecookie = securecookie.New(hashKey, encryptKey)
|
||||
}
|
||||
}
|
||||
|
||||
func WithUnsecure() CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.secureOnly = false
|
||||
}
|
||||
}
|
||||
|
||||
func WithSameSite(sameSite http.SameSite) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.sameSite = sameSite
|
||||
}
|
||||
}
|
||||
|
||||
func WithPath(path string) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.path = path
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaxAge(maxAge int) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.maxAge = maxAge
|
||||
c.securecookie.MaxAge(maxAge)
|
||||
}
|
||||
}
|
||||
|
||||
func WithDomain(domain string) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.domain = domain
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, error) {
|
||||
cookie, err := r.Cookie(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
||||
func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error {
|
||||
cookie, err := r.Cookie(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.securecookie == nil {
|
||||
return errors.ThrowInternal(nil, "HTTP-X6XpnL", "securecookie not configured")
|
||||
}
|
||||
return c.securecookie.Decode(name, cookie.Value, value)
|
||||
}
|
||||
|
||||
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name string, value string) {
|
||||
c.httpSet(w, name, value, c.maxAge)
|
||||
}
|
||||
|
||||
func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name string, value interface{}) error {
|
||||
if c.securecookie == nil {
|
||||
return errors.ThrowInternal(nil, "HTTP-s2HUtx", "securecookie not configured")
|
||||
}
|
||||
encoded, err := c.securecookie.Encode(name, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.httpSet(w, name, encoded, c.maxAge)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
|
||||
c.httpSet(w, name, "", -1)
|
||||
}
|
||||
|
||||
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, value string, maxage int) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Domain: c.domain,
|
||||
Path: c.path,
|
||||
MaxAge: maxage,
|
||||
HttpOnly: true,
|
||||
Secure: c.secureOnly,
|
||||
SameSite: c.sameSite,
|
||||
})
|
||||
}
|
21
internal/api/http/listener.go
Normal file
21
internal/api/http/listener.go
Normal file
@ -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
|
||||
}
|
46
internal/api/http/middleware/cors_interceptor.go
Normal file
46
internal/api/http/middleware/cors_interceptor.go
Normal file
@ -0,0 +1,46 @@
|
||||
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,
|
||||
},
|
||||
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)
|
||||
}
|
12
internal/api/http/middleware/trace_interceptor.go
Normal file
12
internal/api/http/middleware/trace_interceptor.go
Normal file
@ -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...)
|
||||
}
|
28
internal/api/http/parser.go
Normal file
28
internal/api/http/parser.go
Normal file
@ -0,0 +1,28 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
decoder *schema.Decoder
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
d := schema.NewDecoder()
|
||||
d.IgnoreUnknownKeys(true)
|
||||
return &Parser{d}
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(r *http.Request, data interface{}) error {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return errors.ThrowInternal(err, "FORM-lCC9zI", "error parsing http form")
|
||||
}
|
||||
|
||||
return p.decoder.Decode(data, r.Form)
|
||||
}
|
11
internal/api/probes.go
Normal file
11
internal/api/probes.go
Normal file
@ -0,0 +1,11 @@
|
||||
package api
|
||||
|
||||
const (
|
||||
Healthz = "/Healthz"
|
||||
Readiness = "/Ready"
|
||||
Validation = "/Validate"
|
||||
)
|
||||
|
||||
var (
|
||||
Probes = []string{Healthz, Readiness, Validation}
|
||||
)
|
4
internal/auth/config.go
Normal file
4
internal/auth/config.go
Normal file
@ -0,0 +1,4 @@
|
||||
package auth
|
||||
|
||||
type Config struct {
|
||||
}
|
21
internal/config/array_flag.go
Normal file
21
internal/config/array_flag.go
Normal file
@ -0,0 +1,21 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ flag.Value = (*ArrayFlags)(nil)
|
||||
|
||||
//ArrayFlags implements the flag/Value interface
|
||||
//allowing to set multiple string flags with the same name
|
||||
type ArrayFlags []string
|
||||
|
||||
func (i *ArrayFlags) String() string {
|
||||
return strings.Join(*i, ";")
|
||||
}
|
||||
|
||||
func (i *ArrayFlags) Set(value string) error {
|
||||
*i = append(*i, value)
|
||||
return nil
|
||||
}
|
@ -12,26 +12,16 @@ import (
|
||||
"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 = func(data []byte, o interface{}) error { return yaml.Unmarshal(data, o) }
|
||||
)
|
||||
|
||||
// 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":
|
||||
|
231
internal/config/config_test.go
Normal file
231
internal/config/config_test.go
Normal file
@ -0,0 +1,231 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type test struct {
|
||||
Test bool
|
||||
}
|
||||
|
||||
type validatable struct {
|
||||
Test bool
|
||||
}
|
||||
|
||||
type multiple struct {
|
||||
Test bool
|
||||
MoreData string
|
||||
}
|
||||
|
||||
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
|
||||
want interface{}
|
||||
}{
|
||||
{
|
||||
"not supoorted config file error",
|
||||
args{
|
||||
configFiles: []string{"notsupported.unknown"},
|
||||
obj: &test{},
|
||||
},
|
||||
true,
|
||||
&test{},
|
||||
},
|
||||
{
|
||||
"non existing config file error",
|
||||
args{
|
||||
configFiles: []string{"nonexisting.yaml"},
|
||||
obj: &test{},
|
||||
},
|
||||
true,
|
||||
&test{},
|
||||
},
|
||||
{
|
||||
"non parsable config file error",
|
||||
args{
|
||||
configFiles: []string{"./testdata/non_parsable.json"},
|
||||
obj: &test{},
|
||||
},
|
||||
true,
|
||||
&test{},
|
||||
},
|
||||
{
|
||||
"invalid parsable config file error",
|
||||
args{
|
||||
configFiles: []string{"./testdata/invalid.json"},
|
||||
obj: &validatable{},
|
||||
},
|
||||
true,
|
||||
&validatable{},
|
||||
},
|
||||
{
|
||||
"multiple files, one non parsable error ",
|
||||
args{
|
||||
configFiles: []string{"./testdata/non_parsable.json", "./testdata/more_data.yaml"},
|
||||
obj: &multiple{},
|
||||
},
|
||||
true,
|
||||
&multiple{},
|
||||
},
|
||||
{
|
||||
"parsable config file ok",
|
||||
args{
|
||||
configFiles: []string{"./testdata/valid.json"},
|
||||
obj: &test{},
|
||||
},
|
||||
false,
|
||||
&test{Test: true},
|
||||
},
|
||||
{
|
||||
"multiple parsable config files ok",
|
||||
args{
|
||||
configFiles: []string{"./testdata/valid.json", "./testdata/more_data.yaml"},
|
||||
obj: &multiple{},
|
||||
},
|
||||
false,
|
||||
&multiple{Test: true, MoreData: "data"},
|
||||
},
|
||||
{
|
||||
"valid parsable config file ok",
|
||||
args{
|
||||
configFiles: []string{"./testdata/valid.json"},
|
||||
obj: &validatable{},
|
||||
},
|
||||
false,
|
||||
&validatable{Test: true},
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.args.obj, tt.want) {
|
||||
t.Errorf("Read() got = %v, want = %v", tt.args.obj, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
3
internal/config/testdata/invalid.json
vendored
Normal file
3
internal/config/testdata/invalid.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Test" : false
|
||||
}
|
1
internal/config/testdata/more_data.yaml
vendored
Normal file
1
internal/config/testdata/more_data.yaml
vendored
Normal file
@ -0,0 +1 @@
|
||||
MoreData: data
|
1
internal/config/testdata/non_parsable.json
vendored
Normal file
1
internal/config/testdata/non_parsable.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
Test
|
3
internal/config/testdata/valid.json
vendored
Normal file
3
internal/config/testdata/valid.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Test" : true
|
||||
}
|
15
internal/config/types/duration.go
Normal file
15
internal/config/types/duration.go
Normal file
@ -0,0 +1,15 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Duration struct {
|
||||
time.Duration
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalText(data []byte) error {
|
||||
var err error
|
||||
d.Duration, err = time.ParseDuration(string(data))
|
||||
return err
|
||||
}
|
46
internal/config/types/duration_test.go
Normal file
46
internal/config/types/duration_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDuration_UnmarshalText(t *testing.T) {
|
||||
type args struct {
|
||||
data []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
want time.Duration
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
args{
|
||||
data: []byte("10s"),
|
||||
},
|
||||
false,
|
||||
time.Duration(10 * time.Second),
|
||||
},
|
||||
{
|
||||
"error",
|
||||
args{
|
||||
data: []byte("10"),
|
||||
},
|
||||
true,
|
||||
time.Duration(0),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := &Duration{}
|
||||
if err := d.UnmarshalText(tt.args.data); (err != nil) != tt.wantErr {
|
||||
t.Errorf("UnmarshalText() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if d.Duration != tt.want {
|
||||
t.Errorf("UnmarshalText() got = %v, want %v", d.Duration, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
136
internal/crypto/aes.go
Normal file
136
internal/crypto/aes.go
Normal file
@ -0,0 +1,136 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var _ EncryptionAlgorithm = (*AESCrypto)(nil)
|
||||
|
||||
type AESCrypto struct {
|
||||
keys map[string]string
|
||||
encryptionKeyID string
|
||||
keyIDs []string
|
||||
}
|
||||
|
||||
func NewAESCrypto(config *KeyConfig) (*AESCrypto, error) {
|
||||
keys, ids, err := LoadKeys(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AESCrypto{
|
||||
keys: keys,
|
||||
encryptionKeyID: config.EncryptionKeyID,
|
||||
keyIDs: ids,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *AESCrypto) Algorithm() string {
|
||||
return "aes"
|
||||
}
|
||||
|
||||
func (a *AESCrypto) Encrypt(value []byte) ([]byte, error) {
|
||||
return EncryptAES(value, a.encryptionKey())
|
||||
}
|
||||
|
||||
func (a *AESCrypto) Decrypt(value []byte, keyID string) ([]byte, error) {
|
||||
key, err := a.decryptionKey(keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DecryptAES(value, key)
|
||||
}
|
||||
|
||||
func (a *AESCrypto) DecryptString(value []byte, keyID string) (string, error) {
|
||||
key, err := a.decryptionKey(keyID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b, err := DecryptAES(value, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (a *AESCrypto) EncryptionKeyID() string {
|
||||
return a.encryptionKeyID
|
||||
}
|
||||
|
||||
func (a *AESCrypto) DecryptionKeyIDs() []string {
|
||||
return a.keyIDs
|
||||
}
|
||||
|
||||
func (a *AESCrypto) encryptionKey() string {
|
||||
return a.keys[a.encryptionKeyID]
|
||||
}
|
||||
|
||||
func (a *AESCrypto) decryptionKey(keyID string) (string, error) {
|
||||
key, ok := a.keys[keyID]
|
||||
if !ok {
|
||||
return "", errors.ThrowNotFound(nil, "CRYPT-nkj1s", "unknown key id")
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func EncryptAESString(data string, key string) (string, error) {
|
||||
encrypted, err := EncryptAES([]byte(data), key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(encrypted), nil
|
||||
}
|
||||
|
||||
func EncryptAES(plainText []byte, key string) ([]byte, error) {
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cipherText := make([]byte, aes.BlockSize+len(plainText))
|
||||
iv := cipherText[:aes.BlockSize]
|
||||
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(cipherText[aes.BlockSize:], plainText)
|
||||
|
||||
return cipherText, nil
|
||||
}
|
||||
|
||||
func DecryptAESString(data string, key string) (string, error) {
|
||||
text, err := base64.URLEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
decrypted, err := DecryptAES(text, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(decrypted), nil
|
||||
}
|
||||
|
||||
func DecryptAES(cipherText []byte, key string) ([]byte, error) {
|
||||
block, err := aes.NewCipher([]byte(key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cipherText) < aes.BlockSize {
|
||||
err = errors.ThrowPreconditionFailed(nil, "CRYPT-23kH1", "cipher text block too short")
|
||||
return nil, err
|
||||
}
|
||||
iv := cipherText[:aes.BlockSize]
|
||||
cipherText = cipherText[aes.BlockSize:]
|
||||
|
||||
stream := cipher.NewCFBDecrypter(block, iv)
|
||||
stream.XORKeyStream(cipherText, cipherText)
|
||||
|
||||
return cipherText, err
|
||||
}
|
18
internal/crypto/aes_test.go
Normal file
18
internal/crypto/aes_test.go
Normal file
@ -0,0 +1,18 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
//TODO: refactor test style
|
||||
func TestDecrypt_OK(t *testing.T) {
|
||||
encryptedpw, err := EncryptAESString("ThisIsMySecretPw", "passphrasewhichneedstobe32bytes!")
|
||||
assert.NoError(t, err)
|
||||
|
||||
decryptedpw, err := DecryptAESString(encryptedpw, "passphrasewhichneedstobe32bytes!")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "ThisIsMySecretPw", decryptedpw)
|
||||
}
|
27
internal/crypto/bcrypt.go
Normal file
27
internal/crypto/bcrypt.go
Normal file
@ -0,0 +1,27 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var _ HashAlgorithm = (*BCrypt)(nil)
|
||||
|
||||
type BCrypt struct {
|
||||
cost int
|
||||
}
|
||||
|
||||
func NewBCrypt(cost int) *BCrypt {
|
||||
return &BCrypt{cost: cost}
|
||||
}
|
||||
|
||||
func (b *BCrypt) Algorithm() string {
|
||||
return "bcrypt"
|
||||
}
|
||||
|
||||
func (b *BCrypt) Hash(value []byte) ([]byte, error) {
|
||||
return bcrypt.GenerateFromPassword(value, b.cost)
|
||||
}
|
||||
|
||||
func (b *BCrypt) CompareHash(hashed, value []byte) error {
|
||||
return bcrypt.CompareHashAndPassword(hashed, value)
|
||||
}
|
159
internal/crypto/code.go
Normal file
159
internal/crypto/code.go
Normal file
@ -0,0 +1,159 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
LowerLetters = []rune("abcdefghijklmnopqrstuvwxyz")
|
||||
UpperLetters = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
Digits = []rune("0123456789")
|
||||
Symbols = []rune("~!@#$^&*()_+`-={}|[]:<>?,./")
|
||||
)
|
||||
|
||||
type Generator interface {
|
||||
Length() uint
|
||||
Expiry() time.Duration
|
||||
Alg() Crypto
|
||||
Runes() []rune
|
||||
}
|
||||
|
||||
type EncryptionGenerator struct {
|
||||
length uint
|
||||
expiry time.Duration
|
||||
alg EncryptionAlgorithm
|
||||
runes []rune
|
||||
}
|
||||
|
||||
func (g *EncryptionGenerator) Length() uint {
|
||||
return g.length
|
||||
}
|
||||
|
||||
func (g *EncryptionGenerator) Expiry() time.Duration {
|
||||
return g.expiry
|
||||
}
|
||||
|
||||
func (g *EncryptionGenerator) Alg() Crypto {
|
||||
return g.alg
|
||||
}
|
||||
|
||||
func (g *EncryptionGenerator) Runes() []rune {
|
||||
return g.runes
|
||||
}
|
||||
|
||||
func NewEncryptionGenerator(length uint, expiry time.Duration, alg EncryptionAlgorithm, runes []rune) *EncryptionGenerator {
|
||||
return &EncryptionGenerator{
|
||||
length: length,
|
||||
expiry: expiry,
|
||||
alg: alg,
|
||||
runes: runes,
|
||||
}
|
||||
}
|
||||
|
||||
type HashGenerator struct {
|
||||
length uint
|
||||
expiry time.Duration
|
||||
alg HashAlgorithm
|
||||
runes []rune
|
||||
}
|
||||
|
||||
func (g *HashGenerator) Length() uint {
|
||||
return g.length
|
||||
}
|
||||
|
||||
func (g *HashGenerator) Expiry() time.Duration {
|
||||
return g.expiry
|
||||
}
|
||||
|
||||
func (g *HashGenerator) Alg() Crypto {
|
||||
return g.alg
|
||||
}
|
||||
|
||||
func (g *HashGenerator) Runes() []rune {
|
||||
return g.runes
|
||||
}
|
||||
|
||||
func NewHashGenerator(length uint, expiry time.Duration, alg HashAlgorithm, runes []rune) *HashGenerator {
|
||||
return &HashGenerator{
|
||||
length: length,
|
||||
expiry: expiry,
|
||||
alg: alg,
|
||||
runes: runes,
|
||||
}
|
||||
}
|
||||
|
||||
func NewCode(g Generator) (*CryptoValue, string, error) {
|
||||
code, err := generateRandomString(g.Length(), g.Runes())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
crypto, err := Crypt([]byte(code), g.Alg())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return crypto, code, nil
|
||||
}
|
||||
|
||||
func IsCodeExpired(creationDate time.Time, expiry time.Duration) bool {
|
||||
return creationDate.Add(expiry).Before(time.Now().UTC())
|
||||
}
|
||||
|
||||
func VerifyCode(creationDate time.Time, expiry time.Duration, cryptoCode *CryptoValue, verificationCode string, g Generator) error {
|
||||
if IsCodeExpired(creationDate, expiry) {
|
||||
return errors.ThrowPreconditionFailed(nil, "CODE-QvUQ4P", "verification code is expired")
|
||||
}
|
||||
switch alg := g.Alg().(type) {
|
||||
case EncryptionAlgorithm:
|
||||
return verifyEncryptedCode(cryptoCode, verificationCode, alg)
|
||||
case HashAlgorithm:
|
||||
return verifyHashedCode(cryptoCode, verificationCode, alg)
|
||||
}
|
||||
return errors.ThrowInvalidArgument(nil, "CODE-fW2gNa", "generator alg is not supported")
|
||||
}
|
||||
|
||||
func generateRandomString(length uint, chars []rune) (string, error) {
|
||||
if length == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
max := len(chars) - 1
|
||||
maxStr := int(length - 1)
|
||||
|
||||
str := make([]rune, length)
|
||||
randBytes := make([]byte, length)
|
||||
if _, err := rand.Read(randBytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for i, rb := range randBytes {
|
||||
str[i] = chars[int(rb)%max]
|
||||
if i == maxStr {
|
||||
return string(str), nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func verifyEncryptedCode(cryptoCode *CryptoValue, verificationCode string, alg EncryptionAlgorithm) error {
|
||||
if cryptoCode == nil {
|
||||
return errors.ThrowInvalidArgument(nil, "CRYPT-aqrFV", "cryptoCode must not be nil")
|
||||
}
|
||||
code, err := DecryptString(cryptoCode, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if code != verificationCode {
|
||||
return errors.ThrowInvalidArgument(nil, "CODE-woT0xc", "verification code is invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyHashedCode(cryptoCode *CryptoValue, verificationCode string, alg HashAlgorithm) error {
|
||||
if cryptoCode == nil {
|
||||
return errors.ThrowInvalidArgument(nil, "CRYPT-2q3r", "cryptoCode must not be nil")
|
||||
}
|
||||
return CompareHash(cryptoCode, []byte(verificationCode), alg)
|
||||
}
|
90
internal/crypto/code_mock.go
Normal file
90
internal/crypto/code_mock.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: code.go
|
||||
|
||||
// Package crypto is a generated GoMock package.
|
||||
package crypto
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// MockGenerator is a mock of Generator interface
|
||||
type MockGenerator struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockGeneratorMockRecorder
|
||||
}
|
||||
|
||||
// MockGeneratorMockRecorder is the mock recorder for MockGenerator
|
||||
type MockGeneratorMockRecorder struct {
|
||||
mock *MockGenerator
|
||||
}
|
||||
|
||||
// NewMockGenerator creates a new mock instance
|
||||
func NewMockGenerator(ctrl *gomock.Controller) *MockGenerator {
|
||||
mock := &MockGenerator{ctrl: ctrl}
|
||||
mock.recorder = &MockGeneratorMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockGenerator) EXPECT() *MockGeneratorMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Length mocks base method
|
||||
func (m *MockGenerator) Length() uint {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Length")
|
||||
ret0, _ := ret[0].(uint)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Length indicates an expected call of Length
|
||||
func (mr *MockGeneratorMockRecorder) Length() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Length", reflect.TypeOf((*MockGenerator)(nil).Length))
|
||||
}
|
||||
|
||||
// Expiry mocks base method
|
||||
func (m *MockGenerator) Expiry() time.Duration {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Expiry")
|
||||
ret0, _ := ret[0].(time.Duration)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Expiry indicates an expected call of Expiry
|
||||
func (mr *MockGeneratorMockRecorder) Expiry() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Expiry", reflect.TypeOf((*MockGenerator)(nil).Expiry))
|
||||
}
|
||||
|
||||
// Alg mocks base method
|
||||
func (m *MockGenerator) Alg() Crypto {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Alg")
|
||||
ret0, _ := ret[0].(Crypto)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Alg indicates an expected call of Alg
|
||||
func (mr *MockGeneratorMockRecorder) Alg() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Alg", reflect.TypeOf((*MockGenerator)(nil).Alg))
|
||||
}
|
||||
|
||||
// Runes mocks base method
|
||||
func (m *MockGenerator) Runes() []rune {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Runes")
|
||||
ret0, _ := ret[0].([]rune)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Runes indicates an expected call of Runes
|
||||
func (mr *MockGeneratorMockRecorder) Runes() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Runes", reflect.TypeOf((*MockGenerator)(nil).Runes))
|
||||
}
|
352
internal/crypto/code_test.go
Normal file
352
internal/crypto/code_test.go
Normal file
@ -0,0 +1,352 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func createMockEncryptionAlg(t *testing.T) EncryptionAlgorithm {
|
||||
mCrypto := NewMockEncryptionAlgorithm(gomock.NewController(t))
|
||||
mCrypto.EXPECT().Algorithm().AnyTimes().Return("enc")
|
||||
mCrypto.EXPECT().EncryptionKeyID().AnyTimes().Return("id")
|
||||
mCrypto.EXPECT().DecryptionKeyIDs().AnyTimes().Return([]string{"id"})
|
||||
mCrypto.EXPECT().Encrypt(gomock.Any()).DoAndReturn(
|
||||
func(code []byte) ([]byte, error) {
|
||||
return code, nil
|
||||
},
|
||||
)
|
||||
mCrypto.EXPECT().DecryptString(gomock.Any(), gomock.Any()).DoAndReturn(
|
||||
func(code []byte, keyID string) (string, error) {
|
||||
if keyID != "id" {
|
||||
return "", errors.ThrowInternal(nil, "id", "invalid key id")
|
||||
}
|
||||
return string(code), nil
|
||||
},
|
||||
)
|
||||
return mCrypto
|
||||
}
|
||||
|
||||
func createMockHashAlg(t *testing.T) HashAlgorithm {
|
||||
mCrypto := NewMockHashAlgorithm(gomock.NewController(t))
|
||||
mCrypto.EXPECT().Algorithm().AnyTimes().Return("hash")
|
||||
mCrypto.EXPECT().Hash(gomock.Any()).DoAndReturn(
|
||||
func(code []byte) ([]byte, error) {
|
||||
return code, nil
|
||||
},
|
||||
)
|
||||
mCrypto.EXPECT().CompareHash(gomock.Any(), gomock.Any()).DoAndReturn(
|
||||
func(hashed, comparer []byte) error {
|
||||
if string(hashed) != string(comparer) {
|
||||
return errors.ThrowInternal(nil, "id", "invalid")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
return mCrypto
|
||||
}
|
||||
|
||||
func createMockCrypto(t *testing.T) Crypto {
|
||||
mCrypto := NewMockCrypto(gomock.NewController(t))
|
||||
mCrypto.EXPECT().Algorithm().AnyTimes().Return("crypto")
|
||||
return mCrypto
|
||||
}
|
||||
|
||||
func createMockGenerator(t *testing.T, crypto Crypto) Generator {
|
||||
mGenerator := NewMockGenerator(gomock.NewController(t))
|
||||
mGenerator.EXPECT().Alg().AnyTimes().Return(crypto)
|
||||
return mGenerator
|
||||
}
|
||||
|
||||
func TestIsCodeExpired(t *testing.T) {
|
||||
type args struct {
|
||||
creationDate time.Time
|
||||
expiry time.Duration
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
"not expired",
|
||||
args{
|
||||
creationDate: time.Now(),
|
||||
expiry: time.Duration(5 * time.Minute),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"expired",
|
||||
args{
|
||||
creationDate: time.Now().Add(-5 * time.Minute),
|
||||
expiry: time.Duration(5 * time.Minute),
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsCodeExpired(tt.args.creationDate, tt.args.expiry); got != tt.want {
|
||||
t.Errorf("IsCodeExpired() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyCode(t *testing.T) {
|
||||
type args struct {
|
||||
creationDate time.Time
|
||||
expiry time.Duration
|
||||
cryptoCode *CryptoValue
|
||||
verificationCode string
|
||||
g Generator
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"expired",
|
||||
args{
|
||||
creationDate: time.Now().Add(-5 * time.Minute),
|
||||
expiry: 5 * time.Minute,
|
||||
cryptoCode: nil,
|
||||
verificationCode: "",
|
||||
g: nil,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"unsupported alg err",
|
||||
args{
|
||||
creationDate: time.Now(),
|
||||
expiry: 5 * time.Minute,
|
||||
cryptoCode: nil,
|
||||
verificationCode: "code",
|
||||
g: createMockGenerator(t, createMockCrypto(t)),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"encryption alg ok",
|
||||
args{
|
||||
creationDate: time.Now(),
|
||||
expiry: 5 * time.Minute,
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
verificationCode: "code",
|
||||
g: createMockGenerator(t, createMockEncryptionAlg(t)),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"hash alg ok",
|
||||
args{
|
||||
creationDate: time.Now(),
|
||||
expiry: 5 * time.Minute,
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeHash,
|
||||
Algorithm: "hash",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
verificationCode: "code",
|
||||
g: createMockGenerator(t, createMockHashAlg(t)),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := VerifyCode(tt.args.creationDate, tt.args.expiry, tt.args.cryptoCode, tt.args.verificationCode, tt.args.g); (err != nil) != tt.wantErr {
|
||||
t.Errorf("VerifyCode() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_verifyEncryptedCode(t *testing.T) {
|
||||
type args struct {
|
||||
cryptoCode *CryptoValue
|
||||
verificationCode string
|
||||
alg EncryptionAlgorithm
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"nil error",
|
||||
args{
|
||||
cryptoCode: nil,
|
||||
verificationCode: "",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"wrong cryptotype error",
|
||||
args{
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeHash,
|
||||
Crypted: nil,
|
||||
},
|
||||
verificationCode: "",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"wrong algorithm error",
|
||||
args{
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeEncryption,
|
||||
Algorithm: "enc2",
|
||||
Crypted: nil,
|
||||
},
|
||||
verificationCode: "",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"wrong key id error",
|
||||
args{
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
Crypted: nil,
|
||||
},
|
||||
verificationCode: "wrong",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"wrong verification code error",
|
||||
args{
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
verificationCode: "wrong",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"verification code ok",
|
||||
args{
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
verificationCode: "code",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := verifyEncryptedCode(tt.args.cryptoCode, tt.args.verificationCode, tt.args.alg); (err != nil) != tt.wantErr {
|
||||
t.Errorf("verifyEncryptedCode() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_verifyHashedCode(t *testing.T) {
|
||||
type args struct {
|
||||
cryptoCode *CryptoValue
|
||||
verificationCode string
|
||||
alg HashAlgorithm
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
|
||||
{
|
||||
"nil error",
|
||||
args{
|
||||
cryptoCode: nil,
|
||||
verificationCode: "",
|
||||
alg: createMockHashAlg(t),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"wrong cryptotype error",
|
||||
args{
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeEncryption,
|
||||
Crypted: nil,
|
||||
},
|
||||
verificationCode: "",
|
||||
alg: createMockHashAlg(t),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"wrong algorithm error",
|
||||
args{
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeHash,
|
||||
Algorithm: "hash2",
|
||||
Crypted: nil,
|
||||
},
|
||||
verificationCode: "",
|
||||
alg: createMockHashAlg(t),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"wrong verification code error",
|
||||
args{
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeHash,
|
||||
Algorithm: "hash",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
verificationCode: "wrong",
|
||||
alg: createMockHashAlg(t),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"verification code ok",
|
||||
args{
|
||||
cryptoCode: &CryptoValue{
|
||||
CryptoType: TypeHash,
|
||||
Algorithm: "hash",
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
verificationCode: "code",
|
||||
alg: createMockHashAlg(t),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := verifyHashedCode(tt.args.cryptoCode, tt.args.verificationCode, tt.args.alg); (err != nil) != tt.wantErr {
|
||||
t.Errorf("verifyHashedCode() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
106
internal/crypto/crypto.go
Normal file
106
internal/crypto/crypto.go
Normal file
@ -0,0 +1,106 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeEncryption CryptoType = iota
|
||||
TypeHash
|
||||
)
|
||||
|
||||
type Crypto interface {
|
||||
Algorithm() string
|
||||
}
|
||||
|
||||
type EncryptionAlgorithm interface {
|
||||
Crypto
|
||||
EncryptionKeyID() string
|
||||
DecryptionKeyIDs() []string
|
||||
Encrypt(value []byte) ([]byte, error)
|
||||
Decrypt(hashed []byte, keyID string) ([]byte, error)
|
||||
DecryptString(hashed []byte, keyID string) (string, error)
|
||||
}
|
||||
|
||||
type HashAlgorithm interface {
|
||||
Crypto
|
||||
Hash(value []byte) ([]byte, error)
|
||||
CompareHash(hashed, comparer []byte) error
|
||||
}
|
||||
|
||||
type CryptoValue struct {
|
||||
CryptoType CryptoType
|
||||
Algorithm string
|
||||
KeyID string
|
||||
Crypted []byte
|
||||
}
|
||||
|
||||
type CryptoType int
|
||||
|
||||
func Crypt(value []byte, c Crypto) (*CryptoValue, error) {
|
||||
switch alg := c.(type) {
|
||||
case EncryptionAlgorithm:
|
||||
return Encrypt(value, alg)
|
||||
case HashAlgorithm:
|
||||
return Hash(value, alg)
|
||||
}
|
||||
return nil, errors.ThrowInternal(nil, "CRYPT-r4IaHZ", "algorithm not supported")
|
||||
}
|
||||
|
||||
func Encrypt(value []byte, alg EncryptionAlgorithm) (*CryptoValue, error) {
|
||||
encrypted, err := alg.Encrypt(value)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "CRYPT-qCD0JB", "error encrypting value")
|
||||
}
|
||||
return &CryptoValue{
|
||||
CryptoType: TypeEncryption,
|
||||
Algorithm: alg.Algorithm(),
|
||||
KeyID: alg.EncryptionKeyID(),
|
||||
Crypted: encrypted,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func Decrypt(value *CryptoValue, alg EncryptionAlgorithm) ([]byte, error) {
|
||||
if err := checkEncryptionAlgorithm(value, alg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return alg.Decrypt(value.Crypted, value.KeyID)
|
||||
}
|
||||
|
||||
func DecryptString(value *CryptoValue, alg EncryptionAlgorithm) (string, error) {
|
||||
if err := checkEncryptionAlgorithm(value, alg); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return alg.DecryptString(value.Crypted, value.KeyID)
|
||||
}
|
||||
|
||||
func checkEncryptionAlgorithm(value *CryptoValue, alg EncryptionAlgorithm) error {
|
||||
if value.Algorithm != alg.Algorithm() {
|
||||
return errors.ThrowInvalidArgument(nil, "CRYPT-Nx7XlT", "value was encrypted with a different key")
|
||||
}
|
||||
for _, id := range alg.DecryptionKeyIDs() {
|
||||
if id == value.KeyID {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.ThrowInvalidArgument(nil, "CRYPT-Kq12vn", "value was encrypted with a different key")
|
||||
}
|
||||
|
||||
func Hash(value []byte, alg HashAlgorithm) (*CryptoValue, error) {
|
||||
hashed, err := alg.Hash(value)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "CRYPT-rBVaJU", "error hashing value")
|
||||
}
|
||||
return &CryptoValue{
|
||||
CryptoType: TypeHash,
|
||||
Algorithm: alg.Algorithm(),
|
||||
Crypted: hashed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func CompareHash(value *CryptoValue, comparer []byte, alg HashAlgorithm) error {
|
||||
if value.Algorithm != alg.Algorithm() {
|
||||
return errors.ThrowInvalidArgument(nil, "CRYPT-HF32f", "value was hash with a different algorithm")
|
||||
}
|
||||
return alg.CompareHash(value.Crypted, comparer)
|
||||
}
|
223
internal/crypto/crypto_mock.go
Normal file
223
internal/crypto/crypto_mock.go
Normal file
@ -0,0 +1,223 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: crypto.go
|
||||
|
||||
// Package crypto is a generated GoMock package.
|
||||
package crypto
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockCrypto is a mock of Crypto interface
|
||||
type MockCrypto struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockCryptoMockRecorder
|
||||
}
|
||||
|
||||
// MockCryptoMockRecorder is the mock recorder for MockCrypto
|
||||
type MockCryptoMockRecorder struct {
|
||||
mock *MockCrypto
|
||||
}
|
||||
|
||||
// NewMockCrypto creates a new mock instance
|
||||
func NewMockCrypto(ctrl *gomock.Controller) *MockCrypto {
|
||||
mock := &MockCrypto{ctrl: ctrl}
|
||||
mock.recorder = &MockCryptoMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockCrypto) EXPECT() *MockCryptoMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Algorithm mocks base method
|
||||
func (m *MockCrypto) Algorithm() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Algorithm")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Algorithm indicates an expected call of Algorithm
|
||||
func (mr *MockCryptoMockRecorder) Algorithm() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockCrypto)(nil).Algorithm))
|
||||
}
|
||||
|
||||
// MockEncryptionAlgorithm is a mock of EncryptionAlgorithm interface
|
||||
type MockEncryptionAlgorithm struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockEncryptionAlgorithmMockRecorder
|
||||
}
|
||||
|
||||
// MockEncryptionAlgorithmMockRecorder is the mock recorder for MockEncryptionAlgorithm
|
||||
type MockEncryptionAlgorithmMockRecorder struct {
|
||||
mock *MockEncryptionAlgorithm
|
||||
}
|
||||
|
||||
// NewMockEncryptionAlgorithm creates a new mock instance
|
||||
func NewMockEncryptionAlgorithm(ctrl *gomock.Controller) *MockEncryptionAlgorithm {
|
||||
mock := &MockEncryptionAlgorithm{ctrl: ctrl}
|
||||
mock.recorder = &MockEncryptionAlgorithmMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockEncryptionAlgorithm) EXPECT() *MockEncryptionAlgorithmMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Algorithm mocks base method
|
||||
func (m *MockEncryptionAlgorithm) Algorithm() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Algorithm")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Algorithm indicates an expected call of Algorithm
|
||||
func (mr *MockEncryptionAlgorithmMockRecorder) Algorithm() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Algorithm))
|
||||
}
|
||||
|
||||
// EncryptionKeyID mocks base method
|
||||
func (m *MockEncryptionAlgorithm) EncryptionKeyID() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EncryptionKeyID")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EncryptionKeyID indicates an expected call of EncryptionKeyID
|
||||
func (mr *MockEncryptionAlgorithmMockRecorder) EncryptionKeyID() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncryptionKeyID", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).EncryptionKeyID))
|
||||
}
|
||||
|
||||
// DecryptionKeyIDs mocks base method
|
||||
func (m *MockEncryptionAlgorithm) DecryptionKeyIDs() []string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DecryptionKeyIDs")
|
||||
ret0, _ := ret[0].([]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DecryptionKeyIDs indicates an expected call of DecryptionKeyIDs
|
||||
func (mr *MockEncryptionAlgorithmMockRecorder) DecryptionKeyIDs() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptionKeyIDs", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).DecryptionKeyIDs))
|
||||
}
|
||||
|
||||
// Encrypt mocks base method
|
||||
func (m *MockEncryptionAlgorithm) Encrypt(value []byte) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Encrypt", value)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Encrypt indicates an expected call of Encrypt
|
||||
func (mr *MockEncryptionAlgorithmMockRecorder) Encrypt(value interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Encrypt", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Encrypt), value)
|
||||
}
|
||||
|
||||
// Decrypt mocks base method
|
||||
func (m *MockEncryptionAlgorithm) Decrypt(hashed []byte, keyID string) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Decrypt", hashed, keyID)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Decrypt indicates an expected call of Decrypt
|
||||
func (mr *MockEncryptionAlgorithmMockRecorder) Decrypt(hashed, keyID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Decrypt", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).Decrypt), hashed, keyID)
|
||||
}
|
||||
|
||||
// DecryptString mocks base method
|
||||
func (m *MockEncryptionAlgorithm) DecryptString(hashed []byte, keyID string) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DecryptString", hashed, keyID)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DecryptString indicates an expected call of DecryptString
|
||||
func (mr *MockEncryptionAlgorithmMockRecorder) DecryptString(hashed, keyID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptString", reflect.TypeOf((*MockEncryptionAlgorithm)(nil).DecryptString), hashed, keyID)
|
||||
}
|
||||
|
||||
// MockHashAlgorithm is a mock of HashAlgorithm interface
|
||||
type MockHashAlgorithm struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockHashAlgorithmMockRecorder
|
||||
}
|
||||
|
||||
// MockHashAlgorithmMockRecorder is the mock recorder for MockHashAlgorithm
|
||||
type MockHashAlgorithmMockRecorder struct {
|
||||
mock *MockHashAlgorithm
|
||||
}
|
||||
|
||||
// NewMockHashAlgorithm creates a new mock instance
|
||||
func NewMockHashAlgorithm(ctrl *gomock.Controller) *MockHashAlgorithm {
|
||||
mock := &MockHashAlgorithm{ctrl: ctrl}
|
||||
mock.recorder = &MockHashAlgorithmMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockHashAlgorithm) EXPECT() *MockHashAlgorithmMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Algorithm mocks base method
|
||||
func (m *MockHashAlgorithm) Algorithm() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Algorithm")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Algorithm indicates an expected call of Algorithm
|
||||
func (mr *MockHashAlgorithmMockRecorder) Algorithm() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Algorithm", reflect.TypeOf((*MockHashAlgorithm)(nil).Algorithm))
|
||||
}
|
||||
|
||||
// Hash mocks base method
|
||||
func (m *MockHashAlgorithm) Hash(value []byte) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Hash", value)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Hash indicates an expected call of Hash
|
||||
func (mr *MockHashAlgorithmMockRecorder) Hash(value interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Hash", reflect.TypeOf((*MockHashAlgorithm)(nil).Hash), value)
|
||||
}
|
||||
|
||||
// CompareHash mocks base method
|
||||
func (m *MockHashAlgorithm) CompareHash(hashed, comparer []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CompareHash", hashed, comparer)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CompareHash indicates an expected call of CompareHash
|
||||
func (mr *MockHashAlgorithmMockRecorder) CompareHash(hashed, comparer interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompareHash", reflect.TypeOf((*MockHashAlgorithm)(nil).CompareHash), hashed, comparer)
|
||||
}
|
273
internal/crypto/crypto_test.go
Normal file
273
internal/crypto/crypto_test.go
Normal file
@ -0,0 +1,273 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockEncCrypto struct {
|
||||
}
|
||||
|
||||
func (m *mockEncCrypto) Algorithm() string {
|
||||
return "enc"
|
||||
}
|
||||
|
||||
func (m *mockEncCrypto) Encrypt(value []byte) ([]byte, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (m *mockEncCrypto) Decrypt(value []byte, _ string) ([]byte, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (m *mockEncCrypto) DecryptString(value []byte, _ string) (string, error) {
|
||||
return string(value), nil
|
||||
}
|
||||
|
||||
func (m *mockEncCrypto) EncryptionKeyID() string {
|
||||
return "keyID"
|
||||
}
|
||||
func (m *mockEncCrypto) DecryptionKeyIDs() []string {
|
||||
return []string{"keyID"}
|
||||
}
|
||||
|
||||
type mockHashCrypto struct {
|
||||
}
|
||||
|
||||
func (m *mockHashCrypto) Algorithm() string {
|
||||
return "hash"
|
||||
}
|
||||
|
||||
func (m *mockHashCrypto) Hash(value []byte) ([]byte, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (m *mockHashCrypto) CompareHash(hashed, comparer []byte) error {
|
||||
if !bytes.Equal(hashed, comparer) {
|
||||
return errors.New("not equal")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type alg struct{}
|
||||
|
||||
func (a *alg) Algorithm() string {
|
||||
return "alg"
|
||||
}
|
||||
|
||||
func TestCrypt(t *testing.T) {
|
||||
type args struct {
|
||||
value []byte
|
||||
c Crypto
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *CryptoValue
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"encrypt",
|
||||
args{[]byte("test"), &mockEncCrypto{}},
|
||||
&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"hash",
|
||||
args{[]byte("test"), &mockHashCrypto{}},
|
||||
&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"wrong type",
|
||||
args{[]byte("test"), &alg{}},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Crypt(tt.args.value, tt.args.c)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Crypt() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Crypt() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncrypt(t *testing.T) {
|
||||
type args struct {
|
||||
value []byte
|
||||
c EncryptionAlgorithm
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *CryptoValue
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
args{[]byte("test"), &mockEncCrypto{}},
|
||||
&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Encrypt(tt.args.value, tt.args.c)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Encrypt() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Encrypt() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrypt(t *testing.T) {
|
||||
type args struct {
|
||||
value *CryptoValue
|
||||
c EncryptionAlgorithm
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
args{&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")}, &mockEncCrypto{}},
|
||||
[]byte("test"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"wrong id",
|
||||
args{&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID2", Crypted: []byte("test")}, &mockEncCrypto{}},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Decrypt(tt.args.value, tt.args.c)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Decrypt() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Decrypt() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptString(t *testing.T) {
|
||||
type args struct {
|
||||
value *CryptoValue
|
||||
c EncryptionAlgorithm
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
args{&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID", Crypted: []byte("test")}, &mockEncCrypto{}},
|
||||
"test",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"wrong id",
|
||||
args{&CryptoValue{CryptoType: TypeEncryption, Algorithm: "enc", KeyID: "keyID2", Crypted: []byte("test")}, &mockEncCrypto{}},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := DecryptString(tt.args.value, tt.args.c)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DecryptString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("DecryptString() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHash(t *testing.T) {
|
||||
type args struct {
|
||||
value []byte
|
||||
c HashAlgorithm
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *CryptoValue
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
args{[]byte("test"), &mockHashCrypto{}},
|
||||
&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Hash(tt.args.value, tt.args.c)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Hash() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Hash() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareHash(t *testing.T) {
|
||||
type args struct {
|
||||
value *CryptoValue
|
||||
comparer []byte
|
||||
c HashAlgorithm
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"ok",
|
||||
args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test"), &mockHashCrypto{}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"wrong",
|
||||
args{&CryptoValue{CryptoType: TypeHash, Algorithm: "hash", Crypted: []byte("test")}, []byte("test2"), &mockHashCrypto{}},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := CompareHash(tt.args.value, tt.args.comparer, tt.args.c); (err != nil) != tt.wantErr {
|
||||
t.Errorf("CompareHash() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
4
internal/crypto/generate.go
Normal file
4
internal/crypto/generate.go
Normal file
@ -0,0 +1,4 @@
|
||||
package crypto
|
||||
|
||||
//go:generate mockgen -source crypto.go -destination ./crypto_mock.go -package crypto
|
||||
//go:generate mockgen -source code.go -destination ./code_mock.go -package crypto
|
64
internal/crypto/key.go
Normal file
64
internal/crypto/key.go
Normal file
@ -0,0 +1,64 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/config"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
ZitadelKeyPath = "ZITADEL_KEY_PATH"
|
||||
)
|
||||
|
||||
type KeyConfig struct {
|
||||
EncryptionKeyID string
|
||||
DecryptionKeyIDs []string
|
||||
Path string
|
||||
}
|
||||
|
||||
type Keys map[string]string
|
||||
|
||||
func ReadKeys(path string) (Keys, error) {
|
||||
if path == "" {
|
||||
path = os.Getenv(ZitadelKeyPath)
|
||||
if path == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "CRYPT-56lka", "no path set")
|
||||
}
|
||||
}
|
||||
keys := new(Keys)
|
||||
err := config.Read(keys, path)
|
||||
return *keys, err
|
||||
}
|
||||
|
||||
func LoadKeys(config *KeyConfig) (map[string]string, []string, error) {
|
||||
if config == nil {
|
||||
return nil, nil, errors.ThrowInvalidArgument(nil, "CRYPT-dJK8s", "config must not be nil")
|
||||
}
|
||||
readKeys, err := ReadKeys(config.Path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
keys := make(map[string]string)
|
||||
ids := make([]string, 0, len(config.DecryptionKeyIDs)+1)
|
||||
if config.EncryptionKeyID != "" {
|
||||
key, ok := readKeys[config.EncryptionKeyID]
|
||||
if !ok {
|
||||
return nil, nil, errors.ThrowInternalf(nil, "CRYPT-v2Kas", "encryption key not found")
|
||||
}
|
||||
keys[config.EncryptionKeyID] = key
|
||||
ids = append(ids, config.EncryptionKeyID)
|
||||
}
|
||||
for _, id := range config.DecryptionKeyIDs {
|
||||
key, ok := readKeys[id]
|
||||
if !ok {
|
||||
logging.Log("CRYPT-s23rf").Warnf("description key %s not found", id)
|
||||
continue
|
||||
}
|
||||
keys[id] = key
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return keys, ids, nil
|
||||
}
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestAlreadyExistsError(t *testing.T) {
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestErrorMethod(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestDeadlineExceededError(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errors "github.com/caos/utils/errors"
|
||||
caos_errors "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
|
@ -32,7 +32,7 @@ func main() {
|
||||
|
||||
fmt.Print(`
|
||||
!!!!!
|
||||
Add status mapping in grpc/errors/caos_errors.go //TODO: fix path when pkg exists
|
||||
Add status mapping in internal/api/grpc/caos_errors.go
|
||||
!!!!!`)
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func Test{{.ErrorName}}Error(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestInternalError(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestInvalidArgumentError(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestNotFoundError(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestPermissionDeniedError(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestPreconditionFailedError(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestUnauthenticatedError(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestUnavailableError(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestUnimplementedError(t *testing.T) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
caos_errs "github.com/caos/utils/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func TestUnknownError(t *testing.T) {
|
||||
|
4
internal/login/config.go
Normal file
4
internal/login/config.go
Normal file
@ -0,0 +1,4 @@
|
||||
package login
|
||||
|
||||
type Config struct {
|
||||
}
|
3
internal/management/config.go
Normal file
3
internal/management/config.go
Normal file
@ -0,0 +1,3 @@
|
||||
package management
|
||||
|
||||
type Config struct{}
|
50
internal/proto/struct.go
Normal file
50
internal/proto/struct.go
Normal file
@ -0,0 +1,50 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
pb_struct "github.com/golang/protobuf/ptypes/struct"
|
||||
|
||||
"github.com/caos/logging"
|
||||
)
|
||||
|
||||
var (
|
||||
marshaller = new(jsonpb.Marshaler)
|
||||
)
|
||||
|
||||
func MustToPBStruct(object interface{}) *pb_struct.Struct {
|
||||
s, err := ToPBStruct(object)
|
||||
logging.Log("PROTO-7Aa3t").OnError(err).Panic("unable to map object to pb-struct")
|
||||
return s
|
||||
}
|
||||
|
||||
func BytesToPBStruct(b []byte) (*pb_struct.Struct, error) {
|
||||
fields := new(pb_struct.Struct)
|
||||
err := jsonpb.Unmarshal(bytes.NewReader(b), fields)
|
||||
return fields, err
|
||||
}
|
||||
|
||||
func ToPBStruct(object interface{}) (*pb_struct.Struct, error) {
|
||||
marshalled, err := json.Marshal(object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields := new(pb_struct.Struct)
|
||||
err = jsonpb.Unmarshal(bytes.NewReader(marshalled), fields)
|
||||
return fields, err
|
||||
}
|
||||
|
||||
func MustFromPBStruct(object interface{}, s *pb_struct.Struct) {
|
||||
err := FromPBStruct(object, s)
|
||||
logging.Log("PROTO-WeMYY").OnError(err).Panic("unable to map pb-struct into object")
|
||||
}
|
||||
|
||||
func FromPBStruct(object interface{}, s *pb_struct.Struct) error {
|
||||
jsonString, err := marshaller.MarshalToString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal([]byte(jsonString), object)
|
||||
}
|
115
internal/proto/struct_test.go
Normal file
115
internal/proto/struct_test.go
Normal file
@ -0,0 +1,115 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pb_struct "github.com/golang/protobuf/ptypes/struct"
|
||||
)
|
||||
|
||||
func Test_ToPBStruct(t *testing.T) {
|
||||
type obj struct {
|
||||
ID string
|
||||
Seq uint64
|
||||
}
|
||||
type args struct {
|
||||
obj obj
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
length int
|
||||
result obj
|
||||
}{
|
||||
{
|
||||
name: "to pb stuct",
|
||||
args: args{
|
||||
obj: obj{ID: "ID", Seq: 12345},
|
||||
},
|
||||
wantErr: false,
|
||||
length: 2,
|
||||
result: obj{ID: "ID", Seq: 12345},
|
||||
},
|
||||
{
|
||||
name: "empty struct",
|
||||
args: args{
|
||||
obj: obj{},
|
||||
},
|
||||
wantErr: false,
|
||||
length: 2,
|
||||
result: obj{ID: "", Seq: 0},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fields, err := ToPBStruct(tt.args.obj)
|
||||
if tt.wantErr && err == nil {
|
||||
t.Errorf("got wrong result, should get err: actual: %v ", err)
|
||||
}
|
||||
if !tt.wantErr && len(fields.Fields) != tt.length {
|
||||
t.Errorf("got wrong result length, expecting: %v, actual: %v ", tt.length, len(fields.Fields))
|
||||
}
|
||||
if !tt.wantErr && tt.result.ID != fields.Fields["ID"].GetStringValue() {
|
||||
t.Errorf("got wrong result, ID should be same: expecting: %v, actual: %v ", tt.result.ID, fields.Fields["ID"].GetStringValue())
|
||||
}
|
||||
if !tt.wantErr && int(tt.result.Seq) != int(fields.Fields["Seq"].GetNumberValue()) {
|
||||
t.Errorf("got wrong result, Seq should be same: expecting: %v, actual: %v ", tt.result.Seq, fields.Fields["Seq"].GetStringValue())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_FromPBStruct(t *testing.T) {
|
||||
type obj struct {
|
||||
ID string
|
||||
Seq uint64
|
||||
}
|
||||
type args struct {
|
||||
obj *obj
|
||||
fields *pb_struct.Struct
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
result obj
|
||||
}{
|
||||
{
|
||||
name: "from pb stuct",
|
||||
args: args{
|
||||
obj: &obj{},
|
||||
fields: &pb_struct.Struct{Fields: map[string]*pb_struct.Value{
|
||||
"ID": &pb_struct.Value{Kind: &pb_struct.Value_StringValue{StringValue: "ID"}},
|
||||
"Seq": &pb_struct.Value{Kind: &pb_struct.Value_NumberValue{NumberValue: 12345}},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
result: obj{ID: "ID", Seq: 12345},
|
||||
},
|
||||
{
|
||||
name: "no fields",
|
||||
args: args{
|
||||
obj: &obj{},
|
||||
fields: &pb_struct.Struct{Fields: map[string]*pb_struct.Value{},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
result: obj{ID: "", Seq: 0},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := FromPBStruct(tt.args.obj, tt.args.fields)
|
||||
if tt.wantErr && err == nil {
|
||||
t.Errorf("got wrong result, should get err: actual: %v ", err)
|
||||
}
|
||||
if !tt.wantErr && tt.result.ID != tt.args.obj.ID {
|
||||
t.Errorf("got wrong result, ID should be same: expecting: %v, actual: %v ", tt.result.ID, tt.args.obj.ID)
|
||||
}
|
||||
if !tt.wantErr && int(tt.result.Seq) != int(tt.args.obj.Seq) {
|
||||
t.Errorf("got wrong result, Seq should be same: expecting: %v, actual: %v ", tt.result.Seq, tt.args.obj.Seq)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
112
internal/protoc/protoc-base/protoc_helper.go
Normal file
112
internal/protoc/protoc-base/protoc_helper.go
Normal file
@ -0,0 +1,112 @@
|
||||
package protocbase
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/golang/protobuf/proto"
|
||||
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
|
||||
)
|
||||
|
||||
type GeneratorFunc func(target string, registry *descriptor.Registry, file *descriptor.File) (string, string, error)
|
||||
|
||||
type ProtocGenerator interface {
|
||||
Generate(target string, registry *descriptor.Registry, file *descriptor.File) (string, string, error)
|
||||
}
|
||||
|
||||
func (f GeneratorFunc) Generate(target string, registry *descriptor.Registry, file *descriptor.File) (string, string, error) {
|
||||
return f(target, registry, file)
|
||||
}
|
||||
|
||||
func parseReq(r io.Reader) (*plugin.CodeGeneratorRequest, error) {
|
||||
glog.V(1).Info("Parsing code generator request")
|
||||
|
||||
input, err := ioutil.ReadAll(r)
|
||||
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to read code generator request: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &plugin.CodeGeneratorRequest{}
|
||||
|
||||
if err = proto.Unmarshal(input, req); err != nil {
|
||||
glog.Errorf("Failed to unmarshal code generator request: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
glog.V(1).Info("Parsed code generator request")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func RunWithBaseTemplate(targetFileNameFmt string, tmpl *template.Template) {
|
||||
Run(GeneratorFunc(func(target string, registry *descriptor.Registry, file *descriptor.File) (string, string, error) {
|
||||
fileName := fmt.Sprintf(targetFileNameFmt, strings.Split(target, ".")[0])
|
||||
fContent, err := GenerateFromBaseTemplate(tmpl, registry, file)
|
||||
return fileName, fContent, err
|
||||
}))
|
||||
}
|
||||
|
||||
func Run(generator ProtocGenerator) {
|
||||
flag.Parse()
|
||||
defer glog.Flush()
|
||||
|
||||
req, err := parseReq(os.Stdin)
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
|
||||
registry := descriptor.NewRegistry()
|
||||
if err = registry.Load(req); err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
|
||||
var result []*plugin.CodeGeneratorResponse_File
|
||||
|
||||
for _, t := range req.FileToGenerate {
|
||||
file, err := registry.LookupFile(t)
|
||||
if err != nil {
|
||||
EmitError(err)
|
||||
return
|
||||
}
|
||||
|
||||
fName, fContent, err := generator.Generate(t, registry, file)
|
||||
if err != nil {
|
||||
EmitError(err)
|
||||
return
|
||||
}
|
||||
|
||||
result = append(result, &plugin.CodeGeneratorResponse_File{
|
||||
Name: &fName,
|
||||
Content: &fContent,
|
||||
})
|
||||
}
|
||||
|
||||
EmitFiles(result)
|
||||
}
|
||||
|
||||
func EmitFiles(out []*plugin.CodeGeneratorResponse_File) {
|
||||
EmitResp(&plugin.CodeGeneratorResponse{File: out})
|
||||
}
|
||||
|
||||
func EmitError(err error) {
|
||||
EmitResp(&plugin.CodeGeneratorResponse{Error: proto.String(err.Error())})
|
||||
}
|
||||
|
||||
func EmitResp(resp *plugin.CodeGeneratorResponse) {
|
||||
buf, err := proto.Marshal(resp)
|
||||
if err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stdout.Write(buf); err != nil {
|
||||
glog.Fatal(err)
|
||||
}
|
||||
}
|
106
internal/protoc/protoc-base/templates.go
Normal file
106
internal/protoc/protoc-base/templates.go
Normal file
@ -0,0 +1,106 @@
|
||||
package protocbase
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
|
||||
"golang.org/x/tools/imports"
|
||||
)
|
||||
|
||||
var extensions = map[string]*proto.ExtensionDesc{}
|
||||
|
||||
type BaseTemplateData struct {
|
||||
Now time.Time
|
||||
File *descriptor.File
|
||||
|
||||
registry *descriptor.Registry
|
||||
}
|
||||
|
||||
var templateFuncs = map[string]interface{}{
|
||||
"option": getOption,
|
||||
}
|
||||
|
||||
func RegisterTmplFunc(name string, f interface{}) {
|
||||
if _, existing := templateFuncs[name]; existing {
|
||||
panic(fmt.Sprintf("func with name %v is already registered", name))
|
||||
}
|
||||
|
||||
templateFuncs[name] = f
|
||||
}
|
||||
|
||||
func RegisterExtension(ext *proto.ExtensionDesc) {
|
||||
extensions[ext.Name] = ext
|
||||
}
|
||||
|
||||
func GetBaseTemplateData(registry *descriptor.Registry, file *descriptor.File) *BaseTemplateData {
|
||||
return &BaseTemplateData{
|
||||
Now: time.Now().UTC(),
|
||||
File: file,
|
||||
registry: registry,
|
||||
}
|
||||
}
|
||||
|
||||
func getOption(opts proto.Message, extName string) interface{} {
|
||||
extDesc := extensions[extName]
|
||||
|
||||
if !proto.HasExtension(opts, extDesc) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ext, err := proto.GetExtension(opts, extDesc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ext
|
||||
}
|
||||
|
||||
func (data *BaseTemplateData) ResolveMsgType(msgType string) string {
|
||||
msg, err := data.registry.LookupMsg(data.File.GetPackage(), msgType)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return msg.GoType(data.File.GoPkg.Path)
|
||||
}
|
||||
|
||||
func (data *BaseTemplateData) ResolveFile(fileName string) *descriptor.File {
|
||||
file, err := data.registry.LookupFile(fileName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func LoadTemplate(templateData []byte, err error) *template.Template {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return template.Must(template.New("").
|
||||
Funcs(sprig.TxtFuncMap()).
|
||||
Funcs(templateFuncs).
|
||||
Parse(string(templateData)))
|
||||
}
|
||||
|
||||
func GenerateFromTemplate(tmpl *template.Template, data interface{}) (string, error) {
|
||||
var tpl bytes.Buffer
|
||||
err := tmpl.Execute(&tpl, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmplResult := tpl.Bytes()
|
||||
tmplResult, err = imports.Process(".", tmplResult, nil)
|
||||
return string(tmplResult), err
|
||||
}
|
||||
|
||||
func GenerateFromBaseTemplate(tmpl *template.Template, registry *descriptor.Registry, file *descriptor.File) (string, error) {
|
||||
return GenerateFromTemplate(tmpl, GetBaseTemplateData(registry, file))
|
||||
}
|
37
internal/protoc/protoc-gen-authoption/README.md
Normal file
37
internal/protoc/protoc-gen-authoption/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# protoc-gen-authoption
|
||||
|
||||
Proto options to annotate auth methods in protos
|
||||
|
||||
## Generate protos/templates
|
||||
protos: `go generate authoption/generate.go`
|
||||
templates/install: `go generate generate.go`
|
||||
|
||||
## Usage
|
||||
```
|
||||
// proto file
|
||||
import "authoption/options.proto";
|
||||
|
||||
service MyService {
|
||||
|
||||
rpc Hello(Hello) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {
|
||||
get: "/hello"
|
||||
};
|
||||
|
||||
option (caos.zitadel.utils.v1.auth_option) = {
|
||||
zitadel_permission: "hello.read"
|
||||
zitadel_check_param: "id"
|
||||
};
|
||||
}
|
||||
|
||||
message Hello {
|
||||
string id = 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
Caos Auth Option is used for granting groups
|
||||
On each zitadel role is specified which auth methods are allowed to call
|
||||
|
||||
Get protoc-get-authoption: ``go get github.com/caos/zitadel/internal/protoc/protoc-gen-authoption``
|
||||
|
||||
Protc-Flag: ``--authoption_out=.``
|
@ -0,0 +1,3 @@
|
||||
package authoption
|
||||
|
||||
//go:generate protoc -I. -I$GOPATH/src --go_out=plugins=grpc:$GOPATH/src options.proto
|
105
internal/protoc/protoc-gen-authoption/authoption/options.pb.go
Normal file
105
internal/protoc/protoc-gen-authoption/authoption/options.pb.go
Normal file
@ -0,0 +1,105 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: options.proto
|
||||
|
||||
package authoption
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type AuthOption struct {
|
||||
Permission string `protobuf:"bytes,1,opt,name=permission,proto3" json:"permission,omitempty"`
|
||||
CheckFieldName string `protobuf:"bytes,2,opt,name=check_field_name,json=checkFieldName,proto3" json:"check_field_name,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *AuthOption) Reset() { *m = AuthOption{} }
|
||||
func (m *AuthOption) String() string { return proto.CompactTextString(m) }
|
||||
func (*AuthOption) ProtoMessage() {}
|
||||
func (*AuthOption) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_110d40819f1994f9, []int{0}
|
||||
}
|
||||
|
||||
func (m *AuthOption) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_AuthOption.Unmarshal(m, b)
|
||||
}
|
||||
func (m *AuthOption) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_AuthOption.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *AuthOption) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_AuthOption.Merge(m, src)
|
||||
}
|
||||
func (m *AuthOption) XXX_Size() int {
|
||||
return xxx_messageInfo_AuthOption.Size(m)
|
||||
}
|
||||
func (m *AuthOption) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_AuthOption.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_AuthOption proto.InternalMessageInfo
|
||||
|
||||
func (m *AuthOption) GetPermission() string {
|
||||
if m != nil {
|
||||
return m.Permission
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *AuthOption) GetCheckFieldName() string {
|
||||
if m != nil {
|
||||
return m.CheckFieldName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var E_AuthOption = &proto.ExtensionDesc{
|
||||
ExtendedType: (*descriptor.MethodOptions)(nil),
|
||||
ExtensionType: (*AuthOption)(nil),
|
||||
Field: 50000,
|
||||
Name: "caos.zitadel.utils.v1.auth_option",
|
||||
Tag: "bytes,50000,opt,name=auth_option",
|
||||
Filename: "options.proto",
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*AuthOption)(nil), "caos.zitadel.utils.v1.AuthOption")
|
||||
proto.RegisterExtension(E_AuthOption)
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("options.proto", fileDescriptor_110d40819f1994f9) }
|
||||
|
||||
var fileDescriptor_110d40819f1994f9 = []byte{
|
||||
// 252 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x8f, 0x31, 0x4b, 0xc5, 0x30,
|
||||
0x14, 0x85, 0x79, 0x0a, 0x82, 0x79, 0x28, 0x52, 0x10, 0x8a, 0x83, 0x54, 0xa7, 0x2e, 0xef, 0x06,
|
||||
0x75, 0x73, 0xd3, 0x41, 0x44, 0x50, 0xe1, 0x0d, 0x0e, 0x2e, 0x25, 0x4d, 0xef, 0x6b, 0x83, 0x6d,
|
||||
0x6e, 0x49, 0x6e, 0x1c, 0xfc, 0x01, 0xfe, 0x3e, 0x7f, 0x92, 0x34, 0xa9, 0x3e, 0x07, 0xa7, 0x5c,
|
||||
0x0e, 0xe7, 0x9c, 0x7c, 0x47, 0x1c, 0xd0, 0xc8, 0x86, 0xac, 0x87, 0xd1, 0x11, 0x53, 0x76, 0xac,
|
||||
0x15, 0x79, 0xf8, 0x30, 0xac, 0x1a, 0xec, 0x21, 0xb0, 0xe9, 0x3d, 0xbc, 0x5f, 0x9c, 0x14, 0x2d,
|
||||
0x51, 0xdb, 0xa3, 0x8c, 0xa6, 0x3a, 0x6c, 0x64, 0x83, 0x5e, 0x3b, 0x33, 0x32, 0xb9, 0x14, 0x3c,
|
||||
0x7f, 0x11, 0xe2, 0x26, 0x70, 0xf7, 0x1c, 0xdb, 0xb2, 0x53, 0x21, 0x46, 0x74, 0x83, 0xf1, 0xde,
|
||||
0x90, 0xcd, 0x17, 0xc5, 0xa2, 0xdc, 0x5f, 0xff, 0x51, 0xb2, 0x52, 0x1c, 0xe9, 0x0e, 0xf5, 0x5b,
|
||||
0xb5, 0x31, 0xd8, 0x37, 0x95, 0x55, 0x03, 0xe6, 0x3b, 0xd1, 0x75, 0x18, 0xf5, 0xbb, 0x49, 0x7e,
|
||||
0x52, 0x03, 0x5e, 0x37, 0x62, 0xa9, 0x02, 0x77, 0x15, 0xcd, 0xc5, 0x90, 0x48, 0xe0, 0x87, 0x04,
|
||||
0x1e, 0x91, 0x3b, 0x6a, 0xd2, 0xbf, 0x3e, 0xff, 0xfa, 0xdc, 0x2d, 0x16, 0xe5, 0xf2, 0xf2, 0x0c,
|
||||
0xfe, 0x1d, 0x02, 0x5b, 0xc6, 0xb5, 0x50, 0xbf, 0xf7, 0xed, 0xc3, 0xeb, 0x7d, 0x6b, 0xb8, 0x0b,
|
||||
0x35, 0x68, 0x1a, 0xe4, 0x14, 0x95, 0x73, 0x54, 0x1a, 0xcb, 0xe8, 0xac, 0xea, 0xd3, 0x76, 0x3d,
|
||||
0x3f, 0xab, 0x16, 0xed, 0x6a, 0x2a, 0x48, 0x5c, 0x72, 0x7b, 0xd6, 0x7b, 0xd1, 0x71, 0xf5, 0x1d,
|
||||
0x00, 0x00, 0xff, 0xff, 0xd2, 0xa7, 0xf7, 0xca, 0x5a, 0x01, 0x00, 0x00,
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package caos.zitadel.utils.v1;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
option go_package = "github.com/caos/zitadel/internal/protoc/protoc-gen-authoption/authoption";
|
||||
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
AuthOption auth_option = 50000;
|
||||
}
|
||||
|
||||
message AuthOption {
|
||||
string permission = 1;
|
||||
string check_field_name = 2;
|
||||
}
|
4
internal/protoc/protoc-gen-authoption/generate.go
Normal file
4
internal/protoc/protoc-gen-authoption/generate.go
Normal file
@ -0,0 +1,4 @@
|
||||
package main
|
||||
|
||||
//go:generate go-bindata -pkg main -o templates.go templates
|
||||
//go:generate go install
|
15
internal/protoc/protoc-gen-authoption/main.go
Normal file
15
internal/protoc/protoc-gen-authoption/main.go
Normal file
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
base "github.com/caos/zitadel/internal/protoc/protoc-base"
|
||||
"github.com/caos/zitadel/internal/protoc/protoc-gen-authoption/authoption"
|
||||
)
|
||||
|
||||
const (
|
||||
fileName = "%v.pb.authoptions.go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
base.RegisterExtension(authoption.E_AuthOption)
|
||||
base.RunWithBaseTemplate(fileName, base.LoadTemplate(templatesAuth_method_mappingGoTmplBytes()))
|
||||
}
|
237
internal/protoc/protoc-gen-authoption/templates.go
Normal file
237
internal/protoc/protoc-gen-authoption/templates.go
Normal file
@ -0,0 +1,237 @@
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// templates/auth_method_mapping.go.tmpl
|
||||
// DO NOT EDIT!
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
type bindataFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func (fi bindataFileInfo) Name() string {
|
||||
return fi.name
|
||||
}
|
||||
func (fi bindataFileInfo) Size() int64 {
|
||||
return fi.size
|
||||
}
|
||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||
return fi.mode
|
||||
}
|
||||
func (fi bindataFileInfo) ModTime() time.Time {
|
||||
return fi.modTime
|
||||
}
|
||||
func (fi bindataFileInfo) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _templatesAuth_method_mappingGoTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x52\xc1\x6a\xe3\x30\x10\x3d\xaf\xbe\x62\x30\x3e\xb4\x21\x95\xd8\x6b\xa0\x87\x25\xdd\x2e\x3d\xb4\x09\x6c\x76\xaf\x41\xb5\x27\xb2\x88\x2d\x19\x49\x0e\xb4\x42\xff\xbe\x8c\xed\xac\x9d\xb4\x0b\xeb\x93\xac\x99\x37\xef\xbd\xd1\x13\x02\xd6\xb6\x44\x50\x68\xd0\xc9\x80\x25\xbc\xbe\x41\xeb\x6c\xb0\xc5\x9d\x42\x73\x27\xbb\x50\x35\x18\x2a\x5b\x72\x78\xd8\xc0\xcb\x66\x07\xdf\x1f\x9e\x76\x9c\xb1\x56\x16\x47\xa9\x10\x62\xe4\x8f\xba\x46\xfe\xc3\x6e\x8f\x8a\xbf\xc8\x06\x53\x62\x8c\xe9\xa6\xb5\x2e\xc0\x0d\x03\x00\xc8\x94\xb5\xaa\x46\xae\x6c\x2d\x8d\xe2\xd6\x29\xa1\x5c\x5b\x64\x7d\x91\x7d\xe9\x82\xae\xfd\x9e\xa8\x20\x53\x3a\x54\xdd\x2b\x2f\x6c\x23\x0a\x69\xbd\x78\xd7\x41\x96\x58\x0b\x6d\x02\x3a\x23\x6b\x41\x6d\xd9\x19\x43\x53\xfe\x03\x33\x90\xdd\x32\x16\x23\x38\x69\x14\x42\xee\x61\x75\x0f\x83\xf2\x9f\xe8\x4e\xba\x40\x0f\x24\x5c\x2c\x16\x0c\x16\x10\x63\xee\xcf\x66\x60\x21\x18\x3b\x49\x37\xbf\xdc\x7f\xeb\x42\xf5\xdc\x2f\xc6\xc3\x3d\x4c\x0e\xf8\x54\x78\x96\x6d\xab\x8d\x82\xd8\xdb\x9c\xa8\x1b\xa2\xce\x3d\x1f\xba\x88\x60\xfc\x62\x84\xbc\x21\xfc\xa6\x0d\xd4\x63\xdb\xa0\xad\x81\xbc\xe1\x9b\xfe\xe4\x21\x23\x7f\x7c\xf4\xc7\x7b\x56\x7e\xfa\xca\x89\x78\x3f\x74\x67\x70\x39\x50\x1f\x40\x9a\x72\x36\xf7\xef\x89\x6f\xd1\x35\xda\x7b\xa2\x98\x61\xfa\xf7\x12\x31\xe6\xc3\x72\xb6\xc3\x33\xa7\xc4\x67\xee\x45\x8c\xe3\x29\x5b\x5d\x7b\x1f\xa4\xc6\x8b\x79\xf4\x4d\x6c\x2b\xc8\x62\xfc\x4c\x46\x4a\xd9\xf2\x03\x6e\x5d\x61\x71\xdc\x4a\x27\x9b\x2b\x5c\x5f\x78\xd4\x58\x97\xa3\x94\x4b\x6c\x5a\xce\xd6\x80\xe6\xbc\xe7\x18\x61\xf8\x49\x8c\x1d\x3a\x53\x7c\x78\x54\xeb\xf4\xbb\x24\x0b\xfb\x27\x8a\x4f\x81\x6d\xb0\xee\xe6\x84\x4e\x1f\x34\xba\xb9\xdb\x9d\x3d\xa2\xf9\x3d\x16\x96\x40\x77\x6b\x6b\x0e\xb0\xb8\xda\x08\x5d\x6a\x75\x0b\x94\x42\xfe\xcb\x48\xf7\x46\x89\x43\x37\x9b\x3f\x66\xc4\x61\xe8\x9c\x81\x29\xdb\xfc\x42\xd1\x67\x82\x26\xde\xe5\xbf\xf2\x79\x4b\x5e\x07\xdf\x90\xd2\x9f\x00\x00\x00\xff\xff\xbf\x91\xbb\x3b\xf2\x03\x00\x00")
|
||||
|
||||
func templatesAuth_method_mappingGoTmplBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_templatesAuth_method_mappingGoTmpl,
|
||||
"templates/auth_method_mapping.go.tmpl",
|
||||
)
|
||||
}
|
||||
|
||||
func templatesAuth_method_mappingGoTmpl() (*asset, error) {
|
||||
bytes, err := templatesAuth_method_mappingGoTmplBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "templates/auth_method_mapping.go.tmpl", size: 1010, mode: os.FileMode(420), modTime: time.Unix(1584960713, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
for name := range _bindata {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"templates/auth_method_mapping.go.tmpl": templatesAuth_method_mappingGoTmpl,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
// data/
|
||||
// foo.txt
|
||||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"templates": &bintree{nil, map[string]*bintree{
|
||||
"auth_method_mapping.go.tmpl": &bintree{templatesAuth_method_mappingGoTmpl, map[string]*bintree{}},
|
||||
}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
// Code generated by protoc-gen-authmethod. DO NOT EDIT.
|
||||
|
||||
package {{.File.GoPkg.Name}}
|
||||
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
utils_auth "github.com/caos/zitadel/internal/auth"
|
||||
utils_grpc "github.com/caos/zitadel/internal/grpc"
|
||||
)
|
||||
|
||||
{{ range $s := .File.Services }}
|
||||
|
||||
/**
|
||||
* {{$s.Name}}
|
||||
*/
|
||||
|
||||
var {{$s.Name}}_AuthMethods = utils_auth.AuthMethodMapping {
|
||||
{{ range $m := $s.Method}}
|
||||
{{ $mAuthOpt := option $m.Options "caos.zitadel.utils.v1.auth_option" }}
|
||||
{{ if and $mAuthOpt $mAuthOpt.Permission }}
|
||||
"/{{$.File.Package}}.{{$s.Name}}/{{.Name}}": utils_auth.AuthOption{
|
||||
Permission: "{{$mAuthOpt.Permission}}",
|
||||
CheckParam: "{{$mAuthOpt.CheckFieldName}}",
|
||||
},
|
||||
{{end}}
|
||||
{{ end}}
|
||||
}
|
||||
|
||||
func {{$s.Name}}_Authorization_Interceptor(verifier utils_auth.TokenVerifier, authConf *utils_auth.AuthConfig) grpc.UnaryServerInterceptor {
|
||||
return utils_grpc.AuthorizationInterceptor(verifier, authConf, {{$s.Name}}_AuthMethods)
|
||||
}
|
||||
|
||||
{{ end }}
|
22
internal/tracing/caller.go
Normal file
22
internal/tracing/caller.go
Normal file
@ -0,0 +1,22 @@
|
||||
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("TRACE-rWjfC").Debug("no caller")
|
||||
return ""
|
||||
}
|
||||
caller := runtime.FuncForPC(fpcs[0] - 1)
|
||||
if caller == nil {
|
||||
logging.Log("TRACE-25POw").Debug("caller was nil")
|
||||
return ""
|
||||
}
|
||||
return caller.Name()
|
||||
}
|
59
internal/tracing/config/config.go
Normal file
59
internal/tracing/config/config.go
Normal file
@ -0,0 +1,59 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"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 errors.ThrowInternal(err, "TRACE-vmjS", "error parsing config")
|
||||
}
|
||||
|
||||
c.Type = rc.Type
|
||||
|
||||
var err error
|
||||
c.Config, err = newTracingConfig(c.Type, rc.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Config.NewTracer()
|
||||
}
|
||||
|
||||
func newTracingConfig(tracerType string, configData []byte) (tracing.Config, error) {
|
||||
t, ok := tracer[tracerType]
|
||||
if !ok {
|
||||
return nil, errors.ThrowInternalf(nil, "TRACE-HMEJ", "config type %s not supported", tracerType)
|
||||
}
|
||||
|
||||
tracingConfig := t()
|
||||
if len(configData) == 0 {
|
||||
return tracingConfig, nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(configData, tracingConfig); err != nil {
|
||||
return nil, errors.ThrowInternal(err, "TRACE-1tSS", "Could not read config: %v")
|
||||
}
|
||||
|
||||
return tracingConfig, nil
|
||||
}
|
3
internal/tracing/generate.go
Normal file
3
internal/tracing/generate.go
Normal file
@ -0,0 +1,3 @@
|
||||
package tracing
|
||||
|
||||
//go:generate mockgen -package mock -destination mock/tracing_mock.go github.com/caos/zitadel/internal/tracing Tracer
|
24
internal/tracing/google/config.go
Normal file
24
internal/tracing/google/config.go
Normal file
@ -0,0 +1,24 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"go.opencensus.io/trace"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/tracing"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ProjectID string
|
||||
MetricPrefix string
|
||||
Fraction float64
|
||||
}
|
||||
|
||||
func (c *Config) NewTracer() error {
|
||||
if !envIsSet() {
|
||||
return errors.ThrowInvalidArgument(nil, "GOOGL-sdh3a", "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()
|
||||
}
|
95
internal/tracing/google/googletracing.go
Normal file
95
internal/tracing/google/googletracing.go
Normal file
@ -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, ", ")})
|
||||
}
|
30
internal/tracing/http_handler.go
Normal file
30
internal/tracing/http_handler.go
Normal file
@ -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
|
||||
},
|
||||
}
|
||||
}
|
21
internal/tracing/log/config.go
Normal file
21
internal/tracing/log/config.go
Normal file
@ -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()
|
||||
}
|
74
internal/tracing/log/logTracing.go
Normal file
74
internal/tracing/log/logTracing.go
Normal file
@ -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
|
||||
}
|
155
internal/tracing/mock/tracing_mock.go
Normal file
155
internal/tracing/mock/tracing_mock.go
Normal file
@ -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))
|
||||
}
|
20
internal/tracing/mock/tracing_mock_impl.go
Normal file
20
internal/tracing/mock/tracing_mock_impl.go
Normal file
@ -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{})
|
||||
}
|
88
internal/tracing/span.go
Normal file
88
internal/tracing/span.go
Normal file
@ -0,0 +1,88 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/grpc"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
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 {
|
||||
code, msg, _ := grpc.Extract(err)
|
||||
return trace.Status{Code: int32(code), Message: msg}
|
||||
}
|
||||
|
||||
// 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, errors.ThrowInternal(nil, "TRACE-jlq3s", "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)
|
||||
}
|
74
internal/tracing/tracing.go
Normal file
74
internal/tracing/tracing.go
Normal file
@ -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())
|
||||
}
|
@ -3,12 +3,17 @@ package admin
|
||||
import (
|
||||
"context"
|
||||
|
||||
app "github.com/caos/zitadel/internal/admin"
|
||||
"github.com/caos/zitadel/internal/api/auth"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/pkg/admin/api"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
App app.Config
|
||||
API api.Config
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, config Config) error {
|
||||
func Start(ctx context.Context, config Config, authZ auth.Config) error {
|
||||
return errors.ThrowUnimplemented(nil, "ADMIN-n8vw5", "not implemented yet") //TODO: implement
|
||||
}
|
||||
|
7
pkg/admin/api/config.go
Normal file
7
pkg/admin/api/config.go
Normal file
@ -0,0 +1,7 @@
|
||||
package api
|
||||
|
||||
import "github.com/caos/zitadel/internal/api/grpc"
|
||||
|
||||
type Config struct {
|
||||
GRPC grpc.Config
|
||||
}
|
7
pkg/auth/api/config.go
Normal file
7
pkg/auth/api/config.go
Normal file
@ -0,0 +1,7 @@
|
||||
package api
|
||||
|
||||
import "github.com/caos/zitadel/internal/api/grpc"
|
||||
|
||||
type Config struct {
|
||||
GRPC grpc.Config
|
||||
}
|
@ -3,12 +3,17 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/auth"
|
||||
app "github.com/caos/zitadel/internal/auth"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/pkg/auth/api"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
App app.Config
|
||||
API api.Config
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, config Config) error {
|
||||
func Start(ctx context.Context, config Config, authZ auth.Config) error {
|
||||
return errors.ThrowUnimplemented(nil, "AUTH-l7Hdx", "not implemented yet") //TODO: implement
|
||||
}
|
||||
|
16
pkg/console/console.go
Normal file
16
pkg/console/console.go
Normal file
@ -0,0 +1,16 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port string
|
||||
StaticDir string
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, config Config) error {
|
||||
return errors.ThrowUnimplemented(nil, "CONSO-4cT5D", "not implemented yet") //TODO: implement
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user