Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ repos:
- id: go-unit-tests

- repo: https://github.com/golangci/golangci-lint
rev: v1.62.2
rev: v2.11.4
hooks:
- id: golangci-lint
args: [--config=.golangci.yml]
5 changes: 5 additions & 0 deletions cmd/admin/handlers/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,11 @@ func (h *HandlersAdmin) ConfPOSTHandler(w http.ResponseWriter, r *http.Request)
adminErrorResponse(w, "invalid CSRF token", http.StatusInternalServerError, nil)
return
}
// Check if configuration is read-only
if h.OsqueryValues.ReadOnly {
adminErrorResponse(w, "configuration is read-only", http.StatusForbidden, nil)
return
}
if c.ConfigurationB64 != "" {
// Base64 decode received configuration
// TODO verify configuration
Expand Down
59 changes: 47 additions & 12 deletions cmd/admin/templates/conf.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@
</div>
<div class="card-body">

<textarea id="options_conf" name="options_conf">{{ .Environment.Options }}</textarea>
{{ if $leftmeta.OsqueryValues.ReadOnly }}
<div class="alert alert-warning py-2 mb-2" role="alert">
<i class="fas fa-lock mr-1"></i> Editing is disabled by server configuration.
</div>
{{ end }}
<textarea id="options_conf" name="options_conf" {{ if $leftmeta.OsqueryValues.ReadOnly }}readonly{{ end }}>{{ .Environment.Options }}</textarea>
<div class="row">
<div class="col-md-12">
<button id="options_json_status_color" class="text-left btn btn-sm btn-square btn-block btn-success disabled">
Expand Down Expand Up @@ -136,7 +141,12 @@
</div>
<div class="card-body">

<textarea id="schedule_conf" name="schedule_conf">{{ .Environment.Schedule }}</textarea>
{{ if $leftmeta.OsqueryValues.ReadOnly }}
<div class="alert alert-warning py-2 mb-2" role="alert">
<i class="fas fa-lock mr-1"></i> Editing is disabled by server configuration.
</div>
{{ end }}
<textarea id="schedule_conf" name="schedule_conf" {{ if $leftmeta.OsqueryValues.ReadOnly }}readonly{{ end }}>{{ .Environment.Schedule }}</textarea>
<div class="row">
<div class="col-md-12">
<button id="schedule_json_status_color" class="text-left btn btn-sm btn-square btn-block btn-success disabled">
Expand Down Expand Up @@ -170,7 +180,12 @@
</div>
<div class="card-body">

<textarea id="packs_conf" name="packs_conf">{{ .Environment.Packs }}</textarea>
{{ if $leftmeta.OsqueryValues.ReadOnly }}
<div class="alert alert-warning py-2 mb-2" role="alert">
<i class="fas fa-lock mr-1"></i> Editing is disabled by server configuration.
</div>
{{ end }}
<textarea id="packs_conf" name="packs_conf" {{ if $leftmeta.OsqueryValues.ReadOnly }}readonly{{ end }}>{{ .Environment.Packs }}</textarea>
<div class="row">
<div class="col-md-12">
<button id="packs_json_status_color" class="text-left btn btn-sm btn-square btn-block btn-success disabled">
Expand Down Expand Up @@ -204,7 +219,12 @@
</div>
<div class="card-body">

<textarea id="atc_conf" name="atc_conf">{{ .Environment.ATC }}</textarea>
{{ if $leftmeta.OsqueryValues.ReadOnly }}
<div class="alert alert-warning py-2 mb-2" role="alert">
<i class="fas fa-lock mr-1"></i> Editing is disabled by server configuration.
</div>
{{ end }}
<textarea id="atc_conf" name="atc_conf" {{ if $leftmeta.OsqueryValues.ReadOnly }}readonly{{ end }}>{{ .Environment.ATC }}</textarea>
<div class="row">
<div class="col-md-12">
<button id="atc_json_status_color" class="text-left btn btn-sm btn-square btn-block btn-success disabled">
Expand Down Expand Up @@ -238,7 +258,12 @@
</div>
<div class="card-body">

<textarea id="decorators_conf" name="decorators_conf">{{ .Environment.Decorators }}</textarea>
{{ if $leftmeta.OsqueryValues.ReadOnly }}
<div class="alert alert-warning py-2 mb-2" role="alert">
<i class="fas fa-lock mr-1"></i> Editing is disabled by server configuration.
</div>
{{ end }}
<textarea id="decorators_conf" name="decorators_conf" {{ if $leftmeta.OsqueryValues.ReadOnly }}readonly{{ end }}>{{ .Environment.Decorators }}</textarea>
<div class="row">
<div class="col-md-12">
<button id="decorators_json_status_color" class="text-left btn btn-sm btn-square btn-block btn-success disabled">
Expand Down Expand Up @@ -272,7 +297,12 @@
</div>
<div class="card-body">

<textarea id="final_conf" name="final_conf">{{ .Environment.Configuration }}</textarea>
{{ if $leftmeta.OsqueryValues.ReadOnly }}
<div class="alert alert-warning py-2 mb-2" role="alert">
<i class="fas fa-lock mr-1"></i> Editing is disabled by server configuration.
</div>
{{ end }}
<textarea id="final_conf" name="final_conf" {{ if $leftmeta.OsqueryValues.ReadOnly }}readonly{{ end }}>{{ .Environment.Configuration }}</textarea>
<div class="row">
<div class="col-md-12">
<button id="conf_json_status_color" class="text-left btn btn-sm btn-square btn-block btn-success disabled">
Expand Down Expand Up @@ -311,14 +341,19 @@ <h4 class="alert-heading"><i class="fas fa-exclamation-triangle"></i> osquery co
<script src="/static/js/configuration.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var isReadOnly = {{ if $leftmeta.OsqueryValues.ReadOnly }}true{{ else }}false{{ end }};
if (isReadOnly) {
$('.main button').prop("disabled", true).addClass("disabled");
}

// Codemirror editor for configuration
// JSON validity check when content is changed
var editorConfiguration = CodeMirror.fromTextArea(document.getElementById("final_conf"), {
mode: 'application/json',
lineNumbers: true,
styleActiveLine: true,
matchBrackets: true,
readOnly: false
readOnly: isReadOnly
});
$('#final_conf').data('CodeMirrorInstance', editorConfiguration);
editorConfiguration.on('change', function(_editor){
Expand Down Expand Up @@ -361,7 +396,7 @@ <h4 class="alert-heading"><i class="fas fa-exclamation-triangle"></i> osquery co
lineNumbers: true,
styleActiveLine: true,
matchBrackets: true,
readOnly: false
readOnly: isReadOnly
});
$('#options_conf').data('CodeMirrorInstance', editorOptions);
editorOptions.on('change', function(_editor){
Expand Down Expand Up @@ -407,7 +442,7 @@ <h4 class="alert-heading"><i class="fas fa-exclamation-triangle"></i> osquery co
lineNumbers: true,
styleActiveLine: true,
matchBrackets: true,
readOnly: false
readOnly: isReadOnly
});
$('#schedule_conf').data('CodeMirrorInstance', editorSchedule);
editorSchedule.on('change', function(_editor){
Expand Down Expand Up @@ -452,7 +487,7 @@ <h4 class="alert-heading"><i class="fas fa-exclamation-triangle"></i> osquery co
lineNumbers: true,
styleActiveLine: true,
matchBrackets: true,
readOnly: false
readOnly: isReadOnly
});
$('#packs_conf').data('CodeMirrorInstance', editorPacks);
editorPacks.on('change', function(_editor){
Expand Down Expand Up @@ -495,7 +530,7 @@ <h4 class="alert-heading"><i class="fas fa-exclamation-triangle"></i> osquery co
lineNumbers: true,
styleActiveLine: true,
matchBrackets: true,
readOnly: false
readOnly: isReadOnly
});
$('#atc_conf').data('CodeMirrorInstance', editorATC);
editorATC.on('change', function(_editor){
Expand Down Expand Up @@ -538,7 +573,7 @@ <h4 class="alert-heading"><i class="fas fa-exclamation-triangle"></i> osquery co
lineNumbers: true,
styleActiveLine: true,
matchBrackets: true,
readOnly: false
readOnly: isReadOnly
});
$('#decorators_conf').data('CodeMirrorInstance', editorDecorators);
editorDecorators.on('change', function(_editor){
Expand Down
9 changes: 9 additions & 0 deletions cmd/tls/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type HandlersTLS struct {
Logs *logging.LoggerTLS
WriteHandler *batchWriter
OsqueryValues *config.YAMLConfigurationOsquery
ConfigEndpoints *config.YAMLConfigurationEndpoints
DebugHTTP *zerolog.Logger
DebugHTTPConfig *config.YAMLConfigurationDebug
}
Expand Down Expand Up @@ -149,6 +150,14 @@ func WithOsqueryValues(values *config.YAMLConfigurationOsquery) Option {
}
}

// WithConfigEndpoints to pass configuration endpoints values
func WithConfigEndpoints(endpoints *config.YAMLConfigurationEndpoints) Option {
return func(h *HandlersTLS) {
h.ConfigEndpoints = endpoints
}
}

// WithDebugHTTP to pass debug HTTP configuration values
func WithDebugHTTP(cfg *config.YAMLConfigurationDebug) Option {
return func(h *HandlersTLS) {
h.DebugHTTPConfig = cfg
Expand Down
124 changes: 124 additions & 0 deletions cmd/tls/handlers/post.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package handlers

import (
"bytes"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -1063,3 +1066,124 @@ func (h *HandlersTLS) EnrollPackageHandler(w http.ResponseWriter, r *http.Reques
return
}
}

// OsqueryConfigEndpointHandler - Function to handle the osquery configuration endpoint
func (h *HandlersTLS) OsqueryConfigEndpointHandler(w http.ResponseWriter, r *http.Request) {
// Retrieve environment variable
envVar := r.PathValue("env")
if envVar == "" {
utils.HTTPResponse(w, "", http.StatusBadRequest, []byte(""))
return
}
// To prevent abuse, check if the received UUID is valid
if !utils.CheckUUID(envVar) {
utils.HTTPResponse(w, "", http.StatusBadRequest, []byte(""))
return
}
// Extract secret
secretVar := r.PathValue("secret")
if secretVar == "" {
utils.HTTPResponse(w, "", http.StatusBadRequest, []byte(""))
return
}
confirmed := false
integrityCheck := false
for _, confEndpoint := range *h.ConfigEndpoints {
if confEndpoint.Environment == envVar && confEndpoint.Secret == secretVar {
confirmed = true
integrityCheck = confEndpoint.IntegrityCheck
break
}
}
if !confirmed {
utils.HTTPResponse(w, "", http.StatusForbidden, []byte(""))
return
}
// If we are here, the secret is confirmed, so we can proceed to get the environment
env, err := h.Envs.GetByUUID(envVar)
if err != nil {
log.Err(err).Msg("error getting environment")
utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte(""))
return
}
// Debug HTTP
if h.DebugHTTPConfig.EnableHTTP {
utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody)
}
// Decode read POST body
var o types.OsqueryConfigRequest
body, err := io.ReadAll(r.Body)
if err != nil {
log.Err(err).Msg("error reading POST body")
utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte(""))
return
}
if err := json.Unmarshal(body, &o); err != nil {
log.Err(err).Msg("error parsing POST body")
utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte(""))
return
}
// Decode base64 configuration
configDecoded, err := base64.StdEncoding.DecodeString(o.Configuration)
if err != nil {
log.Err(err).Msg("error decoding base64 configuration")
utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte(""))
return
}
// Unzip configuration
gzipReader, err := gzip.NewReader(bytes.NewReader(configDecoded))
if err != nil {
log.Err(err).Msg("error decoding gzip configuration")
utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte(""))
return
}
defer gzipReader.Close()
const maxConfigSize = 500 * 1024
limitedReader := io.LimitReader(gzipReader, maxConfigSize+1)
configuration, err := io.ReadAll(limitedReader)
if err != nil {
log.Err(err).Msg("error reading unzipped configuration")
utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte(""))
return
}
if len(configuration) > maxConfigSize {
log.Error().Msg("unzipped configuration is larger than 500KB")
utils.HTTPResponse(w, "", http.StatusRequestEntityTooLarge, []byte(""))
return
}
// Verify integrity of the configuration using the provided hash
if integrityCheck {
hash := sha256.Sum256(configuration)
computedIntegrity := fmt.Sprintf("%x", hash)
if o.Integrity != computedIntegrity {
log.Warn().
Str("expected_integrity", o.Integrity).
Str("computed_integrity", computedIntegrity).
Msg("configuration integrity check failed")
utils.HTTPResponse(w, "", http.StatusBadRequest, []byte(""))
return
}
}
// Parse configuration
cnf, err := h.Envs.GenStructConf(configuration)
if err != nil {
log.Err(err).Msg("error parsing configuration")
utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte(""))
return
}
// Update full configuration
if err := h.Envs.UpdateConfiguration(env.UUID, cnf); err != nil {
log.Err(err).Msg("error saving configuration")
utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte(""))
return
}
// Update all configuration parts
if err := h.Envs.UpdateConfigurationParts(env.UUID, cnf); err != nil {
log.Err(err).Msg("error saving configuration parts")
utils.HTTPResponse(w, "", http.StatusInternalServerError, []byte(""))
return
}
response := TLSResponse{Message: "configuration saved successfully"}
// Send response
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, response)
}
24 changes: 16 additions & 8 deletions cmd/tls/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,15 @@ func loadYAMLConfiguration(file string) (config.TLSConfiguration, error) {
func init() {
// Initialize default flagParams
flagParams = &config.ServiceParameters{
Service: &config.YAMLConfigurationService{},
DB: &config.YAMLConfigurationDB{},
BatchWriter: &config.YAMLConfigurationWriter{},
Redis: &config.YAMLConfigurationRedis{},
Osquery: &config.YAMLConfigurationOsquery{},
Osctrld: &config.YAMLConfigurationOsctrld{},
Metrics: &config.YAMLConfigurationMetrics{},
TLS: &config.YAMLConfigurationTLS{},
Service: &config.YAMLConfigurationService{},
DB: &config.YAMLConfigurationDB{},
BatchWriter: &config.YAMLConfigurationWriter{},
Redis: &config.YAMLConfigurationRedis{},
Osquery: &config.YAMLConfigurationOsquery{},
Osctrld: &config.YAMLConfigurationOsctrld{},
ConfigEndpoints: &config.YAMLConfigurationEndpoints{},
Metrics: &config.YAMLConfigurationMetrics{},
TLS: &config.YAMLConfigurationTLS{},
Logger: &config.YAMLConfigurationLogger{
DB: &config.YAMLConfigurationDB{},
S3: &config.S3Logger{},
Expand Down Expand Up @@ -283,6 +284,7 @@ func osctrlService() {
handlers.WithLogs(loggerTLS),
handlers.WithWriteHandler(tlsWriter),
handlers.WithOsqueryValues(flagParams.Osquery),
handlers.WithConfigEndpoints(flagParams.ConfigEndpoints),
handlers.WithDebugHTTP(flagParams.Debug),
)
// ///////////////////////// ALL CONTENT IS UNAUTHENTICATED FOR TLS
Expand Down Expand Up @@ -330,6 +332,12 @@ func osctrlService() {
muxTLS.HandleFunc("POST /{env}/{action}/{platform}/"+environments.DefaultScriptPath, handlersTLS.ScriptHandler)
}

// Enable configuration endpoints if passed via YAML configuration
if flagParams.ConfigEndpoints != nil && len(*flagParams.ConfigEndpoints) > 0 {
log.Info().Msgf("Enabling %d configuration endpoints", len(*flagParams.ConfigEndpoints))
muxTLS.HandleFunc("POST /{env}/{secret}/"+environments.DefaultConfigEndpointPath, handlersTLS.OsqueryConfigEndpointHandler)
}

// ////////////////////////////// Everything is ready at this point!
serviceListener := flagParams.Service.Listener + ":" + strconv.Itoa(flagParams.Service.Port)
if flagParams.TLS.Termination {
Expand Down
1 change: 1 addition & 0 deletions cmd/tls/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func loadedYAMLToServiceParams(yml config.TLSConfiguration, loadedFile string) *
BatchWriter: &yml.BatchWriter,
Redis: &yml.Redis,
Osquery: &yml.Osquery,
ConfigEndpoints: &yml.ConfigEndpoints,
Osctrld: &yml.Osctrld,
Metrics: &yml.Metrics,
TLS: &yml.TLS,
Expand Down
Loading
Loading