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 }