Add identity-based access control, closes #51

Add a cli subcommand to verify config files validity
This commit is contained in:
Petu Eusebiu
2021-05-13 21:59:12 +03:00
committed by Ramkumar Chinchani
parent 26926ad4c2
commit 609d85d875
14 changed files with 915 additions and 51 deletions
+80 -15
View File
@@ -4,6 +4,7 @@ import (
"github.com/anuvu/zot/errors"
"github.com/anuvu/zot/pkg/api"
"github.com/anuvu/zot/pkg/storage"
"github.com/fsnotify/fsnotify"
"github.com/mitchellh/mapstructure"
distspec "github.com/opencontainers/distribution-spec/specs-go"
"github.com/rs/zerolog/log"
@@ -31,29 +32,66 @@ func NewRootCmd() *cobra.Command {
Long: "`serve` stores and distributes OCI images",
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
viper.SetConfigFile(args[0])
if err := viper.ReadInConfig(); err != nil {
panic(err)
}
md := &mapstructure.Metadata{}
if err := viper.Unmarshal(&config, metadataConfig(md)); err != nil {
panic(err)
}
// if haven't found a single key or there were unused keys, report it as
// a error
if len(md.Keys) == 0 || len(md.Unused) > 0 {
panic(errors.ErrBadConfig)
}
LoadConfiguration(config, args[0])
}
c := api.NewController(config)
// creates a new file watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
panic(err)
}
defer watcher.Close()
done := make(chan bool)
// run watcher
go func() {
go func() {
for {
select {
// watch for events
case event := <-watcher.Events:
if event.Op == fsnotify.Write {
log.Info().Msg("Config file changed, trying to reload accessControl config")
newConfig := api.NewConfig()
LoadConfiguration(newConfig, args[0])
c.Config.AccessControl = newConfig.AccessControl
}
// watch for errors
case err := <-watcher.Errors:
log.Error().Err(err).Msgf("FsNotify error while watching config %s", args[0])
panic(err)
}
}
}()
if err := watcher.Add(args[0]); err != nil {
log.Error().Err(err).Msgf("Error adding config file %s to FsNotify watcher", args[0])
panic(err)
}
<-done
}()
if err := c.Run(); err != nil {
panic(err)
}
},
}
verifyCmd := &cobra.Command{
Use: "verify <config>",
Aliases: []string{"verify"},
Short: "`verify` validates a zot config file",
Long: "`verify` validates a zot config file",
Run: func(cmd *cobra.Command, args []string) {
if len(args) > 0 {
config := api.NewConfig()
LoadConfiguration(config, args[0])
log.Info().Msgf("Config file %s is valid", args[0])
}
},
}
// "garbage-collect"
gcDelUntagged := false
gcDryRun := false
@@ -98,6 +136,7 @@ func NewRootCmd() *cobra.Command {
rootCmd.AddCommand(serveCmd)
rootCmd.AddCommand(gcCmd)
rootCmd.AddCommand(verifyCmd)
enableCli(rootCmd)
@@ -105,3 +144,29 @@ func NewRootCmd() *cobra.Command {
return rootCmd
}
func LoadConfiguration(config *api.Config, configPath string) {
viper.SetConfigFile(configPath)
if err := viper.ReadInConfig(); err != nil {
log.Error().Err(err).Msg("Error while reading configuration")
panic(err)
}
md := &mapstructure.Metadata{}
if err := viper.Unmarshal(&config, metadataConfig(md)); err != nil {
log.Error().Err(err).Msg("Error while unmarshalling new config")
panic(err)
}
if len(md.Keys) == 0 || len(md.Unused) > 0 {
log.Error().Err(errors.ErrBadConfig).Msg("Bad configuration, retry writing it")
panic(errors.ErrBadConfig)
}
err := config.LoadAccessControlConfig()
if err != nil {
log.Error().Err(errors.ErrBadConfig).Msg("Unable to unmarshal http.accessControl.key.policies")
panic(err)
}
}
+47
View File
@@ -6,8 +6,10 @@ import (
"path"
"testing"
"github.com/anuvu/zot/pkg/api"
"github.com/anuvu/zot/pkg/cli"
. "github.com/smartystreets/goconvey/convey"
"github.com/spf13/viper"
)
func TestUsage(t *testing.T) {
@@ -65,6 +67,51 @@ func TestServe(t *testing.T) {
})
}
func TestVerify(t *testing.T) {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
Convey("Test verify bad config", t, func(c C) {
tmpfile, err := ioutil.TempFile("", "zot-test*.json")
So(err, ShouldBeNil)
defer os.Remove(tmpfile.Name()) // clean up
content := []byte(`{"log":{}}`)
_, err = tmpfile.Write(content)
So(err, ShouldBeNil)
err = tmpfile.Close()
So(err, ShouldBeNil)
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
So(func() { _ = cli.NewRootCmd().Execute() }, ShouldPanic)
})
Convey("Test verify good config", t, func(c C) {
tmpfile, err := ioutil.TempFile("", "zot-test*.json")
So(err, ShouldBeNil)
defer os.Remove(tmpfile.Name()) // clean up
content := []byte(`{"version": "0.1.0-dev", "storage": {"rootDirectory": "/tmp/zot"},
"http": {"address": "127.0.0.1", "port": "8080", "ReadOnly": false},
"log": {"level": "debug"}}`)
_, err = tmpfile.Write(content)
So(err, ShouldBeNil)
err = tmpfile.Close()
So(err, ShouldBeNil)
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
err = cli.NewRootCmd().Execute()
So(err, ShouldBeNil)
})
}
func TestLoadConfig(t *testing.T) {
Convey("Test viper load config", t, func(c C) {
config := api.NewConfig()
So(func() { cli.LoadConfiguration(config, "../../examples/config-policy.json") }, ShouldNotPanic)
adminPolicy := viper.GetStringMapStringSlice("http.accessControl.adminPolicy")
So(config.AccessControl.AdminPolicy.Actions, ShouldResemble, adminPolicy["actions"])
So(config.AccessControl.AdminPolicy.Users, ShouldResemble, adminPolicy["users"])
})
}
func TestGC(t *testing.T) {
oldArgs := os.Args