github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/ctl/auth/simple.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package auth
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"net/http"
    27  )
    28  
    29  // SimpleAuthConfig holds this configuration necessary for a simple auth implementation.
    30  type SimpleAuthConfig struct {
    31  	Authentication authenticationConfig `yaml:"authentication"`
    32  	Authorization  authorizationConfig  `yaml:"authorization"`
    33  }
    34  
    35  // authenticationConfig holds this configuration necessary for a simple authentication implementation.
    36  type authenticationConfig struct {
    37  	// This is an HTTP header that identifies the user performing the operation.
    38  	UserIDHeader string `yaml:"userIDHeader" validate:"nonzero"`
    39  	// This is an HTTP header that identifies the user originating the operation.
    40  	OriginatorIDHeader string `yaml:"originatorIDHeader"`
    41  }
    42  
    43  // authorizationConfig holds this configuration necessary for a simple authorization implementation.
    44  // nolint: maligned
    45  type authorizationConfig struct {
    46  	// This indicates whether reads should use a read whitelist.
    47  	ReadWhitelistEnabled bool `yaml:"readWhitelistEnabled,omitempty"`
    48  	// This is a list of users that are allowed to perform read operations.
    49  	ReadWhitelistedUserIDs []string `yaml:"readWhitelistedUserIDs,omitempty"`
    50  	// This indicates whether writes should use a write whitelist.
    51  	WriteWhitelistEnabled bool `yaml:"writeWhitelistEnabled,omitempty"`
    52  	// This is a list of users that are allowed to perform write operations.
    53  	WriteWhitelistedUserIDs []string `yaml:"writeWhitelistedUserIDs,omitempty"`
    54  }
    55  
    56  // NewSimpleAuth creates a new simple auth instance given using the provided config.
    57  func (ac SimpleAuthConfig) NewSimpleAuth() HTTPAuthService {
    58  	return simpleAuth{
    59  		authentication: simpleAuthentication{
    60  			userIDHeader:       ac.Authentication.UserIDHeader,
    61  			originatorIDHeader: ac.Authentication.OriginatorIDHeader,
    62  		},
    63  		authorization: simpleAuthorization{
    64  			readWhitelistEnabled:    ac.Authorization.ReadWhitelistEnabled,
    65  			readWhitelistedUserIDs:  ac.Authorization.ReadWhitelistedUserIDs,
    66  			writeWhitelistEnabled:   ac.Authorization.WriteWhitelistEnabled,
    67  			writeWhitelistedUserIDs: ac.Authorization.WriteWhitelistedUserIDs,
    68  		},
    69  	}
    70  }
    71  
    72  type simpleAuth struct {
    73  	authentication simpleAuthentication
    74  	authorization  simpleAuthorization
    75  }
    76  
    77  type simpleAuthentication struct {
    78  	userIDHeader       string
    79  	originatorIDHeader string
    80  }
    81  
    82  func (a simpleAuthentication) authenticate(userID string) error {
    83  	if userID == "" {
    84  		return fmt.Errorf("must provide header: [%s]", a.userIDHeader)
    85  	}
    86  	return nil
    87  }
    88  
    89  // nolint: maligned
    90  type simpleAuthorization struct {
    91  	readWhitelistEnabled    bool
    92  	readWhitelistedUserIDs  []string
    93  	writeWhitelistEnabled   bool
    94  	writeWhitelistedUserIDs []string
    95  }
    96  
    97  func (a simpleAuthorization) authorize(authType AuthorizationType, userID string) error {
    98  	switch authType {
    99  	case NoAuthorization:
   100  		return nil
   101  	case ReadOnlyAuthorization:
   102  		return a.authorizeUserForRead(userID)
   103  	case WriteOnlyAuthorization:
   104  		return a.authorizeUserForWrite(userID)
   105  	case ReadWriteAuthorization:
   106  		if err := a.authorizeUserForRead(userID); err != nil {
   107  			return err
   108  		}
   109  		return a.authorizeUserForWrite(userID)
   110  	default:
   111  		return fmt.Errorf("unsupported authorization type %v passed to handler", authType)
   112  	}
   113  }
   114  
   115  func authorizeUserForAccess(userID string, whitelistedUserIDs []string, enabled bool) error {
   116  	if !enabled {
   117  		return nil
   118  	}
   119  
   120  	for _, u := range whitelistedUserIDs {
   121  		if u == userID {
   122  			return nil
   123  		}
   124  	}
   125  	return fmt.Errorf("supplied userID: [%s] is not authorized", userID)
   126  }
   127  
   128  func (a simpleAuthorization) authorizeUserForRead(userID string) error {
   129  	return authorizeUserForAccess(userID, a.readWhitelistedUserIDs, a.readWhitelistEnabled)
   130  }
   131  
   132  func (a simpleAuthorization) authorizeUserForWrite(userID string) error {
   133  	return authorizeUserForAccess(userID, a.writeWhitelistedUserIDs, a.writeWhitelistEnabled)
   134  }
   135  
   136  // Authenticate looks for a header defining a user name. If it finds it, runs the actual http handler passed as a parameter.
   137  // Otherwise, it returns an Unauthorized http response.
   138  func (a simpleAuth) NewAuthHandler(authType AuthorizationType, next http.Handler, errHandler errorResponseHandler) http.Handler {
   139  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   140  		var (
   141  			userID       = r.Header.Get(a.authentication.userIDHeader)
   142  			originatorID = r.Header.Get(a.authentication.originatorIDHeader)
   143  		)
   144  		if originatorID == "" {
   145  			originatorID = userID
   146  		}
   147  		err := a.authentication.authenticate(originatorID)
   148  		if err != nil {
   149  			errHandler(w, http.StatusUnauthorized, err.Error())
   150  			return
   151  		}
   152  
   153  		err = a.authorization.authorize(authType, userID)
   154  		if err != nil {
   155  			errHandler(w, http.StatusForbidden, err.Error())
   156  			return
   157  		}
   158  
   159  		ctx := a.SetUser(r.Context(), originatorID)
   160  		next.ServeHTTP(w, r.WithContext(ctx))
   161  	})
   162  }
   163  
   164  // SetUser sets the user making the changes to the api.
   165  func (a simpleAuth) SetUser(parent context.Context, userID string) context.Context {
   166  	return context.WithValue(parent, UserIDField, userID)
   167  }
   168  
   169  // GetUser fetches the ID of an api caller from the global context.
   170  func (a simpleAuth) GetUser(ctx context.Context) (string, error) {
   171  	id := ctx.Value(UserIDField)
   172  	if id == nil {
   173  		return "", fmt.Errorf("couldn't identify user")
   174  	}
   175  	return id.(string), nil
   176  }