Skip to content
This repository has been archived by the owner on Jan 28, 2021. It is now read-only.

auth: add authentication and authorization interface #496

Merged
merged 12 commits into from
Oct 25, 2018

Conversation

jfontan
Copy link
Contributor

@jfontan jfontan commented Oct 23, 2018

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)

Related to #469, needs audit logs

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>
@jfontan jfontan requested review from smola and a team October 23, 2018 17:45
@kuba-- kuba-- added the wip work in progress label Oct 23, 2018
// for users.
type Auth interface {
Mysql() mysql.AuthServer
Allowed(user string, permission Permission) error
Copy link
Collaborator

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.

Copy link
Contributor

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)

Copy link
Collaborator

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

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)
Copy link
Contributor

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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>
@jfontan jfontan changed the title [WIP] auth: add authentication and authorization interface auth: add authentication and authorization interface Oct 24, 2018
@jfontan jfontan requested a review from a team October 24, 2018 16:44
Signed-off-by: Javi Fontan <jfontan@gmail.com>
Signed-off-by: Javi Fontan <jfontan@gmail.com>
@jfontan jfontan removed the wip work in progress label Oct 24, 2018
auth/auth.go Outdated
import (
"strings"

errors "gopkg.in/src-d/go-errors.v0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use v1.

Copy link
Contributor Author

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
Copy link
Collaborator

@smola smola Oct 25, 2018

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.

Copy link
Contributor Author

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))
Copy link
Contributor

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?

Copy link
Contributor Author

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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>
@ajnavarro ajnavarro merged commit 0c31e30 into src-d:master Oct 25, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants