Skip to content

Commit

Permalink
Merge pull request #46 from containerish/refactor
Browse files Browse the repository at this point in the history
refactor: refactored to make code more manageable
  • Loading branch information
jay-dee7 authored Oct 5, 2021
2 parents 8a53d0f + 0b5ac72 commit 59ec472
Show file tree
Hide file tree
Showing 11 changed files with 328 additions and 194 deletions.
4 changes: 3 additions & 1 deletion auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ import (
"github.com/labstack/echo/v4"
)

// Authentication interface defines the behaviour for container registry and general authentication for the backend
type Authentication interface {
SignUp(ctx echo.Context) error
SignIn(ctx echo.Context) error
BasicAuth(username, password string) (map[string]interface{}, error)
BasicAuth() echo.MiddlewareFunc
}

type auth struct {
store cache.Store
c *config.RegistryConfig
}

// New is the constructor function returns an Authentication implementation
func New(s cache.Store, c *config.RegistryConfig) Authentication {
a := &auth{store: s, c: c}
return a
Expand Down
63 changes: 63 additions & 0 deletions auth/basic_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package auth

import (
"net/http"

"github.com/containerish/OpenRegistry/registry/v2"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

//when we use JWT
/*AuthMiddleware
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Docker-Distribution-Api-Version: registry/2.0
Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",
scope="repository:samalba/my-app:pull,push"
Date: Thu, 10 Sep 2015 19:32:31 GMT
Content-Length: 235
Strict-Transport-Security: max-age=31536000
{"errors":[{"code":"UNAUTHORIZED","message":"","detail":}]}
*/
//var wwwAuthenticate = `Bearer realm="http://0.0.0.0:5000/auth/token",
//service="http://0.0.0.0:5000",scope="repository:%s`

// BasicAuth returns a middleware which in turn can be used to perform http basic auth
func (a *auth) BasicAuth() echo.MiddlewareFunc {
return middleware.BasicAuth(func(username string, password string, ctx echo.Context) (bool, error) {

if ctx.Request().RequestURI != "/v2/" {
if ctx.Request().Method == http.MethodHead || ctx.Request().Method == http.MethodGet {
return true, nil
}
}

if ctx.Request().RequestURI == "/v2/" {
_, err := a.validateUser(username, password)
if err != nil {
return false, ctx.NoContent(http.StatusUnauthorized)
}
return true, nil
}

usernameFromNameSpace := ctx.Param("username")
if usernameFromNameSpace != username {
var errMsg registry.RegistryErrors
errMsg.Errors = append(errMsg.Errors, registry.RegistryError{
Code: registry.RegistryErrorCodeDenied,
Message: "not authorised",
Detail: nil,
})
return false, ctx.JSON(http.StatusForbidden, errMsg)
}
resp, err := a.validateUser(username, password)
if err != nil {
return false, err
}

ctx.Set("basic_auth", resp)
return true, nil
})
}
9 changes: 5 additions & 4 deletions auth/signup.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"unicode"

"github.com/containerish/OpenRegistry/cache"
"github.com/labstack/echo/v4"
)

Expand All @@ -21,13 +22,13 @@ type User struct {

const UserNameSpace = "users"

func (a *auth) ValidateUser(u User) error {
func (u *User) Validate(store cache.Store) error {

if err := verifyEmail(u.Email); err != nil {
return err
}
key := fmt.Sprintf("%s/%s", UserNameSpace, u.Email)
_, err := a.store.Get([]byte(key))
_, err := store.Get([]byte(key))
if err == nil {
return fmt.Errorf("user already exists, try loggin in or password reset")
}
Expand All @@ -36,7 +37,7 @@ func (a *auth) ValidateUser(u User) error {
return fmt.Errorf("username should be atleast 3 chars")
}

bz, err := a.store.ListWithPrefix([]byte(UserNameSpace))
bz, err := store.ListWithPrefix([]byte(UserNameSpace))
if err != nil {
return fmt.Errorf("internal server error")
}
Expand Down Expand Up @@ -156,7 +157,7 @@ func (a *auth) SignUp(ctx echo.Context) error {
})
}

if err := a.ValidateUser(u); err != nil {
if err := u.Validate(a.store); err != nil {
return ctx.JSON(http.StatusBadRequest, echo.Map{
"error": err.Error(),
"msg": "bananas",
Expand Down
2 changes: 1 addition & 1 deletion auth/validate_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/labstack/echo/v4"
)

func (a *auth) BasicAuth(username, password string) (map[string]interface{}, error) {
func (a *auth) validateUser(username, password string) (map[string]interface{}, error) {
if username == "" || password == "" {
return nil, fmt.Errorf("Email/Password cannot be empty")
}
Expand Down
194 changes: 6 additions & 188 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@ package main

import (
"log"
"net/http"
"os"
"time"

"github.com/containerish/OpenRegistry/auth"
"github.com/containerish/OpenRegistry/cache"
"github.com/containerish/OpenRegistry/config"
"github.com/containerish/OpenRegistry/registry/v2"
"github.com/containerish/OpenRegistry/router"
"github.com/containerish/OpenRegistry/skynet"
"github.com/containerish/OpenRegistry/telemetry"
"github.com/fatih/color"
"github.com/labstack/echo-contrib/prometheus"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/rs/zerolog"
)

func main() {
Expand All @@ -26,203 +23,24 @@ func main() {
}

e := echo.New()
p := prometheus.NewPrometheus("echo", nil)
p.Use(e)
e.HideBanner = true

l := setupLogger()
localCache, err := cache.New(".kvstore")
if err != nil {
l.Err(err).Send()
e.Logger.Errorf("error opening local kv store: %s", err)
return
}
defer localCache.Close()

authSvc := auth.New(localCache, cfg)

skynetClient := skynet.NewClient(cfg)

l := telemetry.SetupLogger()
reg, err := registry.NewRegistry(skynetClient, l, localCache, e.Logger)
if err != nil {
l.Err(err).Send()
e.Logger.Errorf("error creating new container registry: %s", err)
return
}
e.Use(echoLogger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())

internal := e.Group("/internal")
authRouter := e.Group("/auth")
betaRouter := e.Group("/beta")
betaRouter.Use(rateLimiter())

authRouter.Add(http.MethodPost, "/signup", authSvc.SignUp)
authRouter.Add(http.MethodPost, "/signin", authSvc.SignIn)
authRouter.Add(http.MethodPost, "/token", authSvc.SignIn)

betaRouter.Add(http.MethodPost, "/register", localCache.RegisterForBeta)
betaRouter.Add(http.MethodGet, "/register", localCache.GetAllEmail)

internal.Add(http.MethodGet, "/metadata", localCache.Metadata)
internal.Add(http.MethodGet, "/digests", localCache.LayerDigests)

v2Router := e.Group("/v2")
nsRouter := v2Router.Group("/:username/:imagename")
nsRouter.Use(BasicAuth(authSvc.BasicAuth))

v2Router.Add(http.MethodGet, "/_catalog", reg.Catalog, BasicAuth(authSvc.BasicAuth))
// ALL THE HEAD METHODS //
// HEAD /v2/<name>/blobs/<digest>
// (LayerExists) should be called reference/digest
nsRouter.Add(http.MethodHead, "/blobs/:digest", reg.LayerExists)

// HEAD /v2/<name>/manifests/<reference>
// should be called reference/digest
nsRouter.Add(http.MethodHead, "/manifests/:reference", reg.ManifestExists)

// ALL THE PUT METHODS
// PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest>
// router.Add(http.MethodPut, "/blobs/uploads/:uuid", reg.MonolithicUpload)

nsRouter.Add(http.MethodPut, "/blobs/uploads/", reg.CompleteUpload)

// PUT /v2/<name>/blobs/uploads/<uuid>?digest=<digest>
nsRouter.Add(http.MethodPut, "/blobs/uploads/:uuid", reg.CompleteUpload)

// PUT /v2/<name>/manifests/<reference>
nsRouter.Add(http.MethodPut, "/manifests/:reference", reg.PushManifest)

// POST METHODS
// POST /v2/<name>/blobs/uploads/
nsRouter.Add(http.MethodPost, "/blobs/uploads/", reg.StartUpload)

// POST /v2/<name>/blobs/uploads/
nsRouter.Add(http.MethodPost, "/blobs/uploads/:uuid", reg.PushLayer)

// PATCH

// PATCH /v2/<name>/blobs/uploads/<uuid>
nsRouter.Add(http.MethodPatch, "/blobs/uploads/:uuid", reg.ChunkedUpload)
// router.Add(http.MethodPatch, "/blobs/uploads/", reg.ChunkedUpload)

// GET
// GET /v2/<name>/manifests/<reference>
nsRouter.Add(http.MethodGet, "/manifests/:reference", reg.PullManifest)

// GET /v2/<name>/blobs/<digest>
nsRouter.Add(http.MethodGet, "/blobs/:digest", reg.PullLayer)

// GET GET /v2/<name>/blobs/uploads/<uuid>
nsRouter.Add(http.MethodGet, "/blobs/uploads/:uuid", reg.UploadProgress)

// router.Add(http.MethodGet, "/blobs/:digest", reg.DownloadBlob)

e.Add(http.MethodGet, "/v2/", reg.ApiVersion, BasicAuth(authSvc.BasicAuth))

///GET /v2/<name>/tags/list
nsRouter.Add(http.MethodGet, "/tags/list", reg.ListTags)

/// mf/sha -> mf/latest
nsRouter.Add(http.MethodDelete, "/blobs/:digest", reg.DeleteLayer)
nsRouter.Add(http.MethodDelete, "/manifests/:reference", reg.DeleteTagOrManifest)

router.Register(e, reg, authSvc, localCache)
log.Println(e.Start(cfg.Address()))
}

func setupLogger() zerolog.Logger {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
zerolog.SetGlobalLevel(zerolog.DebugLevel)
l := zerolog.New(os.Stdout)
l.With().Caller().Logger()

return l
}

//when we use JWT
/*AuthMiddleware
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
Docker-Distribution-Api-Version: registry/2.0
Www-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",
scope="repository:samalba/my-app:pull,push"
Date: Thu, 10 Sep 2015 19:32:31 GMT
Content-Length: 235
Strict-Transport-Security: max-age=31536000
{"errors":[{"code":"UNAUTHORIZED","message":"","detail":}]}
*/
//var wwwAuthenticate = `Bearer realm="http://0.0.0.0:5000/auth/token",
//service="http://0.0.0.0:5000",scope="repository:%s`

func BasicAuth(authfn func(string, string) (map[string]interface{}, error)) echo.MiddlewareFunc {
return middleware.BasicAuth(func(username string, password string, ctx echo.Context) (bool, error) {

if ctx.Request().RequestURI != "/v2/" {
if ctx.Request().Method == http.MethodHead || ctx.Request().Method == http.MethodGet {
return true, nil
}
}

if ctx.Request().RequestURI == "/v2/" {
_, err := authfn(username, password)
if err != nil {
return false, ctx.NoContent(http.StatusUnauthorized)
}
return true, nil
}

usernameFromNameSpace := ctx.Param("username")
if usernameFromNameSpace != username {
var errMsg registry.RegistryErrors
errMsg.Errors = append(errMsg.Errors, registry.RegistryError{
Code: registry.RegistryErrorCodeDenied,
Message: "not authorised",
Detail: nil,
})
return false, ctx.JSON(http.StatusForbidden, errMsg)
}
resp, err := authfn(username, password)
if err != nil {
return false, err
}

ctx.Set("basic_auth", resp)
return true, nil
})
}

func echoLogger() echo.MiddlewareFunc {
logFmt := "method=${method}, uri=${uri}, status=${status} " +
"latency=${latency}, bytes_in=${bytes_in}, bytes_out=${bytes_out}\n"

return middleware.LoggerWithConfig(middleware.LoggerConfig{
Skipper: func(echo.Context) bool {
return false
},
Format: logFmt,
Output: os.Stdout,
})
}

func rateLimiter() echo.MiddlewareFunc {
storeConfig := middleware.RateLimiterMemoryStoreConfig{
Rate: 3,
Burst: 0,
ExpiresIn: time.Hour * 10,
}

return middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
Skipper: middleware.DefaultSkipper,
BeforeFunc: nil,
IdentifierExtractor: func(ctx echo.Context) (string, error) {
return ctx.RealIP(), nil
},
Store: middleware.NewRateLimiterMemoryStoreWithConfig(storeConfig),
ErrorHandler: func(ctx echo.Context, err error) error {
return ctx.JSON(http.StatusForbidden, echo.Map{"error": "Too many requests, try after some time!"})
},
DenyHandler: func(ctx echo.Context, identifier string, err error) error {
return ctx.JSON(http.StatusForbidden, echo.Map{"error": "Too many requests, try after some time!"})
},
})
}
31 changes: 31 additions & 0 deletions ratelimiter/ratelimiter.go
Original file line number Diff line number Diff line change
@@ -1 +1,32 @@
package ratelimiter

import (
"net/http"
"time"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func New() echo.MiddlewareFunc {
storeConfig := middleware.RateLimiterMemoryStoreConfig{
Rate: 3,
Burst: 0,
ExpiresIn: time.Hour * 10,
}

return middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
Skipper: middleware.DefaultSkipper,
BeforeFunc: nil,
IdentifierExtractor: func(ctx echo.Context) (string, error) {
return ctx.RealIP(), nil
},
Store: middleware.NewRateLimiterMemoryStoreWithConfig(storeConfig),
ErrorHandler: func(ctx echo.Context, err error) error {
return ctx.JSON(http.StatusForbidden, echo.Map{"error": "Too many requests, try after some time!"})
},
DenyHandler: func(ctx echo.Context, identifier string, err error) error {
return ctx.JSON(http.StatusForbidden, echo.Map{"error": "Too many requests, try after some time!"})
},
})
}
Loading

0 comments on commit 59ec472

Please sign in to comment.