github.com/minio/console@v1.4.1/api/admin_inspect.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2022 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"encoding/base64"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"strings"
    25  	"unicode/utf8"
    26  
    27  	"github.com/go-openapi/runtime"
    28  	"github.com/go-openapi/runtime/middleware"
    29  	"github.com/minio/console/api/operations"
    30  	inspectApi "github.com/minio/console/api/operations/inspect"
    31  	"github.com/minio/console/models"
    32  	"github.com/minio/madmin-go/v3"
    33  	"github.com/secure-io/sio-go"
    34  )
    35  
    36  func registerInspectHandler(api *operations.ConsoleAPI) {
    37  	api.InspectInspectHandler = inspectApi.InspectHandlerFunc(func(params inspectApi.InspectParams, principal *models.Principal) middleware.Responder {
    38  		if v, err := base64.URLEncoding.DecodeString(params.File); err == nil && utf8.Valid(v) {
    39  			params.File = string(v)
    40  		}
    41  
    42  		if v, err := base64.URLEncoding.DecodeString(params.Volume); err == nil && utf8.Valid(v) {
    43  			params.Volume = string(v)
    44  		}
    45  
    46  		k, r, err := getInspectResult(principal, &params)
    47  		if err != nil {
    48  			return inspectApi.NewInspectDefault(err.Code).WithPayload(err.APIError)
    49  		}
    50  
    51  		return middleware.ResponderFunc(processInspectResponse(&params, k, r))
    52  	})
    53  }
    54  
    55  func getInspectResult(session *models.Principal, params *inspectApi.InspectParams) ([]byte, io.ReadCloser, *CodedAPIError) {
    56  	ctx := params.HTTPRequest.Context()
    57  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
    58  	if err != nil {
    59  		return nil, nil, ErrorWithContext(ctx, err)
    60  	}
    61  
    62  	cfg := madmin.InspectOptions{
    63  		File:   params.File,
    64  		Volume: params.Volume,
    65  	}
    66  
    67  	// TODO: Remove encryption option and always encrypt.
    68  	// Maybe also add public key field.
    69  	if params.Encrypt != nil && *params.Encrypt {
    70  		cfg.PublicKey, _ = base64.StdEncoding.DecodeString("MIIBCgKCAQEAs/128UFS9A8YSJY1XqYKt06dLVQQCGDee69T+0Tip/1jGAB4z0/3QMpH0MiS8Wjs4BRWV51qvkfAHzwwdU7y6jxU05ctb/H/WzRj3FYdhhHKdzear9TLJftlTs+xwj2XaADjbLXCV1jGLS889A7f7z5DgABlVZMQd9BjVAR8ED3xRJ2/ZCNuQVJ+A8r7TYPGMY3wWvhhPgPk3Lx4WDZxDiDNlFs4GQSaESSsiVTb9vyGe/94CsCTM6Cw9QG6ifHKCa/rFszPYdKCabAfHcS3eTr0GM+TThSsxO7KfuscbmLJkfQev1srfL2Ii2RbnysqIJVWKEwdW05ID8ryPkuTuwIDAQAB")
    71  	}
    72  
    73  	// create a MinIO Admin Client interface implementation
    74  	// defining the client to be used
    75  	adminClient := AdminClient{Client: mAdmin}
    76  
    77  	k, r, err := adminClient.inspect(ctx, cfg)
    78  	if err != nil {
    79  		return nil, nil, ErrorWithContext(ctx, err)
    80  	}
    81  	return k, r, nil
    82  }
    83  
    84  // borrowed from mc cli
    85  func decryptInspectV1(key [32]byte, r io.Reader) io.ReadCloser {
    86  	stream, err := sio.AES_256_GCM.Stream(key[:])
    87  	if err != nil {
    88  		return nil
    89  	}
    90  	nonce := make([]byte, stream.NonceSize())
    91  	return io.NopCloser(stream.DecryptReader(r, nonce, nil))
    92  }
    93  
    94  func processInspectResponse(params *inspectApi.InspectParams, k []byte, r io.ReadCloser) func(w http.ResponseWriter, _ runtime.Producer) {
    95  	isEnc := params.Encrypt != nil && *params.Encrypt
    96  	return func(w http.ResponseWriter, _ runtime.Producer) {
    97  		ext := "enc"
    98  		if len(k) == 32 && !isEnc {
    99  			ext = "zip"
   100  			r = decryptInspectV1(*(*[32]byte)(k), r)
   101  		}
   102  		fileName := fmt.Sprintf("inspect-%s-%s.%s", params.Volume, params.File, ext)
   103  		fileName = strings.Map(func(r rune) rune {
   104  			switch {
   105  			case r >= 'A' && r <= 'Z':
   106  				return r
   107  			case r >= 'a' && r <= 'z':
   108  				return r
   109  			case r >= '0' && r <= '9':
   110  				return r
   111  			default:
   112  				if strings.ContainsAny(string(r), "-+._") {
   113  					return r
   114  				}
   115  				return '_'
   116  			}
   117  		}, fileName)
   118  		w.Header().Set("Content-Type", "application/octet-stream")
   119  		w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName))
   120  
   121  		_, err := io.Copy(w, r)
   122  		if err != nil {
   123  			LogError("unable to write all the data: %v", err)
   124  		}
   125  	}
   126  }