github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/httpcontext.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver 5 6 import ( 7 "encoding/base64" 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/http" 12 "strings" 13 14 "github.com/juju/errors" 15 "gopkg.in/juju/names.v2" 16 "gopkg.in/macaroon-bakery.v1/httpbakery" 17 18 "github.com/juju/juju/apiserver/common" 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/state" 21 ) 22 23 // httpContext provides context for HTTP handlers. 24 type httpContext struct { 25 // strictValidation means that empty modelUUID values are not valid. 26 strictValidation bool 27 // controllerModelOnly only validates the controller model. 28 controllerModelOnly bool 29 // srv holds the API server instance. 30 srv *Server 31 } 32 33 // stateForRequestUnauthenticated returns a state instance appropriate for 34 // using for the model implicit in the given request 35 // without checking any authentication information. 36 func (ctxt *httpContext) stateForRequestUnauthenticated(r *http.Request) (*state.State, error) { 37 modelUUID, err := validateModelUUID(validateArgs{ 38 statePool: ctxt.srv.statePool, 39 modelUUID: r.URL.Query().Get(":modeluuid"), 40 strict: ctxt.strictValidation, 41 controllerModelOnly: ctxt.controllerModelOnly, 42 }) 43 if err != nil { 44 return nil, errors.Trace(err) 45 } 46 st, err := ctxt.srv.statePool.Get(modelUUID) 47 if err != nil { 48 return nil, errors.Trace(err) 49 } 50 return st, nil 51 } 52 53 // stateForRequestAuthenticated returns a state instance appropriate for 54 // using for the model implicit in the given request. 55 // It also returns the authenticated entity. 56 func (ctxt *httpContext) stateForRequestAuthenticated(r *http.Request) (*state.State, state.Entity, error) { 57 st, err := ctxt.stateForRequestUnauthenticated(r) 58 if err != nil { 59 return nil, nil, errors.Trace(err) 60 } 61 req, err := ctxt.loginRequest(r) 62 if err != nil { 63 return nil, nil, errors.NewUnauthorized(err, "") 64 } 65 authenticator := ctxt.srv.authCtxt.authenticator(r.Host) 66 entity, _, err := checkCreds(st, req, true, authenticator) 67 if err != nil { 68 if common.IsDischargeRequiredError(err) { 69 return nil, nil, errors.Trace(err) 70 } 71 72 // Handle the special case of a worker on a controller machine 73 // acting on behalf of a hosted model. 74 if isMachineTag(req.AuthTag) { 75 entity, err := checkControllerMachineCreds(ctxt.srv.state, req, authenticator) 76 if err != nil { 77 return nil, nil, errors.NewUnauthorized(err, "") 78 } 79 return st, entity, nil 80 } 81 82 // Any other error at this point should be treated as 83 // "unauthorized". 84 return nil, nil, errors.Trace(errors.NewUnauthorized(err, "")) 85 } 86 return st, entity, nil 87 } 88 89 func isMachineTag(tag string) bool { 90 kind, err := names.TagKind(tag) 91 return err == nil && kind == names.MachineTagKind 92 } 93 94 // checkPermissions verifies that given tag passes authentication check. 95 // For example, if only user tags are accepted, all other tags will be denied access. 96 func checkPermissions(tag names.Tag, acceptFunc common.GetAuthFunc) (bool, error) { 97 accept, err := acceptFunc() 98 if err != nil { 99 return false, errors.Trace(err) 100 } 101 if accept(tag) { 102 return true, nil 103 } 104 return false, errors.NotValidf("tag kind %v", tag.Kind()) 105 } 106 107 // stateForRequestAuthenticatedUser is like stateForRequestAuthenticated 108 // except that it also verifies that the authenticated entity is a user. 109 func (ctxt *httpContext) stateForRequestAuthenticatedUser(r *http.Request) (*state.State, state.Entity, error) { 110 st, entity, err := ctxt.stateForRequestAuthenticated(r) 111 if err != nil { 112 return nil, nil, errors.Trace(err) 113 } 114 if ok, err := checkPermissions(entity.Tag(), common.AuthFuncForTagKind(names.UserTagKind)); !ok { 115 return nil, nil, err 116 } 117 return st, entity, nil 118 } 119 120 // stateForRequestAuthenticatedAgent is like stateForRequestAuthenticated 121 // except that it also verifies that the authenticated entity is an agent. 122 func (ctxt *httpContext) stateForRequestAuthenticatedAgent(r *http.Request) (*state.State, state.Entity, error) { 123 authFunc := common.AuthEither( 124 common.AuthFuncForTagKind(names.MachineTagKind), 125 common.AuthFuncForTagKind(names.UnitTagKind), 126 ) 127 st, entity, err := ctxt.stateForRequestAuthenticated(r) 128 if err != nil { 129 return nil, nil, errors.Trace(err) 130 } 131 if ok, err := checkPermissions(entity.Tag(), authFunc); !ok { 132 return nil, nil, err 133 } 134 return st, entity, nil 135 } 136 137 // loginRequest forms a LoginRequest from the information 138 // in the given HTTP request. 139 func (ctxt *httpContext) loginRequest(r *http.Request) (params.LoginRequest, error) { 140 authHeader := r.Header.Get("Authorization") 141 if authHeader == "" { 142 // No authorization header implies an attempt 143 // to login with external user macaroon authentication. 144 return params.LoginRequest{ 145 Macaroons: httpbakery.RequestMacaroons(r), 146 }, nil 147 } 148 parts := strings.Fields(authHeader) 149 if len(parts) != 2 || parts[0] != "Basic" { 150 // Invalid header format or no header provided. 151 return params.LoginRequest{}, errors.NotValidf("request format") 152 } 153 // Challenge is a base64-encoded "tag:pass" string. 154 // See RFC 2617, Section 2. 155 challenge, err := base64.StdEncoding.DecodeString(parts[1]) 156 if err != nil { 157 return params.LoginRequest{}, errors.NotValidf("request format") 158 } 159 tagPass := strings.SplitN(string(challenge), ":", 2) 160 if len(tagPass) != 2 { 161 return params.LoginRequest{}, errors.NotValidf("request format") 162 } 163 // Ensure that a sensible tag was passed. 164 _, err = names.ParseTag(tagPass[0]) 165 if err != nil { 166 return params.LoginRequest{}, errors.Trace(err) 167 } 168 return params.LoginRequest{ 169 AuthTag: tagPass[0], 170 Credentials: tagPass[1], 171 Macaroons: httpbakery.RequestMacaroons(r), 172 Nonce: r.Header.Get(params.MachineNonceHeader), 173 }, nil 174 } 175 176 // stop returns a channel which will be closed when a handler should 177 // exit. 178 func (ctxt *httpContext) stop() <-chan struct{} { 179 return ctxt.srv.tomb.Dying() 180 } 181 182 // sendJSON writes a JSON-encoded response value 183 // to the given writer along with a trailing newline. 184 func sendJSON(w io.Writer, response interface{}) error { 185 body, err := json.Marshal(response) 186 if err != nil { 187 logger.Errorf("cannot marshal JSON result %#v: %v", response, err) 188 return err 189 } 190 body = append(body, '\n') 191 _, err = w.Write(body) 192 return err 193 } 194 195 // sendStatusAndJSON sends an HTTP status code and 196 // a JSON-encoded response to a client. 197 func sendStatusAndJSON(w http.ResponseWriter, statusCode int, response interface{}) error { 198 body, err := json.Marshal(response) 199 if err != nil { 200 return errors.Errorf("cannot marshal JSON result %#v: %v", response, err) 201 } 202 203 if statusCode == http.StatusUnauthorized { 204 w.Header().Set("WWW-Authenticate", `Basic realm="juju"`) 205 } 206 w.Header().Set("Content-Type", params.ContentTypeJSON) 207 w.Header().Set("Content-Length", fmt.Sprint(len(body))) 208 w.WriteHeader(statusCode) 209 if _, err := w.Write(body); err != nil { 210 return errors.Annotate(err, "cannot write response") 211 } 212 return nil 213 } 214 215 // sendError sends a JSON-encoded error response 216 // for errors encountered during processing. 217 func sendError(w http.ResponseWriter, errToSend error) error { 218 paramsErr, statusCode := common.ServerErrorAndStatus(errToSend) 219 logger.Debugf("sending error: %d %v", statusCode, paramsErr) 220 return errors.Trace(sendStatusAndJSON(w, statusCode, ¶ms.ErrorResult{ 221 Error: paramsErr, 222 })) 223 }