aboutsummaryrefslogtreecommitdiffstats
path: root/dashboard/app
diff options
context:
space:
mode:
authorGreg Steuck <gnezdo@google.com>2021-07-28 11:03:25 -0700
committerDmitry Vyukov <dvyukov@google.com>2021-07-30 18:21:17 +0200
commit5bfcec7dfd4ba51d38b41cea770ecc96e7e59d4d (patch)
treeef129d2b1e22acfd1c790c8189d7776a82daba91 /dashboard/app
parent14f590a6a765d9fbe53e2f7bacb5d9f6d7cb9063 (diff)
pkg/auth: move auth code into a new package for reuse in syz-hub
Diffstat (limited to 'dashboard/app')
-rw-r--r--dashboard/app/api.go7
-rw-r--r--dashboard/app/app_test.go3
-rw-r--r--dashboard/app/auth.go126
-rw-r--r--dashboard/app/auth_test.go99
-rw-r--r--dashboard/app/config.go3
5 files changed, 8 insertions, 230 deletions
diff --git a/dashboard/app/api.go b/dashboard/app/api.go
index 748d7abb2..4107eb200 100644
--- a/dashboard/app/api.go
+++ b/dashboard/app/api.go
@@ -17,6 +17,7 @@ import (
"unicode/utf8"
"github.com/google/syzkaller/dashboard/dashapi"
+ "github.com/google/syzkaller/pkg/auth"
"github.com/google/syzkaller/pkg/email"
"github.com/google/syzkaller/pkg/hash"
"github.com/google/syzkaller/sys/targets"
@@ -104,8 +105,8 @@ func handleAPI(c context.Context, r *http.Request) (reply interface{}, err error
client := r.PostFormValue("client")
method := r.PostFormValue("method")
log.Infof(c, "api %q from %q", method, client)
- auth := makeAuthEndpoint(googleTokenInfoEndpoint)
- subj, err := auth.determineAuthSubj(timeNow(c), r.Header["Authorization"])
+ auth := auth.MakeEndpoint(auth.GoogleTokenInfoEndpoint)
+ subj, err := auth.DetermineAuthSubj(timeNow(c), r.Header["Authorization"])
if err != nil {
return nil, err
}
@@ -1351,7 +1352,7 @@ func GetEmails(r dashapi.Recipients, filter dashapi.RecipientType) []string {
// corresponding namespace.
func checkClient(conf *GlobalConfig, name0, secretPassword, oauthSubject string) (string, error) {
checkAuth := func(ns, a string) (string, error) {
- if strings.HasPrefix(a, oauthMagic) && a == oauthSubject {
+ if strings.HasPrefix(a, auth.OauthMagic) && a == oauthSubject {
return ns, nil
}
if a != secretPassword {
diff --git a/dashboard/app/app_test.go b/dashboard/app/app_test.go
index 49df21d0d..777b73ce8 100644
--- a/dashboard/app/app_test.go
+++ b/dashboard/app/app_test.go
@@ -13,6 +13,7 @@ import (
"time"
"github.com/google/syzkaller/dashboard/dashapi"
+ "github.com/google/syzkaller/pkg/auth"
"github.com/google/syzkaller/sys/targets"
"google.golang.org/appengine/user"
)
@@ -57,7 +58,7 @@ var testConfig = &GlobalConfig{
FixBisectionAutoClose: true,
Clients: map[string]string{
client1: password1,
- "oauth": oauthMagic + "111111122222222",
+ "oauth": auth.OauthMagic + "111111122222222",
},
Repos: []KernelRepo{
{
diff --git a/dashboard/app/auth.go b/dashboard/app/auth.go
deleted file mode 100644
index a6da8e24d..000000000
--- a/dashboard/app/auth.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2021 syzkaller project authors. All rights reserved.
-// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
-
-// Relies on tokeninfo because it is properly documented:
-// https://developers.google.com/identity/protocols/oauth2/openid-connect#validatinganidtoken
-
-// The client
-// The VM that wants to invoke the API:
-// 1) Gets a token from the metainfo server with this http request:
-// META=http://metadata.google.internal/computeMetadata/v1
-// AUD=https://syzkaller.appspot.com/api
-// curl -sH 'Metadata-Flavor: Google' \
-// "$META/instance/service-accounts/default/identity?audience=$AUD"
-// 2) Invokes /api with header 'Authorization: Bearer <token>'
-//
-// The AppEngine api server:
-// 1) Receive the token, invokes this http request:
-// curl -s "https://oauth2.googleapis.com/tokeninfo?id_token=<token>"
-// 2) Checks the resulting JSON having the expected audience and expiration.
-// 3) Looks up the permissions in the config using the value of sub.
-//
-// https://cloud.google.com/iap/docs/signed-headers-howto#retrieving_the_user_identity
-// from the IAP docs agrees to trust sub.
-
-package main
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
-
- "github.com/google/syzkaller/dashboard/dashapi"
-)
-
-const (
- // The official google oauth2 endpoint.
- googleTokenInfoEndpoint = "https://oauth2.googleapis.com/tokeninfo"
- // Used in the config map as a prefix to distinguish auth identifiers from secret passwords
- // (which contain arbitrary strings, that can't have this prefix).
- oauthMagic = "OauthSubject:"
-)
-
-// Represent a verification backend.
-type authEndpoint struct {
- // URL supporting tokeninfo auth2 protocol.
- url string
- // TODO(blackgnezdo): cache tokens with a bit of care for concurrency.
-}
-
-func makeAuthEndpoint(u string) authEndpoint {
- return authEndpoint{url: u}
-}
-
-// The JSON representation of JWT claims.
-type jwtClaimsParse struct {
- Subject string `json:"sub"`
- Audience string `json:"aud"`
- // The field in the JSON is a string but contains a UNIX time.
- Expiration string `json:"exp"`
-}
-
-// The typed representation of JWT claims.
-type jwtClaims struct {
- Subject string
- Audience string
- // The app uses the typed value.
- Expiration time.Time
-}
-
-func (auth *authEndpoint) queryTokenInfo(tokenValue string) (*jwtClaims, error) {
- resp, err := http.PostForm(auth.url, url.Values{"id_token": {tokenValue}})
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- claims := new(jwtClaimsParse)
- if err = json.Unmarshal(body, claims); err != nil {
- return nil, err
- }
- expInt, err := strconv.ParseInt(claims.Expiration, 10, 64)
- if err != nil {
- return nil, err
- }
- r := jwtClaims{
- Subject: claims.Subject,
- Audience: claims.Audience,
- Expiration: time.Unix(expInt, 0),
- }
- return &r, nil
-}
-
-// Returns the verified subject value based on the provided header
-// value or "" if it can't be determined. A valid result starts with
-// oauthMagic. The now parameter is the current time to compare the
-// claims against.
-func (auth *authEndpoint) determineAuthSubj(now time.Time, authHeader []string) (string, error) {
- if len(authHeader) != 1 || !strings.HasPrefix(authHeader[0], "Bearer") {
- // This is a normal case when the client uses a password.
- return "", nil
- }
- // Values past this point are real authentication attempts. Whether
- // or not they are valid is the question.
- tokenValue := strings.TrimSpace(strings.TrimPrefix(authHeader[0], "Bearer"))
- claims, err := auth.queryTokenInfo(tokenValue)
- if err != nil {
- return "", err
- }
- if claims.Audience != dashapi.DashboardAudience {
- err := fmt.Errorf("unexpected audience %v %v", claims.Audience, claims)
- return "", err
- }
- if claims.Expiration.Before(now) {
- err := fmt.Errorf("token past expiration %v", claims.Expiration)
- return "", err
- }
- return oauthMagic + claims.Subject, nil
-}
diff --git a/dashboard/app/auth_test.go b/dashboard/app/auth_test.go
deleted file mode 100644
index c6d5fba23..000000000
--- a/dashboard/app/auth_test.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2017 syzkaller project authors. All rights reserved.
-// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
-
-package main
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "time"
-
- "github.com/google/syzkaller/dashboard/dashapi"
-)
-
-func reponseFor(t *testing.T, claims jwtClaims) (*httptest.Server, authEndpoint) {
- ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- bytes, err := json.Marshal(jwtClaimsParse{
- Subject: claims.Subject,
- Audience: claims.Audience,
- Expiration: fmt.Sprint(claims.Expiration.Unix()),
- })
- if err != nil {
- t.Fatalf("Marshal %v", err)
- }
- w.Header()["Content-Type"] = []string{"application/json"}
- w.Write(bytes)
- }))
- return ts, makeAuthEndpoint(ts.URL)
-}
-
-func TestBearerValid(t *testing.T) {
- tm := time.Now()
- magic := "ValidSubj"
- ts, dut := reponseFor(t, jwtClaims{
- Subject: magic,
- Audience: dashapi.DashboardAudience,
- Expiration: tm.AddDate(0, 0, 1),
- })
- defer ts.Close()
-
- got, err := dut.determineAuthSubj(tm, []string{"Bearer x"})
- if err != nil {
- t.Errorf("Unexpected error %v", err)
- }
- if !strings.HasSuffix(got, magic) {
- t.Errorf("Wrong subj %v not suffix of %v", magic, got)
- }
-}
-
-func TestBearerWrongAudience(t *testing.T) {
- tm := time.Now()
- ts, dut := reponseFor(t, jwtClaims{
- Subject: "irrelevant",
- Expiration: tm.AddDate(0, 0, 1),
- Audience: "junk",
- })
- defer ts.Close()
-
- _, err := dut.determineAuthSubj(tm, []string{"Bearer x"})
- if !strings.HasPrefix(err.Error(), "unexpected audience") {
- t.Fatalf("Unexpected error %v", err)
- }
-}
-
-func TestBearerExpired(t *testing.T) {
- tm := time.Now()
- ts, dut := reponseFor(t, jwtClaims{
- Subject: "irrelevant",
- Expiration: tm.AddDate(0, 0, -1),
- Audience: dashapi.DashboardAudience,
- })
- defer ts.Close()
-
- _, err := dut.determineAuthSubj(tm, []string{"Bearer x"})
- if !strings.HasPrefix(err.Error(), "token past expiration") {
- t.Fatalf("Unexpected error %v", err)
- }
-}
-
-func TestMissingHeader(t *testing.T) {
- ts, dut := reponseFor(t, jwtClaims{})
- defer ts.Close()
- got, err := dut.determineAuthSubj(time.Now(), []string{})
- if err != nil || got != "" {
- t.Errorf("Unexpected error %v %v", got, err)
- }
-}
-
-func TestBadHeader(t *testing.T) {
- ts, dut := reponseFor(t, jwtClaims{})
- defer ts.Close()
- got, err := dut.determineAuthSubj(time.Now(), []string{"bad"})
- if err != nil || got != "" {
- t.Errorf("Unexpected error %v %v", got, err)
- }
-}
diff --git a/dashboard/app/config.go b/dashboard/app/config.go
index 08f8ce850..4aa79348f 100644
--- a/dashboard/app/config.go
+++ b/dashboard/app/config.go
@@ -12,6 +12,7 @@ import (
"time"
"github.com/google/syzkaller/dashboard/dashapi"
+ "github.com/google/syzkaller/pkg/auth"
"github.com/google/syzkaller/pkg/email"
"github.com/google/syzkaller/pkg/vcs"
)
@@ -202,7 +203,7 @@ type KcidbConfig struct {
var (
namespaceNameRe = regexp.MustCompile("^[a-zA-Z0-9-_.]{4,32}$")
clientNameRe = regexp.MustCompile("^[a-zA-Z0-9-_.]{4,100}$")
- clientKeyRe = regexp.MustCompile("^([a-zA-Z0-9]{16,128})|(" + regexp.QuoteMeta(oauthMagic) + ".*)$")
+ clientKeyRe = regexp.MustCompile("^([a-zA-Z0-9]{16,128})|(" + regexp.QuoteMeta(auth.OauthMagic) + ".*)$")
)
type (