Merge pull request #7656 from hashicorp/feature/audit/oss-merge

agent: stub out auditing functionality in OSS
This commit is contained in:
Kit Patella 2020-04-17 13:33:06 -07:00 committed by GitHub
commit e2467f4b2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 92 additions and 25 deletions

View File

@ -312,6 +312,9 @@ type Agent struct {
// httpConnLimiter is used to limit connections to the HTTP server by client // httpConnLimiter is used to limit connections to the HTTP server by client
// IP. // IP.
httpConnLimiter connlimit.Limiter httpConnLimiter connlimit.Limiter
// enterpriseAgent embeds fields that we only access in consul-enterprise builds
enterpriseAgent
} }
// New verifies the configuration given has a Datacenter and DataDir // New verifies the configuration given has a Datacenter and DataDir
@ -430,7 +433,10 @@ func (a *Agent) Start() error {
// waiting to discover a consul server // waiting to discover a consul server
consulCfg.ServerUp = a.sync.SyncFull.Trigger consulCfg.ServerUp = a.sync.SyncFull.Trigger
a.initEnterprise(consulCfg) err = a.initEnterprise(consulCfg)
if err != nil {
return fmt.Errorf("failed to start Consul enterprise component: %v", err)
}
tlsConfigurator, err := tlsutil.NewConfigurator(c.ToTLSUtilConfig(), a.logger) tlsConfigurator, err := tlsutil.NewConfigurator(c.ToTLSUtilConfig(), a.logger)
if err != nil { if err != nil {
@ -4095,6 +4101,11 @@ func (a *Agent) ReloadConfig(newCfg *config.RuntimeConfig) error {
// concurrent due to both gaining a full lock on the stateLock // concurrent due to both gaining a full lock on the stateLock
a.config.ConfigEntryBootstrap = newCfg.ConfigEntryBootstrap a.config.ConfigEntryBootstrap = newCfg.ConfigEntryBootstrap
err := a.reloadEnterprise(newCfg)
if err != nil {
return err
}
// create the config for the rpc server/client // create the config for the rpc server/client
consulCfg, err := a.consulConfig() consulCfg, err := a.consulConfig()
if err != nil { if err != nil {

View File

@ -9,14 +9,29 @@ import (
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
) )
// fillAgentServiceEnterpriseMeta stub // enterpriseAgent embeds fields that we only access in consul-enterprise builds
type enterpriseAgent struct{}
// fillAgentServiceEnterpriseMeta is a noop stub for the func defined agent_ent.go
func fillAgentServiceEnterpriseMeta(_ *api.AgentService, _ *structs.EnterpriseMeta) {} func fillAgentServiceEnterpriseMeta(_ *api.AgentService, _ *structs.EnterpriseMeta) {}
// fillHealthCheckEnterpriseMeta stub // fillHealthCheckEnterpriseMeta is a noop stub for the func defined agent_ent.go
func fillHealthCheckEnterpriseMeta(_ *api.HealthCheck, _ *structs.EnterpriseMeta) {} func fillHealthCheckEnterpriseMeta(_ *api.HealthCheck, _ *structs.EnterpriseMeta) {}
func (a *Agent) initEnterprise(consulCfg *consul.Config) { // initEnterprise is a noop stub for the func defined agent_ent.go
func (a *Agent) initEnterprise(consulCfg *consul.Config) error {
return nil
} }
// loadEnterpriseTokens is a noop stub for the func defined agent_ent.go
func (a *Agent) loadEnterpriseTokens(conf *config.RuntimeConfig) { func (a *Agent) loadEnterpriseTokens(conf *config.RuntimeConfig) {
} }
// reloadEnterprise is a noop stub for the func defined agent_ent.go
func (a *Agent) reloadEnterprise(conf *config.RuntimeConfig) error {
return nil
}
// WriteEvent is a noop stub for the func defined agent_ent.go
func (a *Agent) WriteEvent(eventType string, payload interface{}) {
}

View File

@ -1004,7 +1004,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
return rt, nil return rt, nil
} }
// Validate performs semantical validation of the runtime configuration. // Validate performs semantic validation of the runtime configuration.
func (b *Builder) Validate(rt RuntimeConfig) error { func (b *Builder) Validate(rt RuntimeConfig) error {
// reDatacenter defines a regexp for a valid datacenter name // reDatacenter defines a regexp for a valid datacenter name
var reDatacenter = regexp.MustCompile("^[a-z0-9_-]+$") var reDatacenter = regexp.MustCompile("^[a-z0-9_-]+$")

View File

@ -316,6 +316,9 @@ type Config struct {
SyncCoordinateRateTarget *float64 `json:"sync_coordinate_rate_target,omitempty" hcl:"sync_coordinate_rate_target" mapstructure:"sync_coordinate_rate_target"` SyncCoordinateRateTarget *float64 `json:"sync_coordinate_rate_target,omitempty" hcl:"sync_coordinate_rate_target" mapstructure:"sync_coordinate_rate_target"`
Version *string `json:"version,omitempty" hcl:"version" mapstructure:"version"` Version *string `json:"version,omitempty" hcl:"version" mapstructure:"version"`
VersionPrerelease *string `json:"version_prerelease,omitempty" hcl:"version_prerelease" mapstructure:"version_prerelease"` VersionPrerelease *string `json:"version_prerelease,omitempty" hcl:"version_prerelease" mapstructure:"version_prerelease"`
// enterpriseConfig embeds fields that we only access in consul-enterprise builds
EnterpriseConfig `hcl:",squash" mapstructure:",squash"`
} }
type GossipLANConfig struct { type GossipLANConfig struct {

View File

@ -4,6 +4,9 @@ package config
import "github.com/hashicorp/consul/agent/structs" import "github.com/hashicorp/consul/agent/structs"
// EnterpriseMeta provides a stub for the corresponding struct in config_ent.go
type EnterpriseConfig struct{}
// EnterpriseMeta stub // EnterpriseMeta stub
type EnterpriseMeta struct{} type EnterpriseMeta struct{}

View File

@ -523,7 +523,7 @@ func (c *Config) CheckACL() error {
return nil return nil
} }
// DefaultConfig returns a sane default configuration. // DefaultConfig returns a default configuration.
func DefaultConfig() *Config { func DefaultConfig() *Config {
hostname, err := os.Hostname() hostname, err := os.Hostname()
if err != nil { if err != nil {

View File

@ -399,6 +399,10 @@ var (
func (s *HTTPServer) wrap(handler endpoint, methods []string) http.HandlerFunc { func (s *HTTPServer) wrap(handler endpoint, methods []string) http.HandlerFunc {
httpLogger := s.agent.logger.Named(logging.HTTP) httpLogger := s.agent.logger.Named(logging.HTTP)
return func(resp http.ResponseWriter, req *http.Request) { return func(resp http.ResponseWriter, req *http.Request) {
// Audit log the request
reqPayload := s.auditReq(req)
setHeaders(resp, s.agent.config.HTTPResponseHeaders) setHeaders(resp, s.agent.config.HTTPResponseHeaders)
setTranslateAddr(resp, s.agent.config.TranslateWANAddrs) setTranslateAddr(resp, s.agent.config.TranslateWANAddrs)
@ -476,33 +480,44 @@ func (s *HTTPServer) wrap(handler endpoint, methods []string) http.HandlerFunc {
"from", req.RemoteAddr, "from", req.RemoteAddr,
"error", err, "error", err,
) )
var httpCode int
switch { switch {
case isForbidden(err): case isForbidden(err):
resp.WriteHeader(http.StatusForbidden) httpCode = http.StatusForbidden
resp.WriteHeader(httpCode)
fmt.Fprint(resp, err.Error()) fmt.Fprint(resp, err.Error())
case structs.IsErrRPCRateExceeded(err): case structs.IsErrRPCRateExceeded(err):
resp.WriteHeader(http.StatusTooManyRequests) httpCode = http.StatusTooManyRequests
resp.WriteHeader(httpCode)
case isMethodNotAllowed(err): case isMethodNotAllowed(err):
// RFC2616 states that for 405 Method Not Allowed the response // RFC2616 states that for 405 Method Not Allowed the response
// MUST include an Allow header containing the list of valid // MUST include an Allow header containing the list of valid
// methods for the requested resource. // methods for the requested resource.
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
addAllowHeader(err.(MethodNotAllowedError).Allow) addAllowHeader(err.(MethodNotAllowedError).Allow)
resp.WriteHeader(http.StatusMethodNotAllowed) // 405 httpCode = http.StatusMethodNotAllowed
resp.WriteHeader(httpCode) // 405
fmt.Fprint(resp, err.Error()) fmt.Fprint(resp, err.Error())
case isBadRequest(err): case isBadRequest(err):
resp.WriteHeader(http.StatusBadRequest) httpCode = http.StatusBadRequest
resp.WriteHeader(httpCode)
fmt.Fprint(resp, err.Error()) fmt.Fprint(resp, err.Error())
case isNotFound(err): case isNotFound(err):
resp.WriteHeader(http.StatusNotFound) httpCode = http.StatusNotFound
resp.WriteHeader(httpCode)
fmt.Fprintf(resp, err.Error()) fmt.Fprintf(resp, err.Error())
case isTooManyRequests(err): case isTooManyRequests(err):
resp.WriteHeader(http.StatusTooManyRequests) httpCode = http.StatusTooManyRequests
resp.WriteHeader(httpCode)
fmt.Fprint(resp, err.Error()) fmt.Fprint(resp, err.Error())
default: default:
resp.WriteHeader(http.StatusInternalServerError) httpCode = http.StatusInternalServerError
resp.WriteHeader(httpCode)
fmt.Fprint(resp, err.Error()) fmt.Fprint(resp, err.Error())
} }
// Audit log the error response
s.auditResp(reqPayload, httpCode)
} }
start := time.Now() start := time.Now()
@ -577,6 +592,10 @@ func (s *HTTPServer) wrap(handler endpoint, methods []string) http.HandlerFunc {
} }
resp.Header().Set("Content-Type", contentType) resp.Header().Set("Content-Type", contentType)
resp.WriteHeader(httpCode) resp.WriteHeader(httpCode)
// Audit log the success response
s.auditResp(reqPayload, httpCode)
resp.Write(buf) resp.Write(buf)
} }
} }
@ -925,10 +944,7 @@ func (s *HTTPServer) parseDC(req *http.Request, dc *string) {
} }
// parseTokenInternal is used to parse the ?token query param or the X-Consul-Token header or // parseTokenInternal is used to parse the ?token query param or the X-Consul-Token header or
// Authorization Bearer token (RFC6750) and // Authorization Bearer token (RFC6750).
// optionally resolve proxy tokens to real ACL tokens. If the token is invalid or not specified it will populate
// the token with the agents UserToken (acl_token in the consul configuration)
// Parsing has the following priority: ?token, X-Consul-Token and last "Authorization: Bearer "
func (s *HTTPServer) parseTokenInternal(req *http.Request, token *string) { func (s *HTTPServer) parseTokenInternal(req *http.Request, token *string) {
tok := "" tok := ""
if other := req.URL.Query().Get("token"); other != "" { if other := req.URL.Query().Get("token"); other != "" {
@ -949,25 +965,33 @@ func (s *HTTPServer) parseTokenInternal(req *http.Request, token *string) {
// <Scheme> must be "Bearer" // <Scheme> must be "Bearer"
if strings.ToLower(scheme) == "bearer" { if strings.ToLower(scheme) == "bearer" {
// Since Bearer tokens shouldnt contain spaces (rfc6750#section-2.1) // Since Bearer tokens shouldn't contain spaces (rfc6750#section-2.1)
// "value" is tokenized, only the first item is used // "value" is tokenized, only the first item is used
tok = strings.TrimSpace(strings.Split(value, " ")[0]) tok = strings.TrimSpace(strings.Split(value, " ")[0])
} }
} }
} }
if tok != "" {
*token = tok *token = tok
return return
} }
// parseTokenResolveProxy passes through to parseTokenInternal and optionally resolves proxy tokens to real ACL tokens.
// If the token is invalid or not specified it will populate the token with the agents UserToken (acl_token in the
// consul configuration)
func (s *HTTPServer) parseTokenResolveProxy(req *http.Request, token *string) {
s.parseTokenInternal(req, token) // parseTokenInternal modifies *token
if token != nil && *token == "" {
*token = s.agent.tokens.UserToken() *token = s.agent.tokens.UserToken()
return
}
return
} }
// parseToken is used to parse the ?token query param or the X-Consul-Token header or // parseToken is used to parse the ?token query param or the X-Consul-Token header or
// Authorization Bearer token header (RFC6750) // Authorization Bearer token header (RFC6750). This function is used widely in Consul's endpoints
func (s *HTTPServer) parseToken(req *http.Request, token *string) { func (s *HTTPServer) parseToken(req *http.Request, token *string) {
s.parseTokenInternal(req, token) s.parseTokenResolveProxy(req, token)
} }
func sourceAddrFromRequest(req *http.Request) string { func sourceAddrFromRequest(req *http.Request) string {
@ -1027,7 +1051,7 @@ func (s *HTTPServer) parseMetaFilter(req *http.Request) map[string]string {
func (s *HTTPServer) parseInternal(resp http.ResponseWriter, req *http.Request, dc *string, b structs.QueryOptionsCompat) bool { func (s *HTTPServer) parseInternal(resp http.ResponseWriter, req *http.Request, dc *string, b structs.QueryOptionsCompat) bool {
s.parseDC(req, dc) s.parseDC(req, dc)
var token string var token string
s.parseTokenInternal(req, &token) s.parseTokenResolveProxy(req, &token)
b.SetToken(token) b.SetToken(token)
var filter string var filter string
s.parseFilter(req, &filter) s.parseFilter(req, &filter)

View File

@ -52,3 +52,14 @@ func parseACLAuthMethodEnterpriseMeta(req *http.Request, _ *structs.ACLAuthMetho
return nil return nil
} }
// auditReq is a noop stub for the corresponding func in http_ent.go
func (s *HTTPServer) auditReq(req *http.Request) interface{} {
// note(kit): We return an nil here so we can pass it to auditResp. Auditing the response requires the
// request object for context, so we have it pass it even when it's disabled
return nil
}
// auditResp is a noop stub for the corresponding func in http_ent.go
func (s *HTTPServer) auditResp(reqPayload interface{}, httpCode int) {
}