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:
Bobby Rullo 2015-10-01 11:34:53 -07:00
parent 48b3b38c8b
commit 55040c55fa
3 changed files with 61 additions and 6 deletions

View file

@ -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 ") 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") localConnectorID := fs.String("local-connector", "local", "ID of the local connector")
logDebug := fs.Bool("log-debug", false, "log debug-level information") logDebug := fs.Bool("log-debug", false, "log debug-level information")
logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps") logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps")
@ -124,7 +127,7 @@ func main() {
} }
krot := key.NewPrivateKeyRotator(kRepo, *keyPeriod) krot := key.NewPrivateKeyRotator(kRepo, *keyPeriod)
s := server.NewAdminServer(adminAPI, krot) s := server.NewAdminServer(adminAPI, krot, adminAPISecret.String())
h := s.HTTPHandler() h := s.HTTPHandler()
httpsrv := &http.Server{ httpsrv := &http.Server{
Addr: adminURL.Host, Addr: adminURL.Host,

View file

@ -14,6 +14,10 @@ import (
"github.com/coreos/dex/user" "github.com/coreos/dex/user"
) )
const (
adminAPITestSecret = "admin_secret"
)
type adminAPITestFixtures struct { type adminAPITestFixtures struct {
ur user.UserRepo ur user.UserRepo
pwr user.PasswordInfoRepo 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 { func makeAdminAPITestFixtures() *adminAPITestFixtures {
f := &adminAPITestFixtures{} f := &adminAPITestFixtures{}
@ -65,9 +78,13 @@ func makeAdminAPITestFixtures() *adminAPITestFixtures {
f.ur = ur f.ur = ur
f.pwr = pwr f.pwr = pwr
f.adAPI = admin.NewAdminAPI(um, f.ur, f.pwr, "local") 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.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) f.adClient, _ = adminschema.NewWithBasePath(f.hc, f.hSrv.URL)
return f return f
@ -129,6 +146,7 @@ func TestCreateAdmin(t *testing.T) {
tests := []struct { tests := []struct {
admn *adminschema.Admin admn *adminschema.Admin
errCode int errCode int
secret string
}{ }{
{ {
admn: &adminschema.Admin{ admn: &adminschema.Admin{
@ -137,6 +155,14 @@ func TestCreateAdmin(t *testing.T) {
}, },
errCode: -1, errCode: -1,
}, },
{
admn: &adminschema.Admin{
Email: "foo@example.com",
Password: "foopass",
},
errCode: http.StatusUnauthorized,
secret: "bad_secret",
},
{ {
// duplicate Email // duplicate Email
admn: &adminschema.Admin{ admn: &adminschema.Admin{
@ -156,6 +182,11 @@ func TestCreateAdmin(t *testing.T) {
for i, tt := range tests { for i, tt := range tests {
func() { func() {
f := makeAdminAPITestFixtures() f := makeAdminAPITestFixtures()
if tt.secret != "" {
f.hc.Transport = &adminAPITransport{
secret: tt.secret,
}
}
defer f.close() defer f.close()
admn, err := f.adClient.Admin.Create(tt.admn).Do() admn, err := f.adClient.Admin.Create(tt.admn).Do()

View file

@ -15,7 +15,8 @@ import (
) )
const ( const (
AdminAPIVersion = "v1" AdminAPIVersion = "v1"
AdminAPISecretLength = 128
) )
var ( var (
@ -28,9 +29,10 @@ var (
type AdminServer struct { type AdminServer struct {
adminAPI *admin.AdminAPI adminAPI *admin.AdminAPI
checker health.Checker 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{ return &AdminServer{
adminAPI: adminAPI, adminAPI: adminAPI,
checker: health.Checker{ checker: health.Checker{
@ -38,6 +40,7 @@ func NewAdminServer(adminAPI *admin.AdminAPI, rotator *key.PrivateKeyRotator) *A
rotator, rotator,
}, },
}, },
secret: secret,
} }
} }
@ -48,7 +51,25 @@ func (s *AdminServer) HTTPHandler() http.Handler {
r.GET(AdminGetStateEndpoint, s.getState) r.GET(AdminGetStateEndpoint, s.getState)
r.Handler("GET", httpPathHealth, s.checker) r.Handler("GET", httpPathHealth, s.checker)
r.HandlerFunc("GET", httpPathDebugVars, health.ExpvarHandler) 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) { func (s *AdminServer) getAdmin(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {