Error and Audit logger webhooks (#1855)

Similar to MinIO now it's possible to configure webhooks to log all
triggered errors and incomming requests via env variables:

```
CONSOLE_LOGGER_WEBHOOK_ENABLE_<ID>
CONSOLE_LOGGER_WEBHOOK_ENDPOINT_<ID>
CONSOLE_LOGGER_WEBHOOK_AUTH_TOKEN_<ID>
CONSOLE_LOGGER_WEBHOOK_CLIENT_CERT_<ID>
CONSOLE_LOGGER_WEBHOOK_CLIENT_KEY_<ID>
CONSOLE_LOGGER_WEBHOOK_QUEUE_SIZE_<ID>

CONSOLE_AUDIT_WEBHOOK_ENABLE_<ID>
CONSOLE_AUDIT_WEBHOOK_ENDPOINT_<ID>
CONSOLE_AUDIT_WEBHOOK_AUTH_TOKEN_<ID>
CONSOLE_AUDIT_WEBHOOK_CLIENT_CERT_<ID>
CONSOLE_AUDIT_WEBHOOK_QUEUE_SIZE_<ID>
```

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Lenin Alevski
2022-04-28 12:55:06 -07:00
committed by GitHub
parent 8c18829089
commit 566fb27fc1
100 changed files with 4959 additions and 1738 deletions

1
.gitignore vendored
View File

@@ -19,6 +19,7 @@ vendor/
# Ignore executables
target/
!pkg/logger/target/
console
!console/

View File

@@ -23,6 +23,8 @@ import (
"strings"
"time"
xhttp "github.com/minio/console/pkg/http"
"github.com/minio/console/pkg/utils"
"github.com/minio/pkg/env"
@@ -68,7 +70,7 @@ func GetMinioImage() (*string, error) {
return &image, nil
}
latestMinIOImage, errLatestMinIOImage := utils.GetLatestMinIOImage(
&utils.HTTPClient{
&xhttp.Client{
Client: &http.Client{
Timeout: 5 * time.Second,
},

View File

@@ -20,10 +20,14 @@
package main
import (
"context"
"fmt"
"os"
"strconv"
"time"
"github.com/minio/console/pkg/logger"
"github.com/minio/cli"
"github.com/minio/console/restapi"
)
@@ -36,15 +40,28 @@ var appCmds = []cli.Command{
// StartServer starts the console service
func StartServer(ctx *cli.Context) error {
if os.Getenv("CONSOLE_OPERATOR_MODE") != "" && os.Getenv("CONSOLE_OPERATOR_MODE") == "on" {
return startOperatorServer(ctx)
}
// Load all certificates
if err := loadAllCerts(ctx); err != nil {
// Log this as a warning and continue running console without TLS certificates
restapi.LogError("Unable to load certs: %v", err)
}
xctx := context.Background()
transport := restapi.PrepareSTSClientTransport(false)
if err := logger.InitializeLogger(xctx, transport); err != nil {
fmt.Println("error InitializeLogger", err)
logger.CriticalIf(xctx, err)
}
// custom error configuration
restapi.LogInfo = logger.Info
restapi.LogError = logger.Error
restapi.LogIf = logger.LogIf
if os.Getenv("CONSOLE_OPERATOR_MODE") != "" && os.Getenv("CONSOLE_OPERATOR_MODE") == "on" {
return startOperatorServer(ctx)
}
var rctx restapi.Context
if err := rctx.Load(ctx); err != nil {
restapi.LogError("argument validation failed: %v", err)

View File

@@ -20,9 +20,13 @@
package main
import (
"context"
"fmt"
"strconv"
"time"
"github.com/minio/console/pkg/logger"
"github.com/minio/cli"
"github.com/minio/console/restapi"
)
@@ -39,6 +43,17 @@ func StartServer(ctx *cli.Context) error {
restapi.LogError("Unable to load certs: %v", err)
}
xctx := context.Background()
transport := restapi.PrepareSTSClientTransport(false)
if err := logger.InitializeLogger(xctx, transport); err != nil {
fmt.Println("error InitializeLogger", err)
logger.CriticalIf(xctx, err)
}
// custom error configuration
restapi.LogInfo = logger.Info
restapi.LogError = logger.Error
restapi.LogIf = logger.LogIf
var rctx restapi.Context
if err := rctx.Load(ctx); err != nil {
restapi.LogError("argument validation failed: %v", err)

View File

@@ -2,7 +2,7 @@
// +build operator
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -20,6 +20,7 @@
package main
import (
"context"
"fmt"
"io/ioutil"
"path/filepath"
@@ -27,6 +28,8 @@ import (
"syscall"
"time"
"github.com/minio/console/pkg/logger"
"github.com/minio/console/restapi"
"github.com/go-openapi/loads"
@@ -106,7 +109,7 @@ func buildOperatorServer() (*operatorapi.Server, error) {
}
api := operations.NewOperatorAPI(swaggerSpec)
api.Logger = operatorapi.LogInfo
api.Logger = restapi.LogInfo
server := operatorapi.NewServer(api)
parser := flags.NewParser(server, flags.Default)
@@ -147,7 +150,7 @@ func loadOperatorAllCerts(ctx *cli.Context) error {
}
// load the certificates and the CAs
operatorapi.GlobalRootCAs, operatorapi.GlobalPublicCerts, operatorapi.GlobalTLSCertsManager, err = certs.GetAllCertificatesAndCAs()
restapi.GlobalRootCAs, restapi.GlobalPublicCerts, restapi.GlobalTLSCertsManager, err = certs.GetAllCertificatesAndCAs()
if err != nil {
return fmt.Errorf("unable to load certificates at %s: failed with %w", certs.GlobalCertsDir.Get(), err)
}
@@ -159,12 +162,12 @@ func loadOperatorAllCerts(ctx *cli.Context) error {
swaggerServerCACertificate := ctx.String("tls-ca")
// load tls cert and key from swagger server tls-certificate and tls-key flags
if swaggerServerCertificate != "" && swaggerServerCertificateKey != "" {
if err = operatorapi.GlobalTLSCertsManager.AddCertificate(swaggerServerCertificate, swaggerServerCertificateKey); err != nil {
if err = restapi.GlobalTLSCertsManager.AddCertificate(swaggerServerCertificate, swaggerServerCertificateKey); err != nil {
return err
}
x509Certs, err := certs.ParsePublicCertFile(swaggerServerCertificate)
if err == nil {
operatorapi.GlobalPublicCerts = append(operatorapi.GlobalPublicCerts, x509Certs...)
restapi.GlobalPublicCerts = append(restapi.GlobalPublicCerts, x509Certs...)
}
}
@@ -172,7 +175,7 @@ func loadOperatorAllCerts(ctx *cli.Context) error {
if swaggerServerCACertificate != "" {
caCert, caCertErr := ioutil.ReadFile(swaggerServerCACertificate)
if caCertErr == nil {
operatorapi.GlobalRootCAs.AppendCertsFromPEM(caCert)
restapi.GlobalRootCAs.AppendCertsFromPEM(caCert)
}
}
}
@@ -186,20 +189,32 @@ func loadOperatorAllCerts(ctx *cli.Context) error {
// StartServer starts the console service
func startOperatorServer(ctx *cli.Context) error {
if err := loadOperatorAllCerts(ctx); err != nil {
if err := loadAllCerts(ctx); err != nil {
// Log this as a warning and continue running console without TLS certificates
operatorapi.LogError("Unable to load certs: %v", err)
restapi.LogError("Unable to load certs: %v", err)
}
xctx := context.Background()
transport := restapi.PrepareSTSClientTransport(false)
if err := logger.InitializeLogger(xctx, transport); err != nil {
fmt.Println("error InitializeLogger", err)
logger.CriticalIf(xctx, err)
}
// custom error configuration
restapi.LogInfo = logger.Info
restapi.LogError = logger.Error
restapi.LogIf = logger.LogIf
var rctx operatorapi.Context
if err := rctx.Load(ctx); err != nil {
operatorapi.LogError("argument validation failed: %v", err)
restapi.LogError("argument validation failed: %v", err)
return err
}
server, err := buildOperatorServer()
if err != nil {
operatorapi.LogError("Unable to initialize console server: %v", err)
restapi.LogError("Unable to initialize console server: %v", err)
return err
}
@@ -212,7 +227,7 @@ func startOperatorServer(ctx *cli.Context) error {
operatorapi.Port = strconv.Itoa(server.Port)
operatorapi.Hostname = server.Host
if len(operatorapi.GlobalPublicCerts) > 0 {
if len(restapi.GlobalPublicCerts) > 0 {
// If TLS certificates are provided enforce the HTTPS schema, meaning console will redirect
// plain HTTP connections to HTTPS server
server.EnabledListeners = []string{"http", "https"}

9
go.mod
View File

@@ -6,6 +6,7 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/cheggaaa/pb/v3 v3.0.8
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.13.0
github.com/go-openapi/errors v0.20.2
github.com/go-openapi/loads v0.21.1
github.com/go-openapi/runtime v0.23.3
@@ -14,10 +15,12 @@ require (
github.com/go-openapi/swag v0.21.1
github.com/go-openapi/validate v0.21.0
github.com/golang-jwt/jwt/v4 v4.4.1
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/jessevdk/go-flags v1.5.0
github.com/klauspost/compress v1.15.1
github.com/minio/cli v1.22.0
github.com/minio/highwayhash v1.0.2
github.com/minio/kes v0.19.2
github.com/minio/madmin-go v1.3.12
github.com/minio/mc v0.0.0-20220419155441-cc4ff3a0cc82
@@ -57,7 +60,6 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 // indirect
@@ -74,7 +76,6 @@ require (
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -109,7 +110,7 @@ require (
github.com/navidys/tvxwidgets v0.1.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/philhofer/fwd v1.1.2-0.20210722190033-5c56ac6d0bb9 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/xattr v0.4.5 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
@@ -126,7 +127,7 @@ require (
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tinylib/msgp v1.1.6 // indirect
github.com/tinylib/msgp v1.1.7-0.20211026165309-e818a1881b0e // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect

10
go.sum
View File

@@ -470,6 +470,8 @@ github.com/minio/colorjson v1.0.2 h1:Em3IM68MTm3h+Oxa0nxrV9VQqDgbxvC5iq5A+pqzDeI
github.com/minio/colorjson v1.0.2/go.mod h1:JWxcL2n8T8JVf+NY6awl6kn5nK49aAzHOeQEM33dL0k=
github.com/minio/filepath v1.0.0 h1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY=
github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEXx9T/Bw=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/minio/kes v0.19.2 h1:0kdMAgLMSkiDA33k8pMHC7d6erDuseuLrZF+N3017SM=
github.com/minio/kes v0.19.2/go.mod h1:X2fMkDbAkjbSKDGOQZvyPkHxoG7nuzP6R78Jw+TzXtM=
github.com/minio/madmin-go v1.3.5/go.mod h1:vGKGboQgGIWx4DuDUaXixjlIEZOCIp6ivJkQoiVaACc=
@@ -556,8 +558,9 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/philhofer/fwd v1.1.2-0.20210722190033-5c56ac6d0bb9 h1:6ob53CVz+ja2i7easAStApZJlh7sxyq3Cm7g1Di6iqA=
github.com/philhofer/fwd v1.1.2-0.20210722190033-5c56ac6d0bb9/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -663,8 +666,8 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
github.com/tinylib/msgp v1.1.7-0.20211026165309-e818a1881b0e h1:P5tyWbssToKowBPTA1/EzqPXwrZNc8ZeNPdjgpcDEoI=
github.com/tinylib/msgp v1.1.7-0.20211026165309-e818a1881b0e/go.mod h1:g7jEyb18KPe65d9RRhGw+ThaJr5duyBH8eaFgBUor7Y=
github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
@@ -858,6 +861,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -18,15 +18,14 @@ package auth
import (
"context"
"errors"
errors "github.com/minio/console/restapi"
"github.com/minio/console/cluster"
"github.com/minio/minio-go/v7/pkg/credentials"
operatorClientset "github.com/minio/operator/pkg/client/clientset/versioned"
)
var errInvalidCredentials = errors.New("invalid Login")
// operatorCredentialsProvider is an struct to hold the JWT (service account token)
type operatorCredentialsProvider struct {
serviceAccountJWT string
@@ -86,7 +85,7 @@ func GetConsoleCredentialsForOperator(jwt string) (*credentials.Credentials, err
client: opClientClientSet,
}
if err = checkServiceAccountTokenValid(ctx, opClient); err != nil {
return nil, errInvalidCredentials
return nil, errors.ErrInvalidLogin
}
return credentials.New(operatorCredentialsProvider{serviceAccountJWT: jwt}), nil
}

View File

@@ -23,7 +23,6 @@ import (
"strings"
"github.com/klauspost/compress/gzhttp"
"github.com/minio/console/restapi"
"github.com/unrolled/secure"
@@ -58,7 +57,7 @@ func configureAPI(api *operations.OperatorAPI) http.Handler {
api.KeyAuth = func(token string, scopes []string) (*models.Principal, error) {
// we are validating the session token by decrypting the claims inside, if the operation succeed that means the jwt
// was generated and signed by us in the first place
claims, err := auth.SessionTokenAuthenticate(token)
claims, err := auth.ParseClaimsFromToken(token)
if err != nil {
api.Logger("Unable to validate the session token %s: %v", token, err)
return nil, errors.New(401, "incorrect api key auth")
@@ -101,8 +100,8 @@ func configureAPI(api *operations.OperatorAPI) http.Handler {
// The TLS configuration before HTTPS server starts.
func configureTLS(tlsConfig *tls.Config) {
tlsConfig.RootCAs = GlobalRootCAs
tlsConfig.GetCertificate = GlobalTLSCertsManager.GetCertificate
tlsConfig.RootCAs = restapi.GlobalRootCAs
tlsConfig.GetCertificate = restapi.GlobalTLSCertsManager.GetCertificate
}
// As soon as server is initialized but not run yet, this function will be called.
@@ -118,24 +117,6 @@ func setupMiddlewares(handler http.Handler) http.Handler {
return handler
}
func AuthenticationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, err := auth.GetTokenFromRequest(r)
if err != nil && err != auth.ErrNoAuthToken {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
// All handlers handle appropriately to return errors
// based on their swagger rules, we do not need to
// additionally return error here, let the next ServeHTTPs
// handle it appropriately.
if token != "" {
r.Header.Add("Authorization", "Bearer "+token)
}
next.ServeHTTP(w, r)
})
}
// proxyMiddleware adds the proxy capability
func proxyMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -150,19 +131,23 @@ func proxyMiddleware(next http.Handler) http.Handler {
// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logging and metrics.
func setupGlobalMiddleware(handler http.Handler) http.Handler {
// handle cookie or authorization header for session
next := AuthenticationMiddleware(handler)
// proxy requests
next = proxyMiddleware(next)
next := proxyMiddleware(handler)
// if audit-log is enabled console will log all incoming request
next = restapi.AuditLogMiddleware(next)
// serve static files
next = restapi.FileServerMiddleware(next)
// add information to request context
next = restapi.ContextMiddleware(next)
// handle cookie or authorization header for session
next = restapi.AuthenticationMiddleware(next)
// Secure middleware, this middleware wrap all the previous handlers and add
// HTTP security headers
secureOptions := secure.Options{
AllowedHosts: restapi.GetSecureAllowedHosts(),
AllowedHostsAreRegex: restapi.GetSecureAllowedHostsAreRegex(),
HostsProxyHeaders: restapi.GetSecureHostsProxyHeaders(),
SSLRedirect: restapi.GetTLSRedirect() == "on" && len(GlobalPublicCerts) > 0,
SSLRedirect: restapi.GetTLSRedirect() == "on" && len(restapi.GlobalPublicCerts) > 0,
SSLHost: restapi.GetSecureTLSHost(),
STSSeconds: restapi.GetSecureSTSSeconds(),
STSIncludeSubdomains: restapi.GetSecureSTSIncludeSubdomains(),

View File

@@ -1,219 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package operatorapi
import (
"errors"
"runtime"
"strings"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/madmin-go"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
)
var (
// Generic error messages
errorGeneric = errors.New("an error occurred, please try again")
errInvalidCredentials = errors.New("invalid Login")
errorGenericInvalidSession = errors.New("invalid session")
errorGenericUnauthorized = errors.New("unauthorized")
errorGenericForbidden = errors.New("forbidden")
// ErrorGenericNotFound Generic error for not found
ErrorGenericNotFound = errors.New("not found")
// Explicit error messages
errorInvalidErasureCodingValue = errors.New("invalid Erasure Coding Value")
errorUnableToGetTenantUsage = errors.New("unable to get tenant usage")
errorUnableToGetTenantLogs = errors.New("unable to get tenant logs")
errorUnableToUpdateTenantCertificates = errors.New("unable to update tenant certificates")
errorUpdatingEncryptionConfig = errors.New("unable to update encryption configuration")
errorDeletingEncryptionConfig = errors.New("error disabling tenant encryption")
errorEncryptionConfigNotFound = errors.New("encryption configuration not found")
errBucketBodyNotInRequest = errors.New("error bucket body not in request")
errBucketNameNotInRequest = errors.New("error bucket name not in request")
errGroupBodyNotInRequest = errors.New("error group body not in request")
errGroupNameNotInRequest = errors.New("error group name not in request")
errPolicyNameNotInRequest = errors.New("error policy name not in request")
errPolicyBodyNotInRequest = errors.New("error policy body not in request")
errSSENotConfigured = errors.New("error server side encryption configuration not found")
errBucketLifeCycleNotConfigured = errors.New("error bucket life cycle configuration not found")
errChangePassword = errors.New("error please check your current password")
errInvalidLicense = errors.New("invalid license key")
errLicenseNotFound = errors.New("license not found")
errAvoidSelfAccountDelete = errors.New("logged in user cannot be deleted by itself")
errAccessDenied = errors.New("access denied")
errTooManyNodes = errors.New("cannot request more nodes than what is available in the cluster")
errTooFewNodes = errors.New("there are not enough nodes in the cluster to support this tenant")
errTooFewSchedulableNodes = errors.New("there is not enough schedulable nodes to satisfy this requirement")
errFewerThanFourNodes = errors.New("at least 4 nodes are required for a tenant")
)
// prepareError receives an error object and parse it against k8sErrors, returns the right error code paired with a generic error message
func prepareError(err ...error) *models.Error {
errorCode := int32(500)
errorMessage := errorGeneric.Error()
if len(err) > 0 {
frame := getFrame(2)
fileParts := strings.Split(frame.File, "/")
LogError("original error -> (%s:%d: %v)", fileParts[len(fileParts)-1], frame.Line, err[0])
if k8sErrors.IsUnauthorized(err[0]) {
errorCode = 401
errorMessage = errorGenericUnauthorized.Error()
}
if k8sErrors.IsForbidden(err[0]) {
errorCode = 403
errorMessage = errorGenericForbidden.Error()
}
if k8sErrors.IsNotFound(err[0]) {
errorCode = 404
errorMessage = ErrorGenericNotFound.Error()
}
if err[0] == ErrorGenericNotFound {
errorCode = 404
errorMessage = ErrorGenericNotFound.Error()
}
if errors.Is(err[0], errInvalidCredentials) {
errorCode = 401
errorMessage = errInvalidCredentials.Error()
}
// console invalid erasure coding value
if errors.Is(err[0], errorInvalidErasureCodingValue) {
errorCode = 400
errorMessage = errorInvalidErasureCodingValue.Error()
}
if errors.Is(err[0], errBucketBodyNotInRequest) {
errorCode = 400
errorMessage = errBucketBodyNotInRequest.Error()
}
if errors.Is(err[0], errBucketNameNotInRequest) {
errorCode = 400
errorMessage = errBucketNameNotInRequest.Error()
}
if errors.Is(err[0], errGroupBodyNotInRequest) {
errorCode = 400
errorMessage = errGroupBodyNotInRequest.Error()
}
if errors.Is(err[0], errGroupNameNotInRequest) {
errorCode = 400
errorMessage = errGroupNameNotInRequest.Error()
}
if errors.Is(err[0], errPolicyNameNotInRequest) {
errorCode = 400
errorMessage = errPolicyNameNotInRequest.Error()
}
if errors.Is(err[0], errPolicyBodyNotInRequest) {
errorCode = 400
errorMessage = errPolicyBodyNotInRequest.Error()
}
// console invalid session error
if errors.Is(err[0], errorGenericInvalidSession) {
errorCode = 401
errorMessage = errorGenericInvalidSession.Error()
}
// Bucket life cycle not configured
if errors.Is(err[0], errBucketLifeCycleNotConfigured) {
errorCode = 404
errorMessage = errBucketLifeCycleNotConfigured.Error()
}
// Encryption not configured
if errors.Is(err[0], errSSENotConfigured) {
errorCode = 404
errorMessage = errSSENotConfigured.Error()
}
// account change password
if madmin.ToErrorResponse(err[0]).Code == "SignatureDoesNotMatch" {
errorCode = 403
errorMessage = errChangePassword.Error()
}
if errors.Is(err[0], errLicenseNotFound) {
errorCode = 404
errorMessage = errLicenseNotFound.Error()
}
if errors.Is(err[0], errInvalidLicense) {
errorCode = 404
errorMessage = errInvalidLicense.Error()
}
if errors.Is(err[0], errAvoidSelfAccountDelete) {
errorCode = 403
errorMessage = errAvoidSelfAccountDelete.Error()
}
if madmin.ToErrorResponse(err[0]).Code == "AccessDenied" {
errorCode = 403
errorMessage = errAccessDenied.Error()
}
if madmin.ToErrorResponse(err[0]).Code == "InvalidAccessKeyId" {
errorCode = 401
errorMessage = errorGenericInvalidSession.Error()
}
// console invalid session error
if madmin.ToErrorResponse(err[0]).Code == "XMinioAdminNoSuchUser" {
errorCode = 401
errorMessage = errorGenericInvalidSession.Error()
}
// if we received a second error take that as friendly message but dont override the code
if len(err) > 1 && err[1] != nil {
LogError("friendly error: %v", err[1].Error())
errorMessage = err[1].Error()
}
// if we receive third error we just print that as debugging
if len(err) > 2 && err[2] != nil {
LogError("debugging error: %v", err[2].Error())
}
errRemoteTierExists := errors.New("Specified remote tier already exists") //nolint
if errors.Is(err[0], errRemoteTierExists) {
errorMessage = err[0].Error()
}
if errors.Is(err[0], errTooFewNodes) {
errorCode = 507
errorMessage = errTooFewNodes.Error()
}
if errors.Is(err[0], errTooFewSchedulableNodes) {
errorCode = 507
errorMessage = errTooFewSchedulableNodes.Error()
}
if errors.Is(err[0], errFewerThanFourNodes) {
errorCode = 507
errorMessage = errFewerThanFourNodes.Error()
}
}
return &models.Error{Code: errorCode, Message: swag.String(errorMessage), DetailedMessage: swag.String(err[0].Error())}
}
func getFrame(skipFrames int) runtime.Frame {
// We need the frame at index skipFrames+2, since we never want runtime.Callers and getFrame
targetFrameIndex := skipFrames + 2
// Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need
programCounters := make([]uintptr, targetFrameIndex+2)
n := runtime.Callers(0, programCounters)
frame := runtime.Frame{Function: "unknown"}
if n > 0 {
frames := runtime.CallersFrames(programCounters[:n])
for more, frameIndex := true, 0; more && frameIndex <= targetFrameIndex; frameIndex++ {
var frameCandidate runtime.Frame
frameCandidate, more = frames.Next()
if frameIndex == targetFrameIndex {
frame = frameCandidate
}
}
}
return frame
}

View File

@@ -42,7 +42,7 @@ import (
func registerLoginHandlers(api *operations.OperatorAPI) {
// GET login strategy
api.AuthLoginDetailHandler = authApi.LoginDetailHandlerFunc(func(params authApi.LoginDetailParams) middleware.Responder {
loginDetails, err := getLoginDetailsResponse(params.HTTPRequest)
loginDetails, err := getLoginDetailsResponse(params)
if err != nil {
return authApi.NewLoginDetailDefault(int(err.Code)).WithPayload(err)
}
@@ -50,7 +50,7 @@ func registerLoginHandlers(api *operations.OperatorAPI) {
})
// POST login using k8s service account token
api.AuthLoginOperatorHandler = authApi.LoginOperatorHandlerFunc(func(params authApi.LoginOperatorParams) middleware.Responder {
loginResponse, err := getLoginOperatorResponse(params.Body)
loginResponse, err := getLoginOperatorResponse(params)
if err != nil {
return authApi.NewLoginOperatorDefault(int(err.Code)).WithPayload(err)
}
@@ -63,7 +63,7 @@ func registerLoginHandlers(api *operations.OperatorAPI) {
})
// POST login using external IDP
api.AuthLoginOauth2AuthHandler = authApi.LoginOauth2AuthHandlerFunc(func(params authApi.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params.HTTPRequest, params.Body)
loginResponse, err := getLoginOauth2AuthResponse(params)
if err != nil {
return authApi.NewLoginOauth2AuthDefault(int(err.Code)).WithPayload(err)
}
@@ -87,14 +87,19 @@ func login(credentials restapi.ConsoleCredentialsI) (*string, error) {
// if we made it here, the consoleCredentials work, generate a jwt with claims
token, err := auth.NewEncryptedTokenForClient(&tokens, credentials.GetAccountAccessKey(), nil)
if err != nil {
LogError("error authenticating user: %v", err)
return nil, errInvalidCredentials
restapi.LogError("error authenticating user: %v", err)
return nil, restapi.ErrInvalidLogin
}
return &token, nil
}
// getLoginDetailsResponse returns information regarding the Console authentication mechanism.
func getLoginDetailsResponse(r *http.Request) (*models.LoginDetails, *models.Error) {
func getLoginDetailsResponse(params authApi.LoginDetailParams) (*models.LoginDetails, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
r := params.HTTPRequest
loginStrategy := models.LoginDetailsLoginStrategyServiceDashAccount
redirectURL := ""
@@ -103,7 +108,7 @@ func getLoginDetailsResponse(r *http.Request) (*models.LoginDetails, *models.Err
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(nil, r, restapi.GetConsoleHTTPClient())
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// Validate user against IDP
identityProvider := &auth.IdentityProvider{Client: oauth2Client}
@@ -126,31 +131,35 @@ func verifyUserAgainstIDP(ctx context.Context, provider auth.IdentityProviderI,
return oauth2Token, nil
}
func getLoginOauth2AuthResponse(r *http.Request, lr *models.LoginOauth2AuthRequest) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getLoginOauth2AuthResponse(params authApi.LoginOauth2AuthParams) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
r := params.HTTPRequest
lr := params.Body
if oauth2.IsIDPEnabled() {
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(nil, r, restapi.GetConsoleHTTPClient())
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// initialize new identity provider
identityProvider := auth.IdentityProvider{Client: oauth2Client}
// Validate user against IDP
_, err = verifyUserAgainstIDP(ctx, identityProvider, *lr.Code, *lr.State)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// If we pass here that means the IDP correctly authenticate the user with the operator resource
// we proceed to use the service account token configured in the operator-console pod
creds, err := newConsoleCredentials(getK8sSAToken())
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
token, err := login(restapi.ConsoleCredentials{ConsoleCredentials: creds})
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
return nil, restapi.ErrorWithContext(ctx, restapi.ErrInvalidLogin, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{
@@ -158,7 +167,7 @@ func getLoginOauth2AuthResponse(r *http.Request, lr *models.LoginOauth2AuthReque
}
return loginResponse, nil
}
return nil, prepareError(errorGeneric)
return nil, restapi.ErrorWithContext(ctx, restapi.ErrDefault)
}
func newConsoleCredentials(secretKey string) (*credentials.Credentials, error) {
@@ -170,17 +179,22 @@ func newConsoleCredentials(secretKey string) (*credentials.Credentials, error) {
}
// getLoginOperatorResponse validate the provided service account token against k8s api
func getLoginOperatorResponse(lmr *models.LoginOperatorRequest) (*models.LoginResponse, *models.Error) {
func getLoginOperatorResponse(params authApi.LoginOperatorParams) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
lmr := params.Body
creds, err := newConsoleCredentials(*lmr.Jwt)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
consoleCreds := restapi.ConsoleCredentials{ConsoleCredentials: creds}
// Set a random as access key as session identifier
consoleCreds.AccountAccessKey = fmt.Sprintf("%d", rand.Intn(100000-10000)+10000)
token, err := login(consoleCreds)
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
return nil, restapi.ErrorWithContext(ctx, restapi.ErrInvalidLogin, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{

View File

@@ -20,12 +20,13 @@ import (
"context"
"errors"
"github.com/minio/console/operatorapi/operations/operator_api"
xerrors "github.com/minio/console/restapi"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/cluster"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations"
"github.com/minio/console/operatorapi/operations/operator_api"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
@@ -44,12 +45,12 @@ func registerNamespaceHandlers(api *operations.OperatorAPI) {
}
func getNamespaceCreatedResponse(session *models.Principal, params operator_api.CreateNamespaceParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return xerrors.ErrorWithContext(ctx, err)
}
namespace := *params.Body.Name
@@ -57,7 +58,7 @@ func getNamespaceCreatedResponse(session *models.Principal, params operator_api.
errCreation := getNamespaceCreated(ctx, clientset.CoreV1(), namespace)
if errCreation != nil {
return prepareError(errCreation)
return xerrors.ErrorWithContext(ctx, errCreation)
}
return nil

View File

@@ -21,6 +21,8 @@ import (
"errors"
"sort"
xerrors "github.com/minio/console/restapi"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/console/operatorapi/operations/operator_api"
@@ -52,8 +54,8 @@ func registerNodesHandlers(api *operations.OperatorAPI) {
return operator_api.NewListNodeLabelsOK().WithPayload(*resp)
})
api.OperatorAPIGetAllocatableResourcesHandler = operator_api.GetAllocatableResourcesHandlerFunc(func(params operator_api.GetAllocatableResourcesParams, principal *models.Principal) middleware.Responder {
resp, err := getAllocatableResourcesResponse(params.NumNodes, principal)
api.OperatorAPIGetAllocatableResourcesHandler = operator_api.GetAllocatableResourcesHandlerFunc(func(params operator_api.GetAllocatableResourcesParams, session *models.Principal) middleware.Responder {
resp, err := getAllocatableResourcesResponse(session, params)
if err != nil {
return operator_api.NewGetAllocatableResourcesDefault(int(err.Code)).WithPayload(err)
}
@@ -71,7 +73,7 @@ type NodeResourceInfo struct {
func getMaxAllocatableMemory(ctx context.Context, clientset v1.CoreV1Interface, numNodes int32) (*models.MaxAllocatableMemResponse, error) {
// can't request less than 4 nodes
if numNodes < 4 {
return nil, errFewerThanFourNodes
return nil, xerrors.ErrFewerThanFourNodes
}
// get all nodes from cluster
@@ -97,15 +99,15 @@ func getMaxAllocatableMemory(ctx context.Context, clientset v1.CoreV1Interface,
}
// requesting more nodes than schedulable and less than total number of workers
if int(numNodes) > schedulableNodes && int(numNodes) < nonMasterNodes {
return nil, errTooManyNodes
return nil, xerrors.ErrTooManyNodes
}
if nonMasterNodes < int(numNodes) {
return nil, errTooFewNodes
return nil, xerrors.ErrTooFewNodes
}
// not enough schedulable nodes
if schedulableNodes < int(numNodes) {
return nil, errTooFewSchedulableNodes
return nil, xerrors.ErrTooFewAvailableNodes
}
availableMemSizes := []int64{}
@@ -177,12 +179,12 @@ func min(x, y int64) int64 {
func getMaxAllocatableMemoryResponse(ctx context.Context, session *models.Principal, numNodes int32) (*models.MaxAllocatableMemResponse, *models.Error) {
client, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, xerrors.ErrorWithContext(ctx, err)
}
clusterResources, err := getMaxAllocatableMemory(ctx, client.CoreV1(), numNodes)
if err != nil {
return nil, prepareError(err)
return nil, xerrors.ErrorWithContext(ctx, err)
}
return clusterResources, nil
}
@@ -217,12 +219,12 @@ func getNodeLabels(ctx context.Context, clientset v1.CoreV1Interface) (*models.N
func getNodeLabelsResponse(ctx context.Context, session *models.Principal) (*models.NodeLabels, *models.Error) {
client, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, xerrors.ErrorWithContext(ctx, err)
}
clusterResources, err := getNodeLabels(ctx, client.CoreV1())
if err != nil {
return nil, prepareError(err)
return nil, xerrors.ErrorWithContext(ctx, err)
}
return clusterResources, nil
}
@@ -357,17 +359,16 @@ OUTER:
// Get allocatable resources response
func getAllocatableResourcesResponse(numNodes int32, session *models.Principal) (*models.AllocatableResourcesResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getAllocatableResourcesResponse(session *models.Principal, params operator_api.GetAllocatableResourcesParams) (*models.AllocatableResourcesResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
client, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, xerrors.ErrorWithContext(ctx, err)
}
clusterResources, err := getAllocatableResources(ctx, client.CoreV1(), numNodes)
clusterResources, err := getAllocatableResources(ctx, client.CoreV1(), params.NumNodes)
if err != nil {
return nil, prepareError(err)
return nil, xerrors.ErrorWithContext(ctx, err)
}
return clusterResources, nil
}

View File

@@ -17,9 +17,10 @@
package operatorapi
import (
"context"
"fmt"
"github.com/minio/console/restapi"
errors "github.com/minio/console/restapi"
"github.com/minio/console/pkg/utils"
@@ -50,14 +51,14 @@ func GetParityInfo(nodes int64, disksPerNode int64) (models.ParityResponse, erro
}
func getParityResponse(params operator_api.GetParityParams) (models.ParityResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
nodes := params.Nodes
disksPerNode := params.DisksPerNode
parityValues, err := GetParityInfo(nodes, disksPerNode)
if err != nil {
restapi.LogError("error getting parity info: %v", err)
return nil, prepareError(err)
errors.LogError("error getting parity info: %v", err)
return nil, errors.ErrorWithContext(ctx, err)
}
return parityValues, nil
}

View File

@@ -20,6 +20,8 @@ import (
"context"
"fmt"
xerrors "github.com/minio/console/restapi"
"k8s.io/apimachinery/pkg/api/errors"
"github.com/minio/console/cluster"
@@ -94,18 +96,18 @@ func getResourceQuota(ctx context.Context, client K8sClientI, namespace, resourc
}
func getResourceQuotaResponse(session *models.Principal, params operator_api.GetResourceQuotaParams) (*models.ResourceQuota, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
client, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, xerrors.ErrorWithContext(ctx, err)
}
k8sClient := &k8sClient{
client: client,
}
resourceQuota, err := getResourceQuota(ctx, k8sClient, params.Namespace, params.ResourceQuotaName)
if err != nil {
return nil, prepareError(err)
return nil, xerrors.ErrorWithContext(ctx, err)
}
return resourceQuota, nil
}

View File

@@ -17,8 +17,11 @@
package operatorapi
import (
"context"
"fmt"
errors "github.com/minio/console/restapi"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations"
@@ -28,7 +31,7 @@ import (
func registerSessionHandlers(api *operations.OperatorAPI) {
// session check
api.AuthSessionCheckHandler = authApi.SessionCheckHandlerFunc(func(params authApi.SessionCheckParams, session *models.Principal) middleware.Responder {
sessionResp, err := getSessionResponse(session)
sessionResp, err := getSessionResponse(session, params)
if err != nil {
return authApi.NewSessionCheckDefault(int(err.Code)).WithPayload(err)
}
@@ -37,10 +40,12 @@ func registerSessionHandlers(api *operations.OperatorAPI) {
}
// getSessionResponse parse the token of the current session and returns a list of allowed actions to render in the UI
func getSessionResponse(session *models.Principal) (*models.OperatorSessionResponse, *models.Error) {
func getSessionResponse(session *models.Principal, params authApi.SessionCheckParams) (*models.OperatorSessionResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// serialize output
if session == nil {
return nil, prepareError(errorGenericInvalidSession)
return nil, errors.ErrorWithContext(ctx, errors.ErrInvalidSession)
}
sessionResp := &models.OperatorSessionResponse{
Status: models.OperatorSessionResponseStatusOk,

View File

@@ -41,7 +41,7 @@ import (
func getTenantCreatedResponse(session *models.Principal, params operator_api.CreateTenantParams) (response *models.CreateTenantResponse, mError *models.Error) {
tenantReq := params.Body
minioImage := tenantReq.Image
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if minioImage == "" {
minImg, err := cluster.GetMinioImage()
@@ -56,7 +56,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
client: clientSet,
}
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
ns := *tenantReq.Namespace
@@ -98,7 +98,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
_, err = clientSet.CoreV1().Secrets(ns).Create(ctx, &instanceSecret, metav1.CreateOptions{})
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// Enable/Disable console object browser for MinIO tenant (default is on)
@@ -110,7 +110,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
tenantConfigurationENV["MINIO_ROOT_USER"] = accessKey
tenantConfigurationENV["MINIO_ROOT_PASSWORD"] = secretKey
// delete secrets created if an error occurred during tenant creation,
// delete secrets created if an errors occurred during tenant creation,
defer func() {
if mError != nil {
restapi.LogError("deleting secrets created for failed tenant: %s if any: %v", tenantName, mError)
@@ -127,7 +127,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
// Check the Erasure Coding Parity for validity and pass it to Tenant
if tenantReq.ErasureCodingParity > 0 {
if tenantReq.ErasureCodingParity < 2 || tenantReq.ErasureCodingParity > 8 {
return nil, prepareError(errorInvalidErasureCodingValue)
return nil, restapi.ErrorWithContext(ctx, restapi.ErrInvalidErasureCodingValue)
}
tenantConfigurationENV["MINIO_STORAGE_CLASS_STANDARD"] = fmt.Sprintf("EC:%d", tenantReq.ErasureCodingParity)
}
@@ -204,7 +204,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
}
_, err := clientSet.CoreV1().Secrets(ns).Create(ctx, &userSecret, metav1.CreateOptions{})
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
}
// attach the users to the tenant
@@ -248,7 +248,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
}
_, err := clientSet.CoreV1().Secrets(ns).Create(ctx, &userSecret, metav1.CreateOptions{})
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
}
// attach the users to the tenant
@@ -274,7 +274,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
externalCertSecretName := fmt.Sprintf("%s-instance-external-certificates", secretName)
externalCertSecret, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, tenantReq.TLS.Minio, externalCertSecretName, tenantName)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
minInst.Spec.ExternalCertSecret = externalCertSecret
}
@@ -286,7 +286,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
certificates := []*models.KeyPairConfiguration{tenantReq.Encryption.Client}
certificateSecrets, err := createOrReplaceExternalCertSecrets(ctx, &k8sClient, ns, certificates, tenantExternalClientCertSecretName, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric)
return nil, restapi.ErrorWithContext(ctx, restapi.ErrDefault)
}
if len(certificateSecrets) > 0 {
minInst.Spec.ExternalClientCertSecret = certificateSecrets[0]
@@ -296,7 +296,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
// KES configuration for Tenant instance
minInst.Spec.KES, err = getKESConfiguration(ctx, &k8sClient, ns, tenantReq.Encryption, secretName, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric)
return nil, restapi.ErrorWithContext(ctx, restapi.ErrDefault)
}
// Set Labels, Annotations and Node Selector for KES
minInst.Spec.KES.Labels = tenantReq.Encryption.Labels
@@ -306,7 +306,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if tenantReq.Encryption.SecurityContext != nil {
sc, err := convertModelSCToK8sSC(tenantReq.Encryption.SecurityContext)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
minInst.Spec.KES.SecurityContext = sc
}
@@ -317,7 +317,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
for i, caCertificate := range tenantReq.TLS.CaCertificates {
certificateContent, err := base64.StdEncoding.DecodeString(caCertificate)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
return nil, restapi.ErrorWithContext(ctx, restapi.ErrDefault, nil, err)
}
caCertificates = append(caCertificates, tenantSecret{
Name: fmt.Sprintf("ca-certificate-%d", i),
@@ -329,7 +329,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if len(caCertificates) > 0 {
certificateSecrets, err := createOrReplaceSecrets(ctx, &k8sClient, ns, caCertificates, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
return nil, restapi.ErrorWithContext(ctx, restapi.ErrDefault, nil, err)
}
minInst.Spec.ExternalCaCertSecret = certificateSecrets
}
@@ -347,7 +347,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
pool, err := parseTenantPoolRequest(pool)
if err != nil {
restapi.LogError("parseTenantPoolRequest failed: %v", err)
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
minInst.Spec.Pools = append(minInst.Spec.Pools, *pool)
}
@@ -363,7 +363,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if tenantReq.ImagePullSecret != "" {
imagePullSecret = tenantReq.ImagePullSecret
} else if imagePullSecret, err = setImageRegistry(ctx, tenantReq.ImageRegistry, clientSet.CoreV1(), ns, tenantName); err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// pass the image pull secret to the Tenant
if imagePullSecret != "" {
@@ -410,7 +410,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if tenantReq.LogSearchConfiguration.SecurityContext != nil {
sc, err := convertModelSCToK8sSC(tenantReq.LogSearchConfiguration.SecurityContext)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
logSearchSecurityContext = sc
}
@@ -418,7 +418,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if tenantReq.LogSearchConfiguration.PostgresSecurityContext != nil {
sc, err := convertModelSCToK8sSC(tenantReq.LogSearchConfiguration.PostgresSecurityContext)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
logSearchPgSecurityContext = sc
}
@@ -514,7 +514,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
if tenantReq.PrometheusConfiguration != nil && tenantReq.PrometheusConfiguration.SecurityContext != nil {
sc, err := convertModelSCToK8sSC(tenantReq.PrometheusConfiguration.SecurityContext)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
minInst.Spec.Prometheus.SecurityContext = sc
}
@@ -538,7 +538,7 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
},
}, tenantName)
if err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
return nil, restapi.ErrorWithContext(ctx, restapi.ErrDefault, nil, err)
}
minInst.Spec.Configuration = &corev1.LocalObjectReference{Name: tenantConfigurationName}
@@ -562,20 +562,20 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
opClient, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
_, err = opClient.MinioV2().Tenants(ns).Create(context.Background(), &minInst, metav1.CreateOptions{})
if err != nil {
restapi.LogError("Creating new tenant failed with: %v", err)
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// Integrations
if os.Getenv("GKE_INTEGRATION") != "" {
err := gkeIntegration(clientSet, tenantName, ns, session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
}
response = &models.CreateTenantResponse{

View File

@@ -20,20 +20,21 @@ import (
"context"
"fmt"
errors "github.com/minio/console/restapi"
"github.com/minio/console/cluster"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations/operator_api"
"github.com/minio/console/restapi"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func getTenantDetailsResponse(session *models.Principal, params operator_api.TenantDetailsParams) (*models.Tenant, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, errors.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
@@ -42,7 +43,7 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return nil, prepareError(err)
return nil, errors.ErrorWithContext(ctx, err)
}
info := getTenantInfo(minTenant)
@@ -50,7 +51,7 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, errors.ErrorWithContext(ctx, err)
}
k8sClient := k8sClient{
@@ -59,7 +60,7 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten
tenantConfiguration, err := GetTenantConfiguration(ctx, &k8sClient, minTenant)
if err != nil {
restapi.LogError("unable to fetch configuration for tenant %s: %v", minTenant.Name, err)
errors.LogError("unable to fetch configuration for tenant %s: %v", minTenant.Name, err)
}
// detect if AD/LDAP is enabled
@@ -105,14 +106,14 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten
//minio service
minSvc, err := k8sClient.getService(ctx, minTenant.Namespace, minTenant.MinIOCIServiceName(), metav1.GetOptions{})
if err != nil {
// we can tolerate this error
restapi.LogError("Unable to get MinIO service name: %v, continuing", err)
// we can tolerate this errors
errors.LogError("Unable to get MinIO service name: %v, continuing", err)
}
//console service
conSvc, err := k8sClient.getService(ctx, minTenant.Namespace, minTenant.ConsoleCIServiceName(), metav1.GetOptions{})
if err != nil {
// we can tolerate this error
restapi.LogError("Unable to get MinIO console service name: %v, continuing", err)
// we can tolerate this errors
errors.LogError("Unable to get MinIO console service name: %v, continuing", err)
}
schema := "http"

View File

@@ -22,9 +22,12 @@ import (
"fmt"
"strings"
"github.com/minio/console/restapi"
"github.com/minio/console/pkg/http"
"github.com/minio/console/operatorapi/operations/operator_api"
utils2 "github.com/minio/console/pkg/utils"
"github.com/minio/console/restapi"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
@@ -32,7 +35,7 @@ import (
)
// updateTenantAction does an update on the minioTenant by patching the desired changes
func updateTenantAction(ctx context.Context, operatorClient OperatorClientI, clientset v1.CoreV1Interface, httpCl utils2.HTTPClientI, namespace string, params operator_api.UpdateTenantParams) error {
func updateTenantAction(ctx context.Context, operatorClient OperatorClientI, clientset v1.CoreV1Interface, httpCl http.ClientI, namespace string, params operator_api.UpdateTenantParams) error {
imageToUpdate := params.Body.Image
imageRegistryReq := params.Body.ImageRegistry

View File

@@ -33,11 +33,11 @@ import (
"strings"
"time"
utils2 "github.com/minio/console/pkg/http"
"github.com/dustin/go-humanize"
"github.com/minio/madmin-go"
utils2 "github.com/minio/console/pkg/utils"
"github.com/minio/console/restapi"
"github.com/minio/console/operatorapi/operations/operator_api"
@@ -354,14 +354,16 @@ func registerTenantHandlers(api *operations.OperatorAPI) {
// getDeleteTenantResponse gets the output of deleting a minio instance
func getDeleteTenantResponse(session *models.Principal, params operator_api.DeleteTenantParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
// get Kubernetes Client
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
client: opClientClientSet,
@@ -373,12 +375,12 @@ func getDeleteTenantResponse(session *models.Principal, params operator_api.Dele
tenant, err := opClient.TenantGet(params.HTTPRequest.Context(), params.Namespace, params.Tenant, metav1.GetOptions{})
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
tenant.EnsureDefaults()
if err = deleteTenantAction(params.HTTPRequest.Context(), opClient, clientset.CoreV1(), tenant, deleteTenantPVCs); err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
return nil
}
@@ -396,7 +398,7 @@ func deleteTenantAction(
err := operatorClient.TenantDelete(ctx, tenant.Namespace, tenant.Name, metav1.DeleteOptions{})
if err != nil {
// try to delete pvc even if the tenant doesn't exist anymore but only if deletePvcs is set to true,
// else, we return the error
// else, we return the errors
if (deletePvcs && !k8sErrors.IsNotFound(err)) || !deletePvcs {
return err
}
@@ -440,19 +442,19 @@ func deleteTenantAction(
// getDeleteTenantResponse gets the output of deleting a minio instance
func getDeletePodResponse(session *models.Principal, params operator_api.DeletePodParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// get Kubernetes Client
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
listOpts := metav1.ListOptions{
LabelSelector: fmt.Sprintf("v1.min.io/tenant=%s", params.Tenant),
FieldSelector: fmt.Sprintf("metadata.name=%s%s", params.Tenant, params.PodName[len(params.Tenant):]),
}
if err = clientset.CoreV1().Pods(params.Namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, listOpts); err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
return nil
}
@@ -494,12 +496,12 @@ func getTenantCreds(ctx context.Context, client K8sClientI, tenant *miniov2.Tena
tenantAccessKey, ok := tenantConfiguration["accesskey"]
if !ok {
restapi.LogError("tenant's secret doesn't contain accesskey")
return nil, restapi.ErrorGeneric
return nil, restapi.ErrDefault
}
tenantSecretKey, ok := tenantConfiguration["secretkey"]
if !ok {
restapi.LogError("tenant's secret doesn't contain secretkey")
return nil, restapi.ErrorGeneric
return nil, restapi.ErrDefault
}
return &tenantKeys{accessKey: tenantAccessKey, secretKey: tenantSecretKey}, nil
}
@@ -842,19 +844,19 @@ func updateTenantIdentityProvider(ctx context.Context, operatorClient OperatorCl
func getTenantIdentityProviderResponse(session *models.Principal, params operator_api.TenantIdentityProviderParams) (*models.IdpConfiguration, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
@@ -862,27 +864,27 @@ func getTenantIdentityProviderResponse(session *models.Principal, params operato
client: clientSet,
}
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
info, err := getTenantIdentityProvider(ctx, &k8sClient, minTenant)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
return info, nil
}
func getUpdateTenantIdentityProviderResponse(session *models.Principal, params operator_api.UpdateTenantIdentityProviderParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
k8sClient := k8sClient{
client: clientSet,
@@ -891,25 +893,25 @@ func getUpdateTenantIdentityProviderResponse(session *models.Principal, params o
client: opClientClientSet,
}
if err := updateTenantIdentityProvider(ctx, opClient, &k8sClient, params.Namespace, params); err != nil {
return prepareError(err, errors.New("unable to update tenant"))
return restapi.ErrorWithContext(ctx, err, errors.New("unable to update tenant"))
}
return nil
}
func getTenantSecurityResponse(session *models.Principal, params operator_api.TenantSecurityParams) (*models.TenantSecurityResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
@@ -917,27 +919,27 @@ func getTenantSecurityResponse(session *models.Principal, params operator_api.Te
client: clientSet,
}
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
info, err := getTenantSecurity(ctx, &k8sClient, minTenant)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
return info, nil
}
func getUpdateTenantSecurityResponse(session *models.Principal, params operator_api.UpdateTenantSecurityParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
k8sClient := k8sClient{
client: clientSet,
@@ -946,7 +948,7 @@ func getUpdateTenantSecurityResponse(session *models.Principal, params operator_
client: opClientClientSet,
}
if err := updateTenantSecurity(ctx, opClient, &k8sClient, params.Namespace, params); err != nil {
return prepareError(err, errors.New("unable to update tenant"))
return restapi.ErrorWithContext(ctx, err, errors.New("unable to update tenant"))
}
return nil
}
@@ -1117,36 +1119,36 @@ func listTenants(ctx context.Context, operatorClient OperatorClientI, namespace
}
func getListAllTenantsResponse(session *models.Principal, params operator_api.ListAllTenantsParams) (*models.ListTenantsResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
listT, err := listTenants(ctx, opClient, "", params.Limit)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
return listT, nil
}
// getListTenantsResponse list tenants by namespace
func getListTenantsResponse(session *models.Principal, params operator_api.ListTenantsParams) (*models.ListTenantsResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
listT, err := listTenants(ctx, opClient, params.Namespace, params.Limit)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
return listT, nil
}
@@ -1233,27 +1235,27 @@ func removeAnnotations(annotationsOne, annotationsTwo map[string]string) map[str
}
func getUpdateTenantResponse(session *models.Principal, params operator_api.UpdateTenantParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
httpC := &utils2.HTTPClient{
httpC := &utils2.Client{
Client: &http.Client{
Timeout: 4 * time.Second,
},
}
if err := updateTenantAction(ctx, opClient, clientSet.CoreV1(), httpC, params.Namespace, params); err != nil {
return prepareError(err, errors.New("unable to update tenant"))
return restapi.ErrorWithContext(ctx, err, errors.New("unable to update tenant"))
}
return nil
}
@@ -1284,17 +1286,17 @@ func addTenantPool(ctx context.Context, operatorClient OperatorClientI, params o
}
func getTenantAddPoolResponse(session *models.Principal, params operator_api.TenantAddPoolParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
client: opClientClientSet,
}
if err := addTenantPool(ctx, opClient, params); err != nil {
return prepareError(err, errors.New("unable to add pool"))
return restapi.ErrorWithContext(ctx, err, errors.New("unable to add pool"))
}
return nil
}
@@ -1302,16 +1304,16 @@ func getTenantAddPoolResponse(session *models.Principal, params operator_api.Ten
// getTenantUsageResponse returns the usage of a tenant
func getTenantUsageResponse(session *models.Principal, params operator_api.GetTenantUsageParams) (*models.TenantUsage, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
return nil, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
return nil, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
opClient := &operatorClient{
@@ -1323,7 +1325,7 @@ func getTenantUsageResponse(session *models.Principal, params operator_api.GetTe
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
return nil, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
minTenant.EnsureDefaults()
@@ -1336,7 +1338,7 @@ func getTenantUsageResponse(session *models.Principal, params operator_api.GetTe
svcURL,
)
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
return nil, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -1344,7 +1346,7 @@ func getTenantUsageResponse(session *models.Principal, params operator_api.GetTe
// serialize output
adminInfo, err := restapi.GetAdminInfo(ctx, adminClient)
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantUsage)
return nil, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
info := &models.TenantUsage{Used: adminInfo.Usage, DiskUsed: adminInfo.DisksUsage}
return info, nil
@@ -1353,12 +1355,12 @@ func getTenantUsageResponse(session *models.Principal, params operator_api.GetTe
// getTenantLogsResponse returns the logs of a tenant
func getTenantLogsResponse(session *models.Principal, params operator_api.GetTenantLogsParams) (*models.TenantLogs, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantLogs)
return nil, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantLogs)
}
opClient := &operatorClient{
@@ -1367,7 +1369,7 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return nil, prepareError(err, errorUnableToGetTenantLogs)
return nil, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantLogs)
}
if minTenant.Spec.Log == nil {
retval := &models.TenantLogs{
@@ -1450,12 +1452,12 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen
// setTenantLogsResponse returns the logs of a tenant
func setTenantLogsResponse(session *models.Principal, params operator_api.SetTenantLogsParams) (bool, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
opClient := &operatorClient{
@@ -1464,7 +1466,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
var labels = make(map[string]string)
@@ -1493,7 +1495,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
if reflect.TypeOf(params.Data.LogCPURequest).Kind() == reflect.String && params.Data.LogCPURequest != "0Gi" && params.Data.LogCPURequest != "" {
cpuQuantity, err := resource.ParseQuantity(params.Data.LogCPURequest)
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
logResourceRequest["cpu"] = cpuQuantity
minTenant.Spec.Log.Resources.Requests = logResourceRequest
@@ -1501,7 +1503,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
if reflect.TypeOf(params.Data.LogMemRequest).Kind() == reflect.String {
memQuantity, err := resource.ParseQuantity(params.Data.LogMemRequest)
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
logResourceRequest["memory"] = memQuantity
@@ -1538,7 +1540,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
if reflect.TypeOf(params.Data.LogDBCPURequest).Kind() == reflect.String && params.Data.LogDBCPURequest != "0Gi" && params.Data.LogDBCPURequest != "" {
dbCPUQuantity, err := resource.ParseQuantity(params.Data.LogDBCPURequest)
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
logDBResourceRequest["cpu"] = dbCPUQuantity
minTenant.Spec.Log.Db.Resources.Requests = logDBResourceRequest
@@ -1546,7 +1548,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
if reflect.TypeOf(params.Data.LogDBMemRequest).Kind() == reflect.String {
dbMemQuantity, err := resource.ParseQuantity(params.Data.LogDBMemRequest)
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
logDBResourceRequest["memory"] = dbMemQuantity
minTenant.Spec.Log.Db.Resources.Requests = logDBResourceRequest
@@ -1609,7 +1611,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
_, err = opClient.TenantUpdate(ctx, minTenant, metav1.UpdateOptions{})
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
return true, nil
}
@@ -1617,12 +1619,12 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
// enableTenantLoggingResponse enables Tenant Logging
func enableTenantLoggingResponse(session *models.Principal, params operator_api.EnableTenantLoggingParams) (bool, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
opClient := &operatorClient{
@@ -1631,7 +1633,7 @@ func enableTenantLoggingResponse(session *models.Principal, params operator_api.
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
minTenant.EnsureDefaults()
@@ -1670,7 +1672,7 @@ func enableTenantLoggingResponse(session *models.Principal, params operator_api.
_, err = opClient.TenantUpdate(ctx, minTenant, metav1.UpdateOptions{})
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
return true, nil
}
@@ -1678,15 +1680,15 @@ func enableTenantLoggingResponse(session *models.Principal, params operator_api.
// disableTenantLoggingResponse disables Tenant Logging
func disableTenantLoggingResponse(session *models.Principal, params operator_api.DisableTenantLoggingParams) (bool, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
opClient := &operatorClient{
@@ -1695,31 +1697,31 @@ func disableTenantLoggingResponse(session *models.Principal, params operator_api
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
minTenant.EnsureDefaults()
minTenant.Spec.Log = nil
_, err = opClient.TenantUpdate(ctx, minTenant, metav1.UpdateOptions{})
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
return true, nil
}
func getTenantPodsResponse(session *models.Principal, params operator_api.GetTenantPodsParams) ([]*models.TenantPod, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
listOpts := metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", miniov2.TenantLabel, params.Tenant),
}
pods, err := clientset.CoreV1().Pods(params.Namespace).List(ctx, listOpts)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
retval := []*models.TenantPod{}
for _, pod := range pods.Items {
@@ -1743,35 +1745,35 @@ func getTenantPodsResponse(session *models.Principal, params operator_api.GetTen
}
func getPodLogsResponse(session *models.Principal, params operator_api.GetPodLogsParams) (string, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return "", prepareError(err)
return "", restapi.ErrorWithContext(ctx, err)
}
listOpts := &corev1.PodLogOptions{}
logs := clientset.CoreV1().Pods(params.Namespace).GetLogs(params.PodName, listOpts)
buff, err := logs.DoRaw(ctx)
if err != nil {
return "", prepareError(err)
return "", restapi.ErrorWithContext(ctx, err)
}
return string(buff), nil
}
func getPodEventsResponse(session *models.Principal, params operator_api.GetPodEventsParams) (models.EventListWrapper, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
pod, err := clientset.CoreV1().Pods(params.Namespace).Get(ctx, params.PodName, metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
events, err := clientset.CoreV1().Events(params.Namespace).List(ctx, metav1.ListOptions{FieldSelector: fmt.Sprintf("involvedObject.uid=%s", pod.UID)})
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
retval := models.EventListWrapper{}
for i := 0; i < len(events.Items); i++ {
@@ -1791,12 +1793,12 @@ func getPodEventsResponse(session *models.Principal, params operator_api.GetPodE
//get values for prometheus metrics
func getTenantMonitoringResponse(session *models.Principal, params operator_api.GetTenantMonitoringParams) (*models.TenantMonitoringInfo, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
@@ -1805,7 +1807,7 @@ func getTenantMonitoringResponse(session *models.Principal, params operator_api.
minInst, err := opClient.TenantGet(ctx, params.Namespace, params.Tenant, metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
monitoringInfo := &models.TenantMonitoringInfo{}
@@ -1886,12 +1888,12 @@ func getTenantMonitoringResponse(session *models.Principal, params operator_api.
//sets tenant Prometheus monitoring cofiguration fields to values provided
func setTenantMonitoringResponse(session *models.Principal, params operator_api.SetTenantMonitoringParams) (bool, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
opClient := &operatorClient{
@@ -1900,7 +1902,7 @@ func setTenantMonitoringResponse(session *models.Principal, params operator_api.
minTenant, err := getTenant(ctx, opClient, params.Namespace, params.Tenant)
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
return false, restapi.ErrorWithContext(ctx, err, restapi.ErrUnableToGetTenantUsage)
}
if params.Data.Toggle {
@@ -1916,7 +1918,7 @@ func setTenantMonitoringResponse(session *models.Principal, params operator_api.
}
_, err = opClient.TenantUpdate(ctx, minTenant, metav1.UpdateOptions{})
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
return true, nil
}
@@ -1944,7 +1946,7 @@ func setTenantMonitoringResponse(session *models.Principal, params operator_api.
if params.Data.MonitoringCPURequest != "" {
cpuQuantity, err := resource.ParseQuantity(params.Data.MonitoringCPURequest)
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
monitoringResourceRequest["cpu"] = cpuQuantity
}
@@ -1952,7 +1954,7 @@ func setTenantMonitoringResponse(session *models.Principal, params operator_api.
if params.Data.MonitoringMemRequest != "" {
memQuantity, err := resource.ParseQuantity(params.Data.MonitoringMemRequest)
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
monitoringResourceRequest["memory"] = memQuantity
}
@@ -1977,7 +1979,7 @@ func setTenantMonitoringResponse(session *models.Principal, params operator_api.
minTenant.Spec.Prometheus.ServiceAccountName = params.Data.ServiceAccountName
_, err = opClient.TenantUpdate(ctx, minTenant, metav1.UpdateOptions{})
if err != nil {
return false, prepareError(err)
return false, restapi.ErrorWithContext(ctx, err)
}
return true, nil
@@ -2422,11 +2424,11 @@ func parseNodeSelectorTerm(term *corev1.NodeSelectorTerm) *models.NodeSelectorTe
}
func getTenantUpdatePoolResponse(session *models.Principal, params operator_api.TenantUpdatePoolsParams) (*models.Tenant, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
@@ -2436,7 +2438,7 @@ func getTenantUpdatePoolResponse(session *models.Principal, params operator_api.
t, err := updateTenantPools(ctx, opClient, params.Namespace, params.Tenant, params.Body.Pools)
if err != nil {
restapi.LogError("error updating Tenant's pools: %v", err)
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// parse it to models.Tenant
@@ -2487,20 +2489,19 @@ func updateTenantPools(
}
func getTenantYAML(session *models.Principal, params operator_api.GetTenantYAMLParams) (*models.TenantYAML, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// get Kubernetes Client
opClient, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
tenant, err := opClient.MinioV2().Tenants(params.Namespace).Get(params.HTTPRequest.Context(), params.Tenant, metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
// remove managed fields
tenant.ManagedFields = []metav1.ManagedFieldsEntry{}
//yb, err := yaml.Marshal(tenant)
j8sJSONSerializer := k8sJson.NewSerializerWithOptions(
k8sJson.DefaultMetaFactory, nil, nil,
@@ -2514,7 +2515,7 @@ func getTenantYAML(session *models.Principal, params operator_api.GetTenantYAMLP
err = j8sJSONSerializer.Encode(tenant, buf)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
yb := buf.String()
@@ -2523,6 +2524,8 @@ func getTenantYAML(session *models.Principal, params operator_api.GetTenantYAMLP
}
func getUpdateTenantYAML(session *models.Principal, params operator_api.PutTenantYAMLParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// https://godoc.org/k8s.io/apimachinery/pkg/runtime#Scheme
scheme := runtime.NewScheme()
@@ -2540,12 +2543,12 @@ func getUpdateTenantYAML(session *models.Principal, params operator_api.PutTenan
// get Kubernetes Client
opClient, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
tenant, err := opClient.MinioV2().Tenants(params.Namespace).Get(params.HTTPRequest.Context(), params.Tenant, metav1.GetOptions{})
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
upTenant := tenant.DeepCopy()
// only update safe fields: spec, metadata.finalizers, metadata.labels and metadata.annotations
@@ -2563,23 +2566,23 @@ func getUpdateTenantYAML(session *models.Principal, params operator_api.PutTenan
}
func getTenantEventsResponse(session *models.Principal, params operator_api.GetTenantEventsParams) (models.EventListWrapper, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
client, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
tenant, err := client.MinioV2().Tenants(params.Namespace).Get(ctx, params.Tenant, metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
events, err := clientset.CoreV1().Events(params.Namespace).List(ctx, metav1.ListOptions{FieldSelector: fmt.Sprintf("involvedObject.uid=%s", tenant.UID)})
if err != nil {
return nil, prepareError(err)
return nil, restapi.ErrorWithContext(ctx, err)
}
retval := models.EventListWrapper{}
for _, event := range events.Items {
@@ -2603,7 +2606,7 @@ func getUpdateDomainsResponse(session *models.Principal, params operator_api.Upd
operatorCli, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
opClient := &operatorClient{
@@ -2613,7 +2616,7 @@ func getUpdateDomainsResponse(session *models.Principal, params operator_api.Upd
err = updateTenantDomains(ctx, opClient, params.Namespace, params.Tenant, params.Body.Domains)
if err != nil {
return prepareError(err)
return restapi.ErrorWithContext(ctx, err)
}
return nil

View File

@@ -27,6 +27,8 @@ import (
"strconv"
"time"
xerrors "github.com/minio/console/restapi"
"github.com/minio/console/operatorapi/operations/operator_api"
"errors"
@@ -108,25 +110,25 @@ func tenantUpdateCertificates(ctx context.Context, operatorClient OperatorClient
// getTenantUpdateCertificatesResponse wrapper of tenantUpdateCertificates
func getTenantUpdateCertificatesResponse(session *models.Principal, params operator_api.TenantUpdateCertificateParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err, errorUnableToUpdateTenantCertificates)
return xerrors.ErrorWithContext(ctx, err, xerrors.ErrUnableToUpdateTenantCertificates)
}
k8sClient := k8sClient{
client: clientSet,
}
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err, errorUnableToUpdateTenantCertificates)
return xerrors.ErrorWithContext(ctx, err, xerrors.ErrUnableToUpdateTenantCertificates)
}
opClient := operatorClient{
client: opClientClientSet,
}
if err := tenantUpdateCertificates(ctx, &opClient, &k8sClient, params.Namespace, params); err != nil {
return prepareError(err, errorUnableToUpdateTenantCertificates)
return xerrors.ErrorWithContext(ctx, err, xerrors.ErrUnableToUpdateTenantCertificates)
}
return nil
}
@@ -239,42 +241,42 @@ func tenantUpdateEncryption(ctx context.Context, operatorClient OperatorClientI,
// getTenantDeleteEncryptionResponse is a wrapper for tenantDeleteEncryption
func getTenantDeleteEncryptionResponse(session *models.Principal, params operator_api.TenantDeleteEncryptionParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err, errorDeletingEncryptionConfig)
return xerrors.ErrorWithContext(ctx, err, xerrors.ErrDeletingEncryptionConfig)
}
opClient := operatorClient{
client: opClientClientSet,
}
if err := tenantDeleteEncryption(ctx, &opClient, params.Namespace, params); err != nil {
return prepareError(err, errorDeletingEncryptionConfig)
return xerrors.ErrorWithContext(ctx, err, xerrors.ErrDeletingEncryptionConfig)
}
return nil
}
// getTenantUpdateEncryptionResponse is a wrapper for tenantUpdateEncryption
func getTenantUpdateEncryptionResponse(session *models.Principal, params operator_api.TenantUpdateEncryptionParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err, errorUpdatingEncryptionConfig)
return xerrors.ErrorWithContext(ctx, err, xerrors.ErrUpdatingEncryptionConfig)
}
k8sClient := k8sClient{
client: clientSet,
}
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(err, errorUpdatingEncryptionConfig)
return xerrors.ErrorWithContext(ctx, err, xerrors.ErrUpdatingEncryptionConfig)
}
opClient := operatorClient{
client: opClientClientSet,
}
if err := tenantUpdateEncryption(ctx, &opClient, &k8sClient, params.Namespace, params); err != nil {
return prepareError(err, errorUpdatingEncryptionConfig)
return xerrors.ErrorWithContext(ctx, err, xerrors.ErrUpdatingEncryptionConfig)
}
return nil
}
@@ -453,26 +455,26 @@ func tenantEncryptionInfo(ctx context.Context, operatorClient OperatorClientI, c
// getTenantEncryptionResponse is a wrapper for tenantEncryptionInfo
func getTenantEncryptionInfoResponse(session *models.Principal, params operator_api.TenantEncryptionInfoParams) (*models.EncryptionConfigurationResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// get Kubernetes Client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err, errorEncryptionConfigNotFound)
return nil, xerrors.ErrorWithContext(ctx, err, xerrors.ErrEncryptionConfigNotFound)
}
k8sClient := k8sClient{
client: clientSet,
}
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err, errorEncryptionConfigNotFound)
return nil, xerrors.ErrorWithContext(ctx, err, xerrors.ErrEncryptionConfigNotFound)
}
opClient := operatorClient{
client: opClientClientSet,
}
configuration, err := tenantEncryptionInfo(ctx, &opClient, &k8sClient, params.Namespace, params)
if err != nil {
return nil, prepareError(err, errorEncryptionConfigNotFound)
return nil, xerrors.ErrorWithContext(ctx, err, xerrors.ErrEncryptionConfigNotFound)
}
return configuration, nil
}
@@ -541,8 +543,8 @@ func createOrReplaceSecrets(ctx context.Context, clientSet K8sClientI, ns string
// delete secret with same name if exists
err := clientSet.deleteSecret(ctx, ns, secret.Name, metav1.DeleteOptions{})
if err != nil {
// log the error if any and continue
LogError("deleting secret name %s failed: %v, continuing..", secret.Name, err)
// log the errors if any and continue
xerrors.LogError("deleting secret name %s failed: %v, continuing..", secret.Name, err)
}
k8sSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
@@ -579,8 +581,8 @@ func createOrReplaceExternalCertSecrets(ctx context.Context, clientSet K8sClient
// delete secret with same name if exists
err := clientSet.deleteSecret(ctx, ns, keyPairSecretName, metav1.DeleteOptions{})
if err != nil {
// log the error if any and continue
LogError("deleting secret name %s failed: %v, continuing..", keyPairSecretName, err)
// log the errors if any and continue
xerrors.LogError("deleting secret name %s failed: %v, continuing..", keyPairSecretName, err)
}
imm := true
tlsCrt, err := base64.StdEncoding.DecodeString(*keyPair.Crt)
@@ -625,13 +627,13 @@ func createOrReplaceExternalCertSecrets(ctx context.Context, clientSet K8sClient
func createOrReplaceKesConfigurationSecrets(ctx context.Context, clientSet K8sClientI, ns string, encryptionCfg *models.EncryptionConfiguration, kesConfigurationSecretName, kesClientCertSecretName, tenantName string) (*corev1.LocalObjectReference, *miniov2.LocalCertificateReference, error) {
// delete KES configuration secret if exists
if err := clientSet.deleteSecret(ctx, ns, kesConfigurationSecretName, metav1.DeleteOptions{}); err != nil {
// log the error if any and continue
LogError("deleting secret name %s failed: %v, continuing..", kesConfigurationSecretName, err)
// log the errors if any and continue
xerrors.LogError("deleting secret name %s failed: %v, continuing..", kesConfigurationSecretName, err)
}
// delete KES client cert secret if exists
if err := clientSet.deleteSecret(ctx, ns, kesClientCertSecretName, metav1.DeleteOptions{}); err != nil {
// log the error if any and continue
LogError("deleting secret name %s failed: %v, continuing..", kesClientCertSecretName, err)
// log the errors if any and continue
xerrors.LogError("deleting secret name %s failed: %v, continuing..", kesClientCertSecretName, err)
}
// if autoCert is enabled then Operator will generate the client certificates, calculate the client cert identity
// and pass it to KES via the ${MINIO_KES_IDENTITY} variable

View File

@@ -28,7 +28,7 @@ import (
"testing"
"time"
"github.com/minio/console/pkg/utils"
xhttp "github.com/minio/console/pkg/http"
"github.com/minio/console/operatorapi/operations/operator_api"
@@ -897,7 +897,7 @@ func Test_UpdateTenantAction(t *testing.T) {
type args struct {
ctx context.Context
operatorClient OperatorClientI
httpCl utils.HTTPClientI
httpCl xhttp.ClientI
nameSpace string
tenantName string
mockTenantPatch func(ctx context.Context, namespace string, tenantName string, pt types.PatchType, data []byte, options metav1.PatchOptions) (*miniov2.Tenant, error)

View File

@@ -17,20 +17,24 @@
package operatorapi
import (
"context"
"net/http"
"time"
errors "github.com/minio/console/restapi"
xhttp "github.com/minio/console/pkg/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations"
"github.com/minio/console/operatorapi/operations/user_api"
"net/http"
"time"
"github.com/minio/console/pkg/utils"
)
func registerVersionHandlers(api *operations.OperatorAPI) {
api.UserAPICheckMinIOVersionHandler = user_api.CheckMinIOVersionHandlerFunc(func(params user_api.CheckMinIOVersionParams) middleware.Responder {
versionResponse, err := getVersionResponse()
versionResponse, err := getVersionResponse(params)
if err != nil {
return user_api.NewCheckMinIOVersionDefault(int(err.Code)).WithPayload(err)
}
@@ -39,13 +43,15 @@ func registerVersionHandlers(api *operations.OperatorAPI) {
}
// getSessionResponse parse the token of the current session and returns a list of allowed actions to render in the UI
func getVersionResponse() (*models.CheckOperatorVersionResponse, *models.Error) {
ver, err := utils.GetLatestMinIOImage(&utils.HTTPClient{
func getVersionResponse(params user_api.CheckMinIOVersionParams) (*models.CheckOperatorVersionResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
ver, err := utils.GetLatestMinIOImage(&xhttp.Client{
Client: &http.Client{
Timeout: 15 * time.Second,
}})
if err != nil {
return nil, prepareError(err)
return nil, errors.ErrorWithContext(ctx, err)
}
return &models.CheckOperatorVersionResponse{
LatestVersion: *ver,

View File

@@ -21,6 +21,8 @@ import (
"fmt"
"sort"
errors "github.com/minio/console/restapi"
miniov1 "github.com/minio/operator/pkg/apis/minio.min.io/v1"
"github.com/go-openapi/runtime/middleware"
@@ -33,7 +35,7 @@ import (
func registerVolumesHandlers(api *operations.OperatorAPI) {
api.OperatorAPIListPVCsHandler = operator_api.ListPVCsHandlerFunc(func(params operator_api.ListPVCsParams, session *models.Principal) middleware.Responder {
payload, err := getPVCsResponse(session)
payload, err := getPVCsResponse(session, params)
if err != nil {
return operator_api.NewListPVCsDefault(int(err.Code)).WithPayload(err)
@@ -72,13 +74,13 @@ func registerVolumesHandlers(api *operations.OperatorAPI) {
}
func getPVCsResponse(session *models.Principal) (*models.ListPVCsResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getPVCsResponse(session *models.Principal, params operator_api.ListPVCsParams) (*models.ListPVCsResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, errors.ErrorWithContext(ctx, err)
}
// Filter Tenant PVCs. They keep their v1 tenant annotation
@@ -90,7 +92,7 @@ func getPVCsResponse(session *models.Principal) (*models.ListPVCsResponse, *mode
listAllPvcs, err2 := clientset.CoreV1().PersistentVolumeClaims("").List(ctx, listOpts)
if err2 != nil {
return nil, prepareError(err2)
return nil, errors.ErrorWithContext(ctx, err2)
}
var ListPVCs []*models.PvcsListResponse
@@ -121,12 +123,12 @@ func getPVCsResponse(session *models.Principal) (*models.ListPVCsResponse, *mode
}
func getPVCsForTenantResponse(session *models.Principal, params operator_api.ListPVCsForTenantParams) (*models.ListPVCsResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, errors.ErrorWithContext(ctx, err)
}
// Filter Tenant PVCs. They keep their v1 tenant annotation
@@ -138,7 +140,7 @@ func getPVCsForTenantResponse(session *models.Principal, params operator_api.Lis
listAllPvcs, err2 := clientset.CoreV1().PersistentVolumeClaims(params.Namespace).List(ctx, listOpts)
if err2 != nil {
return nil, prepareError(err2)
return nil, errors.ErrorWithContext(ctx, err2)
}
var ListPVCs []*models.PvcsListResponse
@@ -169,37 +171,37 @@ func getPVCsForTenantResponse(session *models.Principal, params operator_api.Lis
}
func getDeletePVCResponse(session *models.Principal, params operator_api.DeletePVCParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// get Kubernetes Client
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(err)
return errors.ErrorWithContext(ctx, err)
}
listOpts := metav1.ListOptions{
LabelSelector: fmt.Sprintf("v1.min.io/tenant=%s", params.Tenant),
FieldSelector: fmt.Sprintf("metadata.name=%s", params.PVCName),
}
if err = clientset.CoreV1().PersistentVolumeClaims(params.Namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, listOpts); err != nil {
return prepareError(err)
return errors.ErrorWithContext(ctx, err)
}
return nil
}
func getPVCEventsResponse(session *models.Principal, params operator_api.GetPVCEventsParams) (models.EventListWrapper, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
return nil, errors.ErrorWithContext(ctx, err)
}
PVC, err := clientset.CoreV1().PersistentVolumeClaims(params.Namespace).Get(ctx, params.PVCName, metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
return nil, errors.ErrorWithContext(ctx, err)
}
events, err := clientset.CoreV1().Events(params.Namespace).List(ctx, metav1.ListOptions{FieldSelector: fmt.Sprintf("involvedObject.uid=%s", PVC.UID)})
if err != nil {
return nil, prepareError(err)
return nil, errors.ErrorWithContext(ctx, err)
}
retval := models.EventListWrapper{}
for i := 0; i < len(events.Items); i++ {

View File

@@ -89,11 +89,14 @@ func SessionTokenAuthenticate(token string) (*TokenClaims, error) {
if token == "" {
return nil, ErrNoAuthToken
}
// decrypt encrypted token
claimTokens, err := decryptClaims(token)
decryptedToken, err := DecryptToken(token)
if err != nil {
// we print decryption token error information for debugging purposes
// we return a generic error that doesn't give any information to attackers
// fail decrypting token
return nil, errReadingToken
}
claimTokens, err := ParseClaimsFromToken(string(decryptedToken))
if err != nil {
// fail unmarshalling token into data structure
return nil, errReadingToken
}
// claimsTokens contains the decrypted JWT for Console
@@ -136,21 +139,26 @@ func encryptClaims(credentials *TokenClaims) (string, error) {
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// decryptClaims() receives base64 encoded ciphertext, decode it, decrypt it (AES-GCM) and produces a *TokenClaims object
func decryptClaims(ciphertext string) (*TokenClaims, error) {
// ParseClaimsFromToken receive token claims in string format, then unmarshal them to produce a *TokenClaims object
func ParseClaimsFromToken(claims string) (*TokenClaims, error) {
tokenClaims := &TokenClaims{}
if err := json.Unmarshal([]byte(claims), tokenClaims); err != nil {
return nil, err
}
return tokenClaims, nil
}
// DecryptToken receives base64 encoded ciphertext, decode it, decrypt it (AES-GCM) and produces []byte
func DecryptToken(ciphertext string) (plaintext []byte, err error) {
decoded, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return nil, err
}
plaintext, err := decrypt(decoded, []byte{})
plaintext, err = decrypt(decoded, []byte{})
if err != nil {
return nil, err
}
tokenClaims := &TokenClaims{}
if err = json.Unmarshal(plaintext, tokenClaims); err != nil {
return nil, err
}
return tokenClaims, nil
return plaintext, nil
}
const (

View File

@@ -332,3 +332,12 @@ func GetAllCertificatesAndCAs() (*x509.CertPool, []*x509.Certificate, *xcerts.Ma
}
return rootCAs, publicCerts, certsManager, nil
}
// EnsureCertAndKey checks if both client certificate and key paths are provided
func EnsureCertAndKey(clientCert, clientKey string) error {
if (clientCert != "" && clientKey == "") ||
(clientCert == "" && clientKey != "") {
return errors.New("cert and key must be specified as a pair")
}
return nil
}

23
pkg/http/headers.go Normal file
View File

@@ -0,0 +1,23 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package http
// Standard S3 HTTP response constants
const (
ETag = "ETag"
ContentType = "Content-Type"
)

View File

@@ -14,40 +14,61 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package utils
package http
import (
"io"
"io/ioutil"
"net/http"
)
// HTTPClientI interface with all functions to be implemented
// ClientI interface with all functions to be implemented
// by mock when testing, it should include all HttpClient respective api calls
// that are used within this project.
type HTTPClientI interface {
type ClientI interface {
Get(url string) (resp *http.Response, err error)
Post(url, contentType string, body io.Reader) (resp *http.Response, err error)
Do(req *http.Request) (*http.Response, error)
}
// HTTPClient Interface implementation
// Client is an HTTP Interface implementation
//
// Define the structure of a http client and define the functions that are actually used
type HTTPClient struct {
type Client struct {
Client *http.Client
}
// Get implements http.Client.Get()
func (c *HTTPClient) Get(url string) (resp *http.Response, err error) {
func (c *Client) Get(url string) (resp *http.Response, err error) {
return c.Client.Get(url)
}
// Post implements http.Client.Post()
func (c *HTTPClient) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
func (c *Client) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return c.Client.Post(url, contentType, body)
}
// Do implement http.Client.Do()
func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) {
func (c *Client) Do(req *http.Request) (*http.Response, error) {
return c.Client.Do(req)
}
// DrainBody close non nil response with any response Body.
// convenient wrapper to drain any remaining data on response body.
//
// Subsequently this allows golang http RoundTripper
// to re-use the same connection for future requests.
func DrainBody(respBody io.ReadCloser) {
// Callers should close resp.Body when done reading from it.
// If resp.Body is not closed, the Client's underlying RoundTripper
// (typically Transport) may not be able to re-use a persistent TCP
// connection to the server for a subsequent "keep-alive" request.
if respBody != nil {
// Drain any remaining Body and then close the connection.
// Without this closing connection would disallow re-using
// the same connection for future uses.
// - http://stackoverflow.com/a/17961593/4465767
defer respBody.Close()
io.Copy(ioutil.Discard, respBody)
}
}

228
pkg/logger/audit.go Normal file
View File

@@ -0,0 +1,228 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"strconv"
"sync/atomic"
"time"
"github.com/minio/console/pkg/utils"
"github.com/minio/console/pkg/logger/message/audit"
)
// ResponseWriter - is a wrapper to trap the http response status code.
type ResponseWriter struct {
http.ResponseWriter
StatusCode int
// Log body of 4xx or 5xx responses
LogErrBody bool
// Log body of all responses
LogAllBody bool
TimeToFirstByte time.Duration
StartTime time.Time
// number of bytes written
bytesWritten int
// Internal recording buffer
headers bytes.Buffer
body bytes.Buffer
// Indicate if headers are written in the log
headersLogged bool
}
// NewResponseWriter - returns a wrapped response writer to trap
// http status codes for auditing purposes.
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
return &ResponseWriter{
ResponseWriter: w,
StatusCode: http.StatusOK,
StartTime: time.Now().UTC(),
}
}
func (lrw *ResponseWriter) Write(p []byte) (int, error) {
if !lrw.headersLogged {
// We assume the response code to be '200 OK' when WriteHeader() is not called,
// that way following Golang HTTP response behavior.
lrw.WriteHeader(http.StatusOK)
}
n, err := lrw.ResponseWriter.Write(p)
lrw.bytesWritten += n
if lrw.TimeToFirstByte == 0 {
lrw.TimeToFirstByte = time.Now().UTC().Sub(lrw.StartTime)
}
if (lrw.LogErrBody && lrw.StatusCode >= http.StatusBadRequest) || lrw.LogAllBody {
// Always logging error responses.
lrw.body.Write(p)
}
if err != nil {
return n, err
}
return n, err
}
// Write the headers into the given buffer
func (lrw *ResponseWriter) writeHeaders(w io.Writer, statusCode int, headers http.Header) {
n, _ := fmt.Fprintf(w, "%d %s\n", statusCode, http.StatusText(statusCode))
lrw.bytesWritten += n
for k, v := range headers {
n, _ := fmt.Fprintf(w, "%s: %s\n", k, v[0])
lrw.bytesWritten += n
}
}
// BodyPlaceHolder returns a dummy body placeholder
var BodyPlaceHolder = []byte("<BODY>")
// Body - Return response body.
func (lrw *ResponseWriter) Body() []byte {
// If there was an error response or body logging is enabled
// then we return the body contents
if (lrw.LogErrBody && lrw.StatusCode >= http.StatusBadRequest) || lrw.LogAllBody {
return lrw.body.Bytes()
}
// ... otherwise we return the <BODY> place holder
return BodyPlaceHolder
}
// WriteHeader - writes http status code
func (lrw *ResponseWriter) WriteHeader(code int) {
if !lrw.headersLogged {
lrw.StatusCode = code
lrw.writeHeaders(&lrw.headers, code, lrw.ResponseWriter.Header())
lrw.headersLogged = true
lrw.ResponseWriter.WriteHeader(code)
}
}
// Flush - Calls the underlying Flush.
func (lrw *ResponseWriter) Flush() {
lrw.ResponseWriter.(http.Flusher).Flush()
}
// Size - reutrns the number of bytes written
func (lrw *ResponseWriter) Size() int {
return lrw.bytesWritten
}
// SetAuditEntry sets Audit info in the context.
func SetAuditEntry(ctx context.Context, audit *audit.Entry) context.Context {
if ctx == nil {
LogIf(context.Background(), fmt.Errorf("context is nil"))
return nil
}
return context.WithValue(ctx, utils.ContextAuditKey, audit)
}
// GetAuditEntry returns Audit entry if set.
func GetAuditEntry(ctx context.Context) *audit.Entry {
if ctx != nil {
r, ok := ctx.Value(utils.ContextAuditKey).(*audit.Entry)
if ok {
return r
}
r = &audit.Entry{
Version: audit.Version,
//DeploymentID: globalDeploymentID,
Time: time.Now().UTC(),
}
SetAuditEntry(ctx, r)
return r
}
return nil
}
// AuditLog - logs audit logs to all audit targets.
func AuditLog(ctx context.Context, w *ResponseWriter, r *http.Request, reqClaims map[string]interface{}, filterKeys ...string) {
// Fast exit if there is not audit target configured
if atomic.LoadInt32(&nAuditTargets) == 0 {
return
}
var entry audit.Entry
if w != nil && r != nil {
reqInfo := GetReqInfo(ctx)
if reqInfo == nil {
return
}
entry = audit.ToEntry(w, r, reqClaims, GetGlobalDeploymentID())
// indicates all requests for this API call are inbound
entry.Trigger = "incoming"
for _, filterKey := range filterKeys {
delete(entry.ReqClaims, filterKey)
delete(entry.ReqQuery, filterKey)
delete(entry.ReqHeader, filterKey)
delete(entry.RespHeader, filterKey)
}
var (
statusCode int
timeToResponse time.Duration
timeToFirstByte time.Duration
outputBytes int64 = -1 // -1: unknown output bytes
)
if w != nil {
statusCode = w.StatusCode
timeToResponse = time.Now().UTC().Sub(w.StartTime)
timeToFirstByte = w.TimeToFirstByte
outputBytes = int64(w.Size())
}
entry.API.Path = r.URL.Path
entry.API.Status = http.StatusText(statusCode)
entry.API.StatusCode = statusCode
entry.API.Method = r.Method
entry.API.InputBytes = r.ContentLength
entry.API.OutputBytes = outputBytes
entry.RequestID = reqInfo.RequestID
entry.API.TimeToResponse = strconv.FormatInt(timeToResponse.Nanoseconds(), 10) + "ns"
entry.Tags = reqInfo.GetTagsMap()
// ttfb will be recorded only for GET requests, Ignore such cases where ttfb will be empty.
if timeToFirstByte != 0 {
entry.API.TimeToFirstByte = strconv.FormatInt(timeToFirstByte.Nanoseconds(), 10) + "ns"
}
} else {
auditEntry := GetAuditEntry(ctx)
if auditEntry != nil {
entry = *auditEntry
}
}
if anonFlag {
entry.SessionID = hashString(entry.SessionID)
entry.RemoteHost = hashString(entry.RemoteHost)
}
// Send audit logs only to http targets.
for _, t := range AuditTargets() {
if err := t.Send(entry, string(All)); err != nil {
LogAlwaysIf(context.Background(), fmt.Errorf("event(%v) was not sent to Audit target (%v): %v", entry, t, err), All)
}
}
}

60
pkg/logger/color/color.go Normal file
View File

@@ -0,0 +1,60 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package color
import (
"fmt"
"github.com/fatih/color"
)
// global colors.
var (
// Check if we stderr, stdout are dumb terminals, we do not apply
// ansi coloring on dumb terminals.
IsTerminal = func() bool {
return !color.NoColor
}
Bold = func() func(a ...interface{}) string {
if IsTerminal() {
return color.New(color.Bold).SprintFunc()
}
return fmt.Sprint
}()
FgRed = func() func(a ...interface{}) string {
if IsTerminal() {
return color.New(color.FgRed).SprintFunc()
}
return fmt.Sprint
}()
BgRed = func() func(format string, a ...interface{}) string {
if IsTerminal() {
return color.New(color.BgRed).SprintfFunc()
}
return fmt.Sprintf
}()
FgWhite = func() func(format string, a ...interface{}) string {
if IsTerminal() {
return color.New(color.FgWhite).SprintfFunc()
}
return fmt.Sprintf
}()
)

213
pkg/logger/config.go Normal file
View File

@@ -0,0 +1,213 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"errors"
"strconv"
"strings"
"github.com/google/uuid"
"github.com/minio/console/pkg/logger/config"
"github.com/minio/console/pkg/logger/target/http"
"github.com/minio/pkg/env"
)
// NewConfig - initialize new logger config.
func NewConfig() Config {
cfg := Config{
HTTP: make(map[string]http.Config),
AuditWebhook: make(map[string]http.Config),
}
return cfg
}
func lookupLoggerWebhookConfig() (Config, error) {
cfg := NewConfig()
envs := env.List(EnvLoggerWebhookEndpoint)
var loggerTargets []string
for _, k := range envs {
target := strings.TrimPrefix(k, EnvLoggerWebhookEndpoint+config.Default)
if target == EnvLoggerWebhookEndpoint {
target = config.Default
}
loggerTargets = append(loggerTargets, target)
}
// Load HTTP logger from the environment if found
for _, target := range loggerTargets {
if v, ok := cfg.HTTP[target]; ok && v.Enabled {
// This target is already enabled using the
// legacy environment variables, ignore.
continue
}
enableEnv := EnvLoggerWebhookEnable
if target != config.Default {
enableEnv = EnvLoggerWebhookEnable + config.Default + target
}
enable, err := config.ParseBool(env.Get(enableEnv, ""))
if err != nil || !enable {
continue
}
endpointEnv := EnvLoggerWebhookEndpoint
if target != config.Default {
endpointEnv = EnvLoggerWebhookEndpoint + config.Default + target
}
authTokenEnv := EnvLoggerWebhookAuthToken
if target != config.Default {
authTokenEnv = EnvLoggerWebhookAuthToken + config.Default + target
}
clientCertEnv := EnvLoggerWebhookClientCert
if target != config.Default {
clientCertEnv = EnvLoggerWebhookClientCert + config.Default + target
}
clientKeyEnv := EnvLoggerWebhookClientKey
if target != config.Default {
clientKeyEnv = EnvLoggerWebhookClientKey + config.Default + target
}
err = config.EnsureCertAndKey(env.Get(clientCertEnv, ""), env.Get(clientKeyEnv, ""))
if err != nil {
return cfg, err
}
queueSizeEnv := EnvLoggerWebhookQueueSize
if target != config.Default {
queueSizeEnv = EnvLoggerWebhookQueueSize + config.Default + target
}
queueSize, err := strconv.Atoi(env.Get(queueSizeEnv, "100000"))
if err != nil {
return cfg, err
}
if queueSize <= 0 {
return cfg, errors.New("invalid queue_size value")
}
cfg.HTTP[target] = http.Config{
Enabled: true,
Endpoint: env.Get(endpointEnv, ""),
AuthToken: env.Get(authTokenEnv, ""),
ClientCert: env.Get(clientCertEnv, ""),
ClientKey: env.Get(clientKeyEnv, ""),
QueueSize: queueSize,
}
}
return cfg, nil
}
func lookupAuditWebhookConfig() (Config, error) {
cfg := NewConfig()
var loggerAuditTargets []string
envs := env.List(EnvAuditWebhookEndpoint)
for _, k := range envs {
target := strings.TrimPrefix(k, EnvAuditWebhookEndpoint+config.Default)
if target == EnvAuditWebhookEndpoint {
target = config.Default
}
loggerAuditTargets = append(loggerAuditTargets, target)
}
for _, target := range loggerAuditTargets {
if v, ok := cfg.AuditWebhook[target]; ok && v.Enabled {
// This target is already enabled using the
// legacy environment variables, ignore.
continue
}
enableEnv := EnvAuditWebhookEnable
if target != config.Default {
enableEnv = EnvAuditWebhookEnable + config.Default + target
}
enable, err := config.ParseBool(env.Get(enableEnv, ""))
if err != nil || !enable {
continue
}
endpointEnv := EnvAuditWebhookEndpoint
if target != config.Default {
endpointEnv = EnvAuditWebhookEndpoint + config.Default + target
}
authTokenEnv := EnvAuditWebhookAuthToken
if target != config.Default {
authTokenEnv = EnvAuditWebhookAuthToken + config.Default + target
}
clientCertEnv := EnvAuditWebhookClientCert
if target != config.Default {
clientCertEnv = EnvAuditWebhookClientCert + config.Default + target
}
clientKeyEnv := EnvAuditWebhookClientKey
if target != config.Default {
clientKeyEnv = EnvAuditWebhookClientKey + config.Default + target
}
err = config.EnsureCertAndKey(env.Get(clientCertEnv, ""), env.Get(clientKeyEnv, ""))
if err != nil {
return cfg, err
}
queueSizeEnv := EnvAuditWebhookQueueSize
if target != config.Default {
queueSizeEnv = EnvAuditWebhookQueueSize + config.Default + target
}
queueSize, err := strconv.Atoi(env.Get(queueSizeEnv, "100000"))
if err != nil {
return cfg, err
}
if queueSize <= 0 {
return cfg, errors.New("invalid queue_size value")
}
cfg.AuditWebhook[target] = http.Config{
Enabled: true,
Endpoint: env.Get(endpointEnv, ""),
AuthToken: env.Get(authTokenEnv, ""),
ClientCert: env.Get(clientCertEnv, ""),
ClientKey: env.Get(clientKeyEnv, ""),
QueueSize: queueSize,
}
}
return cfg, nil
}
// LookupConfigForSubSys - lookup logger config, override with ENVs if set, for the given sub-system
func LookupConfigForSubSys(subSys string) (cfg Config, err error) {
switch subSys {
case config.LoggerWebhookSubSys:
if cfg, err = lookupLoggerWebhookConfig(); err != nil {
return cfg, err
}
case config.AuditWebhookSubSys:
if cfg, err = lookupAuditWebhookConfig(); err != nil {
return cfg, err
}
}
return cfg, nil
}
// GetGlobalDeploymentID :
func GetGlobalDeploymentID() string {
if globalDeploymentID != "" {
return globalDeploymentID
}
globalDeploymentID = env.Get(EnvGlobalDeploymentID, mustGetUUID())
return globalDeploymentID
}
// mustGetUUID - get a random UUID.
func mustGetUUID() string {
u, err := uuid.NewRandom()
if err != nil {
CriticalIf(GlobalContext, err)
}
return u.String()
}

View File

@@ -0,0 +1,80 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
import (
"encoding/json"
"fmt"
"strconv"
"strings"
)
// BoolFlag - wrapper bool type.
type BoolFlag bool
// String - returns string of BoolFlag.
func (bf BoolFlag) String() string {
if bf {
return "on"
}
return "off"
}
// MarshalJSON - converts BoolFlag into JSON data.
func (bf BoolFlag) MarshalJSON() ([]byte, error) {
return json.Marshal(bf.String())
}
// UnmarshalJSON - parses given data into BoolFlag.
func (bf *BoolFlag) UnmarshalJSON(data []byte) (err error) {
var s string
if err = json.Unmarshal(data, &s); err == nil {
b := BoolFlag(true)
if s == "" {
// Empty string is treated as valid.
*bf = b
} else if b, err = ParseBoolFlag(s); err == nil {
*bf = b
}
}
return err
}
// ParseBool returns the boolean value represented by the string.
func ParseBool(str string) (bool, error) {
switch str {
case "1", "t", "T", "true", "TRUE", "True", "on", "ON", "On":
return true, nil
case "0", "f", "F", "false", "FALSE", "False", "off", "OFF", "Off":
return false, nil
}
if strings.EqualFold(str, "enabled") {
return true, nil
}
if strings.EqualFold(str, "disabled") {
return false, nil
}
return false, fmt.Errorf("ParseBool: parsing '%s': %w", str, strconv.ErrSyntax)
}
// ParseBoolFlag - parses string into BoolFlag.
func ParseBoolFlag(s string) (bf BoolFlag, err error) {
b, err := ParseBool(s)
return BoolFlag(b), err
}

View File

@@ -0,0 +1,126 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
import "testing"
// Test BoolFlag.String()
func TestBoolFlagString(t *testing.T) {
var bf BoolFlag
testCases := []struct {
flag BoolFlag
expectedResult string
}{
{bf, "off"},
{BoolFlag(true), "on"},
{BoolFlag(false), "off"},
}
for _, testCase := range testCases {
str := testCase.flag.String()
if testCase.expectedResult != str {
t.Fatalf("expected: %v, got: %v", testCase.expectedResult, str)
}
}
}
// Test BoolFlag.MarshalJSON()
func TestBoolFlagMarshalJSON(t *testing.T) {
var bf BoolFlag
testCases := []struct {
flag BoolFlag
expectedResult string
}{
{bf, `"off"`},
{BoolFlag(true), `"on"`},
{BoolFlag(false), `"off"`},
}
for _, testCase := range testCases {
data, _ := testCase.flag.MarshalJSON()
if testCase.expectedResult != string(data) {
t.Fatalf("expected: %v, got: %v", testCase.expectedResult, string(data))
}
}
}
// Test BoolFlag.UnmarshalJSON()
func TestBoolFlagUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedResult BoolFlag
expectedErr bool
}{
{[]byte(`{}`), BoolFlag(false), true},
{[]byte(`["on"]`), BoolFlag(false), true},
{[]byte(`"junk"`), BoolFlag(false), true},
{[]byte(`""`), BoolFlag(true), false},
{[]byte(`"on"`), BoolFlag(true), false},
{[]byte(`"off"`), BoolFlag(false), false},
{[]byte(`"true"`), BoolFlag(true), false},
{[]byte(`"false"`), BoolFlag(false), false},
{[]byte(`"ON"`), BoolFlag(true), false},
{[]byte(`"OFF"`), BoolFlag(false), false},
}
for _, testCase := range testCases {
var flag BoolFlag
err := (&flag).UnmarshalJSON(testCase.data)
if !testCase.expectedErr && err != nil {
t.Fatalf("error: expected = <nil>, got = %v", err)
}
if testCase.expectedErr && err == nil {
t.Fatalf("error: expected error, got = <nil>")
}
if err == nil && testCase.expectedResult != flag {
t.Fatalf("result: expected: %v, got: %v", testCase.expectedResult, flag)
}
}
}
// Test ParseBoolFlag()
func TestParseBoolFlag(t *testing.T) {
testCases := []struct {
flagStr string
expectedResult BoolFlag
expectedErr bool
}{
{"", BoolFlag(false), true},
{"junk", BoolFlag(false), true},
{"true", BoolFlag(true), false},
{"false", BoolFlag(false), false},
{"ON", BoolFlag(true), false},
{"OFF", BoolFlag(false), false},
{"on", BoolFlag(true), false},
{"off", BoolFlag(false), false},
}
for _, testCase := range testCases {
bf, err := ParseBoolFlag(testCase.flagStr)
if !testCase.expectedErr && err != nil {
t.Fatalf("error: expected = <nil>, got = %v", err)
}
if testCase.expectedErr && err == nil {
t.Fatalf("error: expected error, got = <nil>")
}
if err == nil && testCase.expectedResult != bf {
t.Fatalf("result: expected: %v, got: %v", testCase.expectedResult, bf)
}
}
}

View File

@@ -0,0 +1,30 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
import (
"errors"
)
// EnsureCertAndKey checks if both client certificate and key paths are provided
func EnsureCertAndKey(clientCert, clientKey string) error {
if (clientCert != "" && clientKey == "") ||
(clientCert == "" && clientKey != "") {
return errors.New("cert and key must be specified as a pair")
}
return nil
}

View File

@@ -0,0 +1,34 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
import (
"github.com/minio/madmin-go"
)
// Default keys
const (
Default = madmin.Default
Enable = madmin.EnableKey
License = "license" // Deprecated Dec 2021
)
// Top level config constants.
const (
LoggerWebhookSubSys = "logger_webhook"
AuditWebhookSubSys = "audit_webhook"
)

223
pkg/logger/console.go Normal file
View File

@@ -0,0 +1,223 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/minio/console/pkg/logger/color"
"github.com/minio/console/pkg/logger/message/log"
c "github.com/minio/pkg/console"
)
// ConsoleLoggerTgt is a stringified value to represent console logging
const ConsoleLoggerTgt = "console+http"
// Logger interface describes the methods that need to be implemented to satisfy the interface requirements.
type Logger interface {
json(msg string, args ...interface{})
quiet(msg string, args ...interface{})
pretty(msg string, args ...interface{})
}
func consoleLog(console Logger, msg string, args ...interface{}) {
switch {
case jsonFlag:
// Strip escape control characters from json message
msg = ansiRE.ReplaceAllLiteralString(msg, "")
console.json(msg, args...)
case quietFlag:
console.quiet(msg+"\n", args...)
default:
console.pretty(msg+"\n", args...)
}
}
// Fatal prints only fatal errors message with no stack trace
// it will be called for input validation failures
func Fatal(err error, msg string, data ...interface{}) {
fatal(err, msg, data...)
}
func fatal(err error, msg string, data ...interface{}) {
var errMsg string
if msg != "" {
errMsg = errorFmtFunc(fmt.Sprintf(msg, data...), err, jsonFlag)
} else {
errMsg = err.Error()
}
consoleLog(fatalMessage, errMsg)
}
var fatalMessage fatalMsg
type fatalMsg struct{}
func (f fatalMsg) json(msg string, args ...interface{}) {
var message string
if msg != "" {
message = fmt.Sprintf(msg, args...)
} else {
message = fmt.Sprint(args...)
}
logJSON, err := json.Marshal(&log.Entry{
Level: FatalLvl.String(),
Message: message,
Time: time.Now().UTC(),
Trace: &log.Trace{Message: message, Source: []string{getSource(6)}},
})
if err != nil {
panic(err)
}
fmt.Println(string(logJSON))
os.Exit(1)
}
func (f fatalMsg) quiet(msg string, args ...interface{}) {
f.pretty(msg, args...)
}
var (
logTag = "ERROR"
logBanner = color.BgRed(color.FgWhite(color.Bold(logTag))) + " "
emptyBanner = color.BgRed(strings.Repeat(" ", len(logTag))) + " "
bannerWidth = len(logTag) + 1
)
func (f fatalMsg) pretty(msg string, args ...interface{}) {
// Build the passed errors message
errMsg := fmt.Sprintf(msg, args...)
tagPrinted := false
// Print the errors message: the following code takes care
// of splitting errors text and always pretty printing the
// red banner along with the errors message. Since the errors
// message itself contains some colored text, we needed
// to use some ANSI control escapes to cursor color state
// and freely move in the screen.
for _, line := range strings.Split(errMsg, "\n") {
if len(line) == 0 {
// No more text to print, just quit.
break
}
for {
// Save the attributes of the current cursor helps
// us save the text color of the passed errors message
ansiSaveAttributes()
// Print banner with or without the log tag
if !tagPrinted {
c.Print(logBanner)
tagPrinted = true
} else {
c.Print(emptyBanner)
}
// Restore the text color of the errors message
ansiRestoreAttributes()
ansiMoveRight(bannerWidth)
// Continue errors message printing
c.Println(line)
break
}
}
// Exit because this is a fatal errors message
os.Exit(1)
}
type infoMsg struct{}
var info infoMsg
func (i infoMsg) json(msg string, args ...interface{}) {
var message string
if msg != "" {
message = fmt.Sprintf(msg, args...)
} else {
message = fmt.Sprint(args...)
}
logJSON, err := json.Marshal(&log.Entry{
Level: InformationLvl.String(),
Message: message,
Time: time.Now().UTC(),
})
if err != nil {
panic(err)
}
fmt.Println(string(logJSON))
}
func (i infoMsg) quiet(msg string, args ...interface{}) {
}
func (i infoMsg) pretty(msg string, args ...interface{}) {
if msg == "" {
c.Println(args...)
}
c.Printf(msg, args...)
}
type errorMsg struct{}
var errorm errorMsg
func (i errorMsg) json(msg string, args ...interface{}) {
var message string
if msg != "" {
message = fmt.Sprintf(msg, args...)
} else {
message = fmt.Sprint(args...)
}
logJSON, err := json.Marshal(&log.Entry{
Level: ErrorLvl.String(),
Message: message,
Time: time.Now().UTC(),
Trace: &log.Trace{Message: message, Source: []string{getSource(6)}},
})
if err != nil {
panic(err)
}
fmt.Println(string(logJSON))
}
func (i errorMsg) quiet(msg string, args ...interface{}) {
i.pretty(msg, args...)
}
func (i errorMsg) pretty(msg string, args ...interface{}) {
if msg == "" {
c.Println(args...)
}
c.Printf(msg, args...)
c.Printf("\n")
}
// Error :
func Error(msg string, data ...interface{}) {
consoleLog(errorm, msg, data...)
}
// Info :
func Info(msg string, data ...interface{}) {
consoleLog(info, msg, data...)
}

56
pkg/logger/const.go Normal file
View File

@@ -0,0 +1,56 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"context"
"github.com/minio/console/pkg/logger/target/http"
)
// Audit/Logger constants
const (
EnvLoggerJSONEnable = "CONSOLE_LOGGER_JSON_ENABLE"
EnvLoggerAnonymousEnable = "CONSOLE_LOGGER_ANONYMOUS_ENABLE"
EnvLoggerQuietEnable = "CONSOLE_LOGGER_QUIET_ENABLE"
EnvGlobalDeploymentID = "CONSOLE_GLOBAL_DEPLOYMENT_ID"
EnvLoggerWebhookEnable = "CONSOLE_LOGGER_WEBHOOK_ENABLE"
EnvLoggerWebhookEndpoint = "CONSOLE_LOGGER_WEBHOOK_ENDPOINT"
EnvLoggerWebhookAuthToken = "CONSOLE_LOGGER_WEBHOOK_AUTH_TOKEN"
EnvLoggerWebhookClientCert = "CONSOLE_LOGGER_WEBHOOK_CLIENT_CERT"
EnvLoggerWebhookClientKey = "CONSOLE_LOGGER_WEBHOOK_CLIENT_KEY"
EnvLoggerWebhookQueueSize = "CONSOLE_LOGGER_WEBHOOK_QUEUE_SIZE"
EnvAuditWebhookEnable = "CONSOLE_AUDIT_WEBHOOK_ENABLE"
EnvAuditWebhookEndpoint = "CONSOLE_AUDIT_WEBHOOK_ENDPOINT"
EnvAuditWebhookAuthToken = "CONSOLE_AUDIT_WEBHOOK_AUTH_TOKEN"
EnvAuditWebhookClientCert = "CONSOLE_AUDIT_WEBHOOK_CLIENT_CERT"
EnvAuditWebhookClientKey = "CONSOLE_AUDIT_WEBHOOK_CLIENT_KEY"
EnvAuditWebhookQueueSize = "CONSOLE_AUDIT_WEBHOOK_QUEUE_SIZE"
)
// Config console and http logger targets
type Config struct {
HTTP map[string]http.Config `json:"http"`
AuditWebhook map[string]http.Config `json:"audit"`
}
var (
globalDeploymentID string
GlobalContext context.Context
)

480
pkg/logger/logger.go Normal file
View File

@@ -0,0 +1,480 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"context"
"crypto/tls"
"encoding/hex"
"errors"
"fmt"
"go/build"
"net/http"
"path/filepath"
"reflect"
"runtime"
"strings"
"syscall"
"time"
"github.com/minio/pkg/env"
"github.com/minio/console/pkg"
"github.com/minio/pkg/certs"
"github.com/minio/console/pkg/logger/config"
"github.com/minio/console/pkg/logger/message/log"
"github.com/minio/highwayhash"
"github.com/minio/minio-go/v7/pkg/set"
)
// HighwayHash key for logging in anonymous mode
var magicHighwayHash256Key = []byte("\x4b\xe7\x34\xfa\x8e\x23\x8a\xcd\x26\x3e\x83\xe6\xbb\x96\x85\x52\x04\x0f\x93\x5d\xa3\x9f\x44\x14\x97\xe0\x9d\x13\x22\xde\x36\xa0")
// Disable disables all logging, false by default. (used for "go test")
var Disable = false
// Level type
type Level int8
// Enumerated level types
const (
InformationLvl Level = iota + 1
ErrorLvl
FatalLvl
)
var trimStrings []string
// TimeFormat - logging time format.
const TimeFormat string = "15:04:05 MST 01/02/2006"
var matchingFuncNames = [...]string{
"http.HandlerFunc.ServeHTTP",
"cmd.serverMain",
"cmd.StartGateway",
// add more here ..
}
func (level Level) String() string {
var lvlStr string
switch level {
case InformationLvl:
lvlStr = "INFO"
case ErrorLvl:
lvlStr = "ERROR"
case FatalLvl:
lvlStr = "FATAL"
}
return lvlStr
}
// quietFlag: Hide startup messages if enabled
// jsonFlag: Display in JSON format, if enabled
var (
quietFlag, jsonFlag, anonFlag bool
// Custom function to format errors
errorFmtFunc func(string, error, bool) string
)
// EnableQuiet - turns quiet option on.
func EnableQuiet() {
quietFlag = true
}
// EnableJSON - outputs logs in json format.
func EnableJSON() {
jsonFlag = true
quietFlag = true
}
// EnableAnonymous - turns anonymous flag
// to avoid printing sensitive information.
func EnableAnonymous() {
anonFlag = true
}
// IsAnonymous - returns true if anonFlag is true
func IsAnonymous() bool {
return anonFlag
}
// IsJSON - returns true if jsonFlag is true
func IsJSON() bool {
return jsonFlag
}
// IsQuiet - returns true if quietFlag is true
func IsQuiet() bool {
return quietFlag
}
// RegisterError registers the specified rendering function. This latter
// will be called for a pretty rendering of fatal errors.
func RegisterError(f func(string, error, bool) string) {
errorFmtFunc = f
}
// Remove any duplicates and return unique entries.
func uniqueEntries(paths []string) []string {
m := make(set.StringSet)
for _, p := range paths {
if !m.Contains(p) {
m.Add(p)
}
}
return m.ToSlice()
}
// Init sets the trimStrings to possible GOPATHs
// and GOROOT directories. Also append github.com/minio/minio
// This is done to clean up the filename, when stack trace is
// displayed when an errors happens.
func Init(goPath string, goRoot string) {
var goPathList []string
var goRootList []string
var defaultgoPathList []string
var defaultgoRootList []string
pathSeperator := ":"
// Add all possible GOPATH paths into trimStrings
// Split GOPATH depending on the OS type
if runtime.GOOS == "windows" {
pathSeperator = ";"
}
goPathList = strings.Split(goPath, pathSeperator)
goRootList = strings.Split(goRoot, pathSeperator)
defaultgoPathList = strings.Split(build.Default.GOPATH, pathSeperator)
defaultgoRootList = strings.Split(build.Default.GOROOT, pathSeperator)
// Add trim string "{GOROOT}/src/" into trimStrings
trimStrings = []string{filepath.Join(runtime.GOROOT(), "src") + string(filepath.Separator)}
// Add all possible path from GOPATH=path1:path2...:pathN
// as "{path#}/src/" into trimStrings
for _, goPathString := range goPathList {
trimStrings = append(trimStrings, filepath.Join(goPathString, "src")+string(filepath.Separator))
}
for _, goRootString := range goRootList {
trimStrings = append(trimStrings, filepath.Join(goRootString, "src")+string(filepath.Separator))
}
for _, defaultgoPathString := range defaultgoPathList {
trimStrings = append(trimStrings, filepath.Join(defaultgoPathString, "src")+string(filepath.Separator))
}
for _, defaultgoRootString := range defaultgoRootList {
trimStrings = append(trimStrings, filepath.Join(defaultgoRootString, "src")+string(filepath.Separator))
}
// Remove duplicate entries.
trimStrings = uniqueEntries(trimStrings)
// Add "github.com/minio/minio" as the last to cover
// paths like "{GOROOT}/src/github.com/minio/minio"
// and "{GOPATH}/src/github.com/minio/minio"
trimStrings = append(trimStrings, filepath.Join("github.com", "minio", "minio")+string(filepath.Separator))
}
func trimTrace(f string) string {
for _, trimString := range trimStrings {
f = strings.TrimPrefix(filepath.ToSlash(f), filepath.ToSlash(trimString))
}
return filepath.FromSlash(f)
}
func getSource(level int) string {
pc, file, lineNumber, ok := runtime.Caller(level)
if ok {
// Clean up the common prefixes
file = trimTrace(file)
_, funcName := filepath.Split(runtime.FuncForPC(pc).Name())
return fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName)
}
return ""
}
// getTrace method - creates and returns stack trace
func getTrace(traceLevel int) []string {
var trace []string
pc, file, lineNumber, ok := runtime.Caller(traceLevel)
for ok && file != "" {
// Clean up the common prefixes
file = trimTrace(file)
// Get the function name
_, funcName := filepath.Split(runtime.FuncForPC(pc).Name())
// Skip duplicate traces that start with file name, "<autogenerated>"
// and also skip traces with function name that starts with "runtime."
if !strings.HasPrefix(file, "<autogenerated>") &&
!strings.HasPrefix(funcName, "runtime.") {
// Form and append a line of stack trace into a
// collection, 'trace', to build full stack trace
trace = append(trace, fmt.Sprintf("%v:%v:%v()", file, lineNumber, funcName))
// Ignore trace logs beyond the following conditions
for _, name := range matchingFuncNames {
if funcName == name {
return trace
}
}
}
traceLevel++
// Read stack trace information from PC
pc, file, lineNumber, ok = runtime.Caller(traceLevel)
}
return trace
}
// Return the highway hash of the passed string
func hashString(input string) string {
hh, _ := highwayhash.New(magicHighwayHash256Key)
hh.Write([]byte(input))
return hex.EncodeToString(hh.Sum(nil))
}
// Kind specifies the kind of errors log
type Kind string
const (
// Minio errors
Minio Kind = "CONSOLE"
// Application errors
Application Kind = "APPLICATION"
// All errors
All Kind = "ALL"
)
// LogAlwaysIf prints a detailed errors message during
// the execution of the server.
func LogAlwaysIf(ctx context.Context, err error, errKind ...interface{}) {
if err == nil {
return
}
logIf(ctx, err, errKind...)
}
// LogIf prints a detailed errors message during
// the execution of the server
func LogIf(ctx context.Context, err error, errKind ...interface{}) {
if err == nil {
return
}
if errors.Is(err, context.Canceled) {
return
}
logIf(ctx, err, errKind...)
}
// logIf prints a detailed errors message during
// the execution of the server.
func logIf(ctx context.Context, err error, errKind ...interface{}) {
if Disable {
return
}
logKind := string(Minio)
if len(errKind) > 0 {
if ek, ok := errKind[0].(Kind); ok {
logKind = string(ek)
}
}
req := GetReqInfo(ctx)
if req == nil {
req = &ReqInfo{API: "SYSTEM"}
}
kv := req.GetTags()
tags := make(map[string]interface{}, len(kv))
for _, entry := range kv {
tags[entry.Key] = entry.Val
}
// Get full stack trace
trace := getTrace(3)
// Get the cause for the Error
message := fmt.Sprintf("%v (%T)", err, err)
if req.DeploymentID == "" {
req.DeploymentID = GetGlobalDeploymentID()
}
entry := log.Entry{
DeploymentID: req.DeploymentID,
Level: ErrorLvl.String(),
LogKind: logKind,
RemoteHost: req.RemoteHost,
Host: req.Host,
RequestID: req.RequestID,
SessionID: req.SessionID,
UserAgent: req.UserAgent,
Time: time.Now().UTC(),
Trace: &log.Trace{
Message: message,
Source: trace,
Variables: tags,
},
}
if anonFlag {
entry.SessionID = hashString(entry.SessionID)
entry.RemoteHost = hashString(entry.RemoteHost)
entry.Trace.Message = reflect.TypeOf(err).String()
entry.Trace.Variables = make(map[string]interface{})
}
// Iterate over all logger targets to send the log entry
for _, t := range SystemTargets() {
if err := t.Send(entry, entry.LogKind); err != nil {
if consoleTgt != nil {
entry.Trace.Message = fmt.Sprintf("event(%#v) was not sent to Logger target (%#v): %#v", entry, t, err)
consoleTgt.Send(entry, entry.LogKind)
}
}
}
}
// ErrCritical is the value panic'd whenever CriticalIf is called.
var ErrCritical struct{}
// CriticalIf logs the provided errors on the console. It fails the
// current go-routine by causing a `panic(ErrCritical)`.
func CriticalIf(ctx context.Context, err error, errKind ...interface{}) {
if err != nil {
LogIf(ctx, err, errKind...)
panic(ErrCritical)
}
}
// FatalIf is similar to Fatal() but it ignores passed nil errors
func FatalIf(err error, msg string, data ...interface{}) {
if err == nil {
return
}
fatal(err, msg, data...)
}
func applyDynamicConfigForSubSys(ctx context.Context, transport *http.Transport, subSys string) error {
switch subSys {
case config.LoggerWebhookSubSys:
loggerCfg, err := LookupConfigForSubSys(config.LoggerWebhookSubSys)
if err != nil {
LogIf(ctx, fmt.Errorf("unable to load logger webhook config: %w", err))
return err
}
userAgent := getUserAgent()
for n, l := range loggerCfg.HTTP {
if l.Enabled {
l.LogOnce = LogOnceIf
l.UserAgent = userAgent
l.Transport = NewHTTPTransportWithClientCerts(transport, l.ClientCert, l.ClientKey)
loggerCfg.HTTP[n] = l
}
}
err = UpdateSystemTargets(loggerCfg)
if err != nil {
LogIf(ctx, fmt.Errorf("unable to update logger webhook config: %w", err))
return err
}
case config.AuditWebhookSubSys:
loggerCfg, err := LookupConfigForSubSys(config.AuditWebhookSubSys)
if err != nil {
LogIf(ctx, fmt.Errorf("unable to load audit webhook config: %w", err))
return err
}
userAgent := getUserAgent()
for n, l := range loggerCfg.AuditWebhook {
if l.Enabled {
l.LogOnce = LogOnceIf
l.UserAgent = userAgent
l.Transport = NewHTTPTransportWithClientCerts(transport, l.ClientCert, l.ClientKey)
loggerCfg.AuditWebhook[n] = l
}
}
err = UpdateAuditWebhookTargets(loggerCfg)
if err != nil {
LogIf(ctx, fmt.Errorf("Unable to update audit webhook targets: %w", err))
return err
}
}
return nil
}
// InitializeLogger :
func InitializeLogger(ctx context.Context, transport *http.Transport) error {
err := applyDynamicConfigForSubSys(ctx, transport, config.LoggerWebhookSubSys)
if err != nil {
return err
}
err = applyDynamicConfigForSubSys(ctx, transport, config.AuditWebhookSubSys)
if err != nil {
return err
}
if enable, _ := config.ParseBool(env.Get(EnvLoggerJSONEnable, "")); enable {
EnableJSON()
}
if enable, _ := config.ParseBool(env.Get(EnvLoggerAnonymousEnable, "")); enable {
EnableAnonymous()
}
if enable, _ := config.ParseBool(env.Get(EnvLoggerQuietEnable, "")); enable {
EnableQuiet()
}
return nil
}
func getUserAgent() string {
userAgentParts := []string{}
// Helper function to concisely append a pair of strings to a
// the user-agent slice.
uaAppend := func(p, q string) {
userAgentParts = append(userAgentParts, p, q)
}
uaAppend("Console (", runtime.GOOS)
uaAppend("; ", runtime.GOARCH)
uaAppend(") Console/", pkg.Version)
uaAppend(" Console/", pkg.ReleaseTag)
uaAppend(" Console/", pkg.CommitID)
return strings.Join(userAgentParts, "")
}
// NewHTTPTransportWithClientCerts returns a new http configuration
// used while communicating with the cloud backends.
func NewHTTPTransportWithClientCerts(parentTransport *http.Transport, clientCert, clientKey string) *http.Transport {
transport := parentTransport.Clone()
if clientCert != "" && clientKey != "" {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c, err := certs.NewManager(ctx, clientCert, clientKey, tls.LoadX509KeyPair)
if err != nil {
LogIf(ctx, fmt.Errorf("failed to load client key and cert, please check your endpoint configuration: %s",
err.Error()))
}
if c != nil {
c.UpdateReloadDuration(10 * time.Second)
c.ReloadOnSignal(syscall.SIGHUP) // allow reloads upon SIGHUP
transport.TLSClientConfig.GetClientCertificate = c.GetClientCertificate
}
}
return transport
}

243
pkg/logger/logger_test.go Normal file
View File

@@ -0,0 +1,243 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"context"
"fmt"
"net/http"
"os"
"testing"
)
func testServer(w http.ResponseWriter, r *http.Request) {
}
func TestInitializeLogger(t *testing.T) {
testServerWillStart := make(chan interface{})
http.HandleFunc("/", testServer)
go func() {
close(testServerWillStart)
err := http.ListenAndServe("127.0.0.1:1337", nil)
if err != nil {
return
}
}()
<-testServerWillStart
loggerWebhookEnable := fmt.Sprintf("%s_TEST", EnvLoggerWebhookEnable)
loggerWebhookEndpoint := fmt.Sprintf("%s_TEST", EnvLoggerWebhookEndpoint)
loggerWebhookAuthToken := fmt.Sprintf("%s_TEST", EnvLoggerWebhookAuthToken)
loggerWebhookClientCert := fmt.Sprintf("%s_TEST", EnvLoggerWebhookClientCert)
loggerWebhookClientKey := fmt.Sprintf("%s_TEST", EnvLoggerWebhookClientKey)
loggerWebhookQueueSize := fmt.Sprintf("%s_TEST", EnvLoggerWebhookQueueSize)
auditWebhookEnable := fmt.Sprintf("%s_TEST", EnvAuditWebhookEnable)
auditWebhookEndpoint := fmt.Sprintf("%s_TEST", EnvAuditWebhookEndpoint)
auditWebhookAuthToken := fmt.Sprintf("%s_TEST", EnvAuditWebhookAuthToken)
auditWebhookClientCert := fmt.Sprintf("%s_TEST", EnvAuditWebhookClientCert)
auditWebhookClientKey := fmt.Sprintf("%s_TEST", EnvAuditWebhookClientKey)
auditWebhookQueueSize := fmt.Sprintf("%s_TEST", EnvAuditWebhookQueueSize)
type args struct {
ctx context.Context
transport *http.Transport
}
tests := []struct {
name string
args args
wantErr bool
setEnvVars func()
unsetEnvVars func()
}{
{
name: "logger or auditlog is not enabled",
args: args{
ctx: context.Background(),
transport: http.DefaultTransport.(*http.Transport).Clone(),
},
wantErr: false,
setEnvVars: func() {
},
unsetEnvVars: func() {
},
},
{
name: "logger webhook initialized correctly",
args: args{
ctx: context.Background(),
transport: http.DefaultTransport.(*http.Transport).Clone(),
},
wantErr: false,
setEnvVars: func() {
os.Setenv(loggerWebhookEnable, "on")
os.Setenv(loggerWebhookEndpoint, "http://127.0.0.1:1337/logger")
os.Setenv(loggerWebhookAuthToken, "test")
os.Setenv(loggerWebhookClientCert, "")
os.Setenv(loggerWebhookClientKey, "")
os.Setenv(loggerWebhookQueueSize, "1000")
},
unsetEnvVars: func() {
os.Unsetenv(loggerWebhookEnable)
os.Unsetenv(loggerWebhookEndpoint)
os.Unsetenv(loggerWebhookAuthToken)
os.Unsetenv(loggerWebhookClientCert)
os.Unsetenv(loggerWebhookClientKey)
os.Unsetenv(loggerWebhookQueueSize)
},
},
{
name: "logger webhook failed to initialize",
args: args{
ctx: context.Background(),
transport: http.DefaultTransport.(*http.Transport).Clone(),
},
wantErr: true,
setEnvVars: func() {
os.Setenv(loggerWebhookEnable, "on")
os.Setenv(loggerWebhookEndpoint, "https://aklsjdakljdjkalsd.com")
os.Setenv(loggerWebhookAuthToken, "test")
os.Setenv(loggerWebhookClientCert, "")
os.Setenv(loggerWebhookClientKey, "")
os.Setenv(loggerWebhookQueueSize, "1000")
},
unsetEnvVars: func() {
os.Unsetenv(loggerWebhookEnable)
os.Unsetenv(loggerWebhookEndpoint)
os.Unsetenv(loggerWebhookAuthToken)
os.Unsetenv(loggerWebhookClientCert)
os.Unsetenv(loggerWebhookClientKey)
os.Unsetenv(loggerWebhookQueueSize)
},
},
{
name: "auditlog webhook initialized correctly",
args: args{
ctx: context.Background(),
transport: http.DefaultTransport.(*http.Transport).Clone(),
},
wantErr: false,
setEnvVars: func() {
os.Setenv(auditWebhookEnable, "on")
os.Setenv(auditWebhookEndpoint, "http://127.0.0.1:1337/audit")
os.Setenv(auditWebhookAuthToken, "test")
os.Setenv(auditWebhookClientCert, "")
os.Setenv(auditWebhookClientKey, "")
os.Setenv(auditWebhookQueueSize, "1000")
},
unsetEnvVars: func() {
os.Unsetenv(auditWebhookEnable)
os.Unsetenv(auditWebhookEndpoint)
os.Unsetenv(auditWebhookAuthToken)
os.Unsetenv(auditWebhookClientCert)
os.Unsetenv(auditWebhookClientKey)
os.Unsetenv(auditWebhookQueueSize)
},
},
{
name: "auditlog webhook failed to initialize",
args: args{
ctx: context.Background(),
transport: http.DefaultTransport.(*http.Transport).Clone(),
},
wantErr: true,
setEnvVars: func() {
os.Setenv(auditWebhookEnable, "on")
os.Setenv(auditWebhookEndpoint, "https://aklsjdakljdjkalsd.com")
os.Setenv(auditWebhookAuthToken, "test")
os.Setenv(auditWebhookClientCert, "")
os.Setenv(auditWebhookClientKey, "")
os.Setenv(auditWebhookQueueSize, "1000")
},
unsetEnvVars: func() {
os.Unsetenv(auditWebhookEnable)
os.Unsetenv(auditWebhookEndpoint)
os.Unsetenv(auditWebhookAuthToken)
os.Unsetenv(auditWebhookClientCert)
os.Unsetenv(auditWebhookClientKey)
os.Unsetenv(auditWebhookQueueSize)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.setEnvVars != nil {
tt.setEnvVars()
}
if err := InitializeLogger(tt.args.ctx, tt.args.transport); (err != nil) != tt.wantErr {
t.Errorf("InitializeLogger() error = %v, wantErr %v", err, tt.wantErr)
}
if tt.unsetEnvVars != nil {
tt.unsetEnvVars()
}
})
}
}
func TestEnableJSON(t *testing.T) {
tests := []struct {
name string
}{
{
name: "enable json",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
EnableJSON()
if !IsJSON() {
t.Errorf("EnableJSON() = %v, want %v", IsJSON(), true)
}
})
}
}
func TestEnableQuiet(t *testing.T) {
tests := []struct {
name string
}{
{
name: "enable quiet",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
EnableQuiet()
if !IsQuiet() {
t.Errorf("EnableQuiet() = %v, want %v", IsQuiet(), true)
}
})
}
}
func TestEnableAnonymous(t *testing.T) {
tests := []struct {
name string
}{
{
name: "enable anonymous",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
EnableAnonymous()
if !IsAnonymous() {
t.Errorf("EnableAnonymous() = %v, want %v", IsAnonymous(), true)
}
})
}
}

92
pkg/logger/logonce.go Normal file
View File

@@ -0,0 +1,92 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"context"
"errors"
"net/http"
"sync"
"time"
)
// Holds a map of recently logged errors.
type logOnceType struct {
IDMap map[interface{}]error
sync.Mutex
}
// One log message per errors.
func (l *logOnceType) logOnceIf(ctx context.Context, err error, id interface{}, errKind ...interface{}) {
if err == nil {
return
}
l.Lock()
shouldLog := false
prevErr := l.IDMap[id]
if prevErr == nil {
l.IDMap[id] = err
shouldLog = true
} else if prevErr.Error() != err.Error() {
l.IDMap[id] = err
shouldLog = true
}
l.Unlock()
if shouldLog {
LogIf(ctx, err, errKind...)
}
}
// Cleanup the map every 30 minutes so that the log message is printed again for the user to notice.
func (l *logOnceType) cleanupRoutine() {
for {
l.Lock()
l.IDMap = make(map[interface{}]error)
l.Unlock()
time.Sleep(30 * time.Minute)
}
}
// Returns logOnceType
func newLogOnceType() *logOnceType {
l := &logOnceType{IDMap: make(map[interface{}]error)}
go l.cleanupRoutine()
return l
}
var logOnce = newLogOnceType()
// LogOnceIf - Logs notification errors - once per errors.
// id is a unique identifier for related log messages, refer to cmd/notification.go
// on how it is used.
func LogOnceIf(ctx context.Context, err error, id interface{}, errKind ...interface{}) {
if err == nil {
return
}
if errors.Is(err, context.Canceled) {
return
}
if err.Error() == http.ErrServerClosed.Error() || err.Error() == "disk not found" {
return
}
logOnce.logOnceIf(ctx, err, id, errKind...)
}

View File

@@ -0,0 +1,130 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package audit
import (
"net/http"
"os"
"strings"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/minio/console/pkg/utils"
xhttp "github.com/minio/console/pkg/http"
)
// Version - represents the current version of audit log structure.
const Version = "1"
// ObjectVersion object version key/versionId
type ObjectVersion struct {
ObjectName string `json:"objectName"`
VersionID string `json:"versionId,omitempty"`
}
// Entry - audit entry logs.
type Entry struct {
Version string `json:"version"`
DeploymentID string `json:"deploymentid,omitempty"`
Time time.Time `json:"time"`
Trigger string `json:"trigger"`
API struct {
Path string `json:"path,omitempty"`
Status string `json:"status,omitempty"`
Method string `json:"method"`
StatusCode int `json:"statusCode,omitempty"`
InputBytes int64 `json:"rx"`
OutputBytes int64 `json:"tx"`
TimeToFirstByte string `json:"timeToFirstByte,omitempty"`
TimeToResponse string `json:"timeToResponse,omitempty"`
} `json:"api"`
RemoteHost string `json:"remotehost,omitempty"`
RequestID string `json:"requestID,omitempty"`
SessionID string `json:"sessionID,omitempty"`
UserAgent string `json:"userAgent,omitempty"`
ReqClaims map[string]interface{} `json:"requestClaims,omitempty"`
ReqQuery map[string]string `json:"requestQuery,omitempty"`
ReqHeader map[string]string `json:"requestHeader,omitempty"`
RespHeader map[string]string `json:"responseHeader,omitempty"`
Tags map[string]interface{} `json:"tags,omitempty"`
}
// NewEntry - constructs an audit entry object with some fields filled
func NewEntry(deploymentID string) Entry {
return Entry{
Version: Version,
DeploymentID: deploymentID,
Time: time.Now().UTC(),
}
}
// ToEntry - constructs an audit entry from a http request
func ToEntry(w http.ResponseWriter, r *http.Request, reqClaims map[string]interface{}, deploymentID string) Entry {
entry := NewEntry(deploymentID)
entry.RemoteHost = r.RemoteAddr
entry.UserAgent = r.UserAgent()
entry.ReqClaims = reqClaims
q := r.URL.Query()
reqQuery := make(map[string]string, len(q))
for k, v := range q {
reqQuery[k] = strings.Join(v, ",")
}
entry.ReqQuery = reqQuery
reqHeader := make(map[string]string, len(r.Header))
for k, v := range r.Header {
reqHeader[k] = strings.Join(v, ",")
}
entry.ReqHeader = reqHeader
wh := w.Header()
var requestID interface{}
requestID = r.Context().Value(utils.ContextRequestID)
if requestID == nil {
requestID, _ = utils.NewUUID()
}
entry.RequestID = requestID.(string)
if val := r.Context().Value(utils.ContextRequestUserID); val != nil {
sessionID := val.(string)
if os.Getenv("CONSOLE_OPERATOR_MODE") != "" && os.Getenv("CONSOLE_OPERATOR_MODE") == "on" {
claims := jwt.MapClaims{}
_, _ = jwt.ParseWithClaims(sessionID, claims, nil)
if sub, ok := claims["sub"]; ok {
sessionID = sub.(string)
}
}
entry.SessionID = sessionID
}
respHeader := make(map[string]string, len(wh))
for k, v := range wh {
respHeader[k] = strings.Join(v, ",")
}
entry.RespHeader = respHeader
if etag := respHeader[xhttp.ETag]; etag != "" {
respHeader[xhttp.ETag] = strings.Trim(etag, `"`)
}
return entry
}

View File

@@ -0,0 +1,126 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package audit
import (
"context"
"net/http"
"net/http/httptest"
"os"
"reflect"
"testing"
"time"
"github.com/minio/console/pkg/utils"
)
func TestNewEntry(t *testing.T) {
type args struct {
deploymentID string
}
tests := []struct {
name string
args args
want Entry
}{
{
name: "constructs an audit entry object with some fields filled",
args: args{
deploymentID: "1",
},
want: Entry{
Version: Version,
DeploymentID: "1",
Time: time.Now().UTC(),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewEntry(tt.args.deploymentID); got.DeploymentID != tt.want.DeploymentID {
t.Errorf("NewEntry() = %v, want %v", got, tt.want)
}
})
}
}
func TestToEntry(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/v1/tenants?test=xyz", nil)
req.Header.Set("Authorization", "xyz")
req.Header.Set("ETag", "\"ABCDE\"")
// applying context information
ctx := context.WithValue(req.Context(), utils.ContextRequestUserID, "eyJhbGciOiJSUzI1NiIsImtpZCI6Ing5cS0wSkEwQzFMWDJlRlR3dHo2b0t0NVNnRzJad0llMGVNczMxbjU0b2sifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJtaW5pby1vcGVyYXRvciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjb25zb2xlLXNhLXRva2VuLWJrZzZwIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImNvbnNvbGUtc2EiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJhZTE2ZGVkNS01MmM3LTRkZTQtOWUxYS1iNmI4NGU2OGMzM2UiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6bWluaW8tb3BlcmF0b3I6Y29uc29sZS1zYSJ9.AjhzekAPC59SQVBQL5sr-1dqr57-jH8a5LVazpnEr_cC0JqT4jXYjdfbrZSF9yaL4gHRv2l0kOhBlrjRK7y-IpMbxE71Fne_lSzaptSuqgI5I9dFvpVfZWP1yMAqav8mrlUoWkWDq9IAkyH4bvvZrVgQJGgd5t9U_7DQCVwbkQvy0wGS5zoMcZhYenn_Ub1BoxWcviADQ1aY1wQju8OP0IOwKTIMXMQqciOFdJ9T5-tQEGUrikTu_tW-1shUHzOxBcEzGVtBvBy2OmbNnRFYogbhmp-Dze6EAi035bY32bfL7XKBUNCW6_3VbN_h3pQNAuT2NJOSKuhJ3cGldCB2zg")
req = req.WithContext(ctx)
w := httptest.NewRecorder()
w.Header().Set("Authorization", "xyz")
w.Header().Set("ETag", "\"ABCDE\"")
type args struct {
w http.ResponseWriter
r *http.Request
reqClaims map[string]interface{}
deploymentID string
}
tests := []struct {
name string
args args
want Entry
preFunc func()
postFunc func()
}{
{
preFunc: func() {
os.Setenv("CONSOLE_OPERATOR_MODE", "on")
},
postFunc: func() {
os.Unsetenv("CONSOLE_OPERATOR_MODE")
},
name: "constructs an audit entry from a http request",
args: args{
w: w,
r: req,
reqClaims: map[string]interface{}{},
deploymentID: "1",
},
want: Entry{
Version: "1",
DeploymentID: "1",
SessionID: "system:serviceaccount:minio-operator:console-sa",
ReqQuery: map[string]string{"test": "xyz"},
ReqHeader: map[string]string{"test": "xyz"},
RespHeader: map[string]string{"test": "xyz", "ETag": "ABCDE"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.preFunc != nil {
tt.preFunc()
}
if got := ToEntry(tt.args.w, tt.args.r, tt.args.reqClaims, tt.args.deploymentID); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ToEntry() = %v, want %v", got, tt.want)
}
if tt.postFunc != nil {
tt.postFunc()
}
})
}
}

View File

@@ -0,0 +1,64 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package log
import (
"time"
)
// ObjectVersion object version key/versionId
type ObjectVersion struct {
ObjectName string `json:"objectName"`
VersionID string `json:"versionId,omitempty"`
}
// Args - defines the arguments for the API.
type Args struct {
Bucket string `json:"bucket,omitempty"`
Object string `json:"object,omitempty"`
VersionID string `json:"versionId,omitempty"`
Objects []ObjectVersion `json:"objects,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// Trace - defines the trace.
type Trace struct {
Message string `json:"message,omitempty"`
Source []string `json:"source,omitempty"`
Variables map[string]interface{} `json:"variables,omitempty"`
}
// API - defines the api type and its args.
type API struct {
Name string `json:"name,omitempty"`
}
// Entry - defines fields and values of each log entry.
type Entry struct {
DeploymentID string `json:"deploymentid,omitempty"`
Level string `json:"level"`
LogKind string `json:"errKind"`
Time time.Time `json:"time"`
API *API `json:"api,omitempty"`
RemoteHost string `json:"remotehost,omitempty"`
Host string `json:"host,omitempty"`
RequestID string `json:"requestID,omitempty"`
SessionID string `json:"sessionID,omitempty"`
UserAgent string `json:"userAgent,omitempty"`
Message string `json:"message,omitempty"`
Trace *Trace `json:"errors,omitempty"`
}

117
pkg/logger/reqinfo.go Normal file
View File

@@ -0,0 +1,117 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"context"
"fmt"
"sync"
"github.com/minio/console/pkg/utils"
)
// KeyVal - appended to ReqInfo.Tags
type KeyVal struct {
Key string
Val interface{}
}
// ObjectVersion object version key/versionId
type ObjectVersion struct {
ObjectName string
VersionID string `json:"VersionId,omitempty"`
}
// ReqInfo stores the request info.
type ReqInfo struct {
RemoteHost string // Client Host/IP
Host string // Node Host/IP
UserAgent string // User Agent
DeploymentID string // x-minio-deployment-id
RequestID string // x-amz-request-id
SessionID string // custom session id
API string // API name - GetObject PutObject NewMultipartUpload etc.
BucketName string `json:",omitempty"` // Bucket name
ObjectName string `json:",omitempty"` // Object name
VersionID string `json:",omitempty"` // corresponding versionID for the object
Objects []ObjectVersion `json:",omitempty"` // Only set during MultiObject delete handler.
AccessKey string // Access Key
tags []KeyVal // Any additional info not accommodated by above fields
sync.RWMutex
}
// GetTags - returns the user defined tags
func (r *ReqInfo) GetTags() []KeyVal {
if r == nil {
return nil
}
r.RLock()
defer r.RUnlock()
return append([]KeyVal(nil), r.tags...)
}
// GetTagsMap - returns the user defined tags in a map structure
func (r *ReqInfo) GetTagsMap() map[string]interface{} {
if r == nil {
return nil
}
r.RLock()
defer r.RUnlock()
m := make(map[string]interface{}, len(r.tags))
for _, t := range r.tags {
m[t.Key] = t.Val
}
return m
}
// SetReqInfo sets ReqInfo in the context.
func SetReqInfo(ctx context.Context, req *ReqInfo) context.Context {
if ctx == nil {
LogIf(context.Background(), fmt.Errorf("context is nil"))
return nil
}
return context.WithValue(ctx, utils.ContextLogKey, req)
}
// GetReqInfo returns ReqInfo if set.
func GetReqInfo(ctx context.Context) *ReqInfo {
if ctx != nil {
r, ok := ctx.Value(utils.ContextLogKey).(*ReqInfo)
if ok {
return r
}
r = &ReqInfo{}
if val, o := ctx.Value(utils.ContextRequestID).(string); o {
r.RequestID = val
}
if val, o := ctx.Value(utils.ContextRequestUserID).(string); o {
r.SessionID = val
}
if val, o := ctx.Value(utils.ContextRequestUserAgent).(string); o {
r.UserAgent = val
}
if val, o := ctx.Value(utils.ContextRequestHost).(string); o {
r.Host = val
}
if val, o := ctx.Value(utils.ContextRequestRemoteAddr).(string); o {
r.RemoteHost = val
}
SetReqInfo(ctx, r)
return r
}
return nil
}

View File

@@ -0,0 +1,226 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package http
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
xhttp "github.com/minio/console/pkg/http"
"github.com/minio/console/pkg/logger/target/types"
)
// Timeout for the webhook http call
const webhookCallTimeout = 5 * time.Second
// Config http logger target
type Config struct {
Enabled bool `json:"enabled"`
Name string `json:"name"`
UserAgent string `json:"userAgent"`
Endpoint string `json:"endpoint"`
AuthToken string `json:"authToken"`
ClientCert string `json:"clientCert"`
ClientKey string `json:"clientKey"`
QueueSize int `json:"queueSize"`
Transport http.RoundTripper `json:"-"`
// Custom logger
LogOnce func(ctx context.Context, err error, id interface{}, errKind ...interface{}) `json:"-"`
}
// Target implements logger.Target and sends the json
// format of a log entry to the configured http endpoint.
// An internal buffer of logs is maintained but when the
// buffer is full, new logs are just ignored and an errors
// is returned to the caller.
type Target struct {
status int32
wg sync.WaitGroup
// Channel of log entries
logCh chan interface{}
config Config
}
// Endpoint returns the backend endpoint
func (h *Target) Endpoint() string {
return h.config.Endpoint
}
func (h *Target) String() string {
return h.config.Name
}
// Init validate and initialize the http target
func (h *Target) Init() error {
ctx, cancel := context.WithTimeout(context.Background(), 2*webhookCallTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, h.config.Endpoint, strings.NewReader(`{}`))
if err != nil {
return err
}
req.Header.Set(xhttp.ContentType, "application/json")
// Set user-agent to indicate MinIO release
// version to the configured log endpoint
req.Header.Set("User-Agent", h.config.UserAgent)
if h.config.AuthToken != "" {
req.Header.Set("Authorization", h.config.AuthToken)
}
client := http.Client{Transport: h.config.Transport}
resp, err := client.Do(req)
if err != nil {
return err
}
// Drain any response.
xhttp.DrainBody(resp.Body)
if !acceptedResponseStatusCode(resp.StatusCode) {
switch resp.StatusCode {
case http.StatusForbidden:
return fmt.Errorf("%s returned '%s', please check if your auth token is correctly set",
h.config.Endpoint, resp.Status)
}
return fmt.Errorf("%s returned '%s', please check your endpoint configuration",
h.config.Endpoint, resp.Status)
}
h.status = 1
go h.startHTTPLogger()
return nil
}
// Accepted HTTP Status Codes
var acceptedStatusCodeMap = map[int]bool{http.StatusOK: true, http.StatusCreated: true, http.StatusAccepted: true, http.StatusNoContent: true}
func acceptedResponseStatusCode(code int) bool {
return acceptedStatusCodeMap[code]
}
func (h *Target) logEntry(entry interface{}) {
logJSON, err := json.Marshal(&entry)
if err != nil {
return
}
ctx, cancel := context.WithTimeout(context.Background(), webhookCallTimeout)
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
h.config.Endpoint, bytes.NewReader(logJSON))
if err != nil {
h.config.LogOnce(ctx, fmt.Errorf("%s returned '%w', please check your endpoint configuration", h.config.Endpoint, err), h.config.Endpoint)
cancel()
return
}
req.Header.Set(xhttp.ContentType, "application/json")
// Set user-agent to indicate MinIO release
// version to the configured log endpoint
req.Header.Set("User-Agent", h.config.UserAgent)
if h.config.AuthToken != "" {
req.Header.Set("Authorization", h.config.AuthToken)
}
client := http.Client{Transport: h.config.Transport}
resp, err := client.Do(req)
cancel()
if err != nil {
h.config.LogOnce(ctx, fmt.Errorf("%s returned '%w', please check your endpoint configuration", h.config.Endpoint, err), h.config.Endpoint)
return
}
// Drain any response.
xhttp.DrainBody(resp.Body)
if !acceptedResponseStatusCode(resp.StatusCode) {
switch resp.StatusCode {
case http.StatusForbidden:
h.config.LogOnce(ctx, fmt.Errorf("%s returned '%s', please check if your auth token is correctly set", h.config.Endpoint, resp.Status), h.config.Endpoint)
default:
h.config.LogOnce(ctx, fmt.Errorf("%s returned '%s', please check your endpoint configuration", h.config.Endpoint, resp.Status), h.config.Endpoint)
}
}
}
func (h *Target) startHTTPLogger() {
// Create a routine which sends json logs received
// from an internal channel.
go func() {
h.wg.Add(1)
defer h.wg.Done()
for entry := range h.logCh {
h.logEntry(entry)
}
}()
}
// New initializes a new logger target which
// sends log over http to the specified endpoint
func New(config Config) *Target {
h := &Target{
logCh: make(chan interface{}, config.QueueSize),
config: config,
}
return h
}
// Send log message 'e' to http target.
func (h *Target) Send(entry interface{}, errKind string) error {
if atomic.LoadInt32(&h.status) == 0 {
// Channel was closed or used before init.
return nil
}
select {
case h.logCh <- entry:
default:
// log channel is full, do not wait and return
// an errors immediately to the caller
return errors.New("log buffer full")
}
return nil
}
// Cancel - cancels the target
func (h *Target) Cancel() {
if atomic.CompareAndSwapInt32(&h.status, 1, 0) {
close(h.logCh)
}
h.wg.Wait()
}
// Type - returns type of the target
func (h *Target) Type() types.TargetType {
return types.TargetHTTP
}

View File

@@ -0,0 +1,27 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package types
// TargetType indicates type of the target e.g. console, http, kafka
type TargetType uint8
// Constants for target types
const (
_ TargetType = iota
TargetConsole
TargetHTTP
)

151
pkg/logger/targets.go Normal file
View File

@@ -0,0 +1,151 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"sync"
"sync/atomic"
"github.com/minio/console/pkg/logger/target/http"
"github.com/minio/console/pkg/logger/target/types"
)
// Target is the entity that we will receive
// a single log entry and Send it to the log target
// e.g. Send the log to a http server
type Target interface {
String() string
Endpoint() string
Init() error
Cancel()
Send(entry interface{}, errKind string) error
Type() types.TargetType
}
var (
// swapMu must be held while reading slice info or swapping targets or auditTargets.
swapMu sync.Mutex
// systemTargets is the set of enabled loggers.
// Must be immutable at all times.
// Can be swapped to another while holding swapMu
systemTargets = []Target{}
// This is always set represent /dev/console target
consoleTgt Target
nTargets int32 // atomic count of len(targets)
)
// SystemTargets returns active targets.
// Returned slice may not be modified in any way.
func SystemTargets() []Target {
if atomic.LoadInt32(&nTargets) == 0 {
// Lock free if none...
return nil
}
swapMu.Lock()
res := systemTargets
swapMu.Unlock()
return res
}
// AuditTargets returns active audit targets.
// Returned slice may not be modified in any way.
func AuditTargets() []Target {
if atomic.LoadInt32(&nAuditTargets) == 0 {
// Lock free if none...
return nil
}
swapMu.Lock()
res := auditTargets
swapMu.Unlock()
return res
}
// auditTargets is the list of enabled audit loggers
// Must be immutable at all times.
// Can be swapped to another while holding swapMu
var (
auditTargets = []Target{}
nAuditTargets int32 // atomic count of len(auditTargets)
)
func cancelAllSystemTargets() {
for _, tgt := range systemTargets {
tgt.Cancel()
}
}
func initSystemTargets(cfgMap map[string]http.Config) (tgts []Target, err error) {
for _, l := range cfgMap {
if l.Enabled {
t := http.New(l)
if err = t.Init(); err != nil {
return tgts, err
}
tgts = append(tgts, t)
}
}
return tgts, err
}
// UpdateSystemTargets swaps targets with newly loaded ones from the cfg
func UpdateSystemTargets(cfg Config) error {
updated, err := initSystemTargets(cfg.HTTP)
if err != nil {
return err
}
swapMu.Lock()
for _, tgt := range systemTargets {
// Preserve console target when dynamically updating
// other HTTP targets, console target is always present.
if tgt.Type() == types.TargetConsole {
updated = append(updated, tgt)
break
}
}
atomic.StoreInt32(&nTargets, int32(len(updated)))
cancelAllSystemTargets() // cancel running targets
systemTargets = updated
swapMu.Unlock()
return nil
}
func cancelAuditTargetType(t types.TargetType) {
for _, tgt := range auditTargets {
if tgt.Type() == t {
tgt.Cancel()
}
}
}
// UpdateAuditWebhookTargets swaps audit webhook targets with newly loaded ones from the cfg
func UpdateAuditWebhookTargets(cfg Config) error {
updated, err := initSystemTargets(cfg.AuditWebhook)
if err != nil {
return err
}
swapMu.Lock()
atomic.StoreInt32(&nAuditTargets, int32(len(updated)))
cancelAuditTargetType(types.TargetHTTP) // cancel running targets
auditTargets = updated
swapMu.Unlock()
return nil
}

60
pkg/logger/utils.go Normal file
View File

@@ -0,0 +1,60 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"fmt"
"regexp"
"runtime"
"github.com/minio/console/pkg/logger/color"
)
var ansiRE = regexp.MustCompile("(\x1b[^m]*m)")
// Print ANSI Control escape
func ansiEscape(format string, args ...interface{}) {
Esc := "\x1b"
fmt.Printf("%s%s", Esc, fmt.Sprintf(format, args...))
}
func ansiMoveRight(n int) {
if runtime.GOOS == "windows" {
return
}
if color.IsTerminal() {
ansiEscape("[%dC", n)
}
}
func ansiSaveAttributes() {
if runtime.GOOS == "windows" {
return
}
if color.IsTerminal() {
ansiEscape("7")
}
}
func ansiRestoreAttributes() {
if runtime.GOOS == "windows" {
return
}
if color.IsTerminal() {
ansiEscape("8")
}
}

View File

@@ -24,7 +24,7 @@ import (
"fmt"
"log"
"github.com/minio/console/pkg/utils"
"github.com/minio/console/pkg/http"
"github.com/minio/pkg/licverifier"
@@ -34,7 +34,7 @@ import (
"github.com/tidwall/gjson"
)
func LoginWithMFA(client utils.HTTPClientI, username, mfaToken, otp string) (*LoginResp, error) {
func LoginWithMFA(client http.ClientI, username, mfaToken, otp string) (*LoginResp, error) {
mfaLoginReq := MfaReq{Username: username, OTP: otp, Token: mfaToken}
resp, err := subnetPostReq(client, subnetMFAURL(), mfaLoginReq, nil)
if err != nil {
@@ -47,7 +47,7 @@ func LoginWithMFA(client utils.HTTPClientI, username, mfaToken, otp string) (*Lo
return nil, errors.New("access token not found in response")
}
func Login(client utils.HTTPClientI, username, password string) (*LoginResp, error) {
func Login(client http.ClientI, username, password string) (*LoginResp, error) {
loginReq := map[string]string{
"username": username,
"password": password,
@@ -71,7 +71,7 @@ func Login(client utils.HTTPClientI, username, password string) (*LoginResp, err
return nil, errors.New("access token not found in response")
}
func GetOrganizations(client utils.HTTPClientI, token string) ([]*models.SubnetOrganization, error) {
func GetOrganizations(client http.ClientI, token string) ([]*models.SubnetOrganization, error) {
headers := subnetAuthHeaders(token)
respStr, err := subnetGetReq(client, subnetOrgsURL(), headers)
if err != nil {
@@ -90,7 +90,7 @@ type LicenseTokenConfig struct {
Proxy string
}
func Register(client utils.HTTPClientI, admInfo madmin.InfoMessage, apiKey, token, accountID string) (*LicenseTokenConfig, error) {
func Register(client http.ClientI, admInfo madmin.InfoMessage, apiKey, token, accountID string) (*LicenseTokenConfig, error) {
var headers map[string]string
regInfo := GetClusterRegInfo(admInfo)
regURL := subnetRegisterURL()
@@ -128,7 +128,7 @@ func Register(client utils.HTTPClientI, admInfo madmin.InfoMessage, apiKey, toke
const publicKey = "/downloads/license-pubkey.pem"
// downloadSubnetPublicKey will download the current subnet public key.
func downloadSubnetPublicKey(client utils.HTTPClientI) (string, error) {
func downloadSubnetPublicKey(client http.ClientI) (string, error) {
// Get the public key directly from Subnet
url := fmt.Sprintf("%s%s", subnetBaseURL(), publicKey)
resp, err := client.Get(url)
@@ -145,7 +145,7 @@ func downloadSubnetPublicKey(client utils.HTTPClientI) (string, error) {
}
// ParseLicense parses the license with the bundle public key and return it's information
func ParseLicense(client utils.HTTPClientI, license string) (*licverifier.LicenseInfo, error) {
func ParseLicense(client http.ClientI, license string) (*licverifier.LicenseInfo, error) {
var publicKeys []string
subnetPubKey, err := downloadSubnetPublicKey(client)

View File

@@ -25,7 +25,7 @@ import (
"io/ioutil"
"net/http"
"github.com/minio/console/pkg/utils"
xhttp "github.com/minio/console/pkg/http"
"github.com/minio/madmin-go"
mc "github.com/minio/mc/cmd"
@@ -69,11 +69,11 @@ func subnetAuthHeaders(authToken string) map[string]string {
return map[string]string{"Authorization": "Bearer " + authToken}
}
func httpDo(client utils.HTTPClientI, req *http.Request) (*http.Response, error) {
func httpDo(client xhttp.ClientI, req *http.Request) (*http.Response, error) {
return client.Do(req)
}
func subnetReqDo(client utils.HTTPClientI, r *http.Request, headers map[string]string) (string, error) {
func subnetReqDo(client xhttp.ClientI, r *http.Request, headers map[string]string) (string, error) {
for k, v := range headers {
r.Header.Add(k, v)
}
@@ -98,10 +98,10 @@ func subnetReqDo(client utils.HTTPClientI, r *http.Request, headers map[string]s
if resp.StatusCode == http.StatusOK {
return respStr, nil
}
return respStr, fmt.Errorf("Request failed with code %d and error: %s", resp.StatusCode, respStr)
return respStr, fmt.Errorf("Request failed with code %d and errors: %s", resp.StatusCode, respStr)
}
func subnetGetReq(client utils.HTTPClientI, reqURL string, headers map[string]string) (string, error) {
func subnetGetReq(client xhttp.ClientI, reqURL string, headers map[string]string) (string, error) {
r, e := http.NewRequest(http.MethodGet, reqURL, nil)
if e != nil {
return "", e
@@ -109,7 +109,7 @@ func subnetGetReq(client utils.HTTPClientI, reqURL string, headers map[string]st
return subnetReqDo(client, r, headers)
}
func subnetPostReq(client utils.HTTPClientI, reqURL string, payload interface{}, headers map[string]string) (string, error) {
func subnetPostReq(client xhttp.ClientI, reqURL string, payload interface{}, headers map[string]string) (string, error) {
body, e := json.Marshal(payload)
if e != nil {
return "", e

39
pkg/utils/utils.go Normal file
View File

@@ -0,0 +1,39 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package utils
import "github.com/google/uuid"
// NewUUID - get a random UUID.
func NewUUID() (string, error) {
u, err := uuid.NewRandom()
if err != nil {
return "", err
}
return u.String(), nil
}
// Key used for Get/SetReqInfo
type key string
const ContextLogKey = key("console-log")
const ContextRequestID = key("request-id")
const ContextRequestUserID = key("request-user-id")
const ContextRequestUserAgent = key("request-user-agent")
const ContextRequestHost = key("request-host")
const ContextRequestRemoteAddr = key("request-remote-addr")
const ContextAuditKey = key("request-audit-entry")

View File

@@ -21,6 +21,8 @@ import (
"fmt"
"io/ioutil"
"regexp"
"github.com/minio/console/pkg/http"
)
var (
@@ -28,7 +30,7 @@ var (
)
// getLatestMinIOImage returns the latest docker image for MinIO if found on the internet
func GetLatestMinIOImage(client HTTPClientI) (*string, error) {
func GetLatestMinIOImage(client http.ClientI) (*string, error) {
resp, err := client.Get("https://dl.min.io/server/minio/release/linux-amd64/")
if err != nil {
return nil, err

View File

@@ -29,7 +29,7 @@ import (
func registerAdminArnsHandlers(api *operations.ConsoleAPI) {
// return a list of arns
api.SystemArnListHandler = systemApi.ArnListHandlerFunc(func(params systemApi.ArnListParams, session *models.Principal) middleware.Responder {
arnsResp, err := getArnsResponse(session)
arnsResp, err := getArnsResponse(session, params)
if err != nil {
return systemApi.NewArnListDefault(int(err.Code)).WithPayload(err)
}
@@ -51,21 +51,21 @@ func getArns(ctx context.Context, client MinioAdmin) (*models.ArnsResponse, erro
}
// getArnsResponse returns a list of active arns in the instance
func getArnsResponse(session *models.Principal) (*models.ArnsResponse, *models.Error) {
func getArnsResponse(session *models.Principal, params systemApi.ArnListParams) (*models.ArnsResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// serialize output
arnsList, err := getArns(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return arnsList, nil
}

View File

@@ -33,7 +33,7 @@ import (
func registerConfigHandlers(api *operations.ConsoleAPI) {
// List Configurations
api.ConfigurationListConfigHandler = cfgApi.ListConfigHandlerFunc(func(params cfgApi.ListConfigParams, session *models.Principal) middleware.Responder {
configListResp, err := getListConfigResponse(session)
configListResp, err := getListConfigResponse(session, params)
if err != nil {
return cfgApi.NewListConfigDefault(int(err.Code)).WithPayload(err)
}
@@ -49,7 +49,7 @@ func registerConfigHandlers(api *operations.ConsoleAPI) {
})
// Set Configuration
api.ConfigurationSetConfigHandler = cfgApi.SetConfigHandlerFunc(func(params cfgApi.SetConfigParams, session *models.Principal) middleware.Responder {
resp, err := setConfigResponse(session, params.Name, params.Body)
resp, err := setConfigResponse(session, params)
if err != nil {
return cfgApi.NewSetConfigDefault(int(err.Code)).WithPayload(err)
}
@@ -57,7 +57,7 @@ func registerConfigHandlers(api *operations.ConsoleAPI) {
})
// Reset Configuration
api.ConfigurationResetConfigHandler = cfgApi.ResetConfigHandlerFunc(func(params cfgApi.ResetConfigParams, session *models.Principal) middleware.Responder {
resp, err := resetConfigResponse(session, params.Name)
resp, err := resetConfigResponse(session, params)
if err != nil {
return cfgApi.NewResetConfigDefault(int(err.Code)).WithPayload(err)
}
@@ -86,10 +86,12 @@ func listConfig(client MinioAdmin) ([]*models.ConfigDescription, error) {
}
// getListConfigResponse performs listConfig() and serializes it to the handler's output
func getListConfigResponse(session *models.Principal) (*models.ListConfigResponse, *models.Error) {
func getListConfigResponse(session *models.Principal, params cfgApi.ListConfigParams) (*models.ListConfigResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -97,7 +99,7 @@ func getListConfigResponse(session *models.Principal) (*models.ListConfigRespons
configDescs, err := listConfig(adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
listGroupsResponse := &models.ListConfigResponse{
Configurations: configDescs,
@@ -134,11 +136,11 @@ func getConfig(ctx context.Context, client MinioAdmin, name string) ([]*models.C
// getConfigResponse performs getConfig() and serializes it to the handler's output
func getConfigResponse(session *models.Principal, params cfgApi.ConfigInfoParams) (*models.Configuration, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -146,7 +148,7 @@ func getConfigResponse(session *models.Principal, params cfgApi.ConfigInfoParams
configkv, err := getConfig(ctx, adminClient, params.Name)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
configurationObj := &models.Configuration{
Name: params.Name,
@@ -191,22 +193,22 @@ func buildConfig(configName *string, kvs []*models.ConfigurationKV) *string {
}
// setConfigResponse implements setConfig() to be used by handler
func setConfigResponse(session *models.Principal, name string, configRequest *models.SetConfigRequest) (*models.SetConfigResponse, *models.Error) {
func setConfigResponse(session *models.Principal, params cfgApi.SetConfigParams) (*models.SetConfigResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
configName := name
configName := params.Name
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
needsRestart, err := setConfigWithARNAccountID(ctx, adminClient, &configName, configRequest.KeyValues, configRequest.ArnResourceID)
needsRestart, err := setConfigWithARNAccountID(ctx, adminClient, &configName, params.Body.KeyValues, params.Body.ArnResourceID)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return &models.SetConfigResponse{Restart: needsRestart}, nil
}
@@ -217,22 +219,22 @@ func resetConfig(ctx context.Context, client MinioAdmin, configName *string) (er
}
// resetConfigResponse implements resetConfig() to be used by handler
func resetConfigResponse(session *models.Principal, configName string) (*models.SetConfigResponse, *models.Error) {
func resetConfigResponse(session *models.Principal, params cfgApi.ResetConfigParams) (*models.SetConfigResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
err = resetConfig(ctx, adminClient, &configName)
err = resetConfig(ctx, adminClient, &params.Name)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return &models.SetConfigResponse{Restart: true}, nil

View File

@@ -32,7 +32,7 @@ import (
func registerGroupsHandlers(api *operations.ConsoleAPI) {
// List Groups
api.GroupListGroupsHandler = groupApi.ListGroupsHandlerFunc(func(params groupApi.ListGroupsParams, session *models.Principal) middleware.Responder {
listGroupsResponse, err := getListGroupsResponse(session)
listGroupsResponse, err := getListGroupsResponse(session, params)
if err != nil {
return groupApi.NewListGroupsDefault(int(err.Code)).WithPayload(err)
}
@@ -48,7 +48,7 @@ func registerGroupsHandlers(api *operations.ConsoleAPI) {
})
// Add Group
api.GroupAddGroupHandler = groupApi.AddGroupHandlerFunc(func(params groupApi.AddGroupParams, session *models.Principal) middleware.Responder {
if err := getAddGroupResponse(session, params.Body); err != nil {
if err := getAddGroupResponse(session, params); err != nil {
return groupApi.NewAddGroupDefault(int(err.Code)).WithPayload(err)
}
return groupApi.NewAddGroupCreated()
@@ -71,12 +71,12 @@ func registerGroupsHandlers(api *operations.ConsoleAPI) {
}
// getListGroupsResponse performs listGroups() and serializes it to the handler's output
func getListGroupsResponse(session *models.Principal) (*models.ListGroupsResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListGroupsResponse(session *models.Principal, params groupApi.ListGroupsParams) (*models.ListGroupsResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -84,7 +84,7 @@ func getListGroupsResponse(session *models.Principal) (*models.ListGroupsRespons
groups, err := adminClient.listGroups(ctx)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// serialize output
@@ -107,11 +107,11 @@ func groupInfo(ctx context.Context, client MinioAdmin, group string) (*madmin.Gr
// getGroupInfoResponse performs groupInfo() and serializes it to the handler's output
func getGroupInfoResponse(session *models.Principal, params groupApi.GroupInfoParams) (*models.Group, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -119,7 +119,7 @@ func getGroupInfoResponse(session *models.Principal, params groupApi.GroupInfoPa
groupDesc, err := groupInfo(ctx, adminClient, params.Name)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
groupResponse := &models.Group{
@@ -146,16 +146,17 @@ func addGroup(ctx context.Context, client MinioAdmin, group string, members []st
}
// getAddGroupResponse performs addGroup() and serializes it to the handler's output
func getAddGroupResponse(session *models.Principal, params *models.AddGroupRequest) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getAddGroupResponse(session *models.Principal, params groupApi.AddGroupParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// AddGroup request needed to proceed
if params == nil {
return prepareError(errGroupBodyNotInRequest)
if params.Body == nil {
return ErrorWithContext(ctx, ErrGroupBodyNotInRequest)
}
groupRequest := params.Body
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -164,13 +165,13 @@ func getAddGroupResponse(session *models.Principal, params *models.AddGroupReque
groupList, _ := adminClient.listGroups(ctx)
for _, b := range groupList {
if b == *params.Group {
return prepareError(errGroupAlreadyExists)
if b == *groupRequest.Group {
return ErrorWithContext(ctx, ErrGroupAlreadyExists)
}
}
if err := addGroup(ctx, adminClient, *params.Group, params.Members); err != nil {
return prepareError(err)
if err := addGroup(ctx, adminClient, *groupRequest.Group, groupRequest.Members); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -191,21 +192,21 @@ func removeGroup(ctx context.Context, client MinioAdmin, group string) error {
// getRemoveGroupResponse performs removeGroup() and serializes it to the handler's output
func getRemoveGroupResponse(session *models.Principal, params groupApi.RemoveGroupParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Name == "" {
return prepareError(errGroupNameNotInRequest)
return ErrorWithContext(ctx, ErrGroupNameNotInRequest)
}
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// createad a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := removeGroup(ctx, adminClient, params.Name); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -265,13 +266,13 @@ func setGroupStatus(ctx context.Context, client MinioAdmin, group, status string
// also sets the group's status if status in the request is different than the current one.
// Then serializes the output to be used by the handler.
func getUpdateGroupResponse(session *models.Principal, params groupApi.UpdateGroupParams) (*models.Group, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Name == "" {
return nil, prepareError(errGroupNameNotInRequest)
return nil, ErrorWithContext(ctx, ErrGroupNameNotInRequest)
}
if params.Body == nil {
return nil, prepareError(errGroupBodyNotInRequest)
return nil, ErrorWithContext(ctx, ErrGroupBodyNotInRequest)
}
expectedGroupUpdate := params.Body
@@ -279,7 +280,7 @@ func getUpdateGroupResponse(session *models.Principal, params groupApi.UpdateGro
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -287,7 +288,7 @@ func getUpdateGroupResponse(session *models.Principal, params groupApi.UpdateGro
groupUpdated, err := groupUpdate(ctx, adminClient, groupName, expectedGroupUpdate)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
groupResponse := &models.Group{
Name: groupUpdated.Name,

View File

@@ -56,7 +56,7 @@ var (
type healItemStatus struct {
Status string `json:"status"`
Error string `json:"error,omitempty"`
Error string `json:"errors,omitempty"`
Type string `json:"type"`
Name string `json:"name"`
Before struct {
@@ -143,7 +143,7 @@ func startHeal(ctx context.Context, conn WSConn, client MinioAdmin, hOpts *healO
}
if res.Summary == "stopped" {
return fmt.Errorf("heal had an error - %s", res.FailureDetail)
return fmt.Errorf("heal had an errors - %s", res.FailureDetail)
}
time.Sleep(time.Second)

View File

@@ -826,6 +826,8 @@ type LabelResults struct {
// getAdminInfoResponse returns the response containing total buckets, objects and usage.
func getAdminInfoResponse(session *models.Principal, params systemApi.AdminInfoParams) (*models.AdminInfoResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
prometheusURL := ""
if !*params.DefaultOnly {
@@ -834,35 +836,30 @@ func getAdminInfoResponse(session *models.Principal, params systemApi.AdminInfoP
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
sessionResp, err2 := getUsageWidgetsForDeployment(prometheusURL, mAdmin)
sessionResp, err2 := getUsageWidgetsForDeployment(ctx, prometheusURL, mAdmin)
if err2 != nil {
return nil, err2
return nil, ErrorWithContext(ctx, err2)
}
return sessionResp, nil
}
func getUsageWidgetsForDeployment(prometheusURL string, mAdmin *madmin.AdminClient) (*models.AdminInfoResponse, *models.Error) {
func getUsageWidgetsForDeployment(ctx context.Context, prometheusURL string, mAdmin *madmin.AdminClient) (*models.AdminInfoResponse, error) {
prometheusNotReady := false
if prometheusURL != "" && !testPrometheusURL(prometheusURL) {
if prometheusURL != "" && !testPrometheusURL(ctx, prometheusURL) {
prometheusNotReady = true
}
if prometheusURL == "" || prometheusNotReady {
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
defer cancel()
// serialize output
usage, err := GetAdminInfo(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, err
}
sessionResp := &models.AdminInfoResponse{
Buckets: usage.Buckets,
@@ -901,80 +898,69 @@ func getUsageWidgetsForDeployment(prometheusURL string, mAdmin *madmin.AdminClie
return sessionResp, nil
}
func unmarshalPrometheus(endpoint string, data interface{}) bool {
func unmarshalPrometheus(ctx context.Context, endpoint string, data interface{}) bool {
httpClnt := GetConsoleHTTPClient()
resp, err := httpClnt.Get(endpoint)
if err != nil {
LogError("Unable to fetch labels from prometheus (%s)", resp.Status)
ErrorWithContext(ctx, fmt.Errorf("Unable to fetch labels from prometheus (%s)", resp.Status))
return true
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
LogError("Unexpected error from prometheus (%s)", resp.Status)
ErrorWithContext(ctx, fmt.Errorf("Unexpected errors from prometheus (%s)", resp.Status))
return true
}
if err = json.NewDecoder(resp.Body).Decode(data); err != nil {
LogError("Unexpected error reading response from prometheus, %v", err)
ErrorWithContext(ctx, fmt.Errorf("Unexpected errors from prometheus (%s)", resp.Status))
return true
}
return false
}
func testPrometheusURL(url string) bool {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
func testPrometheusURL(ctx context.Context, url string) bool {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url+"/-/healthy", nil)
if err != nil {
LogError("Error Building Request: (%v)", err)
ErrorWithContext(ctx, fmt.Errorf("error Building Request: (%v)", err))
return false
}
response, err := GetConsoleHTTPClient().Do(req)
if err != nil {
LogError("Default Prometheus URL not reachable, trying root testing: (%v)", err)
ErrorWithContext(ctx, fmt.Errorf("default Prometheus URL not reachable, trying root testing: (%v)", err))
newTestURL := req.URL.Scheme + "://" + req.URL.Host + "/-/healthy"
req2, err := http.NewRequestWithContext(ctx, http.MethodGet, newTestURL, nil)
if err != nil {
LogError("Error Building Root Request: (%v)", err)
ErrorWithContext(ctx, fmt.Errorf("error Building Root Request: (%v)", err))
return false
}
rootResponse, err := GetConsoleHTTPClient().Do(req2)
if err != nil {
// URL & Root tests didn't work. Prometheus not reachable
LogError("Root Prometheus URL not reachable: (%v)", err)
ErrorWithContext(ctx, fmt.Errorf("root Prometheus URL not reachable: (%v)", err))
return false
}
return rootResponse.StatusCode == http.StatusOK
}
return response.StatusCode == http.StatusOK
}
func getAdminInfoWidgetResponse(params systemApi.DashboardWidgetDetailsParams) (*models.WidgetDetails, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
prometheusURL := getPrometheusURL()
prometheusJobID := getPrometheusJobID()
// We test if prometheus URL is reachable. this is meant to avoid unuseful calls and application hang.
if !testPrometheusURL(prometheusURL) {
error := errors.New("Prometheus URL is unreachable")
return nil, prepareError(error)
if !testPrometheusURL(ctx, prometheusURL) {
return nil, ErrorWithContext(ctx, errors.New("Prometheus URL is unreachable"))
}
return getWidgetDetails(prometheusURL, prometheusJobID, params.WidgetID, params.Step, params.Start, params.End)
return getWidgetDetails(ctx, prometheusURL, prometheusJobID, params.WidgetID, params.Step, params.Start, params.End)
}
func getWidgetDetails(prometheusURL string, prometheusJobID string, widgetID int32, step *int32, start *int64, end *int64) (*models.WidgetDetails, *models.Error) {
func getWidgetDetails(ctx context.Context, prometheusURL string, prometheusJobID string, widgetID int32, step *int32, start *int64, end *int64) (*models.WidgetDetails, *models.Error) {
labelResultsCh := make(chan LabelResults)
for _, lbl := range labels {
@@ -982,7 +968,7 @@ func getWidgetDetails(prometheusURL string, prometheusJobID string, widgetID int
endpoint := fmt.Sprintf("%s/api/v1/label/%s/values", prometheusURL, lbl.Name)
var response LabelResponse
if unmarshalPrometheus(endpoint, &response) {
if unmarshalPrometheus(ctx, endpoint, &response) {
return
}
@@ -1054,7 +1040,7 @@ LabelsWaitLoop:
endpoint := fmt.Sprintf("%s/api/v1/%s?query=%s%s", prometheusURL, apiType, url.QueryEscape(queryExpr), extraParamters)
var response PromResp
if unmarshalPrometheus(endpoint, &response) {
if unmarshalPrometheus(ctx, endpoint, &response) {
return
}

View File

@@ -49,11 +49,11 @@ func registerInspectHandler(api *operations.ConsoleAPI) {
}
func getInspectResult(session *models.Principal, params *inspectApi.InspectParams) (*[32]byte, io.ReadCloser, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, nil, prepareError(err)
return nil, nil, ErrorWithContext(ctx, err)
}
var cfg madmin.InspectOptions
@@ -67,7 +67,7 @@ func getInspectResult(session *models.Principal, params *inspectApi.InspectParam
k, r, err := adminClient.inspect(ctx, cfg)
if err != nil {
return nil, nil, prepareError(err)
return nil, nil, ErrorWithContext(ctx, err)
}
return &k, r, nil
}

View File

@@ -28,7 +28,7 @@ import (
func registerNodesHandler(api *operations.ConsoleAPI) {
api.SystemListNodesHandler = systemApi.ListNodesHandlerFunc(func(params systemApi.ListNodesParams, session *models.Principal) middleware.Responder {
listNodesResponse, err := getListNodesResponse(session)
listNodesResponse, err := getListNodesResponse(session, params)
if err != nil {
return systemApi.NewListNodesDefault(int(err.Code)).WithPayload(err)
}
@@ -37,12 +37,12 @@ func registerNodesHandler(api *operations.ConsoleAPI) {
}
// getListNodesResponse returns a list of available node endpoints .
func getListNodesResponse(session *models.Principal) ([]string, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListNodesResponse(session *models.Principal, params systemApi.ListNodesParams) ([]string, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
var nodeList []string

View File

@@ -29,7 +29,7 @@ import (
func registerAdminNotificationEndpointsHandlers(api *operations.ConsoleAPI) {
// return a list of notification endpoints
api.ConfigurationNotificationEndpointListHandler = configurationApi.NotificationEndpointListHandlerFunc(func(params configurationApi.NotificationEndpointListParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getNotificationEndpointsResponse(session)
notifEndpoints, err := getNotificationEndpointsResponse(session, params)
if err != nil {
return configurationApi.NewNotificationEndpointListDefault(int(err.Code)).WithPayload(err)
}
@@ -37,7 +37,7 @@ func registerAdminNotificationEndpointsHandlers(api *operations.ConsoleAPI) {
})
// add a new notification endpoints
api.ConfigurationAddNotificationEndpointHandler = configurationApi.AddNotificationEndpointHandlerFunc(func(params configurationApi.AddNotificationEndpointParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getAddNotificationEndpointResponse(session, &params)
notifEndpoints, err := getAddNotificationEndpointResponse(session, params)
if err != nil {
return configurationApi.NewAddNotificationEndpointDefault(int(err.Code)).WithPayload(err)
}
@@ -75,20 +75,20 @@ func getNotificationEndpoints(ctx context.Context, client MinioAdmin) (*models.N
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getNotificationEndpointsResponse(session *models.Principal) (*models.NotifEndpointResponse, *models.Error) {
func getNotificationEndpointsResponse(session *models.Principal, params configurationApi.NotificationEndpointListParams) (*models.NotifEndpointResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// serialize output
notfEndpointResp, err := getNotificationEndpoints(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return notfEndpointResp, nil
}
@@ -145,20 +145,20 @@ func addNotificationEndpoint(ctx context.Context, client MinioAdmin, params *con
}
// getNotificationEndpointsResponse returns a list of notification endpoints in the instance
func getAddNotificationEndpointResponse(session *models.Principal, params *configurationApi.AddNotificationEndpointParams) (*models.SetNotificationEndpointResponse, *models.Error) {
func getAddNotificationEndpointResponse(session *models.Principal, params configurationApi.AddNotificationEndpointParams) (*models.SetNotificationEndpointResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// serialize output
notfEndpointResp, err := addNotificationEndpoint(ctx, adminClient, params)
notfEndpointResp, err := addNotificationEndpoint(ctx, adminClient, &params)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return notfEndpointResp, nil
}

View File

@@ -28,7 +28,6 @@ import (
policyApi "github.com/minio/console/restapi/operations/policy"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/console/restapi/operations"
iampolicy "github.com/minio/pkg/iam/policy"
@@ -37,7 +36,7 @@ import (
func registersPoliciesHandler(api *operations.ConsoleAPI) {
// List Policies
api.PolicyListPoliciesHandler = policyApi.ListPoliciesHandlerFunc(func(params policyApi.ListPoliciesParams, session *models.Principal) middleware.Responder {
listPoliciesResponse, err := getListPoliciesResponse(session)
listPoliciesResponse, err := getListPoliciesResponse(session, params)
if err != nil {
return policyApi.NewListPoliciesDefault(int(err.Code)).WithPayload(err)
}
@@ -53,7 +52,7 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
})
// Add Policy
api.PolicyAddPolicyHandler = policyApi.AddPolicyHandlerFunc(func(params policyApi.AddPolicyParams, session *models.Principal) middleware.Responder {
policyResponse, err := getAddPolicyResponse(session, params.Body)
policyResponse, err := getAddPolicyResponse(session, params)
if err != nil {
return policyApi.NewAddPolicyDefault(int(err.Code)).WithPayload(err)
}
@@ -68,55 +67,55 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
})
// Set Policy
api.PolicySetPolicyHandler = policyApi.SetPolicyHandlerFunc(func(params policyApi.SetPolicyParams, session *models.Principal) middleware.Responder {
if err := getSetPolicyResponse(session, params.Body); err != nil {
if err := getSetPolicyResponse(session, params); err != nil {
return policyApi.NewSetPolicyDefault(int(err.Code)).WithPayload(err)
}
return policyApi.NewSetPolicyNoContent()
})
// Set Policy Multiple User/Groups
api.PolicySetPolicyMultipleHandler = policyApi.SetPolicyMultipleHandlerFunc(func(params policyApi.SetPolicyMultipleParams, session *models.Principal) middleware.Responder {
if err := getSetPolicyMultipleResponse(session, params.Body); err != nil {
if err := getSetPolicyMultipleResponse(session, params); err != nil {
return policyApi.NewSetPolicyMultipleDefault(int(err.Code)).WithPayload(err)
}
return policyApi.NewSetPolicyMultipleNoContent()
})
api.BucketListPoliciesWithBucketHandler = bucketApi.ListPoliciesWithBucketHandlerFunc(func(params bucketApi.ListPoliciesWithBucketParams, session *models.Principal) middleware.Responder {
policyResponse, err := getListPoliciesWithBucketResponse(session, params.Bucket)
policyResponse, err := getListPoliciesWithBucketResponse(session, params)
if err != nil {
return bucketApi.NewListPoliciesWithBucketDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewListPoliciesWithBucketOK().WithPayload(policyResponse)
})
api.BucketListAccessRulesWithBucketHandler = bucketApi.ListAccessRulesWithBucketHandlerFunc(func(params bucketApi.ListAccessRulesWithBucketParams, session *models.Principal) middleware.Responder {
policyResponse, err := getListAccessRulesWithBucketResponse(session, params.Bucket)
policyResponse, err := getListAccessRulesWithBucketResponse(session, params)
if err != nil {
return bucketApi.NewListAccessRulesWithBucketDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewListAccessRulesWithBucketOK().WithPayload(policyResponse)
})
api.BucketSetAccessRuleWithBucketHandler = bucketApi.SetAccessRuleWithBucketHandlerFunc(func(params bucketApi.SetAccessRuleWithBucketParams, session *models.Principal) middleware.Responder {
policyResponse, err := getSetAccessRuleWithBucketResponse(session, params.Bucket, params.Prefixaccess)
policyResponse, err := getSetAccessRuleWithBucketResponse(session, params)
if err != nil {
return bucketApi.NewSetAccessRuleWithBucketDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewSetAccessRuleWithBucketOK().WithPayload(policyResponse)
})
api.BucketDeleteAccessRuleWithBucketHandler = bucketApi.DeleteAccessRuleWithBucketHandlerFunc(func(params bucketApi.DeleteAccessRuleWithBucketParams, session *models.Principal) middleware.Responder {
policyResponse, err := getDeleteAccessRuleWithBucketResponse(session, params.Bucket, params.Prefix)
policyResponse, err := getDeleteAccessRuleWithBucketResponse(session, params)
if err != nil {
return bucketApi.NewDeleteAccessRuleWithBucketDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewDeleteAccessRuleWithBucketOK().WithPayload(policyResponse)
})
api.PolicyListUsersForPolicyHandler = policyApi.ListUsersForPolicyHandlerFunc(func(params policyApi.ListUsersForPolicyParams, session *models.Principal) middleware.Responder {
policyUsersResponse, err := getListUsersForPolicyResponse(session, params.Policy)
policyUsersResponse, err := getListUsersForPolicyResponse(session, params)
if err != nil {
return policyApi.NewListUsersForPolicyDefault(int(err.Code)).WithPayload(err)
}
return policyApi.NewListUsersForPolicyOK().WithPayload(policyUsersResponse)
})
api.PolicyListGroupsForPolicyHandler = policyApi.ListGroupsForPolicyHandlerFunc(func(params policyApi.ListGroupsForPolicyParams, session *models.Principal) middleware.Responder {
policyGroupsResponse, err := getListGroupsForPolicyResponse(session, params.Policy)
policyGroupsResponse, err := getListGroupsForPolicyResponse(session, params)
if err != nil {
return policyApi.NewListGroupsForPolicyDefault(int(err.Code)).WithPayload(err)
}
@@ -124,12 +123,13 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
})
}
func getListAccessRulesWithBucketResponse(session *models.Principal, bucket string) (*models.ListAccessRulesResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListAccessRulesWithBucketResponse(session *models.Principal, params bucketApi.ListAccessRulesWithBucketParams) (*models.ListAccessRulesResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucket := params.Bucket
client, err := newS3BucketClient(session, bucket, "")
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
accessRules, _ := client.GetAccessRules(ctx)
var accessRuleList []*models.AccessRule
@@ -139,48 +139,51 @@ func getListAccessRulesWithBucketResponse(session *models.Principal, bucket stri
return &models.ListAccessRulesResponse{AccessRules: accessRuleList}, nil
}
func getSetAccessRuleWithBucketResponse(session *models.Principal, bucket string, prefixAccess *models.PrefixAccessPair) (bool, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getSetAccessRuleWithBucketResponse(session *models.Principal, params bucketApi.SetAccessRuleWithBucketParams) (bool, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
client, err := newS3BucketClient(session, bucket, prefixAccess.Prefix)
prefixAccess := params.Prefixaccess
client, err := newS3BucketClient(session, params.Bucket, prefixAccess.Prefix)
if err != nil {
return false, prepareError(err)
return false, ErrorWithContext(ctx, err)
}
errorVal := client.SetAccess(ctx, prefixAccess.Access, false)
if errorVal != nil {
return false, prepareError(errorVal.Cause)
return false, ErrorWithContext(ctx, errorVal.Cause)
}
return true, nil
}
func getDeleteAccessRuleWithBucketResponse(session *models.Principal, bucket string, prefix *models.PrefixWrapper) (bool, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getDeleteAccessRuleWithBucketResponse(session *models.Principal, params bucketApi.DeleteAccessRuleWithBucketParams) (bool, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucket := params.Bucket
prefix := params.Prefix
client, err := newS3BucketClient(session, bucket, prefix.Prefix)
if err != nil {
return false, prepareError(err)
return false, ErrorWithContext(ctx, err)
}
errorVal := client.SetAccess(ctx, "none", false)
if errorVal != nil {
return false, prepareError(errorVal.Cause)
return false, ErrorWithContext(ctx, errorVal.Cause)
}
return true, nil
}
func getListPoliciesWithBucketResponse(session *models.Principal, bucket string) (*models.ListPoliciesResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListPoliciesWithBucketResponse(session *models.Principal, params bucketApi.ListPoliciesWithBucketParams) (*models.ListPoliciesResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policies, err := listPoliciesWithBucket(ctx, bucket, adminClient)
policies, err := listPoliciesWithBucket(ctx, params.Bucket, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// serialize output
listPoliciesResponse := &models.ListPoliciesResponse{
@@ -205,18 +208,18 @@ func listPoliciesWithBucket(ctx context.Context, bucket string, client MinioAdmi
if err != nil {
return nil, err
}
if policyMatchesBucket(policy, bucket) {
if policyMatchesBucket(ctx, policy, bucket) {
policies = append(policies, policy)
}
}
return policies, nil
}
func policyMatchesBucket(policy *models.Policy, bucket string) bool {
func policyMatchesBucket(ctx context.Context, policy *models.Policy, bucket string) bool {
policyData := &iampolicy.Policy{}
err := json.Unmarshal([]byte(policy.Policy), policyData)
if err != nil {
LogError("error parsing policy: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error parsing policy: %v", err))
return false
}
policyStatements := policyData.Statements
@@ -253,12 +256,12 @@ func listPolicies(ctx context.Context, client MinioAdmin) ([]*models.Policy, err
}
// getListPoliciesResponse performs listPolicies() and serializes it to the handler's output
func getListPoliciesResponse(session *models.Principal) (*models.ListPoliciesResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListPoliciesResponse(session *models.Principal, params policyApi.ListPoliciesParams) (*models.ListPoliciesResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
@@ -266,7 +269,7 @@ func getListPoliciesResponse(session *models.Principal) (*models.ListPoliciesRes
policies, err := listPolicies(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// serialize output
listPoliciesResponse := &models.ListPoliciesResponse{
@@ -277,19 +280,20 @@ func getListPoliciesResponse(session *models.Principal) (*models.ListPoliciesRes
}
// getListUsersForPoliciesResponse performs lists users affected by a given policy.
func getListUsersForPolicyResponse(session *models.Principal, policy string) ([]string, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListUsersForPolicyResponse(session *models.Principal, params policyApi.ListUsersForPolicyParams) ([]string, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
policy := params.Policy
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policies, err := listPolicies(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
found := false
for i := range policies {
@@ -298,15 +302,11 @@ func getListUsersForPolicyResponse(session *models.Principal, policy string) ([]
}
}
if !found {
return nil, &models.Error{
Code: int32(404),
Message: swag.String("Policy does not exist"),
DetailedMessage: swag.String(fmt.Sprintf("The policy %s does not extist", policy)),
}
return nil, ErrorWithContext(ctx, ErrPolicyNotFound, fmt.Errorf("the policy %s does not exist", policy))
}
users, err := listUsers(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
var filteredUsers []string
@@ -322,19 +322,20 @@ func getListUsersForPolicyResponse(session *models.Principal, policy string) ([]
return filteredUsers, nil
}
func getListGroupsForPolicyResponse(session *models.Principal, policy string) ([]string, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListGroupsForPolicyResponse(session *models.Principal, params policyApi.ListGroupsForPolicyParams) ([]string, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
policy := params.Policy
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policies, err := listPolicies(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
found := false
for i := range policies {
@@ -343,23 +344,19 @@ func getListGroupsForPolicyResponse(session *models.Principal, policy string) ([
}
}
if !found {
return nil, &models.Error{
Code: int32(404),
Message: swag.String("Policy does not exist"),
DetailedMessage: swag.String(fmt.Sprintf("The policy %s does not extist", policy)),
}
return nil, ErrorWithContext(ctx, ErrPolicyNotFound, fmt.Errorf("the policy %s does not exist", policy))
}
groups, err := adminClient.listGroups(ctx)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
var filteredGroups []string
for _, group := range groups {
info, err := groupInfo(ctx, adminClient, group)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
if info.Policy == policy {
filteredGroups = append(filteredGroups, group)
@@ -380,21 +377,21 @@ func removePolicy(ctx context.Context, client MinioAdmin, name string) error {
// getRemovePolicyResponse() performs removePolicy() and serializes it to the handler's output
func getRemovePolicyResponse(session *models.Principal, params policyApi.RemovePolicyParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Name == "" {
return prepareError(errPolicyNameNotInRequest)
return ErrorWithContext(ctx, ErrPolicyNameNotInRequest)
}
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := removePolicy(ctx, adminClient, params.Name); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -419,23 +416,22 @@ func addPolicy(ctx context.Context, client MinioAdmin, name, policy string) (*mo
}
// getAddPolicyResponse performs addPolicy() and serializes it to the handler's output
func getAddPolicyResponse(session *models.Principal, params *models.AddPolicyRequest) (*models.Policy, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getAddPolicyResponse(session *models.Principal, params policyApi.AddPolicyParams) (*models.Policy, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params == nil {
return nil, prepareError(errPolicyBodyNotInRequest)
if params.Body == nil {
return nil, ErrorWithContext(ctx, ErrPolicyBodyNotInRequest)
}
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policy, err := addPolicy(ctx, adminClient, *params.Name, *params.Policy)
policy, err := addPolicy(ctx, adminClient, *params.Body.Name, *params.Body.Policy)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return policy, nil
}
@@ -458,18 +454,18 @@ func policyInfo(ctx context.Context, client MinioAdmin, name string) (*models.Po
// getPolicyInfoResponse performs policyInfo() and serializes it to the handler's output
func getPolicyInfoResponse(session *models.Principal, params policyApi.PolicyInfoParams) (*models.Policy, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
policy, err := policyInfo(ctx, adminClient, params.Name)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return policy, nil
}
@@ -484,40 +480,37 @@ func setPolicy(ctx context.Context, client MinioAdmin, name, entityName string,
}
// getSetPolicyResponse() performs setPolicy() and serializes it to the handler's output
func getSetPolicyResponse(session *models.Principal, params *models.SetPolicyNameRequest) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getSetPolicyResponse(session *models.Principal, params policyApi.SetPolicyParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// if len(params.Name) == 0 {
// return prepareError(errPolicyNameNotInRequest)
// }
// Removing this section
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := setPolicy(ctx, adminClient, strings.Join(params.Name, ","), *params.EntityName, *params.EntityType); err != nil {
return prepareError(err)
if err := setPolicy(ctx, adminClient, strings.Join(params.Body.Name, ","), *params.Body.EntityName, *params.Body.EntityType); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
func getSetPolicyMultipleResponse(session *models.Principal, params *models.SetPolicyMultipleNameRequest) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getSetPolicyMultipleResponse(session *models.Principal, params policyApi.SetPolicyMultipleParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := setPolicyMultipleEntities(ctx, adminClient, strings.Join(params.Name, ","), params.Users, params.Groups); err != nil {
return prepareError(err)
if err := setPolicyMultipleEntities(ctx, adminClient, strings.Join(params.Body.Name, ","), params.Body.Users, params.Body.Groups); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}

View File

@@ -300,6 +300,7 @@ func Test_SetPolicyMultiple(t *testing.T) {
func Test_policyMatchesBucket(t *testing.T) {
type args struct {
ctx context.Context
policy *models.Policy
bucket string
}
@@ -310,7 +311,7 @@ func Test_policyMatchesBucket(t *testing.T) {
}{
{
name: "Test1",
args: args{policy: &models.Policy{Name: "consoleAdmin", Policy: `{
args: args{ctx: context.Background(), policy: &models.Policy{Name: "consoleAdmin", Policy: `{
"Version": "2012-10-17",
"Statement": [
{
@@ -334,7 +335,7 @@ func Test_policyMatchesBucket(t *testing.T) {
},
{
name: "Test2",
args: args{policy: &models.Policy{Name: "consoleAdmin", Policy: `{
args: args{ctx: context.Background(), policy: &models.Policy{Name: "consoleAdmin", Policy: `{
"Version": "2012-10-17",
"Statement": [
{
@@ -352,7 +353,7 @@ func Test_policyMatchesBucket(t *testing.T) {
},
{
name: "Test3",
args: args{policy: &models.Policy{Name: "consoleAdmin", Policy: `{
args: args{ctx: context.Background(), policy: &models.Policy{Name: "consoleAdmin", Policy: `{
"Version": "2012-10-17",
"Statement": [
{
@@ -388,7 +389,7 @@ func Test_policyMatchesBucket(t *testing.T) {
},
{
name: "Test4",
args: args{policy: &models.Policy{Name: "consoleAdmin", Policy: `{
args: args{ctx: context.Background(), policy: &models.Policy{Name: "consoleAdmin", Policy: `{
"Version": "2012-10-17",
"Statement": [
{
@@ -407,7 +408,7 @@ func Test_policyMatchesBucket(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := policyMatchesBucket(tt.args.policy, tt.args.bucket); got != tt.want {
if got := policyMatchesBucket(tt.args.ctx, tt.args.policy, tt.args.bucket); got != tt.want {
t.Errorf("policyMatchesBucket() = %v, want %v", got, tt.want)
}
})

View File

@@ -32,7 +32,7 @@ import (
func registerProfilingHandler(api *operations.ConsoleAPI) {
// Start Profiling
api.ProfileProfilingStartHandler = profileApi.ProfilingStartHandlerFunc(func(params profileApi.ProfilingStartParams, session *models.Principal) middleware.Responder {
profilingStartResponse, err := getProfilingStartResponse(session, params.Body)
profilingStartResponse, err := getProfilingStartResponse(session, params)
if err != nil {
return profileApi.NewProfilingStartDefault(int(err.Code)).WithPayload(err)
}
@@ -40,7 +40,7 @@ func registerProfilingHandler(api *operations.ConsoleAPI) {
})
// Stop and download profiling data
api.ProfileProfilingStopHandler = profileApi.ProfilingStopHandlerFunc(func(params profileApi.ProfilingStopParams, session *models.Principal) middleware.Responder {
profilingStopResponse, err := getProfilingStopResponse(session)
profilingStopResponse, err := getProfilingStopResponse(session, params)
if err != nil {
return profileApi.NewProfilingStopDefault(int(err.Code)).WithPayload(err)
}
@@ -63,7 +63,7 @@ func registerProfilingHandler(api *operations.ConsoleAPI) {
// {
// "Success": true,
// "nodeName": "127.0.0.1:9000"
// "error": ""
// "errors": ""
// }
func startProfiling(ctx context.Context, client MinioAdmin, profilerType string) ([]*models.StartProfilingItem, error) {
profilingResults, err := client.startProfiling(ctx, madmin.ProfilerType(profilerType))
@@ -82,22 +82,22 @@ func startProfiling(ctx context.Context, client MinioAdmin, profilerType string)
}
// getProfilingStartResponse performs startProfiling() and serializes it to the handler's output
func getProfilingStartResponse(session *models.Principal, params *models.ProfilingStartRequest) (*models.StartProfilingList, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getProfilingStartResponse(session *models.Principal, params profileApi.ProfilingStartParams) (*models.StartProfilingList, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params == nil {
return nil, prepareError(errPolicyBodyNotInRequest)
if params.Body == nil {
return nil, ErrorWithContext(ctx, ErrPolicyBodyNotInRequest)
}
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
profilingItems, err := startProfiling(ctx, adminClient, *params.Type)
profilingItems, err := startProfiling(ctx, adminClient, *params.Body.Type)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
profilingList := &models.StartProfilingList{
StartResults: profilingItems,
@@ -117,18 +117,18 @@ func stopProfiling(ctx context.Context, client MinioAdmin) (io.ReadCloser, error
}
// getProfilingStopResponse() performs setPolicy() and serializes it to the handler's output
func getProfilingStopResponse(session *models.Principal) (io.ReadCloser, *models.Error) {
ctx := context.Background()
func getProfilingStopResponse(session *models.Principal, params profileApi.ProfilingStopParams) (io.ReadCloser, *models.Error) {
ctx := params.HTTPRequest.Context()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
profilingData, err := stopProfiling(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return profilingData, nil
}

View File

@@ -43,9 +43,9 @@ type RemoteBucketResult struct {
func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
// return list of remote buckets
api.BucketListRemoteBucketsHandler = bucketApi.ListRemoteBucketsHandlerFunc(func(params bucketApi.ListRemoteBucketsParams, session *models.Principal) middleware.Responder {
listResp, err := getListRemoteBucketsResponse(session)
listResp, err := getListRemoteBucketsResponse(session, params)
if err != nil {
return bucketApi.NewListRemoteBucketsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return bucketApi.NewListRemoteBucketsDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewListRemoteBucketsOK().WithPayload(listResp)
})
@@ -54,7 +54,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketRemoteBucketDetailsHandler = bucketApi.RemoteBucketDetailsHandlerFunc(func(params bucketApi.RemoteBucketDetailsParams, session *models.Principal) middleware.Responder {
response, err := getRemoteBucketDetailsResponse(session, params)
if err != nil {
return bucketApi.NewRemoteBucketDetailsDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return bucketApi.NewRemoteBucketDetailsDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewRemoteBucketDetailsOK().WithPayload(response)
})
@@ -63,7 +63,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketDeleteRemoteBucketHandler = bucketApi.DeleteRemoteBucketHandlerFunc(func(params bucketApi.DeleteRemoteBucketParams, session *models.Principal) middleware.Responder {
err := getDeleteRemoteBucketResponse(session, params)
if err != nil {
return bucketApi.NewDeleteRemoteBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return bucketApi.NewDeleteRemoteBucketDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewDeleteRemoteBucketNoContent()
})
@@ -72,7 +72,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketAddRemoteBucketHandler = bucketApi.AddRemoteBucketHandlerFunc(func(params bucketApi.AddRemoteBucketParams, session *models.Principal) middleware.Responder {
err := getAddRemoteBucketResponse(session, params)
if err != nil {
return bucketApi.NewAddRemoteBucketDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return bucketApi.NewAddRemoteBucketDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewAddRemoteBucketCreated()
})
@@ -82,7 +82,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
response, err := setMultiBucketReplicationResponse(session, params)
if err != nil {
return bucketApi.NewSetMultiBucketReplicationDefault(500).WithPayload(err)
return bucketApi.NewSetMultiBucketReplicationDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewSetMultiBucketReplicationOK().WithPayload(response)
@@ -93,7 +93,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
response, err := listExternalBucketsResponse(params)
if err != nil {
return bucketApi.NewListExternalBucketsDefault(500).WithPayload(err)
return bucketApi.NewListExternalBucketsDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewListExternalBucketsOK().WithPayload(response)
@@ -104,7 +104,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
err := deleteReplicationRuleResponse(session, params)
if err != nil {
return bucketApi.NewDeleteBucketReplicationRuleDefault(500).WithPayload(err)
return bucketApi.NewDeleteBucketReplicationRuleDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewDeleteBucketReplicationRuleNoContent()
@@ -115,7 +115,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
err := deleteBucketReplicationRulesResponse(session, params)
if err != nil {
return bucketApi.NewDeleteAllReplicationRulesDefault(500).WithPayload(err)
return bucketApi.NewDeleteAllReplicationRulesDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewDeleteAllReplicationRulesNoContent()
@@ -126,7 +126,7 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
err := deleteSelectedReplicationRulesResponse(session, params)
if err != nil {
return bucketApi.NewDeleteSelectedReplicationRulesDefault(500).WithPayload(err)
return bucketApi.NewDeleteSelectedReplicationRulesDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewDeleteSelectedReplicationRulesNoContent()
@@ -136,25 +136,23 @@ func registerAdminBucketRemoteHandlers(api *operations.ConsoleAPI) {
api.BucketUpdateMultiBucketReplicationHandler = bucketApi.UpdateMultiBucketReplicationHandlerFunc(func(params bucketApi.UpdateMultiBucketReplicationParams, session *models.Principal) middleware.Responder {
err := updateBucketReplicationResponse(session, params)
if err != nil {
return bucketApi.NewUpdateMultiBucketReplicationDefault(500).WithPayload(err)
return bucketApi.NewUpdateMultiBucketReplicationDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewUpdateMultiBucketReplicationCreated()
})
}
func getListRemoteBucketsResponse(session *models.Principal) (*models.ListRemoteBucketsResponse, error) {
ctx, cancel := context.WithCancel(context.Background())
func getListRemoteBucketsResponse(session *models.Principal, params bucketApi.ListRemoteBucketsParams) (*models.ListRemoteBucketsResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
LogError("error creating Madmin Client: %v", err)
return nil, err
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
buckets, err := listRemoteBuckets(ctx, adminClient)
if err != nil {
LogError("error listing remote buckets: %v", err)
return nil, err
return nil, ErrorWithContext(ctx, fmt.Errorf("error listing remote buckets: %v", err))
}
return &models.ListRemoteBucketsResponse{
Buckets: buckets,
@@ -162,55 +160,49 @@ func getListRemoteBucketsResponse(session *models.Principal) (*models.ListRemote
}, nil
}
func getRemoteBucketDetailsResponse(session *models.Principal, params bucketApi.RemoteBucketDetailsParams) (*models.RemoteBucket, error) {
ctx, cancel := context.WithCancel(context.Background())
func getRemoteBucketDetailsResponse(session *models.Principal, params bucketApi.RemoteBucketDetailsParams) (*models.RemoteBucket, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
LogError("error creating Madmin Client: %v", err)
return nil, err
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
bucket, err := getRemoteBucket(ctx, adminClient, params.Name)
if err != nil {
LogError("error getting remote bucket details: %v", err)
return nil, err
return nil, ErrorWithContext(ctx, fmt.Errorf("error getting remote bucket details: %v", err))
}
return bucket, nil
}
func getDeleteRemoteBucketResponse(session *models.Principal, params bucketApi.DeleteRemoteBucketParams) error {
ctx, cancel := context.WithCancel(context.Background())
func getDeleteRemoteBucketResponse(session *models.Principal, params bucketApi.DeleteRemoteBucketParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
LogError("error creating Madmin Client: %v", err)
return err
return ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
err = deleteRemoteBucket(ctx, adminClient, params.SourceBucketName, params.Arn)
if err != nil {
LogError("error deleting remote bucket: %v", err)
return err
return ErrorWithContext(ctx, fmt.Errorf("error deleting remote bucket: %v", err))
}
return err
return nil
}
func getAddRemoteBucketResponse(session *models.Principal, params bucketApi.AddRemoteBucketParams) error {
ctx, cancel := context.WithCancel(context.Background())
func getAddRemoteBucketResponse(session *models.Principal, params bucketApi.AddRemoteBucketParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
LogError("error creating Madmin Client: %v", err)
return err
return ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
_, err = addRemoteBucket(ctx, adminClient, *params.Body)
if err != nil {
LogError("error adding remote bucket: %v", err)
return err
return ErrorWithContext(ctx, fmt.Errorf("error adding remote bucket: %v", err))
}
return err
return nil
}
func listRemoteBuckets(ctx context.Context, client MinioAdmin) ([]*models.RemoteBucket, error) {
@@ -309,7 +301,7 @@ func addBucketReplicationItem(ctx context.Context, session *models.Principal, mi
// we will tolerate this call failing
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
LogError("error fetching replication configuration for bucket %s: %v", bucketName, err)
ErrorWithContext(ctx, fmt.Errorf("error fetching replication configuration for bucket %s: %v", bucketName, err))
}
// add rule
@@ -328,7 +320,7 @@ func addBucketReplicationItem(ctx context.Context, session *models.Principal, mi
s3Client, err := newS3BucketClient(session, bucketName, prefix)
if err != nil {
LogError("error creating S3Client: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error creating S3Client: %v", err))
return err
}
// create a mc S3Client interface implementation
@@ -365,7 +357,7 @@ func addBucketReplicationItem(ctx context.Context, session *models.Principal, mi
err2 := mcClient.setReplication(ctx, &cfg, opts)
if err2 != nil {
LogError("error creating replication for bucket:", err2.Cause)
ErrorWithContext(ctx, fmt.Errorf("error creating replication for bucket: %v", err2.Cause))
return err2.Cause
}
return nil
@@ -375,15 +367,14 @@ func editBucketReplicationItem(ctx context.Context, session *models.Principal, m
// we will tolerate this call failing
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
LogError("error fetching replication configuration for bucket %s: %v", bucketName, err)
ErrorWithContext(ctx, fmt.Errorf("error fetching replication configuration for bucket %s: %v", bucketName, err))
}
maxPrio := int(priority)
s3Client, err := newS3BucketClient(session, bucketName, prefix)
if err != nil {
LogError("error creating S3Client: %v", err)
return err
return fmt.Errorf("error creating S3Client: %v", err)
}
// create a mc S3Client interface implementation
// defining the client to be used
@@ -432,8 +423,7 @@ func editBucketReplicationItem(ctx context.Context, session *models.Principal, m
err2 := mcClient.setReplication(ctx, &cfg, opts)
if err2 != nil {
LogError("error modifying replication for bucket:", err2.Cause)
return err2.Cause
return fmt.Errorf("error modifying replication for bucket: %v", err2.Cause)
}
return nil
}
@@ -516,20 +506,18 @@ func setMultiBucketReplication(ctx context.Context, session *models.Principal, c
}
func setMultiBucketReplicationResponse(session *models.Principal, params bucketApi.SetMultiBucketReplicationParams) (*models.MultiBucketResponseState, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
LogError("error creating Madmin Client:", err)
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating Madmin Client: %v", err))
}
adminClient := AdminClient{Client: mAdmin}
mClient, err := newMinioClient(session)
if err != nil {
LogError("error creating MinIO Client:", err)
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating MinIO Client: %v", err))
}
// create a minioClient interface implementation
// defining the client to be used
@@ -538,8 +526,7 @@ func setMultiBucketReplicationResponse(session *models.Principal, params bucketA
replicationResults := setMultiBucketReplication(ctx, session, adminClient, mnClient, params)
if replicationResults == nil {
err = errors.New("error setting buckets replication")
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, errors.New("error setting buckets replication"))
}
resParsed := []*models.MultiBucketResponseItem{}
@@ -562,18 +549,18 @@ func setMultiBucketReplicationResponse(session *models.Principal, params bucketA
}
func listExternalBucketsResponse(params bucketApi.ListExternalBucketsParams) (*models.ListBucketsResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
remoteAdmin, err := newAdminFromCreds(*params.Body.AccessKey, *params.Body.SecretKey, *params.Body.TargetURL, *params.Body.UseTLS)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
remoteClient := AdminClient{Client: remoteAdmin}
buckets, err := getAccountBuckets(ctx, remoteClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// serialize output
@@ -610,8 +597,7 @@ func getARNsFromIDs(conf *replication.Config, rules []string) []string {
func deleteReplicationRule(ctx context.Context, session *models.Principal, bucketName, ruleID string) error {
mClient, err := newMinioClient(session)
if err != nil {
LogError("error creating MinIO Client: %v", err)
return err
return fmt.Errorf("error creating MinIO Client: %v", err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -619,19 +605,17 @@ func deleteReplicationRule(ctx context.Context, session *models.Principal, bucke
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
LogError("error versioning bucket: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
s3Client, err := newS3BucketClient(session, bucketName, "")
if err != nil {
LogError("error creating S3Client: %v", err)
return err
return fmt.Errorf("error creating S3Client: %v", err)
}
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
LogError("error creating Admin Client: %v", err)
return err
return fmt.Errorf("error creating Admin Client: %v", err)
}
admClient := AdminClient{Client: mAdmin}
@@ -659,8 +643,7 @@ func deleteReplicationRule(ctx context.Context, session *models.Principal, bucke
func deleteAllReplicationRules(ctx context.Context, session *models.Principal, bucketName string) error {
s3Client, err := newS3BucketClient(session, bucketName, "")
if err != nil {
LogError("error creating S3Client: %v", err)
return err
return fmt.Errorf("error creating S3Client: %v", err)
}
// create a mc S3Client interface implementation
// defining the client to be used
@@ -668,8 +651,7 @@ func deleteAllReplicationRules(ctx context.Context, session *models.Principal, b
mClient, err := newMinioClient(session)
if err != nil {
LogError("error creating MinIO Client: %v", err)
return err
return fmt.Errorf("error creating MinIO Client: %v", err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -677,13 +659,12 @@ func deleteAllReplicationRules(ctx context.Context, session *models.Principal, b
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
LogError("error versioning bucket: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
LogError("error creating Admin Client: %v", err)
return err
return fmt.Errorf("error creating Admin Client: %v", err)
}
admClient := AdminClient{Client: mAdmin}
@@ -707,8 +688,7 @@ func deleteAllReplicationRules(ctx context.Context, session *models.Principal, b
func deleteSelectedReplicationRules(ctx context.Context, session *models.Principal, bucketName string, rules []string) error {
mClient, err := newMinioClient(session)
if err != nil {
LogError("error creating MinIO Client: %v", err)
return err
return fmt.Errorf("error creating MinIO Client: %v", err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -716,13 +696,12 @@ func deleteSelectedReplicationRules(ctx context.Context, session *models.Princip
cfg, err := minClient.getBucketReplication(ctx, bucketName)
if err != nil {
LogError("error versioning bucket: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
s3Client, err := newS3BucketClient(session, bucketName, "")
if err != nil {
LogError("error creating S3Client: %v", err)
return err
return fmt.Errorf("error creating S3Client: %v", err)
}
// create a mc S3Client interface implementation
// defining the client to be used
@@ -730,8 +709,7 @@ func deleteSelectedReplicationRules(ctx context.Context, session *models.Princip
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
LogError("error creating Admin Client: %v", err)
return err
return fmt.Errorf("error creating Admin Client: %v", err)
}
admClient := AdminClient{Client: mAdmin}
@@ -756,49 +734,48 @@ func deleteSelectedReplicationRules(ctx context.Context, session *models.Princip
}
func deleteReplicationRuleResponse(session *models.Principal, params bucketApi.DeleteBucketReplicationRuleParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
err := deleteReplicationRule(ctx, session, params.BucketName, params.RuleID)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
func deleteBucketReplicationRulesResponse(session *models.Principal, params bucketApi.DeleteAllReplicationRulesParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
err := deleteAllReplicationRules(ctx, session, params.BucketName)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
func deleteSelectedReplicationRulesResponse(session *models.Principal, params bucketApi.DeleteSelectedReplicationRulesParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
err := deleteSelectedReplicationRules(ctx, session, params.BucketName, params.Rules.Rules)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
func updateBucketReplicationResponse(session *models.Principal, params bucketApi.UpdateMultiBucketReplicationParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
LogError("error creating MinIO Client:", err)
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -822,7 +799,7 @@ func updateBucketReplicationResponse(session *models.Principal, params bucketApi
params.Body.StorageClass)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil

View File

@@ -31,26 +31,24 @@ func registerSiteReplicationStatusHandler(api *operations.ConsoleAPI) {
api.SiteReplicationGetSiteReplicationStatusHandler = siteRepApi.GetSiteReplicationStatusHandlerFunc(func(params siteRepApi.GetSiteReplicationStatusParams, session *models.Principal) middleware.Responder {
rInfo, err := getSRStatusResponse(session, params)
if err != nil {
return siteRepApi.NewGetSiteReplicationStatusDefault(500).WithPayload(prepareError(err))
return siteRepApi.NewGetSiteReplicationStatusDefault(int(err.Code)).WithPayload(err)
}
return siteRepApi.NewGetSiteReplicationStatusOK().WithPayload(rInfo)
})
}
func getSRStatusResponse(session *models.Principal, params siteRepApi.GetSiteReplicationStatusParams) (info *models.SiteReplicationStatusResponse, err error) {
func getSRStatusResponse(session *models.Principal, params siteRepApi.GetSiteReplicationStatusParams) (*models.SiteReplicationStatusResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, err
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
ctx := context.Background()
res, err := getSRStats(ctx, adminClient, params)
if err != nil {
return nil, err
return nil, ErrorWithContext(ctx, err)
}
return res, nil
}

View File

@@ -30,7 +30,7 @@ import (
func registerServiceHandlers(api *operations.ConsoleAPI) {
// Restart Service
api.ServiceRestartServiceHandler = svcApi.RestartServiceHandlerFunc(func(params svcApi.RestartServiceParams, session *models.Principal) middleware.Responder {
if err := getRestartServiceResponse(session); err != nil {
if err := getRestartServiceResponse(session, params); err != nil {
return svcApi.NewRestartServiceDefault(int(err.Code)).WithPayload(err)
}
return svcApi.NewRestartServiceNoContent()
@@ -59,19 +59,19 @@ func serviceRestart(ctx context.Context, client MinioAdmin) error {
}
// getRestartServiceResponse performs serviceRestart()
func getRestartServiceResponse(session *models.Principal) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getRestartServiceResponse(session *models.Principal, params svcApi.RestartServiceParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := serviceRestart(ctx, adminClient); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}

View File

@@ -37,10 +37,9 @@ func (ac adminClientMock) serviceRestart(ctx context.Context) error {
func TestServiceRestart(t *testing.T) {
assert := assert.New(t)
adminClient := adminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
function := "serviceRestart()"
// Test-1 : serviceRestart() restart services no error
// Test-1 : serviceRestart() restart services no errors
// mock function response from listGroups()
minioServiceRestartMock = func(ctx context.Context) error {
return nil
@@ -49,11 +48,11 @@ func TestServiceRestart(t *testing.T) {
return madmin.InfoMessage{}, nil
}
if err := serviceRestart(ctx, adminClient); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error())
}
// Test-2 : serviceRestart() returns error on client.serviceRestart call
// and see that the error is handled correctly and returned
// Test-2 : serviceRestart() returns errors on client.serviceRestart call
// and see that the errors is handled correctly and returned
minioServiceRestartMock = func(ctx context.Context) error {
return errors.New("error")
}
@@ -64,8 +63,8 @@ func TestServiceRestart(t *testing.T) {
assert.Equal("error", err.Error())
}
// Test-3 : serviceRestart() returns error on client.serverInfo() call
// and see that the error is handled correctly and returned
// Test-3 : serviceRestart() returns errors on client.serverInfo() call
// and see that the errors is handled correctly and returned
minioServiceRestartMock = func(ctx context.Context) error {
return nil
}

View File

@@ -29,105 +29,98 @@ import (
func registerSiteReplicationHandler(api *operations.ConsoleAPI) {
api.SiteReplicationGetSiteReplicationInfoHandler = siteRepApi.GetSiteReplicationInfoHandlerFunc(func(params siteRepApi.GetSiteReplicationInfoParams, session *models.Principal) middleware.Responder {
rInfo, err := getSRInfoResponse(session)
rInfo, err := getSRInfoResponse(session, params)
if err != nil {
return siteRepApi.NewGetSiteReplicationInfoDefault(500).WithPayload(prepareError(err))
return siteRepApi.NewGetSiteReplicationInfoDefault(int(err.Code)).WithPayload(err)
}
return siteRepApi.NewGetSiteReplicationInfoOK().WithPayload(rInfo)
})
api.SiteReplicationSiteReplicationInfoAddHandler = siteRepApi.SiteReplicationInfoAddHandlerFunc(func(params siteRepApi.SiteReplicationInfoAddParams, session *models.Principal) middleware.Responder {
eInfo, err := getSRAddResponse(session, &params)
eInfo, err := getSRAddResponse(session, params)
if err != nil {
return siteRepApi.NewSiteReplicationInfoAddDefault(500).WithPayload(err)
return siteRepApi.NewSiteReplicationInfoAddDefault(int(err.Code)).WithPayload(err)
}
return siteRepApi.NewSiteReplicationInfoAddOK().WithPayload(eInfo)
})
api.SiteReplicationSiteReplicationRemoveHandler = siteRepApi.SiteReplicationRemoveHandlerFunc(func(params siteRepApi.SiteReplicationRemoveParams, session *models.Principal) middleware.Responder {
remRes, err := getSRRemoveResponse(session, &params)
remRes, err := getSRRemoveResponse(session, params)
if err != nil {
return siteRepApi.NewSiteReplicationRemoveDefault(500).WithPayload(err)
return siteRepApi.NewSiteReplicationRemoveDefault(int(err.Code)).WithPayload(err)
}
return siteRepApi.NewSiteReplicationRemoveNoContent().WithPayload(remRes)
})
api.SiteReplicationSiteReplicationEditHandler = siteRepApi.SiteReplicationEditHandlerFunc(func(params siteRepApi.SiteReplicationEditParams, session *models.Principal) middleware.Responder {
eInfo, err := getSREditResponse(session, &params)
eInfo, err := getSREditResponse(session, params)
if err != nil {
return siteRepApi.NewSiteReplicationRemoveDefault(500).WithPayload(err)
return siteRepApi.NewSiteReplicationRemoveDefault(int(err.Code)).WithPayload(err)
}
return siteRepApi.NewSiteReplicationEditOK().WithPayload(eInfo)
})
}
func getSRInfoResponse(session *models.Principal) (info *models.SiteReplicationInfoResponse, err error) {
func getSRInfoResponse(session *models.Principal, params siteRepApi.GetSiteReplicationInfoParams) (*models.SiteReplicationInfoResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, err
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
res, err := getSRConfig(ctx, adminClient)
if err != nil {
return nil, err
return nil, ErrorWithContext(ctx, err)
}
return res, nil
}
func getSRAddResponse(session *models.Principal, params *siteRepApi.SiteReplicationInfoAddParams) (*models.SiteReplicationAddResponse, *models.Error) {
func getSRAddResponse(session *models.Principal, params siteRepApi.SiteReplicationInfoAddParams) (*models.SiteReplicationAddResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
res, err := addSiteReplication(ctx, adminClient, params)
res, err := addSiteReplication(ctx, adminClient, &params)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return res, nil
}
func getSREditResponse(session *models.Principal, params *siteRepApi.SiteReplicationEditParams) (*models.PeerSiteEditResponse, *models.Error) {
func getSREditResponse(session *models.Principal, params siteRepApi.SiteReplicationEditParams) (*models.PeerSiteEditResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
eRes, err := editSiteReplication(ctx, adminClient, params)
eRes, err := editSiteReplication(ctx, adminClient, &params)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return eRes, nil
}
func getSRRemoveResponse(session *models.Principal, params *siteRepApi.SiteReplicationRemoveParams) (*models.PeerSiteRemoveResponse, *models.Error) {
func getSRRemoveResponse(session *models.Principal, params siteRepApi.SiteReplicationRemoveParams) (*models.PeerSiteRemoveResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rRes, err := removeSiteReplication(ctx, adminClient, params)
rRes, err := removeSiteReplication(ctx, adminClient, &params)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return rRes, nil
}

View File

@@ -24,7 +24,7 @@ import (
"net/http"
"net/url"
"github.com/minio/console/pkg/utils"
xhttp "github.com/minio/console/pkg/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
@@ -61,7 +61,7 @@ func registerSubnetHandlers(api *operations.ConsoleAPI) {
})
// Get subnet info
api.SubnetSubnetInfoHandler = subnetApi.SubnetInfoHandlerFunc(func(params subnetApi.SubnetInfoParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetInfoResponse(session)
resp, err := GetSubnetInfoResponse(session, params)
if err != nil {
return subnetApi.NewSubnetInfoDefault(int(err.Code)).WithPayload(err)
}
@@ -69,7 +69,7 @@ func registerSubnetHandlers(api *operations.ConsoleAPI) {
})
// Get subnet registration token
api.SubnetSubnetRegTokenHandler = subnetApi.SubnetRegTokenHandlerFunc(func(params subnetApi.SubnetRegTokenParams, session *models.Principal) middleware.Responder {
resp, err := GetSubnetRegTokenResponse(session)
resp, err := GetSubnetRegTokenResponse(session, params)
if err != nil {
return subnetApi.NewSubnetRegTokenDefault(int(err.Code)).WithPayload(err)
}
@@ -100,7 +100,7 @@ func SubnetRegisterWithAPIKey(ctx context.Context, minioClient MinioAdmin, apiKe
return true, nil
}
func SubnetLogin(client utils.HTTPClientI, username, password string) (string, string, error) {
func SubnetLogin(client xhttp.ClientI, username, password string) (string, string, error) {
tokens, err := subnet.Login(client, username, password)
if err != nil {
return "", "", err
@@ -117,22 +117,22 @@ func SubnetLogin(client utils.HTTPClientI, username, password string) (string, s
}
func GetSubnetLoginResponse(session *models.Principal, params subnetApi.SubnetLoginParams) (*models.SubnetLoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
minioClient := AdminClient{Client: mAdmin}
subnetHTTPClient, err := GetSubnetHTTPClient(ctx, minioClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
apiKey := params.Body.APIKey
if apiKey != "" {
registered, err := SubnetRegisterWithAPIKey(ctx, minioClient, apiKey)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return &models.SubnetLoginResponse{
Registered: registered,
@@ -144,7 +144,7 @@ func GetSubnetLoginResponse(session *models.Principal, params subnetApi.SubnetLo
if username != "" && password != "" {
token, mfa, err := SubnetLogin(subnetHTTPClient, username, password)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return &models.SubnetLoginResponse{
MfaToken: mfa,
@@ -152,7 +152,7 @@ func GetSubnetLoginResponse(session *models.Principal, params subnetApi.SubnetLo
Organizations: []*models.SubnetOrganization{},
}, nil
}
return nil, prepareError(ErrorGeneric)
return nil, ErrorWithContext(ctx, ErrDefault)
}
type SubnetRegistration struct {
@@ -161,7 +161,7 @@ type SubnetRegistration struct {
Organizations []models.SubnetOrganization
}
func SubnetLoginWithMFA(client utils.HTTPClientI, username, mfaToken, otp string) (*models.SubnetLoginResponse, error) {
func SubnetLoginWithMFA(client xhttp.ClientI, username, mfaToken, otp string) (*models.SubnetLoginResponse, error) {
tokens, err := subnet.LoginWithMFA(client, username, mfaToken, otp)
if err != nil {
return nil, err
@@ -180,7 +180,7 @@ func SubnetLoginWithMFA(client utils.HTTPClientI, username, mfaToken, otp string
}
// GetSubnetHTTPClient will return a client with proxy if configured, otherwise will return the default console http client
func GetSubnetHTTPClient(ctx context.Context, minioClient MinioAdmin) (*utils.HTTPClient, error) {
func GetSubnetHTTPClient(ctx context.Context, minioClient MinioAdmin) (*xhttp.Client, error) {
var subnetHTTPClient *http.Client
var proxy string
envProxy := getSubnetProxy()
@@ -194,7 +194,7 @@ func GetSubnetHTTPClient(ctx context.Context, minioClient MinioAdmin) (*utils.HT
proxy = envProxy
}
if proxy != "" {
transport := prepareSTSClientTransport(false)
transport := PrepareSTSClientTransport(false)
subnetHTTPClient = &http.Client{
Transport: transport,
}
@@ -206,27 +206,27 @@ func GetSubnetHTTPClient(ctx context.Context, minioClient MinioAdmin) (*utils.HT
} else {
subnetHTTPClient = GetConsoleHTTPClient()
}
clientI := &utils.HTTPClient{
clientI := &xhttp.Client{
Client: subnetHTTPClient,
}
return clientI, nil
}
func GetSubnetLoginWithMFAResponse(session *models.Principal, params subnetApi.SubnetLoginMFAParams) (*models.SubnetLoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
minioClient := AdminClient{Client: mAdmin}
subnetHTTPClient, err := GetSubnetHTTPClient(ctx, minioClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
resp, err := SubnetLoginWithMFA(subnetHTTPClient, *params.Body.Username, *params.Body.MfaToken, *params.Body.Otp)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return resp, nil
}
@@ -257,7 +257,7 @@ func GetSubnetKeyFromMinIOConfig(ctx context.Context, minioClient MinioAdmin) (*
return &res, nil
}
func GetSubnetRegister(ctx context.Context, minioClient MinioAdmin, httpClient utils.HTTPClientI, params subnetApi.SubnetRegisterParams) error {
func GetSubnetRegister(ctx context.Context, minioClient MinioAdmin, httpClient xhttp.ClientI, params subnetApi.SubnetRegisterParams) error {
serverInfo, err := minioClient.serverInfo(ctx)
if err != nil {
return err
@@ -280,45 +280,45 @@ func GetSubnetRegister(ctx context.Context, minioClient MinioAdmin, httpClient u
}
func GetSubnetRegisterResponse(session *models.Principal, params subnetApi.SubnetRegisterParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
subnetHTTPClient, err := GetSubnetHTTPClient(ctx, adminClient)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
err = GetSubnetRegister(ctx, adminClient, subnetHTTPClient, params)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
func GetSubnetInfoResponse(session *models.Principal) (*models.License, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func GetSubnetInfoResponse(session *models.Principal, params subnetApi.SubnetInfoParams) (*models.License, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
subnetTokens, err := GetSubnetKeyFromMinIOConfig(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
if subnetTokens.APIKey == "" {
return nil, prepareError(errLicenseNotFound)
return nil, ErrorWithContext(ctx, ErrLicenseNotFound)
}
client := &utils.HTTPClient{
client := &xhttp.Client{
Client: GetConsoleHTTPClient(),
}
licenseInfo, err := subnet.ParseLicense(client, subnetTokens.License)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
license := &models.License{
Email: licenseInfo.Email,
@@ -345,17 +345,17 @@ func GetSubnetRegToken(ctx context.Context, minioClient MinioAdmin) (string, err
return regToken, nil
}
func GetSubnetRegTokenResponse(session *models.Principal) (*models.SubnetRegTokenResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func GetSubnetRegTokenResponse(session *models.Principal, params subnetApi.SubnetRegTokenParams) (*models.SubnetRegTokenResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
adminClient := AdminClient{Client: mAdmin}
token, err := GetSubnetRegToken(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return &models.SubnetRegTokenResponse{
RegToken: token,

View File

@@ -32,7 +32,7 @@ import (
func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
// return a list of notification endpoints
api.TieringTiersListHandler = tieringApi.TiersListHandlerFunc(func(params tieringApi.TiersListParams, session *models.Principal) middleware.Responder {
tierList, err := getTiersResponse(session)
tierList, err := getTiersResponse(session, params)
if err != nil {
return tieringApi.NewTiersListDefault(int(err.Code)).WithPayload(err)
}
@@ -40,7 +40,7 @@ func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
})
// add a new tiers
api.TieringAddTierHandler = tieringApi.AddTierHandlerFunc(func(params tieringApi.AddTierParams, session *models.Principal) middleware.Responder {
err := getAddTierResponse(session, &params)
err := getAddTierResponse(session, params)
if err != nil {
return tieringApi.NewAddTierDefault(int(err.Code)).WithPayload(err)
}
@@ -48,7 +48,7 @@ func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
})
// get a tier
api.TieringGetTierHandler = tieringApi.GetTierHandlerFunc(func(params tieringApi.GetTierParams, session *models.Principal) middleware.Responder {
notifEndpoints, err := getGetTierResponse(session, &params)
notifEndpoints, err := getGetTierResponse(session, params)
if err != nil {
return tieringApi.NewGetTierDefault(int(err.Code)).WithPayload(err)
}
@@ -56,7 +56,7 @@ func registerAdminTiersHandlers(api *operations.ConsoleAPI) {
})
// edit credentials for a tier
api.TieringEditTierCredentialsHandler = tieringApi.EditTierCredentialsHandlerFunc(func(params tieringApi.EditTierCredentialsParams, session *models.Principal) middleware.Responder {
err := getEditTierCredentialsResponse(session, &params)
err := getEditTierCredentialsResponse(session, params)
if err != nil {
return tieringApi.NewEditTierCredentialsDefault(int(err.Code)).WithPayload(err)
}
@@ -141,21 +141,20 @@ func getTiers(ctx context.Context, client MinioAdmin) (*models.TierListResponse,
}
// getTiersResponse returns a response with a list of tiers
func getTiersResponse(session *models.Principal) (*models.TierListResponse, *models.Error) {
func getTiersResponse(session *models.Principal, params tieringApi.TiersListParams) (*models.TierListResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// serialize output
tiersResp, err := getTiers(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return tiersResp, nil
}
@@ -231,20 +230,21 @@ func addTier(ctx context.Context, client MinioAdmin, params *tieringApi.AddTierP
}
// getAddTierResponse returns the response of admin tier
func getAddTierResponse(session *models.Principal, params *tieringApi.AddTierParams) *models.Error {
func getAddTierResponse(session *models.Principal, params tieringApi.AddTierParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// serialize output
errTier := addTier(ctx, adminClient, params)
errTier := addTier(ctx, adminClient, &params)
if errTier != nil {
return prepareError(errTier)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -309,25 +309,24 @@ func getTier(ctx context.Context, client MinioAdmin, params *tieringApi.GetTierP
}
// build response
return nil, ErrorGenericNotFound
return nil, ErrNotFound
}
// getGetTierResponse returns a tier
func getGetTierResponse(session *models.Principal, params *tieringApi.GetTierParams) (*models.Tier, *models.Error) {
func getGetTierResponse(session *models.Principal, params tieringApi.GetTierParams) (*models.Tier, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// serialize output
addTierResp, err := getTier(ctx, adminClient, params)
addTierResp, err := getTier(ctx, adminClient, &params)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return addTierResp, nil
}
@@ -349,21 +348,20 @@ func editTierCredentials(ctx context.Context, client MinioAdmin, params *tiering
}
// getEditTierCredentialsResponse returns the result of editing credentials for a tier
func getEditTierCredentialsResponse(session *models.Principal, params *tieringApi.EditTierCredentialsParams) *models.Error {
func getEditTierCredentialsResponse(session *models.Principal, params tieringApi.EditTierCredentialsParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// serialize output
err = editTierCredentials(ctx, adminClient, params)
err = editTierCredentials(ctx, adminClient, &params)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}

View File

@@ -45,7 +45,7 @@ const (
func registerUsersHandlers(api *operations.ConsoleAPI) {
// List Users
api.UserListUsersHandler = userApi.ListUsersHandlerFunc(func(params userApi.ListUsersParams, session *models.Principal) middleware.Responder {
listUsersResponse, err := getListUsersResponse(session)
listUsersResponse, err := getListUsersResponse(session, params)
if err != nil {
return userApi.NewListUsersDefault(int(err.Code)).WithPayload(err)
}
@@ -104,7 +104,7 @@ func registerUsersHandlers(api *operations.ConsoleAPI) {
return userApi.NewBulkUpdateUsersGroupsOK()
})
api.BucketListUsersWithAccessToBucketHandler = bucketApi.ListUsersWithAccessToBucketHandlerFunc(func(params bucketApi.ListUsersWithAccessToBucketParams, session *models.Principal) middleware.Responder {
response, err := getListUsersWithAccessToBucketResponse(session, params.Bucket)
response, err := getListUsersWithAccessToBucketResponse(session, params)
if err != nil {
return bucketApi.NewListUsersWithAccessToBucketDefault(int(err.Code)).WithPayload(err)
}
@@ -145,20 +145,19 @@ func listUsers(ctx context.Context, client MinioAdmin) ([]*models.User, error) {
}
// getListUsersResponse performs listUsers() and serializes it to the handler's output
func getListUsersResponse(session *models.Principal) (*models.ListUsersResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListUsersResponse(session *models.Principal, params userApi.ListUsersParams) (*models.ListUsersResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
users, err := listUsers(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// serialize output
listUsersResponse := &models.ListUsersResponse{
@@ -169,7 +168,7 @@ func getListUsersResponse(session *models.Principal) (*models.ListUsersResponse,
// addUser invokes adding a users on `MinioAdmin` and builds the response `models.User`
func addUser(ctx context.Context, client MinioAdmin, accessKey, secretKey *string, groups []string, policies []string) (*models.User, error) {
// Calls into MinIO to add a new user if there's an error return it
// Calls into MinIO to add a new user if there's an errors return it
if err := client.addUser(ctx, *accessKey, *secretKey); err != nil {
return nil, err
}
@@ -208,11 +207,11 @@ func addUser(ctx context.Context, client MinioAdmin, accessKey, secretKey *strin
}
func getUserAddResponse(session *models.Principal, params userApi.AddUserParams) (*models.User, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -223,7 +222,7 @@ func getUserAddResponse(session *models.Principal, params userApi.AddUserParams)
userExists = err == nil
if userExists {
return nil, prepareError(errNonUniqueAccessKey)
return nil, ErrorWithContext(ctx, ErrNonUniqueAccessKey)
}
user, err := addUser(
ctx,
@@ -234,7 +233,7 @@ func getUserAddResponse(session *models.Principal, params userApi.AddUserParams)
params.Body.Policies,
)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return user, nil
}
@@ -245,26 +244,21 @@ func removeUser(ctx context.Context, client MinioAdmin, accessKey string) error
}
func getRemoveUserResponse(session *models.Principal, params userApi.RemoveUserParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
if session.AccountAccessKey == params.Name {
return prepareError(errAvoidSelfAccountDelete)
return ErrorWithContext(ctx, ErrAvoidSelfAccountDelete)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := removeUser(ctx, adminClient, params.Name); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -279,12 +273,12 @@ func getUserInfo(ctx context.Context, client MinioAdmin, accessKey string) (*mad
}
func getUserInfoResponse(session *models.Principal, params userApi.GetUserInfoParams) (*models.User, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
@@ -299,7 +293,7 @@ func getUserInfoResponse(session *models.Principal, params userApi.GetUserInfoPa
errorMessage := "User doesn't exist"
return nil, &models.Error{Code: errorCode, Message: swag.String(errorMessage), DetailedMessage: swag.String(err.Error())}
}
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
var policies []string
@@ -425,12 +419,12 @@ func updateUserGroups(ctx context.Context, client MinioAdmin, user string, group
}
func getUpdateUserGroupsResponse(session *models.Principal, params userApi.UpdateUserGroupsParams) (*models.User, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
@@ -440,7 +434,7 @@ func getUpdateUserGroupsResponse(session *models.Principal, params userApi.Updat
user, err := updateUserGroups(ctx, adminClient, params.Name, params.Body.Groups)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return user, nil
@@ -462,12 +456,12 @@ func setUserStatus(ctx context.Context, client MinioAdmin, user string, status s
}
func getUpdateUserResponse(session *models.Principal, params userApi.UpdateUserInfoParams) (*models.User, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
@@ -479,13 +473,13 @@ func getUpdateUserResponse(session *models.Principal, params userApi.UpdateUserI
groups := params.Body.Groups
if err := setUserStatus(ctx, adminClient, name, status); err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
userElem, errUG := updateUserGroups(ctx, adminClient, name, groups)
if errUG != nil {
return nil, prepareError(errUG)
return nil, ErrorWithContext(ctx, errUG)
}
return userElem, nil
}
@@ -517,14 +511,14 @@ func addUsersListToGroups(ctx context.Context, client MinioAdmin, usersToUpdate
errorsList := []string{} // We get the errors list because we want to have all errors at once.
for _, err := range groupsUpdateList {
errorFromUpdate := <-err // We store the error to avoid Data Race
errorFromUpdate := <-err // We store the errors to avoid Data Race
if errorFromUpdate != nil {
// If there is an error, we store the errors strings so we can join them after we receive all errors
// If there is an errors, we store the errors strings so we can join them after we receive all errors
errorsList = append(errorsList, errorFromUpdate.Error()) // We wait until all the channels have been closed.
}
}
// If there are errors, we throw the final error with the errors inside
// If there are errors, we throw the final errors with the errors inside
if len(errorsList) > 0 {
errGen := fmt.Errorf("error in users-groups assignation: %q", strings.Join(errorsList[:], ","))
return errGen
@@ -534,12 +528,12 @@ func addUsersListToGroups(ctx context.Context, client MinioAdmin, usersToUpdate
}
func getAddUsersListToGroupsResponse(session *models.Principal, params userApi.BulkUpdateUsersGroupsParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
@@ -550,24 +544,27 @@ func getAddUsersListToGroupsResponse(session *models.Principal, params userApi.B
groupsList := params.Body.Groups
if err := addUsersListToGroups(ctx, adminClient, usersList, groupsList); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
func getListUsersWithAccessToBucketResponse(session *models.Principal, bucket string) ([]string, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListUsersWithAccessToBucketResponse(session *models.Principal, params bucketApi.ListUsersWithAccessToBucketParams) ([]string, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
return listUsersWithAccessToBucket(ctx, adminClient, bucket)
list, err := listUsersWithAccessToBucket(ctx, adminClient, params.Bucket)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
return list, nil
}
func policyAllowsAndMatchesBucket(policy *iampolicy.Policy, bucket string) int {
@@ -587,10 +584,10 @@ func policyAllowsAndMatchesBucket(policy *iampolicy.Policy, bucket string) int {
return Unknown
}
func listUsersWithAccessToBucket(ctx context.Context, adminClient MinioAdmin, bucket string) ([]string, *models.Error) {
func listUsersWithAccessToBucket(ctx context.Context, adminClient MinioAdmin, bucket string) ([]string, error) {
users, err := adminClient.listUsers(ctx)
if err != nil {
return nil, prepareError(err)
return nil, err
}
var retval []string
akHasAccess := make(map[string]struct{})
@@ -603,7 +600,7 @@ func listUsersWithAccessToBucket(ctx context.Context, adminClient MinioAdmin, bu
}
policy, err := adminClient.getPolicy(ctx, policyName)
if err != nil {
LogError("unable to fetch policy %s: %v", policyName, err)
ErrorWithContext(ctx, fmt.Errorf("unable to fetch policy %s: %v", policyName, err))
continue
}
if _, ok := akIsDenied[k]; !ok {
@@ -622,19 +619,19 @@ func listUsersWithAccessToBucket(ctx context.Context, adminClient MinioAdmin, bu
groups, err := adminClient.listGroups(ctx)
if err != nil {
LogError("unable to list groups: %v", err)
ErrorWithContext(ctx, fmt.Errorf("unable to list groups: %v", err))
return retval, nil
}
for _, groupName := range groups {
info, err := groupInfo(ctx, adminClient, groupName)
if err != nil {
LogError("unable to fetch group info %s: %v", groupName, err)
ErrorWithContext(ctx, fmt.Errorf("unable to fetch group info %s: %v", groupName, err))
continue
}
policy, err := adminClient.getPolicy(ctx, info.Policy)
if err != nil {
LogError("unable to fetch group policy %s: %v", info.Policy, err)
ErrorWithContext(ctx, fmt.Errorf("unable to fetch group policy %s: %v", info.Policy, err))
continue
}
for _, member := range info.Members {
@@ -665,11 +662,11 @@ func changeUserPassword(ctx context.Context, client MinioAdmin, selectedUser str
// getChangeUserPasswordResponse will change the password of selctedUser to newSecretKey
func getChangeUserPasswordResponse(session *models.Principal, params accountApi.ChangeUserPasswordParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -681,7 +678,7 @@ func getChangeUserPasswordResponse(session *models.Principal, params accountApi.
// changes password of user to newSecretKey
if err := changeUserPassword(ctx, adminClient, user, newSecretKey); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}

View File

@@ -525,7 +525,7 @@ func TestListUsersWithAccessToBucket(t *testing.T) {
}
return mockResponse, nil
}
return nil, ErrorGeneric
return nil, ErrDefault
}
type args struct {
bucket string

View File

@@ -454,7 +454,7 @@ func newS3Config(endpoint, accessKey, secretKey, sessionToken string, insecure b
s3Config.SecretKey = secretKey
s3Config.SessionToken = sessionToken
s3Config.Signature = "S3v4"
s3Config.Transport = prepareSTSClientTransport(insecure)
s3Config.Transport = PrepareSTSClientTransport(insecure)
return s3Config
}

View File

@@ -79,7 +79,7 @@ func Test_computeObjectURLWithoutEncode(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
got, err := computeObjectURLWithoutEncode(tt.args.bucketName, tt.args.prefix)
if (err != nil) != tt.wantErr {
t.Errorf("computeObjectURLWithoutEncode() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("computeObjectURLWithoutEncode() errors = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {

View File

@@ -20,6 +20,7 @@ package restapi
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
@@ -34,6 +35,9 @@ import (
"sync"
"time"
"github.com/minio/console/pkg/logger"
"github.com/minio/console/pkg/utils"
"github.com/klauspost/compress/gzhttp"
portal_ui "github.com/minio/console/portal-ui"
@@ -75,7 +79,7 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
api.KeyAuth = func(token string, scopes []string) (*models.Principal, error) {
// we are validating the session token by decrypting the claims inside, if the operation succeed that means the jwt
// was generated and signed by us in the first place
claims, err := auth.SessionTokenAuthenticate(token)
claims, err := auth.ParseClaimsFromToken(token)
if err != nil {
api.Logger("Unable to validate the session token %s: %v", token, err)
return nil, errors.New(401, "incorrect api key auth")
@@ -167,13 +171,43 @@ func setupMiddlewares(handler http.Handler) http.Handler {
return handler
}
func ContextMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID, err := utils.NewUUID()
if err != nil && err != auth.ErrNoAuthToken {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
ctx := context.WithValue(r.Context(), utils.ContextRequestID, requestID)
ctx = context.WithValue(ctx, utils.ContextRequestUserAgent, r.UserAgent())
ctx = context.WithValue(ctx, utils.ContextRequestHost, r.Host)
ctx = context.WithValue(ctx, utils.ContextRequestRemoteAddr, r.RemoteAddr)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func AuditLogMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rw := logger.NewResponseWriter(w)
next.ServeHTTP(rw, r)
if strings.HasPrefix(r.URL.Path, "/ws") || strings.HasPrefix(r.URL.Path, "/api") {
logger.AuditLog(r.Context(), rw, r, map[string]interface{}{}, "Authorization", "Cookie", "Set-Cookie")
}
})
}
// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logging and metrics
// So this is a good place to plug in a panic handling middleware, logger and metrics
func setupGlobalMiddleware(handler http.Handler) http.Handler {
// handle cookie or authorization header for session
next := AuthenticationMiddleware(handler)
gnext := gzhttp.GzipHandler(handler)
// if audit-log is enabled console will log all incoming request
next := AuditLogMiddleware(gnext)
// serve static files
next = FileServerMiddleware(next)
// add information to request context
next = ContextMiddleware(next)
// handle cookie or authorization header for session
next = AuthenticationMiddleware(next)
sslHostFn := secure.SSLHostFunc(func(host string) string {
h, _, err := net.SplitHostPort(host)
@@ -210,8 +244,7 @@ func setupGlobalMiddleware(handler http.Handler) http.Handler {
}
secureMiddleware := secure.New(secureOptions)
next = secureMiddleware.Handler(next)
gnext := gzhttp.GzipHandler(next)
return RejectS3Middleware(gnext)
return RejectS3Middleware(next)
}
// RejectS3Middleware will reject requests that have AWS S3 specific headers.
@@ -242,14 +275,21 @@ func AuthenticationMiddleware(next http.Handler) http.Handler {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
sessionToken, _ := auth.DecryptToken(token)
// All handlers handle appropriately to return errors
// based on their swagger rules, we do not need to
// additionally return error here, let the next ServeHTTPs
// handle it appropriately.
if token != "" {
r.Header.Add("Authorization", "Bearer "+token)
if len(sessionToken) > 0 {
r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", string(sessionToken)))
}
next.ServeHTTP(w, r)
ctx := r.Context()
claims, _ := auth.ParseClaimsFromToken(string(sessionToken))
if claims != nil {
// save user session id context
ctx = context.WithValue(r.Context(), utils.ContextRequestUserID, claims.STSSessionToken)
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
@@ -362,7 +402,7 @@ func (lw nullWriter) Write(b []byte) (int, error) {
// This function can be called multiple times, depending on the number of serving schemes.
// scheme value will be set accordingly: "http", "https" or "unix"
func configureServer(s *http.Server, _, _ string) {
// Turn-off random logging by Go net/http
// Turn-off random logger by Go net/http
s.ErrorLog = log.New(&nullWriter{}, "", 0)
}

View File

@@ -1,232 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package restapi
import (
"errors"
"runtime"
"strings"
"github.com/minio/minio-go/v7"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/madmin-go"
)
var (
// ErrorGeneric is a generic error message
ErrorGeneric = errors.New("an error occurred, please try again")
errInvalidCredentials = errors.New("invalid Login")
errForbidden = errors.New("403 Forbidden")
errFileTooLarge = errors.New("413 File too Large")
errorGenericInvalidSession = errors.New("invalid session")
// ErrorGenericNotFound Generic error for not found
ErrorGenericNotFound = errors.New("not found")
// Explicit error messages
errorInvalidErasureCodingValue = errors.New("invalid Erasure Coding Value")
errBucketBodyNotInRequest = errors.New("error bucket body not in request")
errBucketNameNotInRequest = errors.New("error bucket name not in request")
errGroupBodyNotInRequest = errors.New("error group body not in request")
errGroupNameNotInRequest = errors.New("error group name not in request")
errGroupAlreadyExists = errors.New("error group name already in use")
errPolicyNameNotInRequest = errors.New("error policy name not in request")
errPolicyBodyNotInRequest = errors.New("error policy body not in request")
errInvalidEncryptionAlgorithm = errors.New("error invalid encryption algorithm")
errSSENotConfigured = errors.New("error server side encryption configuration not found")
errBucketLifeCycleNotConfigured = errors.New("error bucket life cycle configuration not found")
errChangePassword = errors.New("error please check your current password")
errInvalidLicense = errors.New("invalid license key")
errLicenseNotFound = errors.New("license not found")
errAvoidSelfAccountDelete = errors.New("logged in user cannot be deleted by itself")
errAccessDenied = errors.New("access denied")
errOauth2Provider = errors.New("unable to contact configured identity provider")
errNonUniqueAccessKey = errors.New("access key already in use")
)
// Tiering errors
var (
errRemoteTierExists = errors.New("Specified remote tier already exists")
errRemoteTierNotFound = errors.New("Specified remote tier was not found")
errRemoteTierUppercase = errors.New("Tier name must be in uppercase")
errRemoteTierBucketNotFound = errors.New("Remote tier bucket not found")
errRemoteInvalidCredentials = errors.New("Invalid remote tier credentials")
)
// prepareError receives an error object and parse it against k8sErrors, returns the right error code paired with a generic error message
func prepareError(err ...error) *models.Error {
errorCode := int32(500)
errorMessage := ErrorGeneric.Error()
if len(err) > 0 {
frame := getFrame(2)
fileParts := strings.Split(frame.File, "/")
LogError("original error -> (%s:%d: %v)", fileParts[len(fileParts)-1], frame.Line, err[0])
if err[0].Error() == errForbidden.Error() {
errorCode = 403
}
if err[0] == ErrorGenericNotFound {
errorCode = 404
errorMessage = ErrorGenericNotFound.Error()
}
if errors.Is(err[0], errInvalidCredentials) {
errorCode = 401
errorMessage = errInvalidCredentials.Error()
}
// console invalid erasure coding value
if errors.Is(err[0], errorInvalidErasureCodingValue) {
errorCode = 400
errorMessage = errorInvalidErasureCodingValue.Error()
}
if errors.Is(err[0], errBucketBodyNotInRequest) {
errorCode = 400
errorMessage = errBucketBodyNotInRequest.Error()
}
if errors.Is(err[0], errBucketNameNotInRequest) {
errorCode = 400
errorMessage = errBucketNameNotInRequest.Error()
}
if errors.Is(err[0], errGroupBodyNotInRequest) {
errorCode = 400
errorMessage = errGroupBodyNotInRequest.Error()
}
if errors.Is(err[0], errGroupNameNotInRequest) {
errorCode = 400
errorMessage = errGroupNameNotInRequest.Error()
}
if errors.Is(err[0], errGroupAlreadyExists) {
errorCode = 400
errorMessage = errGroupAlreadyExists.Error()
}
if errors.Is(err[0], errPolicyNameNotInRequest) {
errorCode = 400
errorMessage = errPolicyNameNotInRequest.Error()
}
if errors.Is(err[0], errPolicyBodyNotInRequest) {
errorCode = 400
errorMessage = errPolicyBodyNotInRequest.Error()
}
// console invalid session error
if errors.Is(err[0], errorGenericInvalidSession) {
errorCode = 401
errorMessage = errorGenericInvalidSession.Error()
}
// Bucket life cycle not configured
if errors.Is(err[0], errBucketLifeCycleNotConfigured) {
errorCode = 404
errorMessage = errBucketLifeCycleNotConfigured.Error()
}
// Encryption not configured
if errors.Is(err[0], errSSENotConfigured) {
errorCode = 404
errorMessage = errSSENotConfigured.Error()
}
// account change password
if madmin.ToErrorResponse(err[0]).Code == "SignatureDoesNotMatch" {
errorCode = 403
errorMessage = errChangePassword.Error()
}
if errors.Is(err[0], errLicenseNotFound) {
errorCode = 404
errorMessage = errLicenseNotFound.Error()
}
if errors.Is(err[0], errInvalidLicense) {
errorCode = 404
errorMessage = errInvalidLicense.Error()
}
if errors.Is(err[0], errAvoidSelfAccountDelete) {
errorCode = 403
errorMessage = errAvoidSelfAccountDelete.Error()
}
if madmin.ToErrorResponse(err[0]).Code == "AccessDenied" {
errorCode = 403
errorMessage = errAccessDenied.Error()
}
if madmin.ToErrorResponse(err[0]).Code == "InvalidAccessKeyId" {
errorCode = 401
errorMessage = errorGenericInvalidSession.Error()
}
// console invalid session error
if madmin.ToErrorResponse(err[0]).Code == "XMinioAdminNoSuchUser" {
errorCode = 401
errorMessage = errorGenericInvalidSession.Error()
}
// if we received a second error take that as friendly message but don't override the code
if len(err) > 1 && err[1] != nil {
LogError("friendly error: %v", err[1].Error())
errorMessage = err[1].Error()
}
// if we receive third error we just print that as debugging
if len(err) > 2 && err[2] != nil {
LogError("debugging error: %v", err[2].Error())
}
// tiering errors
if err[0].Error() == errRemoteTierExists.Error() {
errorCode = 400
errorMessage = err[0].Error()
}
if err[0].Error() == errRemoteTierNotFound.Error() {
errorCode = 400
errorMessage = err[0].Error()
}
if err[0].Error() == errRemoteTierUppercase.Error() {
errorCode = 400
errorMessage = err[0].Error()
}
if err[0].Error() == errRemoteTierBucketNotFound.Error() {
errorCode = 400
errorMessage = err[0].Error()
}
if err[0].Error() == errRemoteInvalidCredentials.Error() {
errorCode = 403
errorMessage = err[0].Error()
}
if err[0].Error() == errFileTooLarge.Error() {
errorCode = 413
errorMessage = err[0].Error()
}
// bucket already exists
if minio.ToErrorResponse(err[0]).Code == "BucketAlreadyOwnedByYou" {
errorCode = 400
errorMessage = "Bucket already exists"
}
}
return &models.Error{Code: errorCode, Message: swag.String(errorMessage), DetailedMessage: swag.String(err[0].Error())}
}
func getFrame(skipFrames int) runtime.Frame {
// We need the frame at index skipFrames+2, since we never want runtime.Callers and getFrame
targetFrameIndex := skipFrames + 2
// Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need
programCounters := make([]uintptr, targetFrameIndex+2)
n := runtime.Callers(0, programCounters)
frame := runtime.Frame{Function: "unknown"}
if n > 0 {
frames := runtime.CallersFrames(programCounters[:n])
for more, frameIndex := true, 0; more && frameIndex <= targetFrameIndex; frameIndex++ {
var frameCandidate runtime.Frame
frameCandidate, more = frames.Next()
if frameIndex == targetFrameIndex {
frame = frameCandidate
}
}
}
return frame
}

231
restapi/errors.go Normal file
View File

@@ -0,0 +1,231 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package restapi
import (
"context"
"errors"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/minio/madmin-go"
"github.com/minio/minio-go/v7"
)
var (
ErrDefault = errors.New("an errors occurred, please try again")
ErrInvalidLogin = errors.New("invalid Login")
ErrForbidden = errors.New("403 Forbidden")
ErrFileTooLarge = errors.New("413 File too Large")
ErrInvalidSession = errors.New("invalid session")
ErrNotFound = errors.New("not found")
ErrGroupAlreadyExists = errors.New("error group name already in use")
ErrInvalidErasureCodingValue = errors.New("invalid Erasure Coding Value")
ErrBucketBodyNotInRequest = errors.New("error bucket body not in request")
ErrBucketNameNotInRequest = errors.New("error bucket name not in request")
ErrGroupBodyNotInRequest = errors.New("error group body not in request")
ErrGroupNameNotInRequest = errors.New("error group name not in request")
ErrPolicyNameNotInRequest = errors.New("error policy name not in request")
ErrPolicyBodyNotInRequest = errors.New("error policy body not in request")
ErrInvalidEncryptionAlgorithm = errors.New("error invalid encryption algorithm")
ErrSSENotConfigured = errors.New("error server side encryption configuration not found")
ErrBucketLifeCycleNotConfigured = errors.New("error bucket life cycle configuration not found")
ErrChangePassword = errors.New("error please check your current password")
ErrInvalidLicense = errors.New("invalid license key")
ErrLicenseNotFound = errors.New("license not found")
ErrAvoidSelfAccountDelete = errors.New("logged in user cannot be deleted by itself")
ErrAccessDenied = errors.New("access denied")
ErrOauth2Provider = errors.New("unable to contact configured identity provider")
ErrNonUniqueAccessKey = errors.New("access key already in use")
ErrRemoteTierExists = errors.New("specified remote tier already exists")
ErrRemoteTierNotFound = errors.New("specified remote tier was not found")
ErrRemoteTierUppercase = errors.New("tier name must be in uppercase")
ErrRemoteTierBucketNotFound = errors.New("remote tier bucket not found")
ErrRemoteInvalidCredentials = errors.New("invalid remote tier credentials")
ErrUnableToGetTenantUsage = errors.New("unable to get tenant usage")
ErrTooManyNodes = errors.New("cannot request more nodes than what is available in the cluster")
ErrTooFewNodes = errors.New("there are not enough nodes in the cluster to support this tenant")
ErrTooFewAvailableNodes = errors.New("there is not enough available nodes to satisfy this requirement")
ErrFewerThanFourNodes = errors.New("at least 4 nodes are required for a tenant")
ErrUnableToGetTenantLogs = errors.New("unable to get tenant logs")
ErrUnableToUpdateTenantCertificates = errors.New("unable to update tenant certificates")
ErrUpdatingEncryptionConfig = errors.New("unable to update encryption configuration")
ErrDeletingEncryptionConfig = errors.New("error disabling tenant encryption")
ErrEncryptionConfigNotFound = errors.New("encryption configuration not found")
ErrPolicyNotFound = errors.New("policy does not exist")
)
// ErrorWithContext :
func ErrorWithContext(ctx context.Context, err ...interface{}) *models.Error {
errorCode := int32(500)
errorMessage := ErrDefault.Error()
var err1 error
var exists bool
if len(err) > 0 {
if err1, exists = err[0].(error); exists {
if err1.Error() == ErrForbidden.Error() {
errorCode = 403
}
if err1 == ErrNotFound {
errorCode = 404
errorMessage = ErrNotFound.Error()
}
if errors.Is(err1, ErrInvalidLogin) {
errorCode = 401
errorMessage = ErrInvalidLogin.Error()
}
// console invalid erasure coding value
if errors.Is(err1, ErrInvalidErasureCodingValue) {
errorCode = 400
errorMessage = ErrInvalidErasureCodingValue.Error()
}
if errors.Is(err1, ErrBucketBodyNotInRequest) {
errorCode = 400
errorMessage = ErrBucketBodyNotInRequest.Error()
}
if errors.Is(err1, ErrBucketNameNotInRequest) {
errorCode = 400
errorMessage = ErrBucketNameNotInRequest.Error()
}
if errors.Is(err1, ErrGroupBodyNotInRequest) {
errorCode = 400
errorMessage = ErrGroupBodyNotInRequest.Error()
}
if errors.Is(err1, ErrGroupNameNotInRequest) {
errorCode = 400
errorMessage = ErrGroupNameNotInRequest.Error()
}
if errors.Is(err1, ErrPolicyNameNotInRequest) {
errorCode = 400
errorMessage = ErrPolicyNameNotInRequest.Error()
}
if errors.Is(err1, ErrPolicyBodyNotInRequest) {
errorCode = 400
errorMessage = ErrPolicyBodyNotInRequest.Error()
}
// console invalid session errors
if errors.Is(err1, ErrInvalidSession) {
errorCode = 401
errorMessage = ErrInvalidSession.Error()
}
if errors.Is(err1, ErrGroupAlreadyExists) {
errorCode = 400
errorMessage = ErrGroupAlreadyExists.Error()
}
// Bucket life cycle not configured
if errors.Is(err1, ErrBucketLifeCycleNotConfigured) {
errorCode = 404
errorMessage = ErrBucketLifeCycleNotConfigured.Error()
}
// Encryption not configured
if errors.Is(err1, ErrSSENotConfigured) {
errorCode = 404
errorMessage = ErrSSENotConfigured.Error()
}
// account change password
if errors.Is(err1, ErrChangePassword) {
errorCode = 403
errorMessage = ErrChangePassword.Error()
}
if madmin.ToErrorResponse(err1).Code == "SignatureDoesNotMatch" {
errorCode = 403
errorMessage = ErrChangePassword.Error()
}
if errors.Is(err1, ErrLicenseNotFound) {
errorCode = 404
errorMessage = ErrLicenseNotFound.Error()
}
if errors.Is(err1, ErrInvalidLicense) {
errorCode = 404
errorMessage = ErrInvalidLicense.Error()
}
if errors.Is(err1, ErrAvoidSelfAccountDelete) {
errorCode = 403
errorMessage = ErrAvoidSelfAccountDelete.Error()
}
if errors.Is(err1, ErrAccessDenied) {
errorCode = 403
errorMessage = ErrAccessDenied.Error()
}
if errors.Is(err1, ErrPolicyNotFound) {
errorCode = 404
errorMessage = ErrPolicyNotFound.Error()
}
if madmin.ToErrorResponse(err1).Code == "AccessDenied" {
errorCode = 403
errorMessage = ErrAccessDenied.Error()
}
if madmin.ToErrorResponse(err1).Code == "InvalidAccessKeyId" {
errorCode = 401
errorMessage = ErrInvalidSession.Error()
}
// console invalid session errors
if madmin.ToErrorResponse(err1).Code == "XMinioAdminNoSuchUser" {
errorCode = 401
errorMessage = ErrInvalidSession.Error()
}
// tiering errors
if err1.Error() == ErrRemoteTierExists.Error() {
errorCode = 400
errorMessage = err1.Error()
}
if err1.Error() == ErrRemoteTierNotFound.Error() {
errorCode = 400
errorMessage = err1.Error()
}
if err1.Error() == ErrRemoteTierUppercase.Error() {
errorCode = 400
errorMessage = err1.Error()
}
if err1.Error() == ErrRemoteTierBucketNotFound.Error() {
errorCode = 400
errorMessage = err1.Error()
}
if err1.Error() == ErrRemoteInvalidCredentials.Error() {
errorCode = 403
errorMessage = err1.Error()
}
if err1.Error() == ErrFileTooLarge.Error() {
errorCode = 413
errorMessage = err1.Error()
}
// bucket already exists
if minio.ToErrorResponse(err1).Code == "BucketAlreadyOwnedByYou" {
errorCode = 400
errorMessage = "Bucket already exists"
}
LogError(err1.Error(), err...)
LogIf(ctx, err1, err...)
}
if len(err) > 1 && err[1] != nil {
if err2, ok := err[1].(error); ok {
errorMessage = err2.Error()
}
}
}
return &models.Error{Code: errorCode, Message: swag.String(errorMessage), DetailedMessage: swag.String(err1.Error())}
}
// Error receives an errors object and parse it against k8sErrors, returns the right errors code paired with a generic errors message
func Error(err ...interface{}) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
return ErrorWithContext(ctx, err...)
}

138
restapi/errors_test.go Normal file
View File

@@ -0,0 +1,138 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package restapi
import (
"context"
"fmt"
"testing"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
"github.com/stretchr/testify/assert"
)
func TestError(t *testing.T) {
type args struct {
err []interface{}
}
type testError struct {
name string
args args
want *models.Error
}
var tests []testError
type expectedError struct {
err error
code int
}
appErrors := map[string]expectedError{
"ErrDefault": {code: 500, err: ErrDefault},
"ErrInvalidLogin": {code: 401, err: ErrInvalidLogin},
"ErrForbidden": {code: 403, err: ErrForbidden},
"ErrFileTooLarge": {code: 413, err: ErrFileTooLarge},
"ErrInvalidSession": {code: 401, err: ErrInvalidSession},
"ErrNotFound": {code: 404, err: ErrNotFound},
"ErrGroupAlreadyExists": {code: 400, err: ErrGroupAlreadyExists},
"ErrInvalidErasureCodingValue": {code: 400, err: ErrInvalidErasureCodingValue},
"ErrBucketBodyNotInRequest": {code: 400, err: ErrBucketBodyNotInRequest},
"ErrBucketNameNotInRequest": {code: 400, err: ErrBucketNameNotInRequest},
"ErrGroupBodyNotInRequest": {code: 400, err: ErrGroupBodyNotInRequest},
"ErrGroupNameNotInRequest": {code: 400, err: ErrGroupNameNotInRequest},
"ErrPolicyNameNotInRequest": {code: 400, err: ErrPolicyNameNotInRequest},
"ErrPolicyBodyNotInRequest": {code: 400, err: ErrPolicyBodyNotInRequest},
"ErrInvalidEncryptionAlgorithm": {code: 500, err: ErrInvalidEncryptionAlgorithm},
"ErrSSENotConfigured": {code: 404, err: ErrSSENotConfigured},
"ErrBucketLifeCycleNotConfigured": {code: 404, err: ErrBucketLifeCycleNotConfigured},
"ErrChangePassword": {code: 403, err: ErrChangePassword},
"ErrInvalidLicense": {code: 404, err: ErrInvalidLicense},
"ErrLicenseNotFound": {code: 404, err: ErrLicenseNotFound},
"ErrAvoidSelfAccountDelete": {code: 403, err: ErrAvoidSelfAccountDelete},
"ErrAccessDenied": {code: 403, err: ErrAccessDenied},
"ErrNonUniqueAccessKey": {code: 500, err: ErrNonUniqueAccessKey},
"ErrRemoteTierExists": {code: 400, err: ErrRemoteTierExists},
"ErrRemoteTierNotFound": {code: 400, err: ErrRemoteTierNotFound},
"ErrRemoteTierUppercase": {code: 400, err: ErrRemoteTierUppercase},
"ErrRemoteTierBucketNotFound": {code: 400, err: ErrRemoteTierBucketNotFound},
"ErrRemoteInvalidCredentials": {code: 403, err: ErrRemoteInvalidCredentials},
"ErrUnableToGetTenantUsage": {code: 500, err: ErrUnableToGetTenantUsage},
"ErrTooManyNodes": {code: 500, err: ErrTooManyNodes},
"ErrTooFewNodes": {code: 500, err: ErrTooFewNodes},
"ErrTooFewAvailableNodes": {code: 500, err: ErrTooFewAvailableNodes},
"ErrFewerThanFourNodes": {code: 500, err: ErrFewerThanFourNodes},
"ErrUnableToGetTenantLogs": {code: 500, err: ErrUnableToGetTenantLogs},
"ErrUnableToUpdateTenantCertificates": {code: 500, err: ErrUnableToUpdateTenantCertificates},
"ErrUpdatingEncryptionConfig": {code: 500, err: ErrUpdatingEncryptionConfig},
"ErrDeletingEncryptionConfig": {code: 500, err: ErrDeletingEncryptionConfig},
"ErrEncryptionConfigNotFound": {code: 500, err: ErrEncryptionConfigNotFound},
}
for k, e := range appErrors {
tests = append(tests, testError{
name: fmt.Sprintf("%s error", k),
args: args{
err: []interface{}{e.err},
},
want: &models.Error{Code: int32(e.code), Message: swag.String(e.err.Error()), DetailedMessage: swag.String(e.err.Error())},
})
}
tests = append(tests,
testError{
name: "passing multiple errors",
args: args{
err: []interface{}{ErrDefault, ErrInvalidLogin},
},
want: &models.Error{Code: int32(500), Message: swag.String(ErrDefault.Error()), DetailedMessage: swag.String(ErrDefault.Error())},
})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Error(tt.args.err...)
assert.Equalf(t, tt.want.Code, got.Code, "Error(%v) Got (%v)", tt.want.Code, got.Code)
assert.Equalf(t, *tt.want.DetailedMessage, *got.DetailedMessage, "Error(%s) Got (%s)", *tt.want.DetailedMessage, *got.DetailedMessage)
})
}
}
func TestErrorWithContext(t *testing.T) {
type args struct {
ctx context.Context
err []interface{}
}
tests := []struct {
name string
args args
want *models.Error
}{
{
name: "default error",
args: args{
ctx: context.Background(),
err: []interface{}{ErrDefault},
},
want: &models.Error{Code: 500, Message: swag.String(ErrDefault.Error()), DetailedMessage: swag.String(ErrDefault.Error())},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, ErrorWithContext(tt.args.ctx, tt.args.err...), "ErrorWithContext(%v, %v)", tt.args.ctx, tt.args.err)
})
}
}

View File

@@ -18,6 +18,7 @@
package restapi
import (
"context"
"errors"
"log"
"os"
@@ -36,10 +37,14 @@ func logError(msg string, data ...interface{}) {
errorLog.Printf(msg+"\n", data...)
}
func logIf(ctx context.Context, err error, errKind ...interface{}) {
}
// globally changeable logger styles
var (
LogInfo = logInfo
LogError = logError
LogIf = logIf
)
// Context captures all command line flags values

View File

@@ -23,7 +23,8 @@ import (
"time"
)
func prepareSTSClientTransport(insecure bool) *http.Transport {
// PrepareSTSClientTransport :
func PrepareSTSClientTransport(insecure bool) *http.Transport {
// This takes github.com/minio/madmin-go/transport.go as an example
//
// DefaultTransport - this default transport is similar to
@@ -56,7 +57,7 @@ func prepareSTSClientTransport(insecure bool) *http.Transport {
// PrepareConsoleHTTPClient returns an http.Client with custom configurations need it by *credentials.STSAssumeRole
// custom configurations include the use of CA certificates
func PrepareConsoleHTTPClient(insecure bool) *http.Client {
transport := prepareSTSClientTransport(insecure)
transport := PrepareSTSClientTransport(insecure)
// Return http client with default configuration
c := &http.Client{
Transport: transport,

View File

@@ -55,7 +55,7 @@ func changePassword(ctx context.Context, client MinioAdmin, session *models.Prin
// getChangePasswordResponse will validate user knows what is the current password (avoid account hijacking), update user account password
// and authenticate the user generating a new session token/cookie
func getChangePasswordResponse(session *models.Principal, params accountApi.AccountChangePasswordParams) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// changePassword operations requires an AdminClient initialized with parent account credentials not
// STS credentials
@@ -64,7 +64,7 @@ func getChangePasswordResponse(session *models.Principal, params accountApi.Acco
STSSecretAccessKey: *params.Body.CurrentSecretKey,
})
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// parentAccountClient will contain access and secret key credentials for the user
userClient := AdminClient{Client: parentAccountClient}
@@ -73,18 +73,18 @@ func getChangePasswordResponse(session *models.Principal, params accountApi.Acco
// currentSecretKey will compare currentSecretKey against the stored secret key inside the encrypted session
if err := changePassword(ctx, userClient, session, newSecretKey); err != nil {
return nil, prepareError(errChangePassword, nil, err)
return nil, ErrorWithContext(ctx, ErrChangePassword, nil, err)
}
// user credentials are updated at this point, we need to generate a new admin client and authenticate using
// the new credentials
credentials, err := getConsoleCredentials(accessKey, newSecretKey)
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
return nil, ErrorWithContext(ctx, ErrInvalidLogin, nil, err)
}
// authenticate user and generate new session token
sessionID, err := login(credentials, &auth.SessionFeatures{HideMenu: session.Hm})
if err != nil {
return nil, prepareError(errInvalidCredentials, nil, err)
return nil, ErrorWithContext(ctx, ErrInvalidLogin, nil, err)
}
// serialize output
loginResponse := &models.LoginResponse{

View File

@@ -19,6 +19,7 @@ package restapi
import (
"context"
"errors"
"net/http"
"testing"
"github.com/minio/console/models"
@@ -36,6 +37,7 @@ func Test_getChangePasswordResponse(t *testing.T) {
CurrentSecretKey := "string"
NewSecretKey := "string"
changePasswordParameters := accountApi.AccountChangePasswordParams{
HTTPRequest: &http.Request{},
Body: &models.AccountChangePasswordRequest{
CurrentSecretKey: &CurrentSecretKey,
NewSecretKey: &NewSecretKey,

View File

@@ -21,8 +21,6 @@ import (
"errors"
"fmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/restapi/operations"
bucektApi "github.com/minio/console/restapi/operations/bucket"
@@ -53,19 +51,17 @@ func registerBucketQuotaHandlers(api *operations.ConsoleAPI) {
}
func setBucketQuotaResponse(session *models.Principal, params bucektApi.SetBucketQuotaParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
if err := setBucketQuota(params.HTTPRequest.Context(), &adminClient, &params.Name, params.Body); err != nil {
return &models.Error{
Code: 500,
Message: swag.String(err.Error()),
}
if err := setBucketQuota(ctx, &adminClient, &params.Name, params.Body); err != nil {
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -97,32 +93,28 @@ func setBucketQuota(ctx context.Context, ac *AdminClient, bucket *string, bucket
}
func getBucketQuotaResponse(session *models.Principal, params bucektApi.GetBucketQuotaParams) (*models.BucketQuota, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
quota, err := getBucketQuota(params.HTTPRequest.Context(), &adminClient, &params.Name)
quota, err := getBucketQuota(ctx, &adminClient, &params.Name)
if err != nil {
return nil, &models.Error{
Code: 500,
Message: swag.String(err.Error()),
}
return nil, ErrorWithContext(ctx, err)
}
return quota, nil
}
func getBucketQuota(ctx context.Context, ac *AdminClient, bucket *string) (*models.BucketQuota, error) {
quota, err := ac.getBucketQuota(ctx, *bucket)
if err != nil {
return nil, err
}
return &models.BucketQuota{
Quota: int64(quota.Quota),
Type: string(quota.Type),

View File

@@ -47,7 +47,7 @@ import (
func registerBucketsHandlers(api *operations.ConsoleAPI) {
// list buckets
api.BucketListBucketsHandler = bucketApi.ListBucketsHandlerFunc(func(params bucketApi.ListBucketsParams, session *models.Principal) middleware.Responder {
listBucketsResponse, err := getListBucketsResponse(session)
listBucketsResponse, err := getListBucketsResponse(session, params)
if err != nil {
return bucketApi.NewListBucketsDefault(int(err.Code)).WithPayload(err)
}
@@ -55,7 +55,7 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
})
// make bucket
api.BucketMakeBucketHandler = bucketApi.MakeBucketHandlerFunc(func(params bucketApi.MakeBucketParams, session *models.Principal) middleware.Responder {
if err := getMakeBucketResponse(session, params.Body); err != nil {
if err := getMakeBucketResponse(session, params); err != nil {
return bucketApi.NewMakeBucketDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewMakeBucketCreated()
@@ -79,7 +79,7 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
})
// set bucket policy
api.BucketBucketSetPolicyHandler = bucketApi.BucketSetPolicyHandlerFunc(func(params bucketApi.BucketSetPolicyParams, session *models.Principal) middleware.Responder {
bucketSetPolicyResp, err := getBucketSetPolicyResponse(session, params.Name, params.Body)
bucketSetPolicyResp, err := getBucketSetPolicyResponse(session, params)
if err != nil {
return bucketApi.NewBucketSetPolicyDefault(int(err.Code)).WithPayload(err)
}
@@ -87,7 +87,7 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
})
// set bucket tags
api.BucketPutBucketTagsHandler = bucketApi.PutBucketTagsHandlerFunc(func(params bucketApi.PutBucketTagsParams, session *models.Principal) middleware.Responder {
err := getPutBucketTagsResponse(session, params.BucketName, params.Body)
err := getPutBucketTagsResponse(session, params)
if err != nil {
return bucketApi.NewPutBucketTagsDefault(int(err.Code)).WithPayload(err)
}
@@ -95,15 +95,15 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
})
// get bucket versioning
api.BucketGetBucketVersioningHandler = bucketApi.GetBucketVersioningHandlerFunc(func(params bucketApi.GetBucketVersioningParams, session *models.Principal) middleware.Responder {
getBucketVersioning, err := getBucketVersionedResponse(session, params.BucketName)
getBucketVersioning, err := getBucketVersionedResponse(session, params)
if err != nil {
return bucketApi.NewGetBucketVersioningDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return bucketApi.NewGetBucketVersioningDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewGetBucketVersioningOK().WithPayload(getBucketVersioning)
})
// update bucket versioning
api.BucketSetBucketVersioningHandler = bucketApi.SetBucketVersioningHandlerFunc(func(params bucketApi.SetBucketVersioningParams, session *models.Principal) middleware.Responder {
err := setBucketVersioningResponse(session, params.BucketName, &params)
err := setBucketVersioningResponse(session, params)
if err != nil {
return bucketApi.NewSetBucketVersioningDefault(500).WithPayload(err)
}
@@ -111,17 +111,17 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
})
// get bucket replication
api.BucketGetBucketReplicationHandler = bucketApi.GetBucketReplicationHandlerFunc(func(params bucketApi.GetBucketReplicationParams, session *models.Principal) middleware.Responder {
getBucketReplication, err := getBucketReplicationResponse(session, params.BucketName)
getBucketReplication, err := getBucketReplicationResponse(session, params)
if err != nil {
return bucketApi.NewGetBucketReplicationDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return bucketApi.NewGetBucketReplicationDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewGetBucketReplicationOK().WithPayload(getBucketReplication)
})
// get single bucket replication rule
api.BucketGetBucketReplicationRuleHandler = bucketApi.GetBucketReplicationRuleHandlerFunc(func(params bucketApi.GetBucketReplicationRuleParams, session *models.Principal) middleware.Responder {
getBucketReplicationRule, err := getBucketReplicationRuleResponse(session, params.BucketName, params.RuleID)
getBucketReplicationRule, err := getBucketReplicationRuleResponse(session, params)
if err != nil {
return bucketApi.NewGetBucketReplicationRuleDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return bucketApi.NewGetBucketReplicationRuleDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewGetBucketReplicationRuleOK().WithPayload(getBucketReplicationRule)
})
@@ -157,7 +157,7 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
})
// get bucket retention config
api.BucketGetBucketRetentionConfigHandler = bucketApi.GetBucketRetentionConfigHandlerFunc(func(params bucketApi.GetBucketRetentionConfigParams, session *models.Principal) middleware.Responder {
response, err := getBucketRetentionConfigResponse(session, params.BucketName)
response, err := getBucketRetentionConfigResponse(session, params)
if err != nil {
return bucketApi.NewGetBucketRetentionConfigDefault(int(err.Code)).WithPayload(err)
}
@@ -165,9 +165,9 @@ func registerBucketsHandlers(api *operations.ConsoleAPI) {
})
// get bucket object locking status
api.BucketGetBucketObjectLockingStatusHandler = bucketApi.GetBucketObjectLockingStatusHandlerFunc(func(params bucketApi.GetBucketObjectLockingStatusParams, session *models.Principal) middleware.Responder {
getBucketObjectLockingStatus, err := getBucketObjectLockingResponse(session, params.BucketName)
getBucketObjectLockingStatus, err := getBucketObjectLockingResponse(session, params)
if err != nil {
return bucketApi.NewGetBucketObjectLockingStatusDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
return bucketApi.NewGetBucketObjectLockingStatusDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewGetBucketObjectLockingStatusOK().WithPayload(getBucketObjectLockingStatus)
})
@@ -192,17 +192,20 @@ const (
func doSetVersioning(client MCClient, state VersionState) error {
err := client.setVersioning(context.Background(), string(state))
if err != nil {
LogError("error setting versioning for bucket: %s", err.Cause)
return err.Cause
}
return nil
}
func setBucketVersioningResponse(session *models.Principal, bucketName string, params *bucketApi.SetBucketVersioningParams) *models.Error {
func setBucketVersioningResponse(session *models.Principal, params bucketApi.SetBucketVersioningParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucketName := params.BucketName
s3Client, err := newS3BucketClient(session, bucketName, "")
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a mc S3Client interface implementation
// defining the client to be used
@@ -215,28 +218,27 @@ func setBucketVersioningResponse(session *models.Principal, bucketName string, p
}
if err := doSetVersioning(amcClient, versioningState); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, fmt.Errorf("error setting versioning for bucket: %s", err))
}
return nil
}
func getBucketReplicationResponse(session *models.Principal, bucketName string) (*models.BucketReplicationResponse, error) {
ctx, cancel := context.WithCancel(context.Background())
func getBucketReplicationResponse(session *models.Principal, params bucketApi.GetBucketReplicationParams) (*models.BucketReplicationResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
LogError("error creating MinIO Client: %v", err)
return nil, err
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
// we will tolerate this call failing
res, err := minioClient.getBucketReplication(ctx, bucketName)
res, err := minioClient.getBucketReplication(ctx, params.BucketName)
if err != nil {
LogError("error versioning bucket: %v", err)
ErrorWithContext(ctx, err)
}
var rules []*models.BucketReplicationRule
@@ -271,31 +273,30 @@ func getBucketReplicationResponse(session *models.Principal, bucketName string)
return bucketRResponse, nil
}
func getBucketReplicationRuleResponse(session *models.Principal, bucketName, ruleID string) (*models.BucketReplicationRule, error) {
ctx, cancel := context.WithCancel(context.Background())
func getBucketReplicationRuleResponse(session *models.Principal, params bucketApi.GetBucketReplicationRuleParams) (*models.BucketReplicationRule, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
LogError("error creating MinIO Client: %v", err)
return nil, err
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
replicationRules, err := minioClient.getBucketReplication(ctx, bucketName)
replicationRules, err := minioClient.getBucketReplication(ctx, params.BucketName)
if err != nil {
return nil, err
return nil, ErrorWithContext(ctx, err)
}
var foundRule replication.Rule
found := false
for i := range replicationRules.Rules {
if replicationRules.Rules[i].ID == ruleID {
if replicationRules.Rules[i].ID == params.RuleID {
foundRule = replicationRules.Rules[i]
found = true
break
@@ -303,7 +304,7 @@ func getBucketReplicationRuleResponse(session *models.Principal, bucketName, rul
}
if !found {
return nil, errors.New("no rule is set with this ID")
return nil, ErrorWithContext(ctx, errors.New("no rule is set with this ID"))
}
repDelMarkerStatus := false
@@ -340,14 +341,13 @@ func getBucketReplicationRuleResponse(session *models.Principal, bucketName, rul
return returnRule, nil
}
func getBucketVersionedResponse(session *models.Principal, bucketName string) (*models.BucketVersioningResponse, error) {
ctx, cancel := context.WithCancel(context.Background())
func getBucketVersionedResponse(session *models.Principal, params bucketApi.GetBucketVersioningParams) (*models.BucketVersioningResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
LogError("error creating MinIO Client: %v", err)
return nil, err
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
@@ -355,9 +355,9 @@ func getBucketVersionedResponse(session *models.Principal, bucketName string) (*
minioClient := minioClient{client: mClient}
// we will tolerate this call failing
res, err := minioClient.getBucketVersioning(ctx, bucketName)
res, err := minioClient.getBucketVersioning(ctx, params.BucketName)
if err != nil {
LogError("error versioning bucket: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
// serialize output
@@ -412,20 +412,20 @@ func getAccountBuckets(ctx context.Context, client MinioAdmin) ([]*models.Bucket
}
// getListBucketsResponse performs listBuckets() and serializes it to the handler's output
func getListBucketsResponse(session *models.Principal) (*models.ListBucketsResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getListBucketsResponse(session *models.Principal, params bucketApi.ListBucketsParams) (*models.ListBucketsResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
buckets, err := getAccountBuckets(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// serialize output
@@ -443,16 +443,17 @@ func makeBucket(ctx context.Context, client MinioClient, bucketName string, obje
}
// getMakeBucketResponse performs makeBucket() to create a bucket with its access policy
func getMakeBucketResponse(session *models.Principal, br *models.MakeBucketRequest) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getMakeBucketResponse(session *models.Principal, params bucketApi.MakeBucketParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// bucket request needed to proceed
br := params.Body
if br == nil {
return prepareError(errBucketBodyNotInRequest)
return ErrorWithContext(ctx, ErrBucketBodyNotInRequest)
}
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -464,15 +465,15 @@ func getMakeBucketResponse(session *models.Principal, br *models.MakeBucketReque
}
if err := makeBucket(ctx, minioClient, *br.Name, br.Locking); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// make sure to delete bucket if an error occurs after bucket was created
// make sure to delete bucket if an errors occurs after bucket was created
defer func() {
if err != nil {
LogError("error creating bucket: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error creating bucket: %v", err))
if err := removeBucket(minioClient, *br.Name); err != nil {
LogError("error removing bucket: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error removing bucket: %v", err))
}
}
}()
@@ -481,14 +482,14 @@ func getMakeBucketResponse(session *models.Principal, br *models.MakeBucketReque
if br.Versioning || br.Retention != nil {
s3Client, err := newS3BucketClient(session, *br.Name, "")
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a mc S3Client interface implementation
// defining the client to be used
amcClient := mcClient{client: s3Client}
if err = doSetVersioning(amcClient, VersionEnable); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, fmt.Errorf("error setting versioning for bucket: %s", err))
}
}
@@ -496,14 +497,14 @@ func getMakeBucketResponse(session *models.Principal, br *models.MakeBucketReque
if br.Quota != nil && br.Quota.Enabled != nil && *br.Quota.Enabled {
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// we will tolerate this call failing
if err := setBucketQuota(ctx, &adminClient, br.Name, br.Quota); err != nil {
LogError("error versioning bucket:", err)
ErrorWithContext(ctx, fmt.Errorf("error versioning bucket: %v", err))
}
}
@@ -511,7 +512,7 @@ func getMakeBucketResponse(session *models.Principal, br *models.MakeBucketReque
if br.Retention != nil {
err = setBucketRetentionConfig(ctx, minioClient, *br.Name, *br.Retention.Mode, *br.Retention.Unit, br.Retention.Validity)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
}
return nil
@@ -550,14 +551,14 @@ func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName s
// getBucketSetPolicyResponse calls setBucketAccessPolicy() to set a access policy to a bucket
// and returns the serialized output.
func getBucketSetPolicyResponse(session *models.Principal, bucketName string, req *models.SetBucketPolicyRequest) (*models.Bucket, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getBucketSetPolicyResponse(session *models.Principal, params bucketApi.BucketSetPolicyParams) (*models.Bucket, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
// get updated bucket details and return it
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -565,45 +566,49 @@ func getBucketSetPolicyResponse(session *models.Principal, bucketName string, re
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
bucketName := params.Name
req := params.Body
if err := setBucketAccessPolicy(ctx, minioClient, bucketName, *req.Access, req.Definition); err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// set bucket access policy
bucket, err := getBucketInfo(ctx, minioClient, adminClient, bucketName)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return bucket, nil
}
// putBucketTags sets tags for a bucket
func getPutBucketTagsResponse(session *models.Principal, bucketName string, req *models.PutBucketTagsRequest) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getPutBucketTagsResponse(session *models.Principal, params bucketApi.PutBucketTagsParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
req := params.Body
bucketName := params.BucketName
newTagSet, err := tags.NewTags(req.Tags, true)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
err = minioClient.SetBucketTagging(ctx, bucketName, newTagSet)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -615,20 +620,22 @@ func removeBucket(client MinioClient, bucketName string) error {
// getDeleteBucketResponse performs removeBucket() to delete a bucket
func getDeleteBucketResponse(session *models.Principal, params bucketApi.DeleteBucketParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
if params.Name == "" {
return prepareError(errBucketNameNotInRequest)
return ErrorWithContext(ctx, ErrBucketNameNotInRequest)
}
bucketName := params.Name
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
if err := removeBucket(minioClient, bucketName); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -638,8 +645,8 @@ func getBucketInfo(ctx context.Context, client MinioClient, adminClient MinioAdm
var bucketAccess models.BucketAccess
policyStr, err := client.getBucketPolicy(context.Background(), bucketName)
if err != nil {
// we can tolerate this error
LogError("error getting bucket policy: %v", err)
// we can tolerate this errors
ErrorWithContext(ctx, fmt.Errorf("error getting bucket policy: %v", err))
}
if policyStr == "" {
@@ -658,8 +665,8 @@ func getBucketInfo(ctx context.Context, client MinioClient, adminClient MinioAdm
}
bucketTags, err := client.GetBucketTagging(ctx, bucketName)
if err != nil {
// we can tolerate this error
LogError("error getting bucket tags: %v", err)
// we can tolerate this errors
ErrorWithContext(ctx, fmt.Errorf("error getting bucket tags: %v", err))
}
bucketDetails := &models.BucketDetails{}
if bucketTags != nil {
@@ -692,11 +699,11 @@ func getBucketInfo(ctx context.Context, client MinioClient, adminClient MinioAdm
// getBucketInfoResponse calls getBucketInfo() to get the bucket's info
func getBucketInfoResponse(session *models.Principal, params bucketApi.BucketInfoParams) (*models.Bucket, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -704,7 +711,7 @@ func getBucketInfoResponse(session *models.Principal, params bucketApi.BucketInf
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -712,7 +719,7 @@ func getBucketInfoResponse(session *models.Principal, params bucketApi.BucketInf
bucket, err := getBucketInfo(ctx, minioClient, adminClient, params.Name)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return bucket, nil
@@ -751,24 +758,24 @@ func enableBucketEncryption(ctx context.Context, client MinioClient, bucketName
case models.BucketEncryptionTypeSseDashS3:
config = sse.NewConfigurationSSES3()
default:
return errInvalidEncryptionAlgorithm
return ErrInvalidEncryptionAlgorithm
}
return client.setBucketEncryption(ctx, bucketName, config)
}
// enableBucketEncryptionResponse calls enableBucketEncryption() to create new encryption configuration for provided bucket name
func enableBucketEncryptionResponse(session *models.Principal, params bucketApi.EnableBucketEncryptionParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
if err := enableBucketEncryption(ctx, minioClient, params.BucketName, *params.Body.EncType, params.Body.KmsKeyID); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -780,17 +787,17 @@ func disableBucketEncryption(ctx context.Context, client MinioClient, bucketName
// disableBucketEncryptionResponse calls disableBucketEncryption()
func disableBucketEncryptionResponse(session *models.Principal, params bucketApi.DisableBucketEncryptionParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
if err := disableBucketEncryption(ctx, minioClient, params.BucketName); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -801,24 +808,24 @@ func getBucketEncryptionInfo(ctx context.Context, client MinioClient, bucketName
return nil, err
}
if len(bucketInfo.Rules) == 0 {
return nil, ErrorGeneric
return nil, ErrDefault
}
return &models.BucketEncryptionInfo{Algorithm: bucketInfo.Rules[0].Apply.SSEAlgorithm, KmsMasterKeyID: bucketInfo.Rules[0].Apply.KmsMasterKeyID}, nil
}
func getBucketEncryptionInfoResponse(session *models.Principal, params bucketApi.GetBucketEncryptionInfoParams) (*models.BucketEncryptionInfo, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
bucketInfo, err := getBucketEncryptionInfo(ctx, minioClient, params.BucketName)
if err != nil {
return nil, prepareError(errSSENotConfigured, err)
return nil, ErrorWithContext(ctx, ErrSSENotConfigured, err)
}
return bucketInfo, nil
}
@@ -854,18 +861,18 @@ func setBucketRetentionConfig(ctx context.Context, client MinioClient, bucketNam
}
func getSetBucketRetentionConfigResponse(session *models.Principal, params bucketApi.SetBucketRetentionConfigParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
err = setBucketRetentionConfig(ctx, minioClient, params.BucketName, *params.Body.Mode, *params.Body.Unit, params.Body.Validity)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -925,12 +932,13 @@ func getBucketRetentionConfig(ctx context.Context, client MinioClient, bucketNam
return config, nil
}
func getBucketRetentionConfigResponse(session *models.Principal, bucketName string) (*models.GetBucketRetentionConfig, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getBucketRetentionConfigResponse(session *models.Principal, params bucketApi.GetBucketRetentionConfigParams) (*models.GetBucketRetentionConfig, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucketName := params.BucketName
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
@@ -939,19 +947,18 @@ func getBucketRetentionConfigResponse(session *models.Principal, bucketName stri
config, err := getBucketRetentionConfig(ctx, minioClient, bucketName)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return config, nil
}
func getBucketObjectLockingResponse(session *models.Principal, bucketName string) (*models.BucketObLockingResponse, error) {
ctx, cancel := context.WithCancel(context.Background())
func getBucketObjectLockingResponse(session *models.Principal, params bucketApi.GetBucketObjectLockingStatusParams) (*models.BucketObLockingResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucketName := params.BucketName
mClient, err := newMinioClient(session)
if err != nil {
LogError("error creating MinIO Client: %v", err)
return nil, err
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating MinIO Client: %v", err))
}
// create a minioClient interface implementation
// defining the client to be used
@@ -965,7 +972,7 @@ func getBucketObjectLockingResponse(session *models.Principal, bucketName string
ObjectLockingEnabled: false,
}, nil
}
return nil, err
return nil, ErrorWithContext(ctx, err)
}
// serialize output
@@ -975,21 +982,20 @@ func getBucketObjectLockingResponse(session *models.Principal, bucketName string
}
func getBucketRewindResponse(session *models.Principal, params bucketApi.GetBucketRewindParams) (*models.RewindResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
var prefix = ""
if params.Prefix != nil {
encodedPrefix := SanitizeEncodedPrefix(*params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
if err != nil {
LogError("error creating S3Client: %v", err)
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, fmt.Errorf("error creating S3Client: %v", err))
}
// create a mc S3Client interface implementation
@@ -999,7 +1005,7 @@ func getBucketRewindResponse(session *models.Principal, params bucketApi.GetBuck
parsedDate, errDate := time.Parse(time.RFC3339, params.Date)
if errDate != nil {
return nil, prepareError(errDate)
return nil, ErrorWithContext(ctx, errDate)
}
var rewindItems []*models.RewindItem

View File

@@ -39,14 +39,14 @@ func registerBucketEventsHandlers(api *operations.ConsoleAPI) {
})
// create bucket event
api.BucketCreateBucketEventHandler = bucketApi.CreateBucketEventHandlerFunc(func(params bucketApi.CreateBucketEventParams, session *models.Principal) middleware.Responder {
if err := getCreateBucketEventsResponse(session, params.BucketName, params.Body); err != nil {
if err := getCreateBucketEventsResponse(session, params); err != nil {
return bucketApi.NewCreateBucketEventDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewCreateBucketEventCreated()
})
// delete bucket event
api.BucketDeleteBucketEventHandler = bucketApi.DeleteBucketEventHandlerFunc(func(params bucketApi.DeleteBucketEventParams, session *models.Principal) middleware.Responder {
if err := getDeleteBucketEventsResponse(session, params.BucketName, params.Arn, params.Body.Events, params.Body.Prefix, params.Body.Suffix); err != nil {
if err := getDeleteBucketEventsResponse(session, params); err != nil {
return bucketApi.NewDeleteBucketEventDefault(int(err.Code)).WithPayload(err)
}
return bucketApi.NewDeleteBucketEventNoContent()
@@ -125,9 +125,11 @@ func listBucketEvents(client MinioClient, bucketName string) ([]*models.Notifica
// getListBucketsResponse performs listBucketEvents() and serializes it to the handler's output
func getListBucketEventsResponse(session *models.Principal, params bucketApi.ListBucketEventsParams) (*models.ListBucketEventsResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -135,7 +137,7 @@ func getListBucketEventsResponse(session *models.Principal, params bucketApi.Lis
bucketEvents, err := listBucketEvents(minioClient, params.BucketName)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// serialize output
listBucketsResponse := &models.ListBucketEventsResponse{
@@ -175,19 +177,21 @@ func createBucketEvent(ctx context.Context, client MCClient, arn string, notific
}
// getCreateBucketEventsResponse calls createBucketEvent to add a bucket event notification
func getCreateBucketEventsResponse(session *models.Principal, bucketName string, eventReq *models.BucketEventRequest) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getCreateBucketEventsResponse(session *models.Principal, params bucketApi.CreateBucketEventParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucketName := params.BucketName
eventReq := params.Body
s3Client, err := newS3BucketClient(session, bucketName, "")
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
err = createBucketEvent(ctx, mcClient, *eventReq.Configuration.Arn, eventReq.Configuration.Events, eventReq.Configuration.Prefix, eventReq.Configuration.Suffix, eventReq.IgnoreExisting)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -211,19 +215,24 @@ func joinNotificationEvents(events []models.NotificationEventType) string {
}
// getDeleteBucketEventsResponse calls deleteBucketEventNotification() to delete a bucket event notification
func getDeleteBucketEventsResponse(session *models.Principal, bucketName string, arn string, events []models.NotificationEventType, prefix, suffix *string) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getDeleteBucketEventsResponse(session *models.Principal, params bucketApi.DeleteBucketEventParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
bucketName := params.BucketName
arn := params.Arn
events := params.Body.Events
prefix := params.Body.Prefix
suffix := params.Body.Suffix
s3Client, err := newS3BucketClient(session, bucketName, "")
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
err = deleteBucketEventNotification(ctx, mcClient, arn, events, prefix, suffix)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}

View File

@@ -142,11 +142,11 @@ func getBucketLifecycle(ctx context.Context, client MinioClient, bucketName stri
// getBucketLifecycleResponse performs getBucketLifecycle() and serializes it to the handler's output
func getBucketLifecycleResponse(session *models.Principal, params bucketApi.GetBucketLifecycleParams) (*models.BucketLifecycleResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -154,7 +154,7 @@ func getBucketLifecycleResponse(session *models.Principal, params bucketApi.GetB
bucketEvents, err := getBucketLifecycle(ctx, minioClient, params.BucketName)
if err != nil {
return nil, prepareError(errBucketLifeCycleNotConfigured, err)
return nil, ErrorWithContext(ctx, ErrBucketLifeCycleNotConfigured, err)
}
return bucketEvents, nil
}
@@ -226,7 +226,7 @@ func addBucketLifecycle(ctx context.Context, client MinioClient, params bucketAp
}
} else {
// Non set, we return error
// Non set, we return errors
return errors.New("no valid configuration requested")
}
@@ -241,11 +241,11 @@ func addBucketLifecycle(ctx context.Context, client MinioClient, params bucketAp
// getAddBucketLifecycleResponse returns the response of adding a bucket lifecycle response
func getAddBucketLifecycleResponse(session *models.Principal, params bucketApi.AddBucketLifecycleParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -253,7 +253,7 @@ func getAddBucketLifecycleResponse(session *models.Principal, params bucketApi.A
err = addBucketLifecycle(ctx, minioClient, params)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
@@ -325,7 +325,7 @@ func editBucketLifecycle(ctx context.Context, client MinioClient, params bucketA
}
} else {
// Non set, we return error
// Non set, we return errors
return errors.New("no valid configuration requested")
}
@@ -340,11 +340,11 @@ func editBucketLifecycle(ctx context.Context, client MinioClient, params bucketA
// getEditBucketLifecycleRule returns the response of bucket lifecycle tier edit
func getEditBucketLifecycleRule(session *models.Principal, params bucketApi.UpdateBucketLifecycleParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -352,7 +352,7 @@ func getEditBucketLifecycleRule(session *models.Principal, params bucketApi.Upda
err = editBucketLifecycle(ctx, minioClient, params)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
@@ -394,11 +394,11 @@ func deleteBucketLifecycle(ctx context.Context, client MinioClient, params bucke
// getDeleteBucketLifecycleRule returns the response of bucket lifecycle tier delete
func getDeleteBucketLifecycleRule(session *models.Principal, params bucketApi.DeleteBucketLifecycleRuleParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -406,7 +406,7 @@ func getDeleteBucketLifecycleRule(session *models.Principal, params bucketApi.De
err = deleteBucketLifecycle(ctx, minioClient, params)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
@@ -480,11 +480,11 @@ func addMultiBucketLifecycle(ctx context.Context, client MinioClient, params buc
// getAddMultiBucketLifecycleResponse returns the response of multibucket lifecycle assignment
func getAddMultiBucketLifecycleResponse(session *models.Principal, params bucketApi.AddMultiBucketLifecycleParams) (*models.MultiLifecycleResult, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used

View File

@@ -143,18 +143,17 @@ func TestMakeBucket(t *testing.T) {
// mock minIO client
minClient := minioClientMock{}
function := "makeBucket()"
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
// Test-1: makeBucket() create a bucket
// mock function response from makeBucketWithContext(ctx)
minioMakeBucketWithContextMock = func(ctx context.Context, bucketName, location string, objectLock bool) error {
return nil
}
if err := makeBucket(ctx, minClient, "bucktest1", true); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error())
}
// Test-2 makeBucket() make sure errors are handled correctly when error on MakeBucketWithContext
// Test-2 makeBucket() make sure errors are handled correctly when errors on MakeBucketWithContext
minioMakeBucketWithContextMock = func(ctx context.Context, bucketName, location string, objectLock bool) error {
return errors.New("error")
}
@@ -175,10 +174,10 @@ func TestDeleteBucket(t *testing.T) {
return nil
}
if err := removeBucket(minClient, "bucktest1"); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error())
}
// Test-2: removeBucket() make sure errors are handled correctly when error on DeleteBucket()
// Test-2: removeBucket() make sure errors are handled correctly when errors on DeleteBucket()
// mock function response from removeBucket(bucketName)
minioRemoveBucketMock = func(bucketName string) error {
return errors.New("error")
@@ -193,8 +192,7 @@ func TestBucketInfo(t *testing.T) {
// mock minIO client
minClient := minioClientMock{}
adminClient := adminClientMock{}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
function := "getBucketInfo()"
// Test-1: getBucketInfo() get a bucket with PRIVATE access
@@ -248,7 +246,7 @@ func TestBucketInfo(t *testing.T) {
bucketInfo, err := getBucketInfo(ctx, minClient, adminClient, bucketToSet)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error())
}
assert.Equal(outputExpected.Name, bucketInfo.Name)
assert.Equal(outputExpected.Access, bucketInfo.Access)
@@ -272,7 +270,7 @@ func TestBucketInfo(t *testing.T) {
}
bucketInfo, err = getBucketInfo(ctx, minClient, adminClient, bucketToSet)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error())
}
assert.Equal(outputExpected.Name, bucketInfo.Name)
assert.Equal(outputExpected.Access, bucketInfo.Access)
@@ -296,7 +294,7 @@ func TestBucketInfo(t *testing.T) {
}
bucketInfo, err = getBucketInfo(ctx, minClient, adminClient, bucketToSet)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error())
}
assert.Equal(outputExpected.Name, bucketInfo.Name)
assert.Equal(outputExpected.Access, bucketInfo.Access)
@@ -304,7 +302,7 @@ func TestBucketInfo(t *testing.T) {
assert.Equal(outputExpected.Size, bucketInfo.Size)
assert.Equal(outputExpected.Objects, bucketInfo.Objects)
// Test-4: getBucketInfo() returns an error while parsing invalid policy
// Test-4: getBucketInfo() returns an errors while parsing invalid policy
mockPolicy = "policyinvalid"
minioGetBucketPolicyMock = func(bucketName string) (string, error) {
return mockPolicy, nil
@@ -321,14 +319,13 @@ func TestBucketInfo(t *testing.T) {
assert.Equal("invalid character 'p' looking for beginning of value", err.Error())
}
// Test-4: getBucketInfo() handle GetBucketPolicy error correctly
// Test-4: getBucketInfo() handle GetBucketPolicy errors correctly
// Test removed since we can tolerate this scenario now
}
func TestSetBucketAccess(t *testing.T) {
assert := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
// mock minIO client
minClient := minioClientMock{}
@@ -339,30 +336,30 @@ func TestSetBucketAccess(t *testing.T) {
return nil
}
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC, ""); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error())
}
// Test-2: setBucketAccessPolicy() set private access
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPRIVATE, ""); err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
t.Errorf("Failed on %s:, errors occurred: %s", function, err.Error())
}
// Test-3: setBucketAccessPolicy() set invalid access, expected error
// Test-3: setBucketAccessPolicy() set invalid access, expected errors
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "other", ""); assert.Error(err) {
assert.Equal("access: `other` not supported", err.Error())
}
// Test-4: setBucketAccessPolicy() set access on empty bucket name, expected error
// Test-4: setBucketAccessPolicy() set access on empty bucket name, expected errors
if err := setBucketAccessPolicy(ctx, minClient, "", models.BucketAccessPRIVATE, ""); assert.Error(err) {
assert.Equal("error: bucket name not present", err.Error())
}
// Test-5: setBucketAccessPolicy() set empty access on bucket, expected error
// Test-5: setBucketAccessPolicy() set empty access on bucket, expected errors
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "", ""); assert.Error(err) {
assert.Equal("error: bucket access not present", err.Error())
}
// Test-5: setBucketAccessPolicy() handle error on setPolicy call
// Test-5: setBucketAccessPolicy() handle errors on setPolicy call
minioSetBucketPolicyWithContextMock = func(ctx context.Context, bucketName, policy string) error {
return errors.New("error")
}
@@ -373,8 +370,7 @@ func TestSetBucketAccess(t *testing.T) {
}
func Test_enableBucketEncryption(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
minClient := minioClientMock{}
type args struct {
ctx context.Context
@@ -410,7 +406,7 @@ func Test_enableBucketEncryption(t *testing.T) {
bucketName: "test",
encryptionType: "sse-s3",
mockEnableBucketEncryptionFunc: func(ctx context.Context, bucketName string, config *sse.Configuration) error {
return errorGenericInvalidSession
return ErrInvalidSession
},
},
wantErr: true,
@@ -420,15 +416,14 @@ func Test_enableBucketEncryption(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
minioSetBucketEncryptionMock = tt.args.mockEnableBucketEncryptionFunc
if err := enableBucketEncryption(tt.args.ctx, tt.args.client, tt.args.bucketName, tt.args.encryptionType, tt.args.kmsKeyID); (err != nil) != tt.wantErr {
t.Errorf("enableBucketEncryption() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("enableBucketEncryption() errors = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_disableBucketEncryption(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
minClient := minioClientMock{}
type args struct {
ctx context.Context
@@ -460,7 +455,7 @@ func Test_disableBucketEncryption(t *testing.T) {
client: minClient,
bucketName: "test",
mockBucketDisableFunc: func(ctx context.Context, bucketName string) error {
return ErrorGeneric
return ErrDefault
},
},
wantErr: true,
@@ -470,15 +465,14 @@ func Test_disableBucketEncryption(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
minioRemoveBucketEncryptionMock = tt.args.mockBucketDisableFunc
if err := disableBucketEncryption(tt.args.ctx, tt.args.client, tt.args.bucketName); (err != nil) != tt.wantErr {
t.Errorf("disableBucketEncryption() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("disableBucketEncryption() errors = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_getBucketEncryptionInfo(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
minClient := minioClientMock{}
type args struct {
ctx context.Context
@@ -535,7 +529,7 @@ func Test_getBucketEncryptionInfo(t *testing.T) {
client: minClient,
bucketName: "test",
mockBucketEncryptionGet: func(ctx context.Context, bucketName string) (*sse.Configuration, error) {
return nil, errSSENotConfigured
return nil, ErrSSENotConfigured
},
},
wantErr: true,
@@ -546,7 +540,7 @@ func Test_getBucketEncryptionInfo(t *testing.T) {
minioGetBucketEncryptionMock = tt.args.mockBucketEncryptionGet
got, err := getBucketEncryptionInfo(tt.args.ctx, tt.args.client, tt.args.bucketName)
if (err != nil) != tt.wantErr {
t.Errorf("getBucketEncryptionInfo() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("getBucketEncryptionInfo() errors = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
@@ -558,8 +552,7 @@ func Test_getBucketEncryptionInfo(t *testing.T) {
func Test_SetBucketRetentionConfig(t *testing.T) {
assert := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
minClient := minioClientMock{}
type args struct {
ctx context.Context
@@ -651,7 +644,7 @@ func Test_SetBucketRetentionConfig(t *testing.T) {
expectedError: errors.New("invalid retention unit"),
},
{
name: "Handle error on objec lock function",
name: "Handle errors on objec lock function",
args: args{
ctx: ctx,
client: minClient,
@@ -672,9 +665,9 @@ func Test_SetBucketRetentionConfig(t *testing.T) {
err := setBucketRetentionConfig(tt.args.ctx, tt.args.client, tt.args.bucketName, tt.args.mode, tt.args.unit, tt.args.validity)
if tt.expectedError != nil {
fmt.Println(t.Name())
assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("setObjectRetention() error: `%s`, wantErr: `%s`", err, tt.expectedError))
assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("setObjectRetention() errors: `%s`, wantErr: `%s`", err, tt.expectedError))
} else {
assert.Nil(err, fmt.Sprintf("setBucketRetentionConfig() error: %v, wantErr: %v", err, tt.expectedError))
assert.Nil(err, fmt.Sprintf("setBucketRetentionConfig() errors: %v, wantErr: %v", err, tt.expectedError))
}
})
}
@@ -682,8 +675,7 @@ func Test_SetBucketRetentionConfig(t *testing.T) {
func Test_GetBucketRetentionConfig(t *testing.T) {
assert := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
minClient := minioClientMock{}
type args struct {
ctx context.Context
@@ -751,7 +743,7 @@ func Test_GetBucketRetentionConfig(t *testing.T) {
{
// Description: if minio return NoSuchObjectLockConfiguration, don't panic
// and return empty response
name: "Handle NoLock Config error",
name: "Handle NoLock Config errors",
args: args{
ctx: ctx,
client: minClient,
@@ -767,7 +759,7 @@ func Test_GetBucketRetentionConfig(t *testing.T) {
expectedError: nil,
},
{
name: "Return error on invalid mode",
name: "Return errors on invalid mode",
args: args{
ctx: ctx,
client: minClient,
@@ -782,7 +774,7 @@ func Test_GetBucketRetentionConfig(t *testing.T) {
expectedError: errors.New("invalid retention mode"),
},
{
name: "Return error on invalid unit",
name: "Return errors on invalid unit",
args: args{
ctx: ctx,
client: minClient,
@@ -805,9 +797,9 @@ func Test_GetBucketRetentionConfig(t *testing.T) {
if tt.expectedError != nil {
fmt.Println(t.Name())
assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("getBucketRetentionConfig() error: `%s`, wantErr: `%s`", err, tt.expectedError))
assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("getBucketRetentionConfig() errors: `%s`, wantErr: `%s`", err, tt.expectedError))
} else {
assert.Nil(err, fmt.Sprintf("getBucketRetentionConfig() error: %v, wantErr: %v", err, tt.expectedError))
assert.Nil(err, fmt.Sprintf("getBucketRetentionConfig() errors: %v, wantErr: %v", err, tt.expectedError))
if !reflect.DeepEqual(resp, tt.expectedResponse) {
t.Errorf("getBucketRetentionConfig() resp: %v, expectedResponse: %v", resp, tt.expectedResponse)
return
@@ -819,8 +811,7 @@ func Test_GetBucketRetentionConfig(t *testing.T) {
func Test_SetBucketVersioning(t *testing.T) {
assert := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
errorMsg := "Error Message"
minClient := s3ClientMock{}
type args struct {
@@ -874,7 +865,7 @@ func Test_SetBucketVersioning(t *testing.T) {
if tt.expectedError != nil {
fmt.Println(t.Name())
assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("getBucketRetentionConfig() error: `%s`, wantErr: `%s`", err, tt.expectedError))
assert.Equal(tt.expectedError.Error(), err.Error(), fmt.Sprintf("getBucketRetentionConfig() errors: `%s`, wantErr: `%s`", err, tt.expectedError))
}
})
}
@@ -1166,7 +1157,7 @@ func Test_getAccountBuckets(t *testing.T) {
args: args{
ctx: context.Background(),
mockBucketList: madmin.AccountInfo{},
mockError: errors.New("some error"),
mockError: errors.New("some errors"),
},
want: []*models.Bucket{},
wantErr: assert.Error,

View File

@@ -17,6 +17,7 @@
package restapi
import (
"context"
"encoding/json"
"fmt"
"net/http"
@@ -42,7 +43,9 @@ func registerLogSearchHandlers(api *operations.ConsoleAPI) {
// getLogSearchResponse performs a query to Log Search if Enabled
func getLogSearchResponse(session *models.Principal, params logApi.LogSearchParams) (*models.LogSearchResponse, *models.Error) {
sessionResp, err := getSessionResponse(session)
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
sessionResp, err := getSessionResponse(ctx, session)
if err != nil {
return nil, err
}
@@ -80,31 +83,28 @@ func getLogSearchResponse(session *models.Principal, params logApi.LogSearchPara
endpoint = fmt.Sprintf("%s&pageSize=%d", endpoint, *params.PageSize)
endpoint = fmt.Sprintf("%s&pageNo=%d", endpoint, *params.PageNo)
return logSearch(endpoint)
response, errLogSearch := logSearch(endpoint)
if errLogSearch != nil {
return nil, ErrorWithContext(ctx, errLogSearch)
}
return response, nil
}
func logSearch(endpoint string) (*models.LogSearchResponse, *models.Error) {
func logSearch(endpoint string) (*models.LogSearchResponse, error) {
httpClnt := GetConsoleHTTPClient()
resp, err := httpClnt.Get(endpoint)
if err != nil {
return nil, &models.Error{
Code: int32(500),
Message: swag.String("Log Search API not available."),
DetailedMessage: swag.String("The Log Search API cannot be reached. Please review the URL and try again."),
}
return nil, fmt.Errorf("the Log Search API cannot be reached. Please review the URL and try again %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, &models.Error{
Code: int32(resp.StatusCode),
Message: swag.String(fmt.Sprintf("error retrieving logs: %s", http.StatusText(resp.StatusCode))),
}
return nil, fmt.Errorf("error retrieving logs: %s", http.StatusText(resp.StatusCode))
}
var results []map[string]interface{}
if err = json.NewDecoder(resp.Body).Decode(&results); err != nil {
return nil, prepareError(err)
return nil, err
}
return &models.LogSearchResponse{

View File

@@ -17,16 +17,14 @@
package restapi
import (
"crypto/sha256"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"testing"
"github.com/go-openapi/swag"
"github.com/minio/console/models"
asrt "github.com/stretchr/testify/assert"
)
func TestLogSearch(t *testing.T) {
@@ -41,7 +39,7 @@ func TestLogSearch(t *testing.T) {
"request_id": "16595A4E30CCFE79",
"user_agent": "MinIO (linux; amd64) madmin-go/0.0.1",
"response_status": "OK",
"response_status_code": 200,
"response_status_code": float64(200),
"request_content_length": nil,
"response_content_length": nil,
}, {
@@ -54,13 +52,12 @@ func TestLogSearch(t *testing.T) {
"request_id": "16595A4DA906FBA9",
"user_agent": "Go-http-client/1.1",
"response_status": "OK",
"response_status_code": 200,
"response_status_code": float64(200),
"request_content_length": nil,
"response_content_length": nil,
},
}
assert := asrt.New(t)
type args struct {
apiResponse string
apiResponseCode int
@@ -76,7 +73,7 @@ func TestLogSearch(t *testing.T) {
name string
args args
expectedResponse *models.LogSearchResponse
expectedError *models.Error
wantErr bool
}{
{
name: "200 Success response",
@@ -85,7 +82,7 @@ func TestLogSearch(t *testing.T) {
apiResponseCode: 200,
},
expectedResponse: successfulResponse,
expectedError: nil,
wantErr: false,
},
{
name: "500 unsuccessful response",
@@ -94,10 +91,7 @@ func TestLogSearch(t *testing.T) {
apiResponseCode: 500,
},
expectedResponse: nil,
expectedError: &models.Error{
Code: 500,
Message: swag.String(fmt.Sprintf("error retrieving logs: %s", http.StatusText(500))),
},
wantErr: true,
},
}
@@ -112,27 +106,33 @@ func TestLogSearch(t *testing.T) {
resp, err := logSearch(testRequest.URL)
if tt.expectedError != nil {
assert.Equal(tt.expectedError.Code, err.Code, fmt.Sprintf("logSearch() error code: `%v`, wantErr: `%v`", err.Code, tt.expectedError))
assert.Equal(tt.expectedError.Message, err.Message, fmt.Sprintf("logSearch() error message: `%v`, wantErr: `%v`", err.Message, tt.expectedError))
} else {
assert.Nil(err, fmt.Sprintf("logSearch() error: %v, wantErr: %v", err, tt.expectedError))
buf1, err1 := tt.expectedResponse.MarshalBinary()
buf2, err2 := resp.MarshalBinary()
if err1 != err2 {
t.Errorf("logSearch() resp: %v, expectedResponse: %v", resp, tt.expectedResponse)
return
}
h := sha256.New()
h.Write(buf1)
checkSum1 := fmt.Sprintf("%x\n", h.Sum(nil))
h.Reset()
h.Write(buf2)
checkSum2 := fmt.Sprintf("%x\n", h.Sum(nil))
if checkSum1 != checkSum2 {
t.Errorf("logSearch() resp: %v, expectedResponse: %v", resp, tt.expectedResponse)
}
if (err != nil) != tt.wantErr {
t.Errorf("logSearch() error = %v, wantErr %v", err, tt.wantErr)
}
if !reflect.DeepEqual(resp, tt.expectedResponse) {
t.Errorf("\ngot: %d \nwant: %d", resp, tt.expectedResponse)
}
//if tt.wantErr {
// assert.Equal(tt.expectedError.Code, err.Code, fmt.Sprintf("logSearch() error code: `%v`, wantErr: `%v`", err.Code, tt.expectedError))
// assert.Equal(tt.expectedError.Message, err.Message, fmt.Sprintf("logSearch() error message: `%v`, wantErr: `%v`", err.Message, tt.expectedError))
//} else {
// assert.Nil(err, fmt.Sprintf("logSearch() error: %v, wantErr: %v", err, tt.expectedError))
// buf1, err1 := tt.expectedResponse.MarshalBinary()
// buf2, err2 := resp.MarshalBinary()
// if err1 != err2 {
// t.Errorf("logSearch() resp: %v, expectedResponse: %v", resp, tt.expectedResponse)
// return
// }
// h := sha256.New()
// h.Write(buf1)
// checkSum1 := fmt.Sprintf("%x\n", h.Sum(nil))
// h.Reset()
// h.Write(buf2)
// checkSum2 := fmt.Sprintf("%x\n", h.Sum(nil))
// if checkSum1 != checkSum2 {
// t.Errorf("logSearch() resp: %v, expectedResponse: %v", resp, tt.expectedResponse)
// }
//}
})
}
}

View File

@@ -36,7 +36,7 @@ import (
func registerLoginHandlers(api *operations.ConsoleAPI) {
// GET login strategy
api.AuthLoginDetailHandler = authApi.LoginDetailHandlerFunc(func(params authApi.LoginDetailParams) middleware.Responder {
loginDetails, err := getLoginDetailsResponse(params.HTTPRequest)
loginDetails, err := getLoginDetailsResponse(params)
if err != nil {
return authApi.NewLoginDetailDefault(int(err.Code)).WithPayload(err)
}
@@ -44,7 +44,7 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
})
// POST login using user credentials
api.AuthLoginHandler = authApi.LoginHandlerFunc(func(params authApi.LoginParams) middleware.Responder {
loginResponse, err := getLoginResponse(params.Body)
loginResponse, err := getLoginResponse(params)
if err != nil {
return authApi.NewLoginDefault(int(err.Code)).WithPayload(err)
}
@@ -57,7 +57,7 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
})
// POST login using external IDP
api.AuthLoginOauth2AuthHandler = authApi.LoginOauth2AuthHandlerFunc(func(params authApi.LoginOauth2AuthParams) middleware.Responder {
loginResponse, err := getLoginOauth2AuthResponse(params.HTTPRequest, params.Body)
loginResponse, err := getLoginOauth2AuthResponse(params)
if err != nil {
return authApi.NewLoginOauth2AuthDefault(int(err.Code)).WithPayload(err)
}
@@ -82,7 +82,7 @@ func login(credentials ConsoleCredentialsI, sessionFeatures *auth.SessionFeature
token, err := auth.NewEncryptedTokenForClient(&tokens, credentials.GetAccountAccessKey(), sessionFeatures)
if err != nil {
LogError("error authenticating user: %v", err)
return nil, errInvalidCredentials
return nil, ErrInvalidLogin
}
return &token, nil
}
@@ -109,11 +109,14 @@ func getConsoleCredentials(accessKey, secretKey string) (*ConsoleCredentials, er
}
// getLoginResponse performs login() and serializes it to the handler's output
func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, *models.Error) {
func getLoginResponse(params authApi.LoginParams) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
lr := params.Body
// prepare console credentials
consoleCreds, err := getConsoleCredentials(*lr.AccessKey, *lr.SecretKey)
if err != nil {
return nil, prepareError(err, errInvalidCredentials, err)
return nil, ErrorWithContext(ctx, err, ErrInvalidLogin, err)
}
sf := &auth.SessionFeatures{}
if lr.Features != nil {
@@ -121,7 +124,7 @@ func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, *models.E
}
sessionID, err := login(consoleCreds, sf)
if err != nil {
return nil, prepareError(err, errInvalidCredentials, err)
return nil, ErrorWithContext(ctx, err, ErrInvalidLogin, err)
}
// serialize output
loginResponse := &models.LoginResponse{
@@ -131,16 +134,18 @@ func getLoginResponse(lr *models.LoginRequest) (*models.LoginResponse, *models.E
}
// getLoginDetailsResponse returns information regarding the Console authentication mechanism.
func getLoginDetailsResponse(r *http.Request) (*models.LoginDetails, *models.Error) {
func getLoginDetailsResponse(params authApi.LoginDetailParams) (*models.LoginDetails, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
loginStrategy := models.LoginDetailsLoginStrategyForm
redirectURL := ""
r := params.HTTPRequest
if oauth2.IsIDPEnabled() {
loginStrategy = models.LoginDetailsLoginStrategyRedirect
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(nil, r, GetConsoleHTTPClient())
if err != nil {
return nil, prepareError(err, errOauth2Provider)
return nil, ErrorWithContext(ctx, err, ErrOauth2Provider)
}
// Validate user against IDP
identityProvider := &auth.IdentityProvider{Client: oauth2Client}
@@ -159,26 +164,28 @@ func verifyUserAgainstIDP(ctx context.Context, provider auth.IdentityProviderI,
userCredentials, err := provider.VerifyIdentity(ctx, code, state)
if err != nil {
LogError("error validating user identity against idp: %v", err)
return nil, errInvalidCredentials
return nil, ErrInvalidLogin
}
return userCredentials, nil
}
func getLoginOauth2AuthResponse(r *http.Request, lr *models.LoginOauth2AuthRequest) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getLoginOauth2AuthResponse(params authApi.LoginOauth2AuthParams) (*models.LoginResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
r := params.HTTPRequest
lr := params.Body
if oauth2.IsIDPEnabled() {
// initialize new oauth2 client
oauth2Client, err := oauth2.NewOauth2ProviderClient(nil, r, GetConsoleHTTPClient())
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// initialize new identity provider
identityProvider := auth.IdentityProvider{Client: oauth2Client}
// Validate user against IDP
userCredentials, err := verifyUserAgainstIDP(ctx, identityProvider, *lr.Code, *lr.State)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// initialize admin client
// login user against console and generate session token
@@ -187,7 +194,7 @@ func getLoginOauth2AuthResponse(r *http.Request, lr *models.LoginOauth2AuthReque
AccountAccessKey: "",
}, nil)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// serialize output
loginResponse := &models.LoginResponse{
@@ -195,5 +202,5 @@ func getLoginOauth2AuthResponse(r *http.Request, lr *models.LoginOauth2AuthReque
}
return loginResponse, nil
}
return nil, prepareError(ErrorGeneric)
return nil, ErrorWithContext(ctx, ErrDefault)
}

View File

@@ -46,7 +46,7 @@ func logout(credentials ConsoleCredentialsI) {
credentials.Expire()
}
// getLogoutResponse performs logout() and returns nil or error
// getLogoutResponse performs logout() and returns nil or errors
func getLogoutResponse(session *models.Principal) {
creds := getConsoleCredentialsFromSession(session)
credentials := ConsoleCredentials{ConsoleCredentials: creds}

View File

@@ -79,13 +79,13 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) {
// download object
api.ObjectDownloadObjectHandler = objectApi.DownloadObjectHandlerFunc(func(params objectApi.DownloadObjectParams, session *models.Principal) middleware.Responder {
isFolder := false
ctx := params.HTTPRequest.Context()
var prefix string
if params.Prefix != "" {
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return objectApi.NewDownloadObjectDefault(int(400)).WithPayload(prepareError(err))
return objectApi.NewDownloadObjectDefault(400).WithPayload(ErrorWithContext(ctx, err))
}
prefix = string(decodedPrefix)
}
@@ -173,6 +173,8 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) {
// getListObjectsResponse returns a list of objects
func getListObjectsResponse(session *models.Principal, params objectApi.ListObjectsParams) (*models.ListObjectsResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
var prefix string
var recursive bool
var withVersions bool
@@ -181,7 +183,7 @@ func getListObjectsResponse(session *models.Principal, params objectApi.ListObje
encodedPrefix := SanitizeEncodedPrefix(*params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
@@ -196,19 +198,19 @@ func getListObjectsResponse(session *models.Principal, params objectApi.ListObje
}
// bucket request needed to proceed
if params.BucketName == "" {
return nil, prepareError(errBucketNameNotInRequest)
return nil, ErrorWithContext(ctx, ErrBucketNameNotInRequest)
}
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
objs, err := listBucketObjects(params.HTTPRequest.Context(), minioClient, params.BucketName, prefix, recursive, withVersions, withMetadata)
objs, err := listBucketObjects(ctx, minioClient, params.BucketName, prefix, recursive, withVersions, withMetadata)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
resp := &models.ListObjectsResponse{
@@ -254,7 +256,7 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin
if err != nil {
errResp := minio.ToErrorResponse(probe.NewError(err).ToGoError())
if errResp.Code != "InvalidRequest" && errResp.Code != "NoSuchObjectLockConfiguration" {
LogError("error getting legal hold status for %s : %v", lsObj.VersionID, err)
ErrorWithContext(ctx, fmt.Errorf("error getting legal hold status for %s : %v", lsObj.VersionID, err))
}
} else {
if legalHoldStatus != nil {
@@ -266,7 +268,7 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin
if err != nil {
errResp := minio.ToErrorResponse(probe.NewError(err).ToGoError())
if errResp.Code != "InvalidRequest" && errResp.Code != "NoSuchObjectLockConfiguration" {
LogError("error getting retention status for %s : %v", lsObj.VersionID, err)
ErrorWithContext(ctx, fmt.Errorf("error getting retention status for %s : %v", lsObj.VersionID, err))
}
} else {
if retention != nil && retUntilDate != nil {
@@ -277,7 +279,7 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin
}
tags, err := client.getObjectTagging(ctx, bucketName, lsObj.Key, minio.GetObjectTaggingOptions{VersionID: lsObj.VersionID})
if err != nil {
LogError("error getting object tags for %s : %v", lsObj.VersionID, err)
ErrorWithContext(ctx, fmt.Errorf("error getting object tags for %s : %v", lsObj.VersionID, err))
} else {
obj.Tags = tags.ToMap()
}
@@ -367,18 +369,17 @@ func parseRange(s string, size int64) ([]httpRange, error) {
}
func getDownloadObjectResponse(session *models.Principal, params objectApi.DownloadObjectParams) (middleware.Responder, *models.Error) {
ctx := context.Background()
ctx := params.HTTPRequest.Context()
var prefix string
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
if params.Prefix != "" {
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
@@ -391,7 +392,7 @@ func getDownloadObjectResponse(session *models.Principal, params objectApi.Downl
resp, err := mClient.GetObject(ctx, params.BucketName, prefix, opts)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return middleware.ResponderFunc(func(rw http.ResponseWriter, _ runtime.Producer) {
@@ -414,17 +415,15 @@ func getDownloadObjectResponse(session *models.Principal, params objectApi.Downl
stat, err := resp.Stat()
if err != nil {
minErr := minio.ToErrorResponse(err)
// non-200 means we requested something wrong
rw.WriteHeader(minErr.StatusCode)
LogError("Failed to get Stat() response from server for %s (version %s): %v", prefix, opts.VersionID, minErr.Error())
ErrorWithContext(ctx, fmt.Errorf("Failed to get Stat() response from server for %s (version %s): %v", prefix, opts.VersionID, minErr.Error()))
return
}
// if we are getting a Range Request (video) handle that specially
ranges, err := parseRange(params.HTTPRequest.Header.Get("Range"), stat.Size)
if err != nil {
LogError("Unable to parse range header input %s: %v", params.HTTPRequest.Header.Get("Range"), err)
ErrorWithContext(ctx, fmt.Errorf("Unable to parse range header input %s: %v", params.HTTPRequest.Header.Get("Range"), err))
rw.WriteHeader(400)
return
}
@@ -454,7 +453,7 @@ func getDownloadObjectResponse(session *models.Principal, params objectApi.Downl
_, err = resp.Seek(start, io.SeekStart)
if err != nil {
LogError("Unable to seek at offset %d: %v", start, err)
ErrorWithContext(ctx, fmt.Errorf("Unable to seek at offset %d: %v", start, err))
rw.WriteHeader(400)
return
}
@@ -468,21 +467,21 @@ func getDownloadObjectResponse(session *models.Principal, params objectApi.Downl
rw.Header().Set("Content-Length", fmt.Sprintf("%d", length))
_, err = io.Copy(rw, io.LimitReader(resp, length))
if err != nil {
LogError("Unable to write all data to client: %v", err)
ErrorWithContext(ctx, fmt.Errorf("Unable to write all data to client: %v", err))
return
}
}), nil
}
func getDownloadFolderResponse(session *models.Principal, params objectApi.DownloadObjectParams) (middleware.Responder, *models.Error) {
ctx := context.Background()
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
var prefix string
mClient, err := newMinioClient(session)
if params.Prefix != "" {
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
@@ -490,12 +489,12 @@ func getDownloadFolderResponse(session *models.Principal, params objectApi.Downl
folders := strings.Split(prefix, "/")
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
minioClient := minioClient{client: mClient}
objects, err := listBucketObjects(ctx, minioClient, params.BucketName, prefix, true, false, false)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
w := new(bytes.Buffer)
zipw := zip.NewWriter(w)
@@ -507,11 +506,11 @@ func getDownloadFolderResponse(session *models.Principal, params objectApi.Downl
name := folder + objects[i].Name[len(prefix)-1:]
object, err := mClient.GetObject(ctx, params.BucketName, objects[i].Name, minio.GetObjectOptions{})
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
f, err := zipw.Create(name)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
buf := new(bytes.Buffer)
buf.ReadFrom(object)
@@ -530,7 +529,7 @@ func getDownloadFolderResponse(session *models.Principal, params objectApi.Downl
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
LogError("Unable to parse encoded prefix %s: %v", encodedPrefix, err)
ErrorWithContext(ctx, fmt.Errorf("Unable to parse encoded prefix %s: %v", encodedPrefix, err))
return
}
@@ -552,27 +551,27 @@ func getDownloadFolderResponse(session *models.Principal, params objectApi.Downl
// Copy the stream
_, err := io.Copy(rw, resp)
if err != nil {
LogError("Unable to write all the requested data: %v", err)
ErrorWithContext(ctx, fmt.Errorf("Unable to write all the requested data: %v", err))
}
}), nil
}
// getDeleteObjectResponse returns whether there was an error on deletion of object
// getDeleteObjectResponse returns whether there was an errors on deletion of object
func getDeleteObjectResponse(session *models.Principal, params objectApi.DeleteObjectParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
var prefix string
if params.Path != "" {
encodedPrefix := SanitizeEncodedPrefix(params.Path)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a mc S3Client interface implementation
// defining the client to be used
@@ -596,19 +595,19 @@ func getDeleteObjectResponse(session *models.Principal, params objectApi.DeleteO
if allVersions && nonCurrentVersions {
err := errors.New("cannot set delete all versions and delete non-current versions flags at the same time")
return prepareError(err)
return ErrorWithContext(ctx, err)
}
err = deleteObjects(ctx, mcClient, params.BucketName, prefix, version, rec, allVersions, nonCurrentVersions)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
// getDeleteMultiplePathsResponse returns whether there was an error on deletion of any object
// getDeleteMultiplePathsResponse returns whether there was an errors on deletion of any object
func getDeleteMultiplePathsResponse(session *models.Principal, params objectApi.DeleteMultipleObjectsParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
var version string
var allVersions bool
@@ -622,14 +621,14 @@ func getDeleteMultiplePathsResponse(session *models.Principal, params objectApi.
prefix := params.Files[i].Path
s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a mc S3Client interface implementation
// defining the client to be used
mcClient := mcClient{client: s3Client}
err = deleteObjects(ctx, mcClient, params.BucketName, params.Files[i].Path, version, params.Files[i].Recursive, allVersions, false)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
}
return nil
@@ -680,7 +679,7 @@ OUTER_LOOP:
switch content.Err.ToGoError().(type) {
// ignore same as mc
case mc.PathInsufficientPermission:
// Ignore Permission error.
// Ignore Permission errors.
continue
}
close(contentCh)
@@ -696,7 +695,7 @@ OUTER_LOOP:
switch result.Err.ToGoError().(type) {
// ignore same as mc
case mc.PathInsufficientPermission:
// Ignore Permission error.
// Ignore Permission errors.
continue
}
close(contentCh)
@@ -712,7 +711,7 @@ OUTER_LOOP:
switch result.Err.ToGoError().(type) {
// ignore same as mc
case mc.PathInsufficientPermission:
// Ignore Permission error.
// Ignore Permission errors.
continue
}
return result.Err.Cause
@@ -738,7 +737,7 @@ func deleteSingleObject(ctx context.Context, client MCClient, bucket, object str
switch result.Err.ToGoError().(type) {
// ignore same as mc
case mc.PathInsufficientPermission:
// Ignore Permission error.
// Ignore Permission errors.
continue
}
return result.Err.Cause
@@ -767,17 +766,16 @@ func deleteNonCurrentVersions(ctx context.Context, client MCClient, bucket, path
}
func getUploadObjectResponse(session *models.Principal, params objectApi.PostBucketsBucketNameObjectsUploadParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := params.HTTPRequest.Context()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
if err := uploadFiles(ctx, minioClient, params); err != nil {
return prepareError(err, ErrorGeneric)
return ErrorWithContext(ctx, err, ErrDefault)
}
return nil
}
@@ -832,20 +830,19 @@ func uploadFiles(ctx context.Context, client MinioClient, params objectApi.PostB
// getShareObjectResponse returns a share object url
func getShareObjectResponse(session *models.Principal, params objectApi.ShareObjectParams) (*string, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := params.HTTPRequest.Context()
var prefix string
if params.Prefix != "" {
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a mc S3Client interface implementation
// defining the client to be used
@@ -856,7 +853,7 @@ func getShareObjectResponse(session *models.Principal, params objectApi.ShareObj
}
url, err := getShareObjectURL(ctx, mcClient, params.VersionID, expireDuration)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return url, nil
}
@@ -879,11 +876,10 @@ func getShareObjectURL(ctx context.Context, client MCClient, versionID string, d
}
func getSetObjectLegalHoldResponse(session *models.Principal, params objectApi.PutObjectLegalHoldParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := params.HTTPRequest.Context()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -893,13 +889,13 @@ func getSetObjectLegalHoldResponse(session *models.Principal, params objectApi.P
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
err = setObjectLegalHold(ctx, minioClient, params.BucketName, prefix, params.VersionID, *params.Body.Status)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -915,11 +911,10 @@ func setObjectLegalHold(ctx context.Context, client MinioClient, bucketName, pre
}
func getSetObjectRetentionResponse(session *models.Principal, params objectApi.PutObjectRetentionParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := params.HTTPRequest.Context()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -929,13 +924,13 @@ func getSetObjectRetentionResponse(session *models.Principal, params objectApi.P
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
err = setObjectRetention(ctx, minioClient, params.BucketName, params.VersionID, prefix, params.Body)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -968,11 +963,10 @@ func setObjectRetention(ctx context.Context, client MinioClient, bucketName, ver
}
func deleteObjectRetentionResponse(session *models.Principal, params objectApi.DeleteObjectRetentionParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := params.HTTPRequest.Context()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -982,13 +976,13 @@ func deleteObjectRetentionResponse(session *models.Principal, params objectApi.D
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
err = deleteObjectRetention(ctx, minioClient, params.BucketName, prefix, params.VersionID)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -1003,11 +997,10 @@ func deleteObjectRetention(ctx context.Context, client MinioClient, bucketName,
}
func getPutObjectTagsResponse(session *models.Principal, params objectApi.PutObjectTagsParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := params.HTTPRequest.Context()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -1017,13 +1010,13 @@ func getPutObjectTagsResponse(session *models.Principal, params objectApi.PutObj
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
err = putObjectTags(ctx, minioClient, params.BucketName, prefix, params.VersionID, params.Body.Tags)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -1041,11 +1034,10 @@ func putObjectTags(ctx context.Context, client MinioClient, bucketName, prefix,
// Restore Object Version
func getPutObjectRestoreResponse(session *models.Principal, params objectApi.PutObjectRestoreParams) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := params.HTTPRequest.Context()
mClient, err := newMinioClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -1056,14 +1048,14 @@ func getPutObjectRestoreResponse(session *models.Principal, params objectApi.Put
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
err = restoreObject(ctx, minioClient, params.BucketName, prefix, params.VersionID)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -1098,11 +1090,10 @@ func restoreObject(ctx context.Context, client MinioClient, bucketName, prefix,
// Metadata Response from minio-go API
func getObjectMetadataResponse(session *models.Principal, params objectApi.GetObjectMetadataParams) (*models.Metadata, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := params.HTTPRequest.Context()
mClient, err := newMinioClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a minioClient interface implementation
// defining the client to be used
@@ -1113,7 +1104,7 @@ func getObjectMetadataResponse(session *models.Principal, params objectApi.GetOb
encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
@@ -1121,7 +1112,7 @@ func getObjectMetadataResponse(session *models.Principal, params objectApi.GetOb
objectInfo, err := getObjectInfo(ctx, minioClient, params.BucketName, prefix)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
metadata := &models.Metadata{ObjectMetadata: objectInfo.Metadata}

View File

@@ -36,7 +36,7 @@ import (
func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
// Create Service Account
api.ServiceAccountCreateServiceAccountHandler = saApi.CreateServiceAccountHandlerFunc(func(params saApi.CreateServiceAccountParams, session *models.Principal) middleware.Responder {
creds, err := getCreateServiceAccountResponse(session, params.Body)
creds, err := getCreateServiceAccountResponse(session, params)
if err != nil {
return saApi.NewCreateServiceAccountDefault(int(err.Code)).WithPayload(err)
}
@@ -44,7 +44,7 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
})
// Create User Service Account
api.UserCreateAUserServiceAccountHandler = userApi.CreateAUserServiceAccountHandlerFunc(func(params userApi.CreateAUserServiceAccountParams, session *models.Principal) middleware.Responder {
creds, err := getCreateAUserServiceAccountResponse(session, params.Body, params.Name)
creds, err := getCreateAUserServiceAccountResponse(session, params)
if err != nil {
return saApi.NewCreateServiceAccountDefault(int(err.Code)).WithPayload(err)
}
@@ -52,14 +52,14 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
})
// Create User Service Account
api.UserCreateServiceAccountCredentialsHandler = userApi.CreateServiceAccountCredentialsHandlerFunc(func(params userApi.CreateServiceAccountCredentialsParams, session *models.Principal) middleware.Responder {
creds, err := getCreateAUserServiceAccountCredsResponse(session, params.Body, params.Name)
creds, err := getCreateAUserServiceAccountCredsResponse(session, params)
if err != nil {
return saApi.NewCreateServiceAccountDefault(int(err.Code)).WithPayload(err)
}
return userApi.NewCreateServiceAccountCredentialsCreated().WithPayload(creds)
})
api.ServiceAccountCreateServiceAccountCredsHandler = saApi.CreateServiceAccountCredsHandlerFunc(func(params saApi.CreateServiceAccountCredsParams, session *models.Principal) middleware.Responder {
creds, err := getCreateServiceAccountCredsResponse(session, params.Body)
creds, err := getCreateServiceAccountCredsResponse(session, params)
if err != nil {
return saApi.NewCreateServiceAccountDefault(int(err.Code)).WithPayload(err)
}
@@ -67,7 +67,9 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
})
// List Service Accounts for User
api.ServiceAccountListUserServiceAccountsHandler = saApi.ListUserServiceAccountsHandlerFunc(func(params saApi.ListUserServiceAccountsParams, session *models.Principal) middleware.Responder {
serviceAccounts, err := getUserServiceAccountsResponse(session, "")
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
serviceAccounts, err := getUserServiceAccountsResponse(ctx, session, "")
if err != nil {
return saApi.NewListUserServiceAccountsDefault(int(err.Code)).WithPayload(err)
}
@@ -76,7 +78,7 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
// Delete a User's service account
api.ServiceAccountDeleteServiceAccountHandler = saApi.DeleteServiceAccountHandlerFunc(func(params saApi.DeleteServiceAccountParams, session *models.Principal) middleware.Responder {
if err := getDeleteServiceAccountResponse(session, params.AccessKey); err != nil {
if err := getDeleteServiceAccountResponse(session, params); err != nil {
return saApi.NewDeleteServiceAccountDefault(int(err.Code)).WithPayload(err)
}
return saApi.NewDeleteServiceAccountNoContent()
@@ -84,7 +86,9 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
// List Service Accounts for User
api.UserListAUserServiceAccountsHandler = userApi.ListAUserServiceAccountsHandlerFunc(func(params userApi.ListAUserServiceAccountsParams, session *models.Principal) middleware.Responder {
serviceAccounts, err := getUserServiceAccountsResponse(session, params.Name)
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
serviceAccounts, err := getUserServiceAccountsResponse(ctx, session, params.Name)
if err != nil {
return saApi.NewListUserServiceAccountsDefault(int(err.Code)).WithPayload(err)
}
@@ -92,7 +96,7 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
})
api.ServiceAccountGetServiceAccountPolicyHandler = saApi.GetServiceAccountPolicyHandlerFunc(func(params saApi.GetServiceAccountPolicyParams, session *models.Principal) middleware.Responder {
serviceAccounts, err := getServiceAccountPolicyResponse(session, params.AccessKey)
serviceAccounts, err := getServiceAccountPolicyResponse(session, params)
if err != nil {
return saApi.NewGetServiceAccountPolicyDefault(int(err.Code)).WithPayload(err)
}
@@ -100,7 +104,7 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
})
api.ServiceAccountSetServiceAccountPolicyHandler = saApi.SetServiceAccountPolicyHandlerFunc(func(params saApi.SetServiceAccountPolicyParams, session *models.Principal) middleware.Responder {
err := getSetServiceAccountPolicyResponse(session, params.AccessKey, *params.Policy.Policy)
err := getSetServiceAccountPolicyResponse(session, params)
if err != nil {
return saApi.NewSetServiceAccountPolicyDefault(int(err.Code)).WithPayload(err)
}
@@ -109,7 +113,7 @@ func registerServiceAccountsHandlers(api *operations.ConsoleAPI) {
// Delete multiple service accounts
api.ServiceAccountDeleteMultipleServiceAccountsHandler = saApi.DeleteMultipleServiceAccountsHandlerFunc(func(params saApi.DeleteMultipleServiceAccountsParams, session *models.Principal) middleware.Responder {
if err := getDeleteMultipleServiceAccountsResponse(session, params.SelectedSA); err != nil {
if err := getDeleteMultipleServiceAccountsResponse(session, params); err != nil {
return saApi.NewDeleteMultipleServiceAccountsDefault(int(err.Code)).WithPayload(err)
}
return saApi.NewDeleteMultipleServiceAccountsNoContent()
@@ -157,21 +161,21 @@ func createServiceAccountCreds(ctx context.Context, userClient MinioAdmin, polic
// getCreateServiceAccountResponse creates a service account with the defined policy for the user that
// is requestingit ,it first gets the credentials of the user and creates a client which is going to
// make the call to create the Service Account
func getCreateServiceAccountResponse(session *models.Principal, serviceAccount *models.ServiceAccountRequest) (*models.ServiceAccountCreds, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getCreateServiceAccountResponse(session *models.Principal, params saApi.CreateServiceAccountParams) (*models.ServiceAccountCreds, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
userAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO user Admin Client interface implementation
// defining the client to be used
userAdminClient := AdminClient{Client: userAdmin}
saCreds, err := createServiceAccount(ctx, userAdminClient, serviceAccount.Policy)
saCreds, err := createServiceAccount(ctx, userAdminClient, params.Body.Policy)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return saCreds, nil
}
@@ -218,91 +222,88 @@ func createAUserServiceAccountCreds(ctx context.Context, userClient MinioAdmin,
// getCreateServiceAccountResponse creates a service account with the defined policy for the user that
// is requesting it ,it first gets the credentials of the user and creates a client which is going to
// make the call to create the Service Account
func getCreateAUserServiceAccountResponse(session *models.Principal, serviceAccount *models.ServiceAccountRequest, user string) (*models.ServiceAccountCreds, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getCreateAUserServiceAccountResponse(session *models.Principal, params userApi.CreateAUserServiceAccountParams) (*models.ServiceAccountCreds, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
userAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO user Admin Client interface implementation
// defining the client to be used
userAdminClient := AdminClient{Client: userAdmin}
saCreds, err := createAUserServiceAccount(ctx, userAdminClient, serviceAccount.Policy, user)
saCreds, err := createAUserServiceAccount(ctx, userAdminClient, params.Body.Policy, params.Name)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return saCreds, nil
}
// getCreateServiceAccountCredsResponse creates a service account with the defined policy for the user that
// is requesting it, and with the credentials provided
func getCreateAUserServiceAccountCredsResponse(session *models.Principal, serviceAccount *models.ServiceAccountRequestCreds, user string) (*models.ServiceAccountCreds, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getCreateAUserServiceAccountCredsResponse(session *models.Principal, params userApi.CreateServiceAccountCredentialsParams) (*models.ServiceAccountCreds, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
userAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO user Admin Client interface implementation
// defining the client to be used
userAdminClient := AdminClient{Client: userAdmin}
serviceAccount := params.Body
user := params.Name
if user == serviceAccount.AccessKey {
return nil, prepareError(errors.New("Access Key already in use"))
return nil, ErrorWithContext(ctx, errors.New("Access Key already in use"))
}
accounts, err := userAdminClient.listServiceAccounts(ctx, user)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
for i := 0; i < len(accounts.Accounts); i++ {
if accounts.Accounts[i] == serviceAccount.AccessKey {
return nil, prepareError(errors.New("Access Key already in use"))
return nil, ErrorWithContext(ctx, errors.New("Access Key already in use"))
}
}
saCreds, err := createAUserServiceAccountCreds(ctx, userAdminClient, serviceAccount.Policy, user, serviceAccount.AccessKey, serviceAccount.SecretKey)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return saCreds, nil
}
func getCreateServiceAccountCredsResponse(session *models.Principal, serviceAccount *models.ServiceAccountRequestCreds) (*models.ServiceAccountCreds, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getCreateServiceAccountCredsResponse(session *models.Principal, params saApi.CreateServiceAccountCredsParams) (*models.ServiceAccountCreds, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
serviceAccount := params.Body
userAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO user Admin Client interface implementation
// defining the client to be used
userAdminClient := AdminClient{Client: userAdmin}
if session.AccountAccessKey == serviceAccount.AccessKey {
return nil, prepareError(errors.New("Access Key already in use"))
return nil, ErrorWithContext(ctx, errors.New("Access Key already in use"))
}
accounts, err := userAdminClient.listServiceAccounts(ctx, "")
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
for i := 0; i < len(accounts.Accounts); i++ {
if accounts.Accounts[i] == serviceAccount.AccessKey {
return nil, prepareError(errors.New("Access Key already in use"))
return nil, ErrorWithContext(ctx, errors.New("Access Key already in use"))
}
}
saCreds, err := createServiceAccountCreds(ctx, userAdminClient, serviceAccount.Policy, serviceAccount.AccessKey, serviceAccount.SecretKey)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return saCreds, nil
}
@@ -322,13 +323,10 @@ func getUserServiceAccounts(ctx context.Context, userClient MinioAdmin, user str
// getUserServiceAccountsResponse authenticates the user and calls
// getUserServiceAccounts to list the user's service accounts
func getUserServiceAccountsResponse(session *models.Principal, user string) (models.ServiceAccounts, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
func getUserServiceAccountsResponse(ctx context.Context, session *models.Principal, user string) (models.ServiceAccounts, *models.Error) {
userAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
// create a MinIO user Admin Client interface implementation
// defining the client to be used
@@ -336,7 +334,7 @@ func getUserServiceAccountsResponse(session *models.Principal, user string) (mod
serviceAccounts, err := getUserServiceAccounts(ctx, userAdminClient, user)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return serviceAccounts, nil
}
@@ -347,20 +345,19 @@ func deleteServiceAccount(ctx context.Context, userClient MinioAdmin, accessKey
}
// getDeleteServiceAccountResponse authenticates the user and calls deleteServiceAccount
func getDeleteServiceAccountResponse(session *models.Principal, accessKey string) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getDeleteServiceAccountResponse(session *models.Principal, params saApi.DeleteServiceAccountParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
accessKey := params.AccessKey
userAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a MinIO user Admin Client interface implementation
// defining the client to be used
userAdminClient := AdminClient{Client: userAdmin}
if err := deleteServiceAccount(ctx, userAdminClient, accessKey); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
@@ -381,13 +378,13 @@ func getServiceAccountPolicy(ctx context.Context, userClient MinioAdmin, accessK
// getServiceAccountPolicyResponse authenticates the user and calls
// getServiceAccountPolicy to get the policy for a service account
func getServiceAccountPolicyResponse(session *models.Principal, accessKey string) (string, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getServiceAccountPolicyResponse(session *models.Principal, params saApi.GetServiceAccountPolicyParams) (string, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
accessKey := params.AccessKey
userAdmin, err := NewMinioAdminClient(session)
if err != nil {
return "", prepareError(err)
return "", ErrorWithContext(ctx, err)
}
// create a MinIO user Admin Client interface implementation
// defining the client to be used
@@ -395,7 +392,7 @@ func getServiceAccountPolicyResponse(session *models.Principal, accessKey string
serviceAccounts, err := getServiceAccountPolicy(ctx, userAdminClient, accessKey)
if err != nil {
return "", prepareError(err)
return "", ErrorWithContext(ctx, err)
}
return serviceAccounts, nil
}
@@ -408,13 +405,14 @@ func setServiceAccountPolicy(ctx context.Context, userClient MinioAdmin, accessK
// getSetServiceAccountPolicyResponse authenticates the user and calls
// getSetServiceAccountPolicy to set the policy for a service account
func getSetServiceAccountPolicyResponse(session *models.Principal, accessKey string, policy string) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getSetServiceAccountPolicyResponse(session *models.Principal, params saApi.SetServiceAccountPolicyParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
accessKey := params.AccessKey
policy := *params.Policy.Policy
userAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a MinIO user Admin Client interface implementation
// defining the client to be used
@@ -422,25 +420,26 @@ func getSetServiceAccountPolicyResponse(session *models.Principal, accessKey str
err = setServiceAccountPolicy(ctx, userAdminClient, accessKey, policy)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
return nil
}
// getDeleteMultipleServiceAccountsResponse authenticates the user and calls deleteServiceAccount for each account listed in selectedSAs
func getDeleteMultipleServiceAccountsResponse(session *models.Principal, selectedSAs []string) *models.Error {
ctx, cancel := context.WithCancel(context.Background())
func getDeleteMultipleServiceAccountsResponse(session *models.Principal, params saApi.DeleteMultipleServiceAccountsParams) *models.Error {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
selectedSAs := params.SelectedSA
userAdmin, err := NewMinioAdminClient(session)
if err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
// create a MinIO user Admin Client interface implementation
// defining the client to be used
userAdminClient := AdminClient{Client: userAdmin}
for _, sa := range selectedSAs {
if err := deleteServiceAccount(ctx, userAdminClient, sa); err != nil {
return prepareError(err)
return ErrorWithContext(ctx, err)
}
}
return nil

View File

@@ -68,7 +68,7 @@ func isErasureMode() bool {
func registerSessionHandlers(api *operations.ConsoleAPI) {
// session check
api.AuthSessionCheckHandler = authApi.SessionCheckHandlerFunc(func(params authApi.SessionCheckParams, session *models.Principal) middleware.Responder {
sessionResp, err := getSessionResponse(session)
sessionResp, err := getSessionResponse(params.HTTPRequest.Context(), session)
if err != nil {
return authApi.NewSessionCheckDefault(int(err.Code)).WithPayload(err)
}
@@ -91,12 +91,12 @@ func getClaimsFromToken(sessionToken string) (map[string]interface{}, error) {
}
// getSessionResponse parse the token of the current session and returns a list of allowed actions to render in the UI
func getSessionResponse(session *models.Principal) (*models.SessionResponse, *models.Error) {
ctx, cancel := context.WithCancel(context.Background())
func getSessionResponse(ctx context.Context, session *models.Principal) (*models.SessionResponse, *models.Error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// serialize output
if session == nil {
return nil, prepareError(errorGenericInvalidSession)
return nil, ErrorWithContext(ctx, ErrInvalidSession)
}
tokenClaims, _ := getClaimsFromToken(session.STSSessionToken)
@@ -107,20 +107,20 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
STSSessionToken: session.STSSessionToken,
})
if err != nil {
return nil, prepareError(err, errorGenericInvalidSession)
return nil, ErrorWithContext(ctx, err, ErrInvalidSession)
}
userAdminClient := AdminClient{Client: mAdminClient}
// Obtain the current policy assigned to this user
// necessary for generating the list of allowed endpoints
accountInfo, err := getAccountInfo(ctx, userAdminClient)
if err != nil {
return nil, prepareError(err, errorGenericInvalidSession)
return nil, ErrorWithContext(ctx, err, ErrInvalidSession)
}
rawPolicy := policies.ReplacePolicyVariables(tokenClaims, accountInfo)
policy, err := minioIAMPolicy.ParseConfig(bytes.NewReader(rawPolicy))
if err != nil {
return nil, prepareError(err, errorGenericInvalidSession)
return nil, ErrorWithContext(ctx, err, ErrInvalidSession)
}
currTime := time.Now().UTC()
@@ -143,7 +143,7 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
claims, err := getClaimsFromToken(session.STSSessionToken)
if err != nil {
return nil, prepareError(err, errorGenericInvalidSession)
return nil, ErrorWithContext(ctx, err, ErrInvalidSession)
}
// Support all LDAP, JWT variables
@@ -222,12 +222,12 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
}
serializedPolicy, err := json.Marshal(policy)
if err != nil {
return nil, prepareError(err, errorGenericInvalidSession)
return nil, ErrorWithContext(ctx, err, ErrInvalidSession)
}
var sessionPolicy *models.IamPolicy
err = json.Unmarshal(serializedPolicy, &sessionPolicy)
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
sessionResp := &models.SessionResponse{
Features: getListOfEnabledFeatures(session),

View File

@@ -0,0 +1,159 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package restapi
import (
"context"
"os"
"reflect"
"testing"
"github.com/minio/console/models"
"github.com/minio/console/pkg/auth/idp/oauth2"
"github.com/minio/console/pkg/auth/ldap"
"github.com/stretchr/testify/assert"
)
func Test_getSessionResponse(t *testing.T) {
type args struct {
ctx context.Context
session *models.Principal
}
ctx := context.Background()
tests := []struct {
name string
args args
want *models.SessionResponse
wantErr bool
preFunc func()
postFunc func()
}{
{
name: "empty session",
args: args{
ctx: ctx,
session: nil,
},
want: nil,
wantErr: true,
},
{
name: "malformed minio endpoint URL",
args: args{
ctx: ctx,
session: &models.Principal{
STSAccessKeyID: "",
STSSecretAccessKey: "",
STSSessionToken: "",
AccountAccessKey: "",
Hm: false,
},
},
want: nil,
wantErr: true,
preFunc: func() {
os.Setenv(ConsoleMinIOServer, "malformed")
},
postFunc: func() {
os.Unsetenv(ConsoleMinIOServer)
},
},
{
name: "malformed session",
args: args{
ctx: ctx,
session: &models.Principal{
STSAccessKeyID: "W257A03HTI7L30F7YCRD",
STSSecretAccessKey: "g+QVorWQR8aSy+k3OHOoYn0qKpENld72faCMfYps",
STSSessionToken: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJXMjU3QTAzSFRJN0wzMEY3WUNSRCIsImV4cCI6MTY1MTAxNjU1OCwicGFyZW50IjoibWluaW8ifQ.uFFIIEQ6qM_QvMM297ODi_uK2IA1pwvsDbyBGErkQKqtbY_Ynte8GUkNsSHBEMCT9Fr7uUwaxK41kUqjtbqAwA",
AccountAccessKey: "minio",
Hm: false,
},
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.preFunc != nil {
tt.preFunc()
}
session, err := getSessionResponse(tt.args.ctx, tt.args.session)
if (err != nil) != tt.wantErr {
t.Errorf("getSessionResponse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(session, tt.want) {
t.Errorf("getSessionResponse() got = %v, want %v", session, tt.want)
}
if tt.postFunc != nil {
tt.postFunc()
}
})
}
}
func Test_getListOfEnabledFeatures(t *testing.T) {
type args struct {
session *models.Principal
}
tests := []struct {
name string
args args
want []string
preFunc func()
postFunc func()
}{
{
name: "all features are enabled",
args: args{
session: &models.Principal{
STSAccessKeyID: "",
STSSecretAccessKey: "",
STSSessionToken: "",
AccountAccessKey: "",
Hm: true,
},
},
want: []string{"log-search", "oidc-idp", "external-idp", "ldap-idp", "external-idp", "hide-menu"},
preFunc: func() {
os.Setenv(ConsoleLogQueryURL, "http://logsearchapi:8080")
os.Setenv(oauth2.ConsoleIDPURL, "http://external-idp.com")
os.Setenv(oauth2.ConsoleIDPClientID, "eaeaeaeaeaea")
os.Setenv(ldap.ConsoleLDAPEnabled, "on")
},
postFunc: func() {
os.Unsetenv(ConsoleLogQueryURL)
os.Unsetenv(oauth2.ConsoleIDPURL)
os.Unsetenv(oauth2.ConsoleIDPClientID)
os.Unsetenv(ldap.ConsoleLDAPEnabled)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.preFunc != nil {
tt.preFunc()
}
assert.Equalf(t, tt.want, getListOfEnabledFeatures(tt.args.session), "getListOfEnabledFeatures(%v)", tt.args.session)
if tt.postFunc != nil {
tt.postFunc()
}
})
}
}

View File

@@ -17,9 +17,12 @@
package restapi
import (
"context"
"net/http"
"time"
xhttp "github.com/minio/console/pkg/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
"github.com/minio/console/pkg/utils"
@@ -29,7 +32,7 @@ import (
func registerVersionHandlers(api *operations.ConsoleAPI) {
api.SystemCheckMinIOVersionHandler = systemApi.CheckMinIOVersionHandlerFunc(func(params systemApi.CheckMinIOVersionParams) middleware.Responder {
versionResponse, err := getVersionResponse()
versionResponse, err := getVersionResponse(params)
if err != nil {
return systemApi.NewCheckMinIOVersionDefault(int(err.Code)).WithPayload(err)
}
@@ -38,13 +41,15 @@ func registerVersionHandlers(api *operations.ConsoleAPI) {
}
// getSessionResponse parse the token of the current session and returns a list of allowed actions to render in the UI
func getVersionResponse() (*models.CheckVersionResponse, *models.Error) {
ver, err := utils.GetLatestMinIOImage(&utils.HTTPClient{
func getVersionResponse(params systemApi.CheckMinIOVersionParams) (*models.CheckVersionResponse, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
ver, err := utils.GetLatestMinIOImage(&xhttp.Client{
Client: &http.Client{
Timeout: 15 * time.Second,
}})
if err != nil {
return nil, prepareError(err)
return nil, ErrorWithContext(ctx, err)
}
return &models.CheckVersionResponse{
LatestVersion: *ver,

View File

@@ -51,9 +51,13 @@ func startWatch(ctx context.Context, conn WSConn, wsc MCClient, options *watchOp
}
for _, event := range events {
// Serialize message to be sent
bytes, _ := json.Marshal(event)
bytes, err := json.Marshal(event)
if err != nil {
LogError("error on json.Marshal: %v", err)
return err
}
// Send Message through websocket connection
err := conn.writeMessage(websocket.TextMessage, bytes)
err = conn.writeMessage(websocket.TextMessage, bytes)
if err != nil {
LogError("error writeMessage: %v", err)
return err

View File

@@ -17,7 +17,9 @@
package restapi
import (
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
@@ -62,3 +64,153 @@ func TestUniqueKeys(t *testing.T) {
responseArray := UniqueKeys(exampleMixedArray)
assert.ElementsMatchf(responseArray, exampleUniqueArray, "returned array doesn't contain the correct elements %s")
}
func TestRandomCharStringWithAlphabet(t *testing.T) {
type args struct {
n int
alphabet string
}
tests := []struct {
name string
args args
want string
}{
{
name: "generated random string has the right length",
args: args{
n: 10,
alphabet: "A",
},
want: "AAAAAAAAAA",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, RandomCharStringWithAlphabet(tt.args.n, tt.args.alphabet), "RandomCharStringWithAlphabet(%v, %v)", tt.args.n, tt.args.alphabet)
})
}
}
func TestNewSessionCookieForConsole(t *testing.T) {
type args struct {
token string
}
tests := []struct {
name string
args args
want http.Cookie
}{
{
name: "session cookie has the right token an security configuration",
args: args{
token: "jwt-xxxxxxxxx",
},
want: http.Cookie{
Path: "/",
Value: "jwt-xxxxxxxxx",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Name: "token",
MaxAge: 3600,
Expires: time.Now().Add(1 * time.Hour),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewSessionCookieForConsole(tt.args.token)
assert.Equalf(t, tt.want.Value, got.Value, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.Path, got.Path, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.HttpOnly, got.HttpOnly, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.Name, got.Name, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.MaxAge, got.MaxAge, "NewSessionCookieForConsole(%v)", tt.args.token)
assert.Equalf(t, tt.want.SameSite, got.SameSite, "NewSessionCookieForConsole(%v)", tt.args.token)
})
}
}
func TestExpireSessionCookie(t *testing.T) {
tests := []struct {
name string
want http.Cookie
}{
{
name: "cookie is expired correctly",
want: http.Cookie{
Name: "token",
Value: "",
MaxAge: -1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ExpireSessionCookie()
assert.Equalf(t, tt.want.Name, got.Name, "ExpireSessionCookie()")
assert.Equalf(t, tt.want.Value, got.Value, "ExpireSessionCookie()")
assert.Equalf(t, tt.want.MaxAge, got.MaxAge, "ExpireSessionCookie()")
})
}
}
func TestSanitizeEncodedPrefix(t *testing.T) {
type args struct {
rawPrefix string
}
tests := []struct {
name string
args args
want string
}{
{
name: "replace spaces with +",
args: args{
rawPrefix: "hello world",
},
want: "hello+world",
},
{
name: "replace spaces with +",
args: args{
rawPrefix: " hello-world ",
},
want: "+++hello-world+++",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, SanitizeEncodedPrefix(tt.args.rawPrefix), "SanitizeEncodedPrefix(%v)", tt.args.rawPrefix)
})
}
}
func Test_isSafeToPreview(t *testing.T) {
type args struct {
str string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "mime type is safe to preview",
args: args{
str: "image/jpeg",
},
want: true,
},
{
name: "mime type is not safe to preview",
args: args{
str: "application/zip",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, isSafeToPreview(tt.args.str), "isSafeToPreview(%v)", tt.args.str)
})
}
}

View File

@@ -18,12 +18,15 @@ package restapi
import (
"context"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/minio/console/pkg/utils"
"github.com/go-openapi/errors"
"github.com/gorilla/websocket"
"github.com/minio/console/models"
@@ -121,17 +124,25 @@ func (c wsConn) readMessage() (messageType int, p []byte, err error) {
// on the path.
// Request should come like ws://<host>:<port>/ws/<api>
func serveWS(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
// Perform authentication before upgrading to a Websocket Connection
// authenticate WS connection with Console
session, err := auth.GetClaimsFromTokenInRequest(req)
if err != nil {
ErrorWithContext(ctx, err)
errors.ServeError(w, req, errors.New(http.StatusUnauthorized, err.Error()))
return
}
//// DELETE ME !!!
//upgrader.CheckOrigin = func(r *http.Request) bool {
// return true
//}
// upgrades the HTTP server connection to the WebSocket protocol.
conn, err := upgrader.Upgrade(w, req, nil)
if err != nil {
ErrorWithContext(ctx, err)
errors.ServeError(w, req, err)
return
}
@@ -141,6 +152,7 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
case strings.HasPrefix(wsPath, `/trace`):
wsAdminClient, err := newWebSocketAdminClient(conn, session)
if err != nil {
ErrorWithContext(ctx, err)
closeWsConn(conn)
return
}
@@ -172,11 +184,12 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
path: path,
}
go wsAdminClient.trace(traceRequestItem)
go wsAdminClient.trace(ctx, traceRequestItem)
case strings.HasPrefix(wsPath, `/console`):
wsAdminClient, err := newWebSocketAdminClient(conn, session)
if err != nil {
ErrorWithContext(ctx, err)
closeWsConn(conn)
return
}
@@ -187,60 +200,64 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
node: node,
logType: logType,
}
go wsAdminClient.console(logRequestItem)
go wsAdminClient.console(ctx, logRequestItem)
case strings.HasPrefix(wsPath, `/health-info`):
deadline, err := getHealthInfoOptionsFromReq(req)
if err != nil {
LogError("error getting health info options: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error getting health info options: %v", err))
closeWsConn(conn)
return
}
wsAdminClient, err := newWebSocketAdminClient(conn, session)
if err != nil {
ErrorWithContext(ctx, err)
closeWsConn(conn)
return
}
go wsAdminClient.healthInfo(deadline)
go wsAdminClient.healthInfo(ctx, deadline)
case strings.HasPrefix(wsPath, `/heal`):
hOptions, err := getHealOptionsFromReq(req)
if err != nil {
LogError("error getting heal options: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error getting heal options: %v", err))
closeWsConn(conn)
return
}
wsAdminClient, err := newWebSocketAdminClient(conn, session)
if err != nil {
ErrorWithContext(ctx, err)
closeWsConn(conn)
return
}
go wsAdminClient.heal(hOptions)
go wsAdminClient.heal(ctx, hOptions)
case strings.HasPrefix(wsPath, `/watch`):
wOptions, err := getWatchOptionsFromReq(req)
if err != nil {
LogError("error getting watch options: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error getting watch options: %v", err))
closeWsConn(conn)
return
}
wsS3Client, err := newWebSocketS3Client(conn, session, wOptions.BucketName)
if err != nil {
ErrorWithContext(ctx, err)
closeWsConn(conn)
return
}
go wsS3Client.watch(wOptions)
go wsS3Client.watch(ctx, wOptions)
case strings.HasPrefix(wsPath, `/speedtest`):
speedtestOpts, err := getSpeedtestOptionsFromReq(req)
if err != nil {
LogError("error getting speedtest options: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error getting speedtest options: %v", err))
closeWsConn(conn)
return
}
wsAdminClient, err := newWebSocketAdminClient(conn, session)
if err != nil {
ErrorWithContext(ctx, err)
closeWsConn(conn)
return
}
go wsAdminClient.speedtest(speedtestOpts)
go wsAdminClient.speedtest(ctx, speedtestOpts)
default:
// path not found
@@ -292,26 +309,54 @@ func newWebSocketS3Client(conn *websocket.Conn, claims *models.Principal, bucket
// if the client sends a Close Message the context will be
// canceled. If the connection is closed the goroutine inside
// will return.
func wsReadClientCtx(conn WSConn) context.Context {
func wsReadClientCtx(parentContext context.Context, conn WSConn) context.Context {
// a cancel context is needed to end all goroutines used
ctx, cancel := context.WithCancel(context.Background())
var requestID string
var SessionID string
var UserAgent string
var Host string
var RemoteHost string
if val, o := parentContext.Value(utils.ContextRequestID).(string); o {
requestID = val
}
if val, o := parentContext.Value(utils.ContextRequestUserID).(string); o {
SessionID = val
}
if val, o := parentContext.Value(utils.ContextRequestUserAgent).(string); o {
UserAgent = val
}
if val, o := parentContext.Value(utils.ContextRequestHost).(string); o {
Host = val
}
if val, o := parentContext.Value(utils.ContextRequestRemoteAddr).(string); o {
RemoteHost = val
}
ctx = context.WithValue(ctx, utils.ContextRequestID, requestID)
ctx = context.WithValue(ctx, utils.ContextRequestUserID, SessionID)
ctx = context.WithValue(ctx, utils.ContextRequestUserAgent, UserAgent)
ctx = context.WithValue(ctx, utils.ContextRequestHost, Host)
ctx = context.WithValue(ctx, utils.ContextRequestRemoteAddr, RemoteHost)
go func() {
defer cancel()
for {
_, _, err := conn.readMessage()
if err != nil {
// if error of type websocket.CloseError and is Unexpected
// if errors of type websocket.CloseError and is Unexpected
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) {
LogError("error unexpected CloseError on ReadMessage: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error unexpected CloseError on ReadMessage: %v", err))
return
}
// Not all errors are of type websocket.CloseError.
if _, ok := err.(*websocket.CloseError); !ok {
LogError("error on ReadMessage: %v", err)
ErrorWithContext(ctx, fmt.Errorf("error on ReadMessage: %v", err))
return
}
// else is an expected Close Error
LogError("closed conn.ReadMessage: %v", err)
return
}
}
@@ -327,7 +372,7 @@ func closeWsConn(conn *websocket.Conn) {
// trace serves madmin.ServiceTraceInfo
// on a Websocket connection.
func (wsc *wsAdminClient) trace(traceRequestItem TraceRequest) {
func (wsc *wsAdminClient) trace(ctx context.Context, traceRequestItem TraceRequest) {
defer func() {
LogInfo("trace stopped")
// close connection after return
@@ -335,7 +380,7 @@ func (wsc *wsAdminClient) trace(traceRequestItem TraceRequest) {
}()
LogInfo("trace started")
ctx := wsReadClientCtx(wsc.conn)
ctx = wsReadClientCtx(ctx, wsc.conn)
err := startTraceInfo(ctx, wsc.conn, wsc.client, traceRequestItem)
@@ -344,7 +389,7 @@ func (wsc *wsAdminClient) trace(traceRequestItem TraceRequest) {
// console serves madmin.GetLogs
// on a Websocket connection.
func (wsc *wsAdminClient) console(logRequestItem LogRequest) {
func (wsc *wsAdminClient) console(ctx context.Context, logRequestItem LogRequest) {
defer func() {
LogInfo("console logs stopped")
// close connection after return
@@ -352,14 +397,14 @@ func (wsc *wsAdminClient) console(logRequestItem LogRequest) {
}()
LogInfo("console logs started")
ctx := wsReadClientCtx(wsc.conn)
ctx = wsReadClientCtx(ctx, wsc.conn)
err := startConsoleLog(ctx, wsc.conn, wsc.client, logRequestItem)
sendWsCloseMessage(wsc.conn, err)
}
func (wsc *wsS3Client) watch(params *watchOptions) {
func (wsc *wsS3Client) watch(ctx context.Context, params *watchOptions) {
defer func() {
LogInfo("watch stopped")
// close connection after return
@@ -367,14 +412,14 @@ func (wsc *wsS3Client) watch(params *watchOptions) {
}()
LogInfo("watch started")
ctx := wsReadClientCtx(wsc.conn)
ctx = wsReadClientCtx(ctx, wsc.conn)
err := startWatch(ctx, wsc.conn, wsc.client, params)
sendWsCloseMessage(wsc.conn, err)
}
func (wsc *wsAdminClient) heal(opts *healOptions) {
func (wsc *wsAdminClient) heal(ctx context.Context, opts *healOptions) {
defer func() {
LogInfo("heal stopped")
// close connection after return
@@ -382,14 +427,14 @@ func (wsc *wsAdminClient) heal(opts *healOptions) {
}()
LogInfo("heal started")
ctx := wsReadClientCtx(wsc.conn)
ctx = wsReadClientCtx(ctx, wsc.conn)
err := startHeal(ctx, wsc.conn, wsc.client, opts)
sendWsCloseMessage(wsc.conn, err)
}
func (wsc *wsAdminClient) healthInfo(deadline *time.Duration) {
func (wsc *wsAdminClient) healthInfo(ctx context.Context, deadline *time.Duration) {
defer func() {
LogInfo("health info stopped")
// close connection after return
@@ -397,14 +442,14 @@ func (wsc *wsAdminClient) healthInfo(deadline *time.Duration) {
}()
LogInfo("health info started")
ctx := wsReadClientCtx(wsc.conn)
ctx = wsReadClientCtx(ctx, wsc.conn)
err := startHealthInfo(ctx, wsc.conn, wsc.client, deadline)
sendWsCloseMessage(wsc.conn, err)
}
func (wsc *wsAdminClient) speedtest(opts *madmin.SpeedtestOpts) {
func (wsc *wsAdminClient) speedtest(ctx context.Context, opts *madmin.SpeedtestOpts) {
defer func() {
LogInfo("speedtest stopped")
// close connection after return
@@ -412,7 +457,7 @@ func (wsc *wsAdminClient) speedtest(opts *madmin.SpeedtestOpts) {
}()
LogInfo("speedtest started")
ctx := wsReadClientCtx(wsc.conn)
ctx = wsReadClientCtx(ctx, wsc.conn)
err := startSpeedtest(ctx, wsc.conn, wsc.client, opts)
@@ -434,7 +479,7 @@ func sendWsCloseMessage(conn WSConn, err error) {
return
}
// else, internal server error
conn.writeMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, ErrorGeneric.Error()))
conn.writeMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, ErrDefault.Error()))
return
}
// normal closure

View File

@@ -17,6 +17,7 @@
package restapi
import (
"context"
"errors"
"testing"
@@ -50,7 +51,8 @@ func TestWSHandle(t *testing.T) {
connReadMessageMock = func() (messageType int, p []byte, err error) {
return 0, []byte{}, &websocket.CloseError{Code: websocket.CloseAbnormalClosure, Text: ""}
}
ctx := wsReadClientCtx(mockWSConn)
parentCtx := context.Background()
ctx := wsReadClientCtx(parentCtx, mockWSConn)
<-ctx.Done()
// closed ctx correctly
@@ -59,7 +61,7 @@ func TestWSHandle(t *testing.T) {
connReadMessageMock = func() (messageType int, p []byte, err error) {
return 0, []byte{}, errors.New("error")
}
ctx2 := wsReadClientCtx(mockWSConn)
ctx2 := wsReadClientCtx(parentCtx, mockWSConn)
<-ctx2.Done()
// closed ctx correctly
@@ -67,7 +69,7 @@ func TestWSHandle(t *testing.T) {
connReadMessageMock = func() (messageType int, p []byte, err error) {
return 0, []byte{}, &websocket.CloseError{Code: websocket.CloseGoingAway, Text: ""}
}
ctx3 := wsReadClientCtx(mockWSConn)
ctx3 := wsReadClientCtx(parentCtx, mockWSConn)
<-ctx3.Done()
// closed ctx correctly
}