From de74c2531e6622482f2451c266ce6a87841056df Mon Sep 17 00:00:00 2001 From: postmannen Date: Sat, 28 Dec 2024 04:58:26 +0100 Subject: [PATCH] removed debug printing fixed typo in readme added useDetectedShell to message, so it can be decided on the message level to use it or not for cliCommand's added so if shell are defined via flag that will be used, and not the default shell of the OS. For the Linux and Darwin operating system we allow to automatically detect shell interpreter, so the user don't have to type "bash", "-c" as the first two arguments of the methodArgs. replaced tail package, and added nil check when reading lines renaming of key related functions, updated doc updated key comments, and added doc section for central node updated doc and error messages --- TODO.md | 4 ++ central_auth_key_handling.go | 22 ++++----- configuration_flags.go | 24 ++++++++++ doc/src/SUMMARY.md | 17 ++++--- doc/src/core_acl.md | 1 + doc/src/core_audit_log.md | 1 + doc/src/core_central.md | 3 ++ doc/src/core_hello_messages.md | 11 +++++ doc/src/core_signing_keys.md | 1 + doc/src/core_startup_folder.md | 45 ++++++++++++++++-- doc/src/example_standard_reqclicommand.md | 26 +++++++++- go.mod | 3 +- go.sum | 26 ++-------- message_and_subject.go | 2 + processes.go | 2 +- requests.go | 2 +- requests_cli.go | 58 +++++++++++++++++------ requests_file_handling.go | 5 +- requests_keys.go | 19 ++------ requests_operator.go | 4 +- requests_std.go | 7 +-- 21 files changed, 199 insertions(+), 84 deletions(-) create mode 100644 doc/src/core_acl.md create mode 100644 doc/src/core_audit_log.md create mode 100644 doc/src/core_central.md create mode 100644 doc/src/core_hello_messages.md create mode 100644 doc/src/core_signing_keys.md diff --git a/TODO.md b/TODO.md index 07111f8..cb95de2 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,7 @@ # TODO ## Key and ACL updates to use jetstream + +## tailfile + +Replace the hpcloud/tail with diff --git a/central_auth_key_handling.go b/central_auth_key_handling.go index dd33808..44405d8 100644 --- a/central_auth_key_handling.go +++ b/central_auth_key_handling.go @@ -127,25 +127,25 @@ func (c *centralAuth) addPublicKey(proc process, msg Message) { c.pki.nodesAcked.mu.Unlock() if ok && bytes.Equal(existingKey, msg.Data) { - er := fmt.Errorf("info: public key value for REGISTERED node %v is the same, doing nothing", msg.FromNode) + er := fmt.Errorf("info: public key value for registered node %v is the same, doing nothing", msg.FromNode) proc.errorKernel.logDebug(er) return } c.pki.nodeNotAckedPublicKeys.mu.Lock() - existingNotAckedKey, ok := c.pki.nodeNotAckedPublicKeys.KeyMap[msg.FromNode] - // We only want to send one notification to the error kernel about new key detection, - // so we check if the values are the same as the one we already got before we continue - // with registering and logging for the the new key. - if ok && bytes.Equal(existingNotAckedKey, msg.Data) { - c.pki.nodeNotAckedPublicKeys.mu.Unlock() - return - } + // existingNotAckedKey, ok := c.pki.nodeNotAckedPublicKeys.KeyMap[msg.FromNode] + // // We only want to send one notification to the error kernel about new key detection, + // // so we check if the values are the same as the one we already got before we continue + // // with registering and logging for the the new key. + // if ok && bytes.Equal(existingNotAckedKey, msg.Data) { + // c.pki.nodeNotAckedPublicKeys.mu.Unlock() + // return + // } c.pki.nodeNotAckedPublicKeys.KeyMap[msg.FromNode] = msg.Data c.pki.nodeNotAckedPublicKeys.mu.Unlock() - er := fmt.Errorf("info: detected new public key for node: %v. This key will need to be authorized by operator to be allowed into the system", msg.FromNode) + er := fmt.Errorf("info: new public key for node: %v. Key needs to be authorized by operator to be allowed into the system by using the keysAllow method", msg.FromNode) c.pki.errorKernel.infoSend(proc, msg, er) c.pki.errorKernel.logDebug(er) } @@ -294,7 +294,7 @@ func (c *centralAuth) updateHash(proc process, message Message) { b, err := cbor.Marshal(sortedNodesAndKeys) if err != nil { - er := fmt.Errorf("error: methodKeysAllow, failed to marshal slice, and will not update hash for public keys: %v", err) + er := fmt.Errorf("error: updateHash, failed to marshal slice, and will not update hash for public keys: %v", err) c.pki.errorKernel.errSend(proc, message, er, logError) return diff --git a/configuration_flags.go b/configuration_flags.go index ef0e27e..adea6a0 100644 --- a/configuration_flags.go +++ b/configuration_flags.go @@ -2,9 +2,12 @@ package ctrl import ( "flag" + "fmt" "log" "os" + "os/exec" "strconv" + "strings" "github.com/joho/godotenv" ) @@ -16,6 +19,8 @@ import ( // an if check should be added to the checkConfigValues function // to set default values when reading from config file. type Configuration struct { + // Shell on the operating system to use when executing cliCommands + ShellOnNode string // ConfigFolder, the location for the configuration folder on disk ConfigFolder string `comment:"ConfigFolder, the location for the configuration folder on disk"` // The folder where the socket file should live @@ -152,6 +157,7 @@ func NewConfiguration() *Configuration { } //flag.StringVar(&c.ConfigFolder, "configFolder", fc.ConfigFolder, "Defaults to ./usr/local/ctrl/etc/. *NB* This flag is not used, if your config file are located somwhere else than default set the location in an env variable named CONFIGFOLDER") + flag.StringVar(&c.ShellOnNode, "shellOnNode", CheckEnv("SHELL_ON_NODE", c.ShellOnNode).(string), "set a value to override the default shell used as interpreter for running cliCommand's on node.") flag.StringVar(&c.SocketFolder, "socketFolder", CheckEnv("SOCKET_FOLDER", c.SocketFolder).(string), "folder who contains the socket file. Defaults to ./tmp/. If other folder is used this flag must be specified at startup.") flag.StringVar(&c.ReadFolder, "readFolder", CheckEnv("READ_FOLDER", c.ReadFolder).(string), "folder who contains the readfolder. Defaults to ./readfolder/. If other folder is used this flag must be specified at startup.") flag.StringVar(&c.TCPListener, "tcpListener", CheckEnv("TCP_LISTENER", c.TCPListener).(string), "start up a TCP listener in addition to the Unix Socket, to give messages to the system. e.g. localhost:8888. No value means not to start the listener, which is default. NB: You probably don't want to start this on any other interface than localhost") @@ -220,14 +226,32 @@ func NewConfiguration() *Configuration { log.Fatalf("error: the centralNodeName config option or flag cannot be empty, check -help\n") } + if c.ShellOnNode == "" { + c.ShellOnNode = getShell() + } + fmt.Printf("\n******** DETECTED SHELL: %v\n\n", c.ShellOnNode) + flag.Parse() return &c } +func getShell() string { + out, err := exec.Command("echo", os.ExpandEnv("$SHELL")).Output() + if err != nil { + log.Fatalf("error: unable to detect shell: %v\n", err) + } + + shell := string(out) + shell = strings.TrimSuffix(shell, "\n") + + return string(shell) +} + // Get a Configuration struct with the default values set. func newConfigurationDefaults() Configuration { c := Configuration{ + ShellOnNode: "", ConfigFolder: "./etc/", SocketFolder: "./tmp", ReadFolder: "./readfolder", diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 83fb5c6..257e3fa 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -12,14 +12,19 @@ # Core ctrl - [Messaging](./core_messaging_overview.md) -- [Message fields](./core_messaging_message_fields.md) -- [Message jetstream/broadcast](./core_messaging_jetstream.md) -- [Request Methods](./core_request_methods.md) + - [Message fields](./core_messaging_message_fields.md) + - [Message jetstream/broadcast](./core_messaging_jetstream.md) + - [Request Methods](./core_request_methods.md) + - [{{CTRL_DATA}} variable](./core_messaging_CTRL_DATA.md) + - [{{CTRL_FILE}} variable](./core_messaging_CTRL_FILE.md) - [Nats timeouts](./core_nats_timeouts.md) - [Startup folder](./core_startup_folder.md) -- [{{CTRL_DATA}} variable](./core_messaging_CTRL_DATA.md) -- [{{CTRL_FILE}} variable](./core_messaging_CTRL_FILE.md) - [Errors](./core_errors.md) +- [central](./core_central.md) + - [hello messages](./core_hello_messages.md) + - [signing keys](./core_signing_keys.md) + - [ACL](./core_acl.md) + - [audit log](./core_audit_log.md) # Examples standard messages @@ -32,4 +37,4 @@ # Using ctrl -- [ctrl as github action runner](usecase-ctrl-as-github-action-runner) \ No newline at end of file +- [ctrl as github action runner](usecase-ctrl-as-github-action-runner) diff --git a/doc/src/core_acl.md b/doc/src/core_acl.md new file mode 100644 index 0000000..8cd8bbc --- /dev/null +++ b/doc/src/core_acl.md @@ -0,0 +1 @@ +# ACL diff --git a/doc/src/core_audit_log.md b/doc/src/core_audit_log.md new file mode 100644 index 0000000..0698f0f --- /dev/null +++ b/doc/src/core_audit_log.md @@ -0,0 +1 @@ +# audit log diff --git a/doc/src/core_central.md b/doc/src/core_central.md new file mode 100644 index 0000000..fec3bb7 --- /dev/null +++ b/doc/src/core_central.md @@ -0,0 +1,3 @@ +# central + +To get functionality like central audit log, signing keys, authorization with ACL's and hello messages one node should be started with the node name **central** diff --git a/doc/src/core_hello_messages.md b/doc/src/core_hello_messages.md new file mode 100644 index 0000000..bc6c30e --- /dev/null +++ b/doc/src/core_hello_messages.md @@ -0,0 +1,11 @@ +# Hello messages + +All nodes can send hello messages to inform that they are up. The interval between sending a hello message can be set with the `START_PUB_HELLO` environment variable. + +Hello messages are sent to the node with the name **central**. When a hello message are received on central, information with the time and node name will be stored in the **ctrl data folder** + +## Public keys + +ctrl nodes can use ed25519 keys for signing messages, so each ctrl instance will generate a public and private key pair on startup. The public keys are sent to the central server with the hello messages. + +To read more about signing keys here: [signing keys](./core_signing_keys.md) diff --git a/doc/src/core_signing_keys.md b/doc/src/core_signing_keys.md new file mode 100644 index 0000000..f8bed16 --- /dev/null +++ b/doc/src/core_signing_keys.md @@ -0,0 +1 @@ +# signing keys diff --git a/doc/src/core_startup_folder.md b/doc/src/core_startup_folder.md index 376ce6c..102baea 100644 --- a/doc/src/core_startup_folder.md +++ b/doc/src/core_startup_folder.md @@ -8,9 +8,48 @@ Messages put in the startup folder will not be sent to the broker but handled lo This can be really handy when you have some 1-off job you want to done at startup, like some cleaning up. -Another example could be that you have some local prometheus metrics you want to scrape every 5 minutes, and you want to send them to some central metrics system. +## Example reqest metrics from nodes -#### How to send the reply to another node +Another example could be that you have some local prometheus metrics you want to scrape every 5 minutes, and you want to send them to some central metrics system. + +```yaml +--- +- toNodes: + - ["node1","node2"] + method: httpGet + methodArgs: + - "http://localhost:8080/metrics" + replyMethod: file + methodTimeout: 5 + directory: metrics + fileName: metrics.html + schedule : [120,999999999] +``` + +The example above will send out a request to node1 and node2 every 120 second to scrape the metrics and write the results that came back to a folder named **data/metrics** in the current running directory. + +## Example read metrics locally first, and then send to remote node + +But we can also make the nodes publish their metrics instead of requesting it by putting a message in each nodes startup folder, set the **toNode** field to local, and instead use the **fromNode** field to decide where to deliver the result. + +```yaml +--- +- toNodes: + - ["local"] + fromNode: my-metrics-node + method: httpGet + methodArgs: + - "http://localhost:8080/metrics" + replyMethod: file + methodTimeout: 5 + directory: metrics + fileName: metrics.html + schedule : [120,999999999] +``` + +In the above example, the httpGet will be run on the local node, and the result will be sent to my-metrics-node. + +### How to send the reply to another node further explained Normally the **fromNode** field is automatically filled in with the node name of the node where a message originated. Since messages within the startup folder is not received from another node via the normal message path we need set the **fromNode** field in the message to where we want the reply (result) delivered. @@ -38,4 +77,4 @@ Since messages used in startup folder are ment to be delivered locally we can si ``` -This example message will be read at startup, and executed on the local node where it was read, the method will be executed, and the result of the method will be sent to **central**. +This example message will be read at startup, and executed on the local node where it was read, the method will be executed, and the result of the method will be sent to **central**. This is basically the same as the previous example, but we're using cliCommand method with curl instead of the httpGet method. diff --git a/doc/src/example_standard_reqclicommand.md b/doc/src/example_standard_reqclicommand.md index 0cb1fda..71b6f5e 100644 --- a/doc/src/example_standard_reqclicommand.md +++ b/doc/src/example_standard_reqclicommand.md @@ -1,6 +1,28 @@ # cliCommand -In JSON. +With **cliCommand** you specify the command to run in **methodArgs** prefixed with the interpreter to use, for example with bash **bash** `"bash","-c","tree"`. + +On Linux and Darwin, the shell interpreter can also be auto detected by setting the value of **useDetectedShell** in the message to **true**. If set to true the methodArgs only need a single string value with command to run. Example below. + +```yaml +--- +- toNodes: + - node2 + useDetectedShell: true + method: cliCommand + methodArgs: + - | + rm -rf ./data & systemctl restart ctrl + + replyMethod: fileAppend + ACKTimeout: 30 + retries: 1 + ACKTimeout: 30 + directory: system + fileName: system.log +``` + +## Example in JSON ```json [ @@ -18,6 +40,8 @@ In JSON. ] ``` +## Example in YAML + In YAML. ```yaml diff --git a/go.mod b/go.mod index 0ed565c..66cf4d0 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/fxamacker/cbor/v2 v2.5.0 github.com/go-playground/validator/v10 v10.10.1 github.com/google/uuid v1.3.0 - github.com/hpcloud/tail v1.0.0 github.com/jinzhu/copier v0.4.0 github.com/joho/godotenv v1.5.1 github.com/klauspost/compress v1.17.2 @@ -16,6 +15,7 @@ require ( github.com/nats-io/nkeys v0.4.7 github.com/pkg/profile v1.7.0 github.com/prometheus/client_golang v1.14.0 + github.com/tenebris-tech/tail v1.0.5 go.etcd.io/bbolt v1.3.7 golang.org/x/crypto v0.18.0 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 @@ -43,6 +43,5 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect google.golang.org/protobuf v1.30.0 // indirect - gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect ) diff --git a/go.sum b/go.sum index e20d6aa..96f54eb 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= @@ -38,15 +36,11 @@ github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBx github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= -github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -67,13 +61,9 @@ github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a h1:lem6QCvxR0Y28g github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= github.com/nats-io/nats-server/v2 v2.8.4 h1:0jQzze1T9mECg8YZEl8+WYUXb9JKluJfCBriPUtluB4= github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4= -github.com/nats-io/nats.go v1.25.0 h1:t5/wCPGciR7X3Mu8QOi4jiJaXaWM8qtkLu4lzGZvYHE= -github.com/nats-io/nats.go v1.25.0/go.mod h1:D2WALIhz7V8M0pH8Scx8JZXlg6Oqz5VG+nQkK8nJdvg= github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= -github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA= -github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= @@ -102,14 +92,14 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tenebris-tech/tail v1.0.5 h1:gKDA1qEP+kxG/SqaFzJWC/5jLHlG1y4hgibSPHdicrY= +github.com/tenebris-tech/tail v1.0.5/go.mod h1:RpxaZO+UNwbKgXA6VzHdR5FTLTM0pk9zZU/mmHxUeZQ= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= @@ -123,22 +113,14 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= @@ -154,8 +136,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/message_and_subject.go b/message_and_subject.go index 9949a2b..6174e09 100644 --- a/message_and_subject.go +++ b/message_and_subject.go @@ -87,6 +87,8 @@ type Message struct { PreviousMessage *Message // Schedule Schedule []int `json:"schedule" yaml:"schedule"` + // Use auto detection of shell for cliCommands + UseDetectedShell bool `json:"useDetectedShell" yaml:"useDetectedShell"` } // --- Subject diff --git a/processes.go b/processes.go index 1182b08..c7fedbf 100644 --- a/processes.go +++ b/processes.go @@ -112,7 +112,7 @@ func (p *processes) Start(proc process) { if proc.configuration.StartProcesses.StartSubHello { - proc.startup.startProcess(proc, Hello, procFuncHello) + proc.startup.startProcess(proc, Hello, procFuncHelloSubscriber) } if proc.configuration.StartProcesses.IsCentralErrorLogger { diff --git a/requests.go b/requests.go index e4b8d24..13d4b5b 100644 --- a/requests.go +++ b/requests.go @@ -198,7 +198,7 @@ func (m Method) GetMethodsAvailable() MethodsAvailable { TailFile: HandlerFunc(methodTailFile), PublicKey: HandlerFunc(methodPublicKey), KeysRequestUpdate: HandlerFunc(methodKeysRequestUpdate), - KeysDeliverUpdate: HandlerFunc(methodKeysDeliverUpdate), + KeysDeliverUpdate: HandlerFunc(methodKeysReceiveUpdate), KeysAllow: HandlerFunc(methodKeysAllow), KeysDelete: HandlerFunc(methodKeysDelete), diff --git a/requests_cli.go b/requests_cli.go index f8cb03c..36f10b3 100644 --- a/requests_cli.go +++ b/requests_cli.go @@ -3,8 +3,10 @@ package ctrl import ( "bufio" "bytes" + "context" "fmt" "os/exec" + "runtime" "strings" "time" ) @@ -27,8 +29,6 @@ func methodCliCommand(proc process, message Message, node string) ([]byte, error go func() { defer proc.processes.wg.Done() - var a []string - switch { case len(message.MethodArgs) < 1: er := fmt.Errorf("error: methodCliCommand: got <1 number methodArgs") @@ -36,12 +36,8 @@ func methodCliCommand(proc process, message Message, node string) ([]byte, error newReplyMessage(proc, msgForErrors, []byte(er.Error())) return - case len(message.MethodArgs) >= 0: - a = message.MethodArgs[1:] } - c := message.MethodArgs[0] - // Get a context with the timeout specified in message.MethodTimeout. ctx, cancel := getContextForMethodTimeout(proc.ctx, message) @@ -70,7 +66,7 @@ func methodCliCommand(proc process, message Message, node string) ([]byte, error } } - cmd := exec.CommandContext(ctx, c, a...) + cmd := getCmdAndArgs(ctx, proc, message) // Check for the use of env variable for CTRL_DATA, and set env if found. if foundEnvData { @@ -87,6 +83,7 @@ func methodCliCommand(proc process, message Message, node string) ([]byte, error err := cmd.Run() if err != nil { er := fmt.Errorf("error: methodCliCommand: cmd.Run failed : %v, methodArgs: %v, error_output: %v", err, message.MethodArgs, stderr.String()) + proc.errorKernel.errSend(proc, message, er, logWarning) newReplyMessage(proc, msgForErrors, []byte(er.Error())) } @@ -149,8 +146,6 @@ func methodCliCommandCont(proc process, message Message, node string) ([]byte, e // fmt.Printf(" * DONE *\n") }() - var a []string - switch { case len(message.MethodArgs) < 1: er := fmt.Errorf("error: methodCliCommand: got <1 number methodArgs") @@ -158,12 +153,8 @@ func methodCliCommandCont(proc process, message Message, node string) ([]byte, e newReplyMessage(proc, msgForErrors, []byte(er.Error())) return - case len(message.MethodArgs) >= 0: - a = message.MethodArgs[1:] } - c := message.MethodArgs[0] - // Get a context with the timeout specified in message.MethodTimeout. ctx, cancel := getContextForMethodTimeout(proc.ctx, message) // deadline, _ := ctx.Deadline() @@ -176,7 +167,7 @@ func methodCliCommandCont(proc process, message Message, node string) ([]byte, e go func() { defer proc.processes.wg.Done() - cmd := exec.CommandContext(ctx, c, a...) + cmd := getCmdAndArgs(ctx, proc, message) // Using cmd.StdoutPipe here so we are continuosly // able to read the out put of the command. @@ -254,3 +245,42 @@ func methodCliCommandCont(proc process, message Message, node string) ([]byte, e ackMsg := []byte("confirmed from: " + node + ": " + fmt.Sprint(message.ID)) return ackMsg, nil } + +// getCmdAndArgs will return a *exec.Cmd. +func getCmdAndArgs(ctx context.Context, proc process, message Message) *exec.Cmd { + var cmd *exec.Cmd + + // UseDetectedShell defaults to false , or it was not set in message. + // Return a cmd based only on what was defined in the message. + if !message.UseDetectedShell { + cmd = exec.CommandContext(ctx, message.MethodArgs[0], message.MethodArgs[1:]...) + return cmd + } + + // UseDetectedShell have been set to true in message. + // + // Check if a shell have been detected on the node. + if proc.configuration.ShellOnNode != "" { + switch runtime.GOOS { + // If we have a supported os, return the *exec.Cmd based on the + // detected shell on the node, and the args defined in the message. + case "linux", "darwin": + args := []string{"-c"} + args = append(args, message.MethodArgs...) + + cmd = exec.CommandContext(ctx, proc.configuration.ShellOnNode, args...) + return cmd + + // Not supported OS, use cmd and args defined in message only. + default: + cmd = exec.CommandContext(ctx, message.MethodArgs[0], message.MethodArgs[1:]...) + return cmd + } + } + + //args := []string{"-c"} + //args = append(args, message.MethodArgs...) + //cmd = exec.CommandContext(ctx, proc.configuration.ShellOnNode, args...) + + return cmd +} diff --git a/requests_file_handling.go b/requests_file_handling.go index f4d4464..cd486ec 100644 --- a/requests_file_handling.go +++ b/requests_file_handling.go @@ -8,7 +8,7 @@ import ( "os" "path/filepath" - "github.com/hpcloud/tail" + "github.com/tenebris-tech/tail" ) func reqWriteFileOrSocket(isAppend bool, proc process, message Message) error { @@ -159,6 +159,9 @@ func methodTailFile(proc process, message Message, node string) ([]byte, error) for { select { case line := <-t.Lines: + if line == nil { + return + } outCh <- []byte(line.Text + "\n") case <-ctx.Done(): return diff --git a/requests_keys.go b/requests_keys.go index d4a7f68..77e3711 100644 --- a/requests_keys.go +++ b/requests_keys.go @@ -35,9 +35,6 @@ func methodPublicKey(proc process, message Message, node string) ([]byte, error) // case proc.toRingbufferCh <- []subjectAndMessage{sam}: case <-ctx.Done(): case out := <-outCh: - - // Prepare and queue for sending a new message with the output - // of the action executed. newReplyMessage(proc, message, out) } }() @@ -166,7 +163,7 @@ func procFuncKeysRequestUpdate(ctx context.Context, proc process, procFuncCh cha // ---- // Handler to receive the public keys from a central server. -func methodKeysDeliverUpdate(proc process, message Message, node string) ([]byte, error) { +func methodKeysReceiveUpdate(proc process, message Message, node string) ([]byte, error) { // Get a context with the timeout specified in message.MethodTimeout. ctx, _ := getContextForMethodTimeout(proc.ctx, message) @@ -198,11 +195,11 @@ func methodKeysDeliverUpdate(proc process, message Message, node string) ([]byte err := json.Unmarshal(message.Data, &keysAndHash) if err != nil { - er := fmt.Errorf("error: REQKeysDeliverUpdate : json unmarshal failed: %v, message: %v", err, message) + er := fmt.Errorf("error: methodKeysReceiveUpdate : json unmarshal failed: %v, message: %v", err, message) proc.errorKernel.errSend(proc, message, er, logWarning) } - er := fmt.Errorf("<---- REQKeysDeliverUpdate: after unmarshal, nodeAuth keysAndhash contains: %+v", keysAndHash) + er := fmt.Errorf("<---- methodKeysReceiveUpdate: after unmarshal, nodeAuth keysAndhash contains: %+v", keysAndHash) proc.errorKernel.logDebug(er) // If the received map was empty we also want to delete all the locally stored keys, @@ -216,7 +213,7 @@ func methodKeysDeliverUpdate(proc process, message Message, node string) ([]byte proc.nodeAuth.publicKeys.mu.Unlock() if err != nil { - er := fmt.Errorf("error: REQKeysDeliverUpdate : json unmarshal failed: %v, message: %v", err, message) + er := fmt.Errorf("error: methodKeysReceiveUpdate : json unmarshal failed: %v, message: %v", err, message) proc.errorKernel.errSend(proc, message, er, logWarning) } @@ -225,18 +222,12 @@ func methodKeysDeliverUpdate(proc process, message Message, node string) ([]byte err = proc.nodeAuth.publicKeys.saveToFile() if err != nil { - er := fmt.Errorf("error: REQKeysDeliverUpdate : save to file failed: %v, message: %v", err, message) + er := fmt.Errorf("error: methodKeysReceiveUpdate : save to file failed: %v, message: %v", err, message) proc.errorKernel.errSend(proc, message, er, logWarning) } - - // Prepare and queue for sending a new message with the output - // of the action executed. - // newReplyMessage(proc, message, out) } }() - // Send back an ACK message. - // ackMsg := []byte("confirmed from: " + node + ": " + fmt.Sprint(message.ID)) return nil, nil } diff --git a/requests_operator.go b/requests_operator.go index bd4889d..763e1b0 100644 --- a/requests_operator.go +++ b/requests_operator.go @@ -61,7 +61,7 @@ func methodOpProcessStart(proc process, message Message, node string) ([]byte, e method := Method(m) tmpH := mt.getHandler(Method(method)) if tmpH == nil { - er := fmt.Errorf("error: OpProcessStart: no such request type defined: %v" + m) + er := fmt.Errorf("error: OpProcessStart: no such request type defined: %v", m) proc.errorKernel.errSend(proc, message, er, logWarning) return } @@ -119,7 +119,7 @@ func methodOpProcessStop(proc process, message Message, node string) ([]byte, er method := Method(methodString) tmpH := mt.getHandler(Method(method)) if tmpH == nil { - er := fmt.Errorf("error: OpProcessStop: no such request type defined: %v, check that the methodArgs are correct: " + methodString) + er := fmt.Errorf("error: OpProcessStop: no such request type defined: %v, check that the methodArgs are correct: ", methodString) proc.errorKernel.errSend(proc, message, er, logWarning) return } diff --git a/requests_std.go b/requests_std.go index 2b49e69..92e80d6 100644 --- a/requests_std.go +++ b/requests_std.go @@ -49,10 +49,7 @@ func methodHello(proc process, message Message, node string) ([]byte, error) { proc.errorKernel.errSend(proc, message, er, logWarning) } - // -------------------------- - - // send the message to the procFuncCh which is running alongside the process - // and can hold registries and handle special things for an individual process. + // The handling of the public key that is in the message.Data field is handled in the procfunc. proc.procFuncCh <- message ackMsg := []byte("confirmed from: " + node + ": " + fmt.Sprint(message.ID)) @@ -66,7 +63,7 @@ func methodHello(proc process, message Message, node string) ([]byte, error) { // received, the handler will deliver the message to the procFunc on the // proc.procFuncCh, and we can then read that message from the procFuncCh in // the procFunc running. -func procFuncHello(ctx context.Context, proc process, procFuncCh chan Message) error { +func procFuncHelloSubscriber(ctx context.Context, proc process, procFuncCh chan Message) error { // sayHelloNodes := make(map[Node]struct{}) for {