summaryrefslogtreecommitdiff
path: root/go/vendor/gitlab.com
diff options
context:
space:
mode:
Diffstat (limited to 'go/vendor/gitlab.com')
-rw-r--r--go/vendor/gitlab.com/gitlab-org/gitaly/auth/rpccredentials.go31
-rw-r--r--go/vendor/gitlab.com/gitlab-org/gitaly/auth/token.go119
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)
+}