diff --git a/.github/workflows/radix-vulnerability-scanner-api-pr.yml b/.github/workflows/radix-vulnerability-scanner-api-pr.yml index 4acd2ff..ffc9498 100644 --- a/.github/workflows/radix-vulnerability-scanner-api-pr.yml +++ b/.github/workflows/radix-vulnerability-scanner-api-pr.yml @@ -22,16 +22,13 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 2 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' - - name: Install dependencies - run: go mod download - - name: Install GolangCI Lint - run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2 - - name: golangci-lint - run: golangci-lint run --timeout=30m --max-same-issues=0 --out-format=github-actions + uses: golangci/golangci-lint-action@v4 + with: + version: v1.55.2 test: name: Unit Test diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..54504ea --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,16 @@ +run: + timeout: 30m + +linters: + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + - zerologlint + +linters-settings: + issues: + max-same-issues: 0 diff --git a/Makefile b/Makefile index 124be14..e335060 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ mocks: bootstrap mockgen -source ./repository/repository.go -destination ./repository/mock/repository.go -package mock mockgen -source ./service/radixapi.go -destination ./service/mock/radixapi.go -package mock mockgen -source ./radix_api/generated_client/client/environment/environment_client.go -destination ./radix_api/mock_client/client/environment/environment_client.go -package environmentmock - mockgen -source ./router/authorization.go -destination ./router/mock/authorization.go -package mock + mockgen -source ./utils/auth/auth_provider.go -destination ./utils/auth/mock/auth_provider.go -package mock HAS_SWAGGER := $(shell command -v swagger;) HAS_GOLANGCI_LINT := $(shell command -v golangci-lint;) diff --git a/api/errors/errors.go b/api/errors/errors.go index 10773be..1dcdc7e 100644 --- a/api/errors/errors.go +++ b/api/errors/errors.go @@ -5,7 +5,7 @@ import ( "net/http" "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" + "github.com/rs/zerolog" ) var ( @@ -52,9 +52,8 @@ func WrapError(err error, apiError *apiError) error { func HandleErrorJSON(c *gin.Context, err error) { var apiErr APIError - logrus.Errorf("error: %v", err) - if !errors.As(err, &apiErr) { + zerolog.Ctx(c.Request.Context()).Error().Err(err).Msg(err.Error()) apiErr = ErrInternalServerError } diff --git a/api/vulnerability/controller_test.go b/api/vulnerability/controller_test.go index 896643d..fbaca9f 100644 --- a/api/vulnerability/controller_test.go +++ b/api/vulnerability/controller_test.go @@ -13,7 +13,7 @@ import ( handlerMock "github.com/equinor/radix-vulnerability-scanner-api/api/vulnerability/mock" "github.com/equinor/radix-vulnerability-scanner-api/models" "github.com/equinor/radix-vulnerability-scanner-api/router" - routerMock "github.com/equinor/radix-vulnerability-scanner-api/router/mock" + authprovidermock "github.com/equinor/radix-vulnerability-scanner-api/utils/auth/mock" "github.com/golang/mock/gomock" "github.com/stretchr/testify/suite" ) @@ -24,27 +24,29 @@ func Test_ControllerTestSuite(t *testing.T) { type controllerTestSuite struct { suite.Suite - handler *handlerMock.MockHandler - tokenValidator *routerMock.MockTokenValidator + handler *handlerMock.MockHandler + authProvider *authprovidermock.MockAuthProvider + idToken *authprovidermock.MockIDToken } func (s *controllerTestSuite) SetupTest() { ctrl := gomock.NewController(s.T()) s.handler = handlerMock.NewMockHandler(ctrl) - s.tokenValidator = routerMock.NewMockTokenValidator(ctrl) + s.authProvider = authprovidermock.NewMockAuthProvider(ctrl) + s.idToken = authprovidermock.NewMockIDToken(ctrl) } func (s *controllerTestSuite) Test_GetApplicationVulnerabilitySummaries() { rootPath, appName, token := "/api/any", "anyapp", "anytoken" - user := &models.User{RawToken: token} + user := &models.User{RawToken: token, Identity: s.idToken} w := httptest.NewRecorder() expected := []*apiModels.EnvironmentVulnerabilities{} s.handler.EXPECT().GetApplicationVulnerabilitySummaries(gomock.Any(), user, appName).Return(expected, nil) - s.tokenValidator.EXPECT().ValidateToken(gomock.Any()).Return(user, nil) + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(s.idToken, nil) sut := NewController(s.handler) - router := router.NewServer("anycluster", rootPath, s.tokenValidator, sut) + router := router.NewServer("anycluster", rootPath, s.authProvider, sut) req, _ := http.NewRequest("GET", path.Join(rootPath, fmt.Sprintf("applications/vulnerabilities/%s", appName)), nil) req.Header["Authorization"] = []string{"Bearer " + token} router.ServeHTTP(w, req) @@ -57,14 +59,14 @@ func (s *controllerTestSuite) Test_GetApplicationVulnerabilitySummaries() { func (s *controllerTestSuite) Test_GetApplicationVulnerabilitySummaries_ApiError_NotFound() { rootPath, appName := "/api/any", "anyapp" - user := &models.User{} + user := &models.User{RawToken: "anytoken", Identity: s.idToken} w := httptest.NewRecorder() s.handler.EXPECT().GetApplicationVulnerabilitySummaries(gomock.Any(), user, appName).Return(nil, apiErrors.ErrNotFound) - s.tokenValidator.EXPECT().ValidateToken(gomock.Any()).Return(user, nil) + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(s.idToken, nil) sut := NewController(s.handler) - router := router.NewServer("anycluster", rootPath, s.tokenValidator, sut) + router := router.NewServer("anycluster", rootPath, s.authProvider, sut) req, _ := http.NewRequest("GET", path.Join(rootPath, fmt.Sprintf("applications/vulnerabilities/%s", appName)), nil) req.Header["Authorization"] = []string{"Bearer anytoken"} router.ServeHTTP(w, req) @@ -73,15 +75,15 @@ func (s *controllerTestSuite) Test_GetApplicationVulnerabilitySummaries_ApiError func (s *controllerTestSuite) Test_GetEnvironmentVulnerabilitySummary() { rootPath, appName, envName, token := "/api/any", "anyapp", "anyenv", "anytoken" - user := &models.User{RawToken: token} + user := &models.User{RawToken: token, Identity: s.idToken} w := httptest.NewRecorder() expected := apiModels.EnvironmentVulnerabilities{Name: "name"} s.handler.EXPECT().GetEnvironmentVulnerabilitySummary(gomock.Any(), user, appName, envName).Return(&expected, nil) - s.tokenValidator.EXPECT().ValidateToken(gomock.Any()).Return(user, nil) + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(s.idToken, nil) sut := NewController(s.handler) - router := router.NewServer("anycluster", rootPath, s.tokenValidator, sut) + router := router.NewServer("anycluster", rootPath, s.authProvider, sut) req, _ := http.NewRequest("GET", path.Join(rootPath, fmt.Sprintf("applications/%s/environments/%s", appName, envName)), nil) req.Header["Authorization"] = []string{"Bearer " + token} router.ServeHTTP(w, req) @@ -93,14 +95,13 @@ func (s *controllerTestSuite) Test_GetEnvironmentVulnerabilitySummary() { func (s *controllerTestSuite) Test_GetEnvironmentVulnerabilitySummary_ApiError_NotFound() { rootPath, appName, envName := "/api/any", "anyapp", "anyenv" - user := &models.User{} w := httptest.NewRecorder() - s.handler.EXPECT().GetEnvironmentVulnerabilitySummary(gomock.Any(), user, appName, envName).Return(nil, apiErrors.ErrNotFound) - s.tokenValidator.EXPECT().ValidateToken(gomock.Any()).Return(user, nil) + s.handler.EXPECT().GetEnvironmentVulnerabilitySummary(gomock.Any(), gomock.Any(), appName, envName).Return(nil, apiErrors.ErrNotFound) + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(s.idToken, nil) sut := NewController(s.handler) - router := router.NewServer("anycluster", rootPath, s.tokenValidator, sut) + router := router.NewServer("anycluster", rootPath, s.authProvider, sut) req, _ := http.NewRequest("GET", path.Join(rootPath, fmt.Sprintf("applications/%s/environments/%s", appName, envName)), nil) req.Header["Authorization"] = []string{"Bearer anytoken"} router.ServeHTTP(w, req) @@ -109,15 +110,15 @@ func (s *controllerTestSuite) Test_GetEnvironmentVulnerabilitySummary_ApiError_N func (s *controllerTestSuite) Test_GetComponentVulnerabilities() { rootPath, appName, envName, compName, token := "/api/any", "anyapp", "anyenv", "anyComp", "anytoken" - user := &models.User{RawToken: token} + user := &models.User{RawToken: token, Identity: s.idToken} w := httptest.NewRecorder() expected := apiModels.ImageWithLastScan{Image: apiModels.Image{ImageName: "anyimage"}} s.handler.EXPECT().GetComponentVulnerabilities(gomock.Any(), user, appName, envName, compName).Return(&expected, nil) - s.tokenValidator.EXPECT().ValidateToken(gomock.Any()).Return(user, nil) + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(s.idToken, nil) sut := NewController(s.handler) - router := router.NewServer("anycluster", rootPath, s.tokenValidator, sut) + router := router.NewServer("anycluster", rootPath, s.authProvider, sut) req, _ := http.NewRequest("GET", path.Join(rootPath, fmt.Sprintf("applications/%s/environments/%s/components/%s", appName, envName, compName)), nil) req.Header["Authorization"] = []string{"Bearer " + token} router.ServeHTTP(w, req) @@ -129,14 +130,13 @@ func (s *controllerTestSuite) Test_GetComponentVulnerabilities() { func (s *controllerTestSuite) Test_GetComponentVulnerabilities_ApiError_NotFound() { rootPath, appName, envName, compName := "/api/any", "anyapp", "anyenv", "anycomp" - user := &models.User{} w := httptest.NewRecorder() s.handler.EXPECT().GetComponentVulnerabilities(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, apiErrors.ErrNotFound) - s.tokenValidator.EXPECT().ValidateToken(gomock.Any()).Return(user, nil) + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(s.idToken, nil) sut := NewController(s.handler) - router := router.NewServer("anycluster", rootPath, s.tokenValidator, sut) + router := router.NewServer("anycluster", rootPath, s.authProvider, sut) req, _ := http.NewRequest("GET", path.Join(rootPath, fmt.Sprintf("applications/%s/environments/%s/components/%s", appName, envName, compName)), nil) req.Header["Authorization"] = []string{"Bearer anytoken"} router.ServeHTTP(w, req) @@ -145,15 +145,15 @@ func (s *controllerTestSuite) Test_GetComponentVulnerabilities_ApiError_NotFound func (s *controllerTestSuite) Test_GetJobVulnerabilities() { rootPath, appName, envName, compName, token := "/api/any", "anyapp", "anyenv", "anyComp", "anytoken" - user := &models.User{RawToken: token} + user := &models.User{RawToken: token, Identity: s.idToken} w := httptest.NewRecorder() expected := apiModels.ImageWithLastScan{Image: apiModels.Image{ImageName: "anyimage"}} s.handler.EXPECT().GetJobVulnerabilities(gomock.Any(), user, appName, envName, compName).Return(&expected, nil) - s.tokenValidator.EXPECT().ValidateToken(gomock.Any()).Return(user, nil) + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(s.idToken, nil) sut := NewController(s.handler) - router := router.NewServer("anycluster", rootPath, s.tokenValidator, sut) + router := router.NewServer("anycluster", rootPath, s.authProvider, sut) req, _ := http.NewRequest("GET", path.Join(rootPath, fmt.Sprintf("applications/%s/environments/%s/jobs/%s", appName, envName, compName)), nil) req.Header["Authorization"] = []string{"Bearer " + token} router.ServeHTTP(w, req) @@ -165,14 +165,13 @@ func (s *controllerTestSuite) Test_GetJobVulnerabilities() { func (s *controllerTestSuite) Test_GetJobVulnerabilities_ApiError_NotFound() { rootPath, appName, envName, compName := "/api/any", "anyapp", "anyenv", "anycomp" - user := &models.User{} w := httptest.NewRecorder() s.handler.EXPECT().GetJobVulnerabilities(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, apiErrors.ErrNotFound) - s.tokenValidator.EXPECT().ValidateToken(gomock.Any()).Return(user, nil) + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(s.idToken, nil) sut := NewController(s.handler) - router := router.NewServer("anycluster", rootPath, s.tokenValidator, sut) + router := router.NewServer("anycluster", rootPath, s.authProvider, sut) req, _ := http.NewRequest("GET", path.Join(rootPath, fmt.Sprintf("applications/%s/environments/%s/jobs/%s", appName, envName, compName)), nil) req.Header["Authorization"] = []string{"Bearer anytoken"} router.ServeHTTP(w, req) diff --git a/go.mod b/go.mod index 98407ea..e1ecf05 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.21 toolchain go1.21.0 require ( + github.com/coreos/go-oidc/v3 v3.9.0 + github.com/equinor/radix-common v1.8.1 github.com/gin-contrib/cors v1.5.0 github.com/gin-gonic/gin v1.9.1 github.com/go-openapi/errors v0.21.0 @@ -12,11 +14,11 @@ require ( github.com/go-openapi/strfmt v0.22.0 github.com/go-openapi/swag v0.22.7 github.com/go-openapi/validate v0.22.6 - github.com/go-playground/validator/v10 v10.16.0 github.com/go-swagger/go-swagger v0.30.5 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/mock v1.6.0 - github.com/sirupsen/logrus v1.9.3 + github.com/rs/xid v1.5.0 + github.com/rs/zerolog v1.32.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 gorm.io/driver/sqlserver v1.5.1 @@ -26,8 +28,25 @@ require ( require ( github.com/chenzhuoyu/iasm v0.9.0 // indirect + github.com/elnormous/contenttype v1.0.4 // indirect + github.com/go-jose/go-jose/v3 v3.0.1 // indirect + github.com/go-playground/validator/v10 v10.16.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.5.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/pkg/errors v0.9.1 // indirect go.opentelemetry.io/otel/metric v1.17.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/apimachinery v0.29.0 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) require ( diff --git a/go.sum b/go.sum index fc9cc89..ed916f9 100644 --- a/go.sum +++ b/go.sum @@ -14,11 +14,18 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= +github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/elnormous/contenttype v1.0.4 h1:FjmVNkvQOGqSX70yvocph7keC8DtmJaLzTTq6ZOQCI8= +github.com/elnormous/contenttype v1.0.4/go.mod h1:5KTOW8m1kdX1dLMiUJeN9szzR2xkngiv2K+RVZwWBbI= +github.com/equinor/radix-common v1.8.1 h1:HEFWAKOB0/kDjaqEUiLsGOUX/FKTkZsCtjrLM2hhCVM= +github.com/equinor/radix-common v1.8.1/go.mod h1:kFCVR6PxbJGD11NHNiXq5PJNO+aUThQfTiJF10LCfrE= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= @@ -27,6 +34,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= +github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -64,6 +73,9 @@ github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGA github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -74,12 +86,19 @@ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EO github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -101,6 +120,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= @@ -115,6 +136,9 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= @@ -139,12 +163,16 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -153,6 +181,7 @@ github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -168,6 +197,8 @@ github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= @@ -184,7 +215,9 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= @@ -192,12 +225,16 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -208,13 +245,18 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -222,11 +264,12 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -246,6 +289,8 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= @@ -253,17 +298,23 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -279,5 +330,17 @@ gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o= gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/main.go b/main.go index 2b22bfb..4a91697 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,10 @@ package main import ( "context" + "errors" "fmt" "net/http" + _ "net/http/pprof" "os" "os/signal" "syscall" @@ -11,13 +13,12 @@ import ( "github.com/equinor/radix-vulnerability-scanner-api/repository" "github.com/equinor/radix-vulnerability-scanner-api/service" - - _ "net/http/pprof" + "github.com/equinor/radix-vulnerability-scanner-api/utils/auth" "github.com/equinor/radix-vulnerability-scanner-api/api/vulnerability" - models "github.com/equinor/radix-vulnerability-scanner-api/models" + "github.com/equinor/radix-vulnerability-scanner-api/models" "github.com/equinor/radix-vulnerability-scanner-api/router" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "github.com/spf13/pflag" ) @@ -27,22 +28,28 @@ func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() - env := models.NewEnv() + env, ctx, err := models.NewEnv(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "Error:\n%s\n\n", err.Error()) + os.Exit(3) + } + fs := initializeFlagSet() port := fs.StringP("port", "p", defaultPort(), "Port where API will be served") - log.Debugf("Port: %s\n", *port) - log.Debugf("Cluster: %s\n", env.ClusterName) + log.Debug().Msgf("Port: %s", *port) + log.Debug().Msgf("Cluster: %s", env.ClusterName) parseFlagsFromArgs(fs) radixAPIClient := service.NewRadixAPIService(env) + authProvider := auth.NewAuthProvider(ctx, env.OidcIssuer, env.OidcAudience) repo := getRepository(env) router := router.NewServer( env.ClusterName, apiV1Path, - router.NewTokenValidator(), + authProvider, vulnerability.NewController(vulnerability.NewHandler(radixAPIClient, repo)), ) @@ -52,17 +59,17 @@ func main() { } go func() { - log.Infof("API is serving on port %s", *port) - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) + log.Info().Msgf("API is serving on port %s", *port) + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Fatal().Msgf("listen: %s", err) } }() if env.UseProfiler { go func() { - log.Infof("Profiler endpoint is serving on port 7070") - if err := http.ListenAndServe("localhost:7070", nil); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) + log.Info().Msgf("Profiler endpoint is serving on port 7070") + if err := http.ListenAndServe("localhost:7070", nil); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Fatal().Msgf("listen: %s", err) } }() } @@ -72,17 +79,17 @@ func main() { // Restore default behavior on the interrupt signal and notify user of shutdown. stop() - log.Println("shutting down gracefully, press Ctrl+C again to force") + log.Info().Msg("shutting down gracefully, press Ctrl+C again to force") // The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server forced to shutdown: ", err) + log.Fatal().Err(err).Msg("Server forced to shutdown") } - log.Println("Server exiting") + log.Info().Msg("Server exiting") } func getRepository(env *models.Env) repository.Interface { @@ -90,7 +97,7 @@ func getRepository(env *models.Env) repository.Interface { repository.GetSqlServerDsn(env.DbCredentials.Server, env.DbCredentials.Database, env.DbCredentials.UserID, env.DbCredentials.Password), ) if err != nil { - log.Fatal(err) + log.Fatal().Msg(err.Error()) } return repository.NewGormRepository(gormdb) @@ -113,10 +120,10 @@ func initializeFlagSet() *pflag.FlagSet { func parseFlagsFromArgs(fs *pflag.FlagSet) { err := fs.Parse(os.Args[1:]) switch { - case err == pflag.ErrHelp: + case errors.Is(err, pflag.ErrHelp): os.Exit(0) case err != nil: - fmt.Fprintf(os.Stderr, "Error: %s\n\n", err.Error()) + _, _ = fmt.Fprintf(os.Stderr, "Error: %s\n\n", err.Error()) fs.Usage() os.Exit(2) } diff --git a/models/env.go b/models/env.go index d63c896..20383e5 100644 --- a/models/env.go +++ b/models/env.go @@ -1,11 +1,15 @@ package models import ( + "context" + "errors" "fmt" "os" "strings" + "time" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) // DBCredentials hold credentials for database @@ -26,36 +30,49 @@ type Env struct { UseProfiler bool DbCredentials *DBCredentials Cluster string + OidcAudience string + OidcIssuer string } // NewEnv Constructor -func NewEnv() *Env { - switch os.Getenv("LOG_LEVEL") { - case "DEBUG": - log.SetLevel(log.DebugLevel) - default: - log.SetLevel(log.InfoLevel) - } +func NewEnv(ctx context.Context) (*Env, context.Context, error) { var ( + errs []error context = os.Getenv("RADIX_CLUSTER_TYPE") apiEnv = os.Getenv("RADIX_ENVIRONMENT") clusterName = os.Getenv("RADIX_CLUSTERNAME") dnsZone = os.Getenv("RADIX_DNS_ZONE") cluster = os.Getenv("RADIX_CLUSTER_NAME") + issuer = os.Getenv("TOKEN_ISSUER") + audience = os.Getenv("TOKEN_AUDIENCE") useLocalRadixApi = envVarIsTrueOrYes(os.Getenv("USE_LOCAL_RADIX_API")) useProfiler = envVarIsTrueOrYes(os.Getenv("USE_PROFILER")) + prettyPrint = envVarIsTrueOrYes(os.Getenv("PRETTY_PRINT")) + logLevel = os.Getenv("LOG_LEVEL") ) + + ctx, err := initZerologger(ctx, logLevel, prettyPrint) + if err != nil { + errs = append(errs, err) + } + if context == "" { - log.Error("'Context' environment variable is not set") + errs = append(errs, fmt.Errorf("environment variable RADIX_CLUSTER_TYPE is not set")) } if apiEnv == "" { - log.Error("'API-Environment' environment variable is not set") + errs = append(errs, fmt.Errorf("environment variable RADIX_ENVIRONMENT is not set")) } if clusterName == "" { - log.Error("'Cluster' environment variables is not set") + errs = append(errs, fmt.Errorf("environment variable RADIX_CLUSTERNAME is not set")) } if dnsZone == "" { - log.Error("'DNS Zone' environment variables is not set") + errs = append(errs, fmt.Errorf("environment variable RADIX_DNS_ZONE is not set")) + } + if issuer == "" { + errs = append(errs, fmt.Errorf("environment variable TOKEN_ISSUER is not set")) + } + if audience == "" { + errs = append(errs, fmt.Errorf("environment variable TOKEN_AUDIENCE is not set")) } return &Env{ @@ -66,8 +83,28 @@ func NewEnv() *Env { Cluster: cluster, UseLocalRadixApi: useLocalRadixApi, UseProfiler: useProfiler, + OidcAudience: audience, + OidcIssuer: issuer, DbCredentials: getDBCredentials(), + }, ctx, errors.Join(errs...) +} + +func initZerologger(ctx context.Context, logLevel string, prettyPrint bool) (context.Context, error) { + if logLevel == "" { + logLevel = "info" + } + + zerologLevel, err := zerolog.ParseLevel(logLevel) + if err != nil { + return nil, err + } + zerolog.SetGlobalLevel(zerologLevel) + zerolog.DurationFieldUnit = time.Millisecond + if prettyPrint { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.TimeOnly}) } + ctx = log.Logger.WithContext(ctx) + return ctx, nil } func envVarIsTrueOrYes(envVar string) bool { diff --git a/models/user.go b/models/user.go index c5fcb52..cbdd06b 100644 --- a/models/user.go +++ b/models/user.go @@ -1,12 +1,15 @@ package models -import "github.com/golang-jwt/jwt/v4" +import ( + "github.com/equinor/radix-vulnerability-scanner-api/utils/auth" + "github.com/golang-jwt/jwt/v4" +) type Identity struct { jwt.RegisteredClaims } type User struct { - Identity Identity + Identity auth.IDToken RawToken string } diff --git a/radixconfig.yaml b/radixconfig.yaml index e177b02..5891a87 100644 --- a/radixconfig.yaml +++ b/radixconfig.yaml @@ -27,6 +27,9 @@ spec: memory: "1000Mi" cpu: "1000m" public: true + variables: + TOKEN_AUDIENCE: "6dae42f8-4368-4678-94ff-3960e28e3630" + TOKEN_ISSUER: https://sts.windows.net/3aa4a235-b6e2-48d5-9195-7fcf05b459b0/ secrets: - SQL_SERVER - SQL_DATABASE diff --git a/repository/gorm_logger.go b/repository/gorm_logger.go new file mode 100644 index 0000000..e78a8d1 --- /dev/null +++ b/repository/gorm_logger.go @@ -0,0 +1,50 @@ +package repository + +import ( + "context" + "time" + + "github.com/rs/zerolog" + "gorm.io/gorm/logger" +) + +// Copied from https://github.com/mpalmer/gorm-zerolog/blob/master/logger.go @ 27.02.2024 + +type Logger struct { +} + +func NewLogger() Logger { + return Logger{} +} + +func (l Logger) LogMode(logger.LogLevel) logger.Interface { + return l +} + +func (l Logger) Error(ctx context.Context, msg string, opts ...interface{}) { + zerolog.Ctx(ctx).Error().Str("pkg", "gorm").Msgf(msg, opts...) +} + +func (l Logger) Warn(ctx context.Context, msg string, opts ...interface{}) { + zerolog.Ctx(ctx).Warn().Str("pkg", "gorm").Msgf(msg, opts...) +} + +func (l Logger) Info(ctx context.Context, msg string, opts ...interface{}) { + zerolog.Ctx(ctx).Info().Str("pkg", "gorm").Msgf(msg, opts...) +} + +func (l Logger) Trace(ctx context.Context, begin time.Time, f func() (string, int64), err error) { + var level = zerolog.TraceLevel + + if err != nil { + level = zerolog.DebugLevel + } + + sql, rows := f() + zerolog.Ctx(ctx).WithLevel(level). + Dur("elapsed-ms", time.Since(begin)). + Int64("rows", rows). + Str("sql", sql). + Str("pkg", "gorm"). + Send() +} diff --git a/repository/gorm_repository.go b/repository/gorm_repository.go index b171abc..fcaca55 100644 --- a/repository/gorm_repository.go +++ b/repository/gorm_repository.go @@ -26,6 +26,7 @@ func OpenGormSqlServerDB(dsn string) (*gorm.DB, error) { return gorm.Open(sqlserver.Open(dsn), &gorm.Config{ NamingStrategy: schema.NamingStrategy{NoLowerCase: true}, DisableAutomaticPing: false, + Logger: NewLogger(), }) } diff --git a/router/authentication_middleware.go b/router/authentication_middleware.go new file mode 100644 index 0000000..80bb111 --- /dev/null +++ b/router/authentication_middleware.go @@ -0,0 +1,38 @@ +package router + +import ( + radixhttp "github.com/equinor/radix-common/net/http" + apiErrors "github.com/equinor/radix-vulnerability-scanner-api/api/errors" + "github.com/equinor/radix-vulnerability-scanner-api/models" + "github.com/equinor/radix-vulnerability-scanner-api/utils/auth" + "github.com/gin-gonic/gin" + "github.com/rs/zerolog" +) + +func NewAuthenticationMiddleware(authProvider auth.AuthProvider) gin.HandlerFunc { + return func(c *gin.Context) { + ctx := c.Request.Context() + + token, err := radixhttp.GetBearerTokenFromHeader(c.Request) + + if err != nil { + zerolog.Ctx(ctx).Debug().Err(err).Msg("Could not get token from header") + apiErrors.HandleErrorJSON(c, apiErrors.WrapError(err, apiErrors.ErrUnauthorized)) + return + } + + verified, err := authProvider.VerifyToken(ctx, token) + + if err != nil || verified == nil { + zerolog.Ctx(ctx).Debug().Err(err).Msg("Could not verify token") + apiErrors.HandleErrorJSON(c, apiErrors.ErrUnauthorized) + return + } + + zerolog.Ctx(ctx).Debug().Msg("Authenticated") + c.Set("user", &models.User{ + RawToken: token, + Identity: verified, + }) + } +} diff --git a/router/authorization.go b/router/authorization.go deleted file mode 100644 index a91c09b..0000000 --- a/router/authorization.go +++ /dev/null @@ -1,72 +0,0 @@ -package router - -import ( - "strings" - - apiErrors "github.com/equinor/radix-vulnerability-scanner-api/api/errors" - "github.com/equinor/radix-vulnerability-scanner-api/models" - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/golang-jwt/jwt/v4" -) - -type authHeader struct { - Authorization string `header:"Authorization" binding:"required"` -} - -type TokenValidator interface { - ValidateToken(string) (*models.User, error) -} - -type tokenValidator struct{} - -func NewTokenValidator() TokenValidator { - return &tokenValidator{} -} - -func (*tokenValidator) ValidateToken(token string) (*models.User, error) { - p := jwt.Parser{} - identity := models.Identity{} - _, _, err := p.ParseUnverified(token, &identity) - if err != nil { - return nil, err - } - - user := models.User{ - RawToken: token, - Identity: identity, - } - - return &user, nil -} - -func BearerAuth(tokenValidator TokenValidator) gin.HandlerFunc { - return func(c *gin.Context) { - h := authHeader{} - - if err := c.ShouldBindHeader(&h); err != nil { - if _, ok := err.(validator.ValidationErrors); ok { - apiErrors.HandleErrorJSON(c, apiErrors.WrapError(err, apiErrors.ErrUnauthorized)) - return - } - - apiErrors.HandleErrorJSON(c, apiErrors.WrapError(err, apiErrors.ErrInternalServerError)) - return - } - - bearerHeader := strings.Split(h.Authorization, "Bearer ") - if len(bearerHeader) < 2 || len(strings.TrimSpace(bearerHeader[1])) == 0 { - apiErrors.HandleErrorJSON(c, apiErrors.ErrUnauthorized) - return - } - token := strings.TrimSpace(bearerHeader[1]) - - user, err := tokenValidator.ValidateToken(token) - if err != nil { - apiErrors.HandleErrorJSON(c, apiErrors.WrapError(err, apiErrors.ErrUnauthorized)) - return - } - - c.Set("user", user) - } -} diff --git a/router/mock/authorization.go b/router/mock/authorization.go deleted file mode 100644 index a5e299e..0000000 --- a/router/mock/authorization.go +++ /dev/null @@ -1,50 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: ./router/authorization.go - -// Package mock is a generated GoMock package. -package mock - -import ( - reflect "reflect" - - models "github.com/equinor/radix-vulnerability-scanner-api/models" - gomock "github.com/golang/mock/gomock" -) - -// MockTokenValidator is a mock of TokenValidator interface. -type MockTokenValidator struct { - ctrl *gomock.Controller - recorder *MockTokenValidatorMockRecorder -} - -// MockTokenValidatorMockRecorder is the mock recorder for MockTokenValidator. -type MockTokenValidatorMockRecorder struct { - mock *MockTokenValidator -} - -// NewMockTokenValidator creates a new mock instance. -func NewMockTokenValidator(ctrl *gomock.Controller) *MockTokenValidator { - mock := &MockTokenValidator{ctrl: ctrl} - mock.recorder = &MockTokenValidatorMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTokenValidator) EXPECT() *MockTokenValidatorMockRecorder { - return m.recorder -} - -// ValidateToken mocks base method. -func (m *MockTokenValidator) ValidateToken(arg0 string) (*models.User, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ValidateToken", arg0) - ret0, _ := ret[0].(*models.User) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ValidateToken indicates an expected call of ValidateToken. -func (mr *MockTokenValidatorMockRecorder) ValidateToken(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateToken", reflect.TypeOf((*MockTokenValidator)(nil).ValidateToken), arg0) -} diff --git a/router/server.go b/router/server.go index 4912842..3347b05 100644 --- a/router/server.go +++ b/router/server.go @@ -8,10 +8,11 @@ import ( "github.com/equinor/radix-vulnerability-scanner-api/api" "github.com/equinor/radix-vulnerability-scanner-api/swaggerui" - "github.com/gin-gonic/gin" - _ "github.com/equinor/radix-vulnerability-scanner-api/swaggerui" // statik files + "github.com/equinor/radix-vulnerability-scanner-api/utils/auth" "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" ) const ( @@ -20,18 +21,19 @@ const ( ) // NewServer Constructor function -func NewServer(clusterName, apiRootPath string, tokenValidator TokenValidator, controllers ...api.Controller) http.Handler { - rootRouter := gin.Default() - +func NewServer(clusterName, apiRootPath string, authProvider auth.AuthProvider, controllers ...api.Controller) http.Handler { + logger := log.Logger.With().Str("pkg", "gin").Logger() + rootRouter := gin.New() rootRouter.RemoveExtraSlash = true - + rootRouter.Use(gin.Recovery()) + rootRouter.Use(NewZerologHandler(logger)) rootRouter.Use(corsMiddleware(clusterName)) initializeSwaggerUI(rootRouter) v1Router := rootRouter.Group(apiRootPath) { - v1Router.Use(BearerAuth(tokenValidator)) + v1Router.Use(NewAuthenticationMiddleware(authProvider)) initializeAPIServer(v1Router, controllers) } diff --git a/router/server_test.go b/router/server_test.go index c4f2b4f..173d2e1 100644 --- a/router/server_test.go +++ b/router/server_test.go @@ -9,7 +9,7 @@ import ( "github.com/equinor/radix-vulnerability-scanner-api/api" "github.com/equinor/radix-vulnerability-scanner-api/models" - tokenMock "github.com/equinor/radix-vulnerability-scanner-api/router/mock" + authprovidermock "github.com/equinor/radix-vulnerability-scanner-api/utils/auth/mock" "github.com/gin-gonic/gin" "github.com/golang/mock/gomock" "github.com/stretchr/testify/mock" @@ -36,27 +36,29 @@ func (m *mockController) HandleRequest(c *gin.Context) { type routerTestSuite struct { suite.Suite - tokenValidator *tokenMock.MockTokenValidator + authProvider *authprovidermock.MockAuthProvider + idToken *authprovidermock.MockIDToken } func (s *routerTestSuite) SetupTest() { ctrl := gomock.NewController(s.T()) - s.tokenValidator = tokenMock.NewMockTokenValidator(ctrl) + s.authProvider = authprovidermock.NewMockAuthProvider(ctrl) + s.idToken = authprovidermock.NewMockIDToken(ctrl) } func (s *routerTestSuite) Test_Server() { rootPath, resourcePath := "any/root", "resource/subresource" - originalToken, newToken := "anytoken", "newtoken" - user := &models.User{RawToken: newToken} - s.tokenValidator.EXPECT().ValidateToken(originalToken).Return(user, nil) + token := "anytoken" + user := &models.User{RawToken: token, Identity: s.idToken} + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(s.idToken, nil) ctrl := &mockController{} ctrl.On("GetRoutes").Return([]api.Route{{Path: resourcePath, Method: "GET", Handler: ctrl.HandleRequest}}) ctrl.On("HandleRequest", user) - router := NewServer("anycluster", rootPath, s.tokenValidator, ctrl) + router := NewServer("anycluster", rootPath, s.authProvider, ctrl) req, _ := http.NewRequest("GET", path.Join(rootPath, resourcePath), nil) w := httptest.NewRecorder() - req.Header["Authorization"] = []string{"Bearer " + originalToken} + req.Header["Authorization"] = []string{"Bearer " + token} router.ServeHTTP(w, req) s.Equal(http.StatusOK, w.Code) ctrl.AssertExpectations(s.T()) @@ -66,7 +68,7 @@ func (s *routerTestSuite) Test_Server_IncorrectVerb() { rootPath, resourcePath := "root", "path" ctrl := &mockController{} ctrl.On("GetRoutes").Return([]api.Route{{Path: resourcePath, Method: "GET", Handler: ctrl.HandleRequest}}) - router := NewServer("anycluster", rootPath, s.tokenValidator, ctrl) + router := NewServer("anycluster", rootPath, s.authProvider, ctrl) // Correct verb finds route - returns unauthorized (401) because header is missing req, _ := http.NewRequest("GET", path.Join(rootPath, resourcePath), nil) @@ -87,7 +89,7 @@ func (s *routerTestSuite) Test_Server_MissingAuthorizationHeader() { rootPath, resourcePath := "root", "path" ctrl := &mockController{} ctrl.On("GetRoutes").Return([]api.Route{{Path: resourcePath, Method: "GET"}}) - router := NewServer("anycluster", rootPath, s.tokenValidator, ctrl) + router := NewServer("anycluster", rootPath, s.authProvider, ctrl) req, _ := http.NewRequest("GET", path.Join(rootPath, resourcePath), nil) w := httptest.NewRecorder() @@ -100,7 +102,7 @@ func (s *routerTestSuite) Test_Server_MissingBearerToken() { rootPath, resourcePath := "root", "path" ctrl := &mockController{} ctrl.On("GetRoutes").Return([]api.Route{{Path: resourcePath, Method: "GET"}}) - router := NewServer("anycluster", rootPath, s.tokenValidator, ctrl) + router := NewServer("anycluster", rootPath, s.authProvider, ctrl) req, _ := http.NewRequest("GET", path.Join(rootPath, resourcePath), nil) req.Header["Authorization"] = []string{"Bearer "} @@ -112,10 +114,10 @@ func (s *routerTestSuite) Test_Server_MissingBearerToken() { func (s *routerTestSuite) Test_Server_InvalidToken() { rootPath, resourcePath := "root", "path" - s.tokenValidator.EXPECT().ValidateToken(gomock.Any()).Return(nil, errors.New("any error")) + s.authProvider.EXPECT().VerifyToken(gomock.Any(), gomock.Any()).Return(nil, errors.New("any error")) ctrl := &mockController{} ctrl.On("GetRoutes").Return([]api.Route{{Path: resourcePath, Method: "GET"}}) - router := NewServer("anycluster", rootPath, s.tokenValidator, ctrl) + router := NewServer("anycluster", rootPath, s.authProvider, ctrl) req, _ := http.NewRequest("GET", path.Join(rootPath, resourcePath), nil) req.Header["Authorization"] = []string{"Bearer anytoken"} diff --git a/router/zerolog_middleware.go b/router/zerolog_middleware.go new file mode 100644 index 0000000..3c7c9a1 --- /dev/null +++ b/router/zerolog_middleware.go @@ -0,0 +1,33 @@ +package router + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" + "github.com/rs/xid" + "github.com/rs/zerolog" +) + +// NewZerologHandler injects and logs requests. +func NewZerologHandler(log zerolog.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + logger := log.With().Logger() + logger.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Str("trace-id", xid.New().String()) + }) + start := time.Now() + + c.Request = c.Request.WithContext(logger.WithContext(c.Request.Context())) + c.Next() + + logger.Info(). + Str("user-agent", c.GetHeader("User-Agent")). + Str("remote-addr", c.RemoteIP()). + Str("request", c.Request.Method+" "+c.Request.URL.Path). + Str("query", c.Request.URL.RawQuery). + Dur("elapsed-ms", time.Since(start)). + Int("status", c.Writer.Status()). + Msg(http.StatusText(c.Writer.Status())) + } +} diff --git a/utils/auth/auth_provider.go b/utils/auth/auth_provider.go new file mode 100644 index 0000000..c16dc7a --- /dev/null +++ b/utils/auth/auth_provider.go @@ -0,0 +1,78 @@ +package auth + +import ( + "context" + "log" + + "github.com/coreos/go-oidc/v3/oidc" +) + +// AuthProvider interface +type AuthProvider interface { + VerifyToken(ctx context.Context, token string) (IDToken, error) +} + +// IDToken interface +type IDToken interface { + GetClaims(out interface{}) error +} + +type oidcProvider struct { + verifier *oidc.IDTokenVerifier +} + +// IDTokenStruct instance variables +type IDTokenStruct struct { + token *oidc.IDToken +} + +type Claims struct { + Groups []string `json:"groups"` + Email string `json:"email"` +} + +// GetClaims returns claims for the token +func (it *IDTokenStruct) GetClaims(out interface{}) error { + err := it.token.Claims(out) + if err != nil { + return err + } + + return nil +} + +// VerifyToken verifies the provided IDToken +func (p *oidcProvider) VerifyToken(ctx context.Context, token string) (IDToken, error) { + idToken, err := p.verifier.Verify(ctx, token) + + if err != nil { + return nil, err + } + + return &IDTokenStruct{ + token: idToken, + }, nil +} + +// NewAuthProvider creates a new auth provider +func NewAuthProvider(ctx context.Context, issuer, audience string) AuthProvider { + verifier := getTokenVerifier(ctx, issuer, audience) + return &oidcProvider{ + verifier: verifier, + } +} + +func getTokenVerifier(ctx context.Context, issuer, audience string) *oidc.IDTokenVerifier { + + provider, err := oidc.NewProvider(ctx, issuer) + + if err != nil { + log.Fatal(err) + } + + oidcConfig := &oidc.Config{ + ClientID: audience, + } + + return provider.Verifier(oidcConfig) +} diff --git a/utils/auth/mock/auth_provider.go b/utils/auth/mock/auth_provider.go new file mode 100644 index 0000000..8bef292 --- /dev/null +++ b/utils/auth/mock/auth_provider.go @@ -0,0 +1,88 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./utils/auth/auth_provider.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + auth "github.com/equinor/radix-vulnerability-scanner-api/utils/auth" + gomock "github.com/golang/mock/gomock" +) + +// MockAuthProvider is a mock of AuthProvider interface. +type MockAuthProvider struct { + ctrl *gomock.Controller + recorder *MockAuthProviderMockRecorder +} + +// MockAuthProviderMockRecorder is the mock recorder for MockAuthProvider. +type MockAuthProviderMockRecorder struct { + mock *MockAuthProvider +} + +// NewMockAuthProvider creates a new mock instance. +func NewMockAuthProvider(ctrl *gomock.Controller) *MockAuthProvider { + mock := &MockAuthProvider{ctrl: ctrl} + mock.recorder = &MockAuthProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAuthProvider) EXPECT() *MockAuthProviderMockRecorder { + return m.recorder +} + +// VerifyToken mocks base method. +func (m *MockAuthProvider) VerifyToken(ctx context.Context, token string) (auth.IDToken, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "VerifyToken", ctx, token) + ret0, _ := ret[0].(auth.IDToken) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// VerifyToken indicates an expected call of VerifyToken. +func (mr *MockAuthProviderMockRecorder) VerifyToken(ctx, token interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyToken", reflect.TypeOf((*MockAuthProvider)(nil).VerifyToken), ctx, token) +} + +// MockIDToken is a mock of IDToken interface. +type MockIDToken struct { + ctrl *gomock.Controller + recorder *MockIDTokenMockRecorder +} + +// MockIDTokenMockRecorder is the mock recorder for MockIDToken. +type MockIDTokenMockRecorder struct { + mock *MockIDToken +} + +// NewMockIDToken creates a new mock instance. +func NewMockIDToken(ctrl *gomock.Controller) *MockIDToken { + mock := &MockIDToken{ctrl: ctrl} + mock.recorder = &MockIDTokenMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIDToken) EXPECT() *MockIDTokenMockRecorder { + return m.recorder +} + +// GetClaims mocks base method. +func (m *MockIDToken) GetClaims(out interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClaims", out) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetClaims indicates an expected call of GetClaims. +func (mr *MockIDTokenMockRecorder) GetClaims(out interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClaims", reflect.TypeOf((*MockIDToken)(nil).GetClaims), out) +}