Skip to content
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

Merged
merged 2 commits into from
Dec 7, 2018

Conversation

lpabon
Copy link
Member

@lpabon lpabon commented Dec 4, 2018

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:

  • HTTPS support for gRPC
    • HTTPS is now used in all unit tests
  • Unix Domain Socket for the SDK
  • Interceptors
    • Token based authentication and validation
    • Audit
    • Authorization
    • Logging

Copy link
Contributor

@ggriffiths ggriffiths left a 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

}

// Create REST Gateway and connect it to the unix domain socket server
restGeteway, err := newSdkRestGateway(config, udsServer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: restGeteway -> restGateway

Copy link
Member Author

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
Copy link
Contributor

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct.

err.Error())
}
}()
<-ready
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct 👍

Copy link
Member Author

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"
Copy link
Contributor

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

Copy link
Member Author

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)
Copy link
Contributor

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?

Copy link
Member Author

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 (
Copy link
Contributor

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

Copy link
Member Author

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)
Copy link
Contributor

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

Copy link
Member Author

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 {
Copy link
Contributor

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

Copy link
Member Author

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")
Copy link
Contributor

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

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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) {
Copy link
Contributor

@ggriffiths ggriffiths Dec 4, 2018

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

Copy link
Member Author

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.

@ggriffiths
Copy link
Contributor

lgtm 👍


// REST Gateway Handlers
handlers := []func(context.Context, *runtime.ServeMux, *grpc.ClientConn) (err error){
api.RegisterOpenStorageClusterHandler,
Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that works.

Copy link
Member Author

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)
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

return err
}

if len(s.config.RestPort) != 0 {
Copy link
Contributor

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

}

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

@adityadani adityadani Dec 5, 2018

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

Suggested change
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()
Copy link
Contributor

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")
Copy link
Contributor

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

Copy link
Member Author

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")
Copy link
Contributor

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 &&
Copy link
Contributor

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"}
Copy link
Contributor

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) {
Copy link
Contributor

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?

Copy link
Member Author

@lpabon lpabon Dec 5, 2018

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.

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>
Copy link
Contributor

@adityadani adityadani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@lpabon lpabon merged commit 7ad3786 into libopenstorage:master Dec 7, 2018
@lpabon
Copy link
Member Author

lpabon commented Dec 7, 2018

🎉 😄

@lpabon lpabon deleted the sdk-auth-4 branch December 7, 2018 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants