diff --git a/auth_server/authn/ext_auth.go b/auth_server/authn/ext_auth.go index c26b660e..f86a1ffe 100644 --- a/auth_server/authn/ext_auth.go +++ b/auth_server/authn/ext_auth.go @@ -27,8 +27,8 @@ import ( ) type ExtAuthConfig struct { - Command string `yaml:"command"` - Args []string `yaml:"args"` + Command string `mapstructure:"command"` + Args []string `mapstructure:"args"` } type ExtAuthStatus int diff --git a/auth_server/authn/github_auth.go b/auth_server/authn/github_auth.go index 6a47aa53..ed3753b3 100644 --- a/auth_server/authn/github_auth.go +++ b/auth_server/authn/github_auth.go @@ -32,21 +32,21 @@ import ( ) type GitHubAuthConfig struct { - Organization string `yaml:"organization,omitempty"` - ClientId string `yaml:"client_id,omitempty"` - ClientSecret string `yaml:"client_secret,omitempty"` - ClientSecretFile string `yaml:"client_secret_file,omitempty"` - TokenDB string `yaml:"token_db,omitempty"` - GCSTokenDB *GitHubGCSStoreConfig `yaml:"gcs_token_db,omitempty"` - HTTPTimeout time.Duration `yaml:"http_timeout,omitempty"` - RevalidateAfter time.Duration `yaml:"revalidate_after,omitempty"` - GithubWebUri string `yaml:"github_web_uri,omitempty"` - GithubApiUri string `yaml:"github_api_uri,omitempty"` + Organization string `mapstructure:"organization,omitempty"` + ClientId string `mapstructure:"clientid,omitempty"` + ClientSecret string `mapstructure:"clientsecret,omitempty"` + ClientSecretFile string `mapstructure:"clientsecret_file,omitempty"` + TokenDB string `mapstructure:"tokendb,omitempty"` + GCSTokenDB *GitHubGCSStoreConfig `mapstructure:"gcstokendb,omitempty"` + HTTPTimeout time.Duration `mapstructure:"httptimeout,omitempty"` + RevalidateAfter time.Duration `mapstructure:"revalidateafter,omitempty"` + GithubWebUri string `mapstructure:"githubweburi,omitempty"` + GithubApiUri string `mapstructure:"githubapiuri,omitempty"` } type GitHubGCSStoreConfig struct { - Bucket string `yaml:"bucket,omitempty"` - ClientSecretFile string `yaml:"client_secret_file,omitempty"` + Bucket string `mapstructure:"bucket,omitempty"` + ClientSecretFile string `mapstructure:"clientsecretfile,omitempty"` } type GitHubAuthRequest struct { diff --git a/auth_server/authn/google_auth.go b/auth_server/authn/google_auth.go index 10891ab8..2d2cd555 100644 --- a/auth_server/authn/google_auth.go +++ b/auth_server/authn/google_auth.go @@ -31,12 +31,12 @@ import ( ) type GoogleAuthConfig struct { - Domain string `yaml:"domain,omitempty"` - ClientId string `yaml:"client_id,omitempty"` - ClientSecret string `yaml:"client_secret,omitempty"` - ClientSecretFile string `yaml:"client_secret_file,omitempty"` - TokenDB string `yaml:"token_db,omitempty"` - HTTPTimeout int `yaml:"http_timeout,omitempty"` + Domain string `mapstructure:"domain,omitempty"` + ClientId string `mapstructure:"clientid,omitempty"` + ClientSecret string `mapstructure:"clientsecret,omitempty"` + ClientSecretFile string `mapstructure:"clientsecretfile,omitempty"` + TokenDB string `mapstructure:"tokendb,omitempty"` + HTTPTimeout int `mapstructure:"httptimeout,omitempty"` } type GoogleAuthRequest struct { diff --git a/auth_server/authn/ldap_auth.go b/auth_server/authn/ldap_auth.go index a3425ed6..11a7472a 100644 --- a/auth_server/authn/ldap_auth.go +++ b/auth_server/authn/ldap_auth.go @@ -24,21 +24,21 @@ import ( "io/ioutil" "strings" - "github.com/go-ldap/ldap" "github.com/cesanta/glog" + "github.com/go-ldap/ldap" ) type LDAPAuthConfig struct { - Addr string `yaml:"addr,omitempty"` - TLS string `yaml:"tls,omitempty"` - InsecureTLSSkipVerify bool `yaml:"insecure_tls_skip_verify,omitempty"` - CACertificate string `yaml:"ca_certificate,omitempty"` - Base string `yaml:"base,omitempty"` - Filter string `yaml:"filter,omitempty"` - BindDN string `yaml:"bind_dn,omitempty"` - BindPasswordFile string `yaml:"bind_password_file,omitempty"` - GroupBaseDN string `yaml:"group_base_dn,omitempty"` - GroupFilter string `yaml:"group_filter,omitempty"` + Addr string `mapstructure:"addr,omitempty"` + TLS string `mapstructure:"tls,omitempty"` + InsecureTLSSkipVerify bool `mapstructure:"insecuretlsskipverify,omitempty"` + CACertificate string `mapstructure:"cacertificate,omitempty"` + Base string `mapstructure:"base,omitempty"` + Filter string `mapstructure:"filter,omitempty"` + BindDN string `mapstructure:"binddn,omitempty"` + BindPasswordFile string `mapstructure:"bindpasswordfile,omitempty"` + GroupBaseDN string `mapstructure:"groupbasedn,omitempty"` + GroupFilter string `mapstructure:"groupfilter,omitempty"` } type LDAPAuth struct { diff --git a/auth_server/authn/mongo_auth.go b/auth_server/authn/mongo_auth.go index c6aee896..1f0ab8be 100644 --- a/auth_server/authn/mongo_auth.go +++ b/auth_server/authn/mongo_auth.go @@ -30,8 +30,8 @@ import ( ) type MongoAuthConfig struct { - MongoConfig *mgo_session.Config `yaml:"dial_info,omitempty"` - Collection string `yaml:"collection,omitempty"` + MongoConfig *mgo_session.Config `mapstructure:"dialinfo,omitempty"` + Collection string `mapstructure:"collection,omitempty"` } type MongoAuth struct { diff --git a/auth_server/authn/static_auth.go b/auth_server/authn/static_auth.go index 7f6d1f49..5740a332 100644 --- a/auth_server/authn/static_auth.go +++ b/auth_server/authn/static_auth.go @@ -18,12 +18,13 @@ package authn import ( "encoding/json" + "golang.org/x/crypto/bcrypt" ) type Requirements struct { - Password *PasswordString `yaml:"password,omitempty" json:"password,omitempty"` - Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"` + Password *PasswordString `mapstructure:"password,omitempty" json:"password,omitempty"` + Labels Labels `mapstructure:"labels,omitempty" json:"labels,omitempty"` } type staticUsersAuth struct { diff --git a/auth_server/authz/acl.go b/auth_server/authz/acl.go index fe141fa9..d8667559 100644 --- a/auth_server/authz/acl.go +++ b/auth_server/authz/acl.go @@ -18,18 +18,18 @@ import ( type ACL []ACLEntry type ACLEntry struct { - Match *MatchConditions `yaml:"match"` - Actions *[]string `yaml:"actions,flow"` - Comment *string `yaml:"comment,omitempty"` + Match *MatchConditions `mapstructure:"match"` + Actions *[]string `mapstructure:"actions,flow"` + Comment *string `mapstructure:"comment,omitempty"` } type MatchConditions struct { - Account *string `yaml:"account,omitempty" json:"account,omitempty"` - Type *string `yaml:"type,omitempty" json:"type,omitempty"` - Name *string `yaml:"name,omitempty" json:"name,omitempty"` - IP *string `yaml:"ip,omitempty" json:"ip,omitempty"` - Service *string `yaml:"service,omitempty" json:"service,omitempty"` - Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"` + Account *string `mapstructure:"account,omitempty" json:"account,omitempty"` + Type *string `mapstructure:"type,omitempty" json:"type,omitempty"` + Name *string `mapstructure:"name,omitempty" json:"name,omitempty"` + IP *string `mapstructure:"ip,omitempty" json:"ip,omitempty"` + Service *string `mapstructure:"service,omitempty" json:"service,omitempty"` + Labels map[string]string `mapstructure:"labels,omitempty" json:"labels,omitempty"` } type aclAuthorizer struct { diff --git a/auth_server/authz/acl_mongo.go b/auth_server/authz/acl_mongo.go index a5441103..4b92a5df 100644 --- a/auth_server/authz/acl_mongo.go +++ b/auth_server/authz/acl_mongo.go @@ -3,13 +3,14 @@ package authz import ( "errors" "fmt" + "io" + "sync" + "time" + "github.com/cesanta/docker_auth/auth_server/mgo_session" "github.com/cesanta/glog" "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" - "io" - "sync" - "time" ) type MongoACL []MongoACLEntry @@ -20,9 +21,9 @@ type MongoACLEntry struct { } type ACLMongoConfig struct { - MongoConfig *mgo_session.Config `yaml:"dial_info,omitempty"` - Collection string `yaml:"collection,omitempty"` - CacheTTL time.Duration `yaml:"cache_ttl,omitempty"` + MongoConfig *mgo_session.Config `mapstructure:"dialinfo,omitempty"` + Collection string `mapstructure:"collection,omitempty"` + CacheTTL time.Duration `mapstructure:"cachettl,omitempty"` } type aclMongoAuthorizer struct { diff --git a/auth_server/authz/ext_authz.go b/auth_server/authz/ext_authz.go index 98890214..d9d567f2 100644 --- a/auth_server/authz/ext_authz.go +++ b/auth_server/authz/ext_authz.go @@ -27,8 +27,8 @@ import ( ) type ExtAuthzConfig struct { - Command string `yaml:"command"` - Args []string `yaml:"args"` + Command string `mapstructure:"command"` + Args []string `mapstructure:"args"` } type ExtAuthzStatus int diff --git a/auth_server/main.go b/auth_server/main.go index aa0056bf..3c7e1c26 100644 --- a/auth_server/main.go +++ b/auth_server/main.go @@ -37,6 +37,7 @@ import ( type RestartableServer struct { configFile string + envPrefix string hd *httpdown.HTTP authServer *server.AuthServer hs httpdown.Server @@ -156,7 +157,7 @@ func (rs *RestartableServer) WatchConfig() { func (rs *RestartableServer) MaybeRestart() { glog.Infof("Validating new config") - c, err := server.LoadConfig(rs.configFile) + c, err := server.LoadConfig(rs.configFile, rs.envPrefix) if err != nil { glog.Errorf("Failed to reload config (server not restarted): %s", err) return @@ -178,7 +179,13 @@ func main() { if cf == "" { glog.Exitf("Config file not specified") } - c, err := server.LoadConfig(cf) + + envPrefix := flag.Arg(1) + if envPrefix == "" { + envPrefix = "REGAUTH" + } + + c, err := server.LoadConfig(cf, envPrefix) if err != nil { glog.Exitf("Failed to load config: %s", err) } diff --git a/auth_server/mgo_session/mgo_session.go b/auth_server/mgo_session/mgo_session.go index f06719f7..49abed6e 100644 --- a/auth_server/mgo_session/mgo_session.go +++ b/auth_server/mgo_session/mgo_session.go @@ -30,9 +30,9 @@ import ( // Config stores how to connect to the MongoDB server and an optional password file type Config struct { - DialInfo mgo.DialInfo `yaml:",inline"` - PasswordFile string `yaml:"password_file,omitempty"` - EnableTLS bool `yaml:"enable_tls,omitempty"` + DialInfo mgo.DialInfo `mapstructure:",squash"` + PasswordFile string `mapstructure:"passwordfile,omitempty"` + EnableTLS bool `mapstructure:"enabletls,omitempty"` } // Validate ensures the most common fields inside the mgo.DialInfo portion of @@ -40,13 +40,13 @@ type Config struct { // Config itself. func (c *Config) Validate(configKey string) error { if len(c.DialInfo.Addrs) == 0 { - return fmt.Errorf("At least one element in %s.dial_info.addrs is required", configKey) + return fmt.Errorf("At least one element in %s.dialinfo.addrs is required", configKey) } if c.DialInfo.Timeout == 0 { c.DialInfo.Timeout = 10 * time.Second } if c.DialInfo.Database == "" { - return fmt.Errorf("%s.dial_info.database is required", configKey) + return fmt.Errorf("%s.dialinfo.database is required", configKey) } return nil } diff --git a/auth_server/server/config.go b/auth_server/server/config.go index fe08c6d0..13cb0256 100644 --- a/auth_server/server/config.go +++ b/auth_server/server/config.go @@ -19,57 +19,60 @@ package server import ( "crypto/tls" "crypto/x509" + "encoding/json" "errors" "fmt" "io/ioutil" "os" + "path/filepath" "strings" "time" "github.com/cesanta/docker_auth/auth_server/authn" "github.com/cesanta/docker_auth/auth_server/authz" "github.com/docker/libtrust" - yaml "gopkg.in/yaml.v2" + "github.com/spf13/viper" + "gopkg.in/yaml.v2" ) type Config struct { - Server ServerConfig `yaml:"server"` - Token TokenConfig `yaml:"token"` - Users map[string]*authn.Requirements `yaml:"users,omitempty"` - GoogleAuth *authn.GoogleAuthConfig `yaml:"google_auth,omitempty"` - GitHubAuth *authn.GitHubAuthConfig `yaml:"github_auth,omitempty"` - LDAPAuth *authn.LDAPAuthConfig `yaml:"ldap_auth,omitempty"` - MongoAuth *authn.MongoAuthConfig `yaml:"mongo_auth,omitempty"` - ExtAuth *authn.ExtAuthConfig `yaml:"ext_auth,omitempty"` - ACL authz.ACL `yaml:"acl,omitempty"` - ACLMongo *authz.ACLMongoConfig `yaml:"acl_mongo,omitempty"` - ExtAuthz *authz.ExtAuthzConfig `yaml:"ext_authz,omitempty"` + Server ServerConfig `mapstructure:"server"` + Token TokenConfig `mapstructure:"token"` + Users map[string]*authn.Requirements `mapstructure:"users,omitempty"` + GoogleAuth *authn.GoogleAuthConfig `mapstructure:"googleauth,omitempty"` + GitHubAuth *authn.GitHubAuthConfig `mapstructure:"githubauth,omitempty"` + LDAPAuth *authn.LDAPAuthConfig `mapstructure:"ldapauth,omitempty"` + MongoAuth *authn.MongoAuthConfig `mapstructure:"mongoauth,omitempty"` + ExtAuth *authn.ExtAuthConfig `mapstructure:"extauth,omitempty"` + ACL authz.ACL `mapstructure:"acl,omitempty"` + ACLMongo *authz.ACLMongoConfig `mapstructure:"aclmongo,omitempty"` + ExtAuthz *authz.ExtAuthzConfig `mapstructure:"extauthz,omitempty"` } type ServerConfig struct { - ListenAddress string `yaml:"addr,omitempty"` - PathPrefix string `yaml:"path_prefix,omitempty"` - RealIPHeader string `yaml:"real_ip_header,omitempty"` - RealIPPos int `yaml:"real_ip_pos,omitempty"` - CertFile string `yaml:"certificate,omitempty"` - KeyFile string `yaml:"key,omitempty"` - LetsEncrypt LetsEncryptConfig `yaml:"letsencrypt,omitempty"` + ListenAddress string `mapstructure:"addr,omitempty"` + PathPrefix string `mapstructure:"pathprefix,omitempty"` + RealIPHeader string `mapstructure:"realipheader,omitempty"` + RealIPPos int `mapstructure:"realippos,omitempty"` + CertFile string `mapstructure:"certificate,omitempty"` + KeyFile string `mapstructure:"key,omitempty"` + LetsEncrypt LetsEncryptConfig `mapstructure:"letsencrypt,omitempty"` publicKey libtrust.PublicKey privateKey libtrust.PrivateKey } type LetsEncryptConfig struct { - Host string `yaml:"host,omitempty"` - Email string `yaml:"email,omitempty"` - CacheDir string `yaml:"cache_dir,omitempty"` + Host string `mapstructure:"host,omitempty"` + Email string `mapstructure:"email,omitempty"` + CacheDir string `mapstructure:"cachedir,omitempty"` } type TokenConfig struct { - Issuer string `yaml:"issuer,omitempty"` - CertFile string `yaml:"certificate,omitempty"` - KeyFile string `yaml:"key,omitempty"` - Expiration int64 `yaml:"expiration,omitempty"` + Issuer string `mapstructure:"issuer,omitempty"` + CertFile string `mapstructure:"certificate,omitempty"` + KeyFile string `mapstructure:"key,omitempty"` + Expiration int64 `mapstructure:"expiration,omitempty"` publicKey libtrust.PublicKey privateKey libtrust.PrivateKey @@ -93,7 +96,7 @@ func validate(c *Config) error { return errors.New("no auth methods are configured, this is probably a mistake. Use an empty user map if you really want to deny everyone.") } if c.MongoAuth != nil { - if err := c.MongoAuth.Validate("mongo_auth"); err != nil { + if err := c.MongoAuth.Validate("mongoauth"); err != nil { return err } } @@ -179,13 +182,68 @@ func loadCertAndKey(certFile, keyFile string) (pk libtrust.PublicKey, prk libtru return } -func LoadConfig(fileName string) (*Config, error) { - contents, err := ioutil.ReadFile(fileName) +func processEnvVars(envPrefix, fileName string) error { + ext := filepath.Ext(fileName) + ext = ext[1:] + + switch ext { + case "yaml", "json", "yml": + default: + return fmt.Errorf("unsupported config type: %s", ext) + } + + // set values from env variables starting with envPrefix to make sure + // missing map keys on config file can be caught over env variables + envs := os.Environ() + for _, envKey := range envs { + keyVal := strings.SplitN(envKey, "=", 2) + ks := strings.SplitAfterN(keyVal[0], envPrefix+"_", 2) + if len(ks) != 2 { + continue + } + + vKey := strings.ToLower(strings.Replace(ks[1], "_", ".", -1)) + + var val interface{} + var parseErr error + switch ext { + case "yaml", "yml": + parseErr = yaml.Unmarshal([]byte(keyVal[1]), &val) + case "json": + parseErr = json.Unmarshal([]byte(keyVal[1]), &val) + } + if parseErr != nil { + return fmt.Errorf("could not parse env var %s as %s: %v", ks[0], ext, parseErr) + } + + viper.Set(vKey, val) + } + + return nil +} + +func LoadConfig(fileName, envPrefix string) (*Config, error) { + configFile, err := os.Open(fileName) if err != nil { + return nil, fmt.Errorf("could not open %s: %s", fileName, err) + } + + viper.SetConfigFile(fileName) + viper.AutomaticEnv() + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.SetEnvPrefix(envPrefix) + + if err := viper.ReadConfig(configFile); err != nil { return nil, fmt.Errorf("could not read %s: %s", fileName, err) + } + + if err := processEnvVars(envPrefix, fileName); err != nil { + return nil, fmt.Errorf("could not process env variables: %s", err) + } + c := &Config{} - if err = yaml.Unmarshal(contents, c); err != nil { + if err := viper.Unmarshal(c); err != nil { return nil, fmt.Errorf("could not parse config: %s", err) } if err = validate(c); err != nil { diff --git a/auth_server/server/config_test.go b/auth_server/server/config_test.go new file mode 100644 index 00000000..fa79cdf7 --- /dev/null +++ b/auth_server/server/config_test.go @@ -0,0 +1,51 @@ +package server + +import ( + "io/ioutil" + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/moby/moby/pkg/fileutils" + "gopkg.in/yaml.v2" +) + +func TestLoadConfig(t *testing.T) { + conf, err := LoadConfig("../../examples/reference.yml", "AUTH") + if err != nil { + t.Error(err) + return + } + + dir, err := ioutil.TempDir("", "docker_auth_test") + fname := filepath.Join(dir, "conf.yml") + if err := fileutils.CreateIfNotExists(fname, false); err != nil { + t.Fatal(err) + return + } + + f, err := os.OpenFile(fname, os.O_RDWR, 0666) + if err != nil { + t.Fatal(err) + return + } + + out, err := yaml.Marshal(conf) + _, err = f.Write(out) + if err != nil { + t.Fatal(err) + return + } + + reconf, err := LoadConfig(f.Name(), "AUTH") + if err != nil { + t.Fatal(err) + return + } + + if !reflect.DeepEqual(conf, reconf) { + t.Error("reloaded config is not same") + return + } +}