// 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 . package api import ( "context" "errors" "strings" "github.com/minio/minio-go/v7" "github.com/minio/console/models" "github.com/minio/madmin-go/v3" ) var ( ErrDefault = errors.New("an error occurred, please try again") ErrInvalidLogin = errors.New("invalid login") ErrForbidden = errors.New("403 Forbidden") ErrBadRequest = errors.New("400 Bad Request") 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") ErrOauth2Login = errors.New("unable to login using 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") ErrLoginNotAllowed = errors.New("login not allowed") ErrHealthReportFail = errors.New("failure to generate Health report") ErrNetworkError = errors.New("unable to login due to network error") ) type CodedAPIError struct { Code int APIError *models.APIError } // ErrorWithContext : func ErrorWithContext(ctx context.Context, err ...interface{}) *CodedAPIError { errorCode := 500 errorMessage := ErrDefault.Error() var detailedMessage string var err1 error var exists bool if len(err) > 0 { if err1, exists = err[0].(error); exists { detailedMessage = err1.Error() var lastError error if len(err) > 1 { if err2, lastExists := err[1].(error); lastExists { lastError = err2 } } if err1.Error() == ErrForbidden.Error() { errorCode = 403 } if err1.Error() == ErrBadRequest.Error() { errorCode = 400 } if err1 == ErrNotFound { errorCode = 404 errorMessage = ErrNotFound.Error() } if errors.Is(err1, ErrInvalidLogin) { detailedMessage = "" errorCode = 401 errorMessage = ErrInvalidLogin.Error() } if errors.Is(err1, ErrNetworkError) { detailedMessage = "" errorCode = 503 errorMessage = ErrNetworkError.Error() } if strings.Contains(strings.ToLower(err1.Error()), ErrAccessDenied.Error()) { errorCode = 403 errorMessage = err1.Error() } // If the last error is ErrInvalidLogin, this is a login failure if errors.Is(lastError, ErrInvalidLogin) { detailedMessage = "" errorCode = 401 errorMessage = err1.Error() } if strings.Contains(err1.Error(), ErrLoginNotAllowed.Error()) { detailedMessage = "" errorCode = 400 errorMessage = ErrLoginNotAllowed.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() } if errors.Is(err1, ErrEncryptionConfigNotFound) { errorCode = 404 errorMessage = err1.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("ErrorWithContext:%v", err...) LogIf(ctx, err1, err...) } if len(err) > 1 && err[1] != nil { if err2, ok := err[1].(error); ok { errorMessage = err2.Error() } } } return &CodedAPIError{Code: errorCode, APIError: &models.APIError{Message: errorMessage, DetailedMessage: detailedMessage}} } // 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{}) *CodedAPIError { ctx, cancel := context.WithCancel(context.Background()) defer cancel() return ErrorWithContext(ctx, err...) }