Merge pull request #3867 from dolthub/taylor/jwtauth

Sql server jwt auth
This commit is contained in:
Taylor Bantle
2022-07-25 13:50:53 -07:00
committed by GitHub
29 changed files with 1196 additions and 114 deletions
+209
View File
@@ -5455,6 +5455,215 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
= LICENSE 6681c42f6974591d2056518a26201323fa7d42bdc4d64bfc12c332b3 =
================================================================================
================================================================================
= github.com/pquerna/cachecontrol licensed under: =
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
= LICENSE 75cd5500580317e758b5e984e017524dc961140e4889f7d427f85e41 =
================================================================================
================================================================================
= github.com/prometheus/client_golang licensed under: =
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package engine
import (
"fmt"
"strings"
"time"
"github.com/dolthub/go-mysql-server/sql/mysql_db"
"github.com/sirupsen/logrus"
"github.com/dolthub/dolt/go/libraries/utils/jwtauth"
)
type JwksConfig struct {
Name string `yaml:"name"`
LocationUrl string `yaml:"location_url"`
Claims map[string]string `yaml:"claims"`
FieldsToLog []string `yaml:"fields_to_log"`
}
// authenticateDoltJWTPlugin is used to authenticate plaintext user plugins
type authenticateDoltJWTPlugin struct {
jwksConfig []JwksConfig
}
func NewAuthenticateDoltJWTPlugin(jwksConfig []JwksConfig) mysql_db.PlaintextAuthPlugin {
return &authenticateDoltJWTPlugin{jwksConfig: jwksConfig}
}
func (p *authenticateDoltJWTPlugin) Authenticate(db *mysql_db.MySQLDb, user string, userEntry *mysql_db.User, pass string) (bool, error) {
return validateJWT(p.jwksConfig, user, userEntry.Identity, pass, time.Now())
}
func validateJWT(config []JwksConfig, username, identity, token string, reqTime time.Time) (bool, error) {
if len(config) == 0 {
return false, fmt.Errorf("ValidateJWT: JWKS server config not found")
}
expectedClaimsMap := parseUserIdentity(identity)
sub, ok := expectedClaimsMap["sub"]
if ok && sub != username {
return false, fmt.Errorf("ValidateJWT: Subjects do not match")
}
jwksConfig, err := getMatchingJwksConfig(config, expectedClaimsMap["jwks"])
if err != nil {
return false, err
}
pr, err := getJWTProvider(expectedClaimsMap, jwksConfig.LocationUrl)
if err != nil {
return false, err
}
vd := jwtauth.NewJWTValidator(pr)
claims, err := vd.ValidateJWT(token, reqTime)
if err != nil {
return false, err
}
logString := "Authenticating with JWT: "
for _, field := range jwksConfig.FieldsToLog {
logString += fmt.Sprintf("%s: %s,", field, getClaimFromKey(claims, field))
}
logrus.Info(logString)
return true, nil
}
func getJWTProvider(expectedClaimsMap map[string]string, url string) (jwtauth.JWTProvider, error) {
pr := jwtauth.JWTProvider{URL: url}
for name, claim := range expectedClaimsMap {
switch name {
case "iss":
pr.Issuer = claim
case "aud":
pr.Audience = claim
case "sub":
pr.Subject = claim
case "jwks":
continue
default:
return pr, fmt.Errorf("ValidateJWT: Unexpected expected claim found in user identity")
}
}
return pr, nil
}
func getClaimFromKey(claims *jwtauth.Claims, field string) string {
switch field {
case "id":
return claims.ID
case "iss":
return claims.Issuer
case "sub":
return claims.Subject
case "on_behalf_of":
return claims.OnBehalfOf
}
return ""
}
func getMatchingJwksConfig(config []JwksConfig, name string) (*JwksConfig, error) {
for _, item := range config {
if item.Name == name {
return &item, nil
}
}
return nil, fmt.Errorf("ValidateJWT: Matching JWKS config not found")
}
func parseUserIdentity(identity string) map[string]string {
idMap := make(map[string]string)
items := strings.Split(identity, ",")
for _, item := range items {
tup := strings.Split(item, "=")
idMap[tup[0]] = tup[1]
}
return idMap
}
@@ -0,0 +1,78 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package engine
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
)
var jwksName = "jwksname"
var sub = "test_user"
var iss = "dolthub.com"
var aud = "my_resource"
var onBehalfOf = "my_user"
var jwt = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImUwNjA2Y2QwLTkwNWQtNGFiYS05MjBjLTZlNTE0YTFjYmIyNiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsibXlfcmVzb3VyY2UiXSwiZXhwIjoxNjU4Mjc1OTAzLCJpYXQiOjE2NTgyNzU4NzMsImlzcyI6ImRvbHRodWIuY29tIiwianRpIjoiN2ViZTg3YmMtOTkzMi00ZTljLTk5N2EtNjQzMDk0NTBkMWVjIiwib25fYmVoYWxmX29mIjoibXlfdXNlciIsInN1YiI6InRlc3RfdXNlciJ9.u2cUGUkQ2hk4AaxtNQB-6Jcdf5LtehFA7XX2FG8LGgTf6KfwE3cuuGaBIU8Jz9ktD9g8TjAbfAfbrNaFNYnKG6SnDUHp0t7VbfLdgfNDQqSyH0nOK2UF8ffxqa46PRxeMwTSJv8prE07rcmiZNL9Ie4vSGYLncJfMzo_RdE-A-PH7z-ZyZ_TxOMhkgMFq2Af5Px3zFuAKq-Y-PrQNopSuzjPJc0DQ93Q7EcIHfU6Fx6gOVTkzHxnOFcg3Nj-4HhqBSvBa_BdMYEzHJKx3F_9rrCCPqEGUFnxXAqFFmnZUQuQKpN2yW_zhviCVqrvbP7vOCIXmxi8YXLiGiV-4KlxHA"
func TestJWTAuth(t *testing.T) {
jwksConfig := []JwksConfig{
{
Name: jwksName,
LocationUrl: fmt.Sprintf("file:///testdata/test_jwks.json"),
Claims: map[string]string{
"alg": "RS256",
"aud": aud,
"iss": iss,
"sub": sub,
},
FieldsToLog: []string{"id", "on_behalf_of"},
},
}
// Success
tokenCreated := time.Date(2022, 07, 20, 0, 12, 0, 0, time.UTC) // Update time if creating new token
authed, err := validateJWT(jwksConfig, sub, fmt.Sprintf("jwks=%s,sub=%s,iss=%s,aud=%s", jwksName, sub, iss, aud), jwt, tokenCreated)
require.NoError(t, err)
require.True(t, authed)
// Token expired
now := time.Now()
authed, err = validateJWT(jwksConfig, sub, fmt.Sprintf("jwks=%s,sub=%s,iss=%s,aud=%s", jwksName, sub, iss, aud), jwt, now)
require.Error(t, err)
require.False(t, authed)
// Expected sub does not match
authed, err = validateJWT(jwksConfig, sub, fmt.Sprintf("jwks=%s,sub=%s,iss=%s,aud=%s", jwksName, "wrong-sub", iss, aud), jwt, tokenCreated)
require.Error(t, err)
require.False(t, authed)
// Jwks config doesn't exist
authed, err = validateJWT([]JwksConfig{}, sub, fmt.Sprintf("jwks=%s,sub=%s,iss=%s,aud=%s", jwksName, sub, iss, aud), jwt, tokenCreated)
require.Error(t, err)
require.False(t, authed)
// No token
authed, err = validateJWT(jwksConfig, sub, fmt.Sprintf("jwks=%s,sub=%s,iss=%s,aud=%s", jwksName, sub, iss, aud), "", tokenCreated)
require.Error(t, err)
require.False(t, authed)
// Unknown claim in identity string
authed, err = validateJWT(jwksConfig, sub, fmt.Sprintf("jwks=%s,sub=%s,iss=%s,unknown=%s", jwksName, sub, iss, aud), "", tokenCreated)
require.Error(t, err)
require.False(t, authed)
}
+6
View File
@@ -25,6 +25,7 @@ import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/analyzer"
"github.com/dolthub/go-mysql-server/sql/information_schema"
"github.com/dolthub/go-mysql-server/sql/mysql_db"
"github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
@@ -54,6 +55,7 @@ type SqlEngineConfig struct {
ServerPass string
Autocommit bool
Bulk bool
JwksConfig []JwksConfig
}
// NewSqlEngine returns a SqlEngine
@@ -106,6 +108,10 @@ func NewSqlEngine(
// Set up engine
engine := gms.New(analyzer.NewBuilder(pro).WithParallelism(parallelism).Build(), &gms.Config{IsReadOnly: config.IsReadOnly, TemporaryUsers: tempUsers}).WithBackgroundThreads(bThreads)
engine.Analyzer.Catalog.MySQLDb.SetPersister(persister)
engine.Analyzer.Catalog.MySQLDb.SetPlugins(map[string]mysql_db.PlaintextAuthPlugin{
"authentication_dolt_jwt": NewAuthenticateDoltJWTPlugin(config.JwksConfig),
})
// Load MySQL Db information
if err = engine.Analyzer.Catalog.MySQLDb.LoadData(sql.NewEmptyContext(), data); err != nil {
return nil, err
+1
View File
@@ -0,0 +1 @@
{"keys":[{"use":"sig","kty":"RSA","kid":"e0606cd0-905d-4aba-920c-6e514a1cbb26","alg":"RS256","n":"3uhDGQLrA8ZJIPAixKoxzCMzWRHb5_UWNihyccCWUicUnCOuTr5caoP4pTVdvK8SudQDQJFXBcO8i3Y5zIHTafTjYZ_ofe07_E9Pz9hFI8pvfXLfL0PWaSO5Y2XGyCgC2ibLqAEc71VYIqruyVuaYqv4gqG__gmBcR3C4gULOjYkBTNs7jMT2XaqlPE0dWDWSfLC-Zz-Czxkp52fOxUVAYb1tZcuActo6DLwnYGf5cpJMU76BhEegEkJzvJqDr9DdS996cjl2vxhPtCtS2rx7z4fCk5gCnCefztu_JjageY5Lk9IG03xzW3A2TUAXm-t_KJbRKa4-DghWEfBr8zv1w","e":"AQAB"}]}
+1
View File
@@ -139,6 +139,7 @@ func Serve(
ServerUser: serverConfig.User(),
ServerPass: serverConfig.Password(),
Autocommit: serverConfig.AutoCommit(),
JwksConfig: serverConfig.JwksConfig(),
}
sqlEngine, err := engine.NewSqlEngine(
ctx,
@@ -164,6 +164,7 @@ func TestServerGoodParams(t *testing.T) {
DefaultServerConfig().withLogLevel(LogLevel_Info).WithPort(15408),
DefaultServerConfig().withReadOnly(true).WithPort(15409),
DefaultServerConfig().withUser("testusernamE").withPassword("hunter2").withTimeout(4).WithPort(15410),
DefaultServerConfig().withAllowCleartextPasswords(true),
}
for _, test := range tests {
@@ -174,7 +175,7 @@ func TestServerGoodParams(t *testing.T) {
}(test, sc)
err := sc.WaitForStart()
require.NoError(t, err)
conn, err := dbr.Open("mysql", ConnectionString(test), nil)
conn, err := dbr.Open("mysql", ConnectionString(test, "dbname"), nil)
require.NoError(t, err)
err = conn.Close()
require.NoError(t, err)
@@ -198,7 +199,7 @@ func TestServerSelect(t *testing.T) {
require.NoError(t, err)
const dbName = "dolt"
conn, err := dbr.Open("mysql", ConnectionString(serverConfig)+dbName, nil)
conn, err := dbr.Open("mysql", ConnectionString(serverConfig, dbName), nil)
require.NoError(t, err)
defer conn.Close()
sess := conn.NewSession(nil)
@@ -274,7 +275,7 @@ func TestServerSetDefaultBranch(t *testing.T) {
const dbName = "dolt"
conn, err := dbr.Open("mysql", ConnectionString(serverConfig)+dbName, nil)
conn, err := dbr.Open("mysql", ConnectionString(serverConfig, dbName), nil)
require.NoError(t, err)
sess := conn.NewSession(nil)
@@ -316,7 +317,7 @@ func TestServerSetDefaultBranch(t *testing.T) {
}
conn.Close()
conn, err = dbr.Open("mysql", ConnectionString(serverConfig)+dbName, nil)
conn, err = dbr.Open("mysql", ConnectionString(serverConfig, dbName), nil)
require.NoError(t, err)
defer conn.Close()
@@ -353,7 +354,7 @@ func TestServerSetDefaultBranch(t *testing.T) {
}
conn.Close()
conn, err = dbr.Open("mysql", ConnectionString(serverConfig)+dbName, nil)
conn, err = dbr.Open("mysql", ConnectionString(serverConfig, dbName), nil)
require.NoError(t, err)
defer conn.Close()
@@ -431,7 +432,7 @@ func TestReadReplica(t *testing.T) {
multiSetup.PushToRemote(sourceDbName, "remote1", "main")
t.Run("read replica pulls multiple branches", func(t *testing.T) {
conn, err := dbr.Open("mysql", ConnectionString(serverConfig)+readReplicaDbName, nil)
conn, err := dbr.Open("mysql", ConnectionString(serverConfig, readReplicaDbName), nil)
defer conn.Close()
require.NoError(t, err)
sess := conn.NewSession(nil)
+75 -50
View File
@@ -20,6 +20,7 @@ import (
"net"
"path/filepath"
"github.com/dolthub/dolt/go/cmd/dolt/commands/engine"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
)
@@ -36,22 +37,23 @@ const (
)
const (
defaultHost = "localhost"
defaultPort = 3306
defaultUser = "root"
defaultPass = ""
defaultTimeout = 8 * 60 * 60 * 1000 // 8 hours, same as MySQL
defaultReadOnly = false
defaultLogLevel = LogLevel_Info
defaultAutoCommit = true
defaultMaxConnections = 100
defaultQueryParallelism = 2
defaultPersistenceBahavior = loadPerisistentGlobals
defaultDataDir = "."
defaultCfgDir = ".doltcfg"
defaultPrivilegeFilePath = "privileges.db"
defaultMetricsHost = ""
defaultMetricsPort = -1
defaultHost = "localhost"
defaultPort = 3306
defaultUser = "root"
defaultPass = ""
defaultTimeout = 8 * 60 * 60 * 1000 // 8 hours, same as MySQL
defaultReadOnly = false
defaultLogLevel = LogLevel_Info
defaultAutoCommit = true
defaultMaxConnections = 100
defaultQueryParallelism = 2
defaultPersistenceBahavior = loadPerisistentGlobals
defaultDataDir = "."
defaultCfgDir = ".doltcfg"
defaultPrivilegeFilePath = "privileges.db"
defaultMetricsHost = ""
defaultMetricsPort = -1
defaultAllowCleartextPasswords = false
)
const (
@@ -132,27 +134,32 @@ type ServerConfig interface {
PrivilegeFilePath() string
// UserVars is an array containing user specific session variables
UserVars() []UserSessionVars
// JwksConfig is an array containing jwks config
JwksConfig() []engine.JwksConfig
// AllowCleartextPasswords is true if the server should accept cleartext passwords.
AllowCleartextPasswords() bool
}
type commandLineServerConfig struct {
host string
port int
user string
password string
timeout uint64
readOnly bool
logLevel LogLevel
dbNamesAndPaths []env.EnvNameAndPath
dataDir string
cfgDir string
autoCommit bool
maxConnections uint64
queryParallelism int
tlsKey string
tlsCert string
requireSecureTransport bool
persistenceBehavior string
privilegeFilePath string
host string
port int
user string
password string
timeout uint64
readOnly bool
logLevel LogLevel
dbNamesAndPaths []env.EnvNameAndPath
dataDir string
cfgDir string
autoCommit bool
maxConnections uint64
queryParallelism int
tlsKey string
tlsCert string
requireSecureTransport bool
persistenceBehavior string
privilegeFilePath string
allowCleartextPasswords bool
}
var _ ServerConfig = (*commandLineServerConfig)(nil)
@@ -253,6 +260,14 @@ func (cfg *commandLineServerConfig) UserVars() []UserSessionVars {
return nil
}
func (cfg *commandLineServerConfig) JwksConfig() []engine.JwksConfig {
return nil
}
func (cfg *commandLineServerConfig) AllowCleartextPasswords() bool {
return cfg.allowCleartextPasswords
}
// DatabaseNamesAndPaths returns an array of env.EnvNameAndPathObjects corresponding to the databases to be loaded in
// a multiple db configuration. If nil is returned the server will look for a database in the current directory and
// give it a name automatically.
@@ -348,23 +363,29 @@ func (cfg *commandLineServerConfig) withPrivilegeFilePath(privFilePath string) *
return cfg
}
func (cfg *commandLineServerConfig) withAllowCleartextPasswords(allow bool) *commandLineServerConfig {
cfg.allowCleartextPasswords = allow
return cfg
}
// DefaultServerConfig creates a `*ServerConfig` that has all of the options set to their default values.
func DefaultServerConfig() *commandLineServerConfig {
return &commandLineServerConfig{
host: defaultHost,
port: defaultPort,
user: defaultUser,
password: defaultPass,
timeout: defaultTimeout,
readOnly: defaultReadOnly,
logLevel: defaultLogLevel,
autoCommit: defaultAutoCommit,
maxConnections: defaultMaxConnections,
queryParallelism: defaultQueryParallelism,
persistenceBehavior: defaultPersistenceBahavior,
dataDir: defaultDataDir,
cfgDir: filepath.Join(defaultDataDir, defaultCfgDir),
privilegeFilePath: filepath.Join(defaultDataDir, defaultCfgDir, defaultPrivilegeFilePath),
host: defaultHost,
port: defaultPort,
user: defaultUser,
password: defaultPass,
timeout: defaultTimeout,
readOnly: defaultReadOnly,
logLevel: defaultLogLevel,
autoCommit: defaultAutoCommit,
maxConnections: defaultMaxConnections,
queryParallelism: defaultQueryParallelism,
persistenceBehavior: defaultPersistenceBahavior,
dataDir: defaultDataDir,
cfgDir: filepath.Join(defaultDataDir, defaultCfgDir),
privilegeFilePath: filepath.Join(defaultDataDir, defaultCfgDir, defaultPrivilegeFilePath),
allowCleartextPasswords: defaultAllowCleartextPasswords,
}
}
@@ -392,8 +413,12 @@ func ValidateConfig(config ServerConfig) error {
}
// ConnectionString returns a Data Source Name (DSN) to be used by go clients for connecting to a running server.
func ConnectionString(config ServerConfig) string {
return fmt.Sprintf("%v:%v@tcp(%v:%v)/", config.User(), config.Password(), config.Host(), config.Port())
func ConnectionString(config ServerConfig, database string) string {
str := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v", config.User(), config.Password(), config.Host(), config.Port(), database)
if config.AllowCleartextPasswords() {
str += "?allowCleartextPasswords=1"
}
return str
}
// ConfigInfo returns a summary of some of the config which contains some of the more important information
+1 -1
View File
@@ -137,7 +137,7 @@ func (cmd SqlClientCmd) Exec(ctx context.Context, commandStr string, args []stri
}
}
conn, err := dbr.Open("mysql", ConnectionString(serverConfig), nil)
conn, err := dbr.Open("mysql", ConnectionString(serverConfig, ""), nil)
if err != nil {
cli.PrintErrln(err.Error())
serverController.StopServer()
+15 -12
View File
@@ -34,18 +34,19 @@ import (
)
const (
hostFlag = "host"
portFlag = "port"
userFlag = "user"
passwordFlag = "password"
timeoutFlag = "timeout"
readonlyFlag = "readonly"
logLevelFlag = "loglevel"
noAutoCommitFlag = "no-auto-commit"
configFileFlag = "config"
queryParallelismFlag = "query-parallelism"
maxConnectionsFlag = "max-connections"
persistenceBehaviorFlag = "persistence-behavior"
hostFlag = "host"
portFlag = "port"
userFlag = "user"
passwordFlag = "password"
timeoutFlag = "timeout"
readonlyFlag = "readonly"
logLevelFlag = "loglevel"
noAutoCommitFlag = "no-auto-commit"
configFileFlag = "config"
queryParallelismFlag = "query-parallelism"
maxConnectionsFlag = "max-connections"
persistenceBehaviorFlag = "persistence-behavior"
allowCleartextPasswordsFlag = "allow-cleartext-passwords"
)
func indentLines(s string) string {
@@ -145,6 +146,7 @@ func (cmd SqlServerCmd) ArgParser() *argparser.ArgParser {
ap.SupportsInt(maxConnectionsFlag, "", "max-connections", fmt.Sprintf("Set the number of connections handled by the server (default `%d`)", serverConfig.MaxConnections()))
ap.SupportsString(persistenceBehaviorFlag, "", "persistence-behavior", fmt.Sprintf("Indicate whether to `load` or `ignore` persisted global variables (default `%s`)", serverConfig.PersistenceBehavior()))
ap.SupportsString(commands.PrivsFilePathFlag, "", "privilege file", "Path to a file to load and store users and grants. Defaults to $doltcfg-dir/privileges.db")
ap.SupportsString(allowCleartextPasswordsFlag, "", "allow-cleartext-passwords", "Allows use of cleartext passwords. Defaults to false.")
return ap
}
@@ -402,6 +404,7 @@ func getCommandLineServerConfig(dEnv *env.DoltEnv, apr *argparser.ArgParseResult
}
serverConfig.autoCommit = !apr.Contains(noAutoCommitFlag)
serverConfig.allowCleartextPasswords = apr.Contains(allowCleartextPasswordsFlag)
return serverConfig, nil
}
+25 -17
View File
@@ -1,19 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIBNDCB56ADAgECAhEAhVdohjG405MnAoq13fHhEDAFBgMrZXAwGDEWMBQGA1UE
ChMNRG9sdEh1YiwgSW5jLjAeFw0yMTA4MjcyMjI4MzdaFw0yMTA4MjgwMDI4Mzda
MBgxFjAUBgNVBAoTDURvbHRIdWIsIEluYy4wKjAFBgMrZXADIQC/Zq8Ls6UODL79
YzRbfgy9oahzE87ivh8FgM+lKPkBtaNGMEQwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud
JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDwYDVR0RBAgwBocEfwAAATAF
BgMrZXADQQAoT9OOXUnXkw+la93z+1unL867fVNnONbNWkH/slRs/cHXIE2MQy6+
bKq/GWJjmcE+IldXLsGefzDrV49RrucM
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBVzCCAQmgAwIBAgIRAI0WiAW7LLS1/nv1+9T/y0swBQYDK2VwMBgxFjAUBgNV
BAoTDURvbHRIdWIsIEluYy4wHhcNMjEwODI3MjIyODM3WhcNMjEwODI4MDAyODM3
WjAYMRYwFAYDVQQKEw1Eb2x0SHViLCBJbmMuMCowBQYDK2VwAyEAHb23NjQMfMsj
riWIJVCXmqWTFCuAa0ETYI/3kL7QhxqjaDBmMA4GA1UdDwEB/wQEAwIHgDATBgNV
HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRBKiEB
oxoqqO7cTCUUCeFVzdC4iDAPBgNVHREECDAGhwR/AAABMAUGAytlcANBAEV94bXU
ELMAUs+uhcp1Px4/1oHu//x56qwRWVpoeQ1AxF8hJ7ImZao2NQ1xQkbJ5KSZzPWU
od969YFcalxnkQU=
MIIErDCCApQCCQCnSokQKR3M/zANBgkqhkiG9w0BAQUFADAYMRYwFAYDVQQKDA1E
b2x0SHViLCBJbmMuMB4XDTIyMDcyMTIwMDgzMloXDTI2MDcxOTIwMDgzMlowGDEW
MBQGA1UECgwNRG9sdEh1YiwgSW5jLjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
AgoCggIBAMPmzHy0CmW5Xc27rbRYpJG/QKMXVAz+k2v+AkTQkUzBWKv0z8WhePB/
tDNVfVYuYQ2sBiHTaar9nn2Lokon+YkPjyMis2aMETHVuqx0DmJb9YcxniA8M27o
ZlfDrJtQO5UzIp9q2zhsFWj30Qdm6YUOhZ3rTnvYOMUYG/cIYLWXyQCg1oPqRVRr
GldzLP2GdigdrS6QQjA9AdK+Zi3dP2m2vssG4gJ+lkAWOHe7wvv2RJl/alsvWXmw
pur7Q9Z7M+tQmqGDxlyDtkDDecyqvEkxPH7mnKV1jahJjzUFHND1r44JlCN0eTmD
Q3+RldBNZCZSJWQ42yOIK+mTSp4QUvZL9wnJ1/lMb/v7atDlF/MSLeN6SDyAPod7
Oci8PR+nGhaOKacngrogM6SFQ1kF4tlY5Scrpg61IAcf6uxF3eSBP0qEaFvfLXZV
mc136E4g2G1haLt7y2prckCHLXEnxurXU4xlU/SH4cy4jB/zLZJs46tM7J9ZtCjg
QScZeNBA91kKAvHr36f/+suU3MNPAP2fmMCziH2uxh6SxTP8yzsUoV9PCTeaSnXX
rTMB077j0TOB2qsYhLF3XsLMz+B2Jo0b7ydT7c7rMS9yYvyKPA9JSE44nUrZWj3B
7ity1moIfrzwbH3AK3D5I9iUbBV0+JpuIZFPoqTIb15TUXJSusYHAgMBAAEwDQYJ
KoZIhvcNAQEFBQADggIBABGrQEUFJk5StmyFUGvaw/57H+K1ZT62rusFBq1NacMb
61dMh9xJyDMgLiUllQ8q5CS3bjYt2J2KajpU/58ugF/Ct9aoxA4vFDtfHECllYaH
zvoiK0Dkrf901xxNVeCbHDmXbvzJ0N/xTkP80kbT4o+aBOw6fxQVEBGAGg4EEz1D
k7v3/lEsZ2TkCPua1p9kXHaG8+wwE0hAWsaUYgXHTpzz0gUBJ69bOIlBpLKqO9It
HStkPD7wtYnN54pmOM68EAyXAxUC7yZ9PqncX0X04hH0VlmQGfdXFJDR89mSS6B4
P1qsi1XtnKC/hHuJlrY02uMXn7u1cVCf5uWfFm6Xs8rLL+q28gV6Tr2aXqgY0Cjl
tNtUEIP23/irWN48c5/rKOTiUIHJy2m6UofwMQO91jgKFxIyUmkgPQmos2LLNjtk
VFaPRigAaArwvombUmvfXJl6KoyH/je4H4+Gs+rRQURXU/PD1cioHgsOYNXSmYAj
AQJv/xp9QBmpzb1ExJOKeWjnUWGu0Wdv4TCTXJNvfdQqOVkT6k6ty1urgr9fNOxY
PDbHZTI6rXMtT57G108k2gAkaCE6O2R2Dm+vfW7auauqF3lNiZU9Y8IEGU2ybmE3
s2j+THPWmhuepbZKO5daQH0zlma31QgoyhGSoZ6QUWKEjufEvfx4HwGqMP6BEmaP
-----END CERTIFICATE-----
+50 -1
View File
@@ -1,3 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
LWLCd/YowgxkNl5j6a5QMFoZvjLi5A==
-----END PRIVATE KEY-----
+25 -8
View File
@@ -1,10 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIBVzCCAQmgAwIBAgIRAI0WiAW7LLS1/nv1+9T/y0swBQYDK2VwMBgxFjAUBgNV
BAoTDURvbHRIdWIsIEluYy4wHhcNMjEwODI3MjIyODM3WhcNMjEwODI4MDAyODM3
WjAYMRYwFAYDVQQKEw1Eb2x0SHViLCBJbmMuMCowBQYDK2VwAyEAHb23NjQMfMsj
riWIJVCXmqWTFCuAa0ETYI/3kL7QhxqjaDBmMA4GA1UdDwEB/wQEAwIHgDATBgNV
HSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRBKiEB
oxoqqO7cTCUUCeFVzdC4iDAPBgNVHREECDAGhwR/AAABMAUGAytlcANBAEV94bXU
ELMAUs+uhcp1Px4/1oHu//x56qwRWVpoeQ1AxF8hJ7ImZao2NQ1xQkbJ5KSZzPWU
od969YFcalxnkQU=
MIIErDCCApQCCQCP9IKGyBYVUDANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQKDA1E
b2x0SHViLCBJbmMuMB4XDTIyMDcyMTE5NTUyN1oXDTI2MDcyMDE5NTUyN1owGDEW
MBQGA1UECgwNRG9sdEh1YiwgSW5jLjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
AgoCggIBAMRNMquL4n2bAMQmoedUFcT/zI42ThW5o9qxh9U8MhsDqKBet7JL7ruh
3FRj/yaU+3ax2lGgapcTnSJHWHPUX6MzUpqkwrLzKxqMrKQYnU2F96n3GOflllsB
8ISKBy0TIipLWKupndc893qk+j6HUQ9OrtFHwroZ6/Tg32h4LcJvhkwILb8EHZpI
fSSzi637msnXWqln1WYmX+v4ARUVVSwl5kkPGZ4PxMDQRX3ioqy2d27GbHYSo01K
tSScQvnFJeddu09l7hNTSFixRz+XU+F+jYna8xUtqHMawEpfGw8Gsu8sMV9PUfUx
6WrMb1tcyWEDvHwH6RUlhNa085eCRFznHLDZNxrgBwrslII9d9xW/JaRq43d4M0q
e7F+BazB3rImMo2QHFvtvq55N7mvp+LprMjoEneBboiz1I6M0rq7Sn58FxazgQvz
mCZmNYIrznx+S1mlJjxtC7xgDPZn5Z/68TsmT26j5UNLou7i/yb7XFdMVY6kAnoZ
BAf4+rAUEDV85OLz0kWZSJj+DdlPOl/gIhTgrgJev2Cc7THUTnuk+nGETG49DITS
ySWYLsMYFHweSTm3rG6sMyN7mdbRrUSM1bqbARm46mt89+0HGsFX1QLe3v4yda67
ic93J5mkfNPZ96K9Hth3SBbZt43DvB56ZqsFQlzBZeQ3hkk7ooulAgMBAAEwDQYJ
KoZIhvcNAQELBQADggIBAJgopHEYmETWhH06T72EpgLr3xqckCP9QZ6/UBN8eDt0
rRqMCfM5H33qpe2wojjKwFDkR8XpwF/80VflfFBt9hc1c1fuKmyQSES9gUw10uSL
Z54MCPOa+c8+hslkmJR8Na0QyWN5unnozVHf4XIChsgL1/FblXcOgQLPZygzMNM2
IjdT9XpEHiZTDZDp1NmM7rkRkcpgF2J8G2dcRjo/OGpnhH7wHgxm7hS6yWLW8xPP
8M2/8LPYA7H6HMGYeyrYuDPeVzfaHrECTft+4cjHLu/jYnVLukMMSuI9v5FjtxtX
PYtxnLv8hnbParjSnzK8cOlGdfJDRPMUw9/tvZ4bTeyTtJQYgl/jtdaU4mbdWlge
XMzkZGH3kKpsV2rPZXKJuqRq3vzfr51cQhEcnbJ5H8BsDUQADS3ind37guqoKhuZ
6vFUBTKLeYK5VZ4J18ztXhEynAf7kdROKP6XbE53qtH8qQujmOMWliSFQFdidYsj
eItzGQ4M/ZqI84UnPRL3WPdfPkWqa0x1k6PHFRcFJPp8nhl3O5V4ZdyVC3pKhzUJ
Y8sMit5RH1K8ZTYUVtEKwMX9wRMEkbfE4u/P+yItKw7QgYRdKerlDfCGZP8JY9+k
wqYmF56EVGQFaJdJ1ublVEHQkAVHOBowzccwWOV/OPi1sL+cf4RxAaY7gJpuNEIk
-----END CERTIFICATE-----
+50 -1
View File
@@ -1,3 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
+MjyTS/rmN7YbJRkCrgg/4kGlldmYg==
-----END PRIVATE KEY-----
+13 -9
View File
@@ -22,6 +22,7 @@ import (
"gopkg.in/yaml.v2"
"github.com/dolthub/dolt/go/cmd/dolt/commands/engine"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
)
@@ -95,6 +96,8 @@ type ListenerYAMLConfig struct {
TLSCert *string `yaml:"tls_cert"`
// RequireSecureTransport can enable a mode where non-TLS connections are turned away.
RequireSecureTransport *bool `yaml:"require_secure_transport"`
// AllowCleartextPasswords enables use of cleartext passwords.
AllowCleartextPasswords *bool `yaml:"allow_cleartext_passwords"`
}
// PerformanceYAMLConfig contains configuration parameters for performance tweaking
@@ -113,13 +116,6 @@ type UserSessionVars struct {
Vars map[string]string `yaml:"vars"`
}
type JwksYAMLConfig struct {
Name string `yaml:"name"`
LocationUrl string `yaml:"location_url"`
Claims map[string]string `yaml:"claims"`
FieldsToLog []string `yaml:"fields_to_log"`
}
// YAMLConfig is a ServerConfig implementation which is read from a yaml file
type YAMLConfig struct {
LogLevelStr *string `yaml:"log_level"`
@@ -133,7 +129,7 @@ type YAMLConfig struct {
MetricsConfig MetricsYAMLConfig `yaml:"metrics"`
PrivilegeFile *string `yaml:"privilege_file"`
Vars []UserSessionVars `yaml:"user_session_vars"`
Jwks []JwksYAMLConfig `yaml:"jwks"`
Jwks []engine.JwksConfig `yaml:"jwks"`
}
var _ ServerConfig = YAMLConfig{}
@@ -163,6 +159,7 @@ func serverConfigAsYAMLConfig(cfg ServerConfig) YAMLConfig {
nillableStrPtr(cfg.TLSKey()),
nillableStrPtr(cfg.TLSCert()),
nillableBoolPtr(cfg.RequireSecureTransport()),
nillableBoolPtr(cfg.AllowCleartextPasswords()),
},
DatabaseConfig: nil,
}
@@ -348,13 +345,20 @@ func (cfg YAMLConfig) UserVars() []UserSessionVars {
return nil
}
func (cfg YAMLConfig) JwksConfig() []JwksYAMLConfig {
func (cfg YAMLConfig) JwksConfig() []engine.JwksConfig {
if cfg.Jwks != nil {
return cfg.Jwks
}
return nil
}
func (cfg YAMLConfig) AllowCleartextPasswords() bool {
if cfg.ListenerConfig.AllowCleartextPasswords == nil {
return defaultAllowCleartextPasswords
}
return *cfg.ListenerConfig.AllowCleartextPasswords
}
// QueryParallelism returns the parallelism that should be used by the go-mysql-server analyzer
func (cfg YAMLConfig) QueryParallelism() int {
if cfg.PerformanceConfig.QueryParallelism == nil {
@@ -18,9 +18,10 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
"github.com/dolthub/dolt/go/cmd/dolt/commands/engine"
)
func TestUnmarshall(t *testing.T) {
@@ -125,7 +126,7 @@ jwks:
},
},
}
expected.Jwks = []JwksYAMLConfig{
expected.Jwks = []engine.JwksConfig{
{
Name: "jwks_name",
LocationUrl: "https://website.com",
@@ -198,10 +199,12 @@ func TestYAMLConfigDefaults(t *testing.T) {
assert.Equal(t, "", cfg.TLSKey())
assert.Equal(t, "", cfg.TLSCert())
assert.Equal(t, false, cfg.RequireSecureTransport())
assert.Equal(t, false, cfg.AllowCleartextPasswords())
assert.Equal(t, false, cfg.DisableClientMultiStatements())
assert.Equal(t, defaultMetricsHost, cfg.MetricsHost())
assert.Equal(t, defaultMetricsPort, cfg.MetricsPort())
assert.Nil(t, cfg.MetricsConfig.Labels)
assert.Equal(t, defaultAllowCleartextPasswords, cfg.AllowCleartextPasswords())
c, err := LoadTLSConfig(cfg)
assert.NoError(t, err)
@@ -234,7 +237,7 @@ listener:
assert.NoError(t, err)
assert.NotNil(t, c)
assert.Len(t, c.Certificates, 1)
assert.Len(t, c.Certificates[0].Certificate, 2)
assert.Len(t, c.Certificates[0].Certificate, 1)
cfg = YAMLConfig{}
err = yaml.Unmarshal([]byte(`
+6 -1
View File
@@ -52,7 +52,7 @@ require (
google.golang.org/api v0.32.0
google.golang.org/grpc v1.37.0
google.golang.org/protobuf v1.27.1
gopkg.in/square/go-jose.v2 v2.5.1
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/src-d/go-errors.v1 v1.0.0
gopkg.in/yaml.v2 v2.3.0
)
@@ -112,6 +112,7 @@ require (
github.com/pierrec/lz4/v4 v4.1.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/pquerna/cachecontrol v0.1.0
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
@@ -139,4 +140,8 @@ replace (
github.com/oliveagle/jsonpath => github.com/dolthub/jsonpath v0.0.0-20210609232853-d49537a30474
)
// replace github.com/dolthub/vitess => /Users/taylor/go/src/github.com/dolthub/vitess
// replace github.com/dolthub/go-mysql-server => /Users/taylor/go/src/github.com/dolthub/go-mysql-server
go 1.18
+4 -2
View File
@@ -596,6 +596,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
@@ -1199,8 +1201,8 @@ gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mN
gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
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/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc=
gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -331,7 +331,7 @@ func startServer(t *testing.T) (*sqlserver.ServerController, sqlserver.ServerCon
func newConnection(t *testing.T, serverConfig sqlserver.ServerConfig) (*dbr.Connection, *dbr.Session) {
const dbName = "dolt"
conn, err := dbr.Open("mysql", sqlserver.ConnectionString(serverConfig)+dbName, nil)
conn, err := dbr.Open("mysql", sqlserver.ConnectionString(serverConfig, dbName), nil)
require.NoError(t, err)
sess := conn.NewSession(nil)
return conn, sess
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jwtauth
import (
"gopkg.in/square/go-jose.v2/jwt"
)
type Claims struct {
jwt.Claims
OnBehalfOf string `json:"on_behalf_of"`
}
@@ -0,0 +1 @@
Creates files with a jwks and a jwt that can be validating using the jwks. Used in bats.
+127
View File
@@ -0,0 +1,127 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"fmt"
"io/ioutil"
"time"
"github.com/google/uuid"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
var sub = "test_user"
var iss = "dolthub.com"
var aud = "my_resource"
var onBehalfOf = "my_user"
func main() {
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Println(err)
}
pubKey := privKey.Public()
kid, err := uuid.NewRandom()
if err != nil {
fmt.Println(err)
}
err = writeJWKSToFile(pubKey, kid.String())
if err != nil {
fmt.Println(err)
}
jwt, err := generateJWT(privKey, kid.String())
if err != nil {
fmt.Println(err)
}
err = ioutil.WriteFile("token.jwt", []byte(jwt), 0644)
if err != nil {
fmt.Println(err)
}
}
func writeJWKSToFile(pubKey crypto.PublicKey, kid string) error {
jwk := jose.JSONWebKey{
KeyID: kid,
Key: pubKey,
Use: "sig",
Algorithm: "RS256",
}
jwks := jose.JSONWebKeySet{Keys: []jose.JSONWebKey{jwk}}
jwksjson, err := json.Marshal(jwks)
if err != nil {
return err
}
err = ioutil.WriteFile("test_jwks.json", jwksjson, 0644)
if err != nil {
return err
}
return nil
}
func generateJWT(privKey *rsa.PrivateKey, kid string) (string, error) {
id, err := uuid.NewRandom()
if err != nil {
return "", err
}
now := time.Now()
claims := jwt.Claims{
ID: id.String(),
Audience: []string{aud},
Issuer: iss,
Subject: sub,
IssuedAt: jwt.NewNumericDate(now),
Expiry: jwt.NewNumericDate(now.Add(30 * time.Second)),
}
privClaims := struct {
OnBehalfOf string `json:"on_behalf_of"`
}{
onBehalfOf,
}
sig := jose.SigningKey{Algorithm: jose.RS256, Key: privKey}
opts := (&jose.SignerOptions{ExtraHeaders: map[jose.HeaderKey]interface{}{
"kid": kid,
}}).WithType("JWT")
signer, err := jose.NewSigner(sig, opts)
if err != nil {
return "", err
}
jwtBuilder := jwt.Signed(signer)
jwtBuilder = jwtBuilder.Claims(claims)
jwtBuilder = jwtBuilder.Claims(privClaims)
com, err := jwtBuilder.CompactSerialize()
if err != nil {
return "", err
}
return com, nil
}
+102
View File
@@ -0,0 +1,102 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jwtauth
import (
"errors"
"io/ioutil"
"net/http"
"os"
"sync"
"time"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/json"
"github.com/pquerna/cachecontrol"
)
type cachedJWKS struct {
value *jose.JSONWebKeySet
expires time.Time
mutex *sync.Mutex
}
func newCachedJWKS() *cachedJWKS {
return &cachedJWKS{value: nil, expires: time.Now(), mutex: &sync.Mutex{}}
}
type fetchedJWKS struct {
URL string
cache *cachedJWKS
}
func newJWKS(provider JWTProvider) *fetchedJWKS {
return &fetchedJWKS{URL: provider.URL, cache: newCachedJWKS()}
}
func (f *fetchedJWKS) needsRefresh() bool {
return f.cache.value == nil || time.Now().After(f.cache.expires)
}
func (f *fetchedJWKS) GetJWKS() (*jose.JSONWebKeySet, error) {
f.cache.mutex.Lock()
defer f.cache.mutex.Unlock()
if f.needsRefresh() {
tr := &http.Transport{}
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
// Allows use of file:// for jwks location url for tests
tr.RegisterProtocol("file", http.NewFileTransport(http.Dir(pwd)))
client := &http.Client{Transport: tr}
request, err := http.NewRequest("GET", f.URL, nil)
if err != nil {
return nil, err
}
response, err := client.Do(request)
if err != nil {
return nil, err
} else if response.StatusCode/100 != 2 {
return nil, errors.New("FetchedJWKS: Non-2xx status code from JWKS fetch")
} else {
defer response.Body.Close()
contents, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
jwks := jose.JSONWebKeySet{}
err = json.Unmarshal(contents, &jwks)
if err != nil {
return nil, err
}
f.cache.value = &jwks
_, _, err = cachecontrol.CachableResponse(request, response, cachecontrol.Options{})
}
}
return f.cache.value, nil
}
func (f *fetchedJWKS) GetKey(kid string) ([]jose.JSONWebKey, error) {
jwks, err := f.GetJWKS()
if err != nil {
return nil, err
}
return jwks.Key(kid), nil
}
@@ -0,0 +1,22 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jwtauth
type JWTProvider struct {
URL string
Issuer string
Audience string
Subject string
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jwtauth
import (
"errors"
"fmt"
"time"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
type KeyProvider interface {
GetKey(kid string) ([]jose.JSONWebKey, error)
}
var ErrKeyNotFound = errors.New("Key not found")
func ValidateJWT(unparsed string, reqTime time.Time, keyProvider KeyProvider, expectedClaims jwt.Expected) (*Claims, error) {
parsed, err := jwt.ParseSigned(unparsed)
if err != nil {
return nil, err
}
if len(parsed.Headers) != 1 {
return nil, fmt.Errorf("ValidateJWT: Unexpected JWT headers length %v.", len(parsed.Headers))
}
if parsed.Headers[0].Algorithm != "RS512" &&
parsed.Headers[0].Algorithm != "RS256" &&
parsed.Headers[0].Algorithm != "EdDSA" {
return nil, fmt.Errorf("ValidateJWT: Currently only support RS256, RS512 and EdDSA signatures. Unexpected algorithm: %v", parsed.Headers[0].Algorithm)
}
keyID := parsed.Headers[0].KeyID
keys, err := keyProvider.GetKey(keyID)
if err != nil {
return nil, err
}
var claims Claims
claimsError := fmt.Errorf("ValidateJWT: KeyID: %v. Err: %w", keyID, ErrKeyNotFound)
for _, key := range keys {
claimsError = parsed.Claims(key.Key, &claims)
if claimsError == nil {
break
}
}
if claimsError != nil {
return nil, claimsError
}
if err := claims.Validate(expectedClaims.WithTime(reqTime)); err != nil {
return nil, err
}
return &claims, nil
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright 2022 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package jwtauth
import (
"time"
"gopkg.in/square/go-jose.v2/jwt"
)
type JWTValidator interface {
ValidateJWT(unparsed string, reqTime time.Time) (*Claims, error)
}
type fetchingJWTValidator struct {
jwks *fetchedJWKS
expected jwt.Expected
}
func NewJWTValidator(provider JWTProvider) JWTValidator {
expected := jwt.Expected{Issuer: provider.Issuer, Audience: jwt.Audience{provider.Audience}}
return &fetchingJWTValidator{jwks: newJWKS(provider), expected: expected}
}
func (v *fetchingJWTValidator) ValidateJWT(unparsed string, reqTime time.Time) (*Claims, error) {
return ValidateJWT(unparsed, reqTime, v.jwks, v.expected)
}
@@ -198,7 +198,7 @@ func executeServerQueries(ctx context.Context, b *testing.B, dEnv *env.DoltEnv,
}
func executeQuery(cfg srv.ServerConfig, q query) error {
cs := srv.ConnectionString(cfg) + database
cs := srv.ConnectionString(cfg, database)
conn, err := dbr.Open("mysql", cs, nil)
if err != nil {
return err
+1 -1
View File
@@ -210,7 +210,7 @@ func executeServerQueries(ctx context.Context, b *testing.B, dEnv *env.DoltEnv,
}
func executeQuery(cfg srv.ServerConfig, q query) error {
cs := srv.ConnectionString(cfg) + database
cs := srv.ConnectionString(cfg, database)
conn, err := dbr.Open("mysql", cs, nil)
if err != nil {
return err
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bats
load $BATS_TEST_DIRNAME/helper/common.bash
load $BATS_TEST_DIRNAME/helper/query-server-common.bash
make_repo() {
mkdir "$1"
cd "$1"
dolt init
cd ..
}
setup() {
skiponwindows "Missing dependencies"
setup_no_dolt_init
make_repo repo1
(cd "$BATS_TEST_DIRNAME"/../../go/libraries/utils/jwtauth/gen_keys && go run .)
}
teardown() {
stop_sql_server
teardown_common
rm -rf "$BATS_TEST_DIRNAME/../../go/libraries/utils/jwtauth/gen_keys/token.jwt"
rm -rf "$BATS_TEST_DIRNAME/../../go/libraries/utils/jwtauth/gen_keys/test_jwks.json"
}
@test "sql-jwt-auth: jwt auth from config" {
cd repo1
cp "$BATS_TEST_DIRNAME"/../../go/cmd/dolt/commands/sqlserver/testdata/chain_key.pem .
cp "$BATS_TEST_DIRNAME"/../../go/cmd/dolt/commands/sqlserver/testdata/chain_cert.pem .
cp "$BATS_TEST_DIRNAME"/../../go/libraries/utils/jwtauth/gen_keys/test_jwks.json .
let PORT="$$ % (65536-1024) + 1024"
TOKEN="`cat $BATS_TEST_DIRNAME/../../go/libraries/utils/jwtauth/gen_keys/token.jwt`"
cat >config.yml <<EOF
log_level: debug
user:
name: dolt
listener:
host: "0.0.0.0"
port: $PORT
tls_cert: chain_cert.pem
tls_key: chain_key.pem
require_secure_transport: true
privilege_file: privs.json
jwks:
- name: jwksname
location_url: file:///test_jwks.json
claims:
alg: RS256
aud: my_resource
sub: test_jwt_user
iss: dolthub.com
fields_to_log: [on_behalf_of,id]
EOF
dolt sql --privilege-file=privs.json -q "CREATE USER dolt@'127.0.0.1'"
dolt sql --privilege-file=privs.json -q "GRANT ALL ON *.* TO dolt@'127.0.0.1' WITH GRANT OPTION"
dolt sql --privilege-file=privs.json -q "CREATE USER test_jwt_user@'127.0.0.1' IDENTIFIED WITH authentication_dolt_jwt AS 'jwks=jwksname,sub=test_jwt_user,iss=dolthub.com,aud=my_resource'"
dolt sql --privilege-file=privs.json -q "GRANT ALL ON *.* TO test_jwt_user@'127.0.0.1' WITH GRANT OPTION"
dolt sql-server --config ./config.yml &
SERVER_PID=$!
# We do things manually here because we need TLS support.
python3 -c '
import mysql.connector
import sys
import time
import os
i=0
os.environ["LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN"] = "1"
args = sys.argv[sys.argv.index("--") + 1:]
password = args[0]
while True:
try:
with mysql.connector.connect(
host="127.0.0.1",
user="test_jwt_user",
password=password,
port='"$PORT"',
database="repo1",
connection_timeout=1
) as c:
cursor = c.cursor()
cursor.execute("show tables")
for (t) in cursor:
print(t)
sys.exit(0)
except mysql.connector.Error as err:
if err.errno != 2003:
raise err
else:
i += 1
time.sleep(1)
if i == 10:
raise err
' -- "$TOKEN"
}