satellite/admin: add tests to admin auth
This change tests authorization of the admin api. Issue: https://github.com/storj/storj/issues/5699 Change-Id: Iecfe4c27a70ab1b48aeb5ed3251b51a3406140e8
This commit is contained in:
parent
49c5e3de9e
commit
4ee22e0ed8
@ -30,6 +30,15 @@ import (
|
||||
"storj.io/storj/satellite/payments/stripe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnauthorizedThroughOauth - message for full accesses through Oauth.
|
||||
UnauthorizedThroughOauth = "This operation is not authorized through oauth."
|
||||
// UnauthorizedNotInGroup - message for when api user is not part of a required access group.
|
||||
UnauthorizedNotInGroup = "User must be a member of one of these groups to conduct this operation: %s"
|
||||
// AuthorizationNotEnabled - message for when authorization is disabled.
|
||||
AuthorizationNotEnabled = "Authorization not enabled."
|
||||
)
|
||||
|
||||
// Config defines configuration for debug server.
|
||||
type Config struct {
|
||||
Address string `help:"admin peer http listening address" releaseDefault:"" devDefault:""`
|
||||
@ -191,8 +200,7 @@ func withAuth(log *zap.Logger, config Config, allowedGroups []string) func(next
|
||||
if r.Host != config.AllowedOauthHost {
|
||||
// not behind the proxy; use old authentication method.
|
||||
if config.AuthorizationToken == "" {
|
||||
sendJSONError(w, "Authorization not enabled.",
|
||||
"", http.StatusForbidden)
|
||||
sendJSONError(w, AuthorizationNotEnabled, "", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
@ -208,8 +216,8 @@ func withAuth(log *zap.Logger, config Config, allowedGroups []string) func(next
|
||||
} else {
|
||||
// request made from oauth proxy. Check user groups against allowedGroups.
|
||||
if allowedGroups == nil {
|
||||
sendJSONError(w, "Forbidden",
|
||||
"This operation is not authorized through oauth. Please contact a production owner to proceed.", http.StatusForbidden)
|
||||
// Endpoint is a full access endpoint, and requires token auth.
|
||||
sendJSONError(w, "Forbidden", UnauthorizedThroughOauth, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
@ -232,8 +240,7 @@ func withAuth(log *zap.Logger, config Config, allowedGroups []string) func(next
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
sendJSONError(w, "Forbidden",
|
||||
fmt.Sprintf("User must be a member of one of these groups to conduct this operation: %s", allowedGroups), http.StatusForbidden)
|
||||
sendJSONError(w, "Forbidden", fmt.Sprintf(UnauthorizedNotInGroup, allowedGroups), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
package admin_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
@ -14,8 +15,10 @@ import (
|
||||
"storj.io/common/testcontext"
|
||||
"storj.io/storj/private/testplanet"
|
||||
"storj.io/storj/satellite"
|
||||
"storj.io/storj/satellite/admin"
|
||||
)
|
||||
|
||||
// TestBasic tests authorization behaviour without oauth.
|
||||
func TestBasic(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
@ -47,10 +50,13 @@ func TestBasic(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Testing authorization behavior without Oauth from here on out.
|
||||
|
||||
t.Run("NoAccess", func(t *testing.T) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/api/projects/some-id", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// This request is not through the Oauth proxy and has no authorization token, it should fail.
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -99,3 +105,113 @@ func TestBasic(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// TestWithOAuth tests authorization behaviour for requests coming through Oauth.
|
||||
func TestWithOAuth(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
StorageNodeCount: 0,
|
||||
UplinkCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||
config.Admin.Address = "127.0.0.1:52392"
|
||||
// Make this admin server the AllowedOauthHost so withAuth thinks it's Oauth.
|
||||
config.Admin.AllowedOauthHost = "127.0.0.1:52392"
|
||||
config.Admin.StaticDir = "ui/build"
|
||||
config.Admin.Groups = admin.Groups{LimitUpdate: "LimitUpdate"}
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
projectID := planet.Uplinks[0].Projects[0].ID
|
||||
address := sat.Admin.Admin.Listener.Addr()
|
||||
baseURL := "http://" + address.String()
|
||||
|
||||
// Requests that require full access should not be accessible through Oauth.
|
||||
t.Run("UnauthorizedThroughOauth", func(t *testing.T) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/projects/%s/apikeys", baseURL, projectID.String()), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, response.StatusCode)
|
||||
require.Equal(t, "application/json", response.Header.Get("Content-Type"))
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
require.NoError(t, response.Body.Close())
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), admin.UnauthorizedThroughOauth)
|
||||
})
|
||||
|
||||
//
|
||||
t.Run("RequireLimitUpdateAccess", func(t *testing.T) {
|
||||
targetURL := fmt.Sprintf("%s/api/projects/%s/limit", baseURL, projectID.String())
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// this request does not have the {X-Forwarded-Groups: LimitUpdate} header. It should fail.
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, response.StatusCode)
|
||||
require.Equal(t, "application/json", response.Header.Get("Content-Type"))
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
require.NoError(t, response.Body.Close())
|
||||
require.NoError(t, err)
|
||||
errDetail := fmt.Sprintf(admin.UnauthorizedNotInGroup, []string{planet.Satellites[0].Config.Admin.Groups.LimitUpdate})
|
||||
require.Contains(t, string(body), errDetail)
|
||||
|
||||
req, err = http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// adding the header should allow this request.
|
||||
req.Header.Set("X-Forwarded-Groups", "LimitUpdate")
|
||||
|
||||
response, err = http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, response.Body.Close())
|
||||
|
||||
require.Equal(t, http.StatusOK, response.StatusCode)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// TestWithAuthNoToken tests when AuthToken config is set to an empty string (disabled authorization).
|
||||
func TestWithAuthNoToken(t *testing.T) {
|
||||
testplanet.Run(t, testplanet.Config{
|
||||
SatelliteCount: 1,
|
||||
StorageNodeCount: 0,
|
||||
UplinkCount: 1,
|
||||
Reconfigure: testplanet.Reconfigure{
|
||||
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||
config.Admin.Address = "127.0.0.1:0"
|
||||
config.Admin.StaticDir = "ui/build"
|
||||
// Disable authorization.
|
||||
config.Console.AuthToken = ""
|
||||
},
|
||||
},
|
||||
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||
sat := planet.Satellites[0]
|
||||
projectID := planet.Uplinks[0].Projects[0].ID
|
||||
address := sat.Admin.Admin.Listener.Addr()
|
||||
baseURL := "http://" + address.String()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/projects/%s/apikeys", baseURL, projectID.String()), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Authorization disabled, so this should fail.
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, response.StatusCode)
|
||||
require.Equal(t, "application/json", response.Header.Get("Content-Type"))
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
require.NoError(t, response.Body.Close())
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, string(body), admin.AuthorizationNotEnabled)
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user