satellite/{admin,ui}: implement changes for oauth2 proxy
We want to put the OAuth2 proxy, https://github.com/oauth2-proxy/oauth2-proxy, in front of the satellite admin ui. This change implements the changes required/necessary for this to work. Issue: https://github.com/storj/storj/issues/5072 Change-Id: I6da0df090cc6f0c18f1bf41e48ae082493f53f20
This commit is contained in:
parent
9a0c2dd878
commit
c1ed5c06e8
@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@ -30,8 +31,9 @@ import (
|
||||
|
||||
// Config defines configuration for debug server.
|
||||
type Config struct {
|
||||
Address string `help:"admin peer http listening address" releaseDefault:"" devDefault:""`
|
||||
StaticDir string `help:"an alternate directory path which contains the static assets to serve. When empty, it uses the embedded assets" releaseDefault:"" devDefault:""`
|
||||
Address string `help:"admin peer http listening address" releaseDefault:"" devDefault:""`
|
||||
StaticDir string `help:"an alternate directory path which contains the static assets to serve. When empty, it uses the embedded assets" releaseDefault:"" devDefault:""`
|
||||
AllowedOauthHost string `help:"the oauth host allowed to bypass token authentication."`
|
||||
|
||||
AuthorizationToken string `internal:"true"`
|
||||
}
|
||||
@ -87,7 +89,7 @@ func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.S
|
||||
root := mux.NewRouter()
|
||||
|
||||
api := root.PathPrefix("/api/").Subrouter()
|
||||
api.Use(allowedAuthorization(config.AuthorizationToken))
|
||||
api.Use(allowedAuthorization(log, config))
|
||||
|
||||
// When adding new options, also update README.md
|
||||
api.HandleFunc("/users", server.addUser).Methods("POST")
|
||||
@ -159,24 +161,36 @@ func (server *Server) Close() error {
|
||||
return Error.Wrap(server.server.Close())
|
||||
}
|
||||
|
||||
func allowedAuthorization(token string) func(next http.Handler) http.Handler {
|
||||
func allowedAuthorization(log *zap.Logger, config Config) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if token == "" {
|
||||
sendJSONError(w, "Authorization not enabled.",
|
||||
"", http.StatusForbidden)
|
||||
return
|
||||
|
||||
if r.Host != config.AllowedOauthHost {
|
||||
// not behind the proxy; use old authentication method.
|
||||
if config.AuthorizationToken == "" {
|
||||
sendJSONError(w, "Authorization not enabled.",
|
||||
"", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
equality := subtle.ConstantTimeCompare(
|
||||
[]byte(r.Header.Get("Authorization")),
|
||||
[]byte(config.AuthorizationToken),
|
||||
)
|
||||
if equality != 1 {
|
||||
sendJSONError(w, "Forbidden",
|
||||
"", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
equality := subtle.ConstantTimeCompare(
|
||||
[]byte(r.Header.Get("Authorization")),
|
||||
[]byte(token),
|
||||
log.Info(
|
||||
"admin action",
|
||||
zap.String("host", r.Host),
|
||||
zap.String("user", r.Header.Get("X-Forwarded-Email")),
|
||||
zap.String("action", fmt.Sprintf("%s-%s", r.Method, r.RequestURI)),
|
||||
zap.String("queries", r.URL.Query().Encode()),
|
||||
)
|
||||
if equality != 1 {
|
||||
sendJSONError(w, "Forbidden",
|
||||
"", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
r.Header.Set("Cache-Control", "must-revalidate")
|
||||
next.ServeHTTP(w, r)
|
||||
|
@ -16,7 +16,7 @@ wants to perform.
|
||||
import UIGen from '$lib/UIGenerator.svelte';
|
||||
|
||||
const baseURL = `${window.location.protocol}//${window.location.host}/api`;
|
||||
let api: Admin;
|
||||
let api: Admin = new Admin(baseURL);
|
||||
let selectedGroupOp: Operation[];
|
||||
let selectedOp: Operation;
|
||||
let authToken: string;
|
||||
@ -25,21 +25,19 @@ wants to perform.
|
||||
if (authToken) {
|
||||
api = new Admin(baseURL, authToken);
|
||||
} else {
|
||||
api = null;
|
||||
api = new Admin(baseURL);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>
|
||||
In order to use the API you have to set the authentication token in the input box and press enter
|
||||
or click the "confirm" button.
|
||||
If you did not log in using Oauth (e.g. with Google), you have to set the authentication token in
|
||||
the input box and press enter or click the "confirm" button.
|
||||
</p>
|
||||
<p>
|
||||
Token: <input
|
||||
bind:value={authToken}
|
||||
on:focus={() => {
|
||||
api = null;
|
||||
|
||||
// This allows to select the empty item of the second select.
|
||||
selectedOp = null;
|
||||
}}
|
||||
@ -52,38 +50,36 @@ wants to perform.
|
||||
<button on:click={confirmAuthToken}>confirm</button>
|
||||
</p>
|
||||
|
||||
{#if api}
|
||||
<p>
|
||||
Operation:
|
||||
<select
|
||||
bind:value={selectedGroupOp}
|
||||
on:change={() => {
|
||||
// This allows hiding the UIGen component when this select change until
|
||||
// a new operations is selected in the following select element and also
|
||||
// selecting the empty item of the select.
|
||||
selectedOp = null;
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
Operation:
|
||||
<select
|
||||
bind:value={selectedGroupOp}
|
||||
on:change={() => {
|
||||
// This allows hiding the UIGen component when this select change until
|
||||
// a new operations is selected in the following select element and also
|
||||
// selecting the empty item of the select.
|
||||
selectedOp = null;
|
||||
}}
|
||||
>
|
||||
<option selected />
|
||||
{#each Object.keys(api.operations) as group}
|
||||
<option value={api.operations[group]}>{group}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if selectedGroupOp}
|
||||
<select bind:value={selectedOp}>
|
||||
<option selected />
|
||||
{#each Object.keys(api.operations) as group}
|
||||
<option value={api.operations[group]}>{group}</option>
|
||||
{#each selectedGroupOp as op (op)}
|
||||
<option value={op}>{op.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if selectedGroupOp}
|
||||
<select bind:value={selectedOp}>
|
||||
<option selected />
|
||||
{#each selectedGroupOp as op (op)}
|
||||
<option value={op}>{op.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
{#if selectedOp}
|
||||
{#key selectedOp}
|
||||
<UIGen operation={selectedOp} />
|
||||
{/key}
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</p>
|
||||
<hr />
|
||||
<p>
|
||||
{#if selectedOp}
|
||||
{#key selectedOp}
|
||||
<UIGen operation={selectedOp} />
|
||||
{/key}
|
||||
{/if}
|
||||
</p>
|
||||
|
@ -408,7 +408,7 @@ Blank fields will not be updated.`,
|
||||
|
||||
private readonly baseURL: string;
|
||||
|
||||
constructor(baseURL: string, private readonly authToken: string) {
|
||||
constructor(baseURL: string, private readonly authToken: string = '') {
|
||||
this.baseURL = baseURL.endsWith('/') ? baseURL.substring(0, baseURL.length - 1) : baseURL;
|
||||
}
|
||||
|
||||
@ -419,9 +419,11 @@ Blank fields will not be updated.`,
|
||||
data?: Record<string, unknown>
|
||||
): Promise<Record<string, unknown> | null> {
|
||||
const url = this.apiURL(path, query);
|
||||
const headers = new window.Headers({
|
||||
Authorization: this.authToken
|
||||
});
|
||||
const headers = new window.Headers();
|
||||
|
||||
if (this.authToken) {
|
||||
headers.set('Authorization', this.authToken);
|
||||
}
|
||||
|
||||
let body: string;
|
||||
if (data) {
|
||||
|
3
scripts/testdata/satellite-config.yaml.lock
vendored
3
scripts/testdata/satellite-config.yaml.lock
vendored
@ -1,6 +1,9 @@
|
||||
# admin peer http listening address
|
||||
# admin.address: ""
|
||||
|
||||
# the oauth host allowed to bypass token authentication.
|
||||
# admin.allowed-oauth-host: ""
|
||||
|
||||
# an alternate directory path which contains the static assets to serve. When empty, it uses the embedded assets
|
||||
# admin.static-dir: ""
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user