...

Source file src/golang.conradwood.net/go-easyops/authremote/auth_remote.go

Documentation: golang.conradwood.net/go-easyops/authremote

     1  /*
     2  This package provides access to user information which require network I/O, for example lookup of users by email.
     3  
     4  It also provides some wrappers to create a new context. That is for historic reasons. Developers should use and port code to use the ctx package instead.
     5  */
     6  package authremote
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	apb "golang.conradwood.net/apis/auth"
    12  	ge "golang.conradwood.net/apis/goeasyops"
    13  	"golang.conradwood.net/go-easyops/auth"
    14  	"golang.conradwood.net/go-easyops/cache"
    15  	"golang.conradwood.net/go-easyops/client"
    16  	"golang.conradwood.net/go-easyops/cmdline"
    17  	"golang.conradwood.net/go-easyops/common"
    18  	"golang.conradwood.net/go-easyops/ctx"
    19  	"golang.conradwood.net/go-easyops/tokens"
    20  	"sync"
    21  	"time"
    22  )
    23  
    24  var (
    25  	userbyemailcache = cache.NewResolvingCache("userbyemail", time.Duration(60)*time.Second, 9999)
    26  	userbytokencache = cache.NewResolvingCache("userbytoken", time.Duration(60)*time.Second, 9999)
    27  	authServer       apb.AuthenticationServiceClient
    28  	authServerLock   sync.Mutex
    29  	authManager      apb.AuthManagerServiceClient
    30  	authManagerLock  sync.Mutex
    31  	contextRetrieved = false
    32  	lastUser         *apb.SignedUser
    33  	lastService      *apb.SignedUser
    34  )
    35  
    36  func init() {
    37  	common.AddRegistryChangeReceiver(registry_changed)
    38  }
    39  func registry_changed() {
    40  	authManager = nil
    41  	authServer = nil
    42  }
    43  func Context() context.Context {
    44  	client.GetSignatureFromAuth()
    45  	return ContextWithTimeout(cmdline.DefaultTimeout())
    46  }
    47  
    48  /*
    49  create a new context with routing tags (routing criteria to route to specific instances of a service)
    50  if fallback is true, fallback to any service without tags if none is found (default was false)
    51  */
    52  func NewContextWithRouting(kv map[string]string, fallback bool) context.Context {
    53  	return DerivedContextWithRouting(Context(), kv, fallback)
    54  }
    55  
    56  /*
    57  derive  a context with routing tags (routing criteria to route to specific instances of a service)
    58  if fallback is true, fallback to any service without tags if none is found (default was false)
    59  */
    60  func DerivedContextWithRouting(cv context.Context, kv map[string]string, fallback bool) context.Context {
    61  	if cv == nil {
    62  		panic("cannot derive context from nil context")
    63  	}
    64  	cri := &ge.CTXRoutingTags{Tags: kv, FallbackToPlain: fallback}
    65  
    66  	if cmdline.ContextWithBuilder() {
    67  		_, s := GetLocalUsers()
    68  		if s == nil {
    69  			s = auth.GetSignedService(cv)
    70  		}
    71  		cb := ctx.NewContextBuilder()
    72  		cb.WithUser(auth.GetSignedUser(cv))
    73  		cb.WithCreatorService(s)
    74  		cb.WithCallingService(s)
    75  		cb.WithRoutingTags(cri)
    76  		//	cb.WithTimeout(t)
    77  		cb.WithParentContext(cv)
    78  		nctx := cb.ContextWithAutoCancel()
    79  		if auth.GetSignedService(nctx) == nil && s != nil {
    80  			fmt.Printf("[go-easyops] context: %s\n", ctx.Context2String(nctx))
    81  			fmt.Printf("[go-easyops] Localstate: %#v\n", ctx.GetLocalState(nctx))
    82  			fmt.Printf("[go-easyops] WARNING derived context (v=%d) includes no service, but should\n", cmdline.GetContextBuilderVersion())
    83  			//return nil
    84  		}
    85  		if nctx == nil {
    86  			panic("no context")
    87  		}
    88  		return nctx
    89  
    90  	}
    91  	panic("deprecated codepath")
    92  }
    93  
    94  /*
    95  get a context with routing tags, specified by proto
    96  */
    97  func NewContextWithRoutingTags(rt *ge.CTXRoutingTags) context.Context {
    98  	return ContextWithTimeoutAndTags(cmdline.DefaultTimeout(), rt)
    99  }
   100  
   101  /*
   102  	this context gives a context with a full userobject
   103  
   104  todo so it _has_ to call external servers to get a signed userobject.
   105  if started_by_autodeployer will use getContext()
   106  else if environment variable with context, will use auth.Context() (with variable)
   107  else create context by asking auth service for a signed user object
   108  */
   109  func ContextWithTimeout(t time.Duration) context.Context {
   110  	return ContextWithTimeoutAndTags(t, nil)
   111  }
   112  
   113  // get the user and service we are running as. Do not cache this result! (on boot the result may change once auth comes available)
   114  func GetLocalUsers() (*apb.SignedUser, *apb.SignedUser) {
   115  	client.GetSignatureFromAuth()
   116  	if cmdline.DebugAuth() {
   117  		fmt.Printf("[go-easyops] debugauth, contextretrieved=%v, localuser=%s,gotsig=%v\n", contextRetrieved, auth.SignedDescription(lastUser), client.GotSig())
   118  	}
   119  	if !client.GotSig() {
   120  		if cmdline.DebugAuth() {
   121  			fmt.Printf("[go-easyops] debugauth no local users, we do not yet have a signature\n")
   122  		}
   123  		return nil, nil
   124  	}
   125  	if !contextRetrieved {
   126  		utok := tokens.GetUserTokenParameter()
   127  		//		fmt.Printf("utok: \"%s\"\n", utok)
   128  		lastUser = SignedGetByToken(context_background(), utok)
   129  		lastService = SignedGetByToken(context_background(), tokens.GetServiceTokenParameter())
   130  		lu := common.VerifySignedUser(lastUser)
   131  		if lastUser != nil && lu == nil {
   132  			fmt.Printf("[go-easyops] Warning - local user signature invalid\n")
   133  			return nil, nil
   134  		}
   135  		if lu != nil {
   136  			if lu.ServiceAccount {
   137  				fmt.Printf("[go-easyops] Error - local user resolved to a service account\n")
   138  				panic("invalid user configuration")
   139  			}
   140  		}
   141  		if lastService != nil && common.VerifySignedUser(lastService) == nil {
   142  			fmt.Printf("[go-easyops] Warning - local service signature invalid\n")
   143  			return nil, nil
   144  		}
   145  		contextRetrieved = true
   146  	}
   147  	return lastUser, lastService
   148  }
   149  
   150  /*
   151  create a new context with routing tags. This is an EXPERIMENTAL API and very likely to change in future
   152  */
   153  func ContextWithTimeoutAndTags(t time.Duration, rt *ge.CTXRoutingTags) context.Context {
   154  	if cmdline.IsStandalone() {
   155  		return standalone_ContextWithTimeoutAndTags(t, rt)
   156  	}
   157  	sctx := cmdline.GetEnvContext()
   158  	if sctx != "" {
   159  		if ctx.IsSerialisedByBuilder([]byte(sctx)) {
   160  			ctx, err := ctx.DeserialiseContextWithTimeout(t, []byte(sctx))
   161  			if err != nil {
   162  				fmt.Printf("[go-easyops] weird context GE_CTX (%s)\n", err)
   163  			} else {
   164  				return ctx
   165  			}
   166  
   167  		}
   168  		//				fmt.Printf("Recreating context from environment variable GE_CTX\n")
   169  		res, err := auth.RecreateContextWithTimeout(t, []byte(sctx))
   170  		if err == nil {
   171  			return res
   172  		} else {
   173  			fmt.Printf("[go-easyops] invalid context in environment variable GE_CTX\n")
   174  		}
   175  	}
   176  	if cmdline.ContextWithBuilder() {
   177  		u, s := GetLocalUsers()
   178  		cb := ctx.NewContextBuilder()
   179  		cb.WithUser(u)
   180  		cb.WithCreatorService(s)
   181  		cb.WithCallingService(s)
   182  		cb.WithRoutingTags(rt) //rpc.Tags_rpc_to_ge(rt))
   183  		cb.WithTimeout(t)
   184  		return cb.ContextWithAutoCancel()
   185  	}
   186  
   187  	panic("[go-easyops] DEPRECATED CONTEXT creation!\n")
   188  }
   189  
   190  func GetAuthManagerClient() apb.AuthManagerServiceClient {
   191  	managerClient()
   192  	return authManager
   193  }
   194  
   195  // compat with 'create', synonym for GetAuthClient()
   196  func GetAuthenticationServiceClient() apb.AuthenticationServiceClient {
   197  	return GetAuthClient()
   198  }
   199  
   200  // compat with 'create', synonym for GetAuthClient()
   201  func GetAuthenticationService() apb.AuthenticationServiceClient {
   202  	return GetAuthClient()
   203  }
   204  
   205  func GetAuthClient() apb.AuthenticationServiceClient {
   206  	authClient()
   207  	return authServer
   208  }
   209  
   210  // create an outbound context for a given user. user must be valid and signed
   211  // this is an expensive call
   212  // this is not privileged (user must be signed)
   213  func ContextForUser(user *apb.User) (context.Context, error) {
   214  	return ContextForUserWithTimeout(user, 0) //default timeout
   215  }
   216  func ContextForUserWithTimeout(user *apb.User, secs uint64) (context.Context, error) {
   217  	if user == nil {
   218  		return nil, fmt.Errorf("Missing user")
   219  	}
   220  
   221  	if cmdline.ContextWithBuilder() {
   222  		su, err := GetSignedUserByID(Context(), user.ID)
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  		cb := ctx.NewContextBuilder()
   227  		cb.WithTimeout(time.Duration(secs) * time.Second)
   228  		cb.WithUser(su)
   229  		_, svc := GetLocalUsers()
   230  		cb.WithCreatorService(svc)
   231  		cb.WithCallingService(svc)
   232  		return cb.ContextWithAutoCancel(), nil
   233  	}
   234  	panic("obsolete codepath")
   235  }
   236  
   237  // create an outbound context for a given user by id (with current service token)
   238  // this is an expensive call
   239  // it is also privileged
   240  func ContextForUserID(userid string) (context.Context, error) {
   241  	return ContextForUserIDWithTimeout(userid, 0)
   242  }
   243  func ContextForUserIDWithTimeout(userid string, to time.Duration) (context.Context, error) {
   244  	if userid == "" || userid == "0" {
   245  		return nil, fmt.Errorf("Missing userid")
   246  	}
   247  	if cmdline.ContextWithBuilder() {
   248  		su, err := GetSignedUserByID(Context(), userid)
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  		cb := ctx.NewContextBuilder()
   253  		cb.WithTimeout(to)
   254  		cb.WithUser(su)
   255  		_, svc := GetLocalUsers()
   256  		cb.WithCreatorService(svc)
   257  		cb.WithCallingService(svc)
   258  		return cb.ContextWithAutoCancel(), nil
   259  	}
   260  	panic("obsolete codepath")
   261  
   262  }
   263  func GetUserByID(ctx context.Context, userid string) (*apb.User, error) {
   264  	if userid == "" {
   265  		return nil, fmt.Errorf("[go-easyops] No userid provided")
   266  	}
   267  	return usercache_GetUserByID(ctx, userid)
   268  }
   269  
   270  func GetSignedUserByID(ctx context.Context, userid string) (*apb.SignedUser, error) {
   271  	if userid == "" {
   272  		return nil, fmt.Errorf("[go-easyops] No userid provided")
   273  	}
   274  	return usercache_GetSignedUserByID(ctx, userid)
   275  }
   276  
   277  func GetUserByEmail(ctx context.Context, email string) (*apb.User, error) {
   278  	if email == "" {
   279  		return nil, fmt.Errorf("[go-easyops] No email provided")
   280  	}
   281  	o, err := userbyemailcache.Retrieve(email, func(k string) (interface{}, error) {
   282  		managerClient()
   283  		res, err := authManager.GetUserByEmail(ctx, &apb.ByEmailRequest{Email: k})
   284  		return res, err
   285  	})
   286  	if err != nil {
   287  		return nil, err
   288  	}
   289  	return o.(*apb.User), nil
   290  }
   291  func WhoAmI() *apb.User {
   292  	tok := tokens.GetUserTokenParameter()
   293  	return GetByToken(context_background(), tok)
   294  }
   295  func GetByToken(ctx context.Context, token string) *apb.User {
   296  	if token == "" {
   297  		return nil
   298  	}
   299  	authClient()
   300  	ar, err := authServer.GetByToken(ctx, &apb.AuthenticateTokenRequest{Token: token})
   301  	if err != nil {
   302  		return nil
   303  	}
   304  	if !ar.Valid {
   305  		return nil
   306  	}
   307  	if !ar.User.Active {
   308  		return nil
   309  	}
   310  	return ar.User
   311  }
   312  func SignedGetByEmail(ctx context.Context, email string) *apb.SignedUser {
   313  	if email == "" {
   314  		return nil
   315  	}
   316  	managerClient()
   317  	req := &apb.ByEmailRequest{Email: email}
   318  	su, err := authManager.SignedGetUserByEmail(ctx, req)
   319  	if err != nil {
   320  		return nil
   321  	}
   322  	u := common.VerifySignedUser(su)
   323  	if u == nil {
   324  		return nil
   325  	}
   326  	if !u.Active {
   327  		return nil
   328  	}
   329  	return su
   330  }
   331  func SignedGetByToken(ctx context.Context, token string) *apb.SignedUser {
   332  	if token == "" {
   333  		//		utils.PrintStack("[go-easyops] attempt to get user for empty token\n")
   334  		return nil
   335  	}
   336  	su, err := userbytokencache.Retrieve(token, func(k string) (interface{}, error) {
   337  		authClient()
   338  		if cmdline.DebugAuth() {
   339  			fmt.Printf("[go-easyops] getting user for token \"%s\"...\n", k[:5])
   340  		}
   341  		ar, err := authServer.SignedGetByToken(ctx, &apb.AuthenticateTokenRequest{Token: k})
   342  		if err != nil {
   343  			if cmdline.DebugAuth() {
   344  				fmt.Printf("[go-easyops] getting user for token \"%s\" failed: %s\n", k, err)
   345  			}
   346  			return nil, err
   347  		}
   348  		if !ar.Valid {
   349  			if cmdline.DebugAuth() {
   350  				fmt.Printf("[go-easyops] getting user for token \"%s\" invalid", k[:5])
   351  			}
   352  			return nil, fmt.Errorf("user not valid")
   353  		}
   354  		u := common.VerifySignedUser(ar.User)
   355  		if !u.Active {
   356  			if cmdline.DebugAuth() {
   357  				fmt.Printf("[go-easyops] getting user for token \"%s\" inactive", k[:5])
   358  			}
   359  			return nil, fmt.Errorf("user not active")
   360  		}
   361  		if cmdline.DebugAuth() {
   362  			fmt.Printf("[go-easyops] getting user for token \"%s\" resulted in \"%s\"", k[:5], ar.User)
   363  		}
   364  		return ar.User, nil
   365  	})
   366  	if err != nil {
   367  		return nil
   368  	}
   369  	if su == nil {
   370  		return nil
   371  	}
   372  	return su.(*apb.SignedUser)
   373  }
   374  
   375  func authClient() {
   376  	if authServer == nil {
   377  		authServerLock.Lock()
   378  		defer authServerLock.Unlock()
   379  		if authServer != nil {
   380  			return
   381  		}
   382  		authServer = apb.NewAuthenticationServiceClient(client.Connect("auth.AuthenticationService"))
   383  	}
   384  }
   385  func managerClient() {
   386  	if authManager == nil {
   387  		authManagerLock.Lock()
   388  		defer authManagerLock.Unlock()
   389  		if authManager != nil {
   390  			return
   391  		}
   392  		authManager = apb.NewAuthManagerServiceClient(client.Connect("auth.AuthManagerService"))
   393  	}
   394  }
   395  
   396  func context_background() context.Context {
   397  	cb := ctx.NewContextBuilder()
   398  	return cb.ContextWithAutoCancel()
   399  }
   400  

View as plain text