github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/idp-openid-subcommands.go (about) 1 // Copyright (c) 2015-2023 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "fmt" 22 "strings" 23 24 "github.com/charmbracelet/lipgloss" 25 "github.com/minio/cli" 26 json "github.com/minio/colorjson" 27 "github.com/minio/madmin-go/v3" 28 "github.com/minio/mc/pkg/probe" 29 ) 30 31 var idpOpenidAddCmd = cli.Command{ 32 Name: "add", 33 Usage: "Create an OpenID IDP server configuration", 34 Action: mainIDPOpenIDAdd, 35 Before: setGlobalsFromContext, 36 Flags: globalFlags, 37 OnUsageError: onUsageError, 38 CustomHelpTemplate: `NAME: 39 {{.HelpName}} - {{.Usage}} 40 41 USAGE: 42 {{.HelpName}} TARGET [CFG_NAME] [CFG_PARAMS...] 43 44 FLAGS: 45 {{range .VisibleFlags}}{{.}} 46 {{end}} 47 EXAMPLES: 48 1. Create a default OpenID IDP configuration (CFG_NAME is omitted). 49 {{.Prompt}} {{.HelpName}} play/ \ 50 client_id=minio-client-app \ 51 client_secret=minio-client-app-secret \ 52 config_url="http://localhost:5556/dex/.well-known/openid-configuration" \ 53 scopes="openid,groups" \ 54 redirect_uri="http://127.0.0.1:10000/oauth_callback" \ 55 role_policy="consoleAdmin" 56 2. Create OpenID IDP configuration named "dex_test". 57 {{.Prompt}} {{.HelpName}} play/ dex_test \ 58 client_id=minio-client-app \ 59 client_secret=minio-client-app-secret \ 60 config_url="http://localhost:5556/dex/.well-known/openid-configuration" \ 61 scopes="openid,groups" \ 62 redirect_uri="http://127.0.0.1:10000/oauth_callback" \ 63 role_policy="consoleAdmin" 64 `, 65 } 66 67 func mainIDPOpenIDAdd(ctx *cli.Context) error { 68 return mainIDPOpenIDAddOrUpdate(ctx, false) 69 } 70 71 func mainIDPOpenIDAddOrUpdate(ctx *cli.Context, update bool) error { 72 if len(ctx.Args()) < 2 { 73 showCommandHelpAndExit(ctx, 1) 74 } 75 76 args := ctx.Args() 77 78 aliasedURL := args.Get(0) 79 80 // Create a new MinIO Admin Client 81 client, err := newAdminClient(aliasedURL) 82 fatalIf(err, "Unable to initialize admin connection.") 83 84 cfgName := madmin.Default 85 input := args[1:] 86 if !strings.Contains(args.Get(1), "=") { 87 cfgName = args.Get(1) 88 input = args[2:] 89 } 90 91 inputCfg := strings.Join(input, " ") 92 93 restart, e := client.AddOrUpdateIDPConfig(globalContext, madmin.OpenidIDPCfg, cfgName, inputCfg, update) 94 fatalIf(probe.NewError(e), "Unable to add OpenID IDP config to server") 95 96 // Print set config result 97 printMsg(configSetMessage{ 98 targetAlias: aliasedURL, 99 restart: restart, 100 }) 101 102 return nil 103 } 104 105 var idpOpenidUpdateCmd = cli.Command{ 106 Name: "update", 107 Usage: "Update an OpenID IDP configuration", 108 Action: mainIDPOpenIDUpdate, 109 Before: setGlobalsFromContext, 110 OnUsageError: onUsageError, 111 Flags: globalFlags, 112 CustomHelpTemplate: `NAME: 113 {{.HelpName}} - {{.Usage}} 114 115 USAGE: 116 {{.HelpName}} TARGET [CFG_NAME] [CFG_PARAMS...] 117 118 FLAGS: 119 {{range .VisibleFlags}}{{.}} 120 {{end}} 121 EXAMPLES: 122 1. Update the default OpenID IDP configuration (CFG_NAME is omitted). 123 {{.Prompt}} {{.HelpName}} play/ 124 scopes="openid,groups" \ 125 role_policy="consoleAdmin" 126 2. Update configuration for OpenID IDP configuration named "dex_test". 127 {{.Prompt}} {{.HelpName}} play/ dex_test \ 128 scopes="openid,groups" \ 129 role_policy="consoleAdmin" 130 `, 131 } 132 133 func mainIDPOpenIDUpdate(ctx *cli.Context) error { 134 return mainIDPOpenIDAddOrUpdate(ctx, true) 135 } 136 137 var idpOpenidRemoveCmd = cli.Command{ 138 Name: "remove", 139 ShortName: "rm", 140 Usage: "remove OpenID IDP server configuration", 141 Action: mainIDPOpenIDRemove, 142 Before: setGlobalsFromContext, 143 Flags: globalFlags, 144 OnUsageError: onUsageError, 145 CustomHelpTemplate: `NAME: 146 {{.HelpName}} - {{.Usage}} 147 148 USAGE: 149 {{.HelpName}} TARGET [CFG_NAME] 150 151 FLAGS: 152 {{range .VisibleFlags}}{{.}} 153 {{end}} 154 EXAMPLES: 155 1. Remove the default OpenID IDP configuration (CFG_NAME is omitted). 156 {{.Prompt}} {{.HelpName}} play/ 157 2. Remove OpenID IDP configuration named "dex_test". 158 {{.Prompt}} {{.HelpName}} play/ dex_test 159 `, 160 } 161 162 func mainIDPOpenIDRemove(ctx *cli.Context) error { 163 if len(ctx.Args()) < 1 || len(ctx.Args()) > 2 { 164 showCommandHelpAndExit(ctx, 1) 165 } 166 167 args := ctx.Args() 168 169 var cfgName string 170 if len(args) == 2 { 171 cfgName = args.Get(1) 172 } 173 return idpRemove(ctx, true, cfgName) 174 } 175 176 func idpRemove(ctx *cli.Context, isOpenID bool, cfgName string) error { 177 args := ctx.Args() 178 aliasedURL := args.Get(0) 179 180 // Create a new MinIO Admin Client 181 client, err := newAdminClient(aliasedURL) 182 fatalIf(err, "Unable to initialize admin connection.") 183 184 idpType := madmin.LDAPIDPCfg 185 if isOpenID { 186 idpType = madmin.OpenidIDPCfg 187 } 188 189 restart, e := client.DeleteIDPConfig(globalContext, idpType, cfgName) 190 fatalIf(probe.NewError(e), "Unable to remove %s IDP config '%s'", idpType, cfgName) 191 192 printMsg(configSetMessage{ 193 targetAlias: aliasedURL, 194 restart: restart, 195 }) 196 197 return nil 198 } 199 200 var idpOpenidListCmd = cli.Command{ 201 Name: "list", 202 ShortName: "ls", 203 Usage: "list OpenID IDP server configuration(s)", 204 Action: mainIDPOpenIDList, 205 Before: setGlobalsFromContext, 206 Flags: globalFlags, 207 OnUsageError: onUsageError, 208 CustomHelpTemplate: `NAME: 209 {{.HelpName}} - {{.Usage}} 210 211 USAGE: 212 {{.HelpName}} TARGET 213 214 FLAGS: 215 {{range .VisibleFlags}}{{.}} 216 {{end}} 217 EXAMPLES: 218 1. List configurations for OpenID IDP. 219 {{.Prompt}} {{.HelpName}} play/ 220 `, 221 } 222 223 func mainIDPOpenIDList(ctx *cli.Context) error { 224 if len(ctx.Args()) != 1 { 225 showCommandHelpAndExit(ctx, 1) 226 } 227 228 return idpListCommon(ctx, true) 229 } 230 231 func idpListCommon(ctx *cli.Context, isOpenID bool) error { 232 args := ctx.Args() 233 aliasedURL := args.Get(0) 234 235 // Create a new MinIO Admin Client 236 client, err := newAdminClient(aliasedURL) 237 fatalIf(err, "Unable to initialize admin connection.") 238 239 idpType := madmin.LDAPIDPCfg 240 if isOpenID { 241 idpType = madmin.OpenidIDPCfg 242 } 243 result, e := client.ListIDPConfig(globalContext, idpType) 244 fatalIf(probe.NewError(e), "Unable to list IDP config for '%s'", idpType) 245 246 printMsg(idpCfgList(result)) 247 248 return nil 249 } 250 251 type idpCfgList []madmin.IDPListItem 252 253 func (i idpCfgList) JSON() string { 254 bs, e := json.MarshalIndent(i, "", " ") 255 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 256 257 return string(bs) 258 } 259 260 func (i idpCfgList) String() string { 261 maxNameWidth := len("Name") 262 maxRoleARNWidth := len("RoleArn") 263 for _, item := range i { 264 name := item.Name 265 if name == "_" { 266 name = "(default)" // for the un-named config, don't show `_` 267 } 268 if maxNameWidth < len(name) { 269 maxNameWidth = len(name) 270 } 271 if maxRoleARNWidth < len(item.RoleARN) { 272 maxRoleARNWidth = len(item.RoleARN) 273 } 274 } 275 enabledWidth := 5 276 // Add 2 for padding 277 maxNameWidth += 2 278 maxRoleARNWidth += 2 279 280 enabledColStyle := lipgloss.NewStyle(). 281 Align(lipgloss.Center). 282 PaddingLeft(1). 283 PaddingRight(1). 284 Width(enabledWidth) 285 nameColStyle := lipgloss.NewStyle(). 286 Align(lipgloss.Right). 287 PaddingLeft(1). 288 PaddingRight(1). 289 Width(maxNameWidth) 290 arnColStyle := lipgloss.NewStyle(). 291 Align(lipgloss.Left). 292 PaddingLeft(1). 293 PaddingRight(1). 294 Foreground(lipgloss.Color("#04B575")). // green 295 Width(maxRoleARNWidth) 296 297 styles := []lipgloss.Style{enabledColStyle, nameColStyle, arnColStyle} 298 299 headers := []string{"On?", "Name", "RoleARN"} 300 headerRow := []string{} 301 302 // Override some style settings for the header 303 for ii, hdr := range headers { 304 headerRow = append(headerRow, 305 styles[ii].Copy(). 306 Bold(true). 307 Foreground(lipgloss.Color("#6495ed")). // green 308 Align(lipgloss.Center). 309 Render(hdr), 310 ) 311 } 312 313 lines := []string{strings.Join(headerRow, "")} 314 315 enabledOff := "🔴" 316 enabledOn := "🟢" 317 318 for _, item := range i { 319 enabled := enabledOff 320 if item.Enabled { 321 enabled = enabledOn 322 } 323 324 line := []string{ 325 styles[0].Render(enabled), 326 styles[1].Render(item.Name), 327 styles[2].Render(item.RoleARN), 328 } 329 if item.Name == "_" { 330 // For default config, don't display `_` and make it look faint. 331 line[1] = styles[1].Copy(). 332 Faint(true). 333 Render("(default)") 334 } 335 lines = append(lines, strings.Join(line, "")) 336 } 337 338 boxContent := strings.Join(lines, "\n") 339 boxStyle := lipgloss.NewStyle(). 340 BorderStyle(lipgloss.RoundedBorder()) 341 342 return boxStyle.Render(boxContent) 343 } 344 345 var idpOpenidInfoCmd = cli.Command{ 346 Name: "info", 347 Usage: "get OpenID IDP server configuration info", 348 Action: mainIDPOpenIDInfo, 349 Before: setGlobalsFromContext, 350 Flags: globalFlags, 351 OnUsageError: onUsageError, 352 CustomHelpTemplate: `NAME: 353 {{.HelpName}} - {{.Usage}} 354 355 USAGE: 356 {{.HelpName}} TARGET [CFG_NAME] 357 358 FLAGS: 359 {{range .VisibleFlags}}{{.}} 360 {{end}} 361 EXAMPLES: 362 1. Get configuration info on the default OpenID IDP configuration (CFG_NAME is omitted). 363 {{.Prompt}} {{.HelpName}} play/ 364 2. Get configuration info on OpenID IDP configuration named "dex_test". 365 {{.Prompt}} {{.HelpName}} play/ dex_test 366 `, 367 } 368 369 func mainIDPOpenIDInfo(ctx *cli.Context) error { 370 if len(ctx.Args()) < 1 || len(ctx.Args()) > 2 { 371 showCommandHelpAndExit(ctx, 1) 372 } 373 374 args := ctx.Args() 375 var cfgName string 376 if len(args) == 2 { 377 cfgName = args.Get(1) 378 } 379 380 return idpInfo(ctx, true, cfgName) 381 } 382 383 func idpInfo(ctx *cli.Context, isOpenID bool, cfgName string) error { 384 args := ctx.Args() 385 aliasedURL := args.Get(0) 386 387 // Create a new MinIO Admin Client 388 client, err := newAdminClient(aliasedURL) 389 fatalIf(err, "Unable to initialize admin connection.") 390 391 idpType := madmin.LDAPIDPCfg 392 if isOpenID { 393 idpType = madmin.OpenidIDPCfg 394 } 395 396 result, e := client.GetIDPConfig(globalContext, idpType, cfgName) 397 fatalIf(probe.NewError(e), "Unable to get %s IDP config from server", idpType) 398 399 // Print set config result 400 printMsg(idpConfig(result)) 401 402 return nil 403 } 404 405 type idpConfig madmin.IDPConfig 406 407 func (i idpConfig) JSON() string { 408 bs, e := json.MarshalIndent(i, "", " ") 409 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 410 411 return string(bs) 412 } 413 414 func (i idpConfig) String() string { 415 if len(i.Info) == 0 { 416 return "Not configured." 417 } 418 419 // Determine required width for key column. 420 fieldColWidth := 0 421 for _, kv := range i.Info { 422 if fieldColWidth < len(kv.Key) { 423 fieldColWidth = len(kv.Key) 424 } 425 } 426 // Add 1 for the colon-suffix in each entry. 427 fieldColWidth++ 428 429 fieldColStyle := lipgloss.NewStyle(). 430 Width(fieldColWidth). 431 Foreground(lipgloss.Color("#04B575")). // green 432 Bold(true). 433 Align(lipgloss.Right) 434 valueColStyle := lipgloss.NewStyle(). 435 PaddingLeft(1). 436 Align(lipgloss.Left) 437 envMarkStyle := lipgloss.NewStyle(). 438 Foreground(lipgloss.Color("201")). // pinkish-red 439 PaddingLeft(1) 440 441 var lines []string 442 for _, kv := range i.Info { 443 envStr := "" 444 if kv.IsCfg && kv.IsEnv { 445 envStr = " (environment)" 446 } 447 lines = append(lines, fmt.Sprintf("%s%s%s", 448 fieldColStyle.Render(kv.Key+":"), 449 valueColStyle.Render(kv.Value), 450 envMarkStyle.Render(envStr), 451 )) 452 } 453 454 boxContent := strings.Join(lines, "\n") 455 456 boxStyle := lipgloss.NewStyle(). 457 BorderStyle(lipgloss.RoundedBorder()) 458 459 return boxStyle.Render(boxContent) 460 } 461 462 var idpOpenidEnableCmd = cli.Command{ 463 Name: "enable", 464 Usage: "enable an OpenID IDP server configuration", 465 Action: mainIDPOpenIDEnable, 466 Before: setGlobalsFromContext, 467 Flags: globalFlags, 468 OnUsageError: onUsageError, 469 CustomHelpTemplate: `NAME: 470 {{.HelpName}} - {{.Usage}} 471 472 USAGE: 473 {{.HelpName}} TARGET [CFG_NAME] 474 475 FLAGS: 476 {{range .VisibleFlags}}{{.}} 477 {{end}} 478 EXAMPLES: 479 1. Enable the default OpenID IDP configuration (CFG_NAME is omitted). 480 {{.Prompt}} {{.HelpName}} play/ 481 2. Enable OpenID IDP configuration named "dex_test". 482 {{.Prompt}} {{.HelpName}} play/ dex_test 483 `, 484 } 485 486 func mainIDPOpenIDEnable(ctx *cli.Context) error { 487 isOpenID, enable := true, true 488 return idpEnableDisable(ctx, isOpenID, enable) 489 } 490 491 func idpEnableDisable(ctx *cli.Context, isOpenID, enable bool) error { 492 if len(ctx.Args()) < 1 || len(ctx.Args()) > 2 { 493 showCommandHelpAndExit(ctx, 1) 494 } 495 496 args := ctx.Args() 497 cfgName := madmin.Default 498 if len(args) == 2 { 499 cfgName = args.Get(1) 500 } 501 aliasedURL := args.Get(0) 502 503 // Create a new MinIO Admin Client 504 client, err := newAdminClient(aliasedURL) 505 fatalIf(err, "Unable to initialize admin connection.") 506 507 idpType := madmin.LDAPIDPCfg 508 if isOpenID { 509 idpType = madmin.OpenidIDPCfg 510 } 511 512 configBody := "enable=on" 513 if !enable { 514 configBody = "enable=off" 515 } 516 517 restart, e := client.AddOrUpdateIDPConfig(globalContext, idpType, cfgName, configBody, true) 518 fatalIf(probe.NewError(e), "Unable to remove %s IDP config '%s'", idpType, cfgName) 519 520 printMsg(configSetMessage{ 521 targetAlias: aliasedURL, 522 restart: restart, 523 }) 524 525 return nil 526 } 527 528 var idpOpenidDisableCmd = cli.Command{ 529 Name: "disable", 530 Usage: "Disable an OpenID IDP server configuration", 531 Action: mainIDPOpenIDDisable, 532 Before: setGlobalsFromContext, 533 Flags: globalFlags, 534 OnUsageError: onUsageError, 535 CustomHelpTemplate: `NAME: 536 {{.HelpName}} - {{.Usage}} 537 538 USAGE: 539 {{.HelpName}} TARGET [CFG_NAME] 540 541 FLAGS: 542 {{range .VisibleFlags}}{{.}} 543 {{end}} 544 EXAMPLES: 545 1. Disable the default OpenID IDP configuration (CFG_NAME is omitted). 546 {{.Prompt}} {{.HelpName}} play/ 547 2. Disable OpenID IDP configuration named "dex_test". 548 {{.Prompt}} {{.HelpName}} play/ dex_test 549 `, 550 } 551 552 func mainIDPOpenIDDisable(ctx *cli.Context) error { 553 isOpenID, enable := true, false 554 return idpEnableDisable(ctx, isOpenID, enable) 555 }