-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Authentication, Authorization, HTTPS support #773
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks good, just a few minor suggestions and questions
api/server/sdk/server.go
Outdated
} | ||
|
||
// Create REST Gateway and connect it to the unix domain socket server | ||
restGeteway, err := newSdkRestGateway(config, udsServer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: restGeteway
-> restGateway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ha!, nice
) | ||
|
||
type sdkRestGateway struct { | ||
config ServerConfig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason for not making this a pointer? Just to ensure we make a copy of the config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct.
err.Error()) | ||
} | ||
}() | ||
<-ready |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the purpose of this just to block until we know the goroutine has started? I suppose you could use a sync.WaitGroup
to be a little more clear, but that may be overkill for just one goroutine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this code is copied from the older server.go
type InterceptorContextkey string | ||
|
||
const ( | ||
InterceptorContextTokenKey InterceptorContextkey = "tokenclaims" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a comment here about how InterceptorContextkey
and/or InterceptorContextTokenKey
are for those unfamiliar with gRPC interceptors or our design
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
} | ||
|
||
// Setup auditor log | ||
claimsJSON, _ := json.Marshal(claims) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason for ignoring this error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, i shouldn't just in case
|
||
func authorizeClaims(rules []sdk_auth.Rule, fullmethod string) error { | ||
|
||
var ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can just do a single line var reqService, reqApi string
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true
@@ -337,12 +339,18 @@ func start(c *cli.Context) error { | |||
csiServer.Start() | |||
|
|||
// Start SDK Server for this driver | |||
sdksocket := fmt.Sprintf("/var/lib/osd/driver/%s-sdk.sock", d) | |||
os.Remove(sdksocket) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we check for an error from os.Remove
? I'm not sure under what circumstances this would fail, but maybe to be safe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need, imho, if it fails, we will not be able to create it and the starting of the service will show the correct error.
requiredClaims = []string{"exp", "iat", "name", "email"} | ||
) | ||
|
||
type Authenticator interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if we follow golint too closely, but these exported functions & could use comments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I will add some
info.FullMethod) | ||
} | ||
|
||
logger.Info("Authorized") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this include the request timestamp in the logs? From the design doc:
An SDK interceptor will log all successful and denied accesses. Information will include:
- Time of request
- All latest claims information
- API call requested
- Success/Denied status
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, the logrus will take care of that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO these should be logged into a separate audit file or else it is going to flood the storage provider logs with request information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add an issue to have this done. In the mean time I will disable this from the interceptors.
func (j *JwtAuthenticator) AuthenticateToken(rawtoken string) (*sdk_auth.Claims, error) { | ||
|
||
// Parse token | ||
token, err := jwt.Parse(rawtoken, func(token *jwt.Token) (interface{}, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this where we check if the token has expired? If so, is token.Valid
set to false?
If not, where do we check for token expiry? w/ token.exp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's it. the jwt package sets that boolean value and the caller is supposed to check it.
lgtm 👍 |
|
||
// REST Gateway Handlers | ||
handlers := []func(context.Context, *runtime.ServeMux, *grpc.ClientConn) (err error){ | ||
api.RegisterOpenStorageClusterHandler, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about this list of functions is a global variable. So that we can add tests, to ensure if anyone adds a new handler it gets added to this list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good idea. Do you mind if we add this as a PR after this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes that works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue #776 created
} | ||
|
||
// Create REST Gateway and connect it to the unix domain socket server | ||
restGateway, err := newSdkRestGateway(config, udsServer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should error out if config.RestPort (required by rest gateway) is not provided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point
api/server/sdk/server.go
Outdated
return err | ||
} | ||
|
||
if len(s.config.RestPort) != 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we add this check in New(), it will not be needed here.
return nil | ||
|
||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also add a Stop routine for the REST gateway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's much harder to do. It has to do with http server in golang. If it is supports it in later golang versions, I will add it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how we can do it
restGatewayServer := &http.Server{Addr: address, Handler: mux} | |
restGatewayServer.ListenAndServeTLS(s.config.Tls.CertFile, s.config.Tls.KeyFile) | |
// for stopping | |
restGatewayServer.Close() | |
|
||
func (s *Server) Stop() { | ||
s.netServer.Stop() | ||
s.udsServer.Stop() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stop the REST gateway as well.
|
||
// Authenticate user and add authorization information back in the context | ||
func (s *sdkGrpcServer) auth(ctx context.Context) (context.Context, error) { | ||
token, err := grpc_auth.AuthFromMD(ctx, "bearer") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: bearer could be a constant, with some comment explaining what it is and why we are not using basic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do. I will add that it is just standard JWT style
info.FullMethod) | ||
} | ||
|
||
logger.Info("Authorized") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO these should be logged into a separate audit file or else it is going to flood the storage provider logs with request information.
func New(config *JwtAuthConfig) (*JwtAuthenticator, error) { | ||
|
||
// Check at least one is set | ||
if len(config.SharedSecret) == 0 && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add a check for config is not nil
) | ||
|
||
var ( | ||
requiredClaims = []string{"exp", "iat", "name", "email"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: doc would be helpful here.
} | ||
|
||
// Authenticate user and add authorization information back in the context | ||
func (s *sdkGrpcServer) auth(ctx context.Context) (context.Context, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When does this auth(ctx) function get invoked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by the interceptor which is setup during registration and setup of the gRPC server.
f13f445
to
b48bc38
Compare
Signed-off-by: Luis Pabón <luis@portworx.com>
Previously the REST Gateway would directly connect to the gRPC port. Now with HTTPS/TLS support, this is more complicated since the CERTS would have a CN not labeled 'localhost'. For simplicity the SDK now also has a unix domain socket. This UDS is used for local access and for the REST Gateway to communicate with the gRPC server. All models support token authentication and authorization based on JWTs. All SDK unit tests run with HTTPS enabled. Signed-off-by: Luis Pabón <luis@portworx.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
🎉 😄 |
What this PR does / why we need it:
OpenStorage SDK will be the method of accessing any internal driver or cluster APIs. This PR provides a foundation for auth work.
Which issue(s) this PR fixes (optional)
Part of #448
Special notes for your reviewer:
It also has the following enhancements: