diff --git a/config/config.go b/config/config.go index d6f2fcfb..487c52c5 100644 --- a/config/config.go +++ b/config/config.go @@ -445,12 +445,25 @@ func (a *APIServer) Validate() error { type timeToLive string -func (d *timeToLive) Duration() time.Duration { +func (d *timeToLive) ParseDuration() (time.Duration, error) { duration, err := time.ParseDuration(string(*d)) + if err != nil { + return 0, err + } + return duration, nil +} + +func (d *timeToLive) Duration() time.Duration { + duration, err := d.ParseDuration() if err != nil { log.Printf("failed to parse duration: %s", err) return DefaultJWTTTL } + // TODO(gabriel-samfira): should we have a minimum TTL? + if duration < DefaultJWTTTL { + return DefaultJWTTTL + } + return duration } @@ -472,10 +485,10 @@ type JWTAuth struct { // Validate validates the JWTAuth config func (j *JWTAuth) Validate() error { - // TODO: Set defaults somewhere else. - if j.TimeToLive.Duration() < DefaultJWTTTL { - j.TimeToLive = timeToLive(DefaultJWTTTL.String()) + if _, err := j.TimeToLive.ParseDuration(); err != nil { + return errors.Wrap(err, "parsing duration") } + if j.Secret == "" { return fmt.Errorf("invalid JWT secret") } diff --git a/config/config_test.go b/config/config_test.go index d9b39723..c8e4cd9a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -19,6 +19,7 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -499,3 +500,157 @@ func TestDatabaseConfig(t *testing.T) { }) } } + +func TestGormParams(t *testing.T) { + dir, err := ioutil.TempDir("", "garm-config-test") + if err != nil { + t.Fatalf("failed to create temporary directory: %s", err) + } + t.Cleanup(func() { os.RemoveAll(dir) }) + cfg := getDefaultDatabaseConfig(dir) + + dbType, uri, err := cfg.GormParams() + require.Nil(t, err) + require.Equal(t, SQLiteBackend, dbType) + require.Equal(t, filepath.Join(dir, "garm.db"), uri) + + cfg.DbBackend = MySQLBackend + cfg.MySQL = getMySQLDefaultConfig() + cfg.SQLite = SQLite{} + + dbType, uri, err = cfg.GormParams() + require.Nil(t, err) + require.Equal(t, MySQLBackend, dbType) + require.Equal(t, "test:test@tcp(127.0.0.1)/garm?charset=utf8&parseTime=True&loc=Local&timeout=5s", uri) + +} + +func TestSQLiteConfig(t *testing.T) { + dir, err := ioutil.TempDir("", "garm-config-test") + if err != nil { + t.Fatalf("failed to create temporary directory: %s", err) + } + t.Cleanup(func() { os.RemoveAll(dir) }) + + tests := []struct { + name string + cfg SQLite + errString string + }{ + { + name: "Config is valid", + cfg: SQLite{ + DBFile: filepath.Join(dir, "garm.db"), + }, + errString: "", + }, + { + name: "db_file is empty", + cfg: SQLite{ + DBFile: "", + }, + errString: "no valid db_file was specified", + }, + { + name: "db_file must not be a relative path", + cfg: SQLite{ + DBFile: "../test.db", + }, + errString: "please specify an absolute path for db_file", + }, + { + name: "parent folder must exist", + cfg: SQLite{ + DBFile: "/i/dont/exist/test.db", + }, + errString: "accessing db_file parent dir:.*no such file or directory", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.cfg.Validate() + if tc.errString == "" { + require.Nil(t, err) + } else { + require.NotNil(t, err) + require.Regexp(t, tc.errString, err.Error()) + } + }) + } +} + +func TestJWTAuthConfig(t *testing.T) { + cfg := JWTAuth{ + Secret: EncryptionPassphrase, + TimeToLive: "48h", + } + + tests := []struct { + name string + cfg JWTAuth + errString string + }{ + { + name: "Config is valid", + cfg: cfg, + errString: "", + }, + { + name: "secret is empty", + cfg: JWTAuth{ + Secret: "", + TimeToLive: cfg.TimeToLive, + }, + errString: "invalid JWT secret", + }, + { + name: "secret is weak", + cfg: JWTAuth{ + Secret: WeakEncryptionPassphrase, + TimeToLive: cfg.TimeToLive, + }, + errString: "jwt_secret is too weak", + }, + { + name: "time to live is invalid", + cfg: JWTAuth{ + Secret: cfg.Secret, + TimeToLive: "bogus", + }, + errString: "parsing duration: time: invalid duration*", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.cfg.Validate() + if tc.errString == "" { + require.Nil(t, err) + } else { + require.NotNil(t, err) + require.Regexp(t, tc.errString, err.Error()) + } + }) + } +} + +func TestTimeToLiveDuration(t *testing.T) { + cfg := JWTAuth{ + Secret: EncryptionPassphrase, + TimeToLive: "48h", + } + + require.Equal(t, cfg.TimeToLive.Duration(), 48*time.Hour) + + cfg.TimeToLive = "1h" + require.Equal(t, cfg.TimeToLive.Duration(), DefaultJWTTTL) + + cfg.TimeToLive = "72h" + require.Equal(t, cfg.TimeToLive.Duration(), 72*time.Hour) + + cfg.TimeToLive = "2d" + _, err := cfg.TimeToLive.ParseDuration() + require.NotNil(t, err) + require.EqualError(t, err, "time: unknown unit \"d\" in duration \"2d\"") +}