github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/authz/sdk_server_interceptors.go (about)

     1  /*
     2   * Copyright 2021 gRPC 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 authz
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"sync/atomic"
    25  	"time"
    26  	"unsafe"
    27  
    28  	grpc "github.com/hxx258456/ccgo/grpc"
    29  	"github.com/hxx258456/ccgo/grpc/codes"
    30  	"github.com/hxx258456/ccgo/grpc/grpclog"
    31  	"github.com/hxx258456/ccgo/grpc/internal/xds/rbac"
    32  	"github.com/hxx258456/ccgo/grpc/status"
    33  )
    34  
    35  var logger = grpclog.Component("authz")
    36  
    37  // StaticInterceptor contains engines used to make authorization decisions. It
    38  // either contains two engines deny engine followed by an allow engine or only
    39  // one allow engine.
    40  type StaticInterceptor struct {
    41  	engines rbac.ChainEngine
    42  }
    43  
    44  // NewStatic returns a new StaticInterceptor from a static authorization policy
    45  // JSON string.
    46  func NewStatic(authzPolicy string) (*StaticInterceptor, error) {
    47  	rbacs, err := translatePolicy(authzPolicy)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	chainEngine, err := rbac.NewChainEngine(rbacs)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	return &StaticInterceptor{*chainEngine}, nil
    56  }
    57  
    58  // UnaryInterceptor intercepts incoming Unary RPC requests.
    59  // Only authorized requests are allowed to pass. Otherwise, an unauthorized
    60  // error is returned to the client.
    61  func (i *StaticInterceptor) UnaryInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    62  	err := i.engines.IsAuthorized(ctx)
    63  	if err != nil {
    64  		if status.Code(err) == codes.PermissionDenied {
    65  			return nil, status.Errorf(codes.PermissionDenied, "unauthorized RPC request rejected")
    66  		}
    67  		return nil, err
    68  	}
    69  	return handler(ctx, req)
    70  }
    71  
    72  // StreamInterceptor intercepts incoming Stream RPC requests.
    73  // Only authorized requests are allowed to pass. Otherwise, an unauthorized
    74  // error is returned to the client.
    75  func (i *StaticInterceptor) StreamInterceptor(srv interface{}, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
    76  	err := i.engines.IsAuthorized(ss.Context())
    77  	if err != nil {
    78  		if status.Code(err) == codes.PermissionDenied {
    79  			return status.Errorf(codes.PermissionDenied, "unauthorized RPC request rejected")
    80  		}
    81  		return err
    82  	}
    83  	return handler(srv, ss)
    84  }
    85  
    86  // FileWatcherInterceptor contains details used to make authorization decisions
    87  // by watching a file path that contains authorization policy in JSON format.
    88  type FileWatcherInterceptor struct {
    89  	internalInterceptor unsafe.Pointer // *StaticInterceptor
    90  	policyFile          string
    91  	policyContents      []byte
    92  	refreshDuration     time.Duration
    93  	cancel              context.CancelFunc
    94  }
    95  
    96  // NewFileWatcher returns a new FileWatcherInterceptor from a policy file
    97  // that contains JSON string of authorization policy and a refresh duration to
    98  // specify the amount of time between policy refreshes.
    99  func NewFileWatcher(file string, duration time.Duration) (*FileWatcherInterceptor, error) {
   100  	if file == "" {
   101  		return nil, fmt.Errorf("authorization policy file path is empty")
   102  	}
   103  	if duration <= time.Duration(0) {
   104  		return nil, fmt.Errorf("requires refresh interval(%v) greater than 0s", duration)
   105  	}
   106  	i := &FileWatcherInterceptor{policyFile: file, refreshDuration: duration}
   107  	if err := i.updateInternalInterceptor(); err != nil {
   108  		return nil, err
   109  	}
   110  	ctx, cancel := context.WithCancel(context.Background())
   111  	i.cancel = cancel
   112  	// Create a background go routine for policy refresh.
   113  	go i.run(ctx)
   114  	return i, nil
   115  }
   116  
   117  func (i *FileWatcherInterceptor) run(ctx context.Context) {
   118  	ticker := time.NewTicker(i.refreshDuration)
   119  	for {
   120  		if err := i.updateInternalInterceptor(); err != nil {
   121  			logger.Warningf("authorization policy reload status err: %v", err)
   122  		}
   123  		select {
   124  		case <-ctx.Done():
   125  			ticker.Stop()
   126  			return
   127  		case <-ticker.C:
   128  		}
   129  	}
   130  }
   131  
   132  // updateInternalInterceptor checks if the policy file that is watching has changed,
   133  // and if so, updates the internalInterceptor with the policy. Unlike the
   134  // constructor, if there is an error in reading the file or parsing the policy, the
   135  // previous internalInterceptors will not be replaced.
   136  func (i *FileWatcherInterceptor) updateInternalInterceptor() error {
   137  	policyContents, err := ioutil.ReadFile(i.policyFile)
   138  	if err != nil {
   139  		return fmt.Errorf("policyFile(%s) read failed: %v", i.policyFile, err)
   140  	}
   141  	if bytes.Equal(i.policyContents, policyContents) {
   142  		return nil
   143  	}
   144  	i.policyContents = policyContents
   145  	policyContentsString := string(policyContents)
   146  	interceptor, err := NewStatic(policyContentsString)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	atomic.StorePointer(&i.internalInterceptor, unsafe.Pointer(interceptor))
   151  	logger.Infof("authorization policy reload status: successfully loaded new policy %v", policyContentsString)
   152  	return nil
   153  }
   154  
   155  // Close cleans up resources allocated by the interceptor.
   156  func (i *FileWatcherInterceptor) Close() {
   157  	i.cancel()
   158  }
   159  
   160  // UnaryInterceptor intercepts incoming Unary RPC requests.
   161  // Only authorized requests are allowed to pass. Otherwise, an unauthorized
   162  // error is returned to the client.
   163  func (i *FileWatcherInterceptor) UnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
   164  	return ((*StaticInterceptor)(atomic.LoadPointer(&i.internalInterceptor))).UnaryInterceptor(ctx, req, info, handler)
   165  }
   166  
   167  // StreamInterceptor intercepts incoming Stream RPC requests.
   168  // Only authorized requests are allowed to pass. Otherwise, an unauthorized
   169  // error is returned to the client.
   170  func (i *FileWatcherInterceptor) StreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
   171  	return ((*StaticInterceptor)(atomic.LoadPointer(&i.internalInterceptor))).StreamInterceptor(srv, ss, info, handler)
   172  }