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

Add authn for graphql and http admin endpoints #5162

Merged
merged 30 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
91fc1ca
add auth for admin endpoints
abhimanyusinghgaur Apr 8, 2020
c78becc
add poor man's auth
abhimanyusinghgaur Apr 10, 2020
563ecde
add tests
abhimanyusinghgaur Apr 15, 2020
1dc12d1
update auth for /alter and /admin/schema
abhimanyusinghgaur Apr 15, 2020
7006028
remove whitelisting check for /alter
abhimanyusinghgaur Apr 15, 2020
603fd25
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur Apr 17, 2020
5629249
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur Apr 17, 2020
deeb2bb
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur Apr 17, 2020
08772df
add middlewares
abhimanyusinghgaur Apr 19, 2020
4009e29
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur Apr 19, 2020
9ea03fd
fix test
abhimanyusinghgaur Apr 19, 2020
262b944
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur Apr 22, 2020
9af6def
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur Apr 27, 2020
1b64682
incomplete changes
abhimanyusinghgaur Apr 28, 2020
46330f9
final changes
abhimanyusinghgaur Apr 28, 2020
3497cbb
add test
abhimanyusinghgaur Apr 28, 2020
fa2b651
fix tests
abhimanyusinghgaur Apr 29, 2020
1a471ba
add more tests
abhimanyusinghgaur Apr 29, 2020
0b92b7f
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur Apr 29, 2020
076e68b
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur May 13, 2020
636999f
manage middlewares as configuration
abhimanyusinghgaur May 14, 2020
0d75284
route admin http requests through graphql admin server
abhimanyusinghgaur May 15, 2020
75a4d8b
add test
abhimanyusinghgaur May 15, 2020
b6e6863
restructure tests
abhimanyusinghgaur May 15, 2020
4f513fb
final touches
abhimanyusinghgaur May 15, 2020
ed81244
fix tests
abhimanyusinghgaur May 15, 2020
1d80239
fix test
abhimanyusinghgaur May 15, 2020
3677c15
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur May 15, 2020
aa4e3cc
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur May 18, 2020
8fe2324
Merge branch 'master' of github.com:dgraph-io/dgraph into abhimanyu/a…
abhimanyusinghgaur May 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 57 additions & 52 deletions dgraph/cmd/alpha/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,76 +17,98 @@
package alpha

import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net"
"net/http"
"strconv"

"github.com/dgraph-io/dgraph/edgraph"
"github.com/dgraph-io/dgraph/posting"
"github.com/dgraph-io/dgraph/worker"
"github.com/dgraph-io/dgraph/x"
"github.com/golang/glog"
)

// handlerInit does some standard checks. Returns false if something is wrong.
func handlerInit(w http.ResponseWriter, r *http.Request, allowedMethods map[string]bool) bool {
if _, ok := allowedMethods[r.Method]; !ok {
x.SetStatus(w, x.ErrorInvalidMethod, "Invalid method")
return false
}
// adminAuthOptions are used by adminAuthHandler
type adminAuthOptions struct {
allowedMethods map[string]bool
skipIpWhitelisting bool
skipPoormansAuth bool
skipGuardianAuth bool
}

ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil || (!ipInIPWhitelistRanges(ip) && !net.ParseIP(ip).IsLoopback()) {
x.SetStatus(w, x.ErrorUnauthorized, fmt.Sprintf("Request from IP: %v", ip))
// hasPoormansAuth check if poorman's auth is required and if so whether the given http request has
// poorman's auth in it or not
func hasPoormansAuth(r *http.Request) bool {
if worker.Config.AuthToken != "" && worker.Config.AuthToken != r.Header.Get(
"X-Dgraph-AuthToken") {
return false
}
return true
}

func drainingHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPut, http.MethodPost:
enableStr := r.URL.Query().Get("enable")
// adminAuthHandler does some standard checks for admin endpoints.
// It returns if something is wrong. Otherwise, it lets the given handler serve the request.
func adminAuthHandler(handler http.Handler, options adminAuthOptions) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if _, ok := options.allowedMethods[r.Method]; !ok {
x.SetStatus(w, x.ErrorInvalidMethod, "Invalid method")
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

enable, err := strconv.ParseBool(enableStr)
if err != nil {
x.SetStatus(w, x.ErrorInvalidRequest,
"Found invalid value for the enable parameter")
if !options.skipIpWhitelisting {
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil || !x.IsIpWhitelisted(ip) {
x.SetStatus(w, x.ErrorUnauthorized, fmt.Sprintf("Request from IP: %v", ip))
return
}
}

if !options.skipPoormansAuth && !hasPoormansAuth(r) {
x.SetStatus(w, x.ErrorUnauthorized, "Invalid X-Dgraph-AuthToken")
return
}

x.UpdateDrainingMode(enable)
_, err = w.Write([]byte(fmt.Sprintf(`{"code": "Success",`+
`"message": "draining mode has been set to %v"}`, enable)))
if err != nil {
glog.Errorf("Failed to write response: %v", err)
if !options.skipGuardianAuth {
err := edgraph.AuthorizeGuardians(x.AttachAccessJwt(context.Background(), r))
if err != nil {
x.SetStatus(w, x.ErrorUnauthorized, err.Error())
return
}
}
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}

handler.ServeHTTP(w, r)
})
}

func shutDownHandler(w http.ResponseWriter, r *http.Request) {
if !handlerInit(w, r, map[string]bool{
http.MethodGet: true,
}) {
func drainingHandler(w http.ResponseWriter, r *http.Request) {
enableStr := r.URL.Query().Get("enable")

enable, err := strconv.ParseBool(enableStr)
if err != nil {
x.SetStatus(w, x.ErrorInvalidRequest,
"Found invalid value for the enable parameter")
return
}

x.UpdateDrainingMode(enable)
_, err = w.Write([]byte(fmt.Sprintf(`{"code": "Success",`+
`"message": "draining mode has been set to %v"}`, enable)))
if err != nil {
glog.Errorf("Failed to write response: %v", err)
}
}

func shutDownHandler(w http.ResponseWriter, r *http.Request) {
close(worker.ShutdownCh)
w.Header().Set("Content-Type", "application/json")
x.Check2(w.Write([]byte(`{"code": "Success", "message": "Server is shutting down"}`)))
}

func exportHandler(w http.ResponseWriter, r *http.Request) {
if !handlerInit(w, r, map[string]bool{
http.MethodGet: true,
}) {
return
}
if err := r.ParseForm(); err != nil {
x.SetHttpStatus(w, http.StatusBadRequest, "Parse of export request failed.")
return
Expand Down Expand Up @@ -119,8 +141,6 @@ func memoryLimitHandler(w http.ResponseWriter, r *http.Request) {
memoryLimitGetHandler(w, r)
case http.MethodPut:
memoryLimitPutHandler(w, r)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}

Expand Down Expand Up @@ -153,18 +173,3 @@ func memoryLimitGetHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

func ipInIPWhitelistRanges(ipString string) bool {
ip := net.ParseIP(ipString)

if ip == nil {
return false
}

for _, ipRange := range x.WorkerConfig.WhiteListedIPRanges {
if bytes.Compare(ip, ipRange.Lower) >= 0 && bytes.Compare(ip, ipRange.Upper) <= 0 {
return true
}
}
return false
}
11 changes: 4 additions & 7 deletions dgraph/cmd/alpha/admin_backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,14 @@ import (
)

func init() {
http.HandleFunc("/admin/backup", backupHandler)
http.Handle("/admin/backup", adminAuthHandler(http.HandlerFunc(backupHandler),
adminAuthOptions{allowedMethods: map[string]bool{
http.MethodPost: true,
}}))
}

// backupHandler handles backup requests coming from the HTTP endpoint.
func backupHandler(w http.ResponseWriter, r *http.Request) {
if !handlerInit(w, r, map[string]bool{
http.MethodPost: true,
}) {
return
}

destination := r.FormValue("destination")
accessKey := r.FormValue("access_key")
secretKey := r.FormValue("secret_key")
Expand Down
5 changes: 5 additions & 0 deletions dgraph/cmd/alpha/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,11 @@ func adminSchemaHandler(w http.ResponseWriter, r *http.Request, adminServer web.
return
}

if !hasPoormansAuth(r) {
x.SetStatus(w, x.ErrorUnauthorized, "Invalid X-Dgraph-AuthToken")
return
}

b := readRequest(w, r)
if b == nil {
return
Expand Down
25 changes: 9 additions & 16 deletions dgraph/cmd/alpha/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,30 +735,21 @@ func TestHealth(t *testing.T) {
require.True(t, info[0].Uptime > int64(time.Duration(1)))
}

func setDrainingMode(t *testing.T, enable bool) {
func setDrainingMode(t *testing.T, enable bool, accessJwt string) {
drainingRequest := `mutation drain($enable: Boolean) {
draining(enable: $enable) {
response {
code
}
}
}`
adminUrl := fmt.Sprintf("%s/admin", addr)
params := testutil.GraphQLParams{
params := &testutil.GraphQLParams{
Query: drainingRequest,
Variables: map[string]interface{}{"enable": enable},
}
b, err := json.Marshal(params)
require.NoError(t, err)

resp, err := http.Post(adminUrl, "application/json", bytes.NewBuffer(b))
require.NoError(t, err)

defer resp.Body.Close()
b, err = ioutil.ReadAll(resp.Body)
require.NoError(t, err)
require.JSONEq(t, `{"data":{"draining":{"response":{"code":"Success"}}}}`,
string(b))
resp := testutil.MakeGQLRequestWithAccessJwt(t, params, accessJwt)
resp.RequireNoGraphQLErrors(t)
require.JSONEq(t, `{"draining":{"response":{"code":"Success"}}}`, string(resp.Data))
}

func TestDrainingMode(t *testing.T) {
Expand Down Expand Up @@ -800,10 +791,12 @@ func TestDrainingMode(t *testing.T) {

}

setDrainingMode(t, true)
grootJwt, _ := testutil.GrootHttpLogin(addr + "/admin")

setDrainingMode(t, true, grootJwt)
runRequests(true)

setDrainingMode(t, false)
setDrainingMode(t, false, grootJwt)
runRequests(false)
}

Expand Down
42 changes: 26 additions & 16 deletions dgraph/cmd/alpha/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,24 @@ func setupServer(closer *y.Closer) {
// TODO: Figure out what this is for?
http.HandleFunc("/debug/store", storeStatsHandler)

http.HandleFunc("/admin/shutdown", shutDownHandler)
http.HandleFunc("/admin/draining", drainingHandler)
http.HandleFunc("/admin/export", exportHandler)
http.HandleFunc("/admin/config/lru_mb", memoryLimitHandler)
http.Handle("/admin/shutdown", adminAuthHandler(http.HandlerFunc(shutDownHandler),
adminAuthOptions{allowedMethods: map[string]bool{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

change the type for allowedMethods to a alias type

http.MethodGet: true,
}}))
http.Handle("/admin/draining", adminAuthHandler(http.HandlerFunc(drainingHandler),
adminAuthOptions{allowedMethods: map[string]bool{
http.MethodPut: true,
http.MethodPost: true,
}}))
http.Handle("/admin/export", adminAuthHandler(http.HandlerFunc(exportHandler),
adminAuthOptions{allowedMethods: map[string]bool{
http.MethodGet: true,
}}))
http.Handle("/admin/config/lru_mb", adminAuthHandler(http.HandlerFunc(memoryLimitHandler),
adminAuthOptions{allowedMethods: map[string]bool{
http.MethodGet: true,
http.MethodPut: true,
}}))

introspection := Alpha.Conf.GetBool("graphql_introspection")

Expand All @@ -476,20 +490,16 @@ func setupServer(closer *y.Closer) {
globalEpoch := uint64(0)
mainServer, adminServer := admin.NewServers(introspection, &globalEpoch, closer)
http.Handle("/graphql", mainServer.HTTPHandler())

whitelist := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !handlerInit(w, r, map[string]bool{
http.MethodPost: true,
http.Handle("/admin", adminAuthHandler(adminServer.HTTPHandler(),
adminAuthOptions{
allowedMethods: map[string]bool{
http.MethodGet: true,
http.MethodPost: true,
http.MethodOptions: true,
}) {
return
}
h.ServeHTTP(w, r)
})
}
http.Handle("/admin", whitelist(adminServer.HTTPHandler()))
},
skipIpWhitelisting: true,
skipGuardianAuth: true,
}))
http.HandleFunc("/admin/schema", func(w http.ResponseWriter, r *http.Request) {
adminSchemaHandler(w, r, adminServer)
})
Expand Down
2 changes: 1 addition & 1 deletion edgraph/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func authorizeQuery(ctx context.Context, parsedReq *gql.Result, graphql bool) er
return nil
}

func authorizeGuardians(ctx context.Context) error {
func AuthorizeGuardians(ctx context.Context) error {
// always allow access
return nil
}
4 changes: 2 additions & 2 deletions edgraph/access_ee.go
Original file line number Diff line number Diff line change
Expand Up @@ -794,8 +794,8 @@ func authorizeQuery(ctx context.Context, parsedReq *gql.Result, graphql bool) er
return nil
}

// authorizeGuardians authorizes the operation for users which belong to Guardians group.
func authorizeGuardians(ctx context.Context) error {
// AuthorizeGuardians authorizes the operation for users which belong to Guardians group.
func AuthorizeGuardians(ctx context.Context) error {
if len(worker.Config.HmacSecret) == 0 {
// the user has not turned on the acl feature
return nil
Expand Down
4 changes: 2 additions & 2 deletions edgraph/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ func (s *Server) Health(ctx context.Context, all bool) (*api.Response, error) {

var healthAll []pb.HealthInfo
if all {
if err := authorizeGuardians(ctx); err != nil {
if err := AuthorizeGuardians(ctx); err != nil {
return nil, err
}
pool := conn.GetPools().GetAll()
Expand Down Expand Up @@ -738,7 +738,7 @@ func (s *Server) State(ctx context.Context) (*api.Response, error) {
return nil, ctx.Err()
}

if err := authorizeGuardians(ctx); err != nil {
if err := AuthorizeGuardians(ctx); err != nil {
return nil, err
}

Expand Down
Loading