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  }