vitess.io/vitess@v0.16.2/go/vt/servenv/grpc_server_auth_static.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package servenv
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"os"
    24  
    25  	"github.com/spf13/pflag"
    26  	"google.golang.org/grpc/codes"
    27  	"google.golang.org/grpc/metadata"
    28  	"google.golang.org/grpc/status"
    29  
    30  	"vitess.io/vitess/go/vt/log"
    31  )
    32  
    33  var (
    34  	credsFile string
    35  	// StaticAuthPlugin implements AuthPlugin interface
    36  	_ Authenticator = (*StaticAuthPlugin)(nil)
    37  )
    38  
    39  // The datatype for static auth Context keys
    40  type staticAuthKey int
    41  
    42  const (
    43  	// Internal Context key for the authenticated username
    44  	staticAuthUsername staticAuthKey = 0
    45  )
    46  
    47  func registerGRPCServerAuthStaticFlags(fs *pflag.FlagSet) {
    48  	fs.StringVar(&credsFile, "grpc_auth_static_password_file", credsFile, "JSON File to read the users/passwords from.")
    49  }
    50  
    51  // StaticAuthConfigEntry is the container for server side credentials. Current implementation matches the
    52  // the one from the client but this will change in the future as we hooked this pluging into ACL
    53  // features.
    54  type StaticAuthConfigEntry struct {
    55  	Username string
    56  	Password string
    57  	// TODO (@rafael) Add authorization parameters
    58  }
    59  
    60  // StaticAuthPlugin  implements static username/password authentication for grpc. It contains an array of username/passwords
    61  // that will be authorized to connect to the grpc server.
    62  type StaticAuthPlugin struct {
    63  	entries []StaticAuthConfigEntry
    64  }
    65  
    66  // Authenticate implements AuthPlugin interface. This method will be used inside a middleware in grpc_server to authenticate
    67  // incoming requests.
    68  func (sa *StaticAuthPlugin) Authenticate(ctx context.Context, fullMethod string) (context.Context, error) {
    69  	if md, ok := metadata.FromIncomingContext(ctx); ok {
    70  		if len(md["username"]) == 0 || len(md["password"]) == 0 {
    71  			return nil, status.Errorf(codes.Unauthenticated, "username and password must be provided")
    72  		}
    73  		username := md["username"][0]
    74  		password := md["password"][0]
    75  		for _, authEntry := range sa.entries {
    76  			if username == authEntry.Username && password == authEntry.Password {
    77  				return newStaticAuthContext(ctx, username), nil
    78  			}
    79  		}
    80  		return nil, status.Errorf(codes.PermissionDenied, "auth failure: caller %q provided invalid credentials", username)
    81  	}
    82  	return nil, status.Errorf(codes.Unauthenticated, "username and password must be provided")
    83  }
    84  
    85  // StaticAuthUsernameFromContext returns the username authenticated by the static auth plugin and stored in the Context, if any
    86  func StaticAuthUsernameFromContext(ctx context.Context) string {
    87  	username, ok := ctx.Value(staticAuthUsername).(string)
    88  	if ok {
    89  		return username
    90  	}
    91  	return ""
    92  }
    93  
    94  func newStaticAuthContext(ctx context.Context, username string) context.Context {
    95  	return context.WithValue(ctx, staticAuthUsername, username)
    96  }
    97  
    98  func staticAuthPluginInitializer() (Authenticator, error) {
    99  	entries := make([]StaticAuthConfigEntry, 0)
   100  	if credsFile == "" {
   101  		err := fmt.Errorf("failed to load static auth plugin. Plugin configured but grpc_auth_static_password_file not provided")
   102  		return nil, err
   103  	}
   104  
   105  	data, err := os.ReadFile(credsFile)
   106  	if err != nil {
   107  		err := fmt.Errorf("failed to load static auth plugin %v", err)
   108  		return nil, err
   109  	}
   110  
   111  	err = json.Unmarshal(data, &entries)
   112  	if err != nil {
   113  		err := fmt.Errorf("fail to load static auth plugin: %v", err)
   114  		return nil, err
   115  	}
   116  	staticAuthPlugin := &StaticAuthPlugin{
   117  		entries: entries,
   118  	}
   119  	log.Info("static auth plugin have initialized successfully with config from grpc_auth_static_password_file")
   120  	return staticAuthPlugin, nil
   121  }
   122  
   123  func init() {
   124  	RegisterAuthPlugin("static", staticAuthPluginInitializer)
   125  	grpcAuthServerFlagHooks = append(grpcAuthServerFlagHooks, registerGRPCServerAuthStaticFlags)
   126  }