0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

auth: support a read-only mode

This is useful if we want to roll out experimental versions of zot
pointing to some storage shared with another zot instance.

Also, when under storage full conditions, will be useful to turn on this
flag to prevent further writes.
This commit is contained in:
Ramkumar Chinchani 2020-07-10 14:32:58 -07:00
parent 74f48e6ad3
commit 78be4cbe3c
3 changed files with 91 additions and 2 deletions

View file

@ -91,6 +91,12 @@ func basicAuthHandler(c *Controller) mux.MiddlewareFunc {
authFail(w, realm, 5) authFail(w, realm, 5)
return return
} }
if (r.Method != http.MethodGet && r.Method != http.MethodHead) && c.Config.HTTP.ReadOnly {
// Reject modification requests in read-only mode
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
// Process request // Process request
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })
@ -175,6 +181,12 @@ func basicAuthHandler(c *Controller) mux.MiddlewareFunc {
return return
} }
if (r.Method != http.MethodGet && r.Method != http.MethodHead) && c.Config.HTTP.ReadOnly {
// Reject modification requests in read-only mode
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
basicAuth := r.Header.Get("Authorization") basicAuth := r.Header.Get("Authorization")
if basicAuth == "" { if basicAuth == "" {
authFail(w, realm, delay) authFail(w, realm, delay)

View file

@ -46,6 +46,7 @@ type HTTPConfig struct {
Auth *AuthConfig Auth *AuthConfig
Realm string Realm string
AllowReadAccess bool `mapstructure:",omitempty"` AllowReadAccess bool `mapstructure:",omitempty"`
ReadOnly bool `mapstructure:",omitempty"`
} }
type LDAPConfig struct { type LDAPConfig struct {

View file

@ -33,10 +33,12 @@ const (
BaseURL1 = "http://127.0.0.1:8081" BaseURL1 = "http://127.0.0.1:8081"
BaseURL2 = "http://127.0.0.1:8082" BaseURL2 = "http://127.0.0.1:8082"
BaseURL3 = "http://127.0.0.1:8083" BaseURL3 = "http://127.0.0.1:8083"
BaseURL4 = "http://127.0.0.1:8084"
BaseSecureURL2 = "https://127.0.0.1:8082" BaseSecureURL2 = "https://127.0.0.1:8082"
SecurePort1 = "8081" SecurePort1 = "8081"
SecurePort2 = "8082" SecurePort2 = "8082"
SecurePort3 = "8083" SecurePort3 = "8083"
SecurePort4 = "8084"
username = "test" username = "test"
passphrase = "test" passphrase = "test"
ServerCert = "../../test/data/server.cert" ServerCert = "../../test/data/server.cert"
@ -44,6 +46,7 @@ const (
CACert = "../../test/data/ca.crt" CACert = "../../test/data/ca.crt"
AuthorizedNamespace = "everyone/isallowed" AuthorizedNamespace = "everyone/isallowed"
UnauthorizedNamespace = "fortknox/notallowed" UnauthorizedNamespace = "fortknox/notallowed"
ALICE = "alice"
) )
type ( type (
@ -109,8 +112,8 @@ func TestNew(t *testing.T) {
func TestHtpasswdSingleCred(t *testing.T) { func TestHtpasswdSingleCred(t *testing.T) {
Convey("Single cred", t, func() { Convey("Single cred", t, func() {
singleCredtests := []string{} singleCredtests := []string{}
user := "alice" user := ALICE
password := "alice" password := ALICE
singleCredtests = append(singleCredtests, getCredString(user, password)) singleCredtests = append(singleCredtests, getCredString(user, password))
singleCredtests = append(singleCredtests, getCredString(user, password)+"\n") singleCredtests = append(singleCredtests, getCredString(user, password)+"\n")
@ -1439,3 +1442,76 @@ func parseBearerAuthHeader(authHeaderRaw string) *authHeader {
return &h return &h
} }
func TestHTTPReadOnly(t *testing.T) {
Convey("Single cred", t, func() {
singleCredtests := []string{}
user := ALICE
password := ALICE
singleCredtests = append(singleCredtests, getCredString(user, password))
singleCredtests = append(singleCredtests, getCredString(user, password)+"\n")
for _, testString := range singleCredtests {
func() {
config := api.NewConfig()
config.HTTP.Port = SecurePort4
// enable read-only mode
config.HTTP.ReadOnly = true
htpasswdPath := makeHtpasswdFileFromString(testString)
defer os.Remove(htpasswdPath)
config.HTTP.Auth = &api.AuthConfig{
HTPasswd: api.AuthHTPasswd{
Path: htpasswdPath,
},
}
c := api.NewController(config)
dir, err := ioutil.TempDir("", "oci-repo-test")
if err != nil {
panic(err)
}
defer os.RemoveAll(dir)
c.Config.Storage.RootDirectory = dir
go func(controller *api.Controller) {
// this blocks
if err := controller.Run(); err != nil {
return
}
}(c)
// wait till ready
for {
_, err := resty.R().Get(BaseURL4)
if err == nil {
break
}
time.Sleep(100 * time.Millisecond)
}
defer func(controller *api.Controller) {
ctx := context.Background()
_ = controller.Server.Shutdown(ctx)
}(c)
// with creds, should get expected status code
resp, _ := resty.R().SetBasicAuth(user, password).Get(BaseURL4 + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
// with creds, should get expected status code
resp, _ = resty.R().SetBasicAuth(user, password).Get(BaseURL4 + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
// with creds, any modifications should still fail on read-only mode
resp, err = resty.R().SetBasicAuth(user, password).
Post(BaseURL4 + "/v2/" + AuthorizedNamespace + "/blobs/uploads/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 405)
//with invalid creds, it should fail
resp, _ = resty.R().SetBasicAuth("chuck", "chuck").Get(BaseURL4 + "/v2/")
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 401)
}()
}
})
}