2014-01-04 01:15:51 +00:00
package agent
import (
2019-02-27 19:28:31 +00:00
"encoding/json"
2018-09-27 14:00:51 +00:00
"errors"
2014-01-30 23:35:38 +00:00
"fmt"
2016-11-16 21:45:26 +00:00
"log"
2018-09-27 14:00:51 +00:00
"net"
2014-01-04 01:15:51 +00:00
"net/http"
2019-02-27 19:28:31 +00:00
"path/filepath"
2015-01-21 17:53:31 +00:00
"strconv"
2014-01-04 01:15:51 +00:00
"strings"
2018-04-18 20:05:30 +00:00
"time"
2016-06-06 08:53:30 +00:00
2018-06-14 12:52:48 +00:00
"github.com/mitchellh/mapstructure"
2018-04-19 10:15:32 +00:00
"github.com/hashicorp/go-memdb"
"github.com/mitchellh/hashstructure"
2017-08-23 14:52:48 +00:00
"github.com/hashicorp/consul/acl"
2018-04-11 08:52:51 +00:00
"github.com/hashicorp/consul/agent/cache-types"
2017-10-25 09:18:07 +00:00
"github.com/hashicorp/consul/agent/checks"
2017-10-10 23:40:59 +00:00
"github.com/hashicorp/consul/agent/config"
2018-10-17 20:20:35 +00:00
"github.com/hashicorp/consul/agent/debug"
2019-01-07 14:39:23 +00:00
"github.com/hashicorp/consul/agent/local"
2017-07-06 10:34:00 +00:00
"github.com/hashicorp/consul/agent/structs"
2019-02-27 19:28:31 +00:00
token_store "github.com/hashicorp/consul/agent/token"
2017-04-19 23:00:11 +00:00
"github.com/hashicorp/consul/api"
2017-05-15 20:10:36 +00:00
"github.com/hashicorp/consul/ipaddr"
2017-08-14 14:36:07 +00:00
"github.com/hashicorp/consul/lib"
2019-02-27 19:28:31 +00:00
"github.com/hashicorp/consul/lib/file"
2016-11-16 21:45:26 +00:00
"github.com/hashicorp/consul/logger"
2016-06-06 20:19:31 +00:00
"github.com/hashicorp/consul/types"
2016-11-16 21:45:26 +00:00
"github.com/hashicorp/logutils"
2016-06-06 08:53:30 +00:00
"github.com/hashicorp/serf/coordinate"
"github.com/hashicorp/serf/serf"
2018-04-06 06:55:49 +00:00
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
2014-01-04 01:15:51 +00:00
)
2017-04-21 00:46:29 +00:00
type Self struct {
2017-10-04 17:43:17 +00:00
Config interface { }
DebugConfig map [ string ] interface { }
Coord * coordinate . Coordinate
Member serf . Member
Stats map [ string ] map [ string ] string
Meta map [ string ] string
2014-05-27 22:09:28 +00:00
}
2014-05-25 23:59:48 +00:00
func ( s * HTTPServer ) AgentSelf ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-12-14 17:33:57 +00:00
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s . parseToken ( req , & token )
2017-08-23 14:52:48 +00:00
rule , err := s . agent . resolveToken ( token )
2016-12-14 17:33:57 +00:00
if err != nil {
return nil , err
}
2017-08-23 14:52:48 +00:00
if rule != nil && ! rule . AgentRead ( s . agent . config . NodeName ) {
return nil , acl . ErrPermissionDenied
2016-12-14 17:33:57 +00:00
}
2018-01-19 23:25:22 +00:00
var cs lib . CoordinateSet
if ! s . agent . config . DisableCoordinates {
var err error
if cs , err = s . agent . GetLANCoordinate ( ) ; err != nil {
return nil , err
}
}
2017-10-04 17:43:17 +00:00
config := struct {
Datacenter string
NodeName string
2018-01-10 23:17:33 +00:00
NodeID string
2017-10-04 17:43:17 +00:00
Revision string
Server bool
Version string
} {
Datacenter : s . agent . config . Datacenter ,
NodeName : s . agent . config . NodeName ,
2018-01-10 23:17:33 +00:00
NodeID : string ( s . agent . config . NodeID ) ,
2017-10-04 17:43:17 +00:00
Revision : s . agent . config . Revision ,
Server : s . agent . config . ServerMode ,
Version : s . agent . config . Version ,
}
2017-04-21 00:46:29 +00:00
return Self {
2017-10-04 17:43:17 +00:00
Config : config ,
DebugConfig : s . agent . config . Sanitized ( ) ,
Coord : cs [ s . agent . config . SegmentName ] ,
Member : s . agent . LocalMember ( ) ,
Stats : s . agent . Stats ( ) ,
2017-08-28 12:17:13 +00:00
Meta : s . agent . State . Metadata ( ) ,
2014-05-27 22:09:28 +00:00
} , nil
2014-05-25 23:59:48 +00:00
}
2018-04-09 11:16:03 +00:00
// enablePrometheusOutput will look for Prometheus mime-type or format Query parameter the same way as Nomad
func enablePrometheusOutput ( req * http . Request ) bool {
if format := req . URL . Query ( ) . Get ( "format" ) ; format == "prometheus" {
return true
}
return false
}
2017-08-08 20:05:38 +00:00
func ( s * HTTPServer ) AgentMetrics ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s . parseToken ( req , & token )
2017-08-23 14:52:48 +00:00
rule , err := s . agent . resolveToken ( token )
2017-08-08 20:05:38 +00:00
if err != nil {
return nil , err
}
2017-08-23 14:52:48 +00:00
if rule != nil && ! rule . AgentRead ( s . agent . config . NodeName ) {
return nil , acl . ErrPermissionDenied
2017-08-08 20:05:38 +00:00
}
2018-04-09 11:16:03 +00:00
if enablePrometheusOutput ( req ) {
2018-06-14 12:52:48 +00:00
if s . agent . config . Telemetry . PrometheusRetentionTime < 1 {
2018-04-06 12:21:05 +00:00
resp . WriteHeader ( http . StatusUnsupportedMediaType )
2018-10-03 21:47:56 +00:00
fmt . Fprint ( resp , "Prometheus is not enabled since its retention time is not positive" )
2018-04-06 12:21:05 +00:00
return nil , nil
}
2018-04-06 06:55:49 +00:00
handlerOptions := promhttp . HandlerOpts {
2018-04-09 11:16:03 +00:00
ErrorLog : s . agent . logger ,
ErrorHandling : promhttp . ContinueOnError ,
2018-04-06 06:55:49 +00:00
}
2017-08-08 20:05:38 +00:00
2018-04-06 06:55:49 +00:00
handler := promhttp . HandlerFor ( prometheus . DefaultGatherer , handlerOptions )
handler . ServeHTTP ( resp , req )
return nil , nil
}
2017-08-08 20:05:38 +00:00
return s . agent . MemSink . DisplayMetrics ( resp , req )
}
2016-11-30 18:29:42 +00:00
func ( s * HTTPServer ) AgentReload ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-12-14 17:33:57 +00:00
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s . parseToken ( req , & token )
2017-08-23 14:52:48 +00:00
rule , err := s . agent . resolveToken ( token )
2016-12-14 17:33:57 +00:00
if err != nil {
return nil , err
}
2017-08-23 14:52:48 +00:00
if rule != nil && ! rule . AgentWrite ( s . agent . config . NodeName ) {
return nil , acl . ErrPermissionDenied
2016-12-14 17:33:57 +00:00
}
2016-11-30 18:29:42 +00:00
// Trigger the reload
2016-12-14 17:33:57 +00:00
errCh := make ( chan error , 0 )
2016-11-30 18:29:42 +00:00
select {
2017-05-19 15:51:39 +00:00
case <- s . agent . shutdownCh :
2016-11-30 18:29:42 +00:00
return nil , fmt . Errorf ( "Agent was shutdown before reload could be completed" )
case s . agent . reloadCh <- errCh :
}
// Wait for the result of the reload, or for the agent to shutdown
select {
2017-05-19 15:51:39 +00:00
case <- s . agent . shutdownCh :
2016-11-30 18:29:42 +00:00
return nil , fmt . Errorf ( "Agent was shutdown before reload could be completed" )
case err := <- errCh :
return nil , err
}
}
2019-01-07 14:39:23 +00:00
func buildAgentService ( s * structs . NodeService , proxies map [ string ] * local . ManagedProxy ) api . AgentService {
weights := api . AgentWeights { Passing : 1 , Warning : 1 }
if s . Weights != nil {
if s . Weights . Passing > 0 {
weights . Passing = s . Weights . Passing
}
weights . Warning = s . Weights . Warning
}
as := api . AgentService {
Kind : api . ServiceKind ( s . Kind ) ,
ID : s . ID ,
Service : s . Service ,
Tags : s . Tags ,
Meta : s . Meta ,
Port : s . Port ,
Address : s . Address ,
EnableTagOverride : s . EnableTagOverride ,
CreateIndex : s . CreateIndex ,
ModifyIndex : s . ModifyIndex ,
Weights : weights ,
}
if as . Tags == nil {
as . Tags = [ ] string { }
}
if as . Meta == nil {
as . Meta = map [ string ] string { }
}
// Attach Unmanaged Proxy config if exists
if s . Kind == structs . ServiceKindConnectProxy {
as . Proxy = s . Proxy . ToAPI ( )
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
// Also set the deprecated ProxyDestination
as . ProxyDestination = as . Proxy . DestinationServiceName
}
// Attach Connect configs if they exist. We use the actual proxy state since
// that may have had defaults filled in compared to the config that was
// provided with the service as stored in the NodeService here.
if proxy , ok := proxies [ s . ID + "-proxy" ] ; ok {
as . Connect = & api . AgentServiceConnect {
Proxy : & api . AgentServiceConnectProxy {
ExecMode : api . ProxyExecMode ( proxy . Proxy . ExecMode . String ( ) ) ,
Command : proxy . Proxy . Command ,
Config : proxy . Proxy . Config ,
Upstreams : proxy . Proxy . Upstreams . ToAPI ( ) ,
} ,
}
} else if s . Connect . Native {
as . Connect = & api . AgentServiceConnect {
Native : true ,
}
}
return as
}
2014-01-04 01:15:51 +00:00
func ( s * HTTPServer ) AgentServices ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-12-14 22:16:46 +00:00
// Fetch the ACL token, if any.
var token string
s . parseToken ( req , & token )
2017-08-28 12:17:13 +00:00
services := s . agent . State . Services ( )
2016-12-14 22:16:46 +00:00
if err := s . agent . filterServices ( token , & services ) ; err != nil {
return nil , err
}
2017-04-28 01:22:07 +00:00
2018-04-20 13:24:24 +00:00
proxies := s . agent . State . Proxies ( )
// Convert into api.AgentService since that includes Connect config but so far
// NodeService doesn't need to internally. They are otherwise identical since
// that is the struct used in client for reading the one we output here
// anyway.
agentSvcs := make ( map [ string ] * api . AgentService )
2017-04-28 01:22:07 +00:00
// Use empty list instead of nil
2018-02-07 15:02:10 +00:00
for id , s := range services {
2019-01-07 14:39:23 +00:00
agentService := buildAgentService ( s , proxies )
agentSvcs [ id ] = & agentService
2017-04-28 01:22:07 +00:00
}
2018-04-20 13:24:24 +00:00
return agentSvcs , nil
2014-01-21 01:00:52 +00:00
}
2018-09-27 14:00:51 +00:00
// GET /v1/agent/service/:service_id
//
// Returns the service definition for a single local services and allows
// blocking watch using hash-based blocking.
func ( s * HTTPServer ) AgentService ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
// Get the proxy ID. Note that this is the ID of a proxy's service instance.
id := strings . TrimPrefix ( req . URL . Path , "/v1/agent/service/" )
// DEPRECATED(managed-proxies) - remove this whole hack.
//
// Support managed proxies until they are removed entirely. Since built-in
// proxy will now use this endpoint, in order to not break managed proxies in
// the interim until they are removed, we need to mirror the default-setting
// behaviour they had. Rather than thread that through this whole method as
// special cases that need to be unwound later (and duplicate logic in the
// proxy config endpoint) just defer to that and then translater the response.
if managedProxy := s . agent . State . Proxy ( id ) ; managedProxy != nil {
// This is for a managed proxy, use the old endpoint's behaviour
req . URL . Path = "/v1/agent/connect/proxy/" + id
obj , err := s . AgentConnectProxyConfig ( resp , req )
if err != nil {
return obj , err
}
proxyCfg , ok := obj . ( * api . ConnectProxyConfig )
if ! ok {
return nil , errors . New ( "internal error" )
}
// These are all set by defaults so type checks are just sanity checks that
// should never fail.
port , ok := proxyCfg . Config [ "bind_port" ] . ( int )
if ! ok || port < 1 {
return nil , errors . New ( "invalid proxy config" )
}
addr , ok := proxyCfg . Config [ "bind_address" ] . ( string )
if ! ok || addr == "" {
return nil , errors . New ( "invalid proxy config" )
}
localAddr , ok := proxyCfg . Config [ "local_service_address" ] . ( string )
if ! ok || localAddr == "" {
return nil , errors . New ( "invalid proxy config" )
}
// Old local_service_address was a host:port
localAddress , localPortRaw , err := net . SplitHostPort ( localAddr )
if err != nil {
return nil , err
}
localPort , err := strconv . Atoi ( localPortRaw )
if err != nil {
return nil , err
}
reply := & api . AgentService {
Kind : api . ServiceKindConnectProxy ,
ID : proxyCfg . ProxyServiceID ,
Service : managedProxy . Proxy . ProxyService . Service ,
Port : port ,
Address : addr ,
ContentHash : proxyCfg . ContentHash ,
Proxy : & api . AgentServiceConnectProxyConfig {
DestinationServiceName : proxyCfg . TargetServiceName ,
DestinationServiceID : proxyCfg . TargetServiceID ,
LocalServiceAddress : localAddress ,
LocalServicePort : localPort ,
Config : proxyCfg . Config ,
Upstreams : proxyCfg . Upstreams ,
} ,
}
return reply , nil
}
// Maybe block
var queryOpts structs . QueryOptions
if parseWait ( resp , req , & queryOpts ) {
// parseWait returns an error itself
return nil , nil
}
// Parse the token
var token string
s . parseToken ( req , & token )
// Parse hash specially. Eventually this should happen in parseWait and end up
// in QueryOptions but I didn't want to make very general changes right away.
hash := req . URL . Query ( ) . Get ( "hash" )
return s . agentLocalBlockingQuery ( resp , hash , & queryOpts ,
func ( ws memdb . WatchSet ) ( string , interface { } , error ) {
svcState := s . agent . State . ServiceState ( id )
if svcState == nil {
resp . WriteHeader ( http . StatusNotFound )
fmt . Fprintf ( resp , "unknown proxy service ID: %s" , id )
return "" , nil , nil
}
svc := svcState . Service
// Setup watch on the service
ws . Add ( svcState . WatchCh )
// Check ACLs.
rule , err := s . agent . resolveToken ( token )
if err != nil {
return "" , nil , err
}
if rule != nil && ! rule . ServiceRead ( svc . Service ) {
return "" , nil , acl . ErrPermissionDenied
}
var connect * api . AgentServiceConnect
var proxy * api . AgentServiceConnectProxyConfig
if svc . Connect . Native {
connect = & api . AgentServiceConnect {
Native : svc . Connect . Native ,
}
}
if svc . Kind == structs . ServiceKindConnectProxy {
proxy = svc . Proxy . ToAPI ( )
}
2018-10-04 13:08:12 +00:00
var weights api . AgentWeights
if svc . Weights != nil {
err := mapstructure . Decode ( svc . Weights , & weights )
if err != nil {
return "" , nil , err
}
}
2018-09-27 14:00:51 +00:00
// Calculate the content hash over the response, minus the hash field
reply := & api . AgentService {
Kind : api . ServiceKind ( svc . Kind ) ,
ID : svc . ID ,
Service : svc . Service ,
Tags : svc . Tags ,
Meta : svc . Meta ,
Port : svc . Port ,
Address : svc . Address ,
EnableTagOverride : svc . EnableTagOverride ,
2018-10-04 13:08:12 +00:00
Weights : weights ,
2018-09-27 14:00:51 +00:00
Proxy : proxy ,
Connect : connect ,
}
rawHash , err := hashstructure . Hash ( reply , nil )
if err != nil {
return "" , nil , err
}
// Include the ContentHash in the response body
reply . ContentHash = fmt . Sprintf ( "%x" , rawHash )
return reply . ContentHash , reply , nil
} )
}
2014-01-21 01:00:52 +00:00
func ( s * HTTPServer ) AgentChecks ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-12-14 22:16:46 +00:00
// Fetch the ACL token, if any.
var token string
s . parseToken ( req , & token )
2017-08-28 12:17:13 +00:00
checks := s . agent . State . Checks ( )
2016-12-14 22:16:46 +00:00
if err := s . agent . filterChecks ( token , & checks ) ; err != nil {
return nil , err
}
2017-04-28 01:22:07 +00:00
// Use empty list instead of nil
2018-02-07 04:35:55 +00:00
for id , c := range checks {
2017-04-28 01:22:07 +00:00
if c . ServiceTags == nil {
2018-02-07 04:35:55 +00:00
clone := * c
clone . ServiceTags = make ( [ ] string , 0 )
checks [ id ] = & clone
2017-04-28 01:22:07 +00:00
}
}
2014-01-21 01:00:52 +00:00
return checks , nil
2014-01-04 01:15:51 +00:00
}
func ( s * HTTPServer ) AgentMembers ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-12-14 22:16:46 +00:00
// Fetch the ACL token, if any.
var token string
s . parseToken ( req , & token )
2014-01-04 01:15:51 +00:00
// Check if the WAN is being queried
wan := false
if other := req . URL . Query ( ) . Get ( "wan" ) ; other != "" {
wan = true
}
2016-12-14 22:16:46 +00:00
2017-08-14 14:36:07 +00:00
segment := req . URL . Query ( ) . Get ( "segment" )
2017-09-05 20:40:19 +00:00
if wan {
switch segment {
case "" , api . AllSegments :
// The zero value and the special "give me all members"
// key are ok, otherwise the argument doesn't apply to
// the WAN.
default :
resp . WriteHeader ( http . StatusBadRequest )
fmt . Fprint ( resp , "Cannot provide a segment with wan=true" )
return nil , nil
}
2017-08-14 14:36:07 +00:00
}
2016-12-14 22:16:46 +00:00
var members [ ] serf . Member
2014-01-04 01:15:51 +00:00
if wan {
2016-12-14 22:16:46 +00:00
members = s . agent . WANMembers ( )
2017-08-14 14:36:07 +00:00
} else {
var err error
2017-09-05 19:22:20 +00:00
if segment == api . AllSegments {
members , err = s . agent . delegate . LANMembersAllSegments ( )
} else {
members , err = s . agent . delegate . LANSegmentMembers ( segment )
}
2017-08-14 14:36:07 +00:00
if err != nil {
return nil , err
}
2016-12-14 22:16:46 +00:00
}
if err := s . agent . filterMembers ( token , & members ) ; err != nil {
return nil , err
2014-01-04 01:15:51 +00:00
}
2016-12-14 22:16:46 +00:00
return members , nil
2014-01-04 01:15:51 +00:00
}
func ( s * HTTPServer ) AgentJoin ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-12-14 17:33:57 +00:00
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s . parseToken ( req , & token )
2017-08-23 14:52:48 +00:00
rule , err := s . agent . resolveToken ( token )
2016-12-14 17:33:57 +00:00
if err != nil {
return nil , err
}
2017-08-23 14:52:48 +00:00
if rule != nil && ! rule . AgentWrite ( s . agent . config . NodeName ) {
return nil , acl . ErrPermissionDenied
2016-12-14 17:33:57 +00:00
}
2014-01-04 01:15:51 +00:00
// Check if the WAN is being queried
wan := false
if other := req . URL . Query ( ) . Get ( "wan" ) ; other != "" {
wan = true
}
// Get the address
addr := strings . TrimPrefix ( req . URL . Path , "/v1/agent/join/" )
if wan {
2017-04-21 01:59:42 +00:00
_ , err = s . agent . JoinWAN ( [ ] string { addr } )
2014-01-04 01:15:51 +00:00
} else {
2017-04-21 01:59:42 +00:00
_ , err = s . agent . JoinLAN ( [ ] string { addr } )
2014-01-04 01:15:51 +00:00
}
2017-04-21 01:59:42 +00:00
return nil , err
2014-01-04 01:15:51 +00:00
}
2016-11-30 18:29:42 +00:00
func ( s * HTTPServer ) AgentLeave ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-12-14 17:33:57 +00:00
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s . parseToken ( req , & token )
2017-08-23 14:52:48 +00:00
rule , err := s . agent . resolveToken ( token )
2016-12-14 17:33:57 +00:00
if err != nil {
return nil , err
}
2017-08-23 14:52:48 +00:00
if rule != nil && ! rule . AgentWrite ( s . agent . config . NodeName ) {
return nil , acl . ErrPermissionDenied
2016-12-14 17:33:57 +00:00
}
2016-11-30 18:29:42 +00:00
if err := s . agent . Leave ( ) ; err != nil {
return nil , err
}
2017-06-20 07:29:20 +00:00
return nil , s . agent . ShutdownAgent ( )
2016-11-30 18:29:42 +00:00
}
2014-01-04 01:15:51 +00:00
func ( s * HTTPServer ) AgentForceLeave ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-12-14 17:33:57 +00:00
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s . parseToken ( req , & token )
2017-08-23 14:52:48 +00:00
rule , err := s . agent . resolveToken ( token )
2016-12-14 17:33:57 +00:00
if err != nil {
return nil , err
}
2017-08-23 14:52:48 +00:00
if rule != nil && ! rule . AgentWrite ( s . agent . config . NodeName ) {
return nil , acl . ErrPermissionDenied
2016-12-14 17:33:57 +00:00
}
2014-01-04 01:15:51 +00:00
addr := strings . TrimPrefix ( req . URL . Path , "/v1/agent/force-leave/" )
2014-02-19 22:27:01 +00:00
return nil , s . agent . ForceLeave ( addr )
2014-01-04 01:15:51 +00:00
}
2014-01-30 22:58:36 +00:00
2016-12-14 17:33:57 +00:00
// syncChanges is a helper function which wraps a blocking call to sync
// services and checks to the server. If the operation fails, we only
// only warn because the write did succeed and anti-entropy will sync later.
func ( s * HTTPServer ) syncChanges ( ) {
2017-08-28 12:17:13 +00:00
if err := s . agent . State . SyncChanges ( ) ; err != nil {
2017-05-19 09:53:41 +00:00
s . agent . logger . Printf ( "[ERR] agent: failed to sync changes: %v" , err )
2016-12-14 17:33:57 +00:00
}
}
2014-01-30 22:58:36 +00:00
func ( s * HTTPServer ) AgentRegisterCheck ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2017-06-15 16:46:06 +00:00
var args structs . CheckDefinition
2016-12-14 22:16:46 +00:00
// Fixup the type decode of TTL or Interval.
2014-04-21 22:02:36 +00:00
decodeCB := func ( raw interface { } ) error {
return FixupCheckType ( raw )
}
if err := decodeBody ( req , & args , decodeCB ) ; 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 . Fprintf ( resp , "Request decode failed: %v" , err )
2014-01-30 23:35:38 +00:00
return nil , nil
}
2016-12-14 22:16:46 +00:00
// Verify the check has a name.
2014-01-30 23:35:38 +00:00
if args . Name == "" {
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 , "Missing check name" )
2014-01-30 23:35:38 +00:00
return nil , nil
}
2015-04-12 00:53:48 +00:00
if args . Status != "" && ! structs . ValidStatus ( args . Status ) {
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 , "Bad check status" )
2015-04-12 00:53:48 +00:00
return nil , nil
}
2016-12-14 22:16:46 +00:00
// Construct the health check.
2014-02-03 23:15:35 +00:00
health := args . HealthCheck ( s . agent . config . NodeName )
2014-01-30 23:35:38 +00:00
2016-12-14 22:16:46 +00:00
// Verify the check type.
2017-05-15 19:49:13 +00:00
chkType := args . CheckType ( )
2017-10-10 23:54:06 +00:00
err := chkType . Validate ( )
if err != nil {
2017-08-23 19:19:11 +00:00
resp . WriteHeader ( http . StatusBadRequest )
2017-10-10 23:54:06 +00:00
fmt . Fprint ( resp , fmt . Errorf ( "Invalid check: %v" , err ) )
2014-01-30 23:35:38 +00:00
return nil , nil
}
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
2015-04-28 01:26:23 +00:00
var token string
s . parseToken ( req , & token )
2016-12-14 22:16:46 +00:00
if err := s . agent . vetCheckRegister ( token , health ) ; err != nil {
return nil , err
}
2015-04-28 01:26:23 +00:00
2016-12-14 22:16:46 +00:00
// Add the check.
2018-10-11 12:22:11 +00:00
if err := s . agent . AddCheck ( health , chkType , true , token , ConfigSourceRemote ) ; err != nil {
2015-02-20 23:45:06 +00:00
return nil , err
}
s . syncChanges ( )
return nil , nil
2014-01-30 22:58:36 +00:00
}
func ( s * HTTPServer ) AgentDeregisterCheck ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-06-06 20:19:31 +00:00
checkID := types . CheckID ( strings . TrimPrefix ( req . URL . Path , "/v1/agent/check/deregister/" ) )
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
var token string
s . parseToken ( req , & token )
if err := s . agent . vetCheckUpdate ( token , checkID ) ; err != nil {
return nil , err
}
2015-02-20 23:45:06 +00:00
if err := s . agent . RemoveCheck ( checkID , true ) ; err != nil {
return nil , err
}
s . syncChanges ( )
return nil , nil
2014-01-30 22:58:36 +00:00
}
func ( s * HTTPServer ) AgentCheckPass ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-06-06 20:19:31 +00:00
checkID := types . CheckID ( strings . TrimPrefix ( req . URL . Path , "/v1/agent/check/pass/" ) )
2014-01-30 23:18:05 +00:00
note := req . URL . Query ( ) . Get ( "note" )
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
var token string
s . parseToken ( req , & token )
if err := s . agent . vetCheckUpdate ( token , checkID ) ; err != nil {
return nil , err
}
2017-04-19 23:00:11 +00:00
if err := s . agent . updateTTLCheck ( checkID , api . HealthPassing , note ) ; err != nil {
2015-02-20 23:45:06 +00:00
return nil , err
}
s . syncChanges ( )
return nil , nil
2014-01-30 22:58:36 +00:00
}
func ( s * HTTPServer ) AgentCheckWarn ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-06-06 20:19:31 +00:00
checkID := types . CheckID ( strings . TrimPrefix ( req . URL . Path , "/v1/agent/check/warn/" ) )
2014-01-30 23:18:05 +00:00
note := req . URL . Query ( ) . Get ( "note" )
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
var token string
s . parseToken ( req , & token )
if err := s . agent . vetCheckUpdate ( token , checkID ) ; err != nil {
return nil , err
}
2017-04-19 23:00:11 +00:00
if err := s . agent . updateTTLCheck ( checkID , api . HealthWarning , note ) ; err != nil {
2015-02-20 23:45:06 +00:00
return nil , err
}
s . syncChanges ( )
return nil , nil
2014-01-30 22:58:36 +00:00
}
func ( s * HTTPServer ) AgentCheckFail ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-06-06 20:19:31 +00:00
checkID := types . CheckID ( strings . TrimPrefix ( req . URL . Path , "/v1/agent/check/fail/" ) )
2014-01-30 23:18:05 +00:00
note := req . URL . Query ( ) . Get ( "note" )
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
var token string
s . parseToken ( req , & token )
if err := s . agent . vetCheckUpdate ( token , checkID ) ; err != nil {
return nil , err
}
2017-04-19 23:00:11 +00:00
if err := s . agent . updateTTLCheck ( checkID , api . HealthCritical , note ) ; err != nil {
2015-02-20 23:45:06 +00:00
return nil , err
}
s . syncChanges ( )
return nil , nil
2014-01-30 22:58:36 +00:00
}
2016-03-03 01:08:06 +00:00
// checkUpdate is the payload for a PUT to AgentCheckUpdate.
type checkUpdate struct {
2017-04-19 23:00:11 +00:00
// Status us one of the api.Health* states, "passing", "warning", or
2016-03-03 01:08:06 +00:00
// "critical".
Status string
// Output is the information to post to the UI for operators as the
// output of the process that decided to hit the TTL check. This is
// different from the note field that's associated with the check
// itself.
Output string
}
// AgentCheckUpdate is a PUT-based alternative to the GET-based Pass/Warn/Fail
// APIs.
func ( s * HTTPServer ) AgentCheckUpdate ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
var update checkUpdate
if err := decodeBody ( req , & update , nil ) ; 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 . Fprintf ( resp , "Request decode failed: %v" , err )
2016-03-03 01:08:06 +00:00
return nil , nil
}
switch update . Status {
2017-04-19 23:00:11 +00:00
case api . HealthPassing :
case api . HealthWarning :
case api . HealthCritical :
2016-03-03 01:08:06 +00:00
default :
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 . Fprintf ( resp , "Invalid check status: '%s'" , update . Status )
2016-03-03 01:08:06 +00:00
return nil , nil
}
total := len ( update . Output )
2017-10-25 09:18:07 +00:00
if total > checks . BufSize {
2016-03-03 03:47:00 +00:00
update . Output = fmt . Sprintf ( "%s ... (captured %d of %d bytes)" ,
2017-10-25 09:18:07 +00:00
update . Output [ : checks . BufSize ] , checks . BufSize , total )
2016-03-03 01:08:06 +00:00
}
2016-06-06 20:19:31 +00:00
checkID := types . CheckID ( strings . TrimPrefix ( req . URL . Path , "/v1/agent/check/update/" ) )
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
var token string
s . parseToken ( req , & token )
if err := s . agent . vetCheckUpdate ( token , checkID ) ; err != nil {
return nil , err
}
2016-08-16 07:05:55 +00:00
if err := s . agent . updateTTLCheck ( checkID , update . Status , update . Output ) ; err != nil {
2016-03-03 01:08:06 +00:00
return nil , err
}
s . syncChanges ( )
return nil , nil
}
2019-01-07 14:39:23 +00:00
// agentHealthService Returns Health for a given service ID
func agentHealthService ( serviceID string , s * HTTPServer ) ( int , string , api . HealthChecks ) {
checks := s . agent . State . Checks ( )
serviceChecks := make ( api . HealthChecks , 0 )
for _ , c := range checks {
if c . ServiceID == serviceID || c . ServiceID == "" {
// TODO: harmonize struct.HealthCheck and api.HealthCheck (or at least extract conversion function)
healthCheck := & api . HealthCheck {
Node : c . Node ,
CheckID : string ( c . CheckID ) ,
Name : c . Name ,
Status : c . Status ,
Notes : c . Notes ,
Output : c . Output ,
ServiceID : c . ServiceID ,
ServiceName : c . ServiceName ,
ServiceTags : c . ServiceTags ,
}
serviceChecks = append ( serviceChecks , healthCheck )
}
}
status := serviceChecks . AggregatedStatus ( )
switch status {
case api . HealthWarning :
return http . StatusTooManyRequests , status , serviceChecks
case api . HealthPassing :
return http . StatusOK , status , serviceChecks
default :
return http . StatusServiceUnavailable , status , serviceChecks
}
}
func returnTextPlain ( req * http . Request ) bool {
if contentType := req . Header . Get ( "Accept" ) ; strings . HasPrefix ( contentType , "text/plain" ) {
return true
}
if format := req . URL . Query ( ) . Get ( "format" ) ; format != "" {
return format == "text"
}
return false
}
// AgentHealthServiceByID return the local Service Health given its ID
func ( s * HTTPServer ) AgentHealthServiceByID ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
// Pull out the service id (service id since there may be several instance of the same service on this host)
serviceID := strings . TrimPrefix ( req . URL . Path , "/v1/agent/health/service/id/" )
if serviceID == "" {
return nil , & BadRequestError { Reason : "Missing serviceID" }
}
services := s . agent . State . Services ( )
proxies := s . agent . State . Proxies ( )
for _ , service := range services {
if service . ID == serviceID {
code , status , healthChecks := agentHealthService ( serviceID , s )
if returnTextPlain ( req ) {
return status , CodeWithPayloadError { StatusCode : code , Reason : status , ContentType : "text/plain" }
}
serviceInfo := buildAgentService ( service , proxies )
result := & api . AgentServiceChecksInfo {
AggregatedStatus : status ,
Checks : healthChecks ,
Service : & serviceInfo ,
}
return result , CodeWithPayloadError { StatusCode : code , Reason : status , ContentType : "application/json" }
}
}
notFoundReason := fmt . Sprintf ( "ServiceId %s not found" , serviceID )
if returnTextPlain ( req ) {
return notFoundReason , CodeWithPayloadError { StatusCode : http . StatusNotFound , Reason : fmt . Sprintf ( "ServiceId %s not found" , serviceID ) , ContentType : "application/json" }
}
return & api . AgentServiceChecksInfo {
AggregatedStatus : api . HealthCritical ,
Checks : nil ,
Service : nil ,
} , CodeWithPayloadError { StatusCode : http . StatusNotFound , Reason : notFoundReason , ContentType : "application/json" }
}
// AgentHealthServiceByName return the worse status of all the services with given name on an agent
func ( s * HTTPServer ) AgentHealthServiceByName ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
// Pull out the service name
serviceName := strings . TrimPrefix ( req . URL . Path , "/v1/agent/health/service/name/" )
if serviceName == "" {
return nil , & BadRequestError { Reason : "Missing service Name" }
}
code := http . StatusNotFound
status := fmt . Sprintf ( "ServiceName %s Not Found" , serviceName )
services := s . agent . State . Services ( )
result := make ( [ ] api . AgentServiceChecksInfo , 0 , 16 )
proxies := s . agent . State . Proxies ( )
for _ , service := range services {
if service . Service == serviceName {
scode , sstatus , healthChecks := agentHealthService ( service . ID , s )
serviceInfo := buildAgentService ( service , proxies )
res := api . AgentServiceChecksInfo {
AggregatedStatus : sstatus ,
Checks : healthChecks ,
Service : & serviceInfo ,
}
result = append ( result , res )
// When service is not found, we ignore it and keep existing HTTP status
if code == http . StatusNotFound {
code = scode
status = sstatus
}
// We take the worst of all statuses, so we keep iterating
// passing: 200 < warning: 429 < critical: 503
if code < scode {
code = scode
status = sstatus
}
}
}
if returnTextPlain ( req ) {
return status , CodeWithPayloadError { StatusCode : code , Reason : status , ContentType : "text/plain" }
}
return result , CodeWithPayloadError { StatusCode : code , Reason : status , ContentType : "application/json" }
}
2014-01-30 22:58:36 +00:00
func ( s * HTTPServer ) AgentRegisterService ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2017-06-15 16:46:06 +00:00
var args structs . ServiceDefinition
2016-12-14 22:16:46 +00:00
// Fixup the type decode of TTL or Interval if a check if provided.
2014-04-21 22:02:36 +00:00
decodeCB := func ( raw interface { } ) error {
rawMap , ok := raw . ( map [ string ] interface { } )
if ! ok {
return nil
}
2014-04-25 02:44:27 +00:00
2017-10-10 23:40:59 +00:00
// see https://github.com/hashicorp/consul/pull/3557 why we need this
// and why we should get rid of it.
config . TranslateKeys ( rawMap , map [ string ] string {
"enable_tag_override" : "EnableTagOverride" ,
2018-09-27 13:33:12 +00:00
// Managed Proxy Config
"exec_mode" : "ExecMode" ,
// Proxy Upstreams
"destination_name" : "DestinationName" ,
"destination_type" : "DestinationType" ,
"destination_namespace" : "DestinationNamespace" ,
"local_bind_port" : "LocalBindPort" ,
"local_bind_address" : "LocalBindAddress" ,
// Proxy Config
"destination_service_name" : "DestinationServiceName" ,
"destination_service_id" : "DestinationServiceID" ,
"local_service_port" : "LocalServicePort" ,
"local_service_address" : "LocalServiceAddress" ,
// SidecarService
"sidecar_service" : "SidecarService" ,
// DON'T Recurse into these opaque config maps or we might mangle user's
// keys. Note empty canonical is a special sentinel to prevent recursion.
"Meta" : "" ,
// upstreams is an array but this prevents recursion into config field of
// any item in the array.
"Proxy.Config" : "" ,
"Proxy.Upstreams.Config" : "" ,
"Connect.Proxy.Config" : "" ,
"Connect.Proxy.Upstreams.Config" : "" ,
// Same exceptions as above, but for a nested sidecar_service note we use
// the canonical form SidecarService since that is translated by the time
// the lookup here happens. Note that sidecar service doesn't support
// managed proxies (connect.proxy).
"Connect.SidecarService.Meta" : "" ,
"Connect.SidecarService.Proxy.Config" : "" ,
"Connect.SidecarService.Proxy.Upstreams.config" : "" ,
2017-10-10 23:40:59 +00:00
} )
2014-04-25 02:44:27 +00:00
for k , v := range rawMap {
2015-01-14 03:08:30 +00:00
switch strings . ToLower ( k ) {
case "check" :
if err := FixupCheckType ( v ) ; err != nil {
return err
}
case "checks" :
chkTypes , ok := v . ( [ ] interface { } )
if ! ok {
2015-01-24 02:50:51 +00:00
continue
2015-01-14 03:08:30 +00:00
}
for _ , chkType := range chkTypes {
if err := FixupCheckType ( chkType ) ; err != nil {
return err
}
}
2014-04-25 02:44:27 +00:00
}
}
2015-01-14 03:08:30 +00:00
return nil
2014-04-21 22:02:36 +00:00
}
if err := decodeBody ( req , & args , decodeCB ) ; 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 . Fprintf ( resp , "Request decode failed: %v" , err )
2014-01-30 23:35:38 +00:00
return nil , nil
}
2016-12-14 22:16:46 +00:00
// Verify the service has a name.
2014-01-30 23:35:38 +00:00
if args . Name == "" {
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 , "Missing service name" )
2014-01-30 23:35:38 +00:00
return nil , nil
}
2017-05-08 16:34:45 +00:00
// Check the service address here and in the catalog RPC endpoint
2018-03-19 16:56:00 +00:00
// since service registration isn't synchronous.
2017-05-15 20:10:36 +00:00
if ipaddr . IsAny ( args . Address ) {
2017-08-23 19:19:11 +00:00
resp . WriteHeader ( http . StatusBadRequest )
2017-05-08 16:34:45 +00:00
fmt . Fprintf ( resp , "Invalid service address" )
return nil , nil
}
2016-12-14 22:16:46 +00:00
// Get the node service.
2014-02-03 23:15:35 +00:00
ns := args . NodeService ( )
2018-09-07 14:30:47 +00:00
if ns . Weights != nil {
if err := structs . ValidateWeights ( ns . Weights ) ; err != nil {
resp . WriteHeader ( http . StatusBadRequest )
fmt . Fprint ( resp , fmt . Errorf ( "Invalid Weights: %v" , err ) )
return nil , nil
}
}
2018-03-28 14:04:50 +00:00
if err := structs . ValidateMetadata ( ns . Meta , false ) ; err != nil {
2018-02-07 00:54:42 +00:00
resp . WriteHeader ( http . StatusBadRequest )
2018-03-27 20:22:42 +00:00
fmt . Fprint ( resp , fmt . Errorf ( "Invalid Service Meta: %v" , err ) )
2018-02-07 00:54:42 +00:00
return nil , nil
}
2014-01-30 23:35:38 +00:00
2018-03-11 01:42:30 +00:00
// Run validation. This is the same validation that would happen on
// the catalog endpoint so it helps ensure the sync will work properly.
if err := ns . Validate ( ) ; err != nil {
resp . WriteHeader ( http . StatusBadRequest )
fmt . Fprintf ( resp , err . Error ( ) )
return nil , nil
}
2016-12-14 22:16:46 +00:00
// Verify the check type.
2017-10-10 23:54:06 +00:00
chkTypes , err := args . CheckTypes ( )
if err != nil {
resp . WriteHeader ( http . StatusBadRequest )
fmt . Fprint ( resp , fmt . Errorf ( "Invalid check: %v" , err ) )
return nil , nil
}
2015-01-14 01:52:17 +00:00
for _ , check := range chkTypes {
2015-04-12 00:53:48 +00:00
if check . Status != "" && ! structs . ValidStatus ( check . Status ) {
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 , "Status for checks must 'passing', 'warning', 'critical'" )
2015-04-12 00:53:48 +00:00
return nil , nil
}
2014-01-30 23:35:38 +00:00
}
2018-09-27 13:33:12 +00:00
// Verify the sidecar check types
if args . Connect != nil && args . Connect . SidecarService != nil {
chkTypes , err := args . Connect . SidecarService . CheckTypes ( )
if err != nil {
return nil , & BadRequestError {
Reason : fmt . Sprintf ( "Invalid check in sidecar_service: %v" , err ) ,
}
}
for _ , check := range chkTypes {
if check . Status != "" && ! structs . ValidStatus ( check . Status ) {
return nil , & BadRequestError {
Reason : "Status for checks must 'passing', 'warning', 'critical'" ,
}
}
}
}
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
2015-04-28 01:26:23 +00:00
var token string
s . parseToken ( req , & token )
2016-12-14 22:16:46 +00:00
if err := s . agent . vetServiceRegister ( token , ns ) ; err != nil {
return nil , err
}
2015-04-28 01:26:23 +00:00
2018-09-27 13:33:12 +00:00
// See if we have a sidecar to register too
sidecar , sidecarChecks , sidecarToken , err := s . agent . sidecarServiceFromNodeService ( ns , token )
if err != nil {
return nil , & BadRequestError {
Reason : fmt . Sprintf ( "Invalid SidecarService: %s" , err ) }
}
if sidecar != nil {
2018-10-09 16:57:26 +00:00
// Make sure we are allowed to register the sidecar using the token
2018-09-27 13:33:12 +00:00
// specified (might be specific to sidecar or the same one as the overall
// request).
if err := s . agent . vetServiceRegister ( sidecarToken , sidecar ) ; err != nil {
return nil , err
}
// We parsed the sidecar registration, now remove it from the NodeService
// for the actual service since it's done it's job and we don't want to
// persist it in the actual state/catalog. SidecarService is meant to be a
// registration syntax sugar so don't propagate it any further.
ns . Connect . SidecarService = nil
}
2018-04-17 12:29:02 +00:00
// Get any proxy registrations
proxy , err := args . ConnectManagedProxy ( )
if err != nil {
resp . WriteHeader ( http . StatusBadRequest )
fmt . Fprintf ( resp , err . Error ( ) )
return nil , nil
}
2018-06-12 15:35:59 +00:00
// If we have a proxy, verify that we're allowed to add a proxy via the API
if proxy != nil && ! s . agent . config . ConnectProxyAllowManagedAPIRegistration {
return nil , & BadRequestError {
Reason : "Managed proxy registration via the API is disallowed." }
}
2016-12-14 22:16:46 +00:00
// Add the service.
2018-10-11 12:22:11 +00:00
if err := s . agent . AddService ( ns , chkTypes , true , token , ConfigSourceRemote ) ; err != nil {
2015-02-20 23:45:06 +00:00
return nil , err
}
2018-04-17 12:29:02 +00:00
// Add proxy (which will add proxy service so do it before we trigger sync)
if proxy != nil {
2018-10-11 12:22:11 +00:00
if err := s . agent . AddProxy ( proxy , true , false , "" , ConfigSourceRemote ) ; err != nil {
2018-04-17 12:29:02 +00:00
return nil , err
}
}
2018-09-27 13:33:12 +00:00
// Add sidecar.
if sidecar != nil {
2018-10-11 12:22:11 +00:00
if err := s . agent . AddService ( sidecar , sidecarChecks , true , sidecarToken , ConfigSourceRemote ) ; err != nil {
2018-09-27 13:33:12 +00:00
return nil , err
}
}
2015-02-20 23:45:06 +00:00
s . syncChanges ( )
return nil , nil
2014-01-30 22:58:36 +00:00
}
func ( s * HTTPServer ) AgentDeregisterService ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2014-01-30 23:18:05 +00:00
serviceID := strings . TrimPrefix ( req . URL . Path , "/v1/agent/service/deregister/" )
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
var token string
s . parseToken ( req , & token )
if err := s . agent . vetServiceUpdate ( token , serviceID ) ; err != nil {
return nil , err
}
2018-06-13 08:00:23 +00:00
// Verify this isn't a proxy
if s . agent . State . Proxy ( serviceID ) != nil {
return nil , & BadRequestError {
Reason : "Managed proxy service cannot be deregistered directly. " +
"Deregister the service that has a managed proxy to automatically " +
"deregister the managed proxy itself." }
}
2015-02-20 23:45:06 +00:00
if err := s . agent . RemoveService ( serviceID , true ) ; err != nil {
return nil , err
}
2018-06-13 07:57:48 +00:00
2015-02-20 23:45:06 +00:00
s . syncChanges ( )
return nil , nil
2014-01-30 22:58:36 +00:00
}
2015-01-15 08:16:34 +00:00
func ( s * HTTPServer ) AgentServiceMaintenance ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
// Ensure we have a service ID
serviceID := strings . TrimPrefix ( req . URL . Path , "/v1/agent/service/maintenance/" )
if serviceID == "" {
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 , "Missing service ID" )
2015-01-15 08:16:34 +00:00
return nil , nil
}
// Ensure we have some action
params := req . URL . Query ( )
if _ , ok := params [ "enable" ] ; ! ok {
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 , "Missing value for enable" )
2015-01-15 08:16:34 +00:00
return nil , nil
}
raw := params . Get ( "enable" )
2015-01-21 17:53:31 +00:00
enable , err := strconv . ParseBool ( raw )
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 . Fprintf ( resp , "Invalid value for enable: %q" , raw )
2015-01-15 08:16:34 +00:00
return nil , nil
}
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
2015-09-10 18:43:59 +00:00
var token string
s . parseToken ( req , & token )
2016-12-14 22:16:46 +00:00
if err := s . agent . vetServiceUpdate ( token , serviceID ) ; err != nil {
return nil , err
}
2015-09-10 18:43:59 +00:00
2015-01-15 08:16:34 +00:00
if enable {
2015-01-21 20:21:57 +00:00
reason := params . Get ( "reason" )
2015-09-10 18:43:59 +00:00
if err = s . agent . EnableServiceMaintenance ( serviceID , reason , token ) ; err != nil {
2017-08-23 19:19:11 +00:00
resp . WriteHeader ( http . StatusNotFound )
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 , err . Error ( ) )
2015-01-21 21:28:26 +00:00
return nil , nil
2015-01-15 09:17:35 +00:00
}
2015-01-15 08:16:34 +00:00
} else {
2015-01-15 09:17:35 +00:00
if err = s . agent . DisableServiceMaintenance ( serviceID ) ; err != nil {
2017-08-23 19:19:11 +00:00
resp . WriteHeader ( http . StatusNotFound )
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 , err . Error ( ) )
2015-01-21 21:28:26 +00:00
return nil , nil
2015-01-15 09:17:35 +00:00
}
2015-01-15 08:16:34 +00:00
}
2015-02-20 23:45:06 +00:00
s . syncChanges ( )
2015-01-21 21:28:26 +00:00
return nil , nil
2015-01-15 08:16:34 +00:00
}
2015-01-15 19:20:22 +00:00
func ( s * HTTPServer ) AgentNodeMaintenance ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
// Ensure we have some action
params := req . URL . Query ( )
if _ , ok := params [ "enable" ] ; ! ok {
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 , "Missing value for enable" )
2015-01-15 19:20:22 +00:00
return nil , nil
}
raw := params . Get ( "enable" )
2015-01-21 17:53:31 +00:00
enable , err := strconv . ParseBool ( raw )
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 . Fprintf ( resp , "Invalid value for enable: %q" , raw )
2015-01-15 19:20:22 +00:00
return nil , nil
}
2016-12-14 22:16:46 +00:00
// Get the provided token, if any, and vet against any ACL policies.
2015-09-10 18:43:59 +00:00
var token string
s . parseToken ( req , & token )
2017-08-23 14:52:48 +00:00
rule , err := s . agent . resolveToken ( token )
2016-12-14 22:16:46 +00:00
if err != nil {
return nil , err
}
2017-09-14 19:31:01 +00:00
if rule != nil && ! rule . NodeWrite ( s . agent . config . NodeName , nil ) {
2017-08-23 14:52:48 +00:00
return nil , acl . ErrPermissionDenied
2016-12-14 22:16:46 +00:00
}
2015-09-10 18:43:59 +00:00
2015-01-15 19:20:22 +00:00
if enable {
2015-09-10 18:43:59 +00:00
s . agent . EnableNodeMaintenance ( params . Get ( "reason" ) , token )
2015-01-15 19:20:22 +00:00
} else {
s . agent . DisableNodeMaintenance ( )
}
2015-02-20 23:45:06 +00:00
s . syncChanges ( )
2015-01-15 19:20:22 +00:00
return nil , nil
}
2015-02-20 23:45:06 +00:00
2016-11-16 21:45:26 +00:00
func ( s * HTTPServer ) AgentMonitor ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2016-12-14 17:33:57 +00:00
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s . parseToken ( req , & token )
2017-08-23 14:52:48 +00:00
rule , err := s . agent . resolveToken ( token )
2016-12-14 17:33:57 +00:00
if err != nil {
2016-11-28 21:08:31 +00:00
return nil , err
}
2017-08-23 14:52:48 +00:00
if rule != nil && ! rule . AgentRead ( s . agent . config . NodeName ) {
return nil , acl . ErrPermissionDenied
2016-12-14 17:33:57 +00:00
}
2016-11-28 21:08:31 +00:00
2016-12-14 17:33:57 +00:00
// Get the provided loglevel.
2016-11-16 21:45:26 +00:00
logLevel := req . URL . Query ( ) . Get ( "loglevel" )
if logLevel == "" {
logLevel = "INFO"
}
2016-12-14 17:33:57 +00:00
// Upper case the level since that's required by the filter.
2016-11-16 21:45:26 +00:00
logLevel = strings . ToUpper ( logLevel )
2016-12-14 17:33:57 +00:00
// Create a level filter and flusher.
2016-11-16 21:45:26 +00:00
filter := logger . LevelFilter ( )
filter . MinLevel = logutils . LogLevel ( logLevel )
if ! logger . ValidateLevelFilter ( filter . MinLevel , filter ) {
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 . Fprintf ( resp , "Unknown log level: %s" , filter . MinLevel )
2016-11-16 21:45:26 +00:00
return nil , nil
}
flusher , ok := resp . ( http . Flusher )
if ! ok {
return nil , fmt . Errorf ( "Streaming not supported" )
}
2016-12-14 17:33:57 +00:00
// Set up a log handler.
2016-11-16 21:45:26 +00:00
handler := & httpLogHandler {
filter : filter ,
logCh : make ( chan string , 512 ) ,
2017-05-19 09:53:41 +00:00
logger : s . agent . logger ,
2016-11-16 21:45:26 +00:00
}
2017-05-19 15:51:39 +00:00
s . agent . LogWriter . RegisterHandler ( handler )
defer s . agent . LogWriter . DeregisterHandler ( handler )
2016-11-16 21:45:26 +00:00
notify := resp . ( http . CloseNotifier ) . CloseNotify ( )
2018-02-19 21:53:10 +00:00
// Send header so client can start streaming body
resp . WriteHeader ( http . StatusOK )
2018-04-03 20:33:13 +00:00
// 0 byte write is needed before the Flush call so that if we are using
// a gzip stream it will go ahead and write out the HTTP response header
resp . Write ( [ ] byte ( "" ) )
2018-02-19 21:53:10 +00:00
flusher . Flush ( )
2016-12-14 17:33:57 +00:00
// Stream logs until the connection is closed.
2016-11-16 21:45:26 +00:00
for {
select {
case <- notify :
2017-05-19 15:51:39 +00:00
s . agent . LogWriter . DeregisterHandler ( handler )
2016-11-28 21:08:31 +00:00
if handler . droppedCount > 0 {
s . agent . logger . Printf ( "[WARN] agent: Dropped %d logs during monitor request" , handler . droppedCount )
}
2016-11-16 21:45:26 +00:00
return nil , nil
case log := <- handler . logCh :
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 . Fprintln ( resp , log )
2016-11-16 21:45:26 +00:00
flusher . Flush ( )
}
}
}
type httpLogHandler struct {
2016-11-28 21:08:31 +00:00
filter * logutils . LevelFilter
logCh chan string
logger * log . Logger
droppedCount int
2016-11-16 21:45:26 +00:00
}
func ( h * httpLogHandler ) HandleLog ( log string ) {
// Check the log level
if ! h . filter . Check ( [ ] byte ( log ) ) {
return
}
// Do a non-blocking send
select {
case h . logCh <- log :
default :
2016-11-28 21:08:31 +00:00
// Just increment a counter for dropped logs to this handler; we can't log now
// because the lock is already held by the LogWriter invoking this
2017-04-20 19:00:03 +00:00
h . droppedCount ++
2016-11-16 21:45:26 +00:00
}
}
2017-07-26 18:03:43 +00:00
func ( s * HTTPServer ) AgentToken ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2017-11-28 21:47:30 +00:00
if s . checkACLDisabled ( resp , req ) {
return nil , nil
}
2017-07-26 18:03:43 +00:00
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s . parseToken ( req , & token )
2017-08-23 14:52:48 +00:00
rule , err := s . agent . resolveToken ( token )
2017-07-26 18:03:43 +00:00
if err != nil {
return nil , err
}
2017-08-23 14:52:48 +00:00
if rule != nil && ! rule . AgentWrite ( s . agent . config . NodeName ) {
return nil , acl . ErrPermissionDenied
2017-07-26 18:03:43 +00:00
}
// The body is just the token, but it's in a JSON object so we can add
// fields to this later if needed.
var args api . AgentToken
if err := decodeBody ( req , & args , nil ) ; err != nil {
resp . WriteHeader ( http . StatusBadRequest )
fmt . Fprintf ( resp , "Request decode failed: %v" , err )
return nil , nil
}
2019-02-27 19:28:31 +00:00
if s . agent . config . ACLEnableTokenPersistence {
// we hold the lock around updating the internal token store
// as well as persisting the tokens because we don't want to write
// into the store to have something else wipe it out before we can
// persist everything (like an agent config reload). The token store
// lock is only held for those operations so other go routines that
// just need to read some token out of the store will not be impacted
// any more than they would be without token persistence.
s . agent . persistedTokensLock . Lock ( )
defer s . agent . persistedTokensLock . Unlock ( )
}
2017-07-26 18:03:43 +00:00
// Figure out the target token.
target := strings . TrimPrefix ( req . URL . Path , "/v1/agent/token/" )
switch target {
2019-02-27 19:28:31 +00:00
case "acl_token" , "default" :
s . agent . tokens . UpdateUserToken ( args . Token , token_store . TokenSourceAPI )
2017-07-26 18:03:43 +00:00
2019-02-27 19:28:31 +00:00
case "acl_agent_token" , "agent" :
s . agent . tokens . UpdateAgentToken ( args . Token , token_store . TokenSourceAPI )
2017-07-26 18:03:43 +00:00
2019-02-27 19:28:31 +00:00
case "acl_agent_master_token" , "agent_master" :
s . agent . tokens . UpdateAgentMasterToken ( args . Token , token_store . TokenSourceAPI )
2017-08-03 22:39:31 +00:00
2019-02-27 19:28:31 +00:00
case "acl_replication_token" , "replication" :
s . agent . tokens . UpdateReplicationToken ( args . Token , token_store . TokenSourceAPI )
2018-10-15 16:17:48 +00:00
2017-07-26 18:03:43 +00:00
default :
resp . WriteHeader ( http . StatusNotFound )
fmt . Fprintf ( resp , "Token %q is unknown" , target )
return nil , nil
}
2019-02-27 19:28:31 +00:00
if s . agent . config . ACLEnableTokenPersistence {
tokens := persistedTokens { }
if tok , source := s . agent . tokens . UserTokenAndSource ( ) ; tok != "" && source == token_store . TokenSourceAPI {
tokens . Default = tok
}
if tok , source := s . agent . tokens . AgentTokenAndSource ( ) ; tok != "" && source == token_store . TokenSourceAPI {
tokens . Agent = tok
}
if tok , source := s . agent . tokens . AgentMasterTokenAndSource ( ) ; tok != "" && source == token_store . TokenSourceAPI {
tokens . AgentMaster = tok
}
if tok , source := s . agent . tokens . ReplicationTokenAndSource ( ) ; tok != "" && source == token_store . TokenSourceAPI {
tokens . Replication = tok
}
data , err := json . Marshal ( tokens )
if err != nil {
s . agent . logger . Printf ( "[WARN] agent: failed to persist tokens - %v" , err )
return nil , fmt . Errorf ( "Failed to marshal tokens for persistence: %v" , err )
}
if err := file . WriteAtomicWithPerms ( filepath . Join ( s . agent . config . DataDir , tokensPath ) , data , 0600 ) ; err != nil {
s . agent . logger . Printf ( "[WARN] agent: failed to persist tokens - %v" , err )
return nil , fmt . Errorf ( "Failed to persist tokens - %v" , err )
}
}
2018-03-21 15:56:14 +00:00
s . agent . logger . Printf ( "[INFO] agent: Updated agent's ACL token %q" , target )
2017-07-26 18:03:43 +00:00
return nil , nil
}
2018-03-17 04:39:26 +00:00
// AgentConnectCARoots returns the trusted CA roots.
func ( s * HTTPServer ) AgentConnectCARoots ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2018-04-11 08:52:51 +00:00
var args structs . DCSpecificRequest
if done := s . parse ( resp , req , & args . Datacenter , & args . QueryOptions ) ; done {
return nil , nil
}
2018-06-15 12:13:54 +00:00
raw , m , err := s . agent . cache . Get ( cachetype . ConnectCARootName , & args )
2018-04-11 08:52:51 +00:00
if err != nil {
return nil , err
}
2018-06-15 12:13:54 +00:00
defer setCacheMeta ( resp , & m )
// Add cache hit
2018-04-11 08:52:51 +00:00
reply , ok := raw . ( * structs . IndexedCARoots )
if ! ok {
// This should never happen, but we want to protect against panics
return nil , fmt . Errorf ( "internal error: response type not correct" )
}
defer setMeta ( resp , & reply . QueryMeta )
return * reply , nil
2018-03-17 04:39:26 +00:00
}
2018-03-21 17:55:39 +00:00
// AgentConnectCALeafCert returns the certificate bundle for a service
// instance. This supports blocking queries to update the returned bundle.
func ( s * HTTPServer ) AgentConnectCALeafCert ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2018-05-22 17:33:14 +00:00
// Get the service name. Note that this is the name of the sevice,
// not the ID of the service instance.
2018-05-19 06:27:02 +00:00
serviceName := strings . TrimPrefix ( req . URL . Path , "/v1/agent/connect/ca/leaf/" )
2018-03-21 17:55:39 +00:00
2018-04-30 21:23:49 +00:00
args := cachetype . ConnectCALeafRequest {
2018-05-19 06:27:02 +00:00
Service : serviceName , // Need name not ID
2018-04-30 21:23:49 +00:00
}
var qOpts structs . QueryOptions
2018-07-30 13:11:51 +00:00
2018-04-30 21:23:49 +00:00
// Store DC in the ConnectCALeafRequest but query opts separately
2018-07-30 13:11:51 +00:00
// Don't resolve a proxy token to a real token that will be
// done with a call to verifyProxyToken later along with
// other security relevant checks.
if done := s . parseWithoutResolvingProxyToken ( resp , req , & args . Datacenter , & qOpts ) ; done {
2018-04-30 21:23:49 +00:00
return nil , nil
}
args . MinQueryIndex = qOpts . MinQueryIndex
2019-01-10 16:23:37 +00:00
args . MaxQueryTime = qOpts . MaxQueryTime
2018-03-21 17:55:39 +00:00
2018-05-07 04:46:22 +00:00
// Verify the proxy token. This will check both the local proxy token
2018-07-30 13:11:51 +00:00
// as well as the ACL if the token isn't local. The checks done in
// verifyProxyToken are still relevant because a leaf cert can be cached
// verifying the proxy token matches the service id or that a real
// acl token still is valid and has ServiceWrite is necessary or
// that cached cert is potentially unprotected.
2018-06-18 19:37:00 +00:00
effectiveToken , _ , err := s . agent . verifyProxyToken ( qOpts . Token , serviceName , "" )
2018-04-30 21:23:49 +00:00
if err != nil {
2018-03-21 17:55:39 +00:00
return nil , err
}
2018-05-10 16:04:33 +00:00
args . Token = effectiveToken
2018-04-30 21:23:49 +00:00
2018-06-15 12:13:54 +00:00
raw , m , err := s . agent . cache . Get ( cachetype . ConnectCALeafName , & args )
2018-04-30 21:23:49 +00:00
if err != nil {
return nil , err
}
2018-06-15 12:13:54 +00:00
defer setCacheMeta ( resp , & m )
2018-04-30 21:23:49 +00:00
reply , ok := raw . ( * structs . IssuedCert )
if ! ok {
// This should never happen, but we want to protect against panics
return nil , fmt . Errorf ( "internal error: response type not correct" )
}
setIndex ( resp , reply . ModifyIndex )
2018-03-21 17:55:39 +00:00
2018-04-30 21:23:49 +00:00
return reply , nil
2018-03-21 17:55:39 +00:00
}
2018-03-21 23:02:46 +00:00
2018-04-18 20:05:30 +00:00
// GET /v1/agent/connect/proxy/:proxy_service_id
//
// Returns the local proxy config for the identified proxy. Requires token=
// param with the correct local ProxyToken (not ACL token).
2018-04-05 16:15:43 +00:00
func ( s * HTTPServer ) AgentConnectProxyConfig ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2018-04-18 20:05:30 +00:00
// Get the proxy ID. Note that this is the ID of a proxy's service instance.
id := strings . TrimPrefix ( req . URL . Path , "/v1/agent/connect/proxy/" )
// Maybe block
var queryOpts structs . QueryOptions
if parseWait ( resp , req , & queryOpts ) {
// parseWait returns an error itself
return nil , nil
}
2018-07-30 13:11:51 +00:00
// Parse the token - don't resolve a proxy token to a real token
// that will be done with a call to verifyProxyToken later along with
// other security relevant checks.
2018-05-07 04:02:44 +00:00
var token string
2018-07-30 13:11:51 +00:00
s . parseTokenWithoutResolvingProxyToken ( req , & token )
2018-05-07 04:02:44 +00:00
2018-04-18 20:05:30 +00:00
// Parse hash specially since it's only this endpoint that uses it currently.
// Eventually this should happen in parseWait and end up in QueryOptions but I
// didn't want to make very general changes right away.
hash := req . URL . Query ( ) . Get ( "hash" )
2018-04-18 20:48:58 +00:00
return s . agentLocalBlockingQuery ( resp , hash , & queryOpts ,
2018-04-19 10:15:32 +00:00
func ( ws memdb . WatchSet ) ( string , interface { } , error ) {
2018-04-18 20:05:30 +00:00
// Retrieve the proxy specified
proxy := s . agent . State . Proxy ( id )
if proxy == nil {
resp . WriteHeader ( http . StatusNotFound )
fmt . Fprintf ( resp , "unknown proxy service ID: %s" , id )
return "" , nil , nil
}
// Lookup the target service as a convenience
target := s . agent . State . Service ( proxy . Proxy . TargetServiceID )
if target == nil {
// Not found since this endpoint is only useful for agent-managed proxies so
// service missing means the service was deregistered racily with this call.
resp . WriteHeader ( http . StatusNotFound )
fmt . Fprintf ( resp , "unknown target service ID: %s" , proxy . Proxy . TargetServiceID )
return "" , nil , nil
}
2018-07-30 13:11:51 +00:00
// Validate the ACL token - because this endpoint uses data local to a single
// agent, this function is responsible for all enforcement regarding
// protection of the configuration. verifyProxyToken will match the proxies
// token to the correct service or in the case of being provide a real ACL
// token it will ensure that the requester has ServiceWrite privileges
// for this service.
2018-06-18 19:37:00 +00:00
_ , isProxyToken , err := s . agent . verifyProxyToken ( token , target . Service , id )
2018-05-19 06:27:02 +00:00
if err != nil {
return "" , nil , err
}
2018-04-19 10:15:32 +00:00
// Watch the proxy for changes
ws . Add ( proxy . WatchCh )
2018-04-18 20:05:30 +00:00
hash , err := hashstructure . Hash ( proxy . Proxy , nil )
if err != nil {
return "" , nil , err
}
contentHash := fmt . Sprintf ( "%x" , hash )
2018-06-19 11:11:42 +00:00
// Set defaults
config , err := s . agent . applyProxyConfigDefaults ( proxy . Proxy )
if err != nil {
return "" , nil , err
2018-04-26 13:01:20 +00:00
}
2018-06-18 19:37:00 +00:00
// Only merge in telemetry config from agent if the requested is
// authorized with a proxy token. This prevents us leaking potentially
// sensitive config like Circonus API token via a public endpoint. Proxy
// tokens are only ever generated in-memory and passed via ENV to a child
// proxy process so potential for abuse here seems small. This endpoint in
// general is only useful for managed proxies now so it should _always_ be
// true that auth is via a proxy token but inconvenient for testing if we
// lock it down so strictly.
if isProxyToken {
// Add telemetry config. Copy the global config so we can customize the
// prefix.
telemetryCfg := s . agent . config . Telemetry
telemetryCfg . MetricsPrefix = telemetryCfg . MetricsPrefix + ".proxy." + target . ID
// First see if the user has specified telemetry
if userRaw , ok := config [ "telemetry" ] ; ok {
// User specified domething, see if it is compatible with agent
// telemetry config:
var uCfg lib . TelemetryConfig
dec , err := mapstructure . NewDecoder ( & mapstructure . DecoderConfig {
Result : & uCfg ,
// Make sure that if the user passes something that isn't just a
// simple override of a valid TelemetryConfig that we fail so that we
// don't clobber their custom config.
ErrorUnused : true ,
} )
if err == nil {
if err = dec . Decode ( userRaw ) ; err == nil {
// It did decode! Merge any unspecified fields from agent config.
uCfg . MergeDefaults ( & telemetryCfg )
config [ "telemetry" ] = uCfg
}
2018-06-13 15:53:44 +00:00
}
2018-06-18 19:37:00 +00:00
// Failed to decode, just keep user's config["telemetry"] verbatim
// with no agent merge.
} else {
// Add agent telemetry config.
config [ "telemetry" ] = telemetryCfg
2018-06-13 15:53:44 +00:00
}
2018-06-07 13:11:06 +00:00
}
2018-04-20 13:24:24 +00:00
reply := & api . ConnectProxyConfig {
2018-04-18 20:05:30 +00:00
ProxyServiceID : proxy . Proxy . ProxyService . ID ,
TargetServiceID : target . ID ,
TargetServiceName : target . Service ,
ContentHash : contentHash ,
2018-05-03 17:44:10 +00:00
ExecMode : api . ProxyExecMode ( proxy . Proxy . ExecMode . String ( ) ) ,
Command : proxy . Proxy . Command ,
2018-04-26 13:01:20 +00:00
Config : config ,
2018-09-12 16:07:47 +00:00
Upstreams : proxy . Proxy . Upstreams . ToAPI ( ) ,
2018-04-18 20:05:30 +00:00
}
return contentHash , reply , nil
} )
}
2018-04-19 10:15:32 +00:00
type agentLocalBlockingFunc func ( ws memdb . WatchSet ) ( string , interface { } , error )
2018-04-18 20:05:30 +00:00
2018-04-18 20:48:58 +00:00
// agentLocalBlockingQuery performs a blocking query in a generic way against
// local agent state that has no RPC or raft to back it. It uses `hash` paramter
// instead of an `index`. The resp is needed to write the `X-Consul-ContentHash`
// header back on return no Status nor body content is ever written to it.
func ( s * HTTPServer ) agentLocalBlockingQuery ( resp http . ResponseWriter , hash string ,
2018-04-18 20:05:30 +00:00
queryOpts * structs . QueryOptions , fn agentLocalBlockingFunc ) ( interface { } , error ) {
2018-04-19 10:15:32 +00:00
// If we are not blocking we can skip tracking and allocating - nil WatchSet
// is still valid to call Add on and will just be a no op.
var ws memdb . WatchSet
var timeout * time . Timer
2018-04-18 20:05:30 +00:00
if hash != "" {
// TODO(banks) at least define these defaults somewhere in a const. Would be
// nice not to duplicate the ones in consul/rpc.go too...
wait := queryOpts . MaxQueryTime
if wait == 0 {
wait = 5 * time . Minute
}
if wait > 10 * time . Minute {
wait = 10 * time . Minute
}
// Apply a small amount of jitter to the request.
wait += lib . RandomStagger ( wait / 16 )
2018-04-19 10:15:32 +00:00
timeout = time . NewTimer ( wait )
2018-04-18 20:05:30 +00:00
}
for {
2018-04-26 13:01:20 +00:00
// Must reset this every loop in case the Watch set is already closed but
// hash remains same. In that case we'll need to re-block on ws.Watch()
// again.
ws = memdb . NewWatchSet ( )
2018-04-19 10:15:32 +00:00
curHash , curResp , err := fn ( ws )
2018-04-18 20:05:30 +00:00
if err != nil {
return curResp , err
}
2018-04-19 10:15:32 +00:00
// Return immediately if there is no timeout, the hash is different or the
// Watch returns true (indicating timeout fired). Note that Watch on a nil
// WatchSet immediately returns false which would incorrectly cause this to
// loop and repeat again, however we rely on the invariant that ws == nil
// IFF timeout == nil in which case the Watch call is never invoked.
if timeout == nil || hash != curHash || ws . Watch ( timeout . C ) {
resp . Header ( ) . Set ( "X-Consul-ContentHash" , curHash )
return curResp , err
2018-04-18 20:05:30 +00:00
}
2018-04-19 10:15:32 +00:00
// Watch returned false indicating a change was detected, loop and repeat
2018-09-27 14:00:51 +00:00
// the callback to load the new value. If agent sync is paused it means
// local state is currently being bulk-edited e.g. config reload. In this
// case it's likely that local state just got unloaded and may or may not be
// reloaded yet. Wait a short amount of time for Sync to resume to ride out
// typical config reloads.
if syncPauseCh := s . agent . syncPausedCh ( ) ; syncPauseCh != nil {
select {
case <- syncPauseCh :
case <- timeout . C :
}
}
2018-04-18 20:05:30 +00:00
}
}
2018-03-21 23:02:46 +00:00
// AgentConnectAuthorize
//
// POST /v1/agent/connect/authorize
2018-05-11 05:37:02 +00:00
//
2018-05-19 04:03:10 +00:00
// Note: when this logic changes, consider if the Intention.Check RPC method
2018-05-11 05:37:02 +00:00
// also needs to be updated.
2018-03-21 23:02:46 +00:00
func ( s * HTTPServer ) AgentConnectAuthorize ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
2018-03-26 01:50:05 +00:00
// Fetch the token
var token string
s . parseToken ( req , & token )
2018-03-26 00:52:26 +00:00
// Decode the request from the request body
var authReq structs . ConnectAuthorizeRequest
if err := decodeBody ( req , & authReq , nil ) ; err != nil {
2018-10-03 19:37:53 +00:00
return nil , BadRequestError { fmt . Sprintf ( "Request decode failed: %v" , err ) }
2018-05-09 19:30:43 +00:00
}
2018-03-27 17:09:13 +00:00
2018-10-03 19:37:53 +00:00
authz , reason , cacheMeta , err := s . agent . ConnectAuthorize ( token , & authReq )
2018-04-17 23:26:58 +00:00
if err != nil {
2018-03-26 00:52:26 +00:00
return nil , err
}
2018-10-03 19:37:53 +00:00
setCacheMeta ( resp , cacheMeta )
2018-03-26 01:50:05 +00:00
2018-03-26 00:52:26 +00:00
return & connectAuthorizeResp {
2018-03-26 01:50:05 +00:00
Authorized : authz ,
Reason : reason ,
2018-03-26 00:52:26 +00:00
} , nil
}
2018-03-26 01:02:25 +00:00
// connectAuthorizeResp is the response format/structure for the
// /v1/agent/connect/authorize endpoint.
2018-03-26 00:52:26 +00:00
type connectAuthorizeResp struct {
2018-03-26 01:02:25 +00:00
Authorized bool // True if authorized, false if not
Reason string // Reason for the Authorized value (whether true or false)
2018-03-21 23:02:46 +00:00
}
2018-10-17 20:20:35 +00:00
// AgentHost
//
// GET /v1/agent/host
//
// Retrieves information about resources available and in-use for the
// host the agent is running on such as CPU, memory, and disk usage. Requires
// a operator:read ACL token.
func ( s * HTTPServer ) AgentHost ( resp http . ResponseWriter , req * http . Request ) ( interface { } , error ) {
// Fetch the ACL token, if any, and enforce agent policy.
var token string
s . parseToken ( req , & token )
rule , err := s . agent . resolveToken ( token )
if err != nil {
return nil , err
}
2018-11-13 13:43:53 +00:00
2018-10-17 20:20:35 +00:00
if rule != nil && ! rule . OperatorRead ( ) {
return nil , acl . ErrPermissionDenied
}
return debug . CollectHostInfo ( ) , nil
}