| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package conf |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "runtime" |
| "strings" |
| |
| "github.com/gorilla/sessions" |
| "github.com/spf13/viper" |
| "github.com/tidwall/gjson" |
| "golang.org/x/oauth2" |
| |
| "github.com/apisix/manager-api/internal/utils" |
| ) |
| |
| const ( |
| EnvPROD = "prod" |
| EnvBETA = "beta" |
| EnvDEV = "dev" |
| EnvLOCAL = "local" |
| EnvTEST = "test" |
| |
| WebDir = "html/" |
| |
| DefaultCSP = "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:" |
| State = "123456" |
| ) |
| |
| var ( |
| ENV string |
| Schema gjson.Result |
| WorkDir = "." |
| ConfigFile = "" |
| ServerHost = "0.0.0.0" |
| ServerPort = 80 |
| SSLHost = "0.0.0.0" |
| SSLPort = 443 |
| SSLCert string |
| SSLKey string |
| ETCDConfig *Etcd |
| ErrorLogLevel = "warn" |
| ErrorLogPath = "logs/error.log" |
| AccessLogPath = "logs/access.log" |
| UserList = make(map[string]User, 2) |
| AuthConf Authentication |
| SSLDefaultStatus = 1 //enable ssl by default |
| ImportSizeLimit = 10 * 1024 * 1024 |
| AllowList []string |
| Plugins = map[string]bool{} |
| SecurityConf Security |
| CookieStore = sessions.NewCookieStore([]byte("oidc")) |
| OidcEnabled = false |
| OidcId string |
| OidcConfig oauth2.Config |
| OidcExpireTime int |
| OidcUserInfoURL string |
| ) |
| |
| type MTLS struct { |
| CaFile string `mapstructure:"ca_file"` |
| CertFile string `mapstructure:"cert_file"` |
| KeyFile string `mapstructure:"key_file"` |
| } |
| |
| type Etcd struct { |
| Endpoints []string |
| Username string |
| Password string |
| MTLS *MTLS |
| Prefix string |
| } |
| |
| type SSL struct { |
| Host string `mapstructure:"host"` |
| Port int `mapstructure:"port"` |
| Cert string `mapstructure:"cert"` |
| Key string `mapstructure:"key"` |
| } |
| |
| type Listen struct { |
| Host string |
| Port int |
| } |
| |
| type ErrorLog struct { |
| Level string |
| FilePath string `mapstructure:"file_path"` |
| } |
| |
| type AccessLog struct { |
| FilePath string `mapstructure:"file_path"` |
| } |
| |
| type Log struct { |
| ErrorLog ErrorLog `mapstructure:"error_log"` |
| AccessLog AccessLog `mapstructure:"access_log"` |
| } |
| |
| type Conf struct { |
| Etcd Etcd |
| Listen Listen |
| SSL SSL |
| Log Log |
| AllowList []string `mapstructure:"allow_list"` |
| MaxCpu int `mapstructure:"max_cpu"` |
| Security Security |
| } |
| |
| type User struct { |
| Username string |
| Password string |
| } |
| |
| type Authentication struct { |
| Secret string |
| ExpireTime int `mapstructure:"expire_time"` |
| Users []User |
| } |
| |
| type Oidc struct { |
| Enabled bool `mapstructure:"enabled"` |
| ExpireTime int `mapstructure:"expire_time" yaml:"expire_time"` |
| ClientID string `mapstructure:"client_id"` |
| ClientSecret string `mapstructure:"client_secret"` |
| AuthURL string `mapstructure:"auth_url"` |
| TokenURL string `mapstructure:"token_url"` |
| UserInfoURL string `mapstructure:"user_info_url"` |
| RedirectURL string `mapstructure:"redirect_url"` |
| Scope string |
| } |
| |
| type Config struct { |
| Conf Conf |
| Authentication Authentication |
| Plugins []string |
| Oidc Oidc |
| } |
| |
| type Security struct { |
| AllowCredentials string `mapstructure:"access_control_allow_credentials"` |
| AllowOrigin string `mapstructure:"access_control_allow_origin"` |
| AllowMethods string `mapstructure:"access_control-allow_methods"` |
| AllowHeaders string `mapstructure:"access_control_allow_headers"` |
| XFrameOptions string `mapstructure:"x_frame_options"` |
| ContentSecurityPolicy string `mapstructure:"content_security_policy"` |
| } |
| |
| // TODO: we should no longer use init() function after remove all handler's integration tests |
| // ENV=test is for integration tests only, other ENV should call "InitConf" explicitly |
| func init() { |
| if env := os.Getenv("ENV"); env == EnvTEST { |
| InitConf() |
| } |
| } |
| |
| func InitConf() { |
| //go test |
| if workDir := os.Getenv("APISIX_API_WORKDIR"); workDir != "" { |
| WorkDir = workDir |
| } |
| |
| setupConfig() |
| setupEnv() |
| initSchema() |
| } |
| |
| func setupConfig() { |
| // setup config file path |
| if ConfigFile == "" { |
| ConfigFile = "conf.yaml" |
| if profile := os.Getenv("APISIX_PROFILE"); profile != "" { |
| ConfigFile = "conf" + "-" + profile + ".yaml" |
| } |
| viper.SetConfigName(ConfigFile) |
| viper.SetConfigType("yaml") |
| viper.AddConfigPath(WorkDir + "/conf") |
| } else { |
| viper.SetConfigFile(ConfigFile) |
| } |
| |
| // load config |
| if err := viper.ReadInConfig(); err != nil { |
| panic(fmt.Sprintf("fail to read configuration, err: %s", err.Error())) |
| } |
| |
| // unmarshal config |
| config := Config{} |
| err := viper.Unmarshal(&config) |
| if err != nil { |
| panic(fmt.Sprintf("fail to unmarshal configuration: %s, err: %s", ConfigFile, err.Error())) |
| } |
| |
| // listen |
| if config.Conf.Listen.Port != 0 { |
| ServerPort = config.Conf.Listen.Port |
| } |
| if config.Conf.Listen.Host != "" { |
| ServerHost = config.Conf.Listen.Host |
| } |
| |
| // SSL |
| if config.Conf.SSL.Port != 0 { |
| SSLPort = config.Conf.SSL.Port |
| } |
| if config.Conf.SSL.Cert != "" { |
| SSLCert = config.Conf.SSL.Cert |
| } |
| if config.Conf.SSL.Key != "" { |
| SSLKey = config.Conf.SSL.Key |
| } |
| |
| // ETCD Storage |
| if len(config.Conf.Etcd.Endpoints) > 0 { |
| initEtcdConfig(config.Conf.Etcd) |
| } |
| |
| // error log |
| if config.Conf.Log.ErrorLog.Level != "" { |
| ErrorLogLevel = config.Conf.Log.ErrorLog.Level |
| } |
| if config.Conf.Log.ErrorLog.FilePath != "" { |
| ErrorLogPath = config.Conf.Log.ErrorLog.FilePath |
| } |
| |
| // access log |
| if config.Conf.Log.AccessLog.FilePath != "" { |
| AccessLogPath = config.Conf.Log.AccessLog.FilePath |
| } |
| |
| if !filepath.IsAbs(ErrorLogPath) { |
| if strings.HasPrefix(ErrorLogPath, "winfile") { |
| return |
| } |
| ErrorLogPath, err = filepath.Abs(filepath.Join(WorkDir, ErrorLogPath)) |
| if err != nil { |
| panic(err) |
| } |
| if runtime.GOOS == "windows" { |
| ErrorLogPath = `winfile:///` + ErrorLogPath |
| } |
| } |
| if !filepath.IsAbs(AccessLogPath) { |
| if strings.HasPrefix(AccessLogPath, "winfile") { |
| return |
| } |
| AccessLogPath, err = filepath.Abs(filepath.Join(WorkDir, AccessLogPath)) |
| if err != nil { |
| panic(err) |
| } |
| if runtime.GOOS == "windows" { |
| AccessLogPath = `winfile:///` + AccessLogPath |
| } |
| } |
| |
| AllowList = config.Conf.AllowList |
| |
| // set degree of parallelism |
| initParallelism(config.Conf.MaxCpu) |
| |
| // set authentication |
| initAuthentication(config.Authentication) |
| |
| // set Oidc |
| initOidc(config.Oidc) |
| |
| // set plugin |
| initPlugins(config.Plugins) |
| |
| // security configuration |
| initSecurity(config.Conf.Security) |
| } |
| |
| func setupEnv() { |
| ENV = EnvPROD |
| if env := os.Getenv("ENV"); env != "" { |
| ENV = env |
| } |
| } |
| |
| func initAuthentication(conf Authentication) { |
| AuthConf = conf |
| if AuthConf.Secret == "secret" { |
| AuthConf.Secret = utils.GetFlakeUidStr() |
| } |
| |
| userList := conf.Users |
| // create user list |
| for _, item := range userList { |
| UserList[item.Username] = item |
| } |
| } |
| |
| func initOidc(conf Oidc) { |
| OidcEnabled = conf.Enabled |
| OidcExpireTime = conf.ExpireTime |
| OidcConfig.ClientID = conf.ClientID |
| OidcConfig.ClientSecret = conf.ClientSecret |
| OidcConfig.Endpoint = oauth2.Endpoint{AuthURL: conf.AuthURL, TokenURL: conf.TokenURL, AuthStyle: 1} |
| OidcConfig.Scopes = append(OidcConfig.Scopes, conf.Scope) |
| OidcConfig.RedirectURL = conf.RedirectURL |
| OidcUserInfoURL = conf.UserInfoURL |
| } |
| |
| func initPlugins(plugins []string) { |
| for _, pluginName := range plugins { |
| Plugins[pluginName] = true |
| } |
| } |
| |
| func initSchema() { |
| var ( |
| apisixSchemaPath = WorkDir + "/conf/schema.json" |
| customizeSchemaPath = WorkDir + "/conf/customize_schema.json" |
| apisixSchemaContent []byte |
| customizeSchemaContent []byte |
| err error |
| ) |
| |
| if apisixSchemaContent, err = ioutil.ReadFile(apisixSchemaPath); err != nil { |
| panic(fmt.Errorf("fail to read configuration: %s, error: %s", apisixSchemaPath, err.Error())) |
| } |
| |
| if customizeSchemaContent, err = ioutil.ReadFile(customizeSchemaPath); err != nil { |
| panic(fmt.Errorf("fail to read configuration: %s, error: %s", customizeSchemaPath, err.Error())) |
| } |
| |
| content, err := mergeSchema(apisixSchemaContent, customizeSchemaContent) |
| if err != nil { |
| panic(err) |
| } |
| |
| Schema = gjson.ParseBytes(content) |
| } |
| |
| func mergeSchema(apisixSchema, customizeSchema []byte) ([]byte, error) { |
| var ( |
| apisixSchemaMap map[string]map[string]interface{} |
| customizeSchemaMap map[string]map[string]interface{} |
| ) |
| |
| if err := json.Unmarshal(apisixSchema, &apisixSchemaMap); err != nil { |
| return nil, err |
| } |
| if err := json.Unmarshal(customizeSchema, &customizeSchemaMap); err != nil { |
| return nil, err |
| } |
| |
| for key := range apisixSchemaMap["main"] { |
| if _, ok := customizeSchemaMap["main"][key]; ok { |
| return nil, fmt.Errorf("duplicates key: main.%s between schema.json and customize_schema.json", key) |
| } |
| } |
| |
| for k, v := range customizeSchemaMap["main"] { |
| apisixSchemaMap["main"][k] = v |
| } |
| |
| return json.Marshal(apisixSchemaMap) |
| } |
| |
| // initialize etcd config |
| func initEtcdConfig(conf Etcd) { |
| var endpoints = []string{"127.0.0.1:2379"} |
| if len(conf.Endpoints) > 0 { |
| endpoints = conf.Endpoints |
| } |
| |
| prefix := "/apisix" |
| if len(conf.Prefix) > 0 { |
| prefix = conf.Prefix |
| } |
| |
| ETCDConfig = &Etcd{ |
| Endpoints: endpoints, |
| Username: conf.Username, |
| Password: conf.Password, |
| MTLS: conf.MTLS, |
| Prefix: prefix, |
| } |
| } |
| |
| // initialize parallelism settings |
| func initParallelism(choiceCores int) { |
| if choiceCores < 1 { |
| return |
| } |
| maxSupportedCores := runtime.NumCPU() |
| |
| if choiceCores > maxSupportedCores { |
| choiceCores = maxSupportedCores |
| } |
| runtime.GOMAXPROCS(choiceCores) |
| } |
| |
| // initialize security settings |
| func initSecurity(conf Security) { |
| var se Security |
| // if conf == se, then conf is empty, we should use default value |
| if conf != se { |
| SecurityConf = conf |
| if conf.ContentSecurityPolicy == "" { |
| SecurityConf.ContentSecurityPolicy = DefaultCSP |
| } |
| if conf.XFrameOptions == "" { |
| SecurityConf.XFrameOptions = "deny" |
| } |
| return |
| } |
| |
| SecurityConf = Security{ |
| XFrameOptions: "deny", |
| ContentSecurityPolicy: DefaultCSP, |
| } |
| } |