github.com/RobustRoundRobin/quorum@v20.10.0+incompatible/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/ethereum/go-ethereum/plugin/account" 15 "github.com/ethereum/go-ethereum/plugin/helloworld" 16 "github.com/ethereum/go-ethereum/plugin/security" 17 "github.com/ethereum/go-ethereum/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://dl.bintray.com/quorumengineering/quorum-plugins", 85 PublicKeyURI: "/.pgp/" + DefaultPublicKeyFile, 86 InsecureSkipTLSVerify: false, 87 } 88 ) 89 90 type pluginProvider struct { 91 // this allows exposing plugin interfaces to geth RPC API automatically. 92 // nil value implies that plugin won't expose its methods to geth RPC API 93 apiProviderFunc rpcAPIProviderFunc 94 // contains connectors being registered to the plugin library 95 pluginSet plugin.PluginSet 96 } 97 98 type rpcAPIProviderFunc func(ns string, pm *PluginManager) ([]rpc.API, error) 99 type Version string 100 101 // This is to describe a plugin 102 // 103 // Information is used to discover the plugin binary and verify its integrity 104 // before forking a process running the plugin 105 type PluginDefinition struct { 106 Name string `json:"name" toml:""` 107 // the semver version of the plugin 108 Version Version `json:"version" toml:""` 109 // plugin configuration in a form of map/slice/string 110 Config interface{} `json:"config,omitempty" toml:",omitempty"` 111 } 112 113 func ReadMultiFormatConfig(config interface{}) ([]byte, error) { 114 if config == nil { 115 return []byte{}, nil 116 } 117 switch k := reflect.TypeOf(config).Kind(); k { 118 case reflect.Map, reflect.Slice: 119 return json.Marshal(config) 120 case reflect.String: 121 configStr := config.(string) 122 u, err := url.Parse(configStr) 123 if err != nil { // just return as is 124 return []byte(configStr), nil 125 } 126 switch s := u.Scheme; s { 127 case "file": 128 return ioutil.ReadFile(filepath.Join(u.Host, u.Path)) 129 case "env": // config string in an env variable 130 varName := u.Host 131 isFile := u.Query().Get("type") == "file" 132 if v, ok := os.LookupEnv(varName); ok { 133 if isFile { 134 return ioutil.ReadFile(v) 135 } else { 136 return []byte(v), nil 137 } 138 } else { 139 return nil, fmt.Errorf("env variable %s not found", varName) 140 } 141 default: 142 return []byte(configStr), nil 143 } 144 default: 145 return nil, fmt.Errorf("unsupported type of config [%s]", k) 146 } 147 } 148 149 // return remote folder storing the plugin distribution file and signature file 150 // 151 // e.g.: my-plugin/v1.0.0/darwin-amd64 152 func (m *PluginDefinition) RemotePath() string { 153 return fmt.Sprintf("%s/v%s/%s-%s", m.Name, m.Version, runtime.GOOS, runtime.GOARCH) 154 } 155 156 // return plugin name and version 157 func (m *PluginDefinition) FullName() string { 158 return fmt.Sprintf("%s-%s", m.Name, m.Version) 159 } 160 161 // return plugin distribution file name 162 func (m *PluginDefinition) DistFileName() string { 163 return fmt.Sprintf("%s.zip", m.FullName()) 164 } 165 166 // return plugin distribution signature file name 167 func (m *PluginDefinition) SignatureFileName() string { 168 return fmt.Sprintf("%s.sha256sum.asc", m.DistFileName()) 169 } 170 171 // must be always be lowercase when define constants 172 // as when unmarshaling from config, value will be case-lowered 173 type PluginInterfaceName string 174 175 // When this is used as a key in map. This function is not invoked. 176 func (p *PluginInterfaceName) UnmarshalJSON(data []byte) error { 177 var v string 178 if err := json.Unmarshal(data, &v); err != nil { 179 return err 180 } 181 *p = PluginInterfaceName(strings.ToLower(v)) 182 return nil 183 } 184 185 func (p *PluginInterfaceName) UnmarshalTOML(data []byte) error { 186 var v string 187 if err := toml.Unmarshal(data, &v); err != nil { 188 return err 189 } 190 *p = PluginInterfaceName(strings.ToLower(v)) 191 return nil 192 } 193 194 func (p *PluginInterfaceName) UnmarshalText(data []byte) error { 195 *p = PluginInterfaceName(strings.ToLower(string(data))) 196 return nil 197 } 198 199 func (p PluginInterfaceName) String() string { 200 return string(p) 201 } 202 203 // this defines plugins used in the geth node 204 type Settings struct { 205 BaseDir EnvironmentAwaredValue `json:"baseDir" toml:""` 206 CentralConfig *PluginCentralConfiguration `json:"central" toml:"Central"` 207 Providers map[PluginInterfaceName]PluginDefinition `json:"providers" toml:""` 208 } 209 210 func (s *Settings) GetPluginDefinition(name PluginInterfaceName) (*PluginDefinition, bool) { 211 m, ok := s.Providers[name] 212 return &m, ok 213 } 214 215 func (s *Settings) SetDefaults() { 216 if s.CentralConfig == nil { 217 s.CentralConfig = quorumPluginCentralConfiguration 218 } 219 } 220 221 // CheckSettingsAreSupported validates Settings by ensuring that only supportedPlugins are defined. 222 // It is not required for all supportedPlugins to be defined. 223 // An error containing plugin details is returned if one or more unsupported plugins are defined. 224 func (s *Settings) CheckSettingsAreSupported(supportedPlugins []PluginInterfaceName) error { 225 errList := []PluginInterfaceName{} 226 for name := range s.Providers { 227 isValid := false 228 for _, supportedPlugin := range supportedPlugins { 229 if supportedPlugin == name { 230 isValid = true 231 break 232 } 233 } 234 if !isValid { 235 errList = append(errList, name) 236 } 237 } 238 if len(errList) != 0 { 239 return fmt.Errorf("unsupported plugins configured: %v", errList) 240 } 241 return nil 242 } 243 244 type PluginCentralConfiguration struct { 245 // To implement certificate pinning while communicating with PluginCentral 246 // if it's empty, we skip cert pinning logic 247 CertFingerprint string `json:"certFingerprint" toml:""` 248 BaseURL string `json:"baseURL" toml:""` 249 PublicKeyURI string `json:"publicKeyURI" toml:""` 250 InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify" toml:""` 251 } 252 253 // support URI format with 'env' scheme during JSON/TOML/TEXT unmarshalling 254 // e.g.: env://FOO_VAR means read a string value from FOO_VAR environment variable 255 type EnvironmentAwaredValue string 256 257 func (d *EnvironmentAwaredValue) UnmarshalJSON(data []byte) error { 258 return d.unmarshal(data) 259 } 260 261 func (d *EnvironmentAwaredValue) UnmarshalTOML(data []byte) error { 262 return d.unmarshal(data) 263 } 264 265 func (d *EnvironmentAwaredValue) UnmarshalText(data []byte) error { 266 return d.unmarshal(data) 267 } 268 269 func (d *EnvironmentAwaredValue) unmarshal(data []byte) error { 270 v := string(data) 271 isString := strings.HasPrefix(v, "\"") && strings.HasSuffix(v, "\"") 272 if !isString { 273 return fmt.Errorf("not a string") 274 } 275 v = strings.TrimFunc(v, func(r rune) bool { 276 return r == '"' 277 }) 278 if u, err := url.Parse(v); err == nil { 279 switch u.Scheme { 280 case "env": 281 v = os.Getenv(u.Host) 282 } 283 } 284 *d = EnvironmentAwaredValue(v) 285 return nil 286 } 287 288 func (d EnvironmentAwaredValue) String() string { 289 return string(d) 290 }