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 }