2013-12-23 19:38:51 +00:00
|
|
|
package agent
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2014-11-18 16:03:36 +00:00
|
|
|
"fmt"
|
2018-06-05 18:09:45 +00:00
|
|
|
"io"
|
2017-09-25 18:40:42 +00:00
|
|
|
"net"
|
2013-12-23 19:38:51 +00:00
|
|
|
"net/http"
|
2014-03-20 00:50:57 +00:00
|
|
|
"net/http/pprof"
|
2015-04-14 00:31:53 +00:00
|
|
|
"net/url"
|
2019-01-25 01:28:52 +00:00
|
|
|
"reflect"
|
2017-07-15 07:07:08 +00:00
|
|
|
"regexp"
|
2014-02-05 22:36:13 +00:00
|
|
|
"strconv"
|
2014-08-22 19:59:47 +00:00
|
|
|
"strings"
|
2013-12-23 22:26:34 +00:00
|
|
|
"time"
|
2014-08-22 19:59:47 +00:00
|
|
|
|
2018-04-13 16:57:25 +00:00
|
|
|
"github.com/NYTimes/gziphandler"
|
2019-01-10 14:27:26 +00:00
|
|
|
metrics "github.com/armon/go-metrics"
|
2017-08-23 14:52:48 +00:00
|
|
|
"github.com/hashicorp/consul/acl"
|
2018-06-15 12:13:54 +00:00
|
|
|
"github.com/hashicorp/consul/agent/cache"
|
2019-01-22 17:19:36 +00:00
|
|
|
"github.com/hashicorp/consul/agent/consul"
|
2017-07-06 10:34:00 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2019-01-25 01:28:52 +00:00
|
|
|
"github.com/hashicorp/consul/api"
|
2019-01-10 14:27:26 +00:00
|
|
|
cleanhttp "github.com/hashicorp/go-cleanhttp"
|
2014-08-22 19:59:47 +00:00
|
|
|
"github.com/mitchellh/mapstructure"
|
2019-01-10 14:27:26 +00:00
|
|
|
"github.com/pkg/errors"
|
2013-12-23 19:38:51 +00:00
|
|
|
)
|
|
|
|
|
2017-09-26 06:11:19 +00:00
|
|
|
// MethodNotAllowedError should be returned by a handler when the HTTP method is not allowed.
|
|
|
|
type MethodNotAllowedError struct {
|
|
|
|
Method string
|
|
|
|
Allow []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e MethodNotAllowedError) Error() string {
|
|
|
|
return fmt.Sprintf("method %s not allowed", e.Method)
|
|
|
|
}
|
|
|
|
|
2018-05-21 19:02:29 +00:00
|
|
|
// BadRequestError should be returned by a handler when parameters or the payload are not valid
|
|
|
|
type BadRequestError struct {
|
|
|
|
Reason string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e BadRequestError) Error() string {
|
|
|
|
return fmt.Sprintf("Bad request: %s", e.Reason)
|
|
|
|
}
|
|
|
|
|
2019-01-07 14:39:23 +00:00
|
|
|
// CodeWithPayloadError allow returning non HTTP 200
|
|
|
|
// Error codes while not returning PlainText payload
|
|
|
|
type CodeWithPayloadError struct {
|
|
|
|
Reason string
|
|
|
|
StatusCode int
|
|
|
|
ContentType string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e CodeWithPayloadError) Error() string {
|
|
|
|
return e.Reason
|
|
|
|
}
|
|
|
|
|
2019-01-10 14:27:26 +00:00
|
|
|
type ForbiddenError struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e ForbiddenError) Error() string {
|
|
|
|
return "Access is restricted"
|
|
|
|
}
|
|
|
|
|
2017-05-19 09:53:41 +00:00
|
|
|
// HTTPServer provides an HTTP api for an agent.
|
2013-12-23 19:38:51 +00:00
|
|
|
type HTTPServer struct {
|
2017-05-19 09:53:41 +00:00
|
|
|
*http.Server
|
2017-11-07 23:06:59 +00:00
|
|
|
ln net.Listener
|
2017-07-10 20:51:25 +00:00
|
|
|
agent *Agent
|
|
|
|
blacklist *Blacklist
|
|
|
|
|
|
|
|
// proto is filled by the agent to "http" or "https".
|
2017-05-24 13:22:56 +00:00
|
|
|
proto string
|
2014-11-14 19:39:19 +00:00
|
|
|
}
|
|
|
|
|
UI V2 (#4086)
* Move settings to use the same service/route API as the rest of the app
* Put some ideas down for unit testing on adapters
* Favour `Model` over `Entity`
* Move away from using `reopen` to using Mixins
* Amend messages, comment/document some usage
* Make sure the returns are consistent in normalizePayload, also
Add some todo's in to remind me to think consider this further at a
later date. For example, is normalizePayload to be a hook or an
overridable method
* Start stripping back the HTML to semantics
* Use a variable rather than chaining
* Remove unused helpers
* Start picking through the new designs, start with listing pages
* First draft HTML for every page
* Making progress on the CSS
* Keep plugging away at the catalog css
* Looking at scrolling
* Wire up filtering
* Sort out filter counting, more or less done a few outstanding
* Start knocking the forms into shape
* Add in codemirror
* Keep moving forwards with the form like layouts
* Start looking at ACL editing page, add footer in
* Pull the filters back in, look at an autoresizer for scroll views
* First draft toggles
* 2nd draft healthcheck icons
* Tweak node healthcheck icons
* Looking at healthcheck detail icons
* Tweak the filter-bar and add selections to the in content tabs
* Add ACL create, pill-like acl type highlight
* Tweaking the main nav some more
* Working on the filter-bar and freetext-filter
* Masonry layout
* Stick with `checks` instead of healthy/unhealthy
* Fix up the filter numbers/counts
* Use the thead for a measure
* First draft tomography back in
* First draft DC dropdown
* Add a temporary create buttong to kv's
* Move KV and ACL to use a create page
* Move tags
* Run through old tests
* Injectable server
* Start adding test attributes
* Add some page objects
* More test attributes and pages
* Acl filter objects
* Add a page.. page object
* Clickable items in lists
* Add rest/spread babel plugin, remove mirage for now
* Add fix for ember-collection
* Keep track of acl filters
* ember-cli-page-object
* ember-test-selectors
* ui: update version of ui compile deps
* Update static assets
* Centralize radiogroup helper
* Rejig KV's and begin to clean it up
* Work around lack of Tags for the moment..
* Some little css tweaks and start to remove possibles
* Working on the dc page and incidentals
1. Sort the datacenter-picker list
2. Add a selected state to the datacenter-picker
3. Make dc an {Name: dc}
4. Add an env helper to get to 'env vars' from within templates
* Click outside stuff for the datacenter-picker, is-active on nav
* Make sure the dropdown CTA can be active
* Bump ember add pluralize helper
* Little try at sass based custom queries
* Rejig tablular collection so it deals with resizing, actions
1. WIP: start building actions dropdowns
2. Move tabular collection to deal with resizing to rule out differences
* First draft actions dropdowns
* Add ports, selectable IP's
* Flash messages, plus general cleanup/consistency
1. Add ember-cli-flash for flash messages
2. Move everything to get() instead of item.get
3. Spotted a few things that weren't consistent
* DOn't go lower than zero
* First draft vertical menu
* Missed a get, tweak dropmenu tick
* Big cleanup
1. this.get(), this.set() > get(), set()
2. assign > {...{}, ...{}}
3. Seperator > separator
* WIP: settings
* Moved things into a ui-v2 folder
* Decide on a way to do the settings page whilst maintaining the url + dc's
* Start some error pages
* Remove base64 polyfill
* Tie in settings, fix atob bug, tweak layout css
* Centralize confirmations into a component
* Allow switching between the old and new UI with the CONSUL_UI_BETA env var
Currently all the assets are packaged into a single AssetFS and a prefix is configured to switch between the two.
* Attempt at some updates to integrate the v2 ui build into the main infrastructure
* Add redirect to index.html for unknown paths
* Allow redictor to /index.html for new ui when using -ui-dir
* Take ACLs to the correct place on save
* First pass breadcrumbs
* Remove datacenter selector on the index page
* Tweak overall layout
* Make buttons 'resets'
* Tweak last DC stuff
* Validations plus kv keyname viewing tweaks
* Pull sessions back in
* Tweak the env vars to be more reusable
* Move isAnon to the view
* No items and disabled acl css
* ACL and KV details
1. Unauthorized page
2. Make sure the ACL is always selected when it needs it
3. Check record deletion with a changeset
* Few more acl tweaks/corrections
* Add no items view to node > services
* Tags for node > services
* Make sure we have tags
* Fix up the labels on the tomography graph
* Add node link (agent) to kv sessions
* Duplicate up `create` for KV 'root creation'
* Safety check for health checks
* Fix up the grids
* Truncate td a's, fix kv columns
* Watch for spaces in KV id's
* Move actions to their own mixins for now at least
* Link reset to settings incase I want to type it in
* Tweak error page
* Cleanup healthcheck icons in service listing
* Centralize errors and make getting back easier
* Nice numbers
* Compact buttons
* Some incidental css cleanups
* Use 'Key / Value' for root
* Tweak tomography layout
* Fix single healthcheck unhealthy resource
* Get loading screen ready
* Fix healthy healthcheck tick
* Everything in header starts white
* First draft loader
* Refactor the entire backend to use proper unique keys, plus..
1. Make unique keys form dc + slug (uid)
2. Fun with errors...
* Tweak header colors
* Add noopener noreferrer to external links
* Add supers to setupController
* Implement cloning, using ember-data...
* Move the more expensive down the switch order
* First draft empty record cleanup..
* Add the cusomt store test
* Temporarily use the htmlSafe prototype to remove the console warning
* Encode hashes in urls
* Go back to using title for errors for now
* Start removing unused bulma
* Lint
* WIP: Start looking at failing tests
* Remove single redirect test
* Finish off error message styling
* Add full ember-data cache invalidation to avoid stale data...
* Add uncolorable warning icons
* More info icon
* Rearrange single service, plus tag printing
* Logo
* No quotes
* Add a simple startup logo
* Tweak healthcheck statuses
* Fix border-color for healthchecks
* Tweak node tabs
* Catch 401 ACL errors and rethrow with the provided error message
* Remove old acl unauth and error routes
* Missed a super
* Make 'All' refer to number of checks, not services
* Remove ember-resizer, add autoprefixer
* Don't show tomography if its not worth it, viewify it more also
* Little model cleanup
* Chevrons
* Find a way to reliably set the class of html from the view
* Consistent html
* Make sure session id's are visible as long as possible
* Fix single service check count
* Add filters and searchs to the query string
* Don't remember the selected tab
* Change text
* Eror tweaking
* Use chevrons on all breadcrumbs even in kv's
* Clean up a file
* Tweak some messaging
* Makesure the footer overlays whats in the page
* Tweak KV errors
* Move json toggle over to the right
* feedback-dialog along with copy buttons
* Better confirmation dialogs
* Add git sha comment
* Same title as old UI
* Allow defaults
* Make sure value is a string
* WIP: Scrolling dropdowns/confirmations
* Add to kv's
* Remove set
* First pass trace
* Better table rows
* Pull over the hashi code editor styles
* Editor tweaks
* Responsive tabs
* Add number formatting to tomography
* Review whats left todo
* Lint
* Add a coordinate ember data triplet
* Bump in a v2.0.0
* Update old tests
* Get coverage working again
* Make sure query keys are also encoded
* Don't test console.error
* Unit test some more utils
* Tweak the size of the tabular collections
* Clean up gitignore
* Fix copy button rollovers
* Get healthcheck 'icon icons' onto the text baseline
* Tweak healthcheck padding and alignment
* Make sure commas kick in in rtt, probably never get to that
* Improve vertical menu
* Tweak dropdown active state to not have a bg
* Tweak paddings
* Search entire string not just 'startsWith'
* Button states
* Most buttons have 1px border
* More button tweaks
* You can only view kv folders
* CSS cleanup reduction
* Form input states and little cleanup
* More CSS reduction
* Sort checks by importance
* Fix click outside on datacenter picker
* Make sure table th's also auto calculate properly
* Make sure `json` isn't remembered in KV editing
* Fix recursive deletion in KV's
* Centralize size
* Catch updateRecord
* Don't double envode
* model > item consistency
* Action loading and ACL tweaks
* Add settings dependencies to acl tests
* Better loading
* utf-8 base64 encode/decode
* Don't hang off a prototype for htmlSafe
* Missing base64 files...
* Get atob/btoa polyfill right
* Shadowy rollovers
* Disabled button styling for primaries
* autofocuses only onload for now
* Fix footer centering
* Beginning of 'notices'
* Remove the isLocked disabling as we are letting you do what the API does
* Don't forget the documentation link for sessions
* Updates are more likely
* Use exported constant
* Dont export redirectFS and a few other PR updates
* Remove the old bootstrap config which was used for the old UI skin
* Use curlies for multiple properties
2018-05-10 18:52:53 +00:00
|
|
|
type redirectFS struct {
|
|
|
|
fs http.FileSystem
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *redirectFS) Open(name string) (http.File, error) {
|
|
|
|
file, err := fs.fs.Open(name)
|
|
|
|
if err != nil {
|
|
|
|
file, err = fs.fs.Open("/index.html")
|
|
|
|
}
|
|
|
|
return file, err
|
|
|
|
}
|
|
|
|
|
2017-11-29 00:06:26 +00:00
|
|
|
// endpoint is a Consul-specific HTTP handler that takes the usual arguments in
|
|
|
|
// but returns a response object and error, both of which are handled in a
|
|
|
|
// common manner by Consul's HTTP server.
|
|
|
|
type endpoint func(resp http.ResponseWriter, req *http.Request) (interface{}, error)
|
|
|
|
|
|
|
|
// unboundEndpoint is an endpoint method on a server.
|
|
|
|
type unboundEndpoint func(s *HTTPServer, resp http.ResponseWriter, req *http.Request) (interface{}, error)
|
|
|
|
|
|
|
|
// endpoints is a map from URL pattern to unbound endpoint.
|
|
|
|
var endpoints map[string]unboundEndpoint
|
|
|
|
|
2018-02-12 05:28:20 +00:00
|
|
|
// allowedMethods is a map from endpoint prefix to supported HTTP methods.
|
2018-02-18 01:46:11 +00:00
|
|
|
// An empty slice means an endpoint handles OPTIONS requests and MethodNotFound errors itself.
|
2018-02-12 05:28:20 +00:00
|
|
|
var allowedMethods map[string][]string
|
|
|
|
|
2017-11-29 00:06:26 +00:00
|
|
|
// registerEndpoint registers a new endpoint, which should be done at package
|
|
|
|
// init() time.
|
2018-02-12 05:28:20 +00:00
|
|
|
func registerEndpoint(pattern string, methods []string, fn unboundEndpoint) {
|
2017-11-29 00:06:26 +00:00
|
|
|
if endpoints == nil {
|
|
|
|
endpoints = make(map[string]unboundEndpoint)
|
|
|
|
}
|
2018-02-12 05:28:20 +00:00
|
|
|
if endpoints[pattern] != nil || allowedMethods[pattern] != nil {
|
2017-11-29 00:06:26 +00:00
|
|
|
panic(fmt.Errorf("Pattern %q is already registered", pattern))
|
|
|
|
}
|
2018-04-03 20:33:13 +00:00
|
|
|
|
2017-11-29 00:06:26 +00:00
|
|
|
endpoints[pattern] = fn
|
2018-02-12 05:28:20 +00:00
|
|
|
allowedMethods[pattern] = methods
|
2017-11-29 00:06:26 +00:00
|
|
|
}
|
|
|
|
|
2017-12-20 23:47:53 +00:00
|
|
|
// wrappedMux hangs on to the underlying mux for unit tests.
|
|
|
|
type wrappedMux struct {
|
|
|
|
mux *http.ServeMux
|
|
|
|
handler http.Handler
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServeHTTP implements the http.Handler interface.
|
|
|
|
func (w *wrappedMux) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
|
|
|
w.handler.ServeHTTP(resp, req)
|
|
|
|
}
|
|
|
|
|
2017-05-19 09:53:41 +00:00
|
|
|
// handler is used to attach our handlers to the mux
|
|
|
|
func (s *HTTPServer) handler(enableDebug bool) http.Handler {
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
|
|
|
// handleFuncMetrics takes the given pattern and handler and wraps to produce
|
|
|
|
// metrics based on the pattern and request.
|
|
|
|
handleFuncMetrics := func(pattern string, handler http.HandlerFunc) {
|
|
|
|
// Get the parts of the pattern. We omit any initial empty for the
|
|
|
|
// leading slash, and put an underscore as a "thing" placeholder if we
|
|
|
|
// see a trailing slash, which means the part after is parsed. This lets
|
|
|
|
// us distinguish from things like /v1/query and /v1/query/<query id>.
|
|
|
|
var parts []string
|
|
|
|
for i, part := range strings.Split(pattern, "/") {
|
|
|
|
if part == "" {
|
|
|
|
if i == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2016-08-09 22:41:15 +00:00
|
|
|
part = "_"
|
|
|
|
}
|
2017-05-19 09:53:41 +00:00
|
|
|
parts = append(parts, part)
|
2016-08-09 22:41:15 +00:00
|
|
|
}
|
|
|
|
|
2017-05-19 09:53:41 +00:00
|
|
|
// Register the wrapper, which will close over the expensive-to-compute
|
|
|
|
// parts from above.
|
2017-08-08 08:31:38 +00:00
|
|
|
// TODO (kyhavlov): Convert this to utilize metric labels in a major release
|
2017-05-19 09:53:41 +00:00
|
|
|
wrapper := func(resp http.ResponseWriter, req *http.Request) {
|
|
|
|
start := time.Now()
|
|
|
|
handler(resp, req)
|
2017-11-08 00:32:45 +00:00
|
|
|
key := append([]string{"http", req.Method}, parts...)
|
2017-05-19 09:53:41 +00:00
|
|
|
metrics.MeasureSince(key, start)
|
|
|
|
}
|
2018-04-03 20:33:13 +00:00
|
|
|
|
|
|
|
gzipWrapper, _ := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(0))
|
|
|
|
gzipHandler := gzipWrapper(http.HandlerFunc(wrapper))
|
|
|
|
mux.Handle(pattern, gzipHandler)
|
2016-08-09 22:41:15 +00:00
|
|
|
}
|
|
|
|
|
2018-10-17 20:20:35 +00:00
|
|
|
// handlePProf takes the given pattern and pprof handler
|
|
|
|
// and wraps it to add authorization and metrics
|
|
|
|
handlePProf := func(pattern string, handler http.HandlerFunc) {
|
|
|
|
wrapper := func(resp http.ResponseWriter, req *http.Request) {
|
|
|
|
var token string
|
|
|
|
s.parseToken(req, &token)
|
|
|
|
|
|
|
|
rule, err := s.agent.resolveToken(token)
|
|
|
|
if err != nil {
|
|
|
|
resp.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If enableDebug is not set, and ACLs are disabled, write
|
|
|
|
// an unauthorized response
|
|
|
|
if !enableDebug {
|
|
|
|
if s.checkACLDisabled(resp, req) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the token provided does not have the necessary permissions,
|
|
|
|
// write a forbidden response
|
|
|
|
if rule != nil && !rule.OperatorRead() {
|
|
|
|
resp.WriteHeader(http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call the pprof handler
|
|
|
|
handler(resp, req)
|
|
|
|
}
|
|
|
|
|
|
|
|
handleFuncMetrics(pattern, http.HandlerFunc(wrapper))
|
|
|
|
}
|
|
|
|
|
2017-05-19 09:53:41 +00:00
|
|
|
mux.HandleFunc("/", s.Index)
|
2017-11-29 00:06:26 +00:00
|
|
|
for pattern, fn := range endpoints {
|
|
|
|
thisFn := fn
|
2018-02-12 05:28:20 +00:00
|
|
|
methods, _ := allowedMethods[pattern]
|
2017-11-29 00:06:26 +00:00
|
|
|
bound := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return thisFn(s, resp, req)
|
|
|
|
}
|
2018-02-12 05:28:20 +00:00
|
|
|
handleFuncMetrics(pattern, s.wrap(bound, methods))
|
2017-11-29 00:06:26 +00:00
|
|
|
}
|
2018-10-17 20:20:35 +00:00
|
|
|
|
|
|
|
// Register wrapped pprof handlers
|
|
|
|
handlePProf("/debug/pprof/", pprof.Index)
|
|
|
|
handlePProf("/debug/pprof/cmdline", pprof.Cmdline)
|
|
|
|
handlePProf("/debug/pprof/profile", pprof.Profile)
|
|
|
|
handlePProf("/debug/pprof/symbol", pprof.Symbol)
|
|
|
|
handlePProf("/debug/pprof/trace", pprof.Trace)
|
2014-04-23 19:57:06 +00:00
|
|
|
|
UI V2 (#4086)
* Move settings to use the same service/route API as the rest of the app
* Put some ideas down for unit testing on adapters
* Favour `Model` over `Entity`
* Move away from using `reopen` to using Mixins
* Amend messages, comment/document some usage
* Make sure the returns are consistent in normalizePayload, also
Add some todo's in to remind me to think consider this further at a
later date. For example, is normalizePayload to be a hook or an
overridable method
* Start stripping back the HTML to semantics
* Use a variable rather than chaining
* Remove unused helpers
* Start picking through the new designs, start with listing pages
* First draft HTML for every page
* Making progress on the CSS
* Keep plugging away at the catalog css
* Looking at scrolling
* Wire up filtering
* Sort out filter counting, more or less done a few outstanding
* Start knocking the forms into shape
* Add in codemirror
* Keep moving forwards with the form like layouts
* Start looking at ACL editing page, add footer in
* Pull the filters back in, look at an autoresizer for scroll views
* First draft toggles
* 2nd draft healthcheck icons
* Tweak node healthcheck icons
* Looking at healthcheck detail icons
* Tweak the filter-bar and add selections to the in content tabs
* Add ACL create, pill-like acl type highlight
* Tweaking the main nav some more
* Working on the filter-bar and freetext-filter
* Masonry layout
* Stick with `checks` instead of healthy/unhealthy
* Fix up the filter numbers/counts
* Use the thead for a measure
* First draft tomography back in
* First draft DC dropdown
* Add a temporary create buttong to kv's
* Move KV and ACL to use a create page
* Move tags
* Run through old tests
* Injectable server
* Start adding test attributes
* Add some page objects
* More test attributes and pages
* Acl filter objects
* Add a page.. page object
* Clickable items in lists
* Add rest/spread babel plugin, remove mirage for now
* Add fix for ember-collection
* Keep track of acl filters
* ember-cli-page-object
* ember-test-selectors
* ui: update version of ui compile deps
* Update static assets
* Centralize radiogroup helper
* Rejig KV's and begin to clean it up
* Work around lack of Tags for the moment..
* Some little css tweaks and start to remove possibles
* Working on the dc page and incidentals
1. Sort the datacenter-picker list
2. Add a selected state to the datacenter-picker
3. Make dc an {Name: dc}
4. Add an env helper to get to 'env vars' from within templates
* Click outside stuff for the datacenter-picker, is-active on nav
* Make sure the dropdown CTA can be active
* Bump ember add pluralize helper
* Little try at sass based custom queries
* Rejig tablular collection so it deals with resizing, actions
1. WIP: start building actions dropdowns
2. Move tabular collection to deal with resizing to rule out differences
* First draft actions dropdowns
* Add ports, selectable IP's
* Flash messages, plus general cleanup/consistency
1. Add ember-cli-flash for flash messages
2. Move everything to get() instead of item.get
3. Spotted a few things that weren't consistent
* DOn't go lower than zero
* First draft vertical menu
* Missed a get, tweak dropmenu tick
* Big cleanup
1. this.get(), this.set() > get(), set()
2. assign > {...{}, ...{}}
3. Seperator > separator
* WIP: settings
* Moved things into a ui-v2 folder
* Decide on a way to do the settings page whilst maintaining the url + dc's
* Start some error pages
* Remove base64 polyfill
* Tie in settings, fix atob bug, tweak layout css
* Centralize confirmations into a component
* Allow switching between the old and new UI with the CONSUL_UI_BETA env var
Currently all the assets are packaged into a single AssetFS and a prefix is configured to switch between the two.
* Attempt at some updates to integrate the v2 ui build into the main infrastructure
* Add redirect to index.html for unknown paths
* Allow redictor to /index.html for new ui when using -ui-dir
* Take ACLs to the correct place on save
* First pass breadcrumbs
* Remove datacenter selector on the index page
* Tweak overall layout
* Make buttons 'resets'
* Tweak last DC stuff
* Validations plus kv keyname viewing tweaks
* Pull sessions back in
* Tweak the env vars to be more reusable
* Move isAnon to the view
* No items and disabled acl css
* ACL and KV details
1. Unauthorized page
2. Make sure the ACL is always selected when it needs it
3. Check record deletion with a changeset
* Few more acl tweaks/corrections
* Add no items view to node > services
* Tags for node > services
* Make sure we have tags
* Fix up the labels on the tomography graph
* Add node link (agent) to kv sessions
* Duplicate up `create` for KV 'root creation'
* Safety check for health checks
* Fix up the grids
* Truncate td a's, fix kv columns
* Watch for spaces in KV id's
* Move actions to their own mixins for now at least
* Link reset to settings incase I want to type it in
* Tweak error page
* Cleanup healthcheck icons in service listing
* Centralize errors and make getting back easier
* Nice numbers
* Compact buttons
* Some incidental css cleanups
* Use 'Key / Value' for root
* Tweak tomography layout
* Fix single healthcheck unhealthy resource
* Get loading screen ready
* Fix healthy healthcheck tick
* Everything in header starts white
* First draft loader
* Refactor the entire backend to use proper unique keys, plus..
1. Make unique keys form dc + slug (uid)
2. Fun with errors...
* Tweak header colors
* Add noopener noreferrer to external links
* Add supers to setupController
* Implement cloning, using ember-data...
* Move the more expensive down the switch order
* First draft empty record cleanup..
* Add the cusomt store test
* Temporarily use the htmlSafe prototype to remove the console warning
* Encode hashes in urls
* Go back to using title for errors for now
* Start removing unused bulma
* Lint
* WIP: Start looking at failing tests
* Remove single redirect test
* Finish off error message styling
* Add full ember-data cache invalidation to avoid stale data...
* Add uncolorable warning icons
* More info icon
* Rearrange single service, plus tag printing
* Logo
* No quotes
* Add a simple startup logo
* Tweak healthcheck statuses
* Fix border-color for healthchecks
* Tweak node tabs
* Catch 401 ACL errors and rethrow with the provided error message
* Remove old acl unauth and error routes
* Missed a super
* Make 'All' refer to number of checks, not services
* Remove ember-resizer, add autoprefixer
* Don't show tomography if its not worth it, viewify it more also
* Little model cleanup
* Chevrons
* Find a way to reliably set the class of html from the view
* Consistent html
* Make sure session id's are visible as long as possible
* Fix single service check count
* Add filters and searchs to the query string
* Don't remember the selected tab
* Change text
* Eror tweaking
* Use chevrons on all breadcrumbs even in kv's
* Clean up a file
* Tweak some messaging
* Makesure the footer overlays whats in the page
* Tweak KV errors
* Move json toggle over to the right
* feedback-dialog along with copy buttons
* Better confirmation dialogs
* Add git sha comment
* Same title as old UI
* Allow defaults
* Make sure value is a string
* WIP: Scrolling dropdowns/confirmations
* Add to kv's
* Remove set
* First pass trace
* Better table rows
* Pull over the hashi code editor styles
* Editor tweaks
* Responsive tabs
* Add number formatting to tomography
* Review whats left todo
* Lint
* Add a coordinate ember data triplet
* Bump in a v2.0.0
* Update old tests
* Get coverage working again
* Make sure query keys are also encoded
* Don't test console.error
* Unit test some more utils
* Tweak the size of the tabular collections
* Clean up gitignore
* Fix copy button rollovers
* Get healthcheck 'icon icons' onto the text baseline
* Tweak healthcheck padding and alignment
* Make sure commas kick in in rtt, probably never get to that
* Improve vertical menu
* Tweak dropdown active state to not have a bg
* Tweak paddings
* Search entire string not just 'startsWith'
* Button states
* Most buttons have 1px border
* More button tweaks
* You can only view kv folders
* CSS cleanup reduction
* Form input states and little cleanup
* More CSS reduction
* Sort checks by importance
* Fix click outside on datacenter picker
* Make sure table th's also auto calculate properly
* Make sure `json` isn't remembered in KV editing
* Fix recursive deletion in KV's
* Centralize size
* Catch updateRecord
* Don't double envode
* model > item consistency
* Action loading and ACL tweaks
* Add settings dependencies to acl tests
* Better loading
* utf-8 base64 encode/decode
* Don't hang off a prototype for htmlSafe
* Missing base64 files...
* Get atob/btoa polyfill right
* Shadowy rollovers
* Disabled button styling for primaries
* autofocuses only onload for now
* Fix footer centering
* Beginning of 'notices'
* Remove the isLocked disabling as we are letting you do what the API does
* Don't forget the documentation link for sessions
* Updates are more likely
* Use exported constant
* Dont export redirectFS and a few other PR updates
* Remove the old bootstrap config which was used for the old UI skin
* Use curlies for multiple properties
2018-05-10 18:52:53 +00:00
|
|
|
if s.IsUIEnabled() {
|
2018-05-11 16:02:00 +00:00
|
|
|
var uifs http.FileSystem
|
UI V2 (#4086)
* Move settings to use the same service/route API as the rest of the app
* Put some ideas down for unit testing on adapters
* Favour `Model` over `Entity`
* Move away from using `reopen` to using Mixins
* Amend messages, comment/document some usage
* Make sure the returns are consistent in normalizePayload, also
Add some todo's in to remind me to think consider this further at a
later date. For example, is normalizePayload to be a hook or an
overridable method
* Start stripping back the HTML to semantics
* Use a variable rather than chaining
* Remove unused helpers
* Start picking through the new designs, start with listing pages
* First draft HTML for every page
* Making progress on the CSS
* Keep plugging away at the catalog css
* Looking at scrolling
* Wire up filtering
* Sort out filter counting, more or less done a few outstanding
* Start knocking the forms into shape
* Add in codemirror
* Keep moving forwards with the form like layouts
* Start looking at ACL editing page, add footer in
* Pull the filters back in, look at an autoresizer for scroll views
* First draft toggles
* 2nd draft healthcheck icons
* Tweak node healthcheck icons
* Looking at healthcheck detail icons
* Tweak the filter-bar and add selections to the in content tabs
* Add ACL create, pill-like acl type highlight
* Tweaking the main nav some more
* Working on the filter-bar and freetext-filter
* Masonry layout
* Stick with `checks` instead of healthy/unhealthy
* Fix up the filter numbers/counts
* Use the thead for a measure
* First draft tomography back in
* First draft DC dropdown
* Add a temporary create buttong to kv's
* Move KV and ACL to use a create page
* Move tags
* Run through old tests
* Injectable server
* Start adding test attributes
* Add some page objects
* More test attributes and pages
* Acl filter objects
* Add a page.. page object
* Clickable items in lists
* Add rest/spread babel plugin, remove mirage for now
* Add fix for ember-collection
* Keep track of acl filters
* ember-cli-page-object
* ember-test-selectors
* ui: update version of ui compile deps
* Update static assets
* Centralize radiogroup helper
* Rejig KV's and begin to clean it up
* Work around lack of Tags for the moment..
* Some little css tweaks and start to remove possibles
* Working on the dc page and incidentals
1. Sort the datacenter-picker list
2. Add a selected state to the datacenter-picker
3. Make dc an {Name: dc}
4. Add an env helper to get to 'env vars' from within templates
* Click outside stuff for the datacenter-picker, is-active on nav
* Make sure the dropdown CTA can be active
* Bump ember add pluralize helper
* Little try at sass based custom queries
* Rejig tablular collection so it deals with resizing, actions
1. WIP: start building actions dropdowns
2. Move tabular collection to deal with resizing to rule out differences
* First draft actions dropdowns
* Add ports, selectable IP's
* Flash messages, plus general cleanup/consistency
1. Add ember-cli-flash for flash messages
2. Move everything to get() instead of item.get
3. Spotted a few things that weren't consistent
* DOn't go lower than zero
* First draft vertical menu
* Missed a get, tweak dropmenu tick
* Big cleanup
1. this.get(), this.set() > get(), set()
2. assign > {...{}, ...{}}
3. Seperator > separator
* WIP: settings
* Moved things into a ui-v2 folder
* Decide on a way to do the settings page whilst maintaining the url + dc's
* Start some error pages
* Remove base64 polyfill
* Tie in settings, fix atob bug, tweak layout css
* Centralize confirmations into a component
* Allow switching between the old and new UI with the CONSUL_UI_BETA env var
Currently all the assets are packaged into a single AssetFS and a prefix is configured to switch between the two.
* Attempt at some updates to integrate the v2 ui build into the main infrastructure
* Add redirect to index.html for unknown paths
* Allow redictor to /index.html for new ui when using -ui-dir
* Take ACLs to the correct place on save
* First pass breadcrumbs
* Remove datacenter selector on the index page
* Tweak overall layout
* Make buttons 'resets'
* Tweak last DC stuff
* Validations plus kv keyname viewing tweaks
* Pull sessions back in
* Tweak the env vars to be more reusable
* Move isAnon to the view
* No items and disabled acl css
* ACL and KV details
1. Unauthorized page
2. Make sure the ACL is always selected when it needs it
3. Check record deletion with a changeset
* Few more acl tweaks/corrections
* Add no items view to node > services
* Tags for node > services
* Make sure we have tags
* Fix up the labels on the tomography graph
* Add node link (agent) to kv sessions
* Duplicate up `create` for KV 'root creation'
* Safety check for health checks
* Fix up the grids
* Truncate td a's, fix kv columns
* Watch for spaces in KV id's
* Move actions to their own mixins for now at least
* Link reset to settings incase I want to type it in
* Tweak error page
* Cleanup healthcheck icons in service listing
* Centralize errors and make getting back easier
* Nice numbers
* Compact buttons
* Some incidental css cleanups
* Use 'Key / Value' for root
* Tweak tomography layout
* Fix single healthcheck unhealthy resource
* Get loading screen ready
* Fix healthy healthcheck tick
* Everything in header starts white
* First draft loader
* Refactor the entire backend to use proper unique keys, plus..
1. Make unique keys form dc + slug (uid)
2. Fun with errors...
* Tweak header colors
* Add noopener noreferrer to external links
* Add supers to setupController
* Implement cloning, using ember-data...
* Move the more expensive down the switch order
* First draft empty record cleanup..
* Add the cusomt store test
* Temporarily use the htmlSafe prototype to remove the console warning
* Encode hashes in urls
* Go back to using title for errors for now
* Start removing unused bulma
* Lint
* WIP: Start looking at failing tests
* Remove single redirect test
* Finish off error message styling
* Add full ember-data cache invalidation to avoid stale data...
* Add uncolorable warning icons
* More info icon
* Rearrange single service, plus tag printing
* Logo
* No quotes
* Add a simple startup logo
* Tweak healthcheck statuses
* Fix border-color for healthchecks
* Tweak node tabs
* Catch 401 ACL errors and rethrow with the provided error message
* Remove old acl unauth and error routes
* Missed a super
* Make 'All' refer to number of checks, not services
* Remove ember-resizer, add autoprefixer
* Don't show tomography if its not worth it, viewify it more also
* Little model cleanup
* Chevrons
* Find a way to reliably set the class of html from the view
* Consistent html
* Make sure session id's are visible as long as possible
* Fix single service check count
* Add filters and searchs to the query string
* Don't remember the selected tab
* Change text
* Eror tweaking
* Use chevrons on all breadcrumbs even in kv's
* Clean up a file
* Tweak some messaging
* Makesure the footer overlays whats in the page
* Tweak KV errors
* Move json toggle over to the right
* feedback-dialog along with copy buttons
* Better confirmation dialogs
* Add git sha comment
* Same title as old UI
* Allow defaults
* Make sure value is a string
* WIP: Scrolling dropdowns/confirmations
* Add to kv's
* Remove set
* First pass trace
* Better table rows
* Pull over the hashi code editor styles
* Editor tweaks
* Responsive tabs
* Add number formatting to tomography
* Review whats left todo
* Lint
* Add a coordinate ember data triplet
* Bump in a v2.0.0
* Update old tests
* Get coverage working again
* Make sure query keys are also encoded
* Don't test console.error
* Unit test some more utils
* Tweak the size of the tabular collections
* Clean up gitignore
* Fix copy button rollovers
* Get healthcheck 'icon icons' onto the text baseline
* Tweak healthcheck padding and alignment
* Make sure commas kick in in rtt, probably never get to that
* Improve vertical menu
* Tweak dropdown active state to not have a bg
* Tweak paddings
* Search entire string not just 'startsWith'
* Button states
* Most buttons have 1px border
* More button tweaks
* You can only view kv folders
* CSS cleanup reduction
* Form input states and little cleanup
* More CSS reduction
* Sort checks by importance
* Fix click outside on datacenter picker
* Make sure table th's also auto calculate properly
* Make sure `json` isn't remembered in KV editing
* Fix recursive deletion in KV's
* Centralize size
* Catch updateRecord
* Don't double envode
* model > item consistency
* Action loading and ACL tweaks
* Add settings dependencies to acl tests
* Better loading
* utf-8 base64 encode/decode
* Don't hang off a prototype for htmlSafe
* Missing base64 files...
* Get atob/btoa polyfill right
* Shadowy rollovers
* Disabled button styling for primaries
* autofocuses only onload for now
* Fix footer centering
* Beginning of 'notices'
* Remove the isLocked disabling as we are letting you do what the API does
* Don't forget the documentation link for sessions
* Updates are more likely
* Use exported constant
* Dont export redirectFS and a few other PR updates
* Remove the old bootstrap config which was used for the old UI skin
* Use curlies for multiple properties
2018-05-10 18:52:53 +00:00
|
|
|
|
2018-05-11 16:02:00 +00:00
|
|
|
// Use the custom UI dir if provided.
|
UI V2 (#4086)
* Move settings to use the same service/route API as the rest of the app
* Put some ideas down for unit testing on adapters
* Favour `Model` over `Entity`
* Move away from using `reopen` to using Mixins
* Amend messages, comment/document some usage
* Make sure the returns are consistent in normalizePayload, also
Add some todo's in to remind me to think consider this further at a
later date. For example, is normalizePayload to be a hook or an
overridable method
* Start stripping back the HTML to semantics
* Use a variable rather than chaining
* Remove unused helpers
* Start picking through the new designs, start with listing pages
* First draft HTML for every page
* Making progress on the CSS
* Keep plugging away at the catalog css
* Looking at scrolling
* Wire up filtering
* Sort out filter counting, more or less done a few outstanding
* Start knocking the forms into shape
* Add in codemirror
* Keep moving forwards with the form like layouts
* Start looking at ACL editing page, add footer in
* Pull the filters back in, look at an autoresizer for scroll views
* First draft toggles
* 2nd draft healthcheck icons
* Tweak node healthcheck icons
* Looking at healthcheck detail icons
* Tweak the filter-bar and add selections to the in content tabs
* Add ACL create, pill-like acl type highlight
* Tweaking the main nav some more
* Working on the filter-bar and freetext-filter
* Masonry layout
* Stick with `checks` instead of healthy/unhealthy
* Fix up the filter numbers/counts
* Use the thead for a measure
* First draft tomography back in
* First draft DC dropdown
* Add a temporary create buttong to kv's
* Move KV and ACL to use a create page
* Move tags
* Run through old tests
* Injectable server
* Start adding test attributes
* Add some page objects
* More test attributes and pages
* Acl filter objects
* Add a page.. page object
* Clickable items in lists
* Add rest/spread babel plugin, remove mirage for now
* Add fix for ember-collection
* Keep track of acl filters
* ember-cli-page-object
* ember-test-selectors
* ui: update version of ui compile deps
* Update static assets
* Centralize radiogroup helper
* Rejig KV's and begin to clean it up
* Work around lack of Tags for the moment..
* Some little css tweaks and start to remove possibles
* Working on the dc page and incidentals
1. Sort the datacenter-picker list
2. Add a selected state to the datacenter-picker
3. Make dc an {Name: dc}
4. Add an env helper to get to 'env vars' from within templates
* Click outside stuff for the datacenter-picker, is-active on nav
* Make sure the dropdown CTA can be active
* Bump ember add pluralize helper
* Little try at sass based custom queries
* Rejig tablular collection so it deals with resizing, actions
1. WIP: start building actions dropdowns
2. Move tabular collection to deal with resizing to rule out differences
* First draft actions dropdowns
* Add ports, selectable IP's
* Flash messages, plus general cleanup/consistency
1. Add ember-cli-flash for flash messages
2. Move everything to get() instead of item.get
3. Spotted a few things that weren't consistent
* DOn't go lower than zero
* First draft vertical menu
* Missed a get, tweak dropmenu tick
* Big cleanup
1. this.get(), this.set() > get(), set()
2. assign > {...{}, ...{}}
3. Seperator > separator
* WIP: settings
* Moved things into a ui-v2 folder
* Decide on a way to do the settings page whilst maintaining the url + dc's
* Start some error pages
* Remove base64 polyfill
* Tie in settings, fix atob bug, tweak layout css
* Centralize confirmations into a component
* Allow switching between the old and new UI with the CONSUL_UI_BETA env var
Currently all the assets are packaged into a single AssetFS and a prefix is configured to switch between the two.
* Attempt at some updates to integrate the v2 ui build into the main infrastructure
* Add redirect to index.html for unknown paths
* Allow redictor to /index.html for new ui when using -ui-dir
* Take ACLs to the correct place on save
* First pass breadcrumbs
* Remove datacenter selector on the index page
* Tweak overall layout
* Make buttons 'resets'
* Tweak last DC stuff
* Validations plus kv keyname viewing tweaks
* Pull sessions back in
* Tweak the env vars to be more reusable
* Move isAnon to the view
* No items and disabled acl css
* ACL and KV details
1. Unauthorized page
2. Make sure the ACL is always selected when it needs it
3. Check record deletion with a changeset
* Few more acl tweaks/corrections
* Add no items view to node > services
* Tags for node > services
* Make sure we have tags
* Fix up the labels on the tomography graph
* Add node link (agent) to kv sessions
* Duplicate up `create` for KV 'root creation'
* Safety check for health checks
* Fix up the grids
* Truncate td a's, fix kv columns
* Watch for spaces in KV id's
* Move actions to their own mixins for now at least
* Link reset to settings incase I want to type it in
* Tweak error page
* Cleanup healthcheck icons in service listing
* Centralize errors and make getting back easier
* Nice numbers
* Compact buttons
* Some incidental css cleanups
* Use 'Key / Value' for root
* Tweak tomography layout
* Fix single healthcheck unhealthy resource
* Get loading screen ready
* Fix healthy healthcheck tick
* Everything in header starts white
* First draft loader
* Refactor the entire backend to use proper unique keys, plus..
1. Make unique keys form dc + slug (uid)
2. Fun with errors...
* Tweak header colors
* Add noopener noreferrer to external links
* Add supers to setupController
* Implement cloning, using ember-data...
* Move the more expensive down the switch order
* First draft empty record cleanup..
* Add the cusomt store test
* Temporarily use the htmlSafe prototype to remove the console warning
* Encode hashes in urls
* Go back to using title for errors for now
* Start removing unused bulma
* Lint
* WIP: Start looking at failing tests
* Remove single redirect test
* Finish off error message styling
* Add full ember-data cache invalidation to avoid stale data...
* Add uncolorable warning icons
* More info icon
* Rearrange single service, plus tag printing
* Logo
* No quotes
* Add a simple startup logo
* Tweak healthcheck statuses
* Fix border-color for healthchecks
* Tweak node tabs
* Catch 401 ACL errors and rethrow with the provided error message
* Remove old acl unauth and error routes
* Missed a super
* Make 'All' refer to number of checks, not services
* Remove ember-resizer, add autoprefixer
* Don't show tomography if its not worth it, viewify it more also
* Little model cleanup
* Chevrons
* Find a way to reliably set the class of html from the view
* Consistent html
* Make sure session id's are visible as long as possible
* Fix single service check count
* Add filters and searchs to the query string
* Don't remember the selected tab
* Change text
* Eror tweaking
* Use chevrons on all breadcrumbs even in kv's
* Clean up a file
* Tweak some messaging
* Makesure the footer overlays whats in the page
* Tweak KV errors
* Move json toggle over to the right
* feedback-dialog along with copy buttons
* Better confirmation dialogs
* Add git sha comment
* Same title as old UI
* Allow defaults
* Make sure value is a string
* WIP: Scrolling dropdowns/confirmations
* Add to kv's
* Remove set
* First pass trace
* Better table rows
* Pull over the hashi code editor styles
* Editor tweaks
* Responsive tabs
* Add number formatting to tomography
* Review whats left todo
* Lint
* Add a coordinate ember data triplet
* Bump in a v2.0.0
* Update old tests
* Get coverage working again
* Make sure query keys are also encoded
* Don't test console.error
* Unit test some more utils
* Tweak the size of the tabular collections
* Clean up gitignore
* Fix copy button rollovers
* Get healthcheck 'icon icons' onto the text baseline
* Tweak healthcheck padding and alignment
* Make sure commas kick in in rtt, probably never get to that
* Improve vertical menu
* Tweak dropdown active state to not have a bg
* Tweak paddings
* Search entire string not just 'startsWith'
* Button states
* Most buttons have 1px border
* More button tweaks
* You can only view kv folders
* CSS cleanup reduction
* Form input states and little cleanup
* More CSS reduction
* Sort checks by importance
* Fix click outside on datacenter picker
* Make sure table th's also auto calculate properly
* Make sure `json` isn't remembered in KV editing
* Fix recursive deletion in KV's
* Centralize size
* Catch updateRecord
* Don't double envode
* model > item consistency
* Action loading and ACL tweaks
* Add settings dependencies to acl tests
* Better loading
* utf-8 base64 encode/decode
* Don't hang off a prototype for htmlSafe
* Missing base64 files...
* Get atob/btoa polyfill right
* Shadowy rollovers
* Disabled button styling for primaries
* autofocuses only onload for now
* Fix footer centering
* Beginning of 'notices'
* Remove the isLocked disabling as we are letting you do what the API does
* Don't forget the documentation link for sessions
* Updates are more likely
* Use exported constant
* Dont export redirectFS and a few other PR updates
* Remove the old bootstrap config which was used for the old UI skin
* Use curlies for multiple properties
2018-05-10 18:52:53 +00:00
|
|
|
if s.agent.config.UIDir != "" {
|
|
|
|
uifs = http.Dir(s.agent.config.UIDir)
|
|
|
|
} else {
|
|
|
|
fs := assetFS()
|
|
|
|
uifs = fs
|
|
|
|
}
|
2019-04-12 15:02:27 +00:00
|
|
|
uifs = &redirectFS{fs: uifs}
|
2018-05-11 16:02:00 +00:00
|
|
|
|
2018-12-17 18:35:03 +00:00
|
|
|
mux.Handle("/robots.txt", http.FileServer(uifs))
|
UI V2 (#4086)
* Move settings to use the same service/route API as the rest of the app
* Put some ideas down for unit testing on adapters
* Favour `Model` over `Entity`
* Move away from using `reopen` to using Mixins
* Amend messages, comment/document some usage
* Make sure the returns are consistent in normalizePayload, also
Add some todo's in to remind me to think consider this further at a
later date. For example, is normalizePayload to be a hook or an
overridable method
* Start stripping back the HTML to semantics
* Use a variable rather than chaining
* Remove unused helpers
* Start picking through the new designs, start with listing pages
* First draft HTML for every page
* Making progress on the CSS
* Keep plugging away at the catalog css
* Looking at scrolling
* Wire up filtering
* Sort out filter counting, more or less done a few outstanding
* Start knocking the forms into shape
* Add in codemirror
* Keep moving forwards with the form like layouts
* Start looking at ACL editing page, add footer in
* Pull the filters back in, look at an autoresizer for scroll views
* First draft toggles
* 2nd draft healthcheck icons
* Tweak node healthcheck icons
* Looking at healthcheck detail icons
* Tweak the filter-bar and add selections to the in content tabs
* Add ACL create, pill-like acl type highlight
* Tweaking the main nav some more
* Working on the filter-bar and freetext-filter
* Masonry layout
* Stick with `checks` instead of healthy/unhealthy
* Fix up the filter numbers/counts
* Use the thead for a measure
* First draft tomography back in
* First draft DC dropdown
* Add a temporary create buttong to kv's
* Move KV and ACL to use a create page
* Move tags
* Run through old tests
* Injectable server
* Start adding test attributes
* Add some page objects
* More test attributes and pages
* Acl filter objects
* Add a page.. page object
* Clickable items in lists
* Add rest/spread babel plugin, remove mirage for now
* Add fix for ember-collection
* Keep track of acl filters
* ember-cli-page-object
* ember-test-selectors
* ui: update version of ui compile deps
* Update static assets
* Centralize radiogroup helper
* Rejig KV's and begin to clean it up
* Work around lack of Tags for the moment..
* Some little css tweaks and start to remove possibles
* Working on the dc page and incidentals
1. Sort the datacenter-picker list
2. Add a selected state to the datacenter-picker
3. Make dc an {Name: dc}
4. Add an env helper to get to 'env vars' from within templates
* Click outside stuff for the datacenter-picker, is-active on nav
* Make sure the dropdown CTA can be active
* Bump ember add pluralize helper
* Little try at sass based custom queries
* Rejig tablular collection so it deals with resizing, actions
1. WIP: start building actions dropdowns
2. Move tabular collection to deal with resizing to rule out differences
* First draft actions dropdowns
* Add ports, selectable IP's
* Flash messages, plus general cleanup/consistency
1. Add ember-cli-flash for flash messages
2. Move everything to get() instead of item.get
3. Spotted a few things that weren't consistent
* DOn't go lower than zero
* First draft vertical menu
* Missed a get, tweak dropmenu tick
* Big cleanup
1. this.get(), this.set() > get(), set()
2. assign > {...{}, ...{}}
3. Seperator > separator
* WIP: settings
* Moved things into a ui-v2 folder
* Decide on a way to do the settings page whilst maintaining the url + dc's
* Start some error pages
* Remove base64 polyfill
* Tie in settings, fix atob bug, tweak layout css
* Centralize confirmations into a component
* Allow switching between the old and new UI with the CONSUL_UI_BETA env var
Currently all the assets are packaged into a single AssetFS and a prefix is configured to switch between the two.
* Attempt at some updates to integrate the v2 ui build into the main infrastructure
* Add redirect to index.html for unknown paths
* Allow redictor to /index.html for new ui when using -ui-dir
* Take ACLs to the correct place on save
* First pass breadcrumbs
* Remove datacenter selector on the index page
* Tweak overall layout
* Make buttons 'resets'
* Tweak last DC stuff
* Validations plus kv keyname viewing tweaks
* Pull sessions back in
* Tweak the env vars to be more reusable
* Move isAnon to the view
* No items and disabled acl css
* ACL and KV details
1. Unauthorized page
2. Make sure the ACL is always selected when it needs it
3. Check record deletion with a changeset
* Few more acl tweaks/corrections
* Add no items view to node > services
* Tags for node > services
* Make sure we have tags
* Fix up the labels on the tomography graph
* Add node link (agent) to kv sessions
* Duplicate up `create` for KV 'root creation'
* Safety check for health checks
* Fix up the grids
* Truncate td a's, fix kv columns
* Watch for spaces in KV id's
* Move actions to their own mixins for now at least
* Link reset to settings incase I want to type it in
* Tweak error page
* Cleanup healthcheck icons in service listing
* Centralize errors and make getting back easier
* Nice numbers
* Compact buttons
* Some incidental css cleanups
* Use 'Key / Value' for root
* Tweak tomography layout
* Fix single healthcheck unhealthy resource
* Get loading screen ready
* Fix healthy healthcheck tick
* Everything in header starts white
* First draft loader
* Refactor the entire backend to use proper unique keys, plus..
1. Make unique keys form dc + slug (uid)
2. Fun with errors...
* Tweak header colors
* Add noopener noreferrer to external links
* Add supers to setupController
* Implement cloning, using ember-data...
* Move the more expensive down the switch order
* First draft empty record cleanup..
* Add the cusomt store test
* Temporarily use the htmlSafe prototype to remove the console warning
* Encode hashes in urls
* Go back to using title for errors for now
* Start removing unused bulma
* Lint
* WIP: Start looking at failing tests
* Remove single redirect test
* Finish off error message styling
* Add full ember-data cache invalidation to avoid stale data...
* Add uncolorable warning icons
* More info icon
* Rearrange single service, plus tag printing
* Logo
* No quotes
* Add a simple startup logo
* Tweak healthcheck statuses
* Fix border-color for healthchecks
* Tweak node tabs
* Catch 401 ACL errors and rethrow with the provided error message
* Remove old acl unauth and error routes
* Missed a super
* Make 'All' refer to number of checks, not services
* Remove ember-resizer, add autoprefixer
* Don't show tomography if its not worth it, viewify it more also
* Little model cleanup
* Chevrons
* Find a way to reliably set the class of html from the view
* Consistent html
* Make sure session id's are visible as long as possible
* Fix single service check count
* Add filters and searchs to the query string
* Don't remember the selected tab
* Change text
* Eror tweaking
* Use chevrons on all breadcrumbs even in kv's
* Clean up a file
* Tweak some messaging
* Makesure the footer overlays whats in the page
* Tweak KV errors
* Move json toggle over to the right
* feedback-dialog along with copy buttons
* Better confirmation dialogs
* Add git sha comment
* Same title as old UI
* Allow defaults
* Make sure value is a string
* WIP: Scrolling dropdowns/confirmations
* Add to kv's
* Remove set
* First pass trace
* Better table rows
* Pull over the hashi code editor styles
* Editor tweaks
* Responsive tabs
* Add number formatting to tomography
* Review whats left todo
* Lint
* Add a coordinate ember data triplet
* Bump in a v2.0.0
* Update old tests
* Get coverage working again
* Make sure query keys are also encoded
* Don't test console.error
* Unit test some more utils
* Tweak the size of the tabular collections
* Clean up gitignore
* Fix copy button rollovers
* Get healthcheck 'icon icons' onto the text baseline
* Tweak healthcheck padding and alignment
* Make sure commas kick in in rtt, probably never get to that
* Improve vertical menu
* Tweak dropdown active state to not have a bg
* Tweak paddings
* Search entire string not just 'startsWith'
* Button states
* Most buttons have 1px border
* More button tweaks
* You can only view kv folders
* CSS cleanup reduction
* Form input states and little cleanup
* More CSS reduction
* Sort checks by importance
* Fix click outside on datacenter picker
* Make sure table th's also auto calculate properly
* Make sure `json` isn't remembered in KV editing
* Fix recursive deletion in KV's
* Centralize size
* Catch updateRecord
* Don't double envode
* model > item consistency
* Action loading and ACL tweaks
* Add settings dependencies to acl tests
* Better loading
* utf-8 base64 encode/decode
* Don't hang off a prototype for htmlSafe
* Missing base64 files...
* Get atob/btoa polyfill right
* Shadowy rollovers
* Disabled button styling for primaries
* autofocuses only onload for now
* Fix footer centering
* Beginning of 'notices'
* Remove the isLocked disabling as we are letting you do what the API does
* Don't forget the documentation link for sessions
* Updates are more likely
* Use exported constant
* Dont export redirectFS and a few other PR updates
* Remove the old bootstrap config which was used for the old UI skin
* Use curlies for multiple properties
2018-05-10 18:52:53 +00:00
|
|
|
mux.Handle("/ui/", http.StripPrefix("/ui/", http.FileServer(uifs)))
|
2015-02-11 21:25:04 +00:00
|
|
|
}
|
2017-12-20 23:47:53 +00:00
|
|
|
|
|
|
|
// Wrap the whole mux with a handler that bans URLs with non-printable
|
2018-07-26 12:53:39 +00:00
|
|
|
// characters, unless disabled explicitly to deal with old keys that fail this
|
|
|
|
// check.
|
|
|
|
h := cleanhttp.PrintablePathCheckHandler(mux, nil)
|
|
|
|
if s.agent.config.DisableHTTPUnprintableCharFilter {
|
|
|
|
h = mux
|
|
|
|
}
|
2017-12-20 23:47:53 +00:00
|
|
|
return &wrappedMux{
|
|
|
|
mux: mux,
|
2018-07-26 12:53:39 +00:00
|
|
|
handler: h,
|
2017-12-20 23:47:53 +00:00
|
|
|
}
|
2013-12-23 19:38:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-22 04:30:29 +00:00
|
|
|
// nodeName returns the node name of the agent
|
|
|
|
func (s *HTTPServer) nodeName() string {
|
|
|
|
return s.agent.config.NodeName
|
|
|
|
}
|
|
|
|
|
2017-07-15 07:07:08 +00:00
|
|
|
// aclEndpointRE is used to find old ACL endpoints that take tokens in the URL
|
|
|
|
// so that we can redact them. The ACL endpoints that take the token in the URL
|
|
|
|
// are all of the form /v1/acl/<verb>/<token>, and can optionally include query
|
|
|
|
// parameters which are indicated by a question mark. We capture the part before
|
|
|
|
// the token, the token, and any query parameters after, and then reassemble as
|
|
|
|
// $1<hidden>$3 (the token in $2 isn't used), which will give:
|
|
|
|
//
|
|
|
|
// /v1/acl/clone/foo -> /v1/acl/clone/<hidden>
|
|
|
|
// /v1/acl/clone/foo?token=bar -> /v1/acl/clone/<hidden>?token=<hidden>
|
|
|
|
//
|
|
|
|
// The query parameter in the example above is obfuscated like any other, after
|
|
|
|
// this regular expression is applied, so the regular expression substitution
|
|
|
|
// results in:
|
|
|
|
//
|
|
|
|
// /v1/acl/clone/foo?token=bar -> /v1/acl/clone/<hidden>?token=bar
|
|
|
|
// ^---- $1 ----^^- $2 -^^-- $3 --^
|
|
|
|
//
|
|
|
|
// And then the loop that looks for parameters called "token" does the last
|
|
|
|
// step to get to the final redacted form.
|
|
|
|
var (
|
2018-10-19 16:04:07 +00:00
|
|
|
aclEndpointRE = regexp.MustCompile("^(/v1/acl/(create|update|destroy|info|clone|list)/)([^?]+)([?]?.*)$")
|
2017-07-15 07:07:08 +00:00
|
|
|
)
|
|
|
|
|
2013-12-23 19:38:51 +00:00
|
|
|
// wrap is used to wrap functions to make them more convenient
|
2018-02-12 05:28:20 +00:00
|
|
|
func (s *HTTPServer) wrap(handler endpoint, methods []string) http.HandlerFunc {
|
2017-05-11 17:37:47 +00:00
|
|
|
return func(resp http.ResponseWriter, req *http.Request) {
|
2017-09-25 18:40:42 +00:00
|
|
|
setHeaders(resp, s.agent.config.HTTPResponseHeaders)
|
|
|
|
setTranslateAddr(resp, s.agent.config.TranslateWANAddrs)
|
2014-12-28 04:53:19 +00:00
|
|
|
|
2015-04-12 18:17:31 +00:00
|
|
|
// Obfuscate any tokens from appearing in the logs
|
2015-04-14 00:31:53 +00:00
|
|
|
formVals, err := url.ParseQuery(req.URL.RawQuery)
|
|
|
|
if err != nil {
|
2017-05-19 09:53:41 +00:00
|
|
|
s.agent.logger.Printf("[ERR] http: Failed to decode query: %s from=%s", err, req.RemoteAddr)
|
2017-08-23 19:19:11 +00:00
|
|
|
resp.WriteHeader(http.StatusInternalServerError)
|
2015-04-14 00:31:53 +00:00
|
|
|
return
|
|
|
|
}
|
2015-04-12 18:17:31 +00:00
|
|
|
logURL := req.URL.String()
|
2015-04-14 00:31:53 +00:00
|
|
|
if tokens, ok := formVals["token"]; ok {
|
2015-04-12 18:17:31 +00:00
|
|
|
for _, token := range tokens {
|
2015-06-12 07:09:51 +00:00
|
|
|
if token == "" {
|
|
|
|
logURL += "<hidden>"
|
|
|
|
continue
|
|
|
|
}
|
2015-04-12 18:17:31 +00:00
|
|
|
logURL = strings.Replace(logURL, token, "<hidden>", -1)
|
|
|
|
}
|
|
|
|
}
|
2018-10-19 16:04:07 +00:00
|
|
|
logURL = aclEndpointRE.ReplaceAllString(logURL, "$1<hidden>$4")
|
2015-04-12 18:17:31 +00:00
|
|
|
|
2017-07-10 20:51:25 +00:00
|
|
|
if s.blacklist.Block(req.URL.Path) {
|
|
|
|
errMsg := "Endpoint is blocked by agent configuration"
|
|
|
|
s.agent.logger.Printf("[ERR] http: Request %s %v, error: %v from=%s", req.Method, logURL, err, req.RemoteAddr)
|
|
|
|
resp.WriteHeader(http.StatusForbidden)
|
|
|
|
fmt.Fprint(resp, errMsg)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-01-10 14:27:26 +00:00
|
|
|
isForbidden := func(err error) bool {
|
|
|
|
if acl.IsErrPermissionDenied(err) || acl.IsErrNotFound(err) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
_, ok := err.(ForbiddenError)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2017-09-26 06:11:19 +00:00
|
|
|
isMethodNotAllowed := func(err error) bool {
|
|
|
|
_, ok := err.(MethodNotAllowedError)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2018-05-21 19:02:29 +00:00
|
|
|
isBadRequest := func(err error) bool {
|
|
|
|
_, ok := err.(BadRequestError)
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-01-22 17:19:36 +00:00
|
|
|
isTooManyRequests := func(err error) bool {
|
|
|
|
// Sadness net/rpc can't do nice typed errors so this is all we got
|
|
|
|
return err.Error() == consul.ErrRateLimited.Error()
|
|
|
|
}
|
|
|
|
|
2018-02-12 05:28:20 +00:00
|
|
|
addAllowHeader := func(methods []string) {
|
|
|
|
resp.Header().Add("Allow", strings.Join(methods, ","))
|
|
|
|
}
|
|
|
|
|
2017-05-11 17:37:47 +00:00
|
|
|
handleErr := func(err error) {
|
2017-05-19 09:53:41 +00:00
|
|
|
s.agent.logger.Printf("[ERR] http: Request %s %v, error: %v from=%s", req.Method, logURL, err, req.RemoteAddr)
|
2017-08-23 14:52:48 +00:00
|
|
|
switch {
|
2019-01-10 14:27:26 +00:00
|
|
|
case isForbidden(err):
|
2017-08-23 19:19:11 +00:00
|
|
|
resp.WriteHeader(http.StatusForbidden)
|
2017-08-23 14:52:48 +00:00
|
|
|
fmt.Fprint(resp, err.Error())
|
2017-09-01 22:02:50 +00:00
|
|
|
case structs.IsErrRPCRateExceeded(err):
|
|
|
|
resp.WriteHeader(http.StatusTooManyRequests)
|
2017-09-26 06:11:19 +00:00
|
|
|
case isMethodNotAllowed(err):
|
|
|
|
// RFC2616 states that for 405 Method Not Allowed the response
|
|
|
|
// MUST include an Allow header containing the list of valid
|
|
|
|
// methods for the requested resource.
|
|
|
|
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
2018-02-12 05:28:20 +00:00
|
|
|
addAllowHeader(err.(MethodNotAllowedError).Allow)
|
2017-09-26 06:11:19 +00:00
|
|
|
resp.WriteHeader(http.StatusMethodNotAllowed) // 405
|
2017-09-01 22:02:50 +00:00
|
|
|
fmt.Fprint(resp, err.Error())
|
2018-05-21 19:02:29 +00:00
|
|
|
case isBadRequest(err):
|
|
|
|
resp.WriteHeader(http.StatusBadRequest)
|
|
|
|
fmt.Fprint(resp, err.Error())
|
2019-01-22 17:19:36 +00:00
|
|
|
case isTooManyRequests(err):
|
|
|
|
resp.WriteHeader(http.StatusTooManyRequests)
|
|
|
|
fmt.Fprint(resp, err.Error())
|
2017-08-23 14:52:48 +00:00
|
|
|
default:
|
2017-08-23 19:19:11 +00:00
|
|
|
resp.WriteHeader(http.StatusInternalServerError)
|
2017-08-23 14:52:48 +00:00
|
|
|
fmt.Fprint(resp, err.Error())
|
2017-05-11 17:37:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-23 22:26:34 +00:00
|
|
|
start := time.Now()
|
|
|
|
defer func() {
|
2017-10-17 18:38:24 +00:00
|
|
|
s.agent.logger.Printf("[DEBUG] http: Request %s %v (%v) from=%s", req.Method, logURL, time.Since(start), req.RemoteAddr)
|
2013-12-23 22:26:34 +00:00
|
|
|
}()
|
2018-02-12 05:28:20 +00:00
|
|
|
|
|
|
|
var obj interface{}
|
|
|
|
|
2018-02-18 01:33:21 +00:00
|
|
|
// if this endpoint has declared methods, respond appropriately to OPTIONS requests. Otherwise let the endpoint handle that.
|
|
|
|
if req.Method == "OPTIONS" && len(methods) > 0 {
|
2018-02-12 05:28:20 +00:00
|
|
|
addAllowHeader(append([]string{"OPTIONS"}, methods...))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-02-18 01:33:21 +00:00
|
|
|
// if this endpoint has declared methods, check the request method. Otherwise let the endpoint handle that.
|
|
|
|
methodFound := len(methods) == 0
|
2018-02-12 05:28:20 +00:00
|
|
|
for _, method := range methods {
|
|
|
|
if method == req.Method {
|
|
|
|
methodFound = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !methodFound {
|
|
|
|
err = MethodNotAllowedError{req.Method, append([]string{"OPTIONS"}, methods...)}
|
|
|
|
} else {
|
2019-01-10 14:27:26 +00:00
|
|
|
err = s.checkWriteAccess(req)
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
// Invoke the handler
|
|
|
|
obj, err = handler(resp, req)
|
|
|
|
}
|
2018-02-12 05:28:20 +00:00
|
|
|
}
|
2019-01-07 14:39:23 +00:00
|
|
|
contentType := "application/json"
|
|
|
|
httpCode := http.StatusOK
|
2013-12-23 19:38:51 +00:00
|
|
|
if err != nil {
|
2019-01-07 14:39:23 +00:00
|
|
|
if errPayload, ok := err.(CodeWithPayloadError); ok {
|
|
|
|
httpCode = errPayload.StatusCode
|
|
|
|
if errPayload.ContentType != "" {
|
|
|
|
contentType = errPayload.ContentType
|
|
|
|
}
|
|
|
|
if errPayload.Reason != "" {
|
|
|
|
resp.Header().Add("X-Consul-Reason", errPayload.Reason)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
handleErr(err)
|
|
|
|
return
|
|
|
|
}
|
2017-05-11 17:37:47 +00:00
|
|
|
}
|
|
|
|
if obj == nil {
|
2013-12-23 19:38:51 +00:00
|
|
|
return
|
|
|
|
}
|
2019-01-07 14:39:23 +00:00
|
|
|
var buf []byte
|
|
|
|
if contentType == "application/json" {
|
|
|
|
buf, err = s.marshalJSON(req, obj)
|
|
|
|
if err != nil {
|
|
|
|
handleErr(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if strings.HasPrefix(contentType, "text/") {
|
|
|
|
if val, ok := obj.(string); ok {
|
|
|
|
buf = []byte(val)
|
|
|
|
}
|
|
|
|
}
|
2013-12-23 19:38:51 +00:00
|
|
|
}
|
2019-01-07 14:39:23 +00:00
|
|
|
resp.Header().Set("Content-Type", contentType)
|
|
|
|
resp.WriteHeader(httpCode)
|
2017-05-11 17:37:47 +00:00
|
|
|
resp.Write(buf)
|
2013-12-23 19:38:51 +00:00
|
|
|
}
|
|
|
|
}
|
2013-12-24 00:20:51 +00:00
|
|
|
|
2016-05-07 00:50:58 +00:00
|
|
|
// marshalJSON marshals the object into JSON, respecting the user's pretty-ness
|
|
|
|
// configuration.
|
|
|
|
func (s *HTTPServer) marshalJSON(req *http.Request, obj interface{}) ([]byte, error) {
|
2016-11-18 06:31:19 +00:00
|
|
|
if _, ok := req.URL.Query()["pretty"]; ok || s.agent.config.DevMode {
|
2016-05-07 00:50:58 +00:00
|
|
|
buf, err := json.MarshalIndent(obj, "", " ")
|
2016-05-10 21:37:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
buf = append(buf, "\n"...)
|
|
|
|
return buf, nil
|
2016-05-07 00:50:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
buf, err := json.Marshal(obj)
|
2016-05-10 21:37:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-05-07 00:50:58 +00:00
|
|
|
return buf, err
|
|
|
|
}
|
|
|
|
|
2016-02-13 00:11:32 +00:00
|
|
|
// Returns true if the UI is enabled.
|
|
|
|
func (s *HTTPServer) IsUIEnabled() bool {
|
2017-05-11 17:31:58 +00:00
|
|
|
return s.agent.config.UIDir != "" || s.agent.config.EnableUI
|
2016-02-13 00:11:32 +00:00
|
|
|
}
|
|
|
|
|
2013-12-25 01:09:51 +00:00
|
|
|
// Renders a simple index page
|
|
|
|
func (s *HTTPServer) Index(resp http.ResponseWriter, req *http.Request) {
|
2014-04-27 19:10:38 +00:00
|
|
|
// Check if this is a non-index path
|
|
|
|
if req.URL.Path != "/" {
|
2017-08-23 19:19:11 +00:00
|
|
|
resp.WriteHeader(http.StatusNotFound)
|
2014-04-27 19:10:38 +00:00
|
|
|
return
|
2013-12-25 01:09:51 +00:00
|
|
|
}
|
2014-04-27 19:10:38 +00:00
|
|
|
|
2016-02-13 00:11:32 +00:00
|
|
|
// Give them something helpful if there's no UI so they at least know
|
|
|
|
// what this server is.
|
|
|
|
if !s.IsUIEnabled() {
|
Use fmt.Fprint/Fprintf/Fprintln
Used the following rewrite rules:
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b, c, d))) -> fmt.Fprintf(resp, a, b, c, d)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b, c))) -> fmt.Fprintf(resp, a, b, c)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b))) -> fmt.Fprintf(resp, a, b)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a))) -> fmt.Fprint(resp, a)' *.go
gofmt -w -r 'resp.Write([]byte(a + "\n")) -> fmt.Fprintln(resp, a)' *.go
gofmt -w -r 'resp.Write([]byte(a)) -> fmt.Fprint(resp, a)' *.go
2017-04-20 14:07:42 +00:00
|
|
|
fmt.Fprint(resp, "Consul Agent")
|
2014-04-27 19:10:38 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Redirect to the UI endpoint
|
2016-02-05 22:06:42 +00:00
|
|
|
http.Redirect(resp, req, "/ui/", http.StatusMovedPermanently) // 301
|
2013-12-25 01:09:51 +00:00
|
|
|
}
|
|
|
|
|
2013-12-24 00:20:51 +00:00
|
|
|
// decodeBody is used to decode a JSON request body
|
2014-04-21 22:02:36 +00:00
|
|
|
func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error) error {
|
2018-06-05 18:09:45 +00:00
|
|
|
// This generally only happens in tests since real HTTP requests set
|
|
|
|
// a non-nil body with no content. We guard against it anyways to prevent
|
|
|
|
// a panic. The EOF response is the same behavior as an empty reader.
|
|
|
|
if req.Body == nil {
|
|
|
|
return io.EOF
|
|
|
|
}
|
|
|
|
|
2014-04-21 22:02:36 +00:00
|
|
|
var raw interface{}
|
2013-12-24 00:20:51 +00:00
|
|
|
dec := json.NewDecoder(req.Body)
|
2014-04-21 22:02:36 +00:00
|
|
|
if err := dec.Decode(&raw); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invoke the callback prior to decode
|
|
|
|
if cb != nil {
|
|
|
|
if err := cb(raw); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2018-12-12 17:14:02 +00:00
|
|
|
|
|
|
|
decodeConf := &mapstructure.DecoderConfig{
|
2019-01-25 01:28:52 +00:00
|
|
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
|
|
|
mapstructure.StringToTimeDurationHookFunc(),
|
|
|
|
stringToReadableDurationFunc(),
|
|
|
|
),
|
|
|
|
Result: &out,
|
2018-12-12 17:14:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
decoder, err := mapstructure.NewDecoder(decodeConf)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return decoder.Decode(raw)
|
2013-12-24 00:20:51 +00:00
|
|
|
}
|
2014-02-05 22:36:13 +00:00
|
|
|
|
2019-01-25 01:28:52 +00:00
|
|
|
// stringToReadableDurationFunc is a mapstructure hook for decoding a string
|
|
|
|
// into an api.ReadableDuration for backwards compatibility.
|
|
|
|
func stringToReadableDurationFunc() mapstructure.DecodeHookFunc {
|
|
|
|
return func(
|
|
|
|
f reflect.Type,
|
|
|
|
t reflect.Type,
|
|
|
|
data interface{}) (interface{}, error) {
|
|
|
|
var v api.ReadableDuration
|
|
|
|
if t != reflect.TypeOf(v) {
|
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case f.Kind() == reflect.String:
|
|
|
|
if dur, err := time.ParseDuration(data.(string)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
|
|
|
v = api.ReadableDuration(dur)
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
default:
|
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-16 18:31:41 +00:00
|
|
|
// setTranslateAddr is used to set the address translation header. This is only
|
|
|
|
// present if the feature is active.
|
|
|
|
func setTranslateAddr(resp http.ResponseWriter, active bool) {
|
|
|
|
if active {
|
|
|
|
resp.Header().Set("X-Consul-Translate-Addresses", "true")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-05 22:36:13 +00:00
|
|
|
// setIndex is used to set the index response header
|
|
|
|
func setIndex(resp http.ResponseWriter, index uint64) {
|
2018-06-08 21:27:05 +00:00
|
|
|
// If we ever return X-Consul-Index of 0 blocking clients will go into a busy
|
|
|
|
// loop and hammer us since ?index=0 will never block. It's always safe to
|
|
|
|
// return index=1 since the very first Raft write is always an internal one
|
|
|
|
// writing the raft config for the cluster so no user-facing blocking query
|
|
|
|
// will ever legitimately have an X-Consul-Index of 1.
|
|
|
|
if index == 0 {
|
|
|
|
index = 1
|
|
|
|
}
|
2014-10-14 00:53:54 +00:00
|
|
|
resp.Header().Set("X-Consul-Index", strconv.FormatUint(index, 10))
|
2014-02-05 22:36:13 +00:00
|
|
|
}
|
|
|
|
|
2014-04-21 19:40:11 +00:00
|
|
|
// setKnownLeader is used to set the known leader header
|
|
|
|
func setKnownLeader(resp http.ResponseWriter, known bool) {
|
|
|
|
s := "true"
|
|
|
|
if !known {
|
|
|
|
s = "false"
|
|
|
|
}
|
2014-10-14 00:53:54 +00:00
|
|
|
resp.Header().Set("X-Consul-KnownLeader", s)
|
2014-04-21 19:40:11 +00:00
|
|
|
}
|
|
|
|
|
2018-03-30 15:14:44 +00:00
|
|
|
func setConsistency(resp http.ResponseWriter, consistency string) {
|
|
|
|
if consistency != "" {
|
|
|
|
resp.Header().Set("X-Consul-Effective-Consistency", consistency)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-21 19:40:11 +00:00
|
|
|
// setLastContact is used to set the last contact header
|
|
|
|
func setLastContact(resp http.ResponseWriter, last time.Duration) {
|
2017-06-01 11:53:27 +00:00
|
|
|
if last < 0 {
|
|
|
|
last = 0
|
|
|
|
}
|
2014-04-21 19:40:11 +00:00
|
|
|
lastMsec := uint64(last / time.Millisecond)
|
2014-10-14 00:53:54 +00:00
|
|
|
resp.Header().Set("X-Consul-LastContact", strconv.FormatUint(lastMsec, 10))
|
2014-04-21 19:40:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// setMeta is used to set the query response meta data
|
|
|
|
func setMeta(resp http.ResponseWriter, m *structs.QueryMeta) {
|
|
|
|
setIndex(resp, m.Index)
|
|
|
|
setLastContact(resp, m.LastContact)
|
|
|
|
setKnownLeader(resp, m.KnownLeader)
|
2018-03-30 15:14:44 +00:00
|
|
|
setConsistency(resp, m.ConsistencyLevel)
|
2014-04-21 19:40:11 +00:00
|
|
|
}
|
|
|
|
|
2018-06-15 12:13:54 +00:00
|
|
|
// setCacheMeta sets http response headers to indicate cache status.
|
|
|
|
func setCacheMeta(resp http.ResponseWriter, m *cache.ResultMeta) {
|
2018-10-03 19:37:53 +00:00
|
|
|
if m == nil {
|
|
|
|
return
|
|
|
|
}
|
2018-06-15 12:13:54 +00:00
|
|
|
str := "MISS"
|
2018-10-03 19:37:53 +00:00
|
|
|
if m.Hit {
|
2018-06-15 12:13:54 +00:00
|
|
|
str = "HIT"
|
|
|
|
}
|
|
|
|
resp.Header().Set("X-Cache", str)
|
2018-09-06 10:34:28 +00:00
|
|
|
if m.Hit {
|
|
|
|
resp.Header().Set("Age", fmt.Sprintf("%.0f", m.Age.Seconds()))
|
|
|
|
}
|
2018-06-15 12:13:54 +00:00
|
|
|
}
|
|
|
|
|
2014-12-28 04:53:19 +00:00
|
|
|
// setHeaders is used to set canonical response header fields
|
|
|
|
func setHeaders(resp http.ResponseWriter, headers map[string]string) {
|
|
|
|
for field, value := range headers {
|
|
|
|
resp.Header().Set(http.CanonicalHeaderKey(field), value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-05 22:36:13 +00:00
|
|
|
// parseWait is used to parse the ?wait and ?index query params
|
|
|
|
// Returns true on error
|
2014-04-21 19:26:12 +00:00
|
|
|
func parseWait(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) bool {
|
2014-02-05 22:36:13 +00:00
|
|
|
query := req.URL.Query()
|
|
|
|
if wait := query.Get("wait"); wait != "" {
|
|
|
|
dur, err := time.ParseDuration(wait)
|
|
|
|
if err != nil {
|
2017-08-23 19:19:11 +00:00
|
|
|
resp.WriteHeader(http.StatusBadRequest)
|
Use fmt.Fprint/Fprintf/Fprintln
Used the following rewrite rules:
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b, c, d))) -> fmt.Fprintf(resp, a, b, c, d)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b, c))) -> fmt.Fprintf(resp, a, b, c)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b))) -> fmt.Fprintf(resp, a, b)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a))) -> fmt.Fprint(resp, a)' *.go
gofmt -w -r 'resp.Write([]byte(a + "\n")) -> fmt.Fprintln(resp, a)' *.go
gofmt -w -r 'resp.Write([]byte(a)) -> fmt.Fprint(resp, a)' *.go
2017-04-20 14:07:42 +00:00
|
|
|
fmt.Fprint(resp, "Invalid wait time")
|
2014-02-05 22:36:13 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
b.MaxQueryTime = dur
|
|
|
|
}
|
|
|
|
if idx := query.Get("index"); idx != "" {
|
|
|
|
index, err := strconv.ParseUint(idx, 10, 64)
|
|
|
|
if err != nil {
|
2017-08-23 19:19:11 +00:00
|
|
|
resp.WriteHeader(http.StatusBadRequest)
|
Use fmt.Fprint/Fprintf/Fprintln
Used the following rewrite rules:
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b, c, d))) -> fmt.Fprintf(resp, a, b, c, d)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b, c))) -> fmt.Fprintf(resp, a, b, c)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b))) -> fmt.Fprintf(resp, a, b)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a))) -> fmt.Fprint(resp, a)' *.go
gofmt -w -r 'resp.Write([]byte(a + "\n")) -> fmt.Fprintln(resp, a)' *.go
gofmt -w -r 'resp.Write([]byte(a)) -> fmt.Fprint(resp, a)' *.go
2017-04-20 14:07:42 +00:00
|
|
|
fmt.Fprint(resp, "Invalid index")
|
2014-02-05 22:36:13 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
b.MinQueryIndex = index
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-09-06 10:34:28 +00:00
|
|
|
// parseCacheControl parses the CacheControl HTTP header value. So far we only
|
|
|
|
// support maxage directive.
|
|
|
|
func parseCacheControl(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) bool {
|
|
|
|
raw := strings.ToLower(req.Header.Get("Cache-Control"))
|
|
|
|
|
|
|
|
if raw == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Didn't want to import a full parser for this. While quoted strings are
|
|
|
|
// allowed in some directives, max-age does not allow them per
|
|
|
|
// https://tools.ietf.org/html/rfc7234#section-5.2.2.8 so we assume all
|
|
|
|
// well-behaved clients use the exact token form of max-age=<delta-seconds>
|
|
|
|
// where delta-seconds is a non-negative decimal integer.
|
|
|
|
directives := strings.Split(raw, ",")
|
|
|
|
|
|
|
|
parseDurationOrFail := func(raw string) (time.Duration, bool) {
|
|
|
|
i, err := strconv.Atoi(raw)
|
|
|
|
if err != nil {
|
|
|
|
resp.WriteHeader(http.StatusBadRequest)
|
|
|
|
fmt.Fprint(resp, "Invalid Cache-Control header.")
|
|
|
|
return 0, true
|
|
|
|
}
|
|
|
|
return time.Duration(i) * time.Second, false
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, d := range directives {
|
|
|
|
d = strings.ToLower(strings.TrimSpace(d))
|
|
|
|
|
|
|
|
if d == "must-revalidate" {
|
|
|
|
b.MustRevalidate = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.HasPrefix(d, "max-age=") {
|
|
|
|
d, failed := parseDurationOrFail(d[8:])
|
|
|
|
if failed {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
b.MaxAge = d
|
|
|
|
if d == 0 {
|
|
|
|
// max-age=0 specifically means that we need to consider the cache stale
|
|
|
|
// immediately however MaxAge = 0 is indistinguishable from the default
|
|
|
|
// where MaxAge is unset.
|
|
|
|
b.MustRevalidate = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(d, "stale-if-error=") {
|
|
|
|
d, failed := parseDurationOrFail(d[15:])
|
|
|
|
if failed {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
b.StaleIfError = d
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2014-04-21 19:26:12 +00:00
|
|
|
// parseConsistency is used to parse the ?stale and ?consistent query params.
|
|
|
|
// Returns true on error
|
2018-03-30 15:14:44 +00:00
|
|
|
func (s *HTTPServer) parseConsistency(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) bool {
|
2014-04-21 19:26:12 +00:00
|
|
|
query := req.URL.Query()
|
2018-03-30 15:14:44 +00:00
|
|
|
defaults := true
|
2017-06-16 08:55:53 +00:00
|
|
|
if _, ok := query["stale"]; ok {
|
|
|
|
b.AllowStale = true
|
2018-03-30 15:14:44 +00:00
|
|
|
defaults = false
|
2017-06-16 08:55:53 +00:00
|
|
|
}
|
2017-06-27 05:04:55 +00:00
|
|
|
if _, ok := query["consistent"]; ok {
|
|
|
|
b.RequireConsistent = true
|
2018-03-30 15:14:44 +00:00
|
|
|
defaults = false
|
|
|
|
}
|
|
|
|
if _, ok := query["leader"]; ok {
|
|
|
|
defaults = false
|
|
|
|
}
|
2018-09-06 10:34:28 +00:00
|
|
|
if _, ok := query["cached"]; ok {
|
|
|
|
b.UseCache = true
|
|
|
|
defaults = false
|
|
|
|
}
|
2018-03-30 15:14:44 +00:00
|
|
|
if maxStale := query.Get("max_stale"); maxStale != "" {
|
|
|
|
dur, err := time.ParseDuration(maxStale)
|
|
|
|
if err != nil {
|
|
|
|
resp.WriteHeader(http.StatusBadRequest)
|
|
|
|
fmt.Fprintf(resp, "Invalid max_stale value %q", maxStale)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
b.MaxStaleDuration = dur
|
|
|
|
if dur.Nanoseconds() > 0 {
|
|
|
|
b.AllowStale = true
|
|
|
|
defaults = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// No specific Consistency has been specified by caller
|
|
|
|
if defaults {
|
|
|
|
path := req.URL.Path
|
|
|
|
if strings.HasPrefix(path, "/v1/catalog") || strings.HasPrefix(path, "/v1/health") {
|
|
|
|
if s.agent.config.DiscoveryMaxStale.Nanoseconds() > 0 {
|
|
|
|
b.MaxStaleDuration = s.agent.config.DiscoveryMaxStale
|
|
|
|
b.AllowStale = true
|
|
|
|
}
|
|
|
|
}
|
2017-06-27 05:04:55 +00:00
|
|
|
}
|
2014-04-21 19:26:12 +00:00
|
|
|
if b.AllowStale && b.RequireConsistent {
|
2017-08-23 19:19:11 +00:00
|
|
|
resp.WriteHeader(http.StatusBadRequest)
|
Use fmt.Fprint/Fprintf/Fprintln
Used the following rewrite rules:
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b, c, d))) -> fmt.Fprintf(resp, a, b, c, d)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b, c))) -> fmt.Fprintf(resp, a, b, c)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a, b))) -> fmt.Fprintf(resp, a, b)' *.go
gofmt -w -r 'resp.Write([]byte(fmt.Sprintf(a))) -> fmt.Fprint(resp, a)' *.go
gofmt -w -r 'resp.Write([]byte(a + "\n")) -> fmt.Fprintln(resp, a)' *.go
gofmt -w -r 'resp.Write([]byte(a)) -> fmt.Fprint(resp, a)' *.go
2017-04-20 14:07:42 +00:00
|
|
|
fmt.Fprint(resp, "Cannot specify ?stale with ?consistent, conflicting semantics.")
|
2014-04-21 19:26:12 +00:00
|
|
|
return true
|
|
|
|
}
|
2018-09-06 10:34:28 +00:00
|
|
|
if b.UseCache && b.RequireConsistent {
|
|
|
|
resp.WriteHeader(http.StatusBadRequest)
|
|
|
|
fmt.Fprint(resp, "Cannot specify ?cached with ?consistent, conflicting semantics.")
|
|
|
|
return true
|
|
|
|
}
|
2014-04-21 19:26:12 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2014-02-05 22:36:13 +00:00
|
|
|
// parseDC is used to parse the ?dc query param
|
|
|
|
func (s *HTTPServer) parseDC(req *http.Request, dc *string) {
|
|
|
|
if other := req.URL.Query().Get("dc"); other != "" {
|
|
|
|
*dc = other
|
|
|
|
} else if *dc == "" {
|
|
|
|
*dc = s.agent.config.Datacenter
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-17 20:18:42 +00:00
|
|
|
// parseTokenInternal is used to parse the ?token query param or the X-Consul-Token header or
|
|
|
|
// Authorization Bearer token (RFC6750) and
|
|
|
|
// optionally resolve proxy tokens to real ACL tokens. If the token is invalid or not specified it will populate
|
2018-07-30 13:11:51 +00:00
|
|
|
// the token with the agents UserToken (acl_token in the consul configuration)
|
2018-08-17 20:18:42 +00:00
|
|
|
// Parsing has the following priority: ?token, X-Consul-Token and last "Authorization: Bearer "
|
2018-07-30 13:11:51 +00:00
|
|
|
func (s *HTTPServer) parseTokenInternal(req *http.Request, token *string, resolveProxyToken bool) {
|
|
|
|
tok := ""
|
2014-08-12 18:35:22 +00:00
|
|
|
if other := req.URL.Query().Get("token"); other != "" {
|
2018-07-30 13:11:51 +00:00
|
|
|
tok = other
|
|
|
|
} else if other := req.Header.Get("X-Consul-Token"); other != "" {
|
|
|
|
tok = other
|
2018-08-17 20:18:42 +00:00
|
|
|
} else if other := req.Header.Get("Authorization"); other != "" {
|
|
|
|
// HTTP Authorization headers are in the format: <Scheme>[SPACE]<Value>
|
|
|
|
// Ref. https://tools.ietf.org/html/rfc7236#section-3
|
|
|
|
parts := strings.Split(other, " ")
|
|
|
|
|
|
|
|
// Authorization Header is invalid if containing 1 or 0 parts, e.g.:
|
|
|
|
// "" || "<Scheme><Value>" || "<Scheme>" || "<Value>"
|
|
|
|
if len(parts) > 1 {
|
|
|
|
scheme := parts[0]
|
|
|
|
// Everything after "<Scheme>" is "<Value>", trimmed
|
|
|
|
value := strings.TrimSpace(strings.Join(parts[1:], " "))
|
|
|
|
|
|
|
|
// <Scheme> must be "Bearer"
|
|
|
|
if scheme == "Bearer" {
|
|
|
|
// Since Bearer tokens shouldnt contain spaces (rfc6750#section-2.1)
|
|
|
|
// "value" is tokenized, only the first item is used
|
|
|
|
tok = strings.TrimSpace(strings.Split(value, " ")[0])
|
|
|
|
}
|
|
|
|
}
|
2014-08-12 18:35:22 +00:00
|
|
|
}
|
2015-02-06 22:10:01 +00:00
|
|
|
|
2018-07-30 13:11:51 +00:00
|
|
|
if tok != "" {
|
|
|
|
if resolveProxyToken {
|
|
|
|
if p := s.agent.resolveProxyToken(tok); p != nil {
|
|
|
|
*token = s.agent.State.ServiceToken(p.Proxy.TargetServiceID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*token = tok
|
2015-10-19 13:59:24 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-07-26 18:03:43 +00:00
|
|
|
*token = s.agent.tokens.UserToken()
|
2014-08-12 18:35:22 +00:00
|
|
|
}
|
|
|
|
|
2018-08-17 20:18:42 +00:00
|
|
|
// parseToken is used to parse the ?token query param or the X-Consul-Token header or
|
|
|
|
// Authorization Bearer token header (RFC6750) and resolve proxy tokens to real ACL tokens
|
2018-07-30 13:11:51 +00:00
|
|
|
func (s *HTTPServer) parseToken(req *http.Request, token *string) {
|
|
|
|
s.parseTokenInternal(req, token, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseTokenWithoutResolvingProxyToken is used to parse the ?token query param or the X-Consul-Token header
|
2018-08-17 20:18:42 +00:00
|
|
|
// or Authorization Bearer header token (RFC6750) and
|
2018-07-30 13:11:51 +00:00
|
|
|
func (s *HTTPServer) parseTokenWithoutResolvingProxyToken(req *http.Request, token *string) {
|
|
|
|
s.parseTokenInternal(req, token, false)
|
|
|
|
}
|
|
|
|
|
2018-04-12 14:40:46 +00:00
|
|
|
func sourceAddrFromRequest(req *http.Request) string {
|
|
|
|
xff := req.Header.Get("X-Forwarded-For")
|
|
|
|
forwardHosts := strings.Split(xff, ",")
|
|
|
|
if len(forwardHosts) > 0 {
|
|
|
|
forwardIp := net.ParseIP(strings.TrimSpace(forwardHosts[0]))
|
|
|
|
if forwardIp != nil {
|
|
|
|
return forwardIp.String()
|
|
|
|
}
|
|
|
|
}
|
2018-04-13 16:57:25 +00:00
|
|
|
|
2018-04-10 18:50:50 +00:00
|
|
|
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
|
|
|
if err != nil {
|
2018-04-12 14:40:46 +00:00
|
|
|
return ""
|
2018-04-10 18:50:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ip := net.ParseIP(host)
|
2018-04-10 19:35:54 +00:00
|
|
|
if ip != nil {
|
2018-04-12 14:40:46 +00:00
|
|
|
return ip.String()
|
2018-04-10 19:35:54 +00:00
|
|
|
} else {
|
2018-04-12 14:40:46 +00:00
|
|
|
return ""
|
2018-04-10 18:50:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-30 21:25:40 +00:00
|
|
|
// parseSource is used to parse the ?near=<node> query parameter, used for
|
|
|
|
// sorting by RTT based on a source node. We set the source's DC to the target
|
|
|
|
// DC in the request, if given, or else the agent's DC.
|
|
|
|
func (s *HTTPServer) parseSource(req *http.Request, source *structs.QuerySource) {
|
|
|
|
s.parseDC(req, &source.Datacenter)
|
2018-04-12 14:40:46 +00:00
|
|
|
source.Ip = sourceAddrFromRequest(req)
|
2015-06-30 21:25:40 +00:00
|
|
|
if node := req.URL.Query().Get("near"); node != "" {
|
2015-07-28 17:39:37 +00:00
|
|
|
if node == "_agent" {
|
2015-07-24 21:30:53 +00:00
|
|
|
source.Node = s.agent.config.NodeName
|
|
|
|
} else {
|
|
|
|
source.Node = node
|
|
|
|
}
|
2015-06-30 21:25:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-09 19:21:49 +00:00
|
|
|
// parseMetaFilter is used to parse the ?node-meta=key:value query parameter, used for
|
|
|
|
// filtering results to nodes with the given metadata key/value
|
2017-01-11 19:41:12 +00:00
|
|
|
func (s *HTTPServer) parseMetaFilter(req *http.Request) map[string]string {
|
|
|
|
if filterList, ok := req.URL.Query()["node-meta"]; ok {
|
|
|
|
filters := make(map[string]string)
|
|
|
|
for _, filter := range filterList {
|
2017-06-02 10:04:04 +00:00
|
|
|
key, value := ParseMetaPair(filter)
|
2017-01-11 21:07:11 +00:00
|
|
|
filters[key] = value
|
2017-01-09 19:21:49 +00:00
|
|
|
}
|
2017-01-11 19:41:12 +00:00
|
|
|
return filters
|
2017-01-09 19:21:49 +00:00
|
|
|
}
|
2017-01-11 19:41:12 +00:00
|
|
|
return nil
|
2017-01-09 19:21:49 +00:00
|
|
|
}
|
|
|
|
|
2018-07-30 13:11:51 +00:00
|
|
|
// parseInternal is a convenience method for endpoints that need
|
2014-02-05 22:36:13 +00:00
|
|
|
// to use both parseWait and parseDC.
|
2018-07-30 13:11:51 +00:00
|
|
|
func (s *HTTPServer) parseInternal(resp http.ResponseWriter, req *http.Request, dc *string, b *structs.QueryOptions, resolveProxyToken bool) bool {
|
2014-02-05 22:36:13 +00:00
|
|
|
s.parseDC(req, dc)
|
2018-07-30 13:11:51 +00:00
|
|
|
s.parseTokenInternal(req, &b.Token, resolveProxyToken)
|
2018-03-30 15:14:44 +00:00
|
|
|
if s.parseConsistency(resp, req, b) {
|
2014-04-21 19:26:12 +00:00
|
|
|
return true
|
|
|
|
}
|
2018-09-06 10:34:28 +00:00
|
|
|
if parseCacheControl(resp, req, b) {
|
|
|
|
return true
|
|
|
|
}
|
2014-02-05 22:36:13 +00:00
|
|
|
return parseWait(resp, req, b)
|
|
|
|
}
|
2018-07-30 13:11:51 +00:00
|
|
|
|
|
|
|
// parse is a convenience method for endpoints that need
|
|
|
|
// to use both parseWait and parseDC.
|
|
|
|
func (s *HTTPServer) parse(resp http.ResponseWriter, req *http.Request, dc *string, b *structs.QueryOptions) bool {
|
|
|
|
return s.parseInternal(resp, req, dc, b, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseWithoutResolvingProxyToken is a convenience method similar to parse except that it disables resolving proxy tokens
|
|
|
|
func (s *HTTPServer) parseWithoutResolvingProxyToken(resp http.ResponseWriter, req *http.Request, dc *string, b *structs.QueryOptions) bool {
|
|
|
|
return s.parseInternal(resp, req, dc, b, false)
|
|
|
|
}
|
2019-01-10 14:27:26 +00:00
|
|
|
|
|
|
|
func (s *HTTPServer) checkWriteAccess(req *http.Request) error {
|
|
|
|
if req.Method == http.MethodGet || req.Method == http.MethodHead || req.Method == http.MethodOptions {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
allowed := s.agent.config.AllowWriteHTTPFrom
|
|
|
|
if len(allowed) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ipStr, _, err := net.SplitHostPort(req.RemoteAddr)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "unable to parse remote addr")
|
|
|
|
}
|
|
|
|
|
|
|
|
ip := net.ParseIP(ipStr)
|
|
|
|
|
|
|
|
for _, n := range allowed {
|
|
|
|
if n.Contains(ip) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ForbiddenError{}
|
|
|
|
}
|