2019-06-20 18:36:40 -05:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
2021-04-22 14:18:33 -05:00
|
|
|
"time"
|
2019-06-20 18:36:40 -05:00
|
|
|
|
2019-08-27 17:01:29 -05:00
|
|
|
"github.com/anuvu/zot/errors"
|
2021-06-08 15:11:18 -05:00
|
|
|
"github.com/anuvu/zot/pkg/api/config"
|
2020-10-14 16:47:20 -05:00
|
|
|
ext "github.com/anuvu/zot/pkg/extensions"
|
2021-10-15 10:05:00 -05:00
|
|
|
"github.com/anuvu/zot/pkg/extensions/monitoring"
|
2019-11-25 17:33:58 -05:00
|
|
|
"github.com/anuvu/zot/pkg/log"
|
2019-06-20 18:36:40 -05:00
|
|
|
"github.com/anuvu/zot/pkg/storage"
|
2021-07-16 22:53:05 -05:00
|
|
|
"github.com/anuvu/zot/pkg/storage/s3"
|
2019-11-25 17:33:58 -05:00
|
|
|
"github.com/gorilla/handlers"
|
2019-07-10 00:23:59 -05:00
|
|
|
"github.com/gorilla/mux"
|
2021-07-16 22:53:05 -05:00
|
|
|
|
|
|
|
"github.com/docker/distribution/registry/storage/driver/factory"
|
2019-06-20 18:36:40 -05:00
|
|
|
)
|
|
|
|
|
2021-04-22 14:18:33 -05:00
|
|
|
const (
|
|
|
|
idleTimeout = 120 * time.Second
|
|
|
|
)
|
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
type Controller struct {
|
2021-06-08 15:11:18 -05:00
|
|
|
Config *config.Config
|
2021-04-05 19:40:33 -05:00
|
|
|
Router *mux.Router
|
|
|
|
StoreController storage.StoreController
|
|
|
|
Log log.Logger
|
2021-05-25 03:38:21 -05:00
|
|
|
Audit *log.Logger
|
2021-04-05 19:40:33 -05:00
|
|
|
Server *http.Server
|
2021-10-15 10:05:00 -05:00
|
|
|
Metrics monitoring.MetricServer
|
2019-06-20 18:36:40 -05:00
|
|
|
}
|
|
|
|
|
2021-06-08 15:11:18 -05:00
|
|
|
func NewController(config *config.Config) *Controller {
|
2021-05-25 03:38:21 -05:00
|
|
|
var controller Controller
|
|
|
|
|
|
|
|
logger := log.NewLogger(config.Log.Level, config.Log.Output)
|
|
|
|
|
|
|
|
controller.Config = config
|
|
|
|
controller.Log = logger
|
|
|
|
|
|
|
|
if config.Log.Audit != "" {
|
|
|
|
audit := log.NewAuditLogger(config.Log.Level, config.Log.Audit)
|
|
|
|
controller.Audit = audit
|
|
|
|
}
|
|
|
|
|
|
|
|
return &controller
|
2019-06-20 18:36:40 -05:00
|
|
|
}
|
|
|
|
|
2021-05-05 17:31:56 -05:00
|
|
|
func DefaultHeaders() mux.MiddlewareFunc {
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// CORS
|
|
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
|
|
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
|
|
|
|
|
|
|
|
// handle the request
|
|
|
|
next.ServeHTTP(w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-16 22:53:05 -05:00
|
|
|
// nolint: gocyclo
|
2019-06-20 18:36:40 -05:00
|
|
|
func (c *Controller) Run() error {
|
2019-08-15 11:34:54 -05:00
|
|
|
// validate configuration
|
|
|
|
if err := c.Config.Validate(c.Log); err != nil {
|
|
|
|
c.Log.Error().Err(err).Msg("configuration validation failed")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// print the current configuration, but strip secrets
|
|
|
|
c.Log.Info().Interface("params", c.Config.Sanitize()).Msg("configuration settings")
|
|
|
|
|
2019-07-10 00:23:59 -05:00
|
|
|
engine := mux.NewRouter()
|
2021-05-05 17:31:56 -05:00
|
|
|
engine.Use(DefaultHeaders(),
|
2021-10-15 10:05:00 -05:00
|
|
|
SessionLogger(c),
|
2021-05-05 17:31:56 -05:00
|
|
|
handlers.RecoveryHandler(handlers.RecoveryLogger(c.Log),
|
|
|
|
handlers.PrintRecoveryStack(false)))
|
2019-12-13 00:53:18 -05:00
|
|
|
|
2021-05-25 03:38:21 -05:00
|
|
|
if c.Audit != nil {
|
2021-10-15 10:05:00 -05:00
|
|
|
engine.Use(SessionAuditLogger(c.Audit))
|
2021-05-25 03:38:21 -05:00
|
|
|
}
|
|
|
|
|
2021-04-05 19:40:33 -05:00
|
|
|
c.Router = engine
|
|
|
|
c.Router.UseEncodedPath()
|
|
|
|
|
2021-10-15 10:05:00 -05:00
|
|
|
var enabled bool
|
|
|
|
if c.Config != nil &&
|
|
|
|
c.Config.Extensions != nil &&
|
|
|
|
c.Config.Extensions.Metrics != nil &&
|
|
|
|
c.Config.Extensions.Metrics.Enable {
|
|
|
|
enabled = true
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Metrics = monitoring.NewMetricsServer(enabled, c.Log)
|
2021-04-05 19:40:33 -05:00
|
|
|
c.StoreController = storage.StoreController{}
|
|
|
|
|
|
|
|
if c.Config.Storage.RootDirectory != "" {
|
|
|
|
if c.Config.Storage.Dedupe {
|
|
|
|
err := storage.ValidateHardLink(c.Config.Storage.RootDirectory)
|
|
|
|
if err != nil {
|
|
|
|
c.Log.Warn().Msg("input storage root directory filesystem does not supports hardlinking," +
|
|
|
|
"disabling dedupe functionality")
|
|
|
|
|
|
|
|
c.Config.Storage.Dedupe = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-16 22:53:05 -05:00
|
|
|
var defaultStore storage.ImageStore
|
|
|
|
if len(c.Config.Storage.StorageDriver) == 0 {
|
|
|
|
defaultStore = storage.NewImageStore(c.Config.Storage.RootDirectory,
|
|
|
|
c.Config.Storage.GC, c.Config.Storage.Dedupe, c.Log, c.Metrics)
|
|
|
|
} else {
|
|
|
|
storeName := fmt.Sprintf("%v", c.Config.Storage.StorageDriver["name"])
|
|
|
|
if storeName != storage.S3StorageDriverName {
|
|
|
|
c.Log.Fatal().Err(errors.ErrBadConfig).Msgf("unsupported storage driver: %s",
|
|
|
|
c.Config.Storage.StorageDriver["name"])
|
|
|
|
}
|
|
|
|
// Init a Storager from connection string.
|
|
|
|
store, err := factory.Create(storeName, c.Config.Storage.StorageDriver)
|
|
|
|
if err != nil {
|
|
|
|
c.Log.Error().Err(err).Str("rootDir", c.Config.Storage.RootDirectory).Msg("unable to create s3 service")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultStore = s3.NewImageStore(c.Config.Storage.RootDirectory,
|
|
|
|
c.Config.Storage.GC, c.Config.Storage.Dedupe, c.Log, c.Metrics, store)
|
|
|
|
}
|
2021-04-05 19:40:33 -05:00
|
|
|
|
|
|
|
c.StoreController.DefaultStore = defaultStore
|
|
|
|
|
|
|
|
// Enable extensions if extension config is provided
|
|
|
|
if c.Config != nil && c.Config.Extensions != nil {
|
2021-06-08 15:11:18 -05:00
|
|
|
ext.EnableExtensions(c.Config, c.Log, c.Config.Storage.RootDirectory)
|
2021-04-05 19:40:33 -05:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// we can't proceed without global storage
|
|
|
|
c.Log.Error().Err(errors.ErrImgStoreNotFound).Msg("controller: no storage config provided")
|
|
|
|
|
|
|
|
return errors.ErrImgStoreNotFound
|
2020-02-17 16:57:15 -05:00
|
|
|
}
|
|
|
|
|
2021-04-05 19:40:33 -05:00
|
|
|
if c.Config.Storage.SubPaths != nil {
|
|
|
|
if len(c.Config.Storage.SubPaths) > 0 {
|
|
|
|
subPaths := c.Config.Storage.SubPaths
|
|
|
|
|
2021-09-30 08:27:13 -05:00
|
|
|
subImageStore := make(map[string]storage.ImageStore)
|
2021-04-05 19:40:33 -05:00
|
|
|
|
|
|
|
// creating image store per subpaths
|
|
|
|
for route, storageConfig := range subPaths {
|
|
|
|
if storageConfig.Dedupe {
|
|
|
|
err := storage.ValidateHardLink(storageConfig.RootDirectory)
|
|
|
|
if err != nil {
|
|
|
|
c.Log.Warn().Msg("input storage root directory filesystem does not supports hardlinking, " +
|
|
|
|
"disabling dedupe functionality")
|
|
|
|
|
|
|
|
storageConfig.Dedupe = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-16 22:53:05 -05:00
|
|
|
if len(storageConfig.StorageDriver) == 0 {
|
|
|
|
subImageStore[route] = storage.NewImageStore(storageConfig.RootDirectory,
|
|
|
|
storageConfig.GC, storageConfig.Dedupe, c.Log, c.Metrics)
|
|
|
|
} else {
|
|
|
|
storeName := fmt.Sprintf("%v", storageConfig.StorageDriver["name"])
|
|
|
|
if storeName != storage.S3StorageDriverName {
|
|
|
|
c.Log.Fatal().Err(errors.ErrBadConfig).Msgf("unsupported storage driver: %s", storageConfig.StorageDriver["name"])
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init a Storager from connection string.
|
|
|
|
store, err := factory.Create(storeName, storageConfig.StorageDriver)
|
|
|
|
if err != nil {
|
|
|
|
c.Log.Error().Err(err).Str("rootDir", storageConfig.RootDirectory).Msg("Unable to create s3 service")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
subImageStore[route] = s3.NewImageStore(storageConfig.RootDirectory,
|
|
|
|
storageConfig.GC, storageConfig.Dedupe, c.Log, c.Metrics, store)
|
|
|
|
}
|
2021-04-05 19:40:33 -05:00
|
|
|
|
|
|
|
// Enable extensions if extension config is provided
|
|
|
|
if c.Config != nil && c.Config.Extensions != nil {
|
2021-06-08 15:11:18 -05:00
|
|
|
ext.EnableExtensions(c.Config, c.Log, storageConfig.RootDirectory)
|
2021-04-05 19:40:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c.StoreController.SubStore = subImageStore
|
|
|
|
}
|
2020-06-25 03:21:47 -05:00
|
|
|
}
|
|
|
|
|
2021-10-15 10:05:00 -05:00
|
|
|
monitoring.SetServerInfo(c.Metrics, c.Config.Commit, c.Config.BinaryType, c.Config.GoVersion, c.Config.Version)
|
2019-06-20 18:36:40 -05:00
|
|
|
_ = NewRouteHandler(c)
|
|
|
|
|
|
|
|
addr := fmt.Sprintf("%s:%s", c.Config.HTTP.Address, c.Config.HTTP.Port)
|
2021-04-22 14:18:33 -05:00
|
|
|
server := &http.Server{
|
|
|
|
Addr: addr,
|
|
|
|
Handler: c.Router,
|
|
|
|
IdleTimeout: idleTimeout,
|
|
|
|
}
|
2019-06-20 18:36:40 -05:00
|
|
|
c.Server = server
|
|
|
|
|
|
|
|
// Create the listener
|
|
|
|
l, err := net.Listen("tcp", addr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-08-15 11:34:54 -05:00
|
|
|
if c.Config.HTTP.TLS != nil && c.Config.HTTP.TLS.Key != "" && c.Config.HTTP.TLS.Cert != "" {
|
2019-06-20 18:36:40 -05:00
|
|
|
if c.Config.HTTP.TLS.CACert != "" {
|
2019-08-28 16:05:16 -05:00
|
|
|
clientAuth := tls.VerifyClientCertIfGiven
|
2019-08-15 11:34:54 -05:00
|
|
|
if (c.Config.HTTP.Auth == nil || c.Config.HTTP.Auth.HTPasswd.Path == "") && !c.Config.HTTP.AllowReadAccess {
|
2019-08-28 16:05:16 -05:00
|
|
|
clientAuth = tls.RequireAndVerifyClientCert
|
|
|
|
}
|
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
caCert, err := ioutil.ReadFile(c.Config.HTTP.TLS.CACert)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2019-12-13 00:53:18 -05:00
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
caCertPool := x509.NewCertPool()
|
2019-12-13 00:53:18 -05:00
|
|
|
|
2019-08-27 17:01:29 -05:00
|
|
|
if !caCertPool.AppendCertsFromPEM(caCert) {
|
|
|
|
panic(errors.ErrBadCACert)
|
|
|
|
}
|
2019-12-13 00:53:18 -05:00
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
server.TLSConfig = &tls.Config{
|
2019-08-27 17:01:29 -05:00
|
|
|
ClientAuth: clientAuth,
|
|
|
|
ClientCAs: caCertPool,
|
|
|
|
PreferServerCipherSuites: true,
|
|
|
|
MinVersion: tls.VersionTLS12,
|
2019-06-20 18:36:40 -05:00
|
|
|
}
|
2020-05-11 17:13:24 -05:00
|
|
|
server.TLSConfig.BuildNameToCertificate() // nolint: staticcheck
|
2019-06-20 18:36:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return server.ServeTLS(l, c.Config.HTTP.TLS.Cert, c.Config.HTTP.TLS.Key)
|
|
|
|
}
|
2019-12-13 00:53:18 -05:00
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
return server.Serve(l)
|
|
|
|
}
|