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

feat: Auto-refresh github token #3185

Merged
merged 11 commits into from
Apr 17, 2024
318 changes: 220 additions & 98 deletions api/gen/proto/go/vcs/v1/vcs.pb.go

Large diffs are not rendered by default.

336 changes: 336 additions & 0 deletions api/gen/proto/go/vcs/v1/vcs_vtproto.pb.go

Large diffs are not rendered by default.

48 changes: 39 additions & 9 deletions api/gen/proto/go/vcs/v1/vcsv1connect/vcs.connect.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions api/gen/proto/go/vcs/v1/vcsv1connect/vcs.connect.mux.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions api/openapiv2/gen/phlare.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,14 @@
}
}
},
"v1GithubRefreshResponse": {
"type": "object",
"properties": {
"cookie": {
"type": "string"
}
}
},
"v1Hints": {
"type": "object",
"properties": {
Expand Down
7 changes: 7 additions & 0 deletions api/vcs/v1/vcs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package vcs.v1;
service VCSService {
rpc GithubApp(GithubAppRequest) returns (GithubAppResponse) {}
rpc GithubLogin(GithubLoginRequest) returns (GithubLoginResponse) {}
rpc GithubRefresh(GithubRefreshRequest) returns (GithubRefreshResponse) {}
rpc GetFile(GetFileRequest) returns (GetFileResponse) {}
rpc GetCommit(GetCommitRequest) returns (GetCommitResponse) {}
}
Expand All @@ -22,6 +23,12 @@ message GithubLoginResponse {
string cookie = 1;
}

message GithubRefreshRequest {}

message GithubRefreshResponse {
string cookie = 1;
}

message GetFileRequest {
// the full path to the repository
string repositoryURL = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -620,10 +620,6 @@ lifecycler:
# values:
#
# Secure Ciphers:
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_AES_128_GCM_SHA256
# - TLS_AES_256_GCM_SHA384
# - TLS_CHACHA20_POLY1305_SHA256
Expand All @@ -641,7 +637,11 @@ lifecycler:
# Insecure Ciphers:
# - TLS_RSA_WITH_RC4_128_SHA
# - TLS_RSA_WITH_3DES_EDE_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA256
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
Expand Down Expand Up @@ -977,10 +977,6 @@ sharding_ring:
# values:
#
# Secure Ciphers:
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_AES_128_GCM_SHA256
# - TLS_AES_256_GCM_SHA384
# - TLS_CHACHA20_POLY1305_SHA256
Expand All @@ -998,7 +994,11 @@ sharding_ring:
# Insecure Ciphers:
# - TLS_RSA_WITH_RC4_128_SHA
# - TLS_RSA_WITH_3DES_EDE_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA256
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
Expand Down Expand Up @@ -1326,10 +1326,6 @@ sharding_ring:
# values:
#
# Secure Ciphers:
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_AES_128_GCM_SHA256
# - TLS_AES_256_GCM_SHA384
# - TLS_CHACHA20_POLY1305_SHA256
Expand All @@ -1347,7 +1343,11 @@ sharding_ring:
# Insecure Ciphers:
# - TLS_RSA_WITH_RC4_128_SHA
# - TLS_RSA_WITH_3DES_EDE_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA256
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
Expand Down Expand Up @@ -1534,10 +1534,6 @@ backoff_config:
# Override the default cipher suite list (separated by commas). Allowed values:
#
# Secure Ciphers:
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_AES_128_GCM_SHA256
# - TLS_AES_256_GCM_SHA384
# - TLS_CHACHA20_POLY1305_SHA256
Expand All @@ -1555,7 +1551,11 @@ backoff_config:
# Insecure Ciphers:
# - TLS_RSA_WITH_RC4_128_SHA
# - TLS_RSA_WITH_3DES_EDE_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA256
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
Expand Down Expand Up @@ -1747,10 +1747,6 @@ The `memberlist` block configures the Gossip memberlist.
# Override the default cipher suite list (separated by commas). Allowed values:
#
# Secure Ciphers:
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_AES_128_GCM_SHA256
# - TLS_AES_256_GCM_SHA384
# - TLS_CHACHA20_POLY1305_SHA256
Expand All @@ -1768,7 +1764,11 @@ The `memberlist` block configures the Gossip memberlist.
# Insecure Ciphers:
# - TLS_RSA_WITH_RC4_128_SHA
# - TLS_RSA_WITH_3DES_EDE_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA
# - TLS_RSA_WITH_AES_256_CBC_SHA
# - TLS_RSA_WITH_AES_128_CBC_SHA256
# - TLS_RSA_WITH_AES_128_GCM_SHA256
# - TLS_RSA_WITH_AES_256_GCM_SHA384
# - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_RC4_128_SHA
# - TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
Expand Down
4 changes: 4 additions & 0 deletions pkg/frontend/frontend_vcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ func (f *Frontend) GithubLogin(ctx context.Context, req *connect.Request[vcsv1.G
return connectgrpc.RoundTripUnary[vcsv1.GithubLoginRequest, vcsv1.GithubLoginResponse](ctx, f, req)
}

func (f *Frontend) GithubRefresh(ctx context.Context, req *connect.Request[vcsv1.GithubRefreshRequest]) (*connect.Response[vcsv1.GithubRefreshResponse], error) {
return connectgrpc.RoundTripUnary[vcsv1.GithubRefreshRequest, vcsv1.GithubRefreshResponse](ctx, f, req)
}

func (f *Frontend) GetFile(ctx context.Context, req *connect.Request[vcsv1.GetFileRequest]) (*connect.Response[vcsv1.GetFileResponse], error) {
return connectgrpc.RoundTripUnary[vcsv1.GetFileRequest, vcsv1.GetFileResponse](ctx, f, req)
}
Expand Down
86 changes: 3 additions & 83 deletions pkg/querier/vcs/client/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,102 +5,22 @@ import (
"errors"
"fmt"
"net/http"
"os"
"time"

"connectrpc.com/connect"
"github.com/google/go-github/v58/github"
"golang.org/x/oauth2"
o2endpoints "golang.org/x/oauth2/endpoints"

vcsv1 "github.com/grafana/pyroscope/api/gen/proto/go/vcs/v1"
)

var (
GithubAppClientID = os.Getenv("GITHUB_CLIENT_ID")
githubAppClientSecret = os.Getenv("GITHUB_CLIENT_SECRET")
githubSessionSecret = []byte(os.Getenv("GITHUB_SESSION_SECRET"))
)

const (
gitHubCookieName = "GitSession"
)

// githubOAuth returns a github oauth2 config.
// Returns an error if the environment variables are not set.
func githubOAuth() (*oauth2.Config, error) {
if GithubAppClientID == "" {
return nil, errors.New("missing GITHUB_CLIENT_ID environment variable")
}
if githubAppClientSecret == "" {
return nil, errors.New("missing GITHUB_CLIENT_SECRET environment variable")
}
return &oauth2.Config{
ClientID: GithubAppClientID,
ClientSecret: githubAppClientSecret,
Endpoint: o2endpoints.GitHub,
}, nil
}
Comment on lines -19 to -43
Copy link
Contributor Author

Choose a reason for hiding this comment

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

A lot of this OAuth stuff got abstracted to token.go


// GithubClient returns a github client for the given request headers.
func GithubClient(ctx context.Context, requestHeaders http.Header) (*githubClient, error) {
auth, err := githubOAuth()
if err != nil {
return nil, err
}
cookie, err := (&http.Request{Header: requestHeaders}).Cookie(gitHubCookieName)
if err != nil {
return nil, err
}
token, err := decryptToken(cookie.Value, githubSessionSecret)
if err != nil {
return nil, unAuthorizeError(err, cookie)
}
if !token.Valid() {
return nil, unAuthorizeError(errors.New("invalid or expired token"), cookie)
}
// GithubClient returns a github client.
func GithubClient(ctx context.Context, token *oauth2.Token) (*githubClient, error) {
return &githubClient{
client: github.NewClient(auth.Client(ctx, token)),
client: github.NewClient(nil).WithAuthToken(token.AccessToken),
}, nil
}

func unAuthorizeError(err error, cookie *http.Cookie) error {
connectErr := connect.NewError(
connect.CodeUnauthenticated,
err,
)
cookie.Value = ""
cookie.MaxAge = -1
connectErr.Meta().Set("Set-Cookie", cookie.String())
return connectErr
}

func AuthorizeGithub(ctx context.Context, authorizationCode string) (string, error) {
auth, err := githubOAuth()
if err != nil {
return "", err
}
token, err := auth.Exchange(ctx, authorizationCode)
if err != nil {
return "", err
}
cookieValue, err := encryptToken(token, githubSessionSecret)
if err != nil {
return "", err
}
// Sets a cookie with the encrypted token.
// Only the server can decrypt the cookie.
cookie := http.Cookie{
Name: gitHubCookieName,
Value: cookieValue,
Expires: token.Expiry.Add(-10 * time.Second),
HttpOnly: false,
Secure: true,
SameSite: http.SameSiteLaxMode,
}
return cookie.String(), nil
}

type githubClient struct {
client *github.Client
}
Expand Down
Loading
Loading