Skip to content

Commit 8c5968e

Browse files
wilfred-asomaniiStorj Robot
authored andcommitted
satellite/admin: verify proxy host
This change adds an authentication step to check if requests to the back-office API are through a verified host, e.g.: Oauth2 proxy. Change-Id: Iaec4746939578049b9d038001d2d69067bcafea6
1 parent 89aacfc commit 8c5968e

File tree

8 files changed

+97
-2
lines changed

8 files changed

+97
-2
lines changed

satellite/admin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ func NewAdmin(log *zap.Logger, full *identity.FullIdentity, db DB, metabaseDB *m
296296
return nil, err
297297
}
298298

299+
config.Admin.BackOffice.AllowedOauthHost = config.Admin.AllowedOauthHost
299300
peer.Admin.Service = backoffice.NewService(
300301
log.Named("back-office:service"),
301302
peer.DB.Console(),

satellite/admin/back-office/authorization.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ type Authorizer struct {
102102
log *zap.Logger
103103
groupsRoles map[string]Authorization
104104

105-
enabled bool
105+
enabled bool
106+
allowedHost string
106107
}
107108

108109
// NewAuthorizer creates an Authorizer with the list of groups that are assigned to each different
@@ -139,6 +140,7 @@ func NewAuthorizer(
139140
log: log.Named("authorizer"),
140141
groupsRoles: groupsRoles,
141142
enabled: !config.BypassAuth,
143+
allowedHost: config.AllowedOauthHost,
142144
}
143145
}
144146

@@ -196,3 +198,15 @@ func (auth *Authorizer) IsRejected(w http.ResponseWriter, r *http.Request, perms
196198
api.ServeError(auth.log, w, http.StatusUnauthorized, err)
197199
return true
198200
}
201+
202+
// VerifyHost checks that the provided host is allowed to host the back office.
203+
func (auth *Authorizer) VerifyHost(r *http.Request) error {
204+
if !auth.enabled {
205+
return nil
206+
}
207+
208+
if r.Host != auth.allowedHost {
209+
return Error.New("forbidden host: %s", r.Host)
210+
}
211+
return nil
212+
}

satellite/admin/back-office/gen/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,12 @@ type authMiddleware struct {
152152
}
153153

154154
func (a authMiddleware) Generate(_ *apigen.API, _ *apigen.EndpointGroup, ep *apigen.FullEndpoint) string {
155-
format := ""
155+
format := `
156+
if err = h.auth.VerifyHost(r); err != nil {
157+
api.ServeError(h.log, w, http.StatusForbidden, err)
158+
return
159+
}
160+
`
156161
if apigen.LoadSetting(passAuthParamKey, ep, false) {
157162
format += `
158163
authInfo := h.auth.GetAuthInfo(r)

satellite/admin/back-office/handlers.gen.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ func (h *SettingsHandler) handleGetSettings(w http.ResponseWriter, r *http.Reque
139139

140140
w.Header().Set("Content-Type", "application/json")
141141

142+
if err = h.auth.VerifyHost(r); err != nil {
143+
api.ServeError(h.log, w, http.StatusForbidden, err)
144+
return
145+
}
146+
142147
authInfo := h.auth.GetAuthInfo(r)
143148
if authInfo == nil || len(authInfo.Groups) == 0 {
144149
api.ServeError(h.log, w, http.StatusUnauthorized, errs.New("Unauthorized"))
@@ -183,6 +188,11 @@ func (h *UserManagementHandler) handleGetFreezeEventTypes(w http.ResponseWriter,
183188

184189
w.Header().Set("Content-Type", "application/json")
185190

191+
if err = h.auth.VerifyHost(r); err != nil {
192+
api.ServeError(h.log, w, http.StatusForbidden, err)
193+
return
194+
}
195+
186196
retVal, httpErr := h.service.GetFreezeEventTypes(ctx)
187197
if httpErr.Err != nil {
188198
api.ServeError(h.log, w, httpErr.Status, httpErr.Err)
@@ -208,6 +218,11 @@ func (h *UserManagementHandler) handleGetUserByEmail(w http.ResponseWriter, r *h
208218
return
209219
}
210220

221+
if err = h.auth.VerifyHost(r); err != nil {
222+
api.ServeError(h.log, w, http.StatusForbidden, err)
223+
return
224+
}
225+
211226
if h.auth.IsRejected(w, r, 1) {
212227
return
213228
}
@@ -249,6 +264,11 @@ func (h *UserManagementHandler) handleFreezeUser(w http.ResponseWriter, r *http.
249264
return
250265
}
251266

267+
if err = h.auth.VerifyHost(r); err != nil {
268+
api.ServeError(h.log, w, http.StatusForbidden, err)
269+
return
270+
}
271+
252272
if h.auth.IsRejected(w, r, 128) {
253273
return
254274
}
@@ -278,6 +298,11 @@ func (h *UserManagementHandler) handleUnfreezeUser(w http.ResponseWriter, r *htt
278298
return
279299
}
280300

301+
if err = h.auth.VerifyHost(r); err != nil {
302+
api.ServeError(h.log, w, http.StatusForbidden, err)
303+
return
304+
}
305+
281306
if h.auth.IsRejected(w, r, 256) {
282307
return
283308
}
@@ -307,6 +332,11 @@ func (h *ProjectManagementHandler) handleGetProject(w http.ResponseWriter, r *ht
307332
return
308333
}
309334

335+
if err = h.auth.VerifyHost(r); err != nil {
336+
api.ServeError(h.log, w, http.StatusForbidden, err)
337+
return
338+
}
339+
310340
if h.auth.IsRejected(w, r, 8192) {
311341
return
312342
}
@@ -348,6 +378,11 @@ func (h *ProjectManagementHandler) handleUpdateProjectLimits(w http.ResponseWrit
348378
return
349379
}
350380

381+
if err = h.auth.VerifyHost(r); err != nil {
382+
api.ServeError(h.log, w, http.StatusForbidden, err)
383+
return
384+
}
385+
351386
if h.auth.IsRejected(w, r, 16384) {
352387
return
353388
}

satellite/admin/back-office/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type Config struct {
3838
StaticDir string `help:"an alternate directory path which contains the static assets for the satellite administration web app. When empty, it uses the embedded assets" releaseDefault:"" devDefault:""`
3939

4040
BypassAuth bool `help:"ignore authentication for local development" default:"false" hidden:"true"`
41+
// hidden for now because it is provided by the legacy admin server.
42+
AllowedOauthHost string `help:"the oauth host allowed to host the backoffice." default:"" hidden:"true"`
4143

4244
UserGroupsRoleAdmin []string `help:"the list of groups whose users has the administration role" releaseDefault:"" devDefault:""`
4345
UserGroupsRoleViewer []string `help:"the list of groups whose users has the viewer role" releaseDefault:"" devDefault:""`

satellite/admin/back-office/service.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,8 @@ func NewService(
6161
func (s *Service) TestSetBypassAuth(bypass bool) {
6262
s.authorizer.enabled = !bypass
6363
}
64+
65+
// TestSetAllowedHost sets the allowed host for oauth. This is only for testing purposes.
66+
func (s *Service) TestSetAllowedHost(host string) {
67+
s.authorizer.allowedHost = host
68+
}

satellite/admin/back-office/settings_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func TestGetSettings(t *testing.T) {
9898
sat := planet.Satellites[0]
9999
address := sat.Admin.Admin.Listener.Addr()
100100
settingsUrl := "http://" + address.String() + "/back-office/api/v1/settings/"
101+
sat.Admin.Admin.Service.TestSetAllowedHost(address.String())
101102

102103
for _, tc := range testCases {
103104
t.Run(tc.Name, func(t *testing.T) {

satellite/admin/server_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,38 @@ func TestWithOAuth(t *testing.T) {
278278

279279
require.Equal(t, http.StatusOK, response.StatusCode)
280280
})
281+
282+
// Test back-office API access through Oauth.
283+
t.Run("BackOfficeThroughOauth", func(t *testing.T) {
284+
t.Run("AllowedHost", func(t *testing.T) {
285+
sat.Admin.Admin.Service.TestSetAllowedHost(address)
286+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/back-office/api/v1/settings/", nil)
287+
require.NoError(t, err)
288+
req.Header.Set("X-Forwarded-Groups", "LimitUpdate")
289+
290+
response, err := http.DefaultClient.Do(req)
291+
require.NoError(t, err)
292+
require.NoError(t, response.Body.Close())
293+
294+
require.Equal(t, http.StatusOK, response.StatusCode)
295+
})
296+
297+
t.Run("NotAllowedHost", func(t *testing.T) {
298+
sat.Admin.Admin.Service.TestSetAllowedHost("some-other-host")
299+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/back-office/api/v1/settings/", nil)
300+
require.NoError(t, err)
301+
req.Header.Set("X-Forwarded-Groups", "LimitUpdate")
302+
303+
response, err := http.DefaultClient.Do(req)
304+
require.NoError(t, err)
305+
body, err := io.ReadAll(response.Body)
306+
require.NoError(t, response.Body.Close())
307+
require.NoError(t, err)
308+
309+
require.Equal(t, http.StatusForbidden, response.StatusCode)
310+
require.Contains(t, string(body), "forbidden host")
311+
})
312+
})
281313
})
282314
}
283315

0 commit comments

Comments
 (0)