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 ")
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,

View file

@ -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()

View file

@ -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) {