github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/control/credentials.go (about) 1 // Copyright 2022 Edward McFarlane. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package control 6 7 import ( 8 "bytes" 9 "context" 10 "encoding/base64" 11 "fmt" 12 "sync" 13 "time" 14 15 "github.com/emcfarlane/larking/apipb/controlpb" 16 "gocloud.dev/runtimevar" 17 "google.golang.org/grpc/codes" 18 "google.golang.org/grpc/status" 19 "google.golang.org/protobuf/encoding/protojson" 20 "google.golang.org/protobuf/proto" 21 ) 22 23 type PerRPCCredentials struct { 24 v *runtimevar.Variable 25 26 mu sync.Mutex 27 creds *controlpb.Credentials 28 29 err error 30 } 31 32 func (c *PerRPCCredentials) watch(ctx context.Context) error { 33 snap, err := c.v.Watch(ctx) 34 if err != nil { 35 return err 36 } 37 38 var b []byte 39 switch v := snap.Value.(type) { 40 case string: 41 b = []byte(v) 42 case []byte: 43 b = v 44 default: 45 return fmt.Errorf("unexpected PerRPCCredentials type %T", snap.Value) 46 } 47 48 var ( 49 creds controlpb.Credentials 50 ) 51 if bytes.HasPrefix(b, []byte(`{`)) { 52 err = protojson.Unmarshal(b, &creds) 53 } else { 54 err = proto.Unmarshal(b, &creds) 55 } 56 57 c.mu.Lock() 58 c.creds = &creds 59 c.err = err 60 c.mu.Unlock() 61 return err 62 } 63 64 // OpenRPCCredentials accepts a runetimevar URL as a credential store. 65 // The credentials are watched and reload when changed. Close must be called. 66 func OpenRPCCredentials(ctx context.Context, u string) (*PerRPCCredentials, error) { 67 ctx, cancel := context.WithTimeout(ctx, 1*time.Second) 68 defer cancel() 69 70 v, err := runtimevar.OpenVariable(ctx, u) 71 if err != nil { 72 return nil, fmt.Errorf("OpenRPCCredentials: %w", err) 73 } 74 75 c := &PerRPCCredentials{ 76 v: v, 77 } 78 79 if err := c.watch(ctx); err != nil { 80 return nil, fmt.Errorf("OpenRPCCredentials: %w", err) 81 } 82 83 // watch 84 go func() { 85 ctx := context.Background() 86 for { 87 if err := c.watch(ctx); err == runtimevar.ErrClosed { 88 break // closed 89 } 90 } 91 }() 92 return c, nil 93 } 94 95 func (c *PerRPCCredentials) Close() error { return c.v.Close() } 96 97 func basicAuth(username, password string) string { 98 auth := username + ":" + password 99 return base64.StdEncoding.EncodeToString([]byte(auth)) 100 } 101 102 func (c *PerRPCCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 103 c.mu.Lock() 104 defer c.mu.Unlock() 105 106 if err := c.err; err != nil { 107 return nil, err 108 } 109 110 switch v := c.creds.Type.(type) { 111 case *controlpb.Credentials_Insecure: 112 return nil, nil // nothing 113 case *controlpb.Credentials_Bearer: 114 return map[string]string{ 115 "authorization": "bearer " + v.Bearer.AccessToken, 116 }, nil 117 case *controlpb.Credentials_Basic: 118 return map[string]string{ 119 "authorization": "basic " + basicAuth( 120 v.Basic.Username, 121 v.Basic.Password, 122 ), 123 }, nil 124 default: 125 return nil, status.Errorf(codes.Unimplemented, "RPCCredentials unknown credential type") 126 } 127 } 128 129 func (c *PerRPCCredentials) RequireTransportSecurity() bool { 130 c.mu.Lock() 131 defer c.mu.Unlock() 132 return !c.creds.GetInsecure() 133 } 134 135 func (c *PerRPCCredentials) Name() string { 136 c.mu.Lock() 137 defer c.mu.Unlock() 138 return c.creds.Name 139 }