github.com/minio/console@v1.3.0/api/admin_config.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 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  	"context"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"strings"
    24  
    25  	"github.com/go-openapi/runtime/middleware"
    26  	"github.com/go-openapi/swag"
    27  	"github.com/minio/console/api/operations"
    28  	"github.com/minio/console/models"
    29  	madmin "github.com/minio/madmin-go/v3"
    30  
    31  	cfgApi "github.com/minio/console/api/operations/configuration"
    32  )
    33  
    34  func registerConfigHandlers(api *operations.ConsoleAPI) {
    35  	// List Configurations
    36  	api.ConfigurationListConfigHandler = cfgApi.ListConfigHandlerFunc(func(params cfgApi.ListConfigParams, session *models.Principal) middleware.Responder {
    37  		configListResp, err := getListConfigResponse(session, params)
    38  		if err != nil {
    39  			return cfgApi.NewListConfigDefault(err.Code).WithPayload(err.APIError)
    40  		}
    41  		return cfgApi.NewListConfigOK().WithPayload(configListResp)
    42  	})
    43  	// Configuration Info
    44  	api.ConfigurationConfigInfoHandler = cfgApi.ConfigInfoHandlerFunc(func(params cfgApi.ConfigInfoParams, session *models.Principal) middleware.Responder {
    45  		config, err := getConfigResponse(session, params)
    46  		if err != nil {
    47  			return cfgApi.NewConfigInfoDefault(err.Code).WithPayload(err.APIError)
    48  		}
    49  		return cfgApi.NewConfigInfoOK().WithPayload(config)
    50  	})
    51  	// Set Configuration
    52  	api.ConfigurationSetConfigHandler = cfgApi.SetConfigHandlerFunc(func(params cfgApi.SetConfigParams, session *models.Principal) middleware.Responder {
    53  		resp, err := setConfigResponse(session, params)
    54  		if err != nil {
    55  			return cfgApi.NewSetConfigDefault(err.Code).WithPayload(err.APIError)
    56  		}
    57  		return cfgApi.NewSetConfigOK().WithPayload(resp)
    58  	})
    59  	// Reset Configuration
    60  	api.ConfigurationResetConfigHandler = cfgApi.ResetConfigHandlerFunc(func(params cfgApi.ResetConfigParams, session *models.Principal) middleware.Responder {
    61  		resp, err := resetConfigResponse(session, params)
    62  		if err != nil {
    63  			return cfgApi.NewResetConfigDefault(err.Code).WithPayload(err.APIError)
    64  		}
    65  		return cfgApi.NewResetConfigOK().WithPayload(resp)
    66  	})
    67  	// Export Configuration as base64 string.
    68  	api.ConfigurationExportConfigHandler = cfgApi.ExportConfigHandlerFunc(func(params cfgApi.ExportConfigParams, session *models.Principal) middleware.Responder {
    69  		resp, err := exportConfigResponse(session, params)
    70  		if err != nil {
    71  			return cfgApi.NewExportConfigDefault(err.Code).WithPayload(err.APIError)
    72  		}
    73  		return cfgApi.NewExportConfigOK().WithPayload(resp)
    74  	})
    75  	api.ConfigurationPostConfigsImportHandler = cfgApi.PostConfigsImportHandlerFunc(func(params cfgApi.PostConfigsImportParams, session *models.Principal) middleware.Responder {
    76  		_, err := importConfigResponse(session, params)
    77  		if err != nil {
    78  			return cfgApi.NewPostConfigsImportDefault(err.Code).WithPayload(err.APIError)
    79  		}
    80  		return cfgApi.NewPostConfigsImportDefault(200)
    81  	})
    82  }
    83  
    84  // listConfig gets all configurations' names and their descriptions
    85  func listConfig(client MinioAdmin) ([]*models.ConfigDescription, error) {
    86  	ctx, cancel := context.WithCancel(context.Background())
    87  	defer cancel()
    88  	configKeysHelp, err := client.helpConfigKV(ctx, "", "", false)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	var configDescs []*models.ConfigDescription
    93  	for _, c := range configKeysHelp.KeysHelp {
    94  		desc := &models.ConfigDescription{
    95  			Key:         c.Key,
    96  			Description: c.Description,
    97  		}
    98  		configDescs = append(configDescs, desc)
    99  	}
   100  	return configDescs, nil
   101  }
   102  
   103  // getListConfigResponse performs listConfig() and serializes it to the handler's output
   104  func getListConfigResponse(session *models.Principal, params cfgApi.ListConfigParams) (*models.ListConfigResponse, *CodedAPIError) {
   105  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   106  	defer cancel()
   107  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   108  	if err != nil {
   109  		return nil, ErrorWithContext(ctx, err)
   110  	}
   111  	// create a MinIO Admin Client interface implementation
   112  	// defining the client to be used
   113  	adminClient := AdminClient{Client: mAdmin}
   114  
   115  	configDescs, err := listConfig(adminClient)
   116  	if err != nil {
   117  		return nil, ErrorWithContext(ctx, err)
   118  	}
   119  	listGroupsResponse := &models.ListConfigResponse{
   120  		Configurations: configDescs,
   121  		Total:          int64(len(configDescs)),
   122  	}
   123  	return listGroupsResponse, nil
   124  }
   125  
   126  // getConfig gets the key values for a defined configuration.
   127  func getConfig(ctx context.Context, client MinioAdmin, name string) ([]*models.Configuration, error) {
   128  	configBytes, err := client.getConfigKV(ctx, name)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	subSysConfigs, err := madmin.ParseServerConfigOutput(string(configBytes))
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	var configSubSysList []*models.Configuration
   137  	for _, scfg := range subSysConfigs {
   138  		if !madmin.SubSystems.Contains(scfg.SubSystem) {
   139  			return nil, fmt.Errorf("no sub-systems found")
   140  		}
   141  		var confkv []*models.ConfigurationKV
   142  		for _, kv := range scfg.KV {
   143  			var envOverride *models.EnvOverride
   144  
   145  			if kv.EnvOverride != nil {
   146  				envOverride = &models.EnvOverride{
   147  					Name:  kv.EnvOverride.Name,
   148  					Value: kv.EnvOverride.Value,
   149  				}
   150  			}
   151  
   152  			confkv = append(confkv, &models.ConfigurationKV{Key: kv.Key, Value: kv.Value, EnvOverride: envOverride})
   153  		}
   154  		if len(confkv) == 0 {
   155  			continue
   156  		}
   157  		var fullConfigName string
   158  		if scfg.Target == "" {
   159  			fullConfigName = scfg.SubSystem
   160  		} else {
   161  			fullConfigName = scfg.SubSystem + ":" + scfg.Target
   162  		}
   163  		configSubSysList = append(configSubSysList, &models.Configuration{KeyValues: confkv, Name: fullConfigName})
   164  	}
   165  	return configSubSysList, nil
   166  }
   167  
   168  // getConfigResponse performs getConfig() and serializes it to the handler's output
   169  func getConfigResponse(session *models.Principal, params cfgApi.ConfigInfoParams) ([]*models.Configuration, *CodedAPIError) {
   170  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   171  	defer cancel()
   172  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   173  	if err != nil {
   174  		return nil, ErrorWithContext(ctx, err)
   175  	}
   176  	// create a MinIO Admin Client interface implementation
   177  	// defining the client to be used
   178  	adminClient := AdminClient{Client: mAdmin}
   179  
   180  	configurations, err := getConfig(ctx, adminClient, params.Name)
   181  	if err != nil {
   182  		errorVal := ErrorWithContext(ctx, err)
   183  		minioError := madmin.ToErrorResponse(err)
   184  		if minioError.Code == "XMinioConfigError" {
   185  			errorVal.Code = 404
   186  		}
   187  		return nil, errorVal
   188  	}
   189  	return configurations, nil
   190  }
   191  
   192  // setConfig sets a configuration with the defined key values
   193  func setConfig(ctx context.Context, client MinioAdmin, configName *string, kvs []*models.ConfigurationKV) (restart bool, err error) {
   194  	config := buildConfig(configName, kvs)
   195  	restart, err = client.setConfigKV(ctx, *config)
   196  	if err != nil {
   197  		return false, err
   198  	}
   199  	return restart, nil
   200  }
   201  
   202  func setConfigWithARNAccountID(ctx context.Context, client MinioAdmin, configName *string, kvs []*models.ConfigurationKV, arnAccountID string) (restart bool, err error) {
   203  	// if arnAccountID is not empty the configuration will be treated as a notification target
   204  	// arnAccountID will be used as an identifier for that specific target
   205  	// docs: https://min.io/docs/minio/linux/administration/monitoring/bucket-notifications.html
   206  	if arnAccountID != "" {
   207  		configName = swag.String(fmt.Sprintf("%s:%s", *configName, arnAccountID))
   208  	}
   209  	return setConfig(ctx, client, configName, kvs)
   210  }
   211  
   212  // buildConfig builds a concatenated string including name and keyvalues
   213  // e.g. `region name=us-west-1`
   214  func buildConfig(configName *string, kvs []*models.ConfigurationKV) *string {
   215  	var builder strings.Builder
   216  	builder.WriteString(*configName)
   217  	for _, kv := range kvs {
   218  		key := strings.TrimSpace(kv.Key)
   219  		if key == "" {
   220  			continue
   221  		}
   222  		builder.WriteString(" ")
   223  		builder.WriteString(key)
   224  		builder.WriteString("=")
   225  		// All newlines must be converted to ','
   226  		builder.WriteString(strings.ReplaceAll(strings.TrimSpace(fmt.Sprintf("\"%s\"", kv.Value)), "\n", ","))
   227  	}
   228  	config := builder.String()
   229  	return &config
   230  }
   231  
   232  // setConfigResponse implements setConfig() to be used by handler
   233  func setConfigResponse(session *models.Principal, params cfgApi.SetConfigParams) (*models.SetConfigResponse, *CodedAPIError) {
   234  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   235  	defer cancel()
   236  
   237  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   238  	if err != nil {
   239  		return nil, ErrorWithContext(ctx, err)
   240  	}
   241  	// create a MinIO Admin Client interface implementation
   242  	// defining the client to be used
   243  	adminClient := AdminClient{Client: mAdmin}
   244  	configName := params.Name
   245  
   246  	needsRestart, err := setConfigWithARNAccountID(ctx, adminClient, &configName, params.Body.KeyValues, params.Body.ArnResourceID)
   247  	if err != nil {
   248  		return nil, ErrorWithContext(ctx, err)
   249  	}
   250  	return &models.SetConfigResponse{Restart: needsRestart}, nil
   251  }
   252  
   253  func resetConfig(ctx context.Context, client MinioAdmin, configName *string) (err error) {
   254  	err = client.delConfigKV(ctx, *configName)
   255  	return err
   256  }
   257  
   258  // resetConfigResponse implements resetConfig() to be used by handler
   259  func resetConfigResponse(session *models.Principal, params cfgApi.ResetConfigParams) (*models.SetConfigResponse, *CodedAPIError) {
   260  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   261  	defer cancel()
   262  
   263  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   264  	if err != nil {
   265  		return nil, ErrorWithContext(ctx, err)
   266  	}
   267  	// create a MinIO Admin Client interface implementation
   268  	// defining the client to be used
   269  	adminClient := AdminClient{Client: mAdmin}
   270  
   271  	err = resetConfig(ctx, adminClient, &params.Name)
   272  	if err != nil {
   273  		return nil, ErrorWithContext(ctx, err)
   274  	}
   275  
   276  	return &models.SetConfigResponse{Restart: true}, nil
   277  }
   278  
   279  func exportConfigResponse(session *models.Principal, params cfgApi.ExportConfigParams) (*models.ConfigExportResponse, *CodedAPIError) {
   280  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   281  	defer cancel()
   282  
   283  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   284  	if err != nil {
   285  		return nil, ErrorWithContext(ctx, err)
   286  	}
   287  	configRes, err := mAdmin.GetConfig(ctx)
   288  	if err != nil {
   289  		return nil, ErrorWithContext(ctx, err)
   290  	}
   291  	// may contain sensitive information so unpack only when required.
   292  	return &models.ConfigExportResponse{
   293  		Status: "success",
   294  		Value:  base64.StdEncoding.EncodeToString(configRes),
   295  	}, nil
   296  }
   297  
   298  func importConfigResponse(session *models.Principal, params cfgApi.PostConfigsImportParams) (*cfgApi.PostConfigsImportDefault, *CodedAPIError) {
   299  	ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
   300  	defer cancel()
   301  	mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session)
   302  	if err != nil {
   303  		return nil, ErrorWithContext(ctx, err)
   304  	}
   305  	file, _, err := params.HTTPRequest.FormFile("file")
   306  	if err != nil {
   307  		return nil, ErrorWithContext(ctx, err)
   308  	}
   309  	defer file.Close()
   310  
   311  	err = mAdmin.SetConfig(ctx, file)
   312  	if err != nil {
   313  		return nil, ErrorWithContext(ctx, err)
   314  	}
   315  	return &cfgApi.PostConfigsImportDefault{}, nil
   316  }