diff options
author | Alejandro RodrÃguez <alejorro70@gmail.com> | 2018-10-06 18:12:01 -0300 |
---|---|---|
committer | Alejandro RodrÃguez <alejorro70@gmail.com> | 2018-10-09 20:56:42 -0300 |
commit | d586e1f39352ffc47abdb927c4886ed7070260ea (patch) | |
tree | 99317862b725b0d96ed350b13621ff4a57843b24 /go/vendor/gitlab.com | |
parent | 019d85a58921c2834aee3ddef666c6730fd60883 (diff) | |
download | gitlab-shell-gitaly-auth-v2.tar.gz |
Use Gitaly's v2 auth schemegitaly-auth-v2
Diffstat (limited to 'go/vendor/gitlab.com')
-rw-r--r-- | go/vendor/gitlab.com/gitlab-org/gitaly/auth/rpccredentials.go | 31 | ||||
-rw-r--r-- | go/vendor/gitlab.com/gitlab-org/gitaly/auth/token.go | 119 |
2 files changed, 150 insertions, 0 deletions
diff --git a/go/vendor/gitlab.com/gitlab-org/gitaly/auth/rpccredentials.go b/go/vendor/gitlab.com/gitlab-org/gitaly/auth/rpccredentials.go index cbe94c2..c35cd4d 100644 --- a/go/vendor/gitlab.com/gitlab-org/gitaly/auth/rpccredentials.go +++ b/go/vendor/gitlab.com/gitlab-org/gitaly/auth/rpccredentials.go @@ -2,6 +2,9 @@ package gitalyauth import ( "encoding/base64" + "fmt" + "strconv" + "time" "golang.org/x/net/context" "google.golang.org/grpc/credentials" @@ -23,3 +26,31 @@ func (*rpcCredentials) RequireTransportSecurity() bool { return false } func (rc *rpcCredentials) GetRequestMetadata(context.Context, ...string) (map[string]string, error) { return map[string]string{"authorization": "Bearer " + rc.token}, nil } + +// RPCCredentialsV2 can be used with grpc.WithPerRPCCredentials to create a +// grpc.DialOption that inserts an HMAC token with the current timestamp +// for authentication with a Gitaly server. +func RPCCredentialsV2(token string) credentials.PerRPCCredentials { + return &rpcCredentialsV2{token: token} +} + +type rpcCredentialsV2 struct { + token string +} + +func (*rpcCredentialsV2) RequireTransportSecurity() bool { return false } + +func (rc *rpcCredentialsV2) GetRequestMetadata(context.Context, ...string) (map[string]string, error) { + return map[string]string{"authorization": "Bearer " + rc.hmacToken()}, nil +} + +func (rc *rpcCredentialsV2) hmacToken() string { + return hmacToken("v2", []byte(rc.token), time.Now()) +} + +func hmacToken(version string, secret []byte, timestamp time.Time) string { + intTime := timestamp.Unix() + signedTimestamp := hmacSign(secret, strconv.FormatInt(intTime, 10)) + + return fmt.Sprintf("%s.%x.%d", version, signedTimestamp, intTime) +} diff --git a/go/vendor/gitlab.com/gitlab-org/gitaly/auth/token.go b/go/vendor/gitlab.com/gitlab-org/gitaly/auth/token.go new file mode 100644 index 0000000..e1c1966 --- /dev/null +++ b/go/vendor/gitlab.com/gitlab-org/gitaly/auth/token.go @@ -0,0 +1,119 @@ +package gitalyauth + +import ( + "crypto/hmac" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "encoding/hex" + "strconv" + "strings" + "time" + + "github.com/grpc-ecosystem/go-grpc-middleware/auth" + "golang.org/x/net/context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + timestampThreshold = 30 * time.Second +) + +var ( + errUnauthenticated = status.Errorf(codes.Unauthenticated, "authentication required") + errDenied = status.Errorf(codes.PermissionDenied, "permission denied") +) + +// AuthInfo contains the authentication information coming from a request +type AuthInfo struct { + Version string + SignedMessage []byte + Message string +} + +// CheckToken checks the 'authentication' header of incoming gRPC +// metadata in ctx. It returns nil if and only if the token matches +// secret. +func CheckToken(ctx context.Context, secret string, targetTime time.Time) error { + if len(secret) == 0 { + panic("CheckToken: secret may not be empty") + } + + authInfo, err := ExtractAuthInfo(ctx) + if err != nil { + return errUnauthenticated + } + + switch authInfo.Version { + case "v1": + decodedToken, err := base64.StdEncoding.DecodeString(authInfo.Message) + if err != nil { + return errUnauthenticated + } + + if tokensEqual(decodedToken, []byte(secret)) { + return nil + } + case "v2": + if hmacInfoValid(authInfo.Message, authInfo.SignedMessage, []byte(secret), targetTime, timestampThreshold) { + return nil + } + } + + return errDenied +} + +func tokensEqual(tok1, tok2 []byte) bool { + return subtle.ConstantTimeCompare(tok1, tok2) == 1 +} + +// ExtractAuthInfo returns an `AuthInfo` with the data extracted from `ctx` +func ExtractAuthInfo(ctx context.Context) (*AuthInfo, error) { + token, err := grpc_auth.AuthFromMD(ctx, "bearer") + + if err != nil { + return nil, err + } + + split := strings.SplitN(string(token), ".", 3) + + // v1 is base64-encoded using base64.StdEncoding, which cannot contain a ".". + // A v1 token cannot slip through here. + if len(split) != 3 { + return &AuthInfo{Version: "v1", Message: token}, nil + } + + version, sig, msg := split[0], split[1], split[2] + decodedSig, err := hex.DecodeString(sig) + if err != nil { + return nil, err + } + + return &AuthInfo{Version: version, SignedMessage: decodedSig, Message: msg}, nil +} + +func hmacInfoValid(message string, signedMessage, secret []byte, targetTime time.Time, timestampThreshold time.Duration) bool { + expectedHMAC := hmacSign(secret, message) + if !hmac.Equal(signedMessage, expectedHMAC) { + return false + } + + timestamp, err := strconv.ParseInt(message, 10, 64) + if err != nil { + return false + } + + issuedAt := time.Unix(timestamp, 0) + lowerBound := targetTime.Add(-timestampThreshold) + upperBound := targetTime.Add(timestampThreshold) + + return issuedAt.After(lowerBound) && issuedAt.Before(upperBound) +} + +func hmacSign(secret []byte, message string) []byte { + mac := hmac.New(sha256.New, secret) + mac.Write([]byte(message)) + + return mac.Sum(nil) +} |