internal/handlersettings/handlersettingscommon.go (102 lines of code) (raw):

package handlersettings import ( "bytes" "encoding/base64" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "github.com/Azure/run-command-handler-linux/internal/settings" "github.com/pkg/errors" ) type HandlerSettingsFile struct { RuntimeSettings []RunTimeSettingsFile `json:"runtimeSettings"` } type RunTimeSettingsFile struct { HandlerSettings settings.SettingsCommon `json:"handlerSettings"` } // ReadSettings locates the .settings file and returns public settings // JSON, and protected settings JSON (by decrypting it with the keys in // configFolder). func ReadSettings(configFilePath string) (public, protected map[string]interface{}, _ error) { // cf, err := settingsPath(configFolder) // if err != nil { // return nil, nil, fmt.Errorf("canot locate settings file: %v", err) // } hs, err := parseHandlerSettingsFile(configFilePath) if err != nil { return nil, nil, fmt.Errorf("error parsing settings file: %v", err) } public = hs.PublicSettings configFolder := filepath.Dir(configFilePath) if err := unmarshalProtectedSettings(configFolder, hs, &protected); err != nil { return nil, nil, fmt.Errorf("failed to parse protected settings: %v", err) } return public, protected, nil } // UnmarshalHandlerSettings unmarshals given publicSettings/protectedSettings types // assumed underlying values are JSON into references publicV/protectedV respectively // (of struct types that contain structured fields for settings). func UnmarshalHandlerSettings(publicSettings, protectedSettings map[string]interface{}, publicV, protectedV interface{}) error { if err := unmarshalSettings(publicSettings, &publicV); err != nil { return fmt.Errorf("failed to unmarshal public settings: %v", err) } if err := unmarshalSettings(protectedSettings, &protectedV); err != nil { return fmt.Errorf("failed to unmarshal protected settings: %v", err) } return nil } // unmarshalSettings makes a round-trip JSON marshaling and unmarshaling // from in (assumed map[interface]{}) to v (actual settings type). func unmarshalSettings(in interface{}, v interface{}) error { s, err := json.Marshal(in) if err != nil { return fmt.Errorf("failed to marshal into json: %v", err) } if err := json.Unmarshal(s, &v); err != nil { return fmt.Errorf("failed to unmarshal json: %v", err) } return nil } // parseHandlerSettings parses a handler settings file (e.g. 0.settings) and // returns it as a structured object. func parseHandlerSettingsFile(path string) (h settings.SettingsCommon, _ error) { b, err := os.ReadFile(path) if err != nil { return h, fmt.Errorf("error reading %s: %v", path, err) } if len(b) == 0 { // if no config is specified, we get an empty file return h, nil } var f HandlerSettingsFile if err := json.Unmarshal(b, &f); err != nil { return h, fmt.Errorf("error parsing json: %v", err) } if len(f.RuntimeSettings) != 1 { return h, fmt.Errorf("wrong runtimeSettings count. expected:1, got:%d", len(f.RuntimeSettings)) } return f.RuntimeSettings[0].HandlerSettings, nil } // unmarshalProtectedSettings decodes the protected settings from handler // runtime settings JSON file, decrypts it using the certificates and unmarshals // into the given struct v. func unmarshalProtectedSettings(configFolder string, hs settings.SettingsCommon, v interface{}) error { if hs.ProtectedSettingsBase64 == "" { return nil } if hs.SettingsCertThumbprint == "" { return errors.New("HandlerSettings has protected settings but no cert thumbprint") } decoded, err := base64.StdEncoding.DecodeString(hs.ProtectedSettingsBase64) if err != nil { return fmt.Errorf("failed to decode base64: %v", err) } // go two levels up where certs are placed (/var/lib/waagent) crt := filepath.Join(configFolder, "..", "..", fmt.Sprintf("%s.crt", hs.SettingsCertThumbprint)) prv := filepath.Join(configFolder, "..", "..", fmt.Sprintf("%s.prv", hs.SettingsCertThumbprint)) // we use os/exec instead of azure-docker-extension/pkg/executil here as // other extension handlers depend on this package for parsing handler // settings. //using cms command to support for FIPS 140-3 cmd := exec.Command("openssl", "cms", "-inform", "DER", "-decrypt", "-recip", crt, "-inkey", prv) var bOut, bErr bytes.Buffer var errMsg error cmd.Stdin = bytes.NewReader(decoded) cmd.Stdout = &bOut cmd.Stderr = &bErr //back up smime command in case cms fails if err := cmd.Run(); err != nil { errMsg = fmt.Errorf("decrypting protected settings with cms command failed: error=%v stderr=%s \n now decrypting with smime command", err, bErr.String()) cmd = exec.Command("openssl", "smime", "-inform", "DER", "-decrypt", "-recip", crt, "-inkey", prv) cmd.Stdin = bytes.NewReader(decoded) bOut.Reset() bErr.Reset() cmd.Stdout = &bOut cmd.Stderr = &bErr if err := cmd.Run(); err != nil { return errors.Wrapf(errMsg, "decrypting protected settings with smime command failed: error=%v stderr=%s", err, bErr.String()) } } // decrypted: json object for protected settings if err := json.Unmarshal(bOut.Bytes(), &v); err != nil { return fmt.Errorf("failed to unmarshal decrypted settings json: %v", err) } return nil }