Skip to content

Commit

Permalink
Merge pull request #490 from containerish/feat/p2p-image-distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
guacamole authored Nov 26, 2023
2 parents 0562733 + 59bfeaf commit 01e33ed
Show file tree
Hide file tree
Showing 27 changed files with 1,057 additions and 222 deletions.
8 changes: 5 additions & 3 deletions auth/basic_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (a *auth) BasicAuthWithConfig() echo.MiddlewareFunc {
// Need to return `401` for browsers to pop-up login box.
ctx.Response().Header().Set(echo.HeaderWWWAuthenticate, headerValue)
echoErr := ctx.NoContent(http.StatusUnauthorized)
a.logger.Log(ctx, nil).Send()
a.logger.Log(ctx, nil).Str(echo.HeaderWWWAuthenticate, headerValue).Send()
return echoErr
}
}
Expand All @@ -112,7 +112,7 @@ func (a *auth) BasicAuthValidator(username string, password string, ctx echo.Con

_, err := a.validateUser(username, password)
usernameFromReq := ctx.Param("username")
if err != nil || usernameFromReq != username {
if err != nil || usernameFromReq != username || usernameFromReq == types.RepositoryNameIPFS {
var errMsg registry.RegistryErrors
errMsg.Errors = append(errMsg.Errors, registry.RegistryError{
Code: registry.RegistryErrorCodeUnauthorized,
Expand Down Expand Up @@ -146,10 +146,12 @@ func (a *auth) SkipBasicAuth(ctx echo.Context) bool {
Send()
return true
}
repoName := ctx.Param("imagename")

readOp := ctx.Request().Method == http.MethodHead || ctx.Request().Method == http.MethodGet
// if it's a read operation on a public repository, we skip auth requirement
if readOp && repo != nil && repo.Visibility == types.RepositoryVisibilityPublic {
if repoName == types.RepositoryNameIPFS ||
(readOp && repo != nil && repo.Visibility == types.RepositoryVisibilityPublic) {
a.logger.Debug().
Bool("skip_basic_auth", true).
Str("method", ctx.Request().Method).
Expand Down
28 changes: 19 additions & 9 deletions auth/jwt_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ func (a *auth) JWT() echo.MiddlewareFunc {
return false
}

skip := readOp && repo.Visibility == types.RepositoryVisibilityPublic
repoName := ctx.Param("imagename")

skip := (readOp && repo.Visibility == types.RepositoryVisibilityPublic) ||
repoName == types.RepositoryNameIPFS
if skip {
a.logger.Debug().
Bool("skip_jwt_middleware", true).
Expand Down Expand Up @@ -69,12 +72,20 @@ func (a *auth) JWT() echo.MiddlewareFunc {
},
ContinueOnIgnoredError: false,
SuccessHandler: func(ctx echo.Context) {

if token, tokenOk := ctx.Get("user").(*jwt.Token); tokenOk {
claims, claimsOk := token.Claims.(*Claims)
if claimsOk {
userId := uuid.MustParse(claims.ID)
user, err := a.pgStore.GetUserByID(ctx.Request().Context(), userId)
usernameFromReq := ctx.Param("username")
var (
user *types.User
err error
)
if usernameFromReq == types.RepositoryNameIPFS {
user, err = a.pgStore.GetIPFSUser(ctx.Request().Context())
} else {
user, err = a.pgStore.GetUserByID(ctx.Request().Context(), userId)
}
if err == nil {
ctx.Set(string(types.UserContextKey), user)
}
Expand All @@ -97,13 +108,13 @@ func (a *auth) JWT() echo.MiddlewareFunc {

// ACL implies a basic Access Control List on protected resources
func (a *auth) ACL() echo.MiddlewareFunc {
return func(hf echo.HandlerFunc) echo.HandlerFunc {
return func(handler echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
ctx.Set(types.HandlerStartTime, time.Now())

m := ctx.Request().Method
if m == http.MethodGet || m == http.MethodHead {
return hf(ctx)
return handler(ctx)
}

token, ok := ctx.Get("user").(*jwt.Token)
Expand All @@ -120,8 +131,7 @@ func (a *auth) ACL() echo.MiddlewareFunc {
return echoErr
}

username := ctx.Param("username")

usernameFromReq := ctx.Param("username")
userId, err := uuid.Parse(claims.ID)
if err != nil {
echoErr := ctx.JSON(http.StatusBadRequest, echo.Map{
Expand All @@ -138,8 +148,8 @@ func (a *auth) ACL() echo.MiddlewareFunc {
a.logger.Log(ctx, err).Send()
return echoErr
}
if user.Username == username {
return hf(ctx)
if usernameFromReq == user.Username || usernameFromReq == types.RepositoryNameIPFS {
return handler(ctx)
}

return ctx.NoContent(http.StatusUnauthorized)
Expand Down
8 changes: 5 additions & 3 deletions auth/oci_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ func ParseOCITokenPermissionRequest(url *url.URL) (types.OCITokenPermissonClaimL
return nil, fmt.Errorf("ParseOCITokenPermissionRequest: invalid scope")
}

scopeType, scopeName := scopeParts[0], scopeParts[1]

claim := &types.OCITokenPermissonClaim{
Type: scopeParts[0], // this is usually "repository"
Name: scopeParts[1], // this is the registry namespace eg: johndoe/ubuntu
Type: scopeType, // this is usually "repository"
Name: scopeName, // this is the registry namespace eg: johndoe/ubuntu
Actions: strings.Split(scopeParts[2], ","), // request action on a resource (push/pull)
}
claimList = append(claimList, claim)
Expand Down Expand Up @@ -149,7 +151,7 @@ func (a *auth) Token(ctx echo.Context) error {
},
)
echoErr := ctx.JSONBlob(http.StatusBadRequest, registryErr.Bytes())
a.logger.Log(ctx, registryErr).Send()
a.logger.Log(ctx, registryErr).Any("scopes", scopes).Send()
return echoErr
}

Expand Down
6 changes: 3 additions & 3 deletions auth/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ func (a *auth) RepositoryPermissionsMiddleware() echo.MiddlewareFunc {
return echoErr
}

username := strings.Split(namespace, "/")[0]

usernameFromReq := strings.Split(namespace, "/")[0]
repository, err := a.registryStore.GetRepositoryByNamespace(ctx.Request().Context(), namespace)
if err == nil {
if repository.Visibility == types.RepositoryVisibilityPublic {
Expand All @@ -116,7 +115,7 @@ func (a *auth) RepositoryPermissionsMiddleware() echo.MiddlewareFunc {
}

user, ok := ctx.Get(string(types.UserContextKey)).(*types.User)
if !ok || user.Username != username {
if (!ok || user.Username != usernameFromReq) && usernameFromReq != types.RepositoryNameIPFS {
errMsg := common.RegistryErrorResponse(
registry.RegistryErrorCodeUnauthorized,
"access to this resource is restricted, please login or check with the repository owner",
Expand All @@ -129,6 +128,7 @@ func (a *auth) RepositoryPermissionsMiddleware() echo.MiddlewareFunc {
return echoErr
}

a.logger.Log(ctx, nil).Send()
return handler(ctx)
}
}
Expand Down
1 change: 1 addition & 0 deletions auth/reset_password.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ func (a *auth) ResetPassword(ctx echo.Context) error {
a.logger.Log(ctx, err).Send()
return echoErr
}

user, err := a.pgStore.GetUserByID(ctx.Request().Context(), userId)
if err != nil {
echoErr := ctx.JSON(http.StatusNotFound, echo.Map{
Expand Down
10 changes: 10 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type (
DFS struct {
Storj Storj `yaml:"storj" mapstructure:"storj"`
Filebase S3CompatibleDFS `yaml:"filebase" mapstructure:"filebase"`
Ipfs IpfsDFS `yaml:"ipfs" mapstructure:"ipfs"`
Mock S3CompatibleDFS `yaml:"mock" mapstructure:"mock"`
}

Expand Down Expand Up @@ -185,6 +186,15 @@ type (
Otel Otel `yaml:"otel" mapstructure:"otel"`
Enabled bool `yaml:"enabled" mapstructure:"enabled"`
}

IpfsDFS struct {
RPCMultiAddr string `yaml:"rpc_multi_addr" mapstructure:"rpc_multi_addr"`
GatewayEndpoint string `yaml:"gateway_endpoint" mapstructure:"gateway_endpoint"`
Type string `yaml:"type" mapstructure:"type"`
Enabled bool `yaml:"enabled" mapstructure:"enabled"`
Local bool `yaml:"local" mapstructure:"local"`
Pinning bool `yaml:"pinning" mapstructure:"pinning"`
}
)

const (
Expand Down
6 changes: 6 additions & 0 deletions dfs/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/containerish/OpenRegistry/config"
"github.com/containerish/OpenRegistry/dfs"
"github.com/containerish/OpenRegistry/dfs/filebase"
"github.com/containerish/OpenRegistry/dfs/ipfs/p2p"
"github.com/containerish/OpenRegistry/dfs/mock"
"github.com/containerish/OpenRegistry/dfs/storj"
"github.com/containerish/OpenRegistry/dfs/storj/uplink"
Expand All @@ -30,6 +31,11 @@ func NewDFSBackend(env config.Environment, registryEndpoint string, cfg *config.
return uplink.New(env, &cfg.Storj)
}

if cfg.Ipfs.Enabled {
color.Green("Storage backend: IPFS in P2P mode")
return p2p.New(&cfg.Ipfs)
}

if cfg.Mock.Enabled {
return mock.NewMockStorage(env, registryEndpoint, &cfg.Mock)
}
Expand Down
2 changes: 1 addition & 1 deletion dfs/dfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type DFS interface {
DownloadDir(dfsLink, dir string) error
List(path string) ([]*types.Metadata, error)
AddImage(ns string, mf, l map[string][]byte) (string, error)
Metadata(dfsLink string) (*types.ObjectMetadata, error)
Metadata(layer *types.ContainerImageLayer) (*types.ObjectMetadata, error)
GetUploadProgress(identifier, uploadID string) (*types.ObjectMetadata, error)
AbortMultipartUpload(ctx context.Context, layerKey string, uploadId string) error
GeneratePresignedURL(ctx context.Context, key string) (string, error)
Expand Down
4 changes: 3 additions & 1 deletion dfs/filebase/filebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/containerish/OpenRegistry/config"
"github.com/containerish/OpenRegistry/dfs"
"github.com/containerish/OpenRegistry/store/v1/types"
core_types "github.com/containerish/OpenRegistry/types"
oci_digest "github.com/opencontainers/go-digest"
)

Expand Down Expand Up @@ -220,10 +221,11 @@ func (fb *filebase) AddImage(ns string, mf, l map[string][]byte) (string, error)
// Metadata API returns the HEADERS for an object. This object can be a manifest or a layer.
// This API is usually a little behind when it comes to fetching the details for an uploaded object.
// This is why we put it in a retry loop and break it as soon as we get the data
func (fb *filebase) Metadata(identifier string) (*types.ObjectMetadata, error) {
func (fb *filebase) Metadata(layer *types.ContainerImageLayer) (*types.ObjectMetadata, error) {
var resp *s3.HeadObjectOutput
var err error

identifier := core_types.GetLayerIdentifier(layer.ID)
for i := 3; i > 0; i-- {
resp, err = fb.client.HeadObject(context.Background(), &s3.HeadObjectInput{
Bucket: &fb.bucket,
Expand Down
Loading

0 comments on commit 01e33ed

Please sign in to comment.