diff --git a/auth/auth_context.go b/auth/auth_context.go new file mode 100644 index 0000000000..8ec1b963db --- /dev/null +++ b/auth/auth_context.go @@ -0,0 +1,212 @@ +// Contains types needed to start up a standalone OAuth2 Authorization Server or delegate authentication to an external +// provider. It supports OpenId connect for user authentication. +package auth + +import ( + "context" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + "github.com/flyteorg/flyteplugins/go/tasks/pluginmachinery/core" + + "github.com/coreos/go-oidc" + "github.com/flyteorg/flyteadmin/auth/config" + "github.com/flyteorg/flyteadmin/auth/interfaces" + "github.com/flyteorg/flytestdlib/errors" + "github.com/flyteorg/flytestdlib/logger" + "golang.org/x/oauth2" +) + +const ( + IdpConnectionTimeout = 10 * time.Second + + ErrauthCtx errors.ErrorCode = "AUTH_CONTEXT_SETUP_FAILED" + ErrConfigFileRead errors.ErrorCode = "CONFIG_OPTION_FILE_READ_FAILED" +) + +var ( + callbackRelativeURL = config.MustParseURL("/callback") + rootRelativeURL = config.MustParseURL("/") +) + +// Please see the comment on the corresponding AuthenticationContext for more information. +type Context struct { + oauth2Client *oauth2.Config + cookieManager interfaces.CookieHandler + oidcProvider *oidc.Provider + options *config.Config + oauth2Provider interfaces.OAuth2Provider + oauth2ResourceServer interfaces.OAuth2ResourceServer + authServiceImpl service.AuthMetadataServiceServer + identityServiceIml service.IdentityServiceServer + + userInfoURL *url.URL + oauth2MetadataURL *url.URL + oidcMetadataURL *url.URL + httpClient *http.Client +} + +func (c Context) OAuth2Provider() interfaces.OAuth2Provider { + return c.oauth2Provider +} + +func (c Context) OAuth2ClientConfig(requestURL *url.URL) *oauth2.Config { + if requestURL == nil || strings.HasPrefix(c.oauth2Client.RedirectURL, requestURL.ResolveReference(rootRelativeURL).String()) { + return c.oauth2Client + } + + return &oauth2.Config{ + RedirectURL: requestURL.ResolveReference(callbackRelativeURL).String(), + ClientID: c.oauth2Client.ClientID, + ClientSecret: c.oauth2Client.ClientSecret, + Scopes: c.oauth2Client.Scopes, + Endpoint: c.oauth2Client.Endpoint, + } +} + +func (c Context) OidcProvider() *oidc.Provider { + return c.oidcProvider +} + +func (c Context) CookieManager() interfaces.CookieHandler { + return c.cookieManager +} + +func (c Context) Options() *config.Config { + return c.options +} + +func (c Context) GetUserInfoURL() *url.URL { + return c.userInfoURL +} + +func (c Context) GetHTTPClient() *http.Client { + return c.httpClient +} + +func (c Context) GetOAuth2MetadataURL() *url.URL { + return c.oauth2MetadataURL +} + +func (c Context) GetOIdCMetadataURL() *url.URL { + return c.oidcMetadataURL +} + +func (c Context) AuthMetadataService() service.AuthMetadataServiceServer { + return c.authServiceImpl +} + +func (c Context) IdentityService() service.IdentityServiceServer { + return c.identityServiceIml +} + +func (c Context) OAuth2ResourceServer() interfaces.OAuth2ResourceServer { + return c.oauth2ResourceServer +} +func NewAuthenticationContext(ctx context.Context, sm core.SecretManager, oauth2Provider interfaces.OAuth2Provider, + oauth2ResourceServer interfaces.OAuth2ResourceServer, authMetadataService service.AuthMetadataServiceServer, + identityService service.IdentityServiceServer, options *config.Config) (Context, error) { + + // Construct the cookie manager object. + hashKeyBase64, err := sm.Get(ctx, options.UserAuth.CookieHashKeySecretName) + if err != nil { + return Context{}, errors.Wrapf(ErrConfigFileRead, err, "Could not read hash key file") + } + + blockKeyBase64, err := sm.Get(ctx, options.UserAuth.CookieBlockKeySecretName) + if err != nil { + return Context{}, errors.Wrapf(ErrConfigFileRead, err, "Could not read hash key file") + } + + cookieManager, err := NewCookieManager(ctx, hashKeyBase64, blockKeyBase64) + if err != nil { + logger.Errorf(ctx, "Error creating cookie manager %s", err) + return Context{}, errors.Wrapf(ErrauthCtx, err, "Error creating cookie manager") + } + + // Construct an http client for interacting with the IDP if necessary. + httpClient := &http.Client{ + Timeout: IdpConnectionTimeout, + } + + // Construct an oidc Provider, which needs its own http Client. + oidcCtx := oidc.ClientContext(ctx, httpClient) + baseURL := options.UserAuth.OpenID.BaseURL.String() + provider, err := oidc.NewProvider(oidcCtx, baseURL) + if err != nil { + return Context{}, errors.Wrapf(ErrauthCtx, err, "Error creating oidc provider w/ issuer [%v]", baseURL) + } + + // Construct the golang OAuth2 library's own internal configuration object from this package's config + oauth2Config, err := GetOAuth2ClientConfig(ctx, options.UserAuth.OpenID, provider.Endpoint(), sm) + if err != nil { + return Context{}, errors.Wrapf(ErrauthCtx, err, "Error creating OAuth2 library configuration") + } + + logger.Infof(ctx, "Base IDP URL is %s", options.UserAuth.OpenID.BaseURL) + + oauth2MetadataURL, err := url.Parse(OAuth2MetadataEndpoint) + if err != nil { + logger.Errorf(ctx, "Error parsing oauth2 metadata URL %s", err) + return Context{}, errors.Wrapf(ErrauthCtx, err, "Error parsing metadata URL") + } + + logger.Infof(ctx, "Metadata endpoint is %s", oauth2MetadataURL) + + oidcMetadataURL, err := url.Parse(OIdCMetadataEndpoint) + if err != nil { + logger.Errorf(ctx, "Error parsing oidc metadata URL %s", err) + return Context{}, errors.Wrapf(ErrauthCtx, err, "Error parsing metadata URL") + } + + logger.Infof(ctx, "Metadata endpoint is %s", oidcMetadataURL) + + authCtx := Context{ + options: options, + oidcMetadataURL: oidcMetadataURL, + oauth2MetadataURL: oauth2MetadataURL, + oauth2Client: &oauth2Config, + oidcProvider: provider, + httpClient: httpClient, + cookieManager: cookieManager, + oauth2Provider: oauth2Provider, + oauth2ResourceServer: oauth2ResourceServer, + } + + authCtx.authServiceImpl = authMetadataService + authCtx.identityServiceIml = identityService + + return authCtx, nil +} + +// This creates a oauth2 library config object, with values from the Flyte Admin config +func GetOAuth2ClientConfig(ctx context.Context, options config.OpenIDOptions, providerEndpoints oauth2.Endpoint, sm core.SecretManager) (cfg oauth2.Config, err error) { + var secret string + if len(options.DeprecatedClientSecretFile) > 0 { + secretBytes, err := ioutil.ReadFile(options.DeprecatedClientSecretFile) + if err != nil { + return oauth2.Config{}, err + } + + secret = string(secretBytes) + } else { + secret, err = sm.Get(ctx, options.ClientSecretName) + if err != nil { + return oauth2.Config{}, err + } + } + + secret = strings.TrimSuffix(secret, "\n") + + return oauth2.Config{ + RedirectURL: callbackRelativeURL.String(), + ClientID: options.ClientID, + ClientSecret: secret, + Scopes: options.Scopes, + Endpoint: providerEndpoints, + }, nil +} diff --git a/auth/authzserver/authorize.go b/auth/authzserver/authorize.go new file mode 100644 index 0000000000..c735e197d4 --- /dev/null +++ b/auth/authzserver/authorize.go @@ -0,0 +1,133 @@ +package authzserver + +import ( + "fmt" + "log" + "net/http" + "time" + + "github.com/flyteorg/flyteadmin/auth" + "github.com/ory/fosite" + + "github.com/flyteorg/flyteadmin/auth/interfaces" + "github.com/flyteorg/flytestdlib/logger" +) + +const ( + requestedScopePrefix = "f." + accessTokenScope = "access_token" + refreshTokenScope = "offline" +) + +func getAuthEndpoint(authCtx interfaces.AuthenticationContext) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + authEndpoint(authCtx, writer, request) + } +} + +func getAuthCallbackEndpoint(authCtx interfaces.AuthenticationContext) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + authCallbackEndpoint(authCtx, writer, request) + } +} + +// authCallbackEndpoint is the endpoint that gets called after the user-auth flow finishes. It retrieves the original +// /authorize request and issues an auth_code in response. +func authCallbackEndpoint(authCtx interfaces.AuthenticationContext, rw http.ResponseWriter, req *http.Request) { + issuer := GetIssuer(req.Context(), req, authCtx.Options()) + + // This context will be passed to all methods. + ctx := req.Context() + oauth2Provider := authCtx.OAuth2Provider() + + // Get the user's identity + identityContext, err := auth.IdentityContextFromRequest(ctx, req, authCtx) + if err != nil { + logger.Infof(ctx, "Failed to acquire user identity from request: %+v", err) + oauth2Provider.WriteAuthorizeError(rw, fosite.NewAuthorizeRequest(), err) + return + } + + // Get latest user's info either from identity or by making a UserInfo() call to the original + userInfo, err := auth.QueryUserInfo(ctx, identityContext, req, authCtx) + if err != nil { + err = fmt.Errorf("failed to query user info. Error: %w", err) + http.Error(rw, err.Error(), http.StatusUnauthorized) + return + } + + // Rehydrate the original auth code request + arURL, err := authCtx.CookieManager().RetrieveAuthCodeRequest(ctx, req) + if err != nil { + logger.Infof(ctx, "Error occurred in NewAuthorizeRequest: %+v", err) + oauth2Provider.WriteAuthorizeError(rw, fosite.NewAuthorizeRequest(), err) + return + } + + arReq, err := http.NewRequest(http.MethodGet, arURL, nil) + if err != nil { + logger.Infof(ctx, "Error occurred in NewAuthorizeRequest: %+v", err) + oauth2Provider.WriteAuthorizeError(rw, fosite.NewAuthorizeRequest(), err) + return + } + + ar, err := oauth2Provider.NewAuthorizeRequest(ctx, arReq) + if err != nil { + logger.Infof(ctx, "Error occurred in NewAuthorizeRequest: %+v", err) + oauth2Provider.WriteAuthorizeError(rw, ar, err) + return + } + + // TODO: Ideally this is where we show users a consent form. + + // let's see what scopes the user gave consent to + for _, scope := range req.PostForm["scopes"] { + ar.GrantScope(scope) + } + + // Now that the user is authorized, we set up a session: + mySessionData := oauth2Provider.NewJWTSessionToken(identityContext.UserID(), ar.GetClient().GetID(), issuer, issuer, userInfo) + mySessionData.JWTClaims.ExpiresAt = time.Now().Add(authCtx.Options().AppAuth.SelfAuthServer.AccessTokenLifespan.Duration) + mySessionData.SetExpiresAt(fosite.AuthorizeCode, time.Now().Add(authCtx.Options().AppAuth.SelfAuthServer.AuthorizationCodeLifespan.Duration)) + mySessionData.SetExpiresAt(fosite.AccessToken, time.Now().Add(authCtx.Options().AppAuth.SelfAuthServer.AccessTokenLifespan.Duration)) + mySessionData.SetExpiresAt(fosite.RefreshToken, time.Now().Add(authCtx.Options().AppAuth.SelfAuthServer.RefreshTokenLifespan.Duration)) + + // Now we need to get a response. This is the place where the AuthorizeEndpointHandlers kick in and start processing the request. + // NewAuthorizeResponse is capable of running multiple response type handlers. + response, err := oauth2Provider.NewAuthorizeResponse(ctx, ar, mySessionData) + if err != nil { + log.Printf("Error occurred in NewAuthorizeResponse: %+v", err) + oauth2Provider.WriteAuthorizeError(rw, ar, err) + return + } + + // Last but not least, send the response! + oauth2Provider.WriteAuthorizeResponse(rw, ar, response) +} + +// Get the /authorize endpoint handler that is supposed to be invoked in the browser for the user to log in and consent. +func authEndpoint(authCtx interfaces.AuthenticationContext, rw http.ResponseWriter, req *http.Request) { + // This context will be passed to all methods. + ctx := req.Context() + + oauth2Provider := authCtx.OAuth2Provider() + + // Let's create an AuthorizeRequest object! + // It will analyze the request and extract important information like scopes, response type and others. + ar, err := oauth2Provider.NewAuthorizeRequest(ctx, req) + if err != nil { + logger.Infof(ctx, "Error occurred in NewAuthorizeRequest: %+v", err) + oauth2Provider.WriteAuthorizeError(rw, ar, err) + return + } + + err = authCtx.CookieManager().SetAuthCodeCookie(ctx, rw, req.URL.String()) + if err != nil { + logger.Infof(ctx, "Error occurred in NewAuthorizeRequest: %+v", err) + oauth2Provider.WriteAuthorizeError(rw, ar, err) + return + } + + redirectURL := fmt.Sprintf("/login?redirect_url=%v", authorizeCallbackRelativeURL.String()) + http.Redirect(rw, req, redirectURL, http.StatusTemporaryRedirect) +} diff --git a/auth/authzserver/authorize_test.go b/auth/authzserver/authorize_test.go new file mode 100644 index 0000000000..d481283df8 --- /dev/null +++ b/auth/authzserver/authorize_test.go @@ -0,0 +1,180 @@ +package authzserver + +import ( + "context" + "crypto/rand" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + config2 "github.com/flyteorg/flytestdlib/config" + + "github.com/flyteorg/flyteadmin/auth" + "github.com/stretchr/testify/mock" + + "github.com/flyteorg/flyteadmin/auth/interfaces/mocks" + + "github.com/flyteorg/flyteadmin/auth/config" + + "github.com/ory/fosite" + + "github.com/stretchr/testify/assert" +) + +func TestAuthEndpoint(t *testing.T) { + t.Run("Success", func(t *testing.T) { + originalURL := "http://localhost:8088/oauth2/authorize?client_id=my-client&redirect_uri=http%3A%2F%2Flocalhost%3A3846%2Fcallback&response_type=code&scope=photos+openid+offline&state=some-random-state-foobar&nonce=some-random-nonce&code_challenge=p0v_UR0KrXl4--BpxM2BQa7qIW5k3k4WauBhjmkVQw8&code_challenge_method=S256" + req := httptest.NewRequest(http.MethodGet, originalURL, nil) + w := httptest.NewRecorder() + + authCtx := &mocks.AuthenticationContext{} + oauth2Provider := &mocks.OAuth2Provider{} + oauth2Provider.OnNewAuthorizeRequest(req.Context(), req).Return(fosite.NewAuthorizeRequest(), nil) + authCtx.OnOAuth2Provider().Return(oauth2Provider) + + cookieManager := &mocks.CookieHandler{} + cookieManager.OnSetAuthCodeCookie(req.Context(), w, originalURL).Return(nil) + authCtx.OnCookieManager().Return(cookieManager) + + authEndpoint(authCtx, w, req) + assert.Equal(t, http.StatusTemporaryRedirect, w.Code) + }) + + t.Run("Fail to write cookie", func(t *testing.T) { + originalURL := "http://localhost:8088/oauth2/authorize?client_id=my-client&redirect_uri=http%3A%2F%2Flocalhost%3A3846%2Fcallback&response_type=code&scope=photos+openid+offline&state=some-random-state-foobar&nonce=some-random-nonce&code_challenge=p0v_UR0KrXl4--BpxM2BQa7qIW5k3k4WauBhjmkVQw8&code_challenge_method=S256" + req := httptest.NewRequest(http.MethodGet, originalURL, nil) + w := httptest.NewRecorder() + + authCtx := &mocks.AuthenticationContext{} + oauth2Provider := &mocks.OAuth2Provider{} + requester := fosite.NewAuthorizeRequest() + oauth2Provider.OnNewAuthorizeRequest(req.Context(), req).Return(requester, nil) + oauth2Provider.On("WriteAuthorizeError", w, requester, mock.Anything).Run(func(args mock.Arguments) { + rw := args.Get(0).(http.ResponseWriter) + rw.WriteHeader(http.StatusForbidden) + }) + authCtx.OnOAuth2Provider().Return(oauth2Provider) + + cookieManager := &mocks.CookieHandler{} + cookieManager.OnSetAuthCodeCookie(req.Context(), w, originalURL).Return(fmt.Errorf("failure injection")) + authCtx.OnCookieManager().Return(cookieManager) + + authEndpoint(authCtx, w, req) + assert.Equal(t, http.StatusForbidden, w.Code) + }) +} + +// #nosec +const sampleIDToken = `eyJraWQiOiJaNmRtWl9UWGhkdXctalVCWjZ1RUV6dm5oLWpoTk8wWWhlbUI3cWFfTE9jIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiIwMHVra2k0OHBzSDhMaWtZVjVkNiIsIm5hbWUiOiJIYXl0aGFtIEFidWVsZnV0dWgiLCJ2ZXIiOjEsImlzcyI6Imh0dHBzOi8vZGV2LTE0MTg2NDIyLm9rdGEuY29tL29hdXRoMi9hdXNrbmdubjd1QlZpUXE2YjVkNiIsImF1ZCI6IjBvYWtraGV0ZU5qQ01FUnN0NWQ2IiwiaWF0IjoxNjE4NDUzNjc5LCJleHAiOjE2MTg0NTcyNzksImp0aSI6IklELmE0YXpLdUphVFM2YzNTeHdpWWdTMHhPbTM2bVFnVlVVN0I4V2dEdk80dFkiLCJhbXIiOlsicHdkIl0sImlkcCI6IjBvYWtrbTFjaTFVZVBwTlUwNWQ2IiwicHJlZmVycmVkX3VzZXJuYW1lIjoiaGF5dGhhbUB1bmlvbi5haSIsImF1dGhfdGltZSI6MTYxODQ0NjI0NywiYXRfaGFzaCI6Ikg5Q0FweWlrQkpGYXJ4d1FUbnB6ZFEifQ.SJ3BTD_MFcrYvTnql181Ddeb_mOm81z_S7ZKQ6P8mMgWqn94LZ2nG8k8-_odaaNAAT-M1nAFKWqZAQGvliwS1_TsD8_j0cen5zYnGcz2Uu5fFlvoHwuPgy5JYYNOXkXYgPnIb3kNkgXKbkdjS9hdbMfvnPd9rr8v0yzqf0AQBnUe-cPrzY-ZJjvh80IWDZgSjoP244tTYppPkx8UtedJLJZ4tzB7aXlEyoRV-DpmOLfJkAmblRm4OsO1qjwmx3HSIy_T-0PANn-g4AS07rpoMYHRcqncdgcAsVfGxjyWiOg3kbymLqpGlkIZgzmev-TmpoDp0QkUVPOntuiB57GZ6g` + +//func TestAuthCallbackEndpoint(t *testing.T) { +// originalURL := "http://localhost:8088/oauth2/authorize?client_id=my-client&redirect_uri=http%3A%2F%2Flocalhost%3A3846%2Fcallback&response_type=code&scope=photos+openid+offline&state=some-random-state-foobar&nonce=some-random-nonce&code_challenge=p0v_UR0KrXl4--BpxM2BQa7qIW5k3k4WauBhjmkVQw8&code_challenge_method=S256" +// req := httptest.NewRequest(http.MethodGet, originalURL, nil) +// w := httptest.NewRecorder() +// +// authCtx := &mocks.AuthenticationContext{} +// +// oauth2Provider := &mocks.OAuth2Provider{} +// requester := fosite.NewAuthorizeRequest() +// oauth2Provider.OnNewAuthorizeRequest(req.Context(), req).Return(requester, nil) +// oauth2Provider.On("WriteAuthorizeError", w, requester, mock.Anything).Run(func(args mock.Arguments) { +// rw := args.Get(0).(http.ResponseWriter) +// rw.WriteHeader(http.StatusForbidden) +// }) +// +// authCtx.OnOAuth2Provider().Return(oauth2Provider) +// +// cookieManager := &mocks.CookieHandler{} +// cookieManager.OnSetAuthCodeCookie(req.Context(), w, originalURL).Return(nil) +// cookieManager.OnRetrieveTokenValues(req.Context(), req).Return(sampleIDToken, "", "", nil) +// cookieManager.OnRetrieveUserInfo(req.Context(), req).Return(&service.UserInfoResponse{Subject: "abc"}, nil) +// authCtx.OnCookieManager().Return(cookieManager) +// +// authCtx.OnOptions().Return(&config.Config{ +// UserAuth: config.UserAuthConfig{ +// OpenID: config.OpenIDOptions{ +// //ClientID: "http://localhost", +// }, +// }, +// }) +// +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// var issuer string +// hf := func(w http.ResponseWriter, r *http.Request) { +// if r.URL.Path == "/.well-known/openid-configuration" { +// w.Header().Set("Content-Type", "application/json") +// io.WriteString(w, strings.ReplaceAll(`{ +// "issuer": "ISSUER", +// "authorization_endpoint": "https://example.com/auth", +// "token_endpoint": "https://example.com/token", +// "jwks_uri": "ISSUER/keys", +// "id_token_signing_alg_values_supported": ["RS256"] +// }`, "ISSUER", issuer)) +// return +// } else if r.URL.Path == "/keys" { +// w.Header().Set("Content-Type", "application/json") +// io.WriteString(w, `{"keys":[{"kty":"RSA","alg":"RS256","kid":"Z6dmZ_TXhduw-jUBZ6uEEzvnh-jhNO0YhemB7qa_LOc","use":"sig","e":"AQAB","n":"jyMcudBiz7XqeDIvxfMlmG4fvAUU7cl3R4iSIv_ahHanCcVRvqcXOsIknwn7i4rOUjP6MlH45uIYsaj6MuLYgoaIbC-Z823Tu4asoC-rGbpZgf-bMcJLxtZVBNsSagr_M0n8xA1oogHRF1LGRiD93wNr2b9OkKVbWnyNdASk5_xui024nVzakm2-RAEyaC048nHfnjVBvwo4BdJVDgBEK03fbkBCyuaZyE1ZQF545MTbD4keCv58prSCmbDRJgRk48FzaFnQeYTho-pUxXxM9pvhMykeI62WZ7diDfIc9isOpv6ALFOHgKy7Ihhve6pLIylLRTnn2qhHFkGPtU3djQ"}]}`) +// return +// } +// +// http.NotFound(w, r) +// return +// +// } +// +// s := httptest.NewServer(http.HandlerFunc(hf)) +// defer s.Close() +// +// issuer = s.URL +// mockOidcProvider, err := oidc.NewProvider(ctx, issuer) +// if !assert.NoError(t, err) { +// t.FailNow() +// } +// +// authCtx.OnOidcProvider().Return(mockOidcProvider) +// +// authCallbackEndpoint(authCtx, w, req) +// assert.Equal(t, http.StatusTemporaryRedirect, w.Code) +//} + +func TestGetIssuer(t *testing.T) { + t.Run("SelfAuthServerIssuer wins", func(t *testing.T) { + issuer := GetIssuer(context.Background(), nil, &config.Config{ + AppAuth: config.OAuth2Options{ + SelfAuthServer: config.AuthorizationServer{ + Issuer: "my_issuer", + }, + }, + AuthorizedURIs: []config2.URL{{URL: *config.MustParseURL("http://localhost/")}}, + }) + + assert.Equal(t, "my_issuer", issuer) + }) + + t.Run("Fallback to http public uri", func(t *testing.T) { + issuer := GetIssuer(context.Background(), nil, &config.Config{ + AuthorizedURIs: []config2.URL{{URL: *config.MustParseURL("http://localhost/")}}, + }) + + assert.Equal(t, "http://localhost/", issuer) + }) +} + +func TestEncryptDecrypt(t *testing.T) { + cookieHashKey := [auth.SymmetricKeyLength]byte{} + _, err := rand.Read(cookieHashKey[:]) + assert.NoError(t, err) + + input := "hello world" + encrypted, err := encryptString(input, cookieHashKey) + assert.NoError(t, err) + + decrypted, err := decryptString(encrypted, cookieHashKey) + assert.NoError(t, err) + + assert.Equal(t, input, decrypted) + assert.NotEqual(t, input, encrypted) +} diff --git a/auth/authzserver/doc.go b/auth/authzserver/doc.go new file mode 100644 index 0000000000..7546f7b5cf --- /dev/null +++ b/auth/authzserver/doc.go @@ -0,0 +1,2 @@ +// OAuthServer implementation that serve oauth2 authorize and client_credentials flows. +package authzserver diff --git a/auth/authzserver/encryptor.go b/auth/authzserver/encryptor.go new file mode 100644 index 0000000000..bb3804925d --- /dev/null +++ b/auth/authzserver/encryptor.go @@ -0,0 +1,6 @@ +package authzserver + +type Encryptor interface { + Encrypt(raw string) (cypher string, err error) + Decrypt(cypher string) (raw string, err error) +} diff --git a/auth/authzserver/initialize.go b/auth/authzserver/initialize.go new file mode 100644 index 0000000000..b61bae4c89 --- /dev/null +++ b/auth/authzserver/initialize.go @@ -0,0 +1,55 @@ +package authzserver + +import ( + "crypto/rsa" + + "github.com/ory/fosite/handler/oauth2" + + "github.com/ory/fosite" + + "github.com/flyteorg/flyteadmin/auth/interfaces" + + "github.com/ory/fosite/compose" + "github.com/ory/fosite/token/jwt" +) + +// RegisterHandlers registers http endpoints for handling OAuth2 flow (/authorize, +func RegisterHandlers(handler interfaces.HandlerRegisterer, authCtx interfaces.AuthenticationContext) { + if authCtx.OAuth2Provider() != nil { + // Set up oauthserver endpoints. You could also use gorilla/mux or any other router. + handler.HandleFunc(authorizeRelativeURL.String(), getAuthEndpoint(authCtx)) + handler.HandleFunc(authorizeCallbackRelativeURL.String(), getAuthCallbackEndpoint(authCtx)) + handler.HandleFunc(tokenRelativeURL.String(), getTokenEndpointHandler(authCtx)) + handler.HandleFunc(jsonWebKeysURL.String(), GetJSONWebKeysEndpoint(authCtx)) + } +} + +// composeOAuth2Provider builds a fosite.OAuth2Provider that uses JWT for issuing access tokens and uses the provided +// codeProvider to issue AuthCode and RefreshTokens. +func composeOAuth2Provider(codeProvider oauth2.CoreStrategy, config *compose.Config, storage fosite.Storage, + key *rsa.PrivateKey) fosite.OAuth2Provider { + + commonStrategy := &compose.CommonStrategy{ + CoreStrategy: codeProvider, + OpenIDConnectTokenStrategy: compose.NewOpenIDConnectStrategy(config, key), + JWTStrategy: &jwt.RS256JWTStrategy{ + PrivateKey: key, + }, + } + + return compose.Compose( + config, + storage, + commonStrategy, + nil, + + compose.OAuth2AuthorizeExplicitFactory, + compose.OAuth2ClientCredentialsGrantFactory, + compose.OAuth2RefreshTokenGrantFactory, + + compose.OAuth2StatelessJWTIntrospectionFactory, + //compose.OAuth2TokenRevocationFactory, + + compose.OAuth2PKCEFactory, + ) +} diff --git a/auth/authzserver/initialize_test.go b/auth/authzserver/initialize_test.go new file mode 100644 index 0000000000..6394fd3fdc --- /dev/null +++ b/auth/authzserver/initialize_test.go @@ -0,0 +1,37 @@ +package authzserver + +import ( + "testing" + + "github.com/ory/fosite/storage" + + "github.com/ory/fosite/compose" + + "github.com/stretchr/testify/mock" + + "github.com/flyteorg/flyteadmin/auth" + "github.com/flyteorg/flyteadmin/auth/interfaces/mocks" +) + +func TestRegisterHandlers(t *testing.T) { + t.Run("No OAuth2 Provider, no registration required", func(t *testing.T) { + registerer := &mocks.HandlerRegisterer{} + RegisterHandlers(registerer, auth.Context{}) + }) + + t.Run("Register 4 endpoints", func(t *testing.T) { + registerer := &mocks.HandlerRegisterer{} + registerer.On("HandleFunc", "/oauth2/authorize", mock.Anything) + registerer.On("HandleFunc", "/oauth2/authorize_callback", mock.Anything) + registerer.On("HandleFunc", "/oauth2/jwks", mock.Anything) + registerer.On("HandleFunc", "/oauth2/token", mock.Anything) + authCtx := &mocks.AuthenticationContext{} + oauth2Provider := &mocks.OAuth2Provider{} + authCtx.OnOAuth2Provider().Return(oauth2Provider) + RegisterHandlers(registerer, authCtx) + }) +} + +func Test_composeOAuth2Provider(t *testing.T) { + composeOAuth2Provider(nil, &compose.Config{}, &storage.MemoryStore{}, nil) +} diff --git a/auth/authzserver/metadata.go b/auth/authzserver/metadata.go new file mode 100644 index 0000000000..f4c68cce51 --- /dev/null +++ b/auth/authzserver/metadata.go @@ -0,0 +1,64 @@ +package authzserver + +import ( + "context" + "crypto/rsa" + "encoding/json" + "fmt" + "net/http" + + "github.com/flyteorg/flyteadmin/auth" + "github.com/flyteorg/flyteadmin/auth/config" + + "github.com/lestrrat-go/jwx/jwk" + + "github.com/flyteorg/flytestdlib/logger" + + "github.com/flyteorg/flyteadmin/auth/interfaces" +) + +var ( + tokenRelativeURL = config.MustParseURL("/oauth2/token") + authorizeRelativeURL = config.MustParseURL("/oauth2/authorize") + authorizeCallbackRelativeURL = config.MustParseURL("/oauth2/authorize_callback") + jsonWebKeysURL = config.MustParseURL("/oauth2/jwks") + oauth2MetadataEndpoint = config.MustParseURL("/" + auth.OAuth2MetadataEndpoint) +) + +// GetJSONWebKeysEndpoint serves requests to the jwks endpoint. +// ref: https://tools.ietf.org/html/rfc7517 +func GetJSONWebKeysEndpoint(authCtx interfaces.AuthenticationContext) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + s := authCtx.OAuth2Provider().KeySet() + raw, err := json.Marshal(s) + if err != nil { + http.Error(writer, fmt.Errorf("failed to write public key. Error: %w", err).Error(), http.StatusInternalServerError) + return + } + + writer.Header().Set("Content-Type", "application/json") + size, err := writer.Write(raw) + if err != nil { + logger.Errorf(context.Background(), "Wrote JSONWebKeys response size %d, err %s", size, err) + } + } +} + +func newJSONWebKeySet(publicKeys []rsa.PublicKey) (jwk.Set, error) { + s := jwk.NewSet() + for _, publicKey := range publicKeys { + key, err := jwk.New(publicKey) + if err != nil { + return nil, fmt.Errorf("failed to write public key. Error: %w", err) + } + + err = jwk.AssignKeyID(key) + if err != nil { + return nil, fmt.Errorf("failed to write public key. Error: %w", err) + } + + s.Add(key) + } + + return s, nil +} diff --git a/auth/authzserver/metadata_provider.go b/auth/authzserver/metadata_provider.go new file mode 100644 index 0000000000..1f767a73fc --- /dev/null +++ b/auth/authzserver/metadata_provider.go @@ -0,0 +1,90 @@ +package authzserver + +import ( + "context" + "io/ioutil" + "net/http" + "net/url" + + "github.com/flyteorg/flyteadmin/auth" + + authConfig "github.com/flyteorg/flyteadmin/auth/config" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" +) + +type OAuth2MetadataProvider struct { + cfg *authConfig.Config +} + +// Override auth func to enforce anonymous access on the implemented APIs +// Ref: https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/auth/auth.go#L31 +func (s OAuth2MetadataProvider) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) { + return ctx, nil +} + +func (s OAuth2MetadataProvider) GetOAuth2Metadata(ctx context.Context, r *service.OAuth2MetadataRequest) (*service.OAuth2MetadataResponse, error) { + switch s.cfg.AppAuth.AuthServerType { + case authConfig.AuthorizationServerTypeSelf: + u := auth.GetPublicURL(ctx, nil, s.cfg) + doc := &service.OAuth2MetadataResponse{ + Issuer: GetIssuer(ctx, nil, s.cfg), + AuthorizationEndpoint: u.ResolveReference(authorizeRelativeURL).String(), + TokenEndpoint: u.ResolveReference(tokenRelativeURL).String(), + JwksUri: u.ResolveReference(jsonWebKeysURL).String(), + CodeChallengeMethodsSupported: []string{"S256"}, + ResponseTypesSupported: []string{ + "code", + "token", + "code token", + }, + GrantTypesSupported: supportedGrantTypes, + ScopesSupported: []string{auth.ScopeAll}, + TokenEndpointAuthMethodsSupported: []string{ + "client_secret_basic", + }, + } + + return doc, nil + default: + var externalMetadataURL *url.URL + if len(s.cfg.AppAuth.ExternalAuthServer.BaseURL.String()) > 0 { + externalMetadataURL = s.cfg.AppAuth.ExternalAuthServer.BaseURL.ResolveReference(oauth2MetadataEndpoint) + } else { + externalMetadataURL = s.cfg.UserAuth.OpenID.BaseURL.ResolveReference(oauth2MetadataEndpoint) + } + + response, err := http.Get(externalMetadataURL.String()) + if err != nil { + return nil, err + } + + raw, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + resp := &service.OAuth2MetadataResponse{} + err = unmarshalResp(response, raw, resp) + if err != nil { + return nil, err + } + + return resp, nil + } +} + +func (s OAuth2MetadataProvider) GetPublicClientConfig(context.Context, *service.PublicClientAuthConfigRequest) (*service.PublicClientAuthConfigResponse, error) { + return &service.PublicClientAuthConfigResponse{ + ClientId: s.cfg.AppAuth.ThirdParty.FlyteClientConfig.ClientID, + RedirectUri: s.cfg.AppAuth.ThirdParty.FlyteClientConfig.RedirectURI, + Scopes: s.cfg.AppAuth.ThirdParty.FlyteClientConfig.Scopes, + AuthorizationMetadataKey: s.cfg.GrpcAuthorizationHeader, + }, nil +} + +func NewService(config *authConfig.Config) OAuth2MetadataProvider { + return OAuth2MetadataProvider{ + cfg: config, + } +} diff --git a/auth/authzserver/metadata_provider_test.go b/auth/authzserver/metadata_provider_test.go new file mode 100644 index 0000000000..527091c71b --- /dev/null +++ b/auth/authzserver/metadata_provider_test.go @@ -0,0 +1,111 @@ +package authzserver + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + config2 "github.com/flyteorg/flytestdlib/config" + + "github.com/flyteorg/flyteadmin/auth/config" + authConfig "github.com/flyteorg/flyteadmin/auth/config" + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + "github.com/stretchr/testify/assert" +) + +func TestOAuth2MetadataProvider_FlyteClient(t *testing.T) { + provider := NewService(&authConfig.Config{ + AppAuth: authConfig.OAuth2Options{ + ThirdParty: authConfig.ThirdPartyConfigOptions{ + FlyteClientConfig: authConfig.FlyteClientConfig{ + ClientID: "my-client", + RedirectURI: "client/", + Scopes: []string{"all"}, + }, + }, + }, + }) + + ctx := context.Background() + resp, err := provider.GetPublicClientConfig(ctx, &service.PublicClientAuthConfigRequest{}) + assert.NoError(t, err) + assert.Equal(t, "my-client", resp.ClientId) + assert.Equal(t, "client/", resp.RedirectUri) + assert.Equal(t, []string{"all"}, resp.Scopes) +} + +func TestOAuth2MetadataProvider_OAuth2Metadata(t *testing.T) { + t.Run("Self AuthServer", func(t *testing.T) { + provider := NewService(&authConfig.Config{ + AuthorizedURIs: []config2.URL{{URL: *config.MustParseURL("https://issuer/")}}, + }) + + ctx := context.Background() + resp, err := provider.GetOAuth2Metadata(ctx, &service.OAuth2MetadataRequest{}) + assert.NoError(t, err) + assert.Equal(t, "https://issuer/", resp.Issuer) + }) + + var issuer string + hf := func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/.well-known/oauth-authorization-server" { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", "application/json") + _, err := io.WriteString(w, strings.ReplaceAll(`{ + "issuer": "https://dev-14186422.okta.com", + "authorization_endpoint": "https://example.com/auth", + "token_endpoint": "https://example.com/token", + "jwks_uri": "https://example.com/keys", + "id_token_signing_alg_values_supported": ["RS256"] + }`, "ISSUER", issuer)) + if !assert.NoError(t, err) { + t.FailNow() + } + } + + s := httptest.NewServer(http.HandlerFunc(hf)) + defer s.Close() + + http.DefaultClient = s.Client() + + t.Run("External AuthServer", func(t *testing.T) { + provider := NewService(&authConfig.Config{ + AuthorizedURIs: []config2.URL{{URL: *config.MustParseURL("https://issuer/")}}, + AppAuth: authConfig.OAuth2Options{ + AuthServerType: authConfig.AuthorizationServerTypeExternal, + ExternalAuthServer: authConfig.ExternalAuthorizationServer{ + BaseURL: config2.URL{URL: *config.MustParseURL(s.URL)}, + }, + }, + }) + + ctx := context.Background() + resp, err := provider.GetOAuth2Metadata(ctx, &service.OAuth2MetadataRequest{}) + assert.NoError(t, err) + assert.Equal(t, "https://dev-14186422.okta.com", resp.Issuer) + }) + + t.Run("External AuthServer fallback url", func(t *testing.T) { + provider := NewService(&authConfig.Config{ + AuthorizedURIs: []config2.URL{{URL: *config.MustParseURL("https://issuer/")}}, + AppAuth: authConfig.OAuth2Options{ + AuthServerType: authConfig.AuthorizationServerTypeExternal, + }, + UserAuth: authConfig.UserAuthConfig{ + OpenID: authConfig.OpenIDOptions{ + BaseURL: config2.URL{URL: *config.MustParseURL(s.URL)}, + }, + }, + }) + + ctx := context.Background() + resp, err := provider.GetOAuth2Metadata(ctx, &service.OAuth2MetadataRequest{}) + assert.NoError(t, err) + assert.Equal(t, "https://dev-14186422.okta.com", resp.Issuer) + }) +} diff --git a/auth/authzserver/metadata_test.go b/auth/authzserver/metadata_test.go new file mode 100644 index 0000000000..3d041dde3e --- /dev/null +++ b/auth/authzserver/metadata_test.go @@ -0,0 +1,89 @@ +package authzserver + +import ( + "crypto/rsa" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/flyteorg/flyteadmin/auth/interfaces/mocks" + "github.com/lestrrat-go/jwx/jwk" + + "github.com/flyteorg/flyteadmin/auth" + + "github.com/stretchr/testify/assert" +) + +func Test_newJSONWebKeySet(t *testing.T) { + publicKeys := make([]rsa.PublicKey, 0, 5) + for i := 0; i < cap(publicKeys); i++ { + secrets, err := auth.NewSecrets() + assert.NoError(t, err) + publicKeys = append(publicKeys, secrets.TokenSigningRSAPrivateKey.PublicKey) + } + + keySet, err := newJSONWebKeySet(publicKeys) + assert.NoError(t, err) + for i := 0; i < cap(publicKeys); i++ { + k, found := keySet.Get(i) + assert.True(t, found) + + actualPublicKey := &rsa.PublicKey{} + err = k.Raw(actualPublicKey) + assert.NoError(t, err) + assert.Equal(t, &publicKeys[i], actualPublicKey) + } + + _, found := keySet.Get(cap(publicKeys)) + assert.False(t, found) +} + +func TestGetJSONWebKeysEndpoint(t *testing.T) { + t.Run("Empty keyset", func(t *testing.T) { + authCtx := &mocks.AuthenticationContext{} + oauth2Provider := &mocks.OAuth2Provider{} + authCtx.OnOAuth2Provider().Return(oauth2Provider) + oauth2Provider.OnKeySet().Return(jwk.NewSet()) + + handler := GetJSONWebKeysEndpoint(authCtx) + responseRecorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/", nil) + handler.ServeHTTP(responseRecorder, request) + assert.Equal(t, "{\"keys\":[]}", responseRecorder.Body.String()) + }) + + t.Run("2 keys", func(t *testing.T) { + authCtx := &mocks.AuthenticationContext{} + oauth2Provider := &mocks.OAuth2Provider{} + authCtx.OnOAuth2Provider().Return(oauth2Provider) + + publicKeys := make([]rsa.PublicKey, 0, 5) + for i := 0; i < cap(publicKeys); i++ { + secrets, err := auth.NewSecrets() + assert.NoError(t, err) + publicKeys = append(publicKeys, secrets.TokenSigningRSAPrivateKey.PublicKey) + } + + keySet, err := newJSONWebKeySet(publicKeys) + assert.NoError(t, err) + + oauth2Provider.OnKeySet().Return(keySet) + + handler := GetJSONWebKeysEndpoint(authCtx) + responseRecorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/", nil) + handler.ServeHTTP(responseRecorder, request) + + actualKeySet := jwk.NewSet() + assert.NoError(t, json.Unmarshal(responseRecorder.Body.Bytes(), &actualKeySet)) + + for i := 0; i < keySet.Len(); i++ { + expectedKeyRaw, _ := keySet.Get(i) + expectedKey := &rsa.PublicKey{} + assert.NoError(t, expectedKeyRaw.Raw(expectedKey)) + } + + assert.Equal(t, keySet, actualKeySet) + }) +} diff --git a/auth/authzserver/provider.go b/auth/authzserver/provider.go new file mode 100644 index 0000000000..4612f636e8 --- /dev/null +++ b/auth/authzserver/provider.go @@ -0,0 +1,264 @@ +package authzserver + +import ( + "context" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "time" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + "github.com/flyteorg/flytestdlib/logger" + + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/lestrrat-go/jwx/jwk" + + "github.com/ory/x/jwtx" + + "github.com/flyteorg/flyteadmin/auth/interfaces" + + "github.com/flyteorg/flyteadmin/auth" + "github.com/flyteorg/flyteplugins/go/tasks/pluginmachinery/core" + + jwtgo "github.com/dgrijalva/jwt-go" + fositeOAuth2 "github.com/ory/fosite/handler/oauth2" + "github.com/ory/fosite/token/jwt" + + "github.com/flyteorg/flyteadmin/auth/config" + + "github.com/ory/fosite" + "github.com/ory/fosite/compose" + "github.com/ory/fosite/storage" +) + +const ( + ClientIDClaim = "client_id" + UserIDClaim = "user_info" + ScopeClaim = "scp" + KeyIDClaim = "key_id" +) + +// Provider implements OAuth2 Authorization Server. +type Provider struct { + fosite.OAuth2Provider + cfg config.AuthorizationServer + publicKey []rsa.PublicKey + keySet jwk.Set +} + +func (p Provider) PublicKeys() []rsa.PublicKey { + return p.publicKey +} + +func (p Provider) KeySet() jwk.Set { + return p.keySet +} + +// NewJWTSessionToken is a helper function for creating a new session. +func (p Provider) NewJWTSessionToken(subject, appID, issuer, audience string, userInfoClaims *service.UserInfoResponse) *fositeOAuth2.JWTSession { + key, found := p.keySet.Get(0) + keyID := "" + if found { + keyID = key.KeyID() + } + + return &fositeOAuth2.JWTSession{ + JWTClaims: &jwt.JWTClaims{ + Audience: []string{audience}, + Issuer: issuer, + Subject: subject, + ExpiresAt: time.Now().Add(p.cfg.AccessTokenLifespan.Duration), + IssuedAt: time.Now(), + Extra: map[string]interface{}{ + ClientIDClaim: appID, + UserIDClaim: userInfoClaims, + }, + }, + JWTHeader: &jwt.Headers{ + Extra: map[string]interface{}{ + KeyIDClaim: keyID, + }, + }, + } +} + +func findPublicKeyForTokenOrFirst(ctx context.Context, t *jwtgo.Token, publicKeys jwk.Set) (*rsa.PublicKey, error) { + if _, ok := t.Method.(*jwtgo.SigningMethodRSA); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) + } + + if publicKeys.Len() == 0 { + return nil, fmt.Errorf("no keys exist to match") + } + + publicKey := &rsa.PublicKey{} + k, _ := publicKeys.Get(0) + if err := k.Raw(publicKey); err != nil { + return nil, err + } + + if keyID, found := t.Header[KeyIDClaim]; !found { + return publicKey, nil + } else if key, found := publicKeys.LookupKeyID(keyID.(string)); !found { + return publicKey, nil + } else if err := key.Raw(publicKey); err != nil { + logger.Errorf(ctx, "Failed to load public key from key [%v]. Will default to the first key. Error: %v", keyID) + return publicKey, nil + } + + return publicKey, nil +} + +func (p Provider) ValidateAccessToken(ctx context.Context, expectedAudience, tokenStr string) (interfaces.IdentityContext, error) { + // Parse and validate the token. + parsedToken, err := jwtgo.Parse(tokenStr, func(t *jwtgo.Token) (interface{}, error) { + return findPublicKeyForTokenOrFirst(ctx, t, p.KeySet()) + }) + + if err != nil { + return nil, err + } + + if !parsedToken.Valid { + return nil, fmt.Errorf("parsed token is invalid") + } + + claimsRaw := parsedToken.Claims.(jwtgo.MapClaims) + return verifyClaims(expectedAudience, claimsRaw) +} + +func verifyClaims(expectedAudience string, claimsRaw map[string]interface{}) (interfaces.IdentityContext, error) { + claims := jwtx.ParseMapStringInterfaceClaims(claimsRaw) + if len(claims.Audience) != 1 { + return nil, fmt.Errorf("expected exactly one granted audience. found [%v]", len(claims.Audience)) + } + + if claims.Audience[0] != expectedAudience { + return nil, fmt.Errorf("invalid audience [%v]", claims.Audience[0]) + } + + userInfo := &service.UserInfoResponse{} + if userInfoClaim, found := claimsRaw[UserIDClaim]; found && userInfoClaim != nil { + userInfoRaw := userInfoClaim.(map[string]interface{}) + raw, err := json.Marshal(userInfoRaw) + if err != nil { + return nil, err + } + + if err = json.Unmarshal(raw, userInfo); err != nil { + return nil, fmt.Errorf("failed to unmarshal user info claim into UserInfo type. Error: %w", err) + } + } + + clientID := "" + if clientIDClaim, found := claimsRaw[ClientIDClaim]; found { + clientID = clientIDClaim.(string) + } + + scopes := sets.NewString() + if scopesClaim, found := claimsRaw[ScopeClaim]; found { + scopes = sets.NewString(interfaceSliceToStringSlice(scopesClaim.([]interface{}))...) + } + + return auth.NewIdentityContext(claims.Audience[0], claims.Subject, clientID, claims.IssuedAt, scopes, userInfo), nil +} + +// NewProvider creates a new OAuth2 Provider that is able to do OAuth 2-legged and 3-legged flows. It'll lookup +// config.SecretNameClaimSymmetricKey and config.SecretNameTokenSigningRSAKey secrets from the secret manager to use to +// sign and generate hashes for tokens. The RSA Private key is expected to be in PEM format with the public key embedded. +// Use auth.GetInitSecretsCommand() to generate new valid secrets that will be accepted by this provider. +// The config.SecretNameClaimSymmetricKey must be a 32-bytes long key in Base64Encoding. +func NewProvider(ctx context.Context, cfg config.AuthorizationServer, sm core.SecretManager) (Provider, error) { + // fosite requires four parameters for the server to get up and running: + // 1. config - for any enforcement you may desire, you can do this using `compose.Config`. You like PKCE, enforce it! + // 2. store - no auth service is generally useful unless it can remember clients and users. + // fosite is incredibly composable, and the store parameter enables you to build and BYODb (Bring Your Own Database) + // 3. secret - required for code, access and refresh token generation. + // 4. privateKey - required for id/jwt token generation. + + composeConfig := &compose.Config{ + AccessTokenLifespan: cfg.AccessTokenLifespan.Duration, + RefreshTokenLifespan: cfg.RefreshTokenLifespan.Duration, + AuthorizeCodeLifespan: cfg.AuthorizationCodeLifespan.Duration, + RefreshTokenScopes: []string{refreshTokenScope}, + } + + // This secret is used to encryptString/decrypt challenge code to maintain a stateless authcode token. + tokenHashBase64, err := sm.Get(ctx, cfg.ClaimSymmetricEncryptionKeySecretName) + if err != nil { + return Provider{}, fmt.Errorf("failed to read secretTokenHash file. Error: %w", err) + } + + secret, err := base64.RawStdEncoding.DecodeString(tokenHashBase64) + if err != nil { + return Provider{}, fmt.Errorf("failed to decode token hash using base64 encoding. Error: %w", err) + } + + // privateKey is used to sign JWT tokens. The default strategy uses RS256 (RSA Signature with SHA-256) + privateKeyPEM, err := sm.Get(ctx, cfg.TokenSigningRSAKeySecretName) + if err != nil { + return Provider{}, fmt.Errorf("failed to read token signing RSA Key. Error: %w", err) + } + + block, _ := pem.Decode([]byte(privateKeyPEM)) + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return Provider{}, fmt.Errorf("failed to parse PKCS1PrivateKey. Error: %w", err) + } + + // Build an in-memory store with static clients defined in Config. This gives us the potential to move the clients + // storage into DB and allow registration of new clients to users. + store := &StatelessTokenStore{ + MemoryStore: &storage.MemoryStore{ + IDSessions: make(map[string]fosite.Requester), + Clients: toClientIface(cfg.StaticClients), + AuthorizeCodes: map[string]storage.StoreAuthorizeCode{}, + AccessTokens: map[string]fosite.Requester{}, + RefreshTokens: map[string]storage.StoreRefreshToken{}, + PKCES: map[string]fosite.Requester{}, + AccessTokenRequestIDs: map[string]string{}, + RefreshTokenRequestIDs: map[string]string{}, + IssuerPublicKeys: map[string]storage.IssuerPublicKeys{}, + }, + } + + sec := [auth.SymmetricKeyLength]byte{} + copy(sec[:], secret) + codeProvider := NewStatelessCodeProvider(cfg, sec, compose.NewOAuth2JWTStrategy(privateKey, nil)) + + // Build a fosite instance with all OAuth2 and OpenID Connect handlers enabled, plugging in our configurations as specified above. + oauth2Provider := composeOAuth2Provider(codeProvider, composeConfig, store, privateKey) + store.JWTStrategy = &jwt.RS256JWTStrategy{ + PrivateKey: privateKey, + } + store.encryptor = codeProvider + + publicKeys := []rsa.PublicKey{privateKey.PublicKey} + + // Try to load old key to validate tokens using it to support key rotation. + privateKeyPEM, err = sm.Get(ctx, cfg.OldTokenSigningRSAKeySecretName) + if err == nil { + block, _ = pem.Decode([]byte(privateKeyPEM)) + oldPrivateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return Provider{}, fmt.Errorf("failed to parse PKCS1PrivateKey. Error: %w", err) + } + + publicKeys = append(publicKeys, oldPrivateKey.PublicKey) + } + + keysSet, err := newJSONWebKeySet(publicKeys) + if err != nil { + return Provider{}, err + } + + return Provider{ + OAuth2Provider: oauth2Provider, + publicKey: publicKeys, + keySet: keysSet, + }, nil +} diff --git a/auth/authzserver/provider_test.go b/auth/authzserver/provider_test.go new file mode 100644 index 0000000000..792a26e829 --- /dev/null +++ b/auth/authzserver/provider_test.go @@ -0,0 +1,239 @@ +package authzserver + +import ( + "bytes" + "context" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "testing" + "time" + + "k8s.io/apimachinery/pkg/util/sets" + + jwtgo "github.com/dgrijalva/jwt-go" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + + "github.com/flyteorg/flyteadmin/auth" + "github.com/flyteorg/flyteadmin/auth/config" + "github.com/flyteorg/flyteplugins/go/tasks/pluginmachinery/core/mocks" + "github.com/stretchr/testify/assert" +) + +func newMockProvider(t testing.TB) (Provider, auth.SecretsSet) { + secrets, err := auth.NewSecrets() + assert.NoError(t, err) + + ctx := context.Background() + sm := &mocks.SecretManager{} + sm.OnGet(ctx, config.SecretNameClaimSymmetricKey).Return(base64.RawStdEncoding.EncodeToString(secrets.TokenHashKey), nil) + sm.OnGet(ctx, config.SecretNameCookieBlockKey).Return(base64.RawStdEncoding.EncodeToString(secrets.CookieBlockKey), nil) + sm.OnGet(ctx, config.SecretNameCookieHashKey).Return(base64.RawStdEncoding.EncodeToString(secrets.CookieHashKey), nil) + + privBytes := x509.MarshalPKCS1PrivateKey(secrets.TokenSigningRSAPrivateKey) + var buf bytes.Buffer + assert.NoError(t, pem.Encode(&buf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes})) + sm.OnGet(ctx, config.SecretNameTokenSigningRSAKey).Return(buf.String(), nil) + sm.OnGet(ctx, config.SecretNameOldTokenSigningRSAKey).Return("", fmt.Errorf("not found")) + + p, err := NewProvider(ctx, config.DefaultConfig.AppAuth.SelfAuthServer, sm) + assert.NoError(t, err) + return p, secrets +} + +func TestNewProvider(t *testing.T) { + newMockProvider(t) +} + +func TestProvider_KeySet(t *testing.T) { + p, _ := newMockProvider(t) + assert.Equal(t, 1, p.KeySet().Len()) +} + +func TestProvider_NewJWTSessionToken(t *testing.T) { + p, _ := newMockProvider(t) + s := p.NewJWTSessionToken("userID", "appID", "my-issuer", "my-audience", &service.UserInfoResponse{ + Email: "foo@localhost", + }) + + k, found := p.KeySet().Get(0) + assert.True(t, found) + assert.NotEmpty(t, k.KeyID()) + assert.Equal(t, k.KeyID(), s.JWTHeader.Extra[KeyIDClaim]) +} + +func TestProvider_PublicKeys(t *testing.T) { + p, _ := newMockProvider(t) + assert.Len(t, p.PublicKeys(), 1) +} + +type CustomClaimsExample struct { + *jwtgo.StandardClaims + ClientID string `json:"client_id"` + Scopes []string `json:"scp"` + UserID string `json:"user_id"` +} + +func TestProvider_findPublicKeyForTokenOrFirst(t *testing.T) { + ctx := context.Background() + secrets, err := auth.NewSecrets() + assert.NoError(t, err) + + secrets2, err := auth.NewSecrets() + assert.NoError(t, err) + + keySet, err := newJSONWebKeySet([]rsa.PublicKey{secrets.TokenSigningRSAPrivateKey.PublicKey, secrets2.TokenSigningRSAPrivateKey.PublicKey}) + assert.NoError(t, err) + + // set our claims + secondKey, found := keySet.Get(1) + assert.True(t, found) + + t.Run("KeyID Exists", func(t *testing.T) { + // create a signer for rsa 256 + tok := jwtgo.New(jwtgo.GetSigningMethod("RS256")) + + tok.Header[KeyIDClaim] = secondKey.KeyID() + tok.Claims = &CustomClaimsExample{ + StandardClaims: &jwtgo.StandardClaims{}, + } + + // Create token string + _, err = tok.SignedString(secrets2.TokenSigningRSAPrivateKey) + assert.NoError(t, err) + + k, err := findPublicKeyForTokenOrFirst(ctx, tok, keySet) + assert.NoError(t, err) + assert.Equal(t, secrets2.TokenSigningRSAPrivateKey.PublicKey, *k) + }) + + t.Run("Unknown KeyID, Default to first key", func(t *testing.T) { + // create a signer for rsa 256 + tok := jwtgo.New(jwtgo.GetSigningMethod("RS256")) + + tok.Header[KeyIDClaim] = "not found" + tok.Claims = &CustomClaimsExample{ + StandardClaims: &jwtgo.StandardClaims{ + ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), + }, + } + + // Create token string + _, err = tok.SignedString(secrets2.TokenSigningRSAPrivateKey) + assert.NoError(t, err) + + k, err := findPublicKeyForTokenOrFirst(ctx, tok, keySet) + assert.NoError(t, err) + assert.Equal(t, secrets.TokenSigningRSAPrivateKey.PublicKey, *k) + }) + + t.Run("No KeyID Claim, Default to first key", func(t *testing.T) { + // create a signer for rsa 256 + tok := jwtgo.New(jwtgo.GetSigningMethod("RS256")) + + tok.Claims = &CustomClaimsExample{ + StandardClaims: &jwtgo.StandardClaims{ + ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), + }, + } + + // Create token string + _, err = tok.SignedString(secrets2.TokenSigningRSAPrivateKey) + assert.NoError(t, err) + + k, err := findPublicKeyForTokenOrFirst(ctx, tok, keySet) + assert.NoError(t, err) + assert.Equal(t, secrets.TokenSigningRSAPrivateKey.PublicKey, *k) + }) +} + +func TestProvider_ValidateAccessToken(t *testing.T) { + p, _ := newMockProvider(t) + ctx := context.Background() + + t.Run("Invalid JWT", func(t *testing.T) { + _, err := p.ValidateAccessToken(ctx, "myserver", "abc") + assert.Error(t, err) + }) + + t.Run("Invalid Signature", func(t *testing.T) { + _, err := p.ValidateAccessToken(ctx, "myserver", "sampleIDToken") + assert.Error(t, err) + }) + + t.Run("Valid", func(t *testing.T) { + ctx := context.Background() + secrets, err := auth.NewSecrets() + assert.NoError(t, err) + + sm := &mocks.SecretManager{} + sm.OnGet(ctx, config.SecretNameClaimSymmetricKey).Return(base64.RawStdEncoding.EncodeToString(secrets.TokenHashKey), nil) + sm.OnGet(ctx, config.SecretNameCookieBlockKey).Return(base64.RawStdEncoding.EncodeToString(secrets.CookieBlockKey), nil) + sm.OnGet(ctx, config.SecretNameCookieHashKey).Return(base64.RawStdEncoding.EncodeToString(secrets.CookieHashKey), nil) + + privBytes := x509.MarshalPKCS1PrivateKey(secrets.TokenSigningRSAPrivateKey) + var buf bytes.Buffer + assert.NoError(t, pem.Encode(&buf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes})) + sm.OnGet(ctx, config.SecretNameTokenSigningRSAKey).Return(buf.String(), nil) + sm.OnGet(ctx, config.SecretNameOldTokenSigningRSAKey).Return("", fmt.Errorf("not found")) + + p, err := NewProvider(ctx, config.DefaultConfig.AppAuth.SelfAuthServer, sm) + assert.NoError(t, err) + + // create a signer for rsa 256 + tok := jwtgo.New(jwtgo.GetSigningMethod("RS256")) + + keySet, err := newJSONWebKeySet([]rsa.PublicKey{secrets.TokenSigningRSAPrivateKey.PublicKey}) + assert.NoError(t, err) + + // set our claims + k, found := keySet.Get(0) + assert.True(t, found) + + tok.Header[KeyIDClaim] = k.KeyID() + tok.Claims = &CustomClaimsExample{ + StandardClaims: &jwtgo.StandardClaims{ + Audience: "https://myserver", + ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), + }, + ClientID: "client-1", + UserID: "1234", + Scopes: []string{"all"}, + } + + // Create token string + str, err := tok.SignedString(secrets.TokenSigningRSAPrivateKey) + assert.NoError(t, err) + + identity, err := p.ValidateAccessToken(ctx, "https://myserver", str) + assert.NoError(t, err) + assert.False(t, identity.IsEmpty()) + }) +} + +func Test_verifyClaims(t *testing.T) { + t.Run("Empty claims, fail", func(t *testing.T) { + _, err := verifyClaims("https://myserver", map[string]interface{}{}) + assert.Error(t, err) + }) + + t.Run("All filled", func(t *testing.T) { + identityCtx, err := verifyClaims("https://myserver", map[string]interface{}{ + "aud": []string{"https://myserver"}, + "user_info": map[string]interface{}{ + "preferred_name": "John Doe", + }, + "sub": "123", + "client_id": "my-client", + "scp": []interface{}{"all", "offline"}, + }) + + assert.NoError(t, err) + assert.Equal(t, sets.NewString("all", "offline"), identityCtx.Scopes()) + assert.Equal(t, "my-client", identityCtx.AppID()) + assert.Equal(t, "123", identityCtx.UserID()) + }) +} diff --git a/auth/authzserver/resource_server.go b/auth/authzserver/resource_server.go new file mode 100644 index 0000000000..12952d6c34 --- /dev/null +++ b/auth/authzserver/resource_server.go @@ -0,0 +1,117 @@ +package authzserver + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "mime" + "net/http" + "net/url" + "strings" + + "github.com/flyteorg/flytestdlib/config" + + "github.com/coreos/go-oidc" + "github.com/flyteorg/flyteadmin/auth" + + authConfig "github.com/flyteorg/flyteadmin/auth/config" + "github.com/flyteorg/flyteadmin/auth/interfaces" + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + "golang.org/x/oauth2" +) + +// ResourceServer authorizes access requests issued by an external Authorization Server. +type ResourceServer struct { + signatureVerifier oidc.KeySet +} + +func (r ResourceServer) ValidateAccessToken(ctx context.Context, expectedAudience, tokenStr string) (interfaces.IdentityContext, error) { + raw, err := r.signatureVerifier.VerifySignature(ctx, tokenStr) + if err != nil { + return nil, err + } + + claimsRaw := map[string]interface{}{} + if err = json.Unmarshal(raw, &claimsRaw); err != nil { + return nil, fmt.Errorf("failed to unmarshal user info claim into UserInfo type. Error: %w", err) + } + + return verifyClaims(expectedAudience, claimsRaw) +} + +func doRequest(ctx context.Context, req *http.Request) (*http.Response, error) { + client := http.DefaultClient + if c, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok { + client = c + } + return client.Do(req.WithContext(ctx)) +} + +func unmarshalResp(r *http.Response, body []byte, v interface{}) error { + err := json.Unmarshal(body, &v) + if err == nil { + return nil + } + ct := r.Header.Get("Content-Type") + mediaType, _, parseErr := mime.ParseMediaType(ct) + if parseErr == nil && mediaType == "application/json" { + return fmt.Errorf("got Content-Type = application/json, but could not unmarshal as JSON: %v", err) + } + return fmt.Errorf("expected Content-Type = application/json, got %q: %v", ct, err) +} + +func getJwksForIssuer(ctx context.Context, issuerBaseURL url.URL) (oidc.KeySet, error) { + u, err := url.Parse(auth.OAuth2MetadataEndpoint) + if err != nil { + return nil, err + } + + issuerBaseURL.Path = strings.TrimSuffix(issuerBaseURL.Path, "/") + "/" + wellKnown := issuerBaseURL.ResolveReference(u) + req, err := http.NewRequest(http.MethodGet, wellKnown.String(), nil) + if err != nil { + return nil, err + } + + resp, err := doRequest(ctx, req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("unable to read response body: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s: %s", resp.Status, body) + } + + p := &service.OAuth2MetadataResponse{} + err = unmarshalResp(resp, body, &p) + if err != nil { + return nil, fmt.Errorf("failed to decode provider discovery object: %v", err) + } + + return oidc.NewRemoteKeySet(ctx, p.JwksUri), nil +} + +// NewOAuth2ResourceServer initializes a new OAuth2ResourceServer. +func NewOAuth2ResourceServer(ctx context.Context, cfg authConfig.ExternalAuthorizationServer, fallbackBaseURL config.URL) (ResourceServer, error) { + u := cfg.BaseURL + if len(u.String()) == 0 { + u = fallbackBaseURL + } + + verifier, err := getJwksForIssuer(ctx, u.URL) + if err != nil { + return ResourceServer{}, err + } + + return ResourceServer{ + signatureVerifier: verifier, + }, nil +} diff --git a/auth/authzserver/resource_server_test.go b/auth/authzserver/resource_server_test.go new file mode 100644 index 0000000000..f2dc9c98f1 --- /dev/null +++ b/auth/authzserver/resource_server_test.go @@ -0,0 +1,164 @@ +package authzserver + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/lestrrat-go/jwx/jwk" + + "github.com/coreos/go-oidc" + "github.com/flyteorg/flyteadmin/auth/config" + authConfig "github.com/flyteorg/flyteadmin/auth/config" + stdlibConfig "github.com/flyteorg/flytestdlib/config" +) + +func newMockResourceServer(t testing.TB) ResourceServer { + ctx := context.Background() + dummy := "" + serverURL := &dummy + hf := func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/.well-known/oauth-authorization-server" { + w.Header().Set("Content-Type", "application/json") + _, err := io.WriteString(w, strings.ReplaceAll(`{ + "issuer": "https://dev-14186422.okta.com", + "authorization_endpoint": "https://example.com/auth", + "token_endpoint": "https://example.com/token", + "jwks_uri": "URL/keys", + "id_token_signing_alg_values_supported": ["RS256"] + }`, "URL", *serverURL)) + + if !assert.NoError(t, err) { + t.FailNow() + } + + return + } else if r.URL.Path == "/keys" { + keys := jwk.NewSet() + raw, err := json.Marshal(keys) + if err != nil { + http.Error(w, err.Error(), 400) + return + } + + w.Header().Set("Content-Type", "application/json") + _, err = io.WriteString(w, string(raw)) + + if !assert.NoError(t, err) { + t.FailNow() + } + } + + http.NotFound(w, r) + } + + s := httptest.NewServer(http.HandlerFunc(hf)) + defer s.Close() + + *serverURL = s.URL + + http.DefaultClient = s.Client() + + r, err := NewOAuth2ResourceServer(ctx, authConfig.ExternalAuthorizationServer{ + BaseURL: stdlibConfig.URL{URL: *config.MustParseURL(s.URL)}, + }, stdlibConfig.URL{}) + if !assert.NoError(t, err) { + t.FailNow() + } + + return r +} + +func TestNewOAuth2ResourceServer(t *testing.T) { + newMockResourceServer(t) +} + +func TestResourceServer_ValidateAccessToken(t *testing.T) { + r := newMockResourceServer(t) + _, err := r.ValidateAccessToken(context.Background(), "myserver", sampleIDToken) + assert.Error(t, err) +} + +func Test_doRequest(t *testing.T) { + type args struct { + ctx context.Context + req *http.Request + } + tests := []struct { + name string + args args + want *http.Response + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := doRequest(tt.args.ctx, tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("doRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("doRequest() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getJwksForIssuer(t *testing.T) { + type args struct { + ctx context.Context + issuerBaseURL url.URL + } + tests := []struct { + name string + args args + want oidc.KeySet + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getJwksForIssuer(tt.args.ctx, tt.args.issuerBaseURL) + if (err != nil) != tt.wantErr { + t.Errorf("getJwksForIssuer() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("getJwksForIssuer() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_unmarshalResp(t *testing.T) { + type args struct { + r *http.Response + body []byte + v interface{} + } + tests := []struct { + name string + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := unmarshalResp(tt.args.r, tt.args.body, tt.args.v); (err != nil) != tt.wantErr { + t.Errorf("unmarshalResp() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/auth/authzserver/stateless_token_store.go b/auth/authzserver/stateless_token_store.go new file mode 100644 index 0000000000..93cb85065e --- /dev/null +++ b/auth/authzserver/stateless_token_store.go @@ -0,0 +1,286 @@ +package authzserver + +import ( + "context" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/flyteorg/flyteadmin/auth" + + "github.com/flyteorg/flyteadmin/auth/config" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/ory/fosite/handler/oauth2" + oauth22 "github.com/ory/fosite/handler/oauth2" + "github.com/ory/fosite/token/jwt" + + "github.com/ory/fosite" + "github.com/ory/fosite/storage" +) + +const ( + encryptedFormPostClaim = "form" +) + +var ( + formPostParamsToPersist = sets.NewString("code_challenge", "code_challenge_method") +) + +// StatelessTokenStore provides a ship on top of the MemoryStore to avoid storing tokens in memory (or elsewhere) but +// instead hydrates fosite.Request and sessions from the tokens themselves. +type StatelessTokenStore struct { + *storage.MemoryStore + jwt.JWTStrategy + encryptor Encryptor +} + +func (s StatelessTokenStore) rehydrateSession(ctx context.Context, token string) (request *fosite.Request, err error) { + t, err := s.JWTStrategy.Decode(ctx, token) + if err != nil { + return nil, err + } + + ifaceRequest := oauth2.AccessTokenJWTToRequest(t) + rawRequest, casted := ifaceRequest.(*fosite.Request) + if !casted { + return nil, fmt.Errorf("expected *fosite.Request. Found %v", reflect.TypeOf(ifaceRequest)) + } + + client, err := s.GetClient(ctx, rawRequest.GetClient().GetID()) + if err != nil { + return nil, err + } + + rawRequest.Client = client + + jwtSession, casted := rawRequest.GetSession().(*oauth22.JWTSession) + if !casted { + return nil, fmt.Errorf("expected *oauth22.JWTSession. Found %v", reflect.TypeOf(rawRequest.GetSession())) + } + + formPostClaimValue, found := jwtSession.JWTClaims.Extra[encryptedFormPostClaim] + if found { + formPostParams, casted := formPostClaimValue.(map[string]interface{}) + if !casted { + return nil, fmt.Errorf("expected map[string]interface{}. Found %v", reflect.TypeOf(formPostClaimValue)) + } + + rawRequest.Form = url.Values{} + + for key, val := range formPostParams { + rawVal, err := s.encryptor.Decrypt(val.(string)) + if err != nil { + return nil, fmt.Errorf("failed to Decrypt claim [%v]. Error: %w", key, err) + } + + rawRequest.Form.Set(key, rawVal) + } + } + + return rawRequest, nil +} + +func (s StatelessTokenStore) InvalidateAuthorizeCodeSession(_ context.Context, _ string) (err error) { + return nil +} + +func (s StatelessTokenStore) GetAuthorizeCodeSession(ctx context.Context, code string, _ fosite.Session) (fosite.Requester, error) { + request, err := s.rehydrateSession(ctx, code) + if err != nil { + return nil, err + } + + if !request.RequestedScope.Has(accessTokenScope) { + return nil, fmt.Errorf("authcode not found [%v]", code) + } + + requestedScopes := request.RequestedScope + request.RequestedScope = fosite.Arguments{} + for _, requestedScope := range requestedScopes { + if requestedScope != accessTokenScope { + request.AppendRequestedScope(strings.TrimPrefix(requestedScope, requestedScopePrefix)) + } + } + + return request, nil +} + +func (s StatelessTokenStore) GetPKCERequestSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error) { + request, err := s.rehydrateSession(ctx, signature) + if err != nil { + return nil, err + } + + if !request.RequestedScope.Has(accessTokenScope) { + return nil, fmt.Errorf("PKCE request not found [%v]", signature) + } + + requestedScopes := request.RequestedScope + request.RequestedScope = fosite.Arguments{} + for _, requestedScope := range requestedScopes { + if requestedScope != accessTokenScope { + request.AppendRequestedScope(strings.TrimPrefix(requestedScope, requestedScopePrefix)) + } + } + + return request, nil +} + +func (s StatelessTokenStore) GetRefreshTokenSession(ctx context.Context, signature string, _ fosite.Session) (request fosite.Requester, err error) { + rawRequest, err := s.rehydrateSession(ctx, signature) + if err != nil { + return nil, err + } + + requestedScopes := rawRequest.GrantedScope + rawRequest.GrantedScope = fosite.Arguments{} + rawRequest.RequestedScope = fosite.Arguments{} + for _, scope := range requestedScopes { + rawRequest.AppendRequestedScope(strings.TrimPrefix(scope, requestedScopePrefix)) + rawRequest.GrantScope(strings.TrimPrefix(scope, requestedScopePrefix)) + } + + return rawRequest, nil +} + +func (s StatelessTokenStore) DeleteRefreshTokenSession(_ context.Context, _ string) (err error) { + return nil +} + +// StatelessCodeProvider offers a strategy that encodes authorization code and refresh tokens into JWT +// to avoid requiring storing these tokens on the server side. These tokens are usually short lived so storing them to a +// persistent store (e.g. DB) is not desired. A more suitable store would be an in-memory read-efficient store (e.g. +// Redis) however, that would add additional requirements on setting up flyteAdmin and hence why we are going with this +// strategy. +type StatelessCodeProvider struct { + oauth22.CoreStrategy + accessTokenLifespan time.Duration + authorizationCodeLifespan time.Duration + refreshTokenLifespan time.Duration + blockKey [auth.SymmetricKeyLength]byte +} + +func (p StatelessCodeProvider) AuthorizeCodeSignature(token string) string { + return token +} + +func (p StatelessCodeProvider) GenerateAccessToken(ctx context.Context, requester fosite.Requester) (token string, signature string, err error) { + requester.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().Add(p.accessTokenLifespan)) + return p.CoreStrategy.GenerateAccessToken(ctx, requester) +} + +func (p StatelessCodeProvider) GenerateAuthorizeCode(ctx context.Context, requester fosite.Requester) (token string, signature string, err error) { + rawRequest, casted := requester.(*fosite.AuthorizeRequest) + if !casted { + return "", "", fmt.Errorf("expected *fosite.AuthorizeRequest. Found [%v]", reflect.TypeOf(requester)) + } + + for _, requestedScope := range requester.GetRequestedScopes() { + if requestedScope == refreshTokenScope { + requester.GrantScope(refreshTokenScope) + } else { + requester.GrantScope(requestedScopePrefix + requestedScope) + } + } + + jwtSession, casted := rawRequest.Session.(*oauth22.JWTSession) + if !casted { + return "", "", fmt.Errorf("expected *oauth22.JWTSession. Found [%v]", reflect.TypeOf(rawRequest.Session)) + } + + m := make(map[string]interface{}, len(requester.GetRequestForm())) + + for key, val := range requester.GetRequestForm() { + if !formPostParamsToPersist.Has(key) { + continue + } + + if len(val) == 0 { + continue + } + + encryptedVal, err := p.Encrypt(val[0]) + if err != nil { + return "", "", fmt.Errorf("failed to encrypt key [%v]. Error: %w", key, err) + } + + m[key] = encryptedVal + } + + if len(m) > 0 { + jwtSession.JWTClaims.Extra[encryptedFormPostClaim] = m + } + + requester.GrantScope(accessTokenScope) + + // Because all codes/tokens are issued as if they are access tokens (basically a signed JWT that has the information + // we need to carry around), we need to pretend to change the access token lifespan to affect the resulting jwt token + // then revert that change after. + rawRequest.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().Add(p.authorizationCodeLifespan)) + token, _, err = p.CoreStrategy.GenerateAccessToken(ctx, requester) + rawRequest.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().Add(p.accessTokenLifespan)) + + return token, token, err +} + +func (p StatelessCodeProvider) ValidateAuthorizeCode(ctx context.Context, requester fosite.Requester, token string) (err error) { + return p.CoreStrategy.ValidateAccessToken(ctx, requester, token) +} + +func (p StatelessCodeProvider) RefreshTokenSignature(token string) string { + return token +} + +func (p StatelessCodeProvider) GenerateRefreshToken(ctx context.Context, requester fosite.Requester) (token string, signature string, err error) { + rawRequest, casted := requester.(*fosite.AccessRequest) + if !casted { + return "", "", fmt.Errorf("expected *fosite.AccessRequest. Found [%v]", reflect.TypeOf(requester)) + } + + grantedScopes := requester.GetGrantedScopes() + rawRequest.GrantedScope = fosite.Arguments{} + + for _, requestedScope := range grantedScopes { + if requestedScope == refreshTokenScope || requestedScope == accessTokenScope { + requester.GrantScope(requestedScope) + } else if strings.HasPrefix(requestedScope, requestedScopePrefix) { + requester.GrantScope(requestedScope) + } else { + requester.GrantScope(requestedScopePrefix + requestedScope) + } + } + + // Because all codes/tokens are issued as if they are access tokens (basically a signed JWT that has the information + // we need to carry around), we need to pretend to change the access token lifespan to affect the resulting jwt token + // then revert that change after. + rawRequest.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().Add(p.refreshTokenLifespan)) + token, _, err = p.CoreStrategy.GenerateAccessToken(ctx, requester) + rawRequest.GetSession().SetExpiresAt(fosite.AccessToken, time.Now().Add(p.accessTokenLifespan)) + + return token, token, err +} + +func (p StatelessCodeProvider) Encrypt(raw string) (string, error) { + return encryptString(raw, p.blockKey) +} + +func (p StatelessCodeProvider) Decrypt(encrypted string) (string, error) { + return decryptString(encrypted, p.blockKey) +} + +func (p StatelessCodeProvider) ValidateRefreshToken(ctx context.Context, requester fosite.Requester, token string) (err error) { + return p.CoreStrategy.ValidateAccessToken(ctx, requester, token) +} + +func NewStatelessCodeProvider(cfg config.AuthorizationServer, blockKey [auth.SymmetricKeyLength]byte, strategy oauth22.CoreStrategy) StatelessCodeProvider { + return StatelessCodeProvider{ + CoreStrategy: strategy, + accessTokenLifespan: cfg.AccessTokenLifespan.Duration, + authorizationCodeLifespan: cfg.AuthorizationCodeLifespan.Duration, + refreshTokenLifespan: cfg.RefreshTokenLifespan.Duration, + blockKey: blockKey, + } +} diff --git a/auth/authzserver/token.go b/auth/authzserver/token.go new file mode 100644 index 0000000000..f4bc1f9a3f --- /dev/null +++ b/auth/authzserver/token.go @@ -0,0 +1,81 @@ +package authzserver + +import ( + "net/http" + "reflect" + "strings" + + "github.com/ory/fosite" + + "github.com/flyteorg/flytestdlib/logger" + + "github.com/flyteorg/flyteadmin/auth/interfaces" +) + +var ( + supportedGrantTypes = []string{"client_credentials", "refresh_token", "authorization_code"} +) + +func getTokenEndpointHandler(authCtx interfaces.AuthenticationContext) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + tokenEndpoint(authCtx, writer, request) + } +} + +func tokenEndpoint(authCtx interfaces.AuthenticationContext, rw http.ResponseWriter, req *http.Request) { + // This context will be passed to all methods. + ctx := req.Context() + + oauth2Provider := authCtx.OAuth2Provider() + + // Create an empty session object which will be passed to the request handlers + emptySession := oauth2Provider.NewJWTSessionToken("", "", "", "", nil) + + // This will create an access request object and iterate through the registered TokenEndpointHandlers to validate the request. + accessRequest, err := oauth2Provider.NewAccessRequest(ctx, req, emptySession) + if err != nil { + logger.Infof(ctx, "Error occurred in NewAccessRequest: %+v", err) + oauth2Provider.WriteAccessError(rw, accessRequest, err) + return + } + + fositeAccessRequest, casted := accessRequest.(*fosite.AccessRequest) + if !casted { + logger.Errorf(ctx, "Invalid type. Expected *fosite.AccessRequest. Found: %v", reflect.TypeOf(accessRequest)) + oauth2Provider.WriteAccessError(rw, accessRequest, fosite.ErrInvalidRequest) + return + } + + // If this is a client_credentials grant, grant all requested scopes + // NewAccessRequest validated that all requested scopes the client is allowed to perform + // based on configured scope matching strategy. + // If this is authorization_code, we should have consented the user for the requested scopes, so grant those too + if fositeAccessRequest.GetGrantTypes().HasOneOf(supportedGrantTypes...) { + requestedScopes := fositeAccessRequest.GetRequestedScopes() + fositeAccessRequest.GrantedScope = fosite.Arguments{} + for _, scope := range requestedScopes { + fositeAccessRequest.GrantScope(strings.TrimPrefix(scope, requestedScopePrefix)) + } + + aud := GetIssuer(ctx, req, authCtx.Options()) + fositeAccessRequest.GrantAudience(aud) + } else { + logger.Infof(ctx, "Unsupported grant types [%+v]", fositeAccessRequest.GetGrantTypes()) + oauth2Provider.WriteAccessError(rw, fositeAccessRequest, fosite.ErrUnsupportedGrantType) + return + } + + // Next we create a response for the access request. Again, we iterate through the TokenEndpointHandlers + // and aggregate the result in response. + response, err := oauth2Provider.NewAccessResponse(ctx, fositeAccessRequest) + if err != nil { + logger.Infof(ctx, "Error occurred in NewAccessResponse: %+v", err) + oauth2Provider.WriteAccessError(rw, fositeAccessRequest, err) + return + } + + // All done, send the response. + oauth2Provider.WriteAccessResponse(rw, fositeAccessRequest, response) + + // The client now has a valid access token +} diff --git a/auth/authzserver/token_test.go b/auth/authzserver/token_test.go new file mode 100644 index 0000000000..fbe48defd6 --- /dev/null +++ b/auth/authzserver/token_test.go @@ -0,0 +1,115 @@ +package authzserver + +import ( + "bytes" + "context" + "crypto/rsa" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" + "time" + + jwtgo "github.com/dgrijalva/jwt-go" + + "github.com/flyteorg/flyteadmin/auth/config" + + "github.com/stretchr/testify/assert" + + "github.com/flyteorg/flyteadmin/auth/interfaces/mocks" +) + +func Test_tokenEndpoint(t *testing.T) { + t.Run("Empty request", func(t *testing.T) { + body := &bytes.Buffer{} + req := httptest.NewRequest(http.MethodPost, "/token", body) + rw := httptest.NewRecorder() + oauth2Provider, _ := newMockProvider(t) + mockAuthCtx := &mocks.AuthenticationContext{} + mockAuthCtx.OnOAuth2Provider().Return(oauth2Provider) + mockAuthCtx.OnOptions().Return(&config.Config{}) + + tokenEndpoint(mockAuthCtx, rw, req) + assert.NotEmpty(t, rw.Body.String()) + assert.Equal(t, http.StatusBadRequest, rw.Code) + }) + + t.Run("Valid token request", func(t *testing.T) { + // create a signer for rsa 256 + tok := jwtgo.New(jwtgo.GetSigningMethod("RS256")) + + oauth2Provider, secrets := newMockProvider(t) + + //tok.Header[KeyIDClaim] = secrets.TokenSigningRSAPrivateKey.KeyID() + tok.Claims = &CustomClaimsExample{ + StandardClaims: &jwtgo.StandardClaims{}, + ClientID: "flytectl", + Scopes: []string{"access_token", "offline"}, + } + + // Create token string + authCode, err := tok.SignedString(secrets.TokenSigningRSAPrivateKey) + assert.NoError(t, err) + + payload := url.Values{ + "code": {authCode}, + "grant_type": {"authorization_code"}, + "scope": {"all", "offline"}, + } + + req := httptest.NewRequest(http.MethodPost, "/token", bytes.NewReader([]byte(payload.Encode()))) + req.PostForm = payload + req.Header.Set("authorization", basicAuth("flytectl", "foobar")) + mockAuthCtx := &mocks.AuthenticationContext{} + mockAuthCtx.OnOAuth2Provider().Return(oauth2Provider) + mockAuthCtx.OnOptions().Return(&config.Config{}) + + expectedAccessTokenExpiry := time.Now().Add(config.DefaultConfig.AppAuth.SelfAuthServer.AccessTokenLifespan.Duration).Unix() + expectedRefreshTokenExpiry := time.Now().Add(config.DefaultConfig.AppAuth.SelfAuthServer.RefreshTokenLifespan.Duration).Unix() + + rw := httptest.NewRecorder() + tokenEndpoint(mockAuthCtx, rw, req) + if !assert.Equal(t, http.StatusOK, rw.Code) { + t.FailNow() + } + + m := map[string]interface{}{} + assert.NoError(t, json.Unmarshal(rw.Body.Bytes(), &m)) + assert.Equal(t, 0.5*time.Hour.Seconds()-1, m["expires_in"]) + + assert.NotEmpty(t, m["access_token"]) + // Parse and validate the token. + parsedToken, err := jwtgo.Parse(m["access_token"].(string), func(tok *jwtgo.Token) (interface{}, error) { + keySet, err := newJSONWebKeySet([]rsa.PublicKey{secrets.TokenSigningRSAPrivateKey.PublicKey}) + assert.NoError(t, err) + + return findPublicKeyForTokenOrFirst(context.Background(), tok, keySet) + }) + + assert.NoError(t, err) + + claims := parsedToken.Claims.(jwtgo.MapClaims) + assert.True(t, claims.VerifyExpiresAt(expectedAccessTokenExpiry, true)) + + assert.NotEmpty(t, m["refresh_token"]) + // Parse and validate the token. + parsedToken, err = jwtgo.Parse(m["refresh_token"].(string), func(tok *jwtgo.Token) (interface{}, error) { + keySet, err := newJSONWebKeySet([]rsa.PublicKey{secrets.TokenSigningRSAPrivateKey.PublicKey}) + assert.NoError(t, err) + + return findPublicKeyForTokenOrFirst(context.Background(), tok, keySet) + }) + + assert.NoError(t, err) + + claims = parsedToken.Claims.(jwtgo.MapClaims) + assert.True(t, claims.VerifyExpiresAt(expectedRefreshTokenExpiry, true)) + }) +} + +func basicAuth(username, password string) string { + return "Basic " + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))) +} diff --git a/auth/authzserver/utils.go b/auth/authzserver/utils.go new file mode 100644 index 0000000000..306f9ae033 --- /dev/null +++ b/auth/authzserver/utils.go @@ -0,0 +1,57 @@ +package authzserver + +import ( + "context" + "encoding/base64" + "net/http" + + "github.com/flyteorg/flyteadmin/auth" + "github.com/flyteorg/flyteadmin/auth/config" + "github.com/gtank/cryptopasta" + "github.com/ory/fosite" +) + +func interfaceSliceToStringSlice(raw []interface{}) []string { + res := make([]string, 0, len(raw)) + for _, item := range raw { + res = append(res, item.(string)) + } + + return res +} + +func toClientIface(clients map[string]*fosite.DefaultClient) map[string]fosite.Client { + res := make(map[string]fosite.Client, len(clients)) + for clientID, client := range clients { + res[clientID] = client + } + + return res +} + +func GetIssuer(ctx context.Context, req *http.Request, cfg *config.Config) string { + if configIssuer := cfg.AppAuth.SelfAuthServer.Issuer; len(configIssuer) > 0 { + return configIssuer + } + + return auth.GetPublicURL(ctx, req, cfg).String() +} + +func encryptString(plainTextCode string, blockKey [auth.SymmetricKeyLength]byte) (string, error) { + cypher, err := cryptopasta.Encrypt([]byte(plainTextCode), &blockKey) + if err != nil { + return "", err + } + + return base64.RawStdEncoding.EncodeToString(cypher), nil +} + +func decryptString(encryptedEncoded string, blockKey [auth.SymmetricKeyLength]byte) (string, error) { + cypher, err := base64.RawStdEncoding.DecodeString(encryptedEncoded) + if err != nil { + return "", err + } + + raw, err := cryptopasta.Decrypt(cypher, &blockKey) + return string(raw), err +} diff --git a/auth/config/authorizationservertype_enumer.go b/auth/config/authorizationservertype_enumer.go new file mode 100644 index 0000000000..a5c7dc2766 --- /dev/null +++ b/auth/config/authorizationservertype_enumer.go @@ -0,0 +1,68 @@ +// Code generated by "enumer --type=AuthorizationServerType --trimprefix=AuthorizationServerType -json"; DO NOT EDIT. + +// +package config + +import ( + "encoding/json" + "fmt" +) + +const _AuthorizationServerTypeName = "SelfExternal" + +var _AuthorizationServerTypeIndex = [...]uint8{0, 4, 12} + +func (i AuthorizationServerType) String() string { + if i < 0 || i >= AuthorizationServerType(len(_AuthorizationServerTypeIndex)-1) { + return fmt.Sprintf("AuthorizationServerType(%d)", i) + } + return _AuthorizationServerTypeName[_AuthorizationServerTypeIndex[i]:_AuthorizationServerTypeIndex[i+1]] +} + +var _AuthorizationServerTypeValues = []AuthorizationServerType{0, 1} + +var _AuthorizationServerTypeNameToValueMap = map[string]AuthorizationServerType{ + _AuthorizationServerTypeName[0:4]: 0, + _AuthorizationServerTypeName[4:12]: 1, +} + +// AuthorizationServerTypeString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func AuthorizationServerTypeString(s string) (AuthorizationServerType, error) { + if val, ok := _AuthorizationServerTypeNameToValueMap[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to AuthorizationServerType values", s) +} + +// AuthorizationServerTypeValues returns all values of the enum +func AuthorizationServerTypeValues() []AuthorizationServerType { + return _AuthorizationServerTypeValues +} + +// IsAAuthorizationServerType returns "true" if the value is listed in the enum definition. "false" otherwise +func (i AuthorizationServerType) IsAAuthorizationServerType() bool { + for _, v := range _AuthorizationServerTypeValues { + if i == v { + return true + } + } + return false +} + +// MarshalJSON implements the json.Marshaler interface for AuthorizationServerType +func (i AuthorizationServerType) MarshalJSON() ([]byte, error) { + return json.Marshal(i.String()) +} + +// UnmarshalJSON implements the json.Unmarshaler interface for AuthorizationServerType +func (i *AuthorizationServerType) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("AuthorizationServerType should be a string, got %s", data) + } + + var err error + *i, err = AuthorizationServerTypeString(s) + return err +} diff --git a/auth/config/config.go b/auth/config/config.go new file mode 100644 index 0000000000..d3bb232012 --- /dev/null +++ b/auth/config/config.go @@ -0,0 +1,244 @@ +package config + +import ( + "net/url" + "time" + + "github.com/ory/fosite" + + "github.com/flyteorg/flytestdlib/config" +) + +//go:generate pflags Config --default-var=DefaultConfig +//go:generate enumer --type=AuthorizationServerType --trimprefix=AuthorizationServerType -json + +type SecretName = string + +const ( + // #nosec + // Default OIdC client secret name to use. + SecretNameOIdCClientSecret SecretName = "oidc_client_secret" + // #nosec + SecretNameCookieHashKey SecretName = "cookie_hash_key" + // #nosec + SecretNameCookieBlockKey SecretName = "cookie_block_key" + // #nosec + // Base64 encoded secret of exactly 32 bytes + SecretNameClaimSymmetricKey SecretName = "claim_symmetric_key" + // #nosec + // PrivateKey is used to sign JWT tokens. The default strategy uses RS256 (RSA Signature with SHA-256) + SecretNameTokenSigningRSAKey SecretName = "token_rsa_key.pem" + // #nosec + // PrivateKey that was used to sign old JWT tokens. The default strategy uses RS256 (RSA Signature with SHA-256) + // This is used to support key rotation. When present, it'll only be used to validate incoming tokens. New tokens + // will not be issued using this key. + SecretNameOldTokenSigningRSAKey SecretName = "token_rsa_key_old.pem" +) + +// AuthorizationServerType defines the type of Authorization Server to use. +type AuthorizationServerType int + +const ( + // AuthorizationServerTypeSelf determines that FlyteAdmin should act as the authorization server to serve + // OAuth2 token requests + AuthorizationServerTypeSelf AuthorizationServerType = iota + + // AuthorizationServerTypeExternal determines that FlyteAdmin should rely on an external authorization server (e.g. + // Okta) to serve OAuth2 token requests + AuthorizationServerTypeExternal +) + +var ( + DefaultConfig = &Config{ + // Please see the comments in this struct's definition for more information + HTTPAuthorizationHeader: "flyte-authorization", + GrpcAuthorizationHeader: "flyte-authorization", + UserAuth: UserAuthConfig{ + RedirectURL: config.URL{URL: *MustParseURL("/console")}, + CookieHashKeySecretName: SecretNameCookieHashKey, + CookieBlockKeySecretName: SecretNameCookieBlockKey, + OpenID: OpenIDOptions{ + ClientSecretName: SecretNameOIdCClientSecret, + // Default claims that should be supported by any OIdC server. Refer to https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims + // for a complete list. + Scopes: []string{ + "openid", + "profile", + }, + }, + }, + AppAuth: OAuth2Options{ + AuthServerType: AuthorizationServerTypeSelf, + ThirdParty: ThirdPartyConfigOptions{ + FlyteClientConfig: FlyteClientConfig{ + ClientID: "flytectl", + RedirectURI: "http://localhost:53593/callback", + Scopes: []string{"all", "offline"}, + }, + }, + SelfAuthServer: AuthorizationServer{ + AccessTokenLifespan: config.Duration{Duration: 30 * time.Minute}, + RefreshTokenLifespan: config.Duration{Duration: 60 * time.Minute}, + AuthorizationCodeLifespan: config.Duration{Duration: 5 * time.Minute}, + ClaimSymmetricEncryptionKeySecretName: SecretNameClaimSymmetricKey, + TokenSigningRSAKeySecretName: SecretNameTokenSigningRSAKey, + OldTokenSigningRSAKeySecretName: SecretNameOldTokenSigningRSAKey, + StaticClients: map[string]*fosite.DefaultClient{ + "flyte-cli": { + ID: "flyte-cli", + RedirectURIs: []string{"http://localhost:53593/callback", "http://localhost:12345/callback"}, + ResponseTypes: []string{"code", "token"}, + GrantTypes: []string{"refresh_token", "authorization_code"}, + Scopes: []string{"all", "offline", "access_token"}, + Public: true, + }, + "flytectl": { + ID: "flytectl", + RedirectURIs: []string{"http://localhost:53593/callback", "http://localhost:12345/callback"}, + ResponseTypes: []string{"code", "token"}, + GrantTypes: []string{"refresh_token", "authorization_code"}, + Scopes: []string{"all", "offline", "access_token"}, + Public: true, + }, + "flytepropeller": { + ID: "flytepropeller", + Secret: []byte(`$2a$06$pxs1AkG81Kvrhpml1QiLSOQaTk9eePrU/7Yab9y07h3x0TglbaoT6`), // = "foobar" + RedirectURIs: []string{"http://localhost:3846/callback"}, + ResponseTypes: []string{"token"}, + GrantTypes: []string{"refresh_token", "client_credentials"}, + Scopes: []string{"all", "offline", "access_token"}, + }, + }, + }, + }, + } + + cfgSection = config.MustRegisterSection("auth", DefaultConfig) +) + +type Config struct { + // These settings are for non-SSL authentication modes, where Envoy is handling SSL termination + // This is not yet used, but this is the HTTP variant of the setting below. + HTTPAuthorizationHeader string `json:"httpAuthorizationHeader"` + + // In order to support deployments of this Admin service where Envoy is terminating SSL connections, the metadata + // header name cannot be "authorization", which is the standard metadata name. Envoy has special handling for that + // name. Instead, there is a gRPC interceptor, GetAuthenticationCustomMetadataInterceptor, that will translate + // incoming metadata headers with this config setting's name, into that standard header + GrpcAuthorizationHeader string `json:"grpcAuthorizationHeader"` + + // To help ease migration, it was helpful to be able to only selectively enforce authentication. The + // dimension that made the most sense to cut by at time of writing is HTTP vs gRPC as the web UI mainly used HTTP + // and the backend used mostly gRPC. Cutting by individual endpoints is another option but it possibly falls more + // into the realm of authorization rather than authentication. + DisableForHTTP bool `json:"disableForHttp" pflag:",Disables auth enforcement on HTTP Endpoints."` + DisableForGrpc bool `json:"disableForGrpc" pflag:",Disables auth enforcement on Grpc Endpoints."` + + // AuthorizedURIs is optional and defines the set of URIs that clients are allowed to visit the service on. If set, + // the system will attempt to match the incoming host to the first authorized URIs and use that (including the scheme) + // when generating metadata endpoints and when validating audience and issuer claims. If no matching authorizedUri + // is found, it'll default to the first one. If not provided, the urls will be deduced based on the request url and + // the `secure` setting. + AuthorizedURIs []config.URL `json:"authorizedUris" pflag:",Optional: Defines the set of URIs that clients are allowed to visit the service on. If set, the system will attempt to match the incoming host to the first authorized URIs and use that (including the scheme) when generating metadata endpoints and when validating audience and issuer claims. If not provided, the urls will be deduced based on the request url and the 'secure' setting."` + + // UserAuth settings used to authenticate end users in web-browsers. + UserAuth UserAuthConfig `json:"userAuth" pflag:",Defines Auth options for users."` + + // AppAuth settings used to authenticate and control/limit access scopes for apps. + AppAuth OAuth2Options `json:"appAuth" pflag:",Defines Auth options for apps. UserAuth must be enabled for AppAuth to work."` +} + +type AuthorizationServer struct { + // Defines the issuer to use when issuing and validating tokens. The default value is https:/// + Issuer string `json:"issuer" pflag:",Defines the issuer to use when issuing and validating tokens. The default value is https:///"` + + // Defines the lifespan of issued access tokens. + AccessTokenLifespan config.Duration `json:"accessTokenLifespan" pflag:",Defines the lifespan of issued access tokens."` + + // Defines the lifespan of issued access tokens. + RefreshTokenLifespan config.Duration `json:"refreshTokenLifespan" pflag:",Defines the lifespan of issued access tokens."` + + // Defines the lifespan of issued access tokens. + AuthorizationCodeLifespan config.Duration `json:"authorizationCodeLifespan" pflag:",Defines the lifespan of issued access tokens."` + + // Secret names, defaults are set in DefaultConfig variable above but are possible to override through configs. + ClaimSymmetricEncryptionKeySecretName string `json:"claimSymmetricEncryptionKeySecretName" pflag:",OPTIONAL: Secret name to use to encrypt claims in authcode token."` + TokenSigningRSAKeySecretName string `json:"tokenSigningRSAKeySecretName" pflag:",OPTIONAL: Secret name to use to retrieve RSA Signing Key."` + OldTokenSigningRSAKeySecretName string `json:"oldTokenSigningRSAKeySecretName" pflag:",OPTIONAL: Secret name to use to retrieve Old RSA Signing Key. This can be useful during key rotation to continue to accept older tokens."` + + // A list of clients to grant access to. + StaticClients map[string]*fosite.DefaultClient `json:"staticClients" pflag:"-,Defines statically defined list of clients to allow."` +} + +type ExternalAuthorizationServer struct { + // BaseURL should be the base url of the authorization server that you are trying to hit. With Okta for instance, it will look something like https://company.okta.com/oauth2/abcdef123456789/ + // If not provided, the OpenID.BaseURL will be assumed instead. + BaseURL config.URL `json:"baseUrl" pflag:",This should be the base url of the authorization server that you are trying to hit. With Okta for instance, it will look something like https://company.okta.com/oauth2/abcdef123456789/"` +} + +// OAuth2Options defines settings for app auth. +type OAuth2Options struct { + // AuthServerType defines the type of AuthServer to connect to. + AuthServerType AuthorizationServerType `json:"authServerType" pflag:"-,Determines authorization server type to use. Additional config should be provided for the chosen AuthorizationServer"` + + // SelfAuthServer defines settings for running authorization server locally. + SelfAuthServer AuthorizationServer `json:"selfAuthServer" pflag:",Authorization Server config to run as a service. Use this when using an IdP that does not offer a custom OAuth2 Authorization Server."` + + // ExternalAuthServer defines settings for connecting with an external authorization server. + ExternalAuthServer ExternalAuthorizationServer `json:"externalAuthServer" pflag:",External Authorization Server config."` + + // ThirdParty defines settings for the public client flyte-clients (e.g. flytectl, flytecli) should use. + ThirdParty ThirdPartyConfigOptions `json:"thirdPartyConfig" pflag:",Defines settings to instruct flyte cli tools (and optionally others) on what config to use to setup their client."` +} + +type UserAuthConfig struct { + // This is where the user will be redirected to at the end of the flow, but you should not use it. Instead, + // the initial /login handler should be called with a redirect_url parameter, which will get saved to a cookie. + // This setting will only be used when that cookie is missing. + // See the login handler code for more comments. + RedirectURL config.URL `json:"redirectUrl"` + + // OpenID defines settings for connecting and trusting an OpenIDConnect provider. + OpenID OpenIDOptions `json:"openId" pflag:",OpenID Configuration for User Auth"` + // Possibly add basicAuth & SAML/p support. + + // Secret names, defaults are set in DefaultConfig variable above but are possible to override through configs. + CookieHashKeySecretName string `json:"cookieHashKeySecretName" pflag:",OPTIONAL: Secret name to use for cookie hash key."` + CookieBlockKeySecretName string `json:"cookieBlockKeySecretName" pflag:",OPTIONAL: Secret name to use for cookie block key."` +} + +type OpenIDOptions struct { + // The client ID for Admin in your IDP + // See https://tools.ietf.org/html/rfc6749#section-2.2 for more information + ClientID string `json:"clientId"` + + // The client secret used in the exchange of the authorization code for the token. + // https://tools.ietf.org/html/rfc6749#section-2.3 + ClientSecretName string `json:"clientSecretName"` + + // Deprecated: Please use ClientSecretName instead. + DeprecatedClientSecretFile string `json:"clientSecretFile"` + + // This should be the base url of the authorization server that you are trying to hit. With Okta for instance, it + // will look something like https://company.okta.com/oauth2/abcdef123456789/ + BaseURL config.URL `json:"baseUrl"` + + // Provides a list of scopes to request from the IDP when authenticating. Default value requests claims that should + // be supported by any OIdC server. Refer to https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims for + // a complete list. Other providers might support additional scopes that you can define in a config. + Scopes []string `json:"scopes"` +} + +func GetConfig() *Config { + return cfgSection.GetConfig().(*Config) +} + +// MustParseURL panics if the provided url fails parsing. Should only be used in package initialization or tests. +func MustParseURL(rawURL string) *url.URL { + res, err := url.Parse(rawURL) + if err != nil { + panic(err) + } + + return res +} diff --git a/auth/config/config_flags.go b/auth/config/config_flags.go new file mode 100755 index 0000000000..abb77f3b16 --- /dev/null +++ b/auth/config/config_flags.go @@ -0,0 +1,69 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots. + +package config + +import ( + "encoding/json" + "reflect" + + "fmt" + + "github.com/spf13/pflag" +) + +// If v is a pointer, it will get its element value or the zero value of the element type. +// If v is not a pointer, it will return it as is. +func (Config) elemValueOrNil(v interface{}) interface{} { + if t := reflect.TypeOf(v); t.Kind() == reflect.Ptr { + if reflect.ValueOf(v).IsNil() { + return reflect.Zero(t.Elem()).Interface() + } else { + return reflect.ValueOf(v).Interface() + } + } else if v == nil { + return reflect.Zero(t).Interface() + } + + return v +} + +func (Config) mustMarshalJSON(v json.Marshaler) string { + raw, err := v.MarshalJSON() + if err != nil { + panic(err) + } + + return string(raw) +} + +// GetPFlagSet will return strongly types pflags for all fields in Config and its nested types. The format of the +// flags is json-name.json-sub-name... etc. +func (cfg Config) GetPFlagSet(prefix string) *pflag.FlagSet { + cmdFlags := pflag.NewFlagSet("Config", pflag.ExitOnError) + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "httpAuthorizationHeader"), DefaultConfig.HTTPAuthorizationHeader, "") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "grpcAuthorizationHeader"), DefaultConfig.GrpcAuthorizationHeader, "") + cmdFlags.Bool(fmt.Sprintf("%v%v", prefix, "disableForHttp"), DefaultConfig.DisableForHTTP, "Disables auth enforcement on HTTP Endpoints.") + cmdFlags.Bool(fmt.Sprintf("%v%v", prefix, "disableForGrpc"), DefaultConfig.DisableForGrpc, "Disables auth enforcement on Grpc Endpoints.") + cmdFlags.StringSlice(fmt.Sprintf("%v%v", prefix, "authorizedUris"), []string{}, "Optional: Defines the set of URIs that clients are allowed to visit the service on. If set, the system will attempt to match the incoming host to the first authorized URIs and use that (including the scheme) when generating metadata endpoints and when validating audience and issuer claims. If not provided, the urls will be deduced based on the request url and the 'secure' setting.") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "userAuth.redirectUrl"), DefaultConfig.UserAuth.RedirectURL.String(), "") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "userAuth.openId.clientId"), DefaultConfig.UserAuth.OpenID.ClientID, "") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "userAuth.openId.clientSecretName"), DefaultConfig.UserAuth.OpenID.ClientSecretName, "") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "userAuth.openId.clientSecretFile"), DefaultConfig.UserAuth.OpenID.DeprecatedClientSecretFile, "") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "userAuth.openId.baseUrl"), DefaultConfig.UserAuth.OpenID.BaseURL.String(), "") + cmdFlags.StringSlice(fmt.Sprintf("%v%v", prefix, "userAuth.openId.scopes"), []string{}, "") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "userAuth.cookieHashKeySecretName"), DefaultConfig.UserAuth.CookieHashKeySecretName, "OPTIONAL: Secret name to use for cookie hash key.") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "userAuth.cookieBlockKeySecretName"), DefaultConfig.UserAuth.CookieBlockKeySecretName, "OPTIONAL: Secret name to use for cookie block key.") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.selfAuthServer.issuer"), DefaultConfig.AppAuth.SelfAuthServer.Issuer, "Defines the issuer to use when issuing and validating tokens. The default value is https:///") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.selfAuthServer.accessTokenLifespan"), DefaultConfig.AppAuth.SelfAuthServer.AccessTokenLifespan.String(), "Defines the lifespan of issued access tokens.") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.selfAuthServer.refreshTokenLifespan"), DefaultConfig.AppAuth.SelfAuthServer.RefreshTokenLifespan.String(), "Defines the lifespan of issued access tokens.") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.selfAuthServer.authorizationCodeLifespan"), DefaultConfig.AppAuth.SelfAuthServer.AuthorizationCodeLifespan.String(), "Defines the lifespan of issued access tokens.") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.selfAuthServer.claimSymmetricEncryptionKeySecretName"), DefaultConfig.AppAuth.SelfAuthServer.ClaimSymmetricEncryptionKeySecretName, "OPTIONAL: Secret name to use to encrypt claims in authcode token.") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.selfAuthServer.tokenSigningRSAKeySecretName"), DefaultConfig.AppAuth.SelfAuthServer.TokenSigningRSAKeySecretName, "OPTIONAL: Secret name to use to retrieve RSA Signing Key.") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.selfAuthServer.oldTokenSigningRSAKeySecretName"), DefaultConfig.AppAuth.SelfAuthServer.OldTokenSigningRSAKeySecretName, "OPTIONAL: Secret name to use to retrieve Old RSA Signing Key. This can be useful during key rotation to continue to accept older tokens.") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.externalAuthServer.baseUrl"), DefaultConfig.AppAuth.ExternalAuthServer.BaseURL.String(), "This should be the base url of the authorization server that you are trying to hit. With Okta for instance, it will look something like https://company.okta.com/oauth2/abcdef123456789/") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.thirdPartyConfig.flyteClient.clientId"), DefaultConfig.AppAuth.ThirdParty.FlyteClientConfig.ClientID, "public identifier for the app which handles authorization for a Flyte deployment") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "appAuth.thirdPartyConfig.flyteClient.redirectUri"), DefaultConfig.AppAuth.ThirdParty.FlyteClientConfig.RedirectURI, "This is the callback uri registered with the app which handles authorization for a Flyte deployment") + cmdFlags.StringSlice(fmt.Sprintf("%v%v", prefix, "appAuth.thirdPartyConfig.flyteClient.scopes"), []string{}, "Recommended scopes for the client to request.") + return cmdFlags +} diff --git a/auth/config/config_flags_test.go b/auth/config/config_flags_test.go new file mode 100755 index 0000000000..dc3a8d634f --- /dev/null +++ b/auth/config/config_flags_test.go @@ -0,0 +1,630 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots. + +package config + +import ( + "encoding/json" + "fmt" + "reflect" + "strings" + "testing" + + "github.com/mitchellh/mapstructure" + "github.com/stretchr/testify/assert" +) + +var dereferencableKindsConfig = map[reflect.Kind]struct{}{ + reflect.Array: {}, reflect.Chan: {}, reflect.Map: {}, reflect.Ptr: {}, reflect.Slice: {}, +} + +// Checks if t is a kind that can be dereferenced to get its underlying type. +func canGetElementConfig(t reflect.Kind) bool { + _, exists := dereferencableKindsConfig[t] + return exists +} + +// This decoder hook tests types for json unmarshaling capability. If implemented, it uses json unmarshal to build the +// object. Otherwise, it'll just pass on the original data. +func jsonUnmarshalerHookConfig(_, to reflect.Type, data interface{}) (interface{}, error) { + unmarshalerType := reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() + if to.Implements(unmarshalerType) || reflect.PtrTo(to).Implements(unmarshalerType) || + (canGetElementConfig(to.Kind()) && to.Elem().Implements(unmarshalerType)) { + + raw, err := json.Marshal(data) + if err != nil { + fmt.Printf("Failed to marshal Data: %v. Error: %v. Skipping jsonUnmarshalHook", data, err) + return data, nil + } + + res := reflect.New(to).Interface() + err = json.Unmarshal(raw, &res) + if err != nil { + fmt.Printf("Failed to umarshal Data: %v. Error: %v. Skipping jsonUnmarshalHook", data, err) + return data, nil + } + + return res, nil + } + + return data, nil +} + +func decode_Config(input, result interface{}) error { + config := &mapstructure.DecoderConfig{ + TagName: "json", + WeaklyTypedInput: true, + Result: result, + DecodeHook: mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.StringToSliceHookFunc(","), + jsonUnmarshalerHookConfig, + ), + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(input) +} + +func join_Config(arr interface{}, sep string) string { + listValue := reflect.ValueOf(arr) + strs := make([]string, 0, listValue.Len()) + for i := 0; i < listValue.Len(); i++ { + strs = append(strs, fmt.Sprintf("%v", listValue.Index(i))) + } + + return strings.Join(strs, sep) +} + +func testDecodeJson_Config(t *testing.T, val, result interface{}) { + assert.NoError(t, decode_Config(val, result)) +} + +func testDecodeSlice_Config(t *testing.T, vStringSlice, result interface{}) { + assert.NoError(t, decode_Config(vStringSlice, result)) +} + +func TestConfig_GetPFlagSet(t *testing.T) { + val := Config{} + cmdFlags := val.GetPFlagSet("") + assert.True(t, cmdFlags.HasFlags()) +} + +func TestConfig_SetFlags(t *testing.T) { + actual := Config{} + cmdFlags := actual.GetPFlagSet("") + assert.True(t, cmdFlags.HasFlags()) + + t.Run("Test_httpAuthorizationHeader", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("httpAuthorizationHeader"); err == nil { + assert.Equal(t, string(DefaultConfig.HTTPAuthorizationHeader), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("httpAuthorizationHeader", testValue) + if vString, err := cmdFlags.GetString("httpAuthorizationHeader"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.HTTPAuthorizationHeader) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_grpcAuthorizationHeader", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("grpcAuthorizationHeader"); err == nil { + assert.Equal(t, string(DefaultConfig.GrpcAuthorizationHeader), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("grpcAuthorizationHeader", testValue) + if vString, err := cmdFlags.GetString("grpcAuthorizationHeader"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.GrpcAuthorizationHeader) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_disableForHttp", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vBool, err := cmdFlags.GetBool("disableForHttp"); err == nil { + assert.Equal(t, bool(DefaultConfig.DisableForHTTP), vBool) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("disableForHttp", testValue) + if vBool, err := cmdFlags.GetBool("disableForHttp"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vBool), &actual.DisableForHTTP) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_disableForGrpc", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vBool, err := cmdFlags.GetBool("disableForGrpc"); err == nil { + assert.Equal(t, bool(DefaultConfig.DisableForGrpc), vBool) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("disableForGrpc", testValue) + if vBool, err := cmdFlags.GetBool("disableForGrpc"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vBool), &actual.DisableForGrpc) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_authorizedUris", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vStringSlice, err := cmdFlags.GetStringSlice("authorizedUris"); err == nil { + assert.Equal(t, []string([]string{}), vStringSlice) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1,1" + + cmdFlags.Set("authorizedUris", testValue) + if vStringSlice, err := cmdFlags.GetStringSlice("authorizedUris"); err == nil { + testDecodeSlice_Config(t, vStringSlice, &actual.AuthorizedURIs) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_userAuth.redirectUrl", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("userAuth.redirectUrl"); err == nil { + assert.Equal(t, string(DefaultConfig.UserAuth.RedirectURL.String()), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := DefaultConfig.UserAuth.RedirectURL.String() + + cmdFlags.Set("userAuth.redirectUrl", testValue) + if vString, err := cmdFlags.GetString("userAuth.redirectUrl"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.UserAuth.RedirectURL) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_userAuth.openId.clientId", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("userAuth.openId.clientId"); err == nil { + assert.Equal(t, string(DefaultConfig.UserAuth.OpenID.ClientID), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("userAuth.openId.clientId", testValue) + if vString, err := cmdFlags.GetString("userAuth.openId.clientId"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.UserAuth.OpenID.ClientID) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_userAuth.openId.clientSecretName", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("userAuth.openId.clientSecretName"); err == nil { + assert.Equal(t, string(DefaultConfig.UserAuth.OpenID.ClientSecretName), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("userAuth.openId.clientSecretName", testValue) + if vString, err := cmdFlags.GetString("userAuth.openId.clientSecretName"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.UserAuth.OpenID.ClientSecretName) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_userAuth.openId.clientSecretFile", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("userAuth.openId.clientSecretFile"); err == nil { + assert.Equal(t, string(DefaultConfig.UserAuth.OpenID.DeprecatedClientSecretFile), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("userAuth.openId.clientSecretFile", testValue) + if vString, err := cmdFlags.GetString("userAuth.openId.clientSecretFile"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.UserAuth.OpenID.DeprecatedClientSecretFile) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_userAuth.openId.baseUrl", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("userAuth.openId.baseUrl"); err == nil { + assert.Equal(t, string(DefaultConfig.UserAuth.OpenID.BaseURL.String()), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := DefaultConfig.UserAuth.OpenID.BaseURL.String() + + cmdFlags.Set("userAuth.openId.baseUrl", testValue) + if vString, err := cmdFlags.GetString("userAuth.openId.baseUrl"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.UserAuth.OpenID.BaseURL) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_userAuth.openId.scopes", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vStringSlice, err := cmdFlags.GetStringSlice("userAuth.openId.scopes"); err == nil { + assert.Equal(t, []string([]string{}), vStringSlice) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := join_Config("1,1", ",") + + cmdFlags.Set("userAuth.openId.scopes", testValue) + if vStringSlice, err := cmdFlags.GetStringSlice("userAuth.openId.scopes"); err == nil { + testDecodeSlice_Config(t, join_Config(vStringSlice, ","), &actual.UserAuth.OpenID.Scopes) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_userAuth.cookieHashKeySecretName", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("userAuth.cookieHashKeySecretName"); err == nil { + assert.Equal(t, string(DefaultConfig.UserAuth.CookieHashKeySecretName), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("userAuth.cookieHashKeySecretName", testValue) + if vString, err := cmdFlags.GetString("userAuth.cookieHashKeySecretName"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.UserAuth.CookieHashKeySecretName) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_userAuth.cookieBlockKeySecretName", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("userAuth.cookieBlockKeySecretName"); err == nil { + assert.Equal(t, string(DefaultConfig.UserAuth.CookieBlockKeySecretName), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("userAuth.cookieBlockKeySecretName", testValue) + if vString, err := cmdFlags.GetString("userAuth.cookieBlockKeySecretName"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.UserAuth.CookieBlockKeySecretName) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.selfAuthServer.issuer", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.issuer"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.SelfAuthServer.Issuer), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("appAuth.selfAuthServer.issuer", testValue) + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.issuer"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.SelfAuthServer.Issuer) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.selfAuthServer.accessTokenLifespan", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.accessTokenLifespan"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.SelfAuthServer.AccessTokenLifespan.String()), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := DefaultConfig.AppAuth.SelfAuthServer.AccessTokenLifespan.String() + + cmdFlags.Set("appAuth.selfAuthServer.accessTokenLifespan", testValue) + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.accessTokenLifespan"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.SelfAuthServer.AccessTokenLifespan) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.selfAuthServer.refreshTokenLifespan", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.refreshTokenLifespan"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.SelfAuthServer.RefreshTokenLifespan.String()), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := DefaultConfig.AppAuth.SelfAuthServer.RefreshTokenLifespan.String() + + cmdFlags.Set("appAuth.selfAuthServer.refreshTokenLifespan", testValue) + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.refreshTokenLifespan"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.SelfAuthServer.RefreshTokenLifespan) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.selfAuthServer.authorizationCodeLifespan", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.authorizationCodeLifespan"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.SelfAuthServer.AuthorizationCodeLifespan.String()), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := DefaultConfig.AppAuth.SelfAuthServer.AuthorizationCodeLifespan.String() + + cmdFlags.Set("appAuth.selfAuthServer.authorizationCodeLifespan", testValue) + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.authorizationCodeLifespan"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.SelfAuthServer.AuthorizationCodeLifespan) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.selfAuthServer.claimSymmetricEncryptionKeySecretName", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.claimSymmetricEncryptionKeySecretName"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.SelfAuthServer.ClaimSymmetricEncryptionKeySecretName), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("appAuth.selfAuthServer.claimSymmetricEncryptionKeySecretName", testValue) + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.claimSymmetricEncryptionKeySecretName"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.SelfAuthServer.ClaimSymmetricEncryptionKeySecretName) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.selfAuthServer.tokenSigningRSAKeySecretName", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.tokenSigningRSAKeySecretName"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.SelfAuthServer.TokenSigningRSAKeySecretName), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("appAuth.selfAuthServer.tokenSigningRSAKeySecretName", testValue) + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.tokenSigningRSAKeySecretName"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.SelfAuthServer.TokenSigningRSAKeySecretName) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.selfAuthServer.oldTokenSigningRSAKeySecretName", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.oldTokenSigningRSAKeySecretName"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.SelfAuthServer.OldTokenSigningRSAKeySecretName), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("appAuth.selfAuthServer.oldTokenSigningRSAKeySecretName", testValue) + if vString, err := cmdFlags.GetString("appAuth.selfAuthServer.oldTokenSigningRSAKeySecretName"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.SelfAuthServer.OldTokenSigningRSAKeySecretName) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.externalAuthServer.baseUrl", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.externalAuthServer.baseUrl"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.ExternalAuthServer.BaseURL.String()), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := DefaultConfig.AppAuth.ExternalAuthServer.BaseURL.String() + + cmdFlags.Set("appAuth.externalAuthServer.baseUrl", testValue) + if vString, err := cmdFlags.GetString("appAuth.externalAuthServer.baseUrl"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.ExternalAuthServer.BaseURL) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.thirdPartyConfig.flyteClient.clientId", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.thirdPartyConfig.flyteClient.clientId"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.ThirdParty.FlyteClientConfig.ClientID), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("appAuth.thirdPartyConfig.flyteClient.clientId", testValue) + if vString, err := cmdFlags.GetString("appAuth.thirdPartyConfig.flyteClient.clientId"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.ThirdParty.FlyteClientConfig.ClientID) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.thirdPartyConfig.flyteClient.redirectUri", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("appAuth.thirdPartyConfig.flyteClient.redirectUri"); err == nil { + assert.Equal(t, string(DefaultConfig.AppAuth.ThirdParty.FlyteClientConfig.RedirectURI), vString) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := "1" + + cmdFlags.Set("appAuth.thirdPartyConfig.flyteClient.redirectUri", testValue) + if vString, err := cmdFlags.GetString("appAuth.thirdPartyConfig.flyteClient.redirectUri"); err == nil { + testDecodeJson_Config(t, fmt.Sprintf("%v", vString), &actual.AppAuth.ThirdParty.FlyteClientConfig.RedirectURI) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) + t.Run("Test_appAuth.thirdPartyConfig.flyteClient.scopes", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vStringSlice, err := cmdFlags.GetStringSlice("appAuth.thirdPartyConfig.flyteClient.scopes"); err == nil { + assert.Equal(t, []string([]string{}), vStringSlice) + } else { + assert.FailNow(t, err.Error()) + } + }) + + t.Run("Override", func(t *testing.T) { + testValue := join_Config("1,1", ",") + + cmdFlags.Set("appAuth.thirdPartyConfig.flyteClient.scopes", testValue) + if vStringSlice, err := cmdFlags.GetStringSlice("appAuth.thirdPartyConfig.flyteClient.scopes"); err == nil { + testDecodeSlice_Config(t, join_Config(vStringSlice, ","), &actual.AppAuth.ThirdParty.FlyteClientConfig.Scopes) + + } else { + assert.FailNow(t, err.Error()) + } + }) + }) +} diff --git a/auth/config/config_test.go b/auth/config/config_test.go new file mode 100644 index 0000000000..fc3409f5a9 --- /dev/null +++ b/auth/config/config_test.go @@ -0,0 +1,17 @@ +package config + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ory/fosite" +) + +func TestHashFlyteClientSecret(t *testing.T) { + hasher := &fosite.BCrypt{WorkFactor: 6} + res, err := hasher.Hash(context.Background(), []byte("foobar")) + assert.NoError(t, err) + t.Log(string(res)) +} diff --git a/auth/config/third_party_config.go b/auth/config/third_party_config.go new file mode 100644 index 0000000000..b7474d6100 --- /dev/null +++ b/auth/config/third_party_config.go @@ -0,0 +1,17 @@ +package config + +// This struct encapsulates config options for bootstrapping various Flyte applications with config values +// For example, FlyteClientConfig contains application-specific values to initialize the config required by flyte client +type ThirdPartyConfigOptions struct { + FlyteClientConfig FlyteClientConfig `json:"flyteClient"` +} + +type FlyteClientConfig struct { + ClientID string `json:"clientId" pflag:",public identifier for the app which handles authorization for a Flyte deployment"` + RedirectURI string `json:"redirectUri" pflag:",This is the callback uri registered with the app which handles authorization for a Flyte deployment"` + Scopes []string `json:"scopes" pflag:",Recommended scopes for the client to request."` +} + +func (o ThirdPartyConfigOptions) IsEmpty() bool { + return len(o.FlyteClientConfig.ClientID) == 0 && len(o.FlyteClientConfig.RedirectURI) == 0 && len(o.FlyteClientConfig.Scopes) == 0 +} diff --git a/auth/constants.go b/auth/constants.go new file mode 100644 index 0000000000..1b7366f54d --- /dev/null +++ b/auth/constants.go @@ -0,0 +1,24 @@ +package auth + +import "github.com/flyteorg/flytestdlib/contextutils" + +const ( + // OAuth2 Parameters + CsrfFormKey = "state" + AuthorizationResponseCodeType = "code" + DefaultAuthorizationHeader = "authorization" + BearerScheme = "Bearer" + IDTokenScheme = "IDToken" + UserInfoMDKey = "UserInfo" + + // https://tools.ietf.org/html/rfc8414 + // This should be defined without a leading slash. If there is one, the url library's ResolveReference will make it a root path + OAuth2MetadataEndpoint = ".well-known/oauth-authorization-server" + + // https://openid.net/specs/openid-connect-discovery-1_0.html + // This should be defined without a leading slash. If there is one, the url library's ResolveReference will make it a root path + OIdCMetadataEndpoint = ".well-known/openid-configuration" + + ContextKeyIdentityContext = contextutils.Key("identity_context") + ScopeAll = "all" +) diff --git a/pkg/auth/cookie.go b/auth/cookie.go similarity index 94% rename from pkg/auth/cookie.go rename to auth/cookie.go index de4435c8f0..e67c8d4562 100644 --- a/pkg/auth/cookie.go +++ b/auth/cookie.go @@ -9,7 +9,7 @@ import ( "net/url" "time" - "github.com/flyteorg/flyteadmin/pkg/auth/interfaces" + "github.com/flyteorg/flyteadmin/auth/interfaces" "github.com/flyteorg/flytestdlib/errors" "github.com/flyteorg/flytestdlib/logger" "github.com/gorilla/securecookie" @@ -29,6 +29,12 @@ const ( // #nosec idTokenExtra = "id_token" + + // #nosec + authCodeCookieName = "flyte_auth_code" + + // #nosec + userInfoCookieName = "flyte_user_info" ) const ( @@ -158,16 +164,18 @@ func NewRedirectCookie(ctx context.Context, redirectURL string) *http.Cookie { // At the end of the OAuth flow, the server needs to send the user somewhere. This should have been stored as a cookie // during the initial /login call. If that cookie is missing from the request, it will default to the one configured // in this package's Config object. -func getAuthFlowEndRedirect(ctx context.Context, authContext interfaces.AuthenticationContext, request *http.Request) string { +func getAuthFlowEndRedirect(ctx context.Context, authCtx interfaces.AuthenticationContext, request *http.Request) string { queryParams := request.URL.Query() // Use the redirect URL specified in the request if one is available. if redirectURL := queryParams.Get(RedirectURLParameter); len(redirectURL) > 0 { return redirectURL } + cookie, err := request.Cookie(redirectURLCookieName) if err != nil { logger.Debugf(ctx, "Could not detect end-of-flow redirect url cookie") - return authContext.Options().RedirectURL + return authCtx.Options().UserAuth.RedirectURL.String() } + return cookie.Value } diff --git a/pkg/auth/cookie_manager.go b/auth/cookie_manager.go similarity index 70% rename from pkg/auth/cookie_manager.go rename to auth/cookie_manager.go index ead8454177..92c67e8e32 100644 --- a/pkg/auth/cookie_manager.go +++ b/auth/cookie_manager.go @@ -3,9 +3,13 @@ package auth import ( "context" "encoding/base64" + "encoding/json" + "fmt" "net/http" "time" + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + "github.com/flyteorg/flytestdlib/errors" "github.com/flyteorg/flytestdlib/logger" "golang.org/x/oauth2" @@ -71,6 +75,61 @@ func (c CookieManager) RetrieveTokenValues(ctx context.Context, request *http.Re return } +func (c CookieManager) SetUserInfoCookie(ctx context.Context, writer http.ResponseWriter, userInfo *service.UserInfoResponse) error { + raw, err := json.Marshal(userInfo) + if err != nil { + return fmt.Errorf("failed to marshal user info to store in a cookie. Error: %w", err) + } + + userInfoCookie, err := NewSecureCookie(userInfoCookieName, string(raw), c.hashKey, c.blockKey) + if err != nil { + logger.Errorf(ctx, "Error generating encrypted user info cookie %s", err) + return err + } + + http.SetCookie(writer, &userInfoCookie) + + return nil + +} + +func (c CookieManager) RetrieveUserInfo(ctx context.Context, request *http.Request) (*service.UserInfoResponse, error) { + userInfoCookie, err := retrieveSecureCookie(ctx, request, userInfoCookieName, c.hashKey, c.blockKey) + if err != nil { + return nil, err + } + + res := service.UserInfoResponse{} + err = json.Unmarshal([]byte(userInfoCookie), &res) + if err != nil { + return nil, fmt.Errorf("failed to unmasharl user info cookie. Error: %w", err) + } + + return &res, nil + +} + +func (c CookieManager) RetrieveAuthCodeRequest(ctx context.Context, request *http.Request) (authRequestURL string, err error) { + authCodeCookie, err := retrieveSecureCookie(ctx, request, authCodeCookieName, c.hashKey, c.blockKey) + if err != nil { + return "", err + } + + return authCodeCookie, nil +} + +func (c CookieManager) SetAuthCodeCookie(ctx context.Context, writer http.ResponseWriter, authRequestURL string) error { + authCodeCookie, err := NewSecureCookie(authCodeCookieName, authRequestURL, c.hashKey, c.blockKey) + if err != nil { + logger.Errorf(ctx, "Error generating encrypted accesstoken cookie %s", err) + return err + } + + http.SetCookie(writer, &authCodeCookie) + + return nil +} + func (c CookieManager) SetTokenCookies(ctx context.Context, writer http.ResponseWriter, token *oauth2.Token) error { if token == nil { logger.Errorf(ctx, "Attempting to set cookies with nil token") diff --git a/pkg/auth/cookie_manager_test.go b/auth/cookie_manager_test.go similarity index 100% rename from pkg/auth/cookie_manager_test.go rename to auth/cookie_manager_test.go diff --git a/pkg/auth/cookie_test.go b/auth/cookie_test.go similarity index 89% rename from pkg/auth/cookie_test.go rename to auth/cookie_test.go index fa8b6e5ff6..64573c6b6a 100644 --- a/pkg/auth/cookie_test.go +++ b/auth/cookie_test.go @@ -9,12 +9,22 @@ import ( "net/url" "testing" - "github.com/flyteorg/flyteadmin/pkg/auth/config" - "github.com/flyteorg/flyteadmin/pkg/auth/interfaces/mocks" + "github.com/flyteorg/flyteadmin/auth/config" + "github.com/flyteorg/flyteadmin/auth/interfaces/mocks" + stdConfig "github.com/flyteorg/flytestdlib/config" "github.com/gorilla/securecookie" "github.com/stretchr/testify/assert" ) +func mustParseURL(t testing.TB, u string) url.URL { + res, err := url.Parse(u) + if !assert.NoError(t, err) { + t.FailNow() + } + + return *res +} + // This function can also be called locally to generate new keys func TestSecureCookieLifecycle(t *testing.T) { hashKey := securecookie.GenerateRandomKey(64) @@ -130,8 +140,10 @@ func TestGetAuthFlowEndRedirect(t *testing.T) { request, err := http.NewRequest(http.MethodGet, "/test", nil) assert.NoError(t, err) mockAuthCtx := &mocks.AuthenticationContext{} - mockAuthCtx.On("Options").Return(config.OAuthOptions{ - RedirectURL: "/api/v1/projects", + mockAuthCtx.OnOptions().Return(&config.Config{ + UserAuth: config.UserAuthConfig{ + RedirectURL: stdConfig.URL{URL: mustParseURL(t, "/api/v1/projects")}, + }, }) redirect := getAuthFlowEndRedirect(ctx, mockAuthCtx, request) assert.Equal(t, "/api/v1/projects", redirect) diff --git a/auth/handler_utils.go b/auth/handler_utils.go new file mode 100644 index 0000000000..c8a21348ac --- /dev/null +++ b/auth/handler_utils.go @@ -0,0 +1,141 @@ +package auth + +import ( + "context" + "net/http" + "net/url" + + "github.com/flyteorg/flyteadmin/auth/config" + + "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" +) + +const ( + metadataXForwardedHost = "x-forwarded-host" + metadataAuthority = ":authority" +) + +//func NewMockOIdCProvider() (*oidc.Provider, error) { +// ctx, cancel := context.WithCancel(context.Background()) +// defer cancel() +// +// var issuer string +// hf := func(w http.ResponseWriter, r *http.Request) { +// if r.URL.Path == "/.well-known/openid-configuration" { +// w.Header().Set("Content-Type", "application/json") +// io.WriteString(w, strings.ReplaceAll(`{ +// "issuer": "ISSUER", +// "authorization_endpoint": "https://example.com/auth", +// "token_endpoint": "https://example.com/token", +// "jwks_uri": "ISSUER/keys", +// "id_token_signing_alg_values_supported": ["RS256"] +// }`, "ISSUER", issuer)) +// return +// } else if r.URL.Path == "/keys" { +// w.Header().Set("Content-Type", "application/json") +// io.WriteString(w, `{"keys":[{"kty":"RSA","alg":"RS256","kid":"Z6dmZ_TXhduw-jUBZ6uEEzvnh-jhNO0YhemB7qa_LOc","use":"sig","e":"AQAB","n":"jyMcudBiz7XqeDIvxfMlmG4fvAUU7cl3R4iSIv_ahHanCcVRvqcXOsIknwn7i4rOUjP6MlH45uIYsaj6MuLYgoaIbC-Z823Tu4asoC-rGbpZgf-bMcJLxtZVBNsSagr_M0n8xA1oogHRF1LGRiD93wNr2b9OkKVbWnyNdASk5_xui024nVzakm2-RAEyaC048nHfnjVBvwo4BdJVDgBEK03fbkBCyuaZyE1ZQF545MTbD4keCv58prSCmbDRJgRk48FzaFnQeYTho-pUxXxM9pvhMykeI62WZ7diDfIc9isOpv6ALFOHgKy7Ihhve6pLIylLRTnn2qhHFkGPtU3djQ"}]}`) +// return +// } +// +// http.NotFound(w, r) +// return +// +// } +// +// s := httptest.NewServer(http.HandlerFunc(hf)) +// defer s.Close() +// +// issuer = s.URL +// return oidc.NewProvider(ctx, issuer) +//} + +// URLFromRequest attempts to reconstruct the url from the request object. Or nil if not possible +func URLFromRequest(req *http.Request) *url.URL { + if req == nil { + return nil + } + + u, _ := url.ParseRequestURI(req.RequestURI) + if u != nil && u.IsAbs() { + return u + } + + if len(req.Host) == 0 { + return nil + } + + scheme := "https://" + if req.URL != nil && len(req.URL.Scheme) > 0 { + scheme = req.URL.Scheme + "://" + } + + u, _ = url.Parse(scheme + req.Host) + return u +} + +// URLFromContext attempts to retrieve the original url from context. gRPC gateway sets metadata in context that refers +// to the original host. Or nil if metadata isn't set. +func URLFromContext(ctx context.Context) *url.URL { + md := metautils.ExtractIncoming(ctx) + forwardedHost := md.Get(metadataXForwardedHost) + if len(forwardedHost) == 0 { + forwardedHost = md.Get(metadataAuthority) + } + + if len(forwardedHost) == 0 { + return nil + } + + u, _ := url.Parse("https://" + forwardedHost) + return u +} + +// FirstURL gets the first non-nil url from a list of given urls. +func FirstURL(urls ...*url.URL) *url.URL { + for _, u := range urls { + if u != nil { + return u + } + } + + return nil +} + +// GetPublicURL attempts to retrieve the public url of the service. If httpPublicUri is set in the config, it takes +// precedence. If the request is not nil and has a host set, it comes second and lastly it attempts to retrieve the url +// from context if set (e.g. by gRPC gateway). +func GetPublicURL(ctx context.Context, req *http.Request, cfg *config.Config) *url.URL { + u := FirstURL(URLFromRequest(req), URLFromContext(ctx)) + var hostMatching *url.URL + var hostAndPortMatching *url.URL + for _, authorized := range cfg.AuthorizedURIs { + if u == nil { + return &authorized.URL + } + + if u.Hostname() == authorized.Hostname() { + hostMatching = &authorized.URL + if u.Port() == authorized.Port() { + hostAndPortMatching = &authorized.URL + } + + if u.Scheme == authorized.Scheme { + return &authorized.URL + } + } + } + + if hostAndPortMatching != nil { + return hostAndPortMatching + } + + if hostMatching != nil { + return hostMatching + } + + if len(cfg.AuthorizedURIs) > 0 { + return &cfg.AuthorizedURIs[0].URL + } + + return u +} diff --git a/auth/handler_utils_test.go b/auth/handler_utils_test.go new file mode 100644 index 0000000000..33fd14555c --- /dev/null +++ b/auth/handler_utils_test.go @@ -0,0 +1,57 @@ +package auth + +import ( + "context" + "net/http" + "testing" + + config2 "github.com/flyteorg/flytestdlib/config" + + "github.com/flyteorg/flyteadmin/auth/config" + "github.com/stretchr/testify/assert" +) + +func TestGetPublicURL(t *testing.T) { + t.Run("Matching scheme and host", func(t *testing.T) { + req, err := http.NewRequest(http.MethodPost, "https://abc", nil) + assert.NoError(t, err) + u := GetPublicURL(context.Background(), req, &config.Config{ + AuthorizedURIs: []config2.URL{ + {URL: *config.MustParseURL("https://xyz")}, + {URL: *config.MustParseURL("https://abc")}, + }, + }) + assert.Equal(t, "https://abc", u.String()) + }) + + t.Run("Matching host and non-matching scheme", func(t *testing.T) { + req, err := http.NewRequest(http.MethodPost, "https://abc", nil) + assert.NoError(t, err) + u := GetPublicURL(context.Background(), req, &config.Config{ + AuthorizedURIs: []config2.URL{ + {URL: *config.MustParseURL("https://xyz")}, + {URL: *config.MustParseURL("http://abc")}, + }, + }) + assert.Equal(t, "http://abc", u.String()) + }) + + t.Run("No matching", func(t *testing.T) { + req, err := http.NewRequest(http.MethodPost, "https://abc", nil) + assert.NoError(t, err) + u := GetPublicURL(context.Background(), req, &config.Config{ + AuthorizedURIs: []config2.URL{ + {URL: *config.MustParseURL("https://xyz")}, + {URL: *config.MustParseURL("http://xyz")}, + }, + }) + assert.Equal(t, "https://xyz", u.String()) + }) + + t.Run("No config", func(t *testing.T) { + req, err := http.NewRequest(http.MethodPost, "https://abc", nil) + assert.NoError(t, err) + u := GetPublicURL(context.Background(), req, &config.Config{}) + assert.Equal(t, "https://abc", u.String()) + }) +} diff --git a/auth/handlers.go b/auth/handlers.go new file mode 100644 index 0000000000..4bd2d33c42 --- /dev/null +++ b/auth/handlers.go @@ -0,0 +1,435 @@ +package auth + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + + "golang.org/x/oauth2" + + "github.com/flyteorg/flyteadmin/pkg/audit" + "github.com/flyteorg/flyteadmin/pkg/common" + "google.golang.org/grpc/peer" + + "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" + + "github.com/flyteorg/flyteadmin/auth/interfaces" + "github.com/flyteorg/flytestdlib/errors" + "github.com/flyteorg/flytestdlib/logger" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +const ( + RedirectURLParameter = "redirect_url" + FromHTTPKey = "from_http" + FromHTTPVal = "true" +) + +type HTTPRequestToMetadataAnnotator func(ctx context.Context, request *http.Request) metadata.MD + +func RegisterHandlers(ctx context.Context, handler interfaces.HandlerRegisterer, authCtx interfaces.AuthenticationContext) { + // Add HTTP handlers for OAuth2 endpoints + handler.HandleFunc("/login", RefreshTokensIfExists(ctx, authCtx, + GetLoginHandler(ctx, authCtx))) + handler.HandleFunc("/callback", GetCallbackHandler(ctx, authCtx)) + + // The metadata endpoint is an RFC-defined constant, but we need a leading / for the handler to pattern match correctly. + handler.HandleFunc(fmt.Sprintf("/%s", OIdCMetadataEndpoint), GetOIdCMetadataEndpointRedirectHandler(ctx, authCtx)) + + // These endpoints require authentication + handler.HandleFunc("/logout", GetLogoutEndpointHandler(ctx, authCtx)) +} + +// Look for access token and refresh token, if both are present and the access token is expired, then attempt to +// refresh. Otherwise do nothing and proceed to the next handler. If successfully refreshed, proceed to the landing page. +func RefreshTokensIfExists(ctx context.Context, authCtx interfaces.AuthenticationContext, authHandler http.HandlerFunc) http.HandlerFunc { + + return func(writer http.ResponseWriter, request *http.Request) { + // Since we only do one thing if there are no errors anywhere along the chain, we can save code by just + // using one variable and checking for errors at the end. + idToken, accessToken, refreshToken, err := authCtx.CookieManager().RetrieveTokenValues(ctx, request) + if err != nil { + logger.Errorf(ctx, "Failed to retrieve tokens from request, redirecting to login handler. Error: %s", err) + authHandler(writer, request) + return + } + + _, err = ParseIDTokenAndValidate(ctx, authCtx.Options().UserAuth.OpenID.ClientID, idToken, authCtx.OidcProvider()) + if err != nil && errors.IsCausedBy(err, ErrTokenExpired) && len(refreshToken) > 0 { + logger.Debugf(ctx, "Expired id token found, attempting to refresh") + newToken, err := GetRefreshedToken(ctx, authCtx.OAuth2ClientConfig(GetPublicURL(ctx, request, authCtx.Options())), accessToken, refreshToken) + if err != nil { + logger.Infof(ctx, "Failed to refresh tokens. Restarting login flow. Error: %s", err) + authHandler(writer, request) + return + } + + logger.Debugf(ctx, "Tokens are refreshed. Saving new tokens into cookies.") + err = authCtx.CookieManager().SetTokenCookies(ctx, writer, newToken) + if err != nil { + logger.Infof(ctx, "Failed to set token cookies. Restarting login flow. Error: %s", err) + authHandler(writer, request) + return + } + + userInfo, err := QueryUserInfoUsingAccessToken(ctx, request, authCtx, newToken.AccessToken) + if err != nil { + logger.Infof(ctx, "Failed to query user info. Restarting login flow. Error: %s", err) + authHandler(writer, request) + return + } + + err = authCtx.CookieManager().SetUserInfoCookie(ctx, writer, userInfo) + if err != nil { + logger.Infof(ctx, "Failed to set user info cookie. Restarting login flow. Error: %s", err) + authHandler(writer, request) + return + } + } else if err != nil { + logger.Infof(ctx, "Failed to validate tokens. Restarting login flow. Error: %s", err) + authHandler(writer, request) + return + } + + redirectURL := getAuthFlowEndRedirect(ctx, authCtx, request) + http.Redirect(writer, request, redirectURL, http.StatusTemporaryRedirect) + } +} + +// GetLoginHandler builds an http handler that handles authentication calls. Before redirecting to the authentication +// provider, it saves a cookie that contains the redirect url for after the authentication flow is done. +func GetLoginHandler(ctx context.Context, authCtx interfaces.AuthenticationContext) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + csrfCookie := NewCsrfCookie() + csrfToken := csrfCookie.Value + http.SetCookie(writer, &csrfCookie) + + state := HashCsrfState(csrfToken) + logger.Debugf(ctx, "Setting CSRF state cookie to %s and state to %s\n", csrfToken, state) + url := authCtx.OAuth2ClientConfig(GetPublicURL(ctx, request, authCtx.Options())).AuthCodeURL(state) + queryParams := request.URL.Query() + if flowEndRedirectURL := queryParams.Get(RedirectURLParameter); flowEndRedirectURL != "" { + redirectCookie := NewRedirectCookie(ctx, flowEndRedirectURL) + if redirectCookie != nil { + http.SetCookie(writer, redirectCookie) + } else { + logger.Errorf(ctx, "Was not able to create a redirect cookie") + } + } + + http.Redirect(writer, request, url, http.StatusTemporaryRedirect) + } +} + +// GetCallbackHandler returns a handler that is called by the OIdC provider with the authorization code to complete +// the user authentication flow. +func GetCallbackHandler(ctx context.Context, authCtx interfaces.AuthenticationContext) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + logger.Debugf(ctx, "Running callback handler...") + authorizationCode := request.FormValue(AuthorizationResponseCodeType) + + err := VerifyCsrfCookie(ctx, request) + if err != nil { + logger.Errorf(ctx, "Invalid CSRF token cookie %s", err) + writer.WriteHeader(http.StatusUnauthorized) + return + } + + token, err := authCtx.OAuth2ClientConfig(GetPublicURL(ctx, request, authCtx.Options())).Exchange(ctx, authorizationCode) + if err != nil { + logger.Errorf(ctx, "Error when exchanging code %s", err) + writer.WriteHeader(http.StatusForbidden) + return + } + + err = authCtx.CookieManager().SetTokenCookies(ctx, writer, token) + if err != nil { + logger.Errorf(ctx, "Error setting encrypted JWT cookie %s", err) + writer.WriteHeader(http.StatusForbidden) + return + } + + userInfo, err := QueryUserInfoUsingAccessToken(ctx, request, authCtx, token.AccessToken) + if err != nil { + logger.Errorf(ctx, "Failed to query user info. Error: %v", err) + writer.WriteHeader(http.StatusForbidden) + return + } + + err = authCtx.CookieManager().SetUserInfoCookie(ctx, writer, userInfo) + if err != nil { + logger.Errorf(ctx, "Error setting encrypted user info cookie. Error: %v", err) + writer.WriteHeader(http.StatusForbidden) + return + } + + redirectURL := getAuthFlowEndRedirect(ctx, authCtx, request) + http.Redirect(writer, request, redirectURL, http.StatusTemporaryRedirect) + } +} + +func AuthenticationLoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + // Invoke 'handler' to use your gRPC server implementation and get + // the response. + identityContext := IdentityContextFromContext(ctx) + logger.Debugf(ctx, "gRPC server info in logging interceptor [%s] method [%s]\n", identityContext.UserID(), info.FullMethod) + return handler(ctx, req) +} + +// GetAuthenticationCustomMetadataInterceptor produces a gRPC middleware interceptor intended to be used when running +// authentication with non-default gRPC headers (metadata). Because the default `authorization` header is reserved for +// use by Envoy, clients wishing to pass tokens to Admin will need to use a different string, specified in this +// package's Config object. This interceptor will scan for that arbitrary string, and then rename it to the default +// string, which the downstream auth/auditing interceptors will detect and validate. +func GetAuthenticationCustomMetadataInterceptor(authCtx interfaces.AuthenticationContext) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + if authCtx.Options().GrpcAuthorizationHeader != DefaultAuthorizationHeader { + md, ok := metadata.FromIncomingContext(ctx) + if ok { + existingHeader := md.Get(authCtx.Options().GrpcAuthorizationHeader) + if len(existingHeader) > 0 { + logger.Debugf(ctx, "Found existing metadata %s", existingHeader[0]) + newAuthorizationMetadata := metadata.Pairs(DefaultAuthorizationHeader, existingHeader[0]) + joinedMetadata := metadata.Join(md, newAuthorizationMetadata) + newCtx := metadata.NewIncomingContext(ctx, joinedMetadata) + return handler(newCtx, req) + } + } else { + logger.Debugf(ctx, "Could not extract incoming metadata from context, continuing with original ctx...") + } + } + return handler(ctx, req) + } +} + +func SetContextForIdentity(ctx context.Context, identityContext interfaces.IdentityContext) context.Context { + email := identityContext.UserInfo().GetEmail() + newCtx := identityContext.WithContext(ctx) + if len(email) > 0 { + newCtx = WithUserEmail(newCtx, identityContext.UserID()) + } + + return WithAuditFields(newCtx, identityContext.UserID(), []string{identityContext.AppID()}, identityContext.AuthenticatedAt()) +} + +// GetAuthenticationInterceptor chooses to enforce or not enforce authentication. It will attempt to get the token +// from the incoming context, validate it, and decide whether or not to let the request through. +func GetAuthenticationInterceptor(authCtx interfaces.AuthenticationContext) func(context.Context) (context.Context, error) { + return func(ctx context.Context) (context.Context, error) { + logger.Debugf(ctx, "Running authentication gRPC interceptor") + + fromHTTP := metautils.ExtractIncoming(ctx).Get(FromHTTPKey) + isFromHTTP := fromHTTP == FromHTTPVal + + identityContext, err := GRPCGetIdentityFromAccessToken(ctx, authCtx) + if err == nil { + return SetContextForIdentity(ctx, identityContext), nil + } + + logger.Infof(ctx, "Failed to parse Access Token from context. Will attempt to find IDToken. Error: %v", err) + + identityContext, err = GRPCGetIdentityFromIDToken(ctx, authCtx.Options().UserAuth.OpenID.ClientID, + authCtx.OidcProvider()) + + if err == nil { + return SetContextForIdentity(ctx, identityContext), nil + } + + // Only enforcement logic is present. The default case is to let things through. + if (isFromHTTP && !authCtx.Options().DisableForHTTP) || + (!isFromHTTP && !authCtx.Options().DisableForGrpc) { + return ctx, status.Errorf(codes.Unauthenticated, "token parse error %s", err) + } + + return ctx, nil + } +} + +func WithUserEmail(ctx context.Context, email string) context.Context { + return context.WithValue(ctx, common.PrincipalContextKey, email) +} + +func WithAuditFields(ctx context.Context, subject string, clientIds []string, tokenIssuedAt time.Time) context.Context { + var clientIP string + peerInfo, ok := peer.FromContext(ctx) + if ok { + clientIP = peerInfo.Addr.String() + } + return context.WithValue(ctx, common.AuditFieldsContextKey, audit.AuthenticatedClientMeta{ + ClientIds: clientIds, + TokenIssuedAt: tokenIssuedAt, + ClientIP: clientIP, + Subject: subject, + }) +} + +// This is effectively middleware for the grpc gateway, it allows us to modify the translation between HTTP request +// and gRPC request. There are two potential sources for bearer tokens, it can come from an authorization header (not +// yet implemented), or encrypted cookies. Note that when deploying behind Envoy, you have the option to look for a +// configurable, non-standard header name. The token is extracted and turned into a metadata object which is then +// attached to the request, from which the token is extracted later for verification. +func GetHTTPRequestCookieToMetadataHandler(authCtx interfaces.AuthenticationContext) HTTPRequestToMetadataAnnotator { + return func(ctx context.Context, request *http.Request) metadata.MD { + // TODO: Improve error handling + idToken, _, _, _ := authCtx.CookieManager().RetrieveTokenValues(ctx, request) + if len(idToken) == 0 { + // If no token was found in the cookies, look for an authorization header, starting with a potentially + // custom header set in the Config object + if len(authCtx.Options().HTTPAuthorizationHeader) > 0 { + header := authCtx.Options().HTTPAuthorizationHeader + // TODO: There may be a potential issue here when running behind a service mesh that uses the default Authorization + // header. The grpc-gateway code will automatically translate the 'Authorization' header into the appropriate + // metadata object so if two different tokens are presented, one with the default name and one with the + // custom name, AuthFromMD will find the wrong one. + return metadata.MD{ + DefaultAuthorizationHeader: []string{request.Header.Get(header)}, + } + } + + logger.Infof(ctx, "Could not find access token cookie while requesting %s", request.RequestURI) + return nil + } + + meta := metadata.MD{ + DefaultAuthorizationHeader: []string{fmt.Sprintf("%s %s", IDTokenScheme, idToken)}, + } + + userInfo, err := authCtx.CookieManager().RetrieveUserInfo(ctx, request) + if err != nil { + logger.Infof(ctx, "Failed to retrieve user info cookie. Ignoring. Error: %v", err) + } + + raw, err := json.Marshal(userInfo) + if err != nil { + logger.Infof(ctx, "Failed to marshal user info. Ignoring. Error: %v", err) + } + + if len(raw) > 0 { + meta.Set(UserInfoMDKey, string(raw)) + } + + return meta + } +} + +// Intercepts the incoming HTTP requests and marks it as such so that the downstream code can use it to enforce auth. +// See the enforceHTTP/Grpc options for more information. +func GetHTTPMetadataTaggingHandler() HTTPRequestToMetadataAnnotator { + return func(ctx context.Context, request *http.Request) metadata.MD { + return metadata.MD{ + FromHTTPKey: []string{FromHTTPVal}, + } + } +} + +func IdentityContextFromRequest(ctx context.Context, req *http.Request, authCtx interfaces.AuthenticationContext) ( + interfaces.IdentityContext, error) { + + authHeader := DefaultAuthorizationHeader + if len(authCtx.Options().HTTPAuthorizationHeader) > 0 { + authHeader = authCtx.Options().HTTPAuthorizationHeader + } + + headerValue := req.Header.Get(authHeader) + if len(headerValue) == 0 { + headerValue = req.Header.Get(DefaultAuthorizationHeader) + } + + if len(headerValue) > 0 { + logger.Debugf(ctx, "Found authorization header at [%v] header. Validating.", authHeader) + if strings.HasPrefix(headerValue, BearerScheme+" ") { + expectedAudience := GetPublicURL(ctx, req, authCtx.Options()).String() + return authCtx.OAuth2ResourceServer().ValidateAccessToken(ctx, expectedAudience, strings.TrimPrefix(headerValue, BearerScheme+" ")) + } + } + + idToken, _, _, err := authCtx.CookieManager().RetrieveTokenValues(ctx, req) + if err != nil || len(idToken) == 0 { + return nil, fmt.Errorf("unauthenticated request. IDToken Len [%v], Error: %w", len(idToken), err) + } + + userInfo, err := authCtx.CookieManager().RetrieveUserInfo(ctx, req) + if err != nil { + logger.Infof(ctx, "Failed to retrieve user info cookie from request. Error: %v", err) + return nil, fmt.Errorf("unauthenticated request. Error: %w", err) + } + + return IdentityContextFromIDTokenToken(ctx, idToken, authCtx.Options().UserAuth.OpenID.ClientID, + authCtx.OidcProvider(), userInfo) +} + +func QueryUserInfo(ctx context.Context, identityContext interfaces.IdentityContext, request *http.Request, + authCtx interfaces.AuthenticationContext) (*service.UserInfoResponse, error) { + + if identityContext.IsEmpty() { + return &service.UserInfoResponse{}, fmt.Errorf("request is unauthenticated, try /login in again") + } + + if len(identityContext.UserInfo().GetName()) > 0 { + return identityContext.UserInfo(), nil + } + + _, accessToken, _, err := authCtx.CookieManager().RetrieveTokenValues(ctx, request) + if err != nil { + return &service.UserInfoResponse{}, fmt.Errorf("error decoding identify token, try /login in again") + } + + return QueryUserInfoUsingAccessToken(ctx, request, authCtx, accessToken) +} + +func QueryUserInfoUsingAccessToken(ctx context.Context, originalRequest *http.Request, authCtx interfaces.AuthenticationContext, accessToken string) ( + *service.UserInfoResponse, error) { + + originalToken := oauth2.Token{ + AccessToken: accessToken, + } + + tokenSource := authCtx.OAuth2ClientConfig(GetPublicURL(ctx, originalRequest, authCtx.Options())).TokenSource(ctx, &originalToken) + + // TODO: Investigate improving transparency of errors. The errors from this call may be just a local error, or may + // be an error from the HTTP request to the IDP. In the latter case, consider passing along the error code/msg. + userInfo, err := authCtx.OidcProvider().UserInfo(ctx, tokenSource) + if err != nil { + logger.Errorf(ctx, "Error getting user info from IDP %s", err) + return &service.UserInfoResponse{}, fmt.Errorf("error getting user info from IDP") + } + + resp := &service.UserInfoResponse{} + err = userInfo.Claims(&resp) + if err != nil { + logger.Errorf(ctx, "Error getting user info from IDP %s", err) + return &service.UserInfoResponse{}, fmt.Errorf("error getting user info from IDP") + } + + return resp, err +} + +// This returns a handler that will redirect (303) to the well-known metadata endpoint for the OAuth2 authorization server +// See https://tools.ietf.org/html/rfc8414 for more information. +func GetOIdCMetadataEndpointRedirectHandler(ctx context.Context, authCtx interfaces.AuthenticationContext) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + metadataURL := authCtx.Options().UserAuth.OpenID.BaseURL.ResolveReference(authCtx.GetOIdCMetadataURL()) + http.Redirect(writer, request, metadataURL.String(), http.StatusSeeOther) + } +} + +func GetLogoutEndpointHandler(ctx context.Context, authCtx interfaces.AuthenticationContext) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + logger.Debugf(ctx, "Deleting auth cookies") + authCtx.CookieManager().DeleteCookies(ctx, writer) + + // Redirect if one was given + queryParams := request.URL.Query() + if redirectURL := queryParams.Get(RedirectURLParameter); redirectURL != "" { + http.Redirect(writer, request, redirectURL, http.StatusTemporaryRedirect) + } + } +} diff --git a/pkg/auth/handlers_test.go b/auth/handlers_test.go similarity index 74% rename from pkg/auth/handlers_test.go rename to auth/handlers_test.go index d28266dbfe..f58ebd4ba5 100644 --- a/pkg/auth/handlers_test.go +++ b/auth/handlers_test.go @@ -4,13 +4,16 @@ import ( "context" "net/http" "net/http/httptest" - "net/url" "strings" - "github.com/flyteorg/flyteadmin/pkg/auth/config" + "github.com/stretchr/testify/mock" + + stdConfig "github.com/flyteorg/flytestdlib/config" + + "github.com/flyteorg/flyteadmin/auth/config" "github.com/flyteorg/flyteadmin/pkg/common" - "github.com/flyteorg/flyteadmin/pkg/auth/interfaces/mocks" + "github.com/flyteorg/flyteadmin/auth/interfaces/mocks" "github.com/stretchr/testify/assert" "golang.org/x/oauth2" @@ -29,7 +32,8 @@ func TestGetLoginHandler(t *testing.T) { Scopes: []string{"openid", "other"}, } mockAuthCtx := mocks.AuthenticationContext{} - mockAuthCtx.On("OAuth2Config").Return(&dummyOAuth2Config) + mockAuthCtx.OnOptions().Return(&config.Config{}) + mockAuthCtx.OnOAuth2ClientConfigMatch(mock.Anything).Return(&dummyOAuth2Config) handler := GetLoginHandler(ctx, &mockAuthCtx) req, err := http.NewRequest("GET", "/login", nil) assert.NoError(t, err) @@ -48,8 +52,8 @@ func TestGetHTTPRequestCookieToMetadataHandler(t *testing.T) { cookieManager, err := NewCookieManager(ctx, hashKeyEncoded, blockKeyEncoded) assert.NoError(t, err) mockAuthCtx := mocks.AuthenticationContext{} - mockAuthCtx.On("CookieManager").Return(&cookieManager) - mockAuthCtx.OnOptions().Return(config.OAuthOptions{}) + mockAuthCtx.OnCookieManager().Return(&cookieManager) + mockAuthCtx.OnOptions().Return(&config.Config{}) handler := GetHTTPRequestCookieToMetadataHandler(&mockAuthCtx) req, err := http.NewRequest("GET", "/api/v1/projects", nil) assert.NoError(t, err) @@ -62,13 +66,12 @@ func TestGetHTTPRequestCookieToMetadataHandler(t *testing.T) { assert.NoError(t, err) req.AddCookie(&idCookie) - assert.Equal(t, "Bearer a.b.c", handler(ctx, req)["authorization"][0]) + assert.Equal(t, "IDToken a.b.c", handler(ctx, req)["authorization"][0]) } func TestGetHTTPMetadataTaggingHandler(t *testing.T) { ctx := context.Background() - mockAuthCtx := mocks.AuthenticationContext{} - annotator := GetHTTPMetadataTaggingHandler(&mockAuthCtx) + annotator := GetHTTPMetadataTaggingHandler() request, err := http.NewRequest("GET", "/api", nil) assert.NoError(t, err) md := annotator(ctx, request) @@ -84,10 +87,10 @@ func TestGetHTTPRequestCookieToMetadataHandler_CustomHeader(t *testing.T) { assert.NoError(t, err) mockAuthCtx := mocks.AuthenticationContext{} mockAuthCtx.On("CookieManager").Return(&cookieManager) - mockConfig := config.OAuthOptions{ + mockConfig := &config.Config{ HTTPAuthorizationHeader: "Custom-Header", } - mockAuthCtx.On("Options").Return(mockConfig) + mockAuthCtx.OnOptions().Return(mockConfig) handler := GetHTTPRequestCookieToMetadataHandler(&mockAuthCtx) req, err := http.NewRequest("GET", "/api/v1/projects", nil) assert.NoError(t, err) @@ -95,20 +98,24 @@ func TestGetHTTPRequestCookieToMetadataHandler_CustomHeader(t *testing.T) { assert.Equal(t, "Bearer a.b.c", handler(ctx, req)["authorization"][0]) } -func TestGetMetadataEndpointRedirectHandler(t *testing.T) { +func TestGetOIdCMetadataEndpointRedirectHandler(t *testing.T) { ctx := context.Background() - baseURL, err := url.Parse("http://www.google.com") - assert.NoError(t, err) - metadataPath, err := url.Parse(OAuth2MetadataEndpoint) - assert.NoError(t, err) + metadataPath := mustParseURL(t, OIdCMetadataEndpoint) mockAuthCtx := mocks.AuthenticationContext{} - mockAuthCtx.OnGetBaseURL().Return(baseURL) - mockAuthCtx.OnGetOAuth2MetadataURL().Return(metadataPath) - handler := GetOAuth2MetadataEndpointRedirectHandler(ctx, &mockAuthCtx) + mockAuthCtx.OnOptions().Return(&config.Config{ + UserAuth: config.UserAuthConfig{ + OpenID: config.OpenIDOptions{ + BaseURL: stdConfig.URL{URL: mustParseURL(t, "http://www.google.com")}, + }, + }, + }) + + mockAuthCtx.OnGetOIdCMetadataURL().Return(&metadataPath) + handler := GetOIdCMetadataEndpointRedirectHandler(ctx, &mockAuthCtx) req, err := http.NewRequest("GET", "/xyz", nil) assert.NoError(t, err) w := httptest.NewRecorder() handler(w, req) assert.Equal(t, http.StatusSeeOther, w.Code) - assert.Equal(t, "http://www.google.com/.well-known/oauth-authorization-server", w.Header()["Location"][0]) + assert.Equal(t, "http://www.google.com/.well-known/openid-configuration", w.Header()["Location"][0]) } diff --git a/auth/identity_context.go b/auth/identity_context.go new file mode 100644 index 0000000000..c045a36193 --- /dev/null +++ b/auth/identity_context.go @@ -0,0 +1,99 @@ +package auth + +import ( + "context" + "time" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + + "k8s.io/apimachinery/pkg/util/sets" +) + +var ( + emptyIdentityContext = IdentityContext{} +) + +// IdentityContext is an abstract entity to enclose the authenticated identity of the user/app. Both gRPC and HTTP +// servers have interceptors to set the IdentityContext on the context.Context. +// To retrieve the current IdentityContext call auth.IdentityContextFromContext(ctx). +// To check whether there is an identity set, call auth.IdentityContextFromContext(ctx).IsEmpty() +type IdentityContext struct { + audience string + userID string + appID string + authenticatedAt time.Time + userInfo *service.UserInfoResponse + // Set to pointer just to keep this struct go-simple to support equal operator + scopes *sets.String +} + +func (c IdentityContext) Audience() string { + return c.audience +} + +func (c IdentityContext) UserID() string { + return c.userID +} + +func (c IdentityContext) AppID() string { + return c.appID +} + +func (c IdentityContext) UserInfo() *service.UserInfoResponse { + if c.userInfo == nil { + return &service.UserInfoResponse{} + } + + return c.userInfo +} + +func (c IdentityContext) IsEmpty() bool { + return c == emptyIdentityContext +} + +func (c IdentityContext) Scopes() sets.String { + if c.scopes != nil { + return *c.scopes + } + + return sets.NewString() +} + +func (c IdentityContext) WithContext(ctx context.Context) context.Context { + return context.WithValue(ctx, ContextKeyIdentityContext, c) +} + +func (c IdentityContext) AuthenticatedAt() time.Time { + return c.authenticatedAt +} + +// NewIdentityContext creates a new IdentityContext. +func NewIdentityContext(audience, userID, appID string, authenticatedAt time.Time, scopes sets.String, userInfo *service.UserInfoResponse) IdentityContext { + // For some reason, google IdP returns a subject in the ID Token but an empty subject in the /user_info endpoint + if userInfo == nil { + userInfo = &service.UserInfoResponse{} + } + + if len(userInfo.Subject) == 0 { + userInfo.Subject = userID + } + + return IdentityContext{ + audience: audience, + userID: userID, + appID: appID, + userInfo: userInfo, + authenticatedAt: authenticatedAt, + scopes: &scopes, + } +} + +// IdentityContextFromContext retrieves the authenticated identity from context.Context. +func IdentityContextFromContext(ctx context.Context) IdentityContext { + existing := ctx.Value(ContextKeyIdentityContext) + if existing != nil { + return existing.(IdentityContext) + } + + return emptyIdentityContext +} diff --git a/auth/init_secrets.go b/auth/init_secrets.go new file mode 100644 index 0000000000..64e95c0d19 --- /dev/null +++ b/auth/init_secrets.go @@ -0,0 +1,151 @@ +package auth + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/flyteorg/flyteadmin/auth/config" + + "github.com/flyteorg/flytestdlib/logger" + + "github.com/spf13/cobra" +) + +const ( + SymmetricKeyLength = 32 + CookieHashKeyLength = 64 + CookieBlockKeyLength = 32 + rsaPEMType = "RSA PRIVATE KEY" +) + +var ( + localPath string +) + +// GetInitSecretsCommand creates a command to issue secrets to be used for Auth settings. It writes the secrets to the +// working directory. The expectation is that they are put in a location and made available to the serve command later. +// To configure where the serve command looks for secrets, update this config: +// secrets: +// secrets-prefix: +func GetInitSecretsCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "init", + Short: "Generates secrets needed for OpenIDC and OAuth2 providers", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + secrets, err := NewSecrets() + if err != nil { + return err + } + + d, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get working directory. Error: %w", err) + } + + // If not overridden in cmd flags, use the working directory + if len(localPath) == 0 { + localPath = d + } + + err = os.MkdirAll(localPath, 0755) + if err != nil { + return fmt.Errorf("failed to create path [%v]. Error: %w", localPath, err) + } + + return writeSecrets(ctx, secrets, localPath) + }, + } + + cmd.Flags().StringVarP(&localPath, "localPath", "p", "", "Specifies where the secrets should be written.") + + return cmd +} + +type SecretsSet struct { + TokenHashKey []byte + TokenSigningRSAPrivateKey *rsa.PrivateKey + CookieHashKey []byte + CookieBlockKey []byte +} + +func writeSecrets(ctx context.Context, secrets SecretsSet, path string) error { + err := ioutil.WriteFile(filepath.Join(path, config.SecretNameClaimSymmetricKey), []byte(base64.RawStdEncoding.EncodeToString(secrets.TokenHashKey)), os.ModePerm) + if err != nil { + return fmt.Errorf("failed to persist token hash key. Error: %w", err) + } + + logger.Infof(ctx, "wrote %v", config.SecretNameClaimSymmetricKey) + + err = ioutil.WriteFile(filepath.Join(path, config.SecretNameCookieHashKey), []byte(base64.RawStdEncoding.EncodeToString(secrets.CookieHashKey)), os.ModePerm) + if err != nil { + return fmt.Errorf("failed to persist cookie hash key. Error: %w", err) + } + + logger.Infof(ctx, "wrote %v", config.SecretNameCookieHashKey) + + err = ioutil.WriteFile(filepath.Join(path, config.SecretNameCookieBlockKey), []byte(base64.RawStdEncoding.EncodeToString(secrets.CookieBlockKey)), os.ModePerm) + if err != nil { + return fmt.Errorf("failed to persist cookie block key. Error: %w", err) + } + + logger.Infof(ctx, "wrote %v", config.SecretNameCookieBlockKey) + + keyOut, err := os.OpenFile(filepath.Join(path, config.SecretNameTokenSigningRSAKey), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) + if err != nil { + return fmt.Errorf("failed to open key.pem for writing: %w", err) + } + + privBytes := x509.MarshalPKCS1PrivateKey(secrets.TokenSigningRSAPrivateKey) + if err := pem.Encode(keyOut, &pem.Block{Type: rsaPEMType, Bytes: privBytes}); err != nil { + return fmt.Errorf("failed to write data to key.pem: %w", err) + } + + if err := keyOut.Close(); err != nil { + return fmt.Errorf("error closing key.pem: %w", err) + } + + logger.Infof(ctx, "wrote %v", config.SecretNameTokenSigningRSAKey) + + return nil +} + +func NewSecrets() (SecretsSet, error) { + secret := make([]byte, SymmetricKeyLength) + _, err := rand.Read(secret) + if err != nil { + return SecretsSet{}, fmt.Errorf("failed to issue token hash key. Error: %w", err) + } + + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return SecretsSet{}, fmt.Errorf("failed to issue token signing key. Error: %w", err) + } + + cookieHashKey := make([]byte, CookieHashKeyLength) + _, err = rand.Read(cookieHashKey) + if err != nil { + return SecretsSet{}, fmt.Errorf("failed to issue cookie hash key. Error: %w", err) + } + + cookieBlockKey := make([]byte, CookieBlockKeyLength) + _, err = rand.Read(cookieBlockKey) + if err != nil { + return SecretsSet{}, fmt.Errorf("failed to issue cookie block key. Error: %w", err) + } + + return SecretsSet{ + TokenHashKey: secret, + TokenSigningRSAPrivateKey: privateKey, + CookieHashKey: cookieHashKey, + CookieBlockKey: cookieBlockKey, + }, nil +} diff --git a/auth/interfaces/context.go b/auth/interfaces/context.go new file mode 100644 index 0000000000..a703b4d3ad --- /dev/null +++ b/auth/interfaces/context.go @@ -0,0 +1,69 @@ +package interfaces + +import ( + "context" + "net/http" + "net/url" + "time" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/lestrrat-go/jwx/jwk" + + "github.com/ory/fosite" + fositeOAuth2 "github.com/ory/fosite/handler/oauth2" + + "github.com/coreos/go-oidc" + "github.com/flyteorg/flyteadmin/auth/config" + "golang.org/x/oauth2" +) + +//go:generate mockery -all -case=underscore + +type HandlerRegisterer interface { + HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) +} + +// OAuth2Provider represents an OAuth2 Provider that can be used to issue OAuth2 tokens. +type OAuth2Provider interface { + fosite.OAuth2Provider + OAuth2ResourceServer + NewJWTSessionToken(subject, appID, issuer, audience string, userInfoClaims *service.UserInfoResponse) *fositeOAuth2.JWTSession + KeySet() jwk.Set +} + +// OAuth2ResourceServer represents a resource server that can be accessed through an access token. +type OAuth2ResourceServer interface { + ValidateAccessToken(ctx context.Context, expectedAudience, tokenStr string) (IdentityContext, error) +} + +// AuthenticationContext is a convenience wrapper object that holds all the utilities necessary to run Flyte Admin behind authentication +// It is constructed at the root server layer, and passed around to the various auth handlers and utility functions/objects. +type AuthenticationContext interface { + OAuth2Provider() OAuth2Provider + OAuth2ResourceServer() OAuth2ResourceServer + OAuth2ClientConfig(requestURL *url.URL) *oauth2.Config + OidcProvider() *oidc.Provider + CookieManager() CookieHandler + Options() *config.Config + GetOAuth2MetadataURL() *url.URL + GetOIdCMetadataURL() *url.URL + GetHTTPClient() *http.Client + AuthMetadataService() service.AuthMetadataServiceServer + IdentityService() service.IdentityServiceServer +} + +// IdentityContext represents the authenticated identity and can be used to abstract the way the user/app authenticated +// to the platform. +type IdentityContext interface { + UserID() string + AppID() string + UserInfo() *service.UserInfoResponse + AuthenticatedAt() time.Time + Scopes() sets.String + + IsEmpty() bool + WithContext(ctx context.Context) context.Context +} diff --git a/auth/interfaces/cookie.go b/auth/interfaces/cookie.go new file mode 100644 index 0000000000..2fb45ccc85 --- /dev/null +++ b/auth/interfaces/cookie.go @@ -0,0 +1,27 @@ +package interfaces + +import ( + "context" + "net/http" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + + "golang.org/x/oauth2" +) + +type CookieHandler interface { + SetTokenCookies(ctx context.Context, writer http.ResponseWriter, token *oauth2.Token) error + RetrieveTokenValues(ctx context.Context, request *http.Request) (idToken, accessToken, refreshToken string, err error) + + SetUserInfoCookie(ctx context.Context, writer http.ResponseWriter, userInfo *service.UserInfoResponse) error + RetrieveUserInfo(ctx context.Context, request *http.Request) (*service.UserInfoResponse, error) + + // SetAuthCodeCookie stores, in a cookie, the /authorize request url initiated by an app before executing OIdC protocol. + // This enables the service to recover it after the user completes the login process in an external OIdC provider. + SetAuthCodeCookie(ctx context.Context, writer http.ResponseWriter, authRequestURL string) error + + // RetrieveAuthCodeRequest retrieves the /authorize request url from stored cookie to complete the OAuth2 app auth + // flow. + RetrieveAuthCodeRequest(ctx context.Context, request *http.Request) (authRequestURL string, err error) + DeleteCookies(ctx context.Context, writer http.ResponseWriter) +} diff --git a/pkg/auth/interfaces/mocks/authentication_context.go b/auth/interfaces/mocks/authentication_context.go similarity index 54% rename from pkg/auth/interfaces/mocks/authentication_context.go rename to auth/interfaces/mocks/authentication_context.go index eebd0e9020..8e40eb08a5 100644 --- a/pkg/auth/interfaces/mocks/authentication_context.go +++ b/auth/interfaces/mocks/authentication_context.go @@ -5,9 +5,9 @@ package mocks import ( http "net/http" - config "github.com/flyteorg/flyteadmin/pkg/auth/config" + config "github.com/flyteorg/flyteadmin/auth/config" - interfaces "github.com/flyteorg/flyteadmin/pkg/auth/interfaces" + interfaces "github.com/flyteorg/flyteadmin/auth/interfaces" mock "github.com/stretchr/testify/mock" @@ -15,6 +15,8 @@ import ( oidc "github.com/coreos/go-oidc" + service "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + url "net/url" ) @@ -23,33 +25,35 @@ type AuthenticationContext struct { mock.Mock } -type AuthenticationContext_Claims struct { +type AuthenticationContext_AuthMetadataService struct { *mock.Call } -func (_m AuthenticationContext_Claims) Return(_a0 config.Claims) *AuthenticationContext_Claims { - return &AuthenticationContext_Claims{Call: _m.Call.Return(_a0)} +func (_m AuthenticationContext_AuthMetadataService) Return(_a0 service.AuthMetadataServiceServer) *AuthenticationContext_AuthMetadataService { + return &AuthenticationContext_AuthMetadataService{Call: _m.Call.Return(_a0)} } -func (_m *AuthenticationContext) OnClaims() *AuthenticationContext_Claims { - c := _m.On("Claims") - return &AuthenticationContext_Claims{Call: c} +func (_m *AuthenticationContext) OnAuthMetadataService() *AuthenticationContext_AuthMetadataService { + c := _m.On("AuthMetadataService") + return &AuthenticationContext_AuthMetadataService{Call: c} } -func (_m *AuthenticationContext) OnClaimsMatch(matchers ...interface{}) *AuthenticationContext_Claims { - c := _m.On("Claims", matchers...) - return &AuthenticationContext_Claims{Call: c} +func (_m *AuthenticationContext) OnAuthMetadataServiceMatch(matchers ...interface{}) *AuthenticationContext_AuthMetadataService { + c := _m.On("AuthMetadataService", matchers...) + return &AuthenticationContext_AuthMetadataService{Call: c} } -// Claims provides a mock function with given fields: -func (_m *AuthenticationContext) Claims() config.Claims { +// AuthMetadataService provides a mock function with given fields: +func (_m *AuthenticationContext) AuthMetadataService() service.AuthMetadataServiceServer { ret := _m.Called() - var r0 config.Claims - if rf, ok := ret.Get(0).(func() config.Claims); ok { + var r0 service.AuthMetadataServiceServer + if rf, ok := ret.Get(0).(func() service.AuthMetadataServiceServer); ok { r0 = rf() } else { - r0 = ret.Get(0).(config.Claims) + if ret.Get(0) != nil { + r0 = ret.Get(0).(service.AuthMetadataServiceServer) + } } return r0 @@ -89,40 +93,6 @@ func (_m *AuthenticationContext) CookieManager() interfaces.CookieHandler { return r0 } -type AuthenticationContext_GetBaseURL struct { - *mock.Call -} - -func (_m AuthenticationContext_GetBaseURL) Return(_a0 *url.URL) *AuthenticationContext_GetBaseURL { - return &AuthenticationContext_GetBaseURL{Call: _m.Call.Return(_a0)} -} - -func (_m *AuthenticationContext) OnGetBaseURL() *AuthenticationContext_GetBaseURL { - c := _m.On("GetBaseURL") - return &AuthenticationContext_GetBaseURL{Call: c} -} - -func (_m *AuthenticationContext) OnGetBaseURLMatch(matchers ...interface{}) *AuthenticationContext_GetBaseURL { - c := _m.On("GetBaseURL", matchers...) - return &AuthenticationContext_GetBaseURL{Call: c} -} - -// GetBaseURL provides a mock function with given fields: -func (_m *AuthenticationContext) GetBaseURL() *url.URL { - ret := _m.Called() - - var r0 *url.URL - if rf, ok := ret.Get(0).(func() *url.URL); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*url.URL) - } - } - - return r0 -} - type AuthenticationContext_GetHTTPClient struct { *mock.Call } @@ -225,65 +195,65 @@ func (_m *AuthenticationContext) GetOIdCMetadataURL() *url.URL { return r0 } -type AuthenticationContext_GetUserInfoURL struct { +type AuthenticationContext_IdentityService struct { *mock.Call } -func (_m AuthenticationContext_GetUserInfoURL) Return(_a0 *url.URL) *AuthenticationContext_GetUserInfoURL { - return &AuthenticationContext_GetUserInfoURL{Call: _m.Call.Return(_a0)} +func (_m AuthenticationContext_IdentityService) Return(_a0 service.IdentityServiceServer) *AuthenticationContext_IdentityService { + return &AuthenticationContext_IdentityService{Call: _m.Call.Return(_a0)} } -func (_m *AuthenticationContext) OnGetUserInfoURL() *AuthenticationContext_GetUserInfoURL { - c := _m.On("GetUserInfoURL") - return &AuthenticationContext_GetUserInfoURL{Call: c} +func (_m *AuthenticationContext) OnIdentityService() *AuthenticationContext_IdentityService { + c := _m.On("IdentityService") + return &AuthenticationContext_IdentityService{Call: c} } -func (_m *AuthenticationContext) OnGetUserInfoURLMatch(matchers ...interface{}) *AuthenticationContext_GetUserInfoURL { - c := _m.On("GetUserInfoURL", matchers...) - return &AuthenticationContext_GetUserInfoURL{Call: c} +func (_m *AuthenticationContext) OnIdentityServiceMatch(matchers ...interface{}) *AuthenticationContext_IdentityService { + c := _m.On("IdentityService", matchers...) + return &AuthenticationContext_IdentityService{Call: c} } -// GetUserInfoURL provides a mock function with given fields: -func (_m *AuthenticationContext) GetUserInfoURL() *url.URL { +// IdentityService provides a mock function with given fields: +func (_m *AuthenticationContext) IdentityService() service.IdentityServiceServer { ret := _m.Called() - var r0 *url.URL - if rf, ok := ret.Get(0).(func() *url.URL); ok { + var r0 service.IdentityServiceServer + if rf, ok := ret.Get(0).(func() service.IdentityServiceServer); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*url.URL) + r0 = ret.Get(0).(service.IdentityServiceServer) } } return r0 } -type AuthenticationContext_OAuth2Config struct { +type AuthenticationContext_OAuth2ClientConfig struct { *mock.Call } -func (_m AuthenticationContext_OAuth2Config) Return(_a0 *oauth2.Config) *AuthenticationContext_OAuth2Config { - return &AuthenticationContext_OAuth2Config{Call: _m.Call.Return(_a0)} +func (_m AuthenticationContext_OAuth2ClientConfig) Return(_a0 *oauth2.Config) *AuthenticationContext_OAuth2ClientConfig { + return &AuthenticationContext_OAuth2ClientConfig{Call: _m.Call.Return(_a0)} } -func (_m *AuthenticationContext) OnOAuth2Config() *AuthenticationContext_OAuth2Config { - c := _m.On("OAuth2Config") - return &AuthenticationContext_OAuth2Config{Call: c} +func (_m *AuthenticationContext) OnOAuth2ClientConfig(requestURL *url.URL) *AuthenticationContext_OAuth2ClientConfig { + c := _m.On("OAuth2ClientConfig", requestURL) + return &AuthenticationContext_OAuth2ClientConfig{Call: c} } -func (_m *AuthenticationContext) OnOAuth2ConfigMatch(matchers ...interface{}) *AuthenticationContext_OAuth2Config { - c := _m.On("OAuth2Config", matchers...) - return &AuthenticationContext_OAuth2Config{Call: c} +func (_m *AuthenticationContext) OnOAuth2ClientConfigMatch(matchers ...interface{}) *AuthenticationContext_OAuth2ClientConfig { + c := _m.On("OAuth2ClientConfig", matchers...) + return &AuthenticationContext_OAuth2ClientConfig{Call: c} } -// OAuth2Config provides a mock function with given fields: -func (_m *AuthenticationContext) OAuth2Config() *oauth2.Config { - ret := _m.Called() +// OAuth2ClientConfig provides a mock function with given fields: requestURL +func (_m *AuthenticationContext) OAuth2ClientConfig(requestURL *url.URL) *oauth2.Config { + ret := _m.Called(requestURL) var r0 *oauth2.Config - if rf, ok := ret.Get(0).(func() *oauth2.Config); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(*url.URL) *oauth2.Config); ok { + r0 = rf(requestURL) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*oauth2.Config) @@ -293,6 +263,74 @@ func (_m *AuthenticationContext) OAuth2Config() *oauth2.Config { return r0 } +type AuthenticationContext_OAuth2Provider struct { + *mock.Call +} + +func (_m AuthenticationContext_OAuth2Provider) Return(_a0 interfaces.OAuth2Provider) *AuthenticationContext_OAuth2Provider { + return &AuthenticationContext_OAuth2Provider{Call: _m.Call.Return(_a0)} +} + +func (_m *AuthenticationContext) OnOAuth2Provider() *AuthenticationContext_OAuth2Provider { + c := _m.On("OAuth2Provider") + return &AuthenticationContext_OAuth2Provider{Call: c} +} + +func (_m *AuthenticationContext) OnOAuth2ProviderMatch(matchers ...interface{}) *AuthenticationContext_OAuth2Provider { + c := _m.On("OAuth2Provider", matchers...) + return &AuthenticationContext_OAuth2Provider{Call: c} +} + +// OAuth2Provider provides a mock function with given fields: +func (_m *AuthenticationContext) OAuth2Provider() interfaces.OAuth2Provider { + ret := _m.Called() + + var r0 interfaces.OAuth2Provider + if rf, ok := ret.Get(0).(func() interfaces.OAuth2Provider); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interfaces.OAuth2Provider) + } + } + + return r0 +} + +type AuthenticationContext_OAuth2ResourceServer struct { + *mock.Call +} + +func (_m AuthenticationContext_OAuth2ResourceServer) Return(_a0 interfaces.OAuth2ResourceServer) *AuthenticationContext_OAuth2ResourceServer { + return &AuthenticationContext_OAuth2ResourceServer{Call: _m.Call.Return(_a0)} +} + +func (_m *AuthenticationContext) OnOAuth2ResourceServer() *AuthenticationContext_OAuth2ResourceServer { + c := _m.On("OAuth2ResourceServer") + return &AuthenticationContext_OAuth2ResourceServer{Call: c} +} + +func (_m *AuthenticationContext) OnOAuth2ResourceServerMatch(matchers ...interface{}) *AuthenticationContext_OAuth2ResourceServer { + c := _m.On("OAuth2ResourceServer", matchers...) + return &AuthenticationContext_OAuth2ResourceServer{Call: c} +} + +// OAuth2ResourceServer provides a mock function with given fields: +func (_m *AuthenticationContext) OAuth2ResourceServer() interfaces.OAuth2ResourceServer { + ret := _m.Called() + + var r0 interfaces.OAuth2ResourceServer + if rf, ok := ret.Get(0).(func() interfaces.OAuth2ResourceServer); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interfaces.OAuth2ResourceServer) + } + } + + return r0 +} + type AuthenticationContext_OidcProvider struct { *mock.Call } @@ -331,7 +369,7 @@ type AuthenticationContext_Options struct { *mock.Call } -func (_m AuthenticationContext_Options) Return(_a0 config.OAuthOptions) *AuthenticationContext_Options { +func (_m AuthenticationContext_Options) Return(_a0 *config.Config) *AuthenticationContext_Options { return &AuthenticationContext_Options{Call: _m.Call.Return(_a0)} } @@ -346,14 +384,16 @@ func (_m *AuthenticationContext) OnOptionsMatch(matchers ...interface{}) *Authen } // Options provides a mock function with given fields: -func (_m *AuthenticationContext) Options() config.OAuthOptions { +func (_m *AuthenticationContext) Options() *config.Config { ret := _m.Called() - var r0 config.OAuthOptions - if rf, ok := ret.Get(0).(func() config.OAuthOptions); ok { + var r0 *config.Config + if rf, ok := ret.Get(0).(func() *config.Config); ok { r0 = rf() } else { - r0 = ret.Get(0).(config.OAuthOptions) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*config.Config) + } } return r0 diff --git a/auth/interfaces/mocks/cookie_handler.go b/auth/interfaces/mocks/cookie_handler.go new file mode 100644 index 0000000000..b99d6a45ed --- /dev/null +++ b/auth/interfaces/mocks/cookie_handler.go @@ -0,0 +1,253 @@ +// Code generated by mockery v1.0.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + http "net/http" + + mock "github.com/stretchr/testify/mock" + + oauth2 "golang.org/x/oauth2" + + service "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" +) + +// CookieHandler is an autogenerated mock type for the CookieHandler type +type CookieHandler struct { + mock.Mock +} + +// DeleteCookies provides a mock function with given fields: ctx, writer +func (_m *CookieHandler) DeleteCookies(ctx context.Context, writer http.ResponseWriter) { + _m.Called(ctx, writer) +} + +type CookieHandler_RetrieveAuthCodeRequest struct { + *mock.Call +} + +func (_m CookieHandler_RetrieveAuthCodeRequest) Return(authRequestURL string, err error) *CookieHandler_RetrieveAuthCodeRequest { + return &CookieHandler_RetrieveAuthCodeRequest{Call: _m.Call.Return(authRequestURL, err)} +} + +func (_m *CookieHandler) OnRetrieveAuthCodeRequest(ctx context.Context, request *http.Request) *CookieHandler_RetrieveAuthCodeRequest { + c := _m.On("RetrieveAuthCodeRequest", ctx, request) + return &CookieHandler_RetrieveAuthCodeRequest{Call: c} +} + +func (_m *CookieHandler) OnRetrieveAuthCodeRequestMatch(matchers ...interface{}) *CookieHandler_RetrieveAuthCodeRequest { + c := _m.On("RetrieveAuthCodeRequest", matchers...) + return &CookieHandler_RetrieveAuthCodeRequest{Call: c} +} + +// RetrieveAuthCodeRequest provides a mock function with given fields: ctx, request +func (_m *CookieHandler) RetrieveAuthCodeRequest(ctx context.Context, request *http.Request) (string, error) { + ret := _m.Called(ctx, request) + + var r0 string + if rf, ok := ret.Get(0).(func(context.Context, *http.Request) string); ok { + r0 = rf(ctx, request) + } else { + r0 = ret.Get(0).(string) + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *http.Request) error); ok { + r1 = rf(ctx, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type CookieHandler_RetrieveTokenValues struct { + *mock.Call +} + +func (_m CookieHandler_RetrieveTokenValues) Return(idToken string, accessToken string, refreshToken string, err error) *CookieHandler_RetrieveTokenValues { + return &CookieHandler_RetrieveTokenValues{Call: _m.Call.Return(idToken, accessToken, refreshToken, err)} +} + +func (_m *CookieHandler) OnRetrieveTokenValues(ctx context.Context, request *http.Request) *CookieHandler_RetrieveTokenValues { + c := _m.On("RetrieveTokenValues", ctx, request) + return &CookieHandler_RetrieveTokenValues{Call: c} +} + +func (_m *CookieHandler) OnRetrieveTokenValuesMatch(matchers ...interface{}) *CookieHandler_RetrieveTokenValues { + c := _m.On("RetrieveTokenValues", matchers...) + return &CookieHandler_RetrieveTokenValues{Call: c} +} + +// RetrieveTokenValues provides a mock function with given fields: ctx, request +func (_m *CookieHandler) RetrieveTokenValues(ctx context.Context, request *http.Request) (string, string, string, error) { + ret := _m.Called(ctx, request) + + var r0 string + if rf, ok := ret.Get(0).(func(context.Context, *http.Request) string); ok { + r0 = rf(ctx, request) + } else { + r0 = ret.Get(0).(string) + } + + var r1 string + if rf, ok := ret.Get(1).(func(context.Context, *http.Request) string); ok { + r1 = rf(ctx, request) + } else { + r1 = ret.Get(1).(string) + } + + var r2 string + if rf, ok := ret.Get(2).(func(context.Context, *http.Request) string); ok { + r2 = rf(ctx, request) + } else { + r2 = ret.Get(2).(string) + } + + var r3 error + if rf, ok := ret.Get(3).(func(context.Context, *http.Request) error); ok { + r3 = rf(ctx, request) + } else { + r3 = ret.Error(3) + } + + return r0, r1, r2, r3 +} + +type CookieHandler_RetrieveUserInfo struct { + *mock.Call +} + +func (_m CookieHandler_RetrieveUserInfo) Return(_a0 *service.UserInfoResponse, _a1 error) *CookieHandler_RetrieveUserInfo { + return &CookieHandler_RetrieveUserInfo{Call: _m.Call.Return(_a0, _a1)} +} + +func (_m *CookieHandler) OnRetrieveUserInfo(ctx context.Context, request *http.Request) *CookieHandler_RetrieveUserInfo { + c := _m.On("RetrieveUserInfo", ctx, request) + return &CookieHandler_RetrieveUserInfo{Call: c} +} + +func (_m *CookieHandler) OnRetrieveUserInfoMatch(matchers ...interface{}) *CookieHandler_RetrieveUserInfo { + c := _m.On("RetrieveUserInfo", matchers...) + return &CookieHandler_RetrieveUserInfo{Call: c} +} + +// RetrieveUserInfo provides a mock function with given fields: ctx, request +func (_m *CookieHandler) RetrieveUserInfo(ctx context.Context, request *http.Request) (*service.UserInfoResponse, error) { + ret := _m.Called(ctx, request) + + var r0 *service.UserInfoResponse + if rf, ok := ret.Get(0).(func(context.Context, *http.Request) *service.UserInfoResponse); ok { + r0 = rf(ctx, request) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*service.UserInfoResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *http.Request) error); ok { + r1 = rf(ctx, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type CookieHandler_SetAuthCodeCookie struct { + *mock.Call +} + +func (_m CookieHandler_SetAuthCodeCookie) Return(_a0 error) *CookieHandler_SetAuthCodeCookie { + return &CookieHandler_SetAuthCodeCookie{Call: _m.Call.Return(_a0)} +} + +func (_m *CookieHandler) OnSetAuthCodeCookie(ctx context.Context, writer http.ResponseWriter, authRequestURL string) *CookieHandler_SetAuthCodeCookie { + c := _m.On("SetAuthCodeCookie", ctx, writer, authRequestURL) + return &CookieHandler_SetAuthCodeCookie{Call: c} +} + +func (_m *CookieHandler) OnSetAuthCodeCookieMatch(matchers ...interface{}) *CookieHandler_SetAuthCodeCookie { + c := _m.On("SetAuthCodeCookie", matchers...) + return &CookieHandler_SetAuthCodeCookie{Call: c} +} + +// SetAuthCodeCookie provides a mock function with given fields: ctx, writer, authRequestURL +func (_m *CookieHandler) SetAuthCodeCookie(ctx context.Context, writer http.ResponseWriter, authRequestURL string) error { + ret := _m.Called(ctx, writer, authRequestURL) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, http.ResponseWriter, string) error); ok { + r0 = rf(ctx, writer, authRequestURL) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type CookieHandler_SetTokenCookies struct { + *mock.Call +} + +func (_m CookieHandler_SetTokenCookies) Return(_a0 error) *CookieHandler_SetTokenCookies { + return &CookieHandler_SetTokenCookies{Call: _m.Call.Return(_a0)} +} + +func (_m *CookieHandler) OnSetTokenCookies(ctx context.Context, writer http.ResponseWriter, token *oauth2.Token) *CookieHandler_SetTokenCookies { + c := _m.On("SetTokenCookies", ctx, writer, token) + return &CookieHandler_SetTokenCookies{Call: c} +} + +func (_m *CookieHandler) OnSetTokenCookiesMatch(matchers ...interface{}) *CookieHandler_SetTokenCookies { + c := _m.On("SetTokenCookies", matchers...) + return &CookieHandler_SetTokenCookies{Call: c} +} + +// SetTokenCookies provides a mock function with given fields: ctx, writer, token +func (_m *CookieHandler) SetTokenCookies(ctx context.Context, writer http.ResponseWriter, token *oauth2.Token) error { + ret := _m.Called(ctx, writer, token) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, http.ResponseWriter, *oauth2.Token) error); ok { + r0 = rf(ctx, writer, token) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type CookieHandler_SetUserInfoCookie struct { + *mock.Call +} + +func (_m CookieHandler_SetUserInfoCookie) Return(_a0 error) *CookieHandler_SetUserInfoCookie { + return &CookieHandler_SetUserInfoCookie{Call: _m.Call.Return(_a0)} +} + +func (_m *CookieHandler) OnSetUserInfoCookie(ctx context.Context, writer http.ResponseWriter, userInfo *service.UserInfoResponse) *CookieHandler_SetUserInfoCookie { + c := _m.On("SetUserInfoCookie", ctx, writer, userInfo) + return &CookieHandler_SetUserInfoCookie{Call: c} +} + +func (_m *CookieHandler) OnSetUserInfoCookieMatch(matchers ...interface{}) *CookieHandler_SetUserInfoCookie { + c := _m.On("SetUserInfoCookie", matchers...) + return &CookieHandler_SetUserInfoCookie{Call: c} +} + +// SetUserInfoCookie provides a mock function with given fields: ctx, writer, userInfo +func (_m *CookieHandler) SetUserInfoCookie(ctx context.Context, writer http.ResponseWriter, userInfo *service.UserInfoResponse) error { + ret := _m.Called(ctx, writer, userInfo) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, http.ResponseWriter, *service.UserInfoResponse) error); ok { + r0 = rf(ctx, writer, userInfo) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/auth/interfaces/mocks/handler_registerer.go b/auth/interfaces/mocks/handler_registerer.go new file mode 100644 index 0000000000..ab1ed5a335 --- /dev/null +++ b/auth/interfaces/mocks/handler_registerer.go @@ -0,0 +1,19 @@ +// Code generated by mockery v1.0.1. DO NOT EDIT. + +package mocks + +import ( + http "net/http" + + mock "github.com/stretchr/testify/mock" +) + +// HandlerRegisterer is an autogenerated mock type for the HandlerRegisterer type +type HandlerRegisterer struct { + mock.Mock +} + +// HandleFunc provides a mock function with given fields: pattern, handler +func (_m *HandlerRegisterer) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) { + _m.Called(pattern, handler) +} diff --git a/auth/interfaces/mocks/identity_context.go b/auth/interfaces/mocks/identity_context.go new file mode 100644 index 0000000000..2b43d53277 --- /dev/null +++ b/auth/interfaces/mocks/identity_context.go @@ -0,0 +1,250 @@ +// Code generated by mockery v1.0.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + service "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + + sets "k8s.io/apimachinery/pkg/util/sets" + + time "time" +) + +// IdentityContext is an autogenerated mock type for the IdentityContext type +type IdentityContext struct { + mock.Mock +} + +type IdentityContext_AppID struct { + *mock.Call +} + +func (_m IdentityContext_AppID) Return(_a0 string) *IdentityContext_AppID { + return &IdentityContext_AppID{Call: _m.Call.Return(_a0)} +} + +func (_m *IdentityContext) OnAppID() *IdentityContext_AppID { + c := _m.On("AppID") + return &IdentityContext_AppID{Call: c} +} + +func (_m *IdentityContext) OnAppIDMatch(matchers ...interface{}) *IdentityContext_AppID { + c := _m.On("AppID", matchers...) + return &IdentityContext_AppID{Call: c} +} + +// AppID provides a mock function with given fields: +func (_m *IdentityContext) AppID() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +type IdentityContext_AuthenticatedAt struct { + *mock.Call +} + +func (_m IdentityContext_AuthenticatedAt) Return(_a0 time.Time) *IdentityContext_AuthenticatedAt { + return &IdentityContext_AuthenticatedAt{Call: _m.Call.Return(_a0)} +} + +func (_m *IdentityContext) OnAuthenticatedAt() *IdentityContext_AuthenticatedAt { + c := _m.On("AuthenticatedAt") + return &IdentityContext_AuthenticatedAt{Call: c} +} + +func (_m *IdentityContext) OnAuthenticatedAtMatch(matchers ...interface{}) *IdentityContext_AuthenticatedAt { + c := _m.On("AuthenticatedAt", matchers...) + return &IdentityContext_AuthenticatedAt{Call: c} +} + +// AuthenticatedAt provides a mock function with given fields: +func (_m *IdentityContext) AuthenticatedAt() time.Time { + ret := _m.Called() + + var r0 time.Time + if rf, ok := ret.Get(0).(func() time.Time); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Time) + } + + return r0 +} + +type IdentityContext_IsEmpty struct { + *mock.Call +} + +func (_m IdentityContext_IsEmpty) Return(_a0 bool) *IdentityContext_IsEmpty { + return &IdentityContext_IsEmpty{Call: _m.Call.Return(_a0)} +} + +func (_m *IdentityContext) OnIsEmpty() *IdentityContext_IsEmpty { + c := _m.On("IsEmpty") + return &IdentityContext_IsEmpty{Call: c} +} + +func (_m *IdentityContext) OnIsEmptyMatch(matchers ...interface{}) *IdentityContext_IsEmpty { + c := _m.On("IsEmpty", matchers...) + return &IdentityContext_IsEmpty{Call: c} +} + +// IsEmpty provides a mock function with given fields: +func (_m *IdentityContext) IsEmpty() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +type IdentityContext_Scopes struct { + *mock.Call +} + +func (_m IdentityContext_Scopes) Return(_a0 sets.String) *IdentityContext_Scopes { + return &IdentityContext_Scopes{Call: _m.Call.Return(_a0)} +} + +func (_m *IdentityContext) OnScopes() *IdentityContext_Scopes { + c := _m.On("Scopes") + return &IdentityContext_Scopes{Call: c} +} + +func (_m *IdentityContext) OnScopesMatch(matchers ...interface{}) *IdentityContext_Scopes { + c := _m.On("Scopes", matchers...) + return &IdentityContext_Scopes{Call: c} +} + +// Scopes provides a mock function with given fields: +func (_m *IdentityContext) Scopes() sets.String { + ret := _m.Called() + + var r0 sets.String + if rf, ok := ret.Get(0).(func() sets.String); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sets.String) + } + } + + return r0 +} + +type IdentityContext_UserID struct { + *mock.Call +} + +func (_m IdentityContext_UserID) Return(_a0 string) *IdentityContext_UserID { + return &IdentityContext_UserID{Call: _m.Call.Return(_a0)} +} + +func (_m *IdentityContext) OnUserID() *IdentityContext_UserID { + c := _m.On("UserID") + return &IdentityContext_UserID{Call: c} +} + +func (_m *IdentityContext) OnUserIDMatch(matchers ...interface{}) *IdentityContext_UserID { + c := _m.On("UserID", matchers...) + return &IdentityContext_UserID{Call: c} +} + +// UserID provides a mock function with given fields: +func (_m *IdentityContext) UserID() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +type IdentityContext_UserInfo struct { + *mock.Call +} + +func (_m IdentityContext_UserInfo) Return(_a0 *service.UserInfoResponse) *IdentityContext_UserInfo { + return &IdentityContext_UserInfo{Call: _m.Call.Return(_a0)} +} + +func (_m *IdentityContext) OnUserInfo() *IdentityContext_UserInfo { + c := _m.On("UserInfo") + return &IdentityContext_UserInfo{Call: c} +} + +func (_m *IdentityContext) OnUserInfoMatch(matchers ...interface{}) *IdentityContext_UserInfo { + c := _m.On("UserInfo", matchers...) + return &IdentityContext_UserInfo{Call: c} +} + +// UserInfo provides a mock function with given fields: +func (_m *IdentityContext) UserInfo() *service.UserInfoResponse { + ret := _m.Called() + + var r0 *service.UserInfoResponse + if rf, ok := ret.Get(0).(func() *service.UserInfoResponse); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*service.UserInfoResponse) + } + } + + return r0 +} + +type IdentityContext_WithContext struct { + *mock.Call +} + +func (_m IdentityContext_WithContext) Return(_a0 context.Context) *IdentityContext_WithContext { + return &IdentityContext_WithContext{Call: _m.Call.Return(_a0)} +} + +func (_m *IdentityContext) OnWithContext(ctx context.Context) *IdentityContext_WithContext { + c := _m.On("WithContext", ctx) + return &IdentityContext_WithContext{Call: c} +} + +func (_m *IdentityContext) OnWithContextMatch(matchers ...interface{}) *IdentityContext_WithContext { + c := _m.On("WithContext", matchers...) + return &IdentityContext_WithContext{Call: c} +} + +// WithContext provides a mock function with given fields: ctx +func (_m *IdentityContext) WithContext(ctx context.Context) context.Context { + ret := _m.Called(ctx) + + var r0 context.Context + if rf, ok := ret.Get(0).(func(context.Context) context.Context); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(context.Context) + } + } + + return r0 +} diff --git a/auth/interfaces/mocks/o_auth2_provider.go b/auth/interfaces/mocks/o_auth2_provider.go new file mode 100644 index 0000000000..47cddb6510 --- /dev/null +++ b/auth/interfaces/mocks/o_auth2_provider.go @@ -0,0 +1,462 @@ +// Code generated by mockery v1.0.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + fosite "github.com/ory/fosite" + + http "net/http" + + interfaces "github.com/flyteorg/flyteadmin/auth/interfaces" + + jwk "github.com/lestrrat-go/jwx/jwk" + + mock "github.com/stretchr/testify/mock" + + oauth2 "github.com/ory/fosite/handler/oauth2" + + service "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" +) + +// OAuth2Provider is an autogenerated mock type for the OAuth2Provider type +type OAuth2Provider struct { + mock.Mock +} + +type OAuth2Provider_IntrospectToken struct { + *mock.Call +} + +func (_m OAuth2Provider_IntrospectToken) Return(_a0 fosite.TokenType, _a1 fosite.AccessRequester, _a2 error) *OAuth2Provider_IntrospectToken { + return &OAuth2Provider_IntrospectToken{Call: _m.Call.Return(_a0, _a1, _a2)} +} + +func (_m *OAuth2Provider) OnIntrospectToken(ctx context.Context, token string, tokenUse fosite.TokenType, session fosite.Session, scope ...string) *OAuth2Provider_IntrospectToken { + c := _m.On("IntrospectToken", ctx, token, tokenUse, session, scope) + return &OAuth2Provider_IntrospectToken{Call: c} +} + +func (_m *OAuth2Provider) OnIntrospectTokenMatch(matchers ...interface{}) *OAuth2Provider_IntrospectToken { + c := _m.On("IntrospectToken", matchers...) + return &OAuth2Provider_IntrospectToken{Call: c} +} + +// IntrospectToken provides a mock function with given fields: ctx, token, tokenUse, session, scope +func (_m *OAuth2Provider) IntrospectToken(ctx context.Context, token string, tokenUse fosite.TokenType, session fosite.Session, scope ...string) (fosite.TokenType, fosite.AccessRequester, error) { + _va := make([]interface{}, len(scope)) + for _i := range scope { + _va[_i] = scope[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, token, tokenUse, session) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 fosite.TokenType + if rf, ok := ret.Get(0).(func(context.Context, string, fosite.TokenType, fosite.Session, ...string) fosite.TokenType); ok { + r0 = rf(ctx, token, tokenUse, session, scope...) + } else { + r0 = ret.Get(0).(fosite.TokenType) + } + + var r1 fosite.AccessRequester + if rf, ok := ret.Get(1).(func(context.Context, string, fosite.TokenType, fosite.Session, ...string) fosite.AccessRequester); ok { + r1 = rf(ctx, token, tokenUse, session, scope...) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(fosite.AccessRequester) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(context.Context, string, fosite.TokenType, fosite.Session, ...string) error); ok { + r2 = rf(ctx, token, tokenUse, session, scope...) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +type OAuth2Provider_KeySet struct { + *mock.Call +} + +func (_m OAuth2Provider_KeySet) Return(_a0 jwk.Set) *OAuth2Provider_KeySet { + return &OAuth2Provider_KeySet{Call: _m.Call.Return(_a0)} +} + +func (_m *OAuth2Provider) OnKeySet() *OAuth2Provider_KeySet { + c := _m.On("KeySet") + return &OAuth2Provider_KeySet{Call: c} +} + +func (_m *OAuth2Provider) OnKeySetMatch(matchers ...interface{}) *OAuth2Provider_KeySet { + c := _m.On("KeySet", matchers...) + return &OAuth2Provider_KeySet{Call: c} +} + +// KeySet provides a mock function with given fields: +func (_m *OAuth2Provider) KeySet() jwk.Set { + ret := _m.Called() + + var r0 jwk.Set + if rf, ok := ret.Get(0).(func() jwk.Set); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jwk.Set) + } + } + + return r0 +} + +type OAuth2Provider_NewAccessRequest struct { + *mock.Call +} + +func (_m OAuth2Provider_NewAccessRequest) Return(_a0 fosite.AccessRequester, _a1 error) *OAuth2Provider_NewAccessRequest { + return &OAuth2Provider_NewAccessRequest{Call: _m.Call.Return(_a0, _a1)} +} + +func (_m *OAuth2Provider) OnNewAccessRequest(ctx context.Context, req *http.Request, session fosite.Session) *OAuth2Provider_NewAccessRequest { + c := _m.On("NewAccessRequest", ctx, req, session) + return &OAuth2Provider_NewAccessRequest{Call: c} +} + +func (_m *OAuth2Provider) OnNewAccessRequestMatch(matchers ...interface{}) *OAuth2Provider_NewAccessRequest { + c := _m.On("NewAccessRequest", matchers...) + return &OAuth2Provider_NewAccessRequest{Call: c} +} + +// NewAccessRequest provides a mock function with given fields: ctx, req, session +func (_m *OAuth2Provider) NewAccessRequest(ctx context.Context, req *http.Request, session fosite.Session) (fosite.AccessRequester, error) { + ret := _m.Called(ctx, req, session) + + var r0 fosite.AccessRequester + if rf, ok := ret.Get(0).(func(context.Context, *http.Request, fosite.Session) fosite.AccessRequester); ok { + r0 = rf(ctx, req, session) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(fosite.AccessRequester) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *http.Request, fosite.Session) error); ok { + r1 = rf(ctx, req, session) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type OAuth2Provider_NewAccessResponse struct { + *mock.Call +} + +func (_m OAuth2Provider_NewAccessResponse) Return(_a0 fosite.AccessResponder, _a1 error) *OAuth2Provider_NewAccessResponse { + return &OAuth2Provider_NewAccessResponse{Call: _m.Call.Return(_a0, _a1)} +} + +func (_m *OAuth2Provider) OnNewAccessResponse(ctx context.Context, requester fosite.AccessRequester) *OAuth2Provider_NewAccessResponse { + c := _m.On("NewAccessResponse", ctx, requester) + return &OAuth2Provider_NewAccessResponse{Call: c} +} + +func (_m *OAuth2Provider) OnNewAccessResponseMatch(matchers ...interface{}) *OAuth2Provider_NewAccessResponse { + c := _m.On("NewAccessResponse", matchers...) + return &OAuth2Provider_NewAccessResponse{Call: c} +} + +// NewAccessResponse provides a mock function with given fields: ctx, requester +func (_m *OAuth2Provider) NewAccessResponse(ctx context.Context, requester fosite.AccessRequester) (fosite.AccessResponder, error) { + ret := _m.Called(ctx, requester) + + var r0 fosite.AccessResponder + if rf, ok := ret.Get(0).(func(context.Context, fosite.AccessRequester) fosite.AccessResponder); ok { + r0 = rf(ctx, requester) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(fosite.AccessResponder) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, fosite.AccessRequester) error); ok { + r1 = rf(ctx, requester) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type OAuth2Provider_NewAuthorizeRequest struct { + *mock.Call +} + +func (_m OAuth2Provider_NewAuthorizeRequest) Return(_a0 fosite.AuthorizeRequester, _a1 error) *OAuth2Provider_NewAuthorizeRequest { + return &OAuth2Provider_NewAuthorizeRequest{Call: _m.Call.Return(_a0, _a1)} +} + +func (_m *OAuth2Provider) OnNewAuthorizeRequest(ctx context.Context, req *http.Request) *OAuth2Provider_NewAuthorizeRequest { + c := _m.On("NewAuthorizeRequest", ctx, req) + return &OAuth2Provider_NewAuthorizeRequest{Call: c} +} + +func (_m *OAuth2Provider) OnNewAuthorizeRequestMatch(matchers ...interface{}) *OAuth2Provider_NewAuthorizeRequest { + c := _m.On("NewAuthorizeRequest", matchers...) + return &OAuth2Provider_NewAuthorizeRequest{Call: c} +} + +// NewAuthorizeRequest provides a mock function with given fields: ctx, req +func (_m *OAuth2Provider) NewAuthorizeRequest(ctx context.Context, req *http.Request) (fosite.AuthorizeRequester, error) { + ret := _m.Called(ctx, req) + + var r0 fosite.AuthorizeRequester + if rf, ok := ret.Get(0).(func(context.Context, *http.Request) fosite.AuthorizeRequester); ok { + r0 = rf(ctx, req) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(fosite.AuthorizeRequester) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *http.Request) error); ok { + r1 = rf(ctx, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type OAuth2Provider_NewAuthorizeResponse struct { + *mock.Call +} + +func (_m OAuth2Provider_NewAuthorizeResponse) Return(_a0 fosite.AuthorizeResponder, _a1 error) *OAuth2Provider_NewAuthorizeResponse { + return &OAuth2Provider_NewAuthorizeResponse{Call: _m.Call.Return(_a0, _a1)} +} + +func (_m *OAuth2Provider) OnNewAuthorizeResponse(ctx context.Context, requester fosite.AuthorizeRequester, session fosite.Session) *OAuth2Provider_NewAuthorizeResponse { + c := _m.On("NewAuthorizeResponse", ctx, requester, session) + return &OAuth2Provider_NewAuthorizeResponse{Call: c} +} + +func (_m *OAuth2Provider) OnNewAuthorizeResponseMatch(matchers ...interface{}) *OAuth2Provider_NewAuthorizeResponse { + c := _m.On("NewAuthorizeResponse", matchers...) + return &OAuth2Provider_NewAuthorizeResponse{Call: c} +} + +// NewAuthorizeResponse provides a mock function with given fields: ctx, requester, session +func (_m *OAuth2Provider) NewAuthorizeResponse(ctx context.Context, requester fosite.AuthorizeRequester, session fosite.Session) (fosite.AuthorizeResponder, error) { + ret := _m.Called(ctx, requester, session) + + var r0 fosite.AuthorizeResponder + if rf, ok := ret.Get(0).(func(context.Context, fosite.AuthorizeRequester, fosite.Session) fosite.AuthorizeResponder); ok { + r0 = rf(ctx, requester, session) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(fosite.AuthorizeResponder) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, fosite.AuthorizeRequester, fosite.Session) error); ok { + r1 = rf(ctx, requester, session) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type OAuth2Provider_NewIntrospectionRequest struct { + *mock.Call +} + +func (_m OAuth2Provider_NewIntrospectionRequest) Return(_a0 fosite.IntrospectionResponder, _a1 error) *OAuth2Provider_NewIntrospectionRequest { + return &OAuth2Provider_NewIntrospectionRequest{Call: _m.Call.Return(_a0, _a1)} +} + +func (_m *OAuth2Provider) OnNewIntrospectionRequest(ctx context.Context, r *http.Request, session fosite.Session) *OAuth2Provider_NewIntrospectionRequest { + c := _m.On("NewIntrospectionRequest", ctx, r, session) + return &OAuth2Provider_NewIntrospectionRequest{Call: c} +} + +func (_m *OAuth2Provider) OnNewIntrospectionRequestMatch(matchers ...interface{}) *OAuth2Provider_NewIntrospectionRequest { + c := _m.On("NewIntrospectionRequest", matchers...) + return &OAuth2Provider_NewIntrospectionRequest{Call: c} +} + +// NewIntrospectionRequest provides a mock function with given fields: ctx, r, session +func (_m *OAuth2Provider) NewIntrospectionRequest(ctx context.Context, r *http.Request, session fosite.Session) (fosite.IntrospectionResponder, error) { + ret := _m.Called(ctx, r, session) + + var r0 fosite.IntrospectionResponder + if rf, ok := ret.Get(0).(func(context.Context, *http.Request, fosite.Session) fosite.IntrospectionResponder); ok { + r0 = rf(ctx, r, session) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(fosite.IntrospectionResponder) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *http.Request, fosite.Session) error); ok { + r1 = rf(ctx, r, session) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type OAuth2Provider_NewJWTSessionToken struct { + *mock.Call +} + +func (_m OAuth2Provider_NewJWTSessionToken) Return(_a0 *oauth2.JWTSession) *OAuth2Provider_NewJWTSessionToken { + return &OAuth2Provider_NewJWTSessionToken{Call: _m.Call.Return(_a0)} +} + +func (_m *OAuth2Provider) OnNewJWTSessionToken(subject string, appID string, issuer string, audience string, userInfoClaims *service.UserInfoResponse) *OAuth2Provider_NewJWTSessionToken { + c := _m.On("NewJWTSessionToken", subject, appID, issuer, audience, userInfoClaims) + return &OAuth2Provider_NewJWTSessionToken{Call: c} +} + +func (_m *OAuth2Provider) OnNewJWTSessionTokenMatch(matchers ...interface{}) *OAuth2Provider_NewJWTSessionToken { + c := _m.On("NewJWTSessionToken", matchers...) + return &OAuth2Provider_NewJWTSessionToken{Call: c} +} + +// NewJWTSessionToken provides a mock function with given fields: subject, appID, issuer, audience, userInfoClaims +func (_m *OAuth2Provider) NewJWTSessionToken(subject string, appID string, issuer string, audience string, userInfoClaims *service.UserInfoResponse) *oauth2.JWTSession { + ret := _m.Called(subject, appID, issuer, audience, userInfoClaims) + + var r0 *oauth2.JWTSession + if rf, ok := ret.Get(0).(func(string, string, string, string, *service.UserInfoResponse) *oauth2.JWTSession); ok { + r0 = rf(subject, appID, issuer, audience, userInfoClaims) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*oauth2.JWTSession) + } + } + + return r0 +} + +type OAuth2Provider_NewRevocationRequest struct { + *mock.Call +} + +func (_m OAuth2Provider_NewRevocationRequest) Return(_a0 error) *OAuth2Provider_NewRevocationRequest { + return &OAuth2Provider_NewRevocationRequest{Call: _m.Call.Return(_a0)} +} + +func (_m *OAuth2Provider) OnNewRevocationRequest(ctx context.Context, r *http.Request) *OAuth2Provider_NewRevocationRequest { + c := _m.On("NewRevocationRequest", ctx, r) + return &OAuth2Provider_NewRevocationRequest{Call: c} +} + +func (_m *OAuth2Provider) OnNewRevocationRequestMatch(matchers ...interface{}) *OAuth2Provider_NewRevocationRequest { + c := _m.On("NewRevocationRequest", matchers...) + return &OAuth2Provider_NewRevocationRequest{Call: c} +} + +// NewRevocationRequest provides a mock function with given fields: ctx, r +func (_m *OAuth2Provider) NewRevocationRequest(ctx context.Context, r *http.Request) error { + ret := _m.Called(ctx, r) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *http.Request) error); ok { + r0 = rf(ctx, r) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type OAuth2Provider_ValidateAccessToken struct { + *mock.Call +} + +func (_m OAuth2Provider_ValidateAccessToken) Return(_a0 interfaces.IdentityContext, _a1 error) *OAuth2Provider_ValidateAccessToken { + return &OAuth2Provider_ValidateAccessToken{Call: _m.Call.Return(_a0, _a1)} +} + +func (_m *OAuth2Provider) OnValidateAccessToken(ctx context.Context, expectedAudience string, tokenStr string) *OAuth2Provider_ValidateAccessToken { + c := _m.On("ValidateAccessToken", ctx, expectedAudience, tokenStr) + return &OAuth2Provider_ValidateAccessToken{Call: c} +} + +func (_m *OAuth2Provider) OnValidateAccessTokenMatch(matchers ...interface{}) *OAuth2Provider_ValidateAccessToken { + c := _m.On("ValidateAccessToken", matchers...) + return &OAuth2Provider_ValidateAccessToken{Call: c} +} + +// ValidateAccessToken provides a mock function with given fields: ctx, expectedAudience, tokenStr +func (_m *OAuth2Provider) ValidateAccessToken(ctx context.Context, expectedAudience string, tokenStr string) (interfaces.IdentityContext, error) { + ret := _m.Called(ctx, expectedAudience, tokenStr) + + var r0 interfaces.IdentityContext + if rf, ok := ret.Get(0).(func(context.Context, string, string) interfaces.IdentityContext); ok { + r0 = rf(ctx, expectedAudience, tokenStr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interfaces.IdentityContext) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, expectedAudience, tokenStr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// WriteAccessError provides a mock function with given fields: rw, requester, err +func (_m *OAuth2Provider) WriteAccessError(rw http.ResponseWriter, requester fosite.AccessRequester, err error) { + _m.Called(rw, requester, err) +} + +// WriteAccessResponse provides a mock function with given fields: rw, requester, responder +func (_m *OAuth2Provider) WriteAccessResponse(rw http.ResponseWriter, requester fosite.AccessRequester, responder fosite.AccessResponder) { + _m.Called(rw, requester, responder) +} + +// WriteAuthorizeError provides a mock function with given fields: rw, requester, err +func (_m *OAuth2Provider) WriteAuthorizeError(rw http.ResponseWriter, requester fosite.AuthorizeRequester, err error) { + _m.Called(rw, requester, err) +} + +// WriteAuthorizeResponse provides a mock function with given fields: rw, requester, responder +func (_m *OAuth2Provider) WriteAuthorizeResponse(rw http.ResponseWriter, requester fosite.AuthorizeRequester, responder fosite.AuthorizeResponder) { + _m.Called(rw, requester, responder) +} + +// WriteIntrospectionError provides a mock function with given fields: rw, err +func (_m *OAuth2Provider) WriteIntrospectionError(rw http.ResponseWriter, err error) { + _m.Called(rw, err) +} + +// WriteIntrospectionResponse provides a mock function with given fields: rw, r +func (_m *OAuth2Provider) WriteIntrospectionResponse(rw http.ResponseWriter, r fosite.IntrospectionResponder) { + _m.Called(rw, r) +} + +// WriteRevocationResponse provides a mock function with given fields: rw, err +func (_m *OAuth2Provider) WriteRevocationResponse(rw http.ResponseWriter, err error) { + _m.Called(rw, err) +} diff --git a/auth/interfaces/mocks/o_auth2_resource_server.go b/auth/interfaces/mocks/o_auth2_resource_server.go new file mode 100644 index 0000000000..d9cf118c4f --- /dev/null +++ b/auth/interfaces/mocks/o_auth2_resource_server.go @@ -0,0 +1,56 @@ +// Code generated by mockery v1.0.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + interfaces "github.com/flyteorg/flyteadmin/auth/interfaces" + mock "github.com/stretchr/testify/mock" +) + +// OAuth2ResourceServer is an autogenerated mock type for the OAuth2ResourceServer type +type OAuth2ResourceServer struct { + mock.Mock +} + +type OAuth2ResourceServer_ValidateAccessToken struct { + *mock.Call +} + +func (_m OAuth2ResourceServer_ValidateAccessToken) Return(_a0 interfaces.IdentityContext, _a1 error) *OAuth2ResourceServer_ValidateAccessToken { + return &OAuth2ResourceServer_ValidateAccessToken{Call: _m.Call.Return(_a0, _a1)} +} + +func (_m *OAuth2ResourceServer) OnValidateAccessToken(ctx context.Context, expectedAudience string, tokenStr string) *OAuth2ResourceServer_ValidateAccessToken { + c := _m.On("ValidateAccessToken", ctx, expectedAudience, tokenStr) + return &OAuth2ResourceServer_ValidateAccessToken{Call: c} +} + +func (_m *OAuth2ResourceServer) OnValidateAccessTokenMatch(matchers ...interface{}) *OAuth2ResourceServer_ValidateAccessToken { + c := _m.On("ValidateAccessToken", matchers...) + return &OAuth2ResourceServer_ValidateAccessToken{Call: c} +} + +// ValidateAccessToken provides a mock function with given fields: ctx, expectedAudience, tokenStr +func (_m *OAuth2ResourceServer) ValidateAccessToken(ctx context.Context, expectedAudience string, tokenStr string) (interfaces.IdentityContext, error) { + ret := _m.Called(ctx, expectedAudience, tokenStr) + + var r0 interfaces.IdentityContext + if rf, ok := ret.Get(0).(func(context.Context, string, string) interfaces.IdentityContext); ok { + r0 = rf(ctx, expectedAudience, tokenStr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interfaces.IdentityContext) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, expectedAudience, tokenStr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/auth/token.go b/auth/token.go new file mode 100644 index 0000000000..974463f9ab --- /dev/null +++ b/auth/token.go @@ -0,0 +1,137 @@ +package auth + +import ( + "context" + "encoding/json" + "strings" + "time" + + "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" + + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/flyteorg/flyteadmin/auth/interfaces" + + "github.com/coreos/go-oidc" + "github.com/flyteorg/flytestdlib/errors" + "github.com/flyteorg/flytestdlib/logger" + grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" + "golang.org/x/oauth2" +) + +const ( + ErrRefreshingToken errors.ErrorCode = "TOKEN_REFRESH_FAILURE" + ErrTokenExpired errors.ErrorCode = "JWT_EXPIRED" + ErrJwtValidation errors.ErrorCode = "JWT_VERIFICATION_FAILED" +) + +// Refresh a JWT +func GetRefreshedToken(ctx context.Context, oauth *oauth2.Config, accessToken, refreshToken string) (*oauth2.Token, error) { + logger.Debugf(ctx, "Attempting to refresh token") + originalToken := oauth2.Token{ + AccessToken: accessToken, + RefreshToken: refreshToken, + Expiry: time.Now().Add(-1 * time.Minute), // force expired by setting to the past + } + + tokenSource := oauth.TokenSource(ctx, &originalToken) + newToken, err := tokenSource.Token() + if err != nil { + logger.Errorf(ctx, "Error refreshing token %s", err) + return nil, errors.Wrapf(ErrRefreshingToken, err, "Error refreshing token") + } + + return newToken, nil +} + +func ParseIDTokenAndValidate(ctx context.Context, clientID, rawIDToken string, provider *oidc.Provider) (*oidc.IDToken, error) { + cfg := &oidc.Config{ + ClientID: clientID, + } + + if len(clientID) == 0 { + cfg.SkipClientIDCheck = true + cfg.SkipIssuerCheck = true + cfg.SkipExpiryCheck = true + } + + var verifier = provider.Verifier(cfg) + + idToken, err := verifier.Verify(ctx, rawIDToken) + if err != nil { + logger.Debugf(ctx, "JWT parsing with claims failed %s", err) + flyteErr := errors.Wrapf(ErrJwtValidation, err, "jwt parse with claims failed") + // TODO: Contribute an errors package to the go-oidc library for proper error handling + if strings.Contains(err.Error(), "token is expired") { + return idToken, errors.Wrapf(ErrTokenExpired, flyteErr, "token is expired") + } + + return idToken, flyteErr + } + + return idToken, nil +} + +// GRPCGetIdentityFromAccessToken attempts to extract a token from the context, and will then call the validation +// function, passing up any errors. +func GRPCGetIdentityFromAccessToken(ctx context.Context, authCtx interfaces.AuthenticationContext) ( + interfaces.IdentityContext, error) { + + tokenStr, err := grpcauth.AuthFromMD(ctx, BearerScheme) + if err != nil { + logger.Debugf(ctx, "Could not retrieve bearer token from metadata %v", err) + return nil, errors.Wrapf(ErrJwtValidation, err, "Could not retrieve bearer token from metadata") + } + + if tokenStr == "" { + logger.Debugf(ctx, "Found Bearer scheme but token was blank") + return nil, errors.Errorf(ErrJwtValidation, "%v token is blank", IDTokenScheme) + } + + expectedAudience := GetPublicURL(ctx, nil, authCtx.Options()).String() + return authCtx.OAuth2ResourceServer().ValidateAccessToken(ctx, expectedAudience, tokenStr) +} + +// GRPCGetIdentityFromIDToken attempts to extract a token from the context, and will then call the validation function, +// passing up any errors. +func GRPCGetIdentityFromIDToken(ctx context.Context, clientID string, provider *oidc.Provider) ( + interfaces.IdentityContext, error) { + + tokenStr, err := grpcauth.AuthFromMD(ctx, IDTokenScheme) + if err != nil { + logger.Debugf(ctx, "Could not retrieve id token from metadata %v", err) + return nil, errors.Wrapf(ErrJwtValidation, err, "Could not retrieve id token from metadata") + } + + if tokenStr == "" { + logger.Debugf(ctx, "Found Bearer scheme but token was blank") + return nil, errors.Errorf(ErrJwtValidation, "%v token is blank", IDTokenScheme) + } + + meta := metautils.ExtractIncoming(ctx) + userInfoStr := meta.Get(UserInfoMDKey) + userInfo := &service.UserInfoResponse{} + if len(userInfoStr) > 0 { + err = json.Unmarshal([]byte(userInfoStr), userInfo) + if err != nil { + logger.Infof(ctx, "Could not unmarshal user info from metadata %v", err) + } + } + + return IdentityContextFromIDTokenToken(ctx, tokenStr, clientID, provider, userInfo) +} + +func IdentityContextFromIDTokenToken(ctx context.Context, tokenStr, clientID string, provider *oidc.Provider, + userInfo *service.UserInfoResponse) (interfaces.IdentityContext, error) { + + idToken, err := ParseIDTokenAndValidate(ctx, clientID, tokenStr, provider) + if err != nil { + return nil, err + } + + // TODO: Document why automatically specify "all" scope + return NewIdentityContext(idToken.Audience[0], idToken.Subject, "", idToken.IssuedAt, + sets.NewString(ScopeAll), userInfo), nil +} diff --git a/pkg/auth/token_test.go b/auth/token_test.go similarity index 100% rename from pkg/auth/token_test.go rename to auth/token_test.go diff --git a/auth/user_info_provider.go b/auth/user_info_provider.go new file mode 100644 index 0000000000..1619378bf9 --- /dev/null +++ b/auth/user_info_provider.go @@ -0,0 +1,21 @@ +package auth + +import ( + "context" + + "github.com/flyteorg/flyteidl/gen/pb-go/flyteidl/service" +) + +type UserInfoProvider struct { +} + +// UserInfo returns user_info claims about the currently logged in user. +// See the OpenID Connect spec at https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse for more information. +func (s UserInfoProvider) UserInfo(ctx context.Context, _ *service.UserInfoRequest) (*service.UserInfoResponse, error) { + identityContext := IdentityContextFromContext(ctx) + return identityContext.UserInfo(), nil +} + +func NewUserInfoProvider() UserInfoProvider { + return UserInfoProvider{} +} diff --git a/cmd/entrypoints/k8s_secret.go b/cmd/entrypoints/k8s_secret.go new file mode 100644 index 0000000000..5671e70750 --- /dev/null +++ b/cmd/entrypoints/k8s_secret.go @@ -0,0 +1,175 @@ +package entrypoints + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/flyteorg/flytestdlib/logger" + kubeErrors "k8s.io/apimachinery/pkg/api/errors" + + "github.com/flyteorg/flyteadmin/auth" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/flyteorg/flyteadmin/pkg/config" + "github.com/flyteorg/flyteadmin/pkg/executioncluster/impl" + "github.com/flyteorg/flyteadmin/pkg/runtime" + "github.com/flyteorg/flytestdlib/errors" + "github.com/flyteorg/flytestdlib/promutils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "k8s.io/client-go/kubernetes" +) + +const ( + PodNamespaceEnvVar = "POD_NAMESPACE" + podDefaultNamespace = "default" +) + +var ( + secretName string + secretsLocalPath string + forceUpdate bool +) + +var secretsCmd = &cobra.Command{ + Use: "secret", + Aliases: []string{"secrets"}, +} + +var secretsPersistCmd = &cobra.Command{ + Use: "create", + Long: `Creates a new secret (or noop if one exists unless --force is provided) using keys found in the provided path. +If POD_NAMESPACE env var is set, the secret will be created in that namespace. +`, + Example: ` +Create a secret using default name (flyte-admin-auth) in default namespace +flyteadmin secret create --fromPath=/path/in/container + +Override an existing secret if one exists (reads secrets from default path /etc/secrets/): +flyteadmin secret create --name "my-auth-secrets" --force +`, + RunE: func(cmd *cobra.Command, args []string) error { + return persistSecrets(context.Background(), cmd.Flags()) + }, +} + +func init() { + secretsPersistCmd.Flags().StringVar(&secretName, "name", "flyte-admin-auth", "Chooses secret name to create/update") + secretsPersistCmd.Flags().StringVar(&secretsLocalPath, "fromPath", filepath.Join(string(os.PathSeparator), "etc", "secrets"), "Chooses secret name to create/update") + secretsPersistCmd.Flags().BoolVarP(&forceUpdate, "force", "f", false, "Whether to update the secret if one exists") + secretsCmd.AddCommand(secretsPersistCmd) + secretsCmd.AddCommand(auth.GetInitSecretsCommand()) + + RootCmd.AddCommand(secretsCmd) +} + +func buildK8sSecretData(_ context.Context, localPath string) (map[string][]byte, error) { + secretsData := make(map[string][]byte, 4) + + err := filepath.Walk(localPath, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return nil + } + + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + + secretsData[strings.TrimPrefix(path, filepath.Dir(path)+string(filepath.Separator))] = data + return nil + }) + + if err != nil { + return nil, err + } + + return secretsData, nil +} + +func persistSecrets(ctx context.Context, _ *pflag.FlagSet) error { + serverCfg := config.GetConfig() + configuration := runtime.NewConfigurationProvider() + scope := promutils.NewScope(configuration.ApplicationConfiguration().GetTopLevelConfig().MetricsScope) + clusterClient, err := impl.NewInCluster(scope.NewSubScope("secrets"), serverCfg.KubeConfig, serverCfg.Master) + if err != nil { + return err + } + + targets := clusterClient.GetAllValidTargets() + // Since we are targeting the cluster Admin is running in, this list should contain exactly one item + if len(targets) != 1 { + return fmt.Errorf("expected exactly 1 valid target cluster. Found [%v]", len(targets)) + } + + clusterCfg := targets[0].Config + kubeClient, err := kubernetes.NewForConfig(&clusterCfg) + if err != nil { + return errors.Wrapf("INIT", err, "Error building kubernetes clientset") + } + + podNamespace, found := os.LookupEnv(PodNamespaceEnvVar) + if !found { + podNamespace = podDefaultNamespace + } + + secretsData, err := buildK8sSecretData(ctx, secretsLocalPath) + if err != nil { + return errors.Wrapf("INIT", err, "Error building k8s secret's data field.") + } + + secretsClient := kubeClient.CoreV1().Secrets(podNamespace) + newSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: podNamespace, + }, + Type: corev1.SecretTypeOpaque, + Data: secretsData, + } + + _, err = secretsClient.Create(ctx, newSecret, metav1.CreateOptions{}) + + if err != nil && kubeErrors.IsAlreadyExists(err) { + if forceUpdate { + logger.Infof(ctx, "A secret already exists with the same name. Attempting to update it.") + _, err = secretsClient.Update(ctx, newSecret, metav1.UpdateOptions{}) + } else { + var existingSecret *corev1.Secret + existingSecret, err = secretsClient.Get(ctx, newSecret.Name, metav1.GetOptions{}) + if err != nil { + logger.Infof(ctx, "Failed to retrieve existing secret. Error: %v", err) + return err + } + + if existingSecret.Data == nil { + existingSecret.Data = map[string][]byte{} + } + + needsUpdate := false + for key, val := range secretsData { + if _, found := existingSecret.Data[key]; !found { + existingSecret.Data[key] = val + needsUpdate = true + } + } + + if needsUpdate { + _, err = secretsClient.Update(ctx, existingSecret, metav1.UpdateOptions{}) + if err != nil && kubeErrors.IsConflict(err) { + logger.Infof(ctx, "Another instance of flyteadmin has updated the same secret. Ignoring this update") + err = nil + } + } + } + + return err + } + + return err +} diff --git a/cmd/entrypoints/serve.go b/cmd/entrypoints/serve.go index 18fb279439..081bd924f3 100644 --- a/cmd/entrypoints/serve.go +++ b/cmd/entrypoints/serve.go @@ -3,12 +3,20 @@ package entrypoints import ( "context" "crypto/tls" - "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/flyteorg/flytepropeller/pkg/controller/nodes/task/secretmanager" + + authConfig "github.com/flyteorg/flyteadmin/auth/config" + + "github.com/flyteorg/flyteadmin/auth/authzserver" "github.com/gorilla/handlers" - "github.com/flyteorg/flyteadmin/pkg/auth" - "github.com/flyteorg/flyteadmin/pkg/auth/interfaces" + "github.com/flyteorg/flyteadmin/auth" + "github.com/flyteorg/flyteadmin/auth/interfaces" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" @@ -30,13 +38,11 @@ import ( "github.com/flyteorg/flyteadmin/pkg/config" "github.com/flyteorg/flyteadmin/pkg/rpc/adminservice" - rpcConfig "github.com/flyteorg/flyteadmin/pkg/rpc/config" - "github.com/spf13/cobra" "github.com/flyteorg/flytestdlib/contextutils" "github.com/flyteorg/flytestdlib/promutils/labeled" - grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + grpcPrometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ) @@ -52,15 +58,17 @@ var serveCmd = &cobra.Command{ serverConfig := config.GetConfig() if serverConfig.Security.Secure { - return serveGatewaySecure(ctx, serverConfig) + return serveGatewaySecure(ctx, serverConfig, authConfig.GetConfig()) } - return serveGatewayInsecure(ctx, serverConfig) + + return serveGatewayInsecure(ctx, serverConfig, authConfig.GetConfig()) }, } func init() { // Command information RootCmd.AddCommand(serveCmd) + RootCmd.AddCommand(secretsCmd) // Set Keys labeled.SetMetricKeys(contextutils.AppNameKey, contextutils.ProjectKey, contextutils.DomainKey, @@ -68,30 +76,51 @@ func init() { contextutils.TaskTypeKey, common.RuntimeTypeKey, common.RuntimeVersionKey) } +func blanketAuthorization(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) ( + resp interface{}, err error) { + + identityContext := auth.IdentityContextFromContext(ctx) + if identityContext.IsEmpty() { + return handler(ctx, req) + } + + if !identityContext.Scopes().Has(auth.ScopeAll) { + return nil, status.Errorf(codes.Unauthenticated, "authenticated user doesn't have required scope") + } + + return handler(ctx, req) +} + // Creates a new gRPC Server with all the configuration -func newGRPCServer(ctx context.Context, cfg *config.ServerConfig, authContext interfaces.AuthenticationContext, +func newGRPCServer(ctx context.Context, cfg *config.ServerConfig, authCtx interfaces.AuthenticationContext, opts ...grpc.ServerOption) (*grpc.Server, error) { // Not yet implemented for streaming var chainedUnaryInterceptors grpc.UnaryServerInterceptor if cfg.Security.UseAuth { logger.Infof(ctx, "Creating gRPC server with authentication") - chainedUnaryInterceptors = grpc_middleware.ChainUnaryServer(grpc_prometheus.UnaryServerInterceptor, - auth.GetAuthenticationCustomMetadataInterceptor(authContext), - grpcauth.UnaryServerInterceptor(auth.GetAuthenticationInterceptor(authContext)), + chainedUnaryInterceptors = grpc_middleware.ChainUnaryServer(grpcPrometheus.UnaryServerInterceptor, + auth.GetAuthenticationCustomMetadataInterceptor(authCtx), + grpcauth.UnaryServerInterceptor(auth.GetAuthenticationInterceptor(authCtx)), auth.AuthenticationLoggingInterceptor, + blanketAuthorization, ) } else { logger.Infof(ctx, "Creating gRPC server without authentication") - chainedUnaryInterceptors = grpc_middleware.ChainUnaryServer(grpc_prometheus.UnaryServerInterceptor) + chainedUnaryInterceptors = grpc_middleware.ChainUnaryServer(grpcPrometheus.UnaryServerInterceptor) } + serverOpts := []grpc.ServerOption{ - grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor), + grpc.StreamInterceptor(grpcPrometheus.StreamServerInterceptor), grpc.UnaryInterceptor(chainedUnaryInterceptors), } serverOpts = append(serverOpts, opts...) grpcServer := grpc.NewServer(serverOpts...) - grpc_prometheus.Register(grpcServer) + grpcPrometheus.Register(grpcServer) flyteService.RegisterAdminServiceServer(grpcServer, adminservice.NewAdminServer(cfg.KubeConfig, cfg.Master)) + if cfg.Security.UseAuth { + flyteService.RegisterAuthMetadataServiceServer(grpcServer, authCtx.AuthMetadataService()) + flyteService.RegisterIdentityServiceServer(grpcServer, authCtx.IdentityService()) + } healthServer := health.NewServer() healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING) @@ -123,7 +152,7 @@ func healthCheckFunc(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func newHTTPServer(ctx context.Context, cfg *config.ServerConfig, authContext interfaces.AuthenticationContext, +func newHTTPServer(ctx context.Context, cfg *config.ServerConfig, authCfg *authConfig.Config, authCtx interfaces.AuthenticationContext, grpcAddress string, grpcConnectionOpts ...grpc.DialOption) (*http.ServeMux, error) { // Register the server that will serve HTTP/REST Traffic @@ -136,36 +165,23 @@ func newHTTPServer(ctx context.Context, cfg *config.ServerConfig, authContext in // This endpoint will serve the OpenAPI2 spec generated by the swagger protoc plugin, and bundled by go-bindata mux.HandleFunc("/api/v1/openapi", GetHandleOpenapiSpec(ctx)) - // Handles serving config values required to initialize a flyte-client config - mux.HandleFunc("/config/v1/flyte_client", rpcConfig.HandleFlyteCliConfigFunc(ctx, cfg)) - var gwmuxOptions = make([]runtime.ServeMuxOption, 0) // This option means that http requests are served with protobufs, instead of json. We always want this. gwmuxOptions = append(gwmuxOptions, runtime.WithMarshalerOption("application/octet-stream", &runtime.ProtoMarshaller{})) if cfg.Security.UseAuth { - // Add HTTP handlers for OAuth2 endpoints - mux.HandleFunc("/login", auth.RefreshTokensIfExists(ctx, authContext, - auth.GetLoginHandler(ctx, authContext))) - mux.HandleFunc("/logout", auth.GetLogoutEndpointHandler(ctx, authContext)) - mux.HandleFunc("/callback", auth.GetCallbackHandler(ctx, authContext)) - // Install the user info endpoint if there is a user info url configured. - if authContext.GetUserInfoURL() != nil && authContext.GetUserInfoURL().String() != "" { - mux.HandleFunc("/me", auth.GetMeEndpointHandler(ctx, authContext)) - } - - // The metadata endpoint is an RFC-defined constant, but we need a leading / for the handler to pattern match correctly. - mux.HandleFunc(fmt.Sprintf("/%s", auth.OIdCMetadataEndpoint), auth.GetOIdCMetadataEndpointRedirectHandler(ctx, authContext)) + // Add HTTP handlers for OIDC endpoints + auth.RegisterHandlers(ctx, mux, authCtx) - // The metadata endpoint is an RFC-defined constant, but we need a leading / for the handler to pattern match correctly. - mux.HandleFunc(fmt.Sprintf("/%s", auth.OAuth2MetadataEndpoint), auth.GetOAuth2MetadataEndpointRedirectHandler(ctx, authContext)) + // Add HTTP handlers for OAuth2 endpoints + authzserver.RegisterHandlers(mux, authCtx) // This option translates HTTP authorization data (cookies) into a gRPC metadata field - gwmuxOptions = append(gwmuxOptions, runtime.WithMetadata(auth.GetHTTPRequestCookieToMetadataHandler(authContext))) + gwmuxOptions = append(gwmuxOptions, runtime.WithMetadata(auth.GetHTTPRequestCookieToMetadataHandler(authCtx))) // In an attempt to be able to selectively enforce whether or not authentication is required, we're going to tag // the requests that come from the HTTP gateway. See the enforceHttp/Grpc options for more information. - gwmuxOptions = append(gwmuxOptions, runtime.WithMetadata(auth.GetHTTPMetadataTaggingHandler(authContext))) + gwmuxOptions = append(gwmuxOptions, runtime.WithMetadata(auth.GetHTTPMetadataTaggingHandler())) } // Create the grpc-gateway server with the options specified @@ -176,30 +192,62 @@ func newHTTPServer(ctx context.Context, cfg *config.ServerConfig, authContext in return nil, errors.Wrap(err, "error registering admin service") } + err = flyteService.RegisterAuthMetadataServiceHandlerFromEndpoint(ctx, gwmux, grpcAddress, grpcConnectionOpts) + if err != nil { + return nil, errors.Wrap(err, "error registering auth service") + } + + err = flyteService.RegisterIdentityServiceHandlerFromEndpoint(ctx, gwmux, grpcAddress, grpcConnectionOpts) + if err != nil { + return nil, errors.Wrap(err, "error registering identity service") + } + mux.Handle("/", gwmux) return mux, nil } -func serveGatewayInsecure(ctx context.Context, cfg *config.ServerConfig) error { +func serveGatewayInsecure(ctx context.Context, cfg *config.ServerConfig, authCfg *authConfig.Config) error { logger.Infof(ctx, "Serving Flyte Admin Insecure") // This will parse configuration and create the necessary objects for dealing with auth - var authContext interfaces.AuthenticationContext + var authCtx interfaces.AuthenticationContext var err error // This code is here to support authentication without SSL. This setup supports a network topology where // Envoy does the SSL termination. The final hop is made over localhost only on a trusted machine. // Warning: Running authentication without SSL in any other topology is a severe security flaw. // See the auth.Config object for additional settings as well. if cfg.Security.UseAuth { - authContext, err = auth.NewAuthenticationContext(ctx, cfg.Security.Oauth) + sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) + var oauth2Provider interfaces.OAuth2Provider + var oauth2ResourceServer interfaces.OAuth2ResourceServer + if authCfg.AppAuth.AuthServerType == authConfig.AuthorizationServerTypeSelf { + oauth2Provider, err = authzserver.NewProvider(ctx, authCfg.AppAuth.SelfAuthServer, sm) + if err != nil { + logger.Errorf(ctx, "Error creating authorization server %s", err) + return err + } + + oauth2ResourceServer = oauth2Provider + } else { + oauth2ResourceServer, err = authzserver.NewOAuth2ResourceServer(ctx, authCfg.AppAuth.ExternalAuthServer, authCfg.UserAuth.OpenID.BaseURL) + if err != nil { + logger.Errorf(ctx, "Error creating resource server %s", err) + return err + } + } + + oauth2MetadataProvider := authzserver.NewService(authCfg) + oidcUserInfoProvider := auth.NewUserInfoProvider() + + authCtx, err = auth.NewAuthenticationContext(ctx, sm, oauth2Provider, oauth2ResourceServer, oauth2MetadataProvider, oidcUserInfoProvider, authCfg) if err != nil { logger.Errorf(ctx, "Error creating auth context %s", err) return err } } - grpcServer, err := newGRPCServer(ctx, cfg, authContext) + grpcServer, err := newGRPCServer(ctx, cfg, authCtx) if err != nil { return errors.Wrap(err, "failed to create GRPC server") } @@ -216,7 +264,7 @@ func serveGatewayInsecure(ctx context.Context, cfg *config.ServerConfig) error { }() logger.Infof(ctx, "Starting HTTP/1 Gateway server on %s", cfg.GetHostAddress()) - httpServer, err := newHTTPServer(ctx, cfg, authContext, cfg.GetGrpcHostAddress(), grpc.WithInsecure(), + httpServer, err := newHTTPServer(ctx, cfg, authCfg, authCtx, cfg.GetGrpcHostAddress(), grpc.WithInsecure(), grpc.WithMaxHeaderListSize(common.MaxResponseStatusBytes)) if err != nil { return err @@ -256,22 +304,44 @@ func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Ha }) } -func serveGatewaySecure(ctx context.Context, cfg *config.ServerConfig) error { +func serveGatewaySecure(ctx context.Context, cfg *config.ServerConfig, authCfg *authConfig.Config) error { certPool, cert, err := server.GetSslCredentials(ctx, cfg.Security.Ssl.CertificateFile, cfg.Security.Ssl.KeyFile) if err != nil { return err } // This will parse configuration and create the necessary objects for dealing with auth - var authContext interfaces.AuthenticationContext + var authCtx interfaces.AuthenticationContext if cfg.Security.UseAuth { - authContext, err = auth.NewAuthenticationContext(ctx, cfg.Security.Oauth) + sm := secretmanager.NewFileEnvSecretManager(secretmanager.GetConfig()) + var oauth2Provider interfaces.OAuth2Provider + var oauth2ResourceServer interfaces.OAuth2ResourceServer + if authCfg.AppAuth.AuthServerType == authConfig.AuthorizationServerTypeSelf { + oauth2Provider, err = authzserver.NewProvider(ctx, authCfg.AppAuth.SelfAuthServer, sm) + if err != nil { + logger.Errorf(ctx, "Error creating authorization server %s", err) + return err + } + + oauth2ResourceServer = oauth2Provider + } else { + oauth2ResourceServer, err = authzserver.NewOAuth2ResourceServer(ctx, authCfg.AppAuth.ExternalAuthServer, authCfg.UserAuth.OpenID.BaseURL) + if err != nil { + logger.Errorf(ctx, "Error creating resource server %s", err) + return err + } + } + + oauth2MetadataProvider := authzserver.NewService(authCfg) + oidcUserInfoProvider := auth.NewUserInfoProvider() + + authCtx, err = auth.NewAuthenticationContext(ctx, sm, oauth2Provider, oauth2ResourceServer, oauth2MetadataProvider, oidcUserInfoProvider, authCfg) if err != nil { logger.Errorf(ctx, "Error creating auth context %s", err) return err } } - grpcServer, err := newGRPCServer(ctx, cfg, authContext, + grpcServer, err := newGRPCServer(ctx, cfg, authCtx, grpc.Creds(credentials.NewServerTLSFromCert(cert))) if err != nil { return errors.Wrap(err, "failed to create GRPC server") @@ -282,7 +352,7 @@ func serveGatewaySecure(ctx context.Context, cfg *config.ServerConfig) error { ServerName: cfg.GetHostAddress(), RootCAs: certPool, }) - httpServer, err := newHTTPServer(ctx, cfg, authContext, cfg.GetHostAddress(), grpc.WithTransportCredentials(dialCreds)) + httpServer, err := newHTTPServer(ctx, cfg, authCfg, authCtx, cfg.GetHostAddress(), grpc.WithTransportCredentials(dialCreds)) if err != nil { return err } diff --git a/flyteadmin_config.yaml b/flyteadmin_config.yaml index 0b55257d85..ea30c60b93 100644 --- a/flyteadmin_config.yaml +++ b/flyteadmin_config.yaml @@ -6,32 +6,46 @@ server: httpPort: 8088 grpcPort: 8089 grpcServerReflection: true + kube-config: /Users/haythamabuelfutuh/kubeconfig/k3s/k3s.yaml security: secure: false - useAuth: false + useAuth: true allowCors: true allowedOrigins: # Accepting all domains for Sandbox installation - "*" allowedHeaders: - "Content-Type" - oauth: - clientId: yourclientid - clientSecretFile: "/path/to/oauth/secret" - authorizeUrl: "https://idp.com/oauth2/authorize" - tokenUrl: "https://idp.com/oauth2/token" - callbackUrl: "https://localhost:8088/callback" - cookieHashKeyFile: "/path/to/admin_cookie_hash_key" - cookieBlockKeyFile: "/path/to/admin_cookie_block_key" - redirectUrl: "/api/v1/projects" - claims: - iss: "https://idp.com" - aud: "api://default" - idpUserInfoEndpoint: "/v1/userinfo" - thirdPartyConfig: - flyteClient: - clientId: yourPublicAppClientId - redirectUri: yourRegisteredLoginRedirectUri +# Okta OIdC only +auth: + authorizedUris: + - https://efdf60eb2525.ngrok.io + - http://flyteadmin:80 + userAuth: + openId: + # Put the URL of the OpenID Connect provider. + baseUrl: https://dev-14186422.okta.com/oauth2/auskngnn7uBViQq6b5d6 + scopes: + - profile + - openid + - offline_access # Uncomment if OIdC supports issuing refresh tokens. + # Replace with the client id created for Flyte. + clientId: 0oakkheteNjCMERst5d6 + +# Okta OIdC and OAuth2 +#auth: +# appAuth: +# authServerType: External +# userAuth: +# openId: +# # Put the URL of the OpenID Connect provider. +# baseUrl: https://dev-14186422.okta.com/oauth2/auskngnn7uBViQq6b5d6 +# scopes: +# - profile +# - openid +# - offline_access # Uncomment if OIdC supports issuing refresh tokens. +# # Replace with the client id created for Flyte. +# clientId: 0oakkheteNjCMERst5d6 flyteadmin: runScheduler: false roleNameKey: "iam.amazonaws.com/role" diff --git a/go.mod b/go.mod index 4cc74add9e..c0bfc7d678 100644 --- a/go.mod +++ b/go.mod @@ -14,8 +14,10 @@ require ( github.com/benbjohnson/clock v1.1.0 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect github.com/coreos/go-oidc v2.2.1+incompatible + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/evanphx/json-patch v4.9.0+incompatible - github.com/flyteorg/flyteidl v0.18.38 + github.com/flyteorg/flyteidl v0.18.40 + github.com/flyteorg/flyteplugins v0.5.38 github.com/flyteorg/flytepropeller v0.7.8 github.com/flyteorg/flytestdlib v0.3.18 github.com/gofrs/uuid v4.0.0+incompatible // indirect @@ -30,12 +32,15 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69 github.com/imdario/mergo v0.3.12 // indirect github.com/jinzhu/gorm v1.9.16 + github.com/lestrrat-go/jwx v1.1.6 github.com/lib/pq v1.10.0 github.com/magiconair/properties v1.8.4 - github.com/mattn/go-sqlite3 v2.0.1+incompatible // indirect github.com/mitchellh/mapstructure v1.4.1 + github.com/ory/fosite v0.39.0 + github.com/ory/x v0.0.162 github.com/pkg/errors v0.9.1 github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect github.com/prometheus/client_golang v1.9.0 @@ -55,7 +60,6 @@ require ( google.golang.org/grpc/examples v0.0.0-20210315211313-1e7119b13689 // indirect google.golang.org/protobuf v1.25.0 gopkg.in/gormigrate.v1 v1.6.0 - gopkg.in/square/go-jose.v2 v2.5.1 // indirect k8s.io/api v0.20.4 k8s.io/apimachinery v0.20.4 k8s.io/client-go v0.20.2 diff --git a/go.sum b/go.sum index 9e2a0a58fd..5d1884a773 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg= cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -84,20 +86,26 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -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/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v3.4.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v4.0.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/opencensus-go-exporter-datadog v0.0.0-20191210083620-6965a1cfed68/go.mod h1:gMGUEe16aZh0QN941HgDjwrdjU4iTthPoz2/AtDRADE= github.com/DiSiqueira/GoTree v1.0.1-0.20180907134536-53a8e837f295/go.mod h1:e0aH495YLkrsIe9fhedd6aSR6fgU/qhKvtroi6y7G/M= github.com/GoogleCloudPlatform/spark-on-k8s-operator v0.0.0-20200723154620-6f35a1152625/go.mod h1:6PnrZv6zUDkrNMw0mIoGRmGBR7i9LulhKPmxFq4rUiM= github.com/Jeffail/gabs/v2 v2.5.1/go.mod h1:xCn81vdHKxFUuWWAaD5jCTQDNPBMh5pPs9IJ+NcziBI= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/NYTimes/gizmo v1.3.6 h1:K+GRagPdAxojsT1TlTQlMkTeOmgfLxSdvuOhdki7GG0= github.com/NYTimes/gizmo v1.3.6/go.mod h1:8S8QVnITA40p/1jGsUMcPI8R9SSKkoKu+8WF13s9Uhw= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/logrotate v1.0.0/go.mod h1:GxNz1cSw1c6t99PXoZlw+nm90H6cyQyrH66pjVv7x88= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= @@ -116,6 +124,8 @@ github.com/adammck/venv v0.0.0-20160819025605-8a9c907a37d3/go.mod h1:3zXR2a/VSQn github.com/adammck/venv v0.0.0-20200610172036-e77789703e7c/go.mod h1:3zXR2a/VSQndtpShh783rUTaEA2mpqN2VqZclBARBc0= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -131,12 +141,16 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/amazon-sagemaker-operator-for-k8s v1.0.1-0.20210303003444-0fb33b1fd49d/go.mod h1:mZUP7GJmjiWtf8v3FD1X/QdK08BqyeH/1Ejt0qhNzCs= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.23.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.23.19/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.31.3/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= @@ -153,6 +167,7 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.0/go.mod h1:wpMHDCXvOXZxGCRSi github.com/aws/aws-sdk-go-v2/service/athena v1.0.0/go.mod h1:qY8QFbemf2ceqweXcS6hQqiiIe1z42WqTvHsK2Lb0rE= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.0/go.mod h1:3jExOmpbjgPnz2FJaMOfbSk1heTkZ66aD3yNtVhnjvI= github.com/aws/aws-sdk-go-v2/service/sts v1.0.0/go.mod h1:5f+cELGATgill5Pu3/vK3Ebuigstc+qYEHW5MvGWZO4= +github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDyVNp2tHXRq04= github.com/aws/smithy-go v1.0.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -167,11 +182,15 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmatcuk/doublestar/v2 v2.0.3/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -182,13 +201,22 @@ github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgk 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/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= 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/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/cockroachdb/cockroach-go v0.0.0-20200312223839-f565e4789405/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= +github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/coocood/freecache v1.1.1 h1:uukNF7QKCZEdZ9gAV7WQzvh0SbjwdMF6m3x3rxEkaPc= github.com/coocood/freecache v1.1.1/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -202,6 +230,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -209,29 +238,46 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= 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/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0 h1:sgNeV1VRMDzs6rzyPpxyM0jp317hnwiq58Filgag2xw= +github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8= github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE= +github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI= +github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0= +github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= @@ -250,22 +296,27 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flyteorg/flyteidl v0.18.17/go.mod h1:b5Fq4Z8a5b0mF6pEwTd48ufvikUGVkWSjZiMT0ZtqKI= -github.com/flyteorg/flyteidl v0.18.24 h1:Y4+y/tu6Qsb3jNXxuVsflycfSocfthUi6XsMgJTfGuc= github.com/flyteorg/flyteidl v0.18.24/go.mod h1:b5Fq4Z8a5b0mF6pEwTd48ufvikUGVkWSjZiMT0ZtqKI= -github.com/flyteorg/flyteidl v0.18.38 h1:XgAw9d2Q/UjWQyXbnZz/j4N6OVGDxr7jceden6PdCgY= -github.com/flyteorg/flyteidl v0.18.38/go.mod h1:b5Fq4Z8a5b0mF6pEwTd48ufvikUGVkWSjZiMT0ZtqKI= +github.com/flyteorg/flyteidl v0.18.40 h1:YuLBNpIotOFwyLSXSs0aj3B9N9vwPhzLRAQTWxYSI/w= +github.com/flyteorg/flyteidl v0.18.40/go.mod h1:IJD02cc/95QMkGDBJNibsr5aWd6V7TlQiJ8Iz5mVZ28= +github.com/flyteorg/flyteplugins v0.5.38 h1:xAQ1J23cRxzwNDgzbmRuuvflq2PFetntRCjuM5RBfTw= github.com/flyteorg/flyteplugins v0.5.38/go.mod h1:CxerBGWWEmNYmPxSMHnwQEr9cc1Fbo/g5fcABazU6Jo= github.com/flyteorg/flytepropeller v0.7.8 h1:O441kDHJUayS/2rebTj7VG4e1LowrweazQhzTaZ97m4= github.com/flyteorg/flytepropeller v0.7.8/go.mod h1:2SPJtYS0oM5lN4OCqBDbSRozRWvobFTXXlAC2ygbbWk= github.com/flyteorg/flytestdlib v0.3.13/go.mod h1:Tz8JCECAbX6VWGwFT6cmEQ+RJpZ/6L9pswu3fzWs220= github.com/flyteorg/flytestdlib v0.3.18 h1:G6OUvhN8r3OCP90uvEvEqRilYpITtmGGWIlR/GO1Gfk= github.com/flyteorg/flytestdlib v0.3.18/go.mod h1:VlbQuHTE+z2N5qusfwi+6WEkeJoqr8Q0E4NtBAsdwkU= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= @@ -280,6 +331,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -350,10 +402,242 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/gobuffalo/attrs v0.1.0/go.mod h1:fmNpaWyHM0tRm8gCZWKx8yY9fvaNLo2PyzBNSrBZ5Hw= +github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY= +github.com/gobuffalo/buffalo v0.13.0/go.mod h1:Mjn1Ba9wpIbpbrD+lIDMy99pQ0H0LiddMIIDGse7qT4= +github.com/gobuffalo/buffalo-plugins v1.0.2/go.mod h1:pOp/uF7X3IShFHyobahTkTLZaeUXwb0GrUTb9ngJWTs= +github.com/gobuffalo/buffalo-plugins v1.0.4/go.mod h1:pWS1vjtQ6uD17MVFWf7i3zfThrEKWlI5+PYLw/NaDB4= +github.com/gobuffalo/buffalo-plugins v1.4.3/go.mod h1:uCzTY0woez4nDMdQjkcOYKanngeUVRO2HZi7ezmAjWY= +github.com/gobuffalo/buffalo-plugins v1.5.1/go.mod h1:jbmwSZK5+PiAP9cC09VQOrGMZFCa/P0UMlIS3O12r5w= +github.com/gobuffalo/buffalo-plugins v1.6.4/go.mod h1:/+N1aophkA2jZ1ifB2O3Y9yGwu6gKOVMtUmJnbg+OZI= +github.com/gobuffalo/buffalo-plugins v1.6.5/go.mod h1:0HVkbgrVs/MnPZ/FOseDMVanCTm2RNcdM0PuXcL1NNI= +github.com/gobuffalo/buffalo-plugins v1.6.7/go.mod h1:ZGZRkzz2PiKWHs0z7QsPBOTo2EpcGRArMEym6ghKYgk= +github.com/gobuffalo/buffalo-plugins v1.6.9/go.mod h1:yYlYTrPdMCz+6/+UaXg5Jm4gN3xhsvsQ2ygVatZV5vw= +github.com/gobuffalo/buffalo-plugins v1.6.11/go.mod h1:eAA6xJIL8OuynJZ8amXjRmHND6YiusVAaJdHDN1Lu8Q= +github.com/gobuffalo/buffalo-plugins v1.8.2/go.mod h1:9te6/VjEQ7pKp7lXlDIMqzxgGpjlKoAcAANdCgoR960= +github.com/gobuffalo/buffalo-plugins v1.8.3/go.mod h1:IAWq6vjZJVXebIq2qGTLOdlXzmpyTZ5iJG5b59fza5U= +github.com/gobuffalo/buffalo-plugins v1.9.4/go.mod h1:grCV6DGsQlVzQwk6XdgcL3ZPgLm9BVxlBmXPMF8oBHI= +github.com/gobuffalo/buffalo-plugins v1.10.0/go.mod h1:4osg8d9s60txLuGwXnqH+RCjPHj9K466cDFRl3PErHI= +github.com/gobuffalo/buffalo-plugins v1.11.0/go.mod h1:rtIvAYRjYibgmWhnjKmo7OadtnxuMG5ZQLr25ozAzjg= +github.com/gobuffalo/buffalo-plugins v1.15.0/go.mod h1:BqSx01nwgKUQr/MArXzFkSD0QvdJidiky1OKgyfgrK8= +github.com/gobuffalo/buffalo-pop v1.0.5/go.mod h1:Fw/LfFDnSmB/vvQXPvcXEjzP98Tc+AudyNWUBWKCwQ8= +github.com/gobuffalo/envy v1.6.4/go.mod h1:Abh+Jfw475/NWtYMEt+hnJWRiC8INKWibIMyNt1w2Mc= +github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.6/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.7/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.8/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.9/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.10/go.mod h1:X0CFllQjTV5ogsnUrg+Oks2yTI+PU2dGYBJOEI2D1Uo= +github.com/gobuffalo/envy v1.6.11/go.mod h1:Fiq52W7nrHGDggFPhn2ZCcHw4u/rqXkqo+i7FB6EAcg= +github.com/gobuffalo/envy v1.6.12/go.mod h1:qJNrJhKkZpEW0glh5xP2syQHH5kgdmgsKss2Kk8PTP0= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/envy v1.8.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/events v1.0.3/go.mod h1:Txo8WmqScapa7zimEQIwgiJBvMECMe9gJjsKNPN3uZw= +github.com/gobuffalo/events v1.0.7/go.mod h1:z8txf6H9jWhQ5Scr7YPLWg/cgXBRj8Q4uYI+rsVCCSQ= +github.com/gobuffalo/events v1.0.8/go.mod h1:A5KyqT1sA+3GJiBE4QKZibse9mtOcI9nw8gGrDdqYGs= +github.com/gobuffalo/events v1.1.3/go.mod h1:9yPGWYv11GENtzrIRApwQRMYSbUgCsZ1w6R503fCfrk= +github.com/gobuffalo/events v1.1.4/go.mod h1:09/YRRgZHEOts5Isov+g9X2xajxdvOAcUuAHIX/O//A= +github.com/gobuffalo/events v1.1.5/go.mod h1:3YUSzgHfYctSjEjLCWbkXP6djH2M+MLaVRzb4ymbAK0= +github.com/gobuffalo/events v1.1.7/go.mod h1:6fGqxH2ing5XMb3EYRq9LEkVlyPGs4oO/eLzh+S8CxY= +github.com/gobuffalo/events v1.1.8/go.mod h1:UFy+W6X6VbCWS8k2iT81HYX65dMtiuVycMy04cplt/8= +github.com/gobuffalo/events v1.1.9/go.mod h1:/0nf8lMtP5TkgNbzYxR6Bl4GzBy5s5TebgNTdRfRbPM= +github.com/gobuffalo/events v1.3.1/go.mod h1:9JOkQVoyRtailYVE/JJ2ZQ/6i4gTjM5t2HsZK4C1cSA= +github.com/gobuffalo/events v1.4.1/go.mod h1:SjXgWKpeSuvQDvGhgMz5IXx3Czu+IbL+XPLR41NvVQY= +github.com/gobuffalo/fizz v1.0.12/go.mod h1:C0sltPxpYK8Ftvf64kbsQa2yiCZY4RZviurNxXdAKwc= +github.com/gobuffalo/fizz v1.9.8/go.mod h1:w1FEn1yKNVCc49KnADGyYGRPH7jFON3ak4Bj1yUudHo= +github.com/gobuffalo/flect v0.0.0-20180907193754-dc14d8acaf9f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181002182613-4571df4b1daf/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181007231023-ae7ed6bfe683/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181018182602-fd24a256709f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181019110701-3d6f0b585514/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181024204909-8f6be1a8c6c2/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181104133451-1f6e9779237a/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= +github.com/gobuffalo/flect v0.0.0-20181114183036-47375f6d8328/go.mod h1:0HvNbHdfh+WOvDSIASqJOSxTOWSxCCUF++k/Y53v9rI= +github.com/gobuffalo/flect v0.0.0-20181210151238-24a2b68e0316/go.mod h1:en58vff74S9b99Eg42Dr+/9yPu437QjlNsO/hBYPuOk= +github.com/gobuffalo/flect v0.0.0-20190104192022-4af577e09bf2/go.mod h1:en58vff74S9b99Eg42Dr+/9yPu437QjlNsO/hBYPuOk= +github.com/gobuffalo/flect v0.0.0-20190117212819-a62e61d96794/go.mod h1:397QT6v05LkZkn07oJXXT6y9FCfwC8Pug0WA2/2mE9k= +github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/flect v0.2.1/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= +github.com/gobuffalo/genny v0.0.0-20180924032338-7af3a40f2252/go.mod h1:tUTQOogrr7tAQnhajMSH6rv1BVev34H2sa1xNHMy94g= +github.com/gobuffalo/genny v0.0.0-20181003150629-3786a0744c5d/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= +github.com/gobuffalo/genny v0.0.0-20181005145118-318a41a134cc/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= +github.com/gobuffalo/genny v0.0.0-20181007153042-b8de7d566757/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181012161047-33e5f43d83a6/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181017160347-90a774534246/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= +github.com/gobuffalo/genny v0.0.0-20181024195656-51392254bf53/go.mod h1:o9GEH5gn5sCKLVB5rHFC4tq40rQ3VRUzmx6WwmaqISE= +github.com/gobuffalo/genny v0.0.0-20181025145300-af3f81d526b8/go.mod h1:uZ1fFYvdcP8mu0B/Ynarf6dsGvp7QFIpk/QACUuFUVI= +github.com/gobuffalo/genny v0.0.0-20181027191429-94d6cfb5c7fc/go.mod h1:x7SkrQQBx204Y+O9EwRXeszLJDTaWN0GnEasxgLrQTA= +github.com/gobuffalo/genny v0.0.0-20181027195209-3887b7171c4f/go.mod h1:JbKx8HSWICu5zyqWOa0dVV1pbbXOHusrSzQUprW6g+w= +github.com/gobuffalo/genny v0.0.0-20181106193839-7dcb0924caf1/go.mod h1:x61yHxvbDCgQ/7cOAbJCacZQuHgB0KMSzoYcw5debjU= +github.com/gobuffalo/genny v0.0.0-20181107223128-f18346459dbe/go.mod h1:utQD3aKKEsdb03oR+Vi/6ztQb1j7pO10N3OBoowRcSU= +github.com/gobuffalo/genny v0.0.0-20181114215459-0a4decd77f5d/go.mod h1:kN2KZ8VgXF9VIIOj/GM0Eo7YK+un4Q3tTreKOf0q1ng= +github.com/gobuffalo/genny v0.0.0-20181119162812-e8ff4adce8bb/go.mod h1:BA9htSe4bZwBDJLe8CUkoqkypq3hn3+CkoHqVOW718E= +github.com/gobuffalo/genny v0.0.0-20181127225641-2d959acc795b/go.mod h1:l54xLXNkteX/PdZ+HlgPk1qtcrgeOr3XUBBPDbH+7CQ= +github.com/gobuffalo/genny v0.0.0-20181128191930-77e34f71ba2a/go.mod h1:FW/D9p7cEEOqxYA71/hnrkOWm62JZ5ZNxcNIVJEaWBU= +github.com/gobuffalo/genny v0.0.0-20181203165245-fda8bcce96b1/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181203201232-849d2c9534ea/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181206121324-d6fb8a0dbe36/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM= +github.com/gobuffalo/genny v0.0.0-20181207164119-84844398a37d/go.mod h1:y0ysCHGGQf2T3vOhCrGHheYN54Y/REj0ayd0Suf4C/8= +github.com/gobuffalo/genny v0.0.0-20181211165820-e26c8466f14d/go.mod h1:sHnK+ZSU4e2feXP3PA29ouij6PUEiN+RCwECjCTB3yM= +github.com/gobuffalo/genny v0.0.0-20190104222617-a71664fc38e7/go.mod h1:QPsQ1FnhEsiU8f+O0qKWXz2RE4TiDqLVChWkBuh1WaY= +github.com/gobuffalo/genny v0.0.0-20190112155932-f31a84fcacf5/go.mod h1:CIaHCrSIuJ4il6ka3Hub4DR4adDrGoXGEEt2FbBxoIo= +github.com/gobuffalo/genny v0.2.0/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.3.0/go.mod h1:ywJ2CoXrTZj7rbS8HTbzv7uybnLKlsNSBhEQ+yFI3E8= +github.com/gobuffalo/genny v0.6.0/go.mod h1:Vigx9VDiNscYpa/LwrURqGXLSIbzTfapt9+K6gF1kTA= +github.com/gobuffalo/genny/v2 v2.0.5/go.mod h1:kRkJuAw9mdI37AiEYjV4Dl+TgkBDYf8HZVjLkqe5eBg= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/github_flavored_markdown v1.0.4/go.mod h1:uRowCdK+q8d/RF0Kt3/DSalaIXbb0De/dmTqMQdkQ4I= +github.com/gobuffalo/github_flavored_markdown v1.0.5/go.mod h1:U0643QShPF+OF2tJvYNiYDLDGDuQmJZXsf/bHOJPsMY= +github.com/gobuffalo/github_flavored_markdown v1.0.7/go.mod h1:w93Pd9Lz6LvyQXEG6DktTPHkOtCbr+arAD5mkwMzXLI= +github.com/gobuffalo/github_flavored_markdown v1.1.0/go.mod h1:TSpTKWcRTI0+v7W3x8dkSKMLJSUpuVitlptCkpeY8ic= +github.com/gobuffalo/gogen v0.2.0/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/helpers v0.2.2/go.mod h1:xYbzUdCUpVzLwLnqV8HIjT6hmG0Cs7YIBCJkNM597jw= +github.com/gobuffalo/helpers v0.2.4/go.mod h1:NX7v27yxPDOPTgUFYmJ5ow37EbxdoLraucOGvMNawyk= +github.com/gobuffalo/helpers v0.5.0/go.mod h1:stpgxJ2C7T99NLyAxGUnYMM2zAtBk5NKQR0SIbd05j4= +github.com/gobuffalo/helpers v0.6.0/go.mod h1:pncVrer7x/KRvnL5aJABLAuT/RhKRR9klL6dkUOhyv8= +github.com/gobuffalo/helpers v0.6.1/go.mod h1:wInbDi0vTJKZBviURTLRMFLE4+nF2uRuuL2fnlYo7w4= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gobuffalo/httptest v1.0.2/go.mod h1:7T1IbSrg60ankme0aDLVnEY0h056g9M1/ZvpVThtB7E= +github.com/gobuffalo/licenser v0.0.0-20180924033006-eae28e638a42/go.mod h1:Ubo90Np8gpsSZqNScZZkVXXAo5DGhTb+WYFIjlnog8w= +github.com/gobuffalo/licenser v0.0.0-20181025145548-437d89de4f75/go.mod h1:x3lEpYxkRG/XtGCUNkio+6RZ/dlOvLzTI9M1auIwFcw= +github.com/gobuffalo/licenser v0.0.0-20181027200154-58051a75da95/go.mod h1:BzhaaxGd1tq1+OLKObzgdCV9kqVhbTulxOpYbvMQWS0= +github.com/gobuffalo/licenser v0.0.0-20181109171355-91a2a7aac9a7/go.mod h1:m+Ygox92pi9bdg+gVaycvqE8RVSjZp7mWw75+K5NPHk= +github.com/gobuffalo/licenser v0.0.0-20181128165715-cc7305f8abed/go.mod h1:oU9F9UCE+AzI/MueCKZamsezGOOHfSirltllOVeRTAE= +github.com/gobuffalo/licenser v0.0.0-20181203160806-fe900bbede07/go.mod h1:ph6VDNvOzt1CdfaWC+9XwcBnlSTBz2j49PBwum6RFaU= +github.com/gobuffalo/licenser v0.0.0-20181211173111-f8a311c51159/go.mod h1:ve/Ue99DRuvnTaLq2zKa6F4KtHiYf7W046tDjuGYPfM= +github.com/gobuffalo/licenser v1.1.0/go.mod h1:ZVWE6uKUE3rGf7sedUHWVjNWrEgxaUQLVFL+pQiWpfY= +github.com/gobuffalo/logger v0.0.0-20181022175615-46cfb361fc27/go.mod h1:8sQkgyhWipz1mIctHF4jTxmJh1Vxhp7mP8IqbljgJZo= +github.com/gobuffalo/logger v0.0.0-20181027144941-73d08d2bb969/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8= +github.com/gobuffalo/logger v0.0.0-20181027193913-9cf4dd0efe46/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8= +github.com/gobuffalo/logger v0.0.0-20181109185836-3feeab578c17/go.mod h1:oNErH0xLe+utO+OW8ptXMSA5DkiSEDW1u3zGIt8F9Ew= +github.com/gobuffalo/logger v0.0.0-20181117211126-8e9b89b7c264/go.mod h1:5etB91IE0uBlw9k756fVKZJdS+7M7ejVhmpXXiSFj0I= +github.com/gobuffalo/logger v0.0.0-20181127160119-5b956e21995c/go.mod h1:+HxKANrR9VGw9yN3aOAppJKvhO05ctDi63w4mDnKv2U= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= +github.com/gobuffalo/makr v1.1.5/go.mod h1:Y+o0btAH1kYAMDJW/TX3+oAXEu0bmSLLoC9mIFxtzOw= +github.com/gobuffalo/mapi v1.0.0/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.1.0/go.mod h1:pqQ1XAqvpy/JYtRwoieNps2yU8MFiMxBUpAm2FBtQ50= +github.com/gobuffalo/mapi v1.2.1/go.mod h1:giGJ2AUESRepOFYAzWpq8Gf/s/QDryxoEHisQtFN3cY= +github.com/gobuffalo/meta v0.0.0-20181018155829-df62557efcd3/go.mod h1:XTTOhwMNryif3x9LkTTBO/Llrveezd71u3quLd0u7CM= +github.com/gobuffalo/meta v0.0.0-20181018192820-8c6cef77dab3/go.mod h1:E94EPzx9NERGCY69UWlcj6Hipf2uK/vnfrF4QD0plVE= +github.com/gobuffalo/meta v0.0.0-20181025145500-3a985a084b0a/go.mod h1:YDAKBud2FP7NZdruCSlmTmDOZbVSa6bpK7LJ/A/nlKg= +github.com/gobuffalo/meta v0.0.0-20181114191255-b130ebedd2f7/go.mod h1:K6cRZ29ozr4Btvsqkjvg5nDFTLOgTqf03KA70Ks0ypE= +github.com/gobuffalo/meta v0.0.0-20181127070345-0d7e59dd540b/go.mod h1:RLO7tMvE0IAKAM8wny1aN12pvEKn7EtkBLkUZR00Qf8= +github.com/gobuffalo/meta v0.0.0-20190120163247-50bbb1fa260d/go.mod h1:KKsH44nIK2gA8p0PJmRT9GvWJUdphkDUA8AJEvFWiqM= +github.com/gobuffalo/meta v0.0.0-20190329152330-e161e8a93e3b/go.mod h1:mCRSy5F47tjK8yaIDcJad4oe9fXxY5gLrx3Xx2spK+0= +github.com/gobuffalo/meta v0.3.0/go.mod h1:cpr6mrUX5H/B4wEP86Gdq568TK4+dKUD8oRPl698RUw= +github.com/gobuffalo/mw-basicauth v1.0.3/go.mod h1:dg7+ilMZOKnQFHDefUzUHufNyTswVUviCBgF244C1+0= +github.com/gobuffalo/mw-contenttype v0.0.0-20180802152300-74f5a47f4d56/go.mod h1:7EvcmzBbeCvFtQm5GqF9ys6QnCxz2UM1x0moiWLq1No= +github.com/gobuffalo/mw-csrf v0.0.0-20180802151833-446ff26e108b/go.mod h1:sbGtb8DmDZuDUQoxjr8hG1ZbLtZboD9xsn6p77ppcHo= +github.com/gobuffalo/mw-forcessl v0.0.0-20180802152810-73921ae7a130/go.mod h1:JvNHRj7bYNAMUr/5XMkZaDcw3jZhUZpsmzhd//FFWmQ= +github.com/gobuffalo/mw-i18n v0.0.0-20180802152014-e3060b7e13d6/go.mod h1:91AQfukc52A6hdfIfkxzyr+kpVYDodgAeT5cjX1UIj4= +github.com/gobuffalo/mw-paramlogger v0.0.0-20181005191442-d6ee392ec72e/go.mod h1:6OJr6VwSzgJMqWMj7TYmRUqzNe2LXu/W1rRW4MAz/ME= +github.com/gobuffalo/mw-tokenauth v0.0.0-20181001105134-8545f626c189/go.mod h1:UqBF00IfKvd39ni5+yI5MLMjAf4gX7cDKN/26zDOD6c= +github.com/gobuffalo/nulls v0.2.0/go.mod h1:w4q8RoSCEt87Q0K0sRIZWYeIxkxog5mh3eN3C/n+dUc= +github.com/gobuffalo/nulls v0.3.0/go.mod h1:UP49vd/k+bcaz6m0cHMyuk8oQ7XgLnkfxeiVoPAvBSs= +github.com/gobuffalo/packd v0.0.0-20181027182251-01ad393492c8/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181027190505-aafc0d02c411/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181027194105-7ae579e6d213/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc= +github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181104210303-d376b15f8e96/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181111195323-b2e760a5f0ff/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181114190715-f25c5d2471d7/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI= +github.com/gobuffalo/packd v0.0.0-20181124090624-311c6248e5fb/go.mod h1:Foenia9ZvITEvG05ab6XpiD5EfBHPL8A6hush8SJ0o8= +github.com/gobuffalo/packd v0.0.0-20181207120301-c49825f8f6f4/go.mod h1:LYc0TGKFBBFTRC9dg2pcRcMqGCTMD7T2BIMP7OBuQAA= +github.com/gobuffalo/packd v0.0.0-20181212173646-eca3b8fd6687/go.mod h1:LYc0TGKFBBFTRC9dg2pcRcMqGCTMD7T2BIMP7OBuQAA= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.2.0/go.mod h1:k2CkHP3bjbqL2GwxwhxUy1DgnlbW644hkLC9iIUvZwY= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= +github.com/gobuffalo/packr v1.13.7/go.mod h1:KkinLIn/n6+3tVXMwg6KkNvWwVsrRAz4ph+jgpk3Z24= +github.com/gobuffalo/packr v1.15.0/go.mod h1:t5gXzEhIviQwVlNx/+3SfS07GS+cZ2hn76WLzPp6MGI= +github.com/gobuffalo/packr v1.15.1/go.mod h1:IeqicJ7jm8182yrVmNbM6PR4g79SjN9tZLH8KduZZwE= +github.com/gobuffalo/packr v1.19.0/go.mod h1:MstrNkfCQhd5o+Ct4IJ0skWlxN8emOq8DsoT1G98VIU= +github.com/gobuffalo/packr v1.20.0/go.mod h1:JDytk1t2gP+my1ig7iI4NcVaXr886+N0ecUga6884zw= +github.com/gobuffalo/packr v1.21.0/go.mod h1:H00jGfj1qFKxscFJSw8wcL4hpQtPe1PfU2wa6sg/SR0= +github.com/gobuffalo/packr v1.22.0/go.mod h1:Qr3Wtxr3+HuQEwWqlLnNW4t1oTvK+7Gc/Rnoi/lDFvA= +github.com/gobuffalo/packr/v2 v2.0.0-rc.8/go.mod h1:y60QCdzwuMwO2R49fdQhsjCPv7tLQFR0ayzxxla9zes= +github.com/gobuffalo/packr/v2 v2.0.0-rc.9/go.mod h1:fQqADRfZpEsgkc7c/K7aMew3n4aF1Kji7+lIZeR98Fc= +github.com/gobuffalo/packr/v2 v2.0.0-rc.10/go.mod h1:4CWWn4I5T3v4c1OsJ55HbHlUEKNWMITG5iIkdr4Px4w= +github.com/gobuffalo/packr/v2 v2.0.0-rc.11/go.mod h1:JoieH/3h3U4UmatmV93QmqyPUdf4wVM9HELaHEu+3fk= +github.com/gobuffalo/packr/v2 v2.0.0-rc.12/go.mod h1:FV1zZTsVFi1DSCboO36Xgs4pzCZBjB/tDV9Cz/lSaR8= +github.com/gobuffalo/packr/v2 v2.0.0-rc.13/go.mod h1:2Mp7GhBFMdJlOK8vGfl7SYtfMP3+5roE39ejlfjw0rA= +github.com/gobuffalo/packr/v2 v2.0.0-rc.14/go.mod h1:06otbrNvDKO1eNQ3b8hst+1010UooI2MFg+B2Ze4MV8= +github.com/gobuffalo/packr/v2 v2.0.0-rc.15/go.mod h1:IMe7H2nJvcKXSF90y4X1rjYIRlNMJYCxEhssBXNZwWs= +github.com/gobuffalo/packr/v2 v2.4.0/go.mod h1:ra341gygw9/61nSjAbfwcwh8IrYL4WmR4IsPkPBhQiY= +github.com/gobuffalo/packr/v2 v2.5.2/go.mod h1:sgEE1xNZ6G0FNN5xn9pevVu4nywaxHvgup67xisti08= +github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +github.com/gobuffalo/plush v3.7.16+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.20+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.21+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.22+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.23+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.30+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.31+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.7.32+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.8.2+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush v3.8.3+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= +github.com/gobuffalo/plush/v4 v4.0.0/go.mod h1:ErFS3UxKqEb8fpFJT7lYErfN/Nw6vHGiDMTjxpk5bQ0= +github.com/gobuffalo/plushgen v0.0.0-20181128164830-d29dcb966cb2/go.mod h1:r9QwptTFnuvSaSRjpSp4S2/4e2D3tJhARYbvEBcKSb4= +github.com/gobuffalo/plushgen v0.0.0-20181203163832-9fc4964505c2/go.mod h1:opEdT33AA2HdrIwK1aibqnTJDVVKXC02Bar/GT1YRVs= +github.com/gobuffalo/plushgen v0.0.0-20181207152837-eedb135bd51b/go.mod h1:Lcw7HQbEVm09sAQrCLzIxuhFbB3nAgp4c55E+UlynR0= +github.com/gobuffalo/plushgen v0.0.0-20190104222512-177cd2b872b3/go.mod h1:tYxCozi8X62bpZyKXYHw1ncx2ZtT2nFvG42kuLwYjoc= +github.com/gobuffalo/plushgen v0.1.2/go.mod h1:3U71v6HWZpVER1nInTXeAwdoRNsRd4W8aeIa1Lyp+Bk= +github.com/gobuffalo/pop v4.8.2+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.8.3+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.8.4+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop v4.13.1+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= +github.com/gobuffalo/pop/v5 v5.0.11/go.mod h1:mZJHJbA3cy2V18abXYuVop2ldEJ8UZ2DK6qOekC5u5g= +github.com/gobuffalo/release v1.0.35/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= +github.com/gobuffalo/release v1.0.38/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= +github.com/gobuffalo/release v1.0.42/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug= +github.com/gobuffalo/release v1.0.52/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug= +github.com/gobuffalo/release v1.0.53/go.mod h1:FdF257nd8rqhNaqtDWFGhxdJ/Ig4J7VcS3KL7n/a+aA= +github.com/gobuffalo/release v1.0.54/go.mod h1:Pe5/RxRa/BE8whDpGfRqSI7D1a0evGK1T4JDm339tJc= +github.com/gobuffalo/release v1.0.61/go.mod h1:mfIO38ujUNVDlBziIYqXquYfBF+8FDHUjKZgYC1Hj24= +github.com/gobuffalo/release v1.0.72/go.mod h1:NP5NXgg/IX3M5XmHmWR99D687/3Dt9qZtTK/Lbwc1hU= +github.com/gobuffalo/release v1.1.1/go.mod h1:Sluak1Xd6kcp6snkluR1jeXAogdJZpFFRzTYRs/2uwg= +github.com/gobuffalo/release v1.1.3/go.mod h1:CuXc5/m+4zuq8idoDt1l4va0AXAn/OSs08uHOfMVr8E= +github.com/gobuffalo/release v1.1.6/go.mod h1:18naWa3kBsqO0cItXZNJuefCKOENpbbUIqRL1g+p6z0= +github.com/gobuffalo/release v1.7.0/go.mod h1:xH2NjAueVSY89XgC4qx24ojEQ4zQ9XCGVs5eXwJTkEs= +github.com/gobuffalo/shoulders v1.0.1/go.mod h1:V33CcVmaQ4gRUmHKwq1fiTXuf8Gp/qjQBUL5tHPmvbA= +github.com/gobuffalo/shoulders v1.0.4/go.mod h1:LqMcHhKRuBPMAYElqOe3POHiZ1x7Ry0BE8ZZ84Bx+k4= +github.com/gobuffalo/syncx v0.0.0-20181120191700-98333ab04150/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/syncx v0.0.0-20181120194010-558ac7de985f/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobuffalo/syncx v0.1.0/go.mod h1:Mg/s+5pv7IgxEp6sA+NFpqS4o2x+R9dQNwbwT0iuOGQ= +github.com/gobuffalo/tags v2.0.11+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.0.14+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.0.15+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.1.0+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags v2.1.7+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= +github.com/gobuffalo/tags/v3 v3.0.2/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA= +github.com/gobuffalo/tags/v3 v3.1.0/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA= +github.com/gobuffalo/uuid v2.0.3+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/uuid v2.0.4+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= +github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= +github.com/gobuffalo/validate v2.0.4+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= +github.com/gobuffalo/validate/v3 v3.0.0/go.mod h1:HFpjq+AIiA2RHoQnQVTFKF/ZpUPXwyw82LgyDPxQ9r0= +github.com/gobuffalo/validate/v3 v3.1.0/go.mod h1:HFpjq+AIiA2RHoQnQVTFKF/ZpUPXwyw82LgyDPxQ9r0= +github.com/gobuffalo/validate/v3 v3.2.0/go.mod h1:PrhDOdDHxtN8KUgMvF3TDL0r1YZXV4sQnyFX/EmeETY= +github.com/gobuffalo/x v0.0.0-20181003152136-452098b06085/go.mod h1:WevpGD+5YOreDJznWevcn8NTmQEW5STSBgIkpkjzqXc= +github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdKWzMc4odGQQdG2e2DIEmANy5aSJ9yesY= +github.com/goccy/go-json v0.4.8 h1:TfwOxfSp8hXH+ivoOk36RyDNmXATUETRdaNWDaZglf8= +github.com/goccy/go-json v0.4.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid/v3 v3.1.2/go.mod h1:xPwMqoocQ1L5G6pXX5BcE7N5jlzn2o19oqAKxwZW/kI= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -363,6 +647,9 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/gddo v0.0.0-20180828051604-96d2a289f41e/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= +github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= 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-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -378,8 +665,10 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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= @@ -411,6 +700,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -436,6 +726,7 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/readahead v0.0.0-20161222183148-eaceba169032/go.mod h1:qYysrqQXuV4tzsizt4oOQ6mrBZQ0xnQXP3ylXX8Jk5Y= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= @@ -451,6 +742,7 @@ github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2c github.com/googleapis/gnostic v0.5.4 h1:ynbQIWjLw7iv6HAFdixb30U7Uvcmx+f4KlLJpmhkTK0= github.com/googleapis/gnostic v0.5.4/go.mod h1:TRWw1s4gxBGjSe301Dai3c7wXJAZy57+/6tawkOvqHQ= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -458,13 +750,20 @@ github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= 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/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotestyourself/gotestyourself v1.3.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/graymeta/stow v0.2.7 h1:b31cB1Ylw/388sYSZxnmpjT2QxC21AaQ8fRnUtE13b4= github.com/graymeta/stow v0.2.7/go.mod h1:JAs139Zr29qfsecy7b+h9DRsWXbFbsd7LCrbCDYI84k= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -481,6 +780,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.12.2/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69 h1:7xsUJsB2NrdcttQPa7JLEaGzvdbk7KvfrjgHZXOQRo0= +github.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69/go.mod h1:YLEMZOtU+AZ7dhN9T/IpGhXVGly2bvkJQ+zxj3WeVQo= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -521,6 +822,38 @@ github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM= +github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.4.1/go.mod h1:6iSW+JznC0YT+SgBn7rNxoEBsBgSmnC5FwyCekOGUiE= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= @@ -538,6 +871,10 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= +github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -550,10 +887,20 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/ 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 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.7.7/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.10.9/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.15.5/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -561,7 +908,9 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL 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.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -569,17 +918,39 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 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/kubeflow/pytorch-operator v0.6.0/go.mod h1:zHblV+yTwVG4PCgKTU2wPfOmQ6TJdfT87lDfHrP1a1Y= github.com/kubeflow/tf-operator v0.5.3/go.mod h1:EBtz5LQoKaHUl/5fV5vD1qXVNVNyn3TrFaH6eVoQ8SY= +github.com/lestrrat-go/backoff/v2 v2.0.7 h1:i2SeK33aOFJlUNJZzf2IpXRBvqBBnaGXfY5Xaop/GsE= +github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM= +github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc= +github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= +github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.1.6 h1:VfyUo2PAU4lO/liwhdwiSZ55/QZDLTT3EYY5z9KfwZs= +github.com/lestrrat-go/jwx v1.1.6/go.mod h1:c+R8G7qsaFNmTzYjU98A+sMh8Bo/MJqO9GnpqR+X024= +github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/pdebug/v3 v3.0.1 h1:3G5sX/aw/TbMTtVc9U7IHBWRZtMvwvBziF1e4HoQtv8= +github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/luna-duclos/instrumentedsql v0.0.0-20181127104832-b7d587d28109/go.mod h1:PWUIzhtavmOR965zfawVsHXbEuU1G29BPZ/CB3C7jXk= +github.com/luna-duclos/instrumentedsql v1.1.2/go.mod h1:4LGbEqDnopzNAiyxPPDXhLspyunZxgPTMJBKtC6U0BQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -591,23 +962,58 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= +github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= +github.com/markbates/deplist v1.1.3/go.mod h1:BF7ioVzAJYEtzQN/os4rt8H8Ti3h0T7EoN+7eyALktE= +github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c= +github.com/markbates/grift v1.0.4/go.mod h1:wbmtW74veyx+cgfwFhlnnMWqhoz55rnHR47oMXzsyVs= +github.com/markbates/hmax v1.0.0/go.mod h1:cOkR9dktiESxIMu+65oc/r/bdY4bE8zZw3OLhLx0X2c= +github.com/markbates/inflect v1.0.0/go.mod h1:oTeZL2KHA7CUX6X+fovmK9OvIOFuqu0TwdQrZjLTh88= +github.com/markbates/inflect v1.0.1/go.mod h1:uv3UVNBe5qBIfCm8O8Q+DW+S1EopeyINj+Ikhc7rnCk= +github.com/markbates/inflect v1.0.3/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/markbates/oncer v0.0.0-20180924031910-e862a676800b/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20180924034138-723ad0170a46/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/markbates/refresh v1.4.10/go.mod h1:NDPHvotuZmTmesXxr95C9bjlw1/0frJwtME2dzcVKhc= +github.com/markbates/safe v1.0.0/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc= +github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= -github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= -github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/mattn/goveralls v0.0.6 h1:cr8Y0VMo/MnEZBjxNN/vh6G90SZ7IMb6lms1dzMoO+Y= +github.com/mattn/goveralls v0.0.6/go.mod h1:h8b4ow6FxSPMQHF6o2ve3qsclnffZjYTNEKmLesRwqw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -616,7 +1022,10 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= @@ -628,6 +1037,10 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= +github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -643,22 +1056,33 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/ncw/swift v1.0.49/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks= github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oleiade/reflections v1.0.0 h1:0ir4pc6v8/PJ0yw5AEtMddfXpWBXg9cnG7SgSoJuCgY= +github.com/oleiade/reflections v1.0.0/go.mod h1:RbATFBbKYkVdqmSFtx13Bb/tVhR0lgOBXunWTZKeL4w= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.9.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= @@ -666,18 +1090,54 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/analytics-go/v4 v4.0.0/go.mod h1:FMx9cLRD9xN+XevPvZ5FDMfignpmcqPP6FUKnJ9/MmE= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest/v3 v3.5.4/go.mod h1:J8ZUbNB2FOhm1cFZW9xBpDsODqsSWcyYgtJYVPcnF70= +github.com/ory/fosite v0.29.0/go.mod h1:0atSZmXO7CAcs6NPMI/Qtot8tmZYj04Nddoold4S2h0= +github.com/ory/fosite v0.39.0 h1:u1Ct/ME7XYzREvufr7ehBIdq/KatjVLIYg/ABqWzprw= +github.com/ory/fosite v0.39.0/go.mod h1:37r59qkOSPueYKmaA7EHiXrDMF1B+XPN+MgkZgTRg3Y= +github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90/go.mod h1:sxnvPCxChFuSmTJGj8FdMupeq1BezCiEpDjTUXQ4hf4= +github.com/ory/go-acc v0.2.5 h1:31irXHzG2vnKQSE4weJm7AdfrnpaVjVCq3nD7viXCJE= +github.com/ory/go-acc v0.2.5/go.mod h1:4Kb/UnPcT8qRAk3IAxta+hvVapdxTLWtrr7bFLlEgpw= +github.com/ory/go-convenience v0.1.0 h1:zouLKfF2GoSGnJwGq+PE/nJAE6dj2Zj5QlTgmMTsTS8= +github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8TWB0yn9KNs= +github.com/ory/gojsonreference v0.0.0-20190720135523-6b606c2d8ee8/go.mod h1:wsH1C4nIeeQClDtD5AH7kF1uTS6zWyqfjVDTmB0Em7A= +github.com/ory/gojsonschema v1.1.1-0.20190919112458-f254ca73d5e9/go.mod h1:BNZpdJgB74KOLSsWFvzw6roXg1I6O51WO8roMmW+T7Y= +github.com/ory/herodot v0.6.2/go.mod h1:3BOneqcyBsVybCPAJoi92KN2BpJHcmDqAMcAAaJiJow= +github.com/ory/herodot v0.7.0/go.mod h1:YXKOfAXYdQojDP5sD8m0ajowq3+QXNdtxA+QiUXBwn0= +github.com/ory/herodot v0.8.3/go.mod h1:rvLjxOAlU5omtmgjCfazQX2N82EpMfl3BytBWc1jjsk= +github.com/ory/jsonschema/v3 v3.0.1/go.mod h1:jgLHekkFk0uiGdEWGleC+tOm6JSSP8cbf17PnBuGXlw= +github.com/ory/viper v1.5.6/go.mod h1:TYmpFpKLxjQwvT4f0QPpkOn4sDXU1kDgAwJpgLYiQ28= +github.com/ory/viper v1.7.4/go.mod h1:T6sodNZKNGPpashUOk7EtXz2isovz8oCd57GNVkkNmE= +github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= +github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= +github.com/ory/x v0.0.84/go.mod h1:RXLPBG7B+hAViONVg0sHwK+U/ie1Y/NeXrq1JcARfoE= +github.com/ory/x v0.0.93/go.mod h1:lfcTaGXpTZs7IEQAW00r9EtTCOxD//SiP5uWtNiz31g= +github.com/ory/x v0.0.110/go.mod h1:DJfkE3GdakhshNhw4zlKoRaL/ozg/lcTahA9OCih2BE= +github.com/ory/x v0.0.162 h1:xE/UBmmMlInTvlgGXUyo+VeZAcWU5gyWb/xh6jmBWsI= +github.com/ory/x v0.0.162/go.mod h1:sj3z/MeCrAyNFFTfN6yK1nTmHXGSFnw+QwIIQ/Rowec= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/parnurzeal/gorequest v0.2.15/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= @@ -686,7 +1146,9 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -729,8 +1191,10 @@ github.com/prometheus/common v0.19.0 h1:Itb4+NjG9wRdkAWgVucbM/adyIXxEhbw0866e0uZ github.com/prometheus/common v0.19.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -741,40 +1205,86 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/rubenv/sql-migrate v0.0.0-20190212093014-1007f53448d7/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= +github.com/santhosh-tekuri/jsonschema/v2 v2.1.0/go.mod h1:yzJzKUGV4RbWqWIBBP4wSOBqavX5saE02yirLS0OTyg= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentio/analytics-go v3.0.1+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= +github.com/segmentio/analytics-go v3.1.0+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= +github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M= +github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc= +github.com/segmentio/conf v1.2.0/go.mod h1:Y3B9O/PqqWqjyxyWWseyj/quPEtMu1zDp/kVbSWWaB0= +github.com/segmentio/go-snakecase v1.1.0/go.mod h1:jk1miR5MS7Na32PZUykG89Arm+1BUSYhuGR6b7+hJto= +github.com/segmentio/objconv v1.0.1/go.mod h1:auayaH5k3137Cl4SoXTgrzQcuQDmvuVtZgS0fb1Ahys= +github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.0/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg= github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= @@ -782,14 +1292,20 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6 github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= +github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -806,24 +1322,45 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= +github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.elastic.co/apm v1.8.0/go.mod h1:tCw6CkOJgkWnzEthFN9HUP1uL3Gjc/Ur6m7gRPLaoH0= +go.elastic.co/apm/module/apmhttp v1.8.0/go.mod h1:9LPFlEON51/lRbnWDfqAWErihIiAFDUMfMV27YjoWQ8= +go.elastic.co/apm/module/apmot v1.8.0/go.mod h1:Q5Xzabte8G/fkvDjr1jlDuOSUt9hkVWNZEHh6ZNaTjI= +go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHtchs= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -844,13 +1381,15 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.13.0/go.mod h1:TwTkyRaTam1pOIb2wxcAiC2hkMVbokXkt6DEt5nDkD8= +go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -859,37 +1398,66 @@ go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180830192347-182538f80094/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181024171144-74cb1d3d52f4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025113841-85e1b3f9139a/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190102171810-8d7daa0c54b3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +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-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= @@ -899,6 +1467,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -928,12 +1497,20 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -943,6 +1520,7 @@ golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -952,10 +1530,12 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/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-20200114155413-6afb5195e5aa/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-20200219183655-46282727080f/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -980,6 +1560,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210315170653-34ac3e1c2000 h1:6mqyFav9MzRNys8OnKlbKYSJxsoVvhb773Si3bu5fYE= golang.org/x/net v0.0.0-20210315170653-34ac3e1c2000/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/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= @@ -1005,34 +1586,54 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180831094639-fa5fdf94c789/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180921163948-d47a0f339242/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180927150500-dad3d9fb7b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181022134430-8a28ead16f52/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181024145615-5cd93ef61a7c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181025063200-d989b31c8746/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026064943-731415f00dce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181106135930-3a76605856fd/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1040,7 +1641,9 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1053,8 +1656,10 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1095,29 +1700,59 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181003024731-2f84ea8ef872/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181006002542-f60d9635b16a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181013182035-5e66757b835f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181017214349-06f26fdaaa28/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181024171208-a2dc47679d30/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181105230042-78dc5bac0cac/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181107215632-34b416bd17b3/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181114190951-94339b83286c/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181119130350-139d099f6620/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181127195227-b4e97c0ed882/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181127232545-e782529d0ddd/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181203210056-e5f3ab76ea4b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181205224935-3576414c54a4/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181206194817-bcd4e47d0288/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181207183836-8bc39b988060/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181212172921-837e80568c09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190102213336-ca9055ed7d04/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190104182027-498d95493402/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190111214448-fc1d57b08d7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190118193359-16909d206f00/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/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/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-20190613204242-ed0dc450797f/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190711191110-9a621aea19f8/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1130,11 +1765,14 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/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-20200203215610-ab391d50b528/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/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/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1147,9 +1785,11 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200721223218-6123e77877b2/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -1163,8 +1803,11 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1173,6 +1816,12 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k= gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20191229114700-bbb4dff026f8/go.mod h1:2IgXn/sJaRbePPBA1wRj8OE+QLvVaH0q8SK6TSTKlnk= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.0.0-20200111075622-4abb28f724d5/go.mod h1:+HbaZVpsa73UwN7kXGCECULRHovLRJjH+t5cFPgxErs= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 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= @@ -1218,6 +1867,7 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= @@ -1274,6 +1924,7 @@ google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLD google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -1308,7 +1959,10 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/DataDog/dd-trace-go.v1 v1.22.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= +gopkg.in/DataDog/dd-trace-go.v1 v1.27.0/go.mod h1:Sp1lku8WJMvNV0kjDI4Ni/T7J/U3BO5ct5kEaoVU8+I= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= @@ -1317,11 +1971,19 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/mold.v2 v2.2.0/go.mod h1:XMyyRsGtakkDPbxXbrA5VODo6bUXyvoDjLd5l3T0XoA= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gormigrate.v1 v1.6.0 h1:XpYM6RHQPmzwY7Uyu+t+xxMXc86JYFJn4nEc9HzQjsI= gopkg.in/gormigrate.v1 v1.6.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= @@ -1330,14 +1992,17 @@ gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mN gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/kothar/go-backblaze.v0 v0.0.0-20190520213052-702d4e7eb465/go.mod h1:zJ2QpyDCYo1KvLXlmdnFlQAyF/Qfth0fB8239Qg7BIE= +gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.1.9/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/validator.v2 v2.0.0-20180514200540-135c24b11c19/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1345,6 +2010,7 @@ 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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -1363,8 +2029,8 @@ 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= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= k8s.io/api v0.0.0-20210217171935-8e2decd92398/go.mod h1:60tmSUpHxGPFerNHbo/ayI2lKxvtrhbxFyXuEIWJd78= k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= @@ -1428,7 +2094,13 @@ k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/ k8s.io/utils v0.0.0-20210111153108-fddb29f9d009/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210305010621-2afb4311ab10 h1:u5rPykqiCpL+LBfjRkXvnK71gOgIdmq3eHUEkPrbeTI= k8s.io/utils v0.0.0-20210305010621-2afb4311ab10/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= diff --git a/pkg/audit/common.go b/pkg/audit/common.go index 05e5ffb9df..79941ac057 100644 --- a/pkg/audit/common.go +++ b/pkg/audit/common.go @@ -8,4 +8,5 @@ type AuthenticatedClientMeta struct { ClientIds []string TokenIssuedAt time.Time ClientIP string + Subject string } diff --git a/pkg/audit/log.go b/pkg/audit/log.go index a6db031c50..8d427df053 100644 --- a/pkg/audit/log.go +++ b/pkg/audit/log.go @@ -26,17 +26,17 @@ type logBuilder struct { func (b *logBuilder) WithAuthenticatedCtx(ctx context.Context) LogBuilder { clientMeta := ctx.Value(common.AuditFieldsContextKey) - switch clientMeta.(type) { + switch m := clientMeta.(type) { case AuthenticatedClientMeta: b.auditLog.Principal = Principal{ - Subject: ctx.Value(common.PrincipalContextKey).(string), - TokenIssuedAt: clientMeta.(AuthenticatedClientMeta).TokenIssuedAt, + Subject: m.Subject, + TokenIssuedAt: m.TokenIssuedAt, } - if len(clientMeta.(AuthenticatedClientMeta).ClientIds) > 0 { - b.auditLog.Principal.ClientID = clientMeta.(AuthenticatedClientMeta).ClientIds[0] + if len(m.ClientIds) > 0 { + b.auditLog.Principal.ClientID = m.ClientIds[0] } b.auditLog.Client = Client{ - ClientIP: clientMeta.(AuthenticatedClientMeta).ClientIP, + ClientIP: m.ClientIP, } default: logger.Warningf(ctx, "Failed to parse authenticated client metadata when creating audit log") diff --git a/pkg/audit/log_test.go b/pkg/audit/log_test.go index 5370152ca0..02faeb11f7 100644 --- a/pkg/audit/log_test.go +++ b/pkg/audit/log_test.go @@ -22,6 +22,7 @@ func TestLogBuilderLog(t *testing.T) { ClientIds: []string{"12345"}, TokenIssuedAt: tokenIssuedAt, ClientIP: "192.0.2.1:25", + Subject: "prince", }) builder := NewLogBuilder().WithAuthenticatedCtx(ctx).WithRequest( "my_method", map[string]string{ diff --git a/pkg/auth/auth_context.go b/pkg/auth/auth_context.go deleted file mode 100644 index 9c9add0cc5..0000000000 --- a/pkg/auth/auth_context.go +++ /dev/null @@ -1,192 +0,0 @@ -package auth - -import ( - "context" - "io/ioutil" - "net/http" - "net/url" - "strings" - "time" - - "github.com/coreos/go-oidc" - "github.com/flyteorg/flyteadmin/pkg/auth/config" - "github.com/flyteorg/flyteadmin/pkg/auth/interfaces" - "github.com/flyteorg/flytestdlib/errors" - "github.com/flyteorg/flytestdlib/logger" - "golang.org/x/oauth2" -) - -const ( - IdpConnectionTimeout = 10 * time.Second -) - -// Please see the comment on the corresponding AuthenticationContext for more information. -type Context struct { - oauth2 *oauth2.Config - claims config.Claims - cookieManager interfaces.CookieHandler - oidcProvider *oidc.Provider - options config.OAuthOptions - userInfoURL *url.URL - baseURL *url.URL - oauth2MetadataURL *url.URL - oidcMetadataURL *url.URL - httpClient *http.Client -} - -func (c Context) OAuth2Config() *oauth2.Config { - return c.oauth2 -} - -func (c Context) Claims() config.Claims { - return c.claims -} - -func (c Context) OidcProvider() *oidc.Provider { - return c.oidcProvider -} - -func (c Context) CookieManager() interfaces.CookieHandler { - return c.cookieManager -} - -func (c Context) Options() config.OAuthOptions { - return c.options -} - -func (c Context) GetUserInfoURL() *url.URL { - return c.userInfoURL -} - -func (c Context) GetHTTPClient() *http.Client { - return c.httpClient -} - -func (c Context) GetBaseURL() *url.URL { - return c.baseURL -} - -func (c Context) GetOAuth2MetadataURL() *url.URL { - return c.oauth2MetadataURL -} - -func (c Context) GetOIdCMetadataURL() *url.URL { - return c.oidcMetadataURL -} - -const ( - ErrAuthContext errors.ErrorCode = "AUTH_CONTEXT_SETUP_FAILED" - ErrConfigFileRead errors.ErrorCode = "CONFIG_OPTION_FILE_READ_FAILED" -) - -func NewAuthenticationContext(ctx context.Context, options config.OAuthOptions) (Context, error) { - result := Context{ - claims: options.Claims, - options: options, - } - - // Construct the golang OAuth2 library's own internal configuration object from this package's config - oauth2Config, err := GetOauth2Config(options) - if err != nil { - return Context{}, errors.Wrapf(ErrAuthContext, err, "Error creating OAuth2 library configuration") - } - - result.oauth2 = &oauth2Config - - // Construct the cookie manager object. - hashKeyBytes, err := ioutil.ReadFile(options.CookieHashKeyFile) - if err != nil { - return Context{}, errors.Wrapf(ErrConfigFileRead, err, "Could not read hash key file") - } - - blockKeyBytes, err := ioutil.ReadFile(options.CookieBlockKeyFile) - if err != nil { - return Context{}, errors.Wrapf(ErrConfigFileRead, err, "Could not read block key file") - } - - cookieManager, err := NewCookieManager(ctx, string(hashKeyBytes), string(blockKeyBytes)) - if err != nil { - logger.Errorf(ctx, "Error creating cookie manager %s", err) - return Context{}, errors.Wrapf(ErrAuthContext, err, "Error creating cookie manager") - } - - result.cookieManager = cookieManager - - // Construct an oidc Provider, which needs its own http Client. - oidcCtx := oidc.ClientContext(ctx, &http.Client{}) - provider, err := oidc.NewProvider(oidcCtx, options.Claims.Issuer) - if err != nil { - return Context{}, errors.Wrapf(ErrAuthContext, err, "Error creating oidc provider w/ issuer [%v]", options.Claims.Issuer) - } - - result.oidcProvider = provider - - // TODO: Convert all the URLs in this config to the config.URL type - // Then we will not have to do any of the parsing in this code here, and the error handling will be taken care for - // us by the flytestdlib config parser. - // Construct base URL object - base, err := url.Parse(options.BaseURL) - if err != nil { - logger.Errorf(ctx, "Error parsing base URL %s", err) - return Context{}, errors.Wrapf(ErrAuthContext, err, "Error parsing IDP base URL") - } - - logger.Infof(ctx, "Base IDP URL is %s", base) - result.baseURL = base - - result.oauth2MetadataURL, err = url.Parse(OAuth2MetadataEndpoint) - if err != nil { - logger.Errorf(ctx, "Error parsing oauth2 metadata URL %s", err) - return Context{}, errors.Wrapf(ErrAuthContext, err, "Error parsing metadata URL") - } - - logger.Infof(ctx, "Metadata endpoint is %s", result.oauth2MetadataURL) - - result.oidcMetadataURL, err = url.Parse(OIdCMetadataEndpoint) - if err != nil { - logger.Errorf(ctx, "Error parsing oidc metadata URL %s", err) - return Context{}, errors.Wrapf(ErrAuthContext, err, "Error parsing metadata URL") - } - - logger.Infof(ctx, "Metadata endpoint is %s", result.oidcMetadataURL) - - // Construct the URL object for the user info endpoint if applicable - if options.IdpUserInfoEndpoint != "" { - parsedURL, err := url.Parse(options.IdpUserInfoEndpoint) - if err != nil { - logger.Errorf(ctx, "Error parsing total IDP user info path %s as URL %s", options.IdpUserInfoEndpoint, err) - return Context{}, errors.Wrapf(ErrAuthContext, err, - "Error parsing IDP user info path as URL while constructing IDP user info endpoint") - } - finalURL := result.baseURL.ResolveReference(parsedURL) - logger.Infof(ctx, "User info URL for IDP is %s", finalURL.String()) - result.userInfoURL = finalURL - } - - // Construct an http client for interacting with the IDP if necessary. - result.httpClient = &http.Client{ - Timeout: IdpConnectionTimeout, - } - - return result, nil -} - -// This creates a oauth2 library config object, with values from the Flyte Admin config -func GetOauth2Config(options config.OAuthOptions) (oauth2.Config, error) { - secretBytes, err := ioutil.ReadFile(options.ClientSecretFile) - if err != nil { - return oauth2.Config{}, err - } - - secret := strings.TrimSuffix(string(secretBytes), "\n") - return oauth2.Config{ - RedirectURL: options.CallbackURL, - ClientID: options.ClientID, - ClientSecret: secret, - Scopes: options.Scopes, - Endpoint: oauth2.Endpoint{ - AuthURL: options.AuthorizeURL, - TokenURL: options.TokenURL, - }, - }, nil -} diff --git a/pkg/auth/config/config.go b/pkg/auth/config/config.go deleted file mode 100644 index f19726b555..0000000000 --- a/pkg/auth/config/config.go +++ /dev/null @@ -1,71 +0,0 @@ -package config - -type OAuthOptions struct { - // The client ID for Admin in your IDP - // See https://tools.ietf.org/html/rfc6749#section-2.2 for more information - ClientID string `json:"clientId"` - - // The client secret used in the exchange of the authorization code for the token. - // https://tools.ietf.org/html/rfc6749#section-2.3 - ClientSecretFile string `json:"clientSecretFile"` - - // This should be the base url of the authorization server that you are trying to hit. With Okta for instance, it - // will look something like https://company.okta.com/oauth2/abcdef123456789/ - // TODO: Convert all the URLs in this config to the config.URL type - BaseURL string `json:"baseUrl"` - - // These two config elements currently need the entire path, including the already specified baseUrl - // TODO: Refactor to ascertain the paths using discovery (see https://tools.ietf.org/html/rfc8414) - // Also refactor to use relative paths when discovery is not available - AuthorizeURL string `json:"authorizeUrl"` - TokenURL string `json:"tokenUrl"` - - // This is the callback URL that will be sent to the IDP authorize endpoint. It is likely that your IDP application - // needs to have this URL whitelisted before using. - CallbackURL string `json:"callbackUrl"` - Claims Claims `json:"claims"` - - // This is the relative path of the user info endpoint, if there is one, for the given IDP. This will be appended to - // the base URL of the IDP. This is used to support the /me endpoint that Admin will serve when running with authentication - // See https://developer.okta.com/docs/reference/api/oidc/#userinfo as an example. - IdpUserInfoEndpoint string `json:"idpUserInfoEndpoint"` - - // These should point to files that contain base64 encoded secrets. - // You can run `go test -v github.com/flyteorg/flyteadmin/pkg/auth -run TestSecureCookieLifecycle` to generate new ones. - // See https://github.com/gorilla/securecookie#examples for more information - CookieHashKeyFile string `json:"cookieHashKeyFile"` - CookieBlockKeyFile string `json:"cookieBlockKeyFile"` - - // This is where the user will be redirected to at the end of the flow, but you should not use it. Instead, - // the initial /login handler should be called with a redirect_url parameter, which will get saved to a cookie. - // This setting will only be used when that cookie is missing. - // See the login handler code for more comments. - RedirectURL string `json:"redirectUrl"` - - // These settings are for non-SSL authentication modes, where Envoy is handling SSL termination - // This is not yet used, but this is the HTTP variant of the setting below. - HTTPAuthorizationHeader string `json:"httpAuthorizationHeader"` - - // In order to support deployments of this Admin service where Envoy is terminating SSL connections, the metadata - // header name cannot be "authorization", which is the standard metadata name. Envoy has special handling for that - // name. Instead, there is a gRPC interceptor, GetAuthenticationCustomMetadataInterceptor, that will translate - // incoming metadata headers with this config setting's name, into that standard header - GrpcAuthorizationHeader string `json:"grpcAuthorizationHeader"` - - // To help ease migration, it was helpful to be able to only selectively enforce authentication. The - // dimension that made the most sense to cut by at time of writing is HTTP vs gRPC as the web UI mainly used HTTP - // and the backend used mostly gRPC. Cutting by individual endpoints is another option but it possibly falls more - // into the realm of authorization rather than authentication. - DisableForHTTP bool `json:"disableForHttp"` - DisableForGrpc bool `json:"disableForGrpc"` - - // Provides a list of scopes to request from the IDP when authenticating. Default value requests claims that should - // be supported by any OIdC server. Refer to https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims for - // a complete list. Other providers might support additional scopes that you can define in a config. - Scopes []string `json:"scopes"` -} - -type Claims struct { - Audience string `json:"aud"` - Issuer string `json:"iss"` -} diff --git a/pkg/auth/constants.go b/pkg/auth/constants.go deleted file mode 100644 index 58540510fe..0000000000 --- a/pkg/auth/constants.go +++ /dev/null @@ -1,16 +0,0 @@ -package auth - -// OAuth2 Parameters -const CsrfFormKey = "state" -const AuthorizationResponseCodeType = "code" -const RefreshToken = "refresh_token" -const DefaultAuthorizationHeader = "authorization" -const BearerScheme = "Bearer" - -// https://tools.ietf.org/html/rfc8414 -// This should be defined without a leading slash. If there is one, the url library's ResolveReference will make it a root path -const OAuth2MetadataEndpoint = ".well-known/oauth-authorization-server" - -// https://openid.net/specs/openid-connect-discovery-1_0.html -// This should be defined without a leading slash. If there is one, the url library's ResolveReference will make it a root path -const OIdCMetadataEndpoint = ".well-known/openid-configuration" diff --git a/pkg/auth/handler_utils.go b/pkg/auth/handler_utils.go deleted file mode 100644 index 591a85a822..0000000000 --- a/pkg/auth/handler_utils.go +++ /dev/null @@ -1,83 +0,0 @@ -package auth - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - - "github.com/flyteorg/flytestdlib/errors" - "github.com/flyteorg/flytestdlib/logger" -) - -const ( - ErrIdpClient errors.ErrorCode = "IDP_REQUEST_FAILED" -) - -/* -This struct represents what should be returned by an IDP according to the specification at - https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse - -Keep in mind that not all fields are necessarily populated, and additional fields may be present as well. This is a sample -response object returned from Okta for instance - { - "sub": "abc123", - "name": "John Smith", - "locale": "US", - "preferred_username": "jsmith123@company.com", - "given_name": "John", - "family_name": "Smith", - "zoneinfo": "America/Los_Angeles", - "updated_at": 1568750854 - } -*/ -type UserInfoResponse struct { - Sub string `json:"sub"` - Name string `json:"name"` - PreferredUsername string `json:"preferred_username"` - GivenName string `json:"given_name"` - FamilyName string `json:"family_name"` - Email string `json:"email"` - Picture string `json:"picture"` -} - -func postToIdp(ctx context.Context, client *http.Client, userInfoURL, accessToken string) (UserInfoResponse, error) { - request, err := http.NewRequest(http.MethodPost, userInfoURL, nil) - if err != nil { - logger.Errorf(ctx, "Error creating user info request to IDP %s", err) - return UserInfoResponse{}, errors.Wrapf(ErrIdpClient, err, "Error creating user info request to IDP") - } - request.Header.Set(DefaultAuthorizationHeader, fmt.Sprintf("%s %s", BearerScheme, accessToken)) - request.Header.Set("Content-Type", "application/json") - response, err := client.Do(request) - if err != nil { - logger.Errorf(ctx, "Error while requesting user info from IDP %s", err) - return UserInfoResponse{}, errors.Wrapf(ErrIdpClient, err, "Error while requesting user info from IDP") - } - defer func() { - err := response.Body.Close() - if err != nil { - logger.Errorf(ctx, "Error closing response body %s", err) - } - }() - - body, err := ioutil.ReadAll(response.Body) - if err != nil { - logger.Errorf(ctx, "Error reading user info response error %s", response.StatusCode, err) - return UserInfoResponse{}, errors.Wrapf(ErrIdpClient, err, "Error reading user info response") - } - if response.StatusCode < 200 || response.StatusCode > 299 { - logger.Errorf(ctx, "Bad response code from IDP %d", response.StatusCode) - return UserInfoResponse{}, errors.Errorf(ErrIdpClient, - "Error reading user info response, code %d body %v", response.StatusCode, body) - } - - responseObject := &UserInfoResponse{} - err = json.Unmarshal(body, responseObject) - if err != nil { - return UserInfoResponse{}, errors.Wrapf(ErrIdpClient, err, "Could not unmarshal IDP response") - } - - return *responseObject, nil -} diff --git a/pkg/auth/handler_utils_test.go b/pkg/auth/handler_utils_test.go deleted file mode 100644 index e1b3abfa99..0000000000 --- a/pkg/auth/handler_utils_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package auth - -import ( - "bytes" - "context" - "encoding/json" - "io/ioutil" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -type RoundTripFunc func(request *http.Request) *http.Response - -func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req), nil -} - -func NewTestHTTPClient(fn RoundTripFunc) *http.Client { - return &http.Client{ - Transport: fn, - } -} - -func TestPostToIdp(t *testing.T) { - ctx := context.Background() - token := "j.w.t" - - // Use a real client and a real token to run a live test - responseObj := &UserInfoResponse{ - Sub: "abc123", - Name: "John Smith", - PreferredUsername: "jsmith@company.com", - GivenName: "John", - FamilyName: "Smith", - } - responseBytes, err := json.Marshal(responseObj) - assert.NoError(t, err) - client := NewTestHTTPClient(func(request *http.Request) *http.Response { - return &http.Response{ - StatusCode: 200, - Body: ioutil.NopCloser(bytes.NewReader(responseBytes)), - Header: make(http.Header), - } - }) - - obj, err := postToIdp(ctx, client, "https://lyft.okta.com/oauth2/ausc5wmjw96cRKvTd1t7/v1/userinfo", token) - assert.NoError(t, err) - assert.Equal(t, responseObj.Name, obj.Name) - assert.Equal(t, responseObj.Sub, obj.Sub) - assert.Equal(t, responseObj.PreferredUsername, obj.PreferredUsername) - assert.Equal(t, responseObj.GivenName, obj.GivenName) - assert.Equal(t, responseObj.FamilyName, obj.FamilyName) -} diff --git a/pkg/auth/handlers.go b/pkg/auth/handlers.go deleted file mode 100644 index 7b4be36d1d..0000000000 --- a/pkg/auth/handlers.go +++ /dev/null @@ -1,307 +0,0 @@ -package auth - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/flyteorg/flyteadmin/pkg/audit" - "github.com/flyteorg/flyteadmin/pkg/common" - "google.golang.org/grpc/peer" - - "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" - - "github.com/flyteorg/flyteadmin/pkg/auth/interfaces" - "github.com/flyteorg/flytestdlib/contextutils" - "github.com/flyteorg/flytestdlib/errors" - "github.com/flyteorg/flytestdlib/logger" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" -) - -const ( - RedirectURLParameter = "redirect_url" - FromHTTPKey = "from_http" - FromHTTPVal = "true" - bearerTokenContextKey contextutils.Key = "bearer" - PrincipalContextKey contextutils.Key = "principal" -) - -type HTTPRequestToMetadataAnnotator func(ctx context.Context, request *http.Request) metadata.MD - -// Look for access token and refresh token, if both are present and the access token is expired, then attempt to -// refresh. Otherwise do nothing and proceed to the next handler. If successfully refreshed, proceed to the landing page. -func RefreshTokensIfExists(ctx context.Context, authContext interfaces.AuthenticationContext, handlerFunc http.HandlerFunc) http.HandlerFunc { - return func(writer http.ResponseWriter, request *http.Request) { - // Since we only do one thing if there are no errors anywhere along the chain, we can save code by just - // using one variable and checking for errors at the end. - idToken, accessToken, refreshToken, err := authContext.CookieManager().RetrieveTokenValues(ctx, request) - if err == nil { - _, err = ParseAndValidate(ctx, authContext.Claims(), idToken, authContext.OidcProvider()) - if err != nil && errors.IsCausedBy(err, ErrTokenExpired) && len(refreshToken) > 0 { - logger.Debugf(ctx, "Expired id token found, attempting to refresh") - newToken, e := GetRefreshedToken(ctx, authContext.OAuth2Config(), accessToken, refreshToken) - err = e - if err == nil { - logger.Debugf(ctx, "Tokens are refreshed. Saving new tokens into cookies.") - err = authContext.CookieManager().SetTokenCookies(ctx, writer, newToken) - } - } - } - - if err != nil { - logger.Errorf(ctx, "Non-expiration error in refresh token handler, redirecting to login handler. Error: %s", err) - handlerFunc(writer, request) - return - } - - redirectURL := getAuthFlowEndRedirect(ctx, authContext, request) - http.Redirect(writer, request, redirectURL, http.StatusTemporaryRedirect) - } -} - -func GetLoginHandler(ctx context.Context, authContext interfaces.AuthenticationContext) http.HandlerFunc { - return func(writer http.ResponseWriter, request *http.Request) { - csrfCookie := NewCsrfCookie() - csrfToken := csrfCookie.Value - http.SetCookie(writer, &csrfCookie) - - state := HashCsrfState(csrfToken) - logger.Debugf(ctx, "Setting CSRF state cookie to %s and state to %s\n", csrfToken, state) - url := authContext.OAuth2Config().AuthCodeURL(state) - queryParams := request.URL.Query() - if flowEndRedirectURL := queryParams.Get(RedirectURLParameter); flowEndRedirectURL != "" { - redirectCookie := NewRedirectCookie(ctx, flowEndRedirectURL) - if redirectCookie != nil { - http.SetCookie(writer, redirectCookie) - } else { - logger.Errorf(ctx, "Was not able to create a redirect cookie") - } - } - http.Redirect(writer, request, url, http.StatusTemporaryRedirect) - } -} - -func GetCallbackHandler(ctx context.Context, authContext interfaces.AuthenticationContext) http.HandlerFunc { - return func(writer http.ResponseWriter, request *http.Request) { - logger.Debugf(ctx, "Running callback handler...") - authorizationCode := request.FormValue(AuthorizationResponseCodeType) - - err := VerifyCsrfCookie(ctx, request) - if err != nil { - logger.Errorf(ctx, "Invalid CSRF token cookie %s", err) - writer.WriteHeader(http.StatusUnauthorized) - return - } - - token, err := authContext.OAuth2Config().Exchange(ctx, authorizationCode) - if err != nil { - logger.Errorf(ctx, "Error when exchanging code %s", err) - writer.WriteHeader(http.StatusForbidden) - return - } - - err = authContext.CookieManager().SetTokenCookies(ctx, writer, token) - if err != nil { - logger.Errorf(ctx, "Error setting encrypted JWT cookie %s", err) - writer.WriteHeader(http.StatusForbidden) - return - } - - redirectURL := getAuthFlowEndRedirect(ctx, authContext, request) - http.Redirect(writer, request, redirectURL, http.StatusTemporaryRedirect) - } -} - -func AuthenticationLoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - // Invoke 'handler' to use your gRPC server implementation and get - // the response. - logger.Debugf(ctx, "gRPC server info in logging interceptor email %s method %s\n", ctx.Value(common.PrincipalContextKey), info.FullMethod) - return handler(ctx, req) -} - -// This function produces a gRPC middleware interceptor intended to be used when running authentication with non-default -// gRPC headers (metadata). Because the default `authorization` header is reserved for use by Envoy, clients wishing -// to pass tokens to Admin will need to use a different string, specified in this package's Config object. This interceptor -// will scan for that arbitrary string, and then rename it to the default string, which the downstream auth/auditing -// interceptors will detect and validate. -func GetAuthenticationCustomMetadataInterceptor(authCtx interfaces.AuthenticationContext) grpc.UnaryServerInterceptor { - return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - if authCtx.Options().GrpcAuthorizationHeader != DefaultAuthorizationHeader { - md, ok := metadata.FromIncomingContext(ctx) - if ok { - existingHeader := md.Get(authCtx.Options().GrpcAuthorizationHeader) - if len(existingHeader) > 0 { - logger.Debugf(ctx, "Found existing metadata %s", existingHeader[0]) - newAuthorizationMetadata := metadata.Pairs(DefaultAuthorizationHeader, existingHeader[0]) - joinedMetadata := metadata.Join(md, newAuthorizationMetadata) - newCtx := metadata.NewIncomingContext(ctx, joinedMetadata) - return handler(newCtx, req) - } - } else { - logger.Debugf(ctx, "Could not extract incoming metadata from context, continuing with original ctx...") - } - } - return handler(ctx, req) - } -} - -// This is the function that chooses to enforce or not enforce authentication. It will attempt to get the token -// from the incoming context, validate it, and decide whether or not to let the request through. -func GetAuthenticationInterceptor(authContext interfaces.AuthenticationContext) func(context.Context) (context.Context, error) { - return func(ctx context.Context) (context.Context, error) { - logger.Debugf(ctx, "Running authentication gRPC interceptor") - - fromHTTP := metautils.ExtractIncoming(ctx).Get(FromHTTPKey) - isFromHTTP := fromHTTP == FromHTTPVal - - token, err := GetAndValidateTokenObjectFromContext(ctx, authContext.Claims(), authContext.OidcProvider()) - - // Only enforcement logic is present. The default case is to let things through. - if (isFromHTTP && !authContext.Options().DisableForHTTP) || - (!isFromHTTP && !authContext.Options().DisableForGrpc) { - if err != nil { - return ctx, status.Errorf(codes.Unauthenticated, "token parse error %s", err) - } - if token == nil { - return ctx, status.Errorf(codes.Unauthenticated, "Token was nil after parsing") - } else if token.Subject == "" { - return ctx, status.Errorf(codes.Unauthenticated, "no email or empty email found") - } - } - - if token != nil { - newCtx := WithUserEmail(context.WithValue(ctx, bearerTokenContextKey, token), token.Subject) - newCtx = WithAuditFields(newCtx, token.Audience, token.IssuedAt) - return newCtx, nil - } - return ctx, nil - } -} - -func WithUserEmail(ctx context.Context, email string) context.Context { - return context.WithValue(ctx, common.PrincipalContextKey, email) -} - -func WithAuditFields(ctx context.Context, clientIds []string, tokenIssuedAt time.Time) context.Context { - var clientIP string - peer, ok := peer.FromContext(ctx) - if ok { - clientIP = peer.Addr.String() - } - return context.WithValue(ctx, common.AuditFieldsContextKey, audit.AuthenticatedClientMeta{ - ClientIds: clientIds, - TokenIssuedAt: tokenIssuedAt, - ClientIP: clientIP, - }) -} - -// This is effectively middleware for the grpc gateway, it allows us to modify the translation between HTTP request -// and gRPC request. There are two potential sources for bearer tokens, it can come from an authorization header (not -// yet implemented), or encrypted cookies. Note that when deploying behind Envoy, you have the option to look for a -// configurable, non-standard header name. The token is extracted and turned into a metadata object which is then -// attached to the request, from which the token is extracted later for verification. -func GetHTTPRequestCookieToMetadataHandler(authContext interfaces.AuthenticationContext) HTTPRequestToMetadataAnnotator { - return func(ctx context.Context, request *http.Request) metadata.MD { - // TODO: Improve error handling - idToken, _, _, _ := authContext.CookieManager().RetrieveTokenValues(ctx, request) - if len(idToken) == 0 { - // If no token was found in the cookies, look for an authorization header, starting with a potentially - // custom header set in the Config object - if authContext.Options().HTTPAuthorizationHeader != "" { - header := authContext.Options().HTTPAuthorizationHeader - // TODO: There may be a potential issue here when running behind a service mesh that uses the default Authorization - // header. The grpc-gateway code will automatically translate the 'Authorization' header into the appropriate - // metadata object so if two different tokens are presented, one with the default name and one with the - // custom name, AuthFromMD will find the wrong one. - return metadata.MD{ - DefaultAuthorizationHeader: []string{request.Header.Get(header)}, - } - } - logger.Infof(ctx, "Could not find access token cookie while requesting %s", request.RequestURI) - return nil - } - return metadata.MD{ - DefaultAuthorizationHeader: []string{fmt.Sprintf("%s %s", BearerScheme, idToken)}, - } - } -} - -// Intercepts the incoming HTTP requests and marks it as such so that the downstream code can use it to enforce auth. -// See the enforceHTTP/Grpc options for more information. -func GetHTTPMetadataTaggingHandler(authContext interfaces.AuthenticationContext) HTTPRequestToMetadataAnnotator { - return func(ctx context.Context, request *http.Request) metadata.MD { - return metadata.MD{ - FromHTTPKey: []string{FromHTTPVal}, - } - } -} - -// TODO: Add this to the Admin service IDL in Flyte IDL so that this can be exposed from gRPC as well. -// This returns a handler that will retrieve user info, from the OAuth2 authorization server. -// See the OpenID Connect spec at https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse for more information. -func GetMeEndpointHandler(ctx context.Context, authCtx interfaces.AuthenticationContext) http.HandlerFunc { - idpUserInfoEndpoint := authCtx.GetUserInfoURL().String() - return func(writer http.ResponseWriter, request *http.Request) { - _, accessToken, _, err := authCtx.CookieManager().RetrieveTokenValues(ctx, request) - if err != nil { - http.Error(writer, "Error decoding identify token, try /login in again", http.StatusUnauthorized) - return - } - // TODO: Investigate improving transparency of errors. The errors from this call may be just a local error, or may - // be an error from the HTTP request to the IDP. In the latter case, consider passing along the error code/msg. - userInfo, err := postToIdp(ctx, authCtx.GetHTTPClient(), idpUserInfoEndpoint, accessToken) - if err != nil { - logger.Errorf(ctx, "Error getting user info from IDP %s", err) - http.Error(writer, "Error getting user info from IDP", http.StatusFailedDependency) - return - } - - bytes, err := json.Marshal(userInfo) - if err != nil { - logger.Errorf(ctx, "Error marshaling response into JSON %s", err) - http.Error(writer, "Error marshaling response into JSON", http.StatusInternalServerError) - return - } - writer.Header().Set("Content-Type", "application/json") - size, err := writer.Write(bytes) - if err != nil { - logger.Errorf(ctx, "Wrote user info response size %d, err %s", size, err) - } - } -} - -// This returns a handler that will redirect (303) to the well-known metadata endpoint for the OAuth2 authorization server -// See https://tools.ietf.org/html/rfc8414 for more information. -func GetOAuth2MetadataEndpointRedirectHandler(ctx context.Context, authCtx interfaces.AuthenticationContext) http.HandlerFunc { - return func(writer http.ResponseWriter, request *http.Request) { - metadataURL := authCtx.GetBaseURL().ResolveReference(authCtx.GetOAuth2MetadataURL()) - http.Redirect(writer, request, metadataURL.String(), http.StatusSeeOther) - } -} - -// This returns a handler that will redirect (303) to the well-known metadata endpoint for the OAuth2 authorization server -// See https://tools.ietf.org/html/rfc8414 for more information. -func GetOIdCMetadataEndpointRedirectHandler(ctx context.Context, authCtx interfaces.AuthenticationContext) http.HandlerFunc { - return func(writer http.ResponseWriter, request *http.Request) { - metadataURL := authCtx.GetBaseURL().ResolveReference(authCtx.GetOIdCMetadataURL()) - http.Redirect(writer, request, metadataURL.String(), http.StatusSeeOther) - } -} - -func GetLogoutEndpointHandler(ctx context.Context, authCtx interfaces.AuthenticationContext) http.HandlerFunc { - return func(writer http.ResponseWriter, request *http.Request) { - logger.Debugf(ctx, "Deleting auth cookies") - authCtx.CookieManager().DeleteCookies(ctx, writer) - - // Redirect if one was given - queryParams := request.URL.Query() - if redirectURL := queryParams.Get(RedirectURLParameter); redirectURL != "" { - http.Redirect(writer, request, redirectURL, http.StatusTemporaryRedirect) - } - } -} diff --git a/pkg/auth/interfaces/context.go b/pkg/auth/interfaces/context.go deleted file mode 100644 index 80acf69b4b..0000000000 --- a/pkg/auth/interfaces/context.go +++ /dev/null @@ -1,27 +0,0 @@ -package interfaces - -import ( - "net/http" - "net/url" - - "github.com/coreos/go-oidc" - "github.com/flyteorg/flyteadmin/pkg/auth/config" - "golang.org/x/oauth2" -) - -//go:generate mockery -name=AuthenticationContext -case=underscore - -// This interface is a convenience wrapper object that holds all the utilities necessary to run Flyte Admin behind authentication -// It is constructed at the root server layer, and passed around to the various auth handlers and utility functions/objects. -type AuthenticationContext interface { - OAuth2Config() *oauth2.Config - Claims() config.Claims - OidcProvider() *oidc.Provider - CookieManager() CookieHandler - Options() config.OAuthOptions - GetUserInfoURL() *url.URL - GetBaseURL() *url.URL - GetOAuth2MetadataURL() *url.URL - GetOIdCMetadataURL() *url.URL - GetHTTPClient() *http.Client -} diff --git a/pkg/auth/interfaces/cookie.go b/pkg/auth/interfaces/cookie.go deleted file mode 100644 index 27232094cd..0000000000 --- a/pkg/auth/interfaces/cookie.go +++ /dev/null @@ -1,14 +0,0 @@ -package interfaces - -import ( - "context" - "net/http" - - "golang.org/x/oauth2" -) - -type CookieHandler interface { - RetrieveTokenValues(ctx context.Context, request *http.Request) (idToken, accessToken, refreshToken string, err error) - SetTokenCookies(ctx context.Context, writer http.ResponseWriter, token *oauth2.Token) error - DeleteCookies(ctx context.Context, writer http.ResponseWriter) -} diff --git a/pkg/auth/token.go b/pkg/auth/token.go deleted file mode 100644 index 43671457e2..0000000000 --- a/pkg/auth/token.go +++ /dev/null @@ -1,80 +0,0 @@ -package auth - -import ( - "context" - "strings" - "time" - - "github.com/coreos/go-oidc" - "github.com/flyteorg/flyteadmin/pkg/auth/config" - "github.com/flyteorg/flytestdlib/errors" - "github.com/flyteorg/flytestdlib/logger" - grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" - "golang.org/x/oauth2" -) - -const ( - ErrRefreshingToken errors.ErrorCode = "TOKEN_REFRESH_FAILURE" - ErrTokenExpired errors.ErrorCode = "JWT_EXPIRED" - ErrJwtValidation errors.ErrorCode = "JWT_VERIFICATION_FAILED" -) - -// Refresh a JWT -func GetRefreshedToken(ctx context.Context, oauth *oauth2.Config, accessToken, refreshToken string) (*oauth2.Token, error) { - logger.Debugf(ctx, "Attempting to refresh token") - originalToken := oauth2.Token{ - AccessToken: accessToken, - RefreshToken: refreshToken, - Expiry: time.Now().Add(-1 * time.Minute), // force expired by setting to the past - } - - tokenSource := oauth.TokenSource(ctx, &originalToken) - newToken, err := tokenSource.Token() - if err != nil { - logger.Errorf(ctx, "Error refreshing token %s", err) - return nil, errors.Wrapf(ErrRefreshingToken, err, "Error refreshing token") - } - - return newToken, nil -} - -func ParseAndValidate(ctx context.Context, claims config.Claims, rawIDToken string, - provider *oidc.Provider) (*oidc.IDToken, error) { - - var verifier = provider.Verifier(&oidc.Config{ - ClientID: claims.Audience, - }) - - idToken, err := verifier.Verify(ctx, rawIDToken) - if err != nil { - logger.Debugf(ctx, "JWT parsing with claims failed %s", err) - flyteErr := errors.Wrapf(ErrJwtValidation, err, "jwt parse with claims failed") - // TODO: Contribute an errors package to the go-oidc library for proper error handling - if strings.Contains(err.Error(), "token is expired") { - return idToken, errors.Wrapf(ErrTokenExpired, flyteErr, "token is expired") - } - - return idToken, flyteErr - } - - return idToken, nil -} - -// This function attempts to extract a token from the context, and will then call the validation function, passing up -// any errors. -func GetAndValidateTokenObjectFromContext(ctx context.Context, claims config.Claims, - provider *oidc.Provider) (*oidc.IDToken, error) { - - tokenStr, err := grpcauth.AuthFromMD(ctx, BearerScheme) - if err != nil { - logger.Debugf(ctx, "Could not retrieve bearer token from metadata %v", err) - return nil, errors.Wrapf(ErrJwtValidation, err, "Could not retrieve bearer token from metadata") - } - - if tokenStr == "" { - logger.Debugf(ctx, "Found Bearer scheme but token was blank") - return nil, errors.Errorf(ErrJwtValidation, "Bearer token is blank") - } - - return ParseAndValidate(ctx, claims, tokenStr, provider) -} diff --git a/pkg/config/config.go b/pkg/config/config.go index 4775eb3a9b..303c6b71ad 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -3,7 +3,7 @@ package config import ( "fmt" - config2 "github.com/flyteorg/flyteadmin/pkg/auth/config" + authConfig "github.com/flyteorg/flyteadmin/auth/config" "github.com/flyteorg/flytestdlib/config" ) @@ -12,21 +12,22 @@ const SectionKey = "server" //go:generate pflags ServerConfig --default-var=defaultServerConfig type ServerConfig struct { - HTTPPort int `json:"httpPort" pflag:",On which http port to serve admin"` - GrpcPort int `json:"grpcPort" pflag:",On which grpc port to serve admin"` - GrpcServerReflection bool `json:"grpcServerReflection" pflag:",Enable GRPC Server Reflection"` - KubeConfig string `json:"kube-config" pflag:",Path to kubernetes client config file."` - Master string `json:"master" pflag:",The address of the Kubernetes API server."` - Security ServerSecurityOptions `json:"security"` - ThirdPartyConfig ThirdPartyConfigOptions `json:"thirdPartyConfig"` + HTTPPort int `json:"httpPort" pflag:",On which http port to serve admin"` + GrpcPort int `json:"grpcPort" pflag:",On which grpc port to serve admin"` + GrpcServerReflection bool `json:"grpcServerReflection" pflag:",Enable GRPC Server Reflection"` + KubeConfig string `json:"kube-config" pflag:",Path to kubernetes client config file."` + Master string `json:"master" pflag:",The address of the Kubernetes API server."` + Security ServerSecurityOptions `json:"security"` + + // Deprecated: please use auth.AppAuth.ThirdPartyConfig instead. + DeprecatedThirdPartyConfig authConfig.ThirdPartyConfigOptions `json:"thirdPartyConfig" pflag:",Deprecated please use auth.appAuth.thirdPartyConfig instead."` } type ServerSecurityOptions struct { - Secure bool `json:"secure"` - Ssl SslOptions `json:"ssl"` - UseAuth bool `json:"useAuth"` - Oauth config2.OAuthOptions `json:"oauth"` - AuditAccess bool `json:"auditAccess"` + Secure bool `json:"secure"` + Ssl SslOptions `json:"ssl"` + UseAuth bool `json:"useAuth"` + AuditAccess bool `json:"auditAccess"` // These options are here to allow deployments where the Flyte UI (Console) is served from a different domain/port. // Note that CORS only applies to Admin's API endpoints. The health check endpoint for instance is unaffected. @@ -47,22 +48,14 @@ type SslOptions struct { } var defaultServerConfig = &ServerConfig{ - Security: ServerSecurityOptions{ - Oauth: config2.OAuthOptions{ - // Please see the comments in this struct's definition for more information - HTTPAuthorizationHeader: "flyte-authorization", - GrpcAuthorizationHeader: "flyte-authorization", - // Default claims that should be supported by any OIdC server. Refer to https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims - // for a complete list. - Scopes: []string{ - "openid", - "profile", - }, - }, - }, + Security: ServerSecurityOptions{}, } var serverConfig = config.MustRegisterSection(SectionKey, defaultServerConfig) +func MustRegisterSubsection(key config.SectionKey, configSection config.Config) config.Section { + return serverConfig.MustRegisterSection(key, configSection) +} + func GetConfig() *ServerConfig { return serverConfig.GetConfig().(*ServerConfig) } diff --git a/pkg/config/serverconfig_flags.go b/pkg/config/serverconfig_flags.go index e1a1f56ff5..ceed1453b3 100755 --- a/pkg/config/serverconfig_flags.go +++ b/pkg/config/serverconfig_flags.go @@ -28,15 +28,6 @@ func (ServerConfig) elemValueOrNil(v interface{}) interface{} { return v } -func (ServerConfig) mustJsonMarshal(v interface{}) string { - raw, err := json.Marshal(v) - if err != nil { - panic(err) - } - - return string(raw) -} - func (ServerConfig) mustMarshalJSON(v json.Marshaler) string { raw, err := v.MarshalJSON() if err != nil { @@ -59,28 +50,12 @@ func (cfg ServerConfig) GetPFlagSet(prefix string) *pflag.FlagSet { cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.ssl.certificateFile"), defaultServerConfig.Security.Ssl.CertificateFile, "") cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.ssl.keyFile"), defaultServerConfig.Security.Ssl.KeyFile, "") cmdFlags.Bool(fmt.Sprintf("%v%v", prefix, "security.useAuth"), defaultServerConfig.Security.UseAuth, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.clientId"), defaultServerConfig.Security.Oauth.ClientID, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.clientSecretFile"), defaultServerConfig.Security.Oauth.ClientSecretFile, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.baseUrl"), defaultServerConfig.Security.Oauth.BaseURL, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.authorizeUrl"), defaultServerConfig.Security.Oauth.AuthorizeURL, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.tokenUrl"), defaultServerConfig.Security.Oauth.TokenURL, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.callbackUrl"), defaultServerConfig.Security.Oauth.CallbackURL, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.claims.aud"), defaultServerConfig.Security.Oauth.Claims.Audience, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.claims.iss"), defaultServerConfig.Security.Oauth.Claims.Issuer, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.idpUserInfoEndpoint"), defaultServerConfig.Security.Oauth.IdpUserInfoEndpoint, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.cookieHashKeyFile"), defaultServerConfig.Security.Oauth.CookieHashKeyFile, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.cookieBlockKeyFile"), defaultServerConfig.Security.Oauth.CookieBlockKeyFile, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.redirectUrl"), defaultServerConfig.Security.Oauth.RedirectURL, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.httpAuthorizationHeader"), defaultServerConfig.Security.Oauth.HTTPAuthorizationHeader, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "security.oauth.grpcAuthorizationHeader"), defaultServerConfig.Security.Oauth.GrpcAuthorizationHeader, "") - cmdFlags.Bool(fmt.Sprintf("%v%v", prefix, "security.oauth.disableForHttp"), defaultServerConfig.Security.Oauth.DisableForHTTP, "") - cmdFlags.Bool(fmt.Sprintf("%v%v", prefix, "security.oauth.disableForGrpc"), defaultServerConfig.Security.Oauth.DisableForGrpc, "") - cmdFlags.StringSlice(fmt.Sprintf("%v%v", prefix, "security.oauth.scopes"), []string{}, "") cmdFlags.Bool(fmt.Sprintf("%v%v", prefix, "security.auditAccess"), defaultServerConfig.Security.AuditAccess, "") cmdFlags.Bool(fmt.Sprintf("%v%v", prefix, "security.allowCors"), defaultServerConfig.Security.AllowCors, "") cmdFlags.StringSlice(fmt.Sprintf("%v%v", prefix, "security.allowedOrigins"), []string{}, "") cmdFlags.StringSlice(fmt.Sprintf("%v%v", prefix, "security.allowedHeaders"), []string{}, "") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "thirdPartyConfig.flyteClient.clientId"), defaultServerConfig.ThirdPartyConfig.FlyteClientConfig.ClientID, "public identifier for the app which handles authorization for a Flyte deployment") - cmdFlags.String(fmt.Sprintf("%v%v", prefix, "thirdPartyConfig.flyteClient.redirectUri"), defaultServerConfig.ThirdPartyConfig.FlyteClientConfig.RedirectURI, "This is the callback uri registered with the app which handles authorization for a Flyte deployment") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "thirdPartyConfig.flyteClient.clientId"), defaultServerConfig.DeprecatedThirdPartyConfig.FlyteClientConfig.ClientID, "public identifier for the app which handles authorization for a Flyte deployment") + cmdFlags.String(fmt.Sprintf("%v%v", prefix, "thirdPartyConfig.flyteClient.redirectUri"), defaultServerConfig.DeprecatedThirdPartyConfig.FlyteClientConfig.RedirectURI, "This is the callback uri registered with the app which handles authorization for a Flyte deployment") + cmdFlags.StringSlice(fmt.Sprintf("%v%v", prefix, "thirdPartyConfig.flyteClient.scopes"), []string{}, "Recommended scopes for the client to request.") return cmdFlags } diff --git a/pkg/config/serverconfig_flags_test.go b/pkg/config/serverconfig_flags_test.go index 3e9ddd1975..cf17475cf7 100755 --- a/pkg/config/serverconfig_flags_test.go +++ b/pkg/config/serverconfig_flags_test.go @@ -84,7 +84,7 @@ func testDecodeJson_ServerConfig(t *testing.T, val, result interface{}) { assert.NoError(t, decode_ServerConfig(val, result)) } -func testDecodeRaw_ServerConfig(t *testing.T, vStringSlice, result interface{}) { +func testDecodeSlice_ServerConfig(t *testing.T, vStringSlice, result interface{}) { assert.NoError(t, decode_ServerConfig(vStringSlice, result)) } @@ -100,6 +100,14 @@ func TestServerConfig_SetFlags(t *testing.T) { assert.True(t, cmdFlags.HasFlags()) t.Run("Test_httpPort", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vInt, err := cmdFlags.GetInt("httpPort"); err == nil { + assert.Equal(t, int(defaultServerConfig.HTTPPort), vInt) + } else { + assert.FailNow(t, err.Error()) + } + }) t.Run("Override", func(t *testing.T) { testValue := "1" @@ -114,6 +122,14 @@ func TestServerConfig_SetFlags(t *testing.T) { }) }) t.Run("Test_grpcPort", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vInt, err := cmdFlags.GetInt("grpcPort"); err == nil { + assert.Equal(t, int(defaultServerConfig.GrpcPort), vInt) + } else { + assert.FailNow(t, err.Error()) + } + }) t.Run("Override", func(t *testing.T) { testValue := "1" @@ -128,6 +144,14 @@ func TestServerConfig_SetFlags(t *testing.T) { }) }) t.Run("Test_grpcServerReflection", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vBool, err := cmdFlags.GetBool("grpcServerReflection"); err == nil { + assert.Equal(t, bool(defaultServerConfig.GrpcServerReflection), vBool) + } else { + assert.FailNow(t, err.Error()) + } + }) t.Run("Override", func(t *testing.T) { testValue := "1" @@ -142,405 +166,285 @@ func TestServerConfig_SetFlags(t *testing.T) { }) }) t.Run("Test_kube-config", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("kube-config", testValue) + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly if vString, err := cmdFlags.GetString("kube-config"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.KubeConfig) - + assert.Equal(t, string(defaultServerConfig.KubeConfig), vString) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_master", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("master", testValue) - if vString, err := cmdFlags.GetString("master"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Master) + cmdFlags.Set("kube-config", testValue) + if vString, err := cmdFlags.GetString("kube-config"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.KubeConfig) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.secure", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.secure", testValue) - if vBool, err := cmdFlags.GetBool("security.secure"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.Secure) - + t.Run("Test_master", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("master"); err == nil { + assert.Equal(t, string(defaultServerConfig.Master), vString) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.ssl.certificateFile", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("security.ssl.certificateFile", testValue) - if vString, err := cmdFlags.GetString("security.ssl.certificateFile"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Ssl.CertificateFile) + cmdFlags.Set("master", testValue) + if vString, err := cmdFlags.GetString("master"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Master) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.ssl.keyFile", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.ssl.keyFile", testValue) - if vString, err := cmdFlags.GetString("security.ssl.keyFile"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Ssl.KeyFile) - + t.Run("Test_security.secure", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vBool, err := cmdFlags.GetBool("security.secure"); err == nil { + assert.Equal(t, bool(defaultServerConfig.Security.Secure), vBool) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.useAuth", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("security.useAuth", testValue) - if vBool, err := cmdFlags.GetBool("security.useAuth"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.UseAuth) + cmdFlags.Set("security.secure", testValue) + if vBool, err := cmdFlags.GetBool("security.secure"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.Secure) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.oauth.clientId", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.oauth.clientId", testValue) - if vString, err := cmdFlags.GetString("security.oauth.clientId"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.ClientID) - + t.Run("Test_security.ssl.certificateFile", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("security.ssl.certificateFile"); err == nil { + assert.Equal(t, string(defaultServerConfig.Security.Ssl.CertificateFile), vString) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.oauth.clientSecretFile", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("security.oauth.clientSecretFile", testValue) - if vString, err := cmdFlags.GetString("security.oauth.clientSecretFile"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.ClientSecretFile) + cmdFlags.Set("security.ssl.certificateFile", testValue) + if vString, err := cmdFlags.GetString("security.ssl.certificateFile"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Ssl.CertificateFile) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.oauth.baseUrl", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.oauth.baseUrl", testValue) - if vString, err := cmdFlags.GetString("security.oauth.baseUrl"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.BaseURL) - + t.Run("Test_security.ssl.keyFile", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("security.ssl.keyFile"); err == nil { + assert.Equal(t, string(defaultServerConfig.Security.Ssl.KeyFile), vString) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.oauth.authorizeUrl", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("security.oauth.authorizeUrl", testValue) - if vString, err := cmdFlags.GetString("security.oauth.authorizeUrl"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.AuthorizeURL) + cmdFlags.Set("security.ssl.keyFile", testValue) + if vString, err := cmdFlags.GetString("security.ssl.keyFile"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Ssl.KeyFile) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.oauth.tokenUrl", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.oauth.tokenUrl", testValue) - if vString, err := cmdFlags.GetString("security.oauth.tokenUrl"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.TokenURL) - + t.Run("Test_security.useAuth", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vBool, err := cmdFlags.GetBool("security.useAuth"); err == nil { + assert.Equal(t, bool(defaultServerConfig.Security.UseAuth), vBool) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.oauth.callbackUrl", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("security.oauth.callbackUrl", testValue) - if vString, err := cmdFlags.GetString("security.oauth.callbackUrl"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.CallbackURL) + cmdFlags.Set("security.useAuth", testValue) + if vBool, err := cmdFlags.GetBool("security.useAuth"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.UseAuth) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.oauth.claims.aud", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.oauth.claims.aud", testValue) - if vString, err := cmdFlags.GetString("security.oauth.claims.aud"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.Claims.Audience) - + t.Run("Test_security.auditAccess", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vBool, err := cmdFlags.GetBool("security.auditAccess"); err == nil { + assert.Equal(t, bool(defaultServerConfig.Security.AuditAccess), vBool) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.oauth.claims.iss", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("security.oauth.claims.iss", testValue) - if vString, err := cmdFlags.GetString("security.oauth.claims.iss"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.Claims.Issuer) + cmdFlags.Set("security.auditAccess", testValue) + if vBool, err := cmdFlags.GetBool("security.auditAccess"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.AuditAccess) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.oauth.idpUserInfoEndpoint", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.oauth.idpUserInfoEndpoint", testValue) - if vString, err := cmdFlags.GetString("security.oauth.idpUserInfoEndpoint"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.IdpUserInfoEndpoint) - + t.Run("Test_security.allowCors", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vBool, err := cmdFlags.GetBool("security.allowCors"); err == nil { + assert.Equal(t, bool(defaultServerConfig.Security.AllowCors), vBool) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.oauth.cookieHashKeyFile", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("security.oauth.cookieHashKeyFile", testValue) - if vString, err := cmdFlags.GetString("security.oauth.cookieHashKeyFile"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.CookieHashKeyFile) + cmdFlags.Set("security.allowCors", testValue) + if vBool, err := cmdFlags.GetBool("security.allowCors"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.AllowCors) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.oauth.cookieBlockKeyFile", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.oauth.cookieBlockKeyFile", testValue) - if vString, err := cmdFlags.GetString("security.oauth.cookieBlockKeyFile"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.CookieBlockKeyFile) - + t.Run("Test_security.allowedOrigins", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vStringSlice, err := cmdFlags.GetStringSlice("security.allowedOrigins"); err == nil { + assert.Equal(t, []string([]string{}), vStringSlice) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.oauth.redirectUrl", func(t *testing.T) { t.Run("Override", func(t *testing.T) { - testValue := "1" + testValue := join_ServerConfig("1,1", ",") - cmdFlags.Set("security.oauth.redirectUrl", testValue) - if vString, err := cmdFlags.GetString("security.oauth.redirectUrl"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.RedirectURL) + cmdFlags.Set("security.allowedOrigins", testValue) + if vStringSlice, err := cmdFlags.GetStringSlice("security.allowedOrigins"); err == nil { + testDecodeSlice_ServerConfig(t, join_ServerConfig(vStringSlice, ","), &actual.Security.AllowedOrigins) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.oauth.httpAuthorizationHeader", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.oauth.httpAuthorizationHeader", testValue) - if vString, err := cmdFlags.GetString("security.oauth.httpAuthorizationHeader"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.HTTPAuthorizationHeader) - + t.Run("Test_security.allowedHeaders", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vStringSlice, err := cmdFlags.GetStringSlice("security.allowedHeaders"); err == nil { + assert.Equal(t, []string([]string{}), vStringSlice) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.oauth.grpcAuthorizationHeader", func(t *testing.T) { t.Run("Override", func(t *testing.T) { - testValue := "1" + testValue := join_ServerConfig("1,1", ",") - cmdFlags.Set("security.oauth.grpcAuthorizationHeader", testValue) - if vString, err := cmdFlags.GetString("security.oauth.grpcAuthorizationHeader"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.Security.Oauth.GrpcAuthorizationHeader) + cmdFlags.Set("security.allowedHeaders", testValue) + if vStringSlice, err := cmdFlags.GetStringSlice("security.allowedHeaders"); err == nil { + testDecodeSlice_ServerConfig(t, join_ServerConfig(vStringSlice, ","), &actual.Security.AllowedHeaders) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.oauth.disableForHttp", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.oauth.disableForHttp", testValue) - if vBool, err := cmdFlags.GetBool("security.oauth.disableForHttp"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.Oauth.DisableForHTTP) - + t.Run("Test_thirdPartyConfig.flyteClient.clientId", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("thirdPartyConfig.flyteClient.clientId"); err == nil { + assert.Equal(t, string(defaultServerConfig.DeprecatedThirdPartyConfig.FlyteClientConfig.ClientID), vString) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.oauth.disableForGrpc", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("security.oauth.disableForGrpc", testValue) - if vBool, err := cmdFlags.GetBool("security.oauth.disableForGrpc"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.Oauth.DisableForGrpc) - - } else { - assert.FailNow(t, err.Error()) - } - }) - }) - t.Run("Test_security.oauth.scopes", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := join_ServerConfig("1,1", ",") - - cmdFlags.Set("security.oauth.scopes", testValue) - if vStringSlice, err := cmdFlags.GetStringSlice("security.oauth.scopes"); err == nil { - testDecodeRaw_ServerConfig(t, join_ServerConfig(vStringSlice, ","), &actual.Security.Oauth.Scopes) + cmdFlags.Set("thirdPartyConfig.flyteClient.clientId", testValue) + if vString, err := cmdFlags.GetString("thirdPartyConfig.flyteClient.clientId"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.DeprecatedThirdPartyConfig.FlyteClientConfig.ClientID) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.auditAccess", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("security.auditAccess", testValue) - if vBool, err := cmdFlags.GetBool("security.auditAccess"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.AuditAccess) - + t.Run("Test_thirdPartyConfig.flyteClient.redirectUri", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vString, err := cmdFlags.GetString("thirdPartyConfig.flyteClient.redirectUri"); err == nil { + assert.Equal(t, string(defaultServerConfig.DeprecatedThirdPartyConfig.FlyteClientConfig.RedirectURI), vString) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.allowCors", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := "1" - cmdFlags.Set("security.allowCors", testValue) - if vBool, err := cmdFlags.GetBool("security.allowCors"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vBool), &actual.Security.AllowCors) + cmdFlags.Set("thirdPartyConfig.flyteClient.redirectUri", testValue) + if vString, err := cmdFlags.GetString("thirdPartyConfig.flyteClient.redirectUri"); err == nil { + testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.DeprecatedThirdPartyConfig.FlyteClientConfig.RedirectURI) } else { assert.FailNow(t, err.Error()) } }) }) - t.Run("Test_security.allowedOrigins", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := join_ServerConfig("1,1", ",") - - cmdFlags.Set("security.allowedOrigins", testValue) - if vStringSlice, err := cmdFlags.GetStringSlice("security.allowedOrigins"); err == nil { - testDecodeRaw_ServerConfig(t, join_ServerConfig(vStringSlice, ","), &actual.Security.AllowedOrigins) - + t.Run("Test_thirdPartyConfig.flyteClient.scopes", func(t *testing.T) { + t.Run("DefaultValue", func(t *testing.T) { + // Test that default value is set properly + if vStringSlice, err := cmdFlags.GetStringSlice("thirdPartyConfig.flyteClient.scopes"); err == nil { + assert.Equal(t, []string([]string{}), vStringSlice) } else { assert.FailNow(t, err.Error()) } }) - }) - t.Run("Test_security.allowedHeaders", func(t *testing.T) { t.Run("Override", func(t *testing.T) { testValue := join_ServerConfig("1,1", ",") - cmdFlags.Set("security.allowedHeaders", testValue) - if vStringSlice, err := cmdFlags.GetStringSlice("security.allowedHeaders"); err == nil { - testDecodeRaw_ServerConfig(t, join_ServerConfig(vStringSlice, ","), &actual.Security.AllowedHeaders) - - } else { - assert.FailNow(t, err.Error()) - } - }) - }) - t.Run("Test_thirdPartyConfig.flyteClient.clientId", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("thirdPartyConfig.flyteClient.clientId", testValue) - if vString, err := cmdFlags.GetString("thirdPartyConfig.flyteClient.clientId"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.ThirdPartyConfig.FlyteClientConfig.ClientID) - - } else { - assert.FailNow(t, err.Error()) - } - }) - }) - t.Run("Test_thirdPartyConfig.flyteClient.redirectUri", func(t *testing.T) { - - t.Run("Override", func(t *testing.T) { - testValue := "1" - - cmdFlags.Set("thirdPartyConfig.flyteClient.redirectUri", testValue) - if vString, err := cmdFlags.GetString("thirdPartyConfig.flyteClient.redirectUri"); err == nil { - testDecodeJson_ServerConfig(t, fmt.Sprintf("%v", vString), &actual.ThirdPartyConfig.FlyteClientConfig.RedirectURI) + cmdFlags.Set("thirdPartyConfig.flyteClient.scopes", testValue) + if vStringSlice, err := cmdFlags.GetStringSlice("thirdPartyConfig.flyteClient.scopes"); err == nil { + testDecodeSlice_ServerConfig(t, join_ServerConfig(vStringSlice, ","), &actual.DeprecatedThirdPartyConfig.FlyteClientConfig.Scopes) } else { assert.FailNow(t, err.Error()) diff --git a/pkg/config/third_party_config.go b/pkg/config/third_party_config.go deleted file mode 100644 index 785c6e4452..0000000000 --- a/pkg/config/third_party_config.go +++ /dev/null @@ -1,12 +0,0 @@ -package config - -// This struct encapsulates config options for bootstrapping various Flyte applications with config values -// For example, FlyteClientConfig contains application-specific values to initialize the config required by flyte client -type ThirdPartyConfigOptions struct { - FlyteClientConfig FlyteClientConfig `json:"flyteClient"` -} - -type FlyteClientConfig struct { - ClientID string `json:"clientId" pflag:",public identifier for the app which handles authorization for a Flyte deployment"` - RedirectURI string `json:"redirectUri" pflag:",This is the callback uri registered with the app which handles authorization for a Flyte deployment"` -} diff --git a/pkg/executioncluster/impl/in_cluster.go b/pkg/executioncluster/impl/in_cluster.go index b1edcb546b..eabbbb2ba8 100644 --- a/pkg/executioncluster/impl/in_cluster.go +++ b/pkg/executioncluster/impl/in_cluster.go @@ -39,7 +39,7 @@ func NewInCluster(scope promutils.Scope, kubeConfig, master string) (interfaces. if err != nil { return nil, err } - client, err := client.New(clientConfig, client.Options{}) + kubeClient, err := client.New(clientConfig, client.Options{}) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func NewInCluster(scope promutils.Scope, kubeConfig, master string) (interfaces. } return &InCluster{ target: executioncluster.ExecutionTarget{ - Client: client, + Client: kubeClient, FlyteClient: flyteClient, DynamicClient: dynamicClient, Config: *clientConfig, diff --git a/pkg/manager/impl/execution_manager.go b/pkg/manager/impl/execution_manager.go index d3e057fcf6..f054009e16 100644 --- a/pkg/manager/impl/execution_manager.go +++ b/pkg/manager/impl/execution_manager.go @@ -6,6 +6,8 @@ import ( "strconv" "time" + "github.com/flyteorg/flyteadmin/auth" + "k8s.io/apimachinery/pkg/api/resource" "github.com/flyteorg/flyteadmin/pkg/manager/impl/resources" @@ -46,7 +48,6 @@ import ( ) const childContainerQueueKey = "child_queue" -const principalContextKeyFormat = "%v" // Map of [project] -> map of [domain] -> stop watch type projectDomainScopedStopWatchMap = map[string]map[string]*promutils.StopWatch @@ -101,11 +102,8 @@ func getExecutionContext(ctx context.Context, id *core.WorkflowExecutionIdentifi // Returns the unique string which identifies the authenticated end user (if any). func getUser(ctx context.Context) string { - principalContextUser := ctx.Value(common.PrincipalContextKey) - if principalContextUser != nil { - return fmt.Sprintf(principalContextKeyFormat, principalContextUser) - } - return "" + identityContext := auth.IdentityContextFromContext(ctx) + return identityContext.UserID() } func (m *ExecutionManager) populateExecutionQueue( diff --git a/pkg/manager/impl/execution_manager_test.go b/pkg/manager/impl/execution_manager_test.go index aa7c249f35..32c6286448 100644 --- a/pkg/manager/impl/execution_manager_test.go +++ b/pkg/manager/impl/execution_manager_test.go @@ -5,11 +5,13 @@ import ( "errors" "testing" + "k8s.io/apimachinery/pkg/util/sets" + eventWriterMocks "github.com/flyteorg/flyteadmin/pkg/async/events/mocks" "github.com/gogo/protobuf/jsonpb" - "github.com/flyteorg/flyteadmin/pkg/auth" + "github.com/flyteorg/flyteadmin/auth" "github.com/flyteorg/flyteadmin/pkg/common" commonMocks "github.com/flyteorg/flyteadmin/pkg/common/mocks" @@ -263,7 +265,9 @@ func TestCreateExecution(t *testing.T) { request.Spec.Metadata = &admin.ExecutionMetadata{ Principal: "unused - populated from authenticated context", } - ctx := context.WithValue(context.Background(), auth.PrincipalContextKey, principal) + + identity := auth.NewIdentityContext("", principal, "", time.Now(), sets.NewString(), nil) + ctx := identity.WithContext(context.Background()) response, err := execManager.CreateExecution(ctx, request, requestedAt) assert.Nil(t, err) @@ -1845,7 +1849,8 @@ func TestTerminateExecution(t *testing.T) { }) execManager := NewExecutionManager(repository, getMockExecutionsConfigProvider(), getMockStorageForExecTest(context.Background()), mockExecutor, mockScope.NewTestScope(), mockScope.NewTestScope(), &mockPublisher, mockExecutionRemoteURL, nil, nil, nil, &eventWriterMocks.WorkflowExecutionEventWriter{}) - ctx := context.WithValue(context.Background(), auth.PrincipalContextKey, principal) + identity := auth.NewIdentityContext("", principal, "", time.Now(), sets.NewString(), nil) + ctx := identity.WithContext(context.Background()) resp, err := execManager.TerminateExecution(ctx, admin.ExecutionTerminateRequest{ Id: &core.WorkflowExecutionIdentifier{ Project: "project", diff --git a/pkg/rpc/config/flyte_client.go b/pkg/rpc/config/flyte_client.go deleted file mode 100644 index 5d3bb0487b..0000000000 --- a/pkg/rpc/config/flyte_client.go +++ /dev/null @@ -1,40 +0,0 @@ -package config - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/flyteorg/flyteadmin/pkg/config" - "github.com/flyteorg/flytestdlib/logger" -) - -const ( - clientID = "client_id" - redirectURI = "redirect_uri" - scopes = "scopes" - authMetadataKey = "authorization_metadata_key" -) - -func HandleFlyteCliConfigFunc(ctx context.Context, cfg *config.ServerConfig) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - configValues := map[string]interface{}{ - clientID: cfg.ThirdPartyConfig.FlyteClientConfig.ClientID, - redirectURI: cfg.ThirdPartyConfig.FlyteClientConfig.RedirectURI, - scopes: cfg.Security.Oauth.Scopes, - authMetadataKey: cfg.Security.Oauth.GrpcAuthorizationHeader, - } - - configJSON, err := json.Marshal(configValues) - if err != nil { - logger.Infof(ctx, "Failed to marshal flyte_client config to JSON with err: %v", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - _, err = w.Write(configJSON) - if err != nil { - logger.Warningf(ctx, "Failed to write config json [%+v] with err: %v", string(configJSON), err) - } - } -} diff --git a/pkg/rpc/config/flyte_client_test.go b/pkg/rpc/config/flyte_client_test.go deleted file mode 100644 index 6b6fce1665..0000000000 --- a/pkg/rpc/config/flyte_client_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package config - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - authConfig "github.com/flyteorg/flyteadmin/pkg/auth/config" - "github.com/flyteorg/flyteadmin/pkg/config" - "github.com/stretchr/testify/assert" -) - -func TestHandleFlyteCliConfigFunc(t *testing.T) { - testClientID := "12345" - testRedirectURI := "localhost:12345/callback" - testAuthMetadataKey := "flyte-authorization" - - handleFlyteCliConfigFunc := HandleFlyteCliConfigFunc(context.Background(), &config.ServerConfig{ - Security: config.ServerSecurityOptions{ - Oauth: authConfig.OAuthOptions{ - GrpcAuthorizationHeader: testAuthMetadataKey, - }, - }, - ThirdPartyConfig: config.ThirdPartyConfigOptions{ - FlyteClientConfig: config.FlyteClientConfig{ - ClientID: testClientID, - RedirectURI: testRedirectURI, - }, - }, - }) - - responseRecorder := httptest.NewRecorder() - handleFlyteCliConfigFunc(responseRecorder, nil) - assert.Equal(t, http.StatusOK, responseRecorder.Code) - responseBody := responseRecorder.Body - var responseBodyMap map[string]string - err := json.Unmarshal(responseBody.Bytes(), &responseBodyMap) - if err != nil { - t.Fatalf("Failed to unmarshal response body with err: %v", err) - } - assert.EqualValues(t, map[string]string{ - clientID: testClientID, - redirectURI: testRedirectURI, - authMetadataKey: testAuthMetadataKey, - scopes: "", - }, responseBodyMap) -}