From 55040c55fa0f27a4e1a517efa18425571080797a Mon Sep 17 00:00:00 2001 From: Bobby Rullo Date: Thu, 1 Oct 2015 11:34:53 -0700 Subject: [PATCH] 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. --- cmd/dex-overlord/main.go | 5 ++++- integration/admin_api_test.go | 35 +++++++++++++++++++++++++++++++++-- server/admin.go | 27 ++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/cmd/dex-overlord/main.go b/cmd/dex-overlord/main.go index f292e399..523f7bfe 100644 --- a/cmd/dex-overlord/main.go +++ b/cmd/dex-overlord/main.go @@ -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, diff --git a/integration/admin_api_test.go b/integration/admin_api_test.go index f8c75764..c89b71a4 100644 --- a/integration/admin_api_test.go +++ b/integration/admin_api_test.go @@ -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() diff --git a/server/admin.go b/server/admin.go index d142516d..8295dfe6 100644 --- a/server/admin.go +++ b/server/admin.go @@ -15,7 +15,8 @@ import ( ) const ( - AdminAPIVersion = "v1" + 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) {