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
}