github.com/kisexp/xdchain@v0.0.0-20211206025815-490d6b732aa7/plugin/settings.go (about) 1 package plugin 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net/url" 8 "os" 9 "path/filepath" 10 "reflect" 11 "runtime" 12 "strings" 13 14 "github.com/kisexp/xdchain/plugin/account" 15 "github.com/kisexp/xdchain/plugin/helloworld" 16 "github.com/kisexp/xdchain/plugin/security" 17 "github.com/kisexp/xdchain/rpc" 18 "github.com/hashicorp/go-plugin" 19 "github.com/naoina/toml" 20 ) 21 22 const ( 23 HelloWorldPluginInterfaceName = PluginInterfaceName("helloworld") // lower-case always 24 SecurityPluginInterfaceName = PluginInterfaceName("security") 25 AccountPluginInterfaceName = PluginInterfaceName("account") 26 ) 27 28 var ( 29 // define additional plugins being supported here 30 pluginProviders = map[PluginInterfaceName]pluginProvider{ 31 HelloWorldPluginInterfaceName: { 32 apiProviderFunc: func(ns string, pm *PluginManager) ([]rpc.API, error) { 33 template := new(HelloWorldPluginTemplate) 34 if err := pm.GetPluginTemplate(HelloWorldPluginInterfaceName, template); err != nil { 35 return nil, err 36 } 37 service, err := template.Get() 38 if err != nil { 39 return nil, err 40 } 41 return []rpc.API{{ 42 Namespace: ns, 43 Version: "1.0.0", 44 Service: service, 45 Public: true, 46 }}, nil 47 }, 48 pluginSet: plugin.PluginSet{ 49 helloworld.ConnectorName: &helloworld.PluginConnector{}, 50 }, 51 }, 52 SecurityPluginInterfaceName: { 53 pluginSet: plugin.PluginSet{ 54 security.TLSConfigurationConnectorName: &security.TLSConfigurationSourcePluginConnector{}, 55 security.AuthenticationConnectorName: &security.AuthenticationManagerPluginConnector{}, 56 }, 57 }, 58 AccountPluginInterfaceName: { 59 apiProviderFunc: func(ns string, pm *PluginManager) ([]rpc.API, error) { 60 f := new(ReloadableAccountServiceFactory) 61 if err := pm.GetPluginTemplate(AccountPluginInterfaceName, f); err != nil { 62 return nil, err 63 } 64 service, err := f.Create() 65 if err != nil { 66 return nil, err 67 } 68 return []rpc.API{{ 69 Namespace: ns, 70 Version: "1.0.0", 71 Service: account.NewCreator(service), 72 Public: true, 73 }}, nil 74 }, 75 pluginSet: plugin.PluginSet{ 76 account.ConnectorName: &account.PluginConnector{}, 77 }, 78 }, 79 } 80 81 // this is the place holder for future solution of the plugin central 82 quorumPluginCentralConfiguration = &PluginCentralConfiguration{ 83 CertFingerprint: "", 84 BaseURL: "https://artifacts.consensys.net/public/quorum-go-plugins/", 85 PublicKeyURI: DefaultPublicKeyFile, 86 InsecureSkipTLSVerify: false, 87 PluginDistPathTemplate: "maven/bin/{{.Name}}/{{.Version}}/{{.Name}}-{{.Version}}-{{.OS}}-{{.Arch}}.zip", 88 PluginSigPathTemplate: "maven/bin/{{.Name}}/{{.Version}}/{{.Name}}-{{.Version}}-{{.OS}}-{{.Arch}}-sha256.checksum.asc", 89 } 90 ) 91 92 type pluginProvider struct { 93 // this allows exposing plugin interfaces to geth RPC API automatically. 94 // nil value implies that plugin won't expose its methods to geth RPC API 95 apiProviderFunc rpcAPIProviderFunc 96 // contains connectors being registered to the plugin library 97 pluginSet plugin.PluginSet 98 } 99 100 type rpcAPIProviderFunc func(ns string, pm *PluginManager) ([]rpc.API, error) 101 type Version string 102 103 // This is to describe a plugin 104 // 105 // Information is used to discover the plugin binary and verify its integrity 106 // before forking a process running the plugin 107 type PluginDefinition struct { 108 Name string `json:"name" toml:""` 109 // the semver version of the plugin 110 Version Version `json:"version" toml:""` 111 // plugin configuration in a form of map/slice/string 112 Config interface{} `json:"config,omitempty" toml:",omitempty"` 113 } 114 115 func ReadMultiFormatConfig(config interface{}) ([]byte, error) { 116 if config == nil { 117 return []byte{}, nil 118 } 119 switch k := reflect.TypeOf(config).Kind(); k { 120 case reflect.Map, reflect.Slice: 121 return json.Marshal(config) 122 case reflect.String: 123 configStr := config.(string) 124 u, err := url.Parse(configStr) 125 if err != nil { // just return as is 126 return []byte(configStr), nil 127 } 128 switch s := u.Scheme; s { 129 case "file": 130 return ioutil.ReadFile(filepath.Join(u.Host, u.Path)) 131 case "env": // config string in an env variable 132 varName := u.Host 133 isFile := u.Query().Get("type") == "file" 134 if v, ok := os.LookupEnv(varName); ok { 135 if isFile { 136 return ioutil.ReadFile(v) 137 } else { 138 return []byte(v), nil 139 } 140 } else { 141 return nil, fmt.Errorf("env variable %s not found", varName) 142 } 143 default: 144 return []byte(configStr), nil 145 } 146 default: 147 return nil, fmt.Errorf("unsupported type of config [%s]", k) 148 } 149 } 150 151 // return plugin distribution name. i.e.: <Name>-<Version>-<OS>-<Arch> 152 func (m *PluginDefinition) FullName() string { 153 return fmt.Sprintf("%s-%s-%s-%s", m.Name, m.Version, runtime.GOOS, runtime.GOARCH) 154 } 155 156 // return plugin distribution file name stored locally 157 func (m *PluginDefinition) DistFileName() string { 158 return fmt.Sprintf("%s.zip", m.FullName()) 159 } 160 161 // return plugin distribution signature file name stored locally 162 func (m *PluginDefinition) SignatureFileName() string { 163 return fmt.Sprintf("%s.sha256sum.asc", m.DistFileName()) 164 } 165 166 // must be always be lowercase when define constants 167 // as when unmarshaling from config, value will be case-lowered 168 type PluginInterfaceName string 169 170 // When this is used as a key in map. This function is not invoked. 171 func (p *PluginInterfaceName) UnmarshalJSON(data []byte) error { 172 var v string 173 if err := json.Unmarshal(data, &v); err != nil { 174 return err 175 } 176 *p = PluginInterfaceName(strings.ToLower(v)) 177 return nil 178 } 179 180 func (p *PluginInterfaceName) UnmarshalTOML(data []byte) error { 181 var v string 182 if err := toml.Unmarshal(data, &v); err != nil { 183 return err 184 } 185 *p = PluginInterfaceName(strings.ToLower(v)) 186 return nil 187 } 188 189 func (p *PluginInterfaceName) UnmarshalText(data []byte) error { 190 *p = PluginInterfaceName(strings.ToLower(string(data))) 191 return nil 192 } 193 194 func (p PluginInterfaceName) String() string { 195 return string(p) 196 } 197 198 // this defines plugins used in the geth node 199 type Settings struct { 200 BaseDir EnvironmentAwaredValue `json:"baseDir" toml:""` 201 CentralConfig *PluginCentralConfiguration `json:"central" toml:"Central"` 202 Providers map[PluginInterfaceName]PluginDefinition `json:"providers" toml:""` 203 } 204 205 func (s *Settings) GetPluginDefinition(name PluginInterfaceName) (*PluginDefinition, bool) { 206 m, ok := s.Providers[name] 207 return &m, ok 208 } 209 210 func (s *Settings) SetDefaults() { 211 if s.CentralConfig == nil { 212 s.CentralConfig = quorumPluginCentralConfiguration 213 } else { 214 s.CentralConfig.SetDefaults() 215 } 216 } 217 218 // CheckSettingsAreSupported validates Settings by ensuring that only supportedPlugins are defined. 219 // It is not required for all supportedPlugins to be defined. 220 // An error containing plugin details is returned if one or more unsupported plugins are defined. 221 func (s *Settings) CheckSettingsAreSupported(supportedPlugins []PluginInterfaceName) error { 222 errList := []PluginInterfaceName{} 223 for name := range s.Providers { 224 isValid := false 225 for _, supportedPlugin := range supportedPlugins { 226 if supportedPlugin == name { 227 isValid = true 228 break 229 } 230 } 231 if !isValid { 232 errList = append(errList, name) 233 } 234 } 235 if len(errList) != 0 { 236 return fmt.Errorf("unsupported plugins configured: %v", errList) 237 } 238 return nil 239 } 240 241 type PluginCentralConfiguration struct { 242 // To implement certificate pinning while communicating with PluginCentral 243 // if it's empty, we skip cert pinning logic 244 CertFingerprint string `json:"certFingerprint" toml:""` 245 BaseURL string `json:"baseURL" toml:""` 246 PublicKeyURI string `json:"publicKeyURI" toml:""` 247 InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify" toml:""` 248 249 // URL path template to the plugin distribution file. 250 // It uses Golang text template. 251 PluginDistPathTemplate string `json:"pluginDistPathTemplate" toml:""` 252 // URL path template to the plugin sha256 checksum signature file. 253 // It uses Golang text template. 254 PluginSigPathTemplate string `json:"pluginSigPathTemplate" toml:""` 255 } 256 257 // populate default values from quorumPluginCentralConfiguration 258 func (c *PluginCentralConfiguration) SetDefaults() { 259 if len(c.BaseURL) == 0 { 260 c.BaseURL = quorumPluginCentralConfiguration.BaseURL 261 } 262 if len(c.PublicKeyURI) == 0 { 263 c.PublicKeyURI = quorumPluginCentralConfiguration.PublicKeyURI 264 } 265 if len(c.PluginDistPathTemplate) == 0 { 266 c.PluginDistPathTemplate = quorumPluginCentralConfiguration.PluginDistPathTemplate 267 } 268 if len(c.PluginSigPathTemplate) == 0 { 269 c.PluginSigPathTemplate = quorumPluginCentralConfiguration.PluginSigPathTemplate 270 } 271 } 272 273 // support URI format with 'env' scheme during JSON/TOML/TEXT unmarshalling 274 // e.g.: env://FOO_VAR means read a string value from FOO_VAR environment variable 275 type EnvironmentAwaredValue string 276 277 func (d *EnvironmentAwaredValue) UnmarshalJSON(data []byte) error { 278 return d.unmarshal(data) 279 } 280 281 func (d *EnvironmentAwaredValue) UnmarshalTOML(data []byte) error { 282 return d.unmarshal(data) 283 } 284 285 func (d *EnvironmentAwaredValue) UnmarshalText(data []byte) error { 286 return d.unmarshal(data) 287 } 288 289 func (d *EnvironmentAwaredValue) unmarshal(data []byte) error { 290 v := string(data) 291 isString := strings.HasPrefix(v, "\"") && strings.HasSuffix(v, "\"") 292 if !isString { 293 return fmt.Errorf("not a string") 294 } 295 v = strings.TrimFunc(v, func(r rune) bool { 296 return r == '"' 297 }) 298 if u, err := url.Parse(v); err == nil { 299 switch u.Scheme { 300 case "env": 301 v = os.Getenv(u.Host) 302 } 303 } 304 *d = EnvironmentAwaredValue(v) 305 return nil 306 } 307 308 func (d EnvironmentAwaredValue) String() string { 309 return string(d) 310 }