Skip to content

Commit

Permalink
Reapply 6be9232 (#710)
Browse files Browse the repository at this point in the history
* reapply previous commit

* add all the things
  • Loading branch information
wbrowne authored Jun 29, 2023
1 parent 792f8fd commit 8ea68e1
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 2 deletions.
57 changes: 57 additions & 0 deletions experimental/oauthtokenretriever/sign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package oauthtokenretriever

import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"

"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
)

type signer interface {
sign(payload interface{}) (string, error)
}

type jwtSigner struct {
signer jose.Signer
}

// parsePrivateKey parses a PEM encoded private key.
func parsePrivateKey(pemBytes []byte) (signer, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("crypto: no key found")
}

var rawkey interface{}
var alg jose.SignatureAlgorithm
switch block.Type {
case "RSA PRIVATE KEY":
alg = jose.RS256
rsa, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
rawkey = rsa
case "PRIVATE KEY":
alg = jose.ES256
ecdsa, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
rawkey = ecdsa
default:
return nil, fmt.Errorf("crypto: unsupported private key type %q", block.Type)
}
s, err := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: rawkey}, &jose.SignerOptions{})
if err != nil {
return nil, err
}
return &jwtSigner{signer: s}, nil
}

func (s *jwtSigner) sign(payload interface{}) (string, error) {
return jwt.Signed(s.signer).Claims(payload).CompactSerialize()
}
51 changes: 51 additions & 0 deletions experimental/oauthtokenretriever/sign_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package oauthtokenretriever

import (
"testing"

"github.com/stretchr/testify/assert"
)

const (
testRSAKey = `-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQC35vznv35Kaby20gu+RQBDj/kHhPd64b6p9TKKxqiAs8kukNFj
Q8keR6MOO41Md0Jh4b/ZSo1O3C3K3K587NORJDWz0H2wVyTWDvSMI36nI/EnGDhh
4fImv5E/9jIvhOxCJ3Dej57//tMt8TEG1ZETrAKzUvB7EfCfsnazGraMQwIDAQAB
AoGAfbFh4B+w+LlGY4oyvow4vvTTV4FZCOLsRwuwzMs09iprcelHQ9pbxtddqeeo
DsBgXbhHQQPEi0bQAZxNolLX0m4nQ8n9H6by42qOJlwywYZIl7Di3aWYiOiT56v7
PfqCsShSqsvWH8Ok4Jy6/Vcc4QcO4mGi8y8EZdSqfytGvkkCQQDhO+1Y4x36ETAh
NOQx1E/psPuSH8H6YeDoWYeap5z1KXzN4eTo01p8ckPSD93uXIig7LmfIWPMqlGV
yOBSyqD/AkEA0QXBLeDksi8hX8B2XOMfY9hWOBwBRXrlKX6TVF/9Kw+ulJpe3sU5
lc53oytpk1VwXAfJrjNRqyIIIRnFyTJQvQJAMBgFxFcqzXziFBUhLOqy7amW7krN
ttMznSmQ5RspTsg/GA9GO9j1l2EmzjIJJ56mpgYmVK5iiw9LQHqWO9d8rQJASUDz
CtkeTTQnRh91W+hdP+i5jsCB0Y/YcEpj59YcK9M7I+lWBkyoec/6Lb0xKuluj1JL
ZDmoDYnHv5IAtxpjIQJASxC/V51AHfuQ+rWvbZ6jzoHW6owbFpC2RbZPtFanOlda
ozjy/YI5hvWLr/bre/wZ3N81pLA9lPgEpJiOPYem3Q==
-----END RSA PRIVATE KEY-----
`
testECDSAKey = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgYH3q1su2TRDIr4RB
2okegCNvfhn/Q9CycAXtPnfYsZehRANCAARSs6LcDI314KqKqGHbv2FLGoMXjm6B
p6/mP7VLRqyPpiGmhCEKXD5R/695X5JYQRBF34hn2XZpMCW2z2Lr+d6s
-----END PRIVATE KEY-----
`
)

func Test_Sign(t *testing.T) {
for _, test := range []struct {
name string
key string
length int
}{
{"RSA", testRSAKey, 196},
{"ECDSA", testECDSAKey, 111},
} {
t.Run(test.name, func(t *testing.T) {
signer, err := parsePrivateKey([]byte(test.key))
assert.NoError(t, err)
signed, err := signer.sign(map[string]interface{}{})
assert.NoError(t, err)
assert.Equal(t, test.length, len(signed))
})
}
}
107 changes: 107 additions & 0 deletions experimental/oauthtokenretriever/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package oauthtokenretriever

import (
"context"
"fmt"
"net/url"
"os"
"strings"
"time"

"github.com/google/uuid"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
)

type TokenRetriever interface {
OnBehalfOfUser(ctx context.Context, userID string) (string, error)
Self(ctx context.Context) (string, error)
}

type tokenRetriever struct {
signer signer
conf *clientcredentials.Config
}

// tokenPayload returns a JWT payload for the given user ID, client ID, and host.
func (t *tokenRetriever) tokenPayload(userID string) map[string]interface{} {
iat := time.Now().Unix()
exp := iat + 1800
u := uuid.New()
payload := map[string]interface{}{
"iss": t.conf.ClientID,
"sub": fmt.Sprintf("user:id:%s", userID),
"aud": t.conf.TokenURL,
"exp": exp,
"iat": iat,
"jti": u.String(),
}
return payload
}

func (t *tokenRetriever) Self(ctx context.Context) (string, error) {
t.conf.EndpointParams = url.Values{}
tok, err := t.conf.TokenSource(ctx).Token()
if err != nil {
return "", err
}
return tok.AccessToken, nil
}

func (t *tokenRetriever) OnBehalfOfUser(ctx context.Context, userID string) (string, error) {
signed, err := t.signer.sign(t.tokenPayload(userID))
if err != nil {
return "", err
}

t.conf.EndpointParams = url.Values{
"grant_type": {"urn:ietf:params:oauth:grant-type:jwt-bearer"},
"assertion": {signed},
}
tok, err := t.conf.TokenSource(ctx).Token()
if err != nil {
return "", err
}

return tok.AccessToken, nil
}

func New() (TokenRetriever, error) {
// The Grafana URL is required to obtain tokens later on
grafanaAppURL := strings.TrimRight(os.Getenv("GF_APP_URL"), "/")
if grafanaAppURL == "" {
// For debugging purposes only
grafanaAppURL = "http://localhost:3000"
}

clientID := os.Getenv("GF_PLUGIN_APP_CLIENT_ID")
if clientID == "" {
return nil, fmt.Errorf("GF_PLUGIN_APP_CLIENT_ID is required")
}

clientSecret := os.Getenv("GF_PLUGIN_APP_CLIENT_SECRET")
if clientSecret == "" {
return nil, fmt.Errorf("GF_PLUGIN_APP_CLIENT_SECRET is required")
}

privateKey := os.Getenv("GF_PLUGIN_APP_PRIVATE_KEY")
if privateKey == "" {
return nil, fmt.Errorf("GF_PLUGIN_APP_PRIVATE_KEY is required")
}

signer, err := parsePrivateKey([]byte(privateKey))
if err != nil {
return nil, err
}

return &tokenRetriever{
signer: signer,
conf: &clientcredentials.Config{
ClientID: clientID,
ClientSecret: clientSecret,
TokenURL: grafanaAppURL + "/oauth2/token",
AuthStyle: oauth2.AuthStyleInParams,
Scopes: []string{"profile", "email", "entitlements"},
},
}, nil
}
64 changes: 64 additions & 0 deletions experimental/oauthtokenretriever/token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package oauthtokenretriever

import (
"context"
"io"
"net/http"
"net/http/httptest"
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func Test_GetExternalServiceToken(t *testing.T) {
for _, test := range []struct {
name string
userID string
}{
{"On Behalf Of", "1"},
{"Service account", ""},
} {
t.Run(test.name, func(t *testing.T) {
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := io.ReadAll(r.Body)
assert.NoError(t, err)
if test.userID != "" {
assert.Contains(t, string(b), "assertion=")
assert.Contains(t, string(b), "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer")
} else {
assert.NotContains(t, string(b), "assertion=")
assert.Contains(t, string(b), "grant_type=client_credentials")
}
assert.Contains(t, string(b), "client_id=test_client_id")
assert.Contains(t, string(b), "client_secret=test_client_secret")

w.Header().Set("Content-Type", "application/json")
_, err = w.Write([]byte(`{"access_token":"test_token"}`))
assert.NoError(t, err)
}))
defer s.Close()

os.Setenv("GF_APP_URL", s.URL)
defer os.Unsetenv("GF_APP_URL")
os.Setenv("GF_PLUGIN_APP_CLIENT_ID", "test_client_id")
defer os.Unsetenv("GF_PLUGIN_APP_CLIENT_ID")
os.Setenv("GF_PLUGIN_APP_CLIENT_SECRET", "test_client_secret")
defer os.Unsetenv("GF_PLUGIN_APP_CLIENT_SECRET")
os.Setenv("GF_PLUGIN_APP_PRIVATE_KEY", testECDSAKey)
defer os.Unsetenv("GF_PLUGIN_APP_PRIVATE_KEY")

ss, err := New()
assert.NoError(t, err)

var token string
if test.userID != "" {
token, err = ss.OnBehalfOfUser(context.Background(), test.userID)
} else {
token, err = ss.Self(context.Background())
}
assert.NoError(t, err)
assert.Equal(t, "test_token", token)
})
}
}
4 changes: 2 additions & 2 deletions experimental/testdata/folder.golden.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Frame[0] {
"pathSeparator": "/"
}
Name:
Dimensions: 2 Fields by 16 Rows
Dimensions: 2 Fields by 17 Rows
+----------------------+------------------+
| Name: name | Name: media-type |
| Labels: | Labels: |
Expand All @@ -29,4 +29,4 @@ Dimensions: 2 Fields by 16 Rows


====== TEST DATA RESPONSE (arrow base64) ======
FRAME=QVJST1cxAAD/////yAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEEAAoADAAAAAgABAAKAAAACAAAALgAAAADAAAATAAAACgAAAAEAAAAwP7//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADg/v//CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAD///8IAAAAUAAAAEQAAAB7InR5cGUiOiJkaXJlY3RvcnktbGlzdGluZyIsInR5cGVWZXJzaW9uIjpbMCwwXSwicGF0aFNlcGFyYXRvciI6Ii8ifQAAAAAEAAAAbWV0YQAAAAACAAAAeAAAAAQAAACi////FAAAADwAAAA8AAAAAAAABTgAAAABAAAABAAAAJD///8IAAAAEAAAAAYAAABzdHJpbmcAAAYAAAB0c3R5cGUAAAAAAACI////CgAAAG1lZGlhLXR5cGUAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAASAAAAAAAAAVEAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAYAAABzdHJpbmcAAAYAAAB0c3R5cGUAAAAAAAAEAAQABAAAAAQAAABuYW1lAAAAAP/////YAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAqAEAAAAAAAAUAAAAAAAAAwQACgAYAAwACAAEAAoAAAAUAAAAeAAAABAAAAAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAEgAAAAAAAAAzQAAAAAAAAAYAQAAAAAAAAAAAAAAAAAAGAEAAAAAAABEAAAAAAAAAGABAAAAAAAASAAAAAAAAAAAAAAAAgAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAkAAAAQAAAAGgAAACgAAAArAAAANgAAAEYAAABVAAAAaQAAAIMAAACiAAAArQAAALMAAAC3AAAAxQAAAM0AAAAAAAAAUkVBRE1FLm1kYWN0aW9uc2F1dGhjbGllbnRkYXRhc291cmNldGVzdGUyZWZpbGVpbmZvLmdvZmlsZWluZm9fdGVzdC5nb2ZyYW1lX3NvcnRlci5nb2ZyYW1lX3NvcnRlcl90ZXN0LmdvZ29sZGVuX3Jlc3BvbnNlX2NoZWNrZXIuZ29nb2xkZW5fcmVzcG9uc2VfY2hlY2tlcl90ZXN0LmdvaHR0cF9sb2dnZXJtYWNyb3Ntb2NrcmVzdF9jbGllbnQuZ290ZXN0ZGF0YQAAAAAAAAAAAAAACQAAABIAAAAbAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAAC0AAAA2AAAAPwAAAD8AAABIAAAAAAAAAGRpcmVjdG9yeWRpcmVjdG9yeWRpcmVjdG9yeWRpcmVjdG9yeWRpcmVjdG9yeWRpcmVjdG9yeWRpcmVjdG9yeWRpcmVjdG9yeRAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAAEAAEAAADYAQAAAAAAAOAAAAAAAAAAqAEAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAALgAAAADAAAATAAAACgAAAAEAAAAwP7//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADg/v//CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAD///8IAAAAUAAAAEQAAAB7InR5cGUiOiJkaXJlY3RvcnktbGlzdGluZyIsInR5cGVWZXJzaW9uIjpbMCwwXSwicGF0aFNlcGFyYXRvciI6Ii8ifQAAAAAEAAAAbWV0YQAAAAACAAAAeAAAAAQAAACi////FAAAADwAAAA8AAAAAAAABTgAAAABAAAABAAAAJD///8IAAAAEAAAAAYAAABzdHJpbmcAAAYAAAB0c3R5cGUAAAAAAACI////CgAAAG1lZGlhLXR5cGUAAAAAEgAYABQAAAATAAwAAAAIAAQAEgAAABQAAABEAAAASAAAAAAAAAVEAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAYAAABzdHJpbmcAAAYAAAB0c3R5cGUAAAAAAAAEAAQABAAAAAQAAABuYW1lAAAAAPgBAABBUlJPVzE=
FRAME=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
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
Expand Down Expand Up @@ -86,6 +87,7 @@ require (
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.14.0 // indirect
go.opentelemetry.io/otel/metric v0.37.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmn
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
Expand Down Expand Up @@ -368,7 +370,9 @@ go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down

0 comments on commit 8ea68e1

Please sign in to comment.