-
Notifications
You must be signed in to change notification settings - Fork 109
auth: add authentication and authorization interface #496
Conversation
On handler.NewConnection the user is still not authenticated so the connection lack some information needed to create the session. Now the session is created the first time a context is needed. Signed-off-by: Javi Fontan <jfontan@gmail.com>
The interface has two methods: * Mysql: returns a vitess server auth * Allow: checks if a user has permissions needed Currently two auth methods are implemented: * None: allows all logins and permissions * Native: uses mysql_native_password method to authenticate. Native method can read users from a json file. This contains the user credentials and permissions for the user. The credentials can be in plain or in mysql native format. It can also contain a list of all permissions granted to the user. If not specified it uses default permissions that for now is "read". [ { "name": "root", "password": "*9E128DA0C64A6FCCCDCFBDD0FC0A2C967C6DB36F", "permissions": ["read", "write"] }, { "name": "javi", "password": "Passw0rd!" } ] To enforce permissions a new validation rule can be added with the builder: ab := analyzer.NewBuilder(catalog).WithAuth(userAuth) Signed-off-by: Javi Fontan <jfontan@gmail.com>
// for users. | ||
type Auth interface { | ||
Mysql() mysql.AuthServer | ||
Allowed(user string, permission Permission) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe return (bool, error)
?
This would allow to differentiate from not having permissions and failing to check permissions. The former would probably produce a warning audit log (when we have audit logs), the second would probably also produce a regular error log.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(bool, error)
sounds to me like a fuzzy logic:
(true, nil)
(false, nil)
(false, err)
and hopefully it's not possible to have:
(true, err)
It's like returning *bool
it can give you (nil, *true, *false)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kuba-- It's a common pattern in Go, also in our own codebase. (true, err)
is usually not relevant since the value would not be even checked if err != nil
.
But alternatively, you can keep just err
as return value and use a special error kind ErrNotAuthorized
to differentiate from other errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@smola - totally understand, just as I mentioned, personally I don't like this pattern, because have a feeling that it's a boolean logic with extra dimension.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm moving to use go-errors
and returning ErrNotAuthorized
so is easier to tell apart.
server/context.go
Outdated
func (s *SessionManager) NewSession(conn *mysql.Conn) { | ||
s.mu.Lock() | ||
s.sessions[conn.ConnectionID] = s.builder(conn, s.addr) | ||
s.sessions[conn.ConnectionID] = s.newSession(conn) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't understand this change. Why do we need s.builder
call in another function? It's just one line
s.mu.Unlock() | ||
} | ||
|
||
// NewContext creates a new context for the session at the given conn. | ||
func (s *SessionManager) NewContext(conn *mysql.Conn) *sql.Context { | ||
s.mu.Lock() | ||
sess := s.sessions[conn.ConnectionID] | ||
sess, ok := s.sessions[conn.ConnectionID] | ||
if !ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case can't happen. If it does not exist in s.sessions
, the connection is not opened.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not mentioned that I also had to change when sessions are created. Before the session was created in NewConnection
. This is called before the user is authenticated so the sessions did not contain user information (mysql.Conn
had User = ""
). To overcome this the sessions are created the first time NewContext
is called, when he user is already authenticated and we can add the user name to the session.
Any lack of permission error should have ErrNotAuthorized kind. Signed-off-by: Javi Fontan <jfontan@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
4bf1c04
to
277877e
Compare
auth/auth.go
Outdated
import ( | ||
"strings" | ||
|
||
errors "gopkg.in/src-d/go-errors.v0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use v1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
// for users. | ||
type Auth interface { | ||
Mysql() mysql.AuthServer | ||
Allowed(user string, permission Permission) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, add godoc to the interface. Specially a mention to errors returned fo Allowed
. My guess is that the one that is not expected to be implementation-depdendent is ErrNoAuthorized
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
func (s *Native) Allowed(name string, permission Permission) error { | ||
u, ok := s.users[name] | ||
if !ok { | ||
return ErrNotAuthorized.Wrap(ErrNoPermission.New(permission)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this error will print permissions correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Permission
has a String
method so it is correctly printed:
not authorized: user does not have permission: read, write
const CheckAuthorizationRule = "check_authorization" | ||
|
||
// CheckAuthorization creates an authorization check with the given Auth. | ||
func CheckAuthorization(au auth.Auth) RuleFunc { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you delete the old rule that we used to make possible read-only queries?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add the possibility to specify the permissions when using NewNativeSingle
so it can be used to disable writing instead of read only rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Signed-off-by: Javi Fontan <jfontan@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
The interface has two methods:
Currently two auth methods are implemented:
Native method can read users from a json file. This contains the user
credentials and permissions for the user. The credentials can be in
plain or in mysql native format. It can also contain a list of all
permissions granted to the user. If not specified it uses default
permissions that for now is "read".
To enforce permissions a new validation rule can be added with the
builder:
Related to #469, needs audit logs