forked from mystiq/dex
server, integration, cmd: Protect Admin API
Admin API now requires a 128 byte base64 encoded secret to be passed in Authorization header, closing up a potential security hole for those who expose this service.
This commit is contained in:
parent
48b3b38c8b
commit
55040c55fa
3 changed files with 61 additions and 6 deletions
|
@ -41,6 +41,9 @@ func main() {
|
|||
|
||||
adminListen := fs.String("admin-listen", "http://127.0.0.1:5557", "scheme, host and port for listening for administrative operation requests ")
|
||||
|
||||
adminAPISecret := pflag.NewBase64(server.AdminAPISecretLength)
|
||||
fs.Var(adminAPISecret, "admin-api-secret", fmt.Sprintf("A base64-encoded %d byte string which is used to protect the Admin API.", server.AdminAPISecretLength))
|
||||
|
||||
localConnectorID := fs.String("local-connector", "local", "ID of the local connector")
|
||||
logDebug := fs.Bool("log-debug", false, "log debug-level information")
|
||||
logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps")
|
||||
|
@ -124,7 +127,7 @@ func main() {
|
|||
}
|
||||
|
||||
krot := key.NewPrivateKeyRotator(kRepo, *keyPeriod)
|
||||
s := server.NewAdminServer(adminAPI, krot)
|
||||
s := server.NewAdminServer(adminAPI, krot, adminAPISecret.String())
|
||||
h := s.HTTPHandler()
|
||||
httpsrv := &http.Server{
|
||||
Addr: adminURL.Host,
|
||||
|
|
|
@ -14,6 +14,10 @@ import (
|
|||
"github.com/coreos/dex/user"
|
||||
)
|
||||
|
||||
const (
|
||||
adminAPITestSecret = "admin_secret"
|
||||
)
|
||||
|
||||
type adminAPITestFixtures struct {
|
||||
ur user.UserRepo
|
||||
pwr user.PasswordInfoRepo
|
||||
|
@ -58,6 +62,15 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
type adminAPITransport struct {
|
||||
secret string
|
||||
}
|
||||
|
||||
func (a *adminAPITransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
r.Header.Set("Authorization", a.secret)
|
||||
return http.DefaultTransport.RoundTrip(r)
|
||||
}
|
||||
|
||||
func makeAdminAPITestFixtures() *adminAPITestFixtures {
|
||||
f := &adminAPITestFixtures{}
|
||||
|
||||
|
@ -65,9 +78,13 @@ func makeAdminAPITestFixtures() *adminAPITestFixtures {
|
|||
f.ur = ur
|
||||
f.pwr = pwr
|
||||
f.adAPI = admin.NewAdminAPI(um, f.ur, f.pwr, "local")
|
||||
f.adSrv = server.NewAdminServer(f.adAPI, nil)
|
||||
f.adSrv = server.NewAdminServer(f.adAPI, nil, adminAPITestSecret)
|
||||
f.hSrv = httptest.NewServer(f.adSrv.HTTPHandler())
|
||||
f.hc = &http.Client{}
|
||||
f.hc = &http.Client{
|
||||
Transport: &adminAPITransport{
|
||||
secret: adminAPITestSecret,
|
||||
},
|
||||
}
|
||||
f.adClient, _ = adminschema.NewWithBasePath(f.hc, f.hSrv.URL)
|
||||
|
||||
return f
|
||||
|
@ -129,6 +146,7 @@ func TestCreateAdmin(t *testing.T) {
|
|||
tests := []struct {
|
||||
admn *adminschema.Admin
|
||||
errCode int
|
||||
secret string
|
||||
}{
|
||||
{
|
||||
admn: &adminschema.Admin{
|
||||
|
@ -137,6 +155,14 @@ func TestCreateAdmin(t *testing.T) {
|
|||
},
|
||||
errCode: -1,
|
||||
},
|
||||
{
|
||||
admn: &adminschema.Admin{
|
||||
Email: "foo@example.com",
|
||||
Password: "foopass",
|
||||
},
|
||||
errCode: http.StatusUnauthorized,
|
||||
secret: "bad_secret",
|
||||
},
|
||||
{
|
||||
// duplicate Email
|
||||
admn: &adminschema.Admin{
|
||||
|
@ -156,6 +182,11 @@ func TestCreateAdmin(t *testing.T) {
|
|||
for i, tt := range tests {
|
||||
func() {
|
||||
f := makeAdminAPITestFixtures()
|
||||
if tt.secret != "" {
|
||||
f.hc.Transport = &adminAPITransport{
|
||||
secret: tt.secret,
|
||||
}
|
||||
}
|
||||
defer f.close()
|
||||
|
||||
admn, err := f.adClient.Admin.Create(tt.admn).Do()
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
const (
|
||||
AdminAPIVersion = "v1"
|
||||
AdminAPISecretLength = 128
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -28,9 +29,10 @@ var (
|
|||
type AdminServer struct {
|
||||
adminAPI *admin.AdminAPI
|
||||
checker health.Checker
|
||||
secret string
|
||||
}
|
||||
|
||||
func NewAdminServer(adminAPI *admin.AdminAPI, rotator *key.PrivateKeyRotator) *AdminServer {
|
||||
func NewAdminServer(adminAPI *admin.AdminAPI, rotator *key.PrivateKeyRotator, secret string) *AdminServer {
|
||||
return &AdminServer{
|
||||
adminAPI: adminAPI,
|
||||
checker: health.Checker{
|
||||
|
@ -38,6 +40,7 @@ func NewAdminServer(adminAPI *admin.AdminAPI, rotator *key.PrivateKeyRotator) *A
|
|||
rotator,
|
||||
},
|
||||
},
|
||||
secret: secret,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +51,25 @@ func (s *AdminServer) HTTPHandler() http.Handler {
|
|||
r.GET(AdminGetStateEndpoint, s.getState)
|
||||
r.Handler("GET", httpPathHealth, s.checker)
|
||||
r.HandlerFunc("GET", httpPathDebugVars, health.ExpvarHandler)
|
||||
return r
|
||||
|
||||
return authorizer(r, s.secret, httpPathHealth, httpPathDebugVars)
|
||||
}
|
||||
|
||||
func authorizer(h http.Handler, secret string, public ...string) http.Handler {
|
||||
publicSet := map[string]struct{}{}
|
||||
for _, p := range public {
|
||||
publicSet[p] = struct{}{}
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, isPublicPath := publicSet[r.URL.Path]
|
||||
|
||||
if !isPublicPath && r.Header.Get("Authorization") != secret {
|
||||
writeAPIError(w, http.StatusUnauthorized, newAPIError(errorAccessDenied, ""))
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *AdminServer) getAdmin(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
|
|
Loading…
Reference in a new issue