Welcome to mirror list, hosted at ThFree Co, Russian Federation.

token.go « auth - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 3eccacbe048dc664d4c867d024993a5139ab8e02 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package gitalyauth

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"
	"time"

	grpcmwauth "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

var (
	//nolint:gochecknoglobals
	// This infrastructure is required for testing purposes and there is no
	// proper place to put it instead. While we could move it into the
	// config, we certainly don't want to make it configurable for now, so
	// it'd be a bad fit there.
	tokenValidityDuration = 30 * time.Second

	errUnauthenticated = status.Errorf(codes.Unauthenticated, "authentication required")
	errDenied          = status.Errorf(codes.PermissionDenied, "permission denied")

	authErrors = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name: "gitaly_authentication_errors_total",
			Help: "Counts of of Gitaly request authentication errors",
		},
		[]string{"version", "error"},
	)
)

// TokenValidityDuration returns the duration for which any token will be
// valid. This is currently only used by our testing infrastructure.
func TokenValidityDuration() time.Duration {
	return tokenValidityDuration
}

// SetTokenValidityDuration changes the duration for which any token will be
// valid. It only applies to newly created tokens.
func SetTokenValidityDuration(d time.Duration) {
	tokenValidityDuration = d
}

// 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
	}

	if authInfo.Version == "v2" {
		if v2HmacInfoValid(authInfo.Message, authInfo.SignedMessage, []byte(secret), targetTime, tokenValidityDuration) {
			return nil
		}
	}

	return errDenied
}

// ExtractAuthInfo returns an `AuthInfo` with the data extracted from `ctx`
func ExtractAuthInfo(ctx context.Context) (*AuthInfo, error) {
	token, err := grpcmwauth.AuthFromMD(ctx, "bearer")
	if err != nil {
		return nil, err
	}

	split := strings.SplitN(token, ".", 3)

	if len(split) != 3 {
		return nil, fmt.Errorf("invalid token format")
	}

	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 countV2Error(message string) { authErrors.WithLabelValues("v2", message).Inc() }

func v2HmacInfoValid(message string, signedMessage, secret []byte, targetTime time.Time, tokenValidity time.Duration) bool {
	expectedHMAC := hmacSign(secret, message)
	if !hmac.Equal(signedMessage, expectedHMAC) {
		countV2Error("wrong hmac signature")
		return false
	}

	timestamp, err := strconv.ParseInt(message, 10, 64)
	if err != nil {
		countV2Error("cannot parse timestamp")
		return false
	}

	issuedAt := time.Unix(timestamp, 0)
	lowerBound := targetTime.Add(-tokenValidity)
	upperBound := targetTime.Add(tokenValidity)

	if issuedAt.Before(lowerBound) {
		countV2Error("timestamp too old")
		return false
	}

	if issuedAt.After(upperBound) {
		countV2Error("timestamp too new")
		return false
	}

	return true
}

func hmacSign(secret []byte, message string) []byte {
	mac := hmac.New(sha256.New, secret)
	// hash.Hash never returns an error.
	_, _ = mac.Write([]byte(message))

	return mac.Sum(nil)
}