diff --git a/changelog/unreleased/machine-auth.md b/changelog/unreleased/machine-auth.md new file mode 100644 index 0000000000..75575149c7 --- /dev/null +++ b/changelog/unreleased/machine-auth.md @@ -0,0 +1,7 @@ +Enhancement: Machine auth provider + +Adds a new authentication method used to impersonate users, +using a shared secret, called api-key. + + +https://github.com/cs3org/reva/pull/2028 diff --git a/cmd/reva/login.go b/cmd/reva/login.go index afde835094..20298d019a 100644 --- a/cmd/reva/login.go +++ b/cmd/reva/login.go @@ -22,6 +22,7 @@ import ( "bufio" "context" "encoding/gob" + "errors" "fmt" "io" "os" @@ -29,7 +30,6 @@ import ( registry "github.com/cs3org/go-cs3apis/cs3/auth/registry/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - "github.com/pkg/errors" ) var loginCommand = func() *command { @@ -37,9 +37,13 @@ var loginCommand = func() *command { cmd.Description = func() string { return "login into the reva server" } cmd.Usage = func() string { return "Usage: login " } listFlag := cmd.Bool("list", false, "list available login methods") + usernameOpt := cmd.String("username", "", "provide the username (only with machine auth)") + apiKeyOpt := cmd.String("api-key", "", "secret for the machine auth") cmd.ResetFlags = func() { *listFlag = false + *usernameOpt = "" + *apiKeyOpt = "" } cmd.Action = func(w ...io.Writer) error { @@ -81,17 +85,29 @@ var loginCommand = func() *command { } authType := cmd.Args()[0] - reader := bufio.NewReader(os.Stdin) - fmt.Print("username: ") - username, err := read(reader) - if err != nil { - return err - } + var username, password string + var err error + + // if the user select the machine authentication, the only way + // to provide the username and the password (api-key) is through + // the flags -username and -api-key respectively + if authType == "machine" { + username = *usernameOpt + password = *apiKeyOpt + } else { + // for the other methods, take the username and pw from the stdin + reader := bufio.NewReader(os.Stdin) + fmt.Print("username: ") + username, err = read(reader) + if err != nil { + return err + } - fmt.Print("password: ") - password, err := readPassword(0) - if err != nil { - return err + fmt.Print("password: ") + password, err = readPassword(0) + if err != nil { + return err + } } client, err := getClient() diff --git a/pkg/auth/manager/loader/loader.go b/pkg/auth/manager/loader/loader.go index adbc173848..1c6c7eff15 100644 --- a/pkg/auth/manager/loader/loader.go +++ b/pkg/auth/manager/loader/loader.go @@ -25,6 +25,7 @@ import ( _ "github.com/cs3org/reva/pkg/auth/manager/impersonator" _ "github.com/cs3org/reva/pkg/auth/manager/json" _ "github.com/cs3org/reva/pkg/auth/manager/ldap" + _ "github.com/cs3org/reva/pkg/auth/manager/machine" _ "github.com/cs3org/reva/pkg/auth/manager/oidc" _ "github.com/cs3org/reva/pkg/auth/manager/publicshares" // Add your own here diff --git a/pkg/auth/manager/machine/machine.go b/pkg/auth/manager/machine/machine.go new file mode 100644 index 0000000000..a014987a13 --- /dev/null +++ b/pkg/auth/manager/machine/machine.go @@ -0,0 +1,100 @@ +// Copyright 2018-2021 CERN +// +// 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. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package machine + +import ( + "context" + + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/pkg/auth" + "github.com/cs3org/reva/pkg/auth/manager/registry" + "github.com/cs3org/reva/pkg/auth/scope" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +// 'machine' is an authentication method used to impersonate users. +// To impersonate the given user it's only needed an api-key, saved +// in a config file. + +type manager struct { + APIKey string `mapstructure:"api_key"` + GatewayAddr string `mapstructure:"gateway_addr"` +} + +func init() { + registry.Register("machine", New) +} + +// Configure parses the map conf +func (m *manager) Configure(conf map[string]interface{}) error { + err := mapstructure.Decode(conf, m) + if err != nil { + return errors.Wrap(err, "error decoding conf") + } + return nil +} + +// New creates a new manager for the 'machine' authentication +func New(conf map[string]interface{}) (auth.Manager, error) { + m := &manager{} + err := m.Configure(conf) + if err != nil { + return nil, err + } + return m, nil +} + +// Authenticate impersonate an user if the provided secret is equal to the api-key +func (m *manager) Authenticate(ctx context.Context, username, secret string) (*user.User, map[string]*authpb.Scope, error) { + if m.APIKey != secret { + return nil, nil, errtypes.InvalidCredentials("") + } + + gtw, err := pool.GetGatewayServiceClient(m.GatewayAddr) + if err != nil { + return nil, nil, err + } + + userResponse, err := gtw.GetUserByClaim(ctx, &user.GetUserByClaimRequest{ + Claim: "username", + Value: username, + }) + + switch { + case err != nil: + return nil, nil, err + case userResponse.Status.Code == rpc.Code_CODE_NOT_FOUND: + return nil, nil, errtypes.NotFound(userResponse.Status.Message) + case userResponse.Status.Code != rpc.Code_CODE_OK: + return nil, nil, errtypes.InternalError(userResponse.Status.Message) + } + + scope, err := scope.AddOwnerScope(nil) + if err != nil { + return nil, nil, err + } + + return userResponse.GetUser(), scope, nil + +}