open-consul/agent/grpc-external/services/acl/login.go

96 lines
3.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package acl
import (
"context"
"errors"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/consul/auth"
external "github.com/hashicorp/consul/agent/grpc-external"
"github.com/hashicorp/consul/proto-public/pbacl"
)
// Login exchanges the presented bearer token for a Consul ACL token using a
// configured auth method.
func (s *Server) Login(ctx context.Context, req *pbacl.LoginRequest) (*pbacl.LoginResponse, error) {
logger := s.Logger.Named("login").With("request_id", external.TraceID())
logger.Trace("request received")
if err := s.requireACLsEnabled(logger); err != nil {
return nil, err
}
entMeta := acl.NewEnterpriseMetaWithPartition(req.Partition, req.Namespace)
if err := s.ValidateEnterpriseRequest(&entMeta, true); err != nil {
logger.Error("error during enterprise request validation", "error", err.Error())
return nil, status.Errorf(codes.Internal, err.Error())
}
// Forward request to leader in the correct datacenter.
var rsp *pbacl.LoginResponse
handled, err := s.forwardWriteDC(req.Datacenter, func(conn *grpc.ClientConn) error {
var err error
rsp, err = pbacl.NewACLServiceClient(conn).Login(ctx, req)
return err
}, logger)
if handled || err != nil {
return rsp, err
}
// This is also validated by the TokenWriter, but doing it early saves any
// work done by the validator (e.g. roundtrip to the Kubernetes API server).
if err := s.requireLocalTokens(logger); err != nil {
return nil, err
}
authMethod, validator, err := s.LoadAuthMethod(req.AuthMethod, &entMeta)
switch {
case errors.Is(err, acl.ErrNotFound):
return nil, status.Errorf(codes.InvalidArgument, "auth method %q not found", req.AuthMethod)
case err != nil:
logger.Error("failed to load auth method", "error", err.Error())
return nil, status.Error(codes.Internal, "failed to load auth method")
}
verifiedIdentity, err := validator.ValidateLogin(ctx, req.BearerToken)
if err != nil {
// TODO(agentless): errors returned from validators aren't standardized so
// it's hard to tell whether validation failed because of an invalid bearer
// token or something internal/transient. We currently return Unauthenticated
// for all errors because it's the most likely, but we should make validators
// return a typed or sentinel error instead.
logger.Error("failed to validate login", "error", err.Error())
return nil, status.Error(codes.Unauthenticated, err.Error())
}
description, err := auth.BuildTokenDescription("token created via login", req.Meta)
if err != nil {
logger.Error("failed to build token description", "error", err.Error())
return nil, status.Error(codes.Internal, err.Error())
}
token, err := s.NewLogin().TokenForVerifiedIdentity(verifiedIdentity, authMethod, description)
switch {
case acl.IsErrPermissionDenied(err):
return nil, status.Error(codes.PermissionDenied, err.Error())
case err != nil:
logger.Error("failed to create token", "error", err.Error())
return nil, status.Error(codes.Internal, "failed to create token")
}
return &pbacl.LoginResponse{
Token: &pbacl.LoginToken{
AccessorId: token.AccessorID,
SecretId: token.SecretID,
},
}, nil
}