github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/auth/model/model.go (about)

     1  package model
     2  
     3  import (
     4  	"database/sql/driver"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/go-openapi/swag"
    12  	"github.com/google/uuid"
    13  	"github.com/treeverse/lakefs/pkg/auth/crypt"
    14  	"github.com/treeverse/lakefs/pkg/kv"
    15  	"golang.org/x/crypto/bcrypt"
    16  	"google.golang.org/protobuf/proto"
    17  	"google.golang.org/protobuf/types/known/timestamppb"
    18  )
    19  
    20  const (
    21  	StatementEffectAllow   = "allow"
    22  	StatementEffectDeny    = "deny"
    23  	PartitionKey           = "auth"
    24  	groupsPrefix           = "groups"
    25  	groupsUsersPrefix      = "gUsers"
    26  	groupsPoliciesPrefix   = "gPolicies"
    27  	usersPrefix            = "users"
    28  	policiesPrefix         = "policies"
    29  	usersPoliciesPrefix    = "uPolicies"
    30  	usersCredentialsPrefix = "uCredentials" // #nosec G101 -- False positive: this is only a kv key prefix
    31  	credentialsPrefix      = "credentials"
    32  	expiredTokensPrefix    = "expiredTokens"
    33  	metadataPrefix         = "installation_metadata"
    34  )
    35  
    36  //nolint:gochecknoinits
    37  func init() {
    38  	kv.MustRegisterType("auth", "users", (&UserData{}).ProtoReflect().Type())
    39  	kv.MustRegisterType("auth", "policies", (&PolicyData{}).ProtoReflect().Type())
    40  	kv.MustRegisterType("auth", "groups", (&GroupData{}).ProtoReflect().Type())
    41  	kv.MustRegisterType("auth", kv.FormatPath("uCredentials", "*", "credentials"), (&CredentialData{}).ProtoReflect().Type())
    42  	kv.MustRegisterType("auth", kv.FormatPath("gUsers", "*", "users"), (&kv.SecondaryIndex{}).ProtoReflect().Type())
    43  	kv.MustRegisterType("auth", kv.FormatPath("gPolicies", "*", "policies"), (&kv.SecondaryIndex{}).ProtoReflect().Type())
    44  	kv.MustRegisterType("auth", kv.FormatPath("uPolicies", "*", "policies"), (&kv.SecondaryIndex{}).ProtoReflect().Type())
    45  	kv.MustRegisterType("auth", "expiredTokens", (&TokenData{}).ProtoReflect().Type())
    46  	kv.MustRegisterType("auth", "installation_metadata", nil)
    47  }
    48  
    49  func UserPath(userName string) []byte {
    50  	return []byte(kv.FormatPath(usersPrefix, userName))
    51  }
    52  
    53  func PolicyPath(displayName string) []byte {
    54  	return []byte(kv.FormatPath(policiesPrefix, displayName))
    55  }
    56  
    57  func GroupPath(displayName string) []byte {
    58  	return []byte(kv.FormatPath(groupsPrefix, displayName))
    59  }
    60  
    61  func CredentialPath(userName string, accessKeyID string) []byte {
    62  	return []byte(kv.FormatPath(usersCredentialsPrefix, userName, credentialsPrefix, accessKeyID))
    63  }
    64  
    65  func GroupUserPath(groupDisplayName string, userName string) []byte {
    66  	return []byte(kv.FormatPath(groupsUsersPrefix, groupDisplayName, usersPrefix, userName))
    67  }
    68  
    69  func UserPolicyPath(userName string, policyDisplayName string) []byte {
    70  	return []byte(kv.FormatPath(usersPoliciesPrefix, userName, policiesPrefix, policyDisplayName))
    71  }
    72  
    73  func GroupPolicyPath(groupDisplayName string, policyDisplayName string) []byte {
    74  	return []byte(kv.FormatPath(groupsPoliciesPrefix, groupDisplayName, policiesPrefix, policyDisplayName))
    75  }
    76  
    77  func ExpiredTokenPath(tokenID string) []byte {
    78  	return []byte(kv.FormatPath(expiredTokensPrefix, tokenID))
    79  }
    80  
    81  func ExpiredTokensPath() []byte {
    82  	return ExpiredTokenPath("")
    83  }
    84  
    85  func MetadataKeyPath(key string) string {
    86  	return kv.FormatPath(metadataPrefix, key)
    87  }
    88  
    89  var ErrInvalidStatementSrcFormat = errors.New("invalid statements src format")
    90  
    91  type PaginationParams struct {
    92  	Prefix string
    93  	After  string
    94  	Amount int
    95  }
    96  
    97  // Paginator describes the parameters of a slice of data from a database.
    98  type Paginator struct {
    99  	Amount        int
   100  	NextPageToken string
   101  }
   102  
   103  // SuperuserConfiguration requests a particular configuration for a superuser.
   104  type SuperuserConfiguration struct {
   105  	User
   106  	AccessKeyID     string
   107  	SecretAccessKey string
   108  }
   109  
   110  type User struct {
   111  	CreatedAt time.Time `db:"created_at"`
   112  	// Username is a unique identifier for the user. In password-based authentication, it is the email.
   113  	Username string `db:"display_name" json:"display_name"`
   114  	// FriendlyName, if set, is a shorter name for the user than
   115  	// Username.  Unlike Username it does not identify the user (it
   116  	// might not be unique); use it in the user's GUI rather than in
   117  	// backend code.
   118  	FriendlyName      *string `db:"friendly_name" json:"friendly_name"`
   119  	Email             *string `db:"email" json:"email"`
   120  	EncryptedPassword []byte  `db:"encrypted_password" json:"encrypted_password"`
   121  	Source            string  `db:"source" json:"source"`
   122  	ExternalID        *string `db:"external_id" json:"external_id"`
   123  }
   124  
   125  func (u *User) Committer() string {
   126  	if u.Email != nil && *u.Email != "" {
   127  		return *u.Email
   128  	}
   129  	return u.Username
   130  }
   131  
   132  type DBUser struct {
   133  	ID int64 `db:"id"`
   134  	User
   135  }
   136  
   137  func ConvertDBID(id int64) string {
   138  	const base = 10
   139  	return strconv.FormatInt(id, base)
   140  }
   141  
   142  type Group struct {
   143  	ID          string    `db:"id"`
   144  	CreatedAt   time.Time `db:"created_at"`
   145  	DisplayName string    `db:"display_name" json:"display_name"`
   146  }
   147  
   148  type DBGroup struct {
   149  	ID int `db:"id"`
   150  	Group
   151  }
   152  
   153  type ACLPermission string
   154  
   155  type ACL struct {
   156  	Permission ACLPermission `json:"permission"`
   157  }
   158  
   159  type Policy struct {
   160  	CreatedAt   time.Time  `db:"created_at"`
   161  	DisplayName string     `db:"display_name" json:"display_name"`
   162  	Statement   Statements `db:"statement"`
   163  	ACL         ACL        `db:"acl" json:"acl,omitempty"`
   164  }
   165  
   166  type DBPolicy struct {
   167  	ID int `db:"id"`
   168  	Policy
   169  }
   170  
   171  type Statement struct {
   172  	Effect   string   `json:"Effect"`
   173  	Action   []string `json:"Action"`
   174  	Resource string   `json:"Resource"`
   175  }
   176  
   177  type Statements []Statement
   178  
   179  type BaseCredential struct {
   180  	AccessKeyID                   string    `db:"access_key_id"`
   181  	SecretAccessKey               string    `db:"-" json:"-"`
   182  	SecretAccessKeyEncryptedBytes []byte    `db:"secret_access_key" json:"-"`
   183  	IssuedDate                    time.Time `db:"issued_date"`
   184  }
   185  
   186  type Credential struct {
   187  	Username string
   188  	BaseCredential
   189  }
   190  
   191  type DBCredential struct {
   192  	UserID int64 `db:"user_id"`
   193  	BaseCredential
   194  }
   195  
   196  // CredentialKeys - For JSON serialization:
   197  type CredentialKeys struct {
   198  	AccessKeyID     string `json:"access_key_id"`
   199  	SecretAccessKey string `json:"secret_access_key"`
   200  }
   201  
   202  // ExternalPrincipal represents an attachment of a user to an external identity such as an IAM Role ARN
   203  type ExternalPrincipal struct {
   204  	// External Principal ID (i.e ARN)
   205  	ID string `json:"id"`
   206  	// The attached lakeFS user ID
   207  	UserID string `json:"user_id"`
   208  }
   209  
   210  func (u *User) UpdatePassword(password string) error {
   211  	pw, err := HashPassword(password)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	u.EncryptedPassword = pw
   216  	return nil
   217  }
   218  
   219  // HashPassword generates a hashed password from a plaintext string
   220  func HashPassword(password string) ([]byte, error) {
   221  	return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
   222  }
   223  
   224  // Authenticate a user from a password Returns nil on success, or an error on failure.
   225  func (u *User) Authenticate(password string) error {
   226  	return bcrypt.CompareHashAndPassword(u.EncryptedPassword, []byte(password))
   227  }
   228  
   229  func (s Statements) Value() (driver.Value, error) {
   230  	if s == nil {
   231  		return json.Marshal([]struct{}{})
   232  	}
   233  	return json.Marshal(s)
   234  }
   235  
   236  func (s *Statements) Scan(src interface{}) error {
   237  	if src == nil {
   238  		return nil
   239  	}
   240  	data, ok := src.([]byte)
   241  	if !ok {
   242  		return ErrInvalidStatementSrcFormat
   243  	}
   244  	return json.Unmarshal(data, s)
   245  }
   246  
   247  func UserFromProto(pb *UserData) *User {
   248  	return &User{
   249  		CreatedAt:         pb.CreatedAt.AsTime(),
   250  		Username:          pb.Username,
   251  		FriendlyName:      &pb.FriendlyName,
   252  		Email:             &pb.Email,
   253  		EncryptedPassword: pb.EncryptedPassword,
   254  		Source:            pb.Source,
   255  		ExternalID:        &pb.ExternalId,
   256  	}
   257  }
   258  
   259  func ProtoFromUser(u *User) *UserData {
   260  	return &UserData{
   261  		CreatedAt:         timestamppb.New(u.CreatedAt),
   262  		Username:          u.Username,
   263  		FriendlyName:      swag.StringValue(u.FriendlyName),
   264  		Email:             swag.StringValue(u.Email),
   265  		EncryptedPassword: u.EncryptedPassword,
   266  		Source:            u.Source,
   267  		ExternalId:        swag.StringValue(u.ExternalID),
   268  	}
   269  }
   270  
   271  func GroupFromProto(pb *GroupData) *Group {
   272  	return &Group{
   273  		CreatedAt:   pb.CreatedAt.AsTime(),
   274  		DisplayName: pb.DisplayName,
   275  		ID:          pb.DisplayName,
   276  	}
   277  }
   278  
   279  func ProtoFromGroup(g *Group) *GroupData {
   280  	return &GroupData{
   281  		CreatedAt:   timestamppb.New(g.CreatedAt),
   282  		DisplayName: g.DisplayName,
   283  	}
   284  }
   285  
   286  func PolicyFromProto(pb *PolicyData) *Policy {
   287  	policy := &Policy{
   288  		CreatedAt:   pb.CreatedAt.AsTime(),
   289  		DisplayName: pb.DisplayName,
   290  		Statement:   *statementsFromProto(pb.Statements),
   291  	}
   292  	if pb.Acl != nil {
   293  		policy.ACL = ACL{
   294  			Permission: ACLPermission(pb.Acl.Permission),
   295  		}
   296  	}
   297  	return policy
   298  }
   299  
   300  func ProtoFromPolicy(p *Policy) *PolicyData {
   301  	return &PolicyData{
   302  		CreatedAt:   timestamppb.New(p.CreatedAt),
   303  		DisplayName: p.DisplayName,
   304  		Statements:  protoFromStatements(&p.Statement),
   305  		Acl: &ACLData{
   306  			Permission: string(p.ACL.Permission),
   307  		},
   308  	}
   309  }
   310  
   311  func CredentialFromProto(s crypt.SecretStore, pb *CredentialData) (*Credential, error) {
   312  	secret, err := DecryptSecret(s, pb.SecretAccessKeyEncryptedBytes)
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  	return &Credential{
   317  		Username: string(pb.UserId),
   318  		BaseCredential: BaseCredential{
   319  			AccessKeyID:                   pb.AccessKeyId,
   320  			SecretAccessKey:               secret,
   321  			SecretAccessKeyEncryptedBytes: pb.SecretAccessKeyEncryptedBytes,
   322  			IssuedDate:                    pb.IssuedDate.AsTime(),
   323  		},
   324  	}, nil
   325  }
   326  
   327  func ProtoFromCredential(c *Credential) *CredentialData {
   328  	return &CredentialData{
   329  		AccessKeyId:                   c.AccessKeyID,
   330  		SecretAccessKeyEncryptedBytes: c.SecretAccessKeyEncryptedBytes,
   331  		IssuedDate:                    timestamppb.New(c.IssuedDate),
   332  		UserId:                        []byte(c.Username),
   333  	}
   334  }
   335  
   336  func statementFromProto(pb *StatementData) *Statement {
   337  	return &Statement{
   338  		Effect:   pb.Effect,
   339  		Action:   pb.Action,
   340  		Resource: pb.Resource,
   341  	}
   342  }
   343  
   344  func protoFromStatement(s *Statement) *StatementData {
   345  	return &StatementData{
   346  		Effect:   s.Effect,
   347  		Action:   s.Action,
   348  		Resource: s.Resource,
   349  	}
   350  }
   351  
   352  func statementsFromProto(pb []*StatementData) *Statements {
   353  	statements := make(Statements, len(pb))
   354  	for i := range pb {
   355  		statements[i] = *statementFromProto(pb[i])
   356  	}
   357  	return &statements
   358  }
   359  
   360  func protoFromStatements(s *Statements) []*StatementData {
   361  	statements := make([]*StatementData, len(*s))
   362  	x := *s
   363  	for i := range *s {
   364  		statements[i] = protoFromStatement(&x[i])
   365  	}
   366  	return statements
   367  }
   368  
   369  func ConvertUsersDataList(users []proto.Message) []*User {
   370  	kvUsers := make([]*User, 0, len(users))
   371  	for _, u := range users {
   372  		a := u.(*UserData)
   373  		kvUsers = append(kvUsers, UserFromProto(a))
   374  	}
   375  	return kvUsers
   376  }
   377  
   378  func ConvertGroupDataList(group []proto.Message) []*Group {
   379  	res := make([]*Group, 0, len(group))
   380  	for _, g := range group {
   381  		a := g.(*GroupData)
   382  		res = append(res, GroupFromProto(a))
   383  	}
   384  	return res
   385  }
   386  
   387  func ConvertPolicyDataList(policies []proto.Message) []*Policy {
   388  	res := make([]*Policy, 0, len(policies))
   389  	for _, p := range policies {
   390  		a := p.(*PolicyData)
   391  		res = append(res, PolicyFromProto(a))
   392  	}
   393  	return res
   394  }
   395  
   396  func ConvertCredDataList(s crypt.SecretStore, creds []proto.Message) ([]*Credential, error) {
   397  	res := make([]*Credential, 0, len(creds))
   398  	for _, c := range creds {
   399  		credentialData := c.(*CredentialData)
   400  		m, err := CredentialFromProto(s, credentialData)
   401  		if err != nil {
   402  			return nil, fmt.Errorf("credentials for %s: %w", credentialData.AccessKeyId, err)
   403  		}
   404  		m.SecretAccessKey = ""
   405  		res = append(res, m)
   406  	}
   407  	return res, nil
   408  }
   409  
   410  func DecryptSecret(s crypt.SecretStore, value []byte) (string, error) {
   411  	decrypted, err := s.Decrypt(value)
   412  	if err != nil {
   413  		return "", err
   414  	}
   415  	return string(decrypted), nil
   416  }
   417  
   418  func EncryptSecret(s crypt.SecretStore, secretAccessKey string) ([]byte, error) {
   419  	encrypted, err := s.Encrypt([]byte(secretAccessKey))
   420  	if err != nil {
   421  		return nil, err
   422  	}
   423  	return encrypted, nil
   424  }
   425  
   426  func CreateID() string {
   427  	return uuid.New().String()
   428  }