mirror of https://github.com/grpc/grpc-go.git
examples: add example to illustrate the use of file watcher interceptor (#7226)
authz: add example to illustrate the use of file watcher interceptor
This commit is contained in:
parent
03da31acc6
commit
6e59dd1d7f
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "authz",
|
||||||
|
"allow_rules": [
|
||||||
|
{
|
||||||
|
"name": "allow_UnaryEcho",
|
||||||
|
"request": {
|
||||||
|
"paths": ["/grpc.examples.echo.Echo/UnaryEcho"],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "UNARY_ECHO:RW",
|
||||||
|
"values": ["true"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "allow_BidirectionalStreamingEcho",
|
||||||
|
"request": {
|
||||||
|
"paths": ["/grpc.examples.echo.Echo/BidirectionalStreamingEcho"],
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "STREAM_ECHO:RW",
|
||||||
|
"values": ["true"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deny_rules": []
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,29 @@
|
||||||
# RBAC authorization
|
# RBAC authorization
|
||||||
|
|
||||||
This example uses the `StaticInterceptor` from the `google.golang.org/grpc/authz`
|
This example uses the `StaticInterceptor` and `FileWatcherInterceptor` from the
|
||||||
package. It uses a header based RBAC policy to match each gRPC method to a
|
`google.golang.org/grpc/authz` package. It uses a header based RBAC policy to
|
||||||
required role. For simplicity, the context is injected with mock metadata which
|
match each gRPC method to a required role. For simplicity, the context is
|
||||||
includes the required roles, but this should be fetched from an appropriate
|
injected with mock metadata which includes the required roles, but this should
|
||||||
service based on the authenticated context.
|
be fetched from an appropriate service based on the authenticated context.
|
||||||
|
|
||||||
## Try it
|
## Try it
|
||||||
|
|
||||||
Server requires the following roles on an authenticated user to authorize usage
|
Server is expected to require the following roles on an authenticated user to
|
||||||
of these methods:
|
authorize usage of these methods:
|
||||||
|
|
||||||
- `UnaryEcho` requires the role `UNARY_ECHO:W`
|
- `UnaryEcho` requires the role `UNARY_ECHO:W`
|
||||||
- `BidirectionalStreamingEcho` requires the role `STREAM_ECHO:RW`
|
- `BidirectionalStreamingEcho` requires the role `STREAM_ECHO:RW`
|
||||||
|
|
||||||
Upon receiving a request, the server first checks that a token was supplied,
|
Upon receiving a request, the server first checks that a token was supplied,
|
||||||
decodes it and checks that a secret is correctly set (hardcoded to `super-secret`
|
decodes it and checks that a secret is correctly set (hardcoded to
|
||||||
for simplicity, this should use a proper ID provider in production).
|
`super-secret` for simplicity, this should use a proper ID provider in
|
||||||
|
production).
|
||||||
|
|
||||||
If the above is successful, it uses the username in the token to set appropriate
|
If the above is successful, it uses the username in the token to set appropriate
|
||||||
roles (hardcoded to the 2 required roles above if the username matches `super-user`
|
roles (hardcoded to the 2 required roles above if the username matches
|
||||||
for simplicity, these roles should be supplied externally as well).
|
`super-user` for simplicity, these roles should be supplied externally as well).
|
||||||
|
|
||||||
|
### Authorization with static policy
|
||||||
|
|
||||||
Start the server with:
|
Start the server with:
|
||||||
|
|
||||||
|
|
@ -38,3 +41,35 @@ Start the client with:
|
||||||
```
|
```
|
||||||
go run client/main.go
|
go run client/main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Authorization by watching a policy file
|
||||||
|
|
||||||
|
The server accepts an optional `--authz-option filewatcher` flag to set up
|
||||||
|
authorization policy by reading a [policy
|
||||||
|
file](/examples/data/rbac/policy.json), and to look for update on the policy
|
||||||
|
file every 100 millisecond. Having `GRPC_GO_LOG_SEVERITY_LEVEL` environment
|
||||||
|
variable set to `info` will log out the reload activity of the policy every time
|
||||||
|
a file update is detected.
|
||||||
|
|
||||||
|
Start the server with:
|
||||||
|
|
||||||
|
```
|
||||||
|
GRPC_GO_LOG_SEVERITY_LEVEL=info go run server/main.go --authz-option filewatcher
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the client with:
|
||||||
|
|
||||||
|
```
|
||||||
|
go run client/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
The client will first hit `codes.PermissionDenied` error when invoking
|
||||||
|
`UnaryEcho` although a legitimate username (`super-user`) is associated with the
|
||||||
|
RPC. This is because the policy file has an intentional glitch (falsely asks for
|
||||||
|
role `UNARY_ECHO:RW`).
|
||||||
|
|
||||||
|
While the server is still running, edit and save the policy file to replace
|
||||||
|
`UNARY_ECHO:RW` with the correct role `UNARY_ECHO:W` (policy reload activity
|
||||||
|
should now be found in server logs). This time when the client is started again
|
||||||
|
with the command above, it will be able to get responses just as in the
|
||||||
|
static-policy example.
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/authz"
|
"google.golang.org/grpc/authz"
|
||||||
|
|
@ -76,10 +77,13 @@ const (
|
||||||
"deny_rules": []
|
"deny_rules": []
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
authzOptStatic = "static"
|
||||||
|
authzOptFileWatcher = "filewatcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
port = flag.Int("port", 50051, "the port to serve on")
|
port = flag.Int("port", 50051, "the port to serve on")
|
||||||
|
authzOpt = flag.String("authz-option", authzOptStatic, "the authz option (static or filewatcher)")
|
||||||
|
|
||||||
errMissingMetadata = status.Errorf(codes.InvalidArgument, "missing metadata")
|
errMissingMetadata = status.Errorf(codes.InvalidArgument, "missing metadata")
|
||||||
)
|
)
|
||||||
|
|
@ -186,6 +190,10 @@ func authStreamInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServe
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if *authzOpt != authzOptStatic && *authzOpt != authzOptFileWatcher {
|
||||||
|
log.Fatalf("Invalid authz option: %s", *authzOpt)
|
||||||
|
}
|
||||||
|
|
||||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Listening on local port %q: %v", *port, err)
|
log.Fatalf("Listening on local port %q: %v", *port, err)
|
||||||
|
|
@ -197,14 +205,28 @@ func main() {
|
||||||
log.Fatalf("Loading credentials: %v", err)
|
log.Fatalf("Loading credentials: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an authorization interceptor using a static policy.
|
// Create authorization interceptors according to the authz-option command-line flag.
|
||||||
staticInteceptor, err := authz.NewStatic(authzPolicy)
|
var unaryAuthzInterceptor grpc.UnaryServerInterceptor
|
||||||
if err != nil {
|
var streamAuthzInterceptor grpc.StreamServerInterceptor
|
||||||
log.Fatalf("Creating a static authz interceptor: %v", err)
|
if *authzOpt == authzOptStatic {
|
||||||
|
// Create an authorization interceptor using a static policy.
|
||||||
|
staticInterceptor, err := authz.NewStatic(authzPolicy)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Creating a static authz interceptor: %v", err)
|
||||||
|
}
|
||||||
|
unaryAuthzInterceptor, streamAuthzInterceptor = staticInterceptor.UnaryInterceptor, staticInterceptor.StreamInterceptor
|
||||||
|
} else if *authzOpt == authzOptFileWatcher {
|
||||||
|
// Create an authorization interceptor by watching a policy file.
|
||||||
|
fileWatcherInterceptor, err := authz.NewFileWatcher(data.Path("rbac/policy.json"), 100*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Creating a file watcher authz interceptor: %v", err)
|
||||||
|
}
|
||||||
|
unaryAuthzInterceptor, streamAuthzInterceptor = fileWatcherInterceptor.UnaryInterceptor, fileWatcherInterceptor.StreamInterceptor
|
||||||
}
|
}
|
||||||
unaryInts := grpc.ChainUnaryInterceptor(authUnaryInterceptor, staticInteceptor.UnaryInterceptor)
|
|
||||||
streamInts := grpc.ChainStreamInterceptor(authStreamInterceptor, staticInteceptor.StreamInterceptor)
|
unaryInterceptors := grpc.ChainUnaryInterceptor(authUnaryInterceptor, unaryAuthzInterceptor)
|
||||||
s := grpc.NewServer(grpc.Creds(creds), unaryInts, streamInts)
|
streamInterceptors := grpc.ChainStreamInterceptor(authStreamInterceptor, streamAuthzInterceptor)
|
||||||
|
s := grpc.NewServer(grpc.Creds(creds), unaryInterceptors, streamInterceptors)
|
||||||
|
|
||||||
// Register EchoServer on the server.
|
// Register EchoServer on the server.
|
||||||
pb.RegisterEchoServer(s, &server{})
|
pb.RegisterEchoServer(s, &server{})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue