Skip to content

Commit

Permalink
Add option to create config for rhc-worker-bash
Browse files Browse the repository at this point in the history
yaml config at /etc/rhc/workers/rhc-worker-bash.yml can now change behavior of worker
  • Loading branch information
andywaltlova committed Jul 19, 2023
1 parent 8dd21f3 commit 98cb943
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 123 deletions.
49 changes: 36 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,39 @@ vars:
FOO: bar
BAR: foo
```
### Environment variables
Environment variables used by our worker are always prefixed with `RHC_WORKER_`.

Use below variables to adjust worker behavior.
* Related to logging
* `RHC_WORKER_LOG_FOLDER` - default is `"/var/log/rhc-worker-bash"`
* `RHC_WORKER_LOG_FILENAME` - default is `"rhc-worker-bash.log"`
* Related to verification of yaml file containing bash script
* `RHC_WORKER_GPG_CHECK` - default is `"1"`
* `RHC_WORKER_VERIFY_YAML` - default is `"1"`
* Related to script temporary location and execution
* `RHC_WORKER_TMP_DIR` - default is `"/var/lib/rhc-worker-bash"`
## FAQ
### Are there special environment variables that worker uses?
There is one special variable that must be set in order to run our worker and that is `YGG_SOCKET_ADDR`, this variable value is set by `rhcd` via `--socket-addr` option.

Other than that there are no special variables, however if executed bash script contained some `content_vars` (like the example above), then during the execution of the script are all environment variables always prefixed with `RHC_WORKER_`and unset after the bash script completes.

### Can I somehow change behavior of worker? e.g. different destination for logs?

Yes, some values can be changed if config exists at `/etc/rhc/workers/rhc-worker-bash.yml`, **the config must have valid yaml format**, see all available fields below.

Example of full config (with default values):
```yaml
# rhc-worker-bash configuration
# recipient directive to register with dispatcher
directive: "rhc-worker-bash"
# whether to verify incoming yaml files
verify_yaml: true
# perform the insights-client GPG check on the insights-core egg
insights_core_gpg_check: true
# temporary directory in which the temporary files with executed bash scripts are created
temporary_worker_directory: "/var/lib/rhc-worker-bash"
# Options to adjust name and directory for worker logs
log_dir: "/var/log/rhc-worker-bash"
log_filename: "rhc-worker-bash.log"
```

### Can I change the location of rhc-worker bash config?

No, not right now. If you want this feature please create an issue or upvote already existing issue.
31 changes: 20 additions & 11 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,31 @@ import (
)

// Initialized in main
const configFilePath = "/etc/rhc/workers/rhc-worker-bash.yml"

var yggdDispatchSocketAddr string
var logFolder string
var logFileName string
var temporaryWorkerDirectory string
var shouldDoInsightsCoreGPGCheck string
var shouldVerifyYaml string
var config *Config

// main is the entry point of the application. It initializes values from the environment,
// sets up the logger, establishes a connection with the dispatcher, registers as a handler,
// listens for incoming messages, and starts accepting connections as a Worker service.
// Note: The function blocks and runs indefinitely until the server is stopped.
func main() {
initializedOK, errorMsg := initializeEnvironment()
if errorMsg != "" && !initializedOK {
log.Fatal(errorMsg)
var yggSocketAddrExists bool // Has to be separately declared otherwise grpc.Dial doesn't work
yggdDispatchSocketAddr, yggSocketAddrExists = os.LookupEnv("YGG_SOCKET_ADDR")
if !yggSocketAddrExists {
log.Fatal("Missing YGG_SOCKET_ADDR environment variable")
}

logFile := setupLogger(logFolder, logFileName)
config = loadConfigOrDefault(configFilePath)
log.Infoln("Configuration loaded: ", config)

logFile := setupLogger(*config.LogDir, *config.LogDir)
defer logFile.Close()

// Dial the dispatcher on its well-known address.
conn, err := grpc.Dial(yggdDispatchSocketAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
conn, err := grpc.Dial(
yggdDispatchSocketAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}
Expand All @@ -47,7 +50,13 @@ func main() {
defer cancel()

// Register as a handler of the "rhc-worker-bash" type.
r, err := c.Register(ctx, &pb.RegistrationRequest{Handler: "rhc-worker-bash", Pid: int64(os.Getpid()), DetachedContent: true})
r, err := c.Register(
ctx,
&pb.RegistrationRequest{
Handler: "rhc-worker-bash",
Pid: int64(os.Getpid()),
DetachedContent: true,
})
if err != nil {
log.Fatal(err)
}
Expand Down
13 changes: 7 additions & 6 deletions src/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"gopkg.in/yaml.v3"
)

// Received Yaml data has to match the expected yamlConfig structure
type yamlConfig struct {
// Received Yaml data has to match the expected signedYamlFile structure
type signedYamlFile struct {
Vars struct {
InsightsSignature string `yaml:"_insights_signature"`
InsightsSignatureExclude string `yaml:"_insights_signature_exclude"`
Expand All @@ -23,7 +23,7 @@ type yamlConfig struct {
// Verify that no one tampered with yaml file
func verifyYamlFile(yamlData []byte) bool {

if shouldVerifyYaml != "1" {
if !*config.VerifyYAML {
log.Warnln("WARNING: Playbook verification disabled.")
return true
}
Expand All @@ -38,7 +38,7 @@ func verifyYamlFile(yamlData []byte) bool {
}
env := os.Environ()

if shouldDoInsightsCoreGPGCheck == "0" {
if !*config.InsightsCoreGPGCheck {
args = append(args, "--no-gpg")
env = append(env, "BYPASS_GPG=True")
}
Expand Down Expand Up @@ -81,7 +81,7 @@ func processSignedScript(yamlFileContet []byte) string {
log.Infoln("Signature of yaml file is valid")

// Parse the YAML data into the yamlConfig struct
var yamlContent yamlConfig
var yamlContent signedYamlFile
err := yaml.Unmarshal(yamlFileContet, &yamlContent)
if err != nil {
log.Errorln(err)
Expand Down Expand Up @@ -117,7 +117,8 @@ func processSignedScript(yamlFileContet []byte) string {

// Write the file contents to the temporary disk
log.Infoln("Writing temporary bash script")
scriptFileName := writeFileToTemporaryDir([]byte(yamlContent.Vars.Content), temporaryWorkerDirectory)
scriptFileName := writeFileToTemporaryDir(
[]byte(yamlContent.Vars.Content), *config.TemporaryWorkerDirectory)
defer os.Remove(scriptFileName)

// Execute the script
Expand Down
34 changes: 23 additions & 11 deletions src/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import (
)

func TestProcessSignedScript(t *testing.T) {
temporaryWorkerDirectory = "test-dir"
shouldVerifyYaml := false
shouldDoInsightsCoreGPGCheck := false
temporaryWorkerDirectory := "test-dir"
config = &Config{
VerifyYAML: &shouldVerifyYaml,
TemporaryWorkerDirectory: &temporaryWorkerDirectory,
InsightsCoreGPGCheck: &shouldDoInsightsCoreGPGCheck,
}

defer os.RemoveAll(temporaryWorkerDirectory)

// Test case 1: verification disabled, no yaml data supplied = empty output
shouldVerifyYaml = "0"
yamlData := []byte{}

expectedResult := ""
result := processSignedScript(yamlData)
if result != expectedResult {
Expand All @@ -38,8 +44,8 @@ vars:

// FIXME: This is false success because verification fails on missing insighs-client
// Test case 3: verification enabled, invalid signature = error msg returned
shouldVerifyYaml = "1"
shouldDoInsightsCoreGPGCheck = "0"
shouldVerifyYaml = true
shouldDoInsightsCoreGPGCheck = true
expectedResult = "Signature of yaml file is invalid"
result = processSignedScript(yamlData)
if result != expectedResult {
Expand All @@ -48,16 +54,22 @@ vars:
}

func TestVerifyYamlFile(t *testing.T) {
// Test case 1: shouldVerifyYaml is not "1"
shouldVerifyYaml = "0"
shouldVerifyYaml := false
shouldDoInsightsCoreGPGCheck := false

config = &Config{
VerifyYAML: &shouldVerifyYaml,
InsightsCoreGPGCheck: &shouldDoInsightsCoreGPGCheck,
}
// Test case 1: verification disabled
expectedResult := true
result := verifyYamlFile([]byte{})
if result != expectedResult {
t.Errorf("Expected %v, but got %v", expectedResult, result)
}

// Test case 2: shouldVerifyYaml is "1" and verification succeeds
shouldVerifyYaml = "1"
// Test case 2: verification enabled and verification succeeds
shouldVerifyYaml = true
// FIXME: This should succedd but now verification fails on missing insighs-client
// We also need valid signature
expectedResult = false
Expand All @@ -67,8 +79,8 @@ func TestVerifyYamlFile(t *testing.T) {
}

// FIXME: Valid test case but fails because of missing insights-client
// Test case 3: shouldVerifyYaml is "1" and verification fails
shouldVerifyYaml = "1"
// Test case 3: sverification is enabled and verification fails
// shouldVerifyYaml = true
expectedResult = false
result = verifyYamlFile([]byte("invalid-yaml")) // Replace with your YAML data
if result != expectedResult {
Expand Down
6 changes: 4 additions & 2 deletions src/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ func (s *jobServer) Send(ctx context.Context, d *pb.Data) (*pb.Receipt, error) {
commandOutput := processSignedScript(d.GetContent())

// Dial the Dispatcher and call "Finish"
conn, err := grpc.Dial(yggdDispatchSocketAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
conn, err := grpc.Dial(
yggdDispatchSocketAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Error(err)
}
Expand All @@ -78,7 +79,8 @@ func (s *jobServer) Send(ctx context.Context, d *pb.Data) (*pb.Receipt, error) {

// Create a data message to send back to the dispatcher.
log.Infof("Creating payload for message %s", d.GetMessageId())
data := createDataMessage(commandOutput, d.GetMetadata(), d.GetDirective(), d.GetMessageId())
data := createDataMessage(
commandOutput, d.GetMetadata(), d.GetDirective(), d.GetMessageId())

// Call "Send"
log.Infof("Sending message to %s", d.GetMessageId())
Expand Down
101 changes: 78 additions & 23 deletions src/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,9 @@ import (
"os"

"git.sr.ht/~spc/go-log"
"gopkg.in/yaml.v3"
)

// Calls os.LookupEnv for key, if not found then fallback value is returned
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}

// Set initialization values from the environment variables
func initializeEnvironment() (bool, string) {
var yggSocketAddrExists bool // Has to be separately declared otherwise grpc.Dial doesn't work
yggdDispatchSocketAddr, yggSocketAddrExists = os.LookupEnv("YGG_SOCKET_ADDR")
if !yggSocketAddrExists {
return false, "Missing YGG_SOCKET_ADDR environment variable"
}
logFolder = getEnv("RHC_WORKER_LOG_FOLDER", "/var/log/rhc-worker-bash")
logFileName = getEnv("RHC_WORKER_LOG_FILENAME", "rhc-worker-bash.log")
temporaryWorkerDirectory = getEnv("RHC_WORKER_TMP_DIR", "/var/lib/rhc-worker-bash")
shouldDoInsightsCoreGPGCheck = getEnv("RHC_WORKER_GPG_CHECK", "1")
shouldVerifyYaml = getEnv("RHC_WORKER_VERIFY_YAML", "1")
return true, ""
}

// writeFileToTemporaryDir writes the provided data to a temporary file in the
// designated temporary worker directory. It creates the directory if it doesn't exist.
// The function returns the filename of the created temporary file.
Expand Down Expand Up @@ -113,3 +91,80 @@ func constructMetadata(receivedMetadata map[string]string, contentType string) m
}
return ourMetadata
}

// Struc used fro worker global config
type Config struct {
Directive *string `yaml:"directive,omitempty"`
VerifyYAML *bool `yaml:"verify_yaml,omitempty"`
InsightsCoreGPGCheck *bool `yaml:"insights_core_gpg_check,omitempty"`
TemporaryWorkerDirectory *string `yaml:"temporary_worker_directory,omitempty"`
LogDir *string `yaml:"log_dir,omitempty"`
LogFileName *string `yaml:"log_filename,omitempty"`
}

// Set default values for the Config struct
func setDefaultValues(config *Config) {
// Set default values for string and boolean fields if they are nil (not present in the YAML)
if config.Directive == nil {
defaultDirectiveValue := "rhc-worker-bash"
config.Directive = &defaultDirectiveValue
}

if config.VerifyYAML == nil {
defaultVerifyYamlValue := true
config.VerifyYAML = &defaultVerifyYamlValue
}

if config.InsightsCoreGPGCheck == nil {
defaultGpgCheckValue := true
config.InsightsCoreGPGCheck = &defaultGpgCheckValue
}

if config.TemporaryWorkerDirectory == nil {
defaultTemporaryWorkerDirectoryValue := "/var/lib/rhc-worker-bash"
config.TemporaryWorkerDirectory = &defaultTemporaryWorkerDirectoryValue
}

if config.LogDir == nil {
defaultLogFolder := "/var/log/rhc-worker-bash"
config.LogDir = &defaultLogFolder
}

if config.LogFileName == nil {
defaultLogFilename := "rhc-worker-bash.log"
config.LogFileName = &defaultLogFilename
}
}

// Load yaml config, if file doesn't exist or is invalid yaml then empty COnfig is returned
func loadYAMLConfig(filePath string) *Config {
var config Config

data, err := os.ReadFile(filePath)
if err != nil {
log.Error(err)
}

if err := yaml.Unmarshal(data, &config); err != nil {
log.Error(err)
}

return &config
}

// Load config from given filepath, if config doesn't exist then default config values are used
// Directive = rhc-worker-bash
// VerifyYAML = "1"
// InsightsCoreGPGCheck = "1"
func loadConfigOrDefault(filePath string) *Config {
config := &Config{}
_, err := os.Stat(filePath)
if err == nil {
// File exists, load configuration from YAML
config = loadYAMLConfig(filePath)
}

// File doesn't exist, create a new Config with default values
setDefaultValues(config)
return config
}
Loading

0 comments on commit 98cb943

Please sign in to comment.